XPath 技巧

借力 5 个关于在 XSLT 中使用 XPath 的技巧

在本文的 5 个技巧中体验使用 XPath。发现关于二进制逻辑的惊人事实,了解 XPath position() 函数的值如何根据上下文而变化。另一个技巧展示如何使用 XPath 来选择给定名称的第一个元素。最后,学习如何调试一个最常见、最困难的故障。

Doug Domeny, 高级软件工程师, Freelance

Doug Domeny 使用 XSLT、W3C XML Schema、DHTML、JavaScript、jQuery、正则表达式和 CSS 编写开发了基于浏览器的、多语言的、对业务用户友好的 XML 编辑器。他持有韦纳姆戈登学院的计算机科学和数学学士学位,曾多年服务于 OASIS 技术委员会,比如 XML Localization Interchange File Format (XLIFF) 和 Open Architecture for XML Authoring and Localization (OAXAL)。在作为软件工程师期间,他发展了在软件工程设计和架构、UI 设计和技术写作方面的重要技能。



2011 年 7 月 04 日

本文提供 5 个关于在工作中使用 XPath 的技巧 — 都来自实际的应用程序,对于这些应用程序,花了很多时间深入研究 XPath 的一些混乱的、非预期的行为:

  • False 有时是 true。
  • XPath 表达式 (x != y)not(x = y) 是不同的,结果也不同。
  • XPath position() 函数的值随着它的上下文而改变。
  • 利用 XPath 选择一个给定名称的第一个元素。
  • 调试因默认名称空间而失败的 XPath select 表达式。

利用这些技巧,可以少花很多时间在迷茫中摸索。

常用缩写词

  • API:应用程序编程接口
  • W3C:万维网联盟
  • XHTML:可扩展超文本标记语言
  • XML:可扩展标记语言
  • XSLT:可扩展样式表语言转换

技巧 1:False 有时是 true

长度大于 0 的字符串在解析为 Boolean 函数时,总是被判断为 True,即使它的值是字符串 'false' 时亦是如此。

在 XPath 表达式中,比较字符串和 Boolean 函数会得到非预期的结果。为了避免出现麻烦,不要将字符串与内置的 Boolean 函数 true()false() 进行比较。相反,应该将字符串与字符串值 'true''false' 比较。保持 Boolean 函数 true()false() 为 Booleans 值,不让它们被意外地转换成字符串,是很困难的。非空字符串被认为是 True,即使它的值是 'false' 时亦是如此;因此,boolean('false') = true()boolean('true') = true() 都成立。只有空字符串 ('') 是 False — 即 boolean('') = false()

考虑 Boolean 函数转换成字符串以及再转换回来时会出现的非预期结果。首先,Boolean 被转换成字符串(例如,string(false()) = 'false')。当转换回来时,如 boolean(string(false())) 中所示,结果是 true()

但是 string() 函数并不是将 Boolean 函数转换成字符串的惟一方法。结果树片段(Result tree fragment,RTF)— 有时是不可避免的— 是字符串。例如,来看一下 清单 1,这是一个内容为文档及其名称、发布年份和页数的 XML 文档。

清单 1. XML 源文档
<Documents>
  <Document id="18396">
    <Name>Knowing the Good</Name>
    <Year>2010</Year>
    <Pages>35</Pages>
  </Document>
  <Document id="18397">
    <Name>Beyond Standards: Best Practices</Name>
    <Year>2011</Year>
    <Pages>12</Pages>
  </Document>
  <Document id="18398">
    <Name>101 Ways to Do the Same Thing</Name>
    <Year>2011</Year>
    <Pages>50</Pages>
  </Document>
</Documents>

清单 2 是一个 XSLT 模板,用于搜索不少于 20 页的最近文档。可是,该模板产生了不正确的结果。

清单 2. 错误的 XSLT 模板
<xsl:template match="Document">
  <xsl:variable name="recentFullLengthPublications">
    <xsl:choose>
      <xsl:when test="Year=2011 and Pages &gt;= 20">
        <xsl:value-of select="true()"/>
      </xsl:when>
      <xsl:otherwise>
        <xsl:value-of select="false()"/>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:variable>
  <xsl:if test="$recentFullLengthPublications">
    <xsl:copy-of select="Name"/>
  </xsl:if>
