XML 命名空间及其如何影响 XPath 和 XSLT

 

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。 了解命名空间的工作原理以及它们如何与依赖命名空间的其他 W3C 技术交互,对于任何使用 XML 的任何人来说都很重要。

本文探讨 XML 命名空间的进出及其对许多支持命名空间的 XML 技术的影响。

XML 命名空间概述

随着 Internet 上的 XML 使用越来越广泛,能够创建可以组合和重复使用的标记词汇表的好处与软件模块的组合和重用方式类似,变得越来越重要。 如果已存在用于描述硬币收藏、程序配置文件或快餐店菜单的明确定义的标记词汇,那么重用它比从头开始设计更有意义。 将多个现有词汇组合在一起以创建其整体大于其各部分之和的新词汇也成为 XML 用户开始需要的一项功能。

但是,相同标记(特别是 XML 元素和属性)来自不同词汇表、不同语义最终出现在同一文档中的可能性成为一个问题。 XML 的扩展性以及它已在 Internet 上广泛使用的事实,排除了仅指定保留元素或属性名称作为此问题的解决方案。

W3C XML 命名空间建议的目标是创建一种机制,在该机制中,XML 文档中来自不同标记词汇的元素和属性可以明确标识和组合,而不会处理随之而来的问题。 XML 命名空间建议提供了一种方法,用于根据处理要求对 XML 文档中的各个项进行分区,而不会对这些项目的命名方式施加不当限制。 例如,名为 <template><output>和 的 <stylesheet> 元素可以出现在 XSLT 样式表中,而对于它们是转换指令还是转换的潜在输出不明确。

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

命名空间声明

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

命名空间声明通常用于将命名空间 URI 映射到特定前缀。 前缀命名空间映射的范围是命名空间声明发生在其上发生的元素及其所有子元素的范围。 以 前缀 xmlns: 开头的属性声明是命名空间声明。 此类属性声明的值应是命名空间 URI,该 URI 是命名空间名称

下面是 XML 文档的示例,其中根元素包含一个命名空间声明,该声明将前缀 bk 映射到命名空间名称 urn:xmlns:25hoursaday-com:bookstore ,其子元素包含一个 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-trackinginv: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 > 
    <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 路径语言建议扩展名称描述为由命名空间名称和本地名称组成的对。 通用名称是詹姆斯·克拉克为描述同一概念而创造的替代术语。 通用名称由大括号中的命名空间名称和本地名称组成。 通过通用名称的镜头查看时,命名空间往往对人们更有意义。 下面是上一示例中的三个 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) 或统一资源名称 (URNs) 。 URL 用于指定 Internet 上资源的位置,而 URNs 应是信息资源的永久性、与位置无关的标识符。 仅当命名空间名称与字符 (区分大小写) 的字符相同时,才被视为相同。 使用 URI 作为命名空间名称的主要理由是它们已经提供了一种用于指定全局唯一标识的机制。

XML 命名空间建议指出,命名空间名称仅充当唯一标识符,不必实际标识网络可检索资源。 这在 XML 文档的作者和用户中引起了很多混淆,特别是因为使用基于 HTTP 的 URL 作为命名空间名称越来越受欢迎。 由于许多应用程序会将此类 URI 转换为超链接,因此这些“链接”不会导致网页或其他网络可检索资源,这令许多用户感到恼火。 我记得有一个用户把它比作在社交场合被打假电话号码。

避免用户混淆的一种解决方案是使用命名空间命名架构,该架构并不意味着资源的网络可检索性。 我个人将 urn:xmlns: 方案用于此目的,并创建类似于 urn:xmlns:25hoursaday-com 创作供个人使用的 XML 文档的命名空间名称。 本土命名空间 URI 的问题在于,它们可能由于不具有全局唯一性而与 XML 中名称建议的意图相反。 我通过使用个人域名 http://www.25hoursaday.com 作为命名空间 URI 的一部分来绕过全局唯一要求。

另一种解决方案是将网络可检索资源保留在作为命名空间名称的 URI 处,例如使用 XSLTRDDL 命名空间。 通常,此类 URI 实际上是 HTTP URL。 命名此类 URL 的一种好方法是使用 W3C 喜欢的格式,如下所示:

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

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

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

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

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

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

