xmlxslt-2.0xslt-3.0

XSLT split XML into separate messsages by child node


I'd like to split an XML message into separate messages, by each ocurence of a subnode (A_SalesOrderItemType), keeping the parent values for each via XSLT 2.0 or 3.0 transformation.

An excerpt of the XML file

<?xml version="1.0" encoding="UTF-8"?>
<A_SalesOrder>
    <A_SalesOrderType>
        <AdditionalValueDays>0</AdditionalValueDays>
        <CreationDate>2024-03-25T00:00:00.000</CreationDate>
        <to_Item>
            <A_SalesOrderItemType>
                <SalesOrderItemText>1</SalesOrderItemText>
                <AdditionalValueDays>0</AdditionalValueDays>
                <to_ScheduleLine>
                    <A_SalesOrderScheduleLineType>
                        <OpenConfdDelivQtyInOrdQtyUnit>1000</OpenConfdDelivQtyInOrdQtyUnit>
                        <ScheduleLine>1</ScheduleLine>
                    </A_SalesOrderScheduleLineType>
                </to_ScheduleLine>
            </A_SalesOrderItemType>
            <A_SalesOrderItemType>
                <SalesOrderItemText>2</SalesOrderItemText>
                <AdditionalValueDays>0</AdditionalValueDays>
                <to_ScheduleLine>
                    <A_SalesOrderScheduleLineType>
                        <OpenConfdDelivQtyInOrdQtyUnit>2000</OpenConfdDelivQtyInOrdQtyUnit>
                        <ScheduleLine>1</ScheduleLine>
                    </A_SalesOrderScheduleLineType>
                </to_ScheduleLine>
            </A_SalesOrderItemType>
        </to_Item>
    </A_SalesOrderType>
    <A_SalesOrderType>
        <AdditionalValueDays>0</AdditionalValueDays>
        <CreationDate>2024-03-25T00:00:00.000</CreationDate>
        <to_Item>
            <A_SalesOrderItemType>
                <SalesOrderItemText>3</SalesOrderItemText>
                <AdditionalValueDays>0</AdditionalValueDays>
                <to_ScheduleLine>
                    <A_SalesOrderScheduleLineType>
                        <OpenConfdDelivQtyInOrdQtyUnit>3000</OpenConfdDelivQtyInOrdQtyUnit>
                        <ScheduleLine>1</ScheduleLine>
                    </A_SalesOrderScheduleLineType>
                </to_ScheduleLine>
            </A_SalesOrderItemType>
            <A_SalesOrderItemType>
                <SalesOrderItemText>4</SalesOrderItemText>
                <AdditionalValueDays>0</AdditionalValueDays>
                <to_ScheduleLine>
                    <A_SalesOrderScheduleLineType>
                        <OpenConfdDelivQtyInOrdQtyUnit>4000</OpenConfdDelivQtyInOrdQtyUnit>
                        <ScheduleLine>1</ScheduleLine>
                    </A_SalesOrderScheduleLineType>
                </to_ScheduleLine>
            </A_SalesOrderItemType>
        </to_Item>
    </A_SalesOrderType>
</A_SalesOrder>

Desired output

<?xml version="1.0" encoding="UTF-8"?>
<A_SalesOrder>
    <A_SalesOrderType>
        <AdditionalValueDays>0</AdditionalValueDays>
        <CreationDate>2024-03-25T00:00:00.000</CreationDate>
        <to_Item>
            <A_SalesOrderItemType>
                <SalesOrderItemText>1</SalesOrderItemText>
                <AdditionalValueDays>0</AdditionalValueDays>
                <to_ScheduleLine>
                    <A_SalesOrderScheduleLineType>
                        <OpenConfdDelivQtyInOrdQtyUnit>1000</OpenConfdDelivQtyInOrdQtyUnit>
                        <ScheduleLine>1</ScheduleLine>
                    </A_SalesOrderScheduleLineType>
                </to_ScheduleLine>
            </A_SalesOrderItemType>
        </to_Item>
    </A_SalesOrderType>
    <A_SalesOrderType>
        <AdditionalValueDays>0</AdditionalValueDays>
        <CreationDate>2024-03-25T00:00:00.000</CreationDate>
        <to_Item>
            <A_SalesOrderItemType>
                <SalesOrderItemText>2</SalesOrderItemText>
                <AdditionalValueDays>0</AdditionalValueDays>
                <to_ScheduleLine>
                    <A_SalesOrderScheduleLineType>
                        <OpenConfdDelivQtyInOrdQtyUnit>2000</OpenConfdDelivQtyInOrdQtyUnit>
                        <ScheduleLine>1</ScheduleLine>
                    </A_SalesOrderScheduleLineType>
                </to_ScheduleLine>
            </A_SalesOrderItemType>
        </to_Item>
    </A_SalesOrderType>
