XML教程

8 文档类型定义和合法性

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

* 本章的主要内容包括:

* 文档类型定义DTD

* 文档类型声明

* DTD的合法性

* 元素清单

* 元素声明

* DTD中的说明

* 可在文档间共享的通用DTD

8.1 文档类型定义

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

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

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-2greeting.xml),如下所示:

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

<GREETING>

Hello XML!

</GREETING>

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

清单8-1:带有DTDHello 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-1Internet Explorer 5.0中的情况。结果可能正如人们所料,文档源以可折叠的大纲视图出现。Internet Explorer使<!DOCTYPE GREETING ( View Source for full doctype...)>一行变蓝指明有文档类型声明。

8-1 Internet Explorer 5.0中显示的带有DTDHello 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上可找到几十种不同的进行合法性检查的语法分析程序。其中多数是免费的。大多数是以库文件的形式存在的接近完成的产品,以便程序员可将其结合到自己的程序中。这些产品用户界面(如果有的话)较差。这类分析程序包括IBMalphaWorks'XML for JavaMicrosoftDataChannelXJParserSilfideSXP

XML for Javahttp://www.alphaworks.ibm.com/ tech/xml

XJParserhttp://www.datachannel.com/xml_resources/

SXPhttp://www.loria.fr/projets/XSilfide/EN/sxp/

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

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

本书写作时,IBMalphaWorks推出了XML for Java2.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 ExplorerWeb浏览器。也可能是一个数据库。甚至可能是自己写成的定制程序。使用XJParse或其他命令行合法性分析程序来验证是否编写了其他程序可以处理的良好的XML。实质上这是一种校对或质量保证阶段而不是最后的输出。

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

如果正在为浏览器编写文档,验证文档的最简易方法是把文档装入浏览器看一看报告出什么错误。但是并不是所有的浏览器都对文档进行合法性检查,某些浏览器仅接受结构完整的文档,而不管其合法性如何。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 TobinXML合法性检查程序报告的清单8-3中的错误

布朗大学的Scholarly Technology Grouphttp://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元素和正常文本的文档

DIVISION>

</LEAGUE>

</SEASON>