</xsl:template>

转换返回 清单 3 中的 XML,是不正确的。应该只返回一个元素 — 即 “101 Ways to Do the Same Thing”。但是,实际上所有 3 个元素都返回了。

清单 3. 不正确的结果
<Name>Knowing the Good</Name>
<Name>Beyond Standards: Best Practices</Name>
<Name>101 Ways to Do the Same Thing</Name>

结果树片段 (XSLT 1.0)

用 XSLT 表达的转换描述关于将源树转换为结果树的规则。模板针对特定的源元素被实例化,以创建结果树的部分。模板被实例化时,每个指令被执行,并被它创建的 RTF 取代。一个变量可能与一个 RTF 绑定。一个 RTF 被等价地当作一个包含单个根节点的节点集;但是,只有允许在字符串上执行的操作才被允许在 RTF 上执行。(摘自 “XSL Transformations (XSLT) Version 1.0”,参见 参考资料 中的链接。)

名为 “101 Ways to Do the Same Thing” 的文档是 2011 年惟一一个不少于 20 页的文档,那么哪里出错了呢?变量的内容产生一个 RTF。这个片段被判断为字符串,即使它的值是 Boolean 函数。

清单 2 中 的 <xsl:if test="$recentFullLengthPublications"> 中的测试表达式是不正确的。该测试总是为 True;源文档中的所有 <Name> 元素都被返回。甚至 <xsl:if test="$recentFullLengthPublications=true()"> 中的测试表达式也是不正确的。变量 $recentFullLengthPublications 将是字符串 'true' 或者 'false',不是 Boolean 数据类型。因此,语句 <xsl:if test="$recentFullLengthPublications='true'">确实 能够工作。

但是,模板有些混乱,因为它使用了 true()false() 函数,它们俩在本例中是有误导性的。清单 4 中的模板既正确,又更加可读。它使用 'yes''no' 取代 'true''false',以避免与 Boolean 类型混淆。记住用单引号括住 'yes''no'

清单 4. 正确的 XSLT 模板
<xsl:template match="Document">
  <xsl:variable name="recentFullLengthPublications">
    <xsl:choose>
      <xsl:when test="Year=2011 and Pages &gt;= 20">
        <xsl:value-of select="'yes'"/>
      </xsl:when>
      <xsl:otherwise>
        <xsl:value-of select="'no'"/>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:variable>
  <xsl:if test="$recentFullLengthPublications='yes'">
    <xsl:copy-of select="Name"/>
  </xsl:if>
</xsl:template>

当心 Boolean 函数 true()false()。在这个简单的例子中,不必使用变量,若使用的话,应该定义为具有 select 属性。带有 select 属性的结果不被自动转换成字符串;因此,Boolean 值保持为 Boolean 值。select 属性不返回 RTF。

通常,条件对于 select 属性来说太复杂了,您必须将变量定义为 RTF。对于这个简单的例子,清单 5清单 6 中的模板都是首选的。在 清单 5 中,测试不需要与字符串进行比较,因为变量返回一个 Boolean 类型而不是 RTF 作为 select 属性的结果。

清单 5. 改进的简单 XSLT 模板
<xsl:template match="Document">
  <xsl:variable name="recentFullLengthPublications" 
        select="Year=2011 and Pages &gt;= 20" />
  <xsl:if test="$recentFullLengthPublications">
    <xsl:copy-of select="Name"/>
  </xsl:if>
</xsl:template>

另外,您也可以不使用变量,如 清单 6 中所示,因为条件很简单。但是,变量是自我注解的(self-documenting)。

清单 6. 备选的简单 XSLT 模板
<xsl:template match="Document">
  <xsl:if test="Year=2011 and Pages &gt;= 20">
    <xsl:copy-of select="Name"/>
  </xsl:if>
</xsl:template>

技巧 2:知道 (x != y) 与 not(x = y) 之间的区别

XPath 表达式 (x != y)not(x = y) 是不同的。

