Home > Software design >  XSLT: Grouping adjacent elements with same class
XSLT: Grouping adjacent elements with same class

Time:07-07

I'm new to XSLT and I'm stuck on the "grouping adjacent elements" problem. My XML input follows the schema:

<body>
<aside >
   some tags   text
</aside>
<aside >
    some tags   text
</aside>
<aside >
    some tags   text
</aside>
<p>...</p>
<img .../>
<aside >
    some tags   text
</aside>
<aside >
    some tags   text
</aside>
</body>

What I need (and can't achieve) is an output in which the adjacent aside with the same class are grouped together. The desidered output would be like this:

<body>
<tag1 >everything contained in the grouped tags</tag1>
<tag2 >everything contained in the grouped tags</tag2>

<p>...</p>
<img.../>

<tag2 >everything contained in the grouped tags</tag2>
</body>

The new tags can be managed using variables, and that's fine. But how do I obtain this output? So far, I've obtained the best results with this:

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

<xsl:template match="body">
     <xsl:for-each-group select="aside" group-adjacent="@class">
        <xsl:variable name="grouping_tag">
           <xsl:choose>
               <xsl:when test="@class = '1'">tag1</xsl:when>
               <xsl:when test="@class = '2'">tag2</xsl:when>
               <xsl:otherwise>tagX</xsl:otherwise>
           </xsl:choose>
       </xsl:variable>
       <xsl:element name="{$grouping_tag}" >
            <xsl:attribute name="class"><xsl:value-of select="@class"/></xsl:attribute>
            <xsl:for-each select="current-group()/*">                    
                <xsl:copy>
                    <xsl:apply-templates select="@*"/>
                    <xsl:apply-templates/>
                </xsl:copy>
            </xsl:for-each> 
       </xsl:element>           
     </xsl:for-each-group>

</xsl:template>

This solution does indeed group adjacent aside with the same class, but in the output I lost everything that is not contained in them (for example, p, img). And I need to mantain everything else, and in the same position.

Can someone help me?

CodePudding user response:

I would use select="*" group-adjacent=". instance of element(aside), @class" composite="yes" and then you can test current-group()[1] to check the group has aside elements and wrap them, otherwise push the group through apply-templates.

CodePudding user response:

Something like this, perhaps?

<xsl:template match="body">
     <xsl:for-each-group select="aside" group-adjacent="@class">
        <xsl:choose>
           <xsl:when test="@class">
               <xsl:element name="tag{@class}">
                  <xsl:copy-of select="current-group()"/>
               </xsl:element>
           </xsl:when>
           <xsl:otherwise>
               <xsl:copy-of select="current-group()"/>
           </xsl:otherwise>
        </xsl:choose>     
     </xsl:for-each-group>

</xsl:template>

CodePudding user response:

I guess you could do something like:

<xsl:template match="body">
     <xsl:copy>
        <xsl:for-each-group select="*" group-adjacent="boolean(self::aside)">
            <xsl:choose>
                <xsl:when test="current-grouping-key()">
                    <xsl:for-each-group select="current-group()" group-adjacent="@class">
                        <xsl:element name="tag{if (@class=(1,2)) then @class else 'X'}" >
                            <xsl:attribute name="class" select="@class"/>
                            <xsl:copy-of select="current-group()/node()"/>                    
                        </xsl:element>  
                    </xsl:for-each-group>
                </xsl:when>
                <xsl:otherwise>
                    <xsl:copy-of select="current-group()"/>                    
                </xsl:otherwise>
            </xsl:choose>
        </xsl:for-each-group>
     </xsl:copy>
</xsl:template>
  • Related