Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 76 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
76
Dung lượng
213,06 KB
Nội dung
XSLT. Michael Kay calls them context-sensitive and temporary-tree variables. Context-sensitive variables arise in nested for-each loops when the inner loop must refer to the current context of the outer loop. In Example 4-12, the code fragment is borrowed from Example 4-14 in Recipe 4.2, $product1 is a necessary context-sensitive variable, while $product2 simply helps clarify the code. Example 4-12. A context-sensitive variable <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> Temporary-tree variables arise when you want to store the result of XSLT code in a variable. This scenario arises most often when capturing the value returned by the call to a template (see Example 4-13). Example 4-13. Storing a temporary tree <xsl:varaible name="result-tree"> <xsl:call-template name="compute-something"/> </xsl:varaible> XSLT 2.0 (and 1.1 for the processors that support it) allow such variables to be processed as node sets, which is very convenient if you want to do anything significant with them. XSLT 1.0 calls them result-tree fragments, and you cannot do much more than output them without first applying a nonstandard extension node-set( ) function. With 20/20 hindsight, result-tree fragments are the classic example of a really bad idea. However, when XSLT 1.0 was developed, it was unclear how it would be used and whether technical problems would prevent the creation of temporary node trees. 4.1.3.4 Use xsl:key if nodes will be selected by static criteria frequently Of all the optimization methods keys, those of xsl:key are the most likely to give you the biggest bang for the buck if used properly. Of course, nothing in the XSLT standard says that keys must result in the creation of an index, but any decent implementation will create one. Saxon and Xalan are two processors that show significant gain from the key facility. 4.2 Determining if Two Nodes Are the Same 4.2.1 Problem You need to determine if two separate references to a node are the same node. 4.2.2 Solution 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: [2] [2] Recall that a proper subset does not include the entire set as a subset. <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"> 4.2.3 Discussion 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 visa 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 visa 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 visa 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)"/> 4.2.4 See Also Testing node equality has important applications in grouping problems. See Chapter 5 for examples of grouping. 4.3 Ignoring Duplicate Elements 4.3.1 Problem You want to select all nodes that are unique in a given context based on uniqueness criteria. 4.3.2 Solution Selecting unique nodes is a common application of the preceding and preceding- sibling axes. If the elements you select are not all siblings, then use preceding. The following code produces a unique list of products from SalesBySalesperson.xml: <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/> <xsl:template match="/"> <products> <xsl:for-each select="//product[not(@sku=preceding::product/@sku)]"> <xsl:copy-of select="."/> </xsl:for-each> </products> </xsl:template> </xsl:stylesheet> If the elements are all siblings then use preceding-sibling. <products> <product sku="10000" totalSales="10000.00"/> <product sku="10000" totalSales="990000.00"/> <product sku="10000" totalSales="1110000.00"/> <product sku="20000" totalSales="50000.00"/> <product sku="20000" totalSales="150000.00"/> <product sku="20000" totalSales="150000.00"/> <product sku="25000" totalSales="920000.00"/> <product sku="25000" totalSales="2920000.00"/> <product sku="30000" totalSales="5500.00"/> <product sku="30000" totalSales="115500.00"/> <product sku="70000" totalSales="10000.00"/> </products> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/> <xsl:template match="/"> <products> <xsl:for-each select="product[not(@sku=preceding- sibling::product/@sku)]"> <xsl:copy-of select="."/> </xsl:for-each> </products> </xsl:template> </xsl:stylesheet> To avoid preceding, which can be inefficient, travel up to the ancestors that are siblings, and then use preceding-sibling and travel down to the nodes you want to test: <xsl:for-each select="//product[not(@sku= /preceding- sibling::*/product/@sku)]"> <xsl:copy-of select="."/> </xsl:for-each> If you are certain that the elements are sorted so that duplicate nodes are adjacent (as in the earlier products), then you only have to consider the immediately preceding sibling: <xsl:for-each select="/salesperson/product[not(@name=preceding- sibling::product[1]/@name]"> <! do something with each uniquiely named product > </xsl:for-each> 4.3.3 Discussion In XSLT Version 2.0 (or Version 1.0 in conjunction with the node-set( ) extension function), you can also do the following: <xsl:stylesheet version="1.1" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/> <xsl:template match="/"> <xsl:variable name="products"> <xsl:for-each select="//product"> <xsl:sort select="@sku"/> <xsl:copy-of select="."/> </xsl:for-each> </xsl:variable> <products> <xsl:for-each select="$products/product"> <xsl:variable name="pos" select="position( )"/> <xsl:if test="$pos = 1 or not(@sku = $products/preceding- sibling::product[1]/@sku"> <xsl:copy-of select="."/> </xsl:if> </xsl:for-each> </products> </xsl:template> However, I have never found this technique to be faster than using the preceding axis. This technique does have an advantage in situations where the duplicate testing is not trivial. For example, consider a case where duplicates are determined by the concatenation of two attributes. <xsl:stylesheet version="1.1" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/> <xsl:template match="/"> <xsl:variable name="people"> <xsl:for-each select="//person"> <xsl:sort select="concat(@lastname,@firstname)"/> <xsl:copy-of select="."/> </xsl:for-each> </xsl:variable> <products> <xsl:for-each select="$people/person"> <xsl:variable name="pos" select="position( )"/> <xsl:if test="$pos = 1 or concat(@lastname,@firstname) != concat(people/person[$pos - 1]/@lastname, people/person[$pos - 1]/@firstname)"> <xsl:copy-of select="."/> </xsl:if> </xsl:for-each> </products> </xsl:template> When you attempt to remove duplicates, the following examples do not work: <xsl:template match="/"> <products> <xsl:for-each select="//product[not(@sku=preceding::product[1]/@sku)]"> <xsl:sort select="@sku"/> <xsl:copy-of select="."/> </xsl:for-each> </products> </xsl:template> Do not sort to avoid considering all but the immediately preceding element. The axis is relative to the node's original order in the document. The same situation applies when using preceding- sibling. The following code is also sure to fail: <xsl:template match="/"> <xsl:variable name="products"> <xsl:for-each select="//product"> <!— sort removed from here —> <xsl:copy-of select="."/> </xsl:for-each> </xsl:variable> <products> <xsl:for-each select="$products/product"> <xsl:sort select="@sku"/> <xsl:variable name="pos" select="position( )"/> <xsl:if test="$pos = 1 or @sku != $products/product[$pos - 1]/@sku"> <xsl:copy-of select="."/> </xsl:if> </xsl:for-each> </products> </xsl:template> This code fails because position( ) returns the position after sorting, but the contents of $products has not been sorted; instead, an inaccessible copy of it was. 4.3.4 See Also The XSLT FAQ (http://www.dpawson.co.uk/xsl/sect2/N2696.html) describes a solution that uses keys and describes solutions to related problems. [...]... off Betsy Ross has 3 boss(es) to knock off Craig F Frye has 3 boss(es) to knock off Hardy Hamburg has 3 boss(es) to knock off Rich Shaker has 3 boss(es) to knock off Allen Bran has 3 boss(es) to knock off Frank N Berry has 3 boss(es) to knock off Jack Apple has 3 boss(es) to knock off Jack Nickolas has 3 boss(es) to knock off Tom Hanks has 3 boss(es) to knock off Susan Sarandon has 3 boss(es) to knock... off Betsy Ross has 3 boss(es) to knock off Craig F Frye has 3 boss(es) to knock off Hardy Hamburg has 3 boss(es) to knock off Rich Shaker has 3 boss(es) to knock off Allen Bran has 3 boss(es) to knock off Frank N Berry has 3 boss(es) to knock off Jack Apple has 3 boss(es) to knock off Jack Nickolas has 3 boss(es) to knock off Tom Hanks has 3 boss(es) to knock off Susan Sarandon has 3 boss(es) to knock... that computes the total commission paid to salespeople whose commission is a function of their total sales over all products, shown in Example 4 -32 and Example 4 -33 Example 4 -32 Total-commission .xslt stylesheet Total commision = ,3) [@sex='female']"> 3 > ... xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:C="http://www.ora.com/XSLTCookbook/nampespaces/C"> s key facility is not mandatory because you can always write select="//*[count(ancestors::*) =3] " when you need access to level -3 elements; however, performance will suffer if your stylesheet repeatedly evaluates such an expression In fact, if your stylesheet has . store the result of XSLT code in a variable. This scenario arises most often when capturing the value returned by the call to a template (see Example 4- 13) . Example 4- 13. Storing a temporary. examples of grouping. 4 .3 Ignoring Duplicate Elements 4 .3. 1 Problem You want to select all nodes that are unique in a given context based on uniqueness criteria. 4 .3. 2 Solution Selecting. <! do something with each uniquiely named product > </xsl:for-each> 4 .3. 3 Discussion In XSLT Version 2.0 (or Version 1.0 in conjunction with the node-set( ) extension function),