Nathan Merrill
Nathan Merrill

Reputation: 8386

How to conditionally filter out children in XSLT

I currently have something similar to the following XML:

<div class="newsFeed">
   <div class="newsItem"><news position="3"/></div>
   <categoryFilter dayFilter="4">
       <div class="newsItem"><news position="2"/></div>
   </categoryFilter>
</div>

I need to copy the XML, and output the nth news item on the node. Futhermore, I need to be able to filter that news. For this example, lets construct my news as follows:

<xsl:variable name="news">
    <xsl:for-each select="1 to 30">
        <item>
          <day><xsl:value-of select=". mod 4" /></day>
          <content>Content: <xsl:value-of select="." /></content>
        </item>
    </xsl:for-each>
</xsl:variable>

I actually use document() and use the for-each to sort it, but I'm trying to keep it succinct. This would mean that my output XML would be something like the following:

<div class="newsFeed">
    <div class="newsItem">Content: 3</div>  
    <div class="newsItem">Content: 8</div>  
 </div>

The reason the second one is 8 is because the categoryFilter filters out every <item> where the day isn't 4 (which happens to be the 4th, 8th, 12th, and so on), and then we select the second one.

The XSLT to produce the above is as follows:

XSLT:

<xsl:template match="news">
   <xsl:param name="items" select="$news" />
   <xsl:variable name="position" select="@position" />
   <xsl:copy-of select="$items/item[position()=$position]/content" />
</xsl:template>

<xsl:template match="categoryFilter">
   <xsl:param name="items" select="$news" />
   <xsl:variable name="day" select="@dayFilter" />
   <xsl:variable name="filteredItems">
      <xsl:for-each select="$items/item[day=$day]">
         <xsl:copy-of select="." />
      </xsl:for-each>
   </xsl:variable>
   <xsl:apply-templates>
      <xsl:with-param name="items" select="$filteredItems">
   </xsl:apply-templates>
</xsl:template>

My problem lies with the <for-each>. It seems silly that I have to use a for-each to filter out the <item> nodes, but I can't find a better way. Simply doing a <xsl:variable select="$items/item[day=$day]"> changes the structure, and makes it so that the <xsl:template match="news"> doesn't work.

Is there a way to filter out child nodes without using a for-each? I am using <xsl:stylesheet version="3.0">

Upvotes: 0

Views: 677

Answers (1)

Tim C
Tim C

Reputation: 70618

Instead of doing this...

<xsl:variable name="filteredItems">
   <xsl:for-each select="$items/item[day=$day]">
      <xsl:copy-of select="." />
   </xsl:for-each>
</xsl:variable>

... you could use a sequence. This will select the actual items, as opposed to create copies of them

<xsl:variable name="filteredItems">
   <xsl:sequence select="$items/item[day=$day]" />
</xsl:variable>

This was, doing $filteredItems/item will still work.

Alternatively, you could take the opposite approach, and do away with the need to specify /item in all the expressions.

First, define your news variable like so:

<xsl:variable name="news" as="element()*">

This means you can write the expression that uses it like so:

<xsl:copy-of select="$news[position()=$position]/content" />

And similarly for filteredItems....

<xsl:variable name="filteredItems" select="$items[day=$day]" />

Upvotes: 1

Related Questions