Search code examples
xmlxsltxslt-grouping

Limits of XSLT Grouping?


I need to change the snippet below into a hierarchical structure by grouping related elements under new group element. XSLT sounds like the appropriate technology to use here but I don't know enough about it to assess if it's capable of what I need. An example grouping criteria is "1 'd' node followed by some number of '3' nodes, followed by 1 'f' node". I have to perform this task for several separate criteria, so I'd like to know if queries of that nature are possible in XSLT or if I would be better served just giving up and processing the DOM procedurally. I'm completely open to other strategies, as well. For a specific example, I need to convert this-

<root>
    <a>foo</a>
    <b>bar</b>
    <c>baz</c>
    <d>location</d>
    <e>3.14</e>
    <e>6.02</e>
    <e>1.23</e>
    <f>2015</f>
    <d>location</d>
    <e>3.14</e>
    <e>6.02</e>
    <f>2015</f>
</root>

Into this-

<root>
    <a>foo</a>
    <b>bar</b>
    <c>baz</c>
    <sample>
        <d>location</d>
        <e>3.14</e>
        <e>6.02</e>
        <e>1.23</e>
        <f>2015</f>
    </sample>
    <sample>
        <d>location</d>
        <e>3.14</e>
        <e>6.02</e>
        <f>2015</f>
    </sample>
</root> 

Solution

  • If it is that you want to group the elements, starting-with d, you can use the following approach. XSLT-1.0

    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
        <xsl:output indent="yes" method="xml" />
        <xsl:strip-space elements="*"/>
    
        <!-- key to select following (e|f) elements using the first preceding d's id -->
        <xsl:key name="following" match="e | f" use="generate-id(preceding::d[1])"/>
    
        <!-- identity transform template -->
        <xsl:template match="@* | node()">
            <xsl:copy>
                <xsl:apply-templates select="@* | node()"/>
            </xsl:copy>
        </xsl:template>
    
        <!-- template to group d and its following (e|f) elements under sample element-->
        <xsl:template match="d">
            <sample>
                <xsl:copy-of select="current() | key('following', generate-id())"/>
            </sample>
        </xsl:template>
    
        <!-- template to do nothing for e|f elements -->
        <xsl:template match="e|f"/>
    
    </xsl:stylesheet>
    

    And, of course, XSLT isn't limited to such simple grouping.