XML教程

8 文档类型定义和合法性(2)

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

<!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_NAMESURNAMEPOSITIONGAMESGAMES_STARTED子元素。而且,每名球员可能有或可能没有AT_BATSRUNSHITSDOUBLESTRIPLESHOME_RUNSRBISTEALSCAUGHT_STEALINGSACRIFICE_HITSSACRIFICE_FLIESERRORSWALKSSTRUCK_OUTHIT_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元素必须有CASHCREDIT_CARD中的一个子元素。

<!ELEMENT PAYMENT (CASH | CREDIT_CARD)>

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

<!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?)>

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

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

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

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

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

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子元素后接任意数目的CITYSTATEPOSTAL_CODECOUNTRY元素。如果要每个元素不多于一个,那这就不够理想了。遗憾的是,这超出了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-->

接下来,需要改动文档本身。因为要依赖于另一文档中的DTDXML声明不再是独立的文档。所以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-14baseball.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描述单个文档。