XSLT 1.0: How to move an attribute and its value to only the first element of a new element

Viewed 52

I have the following XML:

<?xml version="1.0" encoding="utf-8"?>
<DocumentElement>
  <Invoice>
    <Header Misc="5.00" TotalAmount="33.00" />
    <Packages>
      <Package>
        <Lines>
          <Line>
            <LineCharge Amount="28.00" />
          </Line>
        </Lines>
      </Package>
      <Package>
        <Lines>
          <Line>
            <LineCharge Amount="0.00" />
          </Line>
        </Lines>
      </Package>
    </Packages>
  </Invoice>
</DocumentElement>

And applied the following template to move the Misc attribute and its value from the Header element, created a new Element called LineCharge and assigned the value of Misc to it.

<?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"  xmlns:xe="urn:XSLTExtender"
                exclude-result-prefixes="msxsl">

<xsl:output method="xml" omit-xml-declaration="yes" indent="yes" />

<xsl:template match="@*|node()" >
    <xsl:copy>
      <xsl:apply-templates select="@*[normalize-space()]|node()"/>
    </xsl:copy>
  </xsl:template>

  <xsl:template match="@Misc" />
  <xsl:template match ="LineCharge[1]" >
    <xsl:copy>
      <xsl:apply-templates select="@*|node()" />
    </xsl:copy>
    <xsl:if test="normalize-space(//Invoice/Header/@Misc) != '' and number(//Invoice/Header/@Misc) = normalize-space(//Invoice/Header/@Misc)">
      <xsl:element name ="LineCharge">        
        <xsl:attribute name="Chargecd" >
          <xsl:value-of select="'CD01'"/>
        </xsl:attribute>
        <xsl:attribute name="Amount" >
          <xsl:value-of select="//Invoice/Header/@Misc"/>
        </xsl:attribute>
      </xsl:element>
    </xsl:if>
  </xsl:template>
</xsl:stylesheet>

I got the following output:

<DocumentElement><Invoice>
  <Header TotalAmount="33.00" />
  <Packages>
    <Package>
      <Lines>
        <Line>
          <LineCharge Amount="28.00" />
          <LineCharge Chargecd="CD01" Amount="5.00" />
        </Line>
      </Lines>
    </Package>
    <Package>
      <Lines>
        <Line>
          <LineCharge Amount="0.00" />
          <LineCharge Chargecd="CD01" Amount="5.00" />
        </Line>
      </Lines>
    </Package>
  </Packages>
</Invoice></DocumentElement>

The TotalAmount should be 33.00 (i.e. 28.00 + 5.00), but since the new LineCharge element is being repeated for each Package element, the Total is now 38.00, which is wrong.

How do I get the following output where the Misc value is only moved to the first Package element, and not its siblings:

<DocumentElement><Invoice>
  <Header TotalAmount="33.00" />
  <Packages>
    <Package>
      <Lines>
        <Line>
          <LineCharge Amount="28.00" />
          <LineCharge Chargecd="CD01" Amount="5.00" />
        </Line>
      </Lines>
    </Package>
    <Package>
      <Lines>
        <Line>
          <LineCharge Amount="0.00" />          
        </Line>
      </Lines>
    </Package>
  </Packages>
</Invoice></DocumentElement>
1 Answers

Why not simply:

XSLT 1.0

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>

<!-- identity transform -->
<xsl:template match="@*|node()">
    <xsl:copy>
        <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
</xsl:template>

<xsl:template match="Package[1]/Lines/Line[1]">
    <xsl:copy>
        <xsl:apply-templates select="@*|node()"/>
        <LineCharge Chargecd="CD01" Amount="{ancestor::Invoice/Header/@Misc}" />
    </xsl:copy>
</xsl:template>

<xsl:template match="@Misc"/>

</xsl:stylesheet>
Related