简单总结如下:

  • (x != y) 选择既带有 x 又带有 y 但是二者值不相同的节点。这个表达式比 not(x = y) 限制更严格,因为它选择的节点必须具有 xy
  • not(x = y) 选择缺少 xy 二者之一、或者具有 xy 但是二者值不相同的节点。该表达式的限制更宽松。

对于大多数计算机语言,表达式 x != ynot(x = y) 是等价的;但是对于 XML,值是不明确的,因为缺失一个节点。比较 XML 节点值时,问题出现了:缺少的值应该如何被评估?对于一个简单的等价性比较,考虑 x = y:如果 xy 二者之一缺失,那么结果是 false。另一个问题是,表达式应该如何被否定?下面的例子回答了这个问题。

清单 7 是一个关于文档的 XML 文档。可以查询数据,以找到在给定年份发布的或者页数在某个范围内的文档。最新的文档 —“Prospective Projects”— 不具有年份或页数。该文档是不完全的,因此还没有发布。

清单 7. XML 源文档
<Documents>
  <Document id="18396">
    <Name>Knowing the Good</Name>
    <Year>2010</Year>
    <Pages>35</Pages>
  </Document>
  <Document id="18397">
    <Name>Beyond Standards: Best Practices</Name>
    <Year>2011</Year>
    <Pages>12</Pages>
  </Document>
  <Document id="18398">
    <Name>101 Ways to Do the Same Thing</Name>
    <Year>2011</Year>
    <Pages>50</Pages>
  </Document>
  <Document id="18399">
    <Name>Prospective Projects</Name>
  </Document>
</Documents>

XPath 表达式 //Document[Year = 2011]/Name 选择在 2011 年发布的文档。清单 8 显示,结果是两个文档。

清单 8. 2011 年发布的文档
<Name>Beyond Standards: Best Practices</Name>
<Name>101 Ways to Do the Same Thing</Name>

接下来,考虑如何选择不是 2011 年发布的文档。存在两种否定 XPath 表达式的方式:not()!=。利用 XPath 表达式 //Document[not(Year = 2011)]/Name,可选择所有不在 2011 文档集中的文档。清单 9 显示,结果是另外两个文档。

清单 9. 不是 2011 年发布的文档
<Name>Knowing the Good</Name>
<Name>Prospective Projects</Name>

要选择所有不是 2011 年发布的文档,可以使用表达式 //Document[Year != 2011]/Name。结果是 清单 10,其中只展示了单个文档。

清单 10. 不是 2011 年发布的文档
<Name>Knowing the Good</Name>

如上所示,not(Year = 2011)Year != 2011 这两个 XPath 表达式具有稍微不同的含义。您使用 XPath 表达式 //Document[Pages >= 20]/Name 来获得所有不少于 20 页的文档。结果是 清单 11,其中展示了两个匹配条件的文档。

清单 11. 长文档
<Name>Knowing the Good</Name>
<Name>101 Ways to Do the Same Thing</Name>

接下来,考虑相反的情况:少于 20 页的文档。表达式 //Document[Pages < 20]/Name 选择这些文档。惟一匹配的文档是 清单 12 中的结果。

清单 12. 短文档
<Name>Beyond Standards: Best Practices</Name>

最后,注意 "Prospective Projects" 不具有已知的页数。包括用 not()而不是 != 得到的文档 — 例如,//Document[not(Pages >= 20)]/Name。结果是 清单 13,带有两个文档。

清单 13. 少于 20 页或者未知页数的文档
<Name>Beyond Standards: Best Practices</Name>
<Name>Prospective Projects</Name>

要得到未知页数的文档列表,可使用 XPath 表达式 //Document[not(Pages)]/Name清单 14 展示了匹配条件的文档。

清单 14. 未知页数的文档
<Name>Prospective Projects</Name>

not() 函数对于处理带有缺失属性或元素的节点非常重要。参见 参考资料 中的链接,了解关于 XPath 的更多技巧。


技巧 3:position() 的值取决于它用在哪里

position() 函数的值取决于它的上下文 — 它用在哪里。当它用在谓词中时,尤其存在这种依赖性。

position() 函数返回节点集中节点的基于 1 的索引。position() 函数计数节点 —元素和文本节点— 即使文本只是空白。下面的例子演示了几种不同上下文中的 position()

