开发者

Better control of for-each in xsl/xpath possible?

开发者 https://www.devze.com 2023-01-04 14:50 出处:网络
Hey Friends I\'ve got another xsl/xpath problem. Sure a beginners problem, but I\'m thinking still too \"imperative\".

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:valu开发者_运维问答e-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"?


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">
  <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>


This transformation (completely in the spirit of XSLT):

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <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>


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.

0

精彩评论

暂无评论...
验证码 换一张
取 消