首页 > 解决方案 > 动态分组

问题描述

我试图通过将节点相加来将数据组合在一起。例子

<root>
    <row id="AAA" val="2"/>
    <row id="BBB" val="3"/>
    <row id="CCC" val="1"/>
    <row id="DDD" val="4"/>
    <row id="EEE" val="6"/>
    <row id="FFF" val="3"/>
    <row id="GGG" val="6"/>
    <row id="HHH" val="8"/>
    <row id="III" val="3"/>
    <row id="JJJ" val="4"/>
    <row id="KKK" val="2"/>
    <row id="LLL" val="1"/>
</root>

假设我有一个 10 的参数,那么每次值总和为 10 或小于 10 时,它们应该组合在一起。结果应该是

<root>
    <grouped>
        <row id="AAA" val="2"/>
        <row id="BBB" val="3"/>
        <row id="CCC" val="1"/>
        <row id="DDD" val="4"/>
    </grouped>
    <grouped>
        <row id="EEE" val="6"/>
        <row id="FFF" val="3"/>
    </grouped>
    <grouped>
        <row id="GGG" val="6"/>
    </grouped>
    <grouped>
        <row id="HHH" val="8"/>
    </grouped>
    <grouped>
        <row id="III" val="3"/>
        <row id="JJJ" val="4"/>
        <row id="KKK" val="2"/>
        <row id="LLL" val="1"/>
    </grouped>
</root>

我尝试使用 group-adjacent 和 sum(current/@val + following-sibling::row/@val le 10) 然后尝试 group-by(sum(@val)) 但我可以看到我的基本方法不正确。现在我想知道,这是否可能。所以我想我会问专家!

谢谢!

标签: xsltxslt-2.0xslt-groupingxslt-3.0

解决方案


在 XSLT 1 中,您可以使用兄弟递归,在 XSLT 3 中使用起来更容易但有点冗长xsl:iterate

  <xsl:template match="root">
      <xsl:copy>
          <xsl:iterate select="row">
              <xsl:param name="sum" as="xs:integer" select="0"/>
              <xsl:param name="group" as="element(row)*" select="()"/>
              <xsl:on-completion>
                  <xsl:if test="$group">
                      <group>
                          <xsl:copy-of select="$group"/>
                      </group>
                  </xsl:if>
              </xsl:on-completion>
              <xsl:variable name="current-sum" select="$sum + xs:integer(@val)"/>
              <xsl:if test="$current-sum > 10">
                  <group>
                    <xsl:copy-of select="$group"/>
                  </group>
              </xsl:if>
              <xsl:next-iteration>
                  <xsl:with-param name="sum" select="if ($current-sum > 10) then xs:integer(@val) else $current-sum"/>
                  <xsl:with-param name="group" select="if ($current-sum > 10) then . else ($group, .)"/>
              </xsl:next-iteration>
          </xsl:iterate>
      </xsl:copy>
  </xsl:template>

https://xsltfiddle.liberty-development.net/6pS2B6o

作为替代方案,您可以使用累加器对@val值求和并在“组”建立时“记住”,然后在分组中您可​​以使用group-starting-with来检查累加器:

  <xsl:param name="max" as="xs:integer" select="10"/>

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

  <xsl:output method="xml" indent="yes"/>

  <xsl:accumulator name="window" as="item()*" initial-value="()">
      <xsl:accumulator-rule match="root" select="(0, true())"/>
      <xsl:accumulator-rule match="root/row"
        select="let $val := xs:integer(@val),
                    $sum := $value[1],
                    $window-start := $value[2],
                    $current-sum := $sum + $val
                return
                    if ($current-sum gt $max)
                    then ($val, true())
                    else ($current-sum, false())"/>
  </xsl:accumulator>

  <xsl:template match="root">
      <xsl:copy>
          <xsl:for-each-group select="row" group-starting-with="*[accumulator-before('window')[2]]">
              <grouped>
                  <xsl:apply-templates select="current-group()"/>
              </grouped>
          </xsl:for-each-group>
      </xsl:copy>
  </xsl:template>

https://xsltfiddle.liberty-development.net/6pS2B6o/1

您甚至可以使该流式传输(在 Michael Kay 的帮助下):

<?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:param name="max" as="xs:integer" select="10"/>

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

    <xsl:output method="xml" indent="yes"/>

    <xsl:accumulator name="window" as="item()*" initial-value="()" streamable="yes">
        <xsl:accumulator-rule match="root" select="(0, true())"/>
        <xsl:accumulator-rule match="root/row"
            select="
                let $val := xs:integer(@val),
                    $sum := $value[1],
                    $window-start := $value[2],
                    $current-sum := $sum + $val
                return
                    if ($current-sum gt $max)
                    then
                        ($val, true())
                    else
                        ($current-sum, false())"
        />
    </xsl:accumulator>

    <xsl:template match="root">
        <xsl:copy>
            <xsl:for-each-group select="row"
                group-starting-with="*[boolean(accumulator-before('window')[2])]">
                <grouped>
                    <xsl:apply-templates select="current-group()"/>
                </grouped>
            </xsl:for-each-group>
        </xsl:copy>
    </xsl:template>

</xsl:stylesheet>

推荐阅读