使用 xslt 2 从节点生成 xpath

Posted

技术标签:

【中文标题】使用 xslt 2 从节点生成 xpath【英文标题】:Generate xpath from node using xslt 2 【发布时间】:2016-11-30 19:39:16 【问题描述】:

要为 xml 文件中的每个节点生成 xpath 并将此路径作为属性添加到每个节点,我找到了一些帮助 here。 xslt 文件应如下所示:

<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output indent="yes" encoding="UTF-8"/>
  <xsl:strip-space elements="*"/>

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

  <xsl:template match="*">
    <xsl:copy>
      <xsl:attribute name="xpath">
        <xsl:for-each select="ancestor-or-self::*">
          <xsl:value-of select="concat('/',local-name())"/>
          <!--Predicate is only output when needed.-->
          <xsl:if
            test="(preceding-sibling::*|following-sibling::*)[local-name()=local-name(current())]">
            <xsl:value-of
              select="concat('[',count(preceding-sibling::*[local-name()=local-name(current())])+1,']')"
            />
          </xsl:if>
        </xsl:for-each>
      </xsl:attribute>
      <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
  </xsl:template>

  <xsl:template match="text()"/>

</xsl:stylesheet>

现在我对使用 xslt 2.0 的更紧凑的方式感兴趣。例如,在下面的 xslt 文件中,我有两个函数 createXPath 和 getXpath。第一个返回带有节点名称的路径,第二个返回相应的编号。是否有可能以聪明的方式将它们结合起来?

<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:func="http://www.functx.com">
  <xsl:output method="xml" encoding="utf-8"/>

<xsl:template match="@*|node()">
    <xsl:copy>
        <xsl:attribute name="xpath">
            <xsl:value-of select="func:getXpath(.)"/>
        </xsl:attribute>
        <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
</xsl:template>

   <xsl:function name="func:createXPath" >
    <xsl:param name="pNode" as="node()"/>
    <xsl:value-of select="$pNode/ancestor-or-self::*/local-name()" separator="/"/>
  </xsl:function>

  <xsl:function name="func:getXpath">
  <xsl:param name="pNode" as="node()"/>
   <xsl:value-of select="$pNode/ancestor-or-self::*/(count(preceding-sibling::*) + 1)" separator="/" />
</xsl:function> 

</xsl:stylesheet>

【问题讨论】:

【参考方案1】:

结合这两个函数是相当简单的——例如,你可以这样做:

<xsl:function name="func:path" >
    <xsl:param name="target" as="element()"/>
    <xsl:value-of select="for $step in $target/ancestor-or-self::* return concat(name($step), '[', count($step/preceding-sibling::*[name() = name($step)]) + 1, ']')" separator="/"/>
</xsl:function>

但是,这种方法效率很低,因为它必须反复遍历树。请考虑:

<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> 
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>

<xsl:template match="*">
    <xsl:param name="path"/>
    <xsl:variable name="my-path">
        <xsl:value-of select="$path"/>
        <xsl:text>/</xsl:text>
        <xsl:value-of select="name()"/>
        <xsl:text>[</xsl:text>
        <xsl:value-of select="count(preceding-sibling::*[name() = name(current())]) + 1"/>
        <xsl:text>]</xsl:text>
    </xsl:variable>
    <xsl:copy>
        <xsl:attribute name="xpath">
            <xsl:value-of select="$my-path" />    
        </xsl:attribute>
        <xsl:copy-of select="@*"/>
        <xsl:apply-templates>
            <xsl:with-param name="path" select="$my-path"/>
        </xsl:apply-templates>
    </xsl:copy>
</xsl:template>

</xsl:stylesheet>

它利用了 XSLT 的递归处理模型。

【讨论】:

【参考方案2】:

几点。

(a) 没有“节点的 XPath”这样的东西。有些人在使用这样的术语时,会表示像a/b/c 这样的路径,其他人会表示a[1]/b[5]/c[6],而其他人会表示通过在每个级别使用谓词来测试 namespace-uri( )。

(b) XPath 3.0 提供了一个函数 path(),它返回这种类型的 XPath 表达式;它使用 EQName 语法 Qurilocal 来确保元素名称是上下文无关的。

(c) 我获得您正在生成的路径的方法是

<xsl:function name="f:path" as="xs:string">
  <xsl:param name="e" as="element(*)"/>
  <xsl:value-of select="ancestor-or-self::*/concat(
    '/',
    name($e),
    concat('[',f:index($e),']')[. ne '[1]']
  )" separator=""/>
</xsl:function>

<xsl:function name="f:index" as="xs:integer">
  <xsl:param name="e" as="element(*)"/>
  <xsl:sequence select="count(preceding-sibling::*[name()=name($e)])+1"/>
</xsl:function>

然后

<xsl:copy>
  <xsl:attribute name="path" select="f:path(.)"/>
  ....
</xsl:copy>

【讨论】:

我认为你没有测试过这些。 我很少测试代码示例。我认为读者应该理解代码而不只是逐字使用它很重要,留下奇怪的错误有助于确保这一点。

以上是关于使用 xslt 2 从节点生成 xpath的主要内容,如果未能解决你的问题,请参考以下文章

使用 XSLT 基于 ID 从多个 xPath 中选择 XML 节点

从 XML 节点 java 生成/获取 xpath

XSLT/XPath 中的当前节点与上下文节点?

获取最远的祖先节点 -Xpath, XSLT

XSLT 过滤掉节点并生成新的 xml

使用 XSLT 从 XML 生成 EXCEL