tags:

views:

1401

answers:

2

I'm having a problem with XSL variables. I know that once declared, the value of the variable cant be changed.

But I'm facing a rather strange problem and I cant possibly change the XML output, instead figure out if its possible with XSL itself.

I'm using xsl:for-each to loop over some data in the XML.

The data being looped can be of three types, say Type1, Type2 and Type3

If it is of Type1, I'm calling a template to process that data. Inside the template I'm displaying a header. I need to print this header for the very first time I'm encountering this Type1 data only. After that I dont need to display this.

I could've passed a parameter along with the call-template and set/unset it to determine if I need to print the header text. But as I understand, being a formatting language, the variable state is not preserved.

Can you please provide your valuable suggestions on how to implement this(possibly without xml changes)?

Edit:

The test will work for the XML output mentioned by Patrice. But my XML is different.

<doc> 
<item>foobar</item> 
<item>foo</item> 
<item>bar</item> 
<item>baz</item> 
<item>foo</item> 
<item>bar</item> 
</doc>

From this XML, I need to display a header for the very first time it encounters 'foo'. The order of items could be anything too. I cannot exactly predict when 'foo' will appear in the XML.

Can you please bring up any suggestions?

+3  A: 

You can use a proper selector for that (would be easier with some actual code from you).

Example with this input:

<doc>
    <item>foo</item>
    <item>bar</item>
</doc>

You can use this XSLT:

<xsl:stylesheet version='1.0' xmlns:xsl='http://www.w3.org/1999/XSL/Transform'&gt;
<xsl:output method='html' version='1.0' encoding='utf-8' indent='yes'/>

    <xsl:template match="/">
        <xsl:apply-templates select="/doc/item" />
    </xsl:template>

    <xsl:template match="item">
        <xsl:if test="not(preceding-sibling::item)">
            <h1>First</h1>
        </xsl:if>
        <p><xsl:apply-templates /></p>
    </xsl:template>

</xsl:stylesheet>

And you'll get this output:

<h1>First</h1>
<p>foo</p>
<p>bar</p>

They key is the test

<xsl:if test="not(preceding-sibling::item)">

You can also change that to

<xsl:if test="not(preceding::item)">

depending on your document structure.

See the XPath axes for more details.

Patrice
I have also used not(position() = 1) with some success, but I like your solution better, as it better explains the test.
Ishmael
+1 if on preceding sibling is what I'd go for here, one way or another
annakata
A: 

You probably just need to add a predicate but again, without a concrete example of your input and little detail on the expected output, it's hard to tell.

For the first item whose content is "foo", replace Patrice's tag with

<xsl:if test=".='foo' and not(preceding-sibling::item[.='foo'])">
Josh Davis
Thanks for your help, Josh. The expected output would be <p>foobar</p> <h1>Header</h1> <p>foo</p><p>bar</p> <p>baz</p> <p>foo</p> <p>bar</p> Only the first 'foo' element requires a header.
whoopy_whale