If the compared nodes are element nodes with a unique attribute of
type ID, then comparison is most conveniently made by comparing these
attributes. If this is not the case, then use
generate-id
, as in:
<xsl:if test="generate-id($node1) = generate-id($node2)">
Here we assume $node1
and
$node2
are node sets containing a single node.
An interesting generalization of this test checks if all the nodes in
one node-set are the same as all the nodes in another. For this task,
generate-id
is not useful because it only
generates an ID for the first node in document order. Instead, you
need to take advantage the XPath union operator’s
(|)
ability to determine node
equality.
<xsl:if test="count($ns1|$ns2) = count($ns1) and count($ns1) = count($ns2)">
In other words, if two node sets have the same number of nodes, and the number of nodes resulting from the union of both node sets is the same as the number of nodes in one of those two original node sets, then they must be the same sets. If they are not, then the union must contain at least one more node than either individual set.
If you only care if $ns2
is either equal to or a
subset of $ns1
, then you can simply write:
<xsl:if test="count($ns1|$ns2) = count($ns1)">
On the other hand, if you want to test that $ns2
is a proper subset of $ns1
,
then you need to write:[8]
<xsl:if test="count($ns1|$ns2) = count($ns1) and count($ns1) > count(ns2)">
From these examples, you should also conclude that the alternative to
using generate-id( )
to test equality in the
single node instance is to write:
<xsl:if test="count($ns1|$ns2) = 1">
You can apply these techniques to make sophisticated queries against
a document. For example, consider the stylesheet shown in Example 4-14 that analyzes SalesBySalesPerson.xml
to find products sold by common sets of salespeople. Its
output is listed in Example 4-15.
Example 4-14. products-sold-in-common.xslt
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="text"/> <!-- Extract the unique set of products --> <xsl:template match="/"> <xsl:call-template name="process-products"> <xsl:with-param name="products" select="/*/salesperson/product[not(@sku=preceding::product/@sku)]"/> </xsl:call-template> </xsl:template> <!-- Process all pairs of products --> <xsl:template name="process-products"> <xsl:param name="products"/> <xsl:for-each select="$products"> <xsl:variable name="product1" select="."/> <xsl:for-each select="$products"> <xsl:variable name="product2" select="."/> <-- Don't analyze the product against itself --> <xsl:if test="generate-id($product1) != generate-id($product2)"> <xsl:call-template name="show-products-sold-in-common"> <xsl:with-param name="product1" select="$product1"/> <xsl:with-param name="product2" select="$product2"/> </xsl:call-template> </xsl:if> </xsl:for-each> </xsl:for-each> </xsl:template> <!-- Determine if two products have salespeople in common. --> <xsl:template name="show-products-sold-in-common"> <xsl:param name="product1"/> <xsl:param name="product2"/> <xsl:variable name="who-sold-p1" select="//salesperson[product/@sku = $product1/@sku]"/> <xsl:variable name="who-sold-p2" select="//salesperson[product/@sku = $product2/@sku]"/> <!-- If those who sold product2 is a subset of those who sold product1 then they have these products have common salespeople --> <xsl:if test="count($who-sold-p1|$who-sold-p2) = count($who-sold-p1)"> <xsl:text>All the salespeople who sold product </xsl:text> <xsl:value-of select="$product2/@sku"/> <xsl:text> also sold product </xsl:text> <xsl:value-of select="$product1/@sku"/> <!-- If the counts of both sets are also equal then the two sets are actually the same. --> <xsl:if test="count($who-sold-p1) = count($who-sold-p2)"> <xsl:text> and vice versa</xsl:text> </xsl:if> <xsl:text>.
</xsl:text> </xsl:if> </xsl:template> </xsl:stylesheet>
Example 4-15. Output
All the salespeople who sold product 20000 also sold product 10000 and vice versa. All the salespeople who sold product 25000 also sold product 10000. All the salespeople who sold product 30000 also sold product 10000. All the salespeople who sold product 70000 also sold product 10000. All the salespeople who sold product 10000 also sold product 20000 and vice versa. All the salespeople who sold product 25000 also sold product 20000. All the salespeople who sold product 30000 also sold product 20000. All the salespeople who sold product 70000 also sold product 20000. All the salespeople who sold product 70000 also sold product 25000. All the salespeople who sold product 70000 also sold product 30000.
To stay focused on node-identity testing, this example did not present the most efficient solution to this task. The following solution is preferable in this specific case:
<xsl:template name="process-products"> <xsl:param name="products"/> <xsl:for-each select="$products"> <xsl:variable name="product1" select="."/> <xsl:variable name="pos" select="position( )"/> <xsl:for-each select="$products[position( ) > $pos]"> <xsl:variable name="product2" select="."/> <xsl:call-template name="show-products-sold-in-common"> <xsl:with-param name="product1" select="$product1"/> <xsl:with-param name="product2" select="$product2"/> </xsl:call-template> </xsl:for-each> </xsl:for-each> </xsl:template>
Notice how the solution avoids generate-id( )
by
relying on the uniqueness of position within a single node set.
However, if there were two independent node sets rather than one, you
wouldn’t necessarily rely on position as an
indicator of identity.
In addition, tests like:
<xsl:variable name="who-sold-p1" select="//salesperson[product/@sku = $product1/@sku]"/>
might be done more efficiently with a key, as was suggested in Recipe 4.1:
<xsl:key name="sp_key" match="salesperson" use="product/@sku"/> <xsl:variable name="who-sold-p1" select="key('sp_key',$product1/@sku)"/>
Testing node equality has important applications in grouping problems. See Chapter 5 for examples of grouping.
Get XSLT Cookbook now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.