Skip to content

Instantly share code, notes, and snippets.

@kaero
Created May 6, 2011 23:11
Show Gist options
  • Save kaero/959974 to your computer and use it in GitHub Desktop.
Save kaero/959974 to your computer and use it in GitHub Desktop.
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="group">
<!--Start recursion over group children-->
<xsl:apply-templates select="*[1]" mode="group"/>
</xsl:template>
<!--Matches <group/> children-->
<xsl:template match="*" mode="group">
<!--level is the index of elements combination from source group-->
<xsl:param name="level" select="0"/>
<xsl:variable name="name" select="name()"/>
<xsl:variable name="needs-group" select="count( preceding-sibling::*[ name() = $name ] ) = $level"/>
<!--
Create new group if the current node position in the subset
of the siblings with the same name is equal current level
-->
<xsl:if test="$needs-group">
<group>
<!--
Search elements for the current level in the preceding-siblings.
Exclude elements with the same name as current.
-->
<xsl:apply-templates select="preceding-sibling::*[ name() != $name ]" mode="item-group">
<xsl:with-param name="level" select="$level"/>
</xsl:apply-templates>
<!--Copy current element-->
<xsl:apply-templates select="."/>
<!--
Search elements for the current level in the following-siblings.
-->
<xsl:apply-templates select="following-sibling::*[ name() != $name ]" mode="item-group">
<xsl:with-param name="level" select="$level"/>
</xsl:apply-templates>
<!--
You can join first and last apply-templates in the <group/>
if there is no need in the elements source order.
-->
<!--
<xsl:apply-templates select="../*[ name() != $name ]" mode="item-group">
<xsl:with-param name="level" select="$level"/>
</xsl:apply-templates>
-->
</group>
</xsl:if>
<!--
Iterate over all children of current group.
This is good place to reduce recursion.
-->
<xsl:apply-templates select="following-sibling::*[1]" mode="group">
<!--Increase level if group has been created: $needs-group true() -> 1, false() -> 0-->
<xsl:with-param name="level" select="$level + $needs-group"/>
</xsl:apply-templates>
</xsl:template>
<!--Matches <group/> child and decide include one into target group or not -->
<xsl:template match="*" mode="item-group">
<xsl:param name="level"/>
<xsl:variable name="name" select="name()"/>
<!--
Include current element to the group only if one of the conditions is true:
* the element position in the subset of elements with the same name equal the current level
* count of the elements with the same name in the source group is less,
than the level and this is the last element with the such name
-->
<xsl:if test="count( preceding-sibling::*[ name() = $name ] ) = $level or
( count( ../*[ name() = $name ] ) &lt;= $level and not( following-sibling::*[ name() = $name ] ) )">
<xsl:apply-templates select="."/>
</xsl:if>
</xsl:template>
<xsl:template match="@* | node()">
<xsl:copy>
<xsl:apply-templates select="@* | node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment