如何在 XSLT 中按 'columns' 对多个相邻元素进行分组?

How to group multiple adjacent elements by 'columns' in XSLT?

假设有这个XML片段:

<a>t1</a> <a>t2</a> <b>t3</b> <b>t4</b> <b>t5</b> <c>t6</c>

我们可以将其形象化为:

<a>t1</a> <a>t2</a>
<b>t3</b> <b>t4</b> <b>t5</b>
<c>t6</c>

我要迭代,所以第一遍迭代的元素是:

<a>t1</a> <b>t3</b> <c>t6</c>

第二遍:

<a>t2</a> <b>t4</b>

第三个:

<b>t5</b>

以上数据仅为一例。可能有更长的相邻兄弟序列,而不仅仅是这个固定的数据集。

要求是每个组包含的元素与共享相同元素名称的前面兄弟姐妹的数量相同。

例如,在第一个 'column' 中, 分别没有同名的兄弟姐妹。

第二个 'column' 分别有 1 个同名兄弟姐妹。

我希望能够在 for-each-group 语句中以这种方式迭代项目,但我不确定如何表达 group-by 子句。

模板

<xsl:template match="div">
    <xsl:for-each-group select="*" group-by="count(preceding-sibling::*[node-name(.) = node-name(current())])">
        <group key="{current-grouping-key()}">
            <xsl:copy-of select="current-group()"/>
        </group>
    </xsl:for-each-group>
</xsl:template>

变换

<div>
    <a>t1</a> <a>t2</a> <b>t3</b> <b>t4</b> <b>t5</b> <c>t6</c>
</div>

进入

<group key="0">
   <a>t1</a>
   <b>t3</b>
   <c>t6</c>
</group>
<group key="1">
   <a>t2</a>
   <b>t4</b>
</group>
<group key="2">
   <b>t5</b>
</group>

当前接受的答案具有 O(N^2) 时间复杂度,因为它对每个元素使用 preceding-sibling::*

这是一个可能更有效的解决方案 - 未使用 preceding-sibling::* 轴:

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output method="text"/>

  <xsl:template match="/*">
    <xsl:variable name="vTop" select="."/>
    <xsl:variable name="vNames" select="distinct-values(*/name())"/>
    <xsl:variable name="vCountNames" select="count($vNames)"/>

    <xsl:for-each select="1 to $vCountNames">
      <xsl:variable name="vCol" select="position()"/>
      <xsl:for-each select="$vNames">
        <xsl:apply-templates select="$vTop/*[name() eq current()][$vCol]"/>
      </xsl:for-each>
    </xsl:for-each>
  </xsl:template>
</xsl:stylesheet>

当此转换应用于以下 XML 文档时(提供的片段被顶部(文档)元素包围):

<t>
 <a>t1</a> <a>t2</a>
 <b>t3</b> <b>t4</b> <b>t5</b>
 <c>t6</c>
</t>

产生了想要的结果(按列遍历时每个元素的值):

t1t3t6t2t4t5

这个解决方案是 O(N * M),其中 N 是元素的数量,M 是它们不同名称的数量。

因此,如果 N = k times M 那么这个解决方案将渐进地比 O(N^2) 解决方案快 k 倍。


二.以列方式访问元素的单个纯 XPath 2.0 表达式:

  for $vTop in /*,
      $vCol in 1 to count(distinct-values($vTop/*/name())),
      $vName in distinct-values($vTop/*/name())
     return
      $vTop/*[name() eq $vName][$vCol]  

基于 XSLT 的验证:

  <xsl:template match="/*">
    <xsl:sequence select=
     "for $vTop in /*,
          $vCol in 1 to count(distinct-values($vTop/*/name())),
          $vName in distinct-values($vTop/*/name())
         return
          $vTop/*[name() eq $vName][$vCol]  
     "/>
  </xsl:template>
</xsl:stylesheet>

当应用于同一个 XML 文档时,此转换计算 XPath 表达式并输出此计算的结果

t1t3t6t2t4t5

三. XSLT 1.0 解决方案:

这个变换:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output method="text"/>

 <xsl:key name="kByName" match="/*/*" use="name()"/>

 <xsl:variable name="vDistinctNamed" select=
  "/*/*[generate-id() = generate-id(key('kByName', name())[1])]"/>

  <xsl:variable name="vNumCols">
    <xsl:for-each select="/*/*[generate-id() = generate-id(key('kByName', name())[1])]">
      <xsl:sort select=
       "count(key('kByName', name()))" data-type="number" order="descending"/>
      <xsl:if test="position()=1">
        <xsl:value-of select="count(key('kByName', name()))"/>
      </xsl:if>
    </xsl:for-each>
  </xsl:variable>

  <xsl:template match="/*">
    <xsl:for-each select="*[not(position() > $vNumCols)]">
      <xsl:variable name="vCol" select="position()"/>
      <xsl:for-each select="$vDistinctNamed">
        <xsl:variable name="vthisElement" select="/*/*[name() = name(current())][$vCol]"/>
        <xsl:if test="$vthisElement">
           <xsl:value-of select="concat(/*/*[name() = name(current())][$vCol],', ')"/>
        </xsl:if>
      </xsl:for-each>
    </xsl:for-each>
  </xsl:template>
</xsl:stylesheet>

应用于同一个 XML 文档时,产生相同的正确结果:

t1, t3, t6, t2, t4, t5,