XSL add a wrapper every time sum equals 12

2019-03-04 18:51发布

问题:

I want to turn this

<root>
  <item columns="4"></item>
  <item columns="8"></item>
  <item columns="12"></item>
  <item columns="4"></item>
  <item columns="4"></item>
  <item columns="4"></item>
  <item columns="2"></item>
  <item columns="10"></item>
</root>

into this

<root>
  <row>
    <item columns="4"></item>
    <item columns="8"></item>
  </row>
  <row>
    <item columns="12"></item>
  </row>
  <row>
    <item columns="4"></item>
    <item columns="4"></item>
    <item columns="4"></item>
  </row>
  <row>
    <item columns="2"></item>
    <item columns="10"></item>
  </row>
</root>

回答1:

Here's one way you could look at it:

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:template match="/root">
    <xsl:copy>
        <xsl:call-template name="aggregate">
            <xsl:with-param name="items" select="item"/>
        </xsl:call-template>
    </xsl:copy>
</xsl:template>

<xsl:template name="aggregate">
    <xsl:param name="items" select="/.."/>
    <xsl:param name="i" select="1"/>
    <xsl:variable name="stack-items" select="$items[position() &lt;= $i]" />
    <xsl:choose>
        <xsl:when test="sum($stack-items/@columns) >= 12 or $i >= count($items)">
            <row>
                <xsl:copy-of select="$stack-items"/>
            </row>
            <xsl:if test="$i &lt; count($items)">
                <xsl:call-template name="aggregate">
                    <xsl:with-param name="items" select="$items[position() > $i]"/>
                </xsl:call-template>
            </xsl:if>
        </xsl:when>
        <xsl:otherwise>
            <xsl:call-template name="aggregate">
                <xsl:with-param name="items" select="$items"/>
                <xsl:with-param name="i" select="$i + 1"/>
            </xsl:call-template>
        </xsl:otherwise>
    </xsl:choose>
</xsl:template>

</xsl:stylesheet>

Do a search for sibling recursion for an alternative method.



回答2:

Here's an XSLT 2.0 solution:

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

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

  <xsl:template match="root">
    <xsl:copy>
      <!--
      Group <item> elements, ending each group when the sum of the @columns
      attribute of the current <item> element and the <item> elements that
      precede it is divisible by 12.
      -->
      <xsl:for-each-group select="item" group-ending-with="
        *[sum((
            number(@columns),
            for $c in preceding-sibling::item/@columns return number($c)
          )) mod 12 eq 0]
      ">
        <row>
          <xsl:apply-templates select="current-group()"/>
        </row>
      </xsl:for-each-group>
    </xsl:copy>
  </xsl:template>

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

</xsl:stylesheet>


回答3:

Here a slightly shorter XSLT 1.0 version using following-sibling and an empty notest as default stack value ( <xsl:param name="items" select="/@empty-node-set" /> ).
But basically same as michael.hor257k solution.

<xsl:template match="/root">
    <xsl:copy>
        <xsl:apply-templates select="item[1]" />
    </xsl:copy>
</xsl:template>

<xsl:template match="item">
    <xsl:param name="items" select="/@empty-node-set" />
    <xsl:variable name="stack" select="$items | ." />
    <xsl:choose>
        <xsl:when test="sum($stack/@columns) >= 12 or not(following-sibling::item)"  >
            <row>
                <xsl:copy-of select="$stack"/>
            </row>
            <xsl:apply-templates select="following-sibling::item[1]" />
        </xsl:when>
        <xsl:otherwise>
            <xsl:apply-templates select="following-sibling::item[1]">
                <xsl:with-param name="items" select="$stack"/>
            </xsl:apply-templates>
        </xsl:otherwise>
    </xsl:choose>
</xsl:template>

Using /@empty-node-set as empty stack was from Best practice regarding empty node-set initialization:

... the expression "/@empty-node-set" which is guaranteed (for XML 1.0) to be empty because the root node can never have attribute nodes.



标签: xml xslt