XML教程
第17章 命名域
XML的用途不是单一的。虽然读者可能看到编写只使用一个标记符号集的文档是相当有用的(第4和第5章的棒球运动就是如此),但将不同的XML应用程序的标记混合,并进行匹配,甚至更为有用。例如,可将BIOGRAPHY元素包括在各个PLAYER元素中。由于传记基本上是由自由形态的、格式化的文本组成,所以,以结构整洁的HTML格式编写它就很方便,而无需从零做起重新定义所有的用于段落、分行符、列表项、粗体元素等等的标记。
但是,问题是,当混杂和匹配不同的XML应用程序的标记时,可能会发现同一个标记已用于两个不同对象。TITLE是指页标题还是书的标题?ADDRESS是指公司的邮件地址还是Web站点管理人员的电子邮件地址?命名域(namespace)可以解决这些诸如此类的问题,它是将URI与各标记集相关联,并在每个元素前加上一个前缀,以表示它属于哪个标记集。于是,就可以有BOOK:TITLE和HTML:TITLE两个元素或POSTAL:ADDRESS和HTML:ADDRESS元素,而不只一类TITLE或ADDRESS。本章将说明如何使用命名域。
本章的主要内容如下:
* 何为命名域?
* 命名域语法
* DTD中的命名域
18.1 何为命名域
XML能够使开发者为工程创建自己的标记语言。这些语言可以和工作于世界各地的类似工程的工作者们共享。使用这种方式工作的典型实例之一就是XSL。XSL本身就是用于XML样式文档的一个XML应用程序。XSL变换语言必须输出任意的、结构整洁的XML,或许还包括XSL本身。因此,需要有明确的手段来区分何为XSL转换指令的XML元素、何为输出的XML元素,即便它们有相同的名称也得要区分开!
命名域就是这种解决方案。命名域允许文档中的每个元素和特性放在不同的命名域中。组成XSL转换指令的XML元素放在http://www.w3.org/XSL/Transform/1.0命名域中。成为输出部分的XML元素仍放在某个其他方便的命名域(如http://www.w3. org/TR/REC-html40或http://www.w3.org/XSL/Format/1.0)中。只要命名域不同,那么命名域的精确性就不显得很重要。
如果熟悉C++和其他程序语言命名域,那么在深入阅读本章之前,需要将以前的概念放置一边。XML命名域与编程中使用的命名域类似,但不完全相同。特别是,XML命名域没有必要组成一个集合(没有重名的集合)。
清单15-2是从源符号集转换到XSL格式化对象的变换,最早出现在第15章的"XSL格式化对象"中。它显示了XSL样式单,可从输入XML转换成XSL格式化对象。格式化引擎使用命名域来区分作为XSL指令的元素和用于输出的文字数据。http://www.w3.org/XSL/Transform/1.0命名域中的任何元素都表示一个转换指令。http://www.w3.org/XSL/Format/1.0命名域中的任何元素包括输出部分。
<?xml
version="1.0"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/XSL/Transform/1.0"
xmlns:fo="http://www.w3.org/XSL/Format/1.0"
result-ns="fo" indent-result="yes">
<xsl:template
match="/">
<fo:root xmlns:fo="http://www.w3.org/XSL/Format/1.0">
<fo:layout-master-set>
<fo:simple-page-master
page-master-name="only">
<fo:region-body/>
</fo:simple-page-master>
</fo:layout-master-set>
<fo:page-sequence>
<fo:sequence-specification>
<fo:sequence-specifier-single
page-master-name="only"/>
</fo:sequence-specification>
<fo:flow>
<xsl:apply-templates
select="//ATOM"/>
</fo:flow>
</fo:page-sequence>
</fo:root>
</xsl:template>
<xsl:template
match="ATOM">
<fo:block
font-size="20pt" font-family="serif">
<xsl:value-of
select="NAME"/>
</fo:block>
</xsl:template>
</xsl:stylesheet>
更确切地说,下面这些元素存在于http://www.w3.org/XSL/Transform/1.0命名域中而且是XSL指令:
* stylesheet
* template
* apply-templates
* value-of
下面这些元素存在于http://www.w3.org/XSL/Format/l.0命名域中,是XSL格式化对象和输出部分:
* root
* layout-master-set
* simple-page-master
* region-body
* sequence-specification
* sequence-specifier-single
* page-sequence
* block
下面四个带有xsl前缀的元素使限定名具有以该前缀开始的:
* xsl:stylesheet
* xsl:template
* xsl:apply-templates
* xsl:value-of
但是,它们的完整名称使用URL,而不是前缀:
*
http://www.w3.org/XSL/Transform/l.0:stylesheet
*
http://www.w3.org/XSL/Transform/l.0:template
*
http://www.w3.org/XSL/Transform/l.0:apply-templates
*
http://www.w3.org/XSL/Transform/l.0:value-of
实际上,由于URL经常包含如~、%和/这样的一些在XML名称中不合法的字符,所以作为别名的这种较短的限定名只用于文档内部。但是,限定名的确使文档更易于键入和阅读。
“XML中的命名域"是正式的W3C标准。W3C认为它相当完善,只是可能存在不太重要的错误和说明。但是,在W3C所有的XML规范中,正是这个命名域才最有争议。许多人非常强烈地觉得,这个标准有基本原理上的缺陷。主要的缺陷是命名域实际上与DTD和合法性不兼容。而我对此并没有强烈的某种看法,但我的确有这样的疑问:当人们没有达成一致意见时,发行一个标准是否明智。命名域是许多XML相关规范(如XSL和XHTML)的至关重要的部分,所以需要人们理解。但很多开发者和读者都在他们的工作中忽略此规范。
18.2 命名域句法
命名域高于XML 1.0规范。XML
1.0处理程序对命名域一无所知,但仍能阅读使用命名域的文档,并且不会发现任何错误。使用命名域的文档不破坏现有的XML分析程序(至少不进行合法性检查的分析程序是如此);用户不必等待臭名昭著的、不准时的软件公司来发行昂贵的升级版才使用命名域。
18.2.1 命名域的定义
在使用命名域的有效元素上应用xmlns:prefix特性来定义命名域。prefix由真正的用于命名域的前缀来代替。特性值为命名域的URI。例如,xsl:stylesheet标记将前缀xsl与URI
http//www.w3.org/XSL/Transform/1.0联系在一起。
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/XSL/Transform/1.0">
然后,xsl前缀就可以加到xsl:stylesheet元素内的本地元素和特性名中,以便将它们标识为属于http//www.w3.org/XSL/Transform/1.0命名域。前缀通过冒号与本地名分开。清单14-2为用于周期表的基本的XSL样式单,它最初出现在第14章"XSL变换"中,此清单演示了在stylesheet、template和apply-tempates上使用xsl前缀的方法。
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/XSL/Transform/1.0">
<xsl:template
match="PERIODIC_TABLE">
<html>
<xsl:apply-templates/>
</html>
</xsl:template>
<xsl:template
match="ATOM">
<P>
<xsl:apply-templates/>
</P>
</xsl:template>
</xsl:stylesheet>
定义命名域的URI纯粹是形式上的,其唯一目的就是成组、并明确文档中的元素和特性。它无需指向任何对象,特别在无法确保URI位置上的文档描述了此文档中使用的句法;或者有用于该目的的任何文档存在于该URI位置。正如已说明过的,如果有一个用于特定XML应用程序的URI,那么此URI就可以用于定义命名域。
命名域前缀可以是任何合法的XML名称(不能包含冒号)。回顾第6章的"结构整洁的XML文档",合法的XML名必须以一个字母或下划线(_)开头。名称中的后面的字符可以包括字母、数字、下划线、连字号和句点。但不能包括空白。
有两个前缀明确地不允许使用:xml和xmlns。xml前缀是定义为用来引用http://www.w3.org/XML/1998/namespace的。xmlns前缀用于将元素绑定到命名域上,所以不可用于绑定目标的前缀。
在XML名称中,除了不允许有冒号字符外(不包括用于分隔前缀和本地名的冒号),命名域对标准的XML句法没有直接的影响。使用命名域的文档必须也是结构整洁的,以便对命名域一无所知的处理程序可阅读此文档。如果文档需要检查合法性,那么它无需明确地考虑命名域就肯定能够获得通过。对于XML处理程序,使用命名域的文档只不过是样子古怪的文档,在此文档中有些元素和特性名可能有一个冒号。
命名域的确存在着合法性的问题。如果编写的DTD没有命名域前缀,那么必须使用命名域前缀来重新编写DTD,才能用于对使用该前缀的文档进行合法性检查。例如,考虑下面的元素声明:
<!ELEMENT DIVISION (DIVISION_NAME, TEAM+)>
如果元素都是以bb作命名域前缀,就得按下面的方式重新编写:
<!ELEMENT bb:DIVISION (bb:DIVISION_NAME, bb:TEAM+)>
这意味着,不能将相同的DTD用于带有和不带有命名域的文档,即使这两类文档本来就使用相同的符号集也是如此。事实上,由于DTD受真正的前缀而不是命名域的URI的约束,所以甚至不能将同一个DTD用于使用相同的标记集和命名域、但前缀不同的文档中。
18.2.2 多个命名域
清单14-2并不真正将HTML元素放在命名域中,但要做到这一点则并不困难。清单18-1演示这种用法。正像xsl是XSL转换指令的惯用前缀一样,html也是HTML元素的惯用前缀。在下面的实例中,xsl:stylesheet元素声明两个不同的命名域:一个用于XSL,另一个用于HTML。
清单18-1:使用http://www.w3.org/TR/REC-html40作为命名域用于输出的XSL样式单
<?xml
version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/XSL/Transform/1.0"
xmlns:html="http://www.w3.org/TR/REC-html40">
<xsl:template
match="PERIODIC_TABLE">
<html:html>
<xsl:apply-templates/>
</html:html>
</xsl:template>
<xsl:template
match="ATOM">
<html:p>
<xsl:apply-templates/>
</html:p>
</xsl:template>
</xsl:stylesheet>
虽然将xmlns特性放在根元素上已成为习惯,并且总的来说是很有用的,但也可以出现在其他元素上。在此情况下,命名域前缀只能在声明它的元素内才有效。考虑一下清单18-2。html前缀只在声明它的xsl:template元素中才合法。不能将其施加于其他的模板规则,除非这些模板规则分别声明html命名域。
清单18-2:在模板规则中声明的带有http://www.w3.org/TR/REC-html40命名域的XSL样式单
<?xml
version="1.0"?>
<xsl:stylesheet
xmlns:xsl="httP://www.w3.org/XSL/Transform/1.0">
<xsl:template
match="PERIODIC-TABLE"
xmlns:html="http://www.w3.org/TR/REC-html40">
<html:html>
<xsl:apply-templates/>
</html:html>
</xsl:template>
<xsl:template
match="ATOM">
<P>
<xsl:apply-templates/>
</P>
</xsl:template>
</xsl:stylesheet>
可以在子元素中重新定义命名域。例如,清单18-3中的XSL样式单。此处的xsl前缀出现在不同的元素中,以交替引用http://www.w3.org/XSL/Transform/1.0和http://www.w3.org/XSL/Format/1.0。尽管每个元素都有前缀xsl,但由于xsl前缀的含义随元素而变,所以XSL转换指令和XSL格式化对象仍处于不同的命令位中。
清单18-3:重新定义xsl前缀
<?xml
version="1.0"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/XSL/Transform/1.0">
<xsl:template
match="/">
<xsl:root xmlns:xsl="http://www.w3.org/XSL/Format/1.0">
<xsl:layout-master-set>
<xsl:simple-page-master
page-master-name="only">
<xsl:region-body/>
</xsl:simple-page-master>
</xsl:layout-master-set>
<xsl:page-sequence>
<xsl:sequence-specification>
<xsl:sequence-specifier-single
page-master-name="only"/>
</xsl:sequence-specification>
<xsl:flow>
<xsl:apply-templates
select="//ATOM"/
xmlns:xsl="http://www.w3.org/XSL/Transform/1.0"/>
</xsl:flow>
</xsl:page-sequence>
</xsl:root>
</xsl:template>
<xsl:template
match="ATOM">
<xsl:block
font-size="20pt" font-family="serif"
xmlns:xsl="http://www.w3.org/XSL/Format/1.0">
<xsl:value-of
select="NAME"
xmlns:xsl="http://www.w3.org/XSL/Transform/1.0"/>
</xsl:block>
</xsl:template>
</xsl:stylesheet>
但这样做会产生不必要的混乱,我强烈建议读者避免这样做。可供使用的前缀还有很多,几乎不需要在同一个文档中重复使用。不重复使用前缀的主要价值还在于,来自于不同作者的两个不同的文档碰巧重复使用类似的前缀,此时这两个文档就会组合在一起。这一点也是避免使用像a、m和x这样的短前缀的很好的理由,这些短前缀很可能重新用于不同的目的。
18.2.3 特性
由于特性属于特定元素,所以不使用命名域也可很容易地从类似的命名特性中确定出来。因此,像加到元素中那样,将命名域加到特性中几乎是没有必要的。例如,1999年4月21日的XSL规范工作草案要求所有的XSL转换元素都要加入http://www.w3.org/XSL/Transform/1.0命名域,但是它不要求这些元素的特性也在任何特定命名域中(事实上,它要求元素的特性都不在任何命名域中)。但是,如果需要,可以将命名域前缀加入特性中。例如,下面的PLAYER元素和它所有的特性都处在http://metalab.unc.edu/xml/baseball命名域中。
<bb:PLAYER xmlns:bb="http://metalab.unc.edu/xml/baseball"
bb:GIVEN_NAME="Tom" bb:SURNAME="Glavine"
bb:POSITION="Starting Pitcher" bb:GAMES="33"
bb:GAMES_STARTED="33" bb:WINS="20"
bb:LOSSES="6" bb:SAVES="0"
bb:COMPLETE_GAMES="4" bb:SHUT_OUTS="3"
bb:ERA="2.47"
bb:INNINGS="229.1" bb:HOME_RUNS_AGAINST="13"
bb:RUNS_AGAINST="67" bb:EARNED_RUNS="63"
bb:HIT_BATTER="2"
bb:WILD_PITCHES="3" bb:BALK="O"
bb:WALKED_BATTER="74"
bb:STRUCK_OUT_BATTER="157"/>
如果需要将两个不同XML应用程序中的特性组合到同一个元素中,这种方式有时或许也有用。
可以(虽然通常无意义)将同一个命名域URI与两个不同的前缀相关联。实在是没有道理这么做,不过需要提醒读者,我在此提出可以这样做的唯一前提是,对于带有相同名称的最多只有一个特性的一个元素来说,特性的全名必须满足XML规则。例如,由于bb:GIVEN_NAME和baseball:GIVEN_NAME是相同的,所以下面的形式是不合法的:
<bb:PLAYER xmlns:bb="http://metalab.unc.edu/xml"
xmlns:baseball="http://metalab.unc.edu/xml"
bb:GIVEN_NAME="Hank" bb:SURNAME="Aaron"
baseball:GIVEN_NAME="Henry" />
另一方面,URI实际上并不领会它所指向的是什么对象。URI的http://metalab.unc.edu /xml/和http://www.metalab.unc.edu/xml/指向同一页。但下面的这种是合法的:
<bb:PLAYER xmlns:bb="http://metalab.unc.edu/xml"
xmlns:baseball="http://www.metalab.unc.edu/xml"
bb:GIVEN_NAME="Hank" bb:SURNAME="Aaron"
baseball:GIVEN_NAME="Henry" />
18.2.4 缺省的命名域
在有大量标记的长文档(在所有相同命名域)中,可能会发现要将前缀加到各个元素名中是很不方便的。可以使用没有前缀的xmlns特性,将缺省的命名域与某个元素及其子元素相关联。此元素本身(其所有的子元素也一样)可认为处于定义的命名域中,除非它们拥有明确的前缀。例如,清单18-4显示的XSL样式单就像习惯上的那样,没有使用xsl作为XSL转换元素的前缀。
特性从不处于缺省的命名域中,它们必须明确地作为加上前缀。
清单18-4:使用缺省命名域的XSL样式单
<?xml
version="1.0"?>
<stylesheet
xmlns="http://www.w3.org/XSL/Transform/1.0"
xmlns:fo="http://www.w3.org/XSL/Format/1.0"
result
ns="fo">
<template
match="/">
<fo:root xmlns:fo="http://www.w3.org/XSL/Format/1.0">
<fo:layout-master-set>
<fo:simple-page-master
page-master-name="only">
<fo:region-body/>
</fo:simple-page-master>
</fo:layout-master-set>
<fo:page-sequence>
<fo:sequence-specification>
<fo:sequence-specifier-single
page-master-name="only"/>
</fo:sequence-specification>
<fo:flow>
<apply-templates
select="//ATOM"/>
</fo:flow>
</fo:page-sequence>
</fo:root>
</template>
<template
match="ATOM">
<fo:block
font-size="20pt" font-family="serif">
<value-of
select="NAME"/>
</fo:block>
</template>
</stylesheet>
或许最好使用缺省的命名域来将命名域与正准备加入不同语言标记的、现有的文档中的每个元素相关联。例如,如果将某个MathML放在HTML文档中,只要将前缀加到MathML元素中。只需要将<html>开始标记用下面标记代替,就可以将所有的HTML元素放在http://www.w3.org/TR/REC-html40命名域中:
<html xmlns="http://www.w3.org/TR/REC-html40">
无需编辑文件的其余部分!插入的MathML标记仍需要在各自的命名域中。但是,只要这些标记不与大量的HTML标记相混合,就可以在MathML根元素上简单地声明xmlns特性。这样就定义用于MathML元素的一个缺省命名域,此元素覆盖包含MathML的文档的缺省命名域。如清单18-5所示。
清单18-5:嵌入到使用命名域的、结构完整的HTML文档中的MathML数学元素
<?xml
version="1.0"?>
<html xmlns="http://www.w3.org/TR/REC-html40">
<head>
<title>Fiat Lux</title>
<meta name="GFNFRATOR" content="amaya V1.3b" />
</head>
<body>
<P>And
God said,</P>
<math xmlns="http://www.w3.org/TR/REC-MathML/">
<mrow>
<msub>
<mi>δ</mi>
<mi>α</mi>
</msub>
<msup>
<mi>F</mi>
<mi>αβ</mi>
</msup>
<mi></mi>
<mo>=</mo>
<mi></mi>
<mfrac>
<mrow>
<mn>4</mn>
<mi>π</mi>
</mrow>
<mi>c</mi>
</mfrac>
<mi></mi>
<msup>
<mi>J</mi>
<mrow>
<mi>β</mi>
<mo></mo>
</mrow>
</msup>
</mrow>
</math>
<P>and
there was light</P>
</body>
</html>
此处的math、mrow、msub、mo、mi、mfrac、mn和msup都在http://www.w3.org/
TR/ REC-MathML/命名域中,尽管包含它们的文档使用http://www.w3.org/TR/ REC-html40命名域。
18.3 DTD中的命名域
命名域并没有排除结构整洁和合法性的正常规则。要使带有命名域的文档合法,必须在DTD中声明xmlns特性,这样才能用于与这些特性相关联的元素。此外,如果文档使用math:subset元素,那么DTD必须声明math:subset元素,而不仅仅声明subset元素(当然,这些规定不适用于迄今讨论过的少数几个结构整洁的文档)。例如:
<!ELEMENT math:subset EMPTY>
缺省的特性值以及#IMPLIED特性在此处都有用。例如,下面的ATTLIST声明将每个math:subset元素都放在http://www.w3.org/TR/REC-MathML/命名域中,除非在文档中另外指定。
<!ATTLIST math:subset
xmlns:math "http://www.w3.org/TR/REC-MathML/"
#IMPLIED>
由于缺省命名域不需要在所有的元素前加上前缀,所以当处理合法的文档时,这样的命名域特别有用。给DTD不使用前缀的XML应用程序中的元素加前缀将破坏合法性。
但是,缺省命名域到底起多大作用,却有明确的范围。特别是,这些命名域不足以区分这样的两个元素:即使用的元素名相互矛盾。例如,如果一个DTD定义一个HEAD,同时又包含一个TITLE和一个META元素,并且另一个DTD也定义一个HEAD,同时包含#PCDATA,那么就得在DTD和文档中使用前缀来区分这两个不同的HEAD元素。
正在进行的两种不同的开发,可能(或许不能)最终解决对来自不同领域的相互矛盾的DTD进行融合问题。XML模式为DTD提供更加强大的替代对象;而XML片断能够使不同的文档与差别更大的部分结合起来。但是,这两者至今仍没有完成。因此,如今,融合相互矛盾的DTD或许需要使用前缀来重新编写DTD和文档。
如果对有关使用命名域的文档是否是结构整洁或合法还不太清楚的话,请忘掉有关命名域的所有知识。只将文档作为一个正常的XML文档来看待,只不过这样的文档中的一些元素和特性名碰巧包含冒号罢了。这种文档也是结构整洁和合法的,就像不考虑命名域时的一样。
18.4 本章小结
本章解释了如何使用命名域。特别是学习了如下内容:
* 命名域区别不同XML应用程序中相同名称的元素和特性。
* 命名域是由xmlns特性(其值为命名域的URI)来声明的。由该URI引用的文档可以不存在。
* 与命名域相关联的前缀是xmlns特性名的组成部分,而此命名域前面有一冒号;例如xmlns:prefix。
* 加到所有元素和特性名中的前缀属于由前缀表征的命名域。
* 如果xmlns特性没有前缀,那么它就为元素及该元素的子元素(但不为任何特性)建立一个缺省的命名域。
* DTD必须以这样的方式来编写,以使对命名域一无所知的处理程序仍能分析并验证此文档的合法性。
下一章将探讨资源描述框架(Resource Description Framework,RDF),它是个XML应用程序,用于编译元数据(metadata)和信息结构。