.
.
.
</A_SalesOrder>

The XSLT transformation below works as expected, but is there a more elegant way than copying all parent elements manually?

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="3.0"
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:fo="http://www.w3.org/1999/XSL/Format"
                xmlns:xs="http://www.w3.org/2001/XMLSchema"
                xmlns:math="http://www.w3.org/2005/xpath-functions/math"
                xmlns:fn="http://www.w3.org/2005/xpath-functions"
                xmlns:map = "http://www.w3.org/2005/xpath-functions/map"
                exclude-result-prefixes="xsl fo xs math fn map">
    <xsl:output indent="yes" />
    <!-- defines the format of the output document xml|html|text|name -->
    <xsl:output method="xml" indent="yes"/>
    <xsl:output encoding="utf-8" />
    <!-- remove white space nodes -->
    <xsl:strip-space elements="*"/>

    <xsl:template match="/">
        <A_SalesOrder>
            <xsl:for-each select="A_SalesOrder/A_SalesOrderType/to_Item/A_SalesOrderItemType">
                <A_SalesOrderType>
                    <AdditionalValueDays>
                        <xsl:value-of select="../../AdditionalValueDays" />
                    </AdditionalValueDays>
                    <CreationDate>
                        <xsl:value-of select="../../CreationDate" />
                    </CreationDate>
                    <to_Item>
                        <A_SalesOrderItemType>
                            <SalesOrderItemText>
                                <xsl:value-of select="SalesOrderItemText" />
                            </SalesOrderItemText>
                            <AdditionalValueDays>
                                <xsl:value-of select="AdditionalValueDays" />
                            </AdditionalValueDays>
                            <xsl:copy-of select="to_ScheduleLine"/>
                        </A_SalesOrderItemType>
                    </to_Item>
                </A_SalesOrderType>
            </xsl:for-each>
        </A_SalesOrder>
    </xsl:template>
</xsl:stylesheet>

Solution

  • There is a snapshot function in XSLT 3 you could use if you just wanted the subtree containing a certain element but you seem to want to copy some preceding siblings as well so I think you need an approach like

    <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
      version="3.0"
      xmlns:xs="http://www.w3.org/2001/XMLSchema"
      exclude-result-prefixes="#all">
      
      <xsl:mode on-no-match="shallow-copy"/>
      
      <xsl:output indent="yes"/>
    
      <xsl:template match="/">
        <A_SalesOrder>
          <xsl:apply-templates select="A_SalesOrder/A_SalesOrderType/to_Item/A_SalesOrderItemType" mode="split"/>
        </A_SalesOrder>
      </xsl:template>
      
      <xsl:template match="A_SalesOrderItemType" mode="split">
        <xsl:apply-templates select="ancestor::A_SalesOrderType">
          <xsl:with-param name="item" select="current()" tunnel="yes"/>
        </xsl:apply-templates>
      </xsl:template>
      
      <xsl:template match="A_SalesOrderItemType">
        <xsl:param name="item" tunnel="yes"/>
        <xsl:if test=". is $item">
          <xsl:next-match/>
        </xsl:if>
      </xsl:template>
    
    </xsl:stylesheet>