xsltmodeapply-templates

XSLT 1.0: apply-templates and template mode


I have the following XML:

<?xml version="1.0" encoding="UTF-8"?>
<Order>
<Item>
    <RECORD_ID>RECORD_ID</RECORD_ID>
    <ENTITY_CODE>ENTITY_CODE</ENTITY_CODE>
    <USER_CODE>USER_CODE</USER_CODE>
    <RECORD_DATE>RECORD_DATE</RECORD_DATE>
    <ITEM_CODE>ITEM_CODE</ITEM_CODE>
    <LINE_QUANTITY>LINE_QUANTITY</LINE_QUANTITY>
    <LINE_FREE_STOCK>LINE_FREE STOCK</LINE_FREE_STOCK>
    <LINE_PRICE>LINE_PRICE</LINE_PRICE>
    <LINE_DISCOUNT_PERCENT>LINE_DISCOUNT PERCENT</LINE_DISCOUNT_PERCENT>
</Item>
<Item>
    <RECORD_ID>9046</RECORD_ID>
    <ENTITY_CODE>12010601</ENTITY_CODE>
    <USER_CODE>122</USER_CODE>
    <RECORD_DATE>2011-08-24</RECORD_DATE>
    <ITEM_CODE>804-008165</ITEM_CODE>
    <LINE_QUANTITY>2</LINE_QUANTITY>
    <LINE_FREE_STOCK>1</LINE_FREE_STOCK>
</Item>
<Item>
    <RECORD_ID>9046</RECORD_ID>
    <ENTITY_CODE>12010601</ENTITY_CODE>
    <USER_CODE>122</USER_CODE>
    <RECORD_DATE>2011-08-24</RECORD_DATE>
    <ITEM_CODE>804-008161</ITEM_CODE>
    <LINE_QUANTITY>1</LINE_QUANTITY>
    <LINE_FREE_STOCK>1</LINE_FREE_STOCK>
</Item>
<Item>
    <RECORD_ID>9046</RECORD_ID>
    <ENTITY_CODE>12010601</ENTITY_CODE>
    <USER_CODE>122</USER_CODE>
    <RECORD_DATE>2011-08-24</RECORD_DATE>
    <ITEM_CODE>804-008225</ITEM_CODE>
    <LINE_QUANTITY>5</LINE_QUANTITY>
</Item>
</Order>

Sometimes within the item tag I have the element <LINE_FREE_STOCK>. If that occurs I have to create an additional position in the output XML.

Now I came up with this style sheet:

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

<xsl:template match="/">
    <ORDERS05>
        <IDOC BEGIN="1">
            <xsl:apply-templates select="Order"/>
        </IDOC>
    </ORDERS05>
</xsl:template>

<xsl:template match="Order">
    <Header>
        <xsl:value-of select="'some header data'"/>
    </Header>
    <xsl:apply-templates select="Item[position() >1]"/>
    <xsl:apply-templates select="Item[position() >1 and child::LINE_FREE_STOCK]" mode="freestock"/>
</xsl:template>

<xsl:template match="Item">
        <position>
            <item>
                <number><xsl:value-of select="ITEM_CODE"/></number>
                <quantity><xsl:value-of select="LINE_QUANTITY"/></quantity>
            </item>
        </position>
</xsl:template>

<xsl:template match="Item[position() >1 and child::LINE_FREE_STOCK]" mode="freestock">
        <position>
            <item>
                <number><xsl:value-of select="ITEM_CODE"/></number>
                <freestock_quant><xsl:value-of select="LINE_FREE_STOCK"/></freestock_quant>
            </item>
        </position>
</xsl:template>

</xsl:stylesheet>

It creates this (simplified) wanted output:

<?xml version="1.0" encoding="UTF-8"?>
<ORDERS05>
<IDOC BEGIN="1">
    <Header>some header data</Header>
    <position>
        <item>
            <number>804-008165</number>
            <quantity>2</quantity>
        </item>
    </position>
    <position>
        <item>
            <number>804-008161</number>
            <quantity>1</quantity>
        </item>
    </position>
    <position>
        <item>
            <number>804-008225</number>
            <quantity>5</quantity>
        </item>
    </position>
    <position>
        <item>
            <number>804-008165</number>
            <freestock_quant>1</freestock_quant>
        </item>
    </position>
    <position>
        <item>
            <number>804-008161</number>
            <freestock_quant>1</freestock_quant>
        </item>
    </position>
</IDOC>
</ORDERS05>

804-008165 and 804-008161 show up twice - once as a standard item and once as the free stock item with the respective quantities.

