第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),它提供了文档包含的元素、标记、属性和实体及相互关系的清单。

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

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


第9章 实体和外部DTD子集

一个简单的XML文档从许多不同的资源和文件中取得数据和声明。实际上,有些数据直接来自数据库、CGI脚本或其他非文件格式资源。无论采取何种形式,保存XML文档片段的项目称为实体。实体引用把实体载入到XML主文档中。通用实体引用载入数据到XML文档的基本元素中,而参数实体引用载入数据到文档的DTD中。

本章的主要内容如下:

·         什么是实体?

  • 内部通用实体
  • 外部通用实体
  • 内部参数实体
  • 外部参数实体
  • 怎样从局部开始创建文档
  • 结构完整文档中的实体和DTD

9.1 什么是实体?

从逻辑上说,一个XML文档由一个序进程构成,序进程后有一严密地包含了所有其他元素的基本元素。但XML文档的实际数据可以扩展分布在若干文档中。例如,即使一个棒球联盟中包含了大约900个的所有球员,每个PLAYER元素也可以以独立的文件形式存在。包含XML文档细节内容的存储单元称为实体(entities),实体可能是由一个文件、一个数据库记录或其他包含数据的项目组成。例如,本书中所有完整的XML文件都是实体。

包含XML声明或文档类型声明的存储单元和基本元素称为文档实体(document entity)。不过基本元素和它的派生元素也可包含指向即将插入文档的附加数据的实体引用。进行正确性检查的XML处理器在提交文档给最终应用程序以前或显示文件以前,将把所有不同的实体引用结合为单一逻辑结构的文档。

不进行正确性检查的处理器可以但不一定插入外部对象;他们必须插入内部对象。

实体的主要目的在于保存如下内容:结构完整的XML,其他形式的文本或二进制数据。序进程和文档类型声明是它们所属文档的基本元素的一部分。仅当XSL样式单本身就是一个结构完整的XML文档时,才能作为一个实体。组成XSL样式单的实体并不是应用该样式单的XML文档的组成实体之一。CSS样式单根本就不是一个实体。

大多数实体具有一个可以引用的名。唯一的例外是包含XML文档的主文件与文档实体(与数据库记录、CGI程序的输出结果或其他数据相对比,文档实体也不一定是文件)。文档实体无论采取何种结构,都是一种存储单元,用于储存XML声明、文档类型声明(如果有)和基本元素。因此每个XML文档至少拥有一个实体。

有两种类型的实体:内部实体和外部实体。完全在文档实体内部定义的实体称为内部实体。文档本身就是这样的实体,所以所有的XML文档至少有一个内部实体。

相反,经由URL定位的资源中获取的数据称为外部实体。主文档仅包含一个实际引用数据位置的URL。在HTML中,包含于<HTML>和</HTML>标记之间的文档本身是内部实体时,而IMG元素(实际的图像数据)代表外部实体。

实体分为两类:可析和不可析实体。可析实体包含结构完整的XML文本。不可析实体包含二进制数据或非XML文本(类似电子邮件信息)。如果从本质上说,当前大多数XML处理器不能很好地支持(如果不是完全支持的话)不可析实体,本章所关注的是可析实体。

第11章,非XML数据和不可析对象的嵌套。

通过实体引用,可把来源于多个实体的数据合并在一起构成一个单一的文档。通用实体引用把数据并入到文档内容中。参数实体引用把声明并入到文档的DTD中。实中&lt;、&gt;、&apos;、&quote;、&amp;是预定义的体引用,分别指的是文本实体<、>、’、”、&符号。然而也可在文档DTD中定义新的实体。


.2 内部通用实体

内部通用实体引用可看作经常使用的文本或强制格式文本的缩写。DTD中的<!ENTITY>标记定义缩写,并且该缩写就代替了文本。例如,可在DTD中简单地把页脚定义为实体footer,然后每页只需键入&footer;,而勿需在每页底部键入相同的页脚。此外,若决定更改页脚块(也许是因为你的电子邮件地址更改了),就仅需在DTD中作一次更改即可,勿需对共享同一页脚的页面逐个进行更改。

通用实体引用以“&”符号开始,以“;”结尾,两个符号之间为实体名。例如,“&lt;”就是小于符号(<)的通用实体引用,实体名为lt,该实体的替换文本就是一个字符“<”。实体名由字母和数字的混合排列以及下划线构成,禁止使用空格和其他标点符号字符。类似XML中的其他内容,实体引用是区分大小写的。

尽管从技术上说,允许在对象名中使用冒号“:”,但正如第18章中所提及,此符号要保留用于命名域(namespace)。

9.2.1 定义内部通用实体引用

在DTD中使用标记<!ENTITY>标记定义内部通用实体引用,具有如下格式:

<!ENITY name "replacement text">

name是replacement text的缩写。替换文本需放置于双引号中,因为其中可能包含空格和XML标记。可在文档中键入实体名,而读者所见为替换文本。

例如,我的名字为“Elliotte Rusty Harold”(这得怪我父母取了一个长名)。即使经过多年习惯,我依然常常打错。我可以为我的名字定义通用实体引用,这样每次当我键入&ERH;时,读者将会看见“Elliotte Rusty Harold”,这个定义如下:

<!ENITY ERH" Elliotte Rusty Harold">

清单9-1示例说明了&ERH;通用实体引用,图9-1中显示了载入到Internet Explorer的文档。可看出,源代码中的&ERH;实体引用输出时被替换为“Elliotte Rusty Harold”。

图9-1 清单9-1在内部通用实体引用被实际实体替换后的情形

清单9-1:ERH内部通用实体引用

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

