tags:

views:

730

answers:

2

I've written some XSLT that uses one XML document to filter another. Now I'd like to number my output elements using position(), but my filtering condition is not built in to my <xsl:for-each>, so position() gives me results with numbering gaps. How do I get rid of the gaps?

<xsl:variable name="Astring">
  <a><b>10</b><b>20</b><b>30</b></a>
</xsl:variable>
<xsl:variable name="A" select="msxml:node-set($Astring)" />

<xsl:variable name="Bstring">
  <c><d>20</d><d>30</d></c>
</xsl:variable>
<xsl:variable name="B" select="msxml:node-set($Bstring)" />

<e>
  <xsl:for-each select="$A/a/b">
    <xsl:variable name="matchvalue" select="text()" />
    <xsl:if test="count($B/c/d[text() = $matchvalue]) &gt; 0">
      <xsl:element name="f">
        <xsl:attribute name="i">
          <xsl:value-of select="position()" />
        </xsl:attribute>
        <xsl:copy-of select="text()" />
      </xsl:element>
    </xsl:if>
  </xsl:for-each>
</e>

Here's the result:

<e>
  <f i="2">20</f>
  <f i="3">30</f>
</e>

...but I want this:

<e>
  <f i="1">20</f>
  <f i="2">30</f>
</e>

Is there any way to incorporate the above <xsl:if> filtering test inside the <xsl:for-each> select attribute?

Note: I've seen this question re: filtering and this one re: counters, but the former doesn't number its results and the latter uses position().

+3  A: 

Is there any particular reason why you can't include your condition inside the for-each loop?

<xsl:for-each select="$A/a/b[$B/c/d = .]">

I tested this out in Visual Studio and it works fine for this example.

This was the output:

<e>
  <f i="1">20</f>
  <f i="2">30</f>
</e>
Welbog
Brilliant, thank you! I had tried to move the condition into my for-each but met frustration trying to do it this way:$A/a/b[count($B/c/d[<some match condition here>]) > 0]
Daniel Weaver
+2  A: 
Tomalak
People who are more used to conventional programming probably have a hard time understanding what is going on in that sample. People are simply more comfortable with loops.
Welbog
Thanks. For some reason I was hesitant about apply-templates, knowing I needed position() and not yet knowing how it would work in a template. I agree your revision is loads more elegant.
Daniel Weaver
@Daniel Weaver: position() works exactly like in for-each: It returns the position of the current node in the context of the _selected node-set_. As soon as you see a "select=" attribute on an XSL element, a new node-set context is created and position() will work in this context.
Tomalak