tags:

views:

230

answers:

4

I have a recursive nodes that I'm trying to set up for jquery-checktree. The nodes look like

foo/bar/ID
       /NAME
       /CHECKED
       bar/ID
          /NAME
          /CHECKED
   /bar/ID
       /NAME
   /bar/ID
       /NAME
       /bar/ID
           /NAME
           /CHECKED
           /bar/ID
               /NAME
               /CHECKED

Where any bar may or may not have one or more bar nodes below it, but any bar will have ID and NAME and might have a CHECKED.

and I want to turn that into

<ul>
  <li><input type="checkbox" name="..." value="..." checked="checked"></input>
      <label for="...">...</label>
      <ul>
        <li><input type="checkbox" name="..." value="..." checked="checked"></input>
          <label for="...">...</label>
        </li>
      </ul>
  <li>....</li>
</ul>

I can get the first level by doing:

    <ul class="tree">
    <xsl:for-each select="/foo/bar/">
        <li><input type="checkbox" name="{ID}" value="{ID}">
            <xsl:if test="CHECKED = 'Y'"><xsl:attribute name="checked">checked</xsl:attribute></xsl:if>
            </input><label for="{ID}"><xsl:value-of select="NAME"/></label>
        </li>
    </xsl:for-each>
    </ul>

But I don't know how to recurse down to the embedded "bar" within the "bar", down to however many levels there might be.

+2  A: 

<xsl:template match="foo">
    <ul class="tree">
        <xsl:apply-templates/>
    </ul>
</xsl:template>

<xsl:template match="bar" name="wunderbar">
<!-- we want to match all bars, not only /foo/bars -->
    <li>
        <input type="checkbox" name="{ID}" value="{ID}">
        <xsl:if test="CHECKED = 'Y'"><xsl:attribute name="checked">checked</xsl:attribute></xsl:if>
        </input><label for="{ID}">
            <xsl:value-of select="NAME"/>
        </label>
        <!-- If there is some bar, the next template is applied -->
        <xsl:apply-templates/>
    </li>
</xsl:template>

<xsl:template match="bar/bar">
<!-- Just adds <ul> around bar included in bar and calls the usual template -->
    <ul>
        <xsl:call-template name="wunderbar"/>
    </ul>
</xsl:template>

Krab
+4  A: 

Here's one way:

<xsl:template match="bar">
    <li>
        <input type="checkbox" name="{ID}" value="{ID}">
            <xsl:if test="CHECKED = 'Y'">
                <xsl:attribute name="checked">checked</xsl:attribute>
            </xsl:if>
        </input>
        <label for="{ID}"><xsl:value-of select="NAME"/></label>
        <!-- 

            If we have bar children, make a list and recurse

        -->
        <xsl:if test="bar">
            <ul>
                <xsl:apply-templates select="bar"/>
            </ul>
        </xsl:if>
    </li>
</xsl:template>

This relies on the "automatic" template matching. To ensure the matching takes place, you could either put a <xsl:apply-templates/> inside the <xsl:for-each> loop of your original code, however, you can even improve it all and replace that original code with this template:

<xsl:template match="/foo">
   <ul class="tree">
       <xsl:apply-templates select="bar"/>
   </ul>
</xsl:template>

If you want more control, you can also use <xsl:for-each select="bar"> and call a named template (<xsl:template name="some-name">... and <xsl:call-template>) inside the loop. See: http://www.w3.org/TR/xslt#named-templates

Roland Bouman
+1 Flawless solution.
Tomalak
It doesn't seem to be finding the first "bar".
Paul Tomblin
Thanks, Tomalak :)
Roland Bouman
Also, how do I get the first `<ul></ul>`?
Paul Tomblin
@Paul Tomblin: what you see here is just part of a stylesheet, illustrating only the recursion. I will amend my answer to ensure matching of the entire structure.
Roland Bouman
Thanks - I'm really an xsl newbie and I need all the help I can get.
Paul Tomblin
Paul: xslt is hard and simple at the same time...it's hard to explain. Don't let it get you down, it may take some time to grok it, but once you do, it can make some terribly complicated things extremely simple. Unfortunately it makes some other things that should be simple terribly complicated, but that's another story :)
Roland Bouman
My "xsl:for-each" was in a different "xsl:template", so I had to put a `<xsl:apply-templates select="/foo">` in place of the for-each and put the other templates outside, but that appears to have done what I want. Thanks for your patience.
Paul Tomblin
+3  A: 

This is an example (proof of concept, actually) for a completely input-driven, push-style solution (template matching only, no conditionals, no named templates):

<xsl:stylesheet 
  version="1.0" 
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
>

  <xsl:template match="*[bar]">
    <ul class="tree">
      <xsl:apply-templates select="bar" mode="li" />
    </ul>
  </xsl:template>

  <xsl:template match="bar" mode="li">
    <li>
      <xsl:apply-templates select="." mode="checkbox" />
      <xsl:apply-templates select="(.)[bar]" />
    </li>
  </xsl:template>

  <xsl:template match="bar" mode="checkbox">
    <input type="checkbox" id="{ID}" name="{NAME}">
      <xsl:apply-templates select="CHECKED" />
    </input>
    <label for="{ID}">
      <xsl:value-of select="NAME" />
    </label>
  </xsl:template>

  <xsl:template match="CHECKED">
    <xsl:attribute name="checked">checked</xsl:attribute>
  </xsl:template>

</xsl:stylesheet>

When applied to this input XML (extrapolated from your question):

<foo>
  <bar>
    <ID>nd1</ID>
    <NAME>Node 1</NAME>
    <CHECKED />
    <bar>
      <ID>nd2</ID>
      <NAME>Node 2</NAME>
      <CHECKED />
    </bar>
  </bar>
  <bar>
    <ID>nd3</ID>
    <NAME>Node 3</NAME>
  </bar>
  <bar>
    <ID>nd4</ID>
    <NAME>Node 4</NAME>
    <bar>
      <ID>nd5</ID>
      <NAME>Node 5</NAME>
      <CHECKED />
      <bar>
        <ID>nd6</ID>
        <NAME>Node 6</NAME>
        <CHECKED />
      </bar>
    </bar>
  </bar>
</foo>

It produces this output:

<ul class="tree">
  <li>
    <input type="checkbox" id="nd1" name="Node 1" checked="checked" />
    <label for="nd1">Node 1</label>
    <ul class="tree">
      <li>
        <input type="checkbox" id="nd2" name="Node 2" checked="checked" />
        <label for="nd2">Node 2</label>
      </li>
    </ul>
  </li>
  <li>
    <input type="checkbox" id="nd3" name="Node 3" />
    <label for="nd3">Node 3</label>
  </li>
  <li>
    <input type="checkbox" id="nd4" name="Node 4" />
    <label for="nd4">Node 4</label>
    <ul class="tree">
      <li>
        <input type="checkbox" id="nd5" name="Node 5" checked="checked" />
        <label for="nd5">Node 5</label>
        <ul class="tree">
          <li>
            <input type="checkbox" id="nd6" name="Node 6" checked="checked" />
            <label for="nd6">Node 6</label>
          </li>
        </ul>
      </li>
    </ul>
  </li>
</ul>
Tomalak
Cool! thanks for sharing :)
Roland Bouman
A: 

hi,

if i want that some value from the xml insert into the text area in the label. how can i do that?

for example if i want that the ID attribute inser into the label foreach ID.

joe
Don't ask a question as an answer.
Paul Tomblin