DOM 级别 3 规范的 XML 命名空间部分将命名空间声明视为作为命名空间名称、xmlns前缀或限定名称的常规属性节点http://www.w3.org/2000/xmlns/

DOM 中的元素和属性具有命名空间名称,无论它们在文档中的位置是否发生更改,在创建这些元素和属性后,这些命名空间名称都无法更改。

XPath 数据模型中的命名空间

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

文档中的每个元素在该特定元素的范围内,每个命名空间声明都有一组唯一的命名空间节点。 命名空间节点对该命名空间中的每个元素都是唯一的。 因此,表示同一命名空间声明的两个不同元素的命名空间节点 并不 相同。

XML 信息集中的命名空间

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

此外,与 XPath 数据模型类似,XML 文档信息集中的每个 元素信息项 对于元素范围内的每个命名空间都有一个命名空间 信息 项。

XPath、XSLT 和命名空间

W3C XML 路径语言也称为 XPath,用于处理 XML 文档的各个部分,并用于许多 W3C XML 技术,包括 XSLT、XPointer、XML 架构和 DOM 级别 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

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

    选择父元素为 元素的 book 子元素 bookstore 的所有 title 元素,这将返回:

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

    选择文档中的所有 genre 属性并返回:

       genre="autobiography"
       genre="novel"
    
  3. 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

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

    选择父元素是 元素的 book 子元素 bookstore 的所有 title 元素,这将返回 NO RESULTS。

  2. xpathquery.exe bookstore.xml //@genre

    选择文档中的所有 genre 属性并返回:

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

    选择作者名字为“Herman”的所有标题,这将返回“无结果”。

    第一个查询不返回任何结果,因为 XPath 查询中未预设置的名称适用于没有命名空间的元素或属性。 目标文档中没有 bookstore没有命名空间的 、 booktitle 元素。 第二个查询返回没有命名空间的所有属性节点。 尽管命名空间声明在查询返回的两个属性节点的范围内,但它们没有命名空间,因为命名空间声明不适用于具有未预配置名称的属性。 第三个查询不返回任何结果,原因与第一个查询不返回结果的原因相同。

    执行命名空间感知 XPath 查询的方法是为映射到 XPath 引擎的命名空间提供前缀,然后在查询中使用这些前缀。 提供的前缀不需要与目标文档中的前缀映射的命名空间相同,并且必须是非空前缀。

示例 3

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

    选择父元素为 元素的 book 子元素 bookstore 的所有 title 元素,并返回以下内容:

        <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>
    
    
  2. xpathquery.exe bookstore.xml //@b:genre b urn:xmlns:25hoursaday-com:bookstore

    S从文档的“urn:xmlns:25hoursaday-com:bookstore”命名空间中选择所有 genre 属性,该命名空间返回:

       bk:genre="fiction"
    
    
  3. 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>
    
    

    注意 最后一个示例与前面的示例相同,但已重写为命名空间感知。

有关使用 XPath 的详细信息,请阅读 Aaron Skonnard 的文章 使用 XPath 寻址信息集 ,并查看 ZVON.org XPath 教程中的示例。

XSLT 和命名空间