<!DOCTYPE DOCUME [

<!ETITY ERH "Elliotte Rusty Harold">

<!ELEMENT DOCUME (TITLE, SIGNATURE)>

<!ELEMENT TITLE (#PCDA A)>

<!ELEMENT COPYRIGHT (#PCDATA)>

<!ELEMENT EMAIL (#PCDA A)>

<!ELEMENT LAST_MODIFIED (#PCDATA)>

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

]>

<DOCUMENT>

<TITLE>&ERH;</TITLE>

<SIGNATURE>

<COPYRIGHT >1999 &ERH;</COPYRIGHT>

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

<LAS _MODIFIED>March 10, 1999</LAS _MODIFIED>

</SIGNATURE>

</DOCUMENT>

注意其中的通用实体引用,即使COPYRIGHT和TITLE声明为仅接受#PCDATA的子类,&ERH;依然出现在它们之中。因&ERH; 实体引用的替换文本是可析的字符数据,所以这种排列是合法的。所有的实体引用被实体值替换后,才对文档进行正确性检查。

在使用样式单时,也会发生相同的情形。当存在样式单引用,在实体引用替换为实体值后,才把样式应用于替换后实际存在的元素树状结构中。

可按下列方式把版权、电子邮件或最后的修改日期声明为通用实体引用:

<!ENTITY COPY99 "Copyright 1999">

<!ENTITY EMAIL "elharo@metalab.unc.edu">

<!ENTITY LM "Last modified: ">

因日期对不同的文档可能会发生改变,故而忽略了&LM;中的日期。若把日期作为一个实体引用,不会带来任何好处。

现在,就可把清单9-1内容重写成更加简洁的形式:

<DOCUMENT>

<TITLE>&ERH;</TITLE>

<SIGNATURE>

<COPYRIGHT>&COPY99; &ERH;</COPYRIGHT>

<EMAIL>&EMAIL;</EMAIL>

<LAS _MODIFIED>&LM; March 10, 1999</LAST_MODIFIED>

</SIGNATURE>

</DOCUMENT>

应用实体引用代替书写文本全文的一个好处是使得更改文本更加简便,在简单的DTD被若干文档共享的情况下,特别有用。例如,假设把电子邮件地址从elharo@metalab.unc.edu更改为eharold@solar.stanford.edu,仅需按如下内容更改DTD中的一行内容,而勿需在多个文档中寻找和替换邮件地址:

<!ENTITY EMAIL "eharold@solar.stanford.edu">

9.2.2 在DTD中使用通用实体引用

读者或许对是否能像下面的代码一样在一个通用实体引用中包含另一个通用实体引用表示怀疑,如下所示:

<!ENTITY COPY99 "copyright 1999 &ERH;">

实际上该例是合法的,因为ERH实体作为COPY99实体的一部分存在,而COPY99实体本身最终又成为文档内容的一部分。尽管存在某些限制,对于DTD中的其他地方,若最终能转换成文档内容的一部分(例如作为缺省属性值),则也可在此处使用通用实体引用。第一条限制:语句中不能使用如下的循环引用:

<!ENTITY ERH "&COPY99 Elliotte Rusty Harold">?

<!ENTITY COPY99 "copyright 1999 &ERH;">?

第二条限制:通用实体引用不能插入仅为DTD的一部分且不能作为文档内容的文本。例如,下述简略用法的企图无法实现:

<!ENTITY PCD "(#PCDATA)">

<!ELEMENT ANTIMAL &PCD;>

<!ELEMENT FOOD &PCD;>

然而,利用实体引用把文本合并到文档的DTD中的方法常常是有用的。为此目的,XML使用将在下章中讲述的参数实体引用来实现这一目的。

对通用实体值的限制仅在于不能直接包含三种字符:% 、&、”,可是能经过使用字符引用包含这三种字符。若&和%仅作为实体引用的开头,而不代表自身含义,则可包含其中。限制很少意味着实体可包含标记和分割为多行。例如下面的SIGNATURE实体是有效的:

"<SIGNATURE>

<COPYRIGHT>1999 Elliotte Rusty Harold</COPYRIGH >

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

<LAST_MODIFIED>March 10, 1999</LAST_MODIFIED>

</SIGNATURE>"

>

下一个关心的问题是实体是否可以拥有参数。能否使用上面的SIGNATURE实体,但却改变每页中每一独立的LAST_MODIFIED元素的数据?答案是否定的;实体仅为静态的替换文本。若需要给实体传送数据,应改为使用标记符,并在样式单中随同提供适当的实现指令。

9.2.3 预定义通用实体引用

XML预定义五个通用实体引用,如表9-1所示。五个实体引用出现在XML文档中用来代替一些特殊的字符,这些字符如果不用引用方式就会被解释为标记。例如实体引用&lt;代表小于号<,小于符号<可解释为标记的开头。

考虑到最大限度的兼容,若计划使用预定义实体引用,就该在DTD中声明这些引用。因为需要避免DTD中字符的递归引用,所以声明时必须相当小心。为方便引用的声明,字符引用使用字符的十六进制ASCII值。清单9-2显示了所需要的声明。

表9-1 XML中的预定义实体引用

实体引用

字符

&amp;

&

&lt;

<

&gt;

>

&quot;

"

&apos;

清单9-2:预定义通用实体引用声明

<!ENTITY lt "<">

<!ENTITY gt ">">

<!ENTITY amp "&">

<!ENTITY apos "'">

<!ENTITY quot """>


9.3 外部通用实体

包含基本元素/文档实体的主文件以外的数据称为外部实体。通过外部实体引用可在文档中嵌入外部实体和从若干相互独立的文件中获取数据组建为XML文档。

仅使用内部实体的文档非常类似于HTML模式。文档的完整文本存放于单一文件中,图像、JAVA小程序、声音和非HTML数据也可链接入文件中,但至少在文件中要有所有的文本。当然,HTML模式存在一些问题。特别在文档中嵌入动态信息的过程是一件非常困难的事情。可通过使用CGI、JAVA小程序所爱好的数据库软件、服务器方面提供的手段和其他各种各样的方法嵌入动态信息,但HTML仅提供静态文档支持。从若干文件中获取数据组建文档的行为必须在HTML外部进行。HTML中解决这问题的最简单的方法是使用框架,但这是非常糟糕的用户界面,通常令用户迷惑和讨厌。

部分问题是HTML文档不能自然地插入到另一个HTML文档中,每个HTML文档有且仅有一个BODY,服务器端嵌入法仅能提供把HTML片段嵌入文档的能力,而不是嵌入有效的文档实体,此外服务器端提供的引用需依赖于服务器的存在,而不是真正的HTML文档部分。

然而,XML更加灵活。一个文档的基本元素没有必要与另一文档基本元素相同。即使两个文档共享同一基本元素,DTD也可声明元素对自身的包含。在适当的时候,XML标准并不制止结构完整的XML文档嵌入另一结构完整的XML文档的做法。

但是,XML走得更远一些,可定义一个机制,利用这机制可在若干本地或远程系统上的、较小的XML文档的基础上建立新的XML文档。语法分析器的任务就是按固定的序列把所有不同文档组合起来。文档中可包含另一文档,或许这个还包含其他文档。只要没有递归的文档包含行为(处理器可报告的错误),应用程序就仅能看见一个单一、完整的文档。从本质上说,这种机制提供客户端嵌入的功能。

对XML而言,可使用外部通用实体引用的方法,在文档中嵌入另一文档。在DTD中,可按下述语法结构声明外部引用:

<!ENTITY name SYSTEM "URI">

URI就是Uniform Resource Identifier,与URL类似,但允许更加精确的资源链接规范。从理论上说,URI把资源与其储存位置分开,所以Web浏览器可以选择最近的或最空闲的几个镜像几个镜像站点,而无需明确指明该链接。URI领域的研究非常活跃,争论激烈,因此在实际应用中和在本书中,URI就是多用途的URL。

例如,可能想在站点的每个页面中都放置相同的签字块。为明确所见,我们假设签字块为清单9-3所示的XML代码,而且假定可从URL http://metalab.unc.edu/xml/signature.xml.上获得该代码。

清单9-3:XML签字文件

<?xml version="1.0">

<SIGNATURE>

<COPYRIGHT>1999 Elliotte Rusty Harold</COPYRIGHT>

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

</SIGNATURE>

在DTD中添加如下声明,可把实体引用&SIG;与这个文件联系在一起:

<!ENTITY SIG SYSTEM "http://metalab.unc.edu/xml/signature.xml">

也可使用相关的URL。例如:

<!ENTITY SIG SYSTEM "xml/signature.xml">

如果被引用的文件放置于与引用该文件的文件所处的同一目录中,那么仅需使用一文件名进行引用。例如:

<!ENTITY SIG SYSTEM "signature.xml">

利用上述任一种声明,仅需使用&SIG;,就可在文档的任意位置引用签字文件的内容。如清单9-4中的简单的文档,图9-2显示的是在Internet Explorer 5.0中交付的文档。

图9-2 使用外部通用实体引用的文档

清单9-4:SIG外部通用实体引用

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

<!DOCTYPE DOCUMENT [

<!ELEMENT DOCUMENT (TITLE, SIGNATURE)>

<!ELEMENT TITLE (#PCDATA)>

<!ELEMENT COPYRIGHT (#PCDATA)>

<!ELEMENT EMAIL (#PCDATA)>

<!ELEMENT SIGNATURE (COPYRIGHT, EMAIL)>

<!ENTITY SIG SYSTEM

"http://metalab.unc.edu/xml/signature.xml"?

]>

</DOCUMENT>

<TITLE>Entity references</TITLE>

&SIG;

</DOCUMENT>

注意外部实体引用的附加作用,因为文件不再完整,所以XML声明中的standalone属性值为no。解析文件表明该文件需要外部文件signature.xml中的数据。


.4 内部参数实体

通用实体成为文档的一部分,而非DTD的组成成分。通用实体引用仅可用于DTD中能成为文档组成部分的位置上;通用实体引用不能插入那些仅为DTD而非文档内容的文本。然而在DTD中的实体引用通常是有用的,因此XML提供了参数实体引用的手段。

除了如下两个关键处不同,参数实体引用与通用实体引用非常相似:

1.参数实体引用以百分号%开始,而非&符号。

2.参数实体引用仅可在DTD中出现,而不能位于文档内容中。

参数实体引用在DTD中声明的方法与通用实体类似,只是在实体名前加一百分号。句法结构如下:

<!ENTITY % name "replacement text">

实体名为实体内容的缩写。读者所见为双引号间的替换文本。例如:

<!ENTITY % ERH "Elliotte Rusty Harold">

<!ENTITY COPY99 "Copyright 1999 %ERH;">

当使用参数实体引用替换通用实体引用后,前文中无法实现的缩写(#PCDATA)变为有效:

<!ENTITY % PCD "(#PCDATA)">

<!ELEMENT ANTIMAL %PCD;>

<!ELEMENT FOOD %PCD;>

在元素间的子元素和属性共享通用列表中呈现参数实体引用的真实值。若替换的文本块较大且使用次数较多,则参数实体引用用处更大。例如,假设在DTD声明中有许多层次结构元素,如PARAGRAPH、CELL和HEADING。每个该类元素都有确定的内联元素数目,类似PERSON、DEGREE、MODEL、PRODUCT、ANIMAL、INGREDIENT等等的内部元素。这些元素的声明可能表现为下述方式:

<!ELEMENT PARAGRAPH

(PERSON | DEGREE | MODEL | PRODUCT | ANTIMAL | INGREDIENT)*>

<!ELEMENT CELL

(PERSON | DEGREE | MODEL | PRODUCT | ANTIMAL | INGREDIENT)*>

<!ELEMENT HEADING

(PERSON | DEGREE | MODEL | PRODUCT | ANTIMAL | INGREDIENT)*>

容器元素都有相同的内容。假如创建了一个新元素如EQUATION、CD或ACCOUNT,则该元素必须声明为所有三个元素的子元素。若在前两个元素中添加新元素,却忘了在第三个元素中添加,就会引发问题。若元素的数目为30或者300个,而非3个,则问题将成倍增加。

若对每个容器元素不是给出单独的子元素列表,则DTD的维护将较为简便。替代的方法是子元素列表变为参数实体引用,然后在每个声明元素声明中应用参数实体引用。例如:

<!ENTITY % inlines

"(PERSON | DEGREE | MODEL | PRODUCT | ANTIMAL | INGREDIENT)*">?

<!ELEMENT PARAGRAPH %inlines;>

<!ELEMENT CELL %inlines;>

<!ELEMENT HEADING %inlines;>

当添加新元素时,仅需简单地改变一个参数实体声明,而勿需改变3个、30或300个元素声明。

参数实体引用必须在使用前声明。下例是非法的,因为%PCD;引用在使用两次后才加以声明:

<!ELEMENT FOOD %PCD;>

<!ELEMENT ANTIMAL %PCD;>

<!ENTITY % PCD "(#PCDATA)">

参数实体引用仅能用于提供外部DTD子集中的声明。也就是说,参数实体引用仅能出现在外部DTD子集的声明中,上述例子若用于内部DTD子集,则所有引用无效。

在内部DTD子集中,参数实体引用仅能用于声明之外。例如下述语句在内部和外部DTD子集中均有效。

<!ENTITY % hr "<!ELEMENT HR EMPTY>">

%hr;

当然,这与将HR元素声明为不带参数实体引用相比没有带来使用上的便利:

<!ELEMENT HR EMPTY>

参数实体引用主要用于内部DTD子集引用外部参数实体的情况;也就是引入不同文件中的声明或部分声明。下一节将讲述这部分内容。


9.5 外部参数实体

前述例子中使用单一的DTD,用于定义文档中所有的元素。然而文档越长,这种技术应用越少。此外通常希望将DTD中的部分内容用于许多不同的地方。

例如,对描述很少发生变化的邮件地址DTD来说,地址定义非常普遍,且可很方便地应用在不同的上下文中。类似地,清单9-2中列出的预定义实体引用可用于大部分XML文档中,但并不想总是对此清单进行拷贝和复制的操作。

可用外部参数实体把较小的DTD组成大型的DTD。也就是说,一个外部DTD可以链接到另一外部DTD,第二个DTD引入第一个DTD中声明的元素和实体。尽管严禁使用循环——若DTD2引用DTD1,则DTD1不能引用DTD2��但嵌套的DTD也会大型化和复杂化。

同时,将DTD分解为小的、更便于管理的组块,使得对DTD的分析处理更加简便。由于一个实体文档和完整的DTD存储在单一的文件中,在前几章中的许多例子都过于庞大。若文档和文档的DTD分割为几个独立的文件,就变得更加易于理解。

此外,描述一组元素的DTD中采用较小的、模块化的结构,使得不同的人或组织创建的DTD之间的组合和匹配更加简便。例如,在写一篇关于高温超导的文章,可能会用到描述其中分子的分子科学DTD、记录公式的数学DTD、描述图形的向量DTD和处理解释性文本的HTML DTD。

特殊情况下,可使用Peter Murray-Rust的 Chemical Markup Language中的mol.dtd DTD、W3C的Mathematical Markup Language 中的MathML DTD、W3C的 Scalable Vector Graphics中的SVG DTD和W3C的 XHTML DTD。

我们还可以想出许多混合或者匹配来自不同领域的概念(也就是标记)的例子。人类的想法不会局限在狭窄的定义范围内,总是试图遍及所有领域。所编写的文档就反映了这种思想。

让我们研究如何把棒球比赛统计表组织为几个不同的DTD的联合体。本例的层次非常分明。一个可能的分割方法是为PLAYER、TEAM和SEASON分别编写一个DTD。分割DTD为更便于管理的方法远不止一种,但这也不失为一个很好的例子。清单9-5显示的是只为PALYER建立的单独的DTD,保存在player.dtd文件中。

清单9-5:PLAYER元素和它的子元素的DTD(player.dtd)

<!-Player Info ->

<!ELEMENT PLAYER (GIVEN _N AME, SURNAME, P, G,

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

SH?, SF?, E?, BB?, S?, HBP?, W?, L?, SV?, CG?, SO?, ERA?,

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

>

<!-Player s last name ->

<!ELEMENT SURNAME (#PCDATA)>

<!-Player s first name ->

<!ELEMENT GIVE _ NAME (#PCDATA)>

<!-Position ->

<!ELEMENT P (#PCDATA)>

<!-Games Played ->

<!ELEMENT G (#PCDATA)> <!桮ames 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 Statistics ->

<!-Complete Games ->

<!ELEMENT CG (#PCDATA)>

<!-Wins ->

<!ELEMENT W (#PCDATA)>

<!-Losses ->

<!ELEMENT L (#PCDATA)>

<!-Saves ->

<!ELEMENT SV (#PCDATA)>

<!-Shutouts ->

<!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还无法让你创建非常有趣的文档,清单9-6显示的是仅使用清单9-5中PLAYER DTD的简洁有效的文件。从这来说,这简单的文件并不重要;然而,可在这些较小的部分外创建更加复杂的文件。

清单9-6:使用PLAYER DTD的有效文档

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

<!DOCTYPE PLAYER SYSTEM "Player.dtd">

<PLAYER>

<GIVEN_NAME>Chris</GIVEN_NAME>

<SURNAME>Hoiles</SURNAME>

<P>Catcher</P>

<G>97</G>

<GS>81</GS>

<AB>267</AB>

<R>36</R>

<H>70</H>

<D>12</D>

<T>0</T>

<HR>15</HR>

<RBI>56</RBI>

<SB>0</SB>

<CS>1</CS>

<SH>5</SH>

<SF>4</SF>

<E>3</E>

<BB>38</BB>

<S>50</S>

<HBP>4</HBP>

</PLAYER>

文档的哪部分可拥有自己的DTD?这是显而易见的,TEAM就是其中的主要部分,可按如下方式书写它的DTD:

<!ELEMENT TEAM ( EAM_CITY, EAM_NAME, PLAYER*)>

<!ELEMENT TEAM_CITY (#PCDATA)>

<!ELEMENT TEAM_NAME (#PCDATA)>

然而作仔细的检查之后,就会注意到遗漏了某些东西:PLAYER元素的定义。该定义位于player.dtd独立文件中,需要连接到这个DTD中。

可通过外部参数实体引用连接DTD。对私有的DTD,可按下列格式进行连接:

<!ENTITY % name SYSTEM "URI">

%name;

例如:

<! ENTITY % player SYSTEM "Player.dtd">

%player;

本例中使用了相对的URL(player.dtd),且假定player.dtd文件所在位置与进行链接的DTD的位置相同。若非这种情况,可使用完整的URL如下:

<! ENTITY % player SYSTEM

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

%player;

清单9-7显示的是包含了对PLAYER DTD引用的完整TEAM DTD:

清单9-7:TEAM DTD(team.dtd)

<!ELEMENT EAM ( EAM_CITY, EAM_ NAME, PLAYER*)>

<!ELEMENT EAM_CITY (#PCDATA)>

<!ELEMENT EAM_ NAME (#PCDATA)>

<!ENTITY % player SYSTEM "Player.dtd">

%player;

SEASON包含LEAGUE、DIVISION和TEAM元素。尽管LEAGUE和DIVISION元素可拥有自己的DTD,也没有必要过分追求使用各自独立的DTD。除非希望拥有包含LEAGUE或DIVISION元素的文档,该文档不是SEASON的一部分,在这种情况下,才可在同一DTD中引用所有三个DTD。如清单9-8中说明了这种情况。

清单9-8:SEASON DTD(seasom.dtd)

<!ELEMENT YEAR (#PCDATA)>

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

<!-NAMErican or National ->

<!ELEMENT LEAGUE_ NAME (#PCDATA)>

<!-East, West, or Central ->

<!ELEMENT DIVISION_ NAME (#PCDATA)>

<!ELEMENT DIVISION (DIVISIO _ NAME, EAM+)>

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

<!ENTITY % team SYSTEM "team.dtd">

%team;


.6 根据片段创建文档

棒球的例子已相当庞大,尽管本书中的例子仅为缩减的版本,其中球员数目受到限制,但全文已超过0.5MB,内容过于庞大,不便于装载和查询;特别是在读者仅对其中某一队员、球队或分部感兴趣时,尤其如此。本章中上一节讲述的技术可允许把这个文档分割为许多不同的、较小的、便于管理的文档,每个球队、队员、分部和联盟各自对应一个文档。通过外部实体引用,队员组成球队,球队组成分部,分部构成联盟,联盟构成赛季。

遗憾的是,无法按外部可析实体的样式嵌入XML文档。考虑一下,例如清单9-9 ChrisHoiles.xml,这是清单9-6的修订版本。然而,若仔细检查两个清单,将发现它们的序进程是不同的。9-6清单的序进程为:

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

<!DOCTYPE PLAYER SYSTEM "Player.dtd">

清单9-9的序进程是简单的XML声明,没有standalone属性,但却有encoding属性;而且忽略了文档类型声明。像清单9-9这样的文件表明将被嵌入另一文件中,其中的XML声明称为文本声明,虽然正如我们所看到的,它实际上正是一个合法的XML声明。

清单9-9:ChrisHoiles.xml

<?xml version="1.0"encoding="UTF-8"?>

<PLAYER>

<GIVEN_ NAME>Chris</GIVEN_NAME>

<SURNAME>Hoiles</SURNAME>

<P>Catcher</P>

<G>97</G>

<GS>81</GS>

<AB>267</AB>

<R>36</R>

<H>70</H>

<D>12</D>

<T>0</T>

<HR>15</HR>

<RBI>56</RBI>

<SB>0</SB>

<CS>1</CS>

<SH>5</SH>

<SF>4</SF>

<E>3</E>

<BB>38</BB>

<S>50</S>

<HBP>4</HBP>

</PLAYER>

虽然可在本书附带的CD-ROM上的example"baseball"player目录中找到所有队员名单,但这里省略了大约1200名的队员名单。

文档声明必须具有encoding属性(与XML声明不同,XML声明可以拥有encoding属性,但不是必要的),encoding属性规定实体使用的字符集。允许使用不同字符组写出的复合文档。例如,Latin-5字符组写出的文档可与UTF-8字符集写出的文档结合为一体。处理器或浏览器依然必须理解不同实体使用的编码。

本章中的所有例子以ASCII编码形式给出。因ASCII编码是ISO Latin-1 、UTF-8的严格子集,所以可以使用如下的任一文本声明:

<?xml version="1.0" encoding="ISO-8859-1"?>

<?xml version="1.0" encoding="UTF-8"?>

清单9-10 mets.dtd和清单9-11 mets.xml显示如何利用外部可析实体组建完整的球队文档。在DTD中为球队中的每个队员定义外部实体引用。利用文档内部DTD子集中的外部参数实体引用,XML文档载入该DTD;然后,该文档包括许多外部通用实体引用来载入分立的队员数据。

清单9-10:具有player实体引用的New York Mets DTD(mets.dtd)

<!ENTITY AlLeiter SYSTEM "mets/AlLeiter.xml">

<!ENTITY ArmandoReynoso SYSTEM "mets/ArmandoReynoso.xml">

<!ENTITY BobbyJones SYSTEM "mets/BobbyJones.xml">

<!ENTITY BradClontz SYSTEM "mets/BradClontz.xml">

<!ENTITY DennisCook SYSTEM "mets/DennisCook.xml">

<!ENTITY GregMcmichael SYSTEM "mets/GregMcmichael.xml">

<!ENTITY HideoNomo SYSTEM "mets/HideoNomo.xml">

<!ENTITY JohnFranco SYSTEM "mets/JohnFranco.xml">

<!ENTITY JosiasManzanillo SYSTEM "mets/JosiasManzanillo.xml">

<!ENTITY OctavioDotel SYSTEM "mets/OctavioDotel.xml">

<!ENTITY RickReed SYSTEM "mets/RickReed.xml">

<!ENTITY RigoBeltran SYSTEM "mets/RigoBeltran.xml">

<!ENTITY WillieBlair SYSTEM "mets/WillieBlair.xml">

图9-3显示了载入到Internet Explorer中的XML文档。请注意即使主文档仅包含存储队员数据的实体引用,所有队员数据也能被显示出来。Internet Explorer解决了所有外部引用,这可不是所有的XML语法分析程序或者浏览器都能做到的。

在CD-ROM上的example"baseball目录中可找到其余球队。请特别需要注意,简洁的外部实体引用是如何嵌入多个队员数据的。

图9-3 XML文档显示1998年New York Mets队中的所有球员。

清单9-11:具有从外部实体中载入的队员数据的New York Mets(mets.xml)

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

<!DOCTYPE TEAM SYSTEM "team.dtd"[

<!ENTITY % players SYSTEM "mets.dtd">

%players;

]>

<TEAM>

< TEAM_CITY>New York</ TEAM_CITY>

< TEAM_ NAME>Mets</ TEAM_ NAME>

&AlLeiter;

&ArmandoReynoso;

&BobbyJones;

&BradClontz;

&DennisCook;

&GregMcmichael;

&HideoNomo;

&JohnFranco;

&JosiasManzanillo;

&OctavioDotel;

&RickReed;

&RigoBeltran;

&WillieBlair;

</TEAM>

通过组合球队文件创建分部、通过组合分部文件创建联盟、通过组合联盟文件创建赛季的过程的延续,具有一定的好处。但遗憾的是,所有努力只会带来灾难性的后果。通过外部实体的方法嵌套的文档不能拥有自身的DTD。最多只能是序进程包含文本声明。这就是说,仅能拥有单一层次的文本嵌入。与此不同的是,DTD嵌入可进行任意层次的嵌套。

因此唯一可用的方法就是,在引用了许多不同球员文档的单一文档中包括所有球队、分部、联盟和赛季。需要1200多个实体声明(每个队员对应一个声明)。因为DTD可以深层嵌套,就引入如清单9-10所示包含所有球队定义的DTD开始。如清单9-12所示。

清单9-12:球员的DTD(players.dtd)

<!ENTITY % angels SYSTEM "angels.dtd">

%angels;

<!ENTITY % astros SYSTEM "astros.dtd">

%astros;

<!ENTITY % athletics SYSTEM "athletics.dtd">

%athletics;

<!ENTITY % bluejays SYSTEM "bluejays.dtd">

%bluejays;

<!ENTITY % braves SYSTEM "braves.dtd">

%braves;

<!ENTITY % brewers SYSTEM "brewers.dtd">

%brewers;

<!ENTITY % cubs SYSTEM "cubs.dtd">

%cubs;

<!ENTITY % devilrays SYSTEM "devilrays.dtd">

%devilrays;

<!ENTITY % diamondbacks SYSTEM "diamondbacks.dtd">

%diamondbacks;

<!ENTITY % dodgers SYSTEM "dodgers.dtd">

%dodgers;

<!ENTITY % expos SYSTEM "expos.dtd">

%expos;

<!ENTITY % giants SYSTEM "giants.dtd">

%giants;

<!ENTITY % indians SYSTEM "indians.dtd">

%indians;

<!ENTITY % mariners SYSTEM "mariners.dtd">

%mariners;

<!ENTITY % marlins SYSTEM "marlins.dtd">

%marlins;

<!ENTITY % mets SYSTEM "mets.dtd">

%mets;

<!ENTITY % orioles SYSTEM "orioles.dtd">

%orioles;

<!ENTITY % padres SYSTEM "padres.dtd">

%padres;

<!ENTITY % phillies SYSTEM "phillies.dtd">

%phillies;

<!ENTITY % pirates SYSTEM "pirates.dtd">

%pirates;

<!ENTITY % rangers SYSTEM "rangers.dtd">

%rangers;

<!ENTITY % redsox SYSTEM "redsox.dtd">

%redsox;

<!ENTITY % reds SYSTEM "reds.dtd">

%reds;

<!ENTITY % rockies SYSTEM "rockies.dtd">

%rockies;

<!ENTITY % royals SYSTEM "royals.dtd">

%royals;

<!ENTITY % tigers SYSTEM "tigers.dtd">

%tigers;

<!ENTITY % twins SYSTEM "twins.dtd">

%twins;

<!ENTITY % whitesox SYSTEM "whitesox.dtd">

%whitesox;

<!ENTITY % yankees SYSTEM "yankees.dtd">

%yankees;

清单9-13为主控文档,把所有队员的子文档和定义每个队员的DTD组合为一体。尽管该文档比以前产生的单一文档小(32k 与 628k之比),但仍然太长,所以无法在此引入所有队员。清单9-13的完整版本要依靠33个DTD和1000多个XML文件来生成最终文档。这种方法最大的问题在于,在显示文档之前,需要1000多个与Web服务器的链接。

完整的例子位于光盘上的example/baseball/players/index.xml文件中。

清单9-13:利用球员的外部实体引用的1998年赛季的主控牡?/p>

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

<!DOCTYPE SEASO SYSTEM "baseball.dtd"[

<!ENTITY % players SYSTEM "players.dtd">

%players;

]>

<SEASO >

<YEAR>1998</YEAR>

<LEAGUE>

<LEAGUE_NAME> ational</LEAGUE_NAME>

<DIVISION >

<DIVISION _NAME>East</DIVISION _NAME>

<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>

&RigoBeltran;

&DennisCook;

&SteveDecker;

&JohnFranco;

&MattFranco;

&ButchHuskey;

&BobbyJones;

&MikeKinkade;

&HideoNomo;

&VanceWilson;

</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>

&JeffAbbott;

&MikeCameron;

&MikeCaruso;

&LarryCasian;

&TomFordham;

&MarkJohnson;

&RobertMachado;

&JimParque;

&ToddRizzo;

</TEAM>

</DIVISION >

<DIVISION >

<DIVISION _NAME>West</DIVISION _NAME>

<TEAM>

<TEAM_CITY>Anaheim</TEAM_CITY>

<TEAM_NAME>Angels</TEAM_NAME>

</TEAM>

</DIVISION >

</LEAGUE>

</SEASON>

在选择主文档和嵌套数据的层次结构上具有一定的灵活性。例如,一种可选择的结构就是在清单9-12中使用的,把球队和所有队员的数据放在不同的文件中;然后把球队数据组合为带外部实体的赛季文件,如清单9-14所示。使用尺寸更小、数目更少的XML文件的好处在于Web服务器中所占的空间更小以及下传和显示更加快捷。可是老实地说,一种方法或其他方法所带来的内在的益处很小。请放心大胆使用任意更严密地与数据组织相匹配,或者任一感觉使用方便的简洁方式。

清单9-14:利用对球员的外部实体引用球队的1998年赛季的主控文档

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

<!DOCTYPE SEASO SYSTEM "baseball.dtd"[

<!ENTITY angels SYSTEM "angels.xml">

<!ENTITY astros SYSTEM "astros.xml">

<!ENTITY athletics SYSTEM "athletics.xml">

<!ENTITY bluejays SYSTEM "bluejays.xml">

<!ENTITY braves SYSTEM "braves.xml">

<!ENTITY brewers SYSTEM "brewers.xml">

<!ENTITY cubs SYSTEM "cubs.xml">

<!ENTITY devilrays SYSTEM "devilrays.xml">

<!ENTITY diamondbacks SYSTEM "diamondbacks.xml">

<!ENTITY dodgers SYSTEM "dodgers.xml">

<!ENTITY expos SYSTEM "expos.xml">

<!ENTITY giants SYSTEM "giants.xml">

<!ENTITY indians SYSTEM "indians.xml">

<!ENTITY mariners SYSTEM "mariners.xml">

<!ENTITY marlins SYSTEM "marlins.xml">

<!ENTITY mets SYSTEM "mets.xml">

<!ENTITY orioles SYSTEM "orioles.xml">

<!ENTITY padres SYSTEM "padres.xml">

<!ENTITY phillies SYSTEM "phillies.xml">

<!ENTITY pirates SYSTEM "pirates.xml">

<!ENTITY rangers SYSTEM "rangers.xml">

<!ENTITY redsox SYSTEM "red sox.xml">

<!ENTITY reds SYSTEM "reds.xml">

<!ENTITY rockies SYSTEM "rockies.xml">

<!ENTITY royals SYSTEM "royals.xml">

<!ENTITY tigers SYSTEM "tigers.xml">

<!ENTITY twins SYSTEM "twins.xml">

<!ENTITY whitesox SYSTEM "whitesox.xml">

<!ENTITY yankees SYSTEM "yankees.xml">

]>

<SEASON >

<YEAR>1998</YEAR>

<LEAGUE>

<LEAGUE_NAME> ational</LEAGUE_NAME>

<DIVISION >

<DIVISION _NAME>East</DIVISION _NAME>

&marlins;

&braves;

&expos;

&mets;

&phillies;

</DIVISION >

<DIVISION >

<DIVISION _NAME>Central</DIVISION _NAME>

&cubs;

&reds;

&astros;

&brewers;

&pirates;

</DIVISION >

<DIVISION >

<DIVISION _NAME>West</DIVISION _NAME>

&diamondbacks;

&rockies;

&dodgers;

&padres;

&giants;

</DIVISION >

</LEAGUE>

<LEAGUE>

<LEAGUE_NAME>American</LEAGUE_NAME>

<DIVISION >

<DIVISION _NAME>East</DIVISION _NAME>

&orioles;

&redsox;

&yankees;

&devilrays;

&bluejays

</DIVISION >

<DIVISION >

<DIVISION _NAME>Central</DIVISION _NAME>

&whitesox;

&indians;

&tigers;

&royals;

&twins;

</DIVISION >

<DIVISION >

<DIVISION _NAME>West</DIVISION _NAME>

&angels;

&athletics;

&mariners;

&rangers;

</DIVISION >

</LEAGUE>

</SEASON >

最后,较少使用的方法是,从外部球员实体的基础上创建各分立的球队文件,然后组合所有球队文件为分部、联盟和赛季。主控文档中可定义用于子球队文档中的实体引用。可是在这种情况下,因为实体引用集合在主控文档以前未被定义,所以球队文档不可用于自身。

真正的缺点是仅有顶层文档可附加于DTD之上。这是对外部可析实体用途的一种限制。无论如何,当学习了XLinks和XPointers后,可以明白创建大型、复杂文档的其他方法。然而,那些技术不是XML标准的核心部分内容,进行正确性检查的XML处理器和Web浏览器并无必要像支持本章讲述的技术一样去支持这些技术。

Xlinks将在第16章讲述,XPointers将在第17章讲述。


9.7 结构完整的文档中的实体和DTD

本书第一部分研究了无DTD的结构完整的XML文档,第二部分研究包含DTD和包含DTD中的约束条件的文档,也就是正确的文档。但是还有与XML标准相符合的第三个层次:由于DTD不完整或文档不符合DTD中的约束条件,所以该包含DTD的文档结构完整但不合法;这是三种类型中最不普遍的情况。

可是,没有必要要求所有的文档都是合法的。有时XML文档仅需结构完整就足够了。DTD在结构完整文档中也占有一席之地(虽然不是必需的,但是对合法的文档来说确实是必需的),并且不进行合法性检查的XML处理器可以在DTD中获取有用的信息,而不必完全符合DTD的要求。在本节中将研究该项内容。

若结构完整但无效的XML文档中包含DTD,则该DTD需具有上一章所研究的相同的通用形式。那就是说,开头为文档类型声明,且可包含ELEMENT、ATTLIST和ENITITY声明。与有效文档的区别在于处理器仅处理其ENTITY声明。

9.7.1 内部实体

在结构完整的无效文档中使用DTD的主要益处在于还可以使用内部通用实体引用,除了五个预定义引用&gt;、&lt;、&quot;、&apos;和&amp;之外。可按通常的方法简单地声明所需的实体,然后在文档中使用它们。

例如,回顾前面的例子,假如需要实体引用&ERH;用于替换字符串“Elliotte Rusty Harlod”(好吧,那就假设我需要实体引用&ERH;用于替换字符串“Elliotte Rusty Harlod”),但不想为文档编写一个完整的DTD。可按清单9-15所示,在DTD中简单地声明ERH实体引用。该文档仅仅是结构完整,但却是不合法的文档;若不追求合法性,该文档完全可以接受。

清单9-15:DTD中的ERH实体引用产生了结构完整但不合法的文档

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

<!DOCTYPE DOCUMENT [

<!ENTITY ERH "Elliotte Rusty Harold?">

]>

<DOCUMENT >

<TITLE>&ERH;</TI LE>

<SIGNATURE>

<COPYRIGHT >1999 &ERH;</COPYRIGHT>

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

<LAST_MODIFIED>March 10, 1999</LAST_MODIFIED>

</SIGNATURE>

</DOCUMENT>

清单9-15中的文档类型声明是少见的。除了在定义ERH实体引用的之外,只是简单地说明了基本元素为DOCUMENT。可是文档的结构完整性并不要求文档满足这一小小的约束。例如清单9-16,显示的是另一个使用了PAGE基本元素的文档,但文档类型声明中却说明该基本元素应该是DOCUMENT。该文档结构依然完整,但是与清单9-15的文档一样都是不合法的。

清单9-16:结构完整,但不合法的文档

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

<!DOCTYPE DOCUMENT [

<!ENTITY ERH "Elliotte Rusty Harold?">

]>

<PAGE>

<TITLE>&ERH;</TI LE>

<SIGNATURE>

<COPYRIGHT >1999 &ERH;</COPYRIGHT >

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

<LAST_MODIFIED>March 10, 1999</LAST_MODIFIED>

</SIGNATURE>

</PAGE>

这个DTD同样也可包含其他的<!ELEMENT>、<!ATTLIST>和<!NOTATION>声明,所有这些声明均被不进行合法性检查的处理器忽略,仅处理<!ENTITY>声明。清单9-17中的DTD与其本身的内容相矛盾。例如,根据DTD定义, ADDRESS元素本应为空,但实际上该元素包含几个未声明的子元素。另外,要求每个ADDRESS元素都具有OCCUPANT、STREET、CITY和ZIP属性值,但是却无处可寻。基本元素本应为DOCUMENT,而不是ADDRESS。DOCUMENT元素本应包含的TITLE和SIGNATURE均未在DTD中进行声明。本文档结构依然完整,却无半点合法性。

清单9-17:结构完整却无效的文档

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

<!DOCTYPE DOCUMENT [

<!ENTITY ERH "Elliotte Rusty Harold?">

<!ELEMENT ADDRESS EMPTY>

<!ELEMENT DOCUMENT ( TITLE, ADDRESS+, SIGNATURE)>

<!ATLIST ADDRESS OCCUPANT CDATA #REQUIRED>

<!ATLIST ADDRESS DEPAR ME CDATA #IMPLIED>

<!ATLIST ADDRESS COMPANY CDATA #IMPLIED>

<!ATLIST ADDRESS S REET CDATA #REQUIRED>

<!ATLIST ADDRESS CITY CDATA #REQUIRED>

<!ATLIST ADDRESS ZIP CDATA #REQUIRED>

]>

<ADDRESS>

<OCCUPANT>Elliotte Rusty Harold</OCCUPANT >

<DEPARTMENT >Computer Science</DEPARTMENT >

<COMPANY>Polytechnic University</COMPAN Y>

<STREE >5 Metrotech Center</STREE >

<CITY>Brooklyn</CITY>

<STATE>NY</STATE>

<ZIP>11201</ZIP>

</ADDRESS>

9.7.2 外部实体

不进行合法性检查的处理器可处理外部实体引用,但这不是必须的。详细的说,例如Mozilla使用的开放资源XML语法分析器并不处理外部实体引用。包含IE 5.0在内的其余大部分处理器却要处理外部实体引用。可是不进行合法性检查的处理器可能仅处理可析实体,不处理包含非XML数据(像图像或声音)的外部实体引用。

外部实体对存储样式文本特别有用。例如,HTML预定义非ASCII ISO Latin-1字母的实体引用,这些引用比数字化字符实体引用稍便于记忆。例如,å预定义为&ring;,þ预定义为&thorn;,ý预定义为&Yacute;等等。清单9-18为定义这些引用的正式ISO DTD(对注释进行一些轻微的修改,文中巧妙地应用空格,使得文档看起来形式优美整洁)。

清单9-18:非ASCII ISO Latin-1字符的DTD

<!-(C) International Organization for Standardization 1986

Permission to copy in any form is granted for use with

conforming SGML systems and applications as defined in

ISO 8879, provided this notice is included in all copies.

->

<!-Character entity set. Typical invocation:

<!ENTITY % ISOlat1 PUBLIC

"ISO 8879-1986//E I IES Added Latin 1//E //XML->

%ISOlat1;

->

<!- his version of the entity set can be used with any SGML

document which uses ISO 8859-1 or ISO 10646 as its

document character set. This includes XML documents and

ISO HTML documents.

Version: 1998-10-01

->

<!ENTITY Agrave "À "><!-capital A, grave accent ->

<!ENTITY Aacute "Á "><!-capital A, acute accent ->

<!ENTITY Acirc "Â "><!-capital A, circumflex accent ->

<!ENTITY Atilde "Ã "><!-capital A, tilde ->

<!ENTITY Auml "Ä "><!-capital A, dieresis umlaut ->

<!ENTITY Aring "Å "><!-capital A, ring ->

<!ENTITY AElig "Æ "><!-capital AE diphthong ligature->

<!ENTITY Ccedil "Ç "><!-capital C, cedilla ->

<!ENTITY Egrave "È "><!-capital E, grave accent ->

<!ENTITY Eacute "É "><!-capital E, acute accent ->

<!ENTITY Ecirc "Ê "><!-capital E, circumflex accent ->

<!ENTITY Euml "Ë "><!-capital E, dieresis umlaut ->

<!ENTITY Igrave "Ì "><!-capital I, grave accent ->

<!ENTITY Iacute "Í "><!-capital I, acute accent ->

<!ENTITY Icirc "Î"><!-capital I, circumflex accent ->

<!ENTITY Iuml "Ï"><!-capital I, dieresis umlaut ->

<!ENTITY ETH "Ð"><!-capital Eth, Icelandic ->

<!ENTITY Ntilde "Ñ"><!-capital N, tilde ->

<!ENTITY Ograve "Ò"><!-capital O, grave accent ->

<!ENTITY Oacute "Ó"><!-capital O, acute accent ->

<!ENTITY Ocirc "Ô"><!-capital O, circumflex accent ->

<!ENTITY Otilde "Õ"><!-capital O, tilde ->

<!ENTITY Ouml "Ö-><!-apital O dieresis/umlaut mark->

<!ENTITY Oslash "Ø"><!-capital O, slash ->

<!ENTITY Ugrave "Ù"><!-capital U, grave accent ->

<!ENTITY Uacute "Ú"><!-capital U, acute accent ->

<!ENTITY Ucirc "Û"><!-capital U circumflex accent ->

<!ENTITY Uuml "Ü"><!-capital U dieresis umlaut ->

<!ENTITY Yacute "Ý"><!-capital Y, acute accent ->

<!ENTITY THORN "Þ"><!-capital THORN, Icelandic ->

<!ENTITY szlig "ß"><!-small sharp s, (sz ligature) ->

<!ENTITY agrave "à"><!-small a, grave accent ->

<!ENTITY aacute "á"><!-small a, acute accent ->

<!ENTITY acirc "â"><!-small a, circumflex accent ->

<!ENTITY atilde "ã"><!-small a, tilde ->

<!ENTITY auml "ä"><!-small a dieresis/umlaut mark->

<!ENTITY aring "å"><!-small a, ring ->

<!ENTITY aelig "æ"><!-small ae, diphthong ligature ->

<!ENTITY ccedil "ç"><!-small c, cedilla ->

<!ENTITY egrave "è"><!-small e, grave accent ->

<!ENTITY eacute "é"><!-small e, acute accent ->

<!ENTITY ecirc "ê"><!-small e, circumflex accent ->

<!ENTITY euml "ë"><!-small e, dieresis or umlaut ->

<!ENTITY igrave "ì"><!-small i, grave accent ->

<!ENTITY iacute "í"><!-small i, acute accent ->

<!ENTITY icirc "î"><!-small i, circumflex accent ->

<!ENTITY iuml "ï"><!-small i, dieresis or umlaut ->

<!ENTITY eth "ð"><!-small eth, Icelandic ->

<!ENTITY ntilde "ñ"><!-small n, tilde ->

<!ENTITY ograve "ò"><!-small o, grave accent ->

<!ENTITY oacute "ó"><!-small o, acute accent ->

<!ENTITY ocirc "ô"><!-small o, circumflex accent ->

<!ENTITY otilde "õ"><!-small o, tilde ->

<!ENTITY ouml "ö"><!-small o, dieresis or umlaut->

<!ENTITY oslash "ø"><!-small o, slash ->

<!ENTITY ugrave "ù"><!-small u, grave accent ->

<!ENTITY uacute "ú"><!-small u, acute accent ->

<!ENTITY ucirc "û"><!-small u, circumflex accent ->

<!ENTITY uuml "ü"><!-small u, dieresis or umlaut ->

<!ENTITY yacute "ý"><!-small y, acute accent ->

<!ENTITY thorn "þ"><!-small thorn, Icelandic ->

<!ENTITY yuml "ÿ"><!-small y, dieresis or umlaut ->

可简单地应用参数实体引用链接到清单9-18所示的实体引用,然后在文档中使用通用实体引用,而不需要把清单9-18包含在文档DTD的内部子集中。

例如,假设需要以结构完整的XML文档将中世纪的Hlidebrandslied文档放入Web上,可是原稿为德语书写的,文中使用了非ASCII字符æ、ê、î、ô和û。

为使文档具有最大的可移植性,可按ASCII字符键入诗文,而把这些字母分别编码为&ecirc;、&icirc;、&ocirc;、&ucirc;和&aelig;的实体引用。即使不需要有效完整的文档,也依然需要一个DTD,该DTD声明使用的各种实体引用。获取所需扩展字符的最简单方法就是简单地引用清单9-18中的外部DTD。清单9-19说明了这种情况。

清单9-19:为使用ASCII ISO Latin-1字母而使用实体引用的无效完整文档

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

<!DOCTYPE DOCUMENT [

<!ENTITY % ISOlat1

PUBLIC "ISO 8879-1986//E I IES Added Latin 1//E //XML"

"http://www.schema.net/public-text/ISOlat1.pen">

%ISOlat1;

]>

<DOCUMENT>

<TITLE>Das Hildebrandslied, circa 775 C.E. </TITLE>

<LINE>Ik gih&ocirc;rta dhat seggen,</LINE>

<LINE>dhat sih urh&ecirc;ttun &aelig;non muot&icirc;n,</LINE>

<LINE>Hiltibrant enti Hadhubrant untar heriun tu&ecirc;m.

</LINE>

<LINE>sunufatarungo: iro saro rihtun,</LINE>

<COMMENT>I ll spare you the next 61 lines</COMMENT>

</DOCUMENT>

文档部分是由使用现场编写的标记的结构完整的XML所组成。这些标记未在DTD中声明过,也没有必要去维护文档的结构完整性。可是实体引用需要在内部或外部子集的DTD中声明。在清单9-19中,通过外部参数实体引用%ISO1atl载入清单9-18中声明的实体,也就在外部子集中声明了实体引用。

DTD也可用于储存通用样式文本,该文本用在整个Web站点上的结构完整的XML文档,有利于维护XML文档的合法性。当仅仅处理结构完整的XML文档时,可体现出一定的简便性,这是因为插入到文档中的样式文本与父文档DTD的约束条件上不会出现任何匹配问题。

首先,把不带DTD的样式插入到一个文件中,如清单9-20所示。

清单9-20:不带DTD的Signature样板

<?xml version="1.0"?>

<SIGNATURE>

<COPYRIGHT>1999 Elliotte Rusty Harold</COPYRIGH >

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

</SIGNATURE>

接下来,按清单9-21所示编写一个小型的DTD,该DTD为清单9-20中的文件定义一实体引用。在这里,假设可在文件signature.xml中找到清单9-20所示内容,该文件位于Web服务器根目录上的boilerplate目录中;也假定可在文件singature.dtd中找到清单9-21所示内容,该文件位于Web服务器根目录上的dtds目录中.

清单9-21:定义实体引用的Signature DTD

<!ENTITY SIGNATURE SYSTEM "/boilerplate/signature.xml">

现在,就可在文档中引入signature.dtd,然后使用通用实体引用&SIGNATURE;,就可在文件中嵌入signature.xml的内容。清单9-22说明了这种用法:

清单9-22:使用&SIGNATURE;的文件

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

<!DOCTYPE DOCUMENT [

<!ENTITY % SIG SYSTEM "/dtds/signature.dtd">

%SIG;

]>

<DOCUMENT>

<TITLE>A Very Boring Document</TITLE>

&SIGNATURE;

</DOCUMENT>

似乎这种间接的做法与真正所需的相比较,多了一个层次。例如清单9-23直接在其内部DTD子集中定义了&SIGNATURE;实体引用,且确实有效。但是这种间接做法所增加的层次可保护Web站点免于被更改,这是因为无法通过编辑一个文件的方式仅更改所有页面使用的signature内容。也可通过编辑一个文件的方式更改所有Web页面使用的signature的位置。另一方面,清单9-22中使用的方法越直接,就越便于在不同的页面上使用不同的signature。

清单9-23:使用&SIGNATURE;减少了一层非直接引用的文件

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

<!DOCTYPE DOCUMENT [

<!ENTITY % SIGNATURE SYSTEM "/dtds/signature.dtd">

]>

<DOCUMENT>

<TITLE>A Very Boring Document</TITLE>

&SIGNATURE;

</DOCUMENT>


9.8 本章小结

从本章中,可了解如何从内部和外部实体开始创建XML文档。详细地说,学习了以下内容:

·         实体就是组成文档的物理存储单元。

  • 实体内容为:结构完整的XML文档、其他形式的文本和二进制数据。
  • 内部实体完全在文档内部定义,外部实体可引入通过URL定位的不同资源的内容。
  • 通用实体引用具有“&name的形式,通常用于文档的内容中。
  • 内部通用实体引用由实体声明中给定的实体值所替换。
  • 外部通用实体引用由URL定位的数据所替换,该URL为实体声明中SYSTEM关键词后的内容规定。
  • 内部参数实体引用具有“%name的格式,只在DTD中使用。
  • 可用外部参数实体引用和不同的DTD
  • 外部实体引用提供创建大型复杂文档的能力。
  • XML标准一致性的第三层含义:结构完整,但不合法。不合法的原因在于DTD不完整或文档不满足DTD的约束条件。

当文档使用了属性的时候,必须在DTD中对属性加以声明。下一章讲述如何在DTD中声明属性,以及如何将约束条件附加于属性值进行限制。


第10章 DTDs中的属性声明

一些XML元素具有属性。属性包含应用程序使用的信息。属性仅在程序对元素进行读、写操作时,提供元素的额外信息(如ID号等),对于人类读、写元素来说是毫无意义的。在本章中学习各种属性类型和如何在DTD中声明属性。

本章内容如下:

·         什么是属性?

  • 如何在DTD中声明属性
  • 如何声明多个属性
  • 如何指定属性的缺省值
  • 属性类型
  • 预定义属性
  • 基于属性的棒球比赛统计数据的DTD

10.1 什么是属性?

在第3章曾经讨论过开始标记和空标记可包含由等号“=”分割开的成对的属性名和属性值。例如:

<GREETING LANGUAGE= "English">

Hello XML!

<MOVIE SOURCE= "WavingHand.mov" />

</GREETING>

上述例子中,GREETING元素具有LANGUAGE属性,其属性值为ENGLISH。MOVIE元素具有SOURCE属性,其属性值为WavingHand.mov。GREETING元素内容为Hello XML!。书写内容的语言对内容本身来说是一个有用的信息,可是语言不是内容的一部分。

与此相似,MOVIE元素内容为保存在WavingHand.mov文件中的二进制数据。尽管文件名告诉我们到何处可找到元素内容,但它本身不是元素内容。再次强调,属性包含有关元素内容信息,而不是元素内容本身。

元素可具有多个属性,例如:

<RECTANGLE WIDTH= "30" HEIGHT= "45" />

<SCRIPT LANGUAGE= "javascript" ENCODING= "8859_1" >...</SCRIPT>

上例中,SCRIPT元素属性LANGUAGE的值为javascript,SCRIPT元素属性ENCODING的值为8859_1;RECTANGLE元素属性WIDTH的值为30;RECT元素属性HEIGHT的值为45。这些属性值均为字符串数据,不是数字型数据。

结束标记不能带属性,下例视为非法:

<SCRIPT>...</SCRIPT LANGUAGE= "javascript" ENCODING= "8859_1" >


10.2 在DTD中声明属性

与元素和实体相似,为保持文档的合法性,需要在文档的DTD中声明属性。<!ATTLIST>标记用于声明属性,其形式如下:

<!ATTLIST Element_name Attribute_name Type Default_value>

Element_name为拥有该属性的元素名。Attribute_name为属性名,Type为表10-1列出的10种有效属性类型的一种。最常用的属性类型为CDATA。最后,若未规定属性值,则属性值为Default_value。

例如,研究下列元素:

<GREETING LANGUAGE= "Spanish">

Hola!

</GREETING>

在DTD中,可按如下格式声明该元素:

<!ELEMENT GREETING (#PCDATA)>

<!ATTLIST GREETING LANGUAGE CDATA "English">

<!ELEMENT>标记简单地说明greeting元素包含可析字符数据,这里没什么新内容。<!ATTLIST>标记表明GREETING元素拥有LANGUAGE属性,其值为CDATA类型,本质上与元素内容的#PCDATA相同。若所看见的GREETING标记中没有LANGUAGE属性,则LANGUAGE属性值为缺省指定的English。

表10-1 属性类型

类 型

含 义

CDATA

字符数据不是标记的文本

Enumerated

可能取值的列表,可从中选出正确的值

ID

不能被文档中其他任何ID类型属性共享的数字,具有唯一性

IDREF

文档中元素的ID类型属性的值

IDREFS

由空格分开的若干个ID

ENTITY

在DTD中声明的实体名

ENTITIES

在DTD中声明的若干个实体的名字,彼此间由空格分开

NMTOKEN

XML名称

NOTATION

在DTD中声明的注释名

NMTOKENS

由空格分开的多个XML名称

在各自的标记中分别声明各自的属性列表。属性所属元素的名字包含在<!ATTLIST>标记中,如上例中的属性声明仅用于GREETING元素。如果其余元素也具有LANGUAGE属性,就需要各自独立的<!ATTLIST> 声明。

对大部分声明而言,属性声明在文档中出现的顺序并无严格要求,可位于与其相连的元素声明之前或之后。实际上,甚至可以对同一属性进行多次声明。这时,第一个声明首先执行。

尽管非同寻常,甚至可以为并不存在的标记声明属性。在最初编辑DTD时,可以声明一些并不存在的属性,计划在以后再返回这里继续这些工作,为这些属性声明元素。


10.3 声明多个属性

元素通常具有多个属性。HTML的IMG元素可有HEIGHT、WIDTH、ALT、BORDER、ALIGN和其他几个属性 。实际上,大部分HTML标记都具有多个属性,XML标记也是如此。例如,很自然的RECTANGLE元素需要LENGTH和WIDTH属性:

<RECTANGLE LENGTH= "70px" WIDTH="85px"/>

也可用几个属性声明来声明这些属性,一个属性声明对应一个属性。例如:

<!ELEMENT RECTANGLE EMPTY>

<!ATTLIST RECTANGLE LENGTH CDATA "0px">

<!ATTLIST RECTANGLE WIDTH CDATA "0px">

上例说明,RECTANGLE元素具有LENGTH和WIDTH属性,它们的缺省值均为0px。

可按如下方式,组合两个<!ATTLIST>标记为一个单一声明:

<!ATTLIST RECTANGLE LENGTH CDATA "0px"

WIDTH CDATA "0px">

该声明声明了LENGTH和WIDTH属性,两个属性类型均为CDATA,缺省值为0px。若各属性的类型或缺省值不同,也可用这种语法结构进行声明。如下所示:

<!ATTLIST RECTANGLE LENGTH CDATA "15px"

WIDTH CDATA "34pt">

从个人角度来说,我不喜欢这种风格。看起来很混乱;且为易于辨认,过于依赖于额外的空格在其中的正确放置(尽管这些空格对标签的实际意义而言并不重要)。可是你一定会遇到其他人书写的、这种风格的DTD,所以必须掌握这种书写方法。


10.4 指定属性的缺省值

若不采用明确指定一个缺省属性值(如0px)的方式,属性声明可以要求作者提供属性值,或者完全忽略该属性值,甚至总是使用缺省值。这三种类型分别由三个关键词#REQUIRED、#IMPLIED、#FIXED加以指定。

10.4.1 #REQUIRED

有时要选一个恰当的缺省属性值并不容易。例如,在为创建一个用于内部网的DTD时,可能要求所有的文档都至少有一个空的<AUTHOR1>标记;这些标记通常情况下并不显示,但可用来识别创建文档的作者。标记中拥有NAME、EMAIL和EXTENSION属性,以便与作者联系。例如:

<AUTHOR NAME="Elliotte Rusty Harold"

EMAIL= elharo@metalab.unc.edu EXTENSION= "3459"/>

假设要强制要求在内部网上张贴文档人的表明身份,就不采取为这些属性提供缺省值的方法。然而XML无法阻止任何人把作者身份定为“Luke Skywalker”(洛克天行者),但至少可通过使用#REQUIRED的缺省值方式,要求指定作者身份为某个人。例如:

<!ELEMENT AUTHOR EMPTY>

<!ATTLIST AUTHOR NAME CDATA #REQUIRED>

<!ATTLIST AUTHOR EMAIL CDATA #REQUIRED>

<!ATTLIST AUTHOR EXTENSION CDATA #REQUIRED>

如果语法分析器遇到一个<AUTHOR/>标记,该标记没有包含这些属性中的一个或几个时,将返回一个错误。

也可使用#REQUIRED强迫作者提交IMG元素的WIDTH、HEIGHT和ALT属性。例如:

<!ELEMENT IMG EMPTY>

<!ATTLIST IMG ALT CDATA #REQUIRED>

<!ATTLIST IMG WIDTH CDATA #REQUIRED>

<!ATTLIST IMG HEIGHT CDATA #REQUIRED>

任何试图忽略这些属性的行为(这样的Web页面太多了)都将产生一个不合法文档。XML处理器将注意到这种错误,并且将通知缺少这些属性的作者。

10.4.2 #IMPLIED

有时可能找到一个好的属性缺省值,但也不想要求文档作者包含这属性值。例如,假设在内部网上张贴文档的一些人拥有电子邮件地址,但它们没有电话分机号;为此,不想要求它们在<AUTHOR/>标记中包含EXTENSION(分机号)属性部分。例如:

<AUTHOR NAME="Elliotte Rusty Harold"

EMAIL="elharo@metalab.unc.edu "/>

如果依然不想为EXTENSION(分机号)提供缺省属性值,但是想提供作者引入这种类似属性的能力。在这种情况下,就可使用#IMPLIED的缺省值。如下所示:

<!ELEMENT AUTHOR EMPTY>

<!ATTLIST AUTHOR NAME CDATA #REQUIRED>

<!ATTLIST AUTHOR EMAIL CDATA #REQUIRED>

<!ATTLIST AUTHOR EXTENSION CDATA #IMPLIED>

如果XML处理器遇到没有EXTENSION属性的<AUTHOR/>标记,就不向XML应用程序提供有用的属性值。应用程序按收到的通知进行相应的选择。例如,应用程序把元素送入SQL数据库中,属性映射为字段,应用程序或许在数据库相应字段中插入空的数据。

10.4.3 #FIXED

最后,可能想提供一个不允许作者更改的属性缺省值。例如,希望为在内部网上张贴文档的人员的AUTHOR元素指定一个同等的COMPANY标识属性。方法如下:

<AUTHOR NAME= "Elliotte Rusty Harold" COMPANY="TIC"

EMAIL= "elharo@metalab.unc.edu" EXTENSION="3459"/>

可通过指定缺省值为#FIXED,其后跟随实际的缺省值,来要求所有的人员对COMPANY属性使用该缺省值。例如:

<!ELEMENT AUTHOR EMPTY>

<!ATTLIST AUTHOR NAME CDATA #REQUIRED>

<!ATTLIST AUTHOR EMAIL CDATA #REQUIRED>

<!ATTLIST AUTHOR EXTENSION CDATA #IMPLIED>

<!ATTLIST AUTHOR COMPANY CDATA #FIXED "TIC">

文档作者不需要在它们各自的标记中真正地引用固定的属性。如果它们没有包括固定属性,则使用缺省值;如果包括了固定的属性,无论如何它们使用的属性值必须一致,否则语法分析器将返回一个错误信号。


10.5 属性类型

前面的所有例子都具有CDATA类型的属性。CDATA是最通用的类型,但此外还允许使用其他九种属性类型。所有十种类型如下:

·         CDATA

  • Enumerated(枚举)
  • NMTOKEN
  • NMTOKENS
  • ID
  • IDREF
  • IDREFS
  • ENTITY
  • ENTITIES
  • NOTATION

上述属性类型中的九种在类型字段中的值为常数,而Enumerated是一种特殊的类型,表示属性值必须为一可能取值列表中的一个。下面分别对各类型进行深入的研究。

10.5.1 CDATA属性类型

最通用的属性类型CDATA,表明属性值为不包括小于号(<)和引号(")的任意文本字符串。可通过普通的实体引用(&lt;和&quot;)的方式或由字符引用Unicode值的方式插入小于号和引号字符。原始的和号(&)��不是字符或实际引用开始的和号,必须使用换码符&amp;。

实际上,即使属性值中不可避免的要包含双引号(")的情况下,也不可直接使用;替代的方法是用单引号把双引号括起来。如下例所示:

<RECTANGLE LENGTH= 7" WIDTH= 8.5" >

如果属性值中包含单引号和双引号,而且并不用作界定属性值的用途;它们必须替换为实体引用&apos;(单引号)和&quot;(双引号)。例如:

<RECTANGLE LENGTH= 8& pos;7" WIDTH="10 6&quot;"/>

10.5.2 Enumerated属性类型

Enumerated类型不是XML的关键词,而是由竖线分隔的可能的属性值列表。任一值均需为有效的XML名称。文档作者可选取列表中的一个成员为属性的值,缺省值必须为列表中的一个值。

例如,假设希望某个元素具有可见和不可见属性。希望该元素具有一个VISIBLE属性,其属性值为TRUE或FALSE。如果该元素为简单的P元素,那么<!attlist>声明可如下所示:

<!ATTLIST P VISIBLE (TRUE | FALSE) "TRUE">

上述声明表示P元素的VISIBLE属性可有可无,若拥有VISIBLE属性,则属性值必须为TRUE或FALSE;如果没有VISIBLE属性,则假定该值为TRUE。例如:

<P VISIBLE= "FALSE">You can t see me! Nyah! Nyah!</P>

<P VISIBLE= "TRUE">You can see me.</P>

<P>You can see me too.</P>

就其自身而言,这声明并不是一个提供隐藏文本能力的魔术般的咒语。这种能力依然依靠应用程序去理解不应该显示不可见元素。为决定元素的显示或隐藏,可以通过对元素应用VISIBLE属性的样式单规则来进行设置。例如:

<xsl:template match= "P[@VISIBLE= FALSE ]" >

</xsl:template>

<xsl:template match= "P[@VISIBLE= TRUE ]" >

<xsl: apply-templates/>

</xsl:template>

10.5.3 NMTOKEN属性类型

NMTOKEN属性类型限定属性值为有效的XML名称。如第6章所述,XML名称必须以字母或下划线开头。名字中后面的字符可以为字母、数字、下划线、连字符和句号。但不可包括空格(下划线通常作为空格的替代品)。从技术上说,名字中可包含冒号(:)但不应该使用冒号,因为冒号被保留为与命名域(namespace)一起使用。

当使用编程语言处理XML数据时,证明了NMTOKEN的价值。这并不是一种偶然,除了允许使用冒号以外,上述规则与JAVA,JavaScript和其他程序语言标识符规则一致。例如,可在元素中使用NMTOKEN属性访问特别的JAVA类。那么就应用JAVA的API映射把数据传送到专有类的特有方法中。

当需要从大量名字中选取不是XML的规定部分但与XML命名要求相符的名字时,就能体现NMTOKEN的用途。这些要求的最重要部分就是对空格的限制。例如,NMTOKEN可以用于下述属性,其值必须映射为8.3的DOS文件名,另一方面该属性也能用于UNIX、Macintosh或Windows NT文件名,而这些文件名中通常包含空格。

例如,假如要求<ADDRESS/>标记中的州(state)属性为两个字母缩写;不能用DTD强制这些特性的执行,但可应用如下<!ATTLIST>声明防止人们输入类似“New York”或“Puerto Rico”的值:

<!ATTLIST ADDRESS STATE NMTOKEN #REQUIRED>

无论何种情况,像“California”、“Nevada”和其他一个单词的州名依然为合法值。当然,可以利用具有几十个两个字母的代码的枚举列表的简单方法;但是这种方法将导致巨大的工作量,比人们想象的大得多。举个例子,想一想,如果用两个字母代码代表美国50个州、所有的领土和属地、所有的国外的军事基地和加拿大所有的省份会是一个什么样的情况?另一方面,如果曾经在DTD文件中的参数实体引用定义了这样的列表,就可重复多次使用这个文件。

10.5.4 NMTOKENS属性类型

NMTOKENS属性类型几乎就是NMTOKEN的复数形式。这种类型的属性可以使如下情况合法——属性由若干XML名称字组成,彼此间由空格分隔。通常可为使用NMTOKEN属性相同的理由而使用NMTOKENS属性,但仅仅在需要多个名字的时候。

例如,如果state元素的属性值需要多个两个字母州代码时,就可应用下例所示的方法:

<!ATTLIST ADDRESS STATES NMTOKENS #REQUIRED>

然后,就可编写如下所示的标记:

<ADDRESS STATES="MI NY LA CA">

不幸的是,如果应用这种技术,就不能再排除类似“New York”这样的州名,因为州名中每一独立的部分都为一个合格的NMTOKEN。如下所示:

<ADDRESS STATES="MI New York LA CA">

10.5.5 ID属性类型

一个ID类型的属性标识文档中唯一的元素,编辑工具和其余应用程序通常使用ID列举文档中的元素,并不关心元素的实际意义和各元素彼此之间的关系。

一个ID类型属性值必须为有效的XML名称,该名称以字母开头,由字母数字混排的字符和下划线组成,并且其中不带空格。一个特定的名字不能用作多个标记的ID属性。若在一个文档中两次使用同一ID将导致语法分析器返回一个错误信息;另外,一个元素不能具有超过一个的ID类型的属性。

一般来说,ID属性的存在只是为了处理数据的程序方便。在许多情况下,除了ID属性值外,多个元素可能会是一样的,如果以可以预见的方式来选取ID的话,程序就可以列举出文档中所有不同类型的元素或同一类型的不同元素。

ID类型属性与#FIXED类型的属性不兼容。ID类型属性不能同时具有#FIXED类型的属性,因为#FIXED类型的属性仅能拥有一个单一的值,而每个ID类型属性都具有不同的值。大部分ID属性使用#REQUIRED值。如清单10-1例所示:

清单10-1:required ID属性类型

<?xml version="1.0" standa lone="yes"?>

<!DOCTYPE DOCUMENT [

<!ELEMENT DOCUMENT (P*)>

<!ELEMENT P (#PCDATA)>

<!ATTLIST P PNUMBER ID #REQUIRED>

]>

<DOCUMENT>

<P PNUMBER="p1">The quick brown fox</P>

<P PNUMBER="p2" >The quick brown fox</P>

</DOCUMENT>

10.5.6 IDREF属性类型

IDREF类型的属性值为文档中另一个元素的ID。例如,清单10-2表明IDREF和ID属性用于子元素和父元素之间的连结。

清单10-2:family.xml

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

<!DOCTYPE DOCUMENT [

<!ELEMENT DOCUMENT (PERSON*)>

<!ELEMENT PERSON (#PCDATA)>

<!ATTLIST PERSON PNUMBER ID #REQUIRED>

<!ATTLIST PERSON FATHER IDREF #IMPLIED>

<!ATTLIST PERSON MOTHER IDREF #IMPLIED>

]>

<DOCUMENT>

<PERSON PNUMBER= "a1" >Susan</PERSON>

<PERSON PNUMBER= "a2" >Jack</PERSON>

<PERSON PNUMBER= "a3" MOTHER= "a1" FATHER= "a2" >Chelsea</PERSON>

<PERSON PNUMBER= "a4" MOTHER= "a1" FATHER= "a2" >David</PERSON>

</DOCUMENT>

当在文档结构树上并不冲突的两个元素之间需要建立连结时,通常使用这种并不普遍但很重要的类型。在清单10-2中,每个子元素都有FATHER和MOTHER属性给出,这两个属性包含的是对应的ID属性。

在清单10-2中,无法简洁地使用IDREF建立父元素到子元素之间的链接,这是因为每个父元素都具有不确定的子元素数目。解决的方法就是,可以把所有同一父元素的子元素组成FAMILY元素,然后链接到FAMILY上。即使使用这种方法,当面对半同属(共享唯一一个父元素)的元素时,也不好用。简而言之,IDREF适用于多对一的关系,不适合一对多的关系。

10.5.7 ENTITY属性类型

ENTITY类型的属性提供把外部二进制数据和外部不可析实体链接到文档中的能力。ENTITY属性值为DTD中声明的不可析通用实体名,该实体名链接到外部实际数据。

经典的ENTITY属性的例子就是图像。图像由另一URL处可用的二进制数据组成。假如XML浏览器支持ENTITY类型属性,在DTD中按如下方式声明,就可在XML文档中包括一幅枷瘢?/p>

<!ELEMENT IMAGE EMPTY>

<!ATTLIST IMAGE SOURCE ENTITY #REQUIRED>

<!ENTITY LOGO SYSTEM "logo.gif">

然后在期望图像出现在文档中的位置处,就可插入如下的IMAG标记:

<IMAGE SOURCE="LOGO"/>

所有XML浏览器自动识别的过程并不是在变魔术,这仅仅是一种简单的技术,浏览器和其余应用程序可能采用也可能不采用这种技术在文档中嵌入非XML数据。

这种技术在第11章 “嵌入非XML数据” 中有更深入的探讨。

10.5.8 ENTITIES 属性类型

ENTITIES属性类型几乎就是ENTITY的复数形式。若干由空格分隔的不可析实体名组成ENTITIES类型属性的值。每一实体名指向一个外部非XML数据资源。这种类型属性的用途之一为:使不同图片之间的切换变得光滑平顺,如下例所示:

<!ELEMENT SLIDESHOW EMPTY>

<!ATTLIST SLIDESHOW SOURCES ENTITIES #REQUIRED>

<!ENTITY PIC1 SYSTEM "cat.gif">

<!ENTITY PIC2 SYSTEM "dog.gif">

<!ENTITY PIC3 SYSTEM "cow.gif">

然后在文档中希望显示图片的位置上插入如下标记:

<SLIDESHOW SOURCES="PIC1 PIC2 PIC3">

再一次声明,这不是所有(或任意)XML浏览器可以自动识别的通用格式;仅仅是某些浏览器和其余的应用程序可能采用也可能不采用的在文档中嵌入非XML数据的方法而已。

10.5.9 NOTATION属性类型

NOTATION属性类型指定属性值为DTD中声明的记号名。这一属性的缺省值也必须为DTD中声明的记号名。在下一章中介绍记号的详细内容。简单地说,记号可标识非XML数据的格式;例如为不可析实体指定一帮助程序。

第11章 “嵌入非XML格式数据”讲述了这方面的内容。

例如,SOUND元素的PLAYER属性具有NOTATION类型和缺省值MP,从而标识非XML数据的格式,记号MP表示一个特殊类型的声音文件:

<!ATTLIST SOUND PLAYER NOTATION (MP) #REQUIRED>

<!NOTATION MP SYSTEM "mplay32.exe">

也可提供不同记号的选择。这样做的用法之一是为不同的平台指定不同的帮助应用程序。浏览器可从中选取一可用的值。这种情况下,NOTATION关键词后紧跟一对圆括号,括号内包含由竖直线分隔的、许可的记号名列表。例如:

<!NOTATION MP SYSTEM "mplay32.exe">

<!NOTATION ST SYSTEM "soundtool">

<!NOTATION SM SYSTEM "Sound Machine">

<!ATTLIST SOUND PLAYER NOTATION (MP | SM | ST) #REQUIRED>

这表明SOUND元素的PLAYER属性值可设置为MP、ST或SM。下一章对此再作进一步的研究。

乍看上去,这种处理方法与其余列表属性(如ENTITIES和NMTOKENS)处理方法好像不一致;但其实这两种方法截然不同。ENTITIES和NMTOKENS在文档的实际元素中具有一个属性列表,但在DTD的属性声明中仅有一个值。可是文档中实际元素的NOTATION属性值仅有一个。可取值的列表位于DTD

的属性声明中。


10.6 预定义属性

在某种程度上说,可以在XML中预定义两个属性。必须在DTD中为将应用的每一元素声明这两个属性,但是仅仅可以为原定目标而使用这些声明的属性。通过在属性名前加xml:来标识这类属性。

这两类属性分别为xml:space和xml:lang。Xml:space属性描述如何对待元素中的空格;xml:lang属性描述书写元素的语言(以及可选的方言和国别)。

10.6.1 xml:space

在HTML中,空格并不重要。尽管一个空格和没有空格之间的差别是非常重要的,但是一个空格和两个空格、一个空格和一个回车符、一个空格三个回车符和12个制表符之间的差别并不重要。对某些文本来说,空格非常重要,如计算机源代码、某些大型机数据库报告或者e.e.cumming的诗文,可使用PRE元素指定等宽的字体和保留空格。

不过,XML 的缺省方式为保留空格。XML处理器毫不改变地传送全部空格字符给应用程序。应用程序通常忽略额外的空格。可是XML处理器可通知应用程序某些特定的元素包含需保留的、意义重大的空格。作者可使用xml:space属性,为应用程序说明这些元素。

如果元素包含重要的空格,DTD将为xml:space属性提供一个<!ATTLIST>标记。这个属性具有枚举类型,其值为default和preserve。如清单10-3所示。

清单10-3:用XML编码的具有重要空格的Java源代码

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

<!DOCTYPE PROGRAM [

<!ELEMENT PROGRAM (#PCDATA)>

<!ATTLIST PROGRAM xml:space (default|preserve) preserve >

]>

<PROGRAM xml:space= "preserve">public class AsciiTable {

public static void m in (String[] args) {

for (int i = 0; i &lt; 128; i++) {

System.out.println(i + " "+ (char) i);

}

}

}

</PROGRAM>

不管xml:space值为default或preserve,全部空格都传送给应用程序。可是,若值为default,应用程序按正常方式处理额外的空格;若值为preserve,则按有特殊意义的态度对待这些额外的空格。

空格的特殊意义在某种角度来说依赖于数据的最终目标。例如,Java源代码中的额外空格仅与源代码的编辑器有关,但与编译器无关。

已定义xml:space属性元素的子元素,除非子元素定义了与原值相矛盾的xml:space属性,否则将表现出与其父元素相似的行为(保留或不保留空格)。

10.6.2 xml:lang

xml:lang属性标识书写元素内容的语言。属性值可以为CDATA、NMTOKEN或者枚举列表类型。理想的情况下,每一个属性值均为原始ISO-639标准定义的两个字母语言代码。代码列表可在下述Web地址处找到:

http://www.ics.uci.edu/pub/ietf/http/related/iso639.txt。

例如,研究下面两个例子,其中的句子取自Petronius的 Satiricon,分别用拉丁文(Latin)和英文(English)书写。一个SENTENCE标记包装了这两个句子,但是第一个句子标记使用的xml:lang属性值为“Latin”,而第二个句子中其值为“English”。

拉丁文(Latin):

<SENTENCE xml:lang="la">

Veniebamus in forum deficiente now die, in quo notavimus frequentiam rerum venalium, non quidem pretiosarum sed tamen quarum fidem male mbulantem obscuritas temporis facillime tegeret.

</SENTENCE>

英文(English):

<SENTENCE xml:lang= "en">

We have come to the marketplace now when the day is failing, where we have seen many things for s le, not for the valuable goods but rather that the darkness of the time may most easily conceal their shoddiness.

</SENTENCE>

说英语的读者很容易区分哪个为原稿,哪个为译本;计算机可根据xml:lang属性提供的线索进行区分。这个差别可确定拼写检查器决定如何检查特定的元素和决定使用哪个词典。搜索引擎可以检查这些语言属性,然后确定如何检索页面内容和返回基于用户优先选择的匹配结果。

语言种类过多,而代码不足

XML在使用语言代码问题上依然有点落后。原始的ISO-639标准语言代码由两个不分大小写的ASCII字母字符构成;这标准允许的代码数目不能超过676种不同的代码(26*26),可是现在地球上使用的语言远远不止676种(即使不包括类似Etruscan这种已经消亡的语言)。实际上,合理的代码还少于676种,因为语言的缩写必须与语言的名字具有一定的关系。

ISO-639标准的第二部分使用三个字母的代码,可处理地球上使用的所有语言。可是XML标准规范要求使用两个字母代码。

语言(language)属性应用在元素及其所有的子元素上,除非其中某个子元素声明了不同的语言。前面的SENTENCE元素可按如下方式进行声明:

<!ELEMENT SENTENCE (#PCDATA)>

<!ATTLIST SENTENCE xml:lang NMTOKEN "en">

假如没有适当的ISO代码可利用,也可利用在IANA注册的代码之一,虽然现在IANA仅增加了四种附加代码(如表10-2所示)。最新的列表可在下面的地址处找到:

http://www.isi.edu/iana/assignments/language/tags

表10-2 IANA语言代码

代 码

语 言

no-bok

Norwegian "Book language"(挪威的书面语言)

no-nyn

Norwegian "New Norwegian"(新挪威语言)

i-navajo

Navajo(印第安语)

i-mingo

Mingo

例如:

<P xml:lang="no-nyn">

如果需要使用的语言代码(或许是Klingon)既不包含在ISO代码中也不包含在IANA代码中,就可定义新的语言代码。这些“x-codes”必须以字符串x-或者X-开始,标识为用户自定义、私人使用的代码。例如:

<P xml:lang="x-klingon">

xml:lang属性值可包含附加的子代码部分,用连字符“-”把子代码与主要的语言代码区分开。最常见的情况是第一个子代码为ISO-3166规定的两个字母的国家代码。最新的国家代码列表可在下面的地址中找到:

http://www.isi.edu/in-notes/iana/assignment/country-codes。

例如:

<P xml:lang="en-US">Put the body in the trunk of the car.</P>

<P xml:lang="en-GB">Put the body in the boot of the car.</P>

如果第一个子代码不是ISO规定的两个字母国家代码,就必须是设置IANA注册的语言的字符集子代码,如csDECMCS、roman8、mac、cp037或ebcdic-cp-ca。当前使用的代码列表可以在下述地址中找到:

ftp://ftp.isi.edu/in-notes/iana/assignments/character-sets。

示例如下:

<P xml:lang= "en-mac">

最终的结果可能是第一个子代码,另一个以x-或X-开头的x-code。例如:

<P xml:lang= "en-x-tic">

根据惯例,语言代码写为小写格式,国家代码为大写格式。可是这仅仅是一个惯例。这是XML少数对大小写敏感部分中的一个,因为它继承了ISO对大小写不敏感的特性。

与DTD中使用的其他所有属性相同,为保持文档的合法性。必须明确地声明xml:lang属性,必需直接用于它所施加的元素(对于指定xml:lang属性的元素的子元素是间接施用)。

或许不希望xml:lang取任意值。其允许值也应为合法的XML名称字,所以通常赋予属性NMTOKEN类型。这种类型可限制属性值为合法的XML名称字。例如:

<!ELEMENT P (#PCDATA)>

<!ATTLIST P xml:lang NMTOKEN #IMPLIED "en">

另外,如果仅允许很少的几个语言或方言,就可以应用枚举类型。例如,下述DTD说明P元素可以为English或Latin。

<!ELEMENT P (#PCDATA)>

<!ATTLIST P xml:lang (en | la) "en">

也可以使用CDATA类型,但是没有什么理由要这样做。使用NMTOKEN或者枚举类型有助于发现某些潜在的错误。


10.7 基于属性的棒球统计数据的DTD

在第5章中,为1998 Major League Season创建了一个结构完整的XML文档,文档中应用属性来保存赛季(SEASON)的年份(YEAR)、联盟名(NAME)、分部名、球队名、球队比赛的城市名(CITY)和每个球员的详细统计资料。下面的清单10-4是清单5-1的一个缩略版本,完整的XML文档中包括两个联盟、六个分部、六个球队和两个球员的数据,以便于去理解元素的位置所在和元素具有什么属性。

清单10-4:完整的XML文档

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

<SEASON YEAR= "1998" >

<LEAGUE NAME= "Nation l League" >

<DIVISION NAME="East">

<TEAM CITY=" Atlant NAME=" Braves">

<PLAYER GIVEN_NAME=" Marty SURNAME=" Malloy"

POSITION=" Second Base" GAMES=" 11" GAMES_STARTED=" 8"

AT_BATS=" 28" RUNS=" 3" HITS=" 5" DOUBLES=" 1"

TRIPLES=" 0" HOME_RUNS=" 1" RBI=" 1" STEALS=" 0"

CAUGHT_STEALING=" 0" SACRIFICE_HITS=" 0 "

SACRIFICE_FLIES=" 0" ERRORS=" 0" WALKS=" 2"

STRUCK_OUT=" 2" HIT_BY_PITCH=" 0" />

<PLAYER GIVEN_NAME=" Tom" SURNAME=" Glavine"

POSITION=" Starting Pitcher" GAMES=" 33"

GAMES_STARTED=" 33" WINS=" 20" LOSSES=" 6" SAVES=" 0"

COMPLETE_GAMES=" 4 SHUTOUTS=" 3 ERA=" 2.47"

INNINGS=" 229.1" HOME_RUNS_AGAINST=" 13"

RUNS_AGAINST=" 67" EARNED_RUNS=" 63" HIT_BATTER=" 2"

WILD_PITCHES=" 3" BALK=" 0" WALKED_BATTER=" 74"

STRUCK_OUT_BATTER=" 157" />

</TEAM>

</DIVISION>

<DIVISION NAME=" Central" >

<TEAM CITY=" Chicago NAME=" Cubs" >

</TEAM>

</DIVISION>

<DIVISION NAME=" West >

<TEAM CITY=" San Francisco" NAME=" Giants" >

</TEAM>

</DIVISION>

</LEAGUE>

<LEAGUE NAME=" American League" >

<DIVISION NAME=" East" >

<TEAM CITY=" New York NAME=" Yankees" >

</TEAM>

</DIVISION>

<DIVISION NAME=" Central" >

<TEAM CITY=" Minnesota" NAME=" Twins" >

</TEAM>

</DIVISION>

<DIVISION NAME=" West" >

<TEAM CITY=" Oakland" NAME=" Athletics" >

</TEAM>

</DIVISION>

</LEAGUE>

</SEASON>

为了此文档的合法性和结构的完整性,就需要提供DTD。该DTD中必须声明清单10-4中使用的所有元素和属性。元素的声明与旧版类似,只是由于大部分信息被转移到属性中的缘故,显得更为简短:

<!ELEMENT SEASON (LEAGUE, LEAGUE)>

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

<!ELEMENT DIVISION (TEAM+)>

<!ELEMENT TEAM (PLAYER*)>

<!ELEMENT PLAYER EMPTY>

10.7.1 在DTD中声明SEASON的属性

SEASON拥有单一的属性YEAR。尽管有些语义上的限定,规定什么是,而什么不是一个年份(1998是年份,March 31就不是);DTD不进行这种限定。因此,最好的方法就是声明YEAR属性具有最通用的属性类型CDATA;另外,希望每个赛季都具有年份值(year),就可使YEAR属性为REQUIRED类型。

<!ATTLIST SEASON YEAR CDATA #REQUIRED>

尽管确实无法限制作者输入YEAR属性文本的格式,但是至少可以提供一个记号,表明可以接受何种格式的文本。例如,规定年份(year)需要四位数的格式就不失为一个好主意。

<!ATTLIST SEASON YEAR CDATA #REQUIRED> <!--e.g. 1998 -->

<!--DO NOT USE TWO DIGIT YEARS like 98, 99, 00!! -->

10.7.2 在DTD中声明DIVISION和LEAGUE属性

接下来考虑DIVISION和LEAGUE元素。它们都具有单一的NAME属性。此外自然具有CDATA和REQUIRED属性类型。因为两个不同的元素具有两个相互独立的NAME属性,所以需要两个独立的<!ATTLIST>声明。

<!ATTLIST LEAGUE NAME CDATA #REQUIRED>

<!ATTLIST DIVISION NAME CDATA #REQUIRED>

在这里添加注释可以有助于表明作者期望的格式;例如是否包括单词LeagueDivision作为名字的一部分。

<!ATTLIST LEAGUE NAME CDATA #REQUIRED>

<!--e.g. "Nation l League" -->

<!ATTLIST DIVISION NAME CDATA #REQUIRED>

<!--e.g. "East" -->

10.7.3 在DTD中声明TEAM属性

TEAM元素具有NAME和CITY属性,两个属性均为CDATA和REQUIRED类型。

<!ATTLIST TEAM NAME CDATA #REQUIRED>

<!ATTLIST TEAM CITY CDATA #REQUIRED>

添加注释有助于建立某些并不明显的东西,例如,在一些情况下,CITY属性实际上可以是某个州名:

<!ATTLIST TEAM CITY CDATA #REQUIRED>

<!--e.g. "San Diego" as in "San Diego Padres"

or "Texas" as in "Texas Rangers" -->

换一种方式,可以在一个简单的<!ATTLIST>中声明这两个属性。

<!ATTLIST TEAM NAME CDATA #REQUIRED

CITY CDATA #REQUIRED>

10.7.4 在DTD中声明PLAYER的属性

PLAYER元素可以说是包含了大部分属性类型。首先是GIVEN_NAME和SURNAME两个属性,均为简单的CDATA、REQUIRED类型。

<!ATTLIST PLAYER GIVEN_NAME CDATA #REQUIRED>

<!ATTLIST PLAYER SURNAME CDATA #REQUIRED>

下一个PLAYER属性是POSITION。因为棒球场上球员的位置是一个相当基本的数据,在这里可使用枚举属性类型。可是“First Base”、“Second Base”、“Third Base”、“Starting Pitcher”和“Relief Pitcher”都包含有空格,因此它们都不是有效的XML名称字。因此仅能使用CDATA属性类型。实际情况下没有任何理由为POSITION选定缺省值,所以该属性应为REQUIRED类型。

<!ATTLIST PLAYER POSITION CDATA #REQUIRED>

接下来的是各种各样的统计数据:GAMES、GAMES_STARTED、AT_BAT、RUNS、HITS、WINS、LOSSES、SAVES、SHUTOUTS等等。上述每一个属性本应为数字类型,但是因为XML不提供数字类型机制,所以把它们简单地声明为CDATA类型。因为不是每一个球员都具有如上的每一个统计数据值,所以把上述各属性声明为IMPLIED类型,而不是REQUIRED类型。

<!ATTLIST PLAYER GAMES CDATA #IMPLIED>

<!ATTLIST PLAYER GAMES_STARTED CDATA #IMPLIED>

<!-Batting Statistics ->

<!ATTLIST PLAYER AT_BATS CDATA #IMPLIED>

<!ATTLIST PLAYER RUNS CDATA #IMPLIED>

<!ATTLIST PLAYER HITS CDATA #IMPLIED>

<!ATTLIST PLAYER DOUBLES CDATA #IMPLIED>

<!ATTLIST PLAYER TRIPLES CDATA #IMPLIED>

<!ATTLIST PLAYER HOME_RUNS CDATA #IMPLIED>

<!ATTLIST PLAYER RBI CDATA #IMPLIED>

<!ATTLIST PLAYER STEALS CDATA #IMPLIED>

<!ATTLIST PLAYER CAUGHT_STEALING CDATA #IMPLIED>

<!ATTLIST PLAYER SACRIFICE_HITS CDATA #IMPLIED>

<!ATTLIST PLAYER SACRIFICE_FLIES CDATA #IMPLIED>

<!ATTLIST PLAYER ERRORS CDATA #IMPLIED>

<!ATTLIST PLAYER WALKS CDATA #IMPLIED>

<!ATTLIST PLAYER STRUCK_OUT CDATA #IMPLIED>

<!ATTLIST PLAYER HIT_BY_PITCH CDATA #IMPLIED>

<!-Pitching Statistics ->

<!ATTLIST PLAYER WINS CDATA #IMPLIED>

<!ATTLIST PLAYER LOSSES CDATA #IMPLIED>

<!ATTLIST PLAYER SAVES CDATA #IMPLIED>

<!ATTLIST PLAYER COMPLETE_GAMES CDATA #IMPLIED>

<!ATTLIST PLAYER SHUTOUTS CDATA #IMPLIED>

<!ATTLIST PLAYER ERA CDATA #IMPLIED>

<!ATTLIST PLAYER INNINGS CDATA #IMPLIED>

<!ATTLIST PLAYER HOME_RUNS_AGAINST CDATA #IMPLIED>

<!ATTLIST PLAYER RUNS_AGAINST CDATA #IMPLIED>

<!ATTLIST PLAYER EARNED_RUNS CDATA #IMPLIED>

<!ATTLIST PLAYER HIT_BATTER CDATA #IMPLIED>

<!ATTLIST PLAYER WILD_PITCHES CDATA #IMPLIED>

<!ATTLIST PLAYER BALK CDATA #IMPLIED>

<!ATTLIST PLAYER WALKED_BATTER CDATA #IMPLIED>

<!ATTLIST PLAYER STRUCK_OUT_BATTER CDATA #IMPLIED>

只要你愿意,可以把PLAYER可能使用的所有属性组合为一个外形庞大的<!ATTLIST>声明:

<!ATTLIST PLAYER

GIVEN_NAME CDATA #REQUIRED

SURNAME CDATA #REQUIRED

POSITION CDATA #REQUIRED

GAMES CDATA #IMPLIED

GAMES_STARTED CDATA #IMPLIED

AT_BATS CDATA #IMPLIED

RUNS CDATA #IMPLIED

HITS CDATA #IMPLIED

DOUBLES CDATA #IMPLIED

TRIPLES CDATA #IMPLIED

HOME_RUNS CDATA #IMPLIED

RBI CDATA #IMPLIED

STEALS CDATA #IMPLIED

CAUGHT_STEALING CDATA #IMPLIED

SACRIFICE_HITS CDATA #IMPLIED

SACRIFICE_FLIES CDATA #IMPLIED

ERRORS CDATA #IMPLIED

WALKS CDATA #IMPLIED

STRUCK_OUT CDATA #IMPLIED

HIT_BY_PITCH CDATA #IMPLIED

WINS CDATA #IMPLIED

LOSSES CDATA #IMPLIED

SAVES CDATA #IMPLIED

COMPLETE_GAMES CDATA #IMPLIED

SHUTOUTS CDATA #IMPLIED

ERA CDATA #IMPLIED

INNINGS CDATA #IMPLIED

HOME_RUNS_AGAINST CDATA #IMPLIED

RUNS_AGAINST CDATA #IMPLIED

EARNED_RUNS CDATA #IMPLIED

HIT_BATTER CDATA #IMPLIED

WILD_PITCHES CDATA #IMPLIED

BALK CDATA #IMPLIED

WALKED_BATTER CDATA #IMPLIED

STRUCK_OUT_BATTER CDATA #IMPLIED>

这种方法不会有什么好处,应用这种方法就无法在个别属性后加上简单的注释。

10.7.5 棒球比赛统计数据示例的完整DTD

清单10-5显示的是棒球比赛统计数据基本属性的完整DTD。

清单10-5:利用属性包含大部分信息的棒球比赛统计数据的完整DTD

<!ELEMENT SEASON (LEAGUE, LEAGUE)>

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

<!ELEMENT DIVISION (TEAM+)>

<!ELEMENT TEAM (PLAYER*)>

<!ELEMENT PLAYER EMPTY>

<!ATTLIST SEASON YEAR CDATA #REQUIRED>

<!ATTLIST LEAGUE NAME CDATA #REQUIRED>

<!ATTLIST DIVISION NAME CDATA #REQUIRED>

<!ATTLIST TEAM NAME CDATA #REQUIRED

CITY CDATA #REQUIRED>

<!ATTLIST PLAYER GIVEN_NAME CDATA #REQUIRED>

<!ATTLIST PLAYER SURNAME CDATA #REQUIRED>

<!ATTLIST PLAYER POSITION CDATA #REQUIRED>

<!ATTLIST PLAYER GAMES CDATA #REQUIRED>

<!ATTLIST PLAYER GAMES_STARTED CDATA #REQUIRED>

<!-Batting Statistics ->

<!ATTLIST PLAYER AT_BATS CDATA #IMPLIED>

<!ATTLIST PLAYER RUNS CDATA #IMPLIED>

<!ATTLIST PLAYER HITS CDATA #IMPLIED>

<!ATTLIST PLAYER DOUBLES CDATA #IMPLIED>

<!ATTLIST PLAYER TRIPLES CDATA #IMPLIED>

<!ATTLIST PLAYER HOME_RUNS CDATA #IMPLIED>

<!ATTLIST PLAYER RBI CDATA #IMPLIED>

<!ATTLIST PLAYER STEALS CDATA #IMPLIED>

<!ATTLIST PLAYER CAUGHT_STEALING CDATA #IMPLIED>

<!ATTLIST PLAYER SACRIFICE_HITS CDATA #IMPLIED>

<!ATTLIST PLAYER SACRIFICE_FLIES CDATA #IMPLIED>

<!ATTLIST PLAYER ERRORS CDATA #IMPLIED>

<!ATTLIST PLAYER WALKS CDATA #IMPLIED>

<!ATTLIST PLAYER STRUCK_OUT CDATA #IMPLIED>

<!ATTLIST PLAYER HIT_BY_PITCH CDATA #IMPLIED>

<!-Pitching Statistics ->

<!ATTLIST PLAYER WINS CDATA #IMPLIED>

<!ATTLIST PLAYER LOSSES CDATA #IMPLIED>

<!ATTLIST PLAYER SAVES CDATA #IMPLIED>

<!ATTLIST PLAYER COMPLETE_GAMES CDATA #IMPLIED>

<!ATTLIST PLAYER SHUTOUTS CDATA #IMPLIED>

<!ATTLIST PLAYER ERA CDATA #IMPLIED>

<!ATTLIST PLAYER INNINGS CDATA #IMPLIED>

<!ATTLIST PLAYER HOME_RUNS_AGAINST CDATA #IMPLIED>

<!ATTLIST PLAYER RUNS_AGAINST CDATA #IMPLIED>

<!ATTLIST PLAYER EARNED_RUNS CDATA #IMPLIED>

<!ATTLIST PLAYER HIT_BATTER CDATA #IMPLIED>

<!ATTLIST PLAYER WILD_PITCHES CDATA #IMPLIED>

<!ATTLIST PLAYER BALK CDATA #IMPLIED>

<!ATTLIST PLAYER WALKED_BATTER CDATA #IMPLIED>

<!ATTLIST PLAYER STRUCK_OUT_BATTER CDATA #IMPLIED>

使用如下的序进程把清单10-5的内容引入到清单10-4中,当然我们假设清单10-5保存为一个文件名为“baseballattribtes.dtd”的文件中。

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

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


10.8 本章小结

本章中学习了如何在DTD中声明元素的属性。具体地说,学习了下述内容:

·         在DTD的<!ATTLIST>标记中声明属性。

  • 一个<!ATTLIST>标记可以声明一个元素任意数目的属性。
  • 属性通常具有缺省值,但是可以通过使用关键词#REQUIRED#IMPLIEDU#FIXED改变这种状态。
  • DTD中,可以声明十种属性类型: CDATA、枚举类型、NMTOKENNMTOKENSIDIDREFIDREFSENTITYENTITIESNOTATION
  • 预定义的xml:space属性确定元素中的空格是否有意义。
  • 预定义的xml:lang属性规定书写元素内容的语言。

下一章将学习到如何利用记号、处理指令和不可析实体在XML文档中嵌入非XML数据。


第11章 嵌入非XML数据

不是世界上的所有数据都为XML格式。实际上,可以大胆地说世界上积累下来的数据大部分都不是XML格式。大量数据按无格式文本、HTML和微软的Word格式保存,这里只举出三种常用的非XML格式。在理论上说,如果有兴趣且财力允许的情况下,至少这些数据的大部分可以重写为XML格式,但也不是所有的数据都可以。例如把图像编码为XML格式就将导致处理效率极端低下。

XML提供三种结构:记号、不可析外部实体和处理指令,通常用于处理非XML格式数据。记号描述非XML格式数据;不可析外部实体提供与非XML格式数据实际位置的链接;处理指令给出如何观看这些数据的信息。

本章叙述的具体内容尚有许多争议。尽管我所说的每一个事情都符合XML 1.0规范,但是不是所有人都同意上述观点。肯定可以写出一个XML文档,文档中没有使用注解和外部对象,仅有一些简单的处理说明。可以先跳过本章内容,在后面发现有必要了解这方面内容时,再返回到这一章。

本章的主要内容如下:

·         记号

  • 不可析外部实体
  • 处理指令
  • DTD中的条件部分

11.1 记号

在XML文档中使用非XML格式数据将会遇到的第一个问题是识别数据格式,并通知XML应用程序如何读出和显示这些非XML格式数据。例如,企图在屏幕上画出MP3声音文件就是不合适宜的。

在一个有限的范围内只利用一套固定的用于特定种类的外部实体的标记,就可在单一应用程序中解决外部非XML数据的读取和显示问题。例如,如果全部图片数据都通过IMAGE元素嵌入,全部声音数据通过AVDIO元素嵌入;那么开发一个知道如何处理这两个元素的浏览器并不是一件很难的事。实际上这正是HTML采用的方法,可是这样的方法不允许文档作者为了能更加清楚地描述它们所需的内容,而创建新的标记;例如PERSON元素碰巧就有一个PHOTO属性,该属性指向那人的JPEG格式图片。

再者,没有一个应用程序可理解所有可能的文件格式。大多数Web浏览器可以管理和读出GIF、JPEG、PNG图像文件,或许还包括一些其他格式的图像文件;但是它们在EPS、TIFF、FITS文件前都束手无策,对于是几百种普遍和特殊的图像格式就更加力不从心了。图11-1的对话框或许再熟悉不过了。

图11-1 当Netscape Navigator无法识别一种文件类型时将发生的事情

理想的情况是希望文档会通知应用程序外部实体的格式,因此不必去依赖应用程序来识别文件类型,或是靠具有魔力的数字或者是并不可靠的文件扩展名。此外,如果应用程序自身无法处理这种格式的图像,也可以为应用程序提供一些关于什么程序可用来显示图像的线索。

记号提供了部分解决这个问题的方法(尽管不能获得很好的支持)。记号描述非XML数据的格式。在DTD中,NOTATION声明规定特殊的数据类型。DTD在与元素、属性和实体同一层次上声明记号。每个记号声明都包含一个名字和一个外部标识符,语法结构如下:

<!NOTATION name SYSTEM "externalID">

name为文档中使用的特殊格式的标识符;externalID就是用来标识记号的有意义的字符串。例如,实体GIF图像的记号可以使用MIME类型:

<!NOTATION GIF SYSTEM "Image/gif">

也可以使用PUBLIC代替SYSTEM标识符,这样做就必须提供public ID和URL。例如:

<!NOTATION GIF PUBLIC

"-//IETF// NONSGML Media Type image/gif//EN"

"http://www.isi.edu/in-notes/iana/assignments/media-types/image/gif">

对于如何准确地作出外部标识,还存在激烈的争论。像图像/gif、文本/HTML之类的MIME类型是一种可能性;另一个建议是选择URL,或者其他的标准文档定位方式——像http://www.w3.org/TR/REC-html140/。第三个选择是使用正式的国际标准——如表示日期和时间的ISO 8601标准。某些情况下,可能ISBN或者国会图书馆为文献文档编目的方法更为适用。此外还有其余许多选择。

选取何种方式,取决于对文档生命期的期望值。例如,如果选择不普遍的格式,就不能依赖每个月都会改变的URL方式;如果希望文档在100年内都具有活跃的生命力,那么就该考虑使用在100年中都具有意义的标识符,而不是使用仅具有10年生命力的技术。

也可以使用记号来描述插入到文档中数据。例如,研究下面的DATA元素:

<DATE>05-07-06</DATE>

05-07-06到底表示哪一天?是公元1906年5月7日还是公元1906年7月5日?答案取决于是按美国格式还是欧洲格式理解这个日期。甚至也可能是2006年5月7日或者2006年7月5日。或者是公元6年5月7日,是西方鼎盛时期的罗马帝国的秋天和中国的汉朝。也有可能这个日期根本不是公元纪年,而是犹太历、穆斯林历法或者中国的农历。没有更多的信息,就无法确定其真实的意义。

为了避免这样混淆不清的情况,ISO 8601标准为表示日期规定了一个精确的方法。应用这种方法,在XML中,公元2006年7月5日写为20060705,或者是如下格式:

<DATE>20060705</DATE>

这种格式不是与每个人的想法都相同,对所有人都具有同等程度的迷惑性,不偏向任何一种文化(实际上仍然偏向西方传统日历)。

在DTD中声明记号,并且用记号属性描述嵌入XML文档中的非XML数据的格式。接着再研究日期的例子,清单11-1定义两种日期记号:ISO 8601和美国惯用格式。然后将NOTATION类型必需的FORMAT属性添加到每一个DATE元素中,用来描述特定元素的结构。

清单11-1:ISO 8601和美国惯用格式DATE元素

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

<!DOCTYPE SCHEDULE [

<!NOTATION ISODATE SYSTEM

"http://www.iso.ch/cate/d15903.html">

<!NOTATION USDATE SYSTEM

"http://es.rice.edu/ES/humsoc/Galileo/Things/gregorian_calendar

.html">

<!ELEMENT SCHEDULE (APPOINTMENT*)>

<!ELEMENT APPOINTMENT (NOTE, DATE, TIME?)>

<!ELEMENT NOTE (#PCDATA)>

<!ELEMENT DATE (#PCDATA)>

<!ELEMENT TIME (#PCDATA)>

<!ATTLIST DATE FORMAT NOTATION (ISODATE | USDATE) #IMPLIED>

]>

<SCHEDULE>

<APPOINTMENT>

<NOTE>Deliver presents</NOTE>

<DATE FORMAT="ISDATE">12-25-1999</DATE>

</APPOINTMENT>

<APPOINTMENT>

<NOTE>Party like it s 1999</NOTE>

<DATE FORMAT="ISODATE">19991231</DATE>

</APPOINTMENT>

</SCHEDULE>

记号不能强制作者使用记号描述的格式。因此需要提供除XML基本方法以外的几种语言方案——但是在相信作者能正确描述日期的简单应用场合,记号方法是有效的。


11.2 不可析外部实体

对所有的数据,特别是非文本数据,XML格式都不是理想的格式。例如,可以按下面所示的方式,把位图图像的每一个像素存为一个XML元素:

<PIXEL X="32"Y="28" COLOR="FF5E32"/>

可是,这肯定不是一个好主意。任何微小的错误都会导致气球图像文件的比例严重失衡。XML现在和将来都永远不可能让XML文档具有访问数据的能力,因此无法把所有数据按XML编码。

一个典型的Web页面可以引用GIF和JPEG图像、JAVA小程序、ActiveX控件、各种类型的声音等等。在XML中,因为XML处理器不会去尝试理解非XML格式的数据块,所以把这些数据块称为不可析实体。至多XML处理器通知应用程序存在这样的实体,并且为应用程序提供实体名和实体可能包含的内容(可是这并不是必须执行的动作)。

HTML页面通过各种定制的标记嵌入非HTML实体。图片由具有SRC属性的<IMG>标记引用,SRC属性提供图像文件的URL;JAVA程序由具有CLASS和CODEBASE属性的<APPLET>标记包括,CLASS和CODEBASE属性指向JAVA程序保存的文件和目录;<OBJECT>标记来嵌入CODEBASE属性引用,可从中找到目标数据的URI。每一种情况下,特定的预定义标记表示一种特定的内容。预定义属性包含其内容的URL。

XML应用程序可以但不是必须这样运作,实际上,除了特意为保持与落后的HTML之间的兼容性之外,大部分XML应用程序都不这样做。相反,XML应用程序使用不可析外部实体引用这些内容。不可析外部实体提供与非XML数据的实际位置的链接。接着文档中特定的元素利用其ENTITY属性与实体相连。

11.2.1 声明不可析实体

回忆第9章的内容,外部实体的声明看起来如下面的形式:

<!ENTITY SIG SYSTEM "http://metalab.unc.edu/xml/signature.xml">

可是,仅在URL指明的外部实体恰好为完整的XML文档的时候,才能接受这种格式。如果外部实体不是XML,则不得不使用NDATA关键字指定实体类型。例如,为了用LOGO名字连接GIF格式文件logo.gif,就需在DTD中放置如下的ENTITY声明:

<!ENTITY LOGO SYSTEM "logo.gif" NDATA GIF>

声明中的最终名字必须是DTD中声明的记号名,如本例中的GIF。记号将GIF类的名称与某种类型的外部标识符联系起来,外部标识符标识某种格式。如MIME类型、ISO标准式或者是格式规格的URL。例如,GIF的记号类似下面的形式:

<!NOTATION GIF SYSTEM "image/gif">

通常,作为习惯的表示方法,可以使用绝对或相对的URL指向外部实体。例如:

<!ENTITY LOGO SYSTEM "http://metalab.unc.edu/xml/logo.gif"

NDATA GIF>

<!ENTITY LOGO SYSTEM "/xml/logo.gif" NDATA GIF>

<!ENTITY LOGO SYSTEM "../logo.gif" NDATA GIF>

11.2.2 嵌入不可析实体

不能与用通用实体引用嵌入可析实体一样,在文档中的任意位置简单地嵌入不可析实体。例如,清单11-2就是一个不合法的是XML文档,因为LOGO是不可析实体。如果这里的LOGO是可析实体,本例就为有效的XML文档。

清单11-2:试图用通用实体引用嵌入不可析实体的无效XML文档

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

<!DOCTYPE DOCUMENT [

<!ELEMENT DOCUMENT ANY>

<!ENTITY LOGO SYSTEM "http://metalab.unc.edu/xml/logo.gif"

NDATA GIF>

<!NOTATION GIF SYSTEM "image/gif"

]>

<DOCUMENT>

&LOGO;

</DOCUMENT>

为了嵌入不可析实体,不采用如&LOGO;通用实体引用的方法;而是声明一个元素,把该元素作为不可析实体的占位符(例如IMAGE)。然后声明IMAGE元素属性SOURCE为ENTITY类型,SOURCE属性仅提供不可析实体名。如清单11-3所示。

清单11-3:正确嵌入不可析实体的合法的XML文档

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

<!DOCTYPE DOCUMENT [

<!ELEMENT DOCUMENT ANY>

<!ENTITY LOGO SYSTEM "http://metalab.unc.edu/xml/logo.gif"

NDATA GIF>

<!NOTATION GIF SYSTEM "image/gif"

<!ELEMENT IMAGE EMPTY>

<!ATTLIST IMAGE SOURNE ENTITY #REQUIRED>

]>

<DOCUMENT>

<IMAGE SOURNE="LOGO"/>

</DOCUMENT>

等到应用程序读取XML文档时,就可认出这个不可析实体且显示出来。应用程序也可能不显示不可析实体(当用户使图像载入失效时,Web浏览器将选择不显示图像)。

这些例子表明:空元素就像是为不可析实体准备的容器,可是这不是必须采用的方法。例如,假设有一个基于XML的公司ID系统,就是保安人员使用的查寻进入建筑物的人的系统;PERSON元素拥有NAME、PHONE、OFFICE、EMPLOYEE_ID子类和PHOTO ENTITY属性,如清单11-4所示。

清单11-4:具有PHOTO ENTITY属性的非空PERSON元素

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

<!DOCTYPE PERSON [

<!ELEMENT PERSON (NAME, EMPLOYEE_ID, PHONE, OFFICE)>

<!ELEMENT NAME (#PCDATA)>

<!ELEMENT EMPLOYEE_ID (#PCDATA)>

<!ELEMENT PHONE (#PCDATA)>

<!ELEMENT OFFICE (#PCDATA)>

<!NOTATION JPEG SYSTEM "image/jpg"

<!ENTITY ROGER SYSTEM "rogers.jpg" NDATA JPEG>

<!ATTLIST PERSON PHOTO ENTITY #REQUIRED>

]>

<PERSON PHOTO="ROGER">

<NAME>Jim Rogers</ NAME>

<EMPLOYEE_ID>4534</EMPLOYEE_ID>

<PHONE>X396</PHONE>

<OFFICE>RH 415A</OFFICE>

</PERSON>

这个例子看起来有点做作。实际上,使一个带有SOURCE属性的空DHOTO元素的成为PERSON元素的子元素,而不是PERSON元素的属性。再者,或许可以把这个DTD分割为内部和外部的子集。如清单11-5所示,外部子集声明元素、记号和属性。这些都是可以被不同的文档共享的部分。但是,实体从一个文档到另一个文档会发生改变,因此最好把实体放在如清单11-6显示的文档的内部DTD子集中。

清单11-5:外部DTD子集person.dtd

<!ELEMENT PERSON ( NAME, EMPLOYEE_ID, PHONE, OFFICE, PHOTO)>

<!ELEMENT NAME (#PCDATA)>

<!ELEMENT EMPLOYEE_ID (#PCDATA)>

<!ELEMENT PHONE (#PCDATA)>

<!ELEMENT OFFICE (#PCDATA)>

<!ELEMENT PHOTO EMPTY>

<!NOTATION JPEG SYSTEM "image/jpeg">

<!ATTLIST PHOTO SOURCE ENTITY #REQUIRED>

清单11-6:内含非空PERSON元素和一个内部DTD子集的文档

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

<!DOCTYPE PERSON [

<!ENTITY % PERSON _DTD SYSTEM "person.dtd">

%PERSON_DTD;

<!ENTITY ROGER SYSTEM "rogers.jpg" NDATA JPEG>

]>

<PERSON>

<NAME>Jim Rogers</NAME>

<EMPLOYEE_ID>4534</EMPLOYEE_ID>

<PHONE>X396</PHONE>

<OFFICE>RH 415A</OFFICE>

<PHOTO SOURCE="ROGER"/>

</PERSON>

11.2.3 嵌入多个不可析实体

在某些特殊场合下,一个单一的属性甚至一个标识号,可能需要引用不止一个的不可析实体。就可以声明占位符元素的属性为ENTITIES类型。ENTITIES属性值由空格分隔的多个不可析实体名组成,每个实体名都指向一个外部非XML格式数据资源,并且必须在DTD中声明所有实体。例如,可以用这种方法编写一个以幻灯放映元素来切换不同的图片,DTD需要如下形式的声明:

<!ELEMENT SLIDESHOW EMPTY>

<!ATTLIST SLIDESHOW SOURCES ENTITIES #REQUIRED>

<!NOTATION JPEG SYSTEM "image/jpeg"

<!ENTITY HARM SYSTEM "charm.jpg" NDATA JPEG>

<!ENTITY MARJORIE SYSTEM "marjorie.jpg" NDATA JPEG>

<!ENTITY POSSUM SYSTEM "possum.jpg" NDATA JPEG>

<!ENTITY BLUE SYSTEM "blue.jpg" NDATA JPEG>

然后,在文档中需要幻灯放映出现的位置上,就可插入如下标记:

<SLIDESHOW SOUR ES="CHARM MARJORIE POSSUM BLUE">

必须再次强调,这不是一个XML处理器(甚至任意处理器)可自动理解的具有魔力的方案,这仅仅是一种技巧,在嵌入文档中的非XML数据时,浏览器和其余应用程序可能采用也可能不采用的技术。


11.3 处理指令

指令经常过多地应用于支持HTML的私有范围,如服务端嵌入、浏览器定制脚本语言、数据库模板和其余许多超出HTML标准范围的项目。出于这些目的而使用注释的好处是:其余系统可以简单地忽略它们无法理解的外来数据。这种方法的不利之处在于:剥离了注释的文档可能不再是原来的文档了,并且仅仅作为文档的注释会被误解为这些私有范围的输入数据。为了避免滥用注释,XML提供了处理指令的方法,作为在文件中嵌入信息的明确机制,用于私有的应用程序而不是XML语法分析器或浏览器。其余用途包括,处理指令可以提供关于如何查看不可析外部实体的附加信息。

处理指令就是位于<? 和?>标记之间的一行文本。处理指令中的文本只需要如下句法结构,以XML名开头,其后紧跟空格,空格后为数据。XML名可以是应用程序的实际名字(如latex),或者是在DTD中指向应用程序的记号名(例如LATEX),在DTD中的LATEX声明具有如下形式:

<!NOTATION LATEX SYSTEM "/usr/local/bin/latex">

甚至这个名字可以是可被应用程序识别的其他名字。对于使用处理指令的应用程序来说,各个细节部分是非常明确的。确实,大部分依赖处理指令的应用程序在处理指令的内容上利用更多的结构。例如,研究如下在IBM的Bean Markup Language中使用的处理指令:

<?bmlpi register demos.calculator.EventSourceText2Int?>

使用处理指令的应用程序名字为bmlpi。赋予应用程序的数据为字符串register demos.calculator.EventSourceText2Int,这些数据将包含全部合格的Java类名。这就告诉名为bmlpi的应用程序使用Java类demos.calculator.EventSourceText2Int,将操作事件转换为整数。如果bmlpi在读取文档是遇到这个处理指令,将载入类demos.calculator.EventSourceText2Int,从此往后利用该类元素将事件转化为整数。

如果这听起来很明确也很详细的话,那是因为它们原来就是如此。处理指令不是文档的通用结构部分,它们为特定的应用程序提供额外的明确的信息,而不是为所有读取该文档的应用程序提供信息。如果其余一些应用程序在读取文档时遇到这个说明,它们将简单地跳过这些说明。

处理指令除了不能位于标记或者CDATA字段之内,可以放在XML文档中的任意部位。它们可以位于序进程、DTD、元素内容中,甚至可在文档结束标记之后。因为处理指令不是元素,所以不会影响文档的树型结构。没有必要打开或者关闭处理指令,也没有必要考虑它们在其他元素中的嵌套问题。处理指令不是标记,不会对元素进行限定。

到此我们已经很熟悉了一个处理指令的例子:xml-stylesheet处理指令把样式单与文档相结合:

<?xml-stylesheet type="text/xsl" href="baseball.xsl"?>

虽然这些例子中的处理指令位于序进程中,但是处理指令可以在文档的任意位置出现。因为处理指令不是元素,所以没有必要声明为包含它们元素的子类元素。

以字符串xml开头的处理指令在XML规范中留作特殊的用途。此外,在处理指令中,可以自由使用除文档结束标记符(?>)外的任意名字和任意文本字符串。例如,下面的例子就是完全有效的处理指令:

<?gcc HelloWorld.c ?>

<?acrobat document="passport.pdf"?>

<?Dave remember to replace this one?>

请记住XML处理器不会对处理说明进行任何处理,仅仅是把他们传送给应用程序。应用程序决定如何处理这些说明。大部分应用程序简单地跳过他们无法理解的处理说明。

有些时候了解不可析实体的类型还是不够的。还需要了解应用程序如何运行和查看实体,以及需要提供给应用程序的参数是什么。这些信息都可以通过处理指令来提供。因为处理指令所包含的数据没有什么限制,所以在制定说明时就相对容易一些,这些说明是决定记号中列出的外部程序将采取什么行为。

这样的处理指令可以是查看数据块的程序名,也可以是几千字节的配置信息。应用程序和文档的作者当然必须采用同样的方法来确定何种不可析外部实体采取何种处理指令。清单11-7显示一个方案,该方案使用一个处理指令和PDF记号来通知Acrobat Reader关于物理纸张的PDF格式,以便Acrobat Reader显示PDF的内容。

清单11-7:在XML中嵌入PDF文档

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

<!DOCTYPE PAPER [

<!NOTATION PDF PUBLIC

"-//IETF//NONSGML Media Type application/pdf//EN"

"http://www.isi.edu/in-notes/iana/assignments/media-types/

application/pdf">

<!ELEMENT PAPER (TITLE, AUTHOR+, JOURNAL, DATE_RECEIVED,

VOLUME, ISSUE, PAGES)>

<!ATTLIST PAPER CONTENTS ENTITY #IMPLIED>

<!ENTITY PRLTAO000081000024005270000001 SYSTEM

"http://ojps.aip.org/journal_cgi/getpdf?KEY=PRLTAO&amp;cvips=PR

LTA0000081000024005270000001"

NDATA PDF>

<!ELEMENT AUTHOR (#PNDATA)>

<!ELEMENT JOURNAL (#PNDATA)>

<!ELEMENT YEAR (#PNDATA)>

<!ELEMENT TITLE (#PNDATA)>

<!ELEMENT DATE_RE EIVED (#PNDATA)>

<!ELEMENT VOLUME (#PNDATA)>

<!ELEMENT ISSUE (#PNDATA)>

<!ELEMENT PAGES (#PNDATA)>

]>

<?PDF acroread?>

<PAPER CONTENTS="PRLTAO000081000024005270000001">

<TITLE>Do Naked Singularities Generically Occur in

Generalized Theories of Gravity?</TITLE>

<AUTHOR>Kengo Maeda</AUTHOR>

<AUTHOR>Takashi Torii</AUTHOR>

<AUTHOR>Makoto Narita</AUTHOR>

<JOURNAL>Physical Review Letters</JOURNAL>

<DATE_RE EIVED>19 August 1998</DATE_RE EIVED>

<VOLUME>81</VOLUME>

<ISSUE>24</ISSUE>

<PAGES>5270-5273</PAGES>

</PAPER>

任何时候都该记住不是所有的处理器程序都会以你希望的方式去对待这个例子。实际上,大部分处理器都不会。可是,从让一个应用程序支持PDF文件和其余非XML媒体类型的角度来说,这也是一个值得考虑的方法。


11.4 DTD的条件部分

在创建DTD和文档的时候,或许需要文档中没有反映DTD的部分作一些注释。除了直接使用注释,也可以把DTD中的特定声明组放置在IGNORE指令中的方式,从而忽略该声明组。句法结构如下:

<![ IGNORE

declarations that are ignored

]]>

像通常一样,空格不会对句法结构产生实质性的影响,但是必须保证开始符(<![IGNORE)和结束符(]]>)占单独的一行,以便阅读。

可以忽略任意声明或一组声明——元素、实体、属性甚至包括其他的IGNORE块,但是必须忽略整个声明。IGNORE的构造必须完整包含从DTD中移走的全部声明。不能仅忽略声明的某个局部(例如在不可析实体声明中的NDATA GIF)。

也可以指定包括声明的某个特定部分,也就是说不忽略的部分。INCLUDE指示的句法结构与IGNORE相似,但是关键词不同:

<![ INCLUDE

declarations that are included

]]>

当INCLUDE位于IGNORE之内的时候,INCLUDE和声明都被忽略。当IGNORE位于INCLUDE内,位于IGNORE之内的声明依然被忽略。换一种说法就是INCLUDE不会覆盖IGNORE。

上述给出的情形中,或许会为INCLUDE的存在表示奇怪。简单地移走INCLUDE块,仅留下它们的内容,没有任何DTD会发生改变。INCLUDE好像完全是多余的。可是,对于无法单独使用IGNORE的参数实体引用的情形中,同时应用IGNORE和INCLUDE不失为一个灵巧的方法。首先定义一个如下的参数实体引用:

<!ENTITY % fulldtd "IGNORE">

将元素包裹在下列结构中,就可将其忽略:

<![ %fulldtd;

declarations

]]>

%fulldtd;参数实体引用求出的值为IGNORE,因此声明被忽略。现在,假设对一个单词作出修改,把fulldtd从IGNORE改为INCLUDE,如下所示:

<!ENTITY % fulldtd "INCLUDE">

所有的IGNORE块立即转换为INCLUDE块。实际上,就像是一系列开关,可以打开或者关闭声明块。

在本例中,仅使用了一个开关——fulldtd。可以在DTD中的多个IGNORE/INCLUDE块中使用这种开关。也可以拥有许多可根据不同条件选择开或关的不同IGNORE/INCLUDE块。

当设计其余DTD内含的DTD时,这种能力特别有用。通过改变参数实体开关值,最后的DTD可以改变嵌入的DTD行为。


11.5 本章小结

在本章中,学习了如何通过记号、不可析外部实体和处理指令的方法,把非XML数据与XML文档相结合。具体地说,学习了下述概念:

·         记号描述非XML数据的格式。

  • 不可析外部实体为容纳非XML文本和数据的储存单元。
  • 使用ENTITYENTITIES属性可在文档中包括不可析外部实体。
  • 处理指令包含不作任何改变从处理器传送到最终应用程序的说明。S
  • INCLUDEIGNORE块在文档进行语法分析的时候,分别指定是否处理其中包括的DTD中的声明。

在本书的后面几个部分可以看到更多拥有DTD的文档的例子。但是关于DTD的最基本的句法结构和用法,在本章中已经讨论完毕。在本书的第三部分,我们开始讨论XML的样式语言,从下一章的级联样式单(第一级)开始。


 

posted on 2008-01-15 09:57  liang--liang  阅读(1332)  评论(0编辑  收藏  举报