XSLT if node has attribute value, put it in sibling node

Viewed 55

I've recently "discovered" XSLT and am probably overusing it in my current project, but having alot of fun with it and that's what counts.

In my example, I have some HTML code that looks like this:

<table>
          <tr class="itemRow">
            <td class="hide">MAIN001</td>
            <td class="orderBoxQty ">1</td>
            <td class="orderBoxItem itemCell">Burger</td>
            <td class="orderBoxTotalPrice ">15.00</td>
            <td class="orderBoxCourse ">Mains</td>
            <td class="hide">15.00</td>
            <td class="hide">2</td>
            <td class="hide">item</td>
         </tr>
         <tr class="instructionRow">
            <td class="hide">INSTRUCTION002</td>
            <td class="orderBoxQty invisibleText">1</td>
            <td class="orderBoxItem itemCell">Add Bacon</td>
            <td class="orderBoxTotalPrice ">1.50</td>
            <td class="orderBoxCourse invisibleText">3</td>
            <td class="hide">1.50</td>
            <td class="hide">3</td>
            <td class="hide">instruction</td>
         </tr>
</table>

Which I want to transform to clean XML, which I am doing with an existing XSL transform, so the output looks something like this:

   <item type="item">
      <id>MAIN001</id>
      <quantity>1</quantity>
      <name>Burger</name>
      <unitprice>15.00</unitprice>
      <totalprice>15.00</totalprice>
      <printgroup>Mains</printgroup>
   </item>
   <item type="instruction">
      <id>INSTRUCTION002</id>
      <quantity>1</quantity>
      <name>Add Bacon</name>
      <unitprice>1.50</unitprice>
      <totalprice>1.50</totalprice>
      <printgroup>3</printgroup>
   </item>

I'm achieving that by a foreach of each <tr> and then assigning the type attribute based on the class.

Now I want to be a bit more ambitious. If the HTML has class="instructionRow", I want to embed it in the previous tag, so it would like this:

   <item type="item">
      <id>MAIN001</id>
      <quantity>1</quantity>
      <name>Burger</name>
      <unitprice>15.00</unitprice>
      <totalprice>15.00</totalprice>
      <printgroup>Mains</printgroup>
     <!--if there's an instruction row next, it goes here-->      
     <instruction>
          <id>INSTRUCTION002</id>
          <name>Add Bacon</name>
          <unitprice>1.50</unitprice>
      </instruction>
   </item>

There are no case where an instruction row would ever be present without a preceding item row, and it's no huge deal to manipulate this with programming, but am wondering what I should be looking at for a pure XSLT solution.

The current data and XSLT is here: http://xsltransform.net/gVTZeVo/2

I am testing test="following-sibling::tr/@class = 'instructionRow'" in an if statement, but that checks all siblings with class instructionRow, I only want it to select siblings with instructionRow UNTIL it gets to sibling that doesn't (or runs out of siblings).

1 Answers

Try this:

    <!-- root - kick things off -->
    <xsl:template match="/">
        <order>
            <xsl:apply-templates select='table/tbody/tr[@class = "itemRow"]' mode='item'/>
        </order>
    </xsl:template>

    <!-- iteration item - item -->
    <xsl:template match='tr' mode='item'>
        <item type="item">
            <id><xsl:value-of select="td[1]" /></id>
            <quantity><xsl:value-of select="td[2]" /></quantity>
            <name><xsl:value-of select="td[3]" /> </name>
            <unitprice><xsl:value-of select="td[6]" /> </unitprice>
            <totalprice><xsl:value-of select="td[4]" /> </totalprice>
            <printgroup><xsl:value-of select="td[5]" /></printgroup>      
            <xsl:apply-templates select='following-sibling::tr[@class = "instructionRow" and preceding-sibling::tr[@class = "itemRow"][1] = current()]' mode='instruction'/>
        </item>
    </xsl:template>

    <!-- iteration item - instruction -->
    <xsl:template match='tr' mode='instruction'>
        <instruction>
            <id><xsl:value-of select="td[1]" /></id>
            <name><xsl:value-of select="td[3]" /> </name>
            <unitprice><xsl:value-of select="td[6]" /> </unitprice>
        </instruction>
    </xsl:template>

Note I've ditched the for-each - these are normally best avoided in XSLT in favour of using templates instead.

The logic here is to have a template for each item type (since both match tr, we distinguish between them with the mode identifier attribute).

After transforming an item row, we check its next sibling (only) and, if it's an instruction row, output that - within the current iteration item.

Related