Re: [xsl] cross document id idref pair checks

Subject: Re: [xsl] cross document id idref pair checks
From: Jeni Tennison <mail@xxxxxxxxxxxxxxxx>
Date: Tue, 9 Jan 2001 18:34:52 +0000
David Carlisle wrote:
> Dave Pawson wrote:
>> When using well formed, as apposed to valid, input, How to use the
>> fact that a node-set removes duplicates to check if there is more
>> than one attribute named 'id'?
> If it has duplicated attributes, it's not well formed.

I think (?) that DaveP meant duplicated values for @id attributes
across a document rather than duplicate @id attributes on a particular

If there is an element that has the same @id attribute as the current
element, then:

  @id = (preceding::*/@id | following::*/@id)

would be true.  You can get elements that have repeated ids with:

  //*[@id = (preceding::*/@id | following::*/@id)]

Of course if you test the above expression as a boolean, then it will
return true if there are any repeated ids in the document.

You can list the IDs that are repeated (once per ID) with:

  //*[not(preceding::*/@id = @id) and
      following::*/@id = @id]

The preceding:: and following:: axes are, of course, horribly
inefficient.  You could use a recursive solution instead, or you could
use keys:

<xsl:key name="ids" select="*" use="@id" />

If, for an element, the 'ids' key returns more than one node, then
it's a repeated id, so you can test whether there are any repeats

  //*[key('ids', @id)[2]]

[Aside: I've started using positional predicates to test whether a
node set has more than a certain number of nodes, although perhaps any
processor that optimises '$nodes[2]' will also optimise 'count($nodes)
&gt; 1'. Any thoughts?]

If you want to get a list (without repeats) of the repeated IDs using
the key() method, then you can use the usual:

  //*[key('ids', @id)[2] and
      count(.|key('ids', @id)[1]) = 1]

[or actually, by extension to the above aside:

  //*[key('ids', @id)[2] and
      not((.|key('ids', @id)[1])[2])]

 making identity-testing of nodes even more obscure! :)]

Testing this across documents is harder because (a) nodes from
different documents aren't related to each other through axes and (b)
key() is scoped within the document of the context node. Probably the
simplest solution is to create a node set variable holding copies of
the nodes from the relevant documents, and then test that as if it
were a document on its own:

  <xsl:variable name="IDed-elements">
    <xsl:copy-of select="(/ | document('foo.xml'))//*[@id]" />
  <xsl:if test="$IDed-elements//*[key('ids', @id)[2]]">
    <!-- repeated IDs -->

Alternatively, you could use a recursive method.


Jeni Tennison

 XSL-List info and archive:

Current Thread