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>
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>