Jeni Tennison
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
with: //*[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)
> 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:variable>
<xsl:if test="$IDed-elements//*[key('ids', @id)[2]]">
<!-- repeated IDs -->
</xsl:if>
Alternatively, you could use a recursive method. |