Search code examples
xmlcsvxsltxslt-2.0xslt-grouping

Merge Elements (XML) With Same ID to txt file using XSLT


I have a task where I need to loop through an XML document and merge elements/nodes with the same ID. The output should be a csv file (for further processing) where each line have a fixed length. Based on the values of the nodes, that value needs to be placed at a certain location in the output.

Here is a sample of the XML:

<root>
    <User>
        <UserID>55555</UserID>
        <Value>Active</Value>
    </User>
    <User>
        <UserID>55555</UserID>
        <Value>Admin</Value>
    </User>
    <User>
        <UserID>55555</UserID>
        <Value>Eligible</Value>
    </User>
    <User>
        <UserID>123456</UserID>
        <Value>Active</Value>
    </User>
</root>

My desired output would be:

User ID, Active, Admin, Eligible
55555, Y, Y, Y,
123456, Y, N, N,

NOTE the values are ALWAYS THE SAME (Active, Admin & Eligible), but Users can have different amount of values like in the example.

Currently this is what I got:

    <xsl:template match="/root">
        <Header>
            <xsl:text>User ID</xsl:text>
            <xsl:value-of select="$comma"/>
            
            <xsl:text>Active</xsl:text>
            <xsl:value-of select="$comma"/>
            
            <xsl:text>Admin</xsl:text>
            <xsl:value-of select="$comma"/>
            
            <xsl:text>Eligible</xsl:text>
            <xsl:text>&#xa;</xsl:text>
        </Header>
            <xsl:for-each-group select="User" group-by="UserID"> 
                
                <!-- User ID -->
                <xsl:value-of select="UserID"/>
                <xsl:value-of select="$comma"/>
                
                <xsl:for-each-group select="current-group()" group-by="Value">
                    <xsl:value-of select="current-grouping-key()"/>
                    <xsl:value-of select="$comma"/>
                </xsl:for-each-group>
                
                <xsl:value-of select="$lineFeed"/>
            </xsl:for-each-group>
    </xsl:template>

This group and selects the correct elements, but then I need to place them under the correct headers (like axample with desired output).

Can anyone point me in the right direction here? Any help would be much appreciated.


Solution

  • If I understand this correctly (very big if!), you want to do something like:

    XSLT 2.0

    <xsl:stylesheet version="2.0" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="text" encoding="UTF-8" />
    
    <xsl:template match="/root">
        <!-- header -->
        <xsl:text>User ID, Active, Admin, Eligible&#10;</xsl:text>
        <!-- rows -->
        <xsl:for-each-group select="User" group-by="UserID"> 
            <!-- User ID -->
            <xsl:value-of select="UserID"/>
            <xsl:text>, </xsl:text>
            <!-- Active -->
            <xsl:value-of select="if (current-group()/Value[.='Active']) then 'Y' else'N'"/>
            <xsl:text>, </xsl:text>
            <!-- Admin -->
            <xsl:value-of select="if (current-group()/Value[.='Admin']) then 'Y' else'N'"/>
            <xsl:text>, </xsl:text>
            <!-- Eligible -->
            <xsl:value-of select="if (current-group()/Value[.='Eligible']) then 'Y' else'N'"/>
            <xsl:text>&#10;</xsl:text>
        </xsl:for-each-group>
    </xsl:template>
    
    </xsl:stylesheet>
    

    Or more compactly:

    <xsl:stylesheet version="2.0" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="text" encoding="UTF-8" />
    
    <xsl:template match="/root">
        <!-- header -->
        <xsl:text>User ID, Active, Admin, Eligible&#10;</xsl:text>
        <!-- rows -->
        <xsl:for-each-group select="User" group-by="UserID"> 
            <xsl:value-of select="UserID, for $t in ('Active', 'Admin', 'Eligible') return if (current-group()/Value[.=$t]) then 'Y' else 'N'" separator=", "/>
            <xsl:text>&#10;</xsl:text>
        </xsl:for-each-group>
    </xsl:template>
    
    </xsl:stylesheet>
    

    Note that the result is slightly different from the one you posted: there is no trailing comma in each record.