Reputation: 196
Trying and trying to get the following to work. I have an XML file (serialized C# class) similar to
<?xml version="1.0" encoding="utf-8"?>
<MyXml xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Order>
<Header>
<OrderNumber>1234</OrderNumber>
</Header>
<Line>
<Sku>abc</Sku>
<Qty>300</Qty>
</Line>
<Line>
<Sku>xyz</Sku>
<Qty>19</Qty>
</Line>
</Order>
I need to transform this to:
<?xml version="1.0" encoding="utf-8"?>
<Order Number="1234">
<Line>
<Product>abc</Product>
<Quantity>300</Quantity>
</Line>
<Line>
<Product>xyz</Product>
<Quantity>19</Quantity>
</Line>
</Order>
Making the lines children of the Order element.
Here's my most succesful attempt so far.
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl"
>
<xsl:output method="xml" indent="yes"/>
<xsl:template match="Header" >
<Order>
<xsl:attribute name="Number">
<xsl:value-of select="OrderNumber"/>
</xsl:attribute>
<xsl:apply-templates select="Line"/>
</Order>
</xsl:template>
<xsl:template match="Line">
<Line>
<Product>
<xsl:value-of select="Sku" />
</Product>
<Quantity>
<xsl:value-of select="Qty" />
</Quantity>
</Line>
</xsl:template>
</xsl:stylesheet>
Which produces the incorrect xml below with multiple root nodes.
<?xml version="1.0" encoding="utf-8"?>
<Order OrderNumber="1234" />
<Line><Product>abc</Product><Quantity>300</Quantity></Line>
<Line><Product>xyz</Product><Quantity>19</Quantity></Line>
There's clearly something I'm missing, but after hours of trying I cannot get the output Line elements to be children of Order at all. Can someone point me in the right direction? Also why does the indent option only seem to affect the first level of the output xml?
Upvotes: 0
Views: 270
Reputation: 70608
You have a template matching Header
, but within this you do <xsl:apply-templates select="Line"/>
to get the Line
elements, but Line
is not a child of Header
, so this selects nothing.
The Line
elements in your output are actually being selected due to XSLT's built-in template rules. You don't have a template matching Order
, so XSLT's built-in template is used, which will select both Header
and Line
elements under the Order
.
One solution is is to change your the template matching Header
to match Order
instead.
Try this XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="Order" >
<Order Number="{Header/OrderNumber}">
<xsl:apply-templates select="Line"/>
</Order>
</xsl:template>
<xsl:template match="Line">
<Line>
<Product>
<xsl:value-of select="Sku" />
</Product>
<Quantity>
<xsl:value-of select="Qty" />
</Quantity>
</Line>
</xsl:template>
</xsl:stylesheet>
Note the use of Attribute Value Templates to simplify the creation of the Number
attribute on Order
.
Upvotes: 1
Reputation: 29022
Another solution is using xsl:apply-templates
on ../Line
instead of Line
with a mode
attribute on the template. So the changes to the template are minimal.
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="Header" >
<Order>
<xsl:attribute name="Number">
<xsl:value-of select="OrderNumber"/>
</xsl:attribute>
<xsl:apply-templates select="../Line" mode="sub"/> <!-- set 'mode' to 'sub' and add '../' to XPath-->
</Order>
</xsl:template>
<xsl:template match="Line" mode="sub"> <!-- use template only when 'mode' is set to 'sub' -->
<Line>
<Product>
<xsl:value-of select="Sku" />
</Product>
<Quantity>
<xsl:value-of select="Qty" />
</Quantity>
</Line>
</xsl:template>
<xsl:template match="text()" /> <!-- ignore all unmatched text() nodes -->
</xsl:stylesheet>
Upvotes: 0