清单 15 是一个内容为文档及作者的 XML 文档。有趣的是 <Name> 元素。

清单 15. XML 源文档
<Documents>
  <Document id="18396">
    <Name>Knowing the Good</Name>
    <Authors>
      <Name>Bob Bloggs</Name>
      <Name>Kim Bergess</Name>
    </Authors>
  </Document>
  <Document id="18397">
    <Name>Beyond Standards: Best Practices</Name>
    <Authors>
      <Name>Bill Agnew</Name>
    </Authors>
  </Document>
  <Document id="18398">
    <Name>101 Ways to Do the Same Thing</Name>
    <Authors>
      <Name>Jay Nerks</Name>
      <Name>Paula Towne</Name>
      <Name>Carol Tinney</Name>
    </Authors>
  </Document>
</Documents>

清单 16 中的 XSLT 模板显示了每个 <Name> 元素的位置。清单 17 展示了结果。

清单 16. 使用 position() 的 XSLT 模板
<xsl:template match="Name">
  <xsl:value-of select="concat(position(),' ',.,'&#10;')"/>
</xsl:template>

第一列是 position() 函数的值。清单 17 展示了 XSLT 模板转换的结果。

清单 17. 清单 16 的结果
2 Knowing the Good
2 Bob Bloggs
4 Kim Bergess
2 Beyond Standards: Best Practices
2 Bill Agnew
2 101 Ways to Do the Same Thing
2 Jay Nerks
4 Paula Towne
6 Carol Tinney

结果列出了 <Name> 元素在模板上下文中的位置。即使大多数 name 元素是它们父元素中的第一个元素,它们其实是第二个节点,因为标记之间存在一个空文本节点(包含一个换行)。此外,位置是相对于它的父元素,而不是整个文档。

清单 18 中的模板将 <Name> 元素从文本节点中分离出来。

清单 18. 两个 XSLT 模板
<xsl:template match="Documents">
  <xsl:apply-templates select="//Name"/>
</xsl:template>

<xsl:template match="Name">
  <xsl:value-of select="concat(position(),' ',.,'&#10;')"/>
</xsl:template>

空白文本节点被省略后的结果是 清单 19position() 函数的值显示在第一列中。

清单 19. 清单 18 的结果
1 Knowing the Good
2 Bob Bloggs
3 Kim Bergess
4 Beyond Standards: Best Practices
5 Bill Agnew
6 101 Ways to Do the Same Thing
7 Jay Nerks
8 Paula Towne
9 Carol Tinney

第一个模板选择并处理所有的 <Name> 元素。由于所有 9 个 <Name> 元素被选择为一个集合,所以 position() 是该集合中一个元素的索引。

在下一个例子中,position() 函数用在 xsl:apply-templates 语句的谓词中,选择具有位置 1 的名称。参见 清单 20

清单 20. 在谓词中使用 position() 的模板
<xsl:template match="Documents">
  <xsl:apply-templates select="//Name[position()=1]"/>
</xsl:template>

<xsl:template match="Name">
  <xsl:value-of select="concat(position(),' ',.,'&#10;')"/>
</xsl:template>

结果是 清单 21。注意,只返回了 6 个条目。第一列是 position() 函数的值,这些 Name 元素是它们父元素中的第一个元素。

清单 21. 清单 20 的结果
1 Knowing the Good
2 Bob Bloggs
3 Beyond Standards: Best Practices
4 Bill Agnew
5 101 Ways to Do the Same Thing
6 Jay Nerks

注意,只包括第一个作者。当您在谓词 — 即方括号 ([]) — 中使用 position() 时,索引是相对于 <Name> 元素的父元素的。只返回 <Authors> 元素中的第一个 <Name> 元素。作为一种快捷方式,Name[position()=1] 可以表达为 Name[1]

清单 22 包含一个模板,其中 position() 函数放置在模板的 match 表达式而不是 xsl:apply-templates 语句中。第三个模板丢弃不在位置 1 的 Name 元素。

清单 22. 使用 position() 匹配谓词
<xsl:template match="Documents">
  <xsl:apply-templates select="//Name"/>
