通过嵌套的 xsl:for-each 跟踪迭代器

Keeping track of an iterator through a nested xsl:for-each

我想接受以下输入:

<test>
    <a>
        <b />
        <b />
    </a>
    <a>
        <b />
        <b />
    </a>
</test>

并使用 XSLT 2.0 创建以下输出:

<items>
    <item num="1">
        <item num="2"/>
        <item num="3"/>
    </item>
    <item num="4">
        <item num="5"/>
        <item num="6"/>
    </item>
</items>

我知道这是错误的,但作为起点,这是我当前的 XSLT:

<xsl:template match="test">
    <items>
        <xsl:for-each select="a">
            <item num="{position()}">
                <xsl:for-each select="b">
                    <item num="{position()}"/>
                </xsl:for-each>
            </item>
        </xsl:for-each>
    </items>
</xsl:template>

这显然不是办法,因为position()只考虑同层的元素。但是我该怎么做呢?

我找到了一个解决方案:在任何级别使用 xsl:number 并计算 a 和 b:

<xsl:template match="test">
    <items>
        <xsl:for-each select="a">
            <item>
                <xsl:attribute name="num">
                    <xsl:number level="any" count="a|b"/>
                </xsl:attribute>
                <xsl:for-each select="b">
                    <item>
                        <xsl:attribute name="num">
                            <xsl:number level="any" count="a|b"/>
                        </xsl:attribute>
                    </item>
                </xsl:for-each>
            </item>
        </xsl:for-each>
    </items>
</xsl:template>

这个变换:

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

  <xsl:template match="/*">
    <items><xsl:apply-templates/></items>
  </xsl:template>

  <xsl:template match="a|b">
    <xsl:variable name="vNum">
      <xsl:number level="any" count="a|b"/>
    </xsl:variable>
    <item num="{$vNum}">
      <xsl:apply-templates/>
    </item>
  </xsl:template>
</xsl:stylesheet>

应用于所提供的 XML 文档时:

<test>
    <a>
        <b />
        <b />
    </a>
    <a>
        <b />
        <b />
    </a>
</test>

产生想要的正确结果:

<items>
   <item num="1">
      <item num="2"/>
      <item num="3"/>
   </item>
   <item num="4">
      <item num="5"/>
      <item num="6"/>
   </item>
</items>

在 XSLT 2 中,使用 xsl:number 是正确的方法,如果您的 XSLT 处理器也支持 XSLT 3,则可以使用累加器 https://www.w3.org/TR/xslt-30/#element-accumulator

<?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"
    exclude-result-prefixes="#all"
    version="3.0">

  <xsl:mode on-no-match="shallow-copy" use-accumulators="item-count"/>

  <xsl:accumulator name="item-count" as="xs:integer" initial-value="0">
      <xsl:accumulator-rule match="test" select="0"/>
      <xsl:accumulator-rule match="test/a | test/a/b" select="$value + 1"/>
  </xsl:accumulator>

  <xsl:template match="test">
      <items>
          <xsl:apply-templates/>
      </items>
  </xsl:template>

  <xsl:template match="a | b">
      <item num="{accumulator-before('item-count')}">
          <xsl:apply-templates/>
      </item>
  </xsl:template>

</xsl:stylesheet>

https://xsltfiddle.liberty-development.net/3NSSEvn

这甚至可以在不支持 xsl:number 的 Saxon EE 中使用流式传输:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    exclude-result-prefixes="#all"
    version="3.0">

    <xsl:mode on-no-match="shallow-copy" use-accumulators="item-count" streamable="yes"/>

    <xsl:accumulator name="item-count" as="xs:integer" initial-value="0" streamable="yes">
        <xsl:accumulator-rule match="test" select="0"/>
        <xsl:accumulator-rule match="test/a | test/a/b" select="$value + 1"/>
    </xsl:accumulator>

    <xsl:template match="test">
        <items>
            <xsl:apply-templates/>
        </items>
    </xsl:template>

    <xsl:template match="a | b">
        <item num="{accumulator-before('item-count')}">
            <xsl:apply-templates/>
        </item>
    </xsl:template>

</xsl:stylesheet>