了解 XML 命名空间
Aaron Skonnard
DevelopMentor
更新日期:2002 年 7 月
![*](http://www.microsoft.com/library/gallery/templates/MNP2.Common/images/3squares.gif)
本页内容
![]() | 什么是命名空间? |
![]() | 编程语言中的命名空间 |
![]() | XML 中的命名空间 |
![]() | 对命名空间进行命名 |
![]() | 定义命名空间 |
![]() | 使用命名空间 |
![]() | 命名空间抽象 |
![]() | 小结 |
由 Aaron Skonnard 撰写的“了解 XML 命名空间”最初刊登在 2001 年 7 月的 MSDN Magazine 中。此更新版本的使用经过授权。版权所有 ? 2001 Microsoft Corp. 和 CMP Media LLC。
命名空间是 XML 中许多混淆的来源,初学该技术的用户对命名空间尤其感到困惑。读者、学生和与会者经常问到的问题总是与命名空间有关。这实际上具有一定的讽刺意味,因为 Namespaces in XML Recommendation 是一种精简的 XML 规范(不含附录,不超过 10 页)。然而,这种混淆与命名空间语义有关,而与该规范中概述的语法无关。为了充分了解 XML 命名空间,您必须知道什么是命名空间、如何定义命名空间以及如何使用它们。
本专栏的其余部分将专门从语法和理论上回答这三个问题。在读完本文后,您将了解命名空间是如何影响 XML 技术家族的。
什么是命名空间?
命名空间是一组保持唯一的名称。例如,可以将我所有孩子的姓名视为一个命名空间,就像加利福尼亚州的公司的名称、C++ 类型标识符的名称或 Internet 域名。命名空间就是在逻辑上相关的任何一组名称,而且每个名称都必须唯一。
使用命名空间更便于产生唯一的名称。设想一下,如果姓名必须在全球保持唯一,那么,要给自己的下一个小孩起名将会多么困难。如果将唯一性限制在一个更窄的上下文(例如,我的所有孩子)中,情况就会简单得多。当我为我的下一个孩子起名时,我只需考虑不使用与我的其他孩子重名的名字。另一组父母可以为他们的某个孩子选择我已使用过的姓名,但是这些姓名必须属于不同的命名空间,以便易于区分。
在将新名称添加到某个命名空间中之前,命名空间机构必须确保该命名空间中没有这个新名称。在某些情况下,这会非常简单,因为它属于子命名系统。在其他情况下,这会相当复杂。当今的许多 Internet 命名机构就是一个现实的例子。然而,如果忽略此步骤,重复的名称最终会损坏该命名空间,这使得无法引用某些没有多义性的名称。如果出现这种情况,这组名称将不再被正式视为命名空间 — 根据定义,命名空间必须确保它的成员具有唯一性。
为了使命名空间有用,还必须为其本身赋予名称。在命名空间有了名称之后,就可以引用其成员。例如,考虑显示在图 1 两个框中的示例命名空间,这两个示例命名空间的名称分别是 Microsoft 和 AcmeHardware。请注意,即使这两个命名空间都包含一些相同的本地名称,也可以通过由命名空间限定的名称来引用没有多义性的本地名称,如图1 所示。
![edcb0101](https://i-blog.csdnimg.cn/blog_migrate/fea1c5073efaa5307270b13c56ec0b3a.png)
图 1. 非多义性命名空间
当然,其前提是假设这些命名空间名称也是唯一的。如果不能保证这一点,则还可以将实际命名空间名称本身放到其各自的命名空间中。例如,如果有多个 AcmeHardware 商店(一个在加利福尼亚州,一个在犹他州),则将名称 AcmeHardware 放在两个不同的命名空间就会解决这种冲突,如下所示:
California.AcmeHardware.Paint Utah.AcmeHardware.Paint
这种模式可根据需要重复任意多次,以保证命名空间名称的唯一性。这与 Internet 域名系统 (DNS) 的工作方式完全相同,DNS 就是一个由多个命名空间组成的大命名空间。
如果没有这种类型的命名空间分区,您将不得不使用极长(不常用)的名称来确保唯一性:
MicrosoftWindowsOperatingSystemPaintApplication
设想一下,如果只有一个不能进行分区的全局命名空间,会有多么复杂、多么令人头痛。人们在日常社交中相当依赖命名空间,尽管在大多数情况下人们并没有清楚地意识到这一点。然而,要在软件开发中使用命名空间,必须通过具体的语法明确它们。在转入讨论 XML 中的命名空间之前,让我们看一下当今某个主流编程语言中命名空间的语法示例。
编程语言中的命名空间
要在某个编程语言中使用命名空间,您必须熟悉用来定义命名空间并引用其中的某些内容的语法。当今的许多语言(包括 C++、Java 和 C#)为命名空间提供支持。在 C++ 中,命名空间是通过命名空间块来定义的,如下所示。
namespace foo1 { class bar { ???????????? }; class baz { ???????????? }; } namespace foo2 { class bar { ???????????? }; class baz { ???????????? }; }
本例定义了两个命名空间:foo1 和 foo2。这两个命名空间均包含两个名称:bar 和 baz(在本例中,它们是类标识符)。
foo1::bar b1; // refers to bar class in foo1 foo2::bar b2; // refers to bar class in foo2
要引用特定命名空间的 bar 类,必须用给定的命名空间标识符来限定 bar 标识符。
为方便起见,还可以做如下声明:在给定的源文件中使用特定的命名空间。这会从本质上使指定的命名空间成为源文件的默认命名空间。于是,就没有必要完全限定特定的命名空间成员,当然,在绝对有必要避免多义性时也可以完全限定:
using namespace foo1; bar b1; // refers to bar class in foo1
正如您所看到的,C++ 中定义和使用命名空间的语法简单明了。C# 的工作方式与 C++ 非常相似,只是有几个小区别。Java 中的命名空间语法稍有不同,但概念是相同的。
在许多编程语言中,命名空间可用来帮助避免名称冲突,这正是完成 XML 1.0 规范所需的解决方案类型。
XML 中的命名空间
因为 XML 1.0 规范不提供命名空间支持,所以许多开发人员感到它不完整。因此,用在 XML 文档中的所有名称都属于一个全局命名空间,这便难于实现唯一的名称。
许多开发人员(包括 XML 1.0 作者本身)知道这在基于 XML 的大型分布式系统中最终会导致太多的多义性。例如,考虑下面的 XML 文档:
<student> <id>3235329</id> <name>Jeff Smith</name> <language>C#</language> <rating>9.5</rating> </student>
此文档使用几个名称,每个名称都相当普通。student 元素对软件培训课程的学生进行建模。id、language 和 rating 元素对学生的数据库记录编号、首选的编程语言以及学生对该课程的评分(基准分是 10)进行建模。其中的每个名称肯定都会在其他情况下用到 — 在这些情况下,它们会具有不同的含义。
例如,下面是另一个 XML 文档,它以一种完全不同的方式来使用相同的名称:
<student> <id>534-22-5252</id> <name>Jill Smith</name> <language>Spanish</language> <rating>3.2</rating> </student>
在本例中,student 元素对小学生进行建模。现在,id、language 和 rating 元素分别对孩子的社会保障号、本民族语言和当前的年级平均成绩(基准分是 4)进行建模。这两个文档的作者可以使用较长的、不太常用的名称来帮助确保实现唯一性,但这最终还是无法保证唯一性,而且更加难以使用。
尽管人们能够在查看这两个文档后找出二者的区别,但是它们对于软件来说看上去却完全相同。设想一下,您负责构建一个学生管理应用程序,该应用程序必须支持与学生有关的许多不同的 XML 文档(包括刚提到的两个文档)。在编写代码时,您打算如何(从编程上)区分专业学生和小学生或者任何其他类型的学生?没有一种可靠的方法来进行这种区分。
在同一个文档或应用程序中使用来自不同 XML 词汇表中的元素和属性,无论如何都会产生命名冲突。请考虑 XSLT,它本身是用来定义转换的 XML 词汇表。在给定的转换中,可以输出用户定义的文本元素。因此,既然 XSLT 词汇表中包含名为 template 的元素,那么如何输出名称同样为 template 的用户定义的文本元素呢?
<!-- this is the template element from XSLT --> <template match="foo"> <!-- I want to output this template element --> <template match="foo"/> </template>
在大量混合 XML 词汇表的语言(例如,XSLT 和 XML 架构)中,出现名称冲突的可能性极大。然而,如果 XML 提供对命名空间的支持,就可以很容易地避免这些问题的发生。
“Namespaces in XML Recommendation” 是 W3C 为 XML 1.0 命名问题提供的解决方案。此规范定义了如何对 XML 1.0 具体语法进行扩展,以便支持命名空间。因为大多数开发人员都认为这个新增功能是绝对有必要添加的基本功能,所以此规范通常被视为 XML 1.0 的正式补充,尽管它不是正式的。实际上,许多开发人员现在拒绝单独提及 XML 1.0,而是提及“XML 1.0 + 命名空间”,其原因就在于此。
“Namespaces in XML Recommendation” 定义了 XML 命名空间的命名语法以及在 XML 命名空间中引用某些内容的语法。然而,它没有涉及到用来定义 XML 命名空间中有何内容的语法。这留给了另一个规范(即,XML 架构)。其中的每个领域都需要一些解释。
对命名空间进行命名
当您在编程语言(例如,C++)中定义命名空间时,有一些对可用在该名称中的字符的限制。XML 命名空间标识符还必须符合特定的语法 — 统一资源标识符 (URI) 引用的语法。这表示 XML 命名空间标识符必须遵守由 RFC 2396 定义的 URI 的常用语法。
URI 被定义为用来标识抽象或物理资源的紧凑字符串。在大多数情况下,URI 引用用来标识物理资源(网页、要下载的文件等),但是,对于 XML 命名空间来说,URI 引用用于标识抽象资源(特别是命名空间)。
按照 URI 规范,有两种常规类型的 URI:统一资源定位器 (URL) 和统一资源名称 (URN)。这两种类型的 URI 都可以用作命名空间标识符。下面是一个可用作命名空间标识符的两个 URL 的示例:
http://www.develop.com/student http://www.ed.gov/elementary/students
下面是几个也可用作命名空间标识符的 URN 的示例:
urn:www-develop-com:student urn:www.ed.gov:elementary.students urn:uuid:E7F73B13-05FE-44ec-81CE-F898C4A6CDB4
命名空间标识符最重要的属性是它的唯一性。作者可以通过向 Internet 命名机构注册域名来保证 URL 的唯一性,然后要负责确保域名后面使用的所有字符串都保持唯一。
URN 以同样的方式工作。下面是基本的 URN 语法:
urn:<namespace identifier>:<namespace specific string>
为了保证 URN 的一致性,作者必须再次向 Internet 命名机构注册他们的命名空间标识符。然后,作者负责按照某个方案来生成特定于命名空间的唯一字符串。
定义 XML 命名空间的组织应当为新命名空间名称的创建制定一个一致的方案。例如,W3C 经常定义新的 XML 命名空间。这些组织使用一个相当直观的试探法,该试探法使用当前年份以及工作组的名称。图 2 阐释了由 W3C 使用的模式。
![edcb0101](https://i-blog.csdnimg.cn/blog_migrate/4d9c4ee9387799dfeb5cd42a6a6f09cb.png)
图 2. W3C URI 构造
根据定义,URI 是唯一的,因此完全不必在 XML 命名空间标识符的上面放置其他命名空间。只要命名空间作者保证命名空间标识符的唯一性,总是可以只用单个命名空间限定符来唯一地标识 XML 中的内容。这大大简化了这一在 XML 中处理命名空间的工作。
XML 处理器将命名空间标识符视为不透明的字符串,而从不将它们视为可解析的资源。重申一遍:命名空间标识符仅仅是字符串!当两个命名空间标识符中的各个字符都完全相同时,它们就被视为相同。
最后,它确实与选择使用哪个类型的 URI 引用无关。许多开发人员因 URL 更易于读取和记忆而喜欢使用它们,而其他开发人员因 URN 的灵活性而喜欢使用它们。无论选择哪种类型,您都要确保知道如何保证唯一性。
定义命名空间
“Namespaces in XML Recommendation” 没有提供用来定义 XML 命名空间中有何内容的语法。在许多情况下,这种类型的语法定义甚至不是必需的。目前,大多数 XML 命名空间都是在正式规范文档中定义的,这些文档描述元素的名称,以及属性及其语义。这恰好说明了如何正式定义所有的 W3C 命名空间(请参阅 http://www.w3.org/TR/xslt 上的 XSLT 1.0 规范,以查看相关示例)。
在定义了某个命名空间之后,软件开发人员按照规范中所概述的那样实现该命名空间。例如,MSXML 3.0、Xalan 和 Saxon 都是对 XSLT 1.0 规范的实现。对这些实现进行硬编码,以便查找那些属于 XSLT 1.0 命名空间 (http://www.w3.org/1999/XSL/Transform) 的元素。要使用这些实现,您需要向它们提供一个正确使用 XSLT 1.0 命名空间中名称的 XML 文档(下一节将介绍这方面的详细信息)。如果要更改 XSLT 1.0 命名空间中的任何内容,支持软件将必须进行更新。
XML 架构工作组 (http://www.w3.org/XML/Schema) 已经合并了一个新规范(XML 架构),该规范为在命名空间中定义元素、属性和类型定义了一个基于 XML 的语法。XML 架构最终使得提供命名空间的语法定义成为可能,如下所示。
<schema xmlns='http://www.w3.org/2000/10/XMLSchema' targetNamespace='http://www.develop.com/student' elementFormDefault='qualified' > <element name='student'> <complexType> <sequence> <element name='id' type='long'/> <element name='name' type='string'/> <element name='language' type='string'/> <element name='rating' type='double'/> </sequence> </complexType> </element> </schema>
本例定义的 http://www.develop.com/student 命名空间包含五个命名元素:student、id、name、language 和 rating。此架构不仅提供命名空间,而且还提供其他元数据,例如,student 子元素的顺序以及它们的类型。
在有了语法命名空间定义(例如,那些由 XML 架构提供的定义)时,就可以构建更高级的软件,以便在运行时利用名称和类型信息。XML 架构仍然未定义所定义元素和属性的语义,因此仍将需要一个随附的规范。将来,大多数 XML 命名空间都将通过规范和架构定义同时定义。
使用命名空间
我将命名空间的使用过程定义为:使用 XML 文档中给定命名空间中的一个或多个元素或属性。这要求您了解如下由 “Namespaces in XML Recommendation” 概述的语法:用命名空间标识符限定元素名称和属性名称。
元素和属性的名称实际上都由两部分组成:一个命名空间名称和一个本地名称。这样的两部分名称就是所谓的限定名或 QName。
在 XML 文档中,我们使用命名空间前缀来限定元素和属性的本地名称。前缀实际上只是命名空间标识符 (URI) 的缩写,URI 通常相当长。前缀首先通过命名空间声明映射到命名空间标识符。命名空间声明的语法是:
xmlns:<prefix>='<namespace identifier>'
命名空间声明看起来就像(元素的)属性,但是从文档的逻辑结构来看,它们不被正式视为属性(即,在使用 DOM 时,它们将不出现在元素的属性集中)。
命名空间前缀被视为在声明元素以及它的任何子代元素的作用域内。声明的前缀可用在任何元素或属性名称的前面(用冒号分隔,例如,s:student)。这个包括前缀的完整名称是限定名 (QName) 的词法形式:
QName = <prefix>:<local name>
前缀通过映射到当前位于作用域中的前缀的命名空间标识符与元素或属性关联。
让我们假设某个开发人员希望使用 XSLT 1.0 命名空间。他将需要提供一个命名空间声明,以便将任意前缀映射到正式的 XSLT 1.0 命名空间标识符 (http://www.w3.org/1999/XSL/Transform)。然后,只需在该开发人员希望从 XSLT 1.0 命名空间中使用的每个元素或属性前面加上相应的前缀,如下例所示:
<x:transform version='1.0' xmlns:x='http://www.w3.org/1999/XSL/Transform' > <x:template match='/'> <hello_world/> </x:template> </x:transform>
上例显示了在命名空间中引用元素的语法。前缀为 “x” 的每个元素都来自 http://www.w3.org/1999/XSL/Transform 命名空间,而没有前缀的任何元素(例如,hello_world)都不来自命名空间。处理器现在可以区分 XSLT 1.0 编程构造和将要输出的文本元素(例如,hello_world)。如果 XSLT 1.0 命名空间中有一个字符拼错了,则 XSLT 1.0 处理器将无法将该文档识别为它能够理解的词汇。
在本质上,每个元素现在都有一个由两部分组成的名称:一个命名空间标识符和一个本地名称。这两个名称组合在一起通常称作命名空间名称(注意:这不同于 QName,QName 由前缀和本地名称组合而成)。
下面的 XML 文档是另一个示例,它显示了如何从本专栏前面显示的 XML 架构定义使用元素:
<d:student xmlns:d='http://www.develop.com/student'> <d:id>3235329</d:id> <d:name>Jeff Smith</d:name> <d:language>C#</d:language> <d:rating>9.5</d:rating> </d:student>
请注意,无论命名空间是如何定义的,引用它们的语法都是相同的。
当文档使用多个命名空间中的元素或属性时,通常针对给定的元素进行多个命名空间声明,如下例所示:
<d:student xmlns:d='http://www.develop.com/student' xmlns:i='urn:schemas-develop-com:identifiers' xmlns:p='urn:schemas-develop-com:programming-languages' > <i:id>3235329</i:id> <name>Jeff Smith</name> <p:language>C#</p:language> <d:rating>9.5</d:rating> </d:student>
在这里,student 和 rating 来自同一个命名空间,而 id 和 language 分别来自不同的命名空间,但是 name 不属于命名空间。
命名空间前缀还可以通过在嵌套作用域中重新声明来进行重写,如下所示:
<d:student xmlns:d='http://www.develop.com/student'> <d:id>3235329</d:id> <d:name xmlns:d='urn:names-r-us'>Jeff Smith</d:name> <d:language>C#</d:language> <d:rating>35</d:rating> </d:student>
在本例中,除了 name 元素,所有的内容都来自同一个命名空间 — urn:names-r-us 命名空间。尽管可以重新声明命名空间前缀,但是却无法取消对命名空间前缀的声明。例如,下面的声明是非法的:
<d:student xmlns:d='http://www.develop.com/student'> <d:id xmlns:d=''>3235329</d:id> ?·?·?· </d:student>
对于大多数软件开发人员来说,面向前缀的语法(用来在 XML 命名空间中引用内容)是相当直观的。如果 “Namespaces in XML Recommendation” 只包含这些内容,则命名空间所引起的混淆会小得多。
默认命名空间
可以使用另一种类型的命名空间声明来将命名空间标识符与元素名称相关联。这就是所谓的默认命名空间声明,它使用下面的语法:
xmlns='<namespace identifier>'
请注意,这里没有前缀。在针对某个元素使用默认命名空间声明时,其作用域中的所有非限定元素名称都自动与指定的命名空间标识符相关联。然而,默认命名空间声明决不会影响属性。如果要将属性与命名空间标识符相关联,唯一的方法就是使用前缀。
请考虑下例:
<d:student xmlns:d='http://www.develop.com/student' xmlns='urn:foo' id='3235329' > <name>Jeff Smith</name> <language xmlns=''>C#</language> <rating>35</rating> </d:student>
在这里,“student” 来自 http://www.develop.com/student 命名空间,而 “name” 和 “rating” 来自默认命名空间 urn:foo。id 属性不属于命名空间,这是由于属性不会自动与默认的命名空间标识符相关联。
本例还阐释了您可以取消对默认命名空间的声明(只需将默认的命名空间标识符重新设置为空字符串),如 language 元素所示(请记住,对于前缀声明不能这样做)。因此,language 元素也不属于命名空间。
默认命名空间的语法旨在提高方便程度,但是它们会导致更多的混淆,而不是带来更大的价值。这种混淆通常源自如下事实:元素和属性以不同的方式处理,而且嵌套元素被指定为默认的命名空间标识符也不是立即可见的。然而,除了属性起作用时,在前缀和默认命名空间之间进行选择最终将主要是样式方面的问题。
命名空间抽象
从 XML 文档的抽象视图处理命名空间比处理刚描述的词法问题简单得多。XML 信息集 (Infoset) 定义 XML 文档的抽象结构,这使开发人员不再处理复杂的基础序列化格式(例如,刚描述的命名空间语法)。
按照 Infoset,每个元素或属性都有两个名称属性:一个命名空间标识符和一个本地名称。图 3 阐释了一个 XML 文档的逻辑结构,该文档中包含命名空间限定的名称。请注意,student、id 和 language 都来自同一个命名空间,而 rating 来自另一个命名空间,name 不属于命名空间。此文档可通过上一节中描述的任一方法进行序列化。
![edcb0101](https://i-blog.csdnimg.cn/blog_migrate/87ad6739ee2ab9ae71590904d1d0bb28.png)
图 3. 命名空间限定的 XML 文档
请考虑当今的主流 API、SAX 和 DOM 如何实现这个抽象数据模型。SAX 通过 ContentHandler 的 startElement/endElement 方法调用来对元素进行建模:
public interface contentHandler { ???????????? void startElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException; void endElement(String namespaceURI, String localName, String qName) throws SAXException; ???????????? }
请注意,这些元素由它们的命名空间标识符和本地名称(可以选择使用 QName)的组合来标识。属性还可以通过一组能够针对 Attributes 接口识别命名空间的方法来标识。SAX 分析器(或者任何其他制作者的应用程序)负责提供命名空间名称,因为它提供文档流。也就是说,在使用 SAX 时,以编程方式区分不同类型的 student 元素将会非常简单。
???????????? void startElement(String namespaceURI, String localName, String qName, Attributes atts) { if ( namespaceURI.equals("urn:dm:student") && localName.equals("student") ) { // process Developmentor student element here } else if ( namespaceURI.equals("urn:www.ed.gov:student") && localName.equals("student") ) { // process elementary school student element here } } ????????????
由于命名空间名称(命名空间标识符 + 本地名称)是由 SAX 分析器自动解析的,因此,在源文档中的特定元素或属性中使用什么前缀(如果有的话)并不重要 — 它主要是序列化细节。然而,这并不表示前缀在经过分析之后就可以被扔掉。考虑下面的 XML 文档:
<student xmlns:xsd='http://www.w3.org/2000/10/XMLSchema' xmlns:xsi='http://www.w3.org/2000/10/XMLSchema-instance' > <age xsi:type='xsd:double'>35.0</age> </student>
请注意,在该 XML 架构中,age 的 xsi:type 属性包含一个 QName 值。无论何时在元素或属性内容中使用 QName,商业应用程序都必须手动处理它。只有知道了 “xsd” 要绑定到哪个命名空间标识符,商业应用程序才能正确地解释此值。因此,Infoset 对于文档中的每个元素还维护一组作用域内的命名空间声明。SAX 通过 startPrefixMapping 和 endPrefixMapping 方法调用对该信息进行建模。
DOM API 是对 Infoset 的另一个实现。DOM 的 Node 接口通过以下两个名称属性对元素/属性节点的基本标识进行建模:namespaceURI 和 localName。它还通过 nodeName 和 prefix 属性对节点的 QName 和前缀进行建模。下面的 Java 语言代码阐释了如何使用 DOM 来区分两个不同的 student 元素。
void processStudent(Node n) { if ( n.getNamespaceURI().equals("urn:dm:student") && n.getLocalName().equals("student") ) { // process Developmentor student element here } else if ( n.getNamespaceURI().equals("urn:www.ed.gov:student") && n.getLocalName().equals("student") ) { // process elementary school student element here } }
正如对于 SAX 一样,用来构建 DOM 树的 XML 分析器负责填充相应的命名空间属性。因此,在使用逻辑文档结构时,如何在源文档中声明命名空间同样并不重要。如果您要通过 DOM API 创建文档,则您需要负责在创建文档时为每个元素和属性提供命名空间标识符:
void generateStudentDocument(Document doc) { Node docEl = doc.createElementNS("urn:dm:student", "student"); doc.appendChild(docEl); Node n = doc.createElementNS("", "name"); docEl.appendChild(n); ????????????
正如您所看到的那样,此代码允许您直接创建逻辑结构。然后,由 DOM 实现来负责确定如何将命名空间声明序列化为基础 XML 1.0 文档。这个 DOM 树可按如下方式进行序列化:
<student xmlns='urn:dm:student'> <name xmlns=''/> </student>
当您(通过 SAX/DOM API)处理 XML 文档的抽象视图时,一定要注意其中没有默认命名空间的概念。在前面提到的示例中,在针对 “student” 调用 createElementNS 之后,urn:dm:student 不会神奇地变成默认命名空间。如果针对没有命名空间的 “name” 调用 createElementNS,就会向 name 元素赋予空的命名空间标识符(不是 urn:dm:student)。就一系列 startElement/endElement 方法调用而言,这同样适用于 SAX。每个元素/属性节点总是以独立于名称信息的方式进行处理。
XPath 是另一个 XML 规范,它定义如何在抽象文档结构中标识节点。XPath 表达式允许通过命名空间限定的名称来标识元素和属性。由于 XPath 名称测试是简单的字符串表达式,所以只能通过命名空间前缀来将 XPath 名称测试与命名空间标识符相关联。
您可以将 XPath 节点测试视为属于 QName 类型。这就是说,如果某个节点测试不包括前缀,就像是要查找不属于命名空间的给定名称。例如,请使用下面的 XPath 表达式:
/student/name
此表达式标识不属于命名空间的、作为不属于命名空间的根 student 元素的子级的所有 name 元素。要标识属于 urn:dm:student 命名空间的 student 和 name 元素,首先必须将命名空间前缀与 urn:dm:student 相关联。然后就可以在 XPath 表达式中使用该前缀了。
假设 “dm” 已经与 XPath 上下文中的 urn:dm:student 相关联,则下面的表达式现在将标识属于 urn:dm:store 命名空间的、作为同样属于 urn:dm:store 命名空间的根 student 元素的子级的 name 元素:
/dm:student/dm:name
如果查询到的文档看上去像下面的代码,则上面的表达式将标识作为 student 子级的所有三个 name 元素(与它们的前缀无关),这是由于它们都来自所讨论的同一个命名空间。
<s:student xmlns:s='urn:dm:student'> <s:name/> <n:name xmlns:n='urn:dm:student'/> <s:name/> </s:student>
在 XPath 上下文中,前缀以与实现相关的方式进行映射(有关如何在 MSXML 3.0 中进行此操作的详细信息,请参阅 2001 年 5 月的 The XML Files 专栏)。XSLT 就是这样的一个示例,它提供了一个使用 XPath 表达式的上下文。要在 XSLT 文档中使用命名空间限定的 XPath 表达式,可以使用标准的命名空间声明来将目标命名空间标识符映射到任意前缀:
<x:transform version='1.0' xmlns:x='http://www.w3.org/1999/XSL/Transform' xmlns:d='urn:dm:student' > <x:template match='d:student'> <!-- transform student here --> <x:apply-templates select='d:name'/> </x:template> ?·?·?· </x:transform>
请注意,第一个模板针对 urn:dm:student 命名空间中的 student 元素进行匹配。如果匹配值只是 “student”,它将只匹配不属于命名空间的 student 元素。apply-templates 元素随后处理所有的 name 子元素,这些子元素也属于 urn:dm:student 命名空间。
正如您所看到的那样,从词法和理论上了解命名空间的工作方式,对于了解 XML 规范的整个系列来说是至关重要的。随着 XML 规范的不断涌现,您将遇到许多与所发生的情况相类似的情况。
小结
命名空间是一组保持唯一的名称。XML 中的命名空间允许为元素和属性提供唯一的名称。尽管命名空间总是成为许多混淆的来源,但是在从语法和理论上熟悉了它们的定义和使用方式之后,您会很容易理解它们。有关命名空间的更多信息,请参阅 Namespaces in XML Recommendation 和 XML Namespaces by Example。
Aaron Skonnard 是 DevelopMentor 的讲师兼研究员,他还负责开发 XML 课程。Aaron 撰写了 Essential WinInet(Addison-Wesley Longman,于 1998 年出版),并与他人合著了 Essential XML(Addison-Wesley Longman,于 2000 年出版)。通过 http://staff.develop.com/aarons 可以与 Aaron 取得联系。