</xsl:template>

<xsl:template match="Name[position()=1]">
  <xsl:value-of select="concat(position(),' ',.,'&#10;')"/>
</xsl:template>

<xsl:template match="Name" />

清单 23 中提供的结果是跟前面相同的 6 个元素,但是位置不同。

清单 23. 清单 22 的结果
1 Knowing the Good
2 Bob Bloggs
4 Beyond Standards: Best Practices
5 Bill Agnew
6 101 Ways to Do the Same Thing
7 Jay Nerks

清单 23 中的名称与 清单 21 中的相同,但是位置值不同。所有 9 个名称都应用于模板,但是只有 6 个匹配,而 清单 20 中,只有 6 个应用于模板。作为一种快捷方式,Name[position()=1] 可以表达为 Name[1]

清单 24 展示了只处理 XML 文档中第一个 <Name> 元素的模板。

清单 24. 在应用之前过滤后的元素
<xsl:template match="Documents">
  <xsl:apply-templates select="(//Name)[1]"/>
</xsl:template>

<xsl:template match="Name">
  <xsl:value-of select="concat(position(),' ',.,'&#10;')"/>
</xsl:template>

清单 25 展示了单个结果。

清单 25. 清单 24 的结果
1 Knowing the Good

select 属性中使用的括号 (()) 对于正确的行为很重要。谓词 [1][position()=1] 相同。

检查第二个模板而不是第一个模板中的位置会将决策从模板调用者转移到模板本身,如 清单 26 中所示。

清单 26. 应用之后过滤后的元素
<xsl:template match="Documents">
  <xsl:apply-templates select="//Name"/>
</xsl:template>

<xsl:template match="Name">
  <xsl:if test="position()=1">
    <xsl:value-of select="concat(position(),' ',.,'&#10;')"/>
  </xsl:if>
</xsl:template>

位置检查已经移动到模板本身中。该检查是 xsl:if 语句而不是 match 表达式中谓词的一部分。如果谓词在模板的 match 表达式(即 <xsl:template match="Name[1]">)中,那么结果是 清单 23 中的元素。清单 26 的结果在 清单 27 中给出。

清单 27. 清单 26 的结果
1 Joe Bloggs

清单 28 包含一个模板,它忽略会影响 清单 17<Name> 元素的位置的那些空白文本节点。

清单 28. 忽略文本节点的模板
<xsl:template match="*">
  <xsl:apply-templates select="*"/>
</xsl:template>

<xsl:template match="Name">
  <xsl:value-of select="concat(position(),' ',.,'&#10;')"/>
</xsl:template>

清单 29 展示了忽略文本节点后的结果。

清单 29. 清单 28 的结果
1 Knowing the Good
1 Bob Bloggs
2 Kim Bergess
1 Beyond Standards: Best Practices
1 Bill Agnew
1 101 Ways to Do the Same Thing
1 Jay Nerks
2 Paula Towne
3 Carol Tinney

position() 函数返回不同的结果,取决于它使用在哪里以及它是在模板的 match 表达式中、谓词中,还是 xsl:apply-templates 的选定节点集中。


技巧 4:选择第一个具有某名称的元素

使用 //Name[not(preceding::Name)](这对于模板匹配是必需的)或者 (//Name)[1](使用在 select 属性中),您可以选择 XML 文档中第一个具有名称 Name 的元素。对于选择元素集合中的第一个元素,最容易的方法是利用值 1 的谓词。例如,清单 30 是一个 XML 文档,内容为文档及其名称、发布年份和页数。

清单 30. XML 源文档
<Documents>
  <Document id="18396">
    <Name>Knowing the Good</Name>
    <Year>2010</Year>
    <Pages>35</Pages>
  </Document>
  <Document id="18397">
    <Name>Beyond Standards: Best Practices</Name>
    <Year>2011</Year>
    <Pages>12</Pages>
  </Document>
  <Document id="18398">
    <Name>101 Ways to Do the Same Thing</Name>
    <Year>2011</Year>
    <Pages>50</Pages>
  </Document>
</Documents>

XPath 表达式 //Document[1]— 或者 /Documents/Document[1]— 选择第一个文档元素。结果是 清单 31