XSLT) 建议 (W3C XSL 转换介绍了一种用于将 XML 文档转换为其他 XML 文档的基于 XML 的语言。 XSLT 转换(也称为 XML 样式表)利用 XPath) (模式来匹配目标文档的各个方面。 匹配目标文档中的节点后,可以实例化指定成功匹配输出的模板并用于转换文档。

对命名空间的支持紧密集成到 XSLT 中,特别是因为 XPath 用于匹配源文档中的节点。 与使用 DOM 相比,在 XSLT 内的 XPath 表达式中使用命名空间要容易得多。

下面的示例包含:

  • 用于从命令行执行转换的程序。
  • 一个 XSLT 样式表,在针对bookstoreurn:xmlns:25hoursaday-com:bookstore命名空间中的title文档运行时,打印源 XML 文档中命名空间中的所有元素urn:xmlns:25hoursaday-com:bookstore
  • 生成的输出。

节目

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 指令将其绑定到xsltalias前缀和命名空间名称替换为前缀及其绑定到的命名空间名称。

命名空间还用于指定 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 中所做的那样,而另一种方法是使用元素的命名空间名称作为版本控制机制。 基于命名空间的版本控制目前非常流行,尤其是 W3C,W3C 已将此机制用于各种 XML 技术,包括 SOAP、XHTML、XML 架构和 RDF。 使用 命名空间进行版本控制的文档的命名空间 URI 通常采用以下格式:

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

通过更改后续版本中的命名空间名称对 XML 文档进行版本控制的主要问题是,这意味着处理文档的 XML 命名空间感知应用程序将不再处理文档,并且必须升级。 这主要适用于版本不常更改的文档格式,但在更改时会更改元素和属性的语义,因此要求所有处理器不再使用较新版本,因为害怕误解它们。

另一方面,在很多情况下,基于根元素上的版本属性的 XML 文档 版本 控制机制就足够了。 当文档结构中的更改向后兼容时, 版本 属性主要有用。 以下情况是使用 版本 属性是明智选择的所有方面:

  • 不会更改元素和属性的语义。
  • 对文档的更改涉及添加元素和属性,但很少删除。
  • 应用程序与各种版本的处理软件之间的互操作性是必需的。

这两种版本控制技术不是互斥的,可以同时使用。 例如,XSLT 在 根元素上使用版本属性,以及 版本控制命名空间 URIversion 属性用于对 XML 文档的格式进行向后兼容的增量更改,而更改命名空间名称是对文档语义的重大更改。

文档类型

术语 “文档类型 ”具有误导性,正如有关各种 XML 相关邮件列表的几场哲学辩论所讨论的那样。 在许多情况下,根元素的命名空间名称可用于确定如何处理文档,但是,这不是一般规则,因此它违反了 XML 命名空间的精神,因为它们的设计完全是为了开发人员可以混合和匹配 XML 词汇。

Rick Jelliffe 关于 XML-DEV 的这篇文章是一篇简洁的帖子,它捕捉了为什么认为根元素命名空间 URI 等效于文档类型的概念的本质。 本文的实质是,XML 文档可以具有许多不同的类型,包括其 文档 类型定义 (DTD) 指定的文档类型、其 MIME 媒体类型由 xsi:schemaLocation 属性指定的架构定义、其文件扩展名及其根元素的命名空间名称。 因此,在许多情况下,文档很可能具有许多不同的类型,具体取决于在检查文档时决定采用的视角。

通过查看根元素的命名空间 URI,实际文档类型的两个 XML 文档示例是 RDDL 文档 (示例,请注意,其根元素来自 XHTML 命名空间) 和 带批注的映射架构,其根元素来自 W3C XML 架构命名空间。

简言之,无法通过查看其根元素的命名空间 URI 来确定文档的类型。 否则想是愚蠢的。

命名空间未来

XML 世界中有许多开发侧重于解决围绕 XML 命名空间开发的一些问题。 首先,W3C XML 命名空间建议的当前草稿不提供取消声明已映射到前缀的命名空间的机制。 W3C XML 命名空间 v1.1 工作草案 旨在通过提供在实例文档中取消声明前缀命名空间映射的机制来纠正这种监督。

关于尝试取消引用命名空间 URI 内容时应返回的内容的争论在 XML 世界中引起了争议,目前是 W3C 技术体系结构组讨论的重点。 当前版本的 XML 命名空间建议并不要求命名空间 URI 实际上可解析,因为命名空间 URI 应只是用作唯一标识符的命名空间名称,而不是资源在 Internet 上的位置。

Tim Bray (XML 语言XML 命名空间 建议的原始编辑之一,) 编写了 一篇详尽的论文 ,内容涉及命名空间 URI 和命名空间文档,这些文档可能检索到或可能不会从中检索到。 本文档包含他创建资源目录说明语言 (RDDL) 背后的许多原因,该语言旨在用于创建命名空间文档。

Dare Obasanjo 是 Microsoft WebData 团队的成员,除其他事项外,该团队在 .NET Framework 的 System.Xml 和 System.Data 命名空间中开发组件、Microsoft XML 核心服务 (MSXML) ,以及 Microsoft 数据访问组件 (MDAC) 。

请随时在 GotDotNet 上的 Extreme XML 消息板上 发布有关本文的任何问题或评论。