【问题标题】:Grouping dynamically动态分组
【发布时间】:2020-06-16 07:51:47
【问题描述】:

我正在尝试通过将节点相加来将数据组合在一起。示例

<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)) 但我可以看到我的基本方法不正确.现在我想知道,这是否可能。所以我想我会问专家!

谢谢!

【问题讨论】:

    标签: xslt xslt-2.0 xslt-grouping xslt-3.0


    【解决方案1】:

    在 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>
    

    【讨论】:

    • 非常感谢!我正在使用第一个解决方案,因为我了解正在发生的事情!其他解决方案,我需要弄清楚发生了什么!我才意识到我不知道 xslt :(
    【解决方案2】:

    xsl:for-each-group 指令无法处理此要求。

    除了 Martin 的建议,另一种 3.0 方法是左折叠:

    <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
      xmlns:xs="http://www.w3.org/2001/XMLSchema"
      xmlns:array="http://www.w3.org/2005/xpath-functions/array"
      exclude-result-prefixes="#all"
      version="3.0">
    
      <xsl:param name="max" as="xs:integer" select="10"/>
    
      <xsl:template match="root">
        <xsl:copy>
          <xsl:variable name="groups" select="
            fold-left(row, ([]), function($groups, $next) {
               if (sum(head($groups)?*/@val) + $next/@val le $max)
               then (array:append(head($groups), $next), tail($groups))
               else ([$next], $groups)
            }) => reverse()"/>
          <xsl:for-each select="$groups">
            <grouped>
              <xsl:copy-of select="?*"/>
            </grouped>
          </xsl:for-each>
        </xsl:copy>
      </xsl:template>
    
    </xsl:stylesheet>
    

    这会将组构建为数组序列,每组一个数组,最初以相反的顺序:回调函数对每一行执行一次,如果总数为,则将行添加到第一个(即最后一个)组在您的阈值内,否则它会启动一个新组。

    (为什么要倒序排列?主要是因为head()tail() 很方便,而且没有等价的获取最后一项和“除最后一项之外的所有内容”)。

    【讨论】:

    • 一个小bug:如果第一行的值> 10,则会有一个空组。
    • 谢谢K博士,我需要去文档看看 fold-left 的作用!!
    猜你喜欢
    • 1970-01-01
    • 2021-05-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-03-06
    • 1970-01-01
    相关资源
    最近更新 更多