清单 31. 第一个文档
<Document id="18396">
  <Name>Knowing the Good</Name>
  <Year>2010</Year>
  <Pages>35</Pages>
</Document>

XPath 表达式选择 <Document> 元素列表,然后返回第一个元素。谓词 [1] 表示 “具有位置 1 的元素”。另一种表达这一含义的方式是 //Document[position()=1]

接下来,考虑如何选择第一个文档的名称<Name> 元素是 <Document> 元素的一个子节点。通过将其标记名称附加到 XPath — 即 //Document[1]/Name,这返回第一个 <Document> 元素的 <Name> 元素 — 而选择该子节点 。结果是 清单 32

清单 32. 第一个文档的 Name 元素
<Name>Knowing the Good</Name>

XPath 表达式 //Name[1] 选择第一个 <Name> 元素,无论它位于 XML 文档中的何处;但是,这里有一个问题。清单 33 展示了非预期的结果。返回了三个而不是一个元素。

清单 33. 非预期的结果
<Name>Knowing the Good</Name>
<Name>Beyond Standards: Best Practices</Name>
<Name>101 Ways to Do the Same Thing</Name>

XPath 1.0 轴

XPath 定义 XML 文档的导航方向。文档中的元素形成一个节点树。从树中的给定节点出发,有四个基本方向:向上指向父元素链、向下指向子节点、向前指向文档中的下一个兄弟节点和向后指向前一个兄弟节点。诸如下面列表中的轴创建文档中节点的子集:

  • ancestor父亲,父亲的父亲,等等。
  • ancestor-or-self上下文节点及其祖先。
  • attribute元素的属性。
  • child直接孩子。
  • descendant孩子,孩子的孩子,等等。
  • descendant-or-self上下文节点及其后代。
  • following按照文档顺序,上下文节点后面的所有节点。
  • following-sibling上下文节点后面的所有兄弟节点。
  • namespace上下文元素的名称空间节点。
  • parent上下文节点的父亲。
  • preceding按照文档顺序,上下文节点前面的所有节点。
  • preceding-sibling上下文节点前面的所有兄弟节点。
  • self上下文节点本身。

只预期一个元素,却返回三个元素。每个<Document> 元素中的第一个 <Name> 元素被选中。如果一个 <Document> 元素包含不止一个 <Name> 元素,那么只会返回第一个。

您如何选择 XML 文档中的第一个 <Name> 元素取决于您如何使用 XPath。在一个 XSLT 模板 match 属性中,选择比利用 select 属性更加有限。对于 match XPath 表达式,使用 XPath 轴 preceding 来选择第一个元素。清单 34 演示了该表达式。

清单 34. 使用 preceding 轴的模板匹配
<xsl:template match="//Name[not(preceding::Name)]">

预期的结果是第一个元素,展示在 清单 35 中。

清单 35. 清单 34 中模板的结果
<Name>Knowing the Good</Name>

该表达式的含义是 “选择 XML 文档中较早的不具有 <Name> 元素的 <Name> 元素”。

对于 select 表达式,比如说 xsl:apply-templates 语句,XPath 更加灵活。尽管 清单 34 中的表达式能够工作,但是使用慎重应用的括号则更为清晰,并且容易改编来选择第二个、第三个或者任何其他索引。参见 清单 36

清单 36. 使用 preceding 轴的模板匹配
<xsl:apply-templates select="(//Name)[1]"/>

结果展示在 清单 37 中,这与 清单 35 相同。

清单 37. 清单 36 中模板的结果
<Name>Knowing the Good</Name>

该表达式选择所有的 <Name> 元素,然后返回第一个元素。

选择想要的结果时,括号和轴的慎重放置是至关重要的。


技巧 5:处理不能匹配具有默认名称空间的文档的 XPath 选择表达式

XSLT 的默认名称空间不适用于它的 XPath 表达式。存在三种可能的解决方案:

  • 删除 XML 文档的默认名称空间。
  • 向 XPath 表达式添加名称空间前缀。
  • 进行预处理,以删除名称空间。

选项 1:删除 XML 文档的默认名称空间

