XML教程

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

namereplacement 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-1ERH内部通用实体引用

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

注意其中的通用实体引用,即使COPYRIGHTTITLE声明为仅接受#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 "&#38;#60;">

<!ENTITY gt "&#62;">

<!ENTITY amp "&#38;#38;">

<!ENTITY apos "&#39;">

<!ENTITY quot "&#34;">

9.3 外部通用实体

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

仅使用内部实体的文档非常类似于HTML模式。文档的完整文本存放于单一文件中,图像、JAVA小程序、声音和非HTML数据也可链接入文件中,但至少在文件中要有所有的文本。当然,HTML模式存在一些问题。特别在文档中嵌入动态信息的过程是一件非常困难的事情。可通过使用CGIJAVA小程序所爱好的数据库软件、服务器方面提供的手段和其他各种各样的方法嵌入动态信息,但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-3XML签字文件

<?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-4SIG外部通用实体引用

<?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中的数据。

9.5 外部参数实体

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

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

可用外部参数实体把较小的DTD组成大型的DTD。也就是说,一个外部DTD可以链接到另一外部DTD,第二个DTD引入第一个DTD中声明的元素和实体。尽管严禁使用循环--若DTD2引用DTD1,则DTD1不能引用DTD2&#0;&#0;但嵌套的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;

 

9.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.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-9ChrisHoiles.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 DTDmets.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文档显示1998New York Mets队中的所有球员。

清单9-11:具有从外部实体中载入的队员数据的New York Metsmets.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:球员的DTDplayers.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的完整版本要依靠33DTD1000多个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之上。这是对外部可析实体用途的一种限制。无论如何,当学习了XLinksXPointers后,可以明白创建大型、复杂文档的其他方法。然而,那些技术不是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需具有上一章所研究的相同的通用形式。那就是说,开头为文档类型声明,且可包含ELEMENTATTLISTENITITY声明。与有效文档的区别在于处理器仅处理其ENTITY声明。

9.7.1 内部实体

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

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

清单9-15DTD中的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元素都具有OCCUPANTSTREETCITYZIP属性值,但是却无处可寻。基本元素本应为DOCUMENT,而不是ADDRESSDOCUMENT元素本应包含的TITLESIGNATURE均未在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;t预定义为&thorn;y预定义为&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 "&#192; "><!-capital A, grave accent ->

<!ENTITY Aacute "&#193; "><!-capital A, acute accent ->

<!ENTITY Acirc "&#194; "><!-capital A, circumflex accent ->

<!ENTITY Atilde "&#195; "><!-capital A, tilde ->

<!ENTITY Auml "&#196; "><!-capital A, dieresis umlaut ->

<!ENTITY Aring "&#197; "><!-capital A, ring ->

<!ENTITY AElig "&#198; "><!-capital AE diphthong ligature->

<!ENTITY Ccedil "&#199; "><!-capital C, cedilla ->

<!ENTITY Egrave "&#200; "><!-capital E, grave accent ->

<!ENTITY Eacute "&#201; "><!-capital E, acute accent ->

<!ENTITY Ecirc "&#202; "><!-capital E, circumflex accent ->

<!ENTITY Euml "&#203; "><!-capital E, dieresis umlaut ->

<!ENTITY Igrave "&#204; "><!-capital I, grave accent ->

<!ENTITY Iacute "&#205; "><!-capital I, acute accent ->

<!ENTITY Icirc "&#206;"><!-capital I, circumflex accent ->

<!ENTITY Iuml "&#207;"><!-capital I, dieresis umlaut ->

<!ENTITY ETH "&#208;"><!-capital Eth, Icelandic ->

<!ENTITY Ntilde "&#209;"><!-capital N, tilde ->

<!ENTITY Ograve "&#210;"><!-capital O, grave accent ->

<!ENTITY Oacute "&#211;"><!-capital O, acute accent ->

<!ENTITY Ocirc "&#212;"><!-capital O, circumflex accent ->

<!ENTITY Otilde "&#213;"><!-capital O, tilde ->

<!ENTITY Ouml "&#214;-><!-apital O dieresis/umlaut mark->

<!ENTITY Oslash "&#216;"><!-capital O, slash ->

<!ENTITY Ugrave "&#217;"><!-capital U, grave accent ->

<!ENTITY Uacute "&#218;"><!-capital U, acute accent ->

<!ENTITY Ucirc "&#219;"><!-capital U circumflex accent ->

<!ENTITY Uuml "&#220;"><!-capital U dieresis umlaut ->

<!ENTITY Yacute "&#221;"><!-capital Y, acute accent ->

<!ENTITY THORN "&#222;"><!-capital THORN, Icelandic ->

<!ENTITY szlig "&#223;"><!-small sharp s, (sz ligature) ->

<!ENTITY agrave "&#224;"><!-small a, grave accent ->

<!ENTITY aacute "&#225;"><!-small a, acute accent ->

<!ENTITY acirc "&#226;"><!-small a, circumflex accent ->

<!ENTITY atilde "&#227;"><!-small a, tilde ->

<!ENTITY auml "&#228;"><!-small a dieresis/umlaut mark->

<!ENTITY aring "&#229;"><!-small a, ring ->

<!ENTITY aelig "&#230;"><!-small ae, diphthong ligature ->

<!ENTITY ccedil "&#231;"><!-small c, cedilla ->

<!ENTITY egrave "&#232;"><!-small e, grave accent ->

<!ENTITY eacute "&#233;"><!-small e, acute accent ->

<!ENTITY ecirc "&#234;"><!-small e, circumflex accent ->

<!ENTITY euml "&#235;"><!-small e, dieresis or umlaut ->

<!ENTITY igrave "&#236;"><!-small i, grave accent ->

<!ENTITY iacute "&#237;"><!-small i, acute accent ->

<!ENTITY icirc "&#238;"><!-small i, circumflex accent ->

<!ENTITY iuml "&#239;"><!-small i, dieresis or umlaut ->

<!ENTITY eth "&#240;"><!-small eth, Icelandic ->

<!ENTITY ntilde "&#241;"><!-small n, tilde ->

<!ENTITY ograve "&#242;"><!-small o, grave accent ->

<!ENTITY oacute "&#243;"><!-small o, acute accent ->

<!ENTITY ocirc "&#244;"><!-small o, circumflex accent ->

<!ENTITY otilde "&#245;"><!-small o, tilde ->

<!ENTITY ouml "&#246;"><!-small o, dieresis or umlaut->

<!ENTITY oslash "&#248;"><!-small o, slash ->

<!ENTITY ugrave "&#249;"><!-small u, grave accent ->

<!ENTITY uacute "&#250;"><!-small u, acute accent ->

<!ENTITY ucirc "&#251;"><!-small u, circumflex accent ->

<!ENTITY uuml "&#252;"><!-small u, dieresis or umlaut ->

<!ENTITY yacute "&#253;"><!-small y, acute accent ->

<!ENTITY thorn "&#254;"><!-small thorn, Icelandic ->

<!ENTITY yuml "&#255;"><!-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:不带DTDSignature样板

<?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中声明属性,以及如何将约束条件附加于属性值进行限制。