Search code examples
xsltxpathapply-templates

XSLT apply-templates recursiveness of doom!


I have to following XML document structure:

<option_set id="1">
  <option>Yes</option>
  <option>No</option>
  <option>Maybe</option>
</option_set>

<question option_set="1">
  <text>Do you like cake?</text>
</question>
<question option_set="1">
  <text>Is the cake a lie?</text>
</question>

In the interests of keeping things DRY, the idea is to have a number of different questions which share common sets of options. These can then be built using XSLT. My templates are as follows:

<xsl:template match="question[@option_set and not(option)]">
  <!-- Build a whole question with its options
       (copy the options across and then apply-templates?) -->
</xsl:template>

<xsl:template match="question[option]">
  <!-- Match a whole question, with options, for making pretty HTML out of -->
</xsl:template>

The idea is that once the top template has matched my question, I will be left with something that looks like the following:

<question>
  <text>Do you like cake?</text>
  <option>Yes</option>
  <option>No</option>
  <option>Maybe</option>
</question>

... Which can then be matched by the bottom template and put into my HTML document. My question is how do I create the (top) template that actually does that. I'm close, but this still isn't working:

<xsl:template match="question[@option_set and not(option)]">
  <xsl:variable name="optset" select="@option_set"/>

  <xsl:copy>
    <xsl:copy-of select="text"/>
    <xsl:copy-of select="//option_set[@id=$optset]/option"/>
    <xsl:apply-templates/>
  </xsl:copy>
</xsl:template>

The transformed question block, along with its options is being copied over to the document, instead of being picked up by the top template and made into pretty HTML.

If I try to <xsl:apply-templates select="."/> then I get caught in an infinite loop.


Solution

  • Not sure what you're ultimately trying to do, but this might help you.

    <xsl:template match="question">
      <xsl:value-of select="text"/>: 
      <select>
         <xsl:variable name="option_set_id" select="@option_set"/>
         <xsl:apply-templates select="option | //option_set[@id=$option_set_id]/option"/>
      </select>
    </xsl:template>
    
    <xsl:template match="option">
       <option>
          <xsl:value-of select="."/>
       </option>
    </xsl:template>
    

    There are tweaks, like adding the key above, and checking for unused option_sets etc. but this'll get you started.