XSLT Spaghetti!

  • 15 years ago

    Hey everyone!
    This is going to be a monster; you can tell I'm gonna be trouble!
    Okay, I have an XML document that defines questions and answers in a 'QuestionBase'. Each question is part of a category nested n levels deep. Categories can either contain only questions or nested categories; only terminal categories can have questions.


    For example:

    Code:
    <?xml version="1.0" encoding="utf-8"?>
    <qb:questionbase xmlns:qb="http://www.thespoke.net/MyBlog/str8asacircle_/MyBlog.aspx?questionbase">
       <qb:windows>
           <qb:desktop>
               <qb:general>
                   <qb:icons>
                       <qb:sizes>
                           <qb:question>
                           </qb:question>
                           <qb:question>
                           </qb:question>
                       </qb:sizes>
                       <qb:shapes>
                       </qb:shapes>
                   </qb:icons>
                   <qb:other>
                       <qb:question>
                       </qb:question>
                       <qb:question>
                       </qb:question>
                       <qb:question>
                       </qb:question>
                       <qb:question>
                       </qb:question>
                   </qb:other>
               </qb:general>
           </qb:desktop>
           <qb:explorer>
               <qb:files>
                   <qb:question>
                   </qb:question>
                   <qb:question>
                   </qb:question>
               </qb:files>
           </qb:explorer>
       </qb:windows>
       <qb:word>
           <qb:documents>
               <qb:question>
               </qb:question>
           </qb:documents>
       </qb:word>
       <qb:powerpoint>
           <qb:slides>
               <qb:question>
               </qb:question>
               <qb:question>
               </qb:question>
               <qb:question>
               </qb:question>
               <qb:question>
               </qb:question>
           </qb:slides>
       </qb:powerpoint>
    </qb:questionbase>
    That's all hunky dory. Anyway, I'm working on an XSLT to transform the QuestionBase into an hierarchical list of categories. The one above would be transformed into:


       *  Windows
             o Desktop
                   + General
                         # Icons
                               * Sizes (2 questions)
                         # Other (4 questions)
             o Explorer
                   + Files (2 questions)
       * Word
             o Documents (1 question)
       * PowerPoint
             o Slides (4 questions)


    Each section links to a page showing those questions, but that's not relevant for this question. The basic XSLT 'block' is as follows:

    Code:
    <?xml version="1.0" encoding="utf-8"?>
    <xsl:transform version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:qb="http://www.thespoke.net/MyBlog/str8asacircle_/MyBlog.aspx?questionbase">
       <xsl:variable name="address" select="'./showcategory.aspx?category='"/>
       <xsl:variable name="uncategorised" select="'Other'" />
       <xsl:template match="/">
           <html>
               <body>
                   <h1>Support</h1>
                   <h2>Question Categories</h2>
                   <p>Please select a category of your choice from the list below.</p>
                   <br/>
                   <!-- First level category -->
                   <!-- Are there any non-empty categories? -->
                   <xsl:if test="count(qb:questionbase/descendant::[qb:question]) = 0">
                       <xsl:message terminate="yes">
                           <h3>There are currently no question categories. Please check back soon.</h3>
                       </xsl:message>
                   </xsl:if>
                   <!-- Loop through all categories underneath the root node -->
                   <xsl:for-each select="qb:questionbase/child::
    ">
                       <!-- We know there is at least one category beneath this one that contains questions, so start a list -->
                       <ul>
                           <!-- Does this category have subcategories that contain questions (otherwise discard)? -->
                           <xsl:if test="count(descendant::qb:question) > 0">
                           <!-- Does this category have a name (unnamed catagories are disregarded)? -->
                           <xsl:if test="string-length(@name) != 0">
                               <!-- If so, prepare a list item for it -->
                               <li>                      
                                   <!-- Is the category a terminal category (contains questions)? -->
                                   <xsl:choose>
                                       <xsl:when test="count(qb:question) > 0">
                                           <!-- Print the number of questions next to the category name and create a link to the questions -->
                                           <xsl:element name="a">
                                               <xsl:attribute name="href">
                                                   <xsl:value-of select="$address" />
                                                   <xsl:value-of select="@id" />                                          
                                               </xsl:attribute>
                                               <xsl:value-of select="@name"/>
                                               <xsl:text> </xsl:text>
                                               <xsl:text>(</xsl:text>
                                               <xsl:value-of select="count(qb:question)" />
                                               <xsl:choose>
                                                   <xsl:when test="count(qb:question) = 1">
                                                       <xsl:text> question</xsl:text>
                                                   </xsl:when>
                                                   <xsl:otherwise>
                                                       <xsl:text> questions</xsl:text>
                                                   </xsl:otherwise>
                                               </xsl:choose>
                                               <xsl:text>)</xsl:text>
                                           </xsl:element>
                                       </xsl:when>
                                       <!-- Otherwise, the category has subcategories and hence no questions -->
                                       <xsl:otherwise>  
                                           <!-- Output the category name to the list -->
                                           <xsl:value-of select="@name"/>
                                           <!-- Process inner children (with same algorithm) -->
                                           <!-- Second level category -->
                                           <!-- REPEAT WHAT'S ALREADY GONE -->
                                       </xsl:otherwise>
                                   </xsl:choose>
                               </li>
                           </xsl:if>
                           </xsl:if>
                       </ul>
                   </xsl:for-each>
               </body>
           </html>
       </xsl:template>
    </xsl:transform>
    That's absolutely fine, but as you can see it's a hefty chunk of XSLT already, since it checks if categories end in terminal categories with questions and doesn't output them if they don't, etc.


    Right, now for the question :P Remember that the categories are n levels deep. To process level two, I've got to copy the majority of the code into the bold section. For level three, copied again into the corresponding section in the level two section and so on. So the document ends up as a massive triangle with more tabs than actual code!


    I've always been taught that having code looking like a diamond is very bad practise.


    Is there any way to, upon repeating the node, recurse within it to check it's child nodes? And putting the bulk of the code in a <apply-template> doesn't count if you're considering what I think you're considering. What I want to know is if there's any way to do true recursion in XSLT. If so, I'll have avoided a massive headache because at the moment, I'm having to limit to 5 levels deep just to save my sanity :)


    If you have an answer, I'll be forever in your debt ;P


    Thanks,
    Matt.


    P.S. I'm New Here (well no that's not true, but I've been away a long time)!

Post a reply

No one has replied yet! Why not be the first?

Sign in or Join us (it's free).

Contribute

Why not write for us? Or you could submit an event or a user group in your area. Alternatively just tell us what you think!

Our tools

We've got automatic conversion tools to convert C# to VB.NET, VB.NET to C#. Also you can compress javascript and compress css and generate sql connection strings.

“Programs must be written for people to read, and only incidentally for machines to execute.”