But did I forget anything here? Is there some sort of pitfall I don't see? Is that XSLT robust enough?


Solution

  • As others have noted, the problem is in this code:

    <xsl:apply-templates select="Item"/> 
    <xsl:apply-templates select="Item[child::LINE_FREE_STOCK]" mode="freestock"/> 
    

    If there is a child Item that has a child LINE_FREE_STOCK, templates would be applied on this Item element twice -- here is how you get the repetitions in the output.

    The transformation can be significantly shortened and it doesn't need modes or explicit conditional instructions at all:

    <xsl:stylesheet version="1.0"
     xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
     <xsl:output encoding="UTF-8" indent="yes"/>
    
     <xsl:template match="/">
        <ORDERS05>
            <IDOC BEGIN="1">
                <xsl:apply-templates select="Order"/>
            </IDOC>
        </ORDERS05>
     </xsl:template>
    
     <xsl:template match="Order">
        <Header>
            <xsl:value-of select="'some header data'"/>
        </Header>
            <xsl:apply-templates select="Item[position() >1]"/>
     </xsl:template>
    
     <xsl:template match="Item">
      <position>
        <item>
          <number>
            <xsl:value-of select="ITEM_CODE"/>
          </number>
    
          <xsl:apply-templates select=
            "self::node()[not(LINE_FREE_STOCK)]/LINE_QUANTITY
           |
             LINE_FREE_STOCK"/>
         </item>
      </position>
     </xsl:template>
    
     <xsl:template match="LINE_QUANTITY">
       <quantity>
         <xsl:value-of select="."/>
       </quantity>
     </xsl:template>
    
     <xsl:template match="LINE_FREE_STOCK">
       <freestock_quant>
         <xsl:value-of select="."/>
       </freestock_quant>
     </xsl:template>
    </xsl:stylesheet>
    

    When this transformation is applied on the provided XML document:

    <Order>
        <Item>
            <RECORD_ID>RECORD_ID</RECORD_ID>
            <ENTITY_CODE>ENTITY_CODE</ENTITY_CODE>
            <USER_CODE>USER_CODE</USER_CODE>
            <RECORD_DATE>RECORD_DATE</RECORD_DATE>
            <ITEM_CODE>ITEM_CODE</ITEM_CODE>
            <LINE_QUANTITY>LINE_QUANTITY</LINE_QUANTITY>
            <LINE_FREE_STOCK>LINE_FREE STOCK</LINE_FREE_STOCK>
            <LINE_PRICE>LINE_PRICE</LINE_PRICE>
            <LINE_DISCOUNT_PERCENT>LINE_DISCOUNT PERCENT</LINE_DISCOUNT_PERCENT>
        </Item>
        <Item>
            <RECORD_ID>9046</RECORD_ID>
            <ENTITY_CODE>12010601</ENTITY_CODE>
            <USER_CODE>122</USER_CODE>
            <RECORD_DATE>2011-08-24</RECORD_DATE>
            <ITEM_CODE>804-008165</ITEM_CODE>
            <LINE_QUANTITY>2</LINE_QUANTITY>
            <LINE_FREE_STOCK>1</LINE_FREE_STOCK>
        </Item>
        <Item>
            <RECORD_ID>9046</RECORD_ID>
            <ENTITY_CODE>12010601</ENTITY_CODE>
            <USER_CODE>122</USER_CODE>
            <RECORD_DATE>2011-08-24</RECORD_DATE>
            <ITEM_CODE>804-008161</ITEM_CODE>
            <LINE_QUANTITY>1</LINE_QUANTITY>
            <LINE_FREE_STOCK>1</LINE_FREE_STOCK>
        </Item>
        <Item>
            <RECORD_ID>9046</RECORD_ID>
            <ENTITY_CODE>12010601</ENTITY_CODE>
            <USER_CODE>122</USER_CODE>
            <RECORD_DATE>2011-08-24</RECORD_DATE>
            <ITEM_CODE>804-008225</ITEM_CODE>
            <LINE_QUANTITY>5</LINE_QUANTITY>
        </Item>
    </Order>
    

    the wanted, correct result is produced:

    <ORDERS05>
       <IDOC BEGIN="1">
          <Header>some header data</Header>
          <position>
             <item>
                <number>804-008165</number>
                <freestock_quant>1</freestock_quant>
             </item>
          </position>
          <position>
             <item>
                <number>804-008161</number>
                <freestock_quant>1</freestock_quant>
             </item>
          </position>
          <position>
             <item>
                <number>804-008225</number>
                <quantity>5</quantity>
             </item>
          </position>
       </IDOC>
    </ORDERS05>