Search code examples
xsltxslt-2.0xslt-grouping

How Do I Duplicate XML based on Child Groups without losing Sibling Data


(XSLT 2.0) I am attempting to take a record, and split it into multiple records based off the grouped values of specific child elements - but I need to retain all other elements such that:

Sample XML

   <?xml version="1.0" encoding="UTF-8"?>
    <Report>
        <Record>
            <Name>Testing</Name>
            <Due_Date>2021-10-08</Due_Date>
            <Customer>
                <Name>Test Customer</Name>
                <State>GA</State>
            </Customer>
            <To_Transaction>
                <Contact name="John Smith">
                    <ID>12345</ID>
                </Contact>
                <Contact_Email>[email protected]</Contact_Email>
                <Amount>100.00</Amount>
            </To_Transaction>
            <To_Transaction>
                <Contact name="John Smith">
                    <ID>12345</ID>
                </Contact>
                <Contact_Email>[email protected]</Contact_Email>
                <Amount>150.00</Amount>
            </To_Transaction>
            <To_Transaction>
                <Contact name="Jane Doe">
                    <ID>54321</ID>
                </Contact>
                <Contact_Email>[email protected]</Contact_Email>
                <Amount>100.00</Amount>
            </To_Transaction>
            <From_Transaction>
                <Contact name="John Smith">
                    <ID>12345</ID>
                </Contact>
                <Contact_Email>[email protected]</Contact_Email>
                <Amount>8.00</Amount>
            </From_Transaction>
            <From_Transaction>
                <Contact name="Jane Doe">
                    <ID>54321</ID>
                </Contact>
                <Contact_Email>[email protected]</Contact_Email>
                <Amount>75.00</Amount>
            </From_Transaction>
            <From_Transaction>
                <Contact name="Jane Doe">
                    <ID>54321</ID>
                </Contact>
                <Contact_Email>[email protected]</Contact_Email>
                <Amount>50.00</Amount>
            </From_Transaction>
        </Record>
    </Report>

Becomes... Expected Result

<Report>
    <Record>
        <Name>Testing</Name>
        <Due_Date>2021-10-08</Due_Date>
        <Customer>
            <Name>Test Customer</Name>
            <State>GA</State>
        </Customer>
        <To_Transaction>
            <Contact name="John Smith">
                <ID>12345</ID>
            </Contact>
            <Contact_Email>[email protected]</Contact_Email>
            <Amount>100.00</Amount>
        </To_Transaction>
        <To_Transaction>
            <Contact name="John Smith">
                <ID>12345</ID>
            </Contact>
            <Contact_Email>[email protected]</Contact_Email>
            <Amount>150.00</Amount>
        </To_Transaction>
        <From_Transaction>
            <Contact name="John Smith">
                <ID>12345</ID>
            </Contact>
            <Contact_Email>[email protected]</Contact_Email>
            <Amount>8.00</Amount>
        </From_Transaction>
    </Record>
    <Record>
        <Name>Testing</Name>
        <Due_Date>2021-10-08</Due_Date>
        <Customer>
            <Name>Test Customer</Name>
            <State>GA</State>
        </Customer>
        <To_Transaction>
            <Contact name="Jane Doe">
                <ID>54321</ID>
            </Contact>
            <Contact_Email>[email protected]</Contact_Email>
            <Amount>100.00</Amount>
        </To_Transaction>
        <From_Transaction>
            <Contact name="Jane Doe">
                <ID>54321</ID>
            </Contact>
            <Contact_Email>[email protected]</Contact_Email>
            <Amount>75.00</Amount>
        </From_Transaction>
        <From_Transaction>
            <Contact name="Jane Doe">
                <ID>54321</ID>
            </Contact>
            <Contact_Email>[email protected]</Contact_Email>
            <Amount>50.00</Amount>
        </From_Transaction>
    </Record>
</Report>

Ideally, I would be grouping the transaction elements by the Contact @name attribute. The closest I've gotten so far is below. I've tried identity templates but can't figure out how to make it work with the grouping:

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output omit-xml-declaration="yes" indent="yes"/>

    <xsl:template match="/*">
        <Report>
            <xsl:for-each-group select="Record" group-by="(To_Transaction,'*')">
                <Record>
                    <xsl:copy-of select="."/>                  
                    </Record>
                </xsl:for-each-group>
        </Report>
    </xsl:template>
        
</xsl:stylesheet>

Solution

  • AFAICT, you want to do:

    XSLT 2.0

    <xsl:stylesheet version="2.0" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
    
    <xsl:template match="/Report">
        <xsl:copy>
            <xsl:for-each-group select="Record/(To_Transaction | From_Transaction)" group-by="Contact/ID">
                <Record>
                    <xsl:copy-of select="../(Name | Due_Date | Customer)"/>
                    <xsl:copy-of select="current-group()"/>
                </Record>
            </xsl:for-each-group>
        </xsl:copy>
    </xsl:template>
    
    </xsl:stylesheet>
    

    Or, if you prefer:

    <xsl:stylesheet version="2.0" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
    
    <xsl:template match="/Report">
        <xsl:copy>
            <xsl:for-each-group select="Record/(To_Transaction | From_Transaction)" group-by="Contact/ID">
                <Record>
                    <xsl:copy-of select="../(* except(To_Transaction | From_Transaction))"/>
                    <xsl:copy-of select="current-group()"/>
                </Record>
            </xsl:for-each-group>
        </xsl:copy>
    </xsl:template>
    
    </xsl:stylesheet>