Kaustubh Badrike
Kaustubh Badrike

Reputation: 565

How to group by sequence of tags?

I have the following XML:

<?xml version="1.0" encoding="UTF-8"?>
<Container>
    <Item type="forest"/>
    <Item type="tree"/>
    <Item type="branch"/>
    <Item type="leaf"/>
    <Item type="branch"/>
    <Item type="leaf"/>
    <Item type="leaf"/>
    <Item type="tree"/>
    <Item type="branch"/>
    <Item type="branch"/>
    <Item type="leaf"/>
    <Item type="forest"/>
    <Item type="tree"/>
    <Item type="branch"/>
    <Item type="leaf"/>
</Container>

The data represents the following structure:

  1. Each forest has trees, each tree has branches, each branch has leaves.
  2. A change in tag signifies the next item at the level above.

For the given XML:

  1. There are 2 forests.
  2. The first forest has 2 trees.
  3. The first tree of the first forest has 2 branches, each of which has the same number of leaves as its 1-based index.

I want to process the data in XSLT similar to the following, had the type attributes been tag names:

<?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">
    <xsl:template match='/'>
        <xsl:for-each select="/Container/forest">
            <xsl:for-each select="tree">
                <xsl:for-each select="branch">
                    <xsl:for-each select="leaf">
                    </xsl:for-each>
                </xsl:for-each>
            </xsl:for-each>
        </xsl:for-each>
    </xsl:template>
</xsl:stylesheet>

How can this be achieved?

Upvotes: 1

Views: 92

Answers (2)

Martin Honnen
Martin Honnen

Reputation: 167436

The existing answer has the grouping nicely spelled out but of course the question kind of asks for recursion:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="3.0"
  xmlns:xs="http://www.w3.org/2001/XMLSchema"
  xmlns:mf="http://example.com/mf"
  exclude-result-prefixes="#all"
  expand-text="yes">
  
  <xsl:output indent="yes"/>
  
  <xsl:param name="levels" as="xs:string*" select="'forest', 'tree', 'branch', 'leaf'"/>
  
  <xsl:function name="mf:group" as="element()*">
    <xsl:param name="items" as="element()*"/>
    <xsl:param name="level" as="xs:integer"/>
    <xsl:for-each-group select="$items" group-starting-with="Item[@type = $levels[$level]]">
      <xsl:element name="{@type}">
        <xsl:sequence select="mf:group(tail(current-group()), $level + 1)"/>
      </xsl:element>
    </xsl:for-each-group>
  </xsl:function>

  <xsl:mode on-no-match="shallow-copy"/>
  
  <xsl:template match="Container">
    <root>
      <xsl:sequence select="mf:group(*, 1)"/>
    </root>
  </xsl:template>

</xsl:stylesheet>

Upvotes: 2

michael.hor257k
michael.hor257k

Reputation: 116959

Consider the following example:

XML

<Container>
    <Item type="forest" id="1"/>
    <Item type="tree" id="2"/>
    <Item type="branch" id="3"/>
    <Item type="leaf" id="4"/>
    <Item type="branch" id="5"/>
    <Item type="leaf" id="6"/>
    <Item type="leaf" id="7"/>
    <Item type="tree" id="8"/>
    <Item type="branch" id="9"/>
    <Item type="branch" id="10"/>
    <Item type="leaf" id="11"/>
    <Item type="forest" id="12"/>
    <Item type="tree" id="13"/>
    <Item type="branch" id="14"/>
    <Item type="leaf" id="15"/>
</Container>

XSLT 2.0

<xsl:stylesheet version="2.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="/Container">
    <root>
        <xsl:for-each-group select="Item" group-starting-with="Item[@type='forest']">
            <forest id="{@id}">
                <xsl:for-each-group select="current-group()[position() gt 1]" group-starting-with="Item[@type='tree']">
                    <tree id="{@id}">
                        <xsl:for-each-group select="current-group()[position() gt 1]" group-starting-with="Item[@type='branch']">
                            <branch id="{@id}">
                                <xsl:for-each select="current-group()[position() gt 1]">
                                    <leaf id="{@id}"/>
                                </xsl:for-each>
                            </branch>
                        </xsl:for-each-group>
                    </tree>
                </xsl:for-each-group>
            </forest>
        </xsl:for-each-group>
    </root>
</xsl:template>

</xsl:stylesheet>

Result

<?xml version="1.0" encoding="utf-8"?>
<root>
   <forest id="1">
      <tree id="2">
         <branch id="3">
            <leaf id="4"/>
         </branch>
         <branch id="5">
            <leaf id="6"/>
            <leaf id="7"/>
         </branch>
      </tree>
      <tree id="8">
         <branch id="9"/>
         <branch id="10">
            <leaf id="11"/>
         </branch>
      </tree>
   </forest>
   <forest id="12">
      <tree id="13">
         <branch id="14">
            <leaf id="15"/>
         </branch>
      </tree>
   </forest>
</root>

Upvotes: 3

Related Questions