tags:

views:

27

answers:

2

I have an XML document that models this hierarchy of tasks:

1     Customer
1.1   Product A
1.1.1 Task Alpha
1.1.2 Task Beta
1.2   Product B
1.2.1 Task Alpha
1.2.2 Task Gamma
2     Customer
2.1   Product W
2.1.1 Task Delta

Unknown number of Customers, Products, and Tasks per product. There can also be an unknown number of subtasks, so we could see this:

19.16.8.17.1 Subtask Something

The XML looks like this:

<ROWSET>
    <ROW>
        <PROJECT_CODE>Don't Care</PROJECT_CODE>
    </ROW>
    <ROW>
        <PROJECT_CODE>WBS</PROJECT_CODE>
        <TASK_DETAIL>
            <TASKS>
                <TASK>
                    <TASK_CODE>1</TASK_CODE>
                    <TASK_DESCRIPTION>Customer 1</TASK_DESCRIPTION>
                    <TASKS>
                        <TASK>
                            <TASK_CODE>1.1</TASK_CODE>
                            <TASK_DESCRIPTION>Product A</TASK_DESCRIPTION>
                            <TASKS>
                                <TASK_CODE>1.1.1</TASK_CODE>
                                <TASK_DESCRIPTION>Task Alpha</TASK_DESCRIPTION>
                                <TASKS />
                                <TASK_CODE>1.1.2</TASK_CODE>
                                <TASK_DESCRIPTION>Task Beta</TASK_DESCRIPTION>
                                <TASKS />
                            </TASKS>
                        </TASK>
                        <TASK>
                            <TASK_CODE>1.2</TASK_CODE>
                            <TASK_DESCRIPTION>Product B</TASK_DESCRIPTION>
                            <TASKS>
                                <TASK_CODE>1.2.1</TASK_CODE>
                                <TASK_DESCRIPTION>Task Alpha</TASK_DESCRIPTION>
                                <TASKS />
                                <TASK_CODE>1.2.2</TASK_CODE>
                                <TASK_DESCRIPTION>Task Gamma</TASK_DESCRIPTION>
                                <TASKS />
                            </TASKS>
                        </TASK>
                    </TASKS>
                </TASK>
                <TASK>
                    <TASK_CODE>2</TASK_CODE>
                    <TASK_DESCRIPTION>Customer 2</TASK_DESCRIPTION>
                    <TASKS>
                        <TASK>
                            <TASK_CODE>2.1</TASK_CODE>
                            <TASK_DESCRIPTION>Product W</TASK_DESCRIPTION>
                            <TASKS>
                                <TASK_CODE>2.1.1</TASK_CODE>
                                <TASK_DESCRIPTION>Task Delta</TASK_DESCRIPTION>
                                <TASKS />
                            </TASKS>
                        </TASK>
                    </TASKS>
                </TASK>
            </TASKS>
        </TASK_DETAIL>
    </ROW>
</ROWSET>

My first attempt at an XSLT is this:

<?xml version="1.0" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
<xsl:template match="ROW">
    <xsl:apply-templates select="PROJECT_CODE[.='WBS']"/>
</xsl:template>
<xsl:template match="PROJECT_CODE">
    <h1><xsl:value-of select="."/></h1>
    <table>
        <tr>
            <th>Task Code</th>
            <th>Description</th>
        </tr>
        <xsl:for-each select="./../TASK_DETAIL/TASKS/TASK">
        <tr>
            <td><xsl:value-of select="TASK_CODE"/></td>
            <td><xsl:value-of select="TASK_DESCRIPTION"/></td>
        </tr>
            <xsl:for-each select="./TASKS/TASK">
                <tr>
                    <td><xsl:value-of select="TASK_CODE"/></td>
                    <td><xsl:value-of select="TASK_DESCRIPTION"/></td>
                </tr>
            </xsl:for-each>
        </xsl:for-each>
    </table>
</xsl:template>

As you can see, I'm taking a very naive approach to parsing this thing. I would like to list all the tasks & subtasks of my particular WBS, no matter how many levels deep it goes. How do I do that?

+3  A: 

Instead of nesting xsl:for-each directly just declare a few templates with xsl:template and call them (recursively) via

  1. xsl:call-template if there is a specific named template you want or
  2. xsl:apply-templates if a template should be used based on an XPath expression (which in your case might be as simple as "task"

For basic usage xsl:apply-templates should be enough but sometimes you want to match a specific rule.

I would do it like this (note this is quite rough... I think you can figure out the details with a little help from e.g. w3schools):

  1. one template for tasks and one for task
  2. the template for tasks walks over the task nodes and calls the task template for each task node
  3. the task template itself calls the tasks template again if there is a tasks node
musiKk
Thanks for the broad overview. I started at W3 schools, but wasn't able to piece things together from them.
Nathan DeWitt
+2  A: 

Looks like musikk beat me to an explanation, but here's a demo xslt that does what I understand you want. In general, avoid xsl:for-each for most of the things you think you should use for-each for (i.e. for which you would use for-each in other languages). Instead, use apply-templates or call-templates as musikk says. Read up on modes (mode="foo") too if you have to process the same content several times (e.g. to generate a table of contents and then the body and then an index).

    <?xml version="1.0" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
  <xsl:template match="/">
    <html>
      <body>
        <xsl:apply-templates/>
      </body>
    </html>
  </xsl:template>
  <xsl:template match="ROW[./PROJECT_CODE='WBS']">
    <h1><xsl:value-of select="PROJECT_CODE"/></h1>
    <table>
      <tr>
        <th>Task Code</th>
        <th>Description</th>
      </tr>
      <xsl:apply-templates/>
    </table>
  </xsl:template>
  <xsl:template match="TASK">
    <tr>
      <td><xsl:value-of select="TASK_CODE"/></td>
      <td><xsl:value-of select="TASK_DESCRIPTION"/></td>
    </tr>
    <xsl:apply-templates/>
  </xsl:template>
  <xsl:template match="text()"/>
</xsl:stylesheet>

This produces:

    <html>
   <body>
      <h1>WBS</h1>
      <table>
         <tr>
            <th>Task Code</th>
            <th>Description</th>
         </tr>
         <tr>
            <td>1</td>
            <td>Customer 1</td>
         </tr>
         <tr>
            <td>1.1</td>
            <td>Product A</td>
         </tr>
         <tr>
            <td>1.2</td>
            <td>Product B</td>
         </tr>
         <tr>
            <td>2</td>
            <td>Customer 2</td>
         </tr>
         <tr>
            <td>2.1</td>
            <td>Product W</td>
         </tr>
      </table>
   </body>
</html>
David
can you check your formatting for me? it's not coming through.
Nathan DeWitt
Sorry about that, it's fixed now.
David
This is giving me what I want, except it's matching even the Project Codes that don't match 'WBS', so I'm getting way too much stuff. It also doesn't generate any of the `<th>` or `<h1>` elements, so I think that whole second template isn't matching.
Nathan DeWitt
changing to this: `match="ROWSET[./ROW/PROJECT_CODE='WBS']">` made the `<th>` elements show up, but the `<xsl:value-of select="PROJECT_CODE"/>` still isn't working.
Nathan DeWitt
I started from scratch with what you gave me, and I got it to do what I want when I changed to this: `<xsl:template match="ROW[./PROJECT_CODE=WBS']//TASK">` in the third template. Thanks for your help!
Nathan DeWitt
Sorry I missed your replies. I guess I don't have this site configured to notify me about replies to things I've posted. I'm glad you got things working though.
David