tags:

views:

60

answers:

3

Hey Friends I've got another xsl/xpath problem. Sure a beginners problem, but I'm thinking still too "imperative".

<!-- XML -->
<Module>
    <WideTeaserElement>
      <Headline>Bla bla 1</Headline>
    </WideTeaserElement>
    <WideTeaserElement>
      <Headline>Bla bla 2</Headline>
    </WideTeaserElement>
</Module>

<!-- XSL -->
<!-- You are already in the Node Module -->
<xsl:template match="WideTeaserElement">
    <ul class="TabbedPanelsTabGroup">
        <xsl:for-each select=".">
            <li class="TabbedPanelsTab"><a href="#"><xsl:value-of select="pub:Headline"/></a></li>
         </xsl:for-each>
    </ul>
</xsl>

And what I wanna get as output:

<ul class="TabbedPanelsTabGroup">
     <li class="TabbedPanelsTab"><a href="#">Bla bla 1</a></li>
     <li class="TabbedPanelsTab"><a href="#">Bla bla 2</a></li>
</ul>

With my XSL I get an output with two <ul> Elements. So my question includes two things: 1) How has to be the transformation for my probleme above? 2) How can you handle loops like for-each in XSL, to have it more "under control"?

+2  A: 

You almost never need to use for-each in XSL, and if you find yourself using it, you're probably missing an important bit of XSL's power.

Your case could be much more idiomatically expressed by giving a template for the HeadLine element (containing your <li>...</li> element), and then using <xsl:apply-templates/> within the <ul> element within the WideTeaserElement template.

<xsl:stylesheet version="1.0"
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
  <xsl:template match='Module'>
    <ul class='TabbedPanelsTabGroup'>
      <xsl:apply-templates/>
    </ul>
  </xsl:template>
  <xsl:template match='Headline'>
    <li class='TabbedPanelsTab'><a href='#'><xsl:apply-templates/></a></li>
  </xsl:template>
</xsl:stylesheet>
Norman Gray
Dimitre is perfectly correct: the first `<apply-templates/>` should indeed be within the `<Module>` template, not the `<WideTeaserElement>`, as I had it (I did say it was untested!). My general point still stands, though, that `<apply-templates/>` is a more idiomatic technique in XSL, than `<for-each/>`. Flynn1179 argues that a `<for-each/>` can sometimes be neater: this is true, but the same argument can (with a little less justice) be applied to `goto`, and that way madness lies. XSL is very nearly a functional language -- celebrate it! Go, microfunctions!
Norman Gray
I edited the original version to fix that problem (thanks, Dimitre). Note that I've stuck with `<apply-templates/>` rather than `<value-of select='.'/>`. They're equivalent _in this case_, but `<apply-templates/>` is more general, since `<value-of/>` will always produce a text node, whereas `<apply-templates/>` expands to _anything_ contained within the currently matched element.
Norman Gray
Thanks for this. I couldn't test it yet, but I came up with a thing: Like I mentioned (in the comment), you are already in the Module-Element. The parent template includes already a match for Module. So would the third line in your code <xsl:template match='Module'> be modified to match='WideTeaserElement' ? [to the other answers: thank you very much, but i can sadly just select one answer as accepted - i'll take the first answer (fifo)]
ChrisBenyamin
You say 'you are already _in_ the Module-Element' -- you might not have the best mental model of what's happening. It's not that the processor works through the source document tag by tag; instead it processes it element (ie, `<tag>...</tag>`) by complete element. It sees a complete `<Module>` element, then _replaces_ that with the contents of the matching template: `<ul>....</ul>`. The `<apply-templates/>` then tells it to do the _same_ thing for each of the elements within the `<Module>` which has a matching template. Section 5.1 of the spec, and the start of 5.4, are dense but rewarding.
Norman Gray
+2  A: 

This transformation (completely in the spirit of XSLT):

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

 <xsl:template match="Module">
   <ul>
     <xsl:apply-templates/>
   </ul>
 </xsl:template>

 <xsl:template match="Headline">
   <li class="TabbedPanelsTab"><a href="#"><xsl:value-of select="."/></a></li>
 </xsl:template>
</xsl:stylesheet>

when applied on the provided XML document:

<Module>
    <WideTeaserElement>
      <Headline>Bla bla 1</Headline>
    </WideTeaserElement>
    <WideTeaserElement>
      <Headline>Bla bla 2</Headline>
    </WideTeaserElement>
</Module>

produces the desired result:

<ul>
    <li class="TabbedPanelsTab">
        <a href="#">Bla bla 1</a>
    </li>
    <li class="TabbedPanelsTab">
        <a href="#">Bla bla 2</a>
    </li>
</ul>
Dimitre Novatchev
+1  A: 

The reason you're getting two ul elements is because your template that generates the ul element matches elements called WideTeaserElement, of which you have two.

I think I can see where thinking was going with this- were you assuming that this template would process all WideTeaserElements in one operation, which you could then iterate through with 'for each of these'? Instead, the template is called separately for each occurrence of the WideTeaserElement node, and then 'iterates' through the single occurrence of itself.

I agree with Norman that for-each is rarely the best option, but I can think of two reasons why you might use it.

  • Readability; if the amount of processing for each iteration is relatively small, it can make your stylesheet easier to read if it the code that handles it is within the parent template.

For (very simplified) example, I'd favor

 <xsl:template match="mylist">
   <xsl:element name="ul">
     <xsl:for-each select="listitem">
       <xsl:element name="li">
         <xsl:value-of select="." />
       </xsl:element>
     </xsl:for-each>
   </xsl:element>
 </xsl:template>

instead of

<xsl:template match="mylist">
  <xsl:element name="ul">
    <xsl:apply-templates select="listitem" />
  </xsl:element>
</xsl:template>

<xsl:template match="listitem">
  <xsl:element name="li">
    <xsl:value-of select="." />
  </xsl:element>
</xsl:template>

if the mylist template actually had a lot more code, and the latter solution would mean having to scroll down my code to see how listitem is handled. This is subjective though, some may prefer the latter solution always. Personally I usually find that most templates are large enough that breaking them up is better for readability, but in smaller ones this isn't always the case.

  • Where you don't want the nodes your iterating through to be treated as they normally would by a template. In the above example, the latter solution would convert ALL listitem tags, not just those within a 'mylist' tag. It's possible to restrict it by changing the match to listitem[parent::mylist] of course, but often the for-each is just neater than making multiple templates for the same element name based on ancestry.

Generally speaking though, you can usually substitute

<xsl:template match="foo">
  <xsl:for-each select="bar">
    ..
  </xsl:for-each>
</xsl:template>

with

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

<xsl:template match="bar">
  ..
</xsl:template>

in any document where a bar element always has a foo element as it's parent.

Flynn1179