《XML实用大全》
1.4.1 超文本标记语言(Hypertext Markup Language)
1.4.2 级联样式单(Cascading Style Sheets)
1.4.3 可扩展的样式语言(Extensible Style Language)
2.1.1 化学标记语言(Chemical Markup Language)
2.1.2 数学标记语言(Mathematical Markup Language)
4.2.2 联赛(League)、(分部)Division和(球队)Team数据的XML化
10.7.2 在DTD中声明DIVISION和LEAGUE属性
12.11.4 background-attachment属性
14.9.2 使用xsl:element将元素插入到输出文档中
14.9.3 使用xsl:attribute将特性插入到输出文档中
14.19.3 使用xsl:stylesheet在文档中嵌入样式单
第一部分 XML简介
本部分包括以下各章:
第1章——XML概览
第2章——XML应用简介
第3章——第一个XML文档
第4章——数据的结构化
第5章——特性、空标记和XSL
第6章——结构完整的XML文档
第7章——外国语言与非罗马文字
第1章 XML概览
本章将向读者介绍XML的基本知识以及概略地解释什么是XML以及如何使用XML。还要向读者说明如何将各种不同的XML表达式组合在一起,XML文档是如何创建的并如何向人们发送这种文档。
本章的主要内容包括:
· 什么是XML
- 为什么开发人员对XML感到激动
- XML文档的“生命”
- 相关的技术
1.1 什么是XML
XML代表Extensible Markup Language(eXtensible Markup Language的缩写,意为可扩展的标记语言)。XML是一套定义语义标记的规则,这些标记将文档分成许多部件并对这些部件加以标识。它也是元标记语言,即定义了用于定义其他与特定领域有关的、语义的、结构化的标记语言的句法语言。
1.1.1 XML是元标记语言
关于XML要理解的第一件事是,它不只是像超文本标记语言(Hypertext Markup Language,HTML)或是格式化的程序。这些语言定义了一套固定的标记,用来描述一定数目的元素。如果标记语言中没有所需的标记,用户也就没有办法了。这时只好等待标记语言的下一个版本,希望在新版本中能够包括所需的标记,但是这样一来就得依赖于软件开发商的选择了。
但是XML是一种元标记语言。用户可以定义自己需要的标记。这些标记必须根据某些通用的原理来创建,但是在标记的意义上,也具有相当的灵活性。例如,假如用户正在处理与家谱有关的事情,需要描述人的出生、死亡、埋葬地、家庭、结婚、离婚等,这就必须创建用于每项的标记。新创建的标记可在文档类型定义(Document Type Definition,在以后的篇幅中常简称为DTD)中加以描述。在本书的第二部分中将会学到有关DTD的更多的知识。现在,只需把DTD看作是一本词汇表和某类文档的句法。例如,在Peter Murray-Rust的Chemical Markup Language (化学标记语言,简写为CML)中的MOL.DTD文件中描述了词汇表和分子科学的句法:其中包括chemistry(化学)、crystallography(结晶学)、solid state physics(固体物理)等词汇。它包括用于atoms(原子)、molecules(分子)、bonds(化学键)、spectra(光谱)等的标记。这个DTD可与分子科学领域中的许多不同的人共享。对于其他领域也有其他的DTD,用户还可以创建自己的DTD。
XML定义了一套元句法,与特定领域有关的标记语言(如MusicML、MathML和CML)都必须遵守。如果一个应用程序可以理解这一元句法,那么它也就自动地能够理解所有的由此元语言建立起来的语言。浏览器不必事先了解多种不同的标记语言使用的每个标记。事实是,浏览器在读入文档或是它的DTD时才了解了给定文档使用的标记。关于如何显示这些标记的内容的详细指令是附加在文档上的另外的样式单提供的。例如,考虑薛定格(Schrodinger)方程:
科学论文中充满了这一类方程,但是科学家还必须等待多年,才能让浏览器的开发商支持书写最基本的数学公式所需的标记。音乐家也有同样的局限性,因为Netscape Navigator和Internet Explorer还都不支持乐谱。
有了XML就意味着不必等待浏览器的开发商来满足用户的需要了。用户可以创建自己需要的标记,当需要时,告诉浏览器如何显示这些标记就可以了。
1.1.2 XML描述的是结构和语义,而不是格式化
关于XML要了解的第二件事是,XML标记描述的是文档的结构和意义。它不描述页面元素的格式化。可用样式单为文档增加格式化信息。文档本身只说明文档包括什么标记,而不是说明文档看起来是什么样的。
作为对照,HTML文档包括了格式化、结构和语义的标记。<B>就是一种格式化标记,它使其中的内容变为粗体。<STRONG>是一种语义标记,意味着其中的内容特别重要。<TD>是结构标记,指明内容是表中的一个单元。事实上,某些标记可能具有所有这三种意义。<H1>标记可同时表示20磅的Helvetica字体的粗体、第一级标题和页面标题。
例如,在HTML中,一首歌可能是用定义标题、定义数据、无序的列表和列表项来描述的。但是事实上这些项目没有一件是与音乐有关的。用HTML定义的歌曲可能如下:
<dt>Hot Cop
<dd> by Jacques Morali Henri Belolo and Victor Willis
<ul>
<li>Producer: Jacques Morali
<li>Publisher: PolyGram Records
<li>Length: 6:20
<li>Written: 978
<li>Artist: Village People
</ul>
而在XML中,同样的数据可能标记为:
<SONG>
<TITLE>Hot Cop</TITLE>
<COMPOSER>Jacques Morali</COMPOSER>
<COMPOSER>Henri Belolo</COMPOSER>
<COMPOSER>Victor Willis</COMPOSER>
<PRODUCER>Jacques Morali</PRODUCER>
<PUBLISHER>PolyGram Records</PUBLISHER>
<LENGTH>6:20</LENGTH>
<YEAR> 978</YEAR>
<ARTIST>Village People</ARTIST>
</SONG>
在这个清单中没有使用通用的标记如<dt>和<li>,而是使用了具有意义的标记,如<SONG>、<TITLE>、<COMPOSER>和<YEAR>等。这种用法具有许多优点,包括源码易于被人阅读,使人能够看出作者的含义。
XML标记还使非人类的自动机器人易于找出文档中的所有歌曲。在HTML中,机器人只能告诉我们这个元素是dt。机器人不能决定dt到底代表一首歌的题目还是定义,抑或只是一些设计者喜爱的缩进文本格式。事实上,单一文档中可以很好地包括带有三种意义的各种dt元素。
可以选择XML的元素名称,以便使其在附加的上下文中具有额外的意义。例如,元素名称可以是数据库的域名。XML比HTML更为灵活而且适用于各种应用,因为有限数目的标记不必用于许多不同的目的。
1.2 为什么开发人员对XML感到激动
XML使许多只利用HTML难以解决的任务变得简单,使只利用HTML不可能完成的任务得以完成。因为XML是可扩展的,开发人员喜爱XML有许多原因。到底是哪个更令人感兴趣,取决于每个人的需要。但有一点是肯定的,一旦用上XML,就可发现,它正是解决许多令人感到棘手的问题的有力工具。本节研究一些令开发人员激动的一般应用。在第2章中,还会看到已经用XML开发出来的一些特殊应用。
1.2.1 设计与特定领域有关的标记语言
XML允许各种不同的专业(如音乐、化学、数学等)开发与自己的特定领域有关的标记语言。这就使得该领域中的人们可以交换笔记、数据和信息,而不用担心接收端的人是否有特定的软件来创建数据。特定领域的开发人员甚至可以向本领域外的人发送文档,有相当的理由可以认为,至少接受文档的人能够查看文档的内容。
更进一步说,为特别的领域创建标记语言不会产生“病件”(bloatware)或是对于本专业外的人来说产生不必要的复杂性。一般人也许不会对电力工程图感兴趣,但是电力工程师却对此感兴趣。一般人也许不需要在他的Web页面中包括乐谱,但是作曲家却要这样做。XML让电力工程师描述他们的电路图,让作曲家写乐谱,而不会互相干扰。对于浏览器开发商来说,都不需要对特定的领域提供特殊的支持,也不需要提供复杂的插件。这一点现在已经实现了。
1.2.2 自描述数据
过去40年来的大多数计算机数据都丢失了,不是因为自然损害或是备份介质的磨损(虽然这也是一个问题,这个问题在XML中也没有解决),而只是因为没有人来写出如何读取这些数据介质和格式的文档。在十年前的5.25英寸的软盘上的Lotus 1-2-3文档在今天的大多数公司内都已经读不出来了。以不常用的格式保存的二进制数据,如Lotus Jazz 也许会永远地消失了。XML在基本水平上使用的是非常简单的数据格式。可以用100%的纯ASCII文本来书写,也可以用几种其他定义好的格式来书写。ASCII文本是几乎不会“磨损”的。丢失一些字节甚至是相当多的字节,剩下的数据还是可以读取的。这就与许多格式形成了鲜明的对比,如压缩数据或是串行的Java对象,这些数据即使丢失一个字节,剩余的数据也变得不可读取了。
从高水平上来说,XML是自描述的。假设在23世纪有一个信息考古学者,他在软盘上发现了如下一大段经过时间的“冲刷”而保存下来的XML代码:
<PERSON ID="p1100" SEX="M">
<NAME>
<GIVEN>Judson</GIVEN>
<SURNAME> McDaniel</SURNAME>
</NAME>
<BIRTH>
<DATE>2 Feb 1834</DATE> </BIRTH>
<DEATH>
<DATE>9 Dec 1905</DATE> </DEATH>
</PERSON>
即使这个考古学家不熟悉XML,但假设他可以讲20世纪时的英语,那么就可以很好地了解名为Judson McDaniel的人,此人出生在1834年2月21日,而死于1905年12月9日。事实上,数据中有一些空白或是损坏,还是可以得到这些信息。但对于专有格式的电子表格或是字处理程序的格式,就不是这么回事了。
更进一步说,XML有很好的规格文档。W3C的XML 1.0 规范和大量的论文书籍,如本书,都向人们准确地说明如何来阅读XML数据。没有什么秘密使得人们发生失误。
1.2.3 应用间交换数据
由于XML是非专有的并易于阅读和编写,就使得它成为在不同的应用间交换数据的理想格式。当前正在开发的一种这样的格式是Open Financial Exchange(开放财务交换,简写为OFX)格式。OFX是为个人财务程序,如Microsoft Money和Quicken交换数据而设计的。数据可以在程序间来回交换,还可以与银行、经纪事务所和其他机构交换数据。
有关OFX的内容将在第2章加以讨论。
正如上面所讨论的一样,XML使用的是非专有的格式,不受版权、专利、商业秘密或是其他种类的知识产权的限制。XML的功能是非常强大的,同时对于人类或是计算机程序来说,都容易阅读和编写。因而成为交换语言的首选。
使用XML而不是专有格式,人们就可以利用任何理解XML的工具来处理数据。还可以为不同的目的使用不同的工具。一个程序用来查看而另一程序用来编辑。XML使用户不必因为数据已经用专有格式编写好了或是接受数据的人只接受专有格式而限制在一个特定的程序上。
例如,许多出版商需要用Microsoft Word发稿。这就意味着大多数作者必须使用Word,即使他们更愿意使用WordPerfect或是Nisus Writer。因而这就使得其他出版字处理软件的公司陷入困境,除非他们的软件能够读写Word文件。由于要想达到这个目的,就得让开发人员反向了解未载入文档的Word文件格式,这使得在时间和资源上的投资大增。大多数其他字处理软件具有有限的读写Word文件的能力,但是通常都会丢失图形、宏、样式、修订标记和其他重要的特性。问题就在于Word文档的格式是不公开的专有格式,而且还在不断地变化。这样Word就成为最后的胜利者,即使作者更喜爱其他的更简单的程序。如果在XML中开发了一种通用的字处理格式,作者们就会使这个程序成为他们的首选程序。
1.2.4 结构化和集成的数据
XML对于大型和复杂的文档是理想的,因为数据是结构化的。这不仅使用户可以指定一个定义了文档中的元素的词汇表,而且还可以指定元素之间的关系。例如,如果要将销售客户的地址一起放在Web页面上,这就需要有每个客户的电话号码和电子邮件地址。如果向数据库中输入数据,可确保没有漏下的字段。还需要每部书都有一个作者。当没有数据输入时还可提供一个缺省值。XML也提供客户端的包括机制,可以根据多种来源集成数据并将其作为一个文档来显示。数据还可以马上进行重新排列。数据的各个部分可以根据用户的操作显示或隐藏。当处理大型的信息仓库,比如关系型数据库时是极为有用的。
1.3 XML文档的“生命”
从基本上来说,XML是一种文档格式。它是一系列的关于XML文档看起来是什么样子的规则。与XML标准的符合程度有两种级别。第一级是结构完整性,第二级是正确性。本书的第一部分向读者介绍如何编写结构完整的文档。而第二部分向读者介绍如何编写具有正确性的文档。
HTML是设计用于Internet上和Web页面内部的文档格式。正如本书所叙述的,XML当然也可以用在这些方面。但是XML具有更为广泛的适用性。正如前面所讨论的,可用于字处理器的保存文件的格式,可用于不同程序间的数据交换格式,可用作与Intranet模板一致化的工具,还可用作以人类可读的形式保存数据的手段。
虽然如此,如所有的数据格式一样,XML在有用之前也需要程序和内容。因而对于数据看起来应该是什么样子的,光了解XML本身还是不够的,这不光是一个规范所能解决的问题。用户还需要了解XML文档是如何编辑的,处理程序是如何读取XML文档并将其读取的信息传送给应用程序的,以及这些应用程序是如何处理数据的。
1.3.1 编辑器
XML文档大多数情况下都是用编辑器创建的。编辑器可以是基本的文本编辑器如Notepad(记事本)或是vi,这些编辑器并不真正理解XML。另一方面,也可以用所见即所得的编辑器,如Adobe FrameMaker,这种编辑器可将用户完全隔离于XML底层格式之外。另外也可以是一个结构化的编辑器,如JUMBO,它可将XML文档显示为树状结构。对于最重要的部分,有趣的编辑器并不是太有用,因而本书将注意力集中于用普通的文本编辑器来编写XML文档。
其他程序也可以创建XML文档。例如,本书在讲述设计新的DTD的稍后章节中将可看到某些XML数据可直接从FileMaker的数据库中得出。在这种情况下,数据是先输入到FileMaker数据库中的,然后FileMaker的计算字段将数据转换为XML。一般来说,XML与数据库可协同工作得很好。
准确地说,我们可在第23章“设计新的XML应用”中看到这种情况。
无论在何种情况下,都是编辑器或其他程序创建了XML文档。通常,这一文档是某种计算机硬盘上的实际文件。但也不是必须如此。例如,文档可能是数据库中的记录或是字段,或者可能是从网络上接收来的字节流。
1.3.2 语法分析程序和处理程序
XML的语法分析程序(即所谓的XML处理程序)读取文档并检查其中包括的XML是否是结构完整的。它还要确定文档是否合法,虽然这种测试不是必需的。这种测试的详细情况将在本书的第二部分中讲述。如果文档通过了测试,则处理程序就将文档转换为元素的树状结构。
1.3.3 浏览器和其他工具
最后语法分析程序将树状结构或是树的节点传送给用户端应用程序。这个应用程序可能是浏览器,如Mozilla,或是其他能够理解如何处理数据的程序。如果这个应用程序是浏览器的话,数据就显示给用户。但是其他程序也可以接受数据。例如,可将数据翻译成数据库的输入、一系列要演奏的乐谱或是要运行的Java 程序。XML是非常灵活的,可以用于许多不同的目的。
1.3.4 处理过程总结
总结一下,首先由一个编辑器创建了XML文档。语法分析程序将树状结构传送给浏览器,由浏览器显示出来。图1-1显示了这个处理过程。
图1-1 XML文档的处理流程
请注意,所有这些部分都是独立的,互相分离的。将这些部分联系在一起的是XML文档。改变编辑程序与终端应用程序无关。事实上,很可能在编写文档时就根本不知道最终的应用程序是什么。可能是最终用户来阅读文档,也可能是数据库从中提取数据,甚至还可能是未发明出来的程序,也可能是所有这些情况。文档与读取它的程序是无关的。
HTML也在某种程度上与读写它的程序无关,但是它只适用于浏览器。其他应用,如数据库输入已经不在它的有效范围之内了。例如,HTML没有提供某种方法来包括所需的内容,如每本书都必须有ISBN号码一样。在XML中可以包括这个。甚至可以强制安排元素出现的顺序(如第二级标题必须出现在第一级之后)。
.4 相关技术
XML并不是在真空中操作的。如果将XML用于不只是一种数据格式的话,就需要与多种相关的技术相互作用。这些技术包括为了向后兼容老式的浏览器的HTML、CSS(Cascading Style Sheet,级联样式单)和XSL(eXtensible Style Languages,可扩展的样式语言)、URL和URI、XLL(eXtensible Linking Language,可扩展的链接语言)和Unicode字符集。
1.4.1 超文本标记语言(Hypertext Markup Language)
Mozilla 5.0和Internet Explorer 5.0是首先对XML提供支持(虽然并不完全)的浏览器。但是,要使大多数用户升级到这两种浏览器的新版本上来,可能还要花两年的时间。(我的妻子Beth在1999年还在使用Netscape 1.1。)因而在今后一段时间内,还需要将XML内容转化为经典的HTML。
因而,在转向XML之前,对使用HTML还不应感到别扭。用户不必完全成为一个时髦的图形设计者,但是应该了解如何将一个页面与另一个页面链接起来,了解如何在文档中包括图像,如何使文本变成粗体等等。由于HTML是XML的最普通的输出格式,所以对HTML了解得越多,也就越容易了解如何创建所需的效果。
另一方面,如果已经熟悉了利用表格或是单像素的GIF来安排页面上的对象,或是如果开始借助于画出草图而不是借助于内容来创建Web站点的话,那么也就必须要忘记某些坏的习惯。正如前面所讨论的一样,XML将文档的内容与文档的外观相分离。首先开发内容,然后再用样式单将格式附加其上。将内容与样式分开是非常有效的技术,这既改善了文档内容也改善了文档外观。除此之外,还允许作者和设计者更加互相独立地工作。但是,对于设计Web站点来说,确实需要有不同的思路,如果涉及多人的话,或许要利用不同的项目管理技术。
1.4.2 级联样式单(Cascading Style Sheets)
由于XML允许在文档中包括任意的标记,所以对于浏览器来说,没有办法事先知道如何显示每个元素。当将文档送给用户时,还要向用户发送样式单,通过样式单告诉浏览器如何格式化每个元素。可以使用的一种样式单是级联样式单( Cascading Style Sheet ,简写为CSS)。
CSS开始是为 HTML设计的,它定义字号、字族、字重、段落缩进、段落对齐和其他样式等格式化属性,这些属性都可以施加到个别的元素上。例如,CSS允许HTML文档来指定所有的H1元素应该被格式化为32磅、中间对齐的Helvetica字体的粗体。单独的样式可以施加到大多数HTML标记上,它能够覆盖浏览器的缺省设置。多个样式单可施加到一个文档上,而多个样式也可用于单个元素上。样式根据特定的一套规则级联起来。
CSS规则和属性将在第12章“级联样式单,第一级”和第13章“级联样式单,第二级”中详细介绍。
向XML施加CSS规则是很容易的。只要改变施加规则于其上的标记名称即可。Mozilla 5.0直接支持CSS样式单与XML的结合,虽然到目前为止,此浏览器时常发生崩溃。
1.4.3 可扩展的样式语言(Extensible Style Language)
可扩展的样式语言(Extensible Style Language,简写为XSL)是更为先进的专门用于XML文档的样式单语言。XSL文档本身就是结构完整的XML文档。
XSL文档包括一系列的适用于特定的XML元素样式的规则。XSL处理程序读取XML文档并将其读入的内容与样式单中的模式相比较。当在XML文档中识别出XSL样式单中的模式时,对应的规则输出某些文本的组合。与级联样式单不同,输出的文本比较任意,也不局限于输入文本加上格式化信息。
CSS只能改变特定元素的格式,也只能以元素为基础。但XSL样式单可以重新排列元素并对元素进行重排序。这种样式单可以隐藏一些元素而显示另外一些元素。更进一步说,还可以选择应用样式的标记,而不仅是基于标记的,而且还基于标记的内容和特性,还基于标记在文档中相对于其他元素的位置,以及基于各种其他的准则。
CSS的优越性在于具有广泛的浏览器支持。但是XSL更为灵活和强大,可更好地适用于XML文档。而且带XSL样式单的XML文档可以很容易地转换为带CSS样式单的HTML文档。
XSL样式单将第14章“XSL变换”和第15章“XSL格式化对象”中更为详细地论述。
1.4.4 URL和URI
XML文档可用于Web,正如HTML和其他文档一样。使用时,也如HTML文档一样,被统一资源定位符(Uniform Resource Locator,简写为URL)所引用。例如,在URL http://www.hypermedic.com/style/xml/tempest.xml处,可以找到以XML标记的莎士比亚的歌剧tempest的全文。虽然URL已被人们广泛理解并被广泛支持,但XML规范使用的是更为通用的统一资源标识符(Uniform Resource Identifier,简写为URI)。URI对于定位Internet上的资源是更为通用的架构,更为注重资源而不太注重位置。理论上说,URI可找出镜像文档的最为近似的副本或是找出已经从一个站点移动到另一站点的文档。实际上,URI仍然处于进一步的研究之中,被当前的软件所唯一支持的一种URI正是URL。
1.4.5 XLink和XPointer
只要将XML张贴到Internet上,用户当然希望能够对此文档寻址并且可以将这些文档链接起来。标准的HTML链接标记可用在XML文档中,而且HTML文档也可与XML文档加以链接。例如,下面的HTML代码将链接指向了前文提到的以XML形式出现的Tempest的副本:
<a href="http://www.hypermedic.com/style/xml/tempest.xml">
The Tempest by Shakespeare
</a>
如果用户跟随着链接,浏览器能否显示这个文档,依赖于该浏览器处理XML文件的能力。目前大多数浏览器还不能很好地处理XML文档。
然而,XML利用XLink来与文档链接,用XPointer来确定文档个别部分的位置,就可以有更多的功能。.
XLink使任意元素成为链接,而不只是A元素。进一步说,链接可以是双向的、多向的或是指向多个镜像的站点,并选择这些站点中最近的一个。XLink利用普通的URL来标识它链接的站点。.
XLink将在第16章中加以讨论。
XPointer能使链接不仅指向特定位置处的特定文档,而且还可指向特定文档的特定部分。XPointer可以引用文档中的特定的元素,如第一个、第二个或是第十七个特定的元素。XPointer提供了文档间连接的非常强大的功能,而这些文档不必有包括附加标记的目的文档,正因为如此,其中的个别部分才可以被链接。
进一步说,与HTML的锚(anchor)不同,XPointer不只是引用文档中的一点。XPointer可以指向一个范围或是一个区域。因而XPointer可以用来选择文档的特定部分,或许这样一来,就可以将这部分复制或是将其装入其他程序。
XPointer将在第17章中加以讨论。
1.4.6 Unicode字符集
Web是国际性的,到目前为止其上主要文本部分仍为英文。XML是改变这种状况的开始。XML对双字节的Unicode字符集及其紧凑的表示提供了完全的支持。这一字符集几乎可以支持地球上的每一种常用的字符。遗憾的是,光有XML还是不够的。为了阅读一种文字,需要三个条件:
1. 该种文字的字符集
2. 该字符集的字体
3. 操作系统和应用软件能够理解这种字符集
如果想要以这种文字写作,并阅读这种文字,还需要该种文字的输入法。当然,XML定义了字符引用,可使用户使用纯ASCII字符将未列在本地字符集中的字符加以编码。这对于偶尔引用一下希腊或是中文字符也足够了,当然不能指望用这种办法以其他语言来写一部小说。
在第7章“外国语言和非罗马文本”中,读者将会看到国际文本在计算机中是如何来代表的,XML如何来理解文本,以及如何来利用不得不以非英语来读写的软件。
1.4.7 如何将这些技术融合在一起
XML定义了一些标记的语法规则,可用来标记文档。XML文档是用XML标记来标记的。XML文档的缺省编码方法是Unicode。
XML文档的许多好处之一是,可以包括与其他文档和资源的超链接。这些链接是根据XLink规范创建的。XLink用URI(理论上)或是用URL(实际上)标识出链接的文档。一个XLink可进一步指定它所链接文档的个别部分。这些个别部分是通过XPointer来寻址的。如果打算由人来阅读XML文档,那么样式单就提供个别元素格式化的指令(并不是所有的XML文档都如此)。样式单可用几种样式语言中的任一种来编写。CSS和XSL是两种最常用的样式语言,虽然也存在其他基于XSL的样式语言,如DSSSL(Document Style Semantics and Specification Language,文档样式语义和规格语言)。
我已经在本章中概述了许多令人激动的技术。但是,良知让我告诉读者,我还没有全讨论到。事实上,我所叙述的大部分是XML的前景而不是当前的现实。XML让软件产业中的许多人激动不已,许多程序员正在奋发工作,以便将梦想变为现实。层出不穷的新软件正将我们带入XML的“天堂”,但是由于这一领域非常新,许多新软件还没有经过充分地考验。在本书的其余部分,我将小心地不仅要指出什么将可能出现,而且也指出什么实际已经上出现了。令人沮丧的是,这两件事常常不是一回事。不管怎么说,当前还是可以小心地用XML来做一些实际工作的。
1.5 本章小结
在本章中,读者了解了某些XML可以为我们做的事情。更明确地说,了解了以下几个方面:
· 一种能够为特定文档和领域创建标记语言的元语言。
- XML标记描述了文档内容的结构和语义,而不是内容的格式。格式是在另外的样式单中描述的。
· XML的起因是,用户受到SGML复杂性的挫伤和HTML的不充分。
- XML是用编辑器创建的,由语法分析程序来读取,而由浏览器来显示的。
- 在Web上的XML是建立在由HTML、级联样式单和URL提供的基础之上的。
- 许多支持技术处于XML之上,包括XSL样式单、XLink和XPointer。这些技术使用户可以比只使用CSS和URL完成更多的任务。
- 一定要小心。XML并未彻底完成。它随时会发生变化或是扩展,而在当前的XML软件中可能会遇到这样或那样的错误。
在以下几章中,读者可以看到几个XML应用,学到某些将XML用到现实中的方式。例子包括音乐乐谱、数学、化学、人力资源、Web广播以及其他一些应用。
第2章 XML应用简介
在本章中,我们将要查看XML的几个应用实例、用来进一步改进XML的标记语言和在后台使用的XML。看一看XML的某些应用,即使只是发展的初级阶段,也是令人鼓舞的。本章将向读者讲述XML的广泛应用性的某些看法。在我写作本书时,更多的XML应用正在创建并与其他格式的应用接轨。
第五部分更为详细地讲述了本章中讨论过的一些XML应用程序。
本章的主要内容包括:
· 什么是XML应用程序
- 用于XML的XML
- XML的后台应用
2.1 什么是XML应用程序
XML是一种元标记语言,可用来设计与特定专业领域有关的标记语言。每种基于XML的标记语言都叫做XML应用程序。这种应用不是像Mozilla Web浏览器、Gnumeric电子表格或 XML Pro那样的编辑器一样地使用XML,而是在特定的领域中应用XML,如化学上用的化学标记语言(Chemical Markup Language,简写为CML)或是家谱上用的GedML。每种XML应用程序有它自已的句法和词汇表。这种句法和词汇表遵守XML的基本规则。
这有点像人类语言,每种语言都有它们自己的词汇表和语法,但同时遵循人体解剖学和大脑结构所要求的基本规则。
XML是以文本数据为基础的非常灵活的格式。在本章中讨论的广泛的应用都选择了XML作为基础的原因是(排除大肆宣传的因素),XML提供了切合实际的并清楚地描述了的易于读写的格式。应用程序将这种格式用于它的数据,就能够将大量的处理细节让几个标准工具和库函数去解决。更进一步说,对于这样的程序也容易将附加的句法和语义加到XML提供的基本结构之上。
2.1.1 化学标记语言(Chemical Markup Language)
Peter Murray-Rust的化学标记语言(Chemical Markup Language,简写为CML)可能是第一个XML应用。CML原来是要发展成SGML应用的,但随着XML标准的发展,逐步演化成了XML。在CML的最简单的形式下,CML是“HTML加分子”,但是它的用处却超出了Web的范围。
分子文档常常包括成千上万个不同的详细的对象。例如,单个中等大小的有机分子可能含有几百个原子,每个原子有几个化学键。CML寻求以一种直接方式组织这种复杂的化学对象,以便能够让计算机理解,并显示和能够加以检索。CML可以用于分子结构和序列、光谱分析、结晶学、出版、化学数据库和其他方面。它的词汇表包括分子、原子、化学键、晶体、分子式、序列、对称、反应和其他化学术语。例如,清单2-1是描述水(H2O)的基本CML文档:
清单2-1:水分子H2O
<?xml version="1.0"?>
<CML>
<MOL TITLE="Water">
<ATOMS>
<ARRAY BUILTIN="ELSYM">H O H</ARRAY>
</ATOMS>
<BONDS>
<ARRAY BUILTIN=”ATID1”>1 2</ARRAY>
<ARRAY BUILTIN=”ATID2”>2 3</ARRAY>
<ARRAY BUILTIN=”O DE ”>1 1</ARRAY>
</BONDS>
</MOL>
</CML>
CML提供的对传统的管理化学数据的方法的最大改善在于数据的检索。CML还使得复杂的分子数据可在Web上发送。由于XML的底层是与平台无关的,所以可以避免由于使用不同的平台而引起的二进制格式不兼容的问题,这种问题在使用传统的化学软件和文档(如Protein Data Bank (PDB)格式或者MDL Molfiles)时常常可以遇到。
Murray-Rust还创建了第一个通用目的的XML浏览器JUMBO。图2-1是JUMBO正在显示的一个CML文件。Jumbo将每个XML元素赋给能够显示这些元素的Java类。为了使Jumbo支持新的元素,只要编写用于该元素的Java类即可。Jumbo是与显示基本的一套CML元素(其中包括分子、原子和化学键)的类一起发布的。Jumbo可从http://www.xml-cml.org/ 站点处得到。
2.1.2 数学标记语言(Mathematical Markup Language)
传说CERN的Tim Berners-Lee发明了World Wide Web和HTML,这样一来,高能物理学家们就可以交换论文和印前出版物了。从我个人角度来说,我从不相信这个传说。我是学物理学的,而且我曾在物理、应用数学、天文学和计算机科学等几个学科之间徜徉多年。这几个学科的论文有一点是共同的,就是论文中充满了大量的方程。直到目前为止,Web已经出现了有九年时间了,还没有找到一种在Web页面上包括方程的好办法。
现在有几种办法如Java小程序,可以分析自定义的句法,还有一种转换程序,可将用LaTeX软件编辑的方程转化为GIF图像,另一种是自定义的浏览器,可以读取TeX文件,但所有这些办法都不能产生高质量的结果,而且这些都不能满足Web作者(即使是科学领域的作者)的需求。最终,只有XML才能开始改变这种状况。
图2-1 显示CML文件的JUMBO浏览器
数学标记语言(Mathematical Markup Language,MathML)是一种用于数学方程的XML应用。MathML具有足够的能力来处理大多数形式的数学问题从初中的算术到微积分和微分方程。它也可以处理许多更为高级的课题,但还存在一些空白,如在某些数学的分支中使用的更为高级也更为晦涩的记号。虽然对于MathML来说,在纯数学和理论物理的高端还有局限性,但是却足以处理几乎所有的教育、科学、工程、商业、经济和统计学上的要求。而且将来MathML必然要加以扩展,因而可以认为,即使是最纯粹的数学和纯理论的理论物理都能够在Web上出版和进行研究工作。MathML完成了Web向着科学研究和通信方面的有用工具方向的发展(尽管说它也适用于作为新媒体来制作广告小册子有点离题太远)。
Netscape Navigator和Internet Explorer还不支持MathML。但是许多数学家都抱着热烈的希望,希望这些浏览器在不久的将来能够对此加以支持。W3C已经将某些对MathML的支持集成到他们的浏览器测试平台Amaya中了。图2-2是Amaya显示的用MathML编写的Maxwell方程的协变形式。
Amaya软件可以在本书所附CD-ROM的browsers/amaya目录中找到。
图2-2 Amaya浏览器显示的用MathML编写的协变形式的Maxwell方程
清单2-2列出了Amaya浏览器正在显示的XML文件:
清单2-2:MathML中的麦克斯韦(Maxwell)方程
<?xml version="1.0"?>
<html xmlns="http://www.w3.org/TR/REC-html40"
xmlns:m="http://www.w3.org/T / EC-MathML/"
>
<head>
<title>Fiat Lux</title>
<meta name="GENERATOR" content="amaya V1.3b" />
</head>
<body>
<P>
And God said,
</P>
<math>
<m:mrow>
<m:msub>
<m:mi>δ</m:mi>
<m:mi>α</m:mi>
</m:msub>
<m:msup>
<m:mi>F</m:mi>
<m:mi>αβ</m:mi>
</m:msup>
<m:mi></m:mi>
<m:mo>=</m:mo>
<m:mi></m:mi>
<m:mfrac>
<m:mrow>
<m:m >4</m:m >
<m:mi>π</m:mi>
</m:mrow>
<m:mi>c</m:mi>
</m:mfrac>
<m:mi></m:mi>
<m:msup>
<m:mi>J</m:mi>
<m:mrow>
<m:mi>β</m:mi>
<m:mo></m:mo>
</m:mrow>
</m:msup>
</m:mrow>
</math>
<P>
and there was light
</P>
</body>
</html>
清单2-2是混合使用HTML/XML的页面的例子。其中文本(“Fiat Lux”、“Maxwell’s Equations”、“And God said”、“and there was light”)的标题和段落是用经典的HTML编写的。实际的方程是用MathML编写的,这是一个XML应用。
一般来说,这种混合页面需要浏览器的特殊支持,这里也正是这种情况,否则就得有插件、ActiveX控件或是JavaScript程序来分析和显示内嵌的XML数据。当然最终用户需要像Mozilla 5.0或是Internet Explorer 5.0这样的浏览器,这两种浏览器可以分析和显示纯XML文件,而不需要HTML作为中介。
2.1.3 频道定义格式
Microsoft的频道定义格式(Channel Definition Format,简写为CDF)是用于定义频道的XML应用。Web站点使用频道向预订站点的用户传送信息,一改过去那种坐等用户前来浏览并获取信息的状况。这也叫做Web广播或是“推”。CDF首先是在Internet Explorer 4.0中引入的。
CDF文档是一个XML文件,与被推的站点的HTML文件分别存放,但是却链接到此HTML文件上。CDF文档中的频道定义决定了要发送哪个页面。页面可以通过发送通知向预订者加以推送,但也可以发送整个站点,或是由阅读者在方便的时候自己来“拉”信息。
用户可向自己的站点添加CDF,而不用改变现存的所有内容。只要在页面上添加与CDF文件的一个不可见的链接即可。当浏览者访问这个页面时,浏览器显示一个对话框,询问浏览者是否要预订频道。如果浏览者选择了预订,则浏览器就下载描述频道的CDF文档。然后浏览器将CDF文档用指定的参数与用户自己的优选项结合起来,以便决定什么时候检查服务器上的新内容。这实际上不是真正的“推”,因为客户必须初始化连接,但是这确实是在没有浏览请求的情况下发生的。图2-3是IDG的Active Channel(活动频道)显示在Internet Explorer 4.0中的情况。
图2-3 在Internet Explorer 4.0中显示的IDG的Active Channel(活动频道)
在第21章“用CDF推送Web站点”中将详细地讨论CDF。
Internet Explorer 4.0可在本书所附CD-ROM上的browsers/ie4目录中找到。
2.1.4 经典文学
Jon Bosak曾经将Shakespeare(莎世比亚)的全部话剧翻译成了XML。这些剧本的全文都包括其中了,用XML标记来区分剧名、每幕标题、舞台指导、对白、台词、旁白等。
莎世比亚的全套话剧可以本书所附CD-ROM上的examples/shakespeare目录中找到。
读者可能要问,对于一本书或是一个普通的文本文件来说,这样做有什么好处呢?对于人类读者来说,这没有什么不同,但对分析文字的计算机来说,这样做就使得容易区分组成话剧的不同元素。例如,要让计算机在全文中找出Romeo(罗密欧)的台词就变得简单了。
进一步说,借助于改变格式化文档的样式单,某个演员就很容易地打印出该剧的一个副本,其中他(她)的所有台词都格式化为粗体,而他(她)前面和后面的台词都用斜体来表示。另外还可以想像出来的事是,将剧本分成不同人的道白时,利用XML格式化的版本也比原来的文本要容易得多。
Bosak曾经将新旧约全书、古兰经和摩门教教义的英文译本用XML加以标记。这些书中的标记有些不同。例如,它并不对讲话人加以区分。因而(比如说)也就不能利用这种特殊的XML文档来创建带红色字母的圣经,虽然使用不同的一套标记可以达到这一目的。(带红色字母的圣经将耶稣说的话用红色印刷。)而且由于这些文件是用英语写成的,而不是原来的语言,这对于学术上的文本分析来说,就不是那么有用了。如果时间和资源允许的话,只要愿意,用XML来书写原文也是可以办得到的。这时只要设计一套与Bosak使用的不同,但却是描述同样的数据的词汇表和句法即可。
经XML标记了的圣经、古兰经和摩门教教义都可在本书所附的CD-ROM上的examples/religion目录中找到。
2.2 用于XML的XML
XML对于文本数据来说是最通用的格式。它所用于的某些事物还进一步地完善了XML本身。这包括XSL样式单语言、XLL链接语言和用于XML的文档内容描述(Document Content Description,简写为DCD)。
2.2.1 XSL
XSL(Extensible Style Language,可扩展的样式语言)本身就是XML应用。XSL有两个主要部分。第一部分定义了将XML文档加以转换的词汇表。这一部分的XSL包括用于树的XML标记、节点、式样、模板和其他用于将XML文档从一种标记词汇转换成另一种(或是同一种却以不同的顺序)所需要的元素。
XSL的第二部分定义了用于格式化转换后的XML文档(由第一部分产生的)的词汇表。这包括用于格式化对象(如分页、块、字符、列表、图形、方框、字体和其他)的XML标记。清单2-12中列出了一个典型的XSL样式单:
清单2-12:一个XSL样式单
<?xml version="1.0"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/T /WD-xsl"
xmlns:fo="http://www.w3.org/T /WD-xsl/FO"
result-ns="fo">
<xsl:template match="/">
<fo:basic-page-sequence >
<xsl:apply-templates/>
</fo:basic-page-sequence>
</xsl:template>
<xsl:template match="ATOM">
<fo:block font-size="10pt" font-family="serif" space-before="12pt">
<xsl:value-of select="NAME"/>
</fo:block>
</xsl:template>
</xsl:stylesheet>
我们将在第14章和15章中详细讨论XSL。
2.2.2 XLL
可扩展的链接语言(Extensible Linking Language,简写为XLL)定义了新的名为XLink的更一般种类的链接。XLinks可完成用HTML中的以URL为基础的超链接所能完成的所有任务。例如,脚注元素可像下例一样直接链接注解的文本:
<footnote xlink:form="simple" href="footnote7.xml">7</footnote>
进一步说,XLink可以做HTML链接不能做的事。XLink可以是双向的,因而读者可以返回原来所在的页面(跳转前所在页面)。XLink可以链接到文档中的任意位置。XLink可将文本或是图形数据嵌入文档内部,而不需要用户去激活链接(更像HTML中的<IMG>标记,但更灵活)。简短说,XLink使超链接的功能更为强大。
在第16章“XLink”中将要更加详细地讨论XLink方面的内容。
2.2.3 DCD
XML的用于声明XML元素内容应该如何格式化的工具对于不存在的内容显得功能不足。例如,假设作为数据的一部分,像下面一样建立了MONTH元素:
<MONTH>9</MONTH>
我们能看到MONTH元素的内容应该是字符数据。我们不能说必须给这个元素以从1到12的整数。
已经提出了几种XML本身的方案,以便更严格地限制什么可以出现在任意给定的内容中。有一种方案就是文档内容描述(Document Content Description,简写为DCD)例如,这里有一个DCD,声明了MONTH元素只能含有1到12的整数:
<DCD>
<ElementDef Type="MONTH" Model="Data" Datatype="i1"
Min="1" Max="12" />
</DCD>
我还可以向读者展示好多的用于XML的XML的例子,但是上例已经表明了基本的观点:XML强大得足以来描述和扩展本身。此外,这还意味着,XML规范可以保持短小和简单。完全可以没有XML 2.0,因为任何主要的所需的附加内容都可以根据原来的XML加以建立,而不必成为XML的新功能。需要加强功能的人们和程序员们可以使用这些新功能,而不需要的人可以将其忽略。用户不必了解什么是不使用的。XML提供了“砖和泥”,利用这些“砖和泥”既可以建起“小屋”也可以建起高耸的“城堡”。
2.3 XML的后台应用
并不是所有的XML应用都是公开的、开放的标准。有许多软件开发商正在将其自身的数据转向XML,只是因为XML是被公众很好理解的、通用目的的格式,可以用容易获得的、便宜或免费的工具加以处理。
Microsoft Office 2000已将HTML变为与它的内建二进制格式同等的格式。不过, HTML 4.0还不能提供对Office所需的所有功能的全面支持,如修订跟踪、脚注、批注、索引和术语表项等等。不能用HTML表达的附加数据嵌入到XML的小型代码块中。Word的矢量图形保存在VML中。在这种情况下,嵌入的XML在标准的浏览器中的不可见性是个关键因素。
Federal Express公司将详细跟踪的信息用作为与其他送货公司(如UPS(美国快寄服务公司和Post Office(邮局))相比更有竞争力的优点。首先这种信息来源于顾客软件,然后是通过Web。最近,FedEx公司开始对其API(应用程序接口)和库函数(第三方和内部开发者可使用这些API将他们的软件和系统与FedEx的加以集成)的?测试。这种服务的数据格式就是XML。
Netscape Navigator 5.0 支持XML在Web浏览器上的直接显示,但是,Netscape 实际在内部早在4.5版时就已经开始使用XML了。当用户请求Netscape显示与当前站点相联系的站点的列表时,浏览器就连接到运行在Netscape服务器上的一个CGI程序上。服务器送回来的数据就是XML。清单2-13就是与站点http://metalab.unc.edu/相联系的站点的XML数据:
清单2-13:与http://metalab.unc.edu/相联系的站点的XML数据
<?xml version="1.0"?>
<RDF:RDF>
<RelatedLinks>
<aboutPage
href="http://in fo.netscape.com/fwd/rl/http://metalab.unc.edu:80/*">
</aboutPage>
<child instanceOf="Separator1"></child>
<child
href="http://info.netscape.com/fwd/rl/http://www.sun.com/"
name="Sun Microsystems">
</child>
<child
href="http://info.netscape.com/fwd/rl/http://www.unc.edu/"
name="Unc">
</child>
<child
href="http://info.netscape.com/fwd/rl/http://sunsite.sut.ac.jp/"
name="SunSITE Japan">
</child>
<child
href="http://info.netscape.com/fwd/rl/http://sunsite.nus.sg/"
name="SunSITE Singapore">
</child>
<child
href="http://info.netscape.com/fwd/rl/http://sunsite.berkeley.edu/"
name="Berkeley Digital Library SunSITE">
</child>
<child
href="http://info.netscape.com/fwd/rl/http://www.sun.com/sunsite"
name="SunSITE on the net">
</child>
<child
href="http://info.netscape.com/fwd/rl/http://www.sunsite.auc.dk/"
name="SunSITE Denmark">
</child>
<child
href="http://info.netscape.com/fwd/rl/http://sunsite.edu.cn/"
name="SunSITE China">
</child>
<child
href="http://info.netscape.com/fwd/rl/http://sunsite.stanford.org/"
name="Stanford University SunSITE">
</child>
<child
href="http://info.netscape.com/fwd/rl/http://www.cdromshop.com/
cdshop/desc/p.061590000085.html" name="SunSITE Archive">
</child>
<child instanceOf="Separator1"></child>
<child instanceOf="Separator1"></child>
<child href="http://home.netscape.com/escapes/smart_browsing"
name="Learn About Smart Browsing...">
</child>
</RelatedLinks>
</RDF:RDF>
这一切都完全发生在幕后。用户决不会知道那些数据正在用XML加以传送。实际上显示的是Netscape Navigator中的菜单,而不是XML或HTML页面。
这些实际上还只是将XML用于内部数据的不成熟的表面现象。许多其他使用XML的项目还刚刚起步,还有一些项目将在明年起步。大多数这样的项目不会受公开注意,也不会在商业出版物上受到吹捧,但是不管怎样,在其项目存活期内它们都具有潜力可为公司节约成千上万美元的开发费用。XML的自说明性对于公司内部的数据也是很有用的。例如,许多公司现在正在匆忙地设法找出20年前退休的程序员是否用了两位数字的日期。如果你正在干这样的事情,你是愿意将数据写成下面的样子呢:
3c 79 65 61 72 3e 39 39 3c 2f 79 65 61 72 3e
还是下面的样子:
<YEAR>99</YEAR>
不幸的是,许多程序员现在还坚持将数据写成第一种格式。XML还可使错误容易发现和修改。
2.4 本章小结
本章只是刚刚接触到已经和将要使用XML的应用。一些应用,如CML、MathML和MusicML很明显是用于Web浏览器的HTML扩展。但是许多别的应用,如OFX、 XFDL和HRML完全走的是另一条路。所有这些应用都有建立在XML之上的自己的语义和句法。在某些情况下,XML的“根”是很明显的,但在另外一些情况下,即使在其上工作达一月之久,也不一定会发现它与XML有什么关系。在本章中,我们讨论了下面的可使用XML的应用:
· 使用CML的分子科学
- 使用MathML的科学与数学
- 使用CDF的Web广播
- 古典文学
- 使用SMIL和HTML+TIME的多媒体应用
- 通过OSD的软件更新
- 使用PGML和VML的矢量图形
- 用MusicML 表示的音乐记号
- 使用VoxML的自动语音响应
- 使用OFX的财务数据
- 与XFDL合法捆绑的表单
- 使用HRML的人力资源工作信息
- 通过RDF表示的元数据(Meta-data)
- XML本身包括XSL、 XLL和DCD使XML更加完善
- 许多公司在因特网上应用XML,这些公司包括Microsoft、Federal Express和Netscape
在下一章中,读者将要学习编写自己的XML文档并在Web浏览器上加以显示。
第3章 第一个XML文档
本章教读者用自己定义的可为文档所理解的标记来创建简单的XML文档。读者将学到如何编写样式单,以便用于在文档中描述标记内容如何显示。最后,还要学到如何将文档装到Web浏览器中以便查看。
由于本章利用示例来加以讲解,而不是从原理出发,因而不会涉及许多细节。有经验的读者将会注意到几处例外和特殊情况没有在本章加以讨论。对此不必担心。在下几章中将会讨论到。对于大部分内容,不必太关心技术内容。正如HTML一样,也可通过复制其他人创建的简单的示例并按自己的需要加以修改来学习。
为了达到上述目的,我鼓励大家按我在本章中给出的示例键入程序逐步进行,并将这些代码装入讨论过的不同的程序中。这将使读者对XML产生基本感受,这将使在未来几章中提到的技术细节在特定示例的环境中容易掌握。
本章的主要内容包括:
· 创建简单的XML文档
- 仔细研究这个简单的XML文档
- 赋给XML标记以具体意义
- 在XML文档上附加样式单
3.1 Hello XML
本节遵照老程序员介绍新语言的传统,先用一个能够在屏幕上打印出“Hello World”的程序加以介绍。XML是标记语言,而不是编程语言,但是基本原理还是适用的。最简单的方法是以一个完全的可运行的有扩展能力的示例开始,而不要尝试以更基本的无任何功能的程序开始。如果用户在使用基本的工具时确实遇到了问题,在简短的文档环境中也比在复杂的文档环境下更容易调试和改正。
在本节中,读者将学到如何创建一个简单的XML文档并将其保存在文件中。然后我们对其中的代码及其意义再加以仔细考察。
3.1.1 创建一个简单的XML文档
在本节中,读者将学到如何键入一个实际的XML文档。我们从能够想像得到的最简单的XML文档开始。这个文档列在清单3-1中:
清单3-1:Hello XML
<?xml version="1.0" standalone="yes"?>
<FOO>
Hello XML!
</FOO>
这虽然不太复杂,但却是一个“好”的XML的文档。更准确地说,这是一个结构完整的XML文档(XML中有一些用于文档的专门术语,依照到底满足了哪条规则而被认为是“好”的 。其中“结构完整的”就是一条这样的术语,在本书的后面要对此加以讨论。)可在任何使用方便的文本编辑器,如Notepad、BBEdit或是emacs中键入这个文档。
结构完整性将在第6章“结构完整的XML文档”中加以讨论。
3.1.2 保存XML文件
当键入了上面的代码之后,请将该文档保存在名为hello.xml的文件中。也可以使用诸如HelloWorld.xml、MyFirstDocument.xml或是其他文件名,但三个字母的扩展名.xml是标准的,一般不要更改。而且还要确保以普通的文本格式加以保存,而不要用某些字处理程序,如WordPerfect或Microsoft Word的内建格式。
如果使用的是Windows 95/98上的Notepad来编辑文件,当保存文档时,一定要将文件名用双引号括起来,即“Hello.xml”,而不要只是Hello.xml,正如图3-1所示的一样。如果没有引号,Notepad会在文件名后再加上.txt扩展名,也就是文件名变成了Hello.xml.txt,这完全不是我们所希望出现的。
图3-1 在Notepad中用带引号的文件名来保存XML文档
Windows NT版本的Notepad还会给出将文件保存为Unicode格式的选项。令人惊奇的是,这样保存也可以,不过我们还是坚持使用基本的ASCII文本格式比较好。XML文件既可以是Unicode格式也可以是Unicode的名为UTF-8的压缩版本,这是严格的ASCII的超集,因而纯ASCII文件也是合法的XML文件。
UTF-8和ASCII将在第7章“外国语言和非罗马文本”中加以更为详细的讨论。
3.1.3 将XML文件装入Web浏览器
既然已经创建了第一个XML文档,当然想看一看了。这个文件可以在支持XML的浏览器,如Internet Explorer 5.0中直接打开。图3-2显示的就是结果。
我们看到的结果将依不同的浏览器而有所不同。在本例情况下,文件是格式化得很好的,以不同的颜色来表示不同的句法。不过所看到的并没有吸引人的地方。问题在于浏览器并不了解如何来处理FOO元素。我们必须指示浏览器如何来处理每个元素,这就要用到样式单了。我们将要简单地介绍一下,但首先还是仔细地考察一下这个文档。
图3-2 hello.xml在Internet Explorer 5.0中的显示结果
.2 考察简单的XML文档
让我们检查一下列在清单3-1中的这个简单的XML文档,以便更好地理解每行代码的意义。第一行是XML声明:
<?xml version="1.0" standalone="yes"?>
这是XML处理指令的例子。处理指令以<?开始,而以?>结束。在<?后的第一个单词是处理指令名,在本例中是xml。
XML声明有version和standalone两个特性。特性是由等号分开的名称-数值对。位于等号左边的是特性名,而其值位于等号的右边,并用双引号括起来。
每一个XML文档都以一个XML声明开始,用以指明所用的XML的版本。在上例中, version特性表明这个文档符合XML 1.0规范。XML声明还可以有standalone特性,这告诉我们文档是否在这一个文件里还是需要从外部导入文件。在本例中,以及在以后的几章中,所有的文档都在一个文件里完成,因而standalone特性的值要设置为yes。
现在让我们看一下清单3-1中的下面的三行:
<FOO>
Hello XML!
</FOO>
总体上说,这三行组成了FOO元素。分开说,<FOO>是开始标记,而</FOO>是结束标记,Hello XML!是FOO元素的内容。
读者可能要问,<FOO>标记的意义是什么?回答是“你要让它是什么就是什么”。除了几百个预定义的标记之外,XML还允许用户创建所需的标记。因而<FOO>标记可以具有用户赋于的任何意义。同一个XML文档可以用不同的标记名编写,正如清单3-2、3-3和3-4所表明的:
清单3-2:greeting.xml
<?xml version="1.0" standalone="yes"?>
<GREETING>
Hello XML!
</GREETING>
清单3-3:paragraph.xml
<?xml version="1.0" standalone="yes"?>
<P>
Hello XML!
</P>
清单3-4:document.xml
<?xml version="1.0" standalone="yes"?>
<DOCUMENT>
Hello XML!
</DOCUMENT>
清单3-1到3-4这四个文档用的标记名各不相同,但都是等价的,因为具有相同的结构和内容。
3.3 赋于XML标记以意义
标记可有三类意义:结构、语义和样式。结构将文档分成元素树。语义将单个的元素与外部的实际事物联系起来。而样式指定如何显示元素。
结构只是表达文档的形式,而不管单个标记和元素间的差别。例如,上面清单3-1到3-4中的四个XML文档结构是相同的。它们都指定文档具有一个非空的基本元素。标记的不同名称没有结构上的意义。
语义的意义存在于文档之外,在作者的心中或是读者或是某些生成或读取这些文件的计算机程序中。例如,理解HTML但不理解XML的Web浏览器,可能会将段落的意义赋给<P>和</P>标记,但不会赋给标记<GREETING>和</GREETING>、<FOO>和 </FOO>或是<DOCUMENT>和</DOCUMENT>。讲英语的人可能会比<FOO>和</FOO>或<P>或</P>更容易理解<GREETING>和</GREETING>或是<DOCUMENT>和</DOCUMENT>的意义。正如“美丽”的意义存在于观察者心中。
计算机作为一个哑机器,不能说是真正地理解任何事物的意义。计算机只是根据预先确定的公式来处理位和字节而已(虽然非常快)。对于一台计算机而言,用<FOO>或是<P>与使用<GREETING>或<DOCUMENT>标记没有什么差别。即使对于Web浏览器来说,也不能说它理解什么是段落。所有的浏览器了解的是,当遇到一个段落时,在下一个元素前面要放置一个空行。
自然地,使标记的名称能够尽可能反映其包含的意义更好一些。许多学科,如数学和化学正在创建该学科的工业标准和标记集。如果合适的话,应该使用这些标准和标记集。但是大多数情况下,还是需要什么标记就创建什么标记。
以下是一些其他可能的标记:
<MOLECULE> <INTEGRAL>
<PERSON> <SALARY>
<author> <email>
<planet> <sign>
<Bill> <plus/>
<Hillary> <plus/>
<Gennifer> <plus/>
<Paula> <plus/>
<Monica> <equals/>
<divorce>
可以与标记相联系的第三类意义是样式意义。样式意义指定标记的内容如何在计算机屏幕上或是其他输出设备上展示。样式意义说明特定的元素是否是用粗体、斜体、绿色的24磅的字体还是其他字体加以表示。计算机在理解样式时比理解语义意义要好一些。在XML中,样式意义是通过样式单来施加的。
.4 为XML文档编写样式单
XML允许用户来创建任何所需要的标记。当然,由于用户在创建标记上有完全的自由,因而通用的浏览器无法预期用户的标记的意义,也无法为显示这些标记而提供规则。因而,用户必须为文档编写样式单,告诉浏览器如何显示特定的标记。与标记集类似,用户创建的样式单可由不同的文档不同的人所共享,还可将自己创建的样式单与其他人编写的样式单集成在一起。
正如在第1章中所讨论的,现在有不止一种样式单语言可以使用。这里所用的是级联样式单(Cascading Style Sheets,简写为CSS)。CSS的优势在于它是W3C制定的标准,为编写HTML的许多人所熟悉,且被前卫的具有XML能力的浏览器所支持。
正如在第1章所注意到的,另一种可能的选择是可扩展的样式语言(Extensible Style Language)。XSL是当前最强大和灵活的样式语言,是特别为应用XML而设计的。但是,XSL比CSS更为复杂,而且未被很好地支持,同时还没有完成。
XSL将在第5、14和15章中加以讨论。
清单3-2中的greeting.xml示例只包括一个标记<GREETING>,因而所需做的一切是为GREETING元素定义样式。清单3-5是一个很简单的样式单,指定GREETING元素的内容应该以24磅的粗体显示为块级的元素。
清单3-5:greeting.xsl
GREETING{display: block; font-size: 24pt; font-weight: bold;}
清单3-5应该在文本编辑器中键入,保存为名为greeting.css的新文件,放在与清单3-2中的文件所在的同一目录中。扩展名.css代表级联样式单(Cascading Style Sheet)。同样.css扩展名是重要的,而文件名却不怎么重要。如果打算将这一样式单只用在一个XML文档上的话,那么与XML具有同样的文件名(扩展名为.css而不是.xml)常常更为方便。
.5 将样式单附加到XML文档上
在编写好XML文档和用于该文档的CSS样式单之后,还需要告诉浏览器将样式单作用到该文档上。长时期以来,可能有许多不同的方法可达到这一目的,包括浏览器-服务器通过HTTP文件头协商、命名约定和浏览器一侧的缺省方法。但是目前,唯一的有效方法是在XML文档中包括另一个处理指令,以便指定所要使用的样式单。
处理指令是<?xml-stylesheet?>和它的两个特性,type和href。type特性指定所用的样式语言,而href特性指定一个可以找到样式单的URL(可能是相对的)。在清单3-6中,xml-stylesheet处理指令指明施加于文档的样式单文件名为greeting.css,是用CSS样式单语言编写的。
清单3-6:带有xml样式单处理指令的greeting.xml
<?xml version="1.0" standalone="yes"?>
<?xml-stylesheet type="text/css2" href="greeting.css"?>
<GREETING>
Hello XML!
</GREETING>
既然我们已经创建好了第一个XML文档和样式单,那么当然想看一看结果了。我们所要做的就是将清单3-6装入Mozilla或是Internet Explorer 5.0。图3-3是显示在Internet Explorer 5.0中的具有样式的欢迎画面 。图3-4是显示在早期开发版本的Mozilla中的具有样式的欢迎画面。
图3-3 在Internet Explorer 5.0中显示的styledgreeting.xml文件
图3-4 在早期的开发者版本的Mozilla中显示的styledgreeting.xml文件
3.6 本章小结
在本章中,读者学到了如何创建一个简单的XML文档。总的来说,包括以下内容:
· 如何编写和保存简单的XML文档
- 如何把三种类型的意义(结构、语义和样式)赋给XML标记
- 如何为XML文档编写CSS样式单,从而告诉浏览器如何显示特定的标记
- 如何将带有xml-stylesheet处理指令的CSS样式单附加到XML文档上
- 如何将XML装入浏览器中
在下一章中,我们将要研究XML文档的更为大型的例子,用来演示在选择XML标记时的更多的实际考虑。
第4章 数据的结构化
在本章中,我们将要研究一个较长的示例,用来说明一个较长的有关棒球统计和其他类似数据的列表是如何以XML格式保存的。像这样的文档有好多潜在的应用。最明显的,它可以显示在Web 页面上。还可以用作其他分析数据或是整理数据程序的输入。通过这个示例,读者将学到如何用XML来标记数据、为什么要选用XML标记、如何为文档编制CSS样式单等等内容。
本章的主要内容包括:
· 检查数据
- 数据的XML化
- XML格式的优越性
- 为文档的显示编制样式单
4.1 检查数据
当我写作这本书时(1998年10月),纽约的Yankees队在四场比赛中击败圣·迭格的Padres队,取得了24届世界系列赛的冠军。Yankees队在American League的普通赛季结束时,取得了114场胜利。总体来说,1998是一个令人赞叹的赛季。圣·路易斯Cardinals队的 Mark McGwire和芝加哥Cubs队的Sammy Sosa为了创造新的单一赛季的本垒打纪录在整个9月份展开了争夺,原来的纪录是由Roger Maris保持的。
是什么使1998赛季这样激动人心呢?玩世不恭的人会告诉你,1998是一扩展年,有三个新队加盟,因而总体上来说投手能力减弱了。这就使得著名的击球手如Sosa和McGwire以及著名的球队,如Yankees得到了出风头的机会,因为,虽然他们仍然像他们在1997年一样实力强大,但面对的对手的平均能力弱了许多。当然真正的棒球爱好者了解真正的原因,这是由于统计上的原因造成的。
这实在有点滑稽。在大多数体育项目中,我们都说过心脏、勇气、能力、技巧、决心和其他名词。但是,只有棒球爱好者需要面对这么多原始数字,如平均击球率、平均得分、平均跑垒数、平均进垒数、对左手投手的平均击球率、对右手投手的平均击球率等。
棒球爱好者都被这些数字所迷住了,数字越多越好。在每个赛季中,因特网成了成千上万的棒球爱好者的大本营,狂热的网民们在其中“管理”球队并交换球员,计算他们喜爱的球队在现实中表现的各种数字。STATS, Inc.公司跟踪了每个球员在主要联赛的赛事上的表现,因而可以计算出一个击球手是否表现得比他的平均成绩要好。在以下两节中,为了照顾对棒球不太感兴趣的读者,我们检查一下描述单个球员的击球和投球率的常用统计数字。现场统计数字也可以找到,但是我将把这些数字略去,以便将示例局限于好管理的大小。我使用的这个特殊的例子是纽约的Yankees队,对于任何队的击球手,同样的统计数字也可以得到。
4.1.1 击球手
几年前,Bruce Bukiet、Jose Palacios和我写过一篇名为A Markov Chain Approach to Baseball (用于棒球的马尔可夫链式方法)的文章(刊登在Operations Research(运筹学研究杂志),45卷第1期,1997年1-2月号,pp. 14-23, 还可在以下网址上看到这篇文章http://www.math.njit.edu/~bukiet/Papers /ball.pdf)。在这篇文章中,我们分析了1989年全国棒球联赛中的所有球队的所有可能的比赛顺序。那篇文章的结果还是比较有意思的。球队中的最坏的击球手(通常是投球手)应该是第8位出场击球的人,而不应该是第9位,至少在全国棒球联赛上是如此。但是这里我所关心的是产生那篇文章的工作。作为一个低年级的研究生,用手工算出每个球员在全国棒球联赛上的全部击球历史记录正是我的工作。如果我能够使那些数据变得像XML一样的方便,那个夏季我会过得更愉快一些的。现在,让我们将精力集中于每个球员的数据上。典型的,这种数据是以一行行的数字表示的,如表4-1所示的是1998年Yankees队的进攻队员的数据。在美国棒球联赛的比赛上,由于投球手很少击球,只有实际上击球的队员才列在表中。
每一列有效地定义了一个元素。因而就需要为球员、位置、进行的比赛、击球、跑垒、击球数、两垒、三垒、本垒打、跑入和步行等建立元素。单垒通常都不单独报告。这个数据是从总击打数中减去双垒、三垒和本垒打的总和后得到的。
表4-1 The 1998年Yankees队的进攻队员数据
|
Name |
Postion |
Game Played |
At Bats |
Runs |
Hits |
Doubles |
Triples |
Home Runs |
Runs |
Strike |
Outs |
Hit |
||
|
Scott Brosius |
Third Base |
152 |
530 |
86 |
159 |
34 |
0 |
19 |
98 |
52 |
97 |
10 |
||
|
Homer Bush |
Second BBase |
45 |
71 |
17 |
27 |
3 |
0 |
1 |
5 |
5 |
19 |
0 |
||
|
Chad Curtis |
Outfield |
151 |
456 |
79 |
111 |
21 |
1 |
10 |
56 |
75 |
80 |
7 |
||
|
Chili Davis |
Designated Hitter |
35 |
103 |
11 |
30 |
7 |
0 |
3 |
9 |
14 |
18 |
0 |
||
|
Mike Figga |
catcher |
1 |
4 |
1 |
1 |
0 |
0 |
0 |
0 |
0 |
1 |
0 |
||
|
Joe Girardi |
catcher |
78 |
254 |
31 |
70 |
11 |
4 |
3 |
31 |
14 |
38 |
2 |
||
|
Derek Jeter |
Shortsho |
149 |
626 |
127 |
203 |
25 |
8 |
19 |
84 |
57 |
119 |
5 |
||
|
Chuck |
Second Base |
150 |
603 |
117 |
160 |
25 |
4 |
17 |
64 |
76 |
70 |
|
||
|
Ricky Ledee |
Outfield |
42 |
79 |
13 |
19 |
5 |
2 |
1 |
12 |
7 |
29 |
0 |
||
|
Mike Lowell |
Third Base |
8 |
15 |
1 |
4 |
0 |
0 |
0 |
0 |
0 |
1 |
0 |
||
|
Tino Martinez |
First Base |
142 |
531 |
92 |
149 |
33 |
1 |
28 |
123 |
61 |
83 |
6 |
||
|
Paul O’Neill |
Outfield |
152 |
602 |
95 |
191 |
40 |
2 |
24 |
116 |
57 |
103 |
2 |
||
|
Jorge Posada |
catcher |
111 |
358 |
56 |
96 |
23 |
0 |
17 |
63 |
47 |
92 |
0 |
||
|
Tim Raines |
Outfield |
109 |
321 |
53 |
93 |
13 |
1 |
5 |
47 |
55 |
49 |
3 |
||
|
Luis Sojo |
Shortshop |
54 |
147 |
16 |
34 |
3 |
1 |
0 |
14 |
4 |
15 |
0 |
||
|
Shane Spencer |
Outfield |
27 |
67 |
18 |
25 |
6 |
0 |
10 |
27 |
5 |
12 |
0 |
||
|
Darryl |
Designated |
101 |
295 |
44 |
73 |
11 |
2 |
24 |
57 |
46 |
90 |
3 |
||
|
Dale Sveum |
First Base |
30 |
58 |
6 |
9 |
0 |
0 |
0 |
3 |
4 |
16 |
0 |
||
|
Bernie Williams |
Outfield |
128 |
499 |
101 |
169 |
30 |
5 |
26 |
97 |
74 |
81 |
1 |
||
译者注:棒球数据不过是一种演示。在棒球统计数据的XML文档中,由于使用的是英文专用名词,故这里未翻译成中文。如果翻译过来反而无法相互对照。表4-2也同样处理。
前面表中的数据和下一节中的投球手数据都是加以限制后的列表,只是用来表明在一个典型的棒球赛中收集的数据。除了列出的以外,还有许多其他数据没有在这里列出。我打算使用这些基本信息,以便使示例容易管理。
4.1.2 投球手
人们并不指望投球手成为全垒跑的击球手或是偷袭能手。确实偶尔到达第一垒的投球手是对一个队的意外奖励。对投球手的评价要根据表4-2中列出的全场的不同种类的数字。这个表的每列也定义了一个元素。这些元素中的一部分,如姓名和位置对于投球手和击球手都是有的。其他元素如解救(saves)和成功防守(shutouts)只适用于投球手。还有几个,如得分(runs)和全垒跑(home runs)与击球手统计中的名称相同,但是具有不同意义。例如,击球手的得分数是击球手获得的分数。而对于投球手来说,是指对方在这个投球手下得到的分数。
4.1.3 XML数据的组织
XML是建立在容器模型的基础之上的。每个XML元素可以包含文本或是称为子元素的其他XML元素。有几个XML元素既可以包含文本也可以包含子元素。虽然通常来说,这并不是一种好形式,是应该尽量避免的。
不过,常常有不止一种组织数据的方法,这要取决于需要。XML的一个好处是,它使得编写程序来以不同形式组织数据变得相当直接。在第14章我们讨论XSL变换时还要讨论这一问题。
作为开始,必须注意的第一个问题是什么包含什么?例如,相当明显的是,联赛包含分部,分部包含球队,球队又包含球员,而球员又可在指定的时间进行交易,每个球员必定属于一个球队,每个球队又必定属于一个分部。类似的,一个赛季包含许多场比赛,每场比赛又包含几局,而局又包含击球阶段,击球阶段又包含投球阶段。
但是,赛季包括联赛吗或是联赛包括赛季吗?这个问题就不是很明显。确实对这样的问题没有唯一的答案。将赛季元素定义为联赛元素的子元素还是将联赛元素变为赛季元素的子元素有更多的意义,这要依赖于数据要用来干什么。用户甚至可以创建新的既包含赛季也包含联赛的根元素,哪个元素也不是另外元素的子元素(虽然要有效地这样做,还需要某些先进的技术,在以下几章还讨论不到这些技术)。用户可按用户的意愿来组织数据。
表4-2 1998年Yankees队的投球手
|
Name |
P |
W |
L |
S |
G |
GS |
CG |
SHO |
ERA |
IP |
H |
HR |
R |
ER |
HB |
WP |
BK |
WB |
SO |
|
Joe Borowski |
Relief Pitcher |
1 |
0 |
0 |
8 |
0 |
0 |
0 |
6.52 |
9.2 |
11 |
0 |
7 |
7 |
0 |
0 |
0 |
4 |
7 |
|
Ryan Bradley |
Relief Pitcher |
2 |
1 |
0 |
5 |
1 |
0 |
0 |
5.68 |
12.2 |
12 |
2 |
9 |
8 |
1 |
0 |
0 |
9 |
13 |
|
Jim Bruske |
Relief Pitcher |
1 |
0 |
0 |
3 |
1 |
0 |
0 |
3 |
9 |
9 |
2 |
3 |
3 |
0 |
0 |
0 |
1 |
3 |
|
Mike Buddie |
Relief Pitcher |
4 |
1 |
0 |
24 |
2 |
0 |
0 |
5.62 |
41.2 |
46 |
5 |
29 |
26 |
3 |
2 |
1 |
13 |
20 |
|
David Cone |
Starting Pitcher |
20 |
7 |
0 |
31 |
31 |
3 |
0 |
3.55 |
207.2 |
186 |
20 |
89 |
82 |
15 |
6 |
0 |
59 |
209 |
|
Todd Erdos |
Relief Pitcher |
0 |
0 |
0 |
2 |
0 |
0 |
0 |
9 |
2 |
5 |
0 |
2 |
2 |
0 |
0 |
0 |
1 |
0 |
|
Orlando Hernandez |
Starting Pitcher |
12 |
4 |
0 |
21 |
21 |
3 |
1 |
3.13 |
141 |
113 |
11 |
53 |
49 |
6 |
5 |
2 |
52 |
131 |
|
Darren Holmes |
Relief Pitcher |
0 |
3 |
2 |
34 |
0 |
0 |
0 |
3.33 |
51.1 |
53 |
4 |
19 |
19 |
2 |
1 |
0 |
14 |
31 |
|
Hideki Irabu |
Starting Pitcher |
13 |
9 |
0 |
29 |
28 |
2 |
1 |
4.06 |
173 |
148 |
27 |
79 |
78 |
9 |
6 |
1 |
76 |
126 |
|
Mike Jerzembeck |
Starting Pitcher |
0 |
1 |
0 |
3 |
2 |
0 |
0 |
12.79 |
6.1 |
9 |
2 |
9 |
9 |
0 |
1 |
1 |
4 |
1 |
|
Graeme Lloyd |
Relief Pitcher |
3 |
0 |
0 |
50 |
0 |
0 |
0 |
1.67 |
37.2 |
26 |
3 |
10 |
7 |
2 |
2 |
0 |
6 |
20 |
|
Ramiro Mendoza |
Relief Pitcher |
10 |
2 |
1 |
41 |
14 |
1 |
1 |
3.25 |
130.1 |
131 |
9 |
50 |
47 |
9 |
3 |
0 |
30 |
56 |
|
Jeff Nelson |
Relief Pitcher |
5 |
3 |
3 |
45 |
0 |
0 |
0 |
3.79 |
40.1 |
44 |
1 |
18 |
17 |
8 |
2 |
0 |
22 |
35 |
|
Andy Pettitte |
Starting Pitcher |
16 |
11 |
0 |
33 |
32 |
5 |
0 |
4.24 |
216.1 |
226 |
20 |
10 |
2 |
6 |
5 |
0 |
87 |
146 |
|
Mariano Rivera |
Relief Pitcher |
3 |
0 |
36 |
54 |
0 |
0 |
0 |
1.91 |
61.1 |
48 |
3 |
13 |
13 |
1 |
0 |
0 |
17 |
36 |
|
Mike Stanton |
Relief Pitcher |
4 |
1 |
6 |
67 |
0 |
0 |
0 |
5.47 |
79 |
71 |
13 |
51 |
48 |
4 |
0 |
0 |
26 |
69 |
|
Jay Tessmer |
Relief Pitcher |
1 |
0 |
0 |
7 |
0 |
0 |
0 |
3.12 |
8.2 |
4 |
1 |
3 |
3 |
0 |
1 |
0 |
4 |
6 |
|
David Wells |
Starting Pitcher |
18 |
4 |
0 |
30 |
30 |
8 |
5 |
3.49 |
214.1 |
195 |
29 |
86 |
83 |
1 |
2 |
0 |
29 |
163 |
熟悉数据库理论的读者可能会将XML模型看作为分支型的数据库,因而也就认为与分支数据库具有同样的缺点(和少数优点)。许多时候以表为基础的关系型方法更有实际意义。在本例中,也属于有实际意义的情况。但是,XML并不遵循关系模型。
4.2 数据的XML化
让我们用XML处理1998年的Major League赛季数据的标记开始。请记住,在XML内,允许我们创建标记。我们已经决定,文档的根元素是赛季(season)。赛季包括联赛(leagues),而联赛包括分部(divisions),分部又包括球队(teams),球队包括队员(players)。队员的统计数字包括参加的场数(games played)、击球次数(at bats)、得分数(runs)、击中数(hits)、双垒(doubles)、三垒(triples)、全垒得分(home runs)、击球得分(runs batted in)、走步数(walks)和被投手击中数(hits by pitch)。
4.2.1 开始编写文档:XML声明和根元素
XML文档可由XML声明加以识别。这是放在所有XML文档的开头的一条处理指令,标识正在使用的XML版本。当前可理解的唯一版本号是1.0。
<?xml version="1.0"?>
每个合格的XML文档(所谓合格有特定的意义,这将在下一章中加以讨论)必须有一个根元素。这是一个完全包括文档中其他所有元素的元素。根元素的起始标记要放在所有其他元素的起始标记之前,而根元素的结束标记要放在所有其他元素的结束标记之后。对于我们的根元素SEASON,其起始标记是<SEASON>,而结束标记是</SEASON>。文档现在看起来像下面的样子:
<?xml version="1.0"?>
<SEASON>
</SEASON>
XML声明既不是元素也不是标记。它是处理指令。因而不需要将声明放在根元素SEASON之内。但是,我们在文档中放入的每个元素都得放在起始标记<SEASON>和结束标记</SEASON>之间。
根元素的这种选择方法说明我们已经不能在一个文件中保存多个赛季的数据了。如果想要保存多个赛季的数据的话,可以定义一个新的包括赛季(seasons)的根元素,例如,
<?xml version="1.0"?>
<DOCUMENT>
<SEASON>
</SEASON>
<SEASON>
</SEASON>
</DOCUMENT>
命名约定
在开始之前,我还要说几句关于命名约定的话。正如我们在下一章中所见到的,XML的元素名是比较灵活的,可以包括任意数目的字母和数字,既可是大写的也可是小写的。可以将XML标记写成下面的任何样子:
<SEASON>
<Season>
<season>
<season1998>
<Season98>
<season_98>
这就会有成千上万种可能的变化。全使用大写、全使用小写或是混合大小写都是可以的。但是,我推荐使用一种约定,并坚持下去。
当然,我们对所谈到的赛季加以标识。为达此目的,可为SEASON元素定义一个名为YEAR的子元素。例如:
<?xml version="1.0"?>
<SEASON>
<YEAR>
1998
</YEAR>
</SEASON>
我在此处以及其他例子中使用了缩进,以便指明元素YEAR是元素SEASON的子元素,而文本1998是元素YEAR的内容。这是一种很好的编程习惯,但这不是必须的。XML中的空白没有特殊的意义。同样的例子也可写成下面的样子:
<?xml version="1.0"?>
<SEASON>
<YEAR>1998</YEAR>
</SEASON>
确实,我经常将元素压缩到一行上(当一行上可以放得下,而空间又比较紧张时)。还可以将文档再加以压缩,即使压缩成一行也可以,但这要失去可读性。例如:
<?xml version="1.0"?><SEASON><YEAR>1998</YEAR></SEASON>
当然这样的文档是比较难以阅读和理解的,这也就是为什么我没有这样书写的原因。XML 1.0规范中的第十条目的中写道:“Terseness in XML markup is of minimal importance.”翻译成中文是,“XML标记中的简捷性是不太重要的。”棒球示例完全反映出了这个目的。
4.2.2 联赛(League)、(分部)Division和(球队)Team数据的XML化
主要棒球联赛分成两个联赛:American League和National League。每个联赛都有名称。两个名称可如下编码:
<?xml version="1.0"?>
<SEASON>
<YEAR>1998</YEAR>
<LEAGUE>
<LEAGUE_NAME>National League</LEAGUE_NAME>
</LEAGUE>
<LEAGUE>
<LEAGUE_NAME>American League</LEAGUE_NAME>
</LEAGUE>
</SEASON>
我在这里将联赛的名称定义为元素LEAGUE_NAME,而不是简单的NAME元素。因为NAME太普遍了,而且还打算将其用在其他场合。例如,分部、球队和球员都有名称。
带有相同的名称的不同领域的元素可以利用命名域(namespaces)结合在一起。命名域的问题将在第18章中加以讨论。但是,即使使用命名域,也不要将同一领域(如本例中的TEAM和LEAGUE)的多个术语给予同样的名称。
每个联赛可分为东部(east)、西部(west)和中部(central)分部,可编码如下:
<LEAGUE>
<LEAGUE_NAME>National League</LEAGUE_NAME>
<DIVISION>
<DIVISION_NAME>East</DIVISION_NAME>
</DIVISION>
<DIVISION>
<DIVISION_NAME>Central</DIVISION_NAME>
</DIVISION>
<DIVISION>
<DIVISION_NAME>West</DIVISION_NAME>
</DIVISION>
</LEAGUE>
<LEAGUE>
<LEAGUE_NAME>American League</LEAGUE_NAME>
<DIVISION>
<DIVISION_NAME>East</DIVISION_NAME>
</DIVISION>
<DIVISION>
<DIVISION_NAME>Central</DIVISION_NAME>
</DIVISION>
<DIVISION>
<DIVISION_NAME>West</DIVISION_NAME>
</DIVISION>
</LEAGUE>
元素的实际值依赖于包括该元素的父元素。American League和National League都有East分部,但是这不是一回事。
每个分部又分为多个球队。每个球队都有一个队名和城市名。例如,与American League联赛East分部有关的名称可编码如下:
<DIVISION>
<DIVISION_NAME>East</DIVISION_NAME>
<TEAM>
<TEAM_CITY>Baltimore</TEAM_CITY>
<TEAM_NAME>Orioles</TEAM_NAME>
</TEAM>
<TEAM>
<TEAM_CITY>Boston</TEAM_CITY>
<TEAM_NAME>Red Sox</TEAM_NAME>
</TEAM>
<TEAM>
<TEAM_CITY>New York</TEAM_CITY>
<TEAM_NAME>Yankees</TEAM_NAME>
</TEAM>
<TEAM>
<TEAM_CITY>Tampa Bay</TEAM_CITY>
<TEAM_NAME>Devil Rays</TEAM_NAME>
</TEAM>
<TEAM>
<TEAM_CITY>Toronto</TEAM_CITY>
<TEAM_NAME>Blue Jays</TEAM_NAME>
</TEAM>
</DIVISION>
4.2.3 球员数据的XML化
每个球队是由球员组成的。每个球员都有姓和名。将姓和名分开是重要的,这样一来既可以根据名来分类也可以根据姓来分类。1998年Yankees阵容第一个出场的投球手的数据可编码如下:
<TEAM>
<TEAM_CITY>New York</TEAM_CITY>
<TEAM_NAME>Yankees</TEAM_NAME>
<PLAYER>
<GIVEN_NAME>Orlando</GIVEN_NAME>
<SURNAME>Hernandez</SURNAME>
</PLAYER>
<PLAYER>
<GIVEN_NAME>David</GIVEN_NAME>
<SURNAME>Cone</SURNAME>
</PLAYER>
<PLAYER>
<GIVEN_NAME>David</GIVEN_NAME>
<SURNAME>Wells</SURNAME>
</PLAYER>
<PLAYER>
<GIVEN_NAME>Andy</GIVEN_NAME>
<SURNAME>Pettitte</SURNAME>
</PLAYER>
<PLAYER>
<GIVEN_NAME>Hideki</GIVEN_NAME>
<SURNAME>Irabu</SURNAME>
</PLAYER>
</TEAM>
为了更明显起见,使用标记<GIVEN_NAME>和<SURNAME>比使用<FIRST_NAME> 和<LAST_NAME>或者<FIRST_NAME>和<FAMILY_NAME>更好一些。由于不同国家的文化背景不同,可能名(given name)在先也可能姓(family name)在先。同时在所有的文化背景下,别号(surnames)不一定就是姓(family names)。
4.2.4 球员统计数据的XML化
以下几个步骤提供了每个球员的统计数据。统计数据看起来对于投球手和击球手并没有一点不同,特别是对于American League联赛,这里没有几个投球员击过球。下面是Joe Girardi在1998年的统计数据。他是一个接球手,因而我们使用击球的统计数据:
<PLAYER>
<GIVEN_NAME>Joe </GIVEN_NAME>
<SURNAME>Girard </SURNAME>
<POSITION>Catcher</POSITION>
<GAMES>78</GAMES>
<GAMES_STARTED>76</GAMES_STARTED>
<AT_BATS>254</AT_BATS>
<RUNS>31</RUNS>
<HITS>70</HITS>
<DOUBLES>11</DOUBLES>
<TRIPLES>4</TRIPLES>
<HOME_RUNS>3</HOME_RUNS>
<RBI>31</RBI>
<STEALS>2</STEALS>
<CAUGHT_STEALING>4</CAUGHT_STEALING>
<SACRIFICE_HITS>8</SACRIFICE_HITS>
<SACRIFICE_FLIES>1</SACRIFICE_FLIES>
<ERRORS>3</ERRORS>
<WALKS>14</WALKS>
<STRUCK_OUT>38</STRUCK_OUT>
<HIT_BY_PITCH>2</HIT_BY_PITCH>
</PLAYER>
现在让我们看一下一个投球手的统计数据。虽然投球手在American League中很少击球,但在National League中却常常击球,到目前为止,投球手击球的次数还是比其他球员少。根据投球手的投球表现,雇用或解雇、表扬或批评。如果投球手偶尔击中一球,则会得到额外的奖励。投球的统计包括比赛场数(games played)、得胜场数(wins)、失败场数(losses)、投球局数(innings pitched)、得分(earned runs)、成功防守次数(shutouts)、击中数(hits against)、走步放弃(walks given up)和其他数据。下面是Hideki Irabu1998年统计数据的XML编码:
<PLAYER>
<GIVEN_NAME>Hideki</GIVEN_NAME>
<SURNAME>Irabu</SURNAME>
<POSITION>Start ng P tcher</POSITION>
<WINS>13</WINS>
<LOSSES>9</LOSSES>
<SAVES>0</SAVES>
<GAMES>29</GAMES>
<GAMES_STARTED>28</GAMES_STARTED>
<COMPLETE_GAMES>2</COMPLETE_GAMES>
<SHUT_OUTS> </SHUT_OUTS>
<ERA>4.06</ERA>
<INNINGS> 73</INNINGS>
<HOME_RUNS>148</HOME_RUNS>
<RUNS>27</RUNS>
<EARNED_RUNS>79</EARNED_RUNS>
<HIT_BATTER>78</HIT_BATTER>
<WILD_PITCHES>9</WILD_PITCHES>
<BALK>6</BALK>
<WALKED_BATTER>1</WALKED_BATTER>
<STRUCK_OUT_BATTER>76</STRUCK_OUT_BATTER>
</PLAYER>
XML标记的简洁性不是太重要
从整个示例来看,我已经遵循了XML的明显的原则:“Terseness in XML markup is of minimal importance.”(XML标记的简洁性不是太重要的。)这当然对非棒球文化下的读者很有帮助。这些读者对于棒球术语及其简写不是很熟悉。比如无法了解为什么 walk的简写是 BB(base on balls)而不是人们以为的W。如果文档的大小是个问题的话,将文档用Zip一类的工具进行压缩还是很容易的。
但是,这并不意味着XML是相当长的,也不意味着手工键入是非常枯燥无味的。我承认,本例强烈地吸引我使用简略语来书写,这样一来,清晰性就丧失殆尽。如果我使用简写,那么典型的PLAYER元素可能如下:
<PLAYER>
<GIVEN_NAME>Joe</GIVEN_NAME>
<SURNAME>Girard </SURNAME>
<P>C</P>
<G>78</G>
<AB>254</AB>
<R>31</R>
<H>70</H>
<DO>11</DO>
<TR>4</TR>
<HR>3</HR>
<RBI>3 </RBI>
<BB>14</BB>
<SO>38</SO>
<SB>2</SB>
<CS>4</CS>
<HBP>2</HBP>
</PLAYER>
4.2.5 将XML组装在一起
到目前为止,我向读者展示的只是一段一段(每段一个元素)的XML文档。但现在是该将各段组装在一起,看一看包括1998年Major League赛季的统计数据的全部文档的时候了。清单4-1列出了完成了的XML文档,其中包括两个联赛、六个分部、三十个队和九个球员。
清单4-1:一份完整的XML文档
<?xml version="1.0"?>
<SEASON>
<YEAR>1998</YEAR>
<LEAGUE>
<LEAGUE_NAME>National League</LEAGUE_NAME>
<DIVISION>
<DIVISION_NAME>East</DIVISION_NAME>
<TEAM>
<TEAM_CITY>Atlanta</TEAM_CITY>
<TEAM_NAME>Braves</TEAM_NAME>
<PLAYER>
<SURNAME>Malloy</SURNAME>
<GIVEN_NAME>Marty</GIVEN_NAME>
<POSITION>Second Base</POSITION>
<GAMES>11</GAMES>
<GAMES_STARTED>8</GAMES_STARTED>
<AT_BATS>28</AT_BATS>
<RUNS>3</RUNS>
<HITS>5</HITS>
<DOUBLES>1</DOUBLES>
<TRIPLES>0</TRIPLES>
<HOME_RUNS>1</HOME_RUNS>
<RBI>1</RBI>
<STEALS>0</STEALS>
<CAUGHT_STEALING>0</CAUGHT_STEALING>
<SACRIFICE_HITS>0</SACRIFICE_HITS>
<SACRIFICE_FLIES>0</SACRIFICE_FLIES>
<ERRORS>0</ERRORS>
<WALKS>2</WALKS>
<STRUCK_OUT>2</STRUCK_OUT>
<HIT_BY_PITCH>0</HIT_BY_PITCH>
</PLAYER>
<PLAYER>
<SURNAME>Guillen</SURNAME>
<GIVEN_NAME>Ozzie </GIVEN_NAME>
<POSITION>Shortstop</POSITION>
<GAMES>83</GAMES>
<GAMES_STARTED>59</GAMES_STARTED>
<AT_BATS>264</AT_BATS>
<RUNS>35</RUNS>
<HITS>73</HITS>
<DOUBLES>15</DOUBLES>
<TRIPLES>1</TRIPLES>
<HOME_RUNS>1</HOME_RUNS>
<RBI>22</RBI>
<STEALS>1</STEALS>
<CAUGHT_STEALING>4</CAUGHT_STEALING>
<SACRIFICE_HITS>4</SACRIFICE_HITS>
<SACRIFICE_FLIES>2</SACRIFICE_FLIES>
<ERRORS>6</ERRORS>
<WALKS>24</WALKS>
<STRUCK_OUT>25</STRUCK_OUT>
<HIT_BY_PITCH>1</HIT_BY_PITCH>
</PLAYER>
<PLAYER>
<SURNAME>Bautista</SURNAME>
<GIVEN_NAME>Danny</GIVEN_NAME>
<POSITION>Outfield</POSITION>
<GAMES>82</GAMES>
<GAMES_STARTED>27</GAMES_STARTED>
<AT_BATS>144</AT_BATS>
<RUNS>17</RUNS>
<HITS>36</HITS>
<DOUBLES>1</DOUBLES>
<TRIPLES>0</TRIPLES>
<HOME_RUNS>3</HOME_RUNS>
<RBI>17</RBI>
<STEALS>1</STEALS>
<CAUGHT_STEALING>0</CAUGHT_STEALING>
<SACRIFICE_HITS>3</SACRIFICE_HITS>
<SACRIFICE_FLIES>2</SACRIFICE_FLIES>
<ERRORS>2</ERRORS>
<WALKS>7</WALKS>
<STRUCK_OUT>21</STRUCK_OUT>
<HIT_BY_PITCH>0</HIT_BY_PITCH>
</PLAYER>
<PLAYER>
<SURNAME>Williams</SURNAME>
<GIVEN_NAME>Gerald</GIVEN_NAME>
<POSITION>Outfield</POSITION>
<GAMES>129</GAMES>
<GAMES_STARTED>51</GAMES_STARTED>
<AT_BATS>266</AT_BATS>
<RUNS>46</RUNS>
<HITS>81</HITS>
<DOUBLES>18</DOUBLES>
<TRIPLES>3</TRIPLES>
<HOME_RUNS>10</HOME_RUNS>
<RBI>44</RBI>
<STEALS>1</STEALS>
<CAUGHT_STEALING>5</CAUGHT_STEALING>
<SACRIFICE_HITS>2</SACRIFICE_HITS>
<SACRIFICE_FLIES>1</SACRIFICE_FLIES>
<ERRORS>5</ERRORS>
<WALKS>17</WALKS>
<STRUCK_OUT>48</STRUCK_OUT>
<HIT_BY_PITCH>3</HIT_BY_PITCH>
</PLAYER>
<PLAYER>
<SURNAME>Glavine</SURNAME>
<GIVEN_NAME>Tom</GIVEN_NAME>
<POSITION>Starting Pitcher</POSITION>
<WINS>20</WINS>
<LOSSES>6</LOSSES>
<SAVES>0</SAVES>
<GAMES>33</GAMES>
<GAMES_STARTED>33</GAMES_STARTED>
<COMPLETE_GAMES>4</COMPLETE_GAMES>
<SHUT_OUTS>3</SHUT_OUTS>
<ERA>2.47</ERA>
<INNINGS>229.1</INNINGS>
<HOME_RUNS>202</HOME_RUNS>
<RUNS>13</RUNS>
<EARNED_RUNS>67</EARNED_RUNS>
<HIT_BATTER>63</HIT_BATTER>
<WILD_PITCHES>2</WILD_PITCHES>
<BALK>3</BALK>
<WALKED_BATTER>0</WALKED_BATTER>
<STRUCK_OUT_BATTER>74</STRUCK_OUT_BATTER>
</PLAYER>
<PLAYER>
<SURNAME>Lopez</SURNAME>
<GIVEN_NAME>Javier</GIVEN_NAME>
<POSITION>Catcher</POSITION>
<GAMES>133</GAMES>
<GAMES_STARTED>124</GAMES_STARTED>
<AT_BATS>489</AT_BATS>
<RUNS>73</RUNS>
<HITS>139</HITS>
<DOUBLES>21</DOUBLES>
<TRIPLES>1</TRIPLES>
<HOME_RUNS>34</HOME_RUNS>
<RBI>106</RBI>
<STEALS>5</STEALS>
<CAUGHT_STEALING>3</CAUGHT_STEALING>
<SACRIFICE_HITS>1</SACRIFICE_HITS>
<SACRIFICE_FLIES>8</SACRIFICE_FLIES>
<ERRORS>5</ERRORS>
<WALKS>30</WALKS>
<STRUCK_OUT>85</STRUCK_OUT>
<HIT_BY_PITCH>6</HIT_BY_PITCH>
</PLAYER>
<PLAYER>
<SURNAME>Klesko</SURNAME>
<GIVEN_NAME>Ryan</GIVEN_NAME>
<POSITION>Outfield</POSITION>
<GAMES>129</GAMES>
<GAMES_STARTED>124</GAMES_STARTED>
<AT_BATS>427</AT_BATS>
<RUNS>69</RUNS>
<HITS>17</HITS>
<DOUBLES>29</DOUBLES>
<TRIPLES>1</TRIPLES>
<HOME_RUNS>18</HOME_RUNS>
<RBI>70</RBI>
<STEALS>5</STEALS>
<CAUGHT_STEALING>3</CAUGHT_STEALING>
<SACRIFICE_HITS>0</SACRIFICE_HITS>
<SACRIFICE_FLIES>4</SACRIFICE_FLIES>
<ERRORS>2</ERRORS>
<WALKS>56</WALKS>
<STRUCK_OUT>66</STRUCK_OUT>
<HIT_BY_PITCH>3</HIT_BY_PITCH>
</PLAYER>
<PLAYER>
<SURNAME>Galarraga</SURNAME>
<GIVEN_NAME>Andres</GIVEN_NAME>
<POSITION>First Base</POSITION>
<GAMES>153</GAMES>
<GAMES_STARTED>151</GAMES_STARTED>
<AT_BATS>555</AT_BATS>
<RUNS>103</RUNS>
<HITS>169</HITS>
<DOUBLES>27</DOUBLES>
<TRIPLES>1</TRIPLES>
<HOME_RUNS>44</HOME_RUNS>
<RBI>121</RBI>
<STEALS>7</STEALS>
<CAUGHT_STEALING>6</CAUGHT_STEALING>
<SACRIFICE_HITS>0</SACRIFICE_HITS>
<SACRIFICE_FLIES>5</SACRIFICE_FLIES>
<ERRORS>1</ERRORS>
<WALKS>63</WALKS>
<STRUCK_OUT>146</STRUCK_OUT>
<HIT_BY_PITCH>25</HIT_BY_PITCH>
</PLAYER>
<PLAYER>
<SURNAME>Helms</SURNAME>
<GIVEN_NAME>Wes</GIVEN_NAME>
<POSITION>Third Base</POSITION>
<GAMES>7</GAMES>
<GAMES_STARTED>2</GAMES_STARTED>
<AT_BATS>13</AT_BATS>
<RUNS>2</RUNS>
<HITS>4</HITS>
<DOUBLES>1</DOUBLES>
<TRIPLES>0</TRIPLES>
<HOME_RUNS>1</HOME_RUNS>
<RBI>2</RBI>
<STEALS>0</STEALS>
<CAUGHT_STEALING>0</CAUGHT_STEALING>
<SACRIFICE_HITS>0</SACRIFICE_HITS>
<SACRIFICE_FLIES>0</SACRIFICE_FLIES>
<ERRORS>1</ERRORS>
<WALKS>0</WALKS>
<STRUCK_OUT>4</STRUCK_OUT>
<HIT_BY_PITCH>0</HIT_BY_PITCH></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>
<TEAM>
<TEAM_CITY>Cincinatti</TEAM_CITY>
<TEAM_NAME>Reds</TEAM_NAME>
</TEAM>
<TEAM>
<TEAM_CITY>Houston</TEAM_CITY>
<TEAM_NAME>Astros</TEAM_NAME>
</TEAM>
<TEAM>
<TEAM_CITY>Milwaukee</TEAM_CITY>
<TEAM_NAME>Brewers</TEAM_NAME>
</TEAM>
<TEAM>
<TEAM_CITY>Pittsburgh</TEAM_CITY>
<TEAM_NAME>Pirates</TEAM_NAME>
</TEAM>
<TEAM>
<TEAM_CITY>St. Louis</TEAM_CITY>
<TEAM_NAME>Cardinals</TEAM_NAME>
</TEAM>
</DIVISION>
<DIVISION>
<DIVISION_NAME>West</DIVISION_NAME>
<TEAM>
<TEAM_CITY>Arizona</TEAM_CITY>
<TEAM_NAME>Diamondbacks</TEAM_NAME>
</TEAM>
<TEAM>
<TEAM_CITY>Colorado</TEAM_CITY>
<TEAM_NAME>Rockies</TEAM_NAME>
</TEAM>
<TEAM>
<TEAM_CITY>Los Angeles</TEAM_CITY>
<TEAM_NAME>Dodgers</TEAM_NAME>
</TEAM>
<TEAM>
<TEAM_CITY>San Diego</TEAM_CITY>
<TEAM_NAME>Padres</TEAM_NAME>
</TEAM>
<TEAM>
<TEAM_CITY>San Francisco</TEAM_CITY>
<TEAM_NAME>Giants</TEAM_NAME>
</TEAM>
</DIVISION>
</LEAGUE>
<LEAGUE>
<LEAGUE_NAME>American League</LEAGUE_NAME>
<DIVISION>
<DIVISION_NAME>East</DIVISION_NAME>
<TEAM>
<TEAM_CITY>Baltimore</TEAM_CITY>
<TEAM_NAME>Orioles</TEAM_NAME>
</TEAM>
<TEAM>
<TEAM_CITY>Boston</TEAM_CITY>
<TEAM_NAME>Red Sox</TEAM_NAME>
</TEAM>
<TEAM>
<TEAM_CITY>New York</TEAM_CITY>
<TEAM_NAME>Yankees</TEAM_NAME>
</TEAM>
<TEAM>
<TEAM_CITY>Tampa Bay</TEAM_CITY>
<TEAM_NAME>Devil Rays</TEAM_NAME>
</TEAM>
<TEAM>
<TEAM_CITY>Toronto</TEAM_CITY>
<TEAM_NAME>Blue Jays</TEAM_NAME>
</TEAM>
</DIVISION>
<DIVISION>
<DIVISION_NAME>Central</DIVISION_NAME>
<TEAM>
<TEAM_CITY>Chicago</TEAM_CITY>
<TEAM_NAME>White Sox</TEAM_NAME>
</TEAM>
<TEAM>
<TEAM_CITY>Kansas City</TEAM_CITY>
<TEAM_NAME>Royals</TEAM_NAME>
</TEAM>
<TEAM>
<TEAM_CITY>Detroit</TEAM_CITY>
<TEAM_NAME>Tigers</TEAM_NAME>
</TEAM>
<TEAM>
<TEAM_CITY>Cleveland</TEAM_CITY>
<TEAM_NAME>Indians</TEAM_NAME>
</TEAM>
<TEAM>
<TEAM_CITY>Minnesota</TEAM_CITY>
<TEAM_NAME>Twins</TEAM_NAME>
</TEAM>
</DIVISION>
<DIVISION>
<DIVISION_NAME>West</DIVISION_NAME>
<TEAM>
<TEAM_CITY>Anaheim</TEAM_CITY>
<TEAM_NAME>Angels</TEAM_NAME>
</TEAM>
<TEAM>
<TEAM_CITY>Oakland</TEAM_CITY>
<TEAM_NAME>Athletics</TEAM_NAME>
</TEAM>
<TEAM>
<TEAM_CITY>Seattle</TEAM_CITY>
<TEAM_NAME>Mariners</TEAM_NAME>
</TEAM>
<TEAM>
<TEAM_CITY>Texas</TEAM_CITY>
<TEAM_NAME>Rangers</TEAM_NAME>
</TEAM>
</DIVISION>
</LEAGUE>
</SEASON>
图4-1显示的是将本文档装入Internet Explorer 5.0的情况。
图4-1 在Internet Explorer 5.0中显示的1998年主要联赛的统计数据
即使现在这个文档也是不完全的。此文档只包括一个队的球员(Atlanta Braves队)而且只有该球队的九个球员。如果将全部都写出来的话,则示例就会变得太长,以至于本书无法将其包括。
在名为1998statistics.xml的更为完整的XML文档中,包括了1998年度两大联赛的所有球员的统计数据,这个文档附在本书光盘中,目录为examples/base-ball。同时,我故意将所包括的数据加以限制,以便符合本书的篇幅。实际上,可以包括更为详细的数据。我已经间接提到可按比赛场次、投球次数等来安排数据的可能性。即使没有那样做,还是有许多细节可以添加到每个元素中。球队还有教练、经理、老板(说到Yankees队怎能不提到George Steinbrenner呢?)、室内运动场和其他项目。
我还故意忽略了可以从这里给出的其他项目中计算出来的数字,如平均击球数等。不管如何,球员还有许多其他数据,如臂长、身高、出生日期等。当然球员远不止这里列出的几个。所有这一切都是很容易加进XML文档的。但是我们的XML化就到此为止了,这样我们才能往下进行,首先要简短地讨论一下为什么这一数据格式是有用的,然后再讨论在Web浏览器上实际显示该文档所用的技术。
4.3 XML格式的优点
表4-1对于显示一个球队的击球数据是简捷且易于理解的。我们从改写成的简单4-1中的长得多的形式中会得到什么好处呢?好处有如下几种:
· 数据是自说明的
- 数据可用标准工具加以处理
- 数据可用标准工具查看
- 用样式单可容易地生成同样数据的不同视图
XML格式的第一条主要好处是数据是自描述的。每个数字的意义是清楚的,且不会错误地与数字本身相联系。当读取文档时,用户了解<HITS> 2 </HITS>中的2指的是击中数而不是得分或是防守。如果键入文档的打字员漏掉了一个数字,不会造成其后的数字都错了位。HITS就是HITS,即使它前面的RUNS元素丢失也没关系。
在本书第二部分中,读者会看到,XML还可以使用DTD来加强限制,使得某些元素,如HITS或RUNS必须存在。
第二条好处是XML提供的数据可用广泛的具有XML处理能力的工具加以处理,从相当贵的软件,如Adobe FrameMaker 到免费软件,如Python和Perl。数据量可以很大,但是数据额外的冗余就允许使用更多的工具来处理它。
当查看数据时,也同样有这样的问题。XML文档可装入Internet Explorer 5.0、Mozilla、FrameMaker 5.5.6和许多其他工具,所有这些工具都提供唯一的、有用的一种数据的视图。数据还可以装入简单的文本编辑器中,如vi、BBEdit和TextPad。这就使得数据或多或少的可在多种平台上查看。
使用新软件也不是获得数据的不同视图的唯一方法。在下一节中,我们将为棒球统计数据创建一个样式单,来提供一种与图4-1完全不同的查看数据的方法。每当对同一文档施加不同的样式单,都可以看到不同图景。
最后,要向自己发问,文件大小真是很成问题吗?当前硬盘容量已经相当大了,可以存入大量数据,即使存储得不太节省也没有太大的关系。同时,XML文件的压缩率很大。全部的两大棒球联赛1998年统计数据的文档是653K。如果用gzip 压缩一下的话,只有66K,几乎压缩了90%。先进的HTTP服务器,如Jigsaw可以发送压缩文件,而不必解压缩,因而文档所用的网络带宽与其实际信息内容已相当接近。最后,我们不能认为二进制文件格式(特别通用的格式)必定是高效的。包含1998statistics.xml文件同样数据的Microsoft Excel文件的大小达到了2.37MB,比XML格式大了三倍多。虽然我们能够创建更为有效的文件格式和编码方法,但实际上简单并不是必须的。
4.4 编制样式单以便显示文档
图4-1中的XML文档的原始视图对于某些应用来说也是不错的。例如,此视图允许折叠和展开单个的元素,因而可以只看文档中要看的部分。但大多数时候,人们总希望看到更好的形式,特别是,想要在Web上显示数据时。为了提供更好的外观,必须为文档编写样式单。
在本章中,我们使用的是CSS样式单。CSS样式单将特定的格式化信息与文档中的每个元素联系起来。我们的XML文档中使用的元素的完全列表如下:
SEASON
YEAR
LEAGUE
LEAGUE_NAME
DIVISION
DIVISION_NAME
TEAM
TEAM_CITY
TEAM_NAME
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
一般来说,我们要用重复的过程来为每个元素增加样式规则,一次一个元素地进行,然后检查是否达到了要求,再处理下一个元素。在本例中,这种办法对于不熟悉样式单属性的人来说也有好处。
4.4.1 与样式单连接
样式单的名称可随便取。如果只是为一个文档编制样式单,那么习惯上样式单的文件与文档的文件名一样,但是三字母的扩展名是.css而不是.xml。例如,对于XML文档1998shortstats.xml来说,样式单文件可以叫做1998shortstats.css。另一方面,如果同样的样式单还要用于许多文档,那么,可能需要更为普通的文件名,如baseballstats.css。
由于CSS样式单是级联的,同一文档可有不止一个样式单。因而baseballstats.css可向文档施加某些一般的样式规则,而1998shortstats.css可覆盖其中的几条规则,以便在同一文档(1998shortstats.xml)中处理特定的细节。我们将第12章“级联样式单(级别1)”中讨论这一问题。
为了将样式单与文档联系起来,只要像下面所示简单地在XML声明和根元素间增加一个<?xml-stylesheet?>处理指令就可以了:
<?xml version="1.0" standalone="yes"?>
<?xml-stylesheet type="text/css" href="baseballstats.css"?>
<SEASON>
...
这条指令告诉浏览器读取文档并施加保存在文件baseballstats.css中的样式单。这个文件是假设放在与XML文件同一服务器上的同一目录中的。换句话说,baseballstats.css是个相对的URL。完全的URL也是可以使用的。例如:
<?xml version="1.0" standalone="yes"?>
<?xml-stylesheet type="text/css"
href="http://metalab.unc.edu/xml/examples/baseballstats.css"?>
<SEASON>
...
开始时,用户可以简单地将一个名为baseballstats.css的空文件放在与XML文档相同的目录中。然后向1998shortstats.xml (清单4-1)中增加适当的指令,该文档现在在浏览器中的外观如图4-2所示。只显示了元素内容。可折叠的大纲视图(图4-1)不见了。元素内容的格式使用的是浏览器的缺省格式,在本例中是黑色12磅的Times Roman 字体放在白色背景上。
图4-2 使用了空白样式之后的1998年两大棒球联赛的统计数字显示
如果在指定位置找不到样式单处理指令(xml-stylesheet)中指定的样式单文件名,也可看到一个很像图4-2的视图。
4.4.2 为根元素指定样式规则
用户不必为每个元素指定样式规则。许多元素允许将其父元素的样式串接下来。因而最重要的样式是根元素的样式,在本例中就是SEASON元素。这个样式定义了页面上所有其他元素的缺省样式。大致为72 dpi的分辨率的计算机显示器不如纸上300dpi或更大的分辨率那样高。所以,Web页面通常应该使用较大磅数的字号。首先将缺省样式定义为白色背景上的14磅黑色字,定义如下:
SEASON {font-size: 14pt; background-color: white;
color: black; display: block}
将这条语句放在一个文本文件中,将其以文件名baseballstats.css与清单4-1中的文件(1998shortstats.xml)保存在同一目录中。在浏览器中打开1998shortstats.xml。我们就会看到如图4-3所示的情况。
在图4-2和图4-3之间字号发生了变化,但文本颜色和背景颜色没有变化。其实这没有必要加以设置,因为黑色文本和白色背景是缺省的。但明确地加以设置也没有损失什么。
图4-3 以14磅白地黑字显示的棒球统计数据
4.4.3 为标题指定样式规则
元素YEAR或多或少可算是文档的标题。因而使其显示得大一些,用32磅的字号也就足够大了。同时,它还应该从文档的其余部分突出出来,而不是简单地与其他内容混在一起。利用下面的样式规则可以达到这些目的:
YEAR {display: block; font-size: 32pt; font-weight: bold;
text-align: center}
图4-4显示的是将此规则增加到样式单中之后的文档。请特别注意,在“1998”后面的换行。有这个换行是由于YEAR是块级元素。而在文档中的其他元素都是内联元素。我们只能使块级元素居中(或左对齐、右对齐或两端对齐)。
图4-4 将YEAR元素格式化为标题
在使用了这种样式单的文档中,YEAR元素与HTML中的H1标题元素的功能重复了。 由于这个文档是非常整齐地分支结构,几个其他元素的功能与HTML中的H2、H3等相似。这些元素都可以用相似的规则加以格式化,只是将字号略微减小一些罢了。
例如,SEASON由两个LEAGUE元素组成。每个LEAGUE的名称,即LEAGUE_NAME元素,起了HTML中的H2元素一样的作用。每个LEAGUE元素又由三个DIVISION元素所组成。每个DIVISION的名称,也就是DIVISION_NAME元素,具有HTML中的H3元素的作用。这两条规则分别将这两种元素加以格式化:
LEAGUE_NAME {display: block; text-align: center; font-size:
28pt; font-weight: bold}
DIVISION_NAME {display: block; text-align: center; font-size:
24pt; font-weight: bold}
图4-5显示的是最后的文档。
图4-5 将LEAGUE_NAME和DIVISION_NAME元素格式化为下级标题
HTML和XML的一个重要区别是,在HTML中通常不会出现在一个元素中既包括节标题(H2、H3、H4等),又包括该节的完整内容的情况。节的内容必须包括在一级标题的结束和下一个同级标题的开始之间。这对于必须分析HTML文档的语法的软件来说是非常重要的,例如,要自动生成目录时。
Divisions又分成为TEAM元素。要将此格式化需要一些技巧,因为球队的标题并不就是TEAM_NAME元素,而是TEAM_CITY元素与TEAM_NAME拼接在一起的。所以这需要的是内联元素而不是单独的块级元素。然而,它们仍然是标题,因而我们将其设置为粗斜体的20磅字体。图4-6显示的是将这两条规则加到样式单中的结果。
TEAM_CITY {font-size: 20pt; font-weight: bold;
font-style: italic}
TEAM_NAME {font-size: 20pt; font-weight: bold;
font-style: italic}
图4-6 为队名设置样式
到此为止,将队名与城市名作为结合起来的块级元素来排列结果可能会是不错的。有几种办法可达到这个目的。例如,可以向XML文档中增加一个附加的TEAM_TITLE元素,其目的只是为了包括TEAM_NAME和TEAM_CITY。例如:
<TEAM>
<TEAM_TITLE>
<TEAM_CITY>Colorado</TEAM_CITY>
<TEAM_NAME>Rockies</TEAM_NAME>
</TEAM_TITLE>
</TEAM>
接着,可以增加一条向TEAM_TITLE施加块级格式化的样式规则:
TEAM_TITLE {display: block; text-align: center}
但是,绝不应该为了使样式单简单一些而重新排列XML文档。毕竟,样式单的总的目的是将格式化信息保存于文档之外。不过,用户可以通过别的办法达到同样的效果。其办法是,使紧挨着的上一个和下一个元素变成块级元素,也就是说,将TEAM和PLAYER变成块级元素。这就将TEAM_NAME和TEAM_CITY放在了由它们本身组成的隐式块级元素之中了。图4-7显示了其结果。
TEAM {display: block}
PLAYER {display: block}
图4-7 作为段标题而格式化的队名和城市名
4.4.4 为球员和统计元素指定样式规则
本文档需要的最具技巧的格式化是对每个球员及其统计数据的格式化。每个队有几十个球员。每个球员都有统计数据。应该将TEAM元素看作是由PLAYER元素组成的,且将每个球员放在他自己的块级节中,正如前一个元素所做的那样。不过,排列这些数据的更为吸引人且更为有效的方法是使用表格。达到这一目的的样式规则如下所示:
TEAM {display: table}
TEAM_CITY {display: table-caption}
TEAM_NAME {display: table-caption}
PLAYER {display: table-row}
SURNAME {display: table-cell}
GIVEN_NAME {display: table-cell}
POSITION {display: table-cell}
GAMES {display: table-cell}
GAMES_STARTED {display: table-cell}
AT_BATS {display: table-cell}
RUNS {display: table-cell}
HITS {display: table-cell}
DOUBLES {display: table-cell}
TRIPLES {display: table-cell}
HOME_RUNS {display: table-cell}
RBI {display: table-cell}
STEALS {display: table-cell}
CAUGHT_STEALING {display: table-cell}
SACRIFICE_HITS {display: table-cell}
SACRIFICE_FLIES {display: table-cell}
ERRORS {display: table-cell}
WALKS {display: table-cell}
STRUCK_OUT {display: table-cell}
HIT_BY_PITCH {display: table-cell}
遗憾的是,只有CSS2才支持表格属性,而Internet Explorer 5.0和其他写作本书时已存在的浏览器还不支持CSS2。由于还不能使用表格的格式化方法,我们只好使TEAM和PLAYER成为块级元素,而让其他数据保持缺省格式。
4.4.5 本节小结
清单4-2列出了完成后的样式单。CSS样式单除了一条一条的规则之外,这种样式单没有什么结构。实际上,样式单只是我在上面分别介绍过的所有规则的列表。列表中的顺序不是很重要,只要每条规则都包含进去也就可以了。
清单4-2:baseballstats.css
SEASON {font-size: 4pt; background-color: white;
color: black; display: block}
YEAR {display: block; font-size: 32pt; font-weight: bold;
text-align: center}
LEAGUE_NAME {display: block; text-align: center;
font-size: 28pt; font-weight: bold}
DIVISION_NAME {display: block; text-align: center;
font-size: 24pt; font-weight: bold}
TEAM_CITY {font-size: 20pt; font-weight: bold;
font-style: italic}
TEAM_NAME {font-size: 20pt; font-weight: bold;
font-style: italic}
TEAM {display: block}
PLAYER {display: block}
到此就完成了棒球统计数据的基本格式化的任务。不过很清楚,还有许多工作要做。支持真正表格格式化的浏览器将会大有帮助。然而还有其他工作。下面指出这些工作,其顺序没有什么关系:
· 只是列出了原始的数字,而没有说明数字代表了什么。每个数字应该有一个为其命名的标题,如“RBI”或是“At Bats”。
- 令人感兴趣的数据,如平均击球数由于是可能从这里列出的数据中计算出来的,这样的数据就没有包括进来。
- 某些标题有点太短。如果文档的标题是“1998 Major League Baseball”而不是简单的“1998”可能会更好。
- 如果Major League中的所有球员都包括进来,这一文档就会如此之长,以至于难以阅读。在这种情况下,与Internet Explorer中的可折叠的大纲视图类似的东西可能会更有用。
- 由于投手统计数据与击球手的数据是如此不同,在花名册中分别排序可能会更好。
像这样一类的许多看法都应该向文档中增加更多的内容加以体现。例如,为了将标题从“1998”改为“1998 Major League Baseball”,所要做的工作只是将YEAR 元素改写如下:
1998 Major League Baseball
在每个花名册的顶部,用一个假想的球员名,为球员的统计数据加进小标题,如下所示:
<PLAYER>
<SURNAME>Surname</SURNAME>
<GIVEN_NAME>Given name</GIVEN_NAME>
<POSITION>Position</POSITION>
<GAMES>Games</GAMES>
<GAMES_STARTED>Games Started</GAMES_STARTED>
<AT_BATS>At Bats</AT_BATS>
<RUNS>Runs</RUNS>
<HITS>Hits</HITS>
<DOUBLES>Doubles</DOUBLES>
<TRIPLES>Triples</TRIPLES>
<HOME_RUNS>Home Runs</HOME_RUNS>
<RBI>Runs Batted In</RBI>
<STEALS>Steals</STEALS>
<CAUGHT_STEALING>Caught Stealing</CAUGHT_STEALING>
<SACRIFICE_HITS>Sacrifice Hits</SACRIFICE_HITS>
<SACRIFICE_FLIES>Sacrifice Flies</SACRIFICE_FLIES>
<ERRORS>Errors</ERRORS>
<WALKS>Walks</WALKS>
<STRUCK_OUT>Struck Out</STRUCK_OUT>
<HIT_BY_PITCH>Hit By Pitch</HIT_BY_PITCH>
</PLAYER>
关于这种方法还有一些基本问题需要解决。年份是1998年,而不是1998 Major League Baseball 。小标题“At Bats”与击球数不是一回事。(这正是事物的名称与事物本身之间的差别。)这时可增加一些标记如下(加以解决):
<TABLE_HEAD>
<COLUMN_LABEL>Surname</COLUMN_LABEL>
<COLUMN_LABEL>Given name</COLUMN_LABEL>
<COLUMN_LABEL>Position</COLUMN_LABEL>
<COLUMN_LABEL>Games</COLUMN_LABEL>
<COLUMN_LABEL>Games Started</COLUMN_LABEL>
<COLUMN_LABEL>At Bats</COLUMN_LABEL>
<COLUMN_LABEL>Runs</COLUMN_LABEL>
<COLUMN_LABEL>Hits</COLUMN_LABEL>
<COLUMN_LABEL>Doubles</COLUMN_LABEL>
<COLUMN_LABEL>Triples</COLUMN_LABEL>
<COLUMN_LABEL>Home Runs</COLUMN_LABEL>
<COLUMN_LABEL>Runs Batted In</COLUMN_LABEL>
<COLUMN_LABEL>Steals</COLUMN_LABEL>
<COLUMN_LABEL>Caught Stealing</COLUMN_LABEL>
<COLUMN_LABEL>Sacrifice Hits</COLUMN_LABEL>
<COLUMN_LABEL>Sacrifice Flies</COLUMN_LABEL>
<COLUMN_LABEL>Errors</COLUMN_LABEL>
<COLUMN_LABEL>Walks</COLUMN_LABEL>
<COLUMN_LABEL>Struck Out</COLUMN_LABEL>
<COLUMN_LABEL>Hit By Pitch</COLUMN_LABEL>
</TABLE_HEAD>
不过这样一来,基本上是重新“发明”了HTML,而且使我们又回到了使用标记来格式化而不是用于意义了。同时,我们还重复了已经包括在元素名称中的信息。整个文档还相当大,我们还是希望文档不要太大为好。
增加击球和其他的平均数并不复杂。只要将数据作为附加的元素包括进来就可以了。例如,下面是一个带有该种数据的球员:
<PLAYER>
<SURNAME>Malloy</SURNAME>
<GIVEN_NAME>Marty</GIVEN_NAME>
<POSITION>Second Base</POSITION>
<GAMES>1</GAMES>
<GAMES_STARTED>8</GAMES_STARTED>
<ON_BASE_AVERAGE>.233</ON_BASE_AVERAGE>
<SLUGGING_AVERAGE>.321</SLUGGING_AVERAGE>
<BATTING_AVERAGE>.179</BATTING_AVERAGE>
<AT_BATS>28</AT_BATS>
<RUNS>3</RUNS>
<HITS>5</HITS>
<DOUBLES>1</DOUBLES>
<TRIPLES>0</TRIPLES>
<HOME_RUNS>1</HOME_RUNS>
<RBI>1</RBI>
<STEALS>0</STEALS>
<CAUGHT_STEALING>0</CAUGHT_STEALING>
<SACRIFICE_HITS>0</SACRIFICE_HITS>
<SACRIFICE_FLIES>0</SACRIFICE_FLIES>
<ERRORS>0</ERRORS>
<WALKS>2</WALKS>
<STRUCK_OUT>2</STRUCK_OUT>
<HIT_BY_PITCH>0</HIT_BY_PITCH>
</PLAYER>
但是,这种信息是多余的,因为这些数据可从已经包括进来的数据中计算出来。例如,平均击球数是击中的垒数被击球数除的结果,也就是HITS/AT_BAT。多余数据使得维护和更新数据变得非常困难。对一个元素的简单的改变或是增加都会引起多个位置的改变和重新计算。
真正所需要的是一种不同的样式单语言,能使我们向元素中增加样板内容并根据现存的元素内容执行转换。这样的语言是存在的,这就是可扩展的样式语言(Extensible Style Language,简写为XSL)。
可扩展的样式语言(Extensible Style Language,XSL)将在第14、15章中加以讨论。
CSS比XSL简单,对于基本的Web页面来说,也更适合一些,而且也是更为直接的文档。XSL变得相当复杂,但功能也更为强大。XSL是建立在我们已经在上面学到的简单的CSS格式化的基础之上的,但是也提供了将源文档转换为读者可以查看的不同形式的方法。在调试XML时,首先使用CSS寻找问题,然后再转到XSL,以便获得更大的灵活性,这通常是不错的主意。
4.5 本章小结
在本章中,读者看到了几个展示如何从头创建XML文档的示例。我们特别学到了如下内容:
· 如何检查包括在XML文件中的数据,以便标识元素。
- 如何用自己定义的XML标记来标记数据。
- XML格式所提供的比传统格式的优越性。
- 如何编写样式单,使文档格式化并显示出来。
本章中充满了枯燥的代码。文档是在没有太多的细节的情况下编写出来的。在下一章中,我们将要探讨在XML文档中嵌入信息的附加意义,包括特性、注释和处理指令,并看一看在XML中用另一种对棒球统计数据编码的方法。
第5章 属性、空标记和XSL
使用XML对一组给定的数据进行编码,有很多种方法。但是没有哪一种方法是唯一正确的,只是一些方法相较而言更可取,在特定的应用中更合适。本章采用前面章节中所用的棒球示例,仔细探讨使用XML创建棒球统计的不同方法。文中会特别强调使用属性存储信息和使用空标记定义元素位置。另外,鉴于CSS(级联样式单)对缺乏内容的XML元素执行起来并不顺利,我们将检验另一种功能更强大的样式单语言——XSL。
本章内容包括:
· 属性
- 属性与元素的对比
- 空标记
- XSL
5.1 属性
在上一章中,所有的数据可分为标记名或者元素的内容两类。这种方法直接易懂,但不是唯一的。XML元素与HTML中的元素一样,有自己的属性。元素的每个属性是一个名称-数值对,名称和数值分别为一个字符串,一个元素不能有两个同名的属性。
大家都熟悉HTML的属性句法,请看下面的<IMG>标记实例:
<IMG SRC=cup.gif WIDTH=89 HEIGHT=67 ALT="Cup of coffee">
该标记有4个属性,SRC属性的值是cup.gif,WIDTH属性的值是89,HEIGHT属性的值是67,ALT属性的值是Cup of coffee。然而,与HTML不同,XML中属性的值必须加引号,并且必须有与起始标记匹配的终止标记。上述标记实例用XML表示为:
<IMG SRC="IMAGE"cup.gif" WIDTH="89" HEIGHT="67" ALT="Cup of coffee">
</IMG>
HTML与XML的另一个不同点是:XML没有赋予IMG标记及其属性任何特殊意义。特别是不能保证XML浏览器会把该标记翻译成装载并显示cup.gif文件中的图像的指令。
可以很容易将属性句法应用到棒球示例中,这样会使标记显得简洁明了。例如,我们可以用SEASON元素中的一个YEAR属性代替一个YEAR子元素:
<SEASON YEAR="1998">
</SEASON>
另一方面,LEAGUE应当是SEASON的一个子元素而不是一个属性。因为在一个赛季中可能有两个联赛,而且子元素在任何时候都有可能指代不同的事物。但是,一个元素的属性名是不能重复的。因此,不能像下面的示例那样编写SEASON元素。
<SEASON YEAR="1998" LEAGUE="National" League="American">
</SEASON>
LEAGUE确实是一个子元素而不是一个属性的另一个原因是,它含有子结构,可进一步分成多个DIVISION元素,其属性值是无格式文本。XML元素可对结构方便地加以编码,而属性值却不能。
联赛名称是无结构的普通文本,每一个联赛只有一个名称,因此,LEAGUE元素含有一个NAME属性,而不是一个LEAGUE_NAME子元素:
<LEAGUE NAME="National League">
</LEAGUE>
由于属性与元素的联系比子元素更加紧密,上述的属性名应使用NAME,而不是LEAGUE_NAME,不会出错。各分部和球队这些子元素同样有NAME属性,不必担心与联赛名混淆。一个标记可以有多个属性,只要这些属性不同名即可。我们可以将各队所在的城市看作一个属性,如下所示:
<LEAGUE NAME="American League">
<DIVISION NAME="East">
<TEAM NAME="Orioles" CITY="Baltimore"></TEAM>
<TEAM NAME="Red Sox" CITY="Boston"></TEAM>
<TEAM NAME="Yankees" CITY="New York"></TEAM>
<TEAM NAME="Devil Rays" CITY="Tampa Bay"></TEAM>
<TEAM NAME="Blue Jays" CITY="Toronto"></TEAM>
</DIVISION>
</LEAGUE>
如果把每一项统计选作一个属性,一个队员将包括许多属性。下面的示例是用属性表示的Joe Girardi在1998年的统计数据。
<PLAYER GIVEN_NAME="Joe" SURNAME="Girardi"
GAMES="78" AT_BATS="254" RUNS="31" HITS="70"
DOUBLES="11" TRIPLES="4" HOME_RUNS="3"
RUNS_BATTED_IN="31" WALKS="14" STRUCK_OUT="38"
STOLEN_BASES="2" CAUGHT_STEALING="4"
SACRIFICE_FLY="1" SACRIFICE_HIT="8"
HIT_BY_PITCH="2">
</PLAYER>
清单5-1应用这种新的属性样式展示了一个完整的XML文档,文档是1998年重要棒球联赛的统计。展示的信息与上一章清单4-1中的一样(包括2个联赛、6个分部、30个球队和9名运动员),只是标记的方式不同。图5-1显示了装入到Internet Explorer 5.0中的没有任何样式单的文档。
图5-1 1998年主要棒球联赛统计,使用属性表示信息
清单5-1:使用属性存储棒球统计的完整的XML文档
<?xml version="1.0" standalone="yes"?>
<SEASON YEAR="1998">
<LEAGUE NAME="National League">
<DIVISION NAME="East">
<TEAM CITY="Atlanta" 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>
<PLAYER GIVEN_NAME="Ozzie" SURNAME="Guillen"
POSITION="Shortstop" GAMES="83" GAMES_STARTED="59"
AT_BATS="264" RUNS="35" HITS="73" DOUBLES="15"
TRIPLES="1" HOME_RUNS="1" RBI="22" STEALS="1"
CAUGHT_STEALING="4" SACRIFICE_HITS="4"
SACRIFICE_FLIES="2" ERRORS="6" WALKS="24"
STRUCK_OUT="25" HIT_BY_PITCH="1">
</PLAYER>
<PLAYER GIVEN_NAME="Danny" SURNAME="Bautista"
POSITION="Outfield" GAMES="82" GAMES_STARTED="27"
AT_BATS="144" RUNS="17" HITS="36" DOUBLES="11"
TRIPLES="0" HOME_RUNS="3" RBI="17" STEALS="1"
CAUGHT_STEALING="0" SACRIFICE_HITS="3"
SACRIFICE_FLIES="2" ERRORS="2" WALKS="7"
STRUCK_OUT="21" HIT_BY_PITCH="0">
</PLAYER>
<PLAYER GIVEN_NAME="Gerald" SURNAME="Williams"
POSITION="Outfield" GAMES="129" GAMES_STARTED="51"
AT_BATS="266" RUNS="46" HITS="81" DOUBLES="18"
TRIPLES="3" HOME_RUNS="10" RBI="44" STEALS="11"
CAUGHT_STEALING="5" SACRIFICE_HITS="2"
SACRIFICE_FLIES="1" ERRORS="5" WALKS="17"
STRUCK_OUT="48" HIT_BY_PITCH="3">
</PLAYER>
<PLAYER GIVEN_NAME="Tom" SURNAME="Glavine"
POSITION="Starting Pitcher" GAMES="33"
GAMES_STARTED="33" WINS="20" LOSSES="6" SAVES="0"
COMPLETE_GAMES="4" SHUT_OUTS="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">
</PLAYER>
<PLAYER GIVEN_NAME="Javier" SURNAME="Lopez"
POSITION="Catcher" GAMES="133" GAMES_STARTED="124"
AT_BATS="489" RUNS="73" HITS="139" DOUBLES="21"
TRIPLES="1" HOME_RUNS="34" RBI="106" STEALS="5"
CAUGHT_STEALING="3" SACRIFICE_HITS="1"
SACRIFICE_FLIES="8" ERRORS="5" WALKS="30"
STRUCK_OUT="85" HIT_BY_PITCH="6">
</PLAYER>
<PLAYER GIVEN_NAME="Ryan" SURNAME="Klesko"
POSITION="Outfield" GAMES="129" GAMES_STARTED="124"
AT_BATS="427" RUNS="69" HITS="117" DOUBLES="29"
TRIPLES="1" HOME_RUNS="18" RBI="70" STEALS="5"
CAUGHT_STEALING="3" SACRIFICE_HITS="0"
SACRIFICE_FLIES="4" ERRORS="2" WALKS="56"
STRUCK_OUT="66" HIT_BY_PITCH="3">
</PLAYER>
<PLAYER GIVEN_NAME="Andres" SURNAME="Galarraga"
POSITION="First Base" GAMES="153" GAMES_STARTED="151"
AT_BATS="555" RUNS="103" HITS="169" DOUBLES="27"
TRIPLES="1" HOME_RUNS="44" RBI="121" STEALS="7"
CAUGHT_STEALING="6" SACRIFICE_HITS="0"
SACRIFICE_FLIES="5" ERRORS="11" WALKS="63"
STRUCK_OUT="146" HIT_BY_PITCH="25">
</PLAYER>
<PLAYER GIVEN_NAME="Wes" SURNAME="Helms"
POSITION="Third Base" GAMES="7" GAMES_STARTED="2"
AT_BATS="13" RUNS="2" HITS="4" DOUBLES="1"
TRIPLES="0" HOME_RUNS="1" RBI="2" STEALS="0"
CAUGHT_STEALING="0" SACRIFICE_HITS="0"
SACRIFICE_FLIES="0" ERRORS="1" WALKS="0"
STRUCK_OUT="4" HIT_BY_PITCH="0">
</PLAYER>
</TEAM>
<TEAM CITY="Florida" NAME="Marlins">
</TEAM>
<TEAM CITY="Montreal" NAME="Expos">
</TEAM>
<TEAM CITY="New York" NAME="Mets">
</TEAM>
<TEAM CITY="Philadelphia" NAME="Phillies">
</TEAM>
</DIVISION>
<DIVISION NAME="Central">
<TEAM CITY="Chicago" NAME="Cubs">
</TEAM>
<TEAM CITY="Cincinnati" NAME="Reds">
</TEAM>
<TEAM CITY="Houston" NAME="Astros">
</TEAM>
<TEAM CITY="Milwaukee" NAME="Brewers">
</TEAM>
<TEAM CITY="Pittsburgh" NAME="Pirates">
</TEAM>
<TEAM CITY="St.Louis" NAME="Cardinals">
</TEAM>
</DIVISION>
<DIVISION NAME="West">
<TEAM CITY="Arizona" NAME="Diamondbacks">
</TEAM>
<TEAM CITY="Colorado" NAME="Rockies">
</TEAM>
<TEAM CITY="Los Angeles" NAME="Dodgers">
</TEAM>
<TEAM CITY="San Diego" NAME="Padres">
</TEAM>
<TEAM CITY="San Francisco" NAME="Giants">
</TEAM>
</DIVISION>
</LEAGUE>
<LEAGUE NAME="American League">
<DIVISION NAME="East">
<TEAM CITY="Baltimore" NAME="Orioles">
</TEAM>
<TEAM CITY="Boston" NAME="Red Sox">
</TEAM>
<TEAM CITY="New York" NAME="Yankees">
</TEAM>
<TEAM CITY="Tampa Bay" NAME="Devil Rays">
</TEAM>
<TEAM CITY="Toronto" NAME="Blue Jays">
</TEAM>
</DIVISION>
<DIVISION NAME="Central">
<TEAM CITY="Chicago" NAME="White Sox">
</TEAM>
<TEAM CITY="Kansas City" NAME="Royals">
</TEAM>
<TEAM CITY="Detroit" NAME="Tigers">
</TEAM>
<TEAM CITY="Cleveland" NAME="Indians">
</TEAM>
<TEAM CITY="Minnesota" NAME="Twins">
</TEAM>
</DIVISION>
<DIVISION NAME="West">
<TEAM CITY="Anaheim" NAME="Angels">
</TEAM>
<TEAM CITY="Oakland" NAME="Athletics">
</TEAM>
<TEAM CITY="Seattle" NAME="Mariners">
</TEAM>
<TEAM CITY="Texas" NAME="Rangers">
</TEAM>
</DIVISION>
</LEAGUE>
</SEASON>
在清单5-1中,队员的信息是用属性表示的,清单4-1是用元素内容表示的。当然,也有合二为一的表示方法。例如,队员的名字作为元素内容,而其他部分作为属性,如下所示:
<P>
On Tuesday<PLAYER GAMES="78" AT_BATS="254" RUNS="31"
HITS="70" DOUBLES="11" TRIPLES="4" HOME_RUNS="3"
RUNS_BATTED_IN="31" WALKS="14" STRIKE_OUTS="38"
STOLEN_BASES="2" CAUGHT_STEALING="4"
SACRIFICE_FLY="1" SACRIFICE_HIT="8"
HIT_BY_PITCH="2">Joe Girardi</PLAYER>struck out twice
and...
</P>
这样处理后,Joe Girardi的名字会以一个超链接文本的脚注或者工具提示包含在页面文本中,同时能保证希望深究它的读者得到该统计。一组相同的数据可以用多种方法进行编码,具体选择哪一种编码方法取决于用户特定应用的需要。
5.2 属性与元素的对比
何时使用子元素或属性没有严格的规则可循,通常要看哪一种更适合自己应用的需要。随着经验的增长就会有一种感觉,知道在何时使用属性比子元素更简单,反之亦然。一个好的经验规则是数据本身应当存储在元素中,而有关数据的信息(元数据)应当存储在属性中。不知道怎么做时,就把信息放在元素中。
为区分数据与元数据,首先要问自己是否会有一些读者希望看到一条特别的信息。如果答案是肯定的,该信息应当包含在一个子元素中。相反,则应包含在一个属性中。如果从该文档中删除所有标记与属性,文档的基本信息应当还存在。属性是放置ID号、URL、参考资料及其他与读者不直接相关的信息的好地方。但是,把元数据作为属性存储的基本规则还有许多例外。这些例外包括:
· 属性不能很好地保持原文的结构。
- 元素允许包括元元数据(有关信息的更深层次的信息)。
- 每个人对元数据和非元数据的理解是不一样的。
- 面对以后的变化,元素更具扩展性。
5.2.1 结构化的元数据
需要特别记住的是元素可以有子结构而属性没有。这使元素更加灵活,更方便我们将元数据编译成子元素。例如,设想我们在写一篇论文,而且希望其中包含某件事情的出处,结果可能是这样:
<FACT SOURCE="The Biographical History of Baseball,
Donald Dewey and Nicholas Acocella (New York:Carroll &
Graf Publishers,Inc.1995)p.169">
Josh Gibson is the only person in the history of baseball to
hit a pitch out of Yankee Stadium.
</FACT>
很明显,信息“The Biographical History of Baseball, Donald Dewey and Nicholas Acocella(New York:Carroll &Graf Publishers,Inc. 1995)p.169”是元数据。它不是事情本身而更像事情的有关信息。SOURCE属性暗含了许多子结构。按照下文的方法组织上面的信息可能更有效:
<SOURCE>
<AUTHOR>Donald Dewey</AUTHOR>
<AUTHOR>Nicholas Acocella</AUTHOR>
<BOOK>
<TITLE>The Biographical History of Baseball</TITLE>
<PAGES>169</PAGES>
<YEAR>1995</YEAR>
</BOOK>
</SOURCE>
此外,使用元素代替属性包含附加的信息更容易、直接,例如作者的e-mail地址,可找到文档的电子副本的URL,日报特刊的标题或主题以及其他看似重要的信息等。
日期是另外一个常见的例子。与学术论文有关的一个常用的元数据是第一次收到论文的日期,它对建立发明创造的优先权很重要。在ARTICLE标记中很容易包含一个DATE属性,如下所示:
<ARTICLE DATE="06/28/1969">
Polymerase Reactions in Organic Compounds
</ARTICLE>
DATE属性中含有用/表示的子结构,如果要从属性值中获得该结构要比读取DATE元素的子元素困难得多,如下所示:
<DATE>
<YEAR>1969</YEAR>
<MONTH>06</MONTH>
<DAY>28</DAY>
</DATE>
例如,使用CSS或XSL很容易将日期或月份格式化为看不见的形式,因此只会出现年份。请看下面使用CSS的例子:
YEAR {display:inline}
MONTH {display:none}
DAY {display:none}
如果DATE是作为属性存储的,几乎没有简单的办法可以访问其中任何一部分。我们只有用一种类似ECMAScript或Java的编程语言写一个单独的程序,才能分析其日期格式。使用标准的XML工具和子元素做起来就比较容易。
另外,属性句法显得模糊不清,"10/11/1999"究竟表示10月11日还是11月10日?不同国家的读者对它的理解是不同的。即使语法分析程序能够识别某种格式,但不能保证其他人能够正确输入日期。作此对照用XML表示就不会摸棱两可。
最后,使用DATE子元素允许一个元素有多个日期。例如,学术论文通常要交还作者修改。在此情况下,记录再次收到修改过的论文的日期也很重要。例如:
<ARTICLE>
<TITLE>
Maximum Projectile Velocity in an Augmented Railgun
</TITLE>
<AUTHOR>Elliotte Harold</AUTHOR>
<AUTHOR>Bruce Bukiet</AUTHOR>
<AUTHOR>William Peter</AUTHOR>
<DATE>
<YEAR>1992</YEAR>
<MONTH>10</MONTH>
<DAY>29</DAY>
</DATE>
<DATE>
<YEAR>1993</YEAR>
<MONTH>10</MONTH>
<DAY>26</DAY>
</DATE>
</ARTICLE>
再比如,在HTML中,IMG标记的ALT属性被限定为一个单独的文本字符串。虽然一幅图片比成千的单词更能说明问题,但还是应该用已标记的文本来代替一个IMG标记。例如,考虑图5-2中的饼形图。
图5-2 主要棒球联赛中各位置球员的分布情况
使用ALT属性对该图的最好描述如下:
<IMG SRC="IMAGE"05021.gif"
ALT="Pie Chart of Positions in Major League Baseball"
WIDTH="819" HEIGHT="623">
</IMG>
如果对上图使用一个ALT子元素描述,会更具灵活性,因为我们可以在其中嵌入标记。例如,使用一个写有相关数字的一览表去替代饼形图:
<IMG SRC="IMAGE"05021.gif" WIDTH="819" HEIGHT="623">
<ALT>
<TABLE>
<TR>
<TD>Starting Pitcher</TD><TD>242</TD><TD>20%</TD>
</TR>
<TR>
<TD>Relief Pitcher</TD><TD>336</TD><TD>27%</TD>
</TR>
<TR>
<TD>Catcher</TD><TD>104</TD><TD>9%</TD>
</TR>
<TR>
<TD>Outfield</TD><TD>235</TD><TD>19%</TD>
</TR>
<TR>
<TD>First Base</TD><TD>67</TD><TD>6%</TD>
</TR>
<TR>
<TD>Shortstop</TD><TD>67</TD><TD>6%</TD>
</TR>
<TR>
<TD>Second Base</TD><TD>88</TD><TD>7%</TD>
</TR>
<TR>
<TD>Third Base</TD><TD>67</TD><TD>6%</TD>
</TR>
</TABLE>
</ALT>
</IMG>
在得不到位图图片的情况下,甚至可以使用实际的Postscript、SVG或VML代码来形成该图片。
5.2.2 元元数据
元素可用于元数据,同样也可用于元元数据,或者信息的深层相关信息。例如,一首诗的作者是这首诗的元数据,书写作者姓名所用的语言就是这首诗的元元数据。特别是对于明显的非罗马语言,这并非是无关紧要的。例如,Odyssey的作者是Homer还是Ωμηοδ?如果使用元素就可以很容易写出:
<POET LANGUAGE="English">Homer</POET>
<POET LANGUAGE="Greek">Ωμηοδ</POET>
但是,如果POET是一个属性而不是一个元素,如下所示的这种不易操作的结构会让人感到纠缠不清:
<POEM POET="Homer" POET_LANGUAGE="English"
POEM_LANGUAGE="English">
Tell me,O Muse,of the cunning man...
</POEM>
而且如果想要同时提供诗人的英文名与希腊名的时候,这种表示方法会更显得重要:
<POEM POET_NAME_1="Homer" POET_LANGUAGE_1="English"
POET_NAME_2=" Ωμηοδ" POET_LANGUAGE_2="Greek"
POEM_LANGUAGE="English">
Tell me,O Muse,of the cunning man...
</POEM>
5.2.3 有关元数据的说明
判断元数据的决定权掌握在读者手中,不同的读者和他们的阅读目的决定哪些是元数据,哪些是数据。例如,阅读一份学报上的文章,作者的名字与文章的内容相比就显得无足轻重。但是,如果作为晋升委员会的委员浏览学报来确定发表与未发表文章的人员,作者的名字与所发表文章的数量比其内容更重要。
事实上,人们也许会改变对数据和元数据的看法。今天看似无关紧要的东西,下周可能会变得很有用。你可以使用样式单隐藏今天看似不重要的元素,在以后可改变样式单将其显示出来。但是,显示一个原先存储在属性中的信息很困难。通常在此情况下需要重写整个文档,而不是简单地修改样式单。
5.2.4 元素更具扩展性
在只需要传达一两个字的非结构性信息时,使用属性是很方便的。在此情况下,显然不需要一个子元素。但是这并不排除日后需要它。
例如,目前可能只需要存储一篇文章的作者名而不必区分名和姓。但将来可能会需要存储姓名、e-mail地址、机构、邮政通信处、URL以及更多的东西。如果把文章的作者保存为一个元素,在其中添加子元素包含这些附加的信息会很容易。
尽管上述任何改动都需要重新修改文档、样式单和相关的程序,但是把一个简单的元素修改为元素树比把一个属性修改为元素树简单得多。而且使用了属性就只好继续使用下去。扩展属性句法使之超越最初的设计范围也很困难。
5.2.5 使用属性的最佳时机
在前面已经详尽阐述了应当使用子元素代替属性的原因,然而,必须指出的是,有时候使用属性是有意义的。首先,同前面提到的一样,属性非常适用于那些读者未必想看见的没有子结构的简单数据。例如,IMG中的HEIGHT和WIDTH属性,尽管这些属性值随图片的改变而改变,但是无法想象属性中的数据除了一个很短的字符串外还能是什么。HEIGHT和WIDTH分别是一维的数,因此作为属性执行起来很顺利。
此外,属性也适用于与文档有关而与文档内容无关的简单信息。例如,给每一个元素指定一个ID属性常常是有用的,这是文档中仅隶属于元素的唯一字符串。该字符串可用于不同的目的,包括链接到文档中的特殊元素。甚至在文档发生改变时,这些元素会随之移动。例如:
<SOURCE ID="S1">
<AUTHOR ID="A1">Donald Dewey</AUTHOR>
<AUTHOR ID="A2">Nicholas Acocella</AUTHOR>
<BOOK ID="B1">
<TITLE ID="B2">
The Biographical History of Baseball
</TITLE>
<PAGES ID="B3">169</PAGES>
<YEAR ID="B4">1995</YEAR>
</BOOK>
</SOURCE>
利用ID属性使链接文档中的特定元素成为可能。这样它们就有与HTML中A元素的NAME属性一样的功能。其他与链接有关的数据——HREF属性指明的链接目标,SRC属性指定的图像和二进制数据等等——作为属性都很合适。
在第16章“Xlink”和第17章“XPointer”中讨论XLL——可扩展链接语言时,会看到更多的这种例子。
属性也常用于存储文档的特定样式信息。例如,TITLE元素一般是以粗体出现,但是如果使一个TITLE元素有粗体和斜体两种字体,可以这样描述:
<TITLE style="font-style:italic">Significant Others</TITLE>
这样做可以在不改变文档树状结构的情况下嵌入样式信息。虽然最理想的方法是使用一个单独的元素,但当不能在处理的标记集里添加元素时,这个方案会给文档作者更多的控制权。例如,一个站点的管理员需要使用某一特定的DTD,而且不希望任何人修改该DTD。除此之外,还要允许他人对个别的页面做微小的校正。使用这种方案时要有所节制,否则很快会发现自己又陷入了HTML的“地狱”中,使用XML的本意是要避免这一“地狱”的。
使用属性的最后一个原因是为了保持与HTML的兼容性。甚至扩展到使用的标记,对于诸如<IMG>、<P>和<TD>看起来与HTML相似的标记还是使用标准的HTML属性为好。这样做有双重好处,至少使传统的浏览器能够部分地分析和显示你的文档,而且对于文档的作者来说更熟悉这种方式。
5.3 空标记
上一章中没有属性的方式是一种极端的情况,由此可能会想到另一个极端——将所有的信息全部存储在属性中,而不是存储在内容中。通常不推荐使用这种方式。把信息全部存储在元素内容中同样也是极端的,只是实际处理起来更容易。这一节考虑仅使用属性来说明的可能性。
只要元素中没有内容,就可以使用空标记来简化。可以只包含一个空标记而不是一个起始标记和一个终止标记。空标记与起始标记的区别在于结束标记使用“/>”而不是简单的“>”。例如,不是<PLAYER></PLAYER>而是<PLAYER/>。
空标记可以包含属性。例如,下面是关于Joe Girardi的一个空标记,含有7个属性:
<PLAYER GIVEN_NAME="Joe" SURNAME="Girardi"
GAMES="78" AT_BATS="254" RUNS="31" HITS="70"
DOUBLES="11" TRIPLES="4" HOME_RUNS="3"
RUNS_BATTED_IN="31" WALKS="14" STRUCK_OUT="38"
STOLEN_BASES="2" CAUGHT_STEALING="4"
SACRIFICE_FLY="1" SACRIFICE_HIT="8"
HIT_BY_PITCH="2"/>
XML句法分析器对空标记的处理与非空标记是一样的。下面的PLAYER元素与前面的空标记元素PLAYER精确地说是等价的(尽管不是完全一致):
<PLAYER GIVEN_NAME="Joe" SURNAME="Girardi"
GAMES="78" AT_BATS="254" RUNS="31" HITS="70"
DOUBLES="11" TRIPLES="4" HOME_RUNS="3"
RUNS_BATTED_IN="31" WALKS="14" STRUCK_OUT="38"
STOLEN_BASES="2" CAUGHT_STEALING="4"
SACRIFICE_FLY="1" SACRIFICE_HIT="8"
HIT_BY_PITCH="2"></PLAYER>
<PLAYER/>与<PLAYER></PLAYER>之间的不同只是句法表面的不同,而没有别的不同。如果不喜欢空标记句法或者阅读起来感到困难,就不要使用它。
5.4 XSL
如图5-1所示,属性在文档的XML源视图中是可见的。但是一旦把CSS样式单施加其上,属性就会消失。图5-3显示了清单5-1使用前面章节中棒球统计样式单后的样子。它看起来是一个空白文档,因为CSS样式单仅适用于元素内容,而不适用于属性。在使用CSS时,希望显示给读者的任何数据应当是元素内容的一部分,而不是它的属性。
图5-3 当CSS施加于一个元素中不含任何字符数据的XML文档时显示的空白文档
但是,仍然有一种可选择的样式单语言能够访问并显示属性数据。这就是Extensible Style Language (XSL);Internet Explorer 5.0至少部分支持它。XSL分为两部分:变换部分和格式化部分。
XSL替换部分能够将一个标记替换为另一个标记。通过定义替换规则,使用标准的HTML标记代替XML标记或者使用HTML标记与CSS属性来替换XML标记。同时还可以在文档中重新安排元素和在XML文档中添加没有出现过的附加内容。
XSL格式化部分把功能强大的文档视图定义为页面。XSL格式化功能能够指定页面的外观和编排,包括多个专栏、围绕主题的字数、行间距、相配的字体属性等等。它的功能非常强大,足可以为网络和打印自动处理来自于相同源文档的编排任务。例如,XSL格式化允许包含有show times(在线播放)和广告的XML文档生成本地报纸上电视节目单的打印及在线版本。但是IE 5.0和大多数其他工具还不支持XSL格式化。因此,本节重点介绍XSL变换。
XSL格式化将在第15章XSL格式化对象中讨论。
5.4.1 XSL样式单模板
每个XSL样式单包括一些模板,XML文档中的数据会注入其中。例如,某一模板如下所示:
<HTML>
<HEAD>
<TITLE>
XSL Instructions to get the title
</TITLE>
</HEAD>
<H1>XSL Instructions to get the title </H1>
<BODY>
XSL Instructions to get the statistics
</BODY>
</HTML>
斜体部分将由特定的XSL元素取代,这些元素把基本的XML文档中的数据复制到该模板中。该模板可用于许多不同的数据集。例如,模板设计用于处理棒球示例,那么相同的样式单能够显示不同赛季的统计。
这令人想起了用于HTML的某种服务器端嵌入方案。事实上,这与服务器端嵌入方案极其类似。但是,XML源文档与XSL样式单的实际变换发生在客户端,而不是服务器端。而且输出的文档可以是任何一种结构完整的XML文档,不必是HTML文档。
XSL指令能够提取存储于XML文档中的任何数据。包括元素内容、元素名称和对我们的示例很重要的元素属性。特定的元素由一种模式选定,该模式会考虑元素的名称和值、元素的属性名和值以及在XML文档树状结构中的绝对和相对位置等等。数据一经从一个元素中取出,就可以移动、复制和经过其他多种处理。在这个简要的介绍中描述了使用XML变换部分所能做的事情。读者将学到使用XSL编写一些能够立即在网上看到的令人吃惊的文档。
在第14章的“XSL变换”中对XSL的变换作了彻底的阐述。
5.4.2 文档的主体
请看下面的简单例子,并把它应用于清单5-1所示的棒球统计的XML文档中,清单5-2是一个XSL样式单。它提供XML数据将要注入的HTML“模子”。
清单5-2:一个XSL样式单
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/TR/WD-xsl">
<xsl:template match="/">
<HTML xmlns:xsl="http://www.w3.org/TR/WD-xsl">
<HEAD>
<TITLE>
Major League Baseball Statistics
</TITLE>
</HEAD>
<BODY>
<H1>Major League Baseball Statistics</H1>
<HR></HR>
Copyright 1999
<A HREF="http://www.macfaq.com/personal.html">
Elliotte Rusty Harold
</A>
<BR />
<A HREF="mailto:elharo@metalab.unc.edu">
elharo@metalab.unc.edu
</A>
</BODY>
</HTML>
</xsl:template>
</xsl:stylesheet>
该清单像一个包含在XSL:template元素中的HTML文件,也就是说它的结构更像是这样:
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/TR/WD-xsl">
<xsl:template match="/">
HTML file goes here
</xsl:template>
</xsl:stylesheet>
清单5-2不仅是一个XSL样式单,同样是一个结构完整的HTML文档。它以一个XML名称开始,文档的根元素是xsl:stylesheet。该样式单包含唯一的模板,把XML数据编码为一个xsl:template元素。xsl:template元素有一个match属性,其值为/,内容是一个结构完整的HTML文档。输出的HTML结构完整不是一种巧合。因为HTML首先必须是一个XSL样式单的一部分,并且XSL样式单是结构完整的XML文档,因此在一个XSL样式单中的所有HTML一定结构完整。
Web浏览器尽量使XML文档各部分与每个xsl:template元素相匹配。/模板与文档的根即整个文档本身相匹配。浏览器读取模板并将来自XML中的数据插入XSL指令指明的位置。但是该特定模板不包含XSL指令。因此它的内容只是被逐字逐句地复制到Web浏览器中,产生如图5-4所示的输出结果。请注意该图不显示XML文档的任何数据,只显示XSL模板中的数据。把清单5-2中的XSL样式单与清单5-1中的XML文档连接起来很方便,只需增加一个<?XML-stylesheet?>处理指令,该指令位于XML声明和根元素之间,含有一个值为text/xsl的type属性和一个指向样式单的href属性。例如:
<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="5-2.xsl"?>
<SEASON YEAR="1998">
...
这与在文档上连接CSS样式单的方法一样,唯一不同的是type属性的值为text/xsl而不是text/css。
图5-4 采用清单5-2中XSL样式单后,XML文档中的数据而不是XSL模板中的数据消失了
5.4.3 标题
图5-4很明显丢失了数据。尽管清单5-2中的样式单显示了一些内容(与图5-3所示的CSS样式单不同),但是它没有显示XML文档中的任何数据。要添加这些数据需要使用XSL指令元素把XML源文档中的数据复制到XSL模板中。清单5-3增加了必要的XSL指令,从SEASON元素中抽取YEAR属性并把它插入到结果文档的TITLE和H1标头之间。图5-5显示了处理后的文档。
清单5-3:一个含有抽取SEASON元素和YEAR属性指令的XSL样式单
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/TR/WD-xsl">
<xsl:template match="/">
<HTML xmlns:xsl="http://www.w3.org/TR/WD-xsl">
<HEAD>
<TITLE>
<xsl:for-each select="SEASON">
<xsl:value-of select="@YEAR"/>
</xsl:for-each>
Major League Baseball Statistics
</TITLE>
</HEAD>
<BODY>
<xsl:for-each select="SEASON">
<H1>
<xsl:value-of select="@YEAR"/>
Major League Baseball Statistics
</H1>
</xsl:for-each>
<HR></HR>
Copyright 1999
<A HREF="http://www.macfaq.com/personal.html">
Elliotte Rusty Harold
</A>
<BR />
<A HREF="mailto:elharo@metalab.unc.edu">
elharo@metalab.unc.edu
</A>
</BODY>
</HTML>
</xsl:template>
</xsl:stylesheet>
下面的新XSL指令能够从SEASON元素中抽取YEAR属性。
<xsl:for-each select="SEASON">
<xsl:value-of select="@YEAR"/>
</xsl:for-each>
图5-5 清单5-1采用清单5-3所示的XSL样式单后的显示结果
这些指令出现两次是因为我们希望年份在输出结果中出现两次,一次在H1主题中,一次在TITLE中。这些指令每次出现都执行同样的功能。<xsl:for-each select="SEASON">寻出全部SEASON元素。<xsl:value-of select="@YEAR"/>插入SEASON元素中的YEAR属性值——这就是由<xsl:for-each select="SEASON">找到的字符串“1998”。
这非常重要,重述如下:xsl:for-each选出源文档(例如清单5-1)中的某一特定的XML元素,数据就从此元素中读取。Xsl:value-of把所选取元素的某一特定部分复制到输出文档中。因此,必须使用两个XSL指令。使用任何一个都是无效的。
XSL指令不同于输出的HTML和H1元素是因为这些指令都处于XSL的命名域内。也就是说所有的XSL元素名称都以xsl:开头。命名域由样式单根元素中的xmlns:xsl属性辨别。本书中的清单5-2,5-3和所有其他示例中xmlns:xsl属性的值都是http://www.w3.org/tr/wd-xsl。
命名域将在第18章中详细阐述。
5.4.4 联赛、分部和球队
下面通过添加一些XSL指令取出前面出现过的两个LEAGUE元素,并把这两个元素映射到H2标题中,如清单5-4所示。图5-6显示了使用该样式单后的文档。
清单5-4:一个带有提取LEAGUE元素指令的XSL样式单
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/TR/WD-xsl">
<xsl:template match="/">
<HTML>
<HEAD>
<TITLE>
<xsl:for-each select="SEASON">
<xsl:value-of select="@YEAR"/>
</xsl:for-each>
Major League Baseball Statistics
</TITLE>
</HEAD>
<BODY>
<xsl:for-each select="SEASON">
<H1>
<xsl:value-of select="@YEAR"/>
Major League Baseball Statistics
</H1>
<xsl:for-each select="LEAGUE">
<H2 ALIGN="CENTER">
<xsl:value-of select="@NAME"/>
</H2>
</xsl:for-each>
</xsl:for-each>
<HR></HR>
Copyright 1999
<A HREF="http://www.macfaq.com/personal.html">
Elliotte Rusty Harold
</A>
<BR />
<A HREF="mailto:elharo@metalab.unc.edu">
elharo@metalab.unc.edu
</A>
</BODY>
</HTML>
</xsl:template>
</xsl:stylesheet>
图5-6 当采用清单5-4中的样式单之后,联赛名称显示为H2标题样式
关键的新要素是嵌套的xsl:for-each指令:
<xsl:for-each select="SEASON">
<H1>
<xsl:value-of select="@YEAR"/>
Major League Baseball Statistics
</H1>
<xsl:for-each select="LEAGUE">
<H2 ALIGN="CENTER">
<xsl:value-of select="@NAME"/>
</H2>
</xsl:for-each>
</xsl:for-each>
最外层的指令用于选取SEASON元素,只有找到该元素才能找到它的YEAR属性,并把它的值与另外的文本Major League Baseball Statistics一起放到<H1>与</H1>之间。下一步浏览器会循环选取SEASON元素的每一个LEAGUE子元素,并把它的NAME属性值放到<H2 ALIGN ="CENTER"> 与</H2>之间。尽管只有一个xsl:for-each与LEAGUE元素相配,但是它会对SEASON元素内所有直接的LEAGUE子元素进行循环。因此,该模板在没有联赛和联赛数目不定的情况下都能工作。
同样的技巧可以用于设计表示小组的H3标题和表示各球队的H4标题。清单5-5演示了该程序,图5-7显示了使用这一样式单后的文档。各小组名称和各球队名称是从XML数据中读取的。
清单5-5:一个带有提取DIVISION和TEAM元素指令的XSL样式单
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/TR/WD-xsl">
<xsl:template match="/">
<HTML xmlns:xsl="http://www.w3.org/TR/WD-xsl">
<HEAD>
<TITLE>
<xsl:for-each select="SEASON">
<xsl:value-of select="@YEAR"/>
</xsl:for-each>
Major League Baseball Statistics
</TITLE>
</HEAD>
<BODY>
<xsl:for-each select="SEASON">
<H1>
<xsl:value-of select="@YEAR"/>
Major League Baseball Statistics
</H1>
<xsl:for-each select="LEAGUE">
<H2 ALIGN="CENTER">
<xsl:value-of select="@NAME"/>
</H2>
<xsl:for-each select="DIVISION">
<H3 ALIGN="CENTER">
<xsl:value-of select="@NAME"/>
</H3>
<xsl:for-each select="TEAM">
<H4 ALIGN="CENTER">
<xsl:value-of select="@CITY"/>
<xsl:value-of select="@NAME"/>
</H4>
</xsl:for-each>
</xsl:for-each>
</xsl:for-each>
</xsl:for-each>
<HR></HR>
Copyright 1999
<A HREF="http://www.macfaq.com/personal.html">
Elliotte Rusty Harold
</A>
<BR />
<A HREF="mailto:elharo@metalab.unc.edu">
elharo@metalab.unc.edu
</A>
</BODY>
</HTML>
</xsl:template>
</xsl:stylesheet>
图5-7 采用清单5-5所示的样式单后显示的小组名和队名
对于TEAM元素,它的CITY和NAME属性值是H4标题的内容。同时,请注意嵌套的选取赛季、小组和各队的xsl:for-each元素,它反映了文档自身的分支结构。这并不是一种巧合。其他方案可能不要求与文档的体系相匹配,但这一方案是最简单的,尤其适用于像清单5-1所示的棒球统计这样的高度结构化的数据。
5.4.5 球员
下一个步骤是为各队的每一个队员添加统计数字,最基本的方法是用一个表格表示。清单5-6展示的XSL样式单把队员以及他们的统计数据安排在一个表格中。其中没有引入新的XSL元素。相同的xsl:for-each和xsl:value-of元素被用于PLAYER元素和它的属性中。输出的是标准的HTML表格标记。图5-8显示了结果。
清单5-6:一个将球员及其统计数据放入表格的XSL样式单
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/TR/WD-xsl">
<xsl:template match="/">
<HTML>
<HEAD>
<TITLE>
<xsl:for-each select="SEASON">
<xsl:value-of select="@YEAR"/>
</xsl:for-each>
Major League Baseball Statistics
</TITLE>
</HEAD>
<BODY>
<xsl:for-each select="SEASON">
<H1>
<xsl:value-of select="@YEAR"/>
Major League Baseball Statistics
</H1>
<xsl:for-each select="LEAGUE">
<H2 ALIGN="CENTER">
<xsl:value-of select="@NAME"/>
</H2>
<xsl:for-each select="DIVISION">
<H3 ALIGN="CENTER">
<xsl:value-of select="@NAME"/>
</H3>
<xsl:for-each select="TEAM">
<H4 ALIGN="CENTER">
<xsl:value-of select="@CITY"/>
<xsl:value-of select="@NAME"/>
</H4>
<TABLE>
<THEAD>
<TR>
<TH>Player</TH><TH>P</TH><TH>G</TH>
<TH>GS</TH><TH>AB</TH><TH>R</TH><TH>H</TH>
<TH>D</TH><TH>T</TH><TH>HR</TH><TH>RBI</TH>
<TH>S</TH><TH>CS</TH><TH>SH</TH><TH>SF</TH>
<TH>E</TH><TH>BB</TH><TH>SO</TH><TH>HBP</TH>
</TR>
</THEAD>
<TBODY>
<xsl:for-each select="PLAYER">
<TR>
<TD>
<xsl:value-of select="@GIVEN_NAME"/>
<xsl:value-of select="@SURNAME"/>
</TD>
<TD><xsl:value-of select="@POSITION"/></TD>
<TD><xsl:value-of select="@GAMES"/></TD>
<TD>
<xsl:value-of select="@GAMES_STARTED"/>
</TD>
<TD><xsl:value-of select="@AT_BATS"/></TD>
<TD><xsl:value-of select="@RUNS"/></TD>
<TD><xsl:value-of select="@HITS"/></TD>
<TD><xsl:value-of select="@DOUBLES"/></TD>
<TD><xsl:value-of select="@TRIPLES"/></TD>
<TD><xsl:value-of select="@HOME_RUNS"/></TD>
<TD><xsl:value-of select="@RBI"/></TD>
<TD><xsl:value-of select="@STEALS"/></TD>
<TD>
<xsl:value-of select="@CAUGHT_STEALING"/>
</TD>
<TD>
<xsl:value-of select="@SACRIFICE_HITS"/>
</TD>
<TD>
<xsl:value-of select="@SACRIFICE_FLIES"/>
</TD>
<TD><xsl:value-of select="@ERRORS"/></TD>
<TD><xsl:value-of select="@WALKS"/></TD>
<TD>
<xsl:value-of select="@STRUCK_OUT"/>
</TD>
<TD>
<xsl:value-of select="@HIT_BY_PITCH"/>
</TD>
</TR>
</xsl:for-each>
</TBODY>
</TABLE>
</xsl:for-each>
</xsl:for-each>
</xsl:for-each>
</xsl:for-each>
<HR></HR>
Copyright 1999
<A HREF="http://www.macfaq.com/personal.html">
Elliotte Rusty Harold
</A>
<BR />
<A HREF="mailto:elharo@metalab.unc.edu">
elharo@metalab.unc.edu
</A>
</BODY>
</HTML>
</xsl:template>
</xsl:stylesheet>
5.4.6 区分投手与击球手
可以注意到图5-8中的一个缺点是没有正确处理投手。贯穿本章和第4章,投手是用完全不同的统计数字集表示的,无论他们的统计数据是存储在元素内容中还是属性中。因此,投手确实需要一个表格以区别于其他队员。在把队员放入表格之前必须查看他是不是投手。如果队员的POSITION属性包含“pitcher”就忽略他。然后在只包含投手队员元素的第二个表格中反转上面的过程,投手的PLAYER元素中POSITION属性值是字符串“pitcher”。
要完成这些还必须给xsl:for-each增加另外的选择队员的代码。我们不需要选择所有队员,相反只需要选择那些POSITION属性不是投手的队员,句法如下:
<xsl:for-each select="PLAYER [(@POSITION != Pitcher )">
因为XML文档对首发投手和替补投手做了区分,正确的答案应该检查这两种情况:
<xsl:for-each select="PLAYER [(@POSITION != StartingPitcher )
$and$(@POSITION != Relief Pitcher )]">
图5-8 采用清单5-6的XSL样式单后队员统计的显示情况
关于投手的清单,只需要把Staring Pitcher或者Relief Pitcher前的不等号变为等号。(仅仅把不等号改为等号是不能满足的,同时必须把and改为or。)句法如下:
<xsl:for-each select="PLAYER[(@POSITION= Starting Pitcher )
$or$(@POSITION= Relief Pitcher )]">
与C或JAVA语言不同,这里比较相等只用一个等号而不是双等号,因为XSL中没有赋值操作。
清单5-7显示的XSL样式单把投手和击球手区分在两个不同的表格中,投手表格为所有投手添加了常规统计项目。清单5-1将这些项目编码在以下属性中:wins(投中)、losses(失球)、saves(救球)、shutouts(被封杀)等等。相应省略了一些列标签以保持表格预定的宽度。图5-9显示了最后的结果。
图5-9 采用清单5-7的XSL样式单能够区分投手和击球手
清单5-7:区分投手和击球手的样式单
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/TR/WD-xsl">
<xsl:template match="/">
<HTML>
<HEAD>
<TITLE>
<xsl:for-each select="SEASON">
<xsl:value-of select="@YEAR"/>
</xsl:for-each>
Major League Baseball Statistics
</TITLE>
</HEAD>
<BODY>
<xsl:for-each select="SEASON">
<H1>
<xsl:value-of select="@YEAR"/>
Major League Baseball Statistics
</H1>
<xsl:for-each select="LEAGUE">
<H2 ALIGN="CENTER">
<xsl:value-of select="@NAME"/>
</H2>
<xsl:for-each select="DIVISION">
<H3 ALIGN="CENTER">
<xsl:value-of select="@NAME"/>
</H3>
<xsl:for-each select="TEAM">
<H4 ALIGN="CENTER">
<xsl:value-of select="@CITY"/>
<xsl:value-of select="@NAME"/>
</H4>
<TABLE>
<CAPTION><B>Batters</B></CAPTION>
<THEAD>
<TR>
<TH>Player</TH><TH>P</TH><TH>G</TH>
<TH>GS</TH><TH>AB</TH><TH>R</TH><TH>H</TH>
<TH>D</TH><TH>T</TH><TH>HR</TH><TH>RBI</TH>
<TH>S</TH><TH>CS</TH><TH>SH</TH><TH>SF</TH>
<TH>E</TH><TH>BB</TH><TH>SO</TH>
<TH>HBP</TH>
</TR>
</THEAD>
<TBODY>
<xsl:for-each select="PLAYER [(@POSITION
!= Starting Pitcher )
$and$(@POSITION != Relief Pitcher )]">
<TR>
<TD>
<xsl:value-of select="@GIVEN_NAME"/>
<xsl:value-of select="@SURNAME"/>
</TD>
<TD><xsl:value-of select="@POSITION"/></TD>
<TD><xsl:value-of select="@GAMES"/></TD>
<TD>
<xsl:value-of select="@GAMES_STARTED"/>
</TD>
<TD><xsl:value-of select="@AT_BATS"/></TD>
<TD><xsl:value-of select="@RUNS"/></TD>
<TD><xsl:value-of select="@HITS"/></TD>
<TD><xsl:value-of select="@DOUBLES"/></TD>
<TD><xsl:value-of select="@TRIPLES"/></TD>
<TD>
<xsl:value-of select="@HOME_RUNS"/>
</TD>
<TD><xsl:value-of select="@RBI"/></TD>
<TD><xsl:value-of select="@STEALS"/></TD>
<TD>
<xsl:value-of select="@CAUGHT_STEALING"/>
</TD>
<TD>
<xsl:value-of select="@SACRIFICE_HITS"/>
</TD>
<TD>
<xsl:value-of select="@SACRIFICE_FLIES"/>
</TD>
<TD><xsl:value-of select="@ERRORS"/></TD>
<TD><xsl:value-of select="@WALKS"/></TD>
<TD>
<xsl:value-of select="@STRUCK_OUT"/>
</TD>
<TD>
<xsl:value-of select="@HIT_BY_PITCH"/>
</TD>
</TR>
</xsl:for-each><!— PLAYER —>
</TBODY>
</TABLE>
<TABLE>
<CAPTION><B>Pitchers</B></CAPTION>
<THEAD>
<TR>
<TH>Player</TH><TH>P</TH><TH>G</TH>
<TH>GS</TH><TH>W</TH><TH>L</TH><TH>S</TH>
<TH>CG</TH><TH>SO</TH><TH>ERA</TH>
<TH>IP</TH><TH>HR</TH><TH>R</TH><TH>ER</TH>
<TH>HB</TH><TH>WP</TH><TH>B</TH><TH>BB</TH>
<TH>K</TH>
</TR>
</THEAD>
<TBODY>
<xsl:for-each select="PLAYER[(@POSITION= Starting Pitcher )
$or$(@POSITION= Relief Pitcher )]">
<TR>
<TD>
<xsl:value-of select="@GIVEN_NAME"/>
<xsl:value-of select="@SURNAME"/>
</TD>
<TD><xsl:value-of select="@POSITION"/></TD>
<TD><xsl:value-of select="@GAMES"/></TD>
<TD>
<xsl:value-of select="@GAMES_STARTED"/>
</TD>
<TD><xsl:value-of select="@WINS"/></TD>
<TD><xsl:value-of select="@LOSSES"/></TD>
<TD><xsl:value-of select="@SAVES"/></TD>
<TD>
<xsl:value-of select="@COMPLETE_GAMES"/>
</TD>
<TD>
<xsl:value-of select="@SHUT_OUTS"/>
</TD>
<TD><xsl:value-of select="@ERA"/></TD>
<TD><xsl:value-of select="@INNINGS"/></TD>
<TD>
<xsl:value-of select="@HOME_RUNS_AGAINST"/>
</TD>
<TD>
<xsl:value-of select="@RUNS_AGAINST"/>
</TD>
<TD>
<xsl:value-of select="@EARNED_RUNS"/>
</TD>
<TD>
<xsl:value-of select="@HIT_BATTER"/>
</TD>
<TD>
<xsl:value-of select="@WILD_PITCH"/>
</TD>
<TD><xsl:value-of select="@BALK"/></TD>
<TD>
<xsl:value-of select="@WALKED_BATTER"/>
</TD>
<TD>
<xsl:value-of select="@STRUCK_OUT_BATTER"/>
</TD>
</TR>
</xsl:for-each><!— PLAYER —>
</TBODY>
</TABLE>
</xsl:for-each><!— TEAM —>
</xsl:for-each><!— DIVISION —>
</xsl:for-each><!— LEAGUE —>
</xsl:for-each><!— SEASON —>
<HR></HR>
Copyright 1999
<A HREF="http://www.macfaq.com/personal.html">
Elliotte Rusty Harold
</A>
<BR />
<A HREF="mailto:elharo@metalab.unc.edu">
elharo@metalab.unc.edu
</A>
</BODY>
</HTML>
</xsl:template>
</xsl:stylesheet>
5.4.7 元素内容与select属性
本章集中讨论了使用XSL样式单格式化存储在一个元素属性中的数据,因为使用CSS无法访问属性。如果想要包含一个元素的字符数据而不是属性,XSL同样做得很好。只要简单地把元素名称当作xsl:value-of元素的select属性值就能表明一个元素的文本将被复制到输出文档中。请看清单5-8:
清单5-8:greeting.xml
<?xml version="1.0" standalone="yes"?>
<?xml-stylesheet type="text/xsl" href="greeting.xsl"?>
<GREETING>
Hello XML!
</GREETING>
假如要向标题H1中复制致词“Hello XML!”首先,使用xsl:for-each选择GREETING元素:
<xsl:for-each select="GREETING">
<H1>
</H1>
</xsl:for-each>
只用这一段语句足以把两个H1标记复制到输出中。使用没有select属性的xsl:value-of在两个H1标记之间放置GREETING元素的文本,当前元素(GREETING)的内容就会被默认选中。清单5-9显示了完整的样式单。
清单5-9:greeting.xsl
<?xml version="1.0" ?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/TR/WD-xsl">
<xsl:template match="/">
<HTML>
<BODY>
<xsl:for-each select="GREETING">
<H1>
<xsl:value-of/>
</H1>
</xsl:for-each>
</BODY>
</HTML>
</xsl:template>
</xsl:stylesheet>
使用select同样可以选择一个子元素中的内容,只需把该子元素的名称当作xsl:value-of的select属性值。例如,在上一章的棒球示例中,队员统计被存储在子元素而不是属性中。假定文档的结构是这样(事实上这种结构比本章中的基于属性的结构更常见),表示击球员表格的XSL如下所示:
<TABLE>
<CAPTION><B>Batters</B></CAPTION>
<THEAD>
<TR>
<TH>Player</TH><TH>P</TH><TH>G</TH>
<TH>GS</TH><TH>AB</TH><TH>R</TH><TH>H</TH>
<TH>D</TH><TH>T</TH><TH>HR</TH><TH>RBI</TH>
<TH>S</TH><TH>CS</TH><TH>SH</TH><TH>SF</TH>
<TH>E</TH><TH>BB</TH><TH>SO</TH><TH>HBP</TH>
</TR>
</THEAD>
<TBODY>
<xsl:for-each select="PLAYER [(POSITION
!= Starting Pitcher ) $and$(POSITION != Relief Pitcher )]">
<TR>
<TD>
<xsl:value-of select="GIVEN_NAME"/>
<xsl:value-of select="SURNAME"/>
</TD>
<TD><xsl:value-of select="POSITION"/></TD>
<TD><xsl:value-of select="GAMES"/></TD>
<TD>
<xsl:value-of select="GAMES_STARTED"/>
</TD>
<TD><xsl:value-of select="AT_BATS"/></TD>
<TD><xsl:value-of select="RUNS"/></TD>
<TD><xsl:value-of select="HITS"/></TD>
<TD><xsl:value-of select="DOUBLES"/></TD>
<TD><xsl:value-of select="TRIPLES"/></TD>
<TD><xsl:value-of select="HOME_RUNS"/></TD>
<TD><xsl:value-of select="RBI"/></TD>
<TD><xsl:value-of select="STEALS"/></TD>
<TD>
<xsl:value-of select="CAUGHT_STEALING"/>
</TD>
<TD>
<xsl:value-of select="SACRIFICE_HITS"/>
</TD>
<TD>
<xsl:value-of select="SACRIFICE_FLIES"/>
</TD>
<TD><xsl:value-of select="ERRORS"/></TD>
<TD><xsl:value-of select="WALKS"/></TD>
<TD>
<xsl:value-of select="STRUCK_OUT"/>
</TD>
<TD>
<xsl:value-of select="HIT_BY_PITCH"/>
</TD>
</TR>
</xsl:for-each><!— PLAYER —>
</TBODY>
</TABLE>
在这种情况下,在每个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子元素的内容被抽取出来并被复制到输出文档中。因为本章使用了与上一章PLAYER子元素名称相同的属性名,该示例与清单5-7几乎是一致的。主要差别是@符号没有了。它表明这是一个属性而不是一个元素。
select属性的功能很多。可选择元素:按元素位置(例如第一、第二、最后、第十七个元素等等);按特定的内容;按特殊的属性值;或者按照元素的父或子元素含有一定的内容或属性值进行选择。甚至可以使用全部布尔逻辑运算符来组合各种不同的选择条件。在14章的XSL中将要探讨使用select属性的更多可能。
5.4.8 CSS还是XSL
CSS与XSL在某种程度上是重复的。XSL的功能确实比CSS更强大,但是XSL的功能与其复杂性是分不开的。这一章仅仅涉及了XSL最基本的用途。实际上XSL更复杂,而且比CSS更难学习和使用,同时也带来了一个问题:“什么时候应该使用CSS,什么时候应该使用XSL?”
CSS比XSL得到更广泛的支持。部分CSS Level 1被Netscape 4和Internet Exploer 4支持作为HTML元素(尽管存在一些令人头疼的区别);此外,Internet Exploer 5.0和Mozilla 5.0能很好支持可以同时用于XML和HTML的大部分CSS Level 1的内容和一些CSS Level 2的内容。因此,选择CSS会与更广泛的浏览器相互兼容。
另外,CSS更成熟一些,CSS Level 1(包含目前为止我们已经看到的大部分CSS内容)和CSS Level 2是W3C的推荐规范。XSL仍然是一个早期的工作草案,而且直到本书出版后也不会最终定型。早期的XSL采纳者曾经接受过考验,而且将在形式统一的标准之前接受再一次的考验。选择CSS意味着无须为了追随软件和标准的发展不停地重写自己的样式单。但是,XSL将最终形成一个可用的标准。
因为XSL是一种新事物,不同的软件实现方式不同,实现的是草案标准的不同的子集。在写作本书的1999年春天至少有三种主要不同形式的XSL在广泛应用,到本书出版前将会有更多。如果当前浏览器中不完善的CSS操作已经让人头疼的话,那么众多的XSL变种就会使人发疯。
但是,XSL的功能很明显比CSS强大。CSS仅允许格式化元素内容,不允许改变或重新安排这些内容,必须根据元素的内容或属性为元素选择不同的格式化方式或者增添诸如署名之类简单、额外的文本。XSL非常适用于XML文档仅包含最少的数据,并且数据周围没有HTML装饰的情况。
使用XSL能够从页面上分离出关键数据,如刊头、向导栏和署名等。使用CSS不得不在数据文档中包含全部这些项目。XML+XSL允许数据文档与Web页面文档分离单独存在,从而使得XML+XSL文档更容易维护和处理。
XSL终将成为现实世界和大量数据应用的最佳选择,CSS更适合于简单的页面,如祖母用于向她们孙子寄送图片的页面。但对于这些用途,HTML已经足够。如果使用HTML行不通,XML+CSS不会有多大的帮助。相较而言,XML+XSL能够解决更多HTML不能解决的困难。对于传统的浏览器来说,仍然需要CSS,但长远看来使用XSL才是发展方向。
5.5 本章小结
在本章中,读者看到了从头创建的XML文档的示例。特别是学到如下内容:
· 信息可以保存在元素的属性中。
- 属性是包含在元素起始标记中的一个名字-数值对。
- 属性主要用来保存关于元素的元信息,而不是元素的数据。
- 属性比元素内容更不便处理。
- 对于非常简单并且不随文档改变其形式的信息,使用属性较好。特别是样式信息和链接信息,作为属性执行起来很顺利。
- 空标记给没有内容的元素提供了句法修饰。
- XSL是一种功能强大的样式单语言,使我们能够访问和显示属性数据和转换文档。
下一章将详细介绍结构完整的XML文档必须严格遵循的规则。我们还将研究另外一些在XML文档中嵌入信息如注释和处理命令的方法。
第6章 结构完整的XML文档
HTML 4.0有大约100个不同的标记,大部分标记都有多个可能的属性用于几百种不同的变化。因为XML的功能比HTML强大,你也许认为需要懂得更多标记,但不是这样。XML凭借其简洁性和可扩展性具有强大的功能,并不是大量的标记。
事实上,XML几乎没有预先定义任何标记,相反允许用户在需要时定义自己的标记。但是由自定义标记建立的这些标记和文档并不是随意的,必须遵循一组特定的规则,本章将详细阐述这些规则。遵守这些规则的文档被认为是结构完整的。结构完整是XML处理器和浏览器阅读文件必要的最起码的标准。本章将阐述用于结构完整的XML和HTML文档的规则。请特别注意XML与HTML的区别。
本章的主要内容包括:
· XML文档的组成
- 置标和字符数据
- 独立文档中的结构完整的XML
- 结构完整的HTML
6.1 XML文档的组成
XML文档包含由XML标记和字符数据组成的文本。它是一个有固定长度的有序字节的集合,并遵守特定的约束。它可能是或者不是一个文件。例如,XML文档可能:
· 存储在数据库中
- 由CGI程序在内存中瞬间创建的
- 由几个相互嵌套的不同文件组合而成
- 不存在于自身的文件中
但是如果把一个XML文档看作一个文件也是可以的,只要记住它可能并不是存在于硬盘上的真实文件。
XML由称为“实体”的存储单元组成,每个实体包含文本或者二进制数据,但不能同时存在。文本数据由字符组成,二进制数据用于图片和小程序等类内容。用一个具体的示例说明就是,一个含有<IMG>标记的原始HTML文件是一个实体而不是文档。一个HTML文件加上所有使用<IMG>标记嵌入的图片就组成一个文档。
在本章和后续几章中我们只针对由一个实体构成的简单的XML文档,即文档本身。而且这些文档只包含文本数据,不包含诸如图片小程序一类的二进制数据。这些文档能够完全独立被理解而无需读取其他文件。换句话说,它们是独立存在的。这种文档通常在它的XML标头中含有一个值为yes的standalone属性,如下所示:
<?xml version="1.0" standalone="yes"?
外部实体和实体引用用于组合多个文件和其他数据源以创建一个独立的XML文档。这样的文档如果不引用其他文件就不能进行句法分析。这些文档通常在XML声明中含有一个属性值为no的standalone属性:
<?xml version="1.0" standalone="no"?>
外部实体及实体引用将在第9章“实体与外部DTD子集”中讨论。
6.2 置标和字符数据
XML文档是文本。文本由字符组成。字符是字母、数字、标点符号、空格、制表符号或类似的东西。XML使用Unicode字符集(统一的字符编码标准集),它不仅包含来自英语和其他西欧字母表中的常见字母和符号,也包含来自古斯拉夫语、希腊语、希伯来语、阿拉伯语和梵语的字母表。另外还包含汉语和日语的象形汉字和韩国的Hangul音节表。在本章中只使用英语文本。
国际化字符集将在第7章“外语和非罗马文本”中讨论。
一个XML文档的文本可有两种用途,字符数据和置标。字符数据是文档的基本信息。另一方面,置标主要描述一个文档的逻辑结构。例如,回想一下第三章清单3-2中的greeting.xml:
<?xml version="1.0" standalone="yes"?>
<GREETING>
Hello XML!
</GREETING>
其中<?xml version="1.0" standalone="yes"?>,<GREETING>和</GREETING>是置标。Hello XML!是字符数据。XML比其他格式优越的一点是它把实际数据与置标明显地分隔开。
更确切地说,置标包括所有的注释、字符引用、实体引用、CDATA段定界符、标记、处理指令和DTD。其他的就是字符数据。但是文档被处理后,一些置标会变成字符数据。例如,置标>;变成了大于号(>)。文档经处理后留下的字符数据和所有的代表特定字符的数据称为可分析的字符数据。
6.2.1 注释
XML的注释与HTML的注释很相似,它们以<!--开始,以-->结束。介于<!--和-->之间的全部数据均被XML处理器忽略,就像它们根本不存在一样。注释用于提醒自己或临时标注出文档中不完善的部分。例如:
<?xml version="1.0" standalone="yes"?>
<!--This is Listing 3-2 from The XML Bible-—>
<GREETING>
Hello XML!
<!--Goodbye XML-->
</GREETING>
在使用注释时必须遵循以下几条规则,大致如下:
1. 注释不能出现在XML声明之前,XML声明必须是文档最前面的部分。例如,下面这种情况是不允许的:
<!--This is Listing 3-2 from The XML Bible-->
<?xml version="1.0" standalone="yes"?>
<GREETING>
Hello XML!
<!--Goodbye XML-->
</GREETING>
2. 注释不能放在标记中,例如:下面这种情况是非法的:
<?xml version="1.0" standalone="yes"?>
<GREETING>
Hello XML!
</GREETING <!--Goodbye--> >
3. 注释可以包围和隐藏标记。在下面例子中,<antigreeting>标记及其内容被当作注释;而且文档在浏览器中显示时不会出现,好像不存在一样:
<?xml version="1.0" standalone="yes"?>
<DOCUMENT>
<GREETING>
Hello XML!
</GREETING>
<!--
<ANTIGREETING>
Goodbye XML!
</ANTIGREETING>
-->
</DOCUMENT>
由于注释有效地删除了文本的一些部分,必须保证剩余的文本仍然是一个结构完整的XML文档。例如,在没有注释掉相应的结束标记前千万不要注释掉起始标记。例如,下面的语句是非法的:
<?xml version="1.0" standalone="yes"?>
<GREETING>
Hello XML!
<!--
</GREETING>
-->
一旦删除注释文本,剩余的是:
<?xml version="1.0" standalone="yes"?>
<GREETING>
Hello XML!
因为<GREETING>标记没有与之匹配的结束标记</GREETING>,这已经不再是一个结构完整的XML文档。
4. 两个连字符号(--)除了作为注释起始和结束标记的一部分外,不能出现在该注释中。例如,下面的是非法注释:
<!--The red door--that is,the second one--was left open-->
这意味着不能像下面的语句这样嵌套注释:
<?xml version="1.0" standalone="yes"?>
<DOCUMENT>
<GREETING>
Hello XML!
</GREETING>
<!--
<ANTIGREETING>
<!--Goodbye XML!-->
</ANTIGREETING>
-->
</DOCUMENT>
这也意味着如果注释掉带有表达式如i--或numberLeft--的C、Java或者JavaScript源代码时就会出现问题。通常只要意识到这个问题就不难解决。
6.2.2 实体引用
实体引用是指分析文档时会被字符数据取代的置标。XML预先定义了5个实体引用,列在表6-1中。实体引用用于XML文档中的特殊字符,否则这些字符将被解释为置标的组成部分。例如,实体引用<;代表小于号(<),否则会被解释为一个标记的起始部分。
表6-1 XML预定义的实体引用
|
实体引用 |
字 符 |
|
& |
& |
|
< |
< |
|
> |
> |
|
" |
" |
|
' |
XML中的实体引用与HTML中不同,必须以一个分号结束。因此>是正确的实体引用写法,>是不正确的。
未经处理的小于号(<)同表示“和”的符号(&)在一般的XML文本中往往被分别解释为起始标记和实体引用(特殊文本是指CDATA段,将在后面讨论)。因此,小于号同“和”号必须分别编码为<和&。例如,短语“Ben & Jerry s New York Super Fudge Chunk Ice Cream”应当写成Ben &Jerry s New York Super Fudge Chunk Ice Cream 。
大于号、双引号和撇号在它们可能会被解释成为置标的一部分时也必须编码。但是,养成全部编码的习惯要比努力推测一个特定的应用是否会被解释为置标容易得多。
实体引用也能用于属性值中。例如:
<PARAM NAME="joke" VALUE="The diner said,
"e;Waiter,There's a fly in my soup!"e;">
</PARAM>
6.2.3 CDATA
大多数情况下,出现在一对尖括号(<>)中的是置标,不在尖括号中的是字符数据。但是有一种情况例外,在CDATA段中所有文本都是纯字符数据。看起来与标记或者实体相似的仅仅是它们各自相应的文本。XML处理器无论如何是不会解释它们的。
CDATA段用于需要把整个文本解释为纯字符数据而并不是置标的情况。当有一个包含许多<、>、&或"字符而非置标的庞大文本时,这是非常有用的。对于大部分C和Java源代码,正是这种情况。
如果想使用XML写有关XML的简介,CDATA段同样非常有效。例如,在本书中包含许多小的XML代码块,而我正在使用的字处理器又不能顾及这些情况。但是如果把本书转换为XML,我将不得不很辛苦地用<代替全部小于号,&代替所有“和”字符。如下所示:
<?xml version="1.0" standalone="yes"?>
<GREETING>
Hello XML!
</GREETING>
为了避免这种麻烦,可以使用一个CDATA段表示一个不需翻译的文本块。CDATA段以<![CDATA[ 开始并以 ]]>结束,例如:
<![CDATA[
<?xml version="1.0" standalone="yes"?>
<GREETING>
Hello XML!
</GREETING>
]]>
唯一不许出现在CDATA段中的文本是CDATA的结束界定符]]>。注释可能会出现在CDATA段中,但不再扮演注释的角色。也就是说两个注释标记和包含在它们之间的全部文本都将显示出来。
因为]]>不能出现在CDATA段中,所以CDATA段不能嵌套。这使得使用XML写有关的CDATA段相对困难些。如果需要的话,必须去掉项目符号,并使用<、&和实体引用。
CDATA段不常需要,一旦需要时,它是非常有用的。
6.2.4 标记
置标能够区分XML文件与无格式文本文件。置标的最大部分是标记。前一章讲隽吮昙堑氖褂梅椒ǎ窘诮ㄒ灞昙遣⑻峁┦褂梅椒ā?/p>
简而言之,标记在XML文档中以<开始,以>结束,而且不包含在注释或者CDATA段中。因此,XML标记有与HTML标记相同的形式。开始或打开标记以<开始,后面跟有标记名。终止或结束标记以</开始,后面也跟标记名。遇到的第一个>该标记结束。
6.2.4.1 标记名
每个标记都有一个名称。标记名必须以字母或下划线(_)开始,名称中后续字符可以包含字母、数字、下划线、连字符和句号。其中不能含有空格(经常用下划线替代空格)。下面是一些合法的XML标记:
<HELP>
<Book>
<volume>
<heading1>
<section.paragraph>
<Mary_Smith>
<_8ball>
冒号出现在标记名中从语法上讲是合法的,但是它们被保留用于命名域。命名域可以混合和匹配可能使用同名标记的标记集合。命名域将在第18章讨论。
以下是句法不正确的XML标记:
<Book%7>
<volume control>
<1heading>
<Mary Smith>
<.employee.salary>
事实上标记名的规则也适用于其他许多名称,如属性名、ID属性值、实体名和其他一些将在后面几章遇到的结构。
结束标记与起始标记同名,只是在起始尖括号后加了一个/。例如,如果起始标记是<FOO>,那么结束标记是</FOO>。下面是前面所提到的合法起始标记所对应的结束标记:
</HELP>
</Book>
</volume>
</heading1>
</section.paragraph>
</Mary_Smith>
</_8ball>
XML名称是大小写敏感的。在HTML中的<P>和<p>是同一个标记,</p>可以结束一个<P>标记,但在XML中却不行。下面所示的并不是我们讨论过的合法起始标记所对应的结束标记:
</help>
</book>
</Volume>
</HEADING1>
</Section.Paragraph>
</MARY_SMITH>
</_8BALL>
尽管大小写字母均可以用在XML的标记中,从此观点出发,我会尽可能遵循使用大写的约定。这主要是因为大写在本书中可以更突出,但是有时使用的标记集是别人建立的,那么采用别人的习惯约定是必要的。
6.2.4.2 空标记
许多不含数据的HTML标记没有结束标记。例如,在HTML中没有</LI>、</IMG>、</HR>或</BR>标记。一些页面作者在所列的项目后面确实会包含</LI>标记,一些HTML工具也使用</LI>标记。但是HTML 4.0标准特别否认了这一点的必要性。同HTML中所有没有被公认的标记一样,一个不必要的</LI>的出现对交付的输出没有任何影响。
这在XML中不是问题。XML的总体观点就是在分析文档时允许发现新的标记。因此没有识别的标记就不会被简单地忽略。而且XML处理器一定能够判明以前从没出现过的一个标记有没有结束标记。
XML区分带有结束标记的标记,而不带结束标记的标记称为空标记。空标记以斜杠和一个结束尖括号(/>)结束。例如,<BR/>或<HR/>。
目前的Web浏览器处理这种标记的方法不一致,如果希望保持向后的兼容性,可以用结束标记来代替,只要在两个标记之间不包含任何文本。例如:
<BR></BR>
<HR></HR>
<IMG></IMG>
在学了后续几章中的DTD和样式单后,将会看到在必须由传统浏览器分析的文档中使用HTML可以有多种方法保持向前和向后的兼容性。
6.2.5 属性
在前面的章节中讨论过,起始标记和空标记可以随意地包含属性。属性是用等号(=)分隔开的名称-数值对。例如:
<GREETING LANGUAGE="English">
Hello XML!
<MOVIE SRC="WavingHand.mov"/>
</GREETING>
在此<GREETING>标记有一个LANGUAGE属性,其属性值是English。<MOVIE>标记有一个SRC属性,其属性值为WavingHand.mov。
6.2.5.1 属性名
属性名是字符串,遵循与标记名同样的规则。这就是,属性名必须以字母或下划线(_)开始,名称中后续字符可以包含字母、数字、下划线、连字符和句号。其中不能含有空格(经常用下划线替代空格)。
同一个标记不能有两个同名的属性。例如,下面的例子是不合法的:
<RECTANGLE SIDE="8cm" SIDE="10cm"/>
属性名是区分大小写的。SIDE属性与side或者Side属性不是同一个属性,因此以下例子是合法的:
<BOX SIDE="8cm" side="10cm" Side="31cm"/>
但是上面的这种写法很迷惑人,最好不要这样书写。
6.2.5.2 属性值
属性值也是字符串。如下面所示的LENGTH属性,即使字符串表示的是一个数,但还是两个字符7和2,不是十进制数的72。
<RULE LENGTH="72"/>
如果编写处理XML的代码,在对字符串执行算术运算之前必须把它们转换为一个数。
与属性名不同,对属性值包含的内容没有任何限制。属性值可以包含空格,可以以一个数字或任何标点符号(有时单括号和双括号除外)开头。
XML属性值由引号界定。与HTML属性不同,XML属性值必须加引号。大多数情况下是使用双引号,但是如果属性值本身含有一个引号,就需要使用单引号。例如:
<RECTANGLE LENGTH= 7" WIDTH= 8.5" />
如果属性值中含有两种引号,那么其中不用于界定字符串的一个必须用合适的实体引用代替。我通常替换两个,这种方法很管用。例如:
<RECTANGLE LENGTH= 8'7" WIDTH="10'6""/>
6.3 独立文档中结构完整的XML
尽管可以根据需要编写标记,XML文档为了保持结构完整必须遵循一定的规则。如果一个文档不是结构完整的,大部分读取和显示操作都会失败。
事实上,XML规范严格禁止XML句法分析器分析和解释结构欠妥的文档。正在执行操作的分析器唯一能做的是报告出错。它不会修改错误,不会作最大的努力显示作者想要的东西,也不会忽略不当的结构欠妥的标记。它所能做的是报告错误和退出。
这样做的目的是为了避免对错误的兼容性的竞争。这种竞争已使得编写HTML语法分析程序和显示程序变得非常困难。因为Web浏览器承认畸形的HTML,而Web页面设计者不会特别尽力确保他们的HTML正确无误。事实上,他们甚至利用个别浏览器中的错误达到特殊的效果。为了正确显示被大量安装的HTML页面,每个新的Web浏览器必须支持已有的Web浏览器的每一个细微差别和各自的属性。用户将放弃任何一种严格执行HTML标准的浏览器。正是为了避免这种遗憾,XML处理器才只接受结构完整的XML。
为了使一个文档结构完整,XML文档中的所有置标和字符数据必须遵守前几节中给出的规则。而且有几条关于如何把置标和字符数据相互联系起来的规则。这些规则总结如下:
1.文档的开始必须是XML声明。
2.含有数据的元素必须有起始标记和结束标记。
3.不含数据并且仅使用一个标记的元素必须以/>结束。
4.文档只能包含一个能够包含全部其他元素的元素。
5.元素只能嵌套不能重叠。
6.属性值必须加引号。
7.字符<和&只能用于起始标记和实体引用。
8.出现的实体引用只有&、<、>、'和"。
这八条规则稍加调整就能适用于含有一个DTD的文档,而且对于定义文档与它的DTD之间关系的完整性有另外的规则。我们将在后面几章中介绍。现在请仔细看这些用于没有DTD文档的规则。
DTD将在本书第二部分中讨论。
#1:文档必须以XML声明开始
下面是XML 1.0中独立文档的XML声明:
<?xml version="1.0" standalone="yes"?>
如果声明出现,它绝对是该文件最开头部分,因为XML处理器会读取文件最先的几个字节并将它与字符串<?XML的不同编码作比较来确定正在使用的字符串集(UTF-8、大头(高字节先传格式)或者小头(低字节先传格式))。除去看不见的字节顺序记号,在它之前不能有任何东西,包括空格。例如,下面一行用于XML的开始是不能接受的,因为在该行的前面有多余的空白。
<?xml version="1.0" standalone="yes"?>
UTF-8和Unicode的变种在第7章“外语和非罗马文本”中讨论。
XML确实允许完全省略XML声明。通常不推荐这样做,但这样做有特殊的用途。例如,省略XML声明,通过连接其他结构完整的XML文档有助于重新建立一个结构完整的XML文档。这种方法将在第9章讨论。而且,本章后面将要讲述的一种样式能够编写结构完整的HTML文档。
#2:在非空标记中使用起始和结束标记
如果忘了结束HTML的标记,Web浏览器并不严格追究。例如,如果文档包含一个<B>标记却没有相应的</B>标记,在<B>标记之后的全部文档将变为粗体。但文档仍然能显示。
XML不会如此宽松,每个起始标记必须以相应的结束标记结束。如果一个文档未能结束一个标记,浏览器或移交器将报告一个错误信息,并且不会以任何形式显示任何文档的内容。
#3:用“/>”结束空标记
不包含数据的标记,例如HTML的<BR>、<HR>和<IMG>,不需要结束标记。但是XML空标记必须由/>结束,而不是>。例如<BR>、<HR>和<IMG>的XML等价物是<BR/>、<HR/>和<IMG/>。
当前的Web浏览器处理这种标记的方法不一致。但是如果想保持向后的兼容性,可以使用结束标记来代替,而且不能在其间包含任何文本。例如:
<BR></BR>
<HR></HR>
<IMG></IMG>
即使这样,Netscape处理<BR></BR>也有困难(它把这两个标记解释为行间距,而不是前面所讲的)。因此,在HTML中包含结构完整的空标记也并非总是可行的。
#4:让一个元素完全包含其他所有元素
一个XML文档包含一个根元素,它完全包含了文档中其他所有元素。有时候这种元素被称作文档元素。假设根元素是非空的(通常都是如此),它肯定有起始标记和结束标记。这些标记可能使用但不是必须使用root或DOCUMENT命名。例如,在下面的文档中根元素是GREETING:
<?xml version="1.0" standalone="yes"?>
<GREETING>
Hello XML!
</GREETING>
XML声明不是一个元素,它更像是一个处理指令,因此不必包含在根元素中。类似地,在一个XML文档中的其他非元素数据,诸如其他处理指令、DTD和注释也不必包含在根元素中。但是所有实际的元素(除根元素本身)必须包含在根元素中。
#5:不能重叠元素
元素可以包含别的元素(大多数情况下),但是元素不能重叠。事实上是指,如果一个元素含有一个起始标记,则必须同时含有相应的结束标记。同样,一个元素不能只含有一个结束标记而没有相应的起始标记。例如,下面的XML是允许的:
<PRE><CODE>n =n +1;</CODE></PRE>
下面所示的XML是非法的,因为结束标记</PRE>放在了结束标记</CODE>之前:
<PRE><CODE>n =n +1;</PRE></CODE>
大部分HTML浏览器容易处理这种情况,但是XML浏览器会因为这种结构而报告出错。
空标记可随处出现。例如:
<PLAYWRIGHTS>Oscar Wilde<HR/>Joe Orton</PLAYWRIGHTS>
本规则与规则4联系在一起有如下含义:对于所有非根元素,只能有一个元素包含某一非根元素,但是元素不能包含其中含有非根元素的元素。这个直接含有者称为非根元素的父元素,非根元素被认为是父元素的子元素。因此,每个非根元素只有一个父元素。但是一个单独的元素可以有任意数目的子元素或不含有子元素。
请分析如下所示的清单6-1。根元素是DOCUMENT元素,它含有两个元素。第一个STATE元素含有4个子元素:NAME、TREE、FLOWER和CAPITOL。第二个STATE元素含有3个子元素:NAME、TREE和CAPITOL。这些里层的子元素只包含字符数据,没有子元素。
清单6-1:父元素和子元素
<?xml version="1.0" standalone="yes"?>
<DOCUMENT>
<STATE>
<NAME>Louisiana</NAME>
<TREE>Bald Cypress</TREE>
<FLOWER>Magnolia</FLOWER>
<CAPITOL>Baton Rouge</CAPITOL>
</STATE>
<STATE>
<NAME>Mississippi</NAME>
<TREE>Magnolia</TREE>
<CAPITOL>Jackson</CAPITOL>
</STATE>
</DOCUMENT>
在编程人员的术语中,这意味着XML文档形成了一个树。图6-1显示了清单6-1表示的树形结构以及将该结构称为树的原因。图6-1从根开始,逐级地分支延伸到树末端的叶。
树有一些好的特性使计算机程序易于读取,尽管对于文档的作者而言是无关紧要的。
图6-1 清单6-1表示的树形结构
树通常由上向下画,这就是说树的根应该在图片的顶部而不是底部。但这样看起来不像真正的树,不过并不影响数据结构的拓扑形式。
#6:属性值必须加引号
XML要求所有的属性值必须加引号,不管属性值是否包括空白。例如:
<A HREF="http://metalab.unc.edu/xml/">
HTML的要求则不是这样。比如,HTML允许标记含有不带引号的属性。例如,下面是一个合法的HTML<A>标记:
<A HREF=http://metalab.unc.edu/xml/>
唯一的要求是属性值不能嵌有空格。
如果一个属性值本身含有双引号,可以使用属性值加单引号来代替。例如:
<IMG SRC="sistinechapel.jpg"ALT= And God said,"Let there be light," and there was light />
如果一个属性值包含有单引号和双引号,可以使用实体引用'代替单引号,"代替双引号。例如:
<PARAM name="joke" value="The diner said,
"Waiter,There's a fly in my soup!"">
#7:只在开始标记和实体引用中使用<和&
XML假定最先的<是一个标记的开始,&是一个实体引用的开始(HTML也是如此,如果省略它们,大部分浏览器会假定有一个分号)。例如:
<H1>A Homage to Ben &Jerry s
New York Super Fudge Chunk Ice Cream</H1>
Web浏览器会正确地显示该标记,但是为了最大限度的安全,应当避免使用&,用&来代替,像下面这样:
<H1>A Homage to Ben &Jerry s New York Super Fudge Chunk
Ice Cream</H1>
开尖括号(<)的情况也类似。请看下面很普通的一行Java代码:
<CODE> for (int i =0;i <=args.length;i++){</CODE>
XML与HTML都会把<=中的小于号当作一个标记的开始。该标记会延续到下一个>。因此该行会现示成:
for (int i =0;i
而不是:
for (int i =0;i <=args.length;i++){
“=args.length;i++){”被解释成一个不能识别的标记的一部分。
把小于号写成<可以出现在XML和HTML文本中。例如:
<CODE> for (int i =0;i <=args.length;i++){</CODE>
结构完整的XML要求把&写成&,把<写成<,只要不是作为标记或者实体的一部分时都应如此。
#8:只能使用现有的5个实体引用
读者可能已经熟悉了几个HTML中的实体引用,例如©为插入版权号,®为插入注册商标号。但是除了已经讨论过的五个实体引用,XML只能使用预先在DTD中定义过的实体引用。
但是现在读者可能还不了解DTD,如果与字符&出现在文档中的任何地方,其后必须紧跟amp;、lt;、gt;、apos;或者quot;。所有其他的用法均会破坏结构完整性。
在第9章“实体和外部DTD子集”中将会学习如何使用DTD定义插入特殊符号和大块样板文本的新实体引用。
6.4 结构完整的HTML
即使在大部分Web浏览器还不能直接支持XML的情况下,也可以通过编写结构完整的HTML来练习XML技巧。这就是遵守XML的结构完整性约束,但只是使用标准的HTML标记的HTML。结构完整的HTML比多数人和FrontPage等工具编写的不标准的HTML容易读取,同时容易被Web机器人中用动检索引擎理解。它更为强健,对它作一些改动不会破坏它,在转移的过程中不会因为变换浏览器和操作平台对它产生影响。而且可以使用XML工具服务于HTML文档,这对于那些服务器不支持XML的读者来说仍然保持了向后的兼容性。
6.4.1 现实的Web页面存在的问题
真正的Web页面非常不标准。没有结束标记,元素重叠,在页面中包含未经处理的小于号,忽略实体引用后面的分号。存在这些问题的Web页面严格来讲是无效的,但部分浏览器能接受它们。不过如果校正了这些问题,页面将会更整洁,显示更快,更容易维护。
Web页面包含的一些常见的问题:
1. 起始标记没有对应的结束标记(没有结束元素)
2. 结束标记没有相应的起始标记
3. 元素重叠
4. 属性值未加引号
5. 没有避免使用<、>、&和"符号
6. 没有根元素
7. 结束标记与起始标记不匹配
清单大致按照其重要性排列,但确切的细节因标记不同而变化。例如,没有被结束的<STRONG>标记会把跟随其后的所有元素变为粗体。但是没有被结束的<LI>或者<P>标记不会引发任何问题。
有几条规则仅适用于XML文档,如果试图把它们汇集在已存在的HTML页面中,确实会带来问题。这些规则有:
1.以一个XML声明开始
2.空标记必须以/>结束
3.使用的实体引用只有&、<、>、'和"。
校正这些问题并不难,只是有几个稍不注意就会出现问题。下面让我们仔细加以研究。
6.4.1.1 结束所有的起始标记
任何含有内容的元素,无论是文本还是别的子元素,应该有一个起始标记和结束标记。HTML不绝对要求这样做。例如<P>、<DT>、<DD>和<LI>经常被单独使用。但是,这样做主要依靠Web浏览器能够很好地判断一个元素的结束位置,浏览器并不总能确切地按照作者的意愿去做。因此最好是明确地结束所有起始标记。
对于编写HTML的方法,这里要求对其所作的最大改变是把<P>看作一个容器,而不是一个简单的段落分界符。例如,以前格式化Federalist Papers的开始部分,如下所示:
To the People of the State of New York:
<P>
AFTER an unequivocal experience of the inefficiency of the
subsisting federal government,you are called upon to
deliberate on a new Constitution for the United States of
America.The subject speaks its own importance;comprehending
in its consequences nothing less than the existence of the
UNION,the safety and welfare of the parts of which it is
composed,the fate of an empire in many respects the most
interesting in the world.It has been frequently remarked that
it seems to have been reserved to the people of this country,
by their conduct and example,to decide the important question,
whether societies of men are really capable or not of
establishing good government from reflection and choice,or
whether they are forever destined to depend for their political
constitutions on accident and force.If there be any truth in
the remark,the crisis at which we are arrived may with
propriety be regarded as the era in which that decision is to
be made;and a wrong election of the part we shall act may,in
this view,deserve to be considered as the general misfortune
of mankind.
<P>
结构完整性要求将上面语句格式化为:
<P>
To the People of the State of New York:
</P>
<P>
AFTER an unequivocal experience of the inefficiency of the
subsisting federal government,you are called upon to
deliberate on a new Constitution for the United States of
America.The subject speaks its own importance;comprehending
in its consequences nothing less than the existence of the
UNION,the safety and welfare of the parts of which it is
composed,the fate of an empire in many respects the most
interesting in the world.It has been frequently remarked that
it seems to have been reserved to the people of this country,
by their conduct and example,to decide the important question,
whether societies of men are really capable or not of
establishing good government from reflection and choice,or
whether they are forever destined to depend for their political
constitutions on accident and force.If there be any truth in
the remark,the crisis at which we are arrived may with
propriety be regarded as the era in which that decision is to
be made;and a wrong election of the part we shall act may,in
this view,deserve to be considered as the general misfortune
of mankind.
</P>
你以前学过的可能是把<P>看作一个段落的结束,现在应当把它看作一个开始。这会带来一些好处,例如可以方便地为一个段落指定多种格式化属性。例如,下面是可在http://thomas.loc.gov/home/hres581.html上看到的 House Resolution 581的原始HTML标题:
<center>
<p><h2>House Calendar No.272</h2>
<p><h1>105TH CONGRESS 2D SESSION H.RES.581</h1>
<p>[Report No.105-795 ]
<p><b>Authorizing and directing the Committee on the
Judiciary to investigate whether sufficient grounds
exist for the impeachment of William Jefferson Clinton,
President of the United States.</b>
</center>
下面是同样的文本,但使用的是结构完整的HTML。Align属性代替了相应的center元素,并且使用CSS样式属性代替了<b>标记。
<h2 align="center">House Calendar No.272</h2>
<h1 align="center">105TH CONGRESS 2D SESSION H.RES.581</h1>
<p align="center">[Report No.105-795 ]</p>
<p align="center" style="font-weight:bold">
Authorizing and directing the Committee on the Judiciary to
investigate whether sufficient grounds exist for the
impeachment of William Jefferson Clinton,
President of the United States.
</p>
6.4.1.2 删除孤立的结束标记并且不要使元素重叠
在编辑页面时,删除一个起始标记而忘了删除相应的结束标记,这种情况很常见。在HTML中,一个孤立的结束标记如</STRONG>或者</TD>没有任何相匹配的起始标记不会引发问题。但是这样会使文件比需要的更长,下载速度变慢,而且潜在地使人或工具理解和编辑HTML源文件发生混淆。因此应当确保每个结束标记都有正确的起始标记。
但是结束标记没有任何起始标记往往意味着那些元素错误地重叠了。在Web页面上的大部分重叠元素很容易修改。例如下面这种常见的错误:
<B><I>This text is bold and italic</B></I>
I元素在B元素中开始,也必须在B元素中结束。需要做的只是交换两个结束标记的位置:
<B><I>This text is bold and italic</I></B>
同样可以交换两个起始标记:
<I><B>This text is bold and italic</B></I>
偶尔会遇到一个棘手的问题。例如,下面是来自白宫主页的一个片段(http://www.whitehouse.gov/,1998年11月4日)。其中已醒目地标出了有问题的标记,很容易看出错误所在:
<TD valign=TOP width=85>
<FONT size=+1>
<A HREF="/WH/New"><img border=0
src="/WH/images/pin_calendar.gif"
align=LEFT height=50 width=75 hspace=5 vspace=5></A><br></TD>
<TD valign=TOP width=225>
<A HREF="/WH/New"><B>What ’s New:</B></A><br>
</FONT>
What’s happening at the White <nobr>House -</nobr><br>
<font size=2><b>
<!-- New Begin -->
<a href="/WH/New/html/19981104-12244.html">Remarks Of The
President Regarding Social Security</a>
<BR>
<!-- New End -->
</font>
</b>
</TD>
其中,<FONT size=+1>元素在第一个<TD valign=TOP width=85>元素中开始,但是它的后续部分越过该元素结束于另一个<TD valign=TOP width=225>元素中。在此情况下,正确的处理方法是在第一个</TD>结束标记之前立即结束<FONT size=+1>起始标记,然后在第二个TD元素开始之后立即添加一个新的<FONT size=+1>起始标记,如下所示:
<TD valign=TOP width=85>
<FONT size=+1>
<A HREF="/WH/New"><img border=0
src="/WH/images/pin_calendar.gif"
align=LEFT height=50 width=75 hspace=5 vspace=5></A><br>
</FONT></TD>
<TD valign=TOP width=225>
<FONT size=+1>
<A HREF="/WH/New"><B>What ’s New:</B></A><br>
</FONT>
What ’s happening at the White <nobr>House -</nobr><br>
<font size=2><b>
<!-- New Begin -->
<a href="/WH/New/html/19981104-12244.html">Remarks Of The
President Regarding Social Security</a>
<BR>
<!-- New End -->
</font>
</b>
</TD>
6.4.1.3 给所有属性加引号
HTML属性只有在含有空格时才需要加引号,即使含有引号对它也并无妨碍。而且使用引号有助于以后将属性值修改为含有空格的属性值。很容易忘记加引号,尤其对于<IMG>中ALT这样的属性,在使用Web浏览器查看文档时它们的错误不很明显。
例如下面的<IMG>标记:
<IMG SRC=cup.gif WIDTH=89 HEIGHT=67 ALT=Cup>
应将其改写为:
<IMG SRC="cup.gif" WIDTH="89" HEIGHT="67" ALT="Cup">
6.4.1.4 <、>和&必须转义
HTML对小于号和与号的要求比XML宽松得多。即使这样,在纯HTML文本中它们确实也会引起麻烦,特别是在它们直接跟有其他字符时。例如,考虑下面来自Eudora软件中的From:标题中的email地址在经过复制和粘贴后显示的样子:
Elliotte Rusty Harold <elharo@metalab.unc.edu>
如果用HTML来显示的话,看到的可能是:
Elliotte Rusty Harold
elharo@metalab.unc.edu无意间被尖括号隐藏了。如果想在HTML中包含原始的小于号和与号,应当使用<和&代替。其正确的HTML形式是:
Elliotte Rusty Harold <elharo@metalab.unc.edu>
没有转义的大于号带来的问题不易察觉,如果在它之前有一个未结束的标记,它会被解释为一个置标。文档中会出现这种没有完成的标记,而且附近的大于号会掩盖它们的存在。例如下面的一段Java代码:
for (int i=0;i<10;i++){
for (int j=20;j>10;j--){
这很可能显示为:``
for (int i=0;i10;j--){
如果这只是一个100行程序中的两行,在随便校正时极有可能错过这种疏忽。另一方面,如果转义了大于号,而未转义小于号将会隐藏程序的其余部分,而且这种问题容易被发现。
6.4.1.5 使用一个根元素
用于HTML文件的根元素被假定为html。大部分浏览器允许不包含它的这种疏忽。尽管如此,最好把<html>作为文档的第一个标记,</html>作为文档的最后一个标记。如果其他文本或置标出现在<html>之前或</html>之后,应把它们移到<html>和</html>之间。
这个问题常见的形式是忘记在文档的结尾包括</html>。我通常先键入<html>和</html>,然后在它们之间键入其他内容,而不是在编写完整个文档再加</html>标记,指望着几天后还会记得应该加上</html>标记。
6.4.1.6 所有标记使用相同的大小写形式
HTML对大小写不敏感,XML则不然。应推荐给标记挑选一个唯一的大小写形式,要么都大写,要么都小写,并且贯穿全文。这样做比记住每一个标记的细节要简单。我通常选小写,因为它比较容易输入。而且W3C将HTML再现为XML应用程序的结果也使用这个格式。
在第20章中读取文档类型定义一节将详细描述HTML再现为XML。但是必须停止更深的探讨,因为这项工作使用在后面几章中学不到的技巧。
6.4.1.7 用"/>"结束空标记
把HTML转换成结构完整的XML,其中最令人讨厌的就是空标记。HTML在形式上不能识别XML的<elementname/>空标记句法。虽然很容易将<br>转换为<br/>,<hr>转换为<hr/>,<img>转换为<img/>,但是给定的浏览器是否会正确显示变换后的标记是一个未知数。
不要把<br>,<hr>,<img>这样真正的空元素与标准的HTML中只有一个起始标记但能够带有内容的标记混淆,如<p>,<li>,<dt>,和<dd>。
一个被XML规范认可的最简单的解决办法是用不含有内容的起始和结束标记对替换空标记。浏览器将忽略该不能识别的结束标记,请看下面的实例:
<br></br>
<hr></hr>
<IMG SRC="cup.gif" WIDTH="89" HEIGHT="67" ALT="Cup"></IMG>
在实践中这样做确实没有什么问题,但有一个明显的例外。Netscape 4.5以及更早的版本把</br>和<br>看成是一样的,当作一个换行符号。因此<br>是单个换行符号,<br></br>则是一对换行符号,实际上更像一个段落标记。而且,Netscape完全忽略<br/>。必须支持传统浏览器的Web站点(几乎是所有的Web站点)不能使用<br></br>或者<br/>。在实践中对于XML和传统浏览器都适用的解决办法如下:
<br />
请注意<br和/>之间的空格,确实解释不了这样为什么管用,而其他更多的变化却
不行。如果你确实想使用结构完整的HTML,我所能做的只是提供可能奏效的解决办法。
6.4.1.8 只使用&、<、>、'和"实体引用
许多Web页面除了&、<、>、'、和"之外确实不需要更多的实体引用。但是HTML 4.0中规定了许多:
· ™为商标号(™)
- ©为版权号(©)
- ∞为无穷大号∞
- π为小写的希腊字母pi,π
还有几百个别的实体引用,但是使用任何一个将破坏文档的结构完整性。解决这个问题的方法是使用一个DTD。我们将在第9章中讨论DTD对实体引用的影响。同时下面有几个暂时的解决办法。
最简单的办法是以某一字符集编码一个包含全部所需符号的文档,然后使用一个<META>指令指定正在使用的字符集。例如,指定文档使用UTF-8编码(一个字符集,包含了几乎全部可能用到的字符,将在第7章讨论),而且应当把它放到文档的开头。
<META http-equiv="Content-Type"
content="text/html;charset=UTF-8">
或者可以简单地告诉Web服务器,让它提供必要的内容类型标题。通常使用<META>标记要简单一些:
Content-Type:text/html;charset=UTF-8
采用这种方法的问题是许多浏览器不一定能显示UTF-8字符集。对于其他提供所用的特殊字符的字符集也一样。
HTML 4.0支持XML中的字符实体引用。这就是说可以使用&#后跟Unicode中字符的十进制或者十六进制代码来代替一个字符。例如:
· ™ 为商标号(™)
- © 为版权号(©)
- ∞ 为无穷大号∞
- π 为小写的希腊字母pi,π
HTML 3.2只正式支持界于0和255(ISO Latin-1)之间的数字字符引用,而Navigator 4.0和以后的版本以及Internet Explorer能识别更多的Unicode字符。
如果确实需要一个结构完整的向后与HTML兼容的XML,可以把这些字符作为内联图片。例如:
· <img src="tm.gif" width="12" height="12" alt="TM"></img>, </img>商标号(tm);
- <img src="copyright.gif" width="12" height="12" lt="Copyright"> a,为版权号(c);
- <img src="infinity.gif" width="12" height="12" alt="infinity"></img>,无穷大号∞;
- <img src="pi.gif" width="12" height="12" alt="pi"></img>,小写的希腊字母pi,π。
事实上,我不赞成使用这种方法。结构完整性在HTML中并不太重要,它只是强制读者增加了下载和显示出来的时间。
6.4.1.9 XML声明
HTML文档不需要XML声明,但有也无妨。Web浏览器只忽略它们不承认的标记。从这一点看,下面这一行就是另外一个标记:
<?xml version="1.0" standalone="yes"?>
因为不懂XML的浏览器解释不了<?xml?>标记,它们会简单地忽略它。懂得XML的浏览器会把它当作一个提示,表明该文档是结构完整的XML文档,并按此处理它。
遗憾的是,不完全懂得XML的浏览器分析这些句法非常困难。特别是Internet Explorer 4.0 for the Mac(不是指Netscape Navigator或者其他版本的IE)把它当作下载一个文件的信号,而不作显示。因此,不得不从Web页面中将XML声名删除。
6.4.1.10 遵循规则
按照本章描述的规则编写结构完整的XML文档不是特别困难,但是XML浏览器对于不标准的句法不像HTML浏览器那样宽容,因此要细心编写。
如果违反了任何结构完整性约束,XML分析器和浏览器将报告一个句法错误。因此编写XML的过程与用某种编程语言编写代码的过程有些相似,首先编写,然后编译,如果编译失败再根据报告的错误修改。
通常在能够看到完成的文档之前要经过几次从编辑到编译的过程。而且编写XML文档比编写C和Java源代码要容易得多,只要很少的练习就会达到只出现相当少的错误,编写XML的速度几乎与你输入的速度一样快。
6.4.2 HTML整理工具
有几种工具能够帮助我们整理页面。最引人注目的是来自XML.COM的RUWF(Are You Well Formed?)和由W3C的Dave Raggett编写的HTML Tidy。
6.4.2.1 RUWF
任何能够检验XML文档结构完整性的工具同样能够检验结构完整的HTML文档。其中最容易使用的工具是来自XML.COM的RUWF结构完整性检验程序。图6-2显示了该检验程序。只要键入想检验的页面的URL,RUWF就会返回在页面上发现的几十个错误。
图6-2 RUWF结构完整性检验器
下面是RUWF在白宫主页上找到的第一批错误。这些错误大部分是不标准的XML,但是它们是合法的HTML。但至少有一处错误(“第55行,30列:</FONT>周围没有相应的起始标记”)对HTML和XML都是一个错误。
Line 28,column 7:Encountered </HEAD>expected </META>
...assumed </META>...assumed </META>...assumed </META>
...assumed </META>
Line 36,column 12,character ‘0 ’:after AttrName=in start-tag
Line 37,column 12,character ‘0 ’:after AttrName=in start-tag
Line 38,column 12,character ‘0 ’:after AttrName=in start-tag
Line 40,column 12,character ‘0 ’:after AttrName=in start-tag
Line 41,column 10,character ‘A ’:after AttrName=in start-tag
Line 42,column 12,character ‘0 ’:after AttrName=in start-tag
Line 43,column 14:Encountered </CENTER>expected </br>
...assumed </br>...assumed </br>
Line 51,column 11,character ‘+’:after AttrName=in start-tag
Line 52,column 51,character ‘0 ’:after AttrName=in start-tag
Line 54,column 57:after &
Line 55,column 30:Encountered </FONT>with no start-tag.
Line 57,column 10,character ‘A ’:after AttrName=in start-tag
Line 59,column 15,character ‘+’:after AttrName=in start-tag
6.4.2.2 HTML Tidy
一旦确定了问题就会想到要修改它们,许多常见的问题��例如,给属性值加引号��是能够自动被修改的。做这种修改最便利的工具是Dave Raggett的命令行程序HTML Tidy。Tidy是用ANSI C写成的一个字符-模式程序,能够在许多操作平台如Windows、Unix、BeOS和Mac系统上执行。
Tidy在本书所附的CD-ROM的utilities/tidy目录中,包含用于Windows NT和BeOS的二进制代码和用于所有操作平台的可移植代码。可以从站点http://www.w3.org/People/Raggett/tidy/中下载最新版本的Tidy。
Tidy通过几种不同的方式整理HTML文件,它们并非都与XML结构完整性有关。事实上在默认模式下,Tidy倾向于删除不必要的结束标记(对HTML而言,不是对XML),像</LI>。并且对破坏结构完整性的地方作一些修改。但是可以使用-asxml开关指定需要结构完整的XML输出。例如,把index.html文件转换为结构完整的XML,需要从DOS窗口或者外壳提示符下输入:
C:">tidy -m -asxml index.html
-m标志告诉Tidy就地转换文件。-asxml标志告诉Tidy把输出的文档格式转化为XML文档。
6.5 本章小结
在本章学习了如何编写结构完整的XML。主要包括以下内容:
· XML文档是满足一定结构完整性标准的一连串字符
- XML文档的文本分为字符数据和置标
- 注释可为代码加上说明文字,可能是为了自己看的注释,也可能是通过注释将还没有写好的部分注释掉
- 使用实体引用可以在文档中包含<、>、&、"和
- CDATA段对于嵌有很多<、>和&字符的文档是很有用的
- 在XML文档中的标记以<开始,并以>结束,而且不能出现在注释或者CDATA段中
- 起始标记和空标记可以包含描述元素的属性
- HTML文档稍加处理会变得结构完整
在下一章将要讨论如何使用非英语语言编写XML,尤其是用与英语差别很大的语言。如阿拉伯语、汉语和希腊语。
第7章 外文和非罗马文本
Web是国际性的,然而在其中使用的大多数是英文,XML正在开始改变这种状况。XML全面支持双字节Unicode字符集及其更简洁的描述形式。这对Web作者来说是个好消息,因为Unicode支持世界上每种现代文字通常使用的几乎所有的字符。
本章将学习在计算机应用程序中如何描述国际性文本,XML如何理解文本以及如何利用非英文软件。
本章的主要内容包括:
· 了解非罗马文字在网页上的效果
- 使用文字、字符集、字体和字形
- 传统的字符集
- 使用Unicode字符集
- 使用Unicode编写XML文件
7.1 Web上的非罗马文字
虽然Web是国际化的,但它的大部分文本是英文。由于网络的不断扩展,还能领略到法语、西班牙语、汉语、阿拉伯语、希伯来语、俄语、北印度语和其他语言的网页。很多时候这些网页没有理想的那么多。图7-1是1998年10月一份美国信息部宣传杂志的封面页面:Issues in Democracy(http//www.usia.gov/journals/itdhr/1098/ ijdr/ijdr1098.htm),是用英文编码显示的俄文译本。左上方红色的古斯拉夫文本是一张位图图片文件,因此很清晰(如果懂俄语的话),还有几个清晰的英文单词,如“Adobe Acrobat”。其余的大部分是加重音的罗马元音,不是想象的古斯拉夫字母。
当使用复杂的非西方文字时,如中国或日本文字,网页的质量会更差。图7-2是使用英文浏览器显示JavaBeans(IDG Books,1997,http://www.ohmsha.co.jp /data/books/contents/4-274-06271-6.htm)的日文版主页。同样的结果,位图图片显示了正确的日文(还有英文)文本,页面上其余的文本除了几个可辨认的英文单词像JavaBeans之外,就像是一个随机的字符组合。而希望看到的日文字符完全看不到。
如果使用正确的编码和应用软件,并安装正确的字体,这些页面就可以正确显示。图7-3是使用古斯拉夫的Windows 1251编码显示的Issues in Democracy。可以看到图片下面的文本是可读的(如果懂俄语的话)。
可以从Netscape Navigator或Internet Explorer的View/Encoding(视图/编码)菜单中为网页选取编码方式。在理想情况下,网络服务器会告诉网络浏览器使用何种编码,同时Web浏览器会接受。如果网络服务器能向网络浏览器传送显示页面的字体就更好。事实上,经常需要人工选择编码方式。当原稿有几种编码时,不得不尝试多个编码直至找到特别合适的一个。例如,一张古斯拉夫页面能用Windows 1251、ISO 8859-5或者KOI6-R编码。选择错误的编码可能会显示古斯拉夫字母,但单词将是不知所云、毫无意义的。
图7-1 用一种罗马文字观看的1998年10月版关于探讨民主政治的俄文译本
图7-2 用英文浏览器看到的JavaBeans的日文翻译页面
图7-3 使用古斯拉夫文字看到的Issues of Democracy
即使能够指定编码,也不能确保有显示它的字体。图7-4是使用日文编码的JavaBeans日文主页,但是在计算机中却没有任何一种日文字体。文本中的多数字符显示成方框,表明这是一个得不到的字符轮廓。幸运的是,Netscape Navigator能够辨认出页面上的双字节日文字符和两个单字节的西文字符。
图7-4 在没有必需的日文字体的情况下所显示的JavaBeans日文译本
如果有一种日本地方语言操作系统版本,它包含必要的字体或者别的软件,如Apple的Japanese Language Kit 或南极星的 NJWin(http://www.njstar.com/),这样就可以看到文本,大致如图7-5所示。
图7-5 在安装有所要的日文字体的浏览器上显示的JavaBeans译文
当然,所使用的字体质量越高,文本的效果看起来就越好。中文和日文的字体非常庞大(中文有大约80, 000多个汉字),而且单个文字间的差别很小。日文出版商比西方出版商对纸张和打印技术的要求更高,以保持必要的细节打印日文字符。遗憾的是一个72-dpi的计算机显示器不能很好地显示中文和日文字符,除非使用很大的字体。
由于每个页面只能有一种编码,因而要编写集成了多种文字的网页,如对中文的法文注释,是非常困难的。由于这一原因,网络界需要一种单一的、通用的字符集,使所有计算机和网络浏览器能显示网页中的所有字符。目前仍然没有这样的字符集,XML和Unicode是最好的。
XML文件是用Unicode编写的,这种双字节字符能表示世界各国语言中大部分的字符。如果网页是用Unicode编写的XML网页,而且所用的浏览器懂得Unicode,如XML浏览器,那么就可以在同一页面中包含不同语种的字符。
浏览器不需要区分不同的编码,如Windows 1251、ISO 8859-5或者KOI8-R。浏览器假定网页都是用Unicode编写的。只要双字节字符集有容纳不同字符的余地,就不需要使用多种字符集。因此,浏览器也不必检测使用的是哪一种字符集。
.2 文字、字符集、字体和字形
大部分现代人类语言都有各自的书写形式。用于书写一种语言的字符集称为一种文字。文字可以是语音字母表,也可以不是。例如,汉语、日语和韩语由能够表示整个词汇的表意文字字符组成。不同语言经常共用一些文字,或者有一些细小的改动。例如,汉语、日语和韩语实质上共用相同的80,000多个汉字,尽管大多数字符在不同的语言中表示的意义不同。
单词Script也经常用来指用非类型化和非解释语言写的程序,如JavaScript、Perl和TCL。本章中的Script指书写一种语言使用的字符,不是指任何一种程序。
一些语言能用不同的文字表达。塞尔维亚语和克罗地亚语实际是相同的,通常被称作Serbo-Croatian。但是,塞尔维亚语使用经过修改的古斯拉夫文字,克罗地亚语则使用经过修改的罗马文字。只要计算机不想得到所处理的文字的意义,处理一种文字和处理用这种文字所编写的任何一种语言都是相同的。
遗憾的是,单独的XML无法读取一种文字,计算机要处理一种文字需要四个要素:
1. 与文字对应的一种字符集
2. 用于该字符集的一种字体
3. 该字符集的一种输入方法
4. 理解该字符集的一个操作系统或应用程序
这四个要素只要缺少其中之一,就不能在这种文字环境下工作,尽管XML能够提供一个足可以应急的工作环境。如果在应用过程中只丢失了输入法,还能够读取用该文字写的文本,只是不能用这种文字书写文本。
7.2.1 文字的字符集
计算机只懂得数字。在它处理文本之前,必须用一种特定的字符集将文本编码成数字。例如,在大家熟知的ASCII字符集中,‘A’的编码是65,‘B’的编码是66,‘C’的编码是67,以此类推。
这些是语意学编码,不提供样式或者字体信息。C、C或C的编码都是67。有关如何画出字符的信息存储在别处。
7.2.2 字符集的字体
字符集所采用的各种字形的总和形成一种字体,通常包括一定的尺寸、外观和风格。例如C、C或C是同一字符,只是书写的形状不一样,但其意义是相同的。
不同的系统存储字形的方式不一样。它们可能是位图或矢量图,甚至是印刷厂中的铅字。它们采用的形式与我们无关,关键是字体可以告诉计算机如何从字符集中调出每一个字符。
7.2.3 字符集的输入法
输入文本需要一种输入法,讲英语的人不需要考虑它,只要敲击键盘就可以输入。在大部分欧洲国家也一样,只需要在键盘上附加几个元音变音、变音符号。
基本上,古斯拉夫语、希伯来语、阿拉伯语和希腊语比较难输入。键盘上的按键数目有限,一般不够阿拉伯和罗马字符,或者是罗马和希腊字符使用。假定需要两种字符,键盘上有一个希腊字符锁定键能使键盘在罗马字符和希腊字符之间来回切换,那么希腊字符和罗马字符就能以不同的颜色印在键盘上。这个方案同样适用于希伯来语、阿拉伯语、古斯拉夫语和其他非罗马字符集。
当碰到表意文字如汉语和日语时,上述方法确实不管用。日语的键盘可容纳大约5000个不同的键,但还不到日语的10%!音节、语音和部首表示法能够减少按键的数目,但是键盘是否适合输入这些语种的文本呢?同西方相比,正确的语音和手写体识别在亚洲有更大的潜力。
语音和手写体识别还没有达到足可以让人信赖的程度,目前输入单个字符的方法大部分是使用键盘上的多个键序列。例如,输入汉语的“羊”字,必须按下ALT键并按带有(~)的键,然后输入yang,单击回车键。这种输入方法会显示出一列发音与yang差不多的汉字。例如:
佯楊易暘楊洋瘍羊詳錫陽
接下来就可以选择需要的那个字符“羊”。对于不同的程序、不同的操作系统和不同的语言如何把键入的键值转换成文字字符,如“羊”所使用的GUI(图形用户界面)和翻译系统的细节是不同的。
7.2.4 操作系统和应用软件
主要的Web浏览器(Netscape Navigator和Internet Explorer)能很好地显示非罗马文字。如果潜在的操作系统支持给定的一种文字并存储有相应的字体,Web浏览器就能够显示这种文字。
MacOS 7.1及其新版本能够处理当今世界上常见的多数文字。但是基本操作系统仅支持西方欧洲语言。汉语、日语、韩语、阿拉伯语、希伯来语和古斯拉夫语只能从语言工具中获得,每一种100美元。同时提供相应语言的字体和输入法。也有印度语工具包,用来处理印度次大陆上常见的梵文、吉吉拉特语和Gurmukhu文字。MacOS 8.5增加了对Unicode可选而有限的支持(多数应用软件都不支持Unicode)。
Windows NT 4.0把Unicode当作本身的字符集使用。NT 4.0能够很好地处理罗马语、古斯拉夫语、希腊语、希伯来语和其他几种语言。Lucida Sans Unicode字体覆盖了最常用的1300种Unicode中的大约40,000多个字符。Microsoft Office 97包括汉语、日语和韩语字体,可以安装它来读取这些语言的文本(在你的Office CD-ROM上查询Valupack文件夹中的Fareast文件夹)。
微软宣称Windows 2000(以前称为NT 5.0)将包含能覆盖大部分中-日-韩文字的字体和相应的输入法。但是他们同样许诺过Windows 95包含Unicode支持软件,尽管失败了。因此不必焦虑等待。当然,如果所有的NT版本能够提供世界性的支持软件是非常好的,就不必再依赖于本地化了。
微软的消费类操作系统,如Windows 3.1、95和98不完全支持Unicode。相反它们需要依靠能处理基本英文字符和本地化文字的本地化系统。
主要的Unix变体包含不同等级的Unicode支持软件。Solaris 2.6支持欧洲语言、希腊语和古斯拉夫语。汉语、日语和韩语由本地化版本支持,它们使用不同于Unicode的编码。Linux对Unicode的支持尚在开始阶段,这在不久的将来会很有用。
7.3传统字符集
不同地区的不同计算机使用的默认字符集各不相同,大多数现代计算机使用ASCII码扩展字符集。ASCII码含有英语字母表和大部分常见的标点符号以及空格符的编码。
在美国,Mac计算机使用MacRoman字符集,Windows PC机使用Windows ANSI字符集,大部分Unix工作站使用ISO Latin-1。这些都是扩展的ASCII码,支持西方欧洲语言,如法语和西班牙语中的多出来的字符,如ç和?。在其他地区,如日本、希腊和以色列,计算机仍然使用令人困惑的混合字符集,这些字符集几乎都支持ASCII码加本地语言。
上述方法在Internet上无效。当你正在互联网上阅读San Jose Mercury News,翻页时不会遇到几个用德语或汉语写的栏目。但是在Web页面上,这完全可能。用户将跟随一个链接并停止在一个日文界面的开始。即使网上冲浪者不懂日语,他们如果能看到一个好的日本版面也是不错的。如图7-5所示,而不是图7-2显示的那种随意的字符组合。
XML处理这个问题是通过把小的、局部的字符集以外的字符集合并到一个大的字符集中,并假定它包含了地球上现存语言(和某些已消失的语言)使用的文字。这种字符集称为Unicode。同前面提到的一样,Unicode是一个双字节字符集,它能表示多种文字和几百种语言中的40,000多个不同字符。即使不能全部显示Unicode,所有的XML处理器必须识别Unicode。
在第6章中学过,一个XML文档分成文本和二进制实体两部分,每个文本实体有一种编码方法。如果编码在实体定义中没有明确指定,就会默认为UTF-8��一种Unicode的压缩形式,将保持纯ASCII文本不变。因此,只包含普通ASCII字符的XML文件,不会用处理Unicode这种多字节字符集的复杂工具对它进行编辑。
7.3.1 ASCII字符集
ASCII,即American Standard Code for Information Interchange(美国标准信息交换码),是一个原始的字符集,而且是到目前为止最通用的。它形成了所有字符集必须支持的最主要部分。它基本上只定义了书写英语需要的全部字符,这些字符的编码是0~127。表7-1显示了ASCII字符集。
表7-1 ASCII字符集
|
编码 |
字 符 |
编码 |
字符 |
编码 |
字符 |
编码 |
字符 |
|
0 |
空字符(Control-@) |
32 |
Space |
64 |
@ |
96 |
` |
|
1 |
标题开始字符(Control-A) |
33 |
! |
65 |
A |
97 |
A |
|
2 |
正文开始字符(Control-B) |
34 |
“ |
66 |
B |
98 |
B |
|
3 |
正文结束字符(Control-C) |
35 |
# |
67 |
C |
99 |
C |
|
4 |
传输结束字符(Control-D) |
36 |
$ |
68 |
D |
100 |
d |
|
5 |
询问字符(Control-E) |
37 |
% |
69 |
E |
101 |
e |
|
6 |
应答字符(Control-F) |
38 |
& |
70 |
F |
102 |
f |
|
7 |
响铃字符(Control-G) |
39 |
‘ |
71 |
G |
103 |
g |
|
8 |
退回字符(Control-H) |
40 |
( |
72 |
H |
104 |
h |
|
9 |
制表符(Control-I) |
41 |
) |
73 |
I |
105 |
i |
|
10 |
回行字符(Control-J) |
42 |
* |
74 |
J |
106 |
j |
|
11 |
垂直制表符(Control-K) |
43 |
+ |
75 |
K |
107 |
k |
|
12 |
进纸字符(Control-L) |
44 |
, |
76 |
L |
108 |
l |
|
13 |
回车字符(Control-M) |
45 |
- |
77 |
M |
109 |
m |
|
14 |
移出字符(Control-N) |
46 |
. |
78 |
N |
110 |
n |
|
15 |
移入字符(Control-O) |
47 |
/ |
79 |
O |
111 |
o |
|
16 |
数据连接转义符(Control-P) |
48 |
0 |
80 |
P |
112 |
p |
|
17 |
设备控制1(Control-Q) |
49 |
1 |
81 |
Q |
113 |
q |
|
18 |
设备控制2(Control-R) |
50 |
2 |
82 |
R |
114 |
r |
|
19 |
设备控制3(Control-S) |
51 |
3 |
83 |
S |
115 |
s |
|
20 |
设备控制4(Control-T) |
52 |
4 |
84 |
T |
116 |
t |
|
21 |
拒绝应答字符(Control-U) |
53 |
5 |
85 |
U |
117 |
u |
|
22 |
同步等待字符(Control-V) |
54 |
6 |
86 |
V |
118 |
v |
|
23 |
传输块结束符(Control-W) |
55 |
7 |
87 |
W |
119 |
w |
|
24 |
删除字符(Control-X) |
56 |
8 |
88 |
X |
120 |
x |
|
25 |
媒体结束符(Control-Y) |
57 |
9 |
89 |
Y |
121 |
y |
|
26 |
替换字符(Control-Z) |
58 |
: |
90 |
Z |
122 |
z |
|
27 |
转义字符(Control-[) |
59 |
; |
91 |
[ |
123 |
{ |
|
28 |
文件分隔符(Control-") |
60 |
< |
92 |
" |
124 |
| |
|
29 |
组群分隔符(Control-]) |
61 |
= |
93 |
] |
125 |
} |
|
30 |
记录分隔符(Control-^) |
62 |
> |
94 |
^ |
126 |
~ |
|
31 |
单元分隔符(Control-_) |
63 |
? |
95 |
_ |
127 |
delete |
在0~31之间的字符是非打印控制字符,包括回车、送纸、制表、响铃和其他类似的字符。其中有许多字符是以纸为基础的电传打印机时代遗留下来的。例如,回车在字面上表示把支架移回到左边空白处,就像在打字机上做一样。送纸使打印机滚筒向上移动一行。除了提及的几个字符外,其他的这些字符使用率不高。
人们所碰到的大多数字符集可能是ASCII的扩展字符集。换句话说,它们定义在0到127之间的字符同ASCII一样,只是增加了127以后的字符。
7.3.2 ISO字符集
ASCII中的“A”代表美国,因此ASCII码专门用于书写英语,严格来说是美式英语也就不足为奇了。ASCII码中缺少£、ü、?和许多书写其他语言和地区所需的字符。
可通过指定128以后的更多字符扩展ASCII码。国际标准组织(ISO)定义了几个不同的字符集,它们是在ASCII码基础上增加了其他语言和地区需要的字符。其中最突出的是ISO8859-1,通常叫做Latin-1。Latin-1包括了书写所有西方欧洲语言不可缺少的附加字符,其中0~127的字符与ASCII码相同。表7-2给出了128~255之间的字符,同样前32个字符是极少使用的非打印控制字符。
表7-2 ISO 8859-1 Latin-1 字符集
|
编码 |
字符 |
编码 |
字符 |
编码 |
字符 |
编码 |
字符 |
|
128 |
未定义 |
160 |
不可分空格 |
192 |
À |
224 |
À |
|
129 |
未定义 |
161 |
? |
193 |
Á |
225 |
Á |
|
130 |
Bph |
162 |
¢ |
194 |
 |
226 |
 |
|
131 |
Nbh |
163 |
£ |
195 |
à |
227 |
à |
|
132 |
未定义 |
164 |
¤ |
196 |
Ä |
228 |
Ä |
|
133 |
Nel |
165 |
¥ |
197 |
Å |
229 |
Å |
|
134 |
Ssa |
166 |
B |
198 |
Æ |
230 |
Æ |
|
135 |
Esa |
167 |
§ |
199 |
Ç |
231 |
Ç |
|
136 |
Hts |
168 |
¨ |
200 |
È |
232 |
È |
|
137 |
Htj |
169 |
© |
201 |
É |
233 |
É |
|
138 |
Vts |
170 |
a |
202 |
Ê |
234 |
Ê |
|
139 |
Pld |
171 |
? |
203 |
Ë |
235 |
Ë |
|
140 |
Plu |
172 |
? |
204 |
Ì |
236 |
Ì |
|
141 |
Ri |
173 |
任意字符 |
205 |
Í |
237 |
Í |
|
142 |
ss2 |
174 |
® |
206 |
Î |
238 |
Î |
|
143 |
ss3 |
175 |
ˉ |
207 |
Ï |
239 |
Ï |
|
144 |
Dcs |
176 |
° |
208 |
W |
240 |
e |
|
145 |
pu1 |
177 |
± |
209 |
Ñ |
241 |
Ñ |
|
146 |
pu2 |
178 |
2 |
210 |
Ò |
242 |
Ò |
|
147 |
Sts |
179 |
3 |
211 |
Ó |
243 |
Ó |
|
148 |
Cch |
180 |
′ |
212 |
Ô |
244 |
Ô |
|
149 |
Mw |
181 |
μ |
213 |
Õ |
245 |
Õ |
|
150 |
Spa |
182 |
? |
214 |
Ö |
246 |
Ö |
|
151 |
Epa |
183 |
· |
215 |
´ |
247 |
÷ |
|
152 |
Sos |
184 |
? |
216 |
Ø |
248 |
Ø |
|
153 |
未定义 |
185 |
1 |
217 |
Ù |
249 |
Ù |
|
154 |
Sci |
186 |
o |
218 |
Ú |
250 |
Ú |
|
155 |
Csi |
187 |
? |
219 |
Û |
251 |
Û |
|
156 |
St |
188 |
1/4 |
220 |
Ü |
252 |
Ü |
|
157 |
Osc |
189 |
1/2 |
221 |
Ý |
253 |
Ý |
|
编码 |
字符 |
编码 |
字符 |
编码 |
字符 |
编码 |
字符 |
|
158 |
Pm |
190 |
3/4 |
222 |
254 |
||
|
159 |
Apc |
191 |
? |
223 |
ß |
255 |
? |
Latin-1仍然缺少许多有用的字符,如希腊语、古斯拉夫语、汉语和其他文字及语言需要的字符。你也许会想到从256开始定义这些字符,这样做存在一个问题,单个字节只能表示0~255的数值,如果超出这个范围,需要使用多字节字符集。出于历史的原因,许多程序都是在一个字节表示一个字符的假定下编写的,这些程序在遇到多字节字符集时就会出错。因此,目前大多数操作系统(Windows NT例外)使用不同的单字节字符集而不是一个庞大的多字节字符集。Latin-1是最常见的这种字符集,其他字符集用于处理别的语言。
ISO 8859另外定义了10个适用于不同文字的字符集(8859-2到8859-10和8859-15),还有4个字符集(8859-11到8859-14)正在开发。表7-3列出了ISO字符集以及使用它的语言和文字。这些字符集共享0~127的ASCII码,只是每个字符集都包含了128~255的其他字符。
表7-3 ISO字符集
|
字符集 |
又 名 |
语 言 |
|
ISO 8859-1 |
Latin-1 |
ASCII码加大部分西欧语言要求的字符,包括阿尔巴尼亚语、南非荷兰语、巴斯克语、加泰罗尼亚语、丹麦语、荷兰语、英语、法罗群岛语、芬兰语、佛兰德语、加利尼西亚语、德语、冰岛语、爱尔兰语、意大利语、挪威语、葡萄牙语、苏格兰语、西班牙语、瑞典语。但是其中忽略了ij(荷兰语)、? (法语)和德语的引号 |
|
ISO 8859-2 |
Latin-2 |
ASCII码加中欧语言要求的字符,包括捷克语、英语、德语、匈牙利语、波兰语、罗马尼亚语、克罗地亚语、斯洛伐克语、斯洛文尼亚语和Sorbian |
|
ISO 8859-3 |
Latin-3 |
ASCII码、英语、世界语、德语、马耳他语和加利尼西亚语要求的字符 |
|
ISO 8859-4 |
Latin-4 |
ASCII码加波罗地海语、拉托维亚语、立陶宛语、德语、格陵兰岛语和拉普兰语中要求的并且被ISO 8859-10、Latin-6取代的字符 Latvian,Lithuania |
|
ISO 8859-5 |
|
ASCII码加古斯拉夫字符,用于Byelorussian 、保加利亚语、马其顿语、俄语、塞尔维亚语和乌克兰语 |
|
ISO 8859-6 |
|
ASCII码加阿拉伯语 |
|
ISO 8859-7 |
|
ASCII码加希腊语 |
|
ISO 8859-8 |
|
ASCII码加希伯来语 |
|
ISO 8859-9 |
Latin-5 |
就是Latin-1,但用土耳其字母代替了不常用的冰岛语字母 |
|
ISO 8859-10 |
Latin-6 |
ASCII码和日耳曼语、立陶宛语、爱斯基摩语、拉普兰语和冰岛语中的字符 |
|
ISO 8859-11 |
|
ASCII码加泰国语 |
|
ISO 8859-12 |
|
适用于ASCII码和梵文 |
|
ISO 8859-13 |
Latin-7 |
ASCII码加波罗地海周边的语言,特别是拉托维亚语 |
|
ISO 8859-14 |
Latin-8 |
ASCII码加盖尔语和威尔士语 |
|
ISO 8859-15 |
Latin-9 Latin-0 |
本质上与Latin-1相同,但是带有欧元符号,而不用国际货币符。而且用芬兰字符代替了一些不常用的字符。用法语字母CE、ce代替了1/4、1/2 |
这些字符集常有重叠。有几种语言,特别是英语和德语可以使用多种字符集书写。在一定程度上不同的字符集允许结合不同的语言。例如,Latin-1结合了大部分欧洲语言和冰岛语言,而Latin-5结合了大部分西方语言和土耳其语而不是冰岛语。因此如果需要一个包括英语、法语和冰岛语的文档,应当使用Latin-1。相反,一个文档含有英语、法语和土耳其语,则需要Latin-5。但是,对于一个要求英语、希伯来语和土耳其语的文档,必须使用Unicode来书写,因为没有一个单字节字符集能够完全处理这三种语言和文字。
单字节字符集不能满足汉语、日语和韩语的要求。这些语言含有的字符多于256个,因此必须使用多字节字符集。
7.3.3 MacRoman字符集
Macos比Latin-1早几年出现,ISO 8859-1标准在1987年第一次被采用(第一个Mac计算机是在1984年出现)。这意味着苹果公司不得不定义自己的扩展字符集��MacRoman。其中大部分扩展符同Latin-1一样(除冰岛语中的""),只是字符对应的编码不同。MacRoman中前127个字符与ASCII码和Latin-1中的一样。因此使用扩展字符的文本文件从PC机移到Mac时会显示混乱,反之亦然。表7-4列出了MacRoman字符集的后半部分。
表7-4 MacRoman字符集
|
编码 |
字符 |
编码 |
字符 |
编码 |
字符 |
编码 |
字符 |
|
128 |
 |
160 |
? |
192 |
? |
224 |
? |
|
129 |
Å |
161 |
° |
193 |
? |
225 |
· |
|
130 |
Ç |
162 |
¢ |
194 |
? |
226 |
? |
|
131 |
É |
163 |
£ |
195 |
√ |
227 |
? |
|
132 |
Ñ |
164 |
§ |
196 |
? |
228 |
‰ |
|
133 |
Ö |
165 |
· |
197 |
? |
229 |
 |
|
134 |
Û |
166 |
? |
198 |
? |
230 |
Ê |
|
135 |
Á |
167 |
ß |
199 |
? |
231 |
Á |
|
136 |
À |
168 |
® |
200 |
? |
232 |
|
|
137 |
 |
169 |
© |
201 |
... |
233 |
È |
|
138 |
Ä |
170 |
™ |
202 |
非换行空格 |
234 |
Í |
|
139 |
à |
171 |
′ |
203 |
À |
235 |
Î |
|
140 |
Å |
172 |
¨ |
204 |
à |
236 |
Ï |
|
141 |
Ç |
173 |
≠ |
205 |
Õ |
237 |
Ì |
|
142 |
É |
174 |
Æ |
206 |
? |
238 |
Î |
|
143 |
È |
175 |
Ø |
207 |
? |
239 |
Ó |
|
144 |
Ê |
176 |
∞ |
208 |
ˉ |
240 |
Ô |
|
145 |
Ë |
177 |
± |
209 |
_ |
241 |
Apple |
|
146 |
Í |
178 |
≤ |
210 |
" |
242 |
Ò |
|
147 |
Ì |
179 |
≥ |
211 |
" |
243 |
Ú |
|
148 |
Ì |
180 |
¥ |
212 |
‘ |
244 |
Û |
|
149 |
Ï |
181 |
μ |
213 |
‘ |
245 |
1 |
|
续表 |
|||||||
|
编码 |
字符 |
编码 |
字符 |
编码 |
字符 |
编码 |
字符 |
|
150 |
ñ |
182 |
¶ |
214 |
÷ |
246 |
? |
|
151 |
ó |
183 |
∑ |
215 |
à |
247 |
~ |
|
152 |
ò |
184 |
∏ |
216 |
? |
248 |
ˉ |
|
153 |
ô |
185 |
Π |
217 |
? |
249 |
|
|
154 |
ö |
186 |
∫ |
218 |
/ |
250 |
. |
|
155 |
õ |
187 |
a |
219 |
¤ |
251 |
° |
|
156 |
ú |
188 |
° |
220 |
? |
252 |
? |
|
157 |
Ù |
189 |
Ω |
221 |
? |
253 |
". |
|
158 |
Û |
190 |
Æ |
222 |
fi |
254 |
. |
|
159 |
Ü |
191 |
Ø |
223 |
fl |
255 |
?| |
7.3.4 Windows ANSI字符集
第一个被广泛使用的Windows版本比Mac晚几年出现,因此它能够采用Latin-1字符集。它使用更多的可打印字符代替介于130和159之间的非打印控制字符,从而进一步扩展了使用范围。这个经过修改的Latin-1版本通常被称作Windows ANSI。表7-5列出了Windows ANSI字符集。
表7-5 Windows ANSI 字符集
|
编码 |
字符 |
编码 |
字符 |
编码 |
字符 |
编码 |
字符 |
|
128 |
未定义 |
136 |
? |
144 |
未定义 |
152 |
~ |
|
129 |
未定义 |
137 |
‰ |
145 |
‘ |
153 |
™ |
|
130 |
, |
138 |
146 |
‘ |
154 |
154 |
§ |
|
131 |
□ |
139 |
? |
147 |
" |
155 |
? |
|
132 |
" |
140 |
? |
148 |
" |
156 |
? |
|
133 |
... |
141 |
未定义 |
149 |
� |
157 |
未定义 |
|
134 |
? |
142 |
未定义 |
150 |
– |
158 |
未定义 |
|
135 |
? |
143 |
未定义 |
151 |
— |
159 |
? |
7.4 Unicode字符集
为了使不同的字符集能够处理好不同的文字和语言,必须满足:
1. 不同时引用多种文字。
2. 不与使用不同字符集的人交换文件。
由于Mac和PC机都使用不同的字符集,越来越多的人无法遵循以上原则。很明显的是需要一种得到大家的认可并且编码了全世界各种文字的字符集。建立这样的字符集很难,需要对成百上千种语言和文字有细致的了解。要使软件开发商们同意使用这种字符集就更难了。不过这方面的努力一直在进行,终于创建了一个符合要求的字符集��Unicode。而且主要卖方(微软、苹果、IBM、Sun、Be等)正逐步趋向于使用它。XML把Unicode当作自己的默认字符集。
Unicode使用0~65,535的双字节无符号数对每一个字符进行编码。目前已经定义了40,000多个不同的Unicode字符,剩余25,000个空缺留给将来扩展之用。其中大约20,000个字符用于汉字,另外11,000左右的字符用于韩语音节。Unicode中0~`255的字符与Latin-1中的一致。
如果在本书中显示所有的Unicode字符,那么除了这些字符表格外,书中将容纳不下别的任何东西。如果需要知道Unicode中不同字符的确定编码,买一册Unicode标准(第二版,ISBN 0-201-48346-9,Addison-Wesley出版)。该书共950页,包括对Unicode 2.0的全部详细说明,还包括Unicode 2.0中定义的所有字符集的图表。还可以在Unicode协会的网址:http://www.unicode.org/和http://charts.unicode.org/中发现在线信息。表7-6列出了由Unicode编码的文字,由此可知Unicode的广泛性。每一种文字的字符通常编码在65,536个号码中的一个连续区域内。许多语言都能使用其中某一区域的字符书写(例如,使用古斯拉夫语书写俄语),尽管有一些语言,如克罗地亚语或土耳其语需要混合匹配前4个拉丁文区域中的字符。
表7-6 Unicode文字块
|
文 字 |
范 围 |
目 的 |
|
Basic Latin 基本拉丁语 |
0-127 |
ASCII码,美式英语 |
|
Latin-1 Supplement 拉丁语补充-1 |
126-255 |
ISO Latin-1前半部分结合Basic Latin能处理丹麦语、荷兰语、英语、法罗群岛语、佛兰德语、德语、夏威夷语、冰岛语、印度尼西亚语、爱尔兰语、挪威语、葡萄牙语、西班牙语、斯瓦西里语和瑞典语 |
|
Latin Extended-A 拉丁文扩展集-A |
256-383 |
该字符块增添了ISO 8859字符集Latin-2、Latin-3、Latin-4中的字符,而且是Basic Latin和Latin-1没有的字符。同它们结合能够编码南非荷兰语、法国布里多尼语、巴斯克语、加泰罗尼亚语、捷克语、世界语、爱沙尼亚语、法语、Friesland语、格陵兰岛语、匈牙利语、拉脱维亚语、立陶宛语、马耳它语、波兰语、普罗旺斯语、罗马尼亚语、吉普塞语、斯洛伐克语、斯洛文尼亚语、土耳其语和威尔士语 |
|
Latin Extended-B 拉丁文扩展集-B |
383-591 |
大部分字符用于扩展Latin文字以处理使用非传统文字写的语言,包括许多非洲语言、克罗地亚连字符,与塞尔维亚古斯拉夫字母、中国的拼音和Latin-10中的Sami characters相匹配 |
|
IPA扩展字符集 |
592-687 |
国际音标字母 |
|
间距调节字符 |
686-767 |
通常能够改变前面字母发音的小符号 |
|
可识别的连接字符 |
766-879 |
不独立存在,一般与前面的字母连用(放置在上边)的可识别的记号,如:~、‘and ?? |
|
希腊 |
880-1023 |
基于ISO 8859-7的现代希腊语,同时提供古埃及语字符 |
|
续表 |
||
|
文 字 |
范 围 |
目 的 |
|
古斯拉夫 |
1024-1279 |
基于ISO 8859-5上的语言,俄语和多数斯拉夫语(乌克兰语、Byelorussian等),前苏联的许多非斯拉夫语言(Azerbaijani,Ossetian,卡巴尔德语,Chechen,Tajik等).几种语言(库尔德语,阿布哈西亚语)需要Latin和古斯拉夫字母 |
|
美国 |
1326-1423 |
美语 |
|
希伯来 |
1424-1535 |
希伯来语(古典和现代)、依地语、Judezmo、早期美语。 |
|
阿拉伯 |
1536-1791 |
阿拉伯语,波斯语、Pashto、Sindhi、库尔德语和早期土耳其语 |
|
梵文字母 |
2304-2431 |
梵语,北印度语,尼泊尔语和印度次大陆语言,包括: |
|
孟加拉语 |
2432-2559 |
一种北印度文字,使用于印度的西孟加拉州和孟加拉国的孟加拉语、阿萨姆语、 |
|
Gurmukhi |
2560-2687 |
Punjabi |
|
Gujarati |
2686-2815 |
Gujarati |
|
Oriya |
2816-2943 |
Oriya、Khondi、Santali |
|
泰米尔语 |
2944-3071 |
泰米尔语和Badaga、使用于南印度、斯里兰卡、新加坡和马来西亚部分地区 |
|
Telugu |
3072-3199 |
Telugu、Gondi、Lambadi |
|
埃纳德语 |
3200-3327 |
埃纳德语、Tulu |
|
Malalayam |
3326-3455 |
Malalayam |
|
泰国语 |
3584-3711 |
泰国语、Kuy、Lavna、巴利语 |
|
老挝语 |
3712-3839 |
老挝语 |
|
西藏语 |
3840-4031 |
喜玛拉雅语包括西藏语、Ladakhi和Lahuli |
|
乔治亚语 |
4256-4351 |
乔治亚语,黑海边乔治亚前苏维埃共和国语 |
|
Hangul Jamo |
4352-4607 |
朝鲜、韩国音节的字母组成部分 |
|
Latin的附加扩展集 |
7680-7935 |
标准的Latin字母如E和Y与可识别的记号组合在一起,除了用于越南语元音中,很少使用 |
|
希腊语扩展集 |
7936-8191 |
希腊字母与可识别记号的组合,用于正统的希腊语中 |
|
通用的标点符号 |
8192-8303 |
各种标点符号 |
|
上标和下标 |
8304-8351 |
普通的上标和下标 |
|
货币符号 |
8352-8399 |
货币符号,一般在别的地方找不到 |
|
用于符号的组合记号 |
8400-8447 |
给多个字符做记号 |
|
像字母的符号 |
8446-8527 |
像字母的符号,如™ |
|
数表 |
8526-8591 |
分数和罗马数字 |
|
箭头符号 |
8592-8703 |
箭头符号 |
|
数学符号 |
8704-8959 |
不常出现的数学运算符 |
|
技术杂项 |
8960-9039 |
APL编程语言需要的符号和其他各种技术符号 |
|
控制图形 |
9216-9279 |
ASCII控制字符图形,常用于调试 |
|
光学字符识别 |
9280-9311 |
在打印支票上的OCR-A(光学字符识别)和MICR(磁性墨水字符识别)符号 |
|
续表 |
||
|
文 字 |
范 围 |
目 的 |
|
附加字符 |
9312-9471 |
放在圆和括号中的字母和数字 |
|
画方框字符 |
9472-9599 |
用于在等间距终端上画方框的字符 |
|
块元素 |
9600-9631 |
用于DOS和其他用途的等间距终端图形 |
|
几何形状 |
9632-9727 |
正方形、菱形、三角形等 |
|
杂项符号 |
9726-9983 |
纸牌、象棋、占卜等 |
|
Dingbats |
9984-10175 |
Zapf Dingbat字符 |
|
CJK符号和标点 |
12286-12351 |
用于中国"日本和韩国的标点符号 |
|
平假名 |
12352-12447 |
日文字母的草体. |
|
片假名 |
12446-12543 |
非草体的日文字母,通常用于西方的外来词汇,像"keyboard" |
|
汉语拼音字母 |
12544-12591 |
中国的发音字母表 |
|
Hangul Compatibility Jamo |
12592-12687 |
与KSC 5601代码兼容的韩国字符 |
|
Kanbun |
12686-12703 |
在日文中用于指示古典中文的阅读顺序的记号 |
|
括起来的CJK字母和月份 |
12800-13055 |
用圆和括号括起来的Hangul和片假名字符 |
|
CJK Compatibility |
13056-13311 |
只用于编码KSC 5601和CNS 11643的字符 |
|
统一的CJK象形文字 |
19966-40959 |
用于中文、日文和韩文的Han象形文字 |
|
Hangul音节 |
44032-55203 |
一种韩国音节 |
|
Surrogates |
55296-57343 |
目前还不能使用,将来可用于扩展Unicode,使它包括超过百万的字符 |
|
个人使用 |
57344-63743 |
软件开发者可以在此包含自己的术语,与正在执行的字符不同 |
|
CJK兼容性象形文字 |
63744-64255 |
为了保持与现有的标准的一致性如KSC 5601,而使用的一些汉字象形文字 |
|
字母的表现方式 |
64256-64335 |
使用于Latin、美语和希伯来语中的连字和变种 |
|
阿拉伯表象形式 |
64336-65023 |
各种阿拉伯字符的变种 |
|
组合半记号 |
65056-65071 |
把跨越多个字符的多个可识别记号连成一个可识别的记号 |
|
CJK兼容性形式 |
65072-65103 |
用于台湾汉字象形文字 |
|
小型变种 |
65104-65135 |
用于台湾的ASCII标点符号的小的版本 |
|
附加的阿拉伯表象形式 |
65136-65279 |
各种阿拉伯字符变种 |
|
半宽和全宽形式 |
65280-65519 |
能够在中文和日文的不同代码间转换的字符 |
|
特殊字符 |
65520-65535 |
字节顺序记号和零宽度的非中断性空格,常用于Unicode 文件的开始 |
7.4.1 UTF-8
Unicode使用双字节表示一个字符,因此使用Unicode的英文文本文件大小是使用ASCII码或Latin-1文件的两倍。UTF-8是一个压缩的Unicode版本,使用单个字节表示最常用的字符,即0到127的ASCII字符,较少见的字符使用三个字节表示,特制是韩国音节和汉字。如果主要使用英文,UTF-8能够将文件压缩为原来的一半。如果主要使用汉语、朝语或者日语,UTF-8会使文件的尺寸增加50%��因此应当谨慎使用UTF-8。UTF-8几乎不能处理非罗马文字和非CJK文字,如希腊语、阿拉伯语、古斯拉夫语和希伯来语。
XML处理器在没有被预先通知的情况下假定文本数据是UTF-8格式。这意味着XML处理器能够阅读ASCII码文件,但是使用它处理其他格式的文件像MacRoman 或者 Latin-1会有困难。我们很快就能学会如何在短时间内解决这个问题。
7.4.2 通用字符系统
Unicode因为没有包含足够多的语言和文字而受到批评,特别是亚洲东部的语言。它只定义了中国、日本、朝鲜和古越南使用的80,000象形文字中的20,000个左右。(现代越南语使用一种罗马字母。)
UCS (Universal Character System)��通用字符系统,也称作ISO 10646,使用四个字节(确切地说是31位)表示一个字符,以给20多亿不同的字符提供足够的空间。这样能容易地覆盖地球上任何一种文字和语言使用的每个字符。而且还可以给每一种语言指定一个完整的字符集,使法语中的“e”不同于英语和德语中的“e”等等。
与Unicode一样,UCS定义了许多不同的变种和压缩形式。纯粹的Unicode有时指USC-2,是双字节的UCS。UTF-16是一种特别的编码,它把一些UCS字符安排在长度变化的字符串中,在这种方式下Unicode(UCS-2)数据不会改变。
UCS超越Unicode的优点主要是理论方面的。在UCS中实际定义过的字符就是Unicode中已有的字符。但是UCS为以后的字符扩充提供了更多的空间。
7.5 如何使用Unicode编写XML
Unicode是XML自己的字符集,至少在能得到的字体范围内,XML浏览器会很好的显示它。但是支持全部Unicode的文本编辑程序不是很多。因此,不得不使用下面两种方法之一解决这个问题:
1. 使用本地字符集如Latin-3编写,然后把文件转换成Unicode文件。
2. 在文本中包含Unicode字符引用,它们在数值上等同于特定的字符。
在主要使用一种文字或一种文字附加ASCII码输入大量文本的情况下,第一种方法更可取。文档需要掺少量的多种文字时,可使用第二种方法。
7.5.1 利用字符引用在XML文件中插入字符
一个Unicode字符是介于0和65,535之间的一个数。如果没有使用Unicode书写的文本编辑程序,通常可以使用字符引用在XML文件中插入字符。
Unicode字符引用由两个字符&#组成,后面跟有要插入字符的编码和分号。例如,希腊字母π的Unicode字符值是960,因此需要在XML文件中插入π。古斯拉夫字母ч的Unicode值是1206,需要在XML文件中插入Ҷ。
Unicode字符引用也可以用十六进制数指定,尽管多数人习惯使用十进制数,Unicode规范中给出的字符值是双字节十六进制数。直接使用十六进制数更简单一些,不必把它们转换成十进制数。
使用十六进制数需要在&#之后添加一个x来指明。例如,π的十六进制数是3C0,因此插入XML文件中的是π;古斯拉夫语字母ч的十六进制数是4B6,因此在XML文件中的应当是Ҷ。两个字节表示4个十六进制位,通常在十六进制字符引用中包含一个起始的0来构成4位十六进制数。
十六进制和十进制Unicode字符引用可用来嵌入那些会被解释为置标的字符。例如,与字符(&)的编码是&或&,小于号(<)的编码是<或<。
7.5.2 其他字符集与Unicode字符集之间的转换
输出XML文件的应用软件如Adobe Framemaker,能够自动转换为Unicode或UTF-8文件。否则必须使用一种转换工具。Sun的免费工具包Java Development Kit (JDK)包含一个名为native2ascii的简单命令行实用工具,能够完成多种常见和不常见的本地字符集与Unicode之间的转换。
例如,下面的命令把文件名是myfile.txt文本文件从操作平台默认的编码转换为Unicode。
C:">native2ascii myfile.txt myfile.uni
可使用-encoding选项指定其他编码。
C:>native2ascii -encoding Big5 chinese.txt chinese.uni
还可使用-reverse选项,把Unicode转换为本地编码。
C:>native2ascii -encoding Big5 -reverse chinese.uni chinese.txt
如果没有输出文件名,转换后的文件将打印输出。
native2ascii程序同样能处理java类型的Unicode转义符,它们是以"u09E3的格式嵌入的。这与XML中的数值字符引用不同,尽管比较相似。使用native2ascii把文件转化为Unicode,仍然可以使用XML字符引用��查看程序能够识别它们。
7.5.3 如何使用其他字符集编写XML
在没有被预先告知的情况下,XML处理器默认文本实体字符使用UTF-8编码,因为ASCII码是包含在UTF-8中的一个子集,所以XML处理器同样可以分析ASCII码文本。
除了UTF-8,XML处理器必须能读懂的唯一字符集是原始Unicode。当不能把文本转换成UTF-8或原始Unicode时,可以使文本保持原样并告诉XML处理器文本所使用的字符集。这是最后一种手段,因为这样做并不能保证一个尚未成熟的XML处理器能够处理其他编码。除此之外,Netscape Navigator和Internet Explorer都能很好地解释常见的字符集。
在文件开始的XML声明中包含一个encoding属性,告诉XML处理器正在使用的是非Unicode编码。例如,说明整个文档使用默认的Latin-1(除非在嵌套的实体中有别的处理指令),可使用下面的XML声明:
<?xml version="1.0" encoding="ISO-8859-1" ??>
也可以在XML声明之后包含一个编码声明作为一个单独的处理指令,但是一定要在所有字符数据之前:
<?xml encoding="ISO-8859-1"?>
表7-7列出了目前大部分常用的字符集的正式名称,即出现在XML编码属性中的名称。清单中没有的编码请参考由Internet Assigned Numbers Authority(IANA)提供的正式清单,网址是:http://www.isi.edu/in-notes/iana/assignments/ character-sets。
表7-7 常用字符集名称
|
字符集名称 |
语言/国家 |
|
US-ASCII |
英语 |
|
UTF-8 |
压缩Unicode |
|
UTF-16 |
压缩UCS |
|
ISO-10646-UCS-2 |
原始Unicode |
|
ISO-10646-UCS-4 |
原始UCS |
|
ISO-8859-1 |
Latin-1,西欧 |
|
ISO-8859-2 |
Latin-2,东欧 |
|
ISO-8859-3 |
Latin-3,南欧 |
|
ISO-8859-4 |
Latin-4,北欧 |
|
ISO-8859-5 |
ASCII码加古斯拉夫语 |
|
ISO-8859-6 |
ASCII码加阿拉伯语 |
|
ISO-8859-7 |
ASCII码加希腊语 |
|
ISO-8859-8 |
ASCII码加希伯来语 |
|
ISO-8859-9 |
Latin-5,土耳其语 |
|
ISO-8859-10 |
Latin-6,ASCII码加北欧语 |
|
ISO-8859-11 |
ASCII码加泰国语 |
|
ISO-8859-13 |
Latin-7,ASCII码加波罗地海周边语言和独特的拉托维亚语 |
|
ISO-8859-14 |
Latin-8,ASCII码加盖尔语和威尔式语 |
|
ISO-8859-15 |
Latin-9,Latin-0,西欧 |
|
ISO-2022-JP |
日语 |
|
Shift_JIS |
日文版Windows |
|
EUC-JP |
日文版Unix |
|
Big5 |
中国台湾地区,汉语 |
|
GB2312 |
中国大陆,汉语 |
|
KOI6-R |
俄罗斯 |
|
ISO-2022-KR |
韩语 |
|
EUC-KR |
韩语版Unix |
|
ISO-2022-CN |
汉语 |
7.6 本章小结
在本章中你会了解到以下内容:
· Web页面应当指明使用的编码。
- 什么是文字,文字与语言有何关系,以及与文字有关的四个因素。
- 在计算机中如何使用文字、字符集、字体、字形和输入法。
- 什么样的字符集通常使用在以ASCII码为基础的不同操作平台上。
- 在没有Unicode编辑程序的情况下,如何使用Unicode编写XML文件(使用ASCII码和Unicode字符引用编写文档)。
- 使用别的编码编写XML文件时,应当在XML声明中包含一个encoding属性。
在下一章将研究DTD和如何使用DTD给文档定义及执行词汇表、句法和语法并强制执行。
第二部分 文档类型定义
本部分包括以下各章:
第8章——文档类型定义和合法性
第9章——实体和外部DTD子集
第10章——DTD中的属性声明
第11章——嵌入非XML数据
第8章 文档类型定义和合法性
XML被作为一种元标记语言,是一种描述标记语言的语言。在本章中您将学到如何说明和描述所创建的新标记语言。这些新的标记语言(也叫标记集)要通过文档类型定义(DTD)来定义,这正是本章要讲述的内容。各个文档要与DTD相比较,这一过程称为合法性检验。如果文档符合DTD中的约束,这个文档就被认为是合法的,否则就是不合法的。
· 本章的主要内容包括:
- 文档类型定义(DTD)
- 文档类型声明
- DTD的合法性
- 元素清单
- 元素声明
- DTD中的说明
- 可在文档间共享的通用DTD
8.1 文档类型定义
缩略词DTD代表文档类型定义。一项文档类型定义应规定元素清单、属性、标记、文档中的实体及其相互关系。DTD为文档结构制定了一套规则。例如,一项DTD指定一个BOOK元素有一个ISBN子元素、一个TITLE子元素、一个或多个AUTHOR子元素,有或没有SUBTITLE。DTD以元素、实体、属性和记号的标记声明来做到这一点。
本章重点是元素声明。第9、10、11章分别介绍实体、属性和标记。
DTD可以包括在包含它描述的文档的文件中,或者与外部的URL相链接。这些外部DTD可以被不同文档和网站所共享。DTD为应用程序、组织和兴趣组提供了共同遵循的方法,同时也以文档形式阐述了标记标准并强制遵守此标准。
例如,为了使一部书易于排版,出版商会要求作者遵循一定的格式。作者可能不管是否与本章前面的小标题列出的关键点相符合,而只管成行地写下去。如果作者用XML写作,那么出版商就能很容易地检查出作者是否遵守了DTD作出的预定格式,甚至找出作者在那里以及怎样偏离了格式。这比指望编辑们单纯地从形式上通读文档而找出所有偏离格式的地方要容易得多。
DTD有助于不同的人们和程序互相阅读文件。例如,如果化学家们通过专业机构(如美国化学协会)为中介同意将单一的DTD用于基本的化学记号,那么他们就能够阅读和理解他们当中任何人的文章。DTD精确地定义了什么允许或不允许在文档中出现。DTD还为查看和编辑软件必须支持的元素建立了标准。更重要的是,它建立了超出DTD声明的非法范围。这就使它有助于防止软件商乘机利用和扩展开放协议以便将用户锁定在他们的专利软件上。
而且,DTD可以在没有实际数据的情况下展现出页面上的不同元素是如何安排的。 DTD使人们能脱离实际数据看到文档结构。这意味着可以将许多有趣的样式和格式加在基本结构上,而对基本结构毫无损害。这正如涂饰房子而不必改变基本的建筑计划。页面的读者可能看不见甚至不了解基础结构,但是只要有了DTD,任何人类作者和JavaScript程序、DTD程序、小服务程序、数据库和其他程序就可以使用它。
用DTD还可以做更多的事。可以使用它们来定义词汇实体以插入署名块或地址一类的模板文本。您可以确定输入数据的人们是否遵循了您的格式。您可以从关系数据库或对象数据库中移出数据或把数据送往目标数据库。甚至可以用适当的DTD利用XML作为中间格式来转换不同的格式。所以让我们开始看一看DTD 到底是什么样的。
8.2 文档类型声明
文档类型声明指定了文档使用的DTD。文档类型声明出现在文档的序言部分,处在XML声明之后和基本元素之前。它可能包括文档类型定义或是标识文档类型定义所在文档的URL。有些情况下文档类型定义有内外两个子集,则文档类型声明可能同时包括以上两种情况。
文档类型声明同文档类型定义不是一回事。只有文档类型定义缩写为DTD。文档类型声明必须包含或者引用文档类型定义,但文档类型定义从不包括文档类型声明。我同意这造成了不必要的混乱。遗憾的是XML似乎与这术语密不可分,幸运的是多数情况下二者的区别并不重要。
请回顾一下第3章清单3-2(greeting.xml),如下所示:
<?xml version="1.0" standalone="yes"?>
<GREETING>
Hello XML!
</GREETING>
这个文档包含单一元素GREETING。(请记住,〈?xml version="1.0" standalone="yes"?〉是一条处理指令,不是元素。)清单8-1显示了这一文档,但这次带有文档类型声明。文档类型声明声明了基本元素是GREETING。文档类型声明也包含文档类型定义,它声明了GREETING元素包含可析的字符数据。
清单8-1:带有DTD的Hello XML
<?xml version="1.0" standalone="yes"?>
<!DOCTYPE GREETING [
<!ELEMENT GREETING (#PCDATA)>
]>
<GREETING>
Hello XML!
</GREETING>
清单3-2与清单8-1的唯一区别在于清单8-1增加了3行:
<!DOCTYPE GREETING [
<!ELEMENT GREETING (#PCDATA)>
]>
这几行是清单8-1的文档类型声明。文档类型声明在XML声明与文档本身之间。XML声明与文档类型声明统称为文档序言(Prolog)。在本例中,<?xml version="1.0" standalone="yes"?>是XML声明;<!DOCTYPE GREETING [ <!ELEMENT GREETING (#PCDATA)> ]>是文档类型声明;<!ELEMENT GREETING (#PCDATA)>是文档类型定义;<GREETING> Hello XML! </GREETING>是文档或基本元素。
文档类型声明以<!DOCTYPE为开始,以]>结束。通常将开始和结束放在不同的行上,但断行和多余的空格并不重要。同一文档类型声明也可以写成一行:
<!DOCTYPE GREETING [<!ELEMENT GREETING (#PCDATA)> ]>
本例中基本元素名称——GREETING跟在<!DOCTYPE之后。这不仅是一个名称,也是一项要求。任何带有这种文档类型声明的合法文档必须有基本元素。在[和]之间的内容是文档类型定义。
DTD由一系列声明了特写的元素、实体和属性的标记声明所组成。其中的一项声明基本元素。清单8-1中整个DTD只是如下简单的一行:
<!ELEMENT GREETING (#PCDATA)>
通常情况下DTD当然会更长更复杂。
单个行<!ELEMENT GREETING (#PCDATA)>(正如XML中的大多数对象一样是区分大小写的)是一项元素类型声明。在本例中,声明的元素名称是GREETING。它是唯一的元素。这一元素可以包含可析的字符数据(或#PCDATA)。可析的字符实质上是除标记文本外的任何文本。这也包括实体引用如&,在对文档进行语法分析时,实体引用就被文本所取代。
可以把这一文档像通常一样装入一种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, |
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, & ninety-eight
</YEAR>
甚至下面这个YEAR元素也是合法的,因为XML不会去检查PCDATA的内容,只要是不包括标记的文本就可以。
<YEAR>Delicious, delicious, oh how boring</YEAR>
但是,下面的YEAR元素是非法的,因为它包含了子元素:
<YEAR>
<MONTH>January</MONTH>
<MONTH>February</MONTH>
<MONTH>March</MONTH>
<MONTH>April</MONTH>
<MONTH>May</MONTH>
<MONTH>June</MONTH>
<MONTH>July</MONTH>
<MONTH>August</MONTH>
<MONTH>September</MONTH>
<MONTH>October</MONTH>
<MONTH>November</MONTH>
<MONTH>December</MONTH>
</YEAR>
SEASON和YEAR元素声明应包括在文档类型声明中,如下所示:
<!DOCTYPE SEASON [
<!ELEMENT SEASON ANY>
<!ELEMENT YEAR (#PCDATA)>
]>
通常,空格和缩进无关紧要。元素声明的顺序也不重要。下面这一文档类型声明的作用与上面的声明相同:
<!DOCTYPE SEASON [
<!ELEMENT YEAR (#PCDATA)>
<!ELEMENT SEASON ANY>
]>
上面两个文档声明都是说一个SEASON元素可以包含可析的字符数据和以任意顺序声明的任意数量的其他元素。本例中如此声明的元素只有YEAR,它只能包含可析的字符数据。例如考虑清单8-5中的文档。
清单8-5:一个合法的文档
<?xml version="1.0" standalone="yes"?>
<!DOCTYPE SEASON [
<!ELEMENT YEAR (#PCDATA)>
<!ELEMENT SEASON ANY>
]>
<SEASON>
<YEAR>1998</YEAR>
</SEASON>
因为SEASON元素也可以包含可析的字符数据,所以可以在YEAR元素之外附加文本,如清单8-6所示。
清单8-6:包含YEAR元素和正常文本的合法的文档
<?xml version="1.0" standalone="yes"?>
<!DOCTYPE SEASON [
<!ELEMENT YEAR (#PCDATA)>
<!ELEMENT SEASON ANY>
]>
<SEASON>
<YEAR>1998</YEAR>
Major League Baseball
</SEASON>
我们最后是不接受这样的文档的。但是此时它是合法的,因为SEASON被声明为可以接受ANY内容。大多数时候,在定义一个元素的所有子元素之前以ANY代替一个元素,就比较容易起步。然后再用实际的子元素来替换ANY。
您可以向清单8-6上附加一份简单的样式单,如第4章中创建的baseballstats.css,如清单8-7所示。然后将其装入Web浏览器,结果显示在图8-7中。baseballstats.css样式单包含一些没有出现在DTD或是清单8-7列出的文档部分中的元素的样式规则,但这没有问题。Web浏览器会忽略任何文档中没有的元素的样式规则。
清单8-7:包含样式单、一个YEAR元素和正常文本的合法文档
<?xml version="1.0" standalone="yes"?>
<?xml-stylesheet type="text/css" href="greeting.css"?>
<!DOCTYPE SEASON [
<!ELEMENT YEAR (#PCDATA)>
<!ELEMENT SEASON ANY>
]>
<SEASON>
<YEAR>1998</YEAR>
Major League Baseball
</SEASON>
图8-7 在Internet Explorer 5.0中显示的包含样式单、YEAR元素和正常文本的文档
8.5.3 子元素列表
由于SEASON元素被声明为可以接受任何元素作为子元素,因而可以接受各种各样的元素。当遇到那些多多少少有些非结构化的文本,如杂志文章时,这种情况就很有用。这时段落、副栏、项目列表、序号列表、图形、照片以及子标题可出现在文档的任意位置。然而,有时可能想对数据的安排上多实行些规则和控制。例如,可能会要求每一个LEAGUE元素有一个LEAGUE_NAME子元素,而每个PLAYER元素要有一个GIVEN_NAME和SURNAME子元素,并且GIVEN_NAME要放在SURNAME之前。
为了声明LEAGUE元素必须有一个名称,只要声明LEAGUE_NAME元素,然后在LEAGUE声明后的括号内加入LEAGUE_NAME,如下面这样:
<!ELEMENT LEAGUE (LEAGUE_NAME)>
<!ELEMENT LEAGUE_NAME (#PCDATA)>
每个元素只能在其<!ELEMENT>内声明一次,即使它以其他<!ELEMENT>声明的子元素出现也一样。这里,我把LEAGUE_NAME声明放在引用它的LEAGUE声明之后,这没有关系。XML允许这一类提前引用。只要声明全部包含在DTD中,元素标记出现的顺序无关紧要。
可以向文档中添加这两项声明,然后在SEASON元素中包括LEAGUE和LEAGUE_NAME元素。如清单8-8所示。图8-8是显示出来的文档。
清单8-8:有两个LEAGUE子元素的SEASON元素
<?xml version="1.0" standalone="yes"?>
<?xml-stylesheet type="text/css" href="greeting.css"?>
<!DOCTYPE SEASON [
<!ELEMENT YEAR (#PCDATA)>
<!ELEMENT LEAGUE (LEAGUE_NAME)>
<!ELEMENT LEAGUE_NAME (#PCDATA)>
<!ELEMENT SEASON ANY>
]>
<SEASON>
<YEAR>1998</YEAR>
<LEAGUE>
<LEAGUE_NAME>American League</LEAGUE_NAME>
</LEAGUE>
<LEAGUE>
<LEAGUE_NAME>National League</LEAGUE_NAME>
</LEAGUE>
</SEASON>
图8-8 包含样式单、YEAR元素和两个LEAGUE子元素的合法的文档
8.5.4 序列
让我们限制一下SEASON元素。一个SEASON元素包含正好一个YEAR元素和其后的两个LEAGUE子元素。不把SEASON元素声明为可以包含ANY元素,我们在SEASON元素声明中包括这三个子元素,用括号括起来并用逗号分隔开,如下所示:
<!ELEMENT SEASON (YEAR, LEAGUE, LEAGUE)>
用逗号隔开的一系列子元素称为一个序列。利用这一声明,每个合法的SEASON元素必须包含正好一个YEAR元素,后面正好是两个LEAGUE元素,没有别的。整个文档类型定义现在看上去是下面的样子:
<!DOCTYPE SEASON [
<!ELEMENT YEAR (#PCDATA)>
<!ELEMENT LEAGUE (LEAGUE_NAME)>
<!ELEMENT LEAGUE_NAME (#PCDATA)>
<!ELEMENT SEASON (YEAR, LEAGUE, LEAGUE)>
]>
清单8-8所列的文档部分确实符合这项DTD的规定,因为它的SEASON元素包含一个YEAR子元素,后接两个LEAGUE子元素,再没有别的。但是,如果文档只包括一个SEASON元素,那么这个文档尽管结构完整,也将是非法的。同样,如果LEAGUE在YEAR之前而不是在其后,或者如果LEAGUE有YEAR子元素,或者文档在其他任何方面不符合DTD,那么文档就是不合法的,合法性检查程序将拒绝这样的文档。
可直接将此种技术推广到DIVISION元素。每个LEAGUE有一个LEAGUE_NAME和三个DIVISION子元素。例如:
<!ELEMENT LEAGUE (LEAGUE_NAME, DIVISION, DIVISION, DIVISION)>
8.5.5 一个或多个子元素
每个DIVISION有一个DIVISION_NAME和四到六个TEAM子元素。指定DIVISION_NAME很容易,方法如下:
<!ELEMENT DIVISION (DIVISION_NAME)>
<!ELEMENT DIVISION_NAME (#PCDATA)>
但是,TEAM子元素就很棘手。指明DIVISION元素有四个TEAM子元素很容易,如下所示:
<!ELEMENT DIVISION (DIVISION_NAME, TEAM, TEAM, TEAM, TEAM)>
五个和六个也不难。但是您怎样说明有四到六个TEAM子元素呢?实际上,XML没有提供实现的简单方法。但是可以在子元素清单的元素名后放一个加号(+)来说明有一个或多个子元素,例如:
<!ELEMENT DIVISION (DIVISION_NAME, TEAM+)>
这就是说一个DIVISION元素必须包含一个DIVISION_NAME子元素,后接一个或多个TEAM子元素。
说明DIVISION元素有四到六个TEAM元素,而不是三到七个,这就难了。由于非常复杂,实际上很少有人使用。当读完本章时,看一看您是否已经想出怎样做了。
8.5.6 零或多个子元素
每个TEAM要包含一个TEAM_CITY,一个TEAM_NAME和不确定数目的PLAYER元素。实际上,棒球队至少要九名球员。但是,本书的很多例子中由于篇幅的原因而没有列出球员。因而,我们要指明一个TEAM元素可包含零或多个PLAYER子元素。在子元素清单中在元素名上附加一个星号(*)来实现这一目的。例如:
<!ELEMENT TEAM (TEAM_CITY, TEAM_NAME, PLAYER*)>
<!ELEMENT TEAM_CITY (#PCDATA)>
<!ELEMENT TEAM_NAME (#PCDATA)>
8.5.7 零或一个子元素
文档中出现的最后的元素是PLAYER子元素。它们全部是只包含文本的简单元素。下面是它们的声明:
<!ELEMENT SURNAME (#PCDATA)>
<!ELEMENT GIVEN_NAME (#PCDATA)>
<!ELEMENT POSITION (#PCDATA)>
<!ELEMENT GAMES (#PCDATA)>
<!ELEMENT GAMES_STARTED (#PCDATA)>
<!ELEMENT AT_BATS (#PCDATA)>
<!ELEMENT RUNS (#PCDATA)>
<!ELEMENT HITS (#PCDATA)>
<!ELEMENT DOUBLES (#PCDATA)>
<!ELEMENT TRIPLES (#PCDATA)>
<!ELEMENT HOME_RUNS (#PCDATA)>
<!ELEMENT RBI (#PCDATA)>
<!ELEMENT STEALS (#PCDATA)>
<!ELEMENT CAUGHT_STEALING (#PCDATA)>
<!ELEMENT SACRIFICE_ HITS (#PCDATA)>
<!ELEMENT SACRIFICE_FLIES (#PCDATA)>
<!ELEMENT ERRORS (#PCDATA)>
<!ELEMENT WALKS (#PCDATA)>
<!ELEMENT STRUCK_OUT (#PCDATA)>
<!ELEMENT HIT_BY_PITCH (#PCDATA)>
<!ELEMENT COMPLETE_GAMES (#PCDATA)>
<!ELEMENT SHUT_OUTS (#PCDATA)>
<!ELEMENT ERA (#PCDATA)>
<!ELEMENT INNINGS (#PCDATA)>
<!ELEMENT EARNED_RUNS (#PCDATA)>
<!ELEMENT HIT_BATTER (#PCDATA)>
<!ELEMENT WILD_PITCHES (#PCDATA)>
<!ELEMENT BALK (#PCDATA)>
<!ELEMENT WALKED_BATTER (#PCDATA)>
<!ELEMENT WINS (#PCDATA)>
<!ELEMENT LOSSES (#PCDATA)>
<!ELEMENT SAVES (#PCDATA)>
<!ELEMENT COMPLETE_GAMES (#PCDATA)>
<!ELEMENT STRUCK_OUT_BATTER (#PCDATA)>
现在我们可以编写PLAYER的元素声明了。所有球员有一个GIVEN_NAME、一个SURNAME、一个POSITION、一个GAMES。我们可声明每个PLAYER元素有一个AT_BATS、RUNS、HITS等等。但是,对于没有击球的投球手列出零得分是否准确还不敢确定。因为这可能出现这样一种情况,就是在开始计算平均击球数等问题时会导致被零除的错误。如果某一特定的元素不适合于给定的球员,或没有这一元素,那么就应该从该球员信息中忽略这一元素的统计。对于给定的球员我们不允许多于一个这样的元素。因而,我们就需要给定类型的零个或一个元素。在子元素列表后面附加一个问号(?)可表明这一点,如下所示:
<!ELEMENT PLAYER (GIVEN_NAME, SURNAME, POSITION,
GAMES,GAMES_STARTED,AT_BATS?,RUNS?,HITS?,DOUBLES?,
TRIPLES?, HOME_RUNS?, RBI?, STEALS?, CAUGHT_STEALING?,
SACRIFICE_ HITS?, SACRIFICE_FLIES?,ERRORS?, WALKS?,
STRUCK_OUT?, HIT_BY_PITCH ?, WINS?, LOSSES?, SAVES?,
COMPLETE_GAMES?,SHUT_OUTS?,ERA?,INNINGS EARNED_RUNS?,HIT_BATTER?,WILD_PITCHES?,
BALK?,WALKED_BATTER?,STRUCK_OUT_BATTER?)
>
这就是说每个PLAYER元素有一个GIVEN_NAME、SURNAME、POSITION、GAMES和GAMES_STARTED子元素。而且,每名球员可能有或可能没有AT_BATS、RUNS、HITS、DOUBLES、TRIPLES、HOME_RUNS、RBI、STEALS、CAUGHT_STEALING、SACRIFICE_HITS、SACRIFICE_FLIES、ERRORS、WALKS、STRUCK_OUT和HIT_BY_PITCH。
8.5.8 完整的文档和DTD
我们现在有了棒球统计的完整的DTD。这一DTD连同清单8-4中的文档部分一起,列在清单8-9中。
清单8-9只包括一个队和九名球员。在本书后附CD-ROM上的examples/baseball/1998validstats.xml目录下可找到1998年主要联赛球队和队员的统计文档。
清单8-9:一份合法的棒球统计文档和DTD
<?xml version="1.0" standalone="yes"?>
<!DOCTYPE SEASON [
<!ELEMENT YEAR (#PCDATA)>
<!ELEMENT LEAGUE (LEAGUE_NAME, DIVISION, DIVISION, DIVISION)>
<!ELEMENT LEAGUE_NAME (#PCDATA)>
<!ELEMENT DIVISION_NAME (#PCDATA)>
<!ELEMENT DIVISION (DIVISION_NAME, TEAM+)>
<!ELEMENT SEASON (YEAR, LEAGUE, LEAGUE)>
<!ELEMENT TEAM (TEAM_CITY, TEAM_NAME, PLAYER*)>
<!ELEMENT TEAM_CITY (#PCDATA)>
<!ELEMENT TEAM_NAME (#PCDATA)>
<!ELEMENT PLAYER (GIVEN_NAME, SURNAME, POSITION, GAMES,GAMES_STARTED, WINS?, LOSSES?, SAVES?,
AT_BATS?, RUNS?, HITS?, DOUBLES?, TRIPLES?, HOME_RUNS?,
RBI?, STEALS?, CAUGHT_STEALING?, SACRIFICE_HITS?,
SACRIFICE_FLIES?, ERRORS?, WALKS?, STRUCK_OUT?,
HIT_BY_PITCH?, COMPLETE_GAMES?, SHUT_OUTS?, ERA?,
INNINGS?,EARNED_RUNS?, HIT_BATTER?, WILD_PITCHES?,
BALK?,WALKED_BATTER?, STRUCK_OUT_BATTER?)
>
<!ELEMENT SURNAME (#PCDATA)>
<!ELEMENT GIVEN_NAME (#PCDATA)>
<!ELEMENT POSITION (#PCDATA)>
<!ELEMENT GAMES (#PCDATA)>
<!ELEMENT GAMES_STARTED (#PCDATA)>
<!ELEMENT COMPLETE_GAMES (#PCDATA)>
<!ELEMENT WINS (#PCDATA)>
<!ELEMENT LOSSES (#PCDATA)>
<!ELEMENT SAVES (#PCDATA)>
<!ELEMENT AT_BATS (#PCDATA)>
<!ELEMENT RUNS (#PCDATA)>
<!ELEMENT HITS (#PCDATA)>
<!ELEMENT DOUBLES (#PCDATA)>
<!ELEMENT TRIPLES (#PCDATA)>
<!ELEMENT HOME_RUNS (#PCDATA)>
<!ELEMENT RBI (#PCDATA)>
<!ELEMENT STEALS (#PCDATA)>
<!ELEMENT CAUGHT_STEALING (#PCDATA)>
<!ELEMENT SACRIFICE_HITS (#PCDATA)>
<!ELEMENT SACRIFICE_FLIES (#PCDATA)>
<!ELEMENT ERRORS (#PCDATA)>
<!ELEMENT WALKS (#PCDATA)>
<!ELEMENT STRUCK_OUT (#PCDATA)>
<!ELEMENT HIT_BY_PITCH (#PCDATA)>
<!ELEMENT SHUT_OUTS (#PCDATA)>
<!ELEMENT ERA (#PCDATA)>
<!ELEMENT INNINGS (#PCDATA)>
<!ELEMENT HOME_RUNS_AGAINST (#PCDATA)>
<!ELEMENT RUNS_AGAINST (#PCDATA)>
<!ELEMENT EARNED_RUNS (#PCDATA)>
<!ELEMENT HIT_BATTER (#PCDATA)>
<!ELEMENT WILD_PITCHES (#PCDATA)>
<!ELEMENT BALK (#PCDATA)>
<!ELEMENT WALKED_BATTER (#PCDATA)>
<!ELEMENT STRUCK_OUT_BATTER (#PCDATA)>
]>
<SEASON>
<YEAR>1998</YEAR>
<LEAGUE>
<LEAGUE_NAME>National</LEAGUE_NAME>
<DIVISION>
<DIVISION_NAME>East</DIVISION_NAME>
<TEAM>
<TEAM_CITY>Florida</TEAM_CITY>
<TEAM_NAME>Marlins</TEAM_NAME>
<PLAYER>
<GIVEN_NAME>Eric</GIVEN_NAME>
<SURNAME>Ludwick</SURNAME>
<POSITION>Starting Pitcher</POSITION>
<GAMES>13</GAMES>
<GAMES_STARTED>6</GAMES_STARTED>
<WINS>1</WINS>
<LOSSES>4</LOSSES>
<SAVES>0</SAVES>
<COMPLETE_GAMES>0</COMPLETE_GAMES>
<SHUT_OUTS>0</SHUT_OUTS>
<ERA>7.44</ERA>
<INNINGS>32.2</INNINGS>
<EARNED_RUNS>31</EARNED_RUNS>
<HIT_BATTER>27</HIT_BATTER>
<WILD_PITCHES>0</WILD_PITCHES>
<BALK>2</BALK>
<WALKED_BATTER>0</WALKED_BATTER>
<STRUCK_OUT_BATTER>17</STRUCK_OUT_BATTER>
</PLAYER>
<PLAYER>
<GIVEN_NAME>Brian</GIVEN_NAME>
<SURNAME>Daubach</SURNAME>
<POSITION>First Base</POSITION>
<GAMES>10</GAMES>
<GAMES_STARTED>3</GAMES_STARTED>
<AT_BATS>15</AT_BATS>
<RUNS>0</RUNS>
<HITS>3</HITS>
<DOUBLES>1</DOUBLES>
<TRIPLES>0</TRIPLES>
<HOME_RUNS>0</HOME_RUNS>
<RBI>3</RBI>
<STEALS>0</STEALS>
<CAUGH T_STEALING>0</CAUGHT_STEALING>
<SACRIFICE_ HITS>0</SACRIFICE_HITS>
<SACRIFICE_FLIES>0</SACRIFICE_FLIES>
<ERRORS>0</ERRORS>
<WALKS>1</WALKS>
<STRUCK_OUT>5</STRUCK_OUT>
<HIT_BY_PITCH>1</HIT_BY_PITCH>
</PLAYER>
</TEAM>
<TEAM>
<TEAM_CITY>Montreal</TEAM_CITY>
<TEAM_NAME>Expos</TEAM_NAME>
</TEAM>
<TEAM>
<TEAM_CITY>New York</TEAM_CITY>
<TEAM_NAME>Mets</TEAM_NAME>
</TEAM>
<TEAM>
<TEAM_CITY>Philadelphia</TEAM_CITY>
<TEAM_NAME>Phillies</TEAM_NAME>
</TEAM>
</DIVISION>
<DIVISION>
<DIVISION_NAME>Central</DIVISION_NAME>
<TEAM>
<TEAM_CITY>Chicago</TEAM_CITY>
<TEAM_NAME>Cubs</TEAM_NAME>
</TEAM>
</DIVISION>
<DIVISION>
<DIVISION_NAME>West</DIVISION_NAME>
<TEAM>
<TEAM_CITY>Arizona</TEAM_CITY>
<TEAM_NAME>Diamondbacks</TEAM_NAME>
</TEAM>
</DIVISION>
</LEAGUE>
<LEAGUE>
<LEAGUE_NAME>American</LEAGUE_NAME>
<DIVISION>
<DIVISION_NAME>East</DIVISION_NAME>
<TEAM>
<TEAM_CITY>Baltimore</TEAM_CITY>
<TEAM_NAME>Orioles</TEAM_NAME>
</TEAM>
</DIVISION>
<DIVISION>
<DIVISION_NAME>Central</DIVISION_NAME>
<TEAM>
<TEAM_CITY>Chicago</TEAM_CITY>
<TEAM_NAME>White Sox</TEAM_NAME>
</TEAM>
</DIVISION>
<DIVISION>
<DIVISION_NAME>West</DIVISION_NAME>
<TEAM>
<TEAM_CITY>Anaheim</TEAM_CITY>
<TEAM_NAME>Angels</TEAM_NAME>
</TEAM>
</DIVISION>
</LEAGUE>
</SEASON>
清单8-9不是符合这项DTD的唯一可能的文档,清单8-10也是一份合法的文档,因为它按规定的顺序包含了需要的所有元素,并且不包含未经声明的任何元素。这也许是您根据DTD创建的最短的合法文档。限定因素是这样的要求,每个SEASON包含两个LEAGUE子元素,每个LEAGUE子元素包含三个DIVISION子元素,每个DIVISION包含至少一个TEAM子元素。
清单8-10:另外一份符合棒球DTD的合法的XML文档
<?xml version="1.0" standalone="yes"?>
<!DOCTYPE SEASON [
<!ELEMENT YEAR (#PCDATA)>
<!ELEMENT LEAGUE (LEAGUE_NAME, DIVISION, DIVISION, DIVISION)>
<!ELEMENT LEAGUE_NAME (#PCDATA)>
<!ELEMENT DIVISION_NAME (#PCDATA)>
<!ELEMENT DIVISION (DIVISION_NAME, TEAM+)>
<!ELEMENT SEASON (YEAR, LEAGUE, LEAGUE)>
<!ELEMENT TEAM (TEAM_CITY, TEAM_NAME, PLAYER*)>
<!ELEMENT TEAM_CITY (#PCDATA)>
<!ELEMENT TEAM_NAME (#PCDATA)>
<!ELEMENT PLAYER (GIVEN_NAME, SURNAME, POSITION, GAMES,
GAMES_STARTED, COMPLETE_GAMES?, WINS?, LOSSES?, SAVES?,
AT_BATS?, RUNS?, HITS?, DOUBLES?, TRIPLES?, HOME_RUNS?,
RBI?, STEALS?, CAUGHT_STEALING?, SACRIFICE_ HITS?,
SACRIFICE_FLIES?, ERRORS?, WALKS?, STRUCK_OUT?,
HIT_BY_PITCH?, COMPLETE_GAMES?, SHUT_OUTS?, ERA?, INNINGS?,
EARNED_RUNS?, HIT_BATTER?, WILD_PITCHES?, BALK?,
WALKED_BATTER?, STRUCK_OUT_BATTER?)
>
<!ELEMENT SURNAME (#PCDATA)>
<!ELEMENT GIVEN_NAME (#PCDATA)>
<!ELEMENT POSITION (#PCDATA)>
<!ELEMENT GAMES (#PCDATA)>
<!ELEMENT GAMES_STARTED (#PCDATA)>
<!ELEMENT COMPLETE_GAMES (#PCDATA)>
<!ELEMENT WINS (#PCDATA)>
<!ELEMENT LOSSES (#PCDATA)>
<!ELEMENT SAVES (#PCDATA)>
<!ELEMENT AT_BATS (#PCDATA)>
<!ELEMENT RUNS (#PCDATA)>
<!ELEMENT HITS (#PCDATA)>
<!ELEMENT DOUBLES (#PCDATA)>
<!ELEMENT TRIPLES (#PCDATA)>
<!ELEMENT HOME_RUNS (#PCDATA)>
<!ELEMENT RBI (#PCDATA)>
<!ELEMENT STEALS (#PCDATA)>
<!ELEMENT CAUGHT_STEALING (#PCDATA)>
<!ELEMENT SACRIFICE_ HITS (#PCDATA)>
<!ELEMENT SACRIFICE_FLIES (#PCDATA)>
<!ELEMENT ERRORS (#PCDATA)>
<!ELEMENT WALKS (#PCDATA)>
<!ELEMENT STRUCK_OUT (#PCDATA)>
<!ELEMENT HIT_BY_PITCH (#PCDATA)>
<!ELEMENT SHUT_OUTS (#PCDATA)>
<!ELEMENT ERA (#PCDATA)>
<!ELEMENT INNINGS (#PCDATA)>
<!ELEMENT HOME_RUNS_AGAINST (#PCDATA)>
<!ELEMENT RUNS_AGAINST (#PCDATA)>
<!ELEMENT EARNED_RUNS (#PCDATA)>
<!ELEMENT HIT_BATTER (#PCDATA)>
<!ELEMENT WILD_PITCHES (#PCDATA)>
<!ELEMENT BALK (#PCDATA)>
<!ELEMENT WALKED_BATTER (#PCDATA)>
<!ELEMENT STRUCK_OUT_BATTER (#PCDATA)>
]>
<SEASON>
<YEAR>1998</YEAR>
<LEAGUE>
<LEAGUE_NAME>National</LEAGUE_NAME>
<DIVISION>
<DIVISION_NAME>East</DIVISION_NAME>
<TEAM>
<TEAM_CITY>Atlanta</TEAM_CITY>
<TEAM_NAME>Braves</TEAM_NAME>
</TEAM>
<TEAM>
<TEAM_CITY>Florida</TEAM_CITY>
<TEAM_NAME>Marlins</TEAM_NAME>
</TEAM>
<TEAM>
<TEAM_CITY>Montreal</TEAM_CITY>
<TEAM_NAME>Expos</TEAM_NAME>
</TEAM>
<TEAM>
<TEAM_CITY>New York</TEAM_CITY>
<TEAM_NAME>Mets</TEAM_NAME>
</TEAM>
<TEAM>
<TEAM_CITY>Philadelphia</TEAM_CITY>
<TEAM_NAME>Phillies</TEAM_NAME>
</TEAM>
</DIVISION>
<DIVISION>
<DIVISION_NAME>Central</DIVISION_NAME>
<TEAM>
<TEAM_CITY>Chicago</TEAM_CITY>
<TEAM_NAME>Cubs</TEAM_NAME>
</TEAM>
</DIVISION>
<DIVISION>
<DIVISION_NAME>West</DIVISION_NAME>
<TEAM>
<TEAM_CITY>Arizona</TEAM_CITY>
<TEAM_NAME>Diamondbacks</TEAM_NAME>
</TEAM>
</DIVISION>
</LEAGUE>
<LEAGUE>
<LEAGUE_NAME>American</LEAGUE_NAME>
<DIVISION>
<DIVISION_NAME>East </DIVISION_NAME>
<TEAM>
<TEAM_CITY>Baltimore</TEAM_CITY>
<TEAM_NAME>Orioles</TEAM_NAME>
</TEAM>
</DIVISION>
<DIVISION>
<DIVISION_NAME>Central</DIVISION_NAME>
<TEAM>
<TEAM_CITY>Chicago</TEAM_CITY>
<TEAM_NAME>White Sox</TEAM_NAME>
</TEAM>
</DIVISION>
<DIVISION>
<DIVISION_NAME>West</DIVISION_NAME>
<TEAM>
<TEAM_CITY>Anaheim</TEAM_CITY>
<TEAM_NAME>Angels</TEAM_NAME>
</TEAM>
</DIVISION>
</LEAGUE>
</SEASON>
8.5.9 选择
通常,一个父元素会有许多子元素。为了指明各子元素必须按顺序出现,可将这些子元素用逗号隔开。每个这样的子元素还可以以问号、加号或星号作后缀,以便调节它在那一位置按顺序出现的次数。
到目前为止,已经假定子元素以一定的顺序出现或不出现。还可以使DTD更加灵活,如允许文档作者在给定的地方选择不同的元素。例如,在一项描述顾客购物的DTD中,结帐方式信息中的每项PAYMENT元素都有CREDIT_CARD子元素或CASH子元素以便提供付款方式的信息。然而,单独的PAYMENT元素不能同时使用两者。
在父元素声明中,可以使用竖线(1)而不是逗号来分开子元素,以便指明文档作者需要输入一个或另一个子元素。例如,下面的语句就说明PAYMENT元素必须有CASH或CREDIT_CARD中的一个子元素。
<!ELEMENT PAYMENT (CASH | CREDIT_CARD)>
这种内容规格称为选择。当只使用它们当中的一个时就可用竖线分开任意数目的子元素。例如,下面语句说明PAYMENT元素必须有CASH、CREDIT_CARD或CHECK中的一个子元素。
<!ELEMENT PAYMENT (CASH | CREDIT_CARD | CHECK)>
当用括号对元素分组时竖线还会更有用。可以把元素组合在括号内分组,然后在括号后加星号、问号和加号后缀来指明一定的元素组合会出现零次或多次、零次或一次或者一次或多次。
8.5.10 带括号的子元素
在父元素声明中,必须了解有关子元素安排的最后一件事是如何用括号分组元素。每一对括号把数个元素合为一个独立元素。括号内的元素可以作为独立元素嵌套在其他括号内。而且,还可以加上加号、逗号或问号后缀。您还可以将这些括号组合成更大的括号组合来构成复杂的结构。这是一项功能强大的技术。
例如,考虑一份由两个互相可交换的元素组成的清单。这基本上是HTML中定义清单的方法。每项<dt>标记要与一项<dd>标记相匹配。如果用XML来复制这一结构,dl元素的声明看起来是这样的:
<!ELEMENT dl (dt , dd)*>
括号表明要重复的是相互匹配的<dt><dd>元素对。
元素经常以或多或少的随机顺序出现。新闻杂志文章通常有一个标题,绝大多数后接文章段落,带有图形、照片、副栏、副标题、通篇夹杂的引文,也许在末尾还有作者行。可以在父元素声明中在括号内用竖线分组列出所有子元素来指明这些安排。然后您在括号外加星号来指明允许括号内元素出现零或多次。例如:
<!ELEMENT ARTICLE (TITLE, (P | PHOTO | GRAPH | SIDEBAR
| PULLQUOTE | SUBHEAD)*, BYLINE?)>
再举一例,假设要说明一个DOCUMENT元素,它没有很多子元素,但必须有一个TITLE后接任意数量的混合文本段落和图像,以及一个任选的SIGNATURE块。该元素声明书写如下:
<!ELEMENT DOCUMENT (TITLE, (PARAGRAPH | IMAGE)*, SIGNATURE?)>
这不是描述这一结构的唯一方法。实际上这甚至不是最好的方法。另一种方法是声明一个包含PARAGRAPH和IMAGE元素的BODY元素并把它夹在TITLE和SIGNATURE元素之间,例如:
<!ELEMENT DOCUMENT (TITLE, BODY, SIGNATURE?)>
<!ELEMENT BODY ((PARAGRAPH | IMAGE)*)>
这两种途径的区别在于第二种途径在文档中使用了BODY元素。这一元素对读取文档的应用程序提供了有用的组织层次。问题是文档的读者(可能是另一种计算机程序)是否要把BODY作为单一的项目,并同TITLE和SIGNATURE分开,并可从元素总和中区别出来。
再举一个国际地址的例子。美国以外国家的地址并不遵循美国的约定。尤其是邮政编码有时在国家名之前,有时则在其后,如下两例:
Doberman-YPPAN
Box 2021
St. Nicholas QUEBEC
CAN GOS-3LO
或者
Editions Sybex
10/12 Villa Coeur-de-Vey
75685 Paris Cedex 14
France
虽然地址项不是按照顺序,邮件也可能邮到目的地,但最好还是让地址更加方便灵活些。允许灵活性的地址元素声明可以是这样:
<!ELEMENT ADDRESS (STREET+, (CITY | STATE | POSTAL_CODE
| COUNTRY)*)>
这表明ADDRESS元素必须有一个或多个STREET子元素后接任意数目的CITY、STATE、POSTAL_CODE或COUNTRY元素。如果要每个元素不多于一个,那这就不够理想了。遗憾的是,这超出了DTD的能力。您要使元素的顺序更加灵活方便,就要放弃一些控制每一元素最大数的能力。
另一方面,可能有一份由任意顺序排列的不同元素组成的清单,如一份录音清单就可能包含CD,唱片集和音带。区别各类不同元素的元素声明可能如下:
<!ELEMENT MUSIC_LIST (CD | ALBUM | TAPE)*>
在棒球DTD中,可以使用括号来为投手和击球手做不同的统计数据集。每名队员能用一套或另一套数据,但不能用两者。元素声明如下:
<!ELEMENT PLAYER (GIVEN_NAME, SURNAME, POSITION, GAMES,
GAMES_STARTED,((COMPLETE_GAMES?,WINS?,LOSSES?,SAVES?,
SHUT_OUTS?,ERA?,INNINGS?,EARNED_RUNS?,HIT_BATTER?,
WILD_PITCHES?,BALK?,WALKED_BATTER?,STRUCK_OUT_BATTER? )
|(AT_BATS?, RUNS?,HITS?, DOUBLES?, TRIPLES?, HOME_RUNS?,
RBI?,STEALS?,CAUGHT_STEALING?,SACRIFICE_HITS?,
SACRIFICE_FLIES?, ERRORS?, WALKS?, STRUCK_OUT?,
HIT_BY_PITCH ? )))>
在元素声明中还有一些不好处理的事情。例如,没有好的方法来说明一份文档要以TITLE元素开始而以SIGNATURE元素结束,两者之间可包含其他元素。这是因为ANY不能与其他子元素合用。
还有,通常对元素出现的位置掌握得越不准确,就越不能控制它们的数目。例如,不能说文档应该有一个可能出现在文档任何地方的TITLE元素。
但是,用括号来建立元素块,按顺序的元素用逗号分隔,平行出现的用竖线分隔,能让我们建立带有详细的元素出现的位置规则的复杂结构。但是不要无止境地这样做。简单的解决方法会更好。DTD越复杂,就越难编写出满足要求的合法的文档,更不要说维护DTD自身的复杂性了。
8.5.11 混合内容
读者可能已经注意到了,在以前的多数例子中,元素或者包含子元素,或者包含可析的字符数据,但不能同时包含两者。唯一的例外是以前例子中的一些基本元素。在这些例子中,全部标记的列表还没有完成。由于基本元素可以包含ANY数据,因而就既可以包含子元素又可以包含原始文本。
可以声明同时包含子元素和可析字符数据的标记。这就叫做混合内容。这样就可以给每个TEAM后面加上任意的文本块。例如:
<!ELEMENT TEAM (#PCDATA | TEAM_CITY | TEAM_NAME | PLAYER)*>
带有可析的字符数据的混合子元素会严重地限制文档的结构。特别是,只能指定可出现的子元素的名称。不能限定它们出现的顺序,每个元素的出现次数,或者它们是否出现。借助于DTD,利用下面的DTD中的一部分可实现这一要求:
<!ELEMENT PARENT (#PCDATA | CHILD1 | CHILD2 | CHILD3 )* >
除了改变子元素数目以外的其他事情都是不合法的。不能在包括#PCDATA的元素声明中使用逗号、问号或加号。用竖线分隔的元素和#PCDATA的列表是合法的。其他用法是不合法的。例如,下面的例子就不合法:
<!ELEMENT TEAM (TEAM_CITY, TEAM_NAME, PLAYER*, #PCDATA)>
使用混合内容的最基本的理由是,当将老式的文本数据转换成XML的过程中,随着新标记的增加逐步测试DTD的合法性,而不要在完成全部转换后再试图去发现错误。这是一个很好的技巧,我建议大家都这样做,毕竟从刚完成的代码中立即找出错误比几小时后要容易一些。但是,这仅仅是开发时的一个技巧。最终的用户是不应该看到这些的。当DTD完成后不能把子元素同可析的字符数据混合起来。一般总可以建立一个包括可析的字符数据的新标记。
例如,可以声明只包含#PCDATA数据的BLURB元素并把它增加为TEAM的最后一个子元素,这样就在每个TEAM元素的末尾包括一个文本块。下面是该声明:
<!ELEMENT TEAM (TEAM_CITY, TEAM_NAME, PLAYER*, BLURB)>
<!ELEMENT BLURB (#PCDATA)>
这对文档的文本改变不大。所有的变化只是向每个TEAM元素增加了一个或多个带有开始标记和结束标记的可选元素。但是这就使文档更加健全。而且,从XML程序接收到文档树的XML应用程序就能在更短的时间内处理数据,因为文档具有非混合内容所允许的更为结构化的格式。
8.5.12 空元素
前面讨论过,定义一个没有内容的元素有时是有用的。HTML中的例子包括图像、水平线和中断<IMG>、<R>和<BR>。在XML中,这类空元素是通过以/>结束的空标记来标识的,如<IMG/>、<HR/>和<BR/>。
合法的文档必须声明使用的空元素和非空元素。因为按定义,空元素没有子元素,声明很容易。可像通常一样使用包含空元素名的<!ELEMENT>来声明,但用关键词EMPTY (像所有XML标记一样区分大小写)代替了子元素的列表。例如:
<!ELEMENT BR EMPTY>
<!ELEMENT IMG EMPTY>
<!ELEMENT HR EMPTY>
清单8-11是同时使用了空元素和非空元素的合法文档。
清单8-11:使用了空标记的合法文档
<?xml version="1.0" standalone="yes"?>
<!DOCTYPE DOCUMENT [
<!ELEMENT DOCUMENT (TITLE,SIGNATURE)>
<!ELEMENT TITLE (#PCDATA)>
<!ELEMENT COPYRIGHT (#PCDATA)>
<!ELEMENT EMAIL (#PCDATA)>
<!ELEMENT BR EMPTY>
<!ELEMENT HR EMPTY>
<!ELEMENT LAST_MODIFIED (#PCDATA)>
<!ELEMENT SIGNATURE (HR, COPYRIGHT, BR, EMAIL,
BR, LAST_MODIFIED)>
]>
<DOCUMENT>
<TITLE>Empty Tags</TITLE>
<SIGNATURE>
<HR/>
<COPYRIGHT>1998 Elliotte Rusty Harold</COPYRIGHT><BR/>
<EMAIL>elharo@metalab.unc.edu</EMAIL><BR/>
<LAST_MODIFIED>Thursday,April 22,1999</LAST_MODIFIED>
</SIGNATURE>
</DOCUMENT>
8.6 DTD中的注释
像一份XML文档的其他部分一样,DTD也可以包含注释。这些注释不能在声明中出现,但可以在声明外出现。注释通常用来组织不同部分的DTD,为一些元素的许可内容提供说明,并对元素作进一步的解释。例如,YEAR元素的声明可以有这样的注释:
<!--A four digit year like 1998, 1999, or 2000 ?-->
<!ELEMENT YEAR (#PCDATA)>
像所有注释一样,这只是为了便于人们阅读源代码,XML处理程序会忽略注释部分。
注释的一个可能用法是定义标记中用到的缩略语。例如,在本章及前些章中,我极力避免使用棒球术语的缩略语,因为对一些人来说难以弄清楚。一种可能的途径是使用缩略语但在DTD中用注释加以定义。清单8-12同以前的棒球例子相似,但使用了DTD注释和缩略标记。
清单8-12:使用缩略标记和DTD注释的合法XML文档
<?xml version="1.0" standalone="yes"?>
<!DOCTYPE SEASON [
<!ELEMENT YEAR (#PCDATA)>
<!ELEMENT LEAGUE (LEAGUE_NAME, DIVISION, DIVISION, DIVISION)>
<!--American or National ?
<!ELEMENT LEAGUE_NAME (#PCDATA)>
<!--East , West , or Central ?
<!ELEMENT DIVISION_NAME (#PCDATA)>
<!ELEMENT DIVISION (DIVISION_NAME, TEAM+)>
<!ELEMENT SEASON (YEAR, LEAGUE, LEAGUE)>
<!ELEMENT TEAM (TEAM_CITY, TEAM_NAME, PLAYER*)>
<!ELEMENT TEAM_CITY (#PCDATA)>
<!ELEMENT TEAM_NAME (#PCDATA)>
<!ELEMENT PLAYER (GIVEN_NAME, SURNAME, P, G,
GS, AB?, R?, H?, D?, T?, HR?, RBI?, SB?, CS?,
SH?, SF?, E?, BB?, S?, HBP?, CG?, SO?, ERA?, IP?,
HRA?, RA?, ER?, HB?, WP?, B?, WB?, K?)
>
<!--=======================-->
<!--Player Info-->
<!--Player’s last name-->
<!ELEMENT SURNAME (#PCDATA)>
<!--Player’s first name-->
<!ELEMENT GIVEN_NAME (#PCDATA)>
<!—Position-->
<!ELEMENT P (#PCDATA)>
<!--Games Played-->
<!ELEMENT G (#PCDATA)>
<!--Games Started-->
<!ELEMENT GS (#PCDATA)>
<!--=======================-->
<!--Batting Statistics-->
<!--At Bats-->
<!ELEMENT AB (#PCDATA)>
<!--Runs-->
<!ELEMENT R (#PCDATA)>
<!--Hits-->
<!ELEMENT H (#PCDATA)>
<!--Doubles-->
<!ELEMENT D (#PCDATA)>
<!--Triples-->
<!ELEMENT T (#PCDATA)>
<!--Home Runs-->
<!ELEMENT HR (#PCDATA)>
<!--Runs Batted In-->
<!ELEMENT RBI (#PCDATA)>
<!--Stolen Bases-->
<!ELEMENT SB (#PCDATA)>
<!--Caught Stealing-->
<!ELEMENT CS (#PCDATA)>
<!--Sacrifice Hits-->
<!ELEMENT SH (#PCDATA)>
<!--Sacrifice Flies-->
<!ELEMENT SF (#PCDATA)>
<!--Errors-->
<!ELEMENT E (#PCDATA)>
<!--Walks (Base on Balls)-->
<!ELEMENT BB (#PCDATA)>
<!--Struck Out-->
<!ELEMENT S (#PCDATA)>
<!--Hit By Pitch-->
<!ELEMENT HBP (#PCDATA)>
<!--=======================-->
<!--Pitching Staistics-->
<!--Complete Games-->
<!ELEMENT CG (#PCDATA)>
<!--Shut Outs-->
<!ELEMENT SO (#PCDATA)>
<!--ERA-->
<!ELEMENT ERA (#PCDATA)>
<!--Innings Pitched-->
<!ELEMENT IP (#PCDATA)>
<!--Home Runs hit Against-->
<!ELEMENT HRA (#PCDATA)>
<!--Runs hit Against-->
<!ELEMENT RA (#PCDATA)>
<!--Earned Runs-->
<!ELEMENT ER (#PCDATA)>
<!--Hit Batter-->
<!ELEMENT HB (#PCDATA)>
<!--Wild Pitches-->
<!ELEMENT WP (#PCDATA)>
<!—Balk-->
<!ELEMENT B (#PCDATA)>
<!--Walked Batter-->
<!ELEMENT WB (#PCDATA)>
<!--Struck Out Batter-->
<!ELEMENT K (#PCDATA)>
<!--=======================-->
<!--Fielding Statistics-->
<!--Not yet supported-->
]>
<SEASON>
<YEAR>1998</YEAR>
<LEAGUE>
<LEAGUE_NAME>National</LEAGUE_NAME>
<DIVISION>
<DIVISION_NAME>East </DIVISION_NAME>
<TEAM>
<TEAM_CITY>Atlanta</TEAM_CITY>
<TEAM_NAME>Braves</TEAM_NAME>
<PLAYER>
<GIVEN_NAME>Ozzie</GIVEN_NAME>
<SURNAME>Guillen</SURNAME>
<P>Short stop</P>
<G>83</G>
<GS>59</GS>
<AB>264</AB>
<R>35</R>
<H>73</H >
<D>15</D>
<T>1</T>
<HR>1</HR>
<RBI>22</RBI>
<SB>1</SB>
<CS>4</CS>
<SH>4</SH>
<SF>2</SF>
<E>6</E>
<BB>24</BB>
<S>25</S>
<HBP>1</HBP>
</PLAYER>
</TEAM>
<TEAM>
<TEAM_CITY>Florida</TEAM_CITY>
<TEAM_NAME>Marlins</TEAM_NAME>
</TEAM>
<TEAM>
<TEAM_CITY>Montreal</TEAM_CITY>
<TEAM_NAME>Expos</TEAM_NAME>
</TEAM>
<TEAM>
<TEAM_CITY>New York</TEAM_CITY>
<TEAM_NAME>Mets</TEAM_NAME>
</TEAM>
<TEAM>
<TEAM_CITY>Philadelphia</TEAM_CITY>
<TEAM_NAME>Phillies</TEAM_NAME>
</TEAM>
</DIVISION>
<DIVISION>
<DIVISION_NAME>Central</DIVISION_NAME>
<TEAM>
<TEAM_CITY>Chicago</TEAM_CITY>
<TEAM_NAME>Cubs</TEAM_NAME>
</TEAM>
</DIVISION>
<DIVISION>
<DIVISION_NAME>West</DIVISION_NAME>
<TEAM>
<TEAM_CITY>Arizona</TEAM_CITY>
<TEAM_NAME>Diamondbacks</TEAM_NAME>
</TEAM>
</DIVISION>
</LEAGUE>
<LEAGUE>
<LEAGUE_NAME>American</LEAGUE_NAME>
<DIVISION>
<DIVISION_NAME>East</DIVISION_NAME>
<TEAM>
<TEAM_CITY>Baltimore</TEAM_CITY>
<TEAM_NAME>Orioles</TEAM_NAME>
</TEAM>
</DIVISION>
<DIVISION>
<DIVISION_NAME>Central</DIVISION_NAME>
<TEAM>
<TEAM_CITY>Chicago</TEAM_CITY>
<TEAM_NAME>White Sox</TEAM_NAME>
</TEAM>
</DIVISION>
<DIVISION>
<DIVISION_NAME>West</DIVISION_NAME>
<TEAM>
<TEAM_CITY>Anaheim</TEAM_CITY>
<TEAM_NAME>Angels</TEAM_NAME>
</TEAM>
</DIVISION>
</LEAGUE>
</SEASON>
当整个联赛全包含在内时,产生的文档从带长标记的699K缩短到带短标记的391K,节约了44%。但是信息的内容是相同的。两个文档压缩后很接近,短标记文档58K,长标记文档66K。
在注释内可以包括和应该包括的信息量没有限制。包括得多使DTD更长(这样就使检测更难,下载更慢)。然而,在下面的几章中,您将学会如何在多个XML文档间共享同一DTD以及将DTD拆成更好管理的多个部分。这样,使用注释的缺点就是暂时的了。我建议在DTD中自由地使用注释,尤其是对于打算公用的DTD。
8.7 在文档间共享通用的DTD
前面的合法的例子都在文档的序言部分包含了DTD。但是XML真正的功能来自于不同的人们编写的可为许多文档共享通用的DTD。如果DTD不是直接包含在文档内,而是从外部联结而来,则DTD的改变会自动传播给使用它的所有文档。另一方面,当DTD改变时并不能确保其向后兼容性。不兼容的改变会破坏文档。
当使用外部DTD时,文档类型声明要加以改变。DTD不再是包括在方括号中,而是在SYSTEM关键词后接一个能找到DTD的绝对或相对的URL。例如:
<!DOCTYPE root_element_name SYSTEM "DTD_ URL">
这里root_element_name像以前一样是基本元素的名称,SYSTEM是一个XML关键词,DTD_URL是能找到DTD的绝对或相对的URL。例如:
<!DOCTYPE SEASON SYSTEM "baseball.dtd">
为说明这一过程让我们来转换一个熟悉的例子。清单8-12包括了棒球统计的内部DTD。我们要把这份清单转换为外部DTD。首先,去掉DTD并把它放入自己的文档。DTD是起始于<!DOCTYPE SEASON [终止于]>之间的所有内容。但不包括<!DOCTYPE SEASON [和]>。可以将其保存在名为baseball.dtd的文档内,如清单8-13所示。文档名并不重要,通常用的扩展名为.dtd。
清单8-13:棒球的DTD文档
<!ELEMENT YEAR (#PCDATA)>
<!ELEMENT LEAGUE (LEAGUE_NAME, DIVISION, DIVISION, DIVISION)>
<!--American or National-->
<!ELEMENT LEAGUE_NAME (#PCDATA)>
<!--East, West, or Central-->
<!ELEMENT DIVISION_NAME (#PCDATA)>
<!ELEMENT DIVISION (DIVISION_NAME, TEAM+)>
<!ELEMENT SEASON (YEAR, LEAGUE, LEAGUE)>
<!ELEMENT TEAM (TEAM_CITY, TEAM_NAME, PLAYER*)>
<!ELEMENT TEAM_CITY (#PCDATA)>
<!ELEMENT TEAM_NAME (#PCDATA)>
<!ELEMENT PLAYER (GIVEN_NAME, SURNAME, P, G,
GS, AB?, R?,H?, D?, T?, HR?, RBI?, SB?, CS?,
SH?, SF?, E?, BB?, S?, HBP?, CG?, SO?, ERA?, IP?,
HRA?, RA?, ER?, HB?, WP?, B?, WB?, K?)
>
<!--=======================-->
<!--Player Info-->
<!--Player’s last name-->
<!ELEMENT SURNAME (#PCDATA)>
<!--Player’s first name-->
<!ELEMENT GIVEN_NAME (#PCDATA)>
<!--Position-->
<!ELEMENT P (#PCDATA)>
<!--Games Played-->
<!ELEMENT G (#PCDATA)>
<!--Games Started-->
<!ELEMENT GS (#PCDATA)>
<!--=======================-->
<!--Batting Statistics-->
<!--At Bats-->
<!ELEMENT AB (#PCDATA)>
<!--uns-->
<!ELEMENT R (#PCDATA)>
<!--Hits--> ?
<!ELEMENT H (#PCDATA)>
<!--Doubles-->
<!ELEMENT D (#PCDATA)>
<!--Triples-->
<!ELEMENT T (#PCDATA)>
<!--Home Runs-->
<!ELEMENT HR (#PCDATA)>
<!--Runs Batted In-->
<!ELEMENT RBI (#PCDATA)>
<!--Stolen Bases-->
<!ELEMENT SB (#PCDATA)>
<!--Caught Stealing-->
<!ELEMENT CS (#PCDATA)>
<!--Sacrifice Hits-->
<!ELEMENT SH (#PCDATA)>
<!--Sacrifice Flies-->
<!ELEMENT SF (#PCDATA)>
<!—Errors-->
<!ELEMENT E (#PCDATA)>
<!--Walks (Base on Balls)-->
<!ELEMENT BB (#PCDATA)>
<!--Struck Out-->
<!ELEMENT S (#PCDATA)>
<!--Hit By Pitch-->
<!ELEMENT HBP (#PCDATA)>
<!--=======================-->
<!--Pitching Staistics-->
<!--Complete Games-->
<!ELEMENT CG (#PCDATA)>
<!--Shut Outs-->
<!ELEMENT SO (#PCDATA)>
<!--ERA-->
<!ELEMENT ERA (#PCDATA)>
<!--Innings Pitched-->
<!ELEMENT IP (#PCDATA)>
<!--Home Runs hit Against-->
<!ELEMENT HRA (#PCDATA)>
<!--Runs hit Against-->
<!ELEMENT RA (#PCDATA)>
<!--Earned Runs-->
<!ELEMENT ER (#PCDATA)>
<!--Hit Batter-->
<!ELEMENT HB (#PCDATA)>
<!--Wild Pitches-->
<!ELEMENT WP (#PCDATA)>
<!—Balk-->
<!ELEMENT B (#PCDATA)>
<!--Walked Batter-->
<!ELEMENT WB (#PCDATA)>
<!--Struck Out Batter-->
<!ELEMENT K (#PCDATA)>
<!--=======================-->
<!--Fielding Statistics-->
<!--Not yet supported-->
接下来,需要改动文档本身。因为要依赖于另一文档中的DTD,XML声明不再是独立的文档。所以standalone属性要改为no,如下所示:
<?xml version="1.0" standalone="no"?>
然后还要改变<!DOCTYPE>标记,借助于包括SYSTEM关键字和URL(通常是相对的)使它指向DTD。
<!DOCTYPE SEASON SYSTEM "baseball.dtd" >
文档的其余部分与以前相同。但是,现在序言部分只包含XML声明和文档类型声明而不包括DTD。清单8-14显示了这些代码。
清单8-14:带有外部DTD的棒球统计
<?xml version="1.0" standalone="yes"?>
<!DOCTYPE SEASON SYSTEM "baseball.dtd" >
<SEASON>
<YEAR>1998</YEAR>
<LEAGUE>
<LEAGUE_NAME>National</LEAGUE_NAME>
<DIVISION>
<DIVISION_NAME>East</DIVISION_NAME>
<TEAM>
<TEAM_CITY>Atlanta</TEAM_CITY>
<TEAM_NAME>Braves</TEAM_NAME>
<PLAYER>
<GIVEN_NAME>Ozzie</GIVEN_NAME>
<SURNAME>Guillen</SURNAME>
<P>Shortstop</P>
<G>83</G>
<GS>59</GS>
<AB>264</AB>
<R>35</R>
<H>73</H>
<D>15</D>
<T>1</T>
<HR>1</HR>
<RBI>22</RBI>
<SB>1</SB>
<CS>4</CS>
<S >4</S >
<SF>2</SF>
<E>6</E>
<BB>24</BB>
<S>25</S>
<HBP>1</HBP>
</PLAYER>
</TEAM>
<TEAM>
<TEAM_CITY>Florida</TEAM_CITY>
<TEAM_NAME>Marlins</TEAM_NAME>
</TEAM>
<TEAM>
<TEAM_CITY>Montreal</TEAM_CITY>
<TEAM_NAME>Expos</TEAM_NAME>
</TEAM>
<TEAM>
<TEAM_CITY>New York</TEAM_CITY>
<TEAM_NAME>Mets</TEAM_NAME>
</TEAM>
<TEAM>
<TEAM_CITY>Philadelphia</TEAM_CITY>
<TEAM_NAME>Phillies</TEAM_NAME>
</TEAM>
</DIVISION>
<DIVISION>
<DIVISION_NAME>Central</DIVISION_NAME>
<TEAM>
<TEAM_CITY>Chicago</TEAM_CITY>
<TEAM_NAME>Cubs</TEAM_NAME>
</TEAM>
</DIVISION>
<DIVISION>
<DIVISION_NAME>West</DIVISION_NAME>
<TEAM>
<TEAM_CITY>Arizona</TEAM_CITY>
<TEAM_NAME>Diamondbacks</TEAM_NAME>
</TEAM>
</DIVISION>
</LEAGUE>
<LEAGUE>
<LEAGUE_NAME>American</LEAGUE_NAME>
<DIVISION>
<DIVISION_NAME>East</DIVISION_NAME>
<TEAM>
<TEAM_CITY>Baltimore</TEAM_CITY>
<TEAM_NAME>Orioles</TEAM_NAME>
</TEAM>
</DIVISION>
<DIVISION>
<DIVISION_NAME>Central</DIVISION_NAME>
<TEAM>
<TEAM_CITY>Chicago</TEAM_CITY>
<TEAM_NAME>White Sox</TEAM_NAME>
</TEAM>
</DIVISION>
<DIVISION>
<DIVISION_NAME>West</DIVISION_NAME>
<TEAM>
<TEAM_CITY>Anaheim</TEAM_CITY>
<TEAM_NAME>Angels</TEAM_NAME>
</TEAM>
</DIVISION>
</LEAGUE>
</SEASON>
一定要确保清单8-14和baseball.dtd在同一目录下,然后像通常一样把清单8-14装入Web浏览器。如果一切正常,就会看到同装入清单8-12一样的输出。现在可以使用这个DTD来编写其他文档,如其他年度的统计数据。
如果添加了样式单,那么就在三个不同的文档中保存了文档的三个重要部分。数据在文档文件中,数据应用的结构和语义在DTD文件中,而格式在样式单中。这种结构使我们能相对独立地检查和改变其中任一部分或全部。
DTD与文档之间比文档与样式单之间联系更紧密。改变DTD一般要重新检查文档的合法性,并需要编辑文档使它与DTD相符。这样的顺序必要性取决于编辑方法;增加元素没什么问题,但移走元素就可能有问题。
8.7.1 远程URL上的DTD
如果一个DTD适用于多份文档,就不能总把它放在应用它的每份文档的同一目录下。可以使用URL来准确指明DTD的地址。例如,让我们假设棒球DTD在http://metalab.unc.edu/xml/dtds/baseball.dtd,可在序言中使用下面的<!DOCTYPE> 标记将其链接到文档上:
<!DOCTYPE SEASON SYSTEM
"http://metalab.unc.edu/xml/dtds/baseball.dtd">
本例中使用了完整的URL,从任何地方都是合法的。有时也希望从相对于Web服务器文档根目录或当前目录找出DTD来 。通常,任何相对于文档位置所形成合法的URL的引用都可以接受。例如,下面这些都是合法的文档类型声明:
<!DOCTYPE SEASON SYSTEM"/xml/dtds/baseball.dtd">
<!DOCTYPE SEASON SYSTEM"/dtds/baseball.dtd">
<!DOCTYPE SEASON SYSTEM "../baseball.dtd">
一个文档不能有多于一个的文档类型声明,即不能有多于一个的<!DOCTYPE >标记。要使用不止在一个DTD中声明的元素,就需要使用外部参数实体引用。这些内容将在下一章中讨论。
8.7.2 公共的DTD
关键词SYSTEM是为单个作者或小组所用的私有的DTD使用的。但作为XML承诺的一部分,可令覆盖整个产业的广泛组织(如ISO或IEEE)能够将公共DTD加以标准化,以便用于各自的专门领域。这样的标准化可以让人们不用为同一项目重复作标记,并且使用户共享公用文档更容易。
为创建组织之外的编写者设计的DTD使用PUBLIC关键词而不使用SYSTEM关键词。并且DTD有一个文件名。句法如下:
<!DOCTYPE root_element_name PUBLIC "DTD_name" "DTD_URL">
root_element_name仍然是基本元素名称。PUBLIC是XML关键词,说明这一DTD是公共使用并具有名称。DTD_name是与此DTD联系的名称。有些XML处理程序会使用名称从中心库中检索DTD。最后,如果DTD不能根据名称从熟知的库中检索到,则DTD_URL是一个能找到该DTD的相对或绝对URL。
DTD名称与XML名称略有不同。它们只能包含ASCII字母字符、空格、软回车符、换行符和下面的标点符号: -’()+,/:=?;!*#@$_%。 而且,公共DTD要遵守一些约定。
如果一项DTD是ISO标准,它的名称要以字符串“ISO”开始。如果是非ISO标准组织批准的DTD,它的名称以加号(+)开始。如果不是标准组织批准的DTD,它的名称以连字符 (-)开始。这些开始字符串后接双斜线(//) 和DTD所有者的名字,其后接另一个双斜线和DTD描述的文档类型,然后又是一个双斜线后接ISO639语言标识符,如EN表示英语。在http://www.ics.uci.edu/pub/ietf/http/related/iso639.txt处列有完整的ISO639标识符。例如,棒球DTD可以如下命名:
-//Elliotte Rusty Harold//DTD baseball statistics//EN
本例说明这个DTD不是由任何标准组织批准的(-),为Elliotte Rusty Harold所有,描述棒球统计,用英语编写。通过DTD名称指向这一DTD的完整的文档类型声明如下:
<!DOCTYPE SEASON PUBLIC
"//Elliotte Rusty Harold//DTD baseball statistics//EN"
"http://metalab.unc.edu/xml/dtds/baseball.dtd">
读者也许注意到了许多HTML编辑器如BBEdit会在其创建的每个HTML文档开端放入下列字符串:
<!DOCTYPE HTML PUBLIC"-//W3C//DTD HTML//EN">
现在可能 了解这些字符串是什么意思了!它表明文档符合一项非标准 (-) 的HTML的DTD,由W3C用英语制作。
从技术上说,W3C不是一个标准组织,因为它的成员限于交纳会费的公司而不是官方批准的实体。它只出版建议而不是标准。实际上这种区别没有关系。
8.7.3 内部和外部DTD子集
尽管大多数文档由易于定义的部分组成,但不是所有的文档都使用共同的模板。许多文档为自己使用而增加特定元素时,可能需要像HTML 4.0 DTD 这样的标准DTD。其他文档可能只使用标准元素,但需要对它们重新排序。例如,一个HTML主页可能有一个BODY元素,它必须包含一个H1标题标记后接一份DL定义列表,而另一个HTML主页可能有一个BODY元素,它包含许多不同的顺序不定的标题标记、段落和图像。如果特定的文档与同一站点上其他页面具有不同的结构,在文档本身内定义结构比在单独的DTD中定义更有用。这种方法也使文档更易于编辑。
为达此目的,文档可使用内部和外部DTD。内部声明放在<!DOCTYPE>标记尾部的方括号中。例如,假如需要一个包括棒球统计并有页眉和页脚的主页。这样的文档可如清单8-15所示。棒球信息从文档baseball.dtd中得到,构成外部DTD子集。基本元素DOCUMENT 以及元素TITLE和SIGNATURE的定义来自包含于文档中的内部DTD子集。这有点不寻常。一般的,更为通用的部分可能应该是外部DTD的一部分,而内部内容则更与特定专题有关。
清单8-15:DTD具有内部和外部DTD子集的棒球文档
<?xml version="1.0" standalone="yes"?>
<!DOCTYPE SEASON SYSTEM "baseball.dtd"> [
<!ELEMENT DOCUMENT (TITLE, SEASON, SIGNATURE)>
<!ELEMENT TITLE (#PCDATA)>
<!ELEMENT COPYRIG T (#PCDATA)>
<!ELEMENT EMAIL (#PCDATA)>
<!ELEMENT LAST_MODIFIED (#PCDATA)>
<!ELEMENT SIGNATURE (COPYRIGHT, EMAIL, LAST_MODIFIED)>
]>
<DOCUMENT>
<TITLE>1998 Major League Baseball Statistics</TITLE>
<SEASON>
<YEAR>1998</YEAR>
<LEAGUE>
<LEAGUE_NAME>National</LEAGUE_NAME>
<DIVISION>
<DIVISION_NAME>East</DIVISION_NAME>
<TEAM>
<TEAM_CITY>Atlanta</TEAM_CITY>
<TEAM_NAME>Braves</TEAM_NAME>
</TEAM>
<TEAM>
<TEAM_CITY>Florida</TEAM_CITY>
<TEAM_NAME>Marlins</TEAM_NAME>
</TEAM>
<TEAM>
<TEAM_CITY>Montreal</TEAM_CITY>
<TEAM_NAME>Expos</TEAM_NAME>
</TEAM>
<TEAM>
<TEAM_CITY>New York</TEAM_CITY>
<TEAM_NAME>Mets</TEAM_NAME>
</TEAM>
<TEAM>
<TEAM_CITY>Philadelphia</TEAM_CITY>
<TEAM_NAME>Phillies</TEAM_NAME>
</TEAM>
</DIVISION>
<DIVISION>
<DIVISION_NAME>Central</DIVISION_NAME>
<TEAM>
<TEAM_CITY>Chicago</TEAM_CITY>
<TEAM_NAME>Cubs</TEAM_NAME>
</TEAM>
</DIVISION>
<DIVISION>
<DIVISION_NAME>West</DIVISION_NAME>
<TEAM>
<TEAM_CITY>Arizona</TEAM_CITY>
<TEAM_NAME>Diamondbacks</TEAM_NAME>
</TEAM>
</DIVISION>
</LEAGUE>
<LEAGUE>
<LEAGUE_NAME>American</LEAGUE_NAME>
<DIVISION>
<DIVISION_NAME>East </DIVISION_NAME>
<TEAM>
<TEAM_CITY>Baltimore</TEAM_CITY>
<TEAM_NAME>Orioles</TEAM_NAME>
</TEAM>
</DIVISION>
<DIVISION>
<DIVISION_NAME>Central</DIVISION_NAME>
<TEAM>
<TEAM_CITY>Chicago</TEAM_CITY>
<TEAM_NAME>White Sox</TEAM_NAME>
</TEAM>
</DIVISION>
<DIVISION>
<DIVISION_NAME>West</DIVISION_NAME>
<TEAM>
<TEAM_CITY>Anaheim</TEAM_CITY>
<TEAM_NAME>Angels</TEAM_NAME>
</TEAM>
</DIVISION>
</LEAGUE>
</SEASON>
<SIGNATURE>
<COPYRIGHT>Copyright 1999 Elliotte Rusty Harold</COPYRIGHT>
<EMAIL>elharo@metalab.unc.edu</EMAIL>
<LAST_MODIFIED>March 10, 1999</LAST_MODIFIED>
</SIGNATURE>
</DOCUMENT>
在内部和外部DTD子集中的同名元素之间发生冲突的情况下,内部声明的元素具有优先权。这种优先权提供了不完善部分的继承机制。例如,如要推翻PLAYER元素的定义,以便只包含击球统计数据,而不要投球统计数据。这时可使用大多数的棒球DTD的相同声明,但却要将PLAYER元素作如下改变:
<!DOCTYPE SEASON SYSTEM "baseball.dtd" [
<!ELEMENT PLAYER (GIVEN_NAME, SURNAME, P, G,
GS, AB?, R?, H?, D?, T?, HR?, RBI?, SB?, CS?,
SH ?, SF?, E?, BB?, S?, HBP?)
>
]>
8.8 本章小结
在本章中,学习了如何使用DTD来描述文档结构,包括文档包含的必需元素和任选元素,以及这些元素间的相关关系。特别是学习了以下内容:
· 文档类型定义(DTD),它提供了文档包含的元素、标记、属性和实体及相互关系的清单。
- 文档序言包含文档类型声明,文档类型声明指明基本元素并包含DTD。DTD处在XML声明与实际文档开始之间。由<!DOC-TYPE ROOT [和]>加以界定,ROOT是基本元素名称。
- DTD列出了文档的可允许的标记和结构。遵守DTD规则的文档才是合法的。
- 元素类型声明声明元素名称和子元素。
- 元素类型声明中用逗号分隔的子元素在文档中出现的顺序必须与声明中的顺序相同。
- 加号表示元素可以出现一次或多次。
- 星号表示元素可以出现零次或多次。
- 问号表示元素可以出现零次或一次。
- 竖线表示可以使用这一个也可以使用另一个元素。
- 括号可以组合子元素,以便使元素声明更详尽。
- 混合内容包含元素和可析的字符数据,但会限制父元素可实现的结构。
- 空元素用EMPTY关键词声明。
- 注释使DTD 更具可读性。
- 在文档类型声明中利用SYSTEM关键词和一个URL可以定位外部DTD。
- 在文档类型声明中用PUBLIC关键词可以定位标准DTD。
- 内部DTD子集中的声明可推翻外部DTD子集中的声明。
在下一章中,读者可学到有关DTD的更多知识,包括实体引用如何提供文本替换,如何将DTD与它所描述的文档分开,以便易于在文档间共享。还会学到如何用多份DTD描述单个文档。
第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中。实中<;、>;、&apos;、"e;、&;是预定义的体引用,分别指的是文本实体<、>、’、”、&符号。然而也可在文档DTD中定义新的实体。
.2 内部通用实体
内部通用实体引用可看作经常使用的文本或强制格式文本的缩写。DTD中的<!ENTITY>标记定义缩写,并且该缩写就代替了文本。例如,可在DTD中简单地把页脚定义为实体footer,然后每页只需键入&footer;,而勿需在每页底部键入相同的页脚。此外,若决定更改页脚块(也许是因为你的电子邮件地址更改了),就仅需在DTD中作一次更改即可,勿需对共享同一页脚的页面逐个进行更改。
通用实体引用以“&”符号开始,以“;”结尾,两个符号之间为实体名。例如,“<;”就是小于符号(<)的通用实体引用,实体名为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>©99; &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 "©99 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文档中用来代替一些特殊的字符,这些字符如果不用引用方式就会被解释为标记。例如实体引用<;代表小于号<,小于符号<可解释为标记的开头。
考虑到最大限度的兼容,若计划使用预定义实体引用,就该在DTD中声明这些引用。因为需要避免DTD中字符的递归引用,所以声明时必须相当小心。为方便引用的声明,字符引用使用字符的十六进制ASCII值。清单9-2显示了所需要的声明。
|
表9-1 XML中的预定义实体引用 |
|
|
实体引用 |
字符 |
|
& |
& |
|
< |
< |
|
> |
> |
|
" |
" |
|
' |
|
清单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的主要益处在于还可以使用内部通用实体引用,除了五个预定义引用>、<、"、'和&之外。可按通常的方法简单地声明所需的实体,然后在文档中使用它们。
例如,回顾前面的例子,假如需要实体引用&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字母的实体引用,这些引用比数字化字符实体引用稍便于记忆。例如,å预定义为˚,þ预定义为þ,ý预定义为Ý等等。清单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字符键入诗文,而把这些字母分别编码为ê;、î;、ô;、û;和æ;的实体引用。即使不需要有效完整的文档,也依然需要一个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ôrta dhat seggen,</LINE>
<LINE>dhat sih urhêttun ænon muotîn,</LINE>
<LINE>Hiltibrant enti Hadhubrant untar heriun tuê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。