XML 命名空间以及它们如何影响 XPath 和 XSLT (Extreme XML)

Dare Obasanjo

Microsoft Corporation

2002 年 5 月 20 日

本文是有望长期发表的系列文章的第一篇,这些文章专门阐释由 Microsoft 支持的 W3C XML 技术的更微妙的内容。尽管 XML 的核心仍相当简单,但是围绕它的技术已经变得日益复杂,而且其中的一些技术需要相当多的专业知识才能掌握。本文以及随后的其他文章旨在将各种 W3C XML 建议中的信息提取为便于理解的信息,供 XML 用户和开发人员使用。

在这一系列文章中,第一篇是有关 XML— 命名空间常被误解的方面。XML 命名空间是 W3C 的大部分 XML 建议和工作草案(包括 XPath、XML 架构、XSLT、XQuery、SOAP、RDF、DOM 和 XHTML)中不可缺少的一方面。对于任何使用 XML 的人来说,了解命名空间如何工作以及它们如何与依赖它们的许多其他 W3C 技术进行交互非常重要。

本文介绍 XML 命名空间的详细内容,以及它们在支持命名空间的许多 XML 技术上的分支。

本页内容
XML 命名空间概述 XML 命名空间概述
XPath、XSLT 和命名空间 XPath、XSLT 和命名空间
XML 命名空间警告 XML 命名空间警告
命名空间的未来 命名空间的未来

XML 命名空间概述

随着 XML 在 Internet 上的使用日益广泛,能够创建可组合和重用的标记词汇表(方法类似于软件模块的组合和重用)这一优势变得日益重要。如果已经存在一个定义完善的标记词汇表,用于描述硬币集合、程序配置文件或快餐店的菜单,则重用它会比从头设计一个更有意义。将多个现有的词汇表组合在一起,以便创建“事物的整体比它各个部分的总和还大”的新词汇表,也成为 XML 用户开始需要的一个功能。

但是,在同一个文档中,来自不同词汇表的同一个标记(特别是 XML 元素和属性)可能具有不同的语义,这最终会产生问题。XML 的高度扩展性以及它在 Internet 上的广泛应用排除了只是将保留的元素或属性名称指定为此问题的解决方案。

W3C XML命名空间建议旨在创建一个机制,以便 XML 文档中来自不同标记词汇表的元素和属性可以被明确标识和组合,而无需处理所产生的问题。XML 命名空间建议提供了一种方法,以便基于处理要求对 XML 文档中的各个项目进行分区,而无需针对应当如何命名这些项目设置过多的限制。例如,名为 <template><output><stylesheet> 的元素可以出现在 XSLT 样式表中,而对于它们是转换指令还是转换的可能输出没有二义性。

XML 命名空间是一组由 统一资源标识符 (URI) 引用标识的名称,这些名称在 XML 文档中用作元素名称和属性名称。

命名空间声明


图 1. 利用命名空间的 XML 代码片段

命名空间声明通常用于将命名空间 URI 映射到特定的前缀。前缀-命名空间映射的作用域包括命名空间声明作用的元素及其所有的子级。前缀为 xmlns: 的属性声明是命名空间声明。类似属性声明的值应当是作为命名空间名称的命名空间 URI。

在下面的示例 XML 文档中,根元素包含一个将前缀 bk 映射到命名空间名称 urn:xmlns:25hoursaday-com:bookstore 的命名空间声明,它的子元素包含一个 inventory 元素,inventory 元素中包含一个将前缀 inv 映射到命名空间名称 urn:xmlns:25hoursaday-com:inventory-tracking 的命名空间声明。

<bk:bookstore xmlns:bk="urn:xmlns:25hoursaday-com:bookstore">
<bk:book>
<bk:title>Lord of the Rings</bk:title>
<bk:author>J.R.R. Tolkien</bk:author>
<inv:inventory status="in-stock" isbn="0345340426"
xmlns:inv="urn:xmlns:25hoursaday-com:inventory-tracking" />
</bk:book>
</bk:bookstore>

