您的位置:寻梦网首页编程乐园XML 编程《XML实用大全》

第二部分 文档类型定义

本部分包括以下各章:

第8章——文档类型定义和合法性

第9章——实体和外部DTD子集

第10章——DTD中的属性声明

第11章——嵌入非XML数据

第8章 文档类型定义和合法性

XML被作为一种元标记语言,是一种描述标记语言的语言。在本章中您将学到如何说明和描述所创建的新标记语言。这些新的标记语言(也叫标记集)要通过文档类型定义(DTD)来定义,这正是本章要讲述的内容。各个文档要与DTD相比较,这一过程称为合法性检验。如果文档符合DTD中的约束,这个文档就被认为是合法的,否则就是不合法的。

·          本章的主要内容包括:

  • 文档类型定义(DTD
  • 文档类型声明
  • DTD的合法性
  • 元素清单
  • 元素声明
  • DTD中的说明
  • 可在文档间共享的通用DTD

8.1 文档类型定义

缩略词DTD代表文档类型定义。一项文档类型定义应规定元素清单、属性、标记、文档中的实体及其相互关系。DTD为文档结构制定了一套规则。例如,一项DTD指定一个BOOK元素有一个ISBN子元素、一个TITLE子元素、一个或多个AUTHOR子元素,有或没有SUBTITLE。DTD以元素、实体、属性和记号的标记声明来做到这一点。

本章重点是元素声明。第9、10、11章分别介绍实体、属性和标记。

DTD可以包括在包含它描述的文档的文件中,或者与外部的URL相链接。这些外部DTD可以被不同文档和网站所共享。DTD为应用程序、组织和兴趣组提供了共同遵循的方法,同时也以文档形式阐述了标记标准并强制遵守此标准。

例如,为了使一部书易于排版,出版商会要求作者遵循一定的格式。作者可能不管是否与本章前面的小标题列出的关键点相符合,而只管成行地写下去。如果作者用XML写作,那么出版商就能很容易地检查出作者是否遵守了DTD作出的预定格式,甚至找出作者在那里以及怎样偏离了格式。这比指望编辑们单纯地从形式上通读文档而找出所有偏离格式的地方要容易得多。

DTD有助于不同的人们和程序互相阅读文件。例如,如果化学家们通过专业机构(如美国化学协会)为中介同意将单一的DTD用于基本的化学记号,那么他们就能够阅读和理解他们当中任何人的文章。DTD精确地定义了什么允许或不允许在文档中出现。DTD还为查看和编辑软件必须支持的元素建立了标准。更重要的是,它建立了超出DTD声明的非法范围。这就使它有助于防止软件商乘机利用和扩展开放协议以便将用户锁定在他们的专利软件上。

而且,DTD可以在没有实际数据的情况下展现出页面上的不同元素是如何安排的。 DTD使人们能脱离实际数据看到文档结构。这意味着可以将许多有趣的样式和格式加在基本结构上,而对基本结构毫无损害。这正如涂饰房子而不必改变基本的建筑计划。页面的读者可能看不见甚至不了解基础结构,但是只要有了DTD,任何人类作者和JavaScript程序、DTD程序、 小服务程序、数据库和其他程序就可以使用它。

用DTD还可以做更多的事。可以使用它们来定义词汇实体以插入署名块或地址一类的模板文本。您可以确定输入数据的人们是否遵循了您的格式。您可以从关系数据库或对象数据库中移出数据或把数据送往目标数据库。甚至可以用适当的DTD利用XML作为中间格式来转换不同的格式。所以让我们开始看一看DTD 到底是什么样的。


8.2 文档类型声明

文档类型声明指定了文档使用的DTD。文档类型声明出现在文档的序言部分,处在XML声明之后和基本元素之前。它可能包括文档类型定义或是标识文档类型定义所在文档的URL。有些情况下文档类型定义有内外两个子集,则文档类型声明可能同时包括以上两种情况。

文档类型声明同文档类型定义不是一回事。只有文档类型定义缩写为DTD。文档类型声明必须包含或者引用文档类型定义,但文档类型定义从不包括文档类型声明。我同意这造成了不必要的混乱。遗憾的是XML似乎与这术语密不可分,幸运的是多数情况下二者的区别并不重要。

请回顾一下第3章清单3-2(greeting.xml),如下所示:

<?xml version="1.0" standalone="yes"?>

<GREETING>

Hello XML!

</GREETING>

这个文档包含单一元素GREETING。(请记住,〈?xml version="1.0" standalone="yes"?〉是一条处理指令,不是元素。)清单8-1显示了这一文档,但这次带有文档类型声明。文档类型声明声明了基本元素是GREETING。文档类型声明也包含文档类型定义,它声明了GREETING元素包含可析的字符数据。

清单8-1:带有DTD的Hello XML

<?xml version="1.0" standalone="yes"?>

<!DOCTYPE GREETING [

<!ELEMENT GREETING (#PCDATA)>

]>

<GREETING>

Hello XML!

</GREETING>

清单3-2与清单8-1的唯一区别在于清单8-1增加了3行:

<!DOCTYPE GREETING [

<!ELEMENT GREETING (#PCDATA)>

]>

这几行是清单8-1的文档类型声明。文档类型声明在XML声明与文档本身之间。XML声明与文档类型声明统称为文档序言(Prolog)。在本例中,<?xml version="1.0" standalone="yes"?>是XML声明;<!DOCTYPE GREETING [ <!ELEMENT GREETING (#PCDATA)> ]>是文档类型声明;<!ELEMENT GREETING (#PCDATA)>是文档类型定义;<GREETING> Hello XML! </GREETING>是文档或基本元素。

文档类型声明以<!DOCTYPE为开始,以]>结束。通常将开始和结束放在不同的行上,但断行和多余的空格并不重要。同一文档类型声明也可以写成一行:

<!DOCTYPE GREETING [<!ELEMENT GREETING (#PCDATA)> ]>

本例中基本元素名称——GREETING跟在<!DOCTYPE之后。这不仅是一个名称,也是一项要求。任何带有这种文档类型声明的合法文档必须有基本元素。在[和]之间的内容是文档类型定义。

DTD由一系列声明了特写的元素、实体和属性的标记声明所组成。其中的一项声明基本元素。清单8-1中整个DTD只是如下简单的一行:

<!ELEMENT GREETING (#PCDATA)>

通常情况下DTD当然会更长更复杂。

单个行<!ELEMENT GREETING (#PCDATA)>(正如XML中的大多数对象一样是区分大小写的)是一项元素类型声明。在本例中,声明的元素名称是GREETING。它是唯一的元素。这一元素可以包含可析的字符数据(或#PCDATA)。可析的字符实质上是除标记文本外的任何文本。这也包括实体引用如&amp;,在对文档进行语法分析时,实体引用就被文本所取代。

可以把这一文档像通常一样装入一种XML浏览器中。图8-1显示了清单8-1在Internet Explorer 5.0中的情况。结果可能正如人们所料,文档源以可折叠的大纲视图出现。Internet Explorer使<!DOCTYPE GREETING ( View Source for full doctype…)>一行变蓝指明有文档类型声明。

图8-1 Internet Explorer 5.0中显示的带有DTD的Hello XML

当然,文档可以与样式单结合起来,就像第3章的清单3-6中一样。实际上可以用同一个样式单。如清单8-2所示,只要在序言中增加通常的<?xml-stylesheet?>处理指令。

清单8-2:带有DTD和样式单的Hello XML

<?xml version="1.0" standalone="yes"?>

<?xml-stylesheet type="text/css" href="greeting.css"?>

<!DOCTYPE GREETING [

<!ELEMENT GREETING (#PCDATA)>

]>

<GREETING>

Hello XML!

</GREETING>

图8-2显示的是结果网页。这同第3章中没有DTD的图3-3相同。格式化时通常不考虑DTD。

图8-2 Internet Explorer 5.0所示的带DTD和样式单的Hello XML


8.3 根据DTD的合法性检验

一个合法的文档必须符合DTD指定的约束条件。而且,它的基本元素必须是在文档类型声明中指明的。清单8-1中的文档类型声明和DTD说明一个合法的文档必须是这样的:

<GREETING>

various random text but no markup

</GREETING>

一个合法的文档不能是这样的:

<GREETING>

<sometag>various random text</sometag>

<someEmptyTag/>

</GREETING>

也不能是这样的:

<GREETING>

<GREETING>various random text</GREETING>

</GREETING>

这个文档必须由放在<GREETING>开始标记和<1GREETING>结束标记之间的可析的字符所组成。与只是结构完整的文档不同,合法文档不允许使用任意的标记。使用的任何标记都要在DTD内声明。而且,必须以DTD 允许的方式使用。在清单8-1中,<GREETING>标记只能用作基本元素的开始,且不能嵌套使用。

假设我们对清单8-2做一点变动,以<foo>和</foo>替换<GREETING>和</GREETING>标记,如清单8-3所示。清单8-3是合法的。它是一个结构完整的XML文档,但它不符合文档类型声明和DTD中的约束条件。

清单8-3:不符合DTD规则的不合法的Hello XML

<?xml version="1.0" standalone="yes"?>

<?xml-stylesheet type="text/css" href="greeting.css"?>

<!DOCTYPE GREETING [

<!ELEMENT GREETING (#PCDATA)>

]>

<foo>

Hello XML!

</foo>

不是所有的文档都必须合法,也不是所有的语法分析程序都检查文档的合法性。事实上,多数Web浏览器包括IE5和Mozilla都不检查文档的合法性。

进行合法性检查的语法分析程序读取DTD并检查文档是否合乎DTD指定的规则。如果是,则分析程序将数据传送到XML应用程序(如Web浏览器和数据库)。如果分析程序发现错误,它将报告出错。如果手工编写XML,应在张贴前检查文档的合法性以确保读者不会遇到错误。

在Web上可找到几十种不同的进行合法性检查的语法分析程序。其中多数是免费的。大多数是以库文件的形式存在的接近完成的产品,以便程序员可将其结合到自己的程序中。这些产品用户界面(如果有的话)较差。这类分析程序包括IBM的alphaWorks’XML for Java、Microsoft和DataChannel的XJParser和Silfide的SXP。

XML for Java:http://www.alphaworks.ibm.com/ tech/xml

XJParser:http://www.datachannel.com/xml_resources/

SXP:http://www.loria.fr/projets/XSilfide/EN/sxp/

一些库文件也包括在命令行上运行的独立的分析程序。这些程序读取XML文件并报告发现的错误,但不加以显示。例如,XJParse 是一个Java程序,包括在IBM的Samples. XJParse软件包中的XML for Java 1.1.16类库中。要运行这一程序,必须首先将XML for Java的jar文件添加到Java类库的路径上。然后就可以打开DOS 窗口或外壳程序提示符,向XJParse程序传送要检查合法性的文档的本地文件名或远程URL,以便对文档进行检查,如下所示:

C:\xml4j>java samples.XJParse.XJParse -d D:\XML\08\invalid.xml

本书写作时,IBM的alphaWorks推出了XML for Java的2.0.6版本。在这一版本下,启动的只是XJParse而非Samples. XJParse 。但是,1.1.16版本提供了更多的用于独立检查的功能。

您可以使用URL代替文件名,如下所示:

C:\xml4j>java samples.XJParse.XJParse -d

http://metalab.unc.edu/books/bible/examples/08/invalid.xml

在任一情况下,XJParse将列出发现的错误后跟树状结构的文档作为反应。例如:

D:\XML\07\invalid.xml: 6? 4: Document root element, "foo", must

match DOCTYPE root , "GREETING".

D:\XML\07\invalid.xml: 8, 6: Element "<foo>"is not valid in

this context.

<?xml version="1.0" standalone="yes"?>

<?xml-stylesheet type="text/css" href="greeting.css"?>

<!DOCTYPE GREETING [

<!ELEMENT GREETING (#PCDATA)>

]>

<foo>

Hello XML!

</foo>

这个输出不是特别吸引人。但是,像XJParse这样的合法性检查程序的目的不是显示XML文件。相反,分析程序的任务是把文档分成为树状结构并把树的结点传送给显示数据的程序。这个程序可能是Netscape Navigator或 Internet Explorer等Web浏览器。也可能是一个数据库。甚至可能是自己写成的定制程序。使用XJParse或其他命令行合法性分析程序来验证是否编写了其他程序可以处理的良好的XML。实质上这是一种校对或质量保证阶段而不是最后的输出。

因为XML for Java和多数合法性分析程序是用Java写成的,它们也就具有跨平台的Java程序的所有缺点。首先,在能够运行分析程序之前必须安装Java开发工具(JDK)或Java运行环境。其次,需要将XML for Java的jar文件添加到类路径上。这两项工作都不是太简单。它们都不是为非程序员的最终用户设计的。这些工具有点设计欠佳,使用不便。

如果正在为浏览器编写文档,验证文档的最简易方法是把文档装入浏览器看一看报告出什么错误。但是并不是所有的浏览器都对文档进行合法性检查,某些浏览器仅接受结构完整的文档,而不管其合法性如何。Internet Explorer 5.0β2版对文档进行合法性检查,但正式发行版都不进行了。

如果将文档装入Web服务器且无需特别保密,基于Web的合法性检查程序是一种替代方法。这些分析程序只需要以简单的形式输入文档的URL。它们明显的优点是不需要面对Java运行软件、类路径和环境变量等麻烦。

图8-3显示的是Richard Tobin的基于RXP的以Web为宿主的XML结构完整性和合法性检查程序。可以在http://www.cogsci.ed.ac.uk/%7Erichard/xml-check.html处找到此程序。图8-4显示的是使用这一程序检查清单8-3显示出的错误结果。

图8-3 Richard Tobin的基于RXP的以Web为宿主的XML结构完整性和合法性检查程序

图8-4 Richard Tobin的XML合法性检查程序报告的清单8-3中的错误

布朗大学的Scholarly Technology Group在http://www.stg.brown.edu/

service/xmlvalid/处提供了一种检查程序。这一程序以允许从本地计算机上载文件而不必把文件装入公共服务器而著称。如图8-5所示,图8-6显示了用这一程序检查清单8-3的结果。

图8-5 布朗大学的Scholarly Technology Group的以Web为宿主的XML合法性检查程序

图8-6 布朗大学的Scholarly Technology Group的合法性检查程序报告的清单8-3中的错误


8.4 列出元素

要为一个文档创建适当的DTD的第一步是了解用DTD中定义的元素编码的信息结构。有时候信息就像通讯地址列表一样。有时则具有相对自由的形式,如说明短文或杂志文章。

让我们以已经相对结构化的文档为例,回到第4章所示的棒球统计示例中。在那份文档上加一个DTD,就使我们能把以前只有通过约定才能遵守的约束条件付诸实施。例如,我们可以要求SEASON元素包含正好两个LEAGUE子元素,每个TEAM有TEAM_CITY和TEAM_NAME子元素,并且TEAM_CITY总在TEAM_NAME之前。

回想起来,完整的棒球统计文档包含下面一些元素:

SEASON RBI

YEAR STEALS

LEAGUE CAUGHT-STEALING

LEAGUE-NAME SACRIFICE_ HITS

DIVISION SACRIFICE_FLIES

DIVISION_NAME ERRORS

TEAM WALKS

TEAM_CITY STRUCK_OUT

TEAM_NAME HIT_BY_PITCH

PLAYER COMPLETE_GAMES

SURNAME SHUT_OUTS

GIVEN_NAME ERA

POSITION INNINGS

GAMES HOME_RUNS

GAMES_STARTED RUNS

AT_BATS EARNED_RUNS

RUNS HIT_BATTER

HITS WILD_PITCHES

DOUBLES BALK

TRIPLES WALKED_BATTER

HOME_RUNS STRUCK_OUT_BATTER

WINS COMPLETE_GAMES

LOSSES SHUT_OUTS

SAVES

所编写的DTD要为每个元素作元素声明。每一元素声明列出元素名和它的子元素。例如,DTD规定一个LEAGUE元素有三个DIVISION子元素。还规定SURNAME要放在PLAYER之内而不能在它外面。还规定每个DIVISION有不确定的TEAM元素数目但绝不能少于一个。

DTD可要求PLAYER只有一个GIVEN_NAME、SURNAME、POSITION和GAMES元素,但是否有RBI和ERA元素则任意。而且要求GIVEN_NAME、SURNAME、POSITION和GAMES元素以特定的顺序出现。例如,GIVEN_NAME、SURNAME、POSITION和GAMES只能在PLAYER元素内使用。

如果头脑中有一份具体的结构完整的示例文档,该文档使用了想要出现在DTD中所有的元素,那么开始就很容易了。第4章中的例子在这里就可起这一作用。清单8-4是经过整理的第4章清单4-1的简化版。尽管它只有两名球员,却可以说明所有基本的元素。

清单8-4:需要编写DTD结构完整的XML文档

<?xml version="1.0" standalone="yes"?>

<SEASON>

<YEAR>1998</YEAR>

<LEAGUE>

<LEAGUE_NAME>National</LEAGUE_NAME>

<DIVISION>

<DIVISION_NAME>East </DIVISION_NAME>

<TEAM>

<TEAM_CITY>Florida</TEAM_CITY>

<TEAM_NAME>Marlins</TEAM_NAME>

<PLAYER>

<SURNAME>Ludwick</SURNAME>

<GIVEN_NAME>Eric</GIVEN_NAME>

<POSITION>Starting Pitcher</POSITION>

<WINS>1</WINS>

<LOSSES>4</LOSSES>

<SAVES>0</SAVES>

<GAMES>13</GAMES>

<GAMES_STARTED>6</GAMES_STARTED>

<COMPLETE_GAMES>0</COMPLETE_GAMES>

<SHUT_OUTS>0</SHUT_OUTS>

<ERA>7.44</ERA>

<INNINGS>32.2</INNINGS>

<HOME_RUNS>46</ HOME_RUNS>

<RUNS>7</RUNS>

<EARNED_RUNS>31</EARNED_RUNS>

<HIT_BATTER>27</ HIT_BATTER>

<WILD_PITCHES>0</WILD_PITCHES>

<WALKED_BATTER>0</WALKED_BATTER>

<STRUCK_OUT_BATTER>17</STRUCK_OUT_BATTER>

</PLAYER>

<PLAYER>

<SURNAME>Daubach</SURNAME>

<GIVEN_NAME>Brian</GIVEN_NAME>

<POSITION>First Base</POSITION>

<GAMES>10</GAMES>

<GAMES_STARTED>3</GAMES_STARTED>

<AT_BATS>15</AT_BATS>

<RUNS>0</RUNS>

< HITS>3</HITS>

<DOUBLES>1</DOUBLES>

<TRIPLES>0</TRIPLES>

<HOME_RUNS>0</HOME_RUNS>

<RBI>3</RBI>

<STEALS>0</STEALS>

<CAUGHT_STEALING>0</CAUGHT_STEALING>

<SACRIFICE_ HITS>0</SACRIFICE_HITS>

<SACRIFICE_FLIES>0</SACRIFICE_FLIES>

<ERRORS>0</ERRORS>

<WALKS>1</WALKS>

<STRUCK_OUT>5</STRUCK_OUT>

<HIT_BY_PITCH>1</HIT_BY_PITCH >

</PLAYER>

</TEAM>

<TEAM>

<TEAM_CITY>Montreal</TEAM_CITY>

<TEAM_NAME>Expos</TEAM_NAME>

</TEAM>

<TEAM>

<TEAM_CITY>New York</TEAM_CITY>

<TEAM_NAME>Mets</TEAM_NAME>

</TEAM>

<TEAM>

<TEAM_CITY>Philadelphia</TEAM_CITY>

<TEAM_NAME>Phillies</TEAM_NAME>

</TEAM>

</DIVISION>

<DIVISION>

<DIVISION_NAME>Central</DIVISION_NAME>

<TEAM>

<TEAM_CITY>Chicago</TEAM_CITY>

<TEAM_NAME>Cubs</TEAM_NAME>

</TEAM>

</DIVISION>

<DIVISION>

<DIVISION_NAME>West</DIVISION_NAME>

<TEAM>

<TEAM_CITY>Arizona</TEAM_CITY>

<TEAM_NAME>Diamondbacks</TEAM_NAME>

</TEAM>

</DIVISION>

</LEAGUE>

<LEAGUE>

<LEAGUE_NAME>American</LEAGUE_NAME>

<DIVISION>

<DIVISION_NAME>East </DIVISION_NAME>

<TEAM>

<TEAM_CITY>Baltimore</TEAM_CITY>

<TEAM_NAME>Orioles</TEAM_NAME>

</TEAM>

</DIVISION>

<DIVISION>

<DIVISION_NAME>Central</DIVISION_NAME>

<TEAM>

<TEAM_CITY>Chicago</TEAM_CITY>

<TEAM_NAME>White Sox</TEAM_NAME>

</TEAM>

</DIVISION>

<DIVISION>

<DIVISION_NAME>West</DIVISION_NAME>

<TEAM>

<TEAM_CITY>Anaheim</TEAM_CITY>

<TEAM_NAME>Angels</TEAM_NAME>

</TEAM>

</DIVISION>

</LEAGUE>

</SEASON>

表8-1列出了本例中的元素及它们必须遵守的条件。每一元素都有它必须包含的元素、它可能包含的元素以及必须包含它的元素。有些情况下,一个元素可能包含不止一个同一类型的子元素。SEASON元素包含一个YEAR和两个LEAG UE元素。一个DIVISION通常包含不止一个TEAM元素。较不明显的是,一些击球手在各场比赛中在指定的投球手和外场之间交替出现。这样,一个PLAYER元素就可能有不止一个POSITION。在该表格中,要求的子元素的数目是通过在元素前加数字来指明的(如2 LEAGUE),而多子元素的可能性是通过在元素名尾加(s)指明的,如PLAYER(s)。

清单8-4遵守了这些条件。如果把两个PLAYER元素和一些TEAM元素省略,文档可以短些。如果包括进其他一些PLAYER元素,文档就会长些。但是其他元素的位置都不能变动。

XML元素有两种基本类型。简单元素包含文本,也就是所谓的可析字符数据,即上下文中的#PCDATA或PCDATA。复合元素包含其他元素,有的还包含文本和其他元素。标准XML没有整数、浮点、日期或其他数据类型。因而不能使用DTD说明走步数一定是一个非负的整数,或ERA一定是0.0和1.0之间的一个浮点数,尽管在如本例一样的例子中这样做是有用的。有人做过努力来定义一种方案,以便使用XML句法描述传统上DTD中编码的信息以及数据类型信息。直到1999年中期,这些努力仍主要是理论上的,很少有实际的实现方式。

表格8-1 棒球统计中的元素

元素

必须包含的元素

可能包含的元素

必须包含它的元素

SEASON

YEAR

2 LEAGUE

 

YEAR

文本

 

SEASON

LEAGUE

LEAGUE_NAME,

3 DIVISION

 

SEASON

LEAGUE_NAME

文本

 

LEAGUE

DIVISION

DIVISION_NAME,

TEAM

TEAM(s)

LEAGUE

DIVISION_NAME

文本

 

DIVISION

TEAM

TEAM_CITY,

TEAM_NAME

PLAYER(s)

DIVISION

TEAM_CITY

文本

 

TEAM

TEAM_NAME

文本

 

TEAM

PLAYER

SURNAME, GIVEN_NAME, POSITION, GAMES

GAMES_STARTED, AT_BATS,RUNS, HITS,
DOUBLES,TRIPLES,
HOME_RUNS, RBI,
STEALS,
CAUGHT_STEALING,
SACRIFICE_HITS,
SACRIFICE_FLIES,
ERRORS, WALKS,
STRUCK_OUT,
HIT_BY_PITCH,
COMPLETE_GAMES, SHUT_OUTS,
ERA, INNINGS,
HIT_BATTER,
WILD_PITCHES, BALK,
WALKED_BATTER,
STRUCK_OUT_BATTER

TEAM

SURNAME

文本

 

PLAYER

GIVEN_NAME

文本

 

PLAYER

POSITION

文本

 

PLAYER

GAMES

文本

 

PLAYER

GAMES_STARTED

文本

 

PLAYER

AT_BATS

文本

 

PLAYER

RUNS

文本

 

PLAYER

HITS

文本

 

PLAYER

DOUBLES

文本

 

PLAYER

TRIPLES

文本

 

PLAYER

HOME_RUNS

文本

 

PLAYER

RBI

文本

 

PLAYER

STEALS

文本

 

PLAYER

CAUGHT_STEALING

文本

 

PLAYER

SACRIFICE_HITS

文本

 

PLAYER

SACRIFICE_FLIES

文本

 

PLAYER

ERRORS

文本

 

PLAYER

WALKS

文本

 

PLAYER

STRUCK_OUT

文本

 

PLAYER

HIT_BY_PITCH

文本

 

PLAYER

COMPLETE_GAMES

文本

 

PLAYER

SHUT_OUTS

文本

 

PLAYER

ERA

文本

 

PLAYER

INNINGS

文本

 

PLAYER

HOME_RUNS_AGAINST

文本

 

PLAYER

RUNS_AGAINST

文本

 

PLAYER

HIT_BATTER

文本

 

PLAYER

WILD_PITCHES

文本

 

PLAYER

BATTER

文本

 

PLAYER

STRUCK_OUT_BATTER

文本

 

PLAYER

既然已经标识了要存储的数据,以及这些元素间可选的和必然的关系,就可以为简明概括那些联系的文档建立DTD了。

从一个DTD剪切和粘贴到另一个往往是很可行和方便的。许多元素可以在其他上下文中再使用。例如,对TEAM的描写同样可应用于足球、曲棍球和很多其他在队间进行的运动。

可以把一个DTD包括在另一个之内,这样文档就可以从两个DTD中得到标记。例如,可以使用一份详细地描写单个队员的统计数据的DTD然后把该DTD嵌套在更广泛的球队运动的DTD内。如想从棒球转换到足球,只要简单地把棒球球员DTD换为足球球员DTD就可以了。

为达到此目的,包含DTD的文档就被定义为外部实体。外部参数实体引用将在第9章“实体”中讨论。


8.5 元素声明

在合法的XML文档中使用的每项标记都要在DTD中的元素声明中加以声明。一项元素声明指明了元素名称和元素可能的内容。内容清单有时称为内容规格。内容规格使用一种简单的语法精确地指明文档中允许什么和不允许什么。这听起来复杂,却只需在元素名称上加上如*、?或+的标点以便指明它可能出现不止一次,可能出现或可能不出现,或必须出现至少一次。

DTD很保守,没有明确允许的就是禁止的。然而,DTD句法使您能够严格地区分那些用语句很难说清的关系。例如,DTD很容易地说明GIVEN_NAME要在SURNAME前,而SURNAME必须放在POSITION前,POSITION要放在GAME前,GAME要放在GAMES_STARTED前,GAMES_STARTED要放在AT_BATS前,AT_BATS要放在RUNS前,RUNS要在HITS前,所有这些只能出现在一个PLAYER元素内。

从外到内,逐级建立DTD是最容易的。这使您能在建立DTD的同时建立一份样本文档来验证DTD本身是合法的和真正地描述您想要的格式。

8.5.1 ANY

要做的第一件事是标识基本元素。在棒球的例子中,SEASON是基本元素。!DOCTYPE声明指明了这一点:

<!DOCTYPE SEASON [

]>

但是,这仅仅是说基本标记是SEASON,而没有提到元素能或不能包含的内容,这就是为什么接下来要在元素声明中声明SEASON元素。这可通过下列一行代码来实现:

<!ELEMENT SEASON ANY>

所有的元素类型声明都以<!ELEMENT(区分大小写)开头而以>结束。其中包括声明的元素名称(本例中为SEASON)后接内容规格。关键词ANY(也要区分大小写)表明所有可能的元素以及可析的字符数据都可以是SEASON元素的子元素。

基本元素使用ANY是通常的作法——尤其是对未结构化的文档——但对多数其他元素则应避免使用ANY。通常每项标记的内容应尽可能准确。DTD 经常是在整个开发过程中逐步完善的,随着反映应用情况和首制作中未预料的情况,严格性将减少。所以,最好是开始时严格,以后再放松些。

8.5.2 #PCDATA

尽管文档中可以出现任何元素,但出现的元素必须声明。第一个需要声明的元素是YEAR,下面是YEAR元素的元素声明:

<!ELEMENT YEAR (#PCDATA)>

该声明说明YEAR只能包含可析的字符数据,即非标记文本,但它不能包含自己的子元素。所以,下面这个YEAR元素是合法的:

<YEAR>1998</YEAR>

以下这些YEAR元素都是合法的:

<YEAR>98</YEAR>

<YEAR>1998 C.E.</YEAR>

<YEAR>

The year of our lord one thousand,

nine hundred, &amp; ninety-eight

</YEAR>

甚至下面这个YEAR元素也是合法的,因为XML不会去检查PCDATA的内容,只要是不包括标记的文本就可以。

<YEAR>Delicious, delicious, oh how boring</YEAR>

但是,下面的YEAR元素是非法的,因为它包含了子元素:

<YEAR>

<MONTH>January</MONTH>

<MONTH>February</MONTH>

<MONTH>March</MONTH>

<MONTH>April</MONTH>

<MONTH>May</MONTH>

<MONTH>June</MONTH>

<MONTH>July</MONTH>

<MONTH>August</MONTH>

<MONTH>September</MONTH>

<MONTH>October</MONTH>

<MONTH>November</MONTH>

<MONTH>December</MONTH>

</YEAR>

SEASON和YEAR元素声明应包括在文档类型声明中,如下所示:

<!DOCTYPE SEASON [

<!ELEMENT SEASON ANY>

<!ELEMENT YEAR (#PCDATA)>

]>

通常,空格和缩进无关紧要。元素声明的顺序也不重要。下面这一文档类型声明的作用与上面的声明相同:

<!DOCTYPE SEASON [

<!ELEMENT YEAR (#PCDATA)>

<!ELEMENT SEASON ANY>

]>

上面两个文档声明都是说一个SEASON元素可以包含可析的字符数据和以任意顺序声明的任意数量的其他元素。本例中如此声明的元素只有YEAR,它只能包含可析的字符数据。例如考虑清单8-5中的文档。

清单8-5:一个合法的文档

<?xml version="1.0" standalone="yes"?>

<!DOCTYPE SEASON [

<!ELEMENT YEAR (#PCDATA)>

<!ELEMENT SEASON ANY>

]>

<SEASON>

<YEAR>1998</YEAR>

</SEASON>

因为SEASON元素也可以包含可析的字符数据,所以可以在YEAR元素之外附加文本,如清单8-6所示。

清单8-6:包含YEAR元素和正常文本的合法的文档

<?xml version="1.0" standalone="yes"?>

<!DOCTYPE SEASON [

<!ELEMENT YEAR (#PCDATA)>

<!ELEMENT SEASON ANY>

]>

<SEASON>

<YEAR>1998</YEAR>

Major League Baseball

</SEASON>

我们最后是不接受这样的文档的。但是此时它是合法的,因为SEASON被声明为可以接受ANY内容。大多数时候,在定义一个元素的所有子元素之前以ANY代替一个元素,就比较容易起步。然后再用实际的子元素来替换ANY。

您可以向清单8-6上附加一份简单的样式单,如第4章中创建的baseballstats.css,如清单8-7所示。然后将其装入Web浏览器,结果显示在图8-7中。baseballstats.css样式单包含一些没有出现在DTD或是清单8-7列出的文档部分中的元素的样式规则,但这没有问题。Web浏览器会忽略任何文档中没有的元素的样式规则。

清单8-7:包含样式单、一个YEAR元素和正常文本的合法文档

<?xml version="1.0" standalone="yes"?>

<?xml-stylesheet type="text/css" href="greeting.css"?>

<!DOCTYPE SEASON [

<!ELEMENT YEAR (#PCDATA)>

<!ELEMENT SEASON ANY>

]>

<SEASON>

<YEAR>1998</YEAR>

Major League Baseball

</SEASON>

图8-7 在Internet Explorer 5.0中显示的包含样式单、YEAR元素和正常文本的文档

8.5.3 子元素列表

由于SEASON元素被声明为可以接受任何元素作为子元素,因而可以接受各种各样的元素。当遇到那些多多少少有些非结构化的文本,如杂志文章时,这种情况就很有用。这时段落、副栏、项目列表、序号列表、图形、照片以及子标题可出现在文档的任意位置。然而,有时可能想对数据的安排上多实行些规则和控制。例如,可能会要求每一个LEAGUE元素有一个LEAGUE_NAME子元素,而每个PLAYER元素要有一个GIVEN_NAME和SURNAME子元素,并且GIVEN_NAME要放在SURNAME之前。

为了声明LEAGUE元素必须有一个名称,只要声明LEAGUE_NAME元素,然后在LEAGUE声明后的括号内加入LEAGUE_NAME,如下面这样:

<!ELEMENT LEAGUE (LEAGUE_NAME)>

<!ELEMENT LEAGUE_NAME (#PCDATA)>

每个元素只能在其<!ELEMENT>内声明一次,即使它以其他<!ELEMENT>声明的子元素出现也一样。这里,我把LEAGUE_NAME声明放在引用它的LEAGUE声明之后,这没有关系。XML允许这一类提前引用。只要声明全部包含在DTD中,元素标记出现的顺序无关紧要。

可以向文档中添加这两项声明,然后在SEASON元素中包括LEAGUE和LEAGUE_NAME元素。如清单8-8所示。图8-8是显示出来的文档。

清单8-8:有两个LEAGUE子元素的SEASON元素

<?xml version="1.0" standalone="yes"?>

<?xml-stylesheet type="text/css" href="greeting.css"?>

<!DOCTYPE SEASON [

<!ELEMENT YEAR (#PCDATA)>

<!ELEMENT LEAGUE (LEAGUE_NAME)>

<!ELEMENT LEAGUE_NAME (#PCDATA)>

<!ELEMENT SEASON ANY>

]>

<SEASON>

<YEAR>1998</YEAR>

<LEAGUE>

<LEAGUE_NAME>American League</LEAGUE_NAME>

</LEAGUE>

<LEAGUE>

<LEAGUE_NAME>National League</LEAGUE_NAME>

</LEAGUE>

</SEASON>

图8-8 包含样式单、YEAR元素和两个LEAGUE子元素的合法的文档

8.5.4 序列

让我们限制一下SEASON元素。一个SEASON元素包含正好一个YEAR元素和其后的两个LEAGUE子元素。不把SEASON元素声明为可以包含ANY元素,我们在SEASON元素声明中包括这三个子元素,用括号括起来并用逗号分隔开,如下所示:

<!ELEMENT SEASON (YEAR, LEAGUE, LEAGUE)>

用逗号隔开的一系列子元素称为一个序列。利用这一声明,每个合法的SEASON元素必须包含正好一个YEAR元素,后面正好是两个LEAGUE元素,没有别的。整个文档类型定义现在看上去是下面的样子:

<!DOCTYPE SEASON [

<!ELEMENT YEAR (#PCDATA)>

<!ELEMENT LEAGUE (LEAGUE_NAME)>

<!ELEMENT LEAGUE_NAME (#PCDATA)>

<!ELEMENT SEASON (YEAR, LEAGUE, LEAGUE)>

]>

清单8-8所列的文档部分确实符合这项DTD的规定,因为它的SEASON元素包含一个YEAR子元素,后接两个LEAGUE子元素,再没有别的。但是,如果文档只包括一个SEASON元素,那么这个文档尽管结构完整,也将是非法的。同样,如果LEAGUE在YEAR之前而不是在其后,或者如果LEAGUE有YEAR子元素,或者文档在其他任何方面不符合DTD,那么文档就是不合法的,合法性检查程序将拒绝这样的文档。

可直接将此种技术推广到DIVISION元素。每个LEAGUE有一个LEAGUE_NAME和三个DIVISION子元素。例如:

<!ELEMENT LEAGUE (LEAGUE_NAME, DIVISION, DIVISION, DIVISION)>

8.5.5 一个或多个子元素

每个DIVISION有一个DIVISION_NAME和四到六个TEAM子元素。指定DIVISION_NAME很容易,方法如下:

<!ELEMENT DIVISION (DIVISION_NAME)>

<!ELEMENT DIVISION_NAME (#PCDATA)>

但是,TEAM子元素就很棘手。指明DIVISION元素有四个TEAM子元素很容易,如下所示:

<!ELEMENT DIVISION (DIVISION_NAME, TEAM, TEAM, TEAM, TEAM)>

五个和六个也不难。但是您怎样说明有四到六个TEAM子元素呢?实际上,XML没有提供实现的简单方法。但是可以在子元素清单的元素名后放一个加号(+)来说明有一个或多个子元素,例如:

<!ELEMENT DIVISION (DIVISION_NAME, TEAM+)>

这就是说一个DIVISION元素必须包含一个DIVISION_NAME子元素,后接一个或多个TEAM子元素。

说明DIVISION元素有四到六个TEAM元素,而不是三到七个,这就难了。由于非常复杂,实际上很少有人使用。当读完本章时,看一看您是否已经想出怎样做了。

8.5.6 零或多个子元素

每个TEAM要包含一个TEAM_CITY,一个TEAM_NAME和不确定数目的PLAYER元素。实际上,棒球队至少要九名球员。但是,本书的很多例子中由于篇幅的原因而没有列出球员。因而,我们要指明一个TEAM元素可包含零或多个PLAYER子元素。在子元素清单中在元素名上附加一个星号(*)来实现这一目的。例如:

<!ELEMENT TEAM (TEAM_CITY, TEAM_NAME, PLAYER*)>

<!ELEMENT TEAM_CITY (#PCDATA)>

<!ELEMENT TEAM_NAME (#PCDATA)>

8.5.7 零或一个子元素

文档中出现的最后的元素是PLAYER子元素。它们全部是只包含文本的简单元素。下面是它们的声明:

<!ELEMENT SURNAME (#PCDATA)>

<!ELEMENT GIVEN_NAME (#PCDATA)>

<!ELEMENT POSITION (#PCDATA)>

<!ELEMENT GAMES (#PCDATA)>

<!ELEMENT GAMES_STARTED (#PCDATA)>

<!ELEMENT AT_BATS (#PCDATA)>

<!ELEMENT RUNS (#PCDATA)>

<!ELEMENT HITS (#PCDATA)>

<!ELEMENT DOUBLES (#PCDATA)>

<!ELEMENT TRIPLES (#PCDATA)>

<!ELEMENT HOME_RUNS (#PCDATA)>

<!ELEMENT RBI (#PCDATA)>

<!ELEMENT STEALS (#PCDATA)>

<!ELEMENT CAUGHT_STEALING (#PCDATA)>

<!ELEMENT SACRIFICE_ HITS (#PCDATA)>

<!ELEMENT SACRIFICE_FLIES (#PCDATA)>

<!ELEMENT ERRORS (#PCDATA)>

<!ELEMENT WALKS (#PCDATA)>

<!ELEMENT STRUCK_OUT (#PCDATA)>

<!ELEMENT HIT_BY_PITCH (#PCDATA)>

<!ELEMENT COMPLETE_GAMES (#PCDATA)>

<!ELEMENT SHUT_OUTS (#PCDATA)>

<!ELEMENT ERA (#PCDATA)>

<!ELEMENT INNINGS (#PCDATA)>

<!ELEMENT EARNED_RUNS (#PCDATA)>

<!ELEMENT HIT_BATTER (#PCDATA)>

<!ELEMENT WILD_PITCHES (#PCDATA)>

<!ELEMENT BALK (#PCDATA)>

<!ELEMENT WALKED_BATTER (#PCDATA)>

<!ELEMENT WINS (#PCDATA)>

<!ELEMENT LOSSES (#PCDATA)>

<!ELEMENT SAVES (#PCDATA)>

<!ELEMENT COMPLETE_GAMES (#PCDATA)>

<!ELEMENT STRUCK_OUT_BATTER (#PCDATA)>

现在我们可以编写PLAYER的元素声明了。所有球员有一个GIVEN_NAME、一个SURNAME、一个POSITION、一个GAMES。我们可声明每个PLAYER元素有一个AT_BATS、RUNS、HITS等等。但是,对于没有击球的投球手列出零得分是否准确还不敢确定。因为这可能出现这样一种情况,就是在开始计算平均击球数等问题时会导致被零除的错误。如果某一特定的元素不适合于给定的球员,或没有这一元素,那么就应该从该球员信息中忽略这一元素的统计。对于给定的球员我们不允许多于一个这样的元素。因而,我们就需要给定类型的零个或一个元素。在子元素列表后面附加一个问号(?)可表明这一点,如下所示:

<!ELEMENT PLAYER (GIVEN_NAME, SURNAME, POSITION,

GAMES,GAMES_STARTED,AT_BATS?,RUNS?,HITS?,DOUBLES?,

TRIPLES?, HOME_RUNS?, RBI?, STEALS?, CAUGHT_STEALING?,

SACRIFICE_ HITS?, SACRIFICE_FLIES?,ERRORS?, WALKS?,

STRUCK_OUT?, HIT_BY_PITCH ?, WINS?, LOSSES?, SAVES?,

COMPLETE_GAMES?,SHUT_OUTS?,ERA?,INNINGS EARNED_RUNS?,HIT_BATTER?,WILD_PITCHES?,

BALK?,WALKED_BATTER?,STRUCK_OUT_BATTER?)

这就是说每个PLAYER元素有一个GIVEN_NAME、SURNAME、POSITION、GAMES和GAMES_STARTED子元素。而且,每名球员可能有或可能没有AT_BATS、RUNS、HITS、DOUBLES、TRIPLES、HOME_RUNS、RBI、STEALS、CAUGHT_STEALING、SACRIFICE_HITS、SACRIFICE_FLIES、ERRORS、WALKS、STRUCK_OUT和HIT_BY_PITCH。

8.5.8 完整的文档和DTD

我们现在有了棒球统计的完整的DTD。这一DTD连同清单8-4中的文档部分一起,列在清单8-9中。

清单8-9只包括一个队和九名球员。在本书后附CD-ROM上的examples/baseball/1998validstats.xml目录下可找到1998年主要联赛球队和队员的统计文档。

清单8-9:一份合法的棒球统计文档和DTD

<?xml version="1.0" standalone="yes"?>

<!DOCTYPE SEASON [

<!ELEMENT YEAR (#PCDATA)>

<!ELEMENT LEAGUE (LEAGUE_NAME, DIVISION, DIVISION, DIVISION)>

<!ELEMENT LEAGUE_NAME (#PCDATA)>

<!ELEMENT DIVISION_NAME (#PCDATA)>

<!ELEMENT DIVISION (DIVISION_NAME, TEAM+)>

<!ELEMENT SEASON (YEAR, LEAGUE, LEAGUE)>

<!ELEMENT TEAM (TEAM_CITY, TEAM_NAME, PLAYER*)>

<!ELEMENT TEAM_CITY (#PCDATA)>

<!ELEMENT TEAM_NAME (#PCDATA)>

<!ELEMENT PLAYER (GIVEN_NAME, SURNAME, POSITION, GAMES,GAMES_STARTED, WINS?, LOSSES?, SAVES?,

AT_BATS?, RUNS?, HITS?, DOUBLES?, TRIPLES?, HOME_RUNS?,

RBI?, STEALS?, CAUGHT_STEALING?, SACRIFICE_HITS?,

SACRIFICE_FLIES?, ERRORS?, WALKS?, STRUCK_OUT?,

HIT_BY_PITCH?, COMPLETE_GAMES?, SHUT_OUTS?, ERA?,

INNINGS?,EARNED_RUNS?, HIT_BATTER?, WILD_PITCHES?,

BALK?,WALKED_BATTER?, STRUCK_OUT_BATTER?)

<!ELEMENT SURNAME (#PCDATA)>

<!ELEMENT GIVEN_NAME (#PCDATA)>

<!ELEMENT POSITION (#PCDATA)>

<!ELEMENT GAMES (#PCDATA)>

<!ELEMENT GAMES_STARTED (#PCDATA)>

<!ELEMENT COMPLETE_GAMES (#PCDATA)>

<!ELEMENT WINS (#PCDATA)>

<!ELEMENT LOSSES (#PCDATA)>

<!ELEMENT SAVES (#PCDATA)>

<!ELEMENT AT_BATS (#PCDATA)>

<!ELEMENT RUNS (#PCDATA)>

<!ELEMENT HITS (#PCDATA)>

<!ELEMENT DOUBLES (#PCDATA)>

<!ELEMENT TRIPLES (#PCDATA)>

<!ELEMENT HOME_RUNS (#PCDATA)>

<!ELEMENT RBI (#PCDATA)>

<!ELEMENT STEALS (#PCDATA)>

<!ELEMENT CAUGHT_STEALING (#PCDATA)>

<!ELEMENT SACRIFICE_HITS (#PCDATA)>

<!ELEMENT SACRIFICE_FLIES (#PCDATA)>

<!ELEMENT ERRORS (#PCDATA)>

<!ELEMENT WALKS (#PCDATA)>

<!ELEMENT STRUCK_OUT (#PCDATA)>

<!ELEMENT HIT_BY_PITCH (#PCDATA)>

<!ELEMENT SHUT_OUTS (#PCDATA)>

<!ELEMENT ERA (#PCDATA)>

<!ELEMENT INNINGS (#PCDATA)>

<!ELEMENT HOME_RUNS_AGAINST (#PCDATA)>

<!ELEMENT RUNS_AGAINST (#PCDATA)>

<!ELEMENT EARNED_RUNS (#PCDATA)>

<!ELEMENT HIT_BATTER (#PCDATA)>

<!ELEMENT WILD_PITCHES (#PCDATA)>

<!ELEMENT BALK (#PCDATA)>

<!ELEMENT WALKED_BATTER (#PCDATA)>

<!ELEMENT STRUCK_OUT_BATTER (#PCDATA)>

]>

<SEASON>

<YEAR>1998</YEAR>

<LEAGUE>

<LEAGUE_NAME>National</LEAGUE_NAME>

<DIVISION>

<DIVISION_NAME>East</DIVISION_NAME>

<TEAM>

<TEAM_CITY>Florida</TEAM_CITY>

<TEAM_NAME>Marlins</TEAM_NAME>

<PLAYER>

<GIVEN_NAME>Eric</GIVEN_NAME>

<SURNAME>Ludwick</SURNAME>

<POSITION>Starting Pitcher</POSITION>

<GAMES>13</GAMES>

<GAMES_STARTED>6</GAMES_STARTED>

<WINS>1</WINS>

<LOSSES>4</LOSSES>

<SAVES>0</SAVES>

<COMPLETE_GAMES>0</COMPLETE_GAMES>

<SHUT_OUTS>0</SHUT_OUTS>

<ERA>7.44</ERA>

<INNINGS>32.2</INNINGS>

<EARNED_RUNS>31</EARNED_RUNS>

<HIT_BATTER>27</HIT_BATTER>

<WILD_PITCHES>0</WILD_PITCHES>

<BALK>2</BALK>

<WALKED_BATTER>0</WALKED_BATTER>

<STRUCK_OUT_BATTER>17</STRUCK_OUT_BATTER>

</PLAYER>

<PLAYER>

<GIVEN_NAME>Brian</GIVEN_NAME>

<SURNAME>Daubach</SURNAME>

<POSITION>First Base</POSITION>

<GAMES>10</GAMES>

<GAMES_STARTED>3</GAMES_STARTED>

<AT_BATS>15</AT_BATS>

<RUNS>0</RUNS>

<HITS>3</HITS>

<DOUBLES>1</DOUBLES>

<TRIPLES>0</TRIPLES>

<HOME_RUNS>0</HOME_RUNS>

<RBI>3</RBI>

<STEALS>0</STEALS>

<CAUGH T_STEALING>0</CAUGHT_STEALING>

<SACRIFICE_ HITS>0</SACRIFICE_HITS>

<SACRIFICE_FLIES>0</SACRIFICE_FLIES>

<ERRORS>0</ERRORS>

<WALKS>1</WALKS>

<STRUCK_OUT>5</STRUCK_OUT>

<HIT_BY_PITCH>1</HIT_BY_PITCH>

</PLAYER>

</TEAM>

<TEAM>

<TEAM_CITY>Montreal</TEAM_CITY>

<TEAM_NAME>Expos</TEAM_NAME>

</TEAM>

<TEAM>

<TEAM_CITY>New York</TEAM_CITY>

<TEAM_NAME>Mets</TEAM_NAME>

</TEAM>

<TEAM>

<TEAM_CITY>Philadelphia</TEAM_CITY>

<TEAM_NAME>Phillies</TEAM_NAME>

</TEAM>

</DIVISION>

<DIVISION>

<DIVISION_NAME>Central</DIVISION_NAME>

<TEAM>

<TEAM_CITY>Chicago</TEAM_CITY>

<TEAM_NAME>Cubs</TEAM_NAME>

</TEAM>

</DIVISION>

<DIVISION>

<DIVISION_NAME>West</DIVISION_NAME>

<TEAM>

<TEAM_CITY>Arizona</TEAM_CITY>

<TEAM_NAME>Diamondbacks</TEAM_NAME>

</TEAM>

</DIVISION>

</LEAGUE>

<LEAGUE>

<LEAGUE_NAME>American</LEAGUE_NAME>

<DIVISION>

<DIVISION_NAME>East</DIVISION_NAME>

<TEAM>

<TEAM_CITY>Baltimore</TEAM_CITY>

<TEAM_NAME>Orioles</TEAM_NAME>

</TEAM>

</DIVISION>

<DIVISION>

<DIVISION_NAME>Central</DIVISION_NAME>

<TEAM>

<TEAM_CITY>Chicago</TEAM_CITY>

<TEAM_NAME>White Sox</TEAM_NAME>

</TEAM>

</DIVISION>

<DIVISION>

<DIVISION_NAME>West</DIVISION_NAME>

<TEAM>

<TEAM_CITY>Anaheim</TEAM_CITY>

<TEAM_NAME>Angels</TEAM_NAME>

</TEAM>

</DIVISION>

</LEAGUE>

</SEASON>

清单8-9不是符合这项DTD的唯一可能的文档,清单8-10也是一份合法的文档,因为它按规定的顺序包含了需要的所有元素,并且不包含未经声明的任何元素。这也许是您根据DTD创建的最短的合法文档。限定因素是这样的要求,每个SEASON包含两个LEAGUE子元素,每个LEAGUE子元素包含三个DIVISION子元素,每个DIVISION包含至少一个TEAM子元素。

清单8-10:另外一份符合棒球DTD的合法的XML文档

<?xml version="1.0" standalone="yes"?>

<!DOCTYPE SEASON [

<!ELEMENT YEAR (#PCDATA)>

<!ELEMENT LEAGUE (LEAGUE_NAME, DIVISION, DIVISION, DIVISION)>

<!ELEMENT LEAGUE_NAME (#PCDATA)>

<!ELEMENT DIVISION_NAME (#PCDATA)>

<!ELEMENT DIVISION (DIVISION_NAME, TEAM+)>

<!ELEMENT SEASON (YEAR, LEAGUE, LEAGUE)>

<!ELEMENT TEAM (TEAM_CITY, TEAM_NAME, PLAYER*)>

<!ELEMENT TEAM_CITY (#PCDATA)>

<!ELEMENT TEAM_NAME (#PCDATA)>

<!ELEMENT PLAYER (GIVEN_NAME, SURNAME, POSITION, GAMES,

GAMES_STARTED, COMPLETE_GAMES?, WINS?, LOSSES?, SAVES?,

AT_BATS?, RUNS?, HITS?, DOUBLES?, TRIPLES?, HOME_RUNS?,

RBI?, STEALS?, CAUGHT_STEALING?, SACRIFICE_ HITS?,

SACRIFICE_FLIES?, ERRORS?, WALKS?, STRUCK_OUT?,

HIT_BY_PITCH?, COMPLETE_GAMES?, SHUT_OUTS?, ERA?, INNINGS?,

EARNED_RUNS?, HIT_BATTER?, WILD_PITCHES?, BALK?,

WALKED_BATTER?, STRUCK_OUT_BATTER?)

<!ELEMENT SURNAME (#PCDATA)>

<!ELEMENT GIVEN_NAME (#PCDATA)>

<!ELEMENT POSITION (#PCDATA)>

<!ELEMENT GAMES (#PCDATA)>

<!ELEMENT GAMES_STARTED (#PCDATA)>

<!ELEMENT COMPLETE_GAMES (#PCDATA)>

<!ELEMENT WINS (#PCDATA)>

<!ELEMENT LOSSES (#PCDATA)>

<!ELEMENT SAVES (#PCDATA)>

<!ELEMENT AT_BATS (#PCDATA)>

<!ELEMENT RUNS (#PCDATA)>

<!ELEMENT HITS (#PCDATA)>

<!ELEMENT DOUBLES (#PCDATA)>

<!ELEMENT TRIPLES (#PCDATA)>

<!ELEMENT HOME_RUNS (#PCDATA)>

<!ELEMENT RBI (#PCDATA)>

<!ELEMENT STEALS (#PCDATA)>

<!ELEMENT CAUGHT_STEALING (#PCDATA)>

<!ELEMENT SACRIFICE_ HITS (#PCDATA)>

<!ELEMENT SACRIFICE_FLIES (#PCDATA)>

<!ELEMENT ERRORS (#PCDATA)>

<!ELEMENT WALKS (#PCDATA)>

<!ELEMENT STRUCK_OUT (#PCDATA)>

<!ELEMENT HIT_BY_PITCH (#PCDATA)>

<!ELEMENT SHUT_OUTS (#PCDATA)>

<!ELEMENT ERA (#PCDATA)>

<!ELEMENT INNINGS (#PCDATA)>

<!ELEMENT HOME_RUNS_AGAINST (#PCDATA)>

<!ELEMENT RUNS_AGAINST (#PCDATA)>

<!ELEMENT EARNED_RUNS (#PCDATA)>

<!ELEMENT HIT_BATTER (#PCDATA)>

<!ELEMENT WILD_PITCHES (#PCDATA)>

<!ELEMENT BALK (#PCDATA)>

<!ELEMENT WALKED_BATTER (#PCDATA)>

<!ELEMENT STRUCK_OUT_BATTER (#PCDATA)>

]>

<SEASON>

<YEAR>1998</YEAR>

<LEAGUE>

<LEAGUE_NAME>National</LEAGUE_NAME>

<DIVISION>

<DIVISION_NAME>East</DIVISION_NAME>

<TEAM>

<TEAM_CITY>Atlanta</TEAM_CITY>

<TEAM_NAME>Braves</TEAM_NAME>

</TEAM>

<TEAM>

<TEAM_CITY>Florida</TEAM_CITY>

<TEAM_NAME>Marlins</TEAM_NAME>

</TEAM>

<TEAM>

<TEAM_CITY>Montreal</TEAM_CITY>

<TEAM_NAME>Expos</TEAM_NAME>

</TEAM>

<TEAM>

<TEAM_CITY>New York</TEAM_CITY>

<TEAM_NAME>Mets</TEAM_NAME>

</TEAM>

<TEAM>

<TEAM_CITY>Philadelphia</TEAM_CITY>

<TEAM_NAME>Phillies</TEAM_NAME>

</TEAM>

</DIVISION>

<DIVISION>

<DIVISION_NAME>Central</DIVISION_NAME>

<TEAM>

<TEAM_CITY>Chicago</TEAM_CITY>

<TEAM_NAME>Cubs</TEAM_NAME>

</TEAM>

</DIVISION>

<DIVISION>

<DIVISION_NAME>West</DIVISION_NAME>

<TEAM>

<TEAM_CITY>Arizona</TEAM_CITY>

<TEAM_NAME>Diamondbacks</TEAM_NAME>

</TEAM>

</DIVISION>

</LEAGUE>

<LEAGUE>

<LEAGUE_NAME>American</LEAGUE_NAME>

<DIVISION>

<DIVISION_NAME>East </DIVISION_NAME>

<TEAM>

<TEAM_CITY>Baltimore</TEAM_CITY>

<TEAM_NAME>Orioles</TEAM_NAME>

</TEAM>

</DIVISION>

<DIVISION>

<DIVISION_NAME>Central</DIVISION_NAME>

<TEAM>

<TEAM_CITY>Chicago</TEAM_CITY>

<TEAM_NAME>White Sox</TEAM_NAME>

</TEAM>

</DIVISION>

<DIVISION>

<DIVISION_NAME>West</DIVISION_NAME>

<TEAM>

<TEAM_CITY>Anaheim</TEAM_CITY>

<TEAM_NAME>Angels</TEAM_NAME>

</TEAM>

</DIVISION>

</LEAGUE>

</SEASON>

8.5.9 选择

通常,一个父元素会有许多子元素。为了指明各子元素必须按顺序出现,可将这些子元素用逗号隔开。每个这样的子元素还可以以问号、加号或星号作后缀,以便调节它在那一位置按顺序出现的次数。

到目前为止,已经假定子元素以一定的顺序出现或不出现。还可以使DTD更加灵活,如允许文档作者在给定的地方选择不同的元素。例如,在一项描述顾客购物的DTD中,结帐方式信息中的每项PAYMENT元素都有CREDIT_CARD子元素或CASH子元素以便提供付款方式的信息。然而,单独的PAYMENT元素不能同时使用两者。

在父元素声明中,可以使用竖线(1)而不是逗号来分开子元素,以便指明文档作者需要输入一个或另一个子元素。例如,下面的语句就说明PAYMENT元素必须有CASH或CREDIT_CARD中的一个子元素。

<!ELEMENT PAYMENT (CASH | CREDIT_CARD)>

这种内容规格称为选择。当只使用它们当中的一个时就可用竖线分开任意数目的子元素。例如,下面语句说明PAYMENT元素必须有CASH、CREDIT_CARD或CHECK中的一个子元素。

<!ELEMENT PAYMENT (CASH | CREDIT_CARD | CHECK)>

当用括号对元素分组时竖线还会更有用。可以把元素组合在括号内分组,然后在括号后加星号、问号和加号后缀来指明一定的元素组合会出现零次或多次、零次或一次或者一次或多次。

8.5.10 带括号的子元素

在父元素声明中,必须了解有关子元素安排的最后一件事是如何用括号分组元素。每一对括号把数个元素合为一个独立元素。括号内的元素可以作为独立元素嵌套在其他括号内。而且,还可以加上加号、逗号或问号后缀。您还可以将这些括号组合成更大的括号组合来构成复杂的结构。这是一项功能强大的技术。

例如,考虑一份由两个互相可交换的元素组成的清单。这基本上是HTML中定义清单的方法。每项<dt>标记要与一项<dd>标记相匹配。如果用XML来复制这一结构,dl元素的声明看起来是这样的:

<!ELEMENT dl (dt , dd)*>

括号表明要重复的是相互匹配的<dt><dd>元素对。

元素经常以或多或少的随机顺序出现。新闻杂志文章通常有一个标题,绝大多数后接文章段落,带有图形、照片、副栏、副标题、通篇夹杂的引文,也许在末尾还有作者行。可以在父元素声明中在括号内用竖线分组列出所有子元素来指明这些安排。然后您在括号外加星号来指明允许括号内元素出现零或多次。例如:

<!ELEMENT ARTICLE (TITLE, (P | PHOTO | GRAPH | SIDEBAR

| PULLQUOTE | SUBHEAD)*, BYLINE?)>

再举一例,假设要说明一个DOCUMENT元素,它没有很多子元素,但必须有一个TITLE后接任意数量的混合文本段落和图像,以及一个任选的SIGNATURE块。该元素声明书写如下:

<!ELEMENT DOCUMENT (TITLE, (PARAGRAPH | IMAGE)*, SIGNATURE?)>

这不是描述这一结构的唯一方法。实际上这甚至不是最好的方法。另一种方法是声明一个包含PARAGRAPH和IMAGE元素的BODY元素并把它夹在TITLE和SIGNATURE元素之间,例如:

<!ELEMENT DOCUMENT (TITLE, BODY, SIGNATURE?)>

<!ELEMENT BODY ((PARAGRAPH | IMAGE)*)>

这两种途径的区别在于第二种途径在文档中使用了BODY元素。这一元素对读取文档的应用程序提供了有用的组织层次。问题是文档的读者(可能是另一种计算机程序)是否要把BODY作为单一的项目,并同TITLE和SIGNATURE分开,并可从元素总和中区别出来。

再举一个国际地址的例子。美国以外国家的地址并不遵循美国的约定。尤其是邮政编码有时在国家名之前,有时则在其后,如下两例:

Doberman-YPPAN

Box 2021

St. Nicholas QUEBEC

CAN GOS-3LO

或者

Editions Sybex

10/12 Villa Coeur-de-Vey

75685 Paris Cedex 14

France

虽然地址项不是按照顺序,邮件也可能邮到目的地,但最好还是让地址更加方便灵活些。允许灵活性的地址元素声明可以是这样:

<!ELEMENT ADDRESS (STREET+, (CITY | STATE | POSTAL_CODE

| COUNTRY)*)>

这表明ADDRESS元素必须有一个或多个STREET子元素后接任意数目的CITY、STATE、POSTAL_CODE或COUNTRY元素。如果要每个元素不多于一个,那这就不够理想了。遗憾的是,这超出了DTD的能力。您要使元素的顺序更加灵活方便,就要放弃一些控制每一元素最大数的能力。

另一方面,可能有一份由任意顺序排列的不同元素组成的清单,如一份录音清单就可能包含CD,唱片集和音带。区别各类不同元素的元素声明可能如下:

<!ELEMENT MUSIC_LIST (CD | ALBUM | TAPE)*>

在棒球DTD中,可以使用括号来为投手和击球手做不同的统计数据集。每名队员能用一套或另一套数据,但不能用两者。元素声明如下:

<!ELEMENT PLAYER (GIVEN_NAME, SURNAME, POSITION, GAMES,

GAMES_STARTED,((COMPLETE_GAMES?,WINS?,LOSSES?,SAVES?,

SHUT_OUTS?,ERA?,INNINGS?,EARNED_RUNS?,HIT_BATTER?,

WILD_PITCHES?,BALK?,WALKED_BATTER?,STRUCK_OUT_BATTER? )

|(AT_BATS?, RUNS?,HITS?, DOUBLES?, TRIPLES?, HOME_RUNS?,

RBI?,STEALS?,CAUGHT_STEALING?,SACRIFICE_HITS?,

SACRIFICE_FLIES?, ERRORS?, WALKS?, STRUCK_OUT?,

HIT_BY_PITCH ? )))>

在元素声明中还有一些不好处理的事情。例如,没有好的方法来说明一份文档要以TITLE元素开始而以SIGNATURE元素结束,两者之间可包含其他元素。这是因为ANY不能与其他子元素合用。

还有,通常对元素出现的位置掌握得越不准确,就越不能控制它们的数目。例如,不能说文档应该有一个可能出现在文档任何地方的TITLE元素。

但是,用括号来建立元素块,按顺序的元素用逗号分隔,平行出现的用竖线分隔,能让我们建立带有详细的元素出现的位置规则的复杂结构。但是不要无止境地这样做。简单的解决方法会更好。DTD越复杂,就越难编写出满足要求的合法的文档,更不要说维护DTD自身的复杂性了。

8.5.11 混合内容

读者可能已经注意到了,在以前的多数例子中,元素或者包含子元素,或者包含可析的字符数据,但不能同时包含两者。唯一的例外是以前例子中的一些基本元素。在这些例子中,全部标记的列表还没有完成。由于基本元素可以包含ANY数据,因而就既可以包含子元素又可以包含原始文本。

可以声明同时包含子元素和可析字符数据的标记。这就叫做混合内容。这样就可以给每个TEAM后面加上任意的文本块。例如:

<!ELEMENT TEAM (#PCDATA | TEAM_CITY | TEAM_NAME | PLAYER)*>

带有可析的字符数据的混合子元素会严重地限制文档的结构。特别是,只能指定可出现的子元素的名称。不能限定它们出现的顺序,每个元素的出现次数,或者它们是否出现。借助于DTD,利用下面的DTD中的一部分可实现这一要求:

<!ELEMENT PARENT (#PCDATA | CHILD1 | CHILD2 | CHILD3 )* >

除了改变子元素数目以外的其他事情都是不合法的。不能在包括#PCDATA的元素声明中使用逗号、问号或加号。用竖线分隔的元素和#PCDATA的列表是合法的。其他用法是不合法的。例如,下面的例子就不合法:

<!ELEMENT TEAM (TEAM_CITY, TEAM_NAME, PLAYER*, #PCDATA)>

使用混合内容的最基本的理由是,当将老式的文本数据转换成XML的过程中,随着新标记的增加逐步测试DTD的合法性,而不要在完成全部转换后再试图去发现错误。这是一个很好的技巧,我建议大家都这样做,毕竟从刚完成的代码中立即找出错误比几小时后要容易一些。但是,这仅仅是开发时的一个技巧。最终的用户是不应该看到这些的。当DTD完成后不能把子元素同可析的字符数据混合起来。一般总可以建立一个包括可析的字符数据的新标记。

例如,可以声明只包含#PCDATA数据的BLURB元素并把它增加为TEAM的最后一个子元素,这样就在每个TEAM元素的末尾包括一个文本块。下面是该声明:

<!ELEMENT TEAM (TEAM_CITY, TEAM_NAME, PLAYER*, BLURB)>

<!ELEMENT BLURB (#PCDATA)>

这对文档的文本改变不大。所有的变化只是向每个TEAM元素增加了一个或多个带有开始标记和结束标记的可选元素。但是这就使文档更加健全。而且,从XML程序接收到文档树的XML应用程序就能在更短的时间内处理数据,因为文档具有非混合内容所允许的更为结构化的格式。

8.5.12 空元素

前面讨论过,定义一个没有内容的元素有时是有用的。HTML中的例子包括图像、水平线和中断<IMG>、<R>和<BR>。在XML中,这类空元素是通过以/>结束的空标记来标识的,如<IMG/>、<HR/>和<BR/>。

合法的文档必须声明使用的空元素和非空元素。因为按定义,空元素没有子元素,声明很容易。可像通常一样使用包含空元素名的<!ELEMENT>来声明,但用关键词EMPTY (像所有XML标记一样区分大小写)代替了子元素的列表。例如:

<!ELEMENT BR EMPTY>

<!ELEMENT IMG EMPTY>

<!ELEMENT HR EMPTY>

清单8-11是同时使用了空元素和非空元素的合法文档。

清单8-11:使用了空标记的合法文档

<?xml version="1.0" standalone="yes"?>

<!DOCTYPE DOCUMENT [

<!ELEMENT DOCUMENT (TITLE,SIGNATURE)>

<!ELEMENT TITLE (#PCDATA)>

<!ELEMENT COPYRIGHT (#PCDATA)>

<!ELEMENT EMAIL (#PCDATA)>

<!ELEMENT BR EMPTY>

<!ELEMENT HR EMPTY>

<!ELEMENT LAST_MODIFIED (#PCDATA)>

<!ELEMENT SIGNATURE (HR, COPYRIGHT, BR, EMAIL,

BR, LAST_MODIFIED)>

]>

<DOCUMENT>

<TITLE>Empty Tags</TITLE>

<SIGNATURE>

<HR/>

<COPYRIGHT>1998 Elliotte Rusty Harold</COPYRIGHT><BR/>

<EMAIL>elharo@metalab.unc.edu</EMAIL><BR/>

<LAST_MODIFIED>Thursday,April 22,1999</LAST_MODIFIED>

</SIGNATURE>

</DOCUMENT>


8.6 DTD中的注释

像一份XML文档的其他部分一样,DTD也可以包含注释。这些注释不能在声明中出现,但可以在声明外出现。注释通常用来组织不同部分的DTD,为一些元素的许可内容提供说明,并对元素作进一步的解释。例如,YEAR元素的声明可以有这样的注释:

<!--A four digit year like 1998, 1999, or 2000 ?-->

<!ELEMENT YEAR (#PCDATA)>

像所有注释一样,这只是为了便于人们阅读源代码,XML处理程序会忽略注释部分。

注释的一个可能用法是定义标记中用到的缩略语。例如,在本章及前些章中,我极力避免使用棒球术语的缩略语,因为对一些人来说难以弄清楚。一种可能的途径是使用缩略语但在DTD中用注释加以定义。清单8-12同以前的棒球例子相似,但使用了DTD注释和缩略标记。

清单8-12:使用缩略标记和DTD注释的合法XML文档

<?xml version="1.0" standalone="yes"?>

<!DOCTYPE SEASON [

<!ELEMENT YEAR (#PCDATA)>

<!ELEMENT LEAGUE (LEAGUE_NAME, DIVISION, DIVISION, DIVISION)>

<!--American or National ?

<!ELEMENT LEAGUE_NAME (#PCDATA)>

<!--East , West , or Central ?

<!ELEMENT DIVISION_NAME (#PCDATA)>

<!ELEMENT DIVISION (DIVISION_NAME, TEAM+)>

<!ELEMENT SEASON (YEAR, LEAGUE, LEAGUE)>

<!ELEMENT TEAM (TEAM_CITY, TEAM_NAME, PLAYER*)>

<!ELEMENT TEAM_CITY (#PCDATA)>

<!ELEMENT TEAM_NAME (#PCDATA)>

<!ELEMENT PLAYER (GIVEN_NAME, SURNAME, P, G,

GS, AB?, R?, H?, D?, T?, HR?, RBI?, SB?, CS?,

SH?, SF?, E?, BB?, S?, HBP?, CG?, SO?, ERA?, IP?,

HRA?, RA?, ER?, HB?, WP?, B?, WB?, K?)

<!--=======================-->

<!--Player Info-->

<!--Player’s last name-->

<!ELEMENT SURNAME (#PCDATA)>

<!--Player’s first name-->

<!ELEMENT GIVEN_NAME (#PCDATA)>

<!—Position-->

<!ELEMENT P (#PCDATA)>

<!--Games Played-->

<!ELEMENT G (#PCDATA)>

<!--Games Started-->

<!ELEMENT GS (#PCDATA)>

<!--=======================-->

<!--Batting Statistics-->

<!--At Bats-->

<!ELEMENT AB (#PCDATA)>

<!--Runs-->

<!ELEMENT R (#PCDATA)>

<!--Hits-->

<!ELEMENT H (#PCDATA)>

<!--Doubles-->

<!ELEMENT D (#PCDATA)>

<!--Triples-->

<!ELEMENT T (#PCDATA)>

<!--Home Runs-->

<!ELEMENT HR (#PCDATA)>

<!--Runs Batted In-->

<!ELEMENT RBI (#PCDATA)>

<!--Stolen Bases-->

<!ELEMENT SB (#PCDATA)>

<!--Caught Stealing-->

<!ELEMENT CS (#PCDATA)>

<!--Sacrifice Hits-->

<!ELEMENT SH (#PCDATA)>

<!--Sacrifice Flies-->

<!ELEMENT SF (#PCDATA)>

<!--Errors-->

<!ELEMENT E (#PCDATA)>

<!--Walks (Base on Balls)-->

<!ELEMENT BB (#PCDATA)>

<!--Struck Out-->

<!ELEMENT S (#PCDATA)>

<!--Hit By Pitch-->

<!ELEMENT HBP (#PCDATA)>

<!--=======================-->

<!--Pitching Staistics-->

<!--Complete Games-->

<!ELEMENT CG (#PCDATA)>

<!--Shut Outs-->

<!ELEMENT SO (#PCDATA)>

<!--ERA-->

<!ELEMENT ERA (#PCDATA)>

<!--Innings Pitched-->

<!ELEMENT IP (#PCDATA)>

<!--Home Runs hit Against-->

<!ELEMENT HRA (#PCDATA)>

<!--Runs hit Against-->

<!ELEMENT RA (#PCDATA)>

<!--Earned Runs-->

<!ELEMENT ER (#PCDATA)>

<!--Hit Batter-->

<!ELEMENT HB (#PCDATA)>

<!--Wild Pitches-->

<!ELEMENT WP (#PCDATA)>

<!—Balk-->

<!ELEMENT B (#PCDATA)>

<!--Walked Batter-->

<!ELEMENT WB (#PCDATA)>

<!--Struck Out Batter-->

<!ELEMENT K (#PCDATA)>

<!--=======================-->

<!--Fielding Statistics-->

<!--Not yet supported-->

]>

<SEASON>

<YEAR>1998</YEAR>

<LEAGUE>

<LEAGUE_NAME>National</LEAGUE_NAME>

<DIVISION>

<DIVISION_NAME>East </DIVISION_NAME>

<TEAM>

<TEAM_CITY>Atlanta</TEAM_CITY>

<TEAM_NAME>Braves</TEAM_NAME>

<PLAYER>

<GIVEN_NAME>Ozzie</GIVEN_NAME>

<SURNAME>Guillen</SURNAME>

<P>Short stop</P>

<G>83</G>

<GS>59</GS>

<AB>264</AB>

<R>35</R>

<H>73</H >

<D>15</D>

<T>1</T>

<HR>1</HR>

<RBI>22</RBI>

<SB>1</SB>

<CS>4</CS>

<SH>4</SH>

<SF>2</SF>

<E>6</E>

<BB>24</BB>

<S>25</S>

<HBP>1</HBP>

</PLAYER>

</TEAM>

<TEAM>

<TEAM_CITY>Florida</TEAM_CITY>

<TEAM_NAME>Marlins</TEAM_NAME>

</TEAM>

<TEAM>

<TEAM_CITY>Montreal</TEAM_CITY>

<TEAM_NAME>Expos</TEAM_NAME>

</TEAM>

<TEAM>

<TEAM_CITY>New York</TEAM_CITY>

<TEAM_NAME>Mets</TEAM_NAME>

</TEAM>

<TEAM>

<TEAM_CITY>Philadelphia</TEAM_CITY>

<TEAM_NAME>Phillies</TEAM_NAME>

</TEAM>

</DIVISION>

<DIVISION>

<DIVISION_NAME>Central</DIVISION_NAME>

<TEAM>

<TEAM_CITY>Chicago</TEAM_CITY>

<TEAM_NAME>Cubs</TEAM_NAME>

</TEAM>

</DIVISION>

<DIVISION>

<DIVISION_NAME>West</DIVISION_NAME>

<TEAM>

<TEAM_CITY>Arizona</TEAM_CITY>

<TEAM_NAME>Diamondbacks</TEAM_NAME>

</TEAM>

</DIVISION>

</LEAGUE>

<LEAGUE>

<LEAGUE_NAME>American</LEAGUE_NAME>

<DIVISION>

<DIVISION_NAME>East</DIVISION_NAME>

<TEAM>

<TEAM_CITY>Baltimore</TEAM_CITY>

<TEAM_NAME>Orioles</TEAM_NAME>

</TEAM>

</DIVISION>

<DIVISION>

<DIVISION_NAME>Central</DIVISION_NAME>

<TEAM>

<TEAM_CITY>Chicago</TEAM_CITY>

<TEAM_NAME>White Sox</TEAM_NAME>

</TEAM>

</DIVISION>

<DIVISION>

<DIVISION_NAME>West</DIVISION_NAME>

<TEAM>

<TEAM_CITY>Anaheim</TEAM_CITY>

<TEAM_NAME>Angels</TEAM_NAME>

</TEAM>

</DIVISION>

</LEAGUE>

</SEASON>

当整个联赛全包含在内时,产生的文档从带长标记的699K缩短到带短标记的391K,节约了44%。但是信息的内容是相同的。两个文档压缩后很接近,短标记文档58K,长标记文档66K。

在注释内可以包括和应该包括的信息量没有限制。包括得多使DTD更长(这样就使检测更难,下载更慢)。然而,在下面的几章中,您将学会如何在多个XML文档间共享同一DTD以及将DTD拆成更好管理的多个部分。这样,使用注释的缺点就是暂时的了。我建议在DTD中自由地使用注释,尤其是对于打算公用的DTD。


8.7 在文档间共享通用的DTD

前面的合法的例子都在文档的序言部分包含了DTD。但是XML真正的功能来自于不同的人们编写的可为许多文档共享通用的DTD。如果DTD不是直接包含在文档内,而是从外部联结而来,则DTD的改变会自动传播给使用它的所有文档。另一方面,当DTD改变时并不能确保其向后兼容性。不兼容的改变会破坏文档。

当使用外部DTD时,文档类型声明要加以改变。DTD不再是包括在方括号中,而是在SYSTEM关键词后接一个能找到DTD的绝对或相对的URL。例如:

<!DOCTYPE root_element_name SYSTEM "DTD_ URL">

这里root_element_name像以前一样是基本元素的名称,SYSTEM是一个XML关键词,DTD_URL是能找到DTD的绝对或相对的URL。例如:

<!DOCTYPE SEASON SYSTEM "baseball.dtd">

为说明这一过程让我们来转换一个熟悉的例子。清单8-12包括了棒球统计的内部DTD。我们要把这份清单转换为外部DTD。首先,去掉DTD并把它放入自己的文档。DTD是起始于<!DOCTYPE SEASON [终止于]>之间的所有内容。但不包括<!DOCTYPE SEASON [和]>。可以将其保存在名为baseball.dtd的文档内,如清单8-13所示。文档名并不重要,通常用的扩展名为.dtd。

清单8-13:棒球的DTD文档

<!ELEMENT YEAR (#PCDATA)>

<!ELEMENT LEAGUE (LEAGUE_NAME, DIVISION, DIVISION, DIVISION)>

<!--American or National-->

<!ELEMENT LEAGUE_NAME (#PCDATA)>

<!--East, West, or Central-->

<!ELEMENT DIVISION_NAME (#PCDATA)>

<!ELEMENT DIVISION (DIVISION_NAME, TEAM+)>

<!ELEMENT SEASON (YEAR, LEAGUE, LEAGUE)>

<!ELEMENT TEAM (TEAM_CITY, TEAM_NAME, PLAYER*)>

<!ELEMENT TEAM_CITY (#PCDATA)>

<!ELEMENT TEAM_NAME (#PCDATA)>

<!ELEMENT PLAYER (GIVEN_NAME, SURNAME, P, G,

GS, AB?, R?,H?, D?, T?, HR?, RBI?, SB?, CS?,

SH?, SF?, E?, BB?, S?, HBP?, CG?, SO?, ERA?, IP?,

HRA?, RA?, ER?, HB?, WP?, B?, WB?, K?)

<!--=======================-->

<!--Player Info-->

<!--Player’s last name-->

<!ELEMENT SURNAME (#PCDATA)>

<!--Player’s first name-->

<!ELEMENT GIVEN_NAME (#PCDATA)>

<!--Position-->

<!ELEMENT P (#PCDATA)>

<!--Games Played-->

<!ELEMENT G (#PCDATA)>

<!--Games Started-->

<!ELEMENT GS (#PCDATA)>

<!--=======================-->

<!--Batting Statistics-->

<!--At Bats-->

<!ELEMENT AB (#PCDATA)>

<!--uns-->

<!ELEMENT R (#PCDATA)>

<!--Hits--> ?

<!ELEMENT H (#PCDATA)>

<!--Doubles-->

<!ELEMENT D (#PCDATA)>

<!--Triples-->

<!ELEMENT T (#PCDATA)>

<!--Home Runs-->

<!ELEMENT HR (#PCDATA)>

<!--Runs Batted In-->

<!ELEMENT RBI (#PCDATA)>

<!--Stolen Bases-->

<!ELEMENT SB (#PCDATA)>

<!--Caught Stealing-->

<!ELEMENT CS (#PCDATA)>

<!--Sacrifice Hits-->

<!ELEMENT SH (#PCDATA)>

<!--Sacrifice Flies-->

<!ELEMENT SF (#PCDATA)>

<!—Errors-->

<!ELEMENT E (#PCDATA)>

<!--Walks (Base on Balls)-->

<!ELEMENT BB (#PCDATA)>

<!--Struck Out-->

<!ELEMENT S (#PCDATA)>

<!--Hit By Pitch-->

<!ELEMENT HBP (#PCDATA)>

<!--=======================-->

<!--Pitching Staistics-->

<!--Complete Games-->

<!ELEMENT CG (#PCDATA)>

<!--Shut Outs-->

<!ELEMENT SO (#PCDATA)>

<!--ERA-->

<!ELEMENT ERA (#PCDATA)>

<!--Innings Pitched-->

<!ELEMENT IP (#PCDATA)>

<!--Home Runs hit Against-->

<!ELEMENT HRA (#PCDATA)>

<!--Runs hit Against-->

<!ELEMENT RA (#PCDATA)>

<!--Earned Runs-->

<!ELEMENT ER (#PCDATA)>

<!--Hit Batter-->

<!ELEMENT HB (#PCDATA)>

<!--Wild Pitches-->

<!ELEMENT WP (#PCDATA)>

<!—Balk-->

<!ELEMENT B (#PCDATA)>

<!--Walked Batter-->

<!ELEMENT WB (#PCDATA)>

<!--Struck Out Batter-->

<!ELEMENT K (#PCDATA)>

<!--=======================-->

<!--Fielding Statistics-->

<!--Not yet supported-->

接下来,需要改动文档本身。因为要依赖于另一文档中的DTD,XML声明不再是独立的文档。所以standalone属性要改为no,如下所示:

<?xml version="1.0" standalone="no"?>

然后还要改变<!DOCTYPE>标记,借助于包括SYSTEM关键字和URL(通常是相对的)使它指向DTD。

<!DOCTYPE SEASON SYSTEM "baseball.dtd" >

文档的其余部分与以前相同。但是,现在序言部分只包含XML声明和文档类型声明而不包括DTD。清单8-14显示了这些代码。

清单8-14:带有外部DTD的棒球统计

<?xml version="1.0" standalone="yes"?>

<!DOCTYPE SEASON SYSTEM "baseball.dtd" >

<SEASON>

<YEAR>1998</YEAR>

<LEAGUE>

<LEAGUE_NAME>National</LEAGUE_NAME>

<DIVISION>

<DIVISION_NAME>East</DIVISION_NAME>

<TEAM>

<TEAM_CITY>Atlanta</TEAM_CITY>

<TEAM_NAME>Braves</TEAM_NAME>

<PLAYER>

<GIVEN_NAME>Ozzie</GIVEN_NAME>

<SURNAME>Guillen</SURNAME>

<P>Shortstop</P>

<G>83</G>

<GS>59</GS>

<AB>264</AB>

<R>35</R>

<H>73</H>

<D>15</D>

<T>1</T>

<HR>1</HR>

<RBI>22</RBI>

<SB>1</SB>

<CS>4</CS>

<S >4</S >

<SF>2</SF>

<E>6</E>

<BB>24</BB>

<S>25</S>

<HBP>1</HBP>

</PLAYER>

</TEAM>

<TEAM>

<TEAM_CITY>Florida</TEAM_CITY>

<TEAM_NAME>Marlins</TEAM_NAME>

</TEAM>

<TEAM>

<TEAM_CITY>Montreal</TEAM_CITY>

<TEAM_NAME>Expos</TEAM_NAME>

</TEAM>

<TEAM>

<TEAM_CITY>New York</TEAM_CITY>

<TEAM_NAME>Mets</TEAM_NAME>

</TEAM>

<TEAM>

<TEAM_CITY>Philadelphia</TEAM_CITY>

<TEAM_NAME>Phillies</TEAM_NAME>

</TEAM>

</DIVISION>

<DIVISION>

<DIVISION_NAME>Central</DIVISION_NAME>

<TEAM>

<TEAM_CITY>Chicago</TEAM_CITY>

<TEAM_NAME>Cubs</TEAM_NAME>

</TEAM>

</DIVISION>

<DIVISION>

<DIVISION_NAME>West</DIVISION_NAME>

<TEAM>

<TEAM_CITY>Arizona</TEAM_CITY>

<TEAM_NAME>Diamondbacks</TEAM_NAME>

</TEAM>

</DIVISION>

</LEAGUE>

<LEAGUE>

<LEAGUE_NAME>American</LEAGUE_NAME>

<DIVISION>

<DIVISION_NAME>East</DIVISION_NAME>

<TEAM>

<TEAM_CITY>Baltimore</TEAM_CITY>

<TEAM_NAME>Orioles</TEAM_NAME>

</TEAM>

</DIVISION>

<DIVISION>

<DIVISION_NAME>Central</DIVISION_NAME>

<TEAM>

<TEAM_CITY>Chicago</TEAM_CITY>

<TEAM_NAME>White Sox</TEAM_NAME>

</TEAM>

</DIVISION>

<DIVISION>

<DIVISION_NAME>West</DIVISION_NAME>

<TEAM>

<TEAM_CITY>Anaheim</TEAM_CITY>

<TEAM_NAME>Angels</TEAM_NAME>

</TEAM>

</DIVISION>

</LEAGUE>

</SEASON>

一定要确保清单8-14和baseball.dtd在同一目录下,然后像通常一样把清单8-14装入Web浏览器。如果一切正常,就会看到同装入清单8-12一样的输出。现在可以使用这个DTD来编写其他文档,如其他年度的统计数据。

如果添加了样式单,那么就在三个不同的文档中保存了文档的三个重要部分。数据在文档文件中,数据应用的结构和语义在DTD文件中,而格式在样式单中。这种结构使我们能相对独立地检查和改变其中任一部分或全部。

DTD与文档之间比文档与样式单之间联系更紧密。改变DTD一般要重新检查文档的合法性,并需要编辑文档使它与DTD相符。这样的顺序必要性取决于编辑方法;增加元素没什么问题,但移走元素就可能有问题。

8.7.1 远程URL上的DTD

如果一个DTD适用于多份文档,就不能总把它放在应用它的每份文档的同一目录下。可以使用URL来准确指明DTD的地址。例如,让我们假设棒球DTD在http://metalab.unc.edu/xml/dtds/baseball.dtd,可在序言中使用下面的<!DOCTYPE> 标记将其链接到文档上:

<!DOCTYPE SEASON SYSTEM

"http://metalab.unc.edu/xml/dtds/baseball.dtd">

本例中使用了完整的URL,从任何地方都是合法的。有时也希望从相对于Web服务器文档根目录或当前目录找出DTD来 。通常,任何相对于文档位置所形成合法的URL的引用都可以接受。例如,下面这些都是合法的文档类型声明:

<!DOCTYPE SEASON SYSTEM"/xml/dtds/baseball.dtd">

<!DOCTYPE SEASON SYSTEM"/dtds/baseball.dtd">

<!DOCTYPE SEASON SYSTEM "../baseball.dtd">

一个文档不能有多于一个的文档类型声明,即不能有多于一个的<!DOCTYPE >标记。要使用不止在一个DTD中声明的元素,就需要使用外部参数实体引用。这些内容将在下一章中讨论。

8.7.2 公共的DTD

关键词SYSTEM是为单个作者或小组所用的私有的DTD使用的。但作为XML承诺的一部分,可令覆盖整个产业的广泛组织(如ISO或IEEE)能够将公共DTD加以标准化,以便用于各自的专门领域。这样的标准化可以让人们不用为同一项目重复作标记,并且使用户共享公用文档更容易。

为创建组织之外的编写者设计的DTD使用PUBLIC关键词而不使用SYSTEM关键词。并且DTD有一个文件名。句法如下:

<!DOCTYPE root_element_name PUBLIC "DTD_name" "DTD_URL">

root_element_name仍然是基本元素名称。PUBLIC是XML关键词,说明这一DTD是公共使用并具有名称。DTD_name是与此DTD联系的名称。有些XML处理程序会使用名称从中心库中检索DTD。最后,如果DTD不能根据名称从熟知的库中检索到,则DTD_URL是一个能找到该DTD的相对或绝对URL。

DTD名称与XML名称略有不同。它们只能包含ASCII字母字符、空格、软回车符、换行符和下面的标点符号: -’()+,/:=?;!*#@$_%。 而且,公共DTD要遵守一些约定。

如果一项DTD是ISO标准,它的名称要以字符串“ISO”开始。如果是非ISO标准组织批准的DTD,它的名称以加号(+)开始。如果不是标准组织批准的DTD,它的名称以连字符 (-)开始。这些开始字符串后接双斜线(//) 和DTD所有者的名字,其后接另一个双斜线和DTD描述的文档类型,然后又是一个双斜线后接ISO639语言标识符,如EN表示英语。在http://www.ics.uci.edu/pub/ietf/http/related/iso639.txt处列有完整的ISO639标识符。例如,棒球DTD可以如下命名:

-//Elliotte Rusty Harold//DTD baseball statistics//EN

本例说明这个DTD不是由任何标准组织批准的(-),为Elliotte Rusty Harold所有,描述棒球统计,用英语编写。通过DTD名称指向这一DTD的完整的文档类型声明如下:

<!DOCTYPE SEASON PUBLIC

"//Elliotte Rusty Harold//DTD baseball statistics//EN"

"http://metalab.unc.edu/xml/dtds/baseball.dtd">

读者也许注意到了许多HTML编辑器如BBEdit会在其创建的每个HTML文档开端放入下列字符串:

<!DOCTYPE HTML PUBLIC"-//W3C//DTD HTML//EN">

现在可能 了解这些字符串是什么意思了!它表明文档符合一项非标准 (-) 的HTML的DTD,由W3C用英语制作。

从技术上说,W3C不是一个标准组织,因为它的成员限于交纳会费的公司而不是官方批准的实体。它只出版建议而不是标准。实际上这种区别没有关系。

8.7.3 内部和外部DTD子集

尽管大多数文档由易于定义的部分组成,但不是所有的文档都使用共同的模板。许多文档为自己使用而增加特定元素时,可能需要像HTML 4.0 DTD 这样的标准DTD。其他文档可能只使用标准元素,但需要对它们重新排序。例如,一个HTML主页可能有一个BODY元素,它必须包含一个H1标题标记后接一份DL定义列表,而另一个HTML主页可能有一个BODY元素,它包含许多不同的顺序不定的标题标记、段落和图像。如果特定的文档与同一站点上其他页面具有不同的结构,在文档本身内定义结构比在单独的DTD中定义更有用。这种方法也使文档更易于编辑。

为达此目的,文档可使用内部和外部DTD。内部声明放在<!DOCTYPE>标记尾部的方括号中。例如,假如需要一个包括棒球统计并有页眉和页脚的主页。这样的文档可如清单8-15所示。棒球信息从文档baseball.dtd中得到,构成外部DTD子集。基本元素DOCUMENT 以及元素TITLE和SIGNATURE的定义来自包含于文档中的内部DTD子集。这有点不寻常。一般的,更为通用的部分可能应该是外部DTD的一部分,而内部内容则更与特定专题有关。

清单8-15:DTD具有内部和外部DTD子集的棒球文档

<?xml version="1.0" standalone="yes"?>

<!DOCTYPE SEASON SYSTEM "baseball.dtd"> [

<!ELEMENT DOCUMENT (TITLE, SEASON, SIGNATURE)>

<!ELEMENT TITLE (#PCDATA)>

<!ELEMENT COPYRIG T (#PCDATA)>

<!ELEMENT EMAIL (#PCDATA)>

<!ELEMENT LAST_MODIFIED (#PCDATA)>

<!ELEMENT SIGNATURE (COPYRIGHT, EMAIL, LAST_MODIFIED)>

]>

<DOCUMENT>

<TITLE>1998 Major League Baseball Statistics</TITLE>

<SEASON>

<YEAR>1998</YEAR>

<LEAGUE>

<LEAGUE_NAME>National</LEAGUE_NAME>

<DIVISION>

<DIVISION_NAME>East</DIVISION_NAME>

<TEAM>

<TEAM_CITY>Atlanta</TEAM_CITY>

<TEAM_NAME>Braves</TEAM_NAME>

</TEAM>

<TEAM>

<TEAM_CITY>Florida</TEAM_CITY>

<TEAM_NAME>Marlins</TEAM_NAME>

</TEAM>

<TEAM>

<TEAM_CITY>Montreal</TEAM_CITY>

<TEAM_NAME>Expos</TEAM_NAME>

</TEAM>

<TEAM>

<TEAM_CITY>New York</TEAM_CITY>

<TEAM_NAME>Mets</TEAM_NAME>

</TEAM>

<TEAM>

<TEAM_CITY>Philadelphia</TEAM_CITY>

<TEAM_NAME>Phillies</TEAM_NAME>

</TEAM>

</DIVISION>

<DIVISION>

<DIVISION_NAME>Central</DIVISION_NAME>

<TEAM>

<TEAM_CITY>Chicago</TEAM_CITY>

<TEAM_NAME>Cubs</TEAM_NAME>

</TEAM>

</DIVISION>

<DIVISION>

<DIVISION_NAME>West</DIVISION_NAME>

<TEAM>

<TEAM_CITY>Arizona</TEAM_CITY>

<TEAM_NAME>Diamondbacks</TEAM_NAME>

</TEAM>

</DIVISION>

</LEAGUE>

<LEAGUE>

<LEAGUE_NAME>American</LEAGUE_NAME>

<DIVISION>

<DIVISION_NAME>East </DIVISION_NAME>

<TEAM>

<TEAM_CITY>Baltimore</TEAM_CITY>

<TEAM_NAME>Orioles</TEAM_NAME>

</TEAM>

</DIVISION>

<DIVISION>

<DIVISION_NAME>Central</DIVISION_NAME>

<TEAM>

<TEAM_CITY>Chicago</TEAM_CITY>

<TEAM_NAME>White Sox</TEAM_NAME>

</TEAM>

</DIVISION>

<DIVISION>

<DIVISION_NAME>West</DIVISION_NAME>

<TEAM>

<TEAM_CITY>Anaheim</TEAM_CITY>

<TEAM_NAME>Angels</TEAM_NAME>

</TEAM>

</DIVISION>

</LEAGUE>

</SEASON>

<SIGNATURE>

<COPYRIGHT>Copyright 1999 Elliotte Rusty Harold</COPYRIGHT>

<EMAIL>elharo@metalab.unc.edu</EMAIL>

<LAST_MODIFIED>March 10, 1999</LAST_MODIFIED>

</SIGNATURE>

</DOCUMENT>

在内部和外部DTD子集中的同名元素之间发生冲突的情况下,内部声明的元素具有优先权。这种优先权提供了不完善部分的继承机制。例如,如要推翻PLAYER元素的定义,以便只包含击球统计数据,而不要投球统计数据。这时可使用大多数的棒球DTD的相同声明,但却要将PLAYER元素作如下改变:

<!DOCTYPE SEASON SYSTEM "baseball.dtd" [

<!ELEMENT PLAYER (GIVEN_NAME, SURNAME, P, G,

GS, AB?, R?, H?, D?, T?, HR?, RBI?, SB?, CS?,

SH ?, SF?, E?, BB?, S?, HBP?)

]>


8.8 本章小结

在本章中,学习了如何使用DTD来描述文档结构,包括文档包含的必需元素和任选元素,以及这些元素间的相关关系。特别是学习了以下内容:

·          文档类型定义(DTD),它提供了文档包含的元素、标记、属性和实体及相互关系的清单。

  • 文档序言包含文档类型声明,文档类型声明指明基本元素并包含DTD。DTD处在XML声明与实际文档开始之间。由<!DOC-TYPE ROOT [和]>加以界定,ROOT是基本元素名称。
  • DTD列出了文档的可允许的标记和结构。遵守DTD规则的文档才是合法的。
  • 元素类型声明声明元素名称和子元素。
  • 元素类型声明中用逗号分隔的子元素在文档中出现的顺序必须与声明中的顺序相同。
  • 加号表示元素可以出现一次或多次。
  • 星号表示元素可以出现零次或多次。
  • 问号表示元素可以出现零次或一次。
  • 竖线表示可以使用这一个也可以使用另一个元素。
  • 括号可以组合子元素,以便使元素声明更详尽。
  • 混合内容包含元素和可析的字符数据,但会限制父元素可实现的结构。
  • 空元素用EMPTY关键词声明。
  • 注释使DTD 更具可读性。
  • 在文档类型声明中利用SYSTEM关键词和一个URL可以定位外部DTD
  • 在文档类型声明中用PUBLIC关键词可以定位标准DTD。
  • 内部DTD子集中的声明可推翻外部DTD子集中的声明。

在下一章中,读者可学到有关DTD的更多知识,包括实体引用如何提供文本替换,如何将DTD与它所描述的文档分开,以便易于在文档间共享。还会学到如何用多份DTD描述单个文档。



[上页] [目录] [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11] [12] [13] [14] [15] [16] [18] [下页]