Re: [xsl] position() returning incorrect results

Subject: Re: [xsl] position() returning incorrect results
From: Jeni Tennison <jeni@xxxxxxxxxxxxxxxx>
Date: Thu, 14 Feb 2002 15:54:42 +0000
Hi Jon,

> i just had an <xsl:apply-templates> to get the stylesheet to work on
> an html like file, which just had lots of <p> tags for paragraphs in
> it. when i do the <xsl:template match="p"> and then try to print the
> position() it all goes wrong and seems to count up in twos. what
> confuses me more is that if i have a seperate template for
> <xsl:template match="p[1]"> it will correctly pick out the first
> paragraph and report it's position() as being 1.

Yep, this is because the way position() works within a template is
different from the way position() works within a pattern.

Within a pattern (in a match attribute), you actually test the
position of a node amongst its siblings that are the same type as that
node. So your pattern of p[1] gives you a template that matches the p
elements that are the first p element children of their parent
elements. It would match a p element whose only preceding sibling was
a comment, or a text node, or a ul element.

Within a template, on the other hand, the position() function gives
you the position of the node that you're processing with that template
within the list of nodes that are currently being processed. That
means that the position() of a node according to a template depends on
the xsl:apply-templates that's used to apply the template. If, as is
frequently the case, you use:

  <xsl:apply-templates />

then the position() of the node will be the position of the node
amongst all its sibling nodes.

"But," I hear you wonder "the p elements that I'm applying templates
to are the only children of their parent element anyway, so surely the
first p element should have the position 1?"

And the answer would be "yes" if that were the case. But usually, and
I'm guessing in your case, the elements actually have invisible
siblings -- whitespace-only text nodes! Take the example:

  <div>
    <p>...</p>
    <p>...</p>
  </div>

The div element actually has five children - a whitespace-only text
node (giving a line break and some indentation), the first p element,
another whitespace-only text node, the second p element, and a final
whitespace-only text node.

There are two solutions to this predicament. The first solution is to
only select the p elements when you apply templates:

  <xsl:apply-templates select="p" />

That way the node set only contains the nodes that you actually want
to count.

The second solution is to strip out those pesky whitespace-only text
nodes from the node tree, so that they're not counted anyway. You can
do this by placing a xsl:strip-spaces directive at the top of your
stylesheet. When I'm processing data-oriented XML (which doesn't
include mixed content), I usually use:

<xsl:strip-space elements="*" />

To strip whitespace-only text nodes from all the elements in the
document. You might want to be more careful, and only strip the
whitespace-only text nodes from (say) the body and div elements, with:

<xsl:strip-space elements="body div" />

I hope that helps,

Jeni

---
Jeni Tennison
http://www.jenitennison.com/


 XSL-List info and archive:  http://www.mulberrytech.com/xsl/xsl-list


Current Thread