在上例中,urn:xmlns:25hoursaday-com:bookstore 命名空间名称的命名空间声明作用域是整个 bk:bookstore 元素,而 urn:xmlns:25hoursaday-com:inventory-tracking 的命名空间声明作用域是 inv:inventory 元素。能够识别命名空间的处理器可以独立处理来自这两个命名空间的项目,这会使其能够对 XML 文档执行多层处理。例如,RDDL 文档是有效的 XHTML 文档,这些文档不仅可以由 Web 浏览器呈现,而且还包含使用 http://www.rddl.org 命名空间中元素的信息,该命名空间可用于查找有关 XML 命名空间成员的机读资源。

应当注意的是,按照定义,前缀 xml 绑定到 XML 命名空间名称,而且这个特殊的命名空间自动在每个格式规范的 XML 文档中对文档作用域预先进行声明。

默认命名空间

有关命名空间声明的上一节不是完整的,因为它未考虑默认命名空间。默认命名空间声明是一个属性声明,该属性声明的名称是 xmlns,其值是作为命名空间名称的命名空间 URI。

默认命名空间声明指定其作用域中所有不带前缀的元素名称都来自声明的命名空间。下面的书店示例使用默认命名空间,而不使用前缀-命名空间映射。

<bookstore xmlns="urn:xmlns:25hoursaday-com:bookstore">
<book>
<title>Lord of the Rings</title>
<author>J.R.R. Tolkien</author>
<inv:inventory status="in-stock" isbn="0345340426"
xmlns:inv="urn:xmlns:25hoursaday-com:inventory-tracking" />
</book>
</bookstore>

在上例中,除 inv:inventory 元素以外的所有元素都属于 urn:xmlns:25hoursaday-com:bookstore 命名空间。默认命名空间的主要目的是缩短使用命名空间的 XML 文档。但是,如果对于元素名称使用默认命名空间,而不使用显式映射的前缀,可能会导致混淆,因为文档中的元素不是明显地属于命名空间的作用域。

此外,与常规的命名空间声明不同的是,默认命名空间声明可以通过将 xmlns 属性的值设置为空字符串来取消声明。应当避免取消对默认命名空间声明的声明,因为这一做法可能导致在文档的一部分中,具有属于某个命名空间且不带前缀的名称,但是在另一部分中却没有。例如,在下面的文档中,只有 bookstore 元素来自 urn:xmlns:25hoursaday-com:bookstore,而其他不带前缀的元素没有命名空间名称。

<bookstore xmlns="urn:xmlns:25hoursaday-com:bookstore">
<book xmlns="">
<title>Lord of the Rings</title>
<author>J.R.R. Tolkien</author>
<inv:inventory status="in-stock" isbn="0345340426"
xmlns:inv="urn:xmlns:25hoursaday-com:inventory-tracking" />
</book>
</bookstore>

应当避免这一做法,因为它对于 XML 文档的读者会产生非常容易混淆的情况。有关对命名空间声明取消声明的详细信息,请参阅“命名空间的未来”一节。

限定名称和扩展名称

限定名称又称为 QName,是一个名为本地名称的 XML 名称,它的前面可以有另一个名为前缀的 XML 名称和一个冒号 (':')字符。用作前缀的 XML 名称和本地名称必须与产生的 NCName 相匹配,这意味着它们不得包含冒号字符。限定名称的前缀必须已经通过作用域内的命名空间声明(将前缀映射到命名空间 URI)映射到命名空间 URI。限定名称可以用作属性名称,也可以用作元素名称。

尽管 QName 是重要的助记指导,可以帮助确定文档中的元素和属性是从哪个命名空间派生的,但是它们对于能够识别 XML 的处理器无关紧要。例如,下面的三个 XML 文档将被一系列 XML 技术(当然包括 XML 架构验证程序)以同样的方式处理。

<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:complexType id="123" name="fooType"/>
</xs:schema>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<xsd:complexType id="123" name="fooType"/>
</xsd:schema>
<schema xmlns="http://www.w3.org/2001/XMLSchema">
<complexType id="123" name="fooType"/>
</schema>

W3C XML 路径语言建议扩展名称描述为命名空间名称和本地名称对。通用名称是由 James Clark 创造的一个替换术语,可用来描述同一个概念。通用名称由命名空间名称(用大括号括起来)和本地名称组成。从通用名称的角度看,命名空间对于人们的意义更大。下面是上面示例中的三个 XML 文档,但是 QName 被替换为通用名称。请注意,下面的语法不是有效的 XML 语法。

