2

给定一个 xpath 语句列表,我想编写一个样式表,它将贯穿一个 xml 文档并输出相同的文档,但在每个 xpath 语句中标识的节点之前插入一个注释。让我们举个例子。从包含 xpath 语句的 xml 实例开始:

<paths>
  <xpath location="/root/a" annotate="1"/>
  <xpath location="/root/a/b" annotate="2"/>
</paths>

给定输入:

<root>
  <a>
    <b>B</b>
  </a>
  <c>C</c>
</root>

它应该产生:

<root>
  <!-- 1 -->
  <a>
    <!-- 2 -->
    <b>B</b>
  </a>
  <c>C</c>
</root>

我最初的想法是有一个带有file-list参数的身份样式表,调用document它上面的函数来获取 xpath 节点的列表。然后它会根据该列表检查输入的每个节点,然后在找到一个时插入注释节点,但我希望随着 xpaths 列表变大(或者可能不是,告诉我。我正在使用撒克逊 9)。

所以我的问题是:有没有一种有效的方法来做这样的事情?

4

3 回答 3

3

假设 Saxon 9 PE 或 EE,也应该可以使用 XSLT 3.0 和xsl:evaluate如下:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:math="http://www.w3.org/2005/xpath-functions/math"
    xmlns:map="http://www.w3.org/2005/xpath-functions/map"
    xmlns:mf="http://example.com/mf"
    exclude-result-prefixes="xs math map mf"
    version="3.0">

    <xsl:output indent="yes"/>

    <xsl:param name="paths-url" as="xs:string" select="'paths1.xml'"/>
    <xsl:param name="paths-doc" as="document-node()" select="doc($paths-url)"/>

    <xsl:variable name="main-root" select="/"/>

    <xsl:variable 
        name="mapped-nodes">
        <map>
            <xsl:for-each select="$paths-doc/paths/xpath">
                <xsl:variable name="node" as="node()?" select="mf:evaluate(@location, $main-root)"/>
                <xsl:if test="$node">
                    <entry key="{generate-id($node)}">
                        <xsl:value-of select="@annotate"/>
                    </entry>
                </xsl:if>
            </xsl:for-each>
        </map>
    </xsl:variable>

    <xsl:key name="node-by-id" match="map/entry" use="@key"/>

    <xsl:function name="mf:evaluate" as="node()?">
        <xsl:param name="path" as="xs:string"/>
        <xsl:param name="context" as="node()"/>
        <xsl:evaluate xpath="$path" context-item="$context"></xsl:evaluate>
    </xsl:function>

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

    <xsl:template match="node()[key('node-by-id', generate-id(), $mapped-nodes)]">
        <xsl:comment select="key('node-by-id', generate-id(), $mapped-nodes)"/>
        <xsl:text>&#10;</xsl:text>
        <xsl:copy>
            <xsl:apply-templates select="@* , node()"/>
        </xsl:copy>
    </xsl:template>


</xsl:stylesheet>

这是最初发布的代码的编辑版本,它使用 XSLT 3.0 映射功能而不是临时文档来存储通过动态 XPath 评估找到的节点的生成 id 与注释之间的关联:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:math="http://www.w3.org/2005/xpath-functions/math"
    xmlns:map="http://www.w3.org/2005/xpath-functions/map"
    xmlns:mf="http://example.com/mf"
    exclude-result-prefixes="xs math map mf"
    version="3.0">

    <xsl:param name="paths-url" as="xs:string" select="'paths1.xml'"/>
    <xsl:param name="paths-doc" as="document-node()" select="doc($paths-url)"/>

    <xsl:output indent="yes"/>

    <xsl:variable 
        name="mapped-nodes"
        as="map(xs:string, xs:string)"
        select="map:new(for $path in $paths-doc/paths/xpath, $node in mf:evaluate($path/@location, /) return map:entry(generate-id($node), string($path/@annotate)))"/>

    <xsl:function name="mf:evaluate" as="node()?">
        <xsl:param name="path" as="xs:string"/>
        <xsl:param name="context" as="node()"/>
        <xsl:evaluate xpath="$path" context-item="$context"></xsl:evaluate>
    </xsl:function>

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

    <xsl:template match="node()[map:contains($mapped-nodes, generate-id())]">
        <xsl:comment select="$mapped-nodes(generate-id())"/>
        <xsl:text>&#10;</xsl:text>
        <xsl:copy>
            <xsl:apply-templates select="@* , node()"/>
        </xsl:copy>
    </xsl:template>


</xsl:stylesheet>

作为第一个样式表,它需要 Saxon 9.5 PE 或 EE 才能运行。

于 2013-10-05T14:03:37.020 回答
2

概述:

编写一个元 XSLT 转换,将paths文件作为输入并生成一个新的 XSLT 转换作为输出。这个新的 XSLT 将从您的root输入 XML 转换为带注释的副本输出 XML。