如果 XML 文档不能修改,则删除默认的名称空间。清单 38 是一个带有默认名称空间的 XHTML 文档 — 根据定义,是一个 XML 文档。

清单 38. 带有默认名称空间的 XHTML 文档
<?xml version="1.0"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <title>This is the title</title>
  </head>
  <body>
    <p>Hello world.</p>
  </body>
</html>

选项 2:向 XPath 表达式添加名称空间前缀

清单 39 是一个 XSLT,其中 XML 文档的默认名称空间被分配一个前缀。为本例选择了前缀 xhtml,但是任何前缀都是可接受的。

清单 39. 使用名称空间前缀的 XSLT
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
    xmlns:xhtml="http://www.w3.org/1999/xhtml" 
    xmlns="http://www.w3.org/1999/xhtml" 
    exclude-result-prefixes="xhtml" >
  <xsl:output method="xml" version="1.0"/>
  <xsl:template match="xhtml:title">
    <title>Replaced with a new title</title>
  </xsl:template>

  <xsl:template match="@*|node()">
    <xsl:copy>
      <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
  </xsl:template>
</xsl:stylesheet>

清单 40 展示了生成的 XHTML 文档。<title> 元素中的文本已经被正确替换。通过在 XSLT 中使用 exclude-result-prefixes 属性,xhtml: 前缀已从转换的输出文档中排除。

清单 40. 转换的 XHTML 文档
<?xml version="1.0"?>
<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <title>Replaced with a new title</title>
  </head>
  <body>
    <p>Hello world.</p>
  </body>
</html>

选项 3:进行预处理,以删除名称空间

利用一个 XSLT 对 XML 文档进行预处理以删除其中的名称空间也是一种解决方案,但是这样做可能比较费时间。清单 41 中的 XSLT 复制源 XML 文档中的文本、元素和属性节点,但是忽略名称空间节点和 DOCTYPE 声明。

清单 41. 用于删除名称空间节点的 XSLT
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="xml" version="1.0"/>

  <!-- copy elements without namespace -->
  <xsl:template match="*">
    <xsl:element name="{name()}">
      <xsl:apply-templates select="node()|@*"/>
    </xsl:element>
  </xsl:template>

  <xsl:template match="@*|text()|processing-instruction()|comment()">
    <xsl:copy>
      <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
  </xsl:template>
</xsl:stylesheet>

这个两段式方法转换文档两次,结果是第二个 XSLT 转换第一个 XSLT 得到的结果。有关多阶段转换的更多信息,参见 参考资料

选项 1(如果您可以应用它的话)是最容易、最简单的解决方案。如果 XML 文档一直具有默认的名称空间(通常有这种情况),那么 选项 2 是可能的。尽管向 XPath 表达式添加名称空间前缀需要进行一点额外的录入,但是该解决方案可行,且不增加额外的处理。选项 3 处理 XML 文档两次,因此也更慢。但是,不必关心源 XML 文档是否具有默认的名称空间。


结束语

XPath 表达式中的二进制逻辑比简单的 true 和 false 要稍微复杂一点。当比较等于或不等于时,值的缺少解析为 False。当解释为 Boolean 值时,Boolean 值到字符串的转换被评估为 True。第三个技巧解释了 position() 函数取决于上下文的相对特征,第四个技巧展示了如何使用 XPath preceding 轴或者谓词,来选择列表中某个给定名称的第一个元素。最后,失败 XPath select 表达式的常见问题被识别为 XML 文档中默认名称空间的结果,并给出了几个可能的解决方案。

XPath 是一种强大的用于选择 XML 文档中节点的函数式语言。但是跟所有计算机语言一样,XPath 也有学习曲线。体验这些例子,获得更深入的理解。

参考资料

学习

获得产品和技术

讨论

  • XML 专区讨论论坛:参与任何一个 XML 相关讨论。
  • 加入 developerWorks 中文社区,developerWorks 社区是一个面向全球 IT 专业人员,可以提供博客、书签、wiki、群组、联系、共享和协作等社区功能的专业社交网络社区。

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=XML
ArticleID=696977
ArticleTitle=XPath 技巧
publish-date=07042011