<{http://www.w3.org/2001/XMLSchema}schema>
<{http://www.w3.org/2001/XMLSchema}complexType id="123" name="fooType"/>
</{http://www.w3.org/2001/XMLSchema}schema>
<{http://www.w3.org/2001/XMLSchema}schema>
<{http://www.w3.org/2001/XMLSchema}complexType id="123" name="fooType"/>
</{http://www.w3.org/2001/XMLSchema}schema>
<{http://www.w3.org/2001/XMLSchema}schema>
<{http://www.w3.org/2001/XMLSchema}complexType id="123" name="fooType"/>
</{http://www.w3.org/2001/XMLSchema}schema>

对许多 XML 应用程序来说,XML 文档中元素和属性的通用名称非常重要,而用在特定 QName 中的前缀的值并不重要。XML 中的命名空间建议之所以没有使用扩展名称方法来指定命名空间,主要是因为它很冗长。相反,之所以提供前缀映射和默认命名空间,是为了避免我们因不断键入命名空间 URI 而产生腕管综合症。

命名空间和属性

除非属性的名称有前缀,否则命名空间声明不应用于属性。在下面显示的 XML 文档中,title 属性属于 bk:book 元素且没有命名空间,而 bk:title 属性将 urn:xmlns:25hoursaday-com:bookstore 作为其命名空间名称。请注意,即使这两个属性具有相同的本地名称,该文档的结构也是完整的。

<bk:bookstore xmlns:bk="urn:xmlns:25hoursaday-com:bookstore">
<bk:book title="Lord of the Rings, Book 3" bk:title="Return of the King"/>
</bk:bookstore>

在下例中,即使指定了一个默认命名空间,title 属性仍然没有命名空间,且属于 book 元素。换句话说,属性不能继承默认命名空间。

<bookstore xmlns="urn:xmlns:25hoursaday-com:bookstore">
<book title="Lord of the Rings, Book 3" />
</bookstore>

命名空间 URI

按照 RFC 2396 中的规定,命名空间名称是统一资源标识符 (URI)。URI 是统一资源定位器 (URL) 或统一资源名称 (URN)。URL 用于指定资源在 Internet 上的位置,而 URN 应该是信息资源持久的、独立于位置的标识符。对于不同的命名空间名称来说,只有当它们的每个字符(区分大小写)都相同时,才被视为相同。之所以将 URI 用作命名空间名称,主要是因为它们已经提供了一种用来指定全局唯一标识符的机制。

XML 命名空间建议声明,命名空间名称只是充当唯一标识符,而不必实际标识可从网络检索的资源。这就在 XML 文档的作者和用户之间产生了很大的混淆,特别是由于人们已经普遍将基于 HTTP 的 URL 用作命名空间名称。因为许多应用程序都将这样的 URI 转换为超级链接,但是当这些“链接”无法跳转到网页或其他可从网络检索的资源时,会令许多用户感到恼火。我记得有一个用户将这比作在社交场合中得到了一个假电话号码。

为了避免使用户感到混淆,一个解决方案就是使用不暗示资源具有网络可检索性的命名空间-命名架构。在编写供个人使用的 XML 文档时,我使用 urn:xmlns: 方案来实现此目的,并创建了一个类似于 urn:xmlns:25hoursaday-com 的命名空间名称。自建命名空间 URI 有一个问题,那就是它们可能会由于全局不唯一而与 XML 中的名称建议相抵触。我通过将我的个人域名 http://www.25hoursaday.com 用作命名空间 URI 的一部分来满足这个全局唯一要求。

另一个解决方案是将可从网络检索的资源保留在作为命名空间名称的 URI(如用 XSLTRDDL 命名空间实现的 URI)处。通常,这样的 URI 实际上是 HTTP URL。通过使用 W3C 所建议的格式,可以很好地对这样的 URL 进行命名,如下所示:

      http://my.domain.example.org/product/[year/month][/area]

有关将结构类似的命名空间名称用作版本控制机制的详细信息,请参阅“命名空间和版本控制”一节。

有关命名空间的 DOM、XPath 和 XML 信息集

W3C 已经定义了许多可以为 XML 文档提供数据模型的技术。这些数据模型通常是一致的,但是由于历史原因,它们在处理各种边缘案例的方式上有时会有所不同。对 XML 命名空间和命名空间声明的处理就是边缘案例的一个示例,在 W3C 建议中的三个主要数据模型中,该案例会以不同的方式进行处理。这三个数据模型是 XPath 数据模型、文档对象模型 (DOM) 和 XML 信息集。

XML 信息集 (XML infoset) 是对 XML 文档中数据的抽象说明,它可视为 XML 文档的主要数据模型。XPath 数据模型是类似于 XML 信息集的基于树的模型,在查询 XML 文档时会遍历该模型。DOM 优先于 XPath 和 XML 信息集这两种数据模型,但是它在许多方面也与这两个数据模型相似。DOM 和 XPath 数据模型可视为对 XML信息集的解释。

文档对象模型 (DOM) 中的命名空间

DOM Level 3 规范的 XML 命名空间部分将命名空间声明视为以 http://www.w3.org/2000/xmlns/ 作为其命名空间名称的常规属性节点,并将 xmlns 视为它们的前缀或限定名称。

DOM 中的元素和属性有一个无法在创建之后进行修改的命名空间名称,不管它们在文档中的位置是否发生改变。

XPath 数据模型中的命名空间

W3C XPath 建议不将命名空间声明视为属性节点,而且不提供以该资格访问它们的权限。相反,在 XPath 中,XML 文档中的每个元素都有许多可使用 XPath 命名空间导航轴检索的命名空间节点

在文档中每个元素的作用域内,该元素对于每个命名空间声明都有一组唯一的命名空间节点。在该命名空间中,命名空间节点对于每个元素都是唯一的。因此,代表同一个命名空间声明的两个不同元素的命名空间节点是不同的。

XML 信息集中的命名空间

XML 信息集建议将命名空间声明视为属性信息项

另外,与 XPath 数据模型相似的是,XML 文档信息集内的每个元素信息项,对于该元素作用域内每个命名空间都有一个命名空间信息项目。

XPath、XSLT 和命名空间

W3C XML 路径语言(也称为 XPath)用于对 XML 文档的某些部分进行寻址,它可用在许多 W3C XML 技术(包括 XSLT、XPointer、XML 架构和 DOM Level 3)中。XPath 使用类似于文件系统和 URL 中使用的分层寻址机制来检索 XML 文档的某些部分。XPath 支持对字符串、数字和布尔值进行基本操作。

XPath 和命名空间

XPath 数据模型将 XML 文档视为节点(如元素、属性和文本节点)树。在节点树中,每个节点的名称都由其本地名称和命名空间名称(即,它的通用名称或扩展名称)组合而成。

对于没有命名空间的元素和属性节点,执行 XPath 查询是相当简单的。下面的程序可用于从命令行查询 XML 文档,并将用来阐释命名空间对 XPath 查询的影响。

using System.Xml.XPath;
using System.Xml;
using System;
using System.IO;
class XPathQuery{
public static string PrintError(Exception e, string errStr){
if(e == null)
return errStr;
else
return PrintError(e.InnerException, errStr + e.Message );
}
public static void Main(string[] args){
if((args.Length == 0) || (args.Length % 2)!= 0){
Console.WriteLine("Usage: xpathquery source query <zero or more
prefix and namespace pairs>");
return;
}
try{
//Load the file.
XmlDocument doc = new XmlDocument();
doc.Load(args[0]);
//create prefix<->namespace mappings (if any)
XmlNamespaceManager  nsMgr = new XmlNamespaceManager(doc.NameTable);
for(int i=2; i < args.Length; i+= 2)
nsMgr.AddNamespace(args[i], args[i + 1]);
//Query the document
XmlNodeList nodes = doc.SelectNodes(args[1], nsMgr);
//print output
foreach(XmlNode node in nodes)
Console.WriteLine(node.OuterXml + "\n\n");
}catch(XmlException xmle){
Console.WriteLine("ERROR: XML Parse error occured because " +
PrintError(xmle, null));
}catch(FileNotFoundException fnfe){
Console.WriteLine("ERROR: " + PrintError(fnfe, null));
}catch(XPathException xpath){
Console.WriteLine("ERROR: The following error occured while querying
the document: "
+ PrintError(xpath, null));
}catch(Exception e){
Console.WriteLine("UNEXPECTED ERROR" + PrintError(e, null));
}
}
}

假设下面的 XML 文档不声明任何命名空间,则查询相当简单,如以下代码后面的示例所示。

<?xml version="1.0" encoding="utf-8" ?>
<bookstore>
<book genre="autobiography">
<title>The Autobiography of Benjamin Franklin</title>
<author>
<first-name>Benjamin</first-name>
<last-name>Franklin</last-name>
</author>
<price>8.99</price>
</book>
<book genre="novel">
<title>The Confidence Man</title>
<author>
<first-name>Herman</first-name>
<last-name>Melville</last-name>
</author>
<price>11.99</price>
</book>
</bookstore>

示例 1

xpathquery.exe bookstore.xml /bookstore/book/title
            

选择所有作为 book 元素(其父级为 bookstore 元素)子级的标题元素,并返回如下结果:

   <title>The Autobiography of Benjamin Franklin</title>
            <title>The Confidence Man</title>
            
xpathquery.exe bookstore.xml //@genre
            

选择文档中的所有 genre 属性并返回如下结果:

   genre="autobiography"
            genre="novel"
            
xpathquery.exe bookstore.xml //title[(../author/first-name = 'Herman')]
            

选择作者名为 "Herman" 的所有标题并返回如下结果:

   <title>The Confidence Man</title>
            

但是,在将命名空间添加到混合名称中之后,事情不再那么简单。除了向某个 book 元素中添加了命名空间和一个属性以外,下面的文件与原始文件相同。

<bookstore xmlns="urn:xmlns:25hoursaday-com:bookstore">
            <book genre="autobiography">
            <title>The Autobiography of Benjamin Franklin</title>
            <author>
            <first-name>Benjamin</first-name>
            <last-name>Franklin</last-name>
            </author>
            <price>8.99</price>
            </book>
            <bk:book genre="novel" bk:genre="fiction"
            xmlns:bk="urn:xmlns:25hoursaday-com:bookstore">
            <bk:title>The Confidence Man</bk:title>
            <bk:author>
            <bk:first-name>Herman</bk:first-name>
            <bk:last-name>Melville</bk:last-name>
            </bk:author>
            <bk:price>11.99</bk:price>
            </bk:book>
            </bookstore>
            

请注意,默认命名空间位于整个 XML 文档的作用域内,而将前缀 bk 映射到命名空间名称 urn:xmlns:25hoursaday-com:bookstore 的命名空间声明只位于第二个 book 元素的作用域内。

示例 2

xpathquery.exe bookstore.xml /bookstore/book/title
            

选择所有作为 book 元素(其父级为 bookstore 元素)子级的标题元素,这不返回任何结果。

xpathquery.exe bookstore.xml //@genre
            

选择文档中的所有 genre 属性并返回如下结果:

   genre="autobiography"
            genre="novel"
            
xpathquery.exe bookstore.xml //title[(../author/first-name = 'Herman')]
            

选择作者名字为 "Herman" 的所有标题,这不返回任何结果。

第一个查询之所以不返回任何结果,是因为 XPath 查询中不带前缀的名称应用于没有命名空间的元素或属性。在没有命名空间的目标文档中没有 bookstorebooktitle 元素。第二个查询返回没有命名空间的所有属性节点。尽管命名空间声明位于由该查询返回的这两个属性节点的作用域内,但是由于命名空间声明不应用于没有前缀名称的属性,所以它们没有命名空间。出于与第一个查询同样的原因,第三个查询不返回任何结果。

可通过以下方法来执行能够识别命名空间的 XPath 查询:提供一个到 XPath 引擎的前缀-命名空间映射,然后在该查询中使用这些前缀。所提供的前缀不必与目标文档中的命名空间-前缀映射相同,而且它们必须是非空前缀。

示例 3

xpathquery.exe bookstore.xml /b:bookstore/b:book/b:title b urn:xmlns:25hoursaday-com:bookstore
            

选择所有作为 book 元素(其父级为 bookstore 元素)子级的标题元素,并返回如下结果:

    <title xmlns="urn:xmlns:25hoursaday-com:bookstore">The Autobiography of Benjamin Franklin</title>
            <bk:title xmlns:bk="urn:xmlns:25hoursaday-com:bookstore">The Confidence Man</bk:title>
            
xpathquery.exe bookstore.xml //@b:genre b urn:xmlns:25hoursaday-com:bookstore

选择文档中来自 "urn:xmlns:25hoursaday-com:bookstore" 命名空间的所有 genre 属性并返回如下结果:

   bk:genre="fiction"
            
xpathquery.exe bookstore.xml //bk:title[(../bk:author/bk:first-name = 'Herman')] bk urn:xmlns:25hoursaday-com:bookstore
            

选择作者名为 "Herman" 的所有标题并返回如下结果:

   <bk:title xmlns:bk="urn:xmlns:25hoursaday-com:bookstore">The Confidence Man</bk:title>
            

注意 示例 3 与示例 1 和 2 相同,但是它被重写为能够识别命名空间。

有关使用 XPath 的详细信息,请阅读 Aaron Skonnard 的文章 Addressing Infosets with XPath 并查看 ZVON.org XPath tutorial 上的示例。

XSLT 和命名空间

W3C XSL 转换 (XSLT) 建议描述一种基于 XML 的语言,用于将 XML 文档转换为其他 XML 文档。XSLT 转换又称为 XML 样式表,它使用模式 (XPath) 来与目标文档的各个方面进行匹配。在与目标文档中的节点进行匹配时,可以对那些指定成功匹配输出结果的模板进行实例化,并使用这些模板来转换文档。

对命名空间的支持已紧密集成到 XSLT 中,特别是由于 XPath 用于与源文档中的节点进行匹配。在 XSLT 内部的 XPath 表达式中使用命名空间比使用 DOM 方便得多。

随后的示例包含:

一个用于从命令行执行转换的程序。

一个 XSLT 样式表,当它在来自 urn:xmlns:25hoursaday-com:bookstore 命名空间的 bookstore 文档中运行时,打印源 XML 文档中来自 urn:xmlns:25hoursaday-com:bookstore 的所有 title 元素。

得到的输出结果。

程序

Imports System.Xml.Xsl
Imports System.Xml
Imports System
Imports System.IO
Class Transformer
Public Shared Function PrintError(e As Exception, errStr As String) As String
If e Is Nothing Then
Return errStr
Else
Return PrintError(e.InnerException, errStr + e.Message)
End If
End Function 'PrintError
'Entry point which delegates to C-style main Private Function
Public Overloads Shared Sub Main()
Run(System.Environment.GetCommandLineArgs())
End Sub 'Main
Overloads Public Shared Sub Run(args() As String)
If args.Length <> 2 Then
Console.WriteLine("Usage: xslt source stylesheet")
Return
End If
Try
'Create the XslTransform object.
Dim xslt As New XslTransform()
'Load the stylesheet.
xslt.Load(args(1))
'Transform the file.
Dim doc As New XmlDocument()
doc.Load(args(0))
xslt.Transform(doc, Nothing, Console.Out)
Catch xmle As XmlException
Console.WriteLine(("ERROR: XML Parse error occured because " +
PrintError(xmle, Nothing)))
Catch fnfe As FileNotFoundException
Console.WriteLine(("ERROR: " + PrintError(fnfe, Nothing)))
Catch xslte As XsltException
Console.WriteLine(("ERROR: The following error occured while
transforming the document: " + PrintError(xslte, Nothing)))
Catch e As Exception
Console.WriteLine(("UNEXPECTED ERROR" + PrintError(e, Nothing)))
End Try
End Sub
End Class 'Transformer

XSLT 样式表

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:b="urn:xmlns:25hoursaday-com:bookstore">
<xsl:template match="b:bookstore">
<book-titles>
<xsl:apply-templates select="b:book/b:title"/>
</book-titles>
</xsl:template>
<xsl:template match="b:title">
<xsl:copy-of select="." />
</xsl:template>
</xsl:stylesheet>

输出

<?xml version="1.0" ?>
<book-titles xmlns:msxsl="urn:schemas-microsoft-com:xslt"
xmlns:ext="urn:my_extensions" xmlns:b="urn:xmlns:25hoursaday-com:bookstore">
<title xmlns="urn:xmlns:25hoursaday-com:bookstore">The Autobiography of
Benjamin Franklin</title>
<bk:title xmlns="urn:xmlns:25hoursaday-com:bookstore"
xmlns:bk="urn:xmlns:25hoursaday-com:bookstore">The Confidence
Man</bk:title>
</book-titles>

请注意,该样式表中的命名空间声明结束于输出 XML 文档的根节点上。另请注意,XSLT 命名空间未包括在输出 XML 文档中。

从 XSLT 转换的输出结果生成 XSLT 样式表有些麻烦,这是由于处理器必须能够从实际的样式表指令确定输出元素。我发现可以通过两种方法来解决此问题,我将通过显示一些样式表来阐释这两种方法,这些样式表生成下面的 XMLT 样式表作为输出结果。

<xslt:stylesheet version="1.0"
xmlns:xslt="http://www.w3.org/1999/XSL/Transform">
<xslt:output method="text"/>
<xslt:template match="/"><xslt:text>HELLO WORLD</xslt:text></xslt:template>
</xslt:stylesheet>

第一种方法涉及到创建一个包含要创建的样式表的变量,然后结合使用 value-ofdisable-output-escaping 属性来创建该样式表。

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml"  encoding="utf-8"/>
<xsl:variable name="stylesheet">
&lt;xslt:stylesheet version="1.0"
xmlns:xslt="http://www.w3.org/1999/XSL/Transform"&gt;
&lt;xslt:output method="text"/&gt;
&lt;xslt:template match="/"&gt;&lt;xslt:text&gt;HELLO
WORLD&lt;/xslt:text&gt;&lt;/xslt:template&gt;
&lt;/xslt:stylesheet&gt;
</xsl:variable>
<xsl:template match="/">
<xsl:value-of select="$stylesheet" disable-output-escaping="yes" />
</xsl:template>
</xsl:stylesheet>

如果所创建的样式表可以方便地进行分区,以便放在变量中,则第一种方法非常有效。尽管此方法快速方便,但是它还会归入杂项 类别,这在面临需要灵活性的任何情况时往往会变得难以处理。例如,如果在创建新样式表时涉及到创建许多动态文本并且与样式表指令交叉在一起,则相对于上面提到的杂项,以下方法会更适合。

<xslt:stylesheet version="1.0" xmlns:xslt="http://www.w3.org/1999/XSL/Transform"
xmlns:alias="http://www.w3.org/1999/XSL/Transform-alias">
<xslt:output method="xml"  encoding="utf-8"/>
<xslt:namespace-alias stylesheet-prefix="alias" result-prefix="xslt"/>
<xslt:template match="/">
<alias:stylesheet version="1.0">
<alias:output method="text"/>
<alias:template match="/"><alias:text>HELLO
WORLD</alias:text></alias:template>
</alias:stylesheet>
</xslt:template>
</xslt:stylesheet>

上面的文档使用 namespace-alias 指令,将 alias 前缀及其所绑定到的命名空间名称替换为 xslt 前缀及其所绑定到的命名空间名称。

命名空间还用于指定扩展 XSLT 的机制。可以创建执行方式与 XSLT 函数相同的、带有命名空间前缀的函数。同样,某些命名空间中的元素可被视为对 XSLT 的扩展,并且可以像执行转换指令(如 templatecopyvalue-of 等)那样执行。下面是 Hello World 程序的示例,该程序使用基于命名空间的扩展函数来打印签名问候语。

<stylesheet version="1.0"
xmlns="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
xmlns:newfunc="urn:my-newfunc">
<output method="text"/>
<template match="/">
<value-of select="newfunc:SayHello()" />
</template>
<msxsl:script language="JavaScript" implements-prefix="newfunc">
function SayHello() {
return "Hello World";
}
</msxsl:script>
</stylesheet>

XML 命名空间警告

与任何有用的工具一样,XML 中的命名空间可能会被错误地使用,而且它还具有许多微妙之处,可能会产生问题(如果用户没有意识到这些微妙处)。本节重点介绍 XML 命名空间的用户通常会在哪些方面遇到问题或面临误解。

版本控制和命名空间

在实际中,主要有两种机制来创建不同版本的 XML 实例文档。一种方法是像在 XSLT 中那样使用根元素的 version 属性,另一种方法是将元素的命名空间名称用作版本控制机制。目前,基于命名空间的版本控制非常普遍,对于 W3C 尤其如此。W3C 已经将该机制用于各种 XML 技术,包括 SOAP、XHTML、XML 架构和 RDF。对于那些使用命名空间控制版本的文档,其命名空间 URI 通常采用如下格式:

http://my.domain.example.org/product/[year/month][/area]

通过修改后续版本中的命名空间名称来控制 XML 文档的版本有一个主要问题,那就是这意味着处理这些文档的、能够识别 XML 命名空间的应用程序将不再处理这些文档,并且将必须进行升级。这主要有益于版本不经常更改的文档格式,但是如果在更改版本时会修改元素和属性的语义,则会要求所有的处理器不再处理新版本,以防对它们产生错误理解。

另一方面,在许多情况下,让 XML 文档的版本控制机制基于根元素的 version 属性就足够了。version 属性主要有益于文档结构的更改可以向后兼容的情况。在以下情况下,使用 version 属性都是非常明智的选择:

元素和属性的语义将不会被修改。

对文档进行更改时涉及到添加元素和属性,但是很少涉及到删除它们。

应用程序与各种版本的处理软件之间的互操作性是必需的。

这两种版本控制方法不是互斥的,它们可同时使用。例如,XSLT 既使用根元素的 version 属性,又使用版本控制的命名空间 URI。version 属性用于对 XML 文档格式进行递增式向后兼容的更改,而修改命名空间名称是为了对文档的语义进行重大的更改。

文档类型

正如在几个有关各种 XML 相关邮件列表的哲学争论中讨论的那样,术语文档类型 容易引起误解。在许多情况下,根元素的命名空间名称可用于确定如何处理文档,然而,这很难成为一个一般性规则,并且如此声明会违反 XML 命名空间的精神,因为它们在设计上完全是为了让开发人员能够混合和匹配 XML 词汇表。

Rick Jelliffe 有关 XML-DEV 的帖子简明扼要,它抓住了为什么认为根命名空间的 URI 等同于文档类型概念这一问题的本质。这个帖子的本质是,一个 XML 文档可以有许多不同的类型,包括它的文档类型(由它的文档类型定义 (DTD) 指定)、它的 MIME 媒体类型、它的架构定义(由 xsi:schemaLocation 属性指定)、它的文件扩展名以及它的根元素的命名空间名称。因此,在许多情况下,根据用户从哪个角度来检查文档,一个文档将很有可能有许多不同的类型。

RDDL 文档示例,请注意,它的根元素来自 XHTML 命名空间)和批注的映射架构(它们的根元素来自 W3C XML 架构命名空间)是 XML 文档的两个示例,在这些示例中,如果只是查看根元素的命名空间 URI,就会曲解实际文档类型。

一言以蔽之,不能通过查看文档根元素的命名空间 URI 来最终确定文档的类型。一定要进行思考,否则是非常愚蠢的。

命名空间的未来

在 XML 领域中,有许多开发都侧重于处理围绕 XML 命名空间开发而产生的一些问题。首先,当前的 W3C XML 命名空间建议的草案没有为取消对已映射到前缀的命名空间的声明提供机制。W3C XML 命名空间 v1.1 工作草案将提供一种机制来取消对实例文档中前缀-命名空间映射的声明,以纠正这种疏忽。

对于在试图取消引用命名空间 URI 的内容时应当返回什么内容存在着争论,这导致在 XML 领域中引起富有争议的辩论,并且还成为 W3C 的 Technical Architecture Group 目前争论的焦点。当前版本的 XML 命名空间建议不要求命名空间 URI 实际上是可解析的,因为命名空间 URI 应当只是一个用作唯一标识符的命名空间名称,而不是资源在 Internet 上的位置。

Tim Bray(XML 语言XML 命名空间建议最初的编辑之一)已经撰写了一篇详尽的论文,论述有关命名空间 URI 和命名空间文档的问题,可以(或者也许不可以)从 URI 中检索到这些文档。此文包含在创建资源目录描述语言 (RDDL) 时涉及到的许多推理。RDDL 设计用于创建命名空间文档。

Dare Obasanjo 是 Microsoft 的 WebData 组的成员,该小组在 .NET 框架的 System.Xml 和 System.Data 命名空间、Microsoft XML 核心服务 (MSXML) 和 Microsoft 数据访问组件 (MDAC) 中开发组件。

有关本文的任何问题或评论,欢迎张贴到 GotDotNet 上的 Extreme XML 留言板

转到原英文页面

posted @ 2007-03-16 17:38  永不言败  阅读(2319)  评论(0编辑  收藏  举报