笔记:

  1. 适用于 XSLT 1.0、2.0 或 3.0。
  2. 应该非常高效,特别是如果生成的转换必须在大量输入上运行或必须重复运行,因为它可以有效地编译为本机 XSLT,而不是重新实现与基于 XSLT 的解释器的匹配。
  3. 比必须在代码中手动重建元素祖先的方法更健壮。由于它将路径映射到 属性,因此可以有效地使用 ingtemplate/@match的完整复杂性。@match我以一个属性值测试为例。
  4. 请务必考虑@DanielHaley 和@MartinHonnen 提供的优雅的XSLT 2.0 和3.0 解决方案,尤其是在中间元XSLT 文件不适合您的情况下。通过利用 XSLT 3.0 的 XPath 评估工具,@MartinHonnen 的答案似乎能够提供比template/@match这里更强大的匹配。

此输入 XML 指定 XPath 和注释:

<paths>
  <xpath location="/root/a" annotate="1"/>
  <xpath location="/root/a/b" annotate="2"/>
  <xpath location="/root/c[@x='123']" annotate="3"/>
</paths>

当输入到这个元 XSLT 转换时:

<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="xml" indent="yes"/>

  <xsl:template match="/paths">
    <xsl:element name="xsl:stylesheet">
      <xsl:attribute name="version">1.0</xsl:attribute>
      <xsl:element name="xsl:output">
        <xsl:attribute name="method">xml</xsl:attribute>
        <xsl:attribute name="indent">yes</xsl:attribute>
      </xsl:element>
      <xsl:call-template name="gen_identity_template"/>
      <xsl:apply-templates select="xpath"/>
    </xsl:element>
  </xsl:template>

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

  <xsl:template match="xpath">
    <xsl:element name="xsl:template">
      <xsl:attribute name="match">
        <xsl:value-of select="@location"/>
      </xsl:attribute>
      <xsl:element name="xsl:comment">
        <xsl:value-of select="@annotate"/>
      </xsl:element>
      <xsl:element name="xsl:text">
        <xsl:text disable-output-escaping="yes">&amp;#xa;</xsl:text>
      </xsl:element>
      <xsl:element name="xsl:copy">
        <xsl:element name="xsl:apply-templates">
          <xsl:attribute name="select">node()|@*</xsl:attribute>
        </xsl:element>
      </xsl:element>
    </xsl:element>
  </xsl:template>
</xsl:stylesheet>

将产生这个 XSLT 转换:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
   <xsl:output method="xml" indent="yes"/>
   <xsl:template match="node()|@*">
      <xsl:copy>
         <xsl:apply-templates select="node()|@*"/>
      </xsl:copy>
   </xsl:template>
   <xsl:template match="/root/a">
      <xsl:comment>1</xsl:comment>
      <xsl:text>&#xa;</xsl:text>
      <xsl:copy>
         <xsl:apply-templates select="node()|@*"/>
      </xsl:copy>
   </xsl:template>
   <xsl:template match="/root/a/b">
      <xsl:comment>2</xsl:comment>
      <xsl:text>&#xa;</xsl:text>
      <xsl:copy>
         <xsl:apply-templates select="node()|@*"/>
      </xsl:copy>
   </xsl:template>
   <xsl:template match="/root/c[@x='123']">
      <xsl:comment>3</xsl:comment>
      <xsl:text>&#xa;</xsl:text>
      <xsl:copy>
         <xsl:apply-templates select="node()|@*"/>
      </xsl:copy>
   </xsl:template>
</xsl:stylesheet>

其中,当提供此输入 XML 文件时:

<root>
  <a>
    <b>B</b>
  </a>
  <c x="123">C</c>
</root>

将生成所需的输出 XML 文件:

<?xml version="1.0" encoding="UTF-8"?>
<root>
  <!--1-->
   <a>
    <!--2-->
      <b>B</b>
  </a>
  <!--3-->
   <c x="123">C</c>
</root>
于 2013-10-05T03:57:57.683 回答
2

我不确定 kjhughes 关于创建第二个转换的建议是否比您最初的想法更有效。paths如果您的XML 变大,我确实看到第二次转换的可能性会变得很大。

这就是我的做法......

XML 输入

<root>
    <a>
        <b>B</b>
    </a>
    <c>C</c>
</root>

“路径”XML (paths.xml)

<paths>
    <xpath location="/root/a" annotate="1"/>
    <xpath location="/root/a/b" annotate="2"/>
</paths>

XSLT 2.0

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output indent="yes"/>
    <xsl:strip-space elements="*"/>

    <xsl:param name="paths" select="document('paths.xml')"/>

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

    <xsl:template match="*" priority="1">
        <xsl:variable name="path">
            <xsl:for-each select="ancestor-or-self::*">
                <xsl:value-of select="concat('/',local-name())"/>
            </xsl:for-each>
        </xsl:variable>
        <xsl:if test="$paths/*/xpath[@location=$path]">
            <xsl:comment select="$paths/*/xpath[@location=$path]/@annotate"/>
        </xsl:if>
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>

</xsl:stylesheet>

XML 输出

<root>
    <!--1-->
    <a>
        <!--2-->
        <b>B</b>
    </a>
    <c>C</c>
</root>
于 2013-10-05T04:30:07.483 回答