XSLT 1.0:将平面结构转换为嵌套的 Muenschian 分组:避免创建虚假副本

XSLT 1.0: Transforming flat structure to nested with Muenschian grouping: avoid creation of spurious copies

我是 XSLT 初学者,通过示例和项目工作来学习。目前,我正在致力于从平面创建分组的嵌套结构。

考虑这个示例 xml 输入:

<root>
    <a>First text</a>
    <b>Text</b>
    <c>More text in c tag</c>
    <d>There is even d tag</d>
    <a>Another "a" test.</a>
    <b>ěščřžýáíéúů</b>
    <b>More b tags</b>
    <c>One followed by c tag</c>
    <a>Last a tag</a>
    <b>This time only with b tag, but this goes on and on</b>
</root>

还有这个 XSLT:

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

  <xsl:output method="xml" encoding="utf-8"/>

  <xsl:key name="groupA" match="b|c|d" use="generate-id(preceding-sibling::a[1])" />
  <xsl:key name="groupB" match="c|d" use="generate-id(preceding-sibling::b[1])"/>

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

  <xsl:template match="root">
    <wrapperTest>
      <xsl:apply-templates/>
    </wrapperTest>
  </xsl:template>

  <xsl:template match="root">
    <xsl:apply-templates select="@*|a"/>
    <xsl:apply-templates select="@*|b"/>
  </xsl:template>

  <xsl:template match="a">
    <xsl:copy>
      <xsl:apply-templates select="key('groupA', generate-id())" />
    </xsl:copy>
  </xsl:template>
  
  <xsl:template match="b">
    <xsl:copy>
      <xsl:apply-templates select="key('groupB', generate-id())" />
    </xsl:copy>
  </xsl:template>

</xsl:stylesheet>

预期输出为:

<wrapperTest>
    <a>First text
      <b>Text
        <c>More text in c tag</c>
        <d>There is even d tag</d>
      </b>
    </a>
    <a>Another "a" test.
      <b>ěščřžýáíéúů</b>
      <b>More b tags
        <c>One followed by c tag</c>
      </b>
    </a>
    <a>Last a tag
      <b>This time only with b tag, but this goes on and on</b>
    </a>
</wrapperTest>

在我创建的转换中创建了过多的副本,我不知道为什么。我想我遇到的问题本质上是基本的,但我想不通。

解决方案的唯一限制是,最好它应该在 XSLT 1.0 中(因为该项目包含在 python 脚本中 lxml)。在极端情况下,当 XSLT 1.0 无法实现时,我可以适应最近的 saxon 版本,它消除了任何限制 ...

我已经看过答案 here, here 和其他答案,但其中大多数使用 XSLT 2.0 或者对于初学者来说非常复杂。

最后说明:理想情况下,建议的解决方案本质上应该是可扩展的,因为我的项目的最终形式也应该按标签 <c> 分组,就像这样:

<wrapperTest>
    <a>First text
      <b>Text
        <c>More text in c tag
          <d>There is even d tag</d>
        </c>
      </b>
    </a>
    <a>Another "a" test.
      <b>ěščřžýáíéúů</b>
      <b>More b tags
        <c>One followed by c tag</c>
      </b>
    </a>
    <a>Last a tag
      <b>This time only with b tag, but this goes on and on</b>
    </a>
</wrapperTest>

作为学习练习,我会很乐意这样做。

所有级别的递归 XSLT 2/3 for-each-group group-starting-with 归结为

<xsl:function name="mf:wrap" as="node()*">
    <xsl:param name="input" as="node()*"/>
    <xsl:for-each-group select="$input" group-starting-with="node()[node-name() = node-name($input[1])]">
        <xsl:copy>
            <xsl:sequence select="node(), mf:wrap(tail(current-group()))"/>
        </xsl:copy>
    </xsl:for-each-group>
</xsl:function>
        
<xsl:template match="root">
    <xsl:copy>
        <xsl:sequence select="mf:wrap(*)"/>
    </xsl:copy>
</xsl:template>

https://xsltfiddle.liberty-development.net/gVhEaj8

怎么样:

XSLT 1.0

<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:key name="b" match="b" use="generate-id(preceding-sibling::a[1])" />
<xsl:key name="cd" match="c|d" use="generate-id(preceding-sibling::b[1])"/>

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

<xsl:template match="/root">
    <wrapperTest>
        <xsl:apply-templates select="a"/>
    </wrapperTest>
</xsl:template>

<xsl:template match="a">
    <xsl:copy>
        <xsl:apply-templates/>
        <xsl:apply-templates select="key('b', generate-id())" />
    </xsl:copy>
</xsl:template>
  
<xsl:template match="b">
    <xsl:copy>
        <xsl:apply-templates/>
        <xsl:apply-templates select="key('cd', generate-id())" />
    </xsl:copy>
</xsl:template>

</xsl:stylesheet>