Search code examples
variablesxsltxslt-2.0xslt-grouping

XSLT: filter/group nodes in variables


I am trying to filter/group nodes in a variable.

XML:

<?xml version="1.0" encoding="UTF-8"?>
<row>
    <cell n="1"/>
    <cell n="2"/>
    <cell n="3"/>
    <cell n="4"/>
    <cell n="2"/>
    <cell n="5"/>
</row>

XSLT 2:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

    <xsl:template match="row">
        <xsl:element name="row">
            <xsl:apply-templates/>
        </xsl:element>
    </xsl:template>   

    <xsl:template match="cell">
        <xsl:variable name="attribute" select="@n"/>
        <xsl:variable name="sequence">
            <xsl:call-template name="seq">
                <xsl:with-param name="attribute" select="$attribute"/>
            </xsl:call-template>
        </xsl:variable>
        <xsl:for-each-group select="$sequence" group-by="cell/@val">
            <xsl:sequence select="."></xsl:sequence>
        </xsl:for-each-group>
    </xsl:template>

    <xsl:template name="seq">
        <xsl:param name="attribute"/>
        <xsl:element name="cell">
            <xsl:attribute name="val">
                <xsl:value-of select="$attribute"/>
            </xsl:attribute>
        </xsl:element>
    </xsl:template>

</xsl:stylesheet>

Intended output:

<?xml version="1.0" encoding="UTF-8"?>
<row>
    <cell val="1"/>
    <cell val="2"/>
    <cell val="3"/>
    <cell val="4"/>
    <cell val="5"/>
</row>

The XML-Code is just a example for a much more complicated File. What I want to do is process all the Elements cell first and save the result in a variable. In the second run I want to filter or regroup the nodes of the sequence.

I also tried to filter $sequence with predicates:

<xsl:sequence select="$sequence/*[@val != preceding-sibling::cell/@val]"/> 

In this case the output-file is empty.

EDIT 2 (now it works):

<?xml version="1.0" encoding="UTF-8"?>

<xsl:template match="row">
    <xsl:variable name="sequence">
        <xsl:for-each select="cell">
            <xsl:variable name="attribute" select="@n"/>
            <xsl:call-template name="seq">
                <xsl:with-param name="attribute" select="$attribute"/>
            </xsl:call-template>
        </xsl:for-each>
    </xsl:variable>
    <row>
        <xsl:for-each-group group-by="@val" select="$sequence/*">
            <xsl:sequence select="."/>
        </xsl:for-each-group>
    </row>
</xsl:template>

<xsl:template name="seq">
    <xsl:param name="attribute"/>
    <xsl:element name="cell">
        <xsl:attribute name="val">
            <xsl:value-of select="$attribute"/>
        </xsl:attribute>
    </xsl:element>
</xsl:template>


Solution

  • Here is an XSLT 2.0 stylesheet that transforms <cell n="x">...</cell> into <cell val="n">...</cell> first and then groups those cell elements by the val attribute to eliminate duplicates:

    <xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
    
        <xsl:output indent="yes"/>
    
        <xsl:template match="@*|node()">
            <xsl:copy>
                <xsl:apply-templates select="@*|node()"/>
            </xsl:copy>
        </xsl:template>
    
        <xsl:template match="row">
            <xsl:copy>
                <xsl:variable name="transformed-cells">
                    <xsl:apply-templates select="cell"/>
                </xsl:variable>
                <xsl:for-each-group select="$transformed-cells/cell" group-by="@val">
                    <xsl:copy-of select="."/>
                </xsl:for-each-group>
            </xsl:copy>
        </xsl:template>
    
        <xsl:template match="cell/@n">
            <xsl:attribute name="val" select="."/>
        </xsl:template>
    
    </xsl:transform>
    

    Output is

    <row>
       <cell val="1"/>
       <cell val="2"/>
       <cell val="3"/>
       <cell val="4"/>
       <cell val="5"/>
    </row>
    

    http://xsltransform.net/a9Giwz