简体   繁体   中英

Failure for XSL to create child elements in XML transformation

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?

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 .

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>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM