Re: [xsl] Get Position of Node in Ancestor Context

Subject: Re: [xsl] Get Position of Node in Ancestor Context
From: Jeni Tennison <jeni@xxxxxxxxxxxxxxxx>
Date: Mon, 10 Mar 2003 17:33:58 +0000
Hi Ted,

> Answered my own question, but would still be interested in knowing
> what others think of this solution...
>
> <xsl:template match="input|select|textarea">
>      <xsl:copy>
>          <xsl:for-each select="@*">
>              <xsl:copy />
>          </xsl:for-each>
>          <xsl:variable name="currElemId" select="@id" />
>          <xsl:attribute name="tabindex"></xsl:attribute>
>          <xsl:for-each  
> select="ancestor::form//select|ancestor::form//input|ancestor::form// 
> textarea">
>              <xsl:if test="not(@type = 'hidden') or @type = 'submit'">
>                  <xsl:if test="@id = $currElemId">
>                      <xsl:attribute name="tabindex"><xsl:value-of  
> select="position()" /></xsl:attribute>
>                  </xsl:if>
>              </xsl:if>
>          </xsl:for-each>
>          <xsl:apply-templates />
>      </xsl:copy>
> </xsl:template>

I think that you'd probably be better off using <xsl:number> here.
Try:

<xsl:template match="input | select | textarea">
  ...
  <xsl:attribute name="tabindex">
    <xsl:number from="form" level="any"
      count="*[self::input or self::select or self::textarea]
              [@type != 'hidden']" />
  </xsl:attribute>
  ...
</xsl:template>

You could use "input[@type != 'hidden'] | select[@type != 'hidden'] |
textarea[@type != 'hidden']" as the value of the count attribute if
you prefer. Note that I dropped the "or @type = 'select'" since any
element with a type attribute equal to 'select' will pass the "@type
!= 'hidden'" test.

> The style sheet basically adds tabindex attributes to all form
> elements. What I'm unhappy with is the value of the select attribute
> of the second for-each element (the one that creates a node set of
> all child elements of a FORM element, regardless of what separates
> them). It seems odd that I have to fully qualify the child element
> I'm looking for, but using ancestor::form//select|input|textarea
> only matches the first element in the list.

Yes; in XPath 2.0, we'll be able to use:

  ancestor::form//(select | input | textarea)

which is a good deal shorter, and likewise with the count attribute
above, we'll be able to do:

  (input | select | textarea)[@type != 'hidden']

> Also, what is the performance impact on setting up such for-each
> loops and is there a faster way to reproduce this node-set in the
> context of the current element (call-template with-param???)?

Numbering based on position within the node tree is always fairly
expensive because it can require a lot of node visits. If the <input>,
<select> and <textarea> elements are siblings, you can use
level="single", which will mean that the processor doesn't have to
look at quite as many nodes. Or you could use a step-by-step recursive
template and pass the current count on from sibling to sibling. But
unless you find performance is really suffering here, I'd stick with
the <xsl:number> solution above.

By the way, rather than:

  <xsl:for-each select="@*"><xsl:copy /></xsl:for-each>

you could just do:

  <xsl:copy-of select="@*" />

Cheers,

Jeni

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


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


Current Thread