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在文档中嵌入样式单
本部分包括以下各章:
第1章——XML概览
第2章——XML应用简介
第3章——第一个XML文档
第4章——数据的结构化
第5章——特性、空标记和XSL
第6章——结构完整的XML文档
第7章——外国语言与非罗马文字
本章将向读者介绍XML的基本知识以及概略地解释什么是XML以及如何使用XML。还要向读者说明如何将各种不同的XML表达式组合在一起,XML文档是如何创建的并如何向人们发送这种文档。
本章的主要内容包括:
· 什么是XML
XML代表Extensible Markup Language(eXtensible Markup Language的缩写,意为可扩展的标记语言)。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就意味着不必等待浏览器的开发商来满足用户的需要了。用户可以创建自己需要的标记,当需要时,告诉浏览器如何显示这些标记就可以了。
关于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更为灵活而且适用于各种应用,因为有限数目的标记不必用于许多不同的目的。
XML使许多只利用HTML难以解决的任务变得简单,使只利用HTML不可能完成的任务得以完成。因为XML是可扩展的,开发人员喜爱XML有许多原因。到底是哪个更令人感兴趣,取决于每个人的需要。但有一点是肯定的,一旦用上XML,就可发现,它正是解决许多令人感到棘手的问题的有力工具。本节研究一些令开发人员激动的一般应用。在第2章中,还会看到已经用XML开发出来的一些特殊应用。
XML允许各种不同的专业(如音乐、化学、数学等)开发与自己的特定领域有关的标记语言。这就使得该领域中的人们可以交换笔记、数据和信息,而不用担心接收端的人是否有特定的软件来创建数据。特定领域的开发人员甚至可以向本领域外的人发送文档,有相当的理由可以认为,至少接受文档的人能够查看文档的内容。
更进一步说,为特别的领域创建标记语言不会产生“病件”(bloatware)或是对于本专业外的人来说产生不必要的复杂性。一般人也许不会对电力工程图感兴趣,但是电力工程师却对此感兴趣。一般人也许不需要在他的Web页面中包括乐谱,但是作曲家却要这样做。XML让电力工程师描述他们的电路图,让作曲家写乐谱,而不会互相干扰。对于浏览器开发商来说,都不需要对特定的领域提供特殊的支持,也不需要提供复杂的插件。这一点现在已经实现了。
过去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数据。没有什么秘密使得人们发生失误。
由于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中开发了一种通用的字处理格式,作者们就会使这个程序成为他们的首选程序。
XML对于大型和复杂的文档是理想的,因为数据是结构化的。这不仅使用户可以指定一个定义了文档中的元素的词汇表,而且还可以指定元素之间的关系。例如,如果要将销售客户的地址一起放在Web页面上,这就需要有每个客户的电话号码和电子邮件地址。如果向数据库中输入数据,可确保没有漏下的字段。还需要每部书都有一个作者。当没有数据输入时还可提供一个缺省值。XML也提供客户端的包括机制,可以根据多种来源集成数据并将其作为一个文档来显示。数据还可以马上进行重新排列。数据的各个部分可以根据用户的操作显示或隐藏。当处理大型的信息仓库,比如关系型数据库时是极为有用的。
从基本上来说,XML是一种文档格式。它是一系列的关于XML文档看起来是什么样子的规则。与XML标准的符合程度有两种级别。第一级是结构完整性,第二级是正确性。本书的第一部分向读者介绍如何编写结构完整的文档。而第二部分向读者介绍如何编写具有正确性的文档。
HTML是设计用于Internet上和Web页面内部的文档格式。正如本书所叙述的,XML当然也可以用在这些方面。但是XML具有更为广泛的适用性。正如前面所讨论的,可用于字处理器的保存文件的格式,可用于不同程序间的数据交换格式,可用作与Intranet模板一致化的工具,还可用作以人类可读的形式保存数据的手段。
虽然如此,如所有的数据格式一样,XML在有用之前也需要程序和内容。因而对于数据看起来应该是什么样子的,光了解XML本身还是不够的,这不光是一个规范所能解决的问题。用户还需要了解XML文档是如何编辑的,处理程序是如何读取XML文档并将其读取的信息传送给应用程序的,以及这些应用程序是如何处理数据的。
XML文档大多数情况下都是用编辑器创建的。编辑器可以是基本的文本编辑器如Notepad(记事本)或是vi,这些编辑器并不真正理解XML。另一方面,也可以用所见即所得的编辑器,如Adobe FrameMaker,这种编辑器可将用户完全隔离于XML底层格式之外。另外也可以是一个结构化的编辑器,如JUMBO,它可将XML文档显示为树状结构。对于最重要的部分,有趣的编辑器并不是太有用,因而本书将注意力集中于用普通的文本编辑器来编写XML文档。
其他程序也可以创建XML文档。例如,本书在讲述设计新的DTD的稍后章节中将可看到某些XML数据可直接从FileMaker的数据库中得出。在这种情况下,数据是先输入到FileMaker数据库中的,然后FileMaker的计算字段将数据转换为XML。一般来说,XML与数据库可协同工作得很好。
准确地说,我们可在第23章“设计新的XML应用”中看到这种情况。
无论在何种情况下,都是编辑器或其他程序创建了XML文档。通常,这一文档是某种计算机硬盘上的实际文件。但也不是必须如此。例如,文档可能是数据库中的记录或是字段,或者可能是从网络上接收来的字节流。
XML的语法分析程序(即所谓的XML处理程序)读取文档并检查其中包括的XML是否是结构完整的。它还要确定文档是否合法,虽然这种测试不是必需的。这种测试的详细情况将在本书的第二部分中讲述。如果文档通过了测试,则处理程序就将文档转换为元素的树状结构。
最后语法分析程序将树状结构或是树的节点传送给用户端应用程序。这个应用程序可能是浏览器,如Mozilla,或是其他能够理解如何处理数据的程序。如果这个应用程序是浏览器的话,数据就显示给用户。但是其他程序也可以接受数据。例如,可将数据翻译成数据库的输入、一系列要演奏的乐谱或是要运行的Java 程序。XML是非常灵活的,可以用于许多不同的目的。
总结一下,首先由一个编辑器创建了XML文档。语法分析程序将树状结构传送给浏览器,由浏览器显示出来。图1-1显示了这个处理过程。
图1-1 XML文档的处理流程
请注意,所有这些部分都是独立的,互相分离的。将这些部分联系在一起的是XML文档。改变编辑程序与终端应用程序无关。事实上,很可能在编写文档时就根本不知道最终的应用程序是什么。可能是最终用户来阅读文档,也可能是数据库从中提取数据,甚至还可能是未发明出来的程序,也可能是所有这些情况。文档与读取它的程序是无关的。
HTML也在某种程度上与读写它的程序无关,但是它只适用于浏览器。其他应用,如数据库输入已经不在它的有效范围之内了。例如,HTML没有提供某种方法来包括所需的内容,如每本书都必须有ISBN号码一样。在XML中可以包括这个。甚至可以强制安排元素出现的顺序(如第二级标题必须出现在第一级之后)。
XML并不是在真空中操作的。如果将XML用于不只是一种数据格式的话,就需要与多种相关的技术相互作用。这些技术包括为了向后兼容老式的浏览器的HTML、CSS(Cascading Style Sheet,级联样式单)和XSL(eXtensible Style Languages,可扩展的样式语言)、URL和URI、XLL(eXtensible Linking Language,可扩展的链接语言)和Unicode字符集。
Mozilla 5.0和Internet Explorer 5.0是首先对XML提供支持(虽然并不完全)的浏览器。但是,要使大多数用户升级到这两种浏览器的新版本上来,可能还要花两年的时间。(我的妻子Beth在1999年还在使用Netscape 1.1。)因而在今后一段时间内,还需要将XML内容转化为经典的HTML。
因而,在转向XML之前,对使用HTML还不应感到别扭。用户不必完全成为一个时髦的图形设计者,但是应该了解如何将一个页面与另一个页面链接起来,了解如何在文档中包括图像,如何使文本变成粗体等等。由于HTML是XML的最普通的输出格式,所以对HTML了解得越多,也就越容易了解如何创建所需的效果。
另一方面,如果已经熟悉了利用表格或是单像素的GIF来安排页面上的对象,或是如果开始借助于画出草图而不是借助于内容来创建Web站点的话,那么也就必须要忘记某些坏的习惯。正如前面所讨论的一样,XML将文档的内容与文档的外观相分离。首先开发内容,然后再用样式单将格式附加其上。将内容与样式分开是非常有效的技术,这既改善了文档内容也改善了文档外观。除此之外,还允许作者和设计者更加互相独立地工作。但是,对于设计Web站点来说,确实需要有不同的思路,如果涉及多人的话,或许要利用不同的项目管理技术。
由于XML允许在文档中包括任意的标记,所以对于浏览器来说,没有办法事先知道如何显示每个元素。当将文档送给用户时,还要向用户发送样式单,通过样式单告诉浏览器如何格式化每个元素。可以使用的一种样式单是级联样式单( Cascading Style Sheet ,简写为CSS)。
CSS开始是为 HTML设计的,它定义字号、字族、字重、段落缩进、段落对齐和其他样式等格式化属性,这些属性都可以施加到个别的元素上。例如,CSS允许HTML文档来指定所有的H1元素应该被格式化为32磅、中间对齐的Helvetica字体的粗体。单独的样式可以施加到大多数HTML标记上,它能够覆盖浏览器的缺省设置。多个样式单可施加到一个文档上,而多个样式也可用于单个元素上。样式根据特定的一套规则级联起来。
CSS规则和属性将在第12章“级联样式单,第一级”和第13章“级联样式单,第二级”中详细介绍。
向XML施加CSS规则是很容易的。只要改变施加规则于其上的标记名称即可。Mozilla 5.0直接支持CSS样式单与XML的结合,虽然到目前为止,此浏览器时常发生崩溃。
可扩展的样式语言(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格式化对象”中更为详细地论述。
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。
只要将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章中加以讨论。
Web是国际性的,到目前为止其上主要文本部分仍为英文。XML是改变这种状况的开始。XML对双字节的Unicode字符集及其紧凑的表示提供了完全的支持。这一字符集几乎可以支持地球上的每一种常用的字符。遗憾的是,光有XML还是不够的。为了阅读一种文字,需要三个条件:
1. 该种文字的字符集
2. 该字符集的字体
3. 操作系统和应用软件能够理解这种字符集
如果想要以这种文字写作,并阅读这种文字,还需要该种文字的输入法。当然,XML定义了字符引用,可使用户使用纯ASCII字符将未列在本地字符集中的字符加以编码。这对于偶尔引用一下希腊或是中文字符也足够了,当然不能指望用这种办法以其他语言来写一部小说。
在第7章“外国语言和非罗马文本”中,读者将会看到国际文本在计算机中是如何来代表的,XML如何来理解文本,以及如何来利用不得不以非英语来读写的软件。
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来做一些实际工作的。
在本章中,读者了解了某些XML可以为我们做的事情。更明确地说,了解了以下几个方面:
· 一种能够为特定文档和领域创建标记语言的元语言。
· XML的起因是,用户受到SGML复杂性的挫伤和HTML的不充分。
在以下几章中,读者可以看到几个XML应用,学到某些将XML用到现实中的方式。例子包括音乐乐谱、数学、化学、人力资源、Web广播以及其他一些应用。
在本章中,我们将要查看XML的几个应用实例、用来进一步改进XML的标记语言和在后台使用的XML。看一看XML的某些应用,即使只是发展的初级阶段,也是令人鼓舞的。本章将向读者讲述XML的广泛应用性的某些看法。在我写作本书时,更多的XML应用正在创建并与其他格式的应用接轨。
第五部分更为详细地讲述了本章中讨论过的一些XML应用程序。
本章的主要内容包括:
· 什么是XML应用程序
XML是一种元标记语言,可用来设计与特定专业领域有关的标记语言。每种基于XML的标记语言都叫做XML应用程序。这种应用不是像Mozilla Web浏览器、Gnumeric电子表格或 XML Pro那样的编辑器一样地使用XML,而是在特定的领域中应用XML,如化学上用的化学标记语言(Chemical Markup Language,简写为CML)或是家谱上用的GedML。每种XML应用程序有它自已的句法和词汇表。这种句法和词汇表遵守XML的基本规则。
这有点像人类语言,每种语言都有它们自己的词汇表和语法,但同时遵循人体解剖学和大脑结构所要求的基本规则。
XML是以文本数据为基础的非常灵活的格式。在本章中讨论的广泛的应用都选择了XML作为基础的原因是(排除大肆宣传的因素),XML提供了切合实际的并清楚地描述了的易于读写的格式。应用程序将这种格式用于它的数据,就能够将大量的处理细节让几个标准工具和库函数去解决。更进一步说,对于这样的程序也容易将附加的句法和语义加到XML提供的基本结构之上。
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/ 站点处得到。
传说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作为中介。
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目录中找到。
Jon Bosak曾经将Shakespeare(莎世比亚)的全部话剧翻译成了XML。这些剧本的全文都包括其中了,用XML标记来区分剧名、每幕标题、舞台指导、对白、台词、旁白等。
莎世比亚的全套话剧可以本书所附CD-ROM上的examples/shakespeare目录中找到。
读者可能要问,对于一本书或是一个普通的文本文件来说,这样做有什么好处呢?对于人类读者来说,这没有什么不同,但对分析文字的计算机来说,这样做就使得容易区分组成话剧的不同元素。例如,要让计算机在全文中找出Romeo(罗密欧)的台词就变得简单了。
进一步说,借助于改变格式化文档的样式单,某个演员就很容易地打印出该剧的一个副本,其中他(她)的所有台词都格式化为粗体,而他(她)前面和后面的台词都用斜体来表示。另外还可以想像出来的事是,将剧本分成不同人的道白时,利用XML格式化的版本也比原来的文本要容易得多。
Bosak曾经将新旧约全书、古兰经和摩门教教义的英文译本用XML加以标记。这些书中的标记有些不同。例如,它并不对讲话人加以区分。因而(比如说)也就不能利用这种特殊的XML文档来创建带红色字母的圣经,虽然使用不同的一套标记可以达到这一目的。(带红色字母的圣经将耶稣说的话用红色印刷。)而且由于这些文件是用英语写成的,而不是原来的语言,这对于学术上的文本分析来说,就不是那么有用了。如果时间和资源允许的话,只要愿意,用XML来书写原文也是可以办得到的。这时只要设计一套与Bosak使用的不同,但却是描述同样的数据的词汇表和句法即可。
经XML标记了的圣经、古兰经和摩门教教义都可在本书所附的CD-ROM上的examples/religion目录中找到。
XML对于文本数据来说是最通用的格式。它所用于的某些事物还进一步地完善了XML本身。这包括XSL样式单语言、XLL链接语言和用于XML的文档内容描述(Document Content Description,简写为DCD)。
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。
可扩展的链接语言(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方面的内容。
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提供了“砖和泥”,利用这些“砖和泥”既可以建起“小屋”也可以建起高耸的“城堡”。
并不是所有的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还可使错误容易发现和修改。
本章只是刚刚接触到已经和将要使用XML的应用。一些应用,如CML、MathML和MusicML很明显是用于Web浏览器的HTML扩展。但是许多别的应用,如OFX、 XFDL和HRML完全走的是另一条路。所有这些应用都有建立在XML之上的自己的语义和句法。在某些情况下,XML的“根”是很明显的,但在另外一些情况下,即使在其上工作达一月之久,也不一定会发现它与XML有什么关系。在本章中,我们讨论了下面的可使用XML的应用:
· 使用CML的分子科学
在下一章中,读者将要学习编写自己的XML文档并在Web浏览器上加以显示。
本章教读者用自己定义的可为文档所理解的标记来创建简单的XML文档。读者将学到如何编写样式单,以便用于在文档中描述标记内容如何显示。最后,还要学到如何将文档装到Web浏览器中以便查看。
由于本章利用示例来加以讲解,而不是从原理出发,因而不会涉及许多细节。有经验的读者将会注意到几处例外和特殊情况没有在本章加以讨论。对此不必担心。在下几章中将会讨论到。对于大部分内容,不必太关心技术内容。正如HTML一样,也可通过复制其他人创建的简单的示例并按自己的需要加以修改来学习。
为了达到上述目的,我鼓励大家按我在本章中给出的示例键入程序逐步进行,并将这些代码装入讨论过的不同的程序中。这将使读者对XML产生基本感受,这将使在未来几章中提到的技术细节在特定示例的环境中容易掌握。
本章的主要内容包括:
· 创建简单的XML文档
本节遵照老程序员介绍新语言的传统,先用一个能够在屏幕上打印出“Hello World”的程序加以介绍。XML是标记语言,而不是编程语言,但是基本原理还是适用的。最简单的方法是以一个完全的可运行的有扩展能力的示例开始,而不要尝试以更基本的无任何功能的程序开始。如果用户在使用基本的工具时确实遇到了问题,在简短的文档环境中也比在复杂的文档环境下更容易调试和改正。
在本节中,读者将学到如何创建一个简单的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文档”中加以讨论。
当键入了上面的代码之后,请将该文档保存在名为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章“外国语言和非罗马文本”中加以更为详细的讨论。
既然已经创建了第一个XML文档,当然想看一看了。这个文件可以在支持XML的浏览器,如Internet Explorer 5.0中直接打开。图3-2显示的就是结果。
我们看到的结果将依不同的浏览器而有所不同。在本例情况下,文件是格式化得很好的,以不同的颜色来表示不同的句法。不过所看到的并没有吸引人的地方。问题在于浏览器并不了解如何来处理FOO元素。我们必须指示浏览器如何来处理每个元素,这就要用到样式单了。我们将要简单地介绍一下,但首先还是仔细地考察一下这个文档。
图3-2 hello.xml在Internet Explorer 5.0中的显示结果
让我们检查一下列在清单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-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中,样式意义是通过样式单来施加的。
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)常常更为方便。
在编写好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文件
在本章中,读者学到了如何创建一个简单的XML文档。总的来说,包括以下内容:
· 如何编写和保存简单的XML文档
在下一章中,我们将要研究XML文档的更为大型的例子,用来演示在选择XML标记时的更多的实际考虑。
在本章中,我们将要研究一个较长的示例,用来说明一个较长的有关棒球统计和其他类似数据的列表是如何以XML格式保存的。像这样的文档有好多潜在的应用。最明显的,它可以显示在Web 页面上。还可以用作其他分析数据或是整理数据程序的输入。通过这个示例,读者将学到如何用XML来标记数据、为什么要选用XML标记、如何为文档编制CSS样式单等等内容。
本章的主要内容包括:
· 检查数据
当我写作这本书时(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队,对于任何队的击球手,同样的统计数字也可以得到。
几年前,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-2中列出的全场的不同种类的数字。这个表的每列也定义了一个元素。这些元素中的一部分,如姓名和位置对于投球手和击球手都是有的。其他元素如解救(saves)和成功防守(shutouts)只适用于投球手。还有几个,如得分(runs)和全垒跑(home runs)与击球手统计中的名称相同,但是具有不同意义。例如,击球手的得分数是击球手获得的分数。而对于投球手来说,是指对方在这个投球手下得到的分数。
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并不遵循关系模型。
让我们用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)。
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标记中的简捷性是不太重要的。”棒球示例完全反映出了这个目的。
主要棒球联赛分成两个联赛: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>
每个球队是由球员组成的。每个球员都有姓和名。将姓和名分开是重要的,这样一来既可以根据名来分类也可以根据姓来分类。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)。
以下几个步骤提供了每个球员的统计数据。统计数据看起来对于投球手和击球手并没有一点不同,特别是对于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>
到目前为止,我向读者展示的只是一段一段(每段一个元素)的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-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-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
一般来说,我们要用重复的过程来为每个元素增加样式规则,一次一个元素地进行,然后检查是否达到了要求,再处理下一个元素。在本例中,这种办法对于不熟悉样式单属性的人来说也有好处。
样式单的名称可随便取。如果只是为一个文档编制样式单,那么习惯上样式单的文件与文档的文件名一样,但是三字母的扩展名是.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的视图。
用户不必为每个元素指定样式规则。许多元素允许将其父元素的样式串接下来。因而最重要的样式是根元素的样式,在本例中就是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磅白地黑字显示的棒球统计数据
元素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 作为段标题而格式化的队名和城市名
本文档需要的最具技巧的格式化是对每个球员及其统计数据的格式化。每个队有几十个球员。每个球员都有统计数据。应该将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-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”改为“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,以便获得更大的灵活性,这通常是不错的主意。
在本章中,读者看到了几个展示如何从头创建XML文档的示例。我们特别学到了如下内容:
· 如何检查包括在XML文件中的数据,以便标识元素。
本章中充满了枯燥的代码。文档是在没有太多的细节的情况下编写出来的。在下一章中,我们将要探讨在XML文档中嵌入信息的附加意义,包括特性、注释和处理指令,并看一看在XML中用另一种对棒球统计数据编码的方法。
使用XML对一组给定的数据进行编码,有很多种方法。但是没有哪一种方法是唯一正确的,只是一些方法相较而言更可取,在特定的应用中更合适。本章采用前面章节中所用的棒球示例,仔细探讨使用XML创建棒球统计的不同方法。文中会特别强调使用属性存储信息和使用空标记定义元素位置。另外,鉴于CSS(级联样式单)对缺乏内容的XML元素执行起来并不顺利,我们将检验另一种功能更强大的样式单语言——XSL。
本章内容包括:
· 属性
在上一章中,所有的数据可分为标记名或者元素的内容两类。这种方法直接易懂,但不是唯一的。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的名字会以一个超链接文本的脚注或者工具提示包含在页面文本中,同时能保证希望深究它的读者得到该统计。一组相同的数据可以用多种方法进行编码,具体选择哪一种编码方法取决于用户特定应用的需要。
何时使用子元素或属性没有严格的规则可循,通常要看哪一种更适合自己应用的需要。随着经验的增长就会有一种感觉,知道在何时使用属性比子元素更简单,反之亦然。一个好的经验规则是数据本身应当存储在元素中,而有关数据的信息(元数据)应当存储在属性中。不知道怎么做时,就把信息放在元素中。
为区分数据与元数据,首先要问自己是否会有一些读者希望看到一条特别的信息。如果答案是肯定的,该信息应当包含在一个子元素中。相反,则应包含在一个属性中。如果从该文档中删除所有标记与属性,文档的基本信息应当还存在。属性是放置ID号、URL、参考资料及其他与读者不直接相关的信息的好地方。但是,把元数据作为属性存储的基本规则还有许多例外。这些例外包括:
· 属性不能很好地保持原文的结构。
需要特别记住的是元素可以有子结构而属性没有。这使元素更加灵活,更方便我们将元数据编译成子元素。例如,设想我们在写一篇论文,而且希望其中包含某件事情的出处,结果可能是这样:
<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代码来形成该图片。
元素可用于元数据,同样也可用于元元数据,或者信息的深层相关信息。例如,一首诗的作者是这首诗的元数据,书写作者姓名所用的语言就是这首诗的元元数据。特别是对于明显的非罗马语言,这并非是无关紧要的。例如,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>
判断元数据的决定权掌握在读者手中,不同的读者和他们的阅读目的决定哪些是元数据,哪些是数据。例如,阅读一份学报上的文章,作者的名字与文章的内容相比就显得无足轻重。但是,如果作为晋升委员会的委员浏览学报来确定发表与未发表文章的人员,作者的名字与所发表文章的数量比其内容更重要。
事实上,人们也许会改变对数据和元数据的看法。今天看似无关紧要的东西,下周可能会变得很有用。你可以使用样式单隐藏今天看似不重要的元素,在以后可改变样式单将其显示出来。但是,显示一个原先存储在属性中的信息很困难。通常在此情况下需要重写整个文档,而不是简单地修改样式单。
在只需要传达一两个字的非结构性信息时,使用属性是很方便的。在此情况下,显然不需要一个子元素。但是这并不排除日后需要它。
例如,目前可能只需要存储一篇文章的作者名而不必区分名和姓。但将来可能会需要存储姓名、e-mail地址、机构、邮政通信处、URL以及更多的东西。如果把文章的作者保存为一个元素,在其中添加子元素包含这些附加的信息会很容易。
尽管上述任何改动都需要重新修改文档、样式单和相关的程序,但是把一个简单的元素修改为元素树比把一个属性修改为元素树简单得多。而且使用了属性就只好继续使用下去。扩展属性句法使之超越最初的设计范围也很困难。
在前面已经详尽阐述了应当使用子元素代替属性的原因,然而,必须指出的是,有时候使用属性是有意义的。首先,同前面提到的一样,属性非常适用于那些读者未必想看见的没有子结构的简单数据。例如,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属性为好。这样做有双重好处,至少使传统的浏览器能够部分地分析和显示你的文档,而且对于文档的作者来说更熟悉这种方式。
上一章中没有属性的方式是一种极端的情况,由此可能会想到另一个极端——将所有的信息全部存储在属性中,而不是存储在内容中。通常不推荐使用这种方式。把信息全部存储在元素内容中同样也是极端的,只是实际处理起来更容易。这一节考虑仅使用属性来说明的可能性。
只要元素中没有内容,就可以使用空标记来简化。可以只包含一个空标记而不是一个起始标记和一个终止标记。空标记与起始标记的区别在于结束标记使用“/>”而不是简单的“>”。例如,不是<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-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格式化对象中讨论。
每个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-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:[email protected]">
</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很明显丢失了数据。尽管清单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:[email protected]">
</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章中详细阐述。
下面通过添加一些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:[email protected]">
</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:[email protected]">
</A>
</BODY>
</HTML>
</xsl:template>
</xsl:stylesheet>
图5-7 采用清单5-5所示的样式单后显示的小组名和队名
对于TEAM元素,它的CITY和NAME属性值是H4标题的内容。同时,请注意嵌套的选取赛季、小组和各队的xsl:for-each元素,它反映了文档自身的分支结构。这并不是一种巧合。其他方案可能不要求与文档的体系相匹配,但这一方案是最简单的,尤其适用于像清单5-1所示的棒球统计这样的高度结构化的数据。
下一个步骤是为各队的每一个队员添加统计数字,最基本的方法是用一个表格表示。清单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:[email protected]">
</A>
</BODY>
</HTML>
</xsl:template>
</xsl:stylesheet>
可以注意到图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:[email protected]">
</A>
</BODY>
</HTML>
</xsl:template>
</xsl:stylesheet>
本章集中讨论了使用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属性的更多可能。
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才是发展方向。
在本章中,读者看到了从头创建的XML文档的示例。特别是学到如下内容:
· 信息可以保存在元素的属性中。
下一章将详细介绍结构完整的XML文档必须严格遵循的规则。我们还将研究另外一些在XML文档中嵌入信息如注释和处理命令的方法。
HTML 4.0有大约100个不同的标记,大部分标记都有多个可能的属性用于几百种不同的变化。因为XML的功能比HTML强大,你也许认为需要懂得更多标记,但不是这样。XML凭借其简洁性和可扩展性具有强大的功能,并不是大量的标记。
事实上,XML几乎没有预先定义任何标记,相反允许用户在需要时定义自己的标记。但是由自定义标记建立的这些标记和文档并不是随意的,必须遵循一组特定的规则,本章将详细阐述这些规则。遵守这些规则的文档被认为是结构完整的。结构完整是XML处理器和浏览器阅读文件必要的最起码的标准。本章将阐述用于结构完整的XML和HTML文档的规则。请特别注意XML与HTML的区别。
本章的主要内容包括:
· XML文档的组成
XML文档包含由XML标记和字符数据组成的文本。它是一个有固定长度的有序字节的集合,并遵守特定的约束。它可能是或者不是一个文件。例如,XML文档可能:
· 存储在数据库中
但是如果把一个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子集”中讨论。
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。其他的就是字符数据。但是文档被处理后,一些置标会变成字符数据。例如,置标>;变成了大于号(>)。文档经处理后留下的字符数据和所有的代表特定字符的数据称为可分析的字符数据。
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源代码时就会出现问题。通常只要意识到这个问题就不难解决。
实体引用是指分析文档时会被字符数据取代的置标。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>
大多数情况下,出现在一对尖括号(<>)中的是置标,不在尖括号中的是字符数据。但是有一种情况例外,在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段不常需要,一旦需要时,它是非常有用的。
置标能够区分XML文件与无格式文本文件。置标的最大部分是标记。前一章讲隽吮昙堑氖褂梅椒ǎ窘诮ㄒ灞昙遣⑻峁┦褂梅椒ā?/p>
简而言之,标记在XML文档中以<开始,以>结束,而且不包含在注释或者CDATA段中。因此,XML标记有与HTML标记相同的形式。开始或打开标记以<开始,后面跟有标记名。终止或结束标记以</开始,后面也跟标记名。遇到的第一个>该标记结束。
每个标记都有一个名称。标记名必须以字母或下划线(_)开始,名称中后续字符可以包含字母、数字、下划线、连字符和句号。其中不能含有空格(经常用下划线替代空格)。下面是一些合法的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的标记中,从此观点出发,我会尽可能遵循使用大写的约定。这主要是因为大写在本书中可以更突出,但是有时使用的标记集是别人建立的,那么采用别人的习惯约定是必要的。
许多不含数据的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可以有多种方法保持向前和向后的兼容性。
在前面的章节中讨论过,起始标记和空标记可以随意地包含属性。属性是用等号(=)分隔开的名称-数值对。例如:
<GREETING LANGUAGE="English">
Hello XML!
<MOVIE SRC="WavingHand.mov"/>
</GREETING>
在此<GREETING>标记有一个LANGUAGE属性,其属性值是English。<MOVIE>标记有一个SRC属性,其属性值为WavingHand.mov。
属性名是字符串,遵循与标记名同样的规则。这就是,属性名必须以字母或下划线(_)开始,名称中后续字符可以包含字母、数字、下划线、连字符和句号。其中不能含有空格(经常用下划线替代空格)。
同一个标记不能有两个同名的属性。例如,下面的例子是不合法的:
<RECTANGLE SIDE="8cm" SIDE="10cm"/>
属性名是区分大小写的。SIDE属性与side或者Side属性不是同一个属性,因此以下例子是合法的:
<BOX SIDE="8cm" side="10cm" Side="31cm"/>
但是上面的这种写法很迷惑人,最好不要这样书写。
属性值也是字符串。如下面所示的LENGTH属性,即使字符串表示的是一个数,但还是两个字符7和2,不是十进制数的72。
<RULE LENGTH="72"/>
如果编写处理XML的代码,在对字符串执行算术运算之前必须把它们转换为一个数。
与属性名不同,对属性值包含的内容没有任何限制。属性值可以包含空格,可以以一个数字或任何标点符号(有时单括号和双括号除外)开头。
XML属性值由引号界定。与HTML属性不同,XML属性值必须加引号。大多数情况下是使用双引号,但是如果属性值本身含有一个引号,就需要使用单引号。例如:
<RECTANGLE LENGTH= 7" WIDTH= 8.5" />
如果属性值中含有两种引号,那么其中不用于界定字符串的一个必须用合适的实体引用代替。我通常替换两个,这种方法很管用。例如:
<RECTANGLE LENGTH= 8'7" WIDTH="10'6""/>
尽管可以根据需要编写标记,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定义插入特殊符号和大块样板文本的新实体引用。
即使在大部分Web浏览器还不能直接支持XML的情况下,也可以通过编写结构完整的HTML来练习XML技巧。这就是遵守XML的结构完整性约束,但只是使用标准的HTML标记的HTML。结构完整的HTML比多数人和FrontPage等工具编写的不标准的HTML容易读取,同时容易被Web机器人中用动检索引擎理解。它更为强健,对它作一些改动不会破坏它,在转移的过程中不会因为变换浏览器和操作平台对它产生影响。而且可以使用XML工具服务于HTML文档,这对于那些服务器不支持XML的读者来说仍然保持了向后的兼容性。
真正的Web页面非常不标准。没有结束标记,元素重叠,在页面中包含未经处理的小于号,忽略实体引用后面的分号。存在这些问题的Web页面严格来讲是无效的,但部分浏览器能接受它们。不过如果校正了这些问题,页面将会更整洁,显示更快,更容易维护。
Web页面包含的一些常见的问题:
1. 起始标记没有对应的结束标记(没有结束元素)
2. 结束标记没有相应的起始标记
3. 元素重叠
4. 属性值未加引号
5. 没有避免使用<、>、&和"符号
6. 没有根元素
7. 结束标记与起始标记不匹配
清单大致按照其重要性排列,但确切的细节因标记不同而变化。例如,没有被结束的<STRONG>标记会把跟随其后的所有元素变为粗体。但是没有被结束的<LI>或者<P>标记不会引发任何问题。
有几条规则仅适用于XML文档,如果试图把它们汇集在已存在的HTML页面中,确实会带来问题。这些规则有:
1.以一个XML声明开始
2.空标记必须以/>结束
3.使用的实体引用只有&、<、>、'和"。
校正这些问题并不难,只是有几个稍不注意就会出现问题。下面让我们仔细加以研究。
任何含有内容的元素,无论是文本还是别的子元素,应该有一个起始标记和结束标记。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>
在编辑页面时,删除一个起始标记而忘了删除相应的结束标记,这种情况很常见。在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>
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">
HTML对小于号和与号的要求比XML宽松得多。即使这样,在纯HTML文本中它们确实也会引起麻烦,特别是在它们直接跟有其他字符时。例如,考虑下面来自Eudora软件中的From:标题中的email地址在经过复制和粘贴后显示的样子:
Elliotte Rusty Harold <[email protected]>
如果用HTML来显示的话,看到的可能是:
Elliotte Rusty Harold
[email protected]无意间被尖括号隐藏了。如果想在HTML中包含原始的小于号和与号,应当使用<和&代替。其正确的HTML形式是:
Elliotte Rusty Harold <[email protected]>
没有转义的大于号带来的问题不易察觉,如果在它之前有一个未结束的标记,它会被解释为一个置标。文档中会出现这种没有完成的标记,而且附近的大于号会掩盖它们的存在。例如下面的一段Java代码:
for (int i=0;i<10;i++){
for (int j=20;j>10;j--){
这很可能显示为:``
for (int i=0;i10;j--){
如果这只是一个100行程序中的两行,在随便校正时极有可能错过这种疏忽。另一方面,如果转义了大于号,而未转义小于号将会隐藏程序的其余部分,而且这种问题容易被发现。
用于HTML文件的根元素被假定为html。大部分浏览器允许不包含它的这种疏忽。尽管如此,最好把<html>作为文档的第一个标记,</html>作为文档的最后一个标记。如果其他文本或置标出现在<html>之前或</html>之后,应把它们移到<html>和</html>之间。
这个问题常见的形式是忘记在文档的结尾包括</html>。我通常先键入<html>和</html>,然后在它们之间键入其他内容,而不是在编写完整个文档再加</html>标记,指望着几天后还会记得应该加上</html>标记。
HTML对大小写不敏感,XML则不然。应推荐给标记挑选一个唯一的大小写形式,要么都大写,要么都小写,并且贯穿全文。这样做比记住每一个标记的细节要简单。我通常选小写,因为它比较容易输入。而且W3C将HTML再现为XML应用程序的结果也使用这个格式。
在第20章中读取文档类型定义一节将详细描述HTML再现为XML。但是必须停止更深的探讨,因为这项工作使用在后面几章中学不到的技巧。
把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,我所能做的只是提供可能奏效的解决办法。
许多Web页面除了&、<、>、'、和"之外确实不需要更多的实体引用。但是HTML 4.0中规定了许多:
· ™为商标号(™)
还有几百个别的实体引用,但是使用任何一个将破坏文档的结构完整性。解决这个问题的方法是使用一个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中字符的十进制或者十六进制代码来代替一个字符。例如:
· ™ 为商标号(™)
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);
事实上,我不赞成使用这种方法。结构完整性在HTML中并不太重要,它只是强制读者增加了下载和显示出来的时间。
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声名删除。
按照本章描述的规则编写结构完整的XML文档不是特别困难,但是XML浏览器对于不标准的句法不像HTML浏览器那样宽容,因此要细心编写。
如果违反了任何结构完整性约束,XML分析器和浏览器将报告一个句法错误。因此编写XML的过程与用某种编程语言编写代码的过程有些相似,首先编写,然后编译,如果编译失败再根据报告的错误修改。
通常在能够看到完成的文档之前要经过几次从编辑到编译的过程。而且编写XML文档比编写C和Java源代码要容易得多,只要很少的练习就会达到只出现相当少的错误,编写XML的速度几乎与你输入的速度一样快。
有几种工具能够帮助我们整理页面。最引人注目的是来自XML.COM的RUWF(Are You Well Formed?)和由W3C的Dave Raggett编写的HTML Tidy。
任何能够检验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
一旦确定了问题就会想到要修改它们,许多常见的问题 例如,给属性值加引号 是能够自动被修改的。做这种修改最便利的工具是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文档。
在本章学习了如何编写结构完整的XML。主要包括以下内容:
· XML文档是满足一定结构完整性标准的一连串字符
在下一章将要讨论如何使用非英语语言编写XML,尤其是用与英语差别很大的语言。如阿拉伯语、汉语和希腊语。
Web是国际性的,然而在其中使用的大多数是英文,XML正在开始改变这种状况。XML全面支持双字节Unicode字符集及其更简洁的描述形式。这对Web作者来说是个好消息,因为Unicode支持世界上每种现代文字通常使用的几乎所有的字符。
本章将学习在计算机应用程序中如何描述国际性文本,XML如何理解文本以及如何利用非英文软件。
本章的主要内容包括:
· 了解非罗马文字在网页上的效果
虽然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编写的。只要双字节字符集有容纳不同字符的余地,就不需要使用多种字符集。因此,浏览器也不必检测使用的是哪一种字符集。
大部分现代人类语言都有各自的书写形式。用于书写一种语言的字符集称为一种文字。文字可以是语音字母表,也可以不是。例如,汉语、日语和韩语由能够表示整个词汇的表意文字字符组成。不同语言经常共用一些文字,或者有一些细小的改动。例如,汉语、日语和韩语实质上共用相同的80,000多个汉字,尽管大多数字符在不同的语言中表示的意义不同。
单词Script也经常用来指用非类型化和非解释语言写的程序,如JavaScript、Perl和TCL。本章中的Script指书写一种语言使用的字符,不是指任何一种程序。
一些语言能用不同的文字表达。塞尔维亚语和克罗地亚语实际是相同的,通常被称作Serbo-Croatian。但是,塞尔维亚语使用经过修改的古斯拉夫文字,克罗地亚语则使用经过修改的罗马文字。只要计算机不想得到所处理的文字的意义,处理一种文字和处理用这种文字所编写的任何一种语言都是相同的。
遗憾的是,单独的XML无法读取一种文字,计算机要处理一种文字需要四个要素:
1. 与文字对应的一种字符集
2. 用于该字符集的一种字体
3. 该字符集的一种输入方法
4. 理解该字符集的一个操作系统或应用程序
这四个要素只要缺少其中之一,就不能在这种文字环境下工作,尽管XML能够提供一个足可以应急的工作环境。如果在应用过程中只丢失了输入法,还能够读取用该文字写的文本,只是不能用这种文字书写文本。
计算机只懂得数字。在它处理文本之前,必须用一种特定的字符集将文本编码成数字。例如,在大家熟知的ASCII字符集中,‘A’的编码是65,‘B’的编码是66,‘C’的编码是67,以此类推。
这些是语意学编码,不提供样式或者字体信息。C、C或C的编码都是67。有关如何画出字符的信息存储在别处。
字符集所采用的各种字形的总和形成一种字体,通常包括一定的尺寸、外观和风格。例如C、C或C是同一字符,只是书写的形状不一样,但其意义是相同的。
不同的系统存储字形的方式不一样。它们可能是位图或矢量图,甚至是印刷厂中的铅字。它们采用的形式与我们无关,关键是字体可以告诉计算机如何从字符集中调出每一个字符。
输入文本需要一种输入法,讲英语的人不需要考虑它,只要敲击键盘就可以输入。在大部分欧洲国家也一样,只需要在键盘上附加几个元音变音、变音符号。
基本上,古斯拉夫语、希伯来语、阿拉伯语和希腊语比较难输入。键盘上的按键数目有限,一般不够阿拉伯和罗马字符,或者是罗马和希腊字符使用。假定需要两种字符,键盘上有一个希腊字符锁定键能使键盘在罗马字符和希腊字符之间来回切换,那么希腊字符和罗马字符就能以不同的颜色印在键盘上。这个方案同样适用于希伯来语、阿拉伯语、古斯拉夫语和其他非罗马字符集。
当碰到表意文字如汉语和日语时,上述方法确实不管用。日语的键盘可容纳大约5000个不同的键,但还不到日语的10%!音节、语音和部首表示法能够减少按键的数目,但是键盘是否适合输入这些语种的文本呢?同西方相比,正确的语音和手写体识别在亚洲有更大的潜力。
语音和手写体识别还没有达到足可以让人信赖的程度,目前输入单个字符的方法大部分是使用键盘上的多个键序列。例如,输入汉语的“羊”字,必须按下ALT键并按带有(~)的键,然后输入yang,单击回车键。这种输入方法会显示出一列发音与yang差不多的汉字。例如:
佯楊易暘楊洋瘍羊詳錫陽
接下来就可以选择需要的那个字符“羊”。对于不同的程序、不同的操作系统和不同的语言如何把键入的键值转换成文字字符,如“羊”所使用的GUI(图形用户界面)和翻译系统的细节是不同的。
主要的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的支持尚在开始阶段,这在不久的将来会很有用。
不同地区的不同计算机使用的默认字符集各不相同,大多数现代计算机使用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这种多字节字符集的复杂工具对它进行编辑。
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以后的字符。
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个,因此必须使用多字节字符集。
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 |
?| |
第一个被广泛使用的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 |
未定义 |