Subject: [xsl] XSLT pointers / questions From: Hermann Stamm-Wilbrandt <STAMMW@xxxxxxxxxx> Date: Tue, 21 Sep 2010 21:35:09 +0200 |
Hello, I was finally successful in getting XSLT pointers working. After some problems in addressing namespace nodes there was a solution for that. The most tricky thing is dereferencing an id-node-set back to the node-set it represents. Find below (A) an explanation on how XSLT pointers work. Question 1: $result-nodes gets created in document order for all browsers but the Webkit browsers Chrome and Safari. Is it correct that Webkit does not generate the nodes in document order? Question 2: Can xsl:copy-of help making Chrome and Safari behave like the other browsers and generate $result-nodes in document order? http://www.w3.org/TR/xslt#copy-of The sample input XML document can be found here: http://www.stamm-wilbrandt.de/en/xsl-list/xsltPointers/sevennodetypes.xml <?xml-stylesheet type="text/xsl" href="demo.xsl"?><!--comment--> <e1 att="a">testtext <e2 xmlns:m="urn:m" xmlns:ns="urn:n"> <e3 xmlns="urn:d" /> <e4 xmlns:ns="urn:n2" /> </e2></e1> Below text (A) gets generated by clicking on sevennodetypes.xml. The stylesheet (B) below can be found here: http://www.stamm-wilbrandt.de/en/xsl-list/xsltPointers/demo.xsl (A) XSLT pointers (Referencing) Every non-namespace node will be represented by: <id><gen><xsl:value-of select="generate-id()"/></gen></id> This allows to dereference the pointers by key('nodes-by-genid',...). <xsl:key name="nodes-by-genid" match="/ | node() | @*" use="generate-id ()"/> Namespace nodes cannot be matched by <xsl:key>. Therefore we represent its parent and its name. Both parts are seperated by colon character: <id><gen><xsl:value-of select="generate-id(..)"/> </gen>:<pre><xsl:value-of select="name()"/></pre></id> The string value of this representation allows for duplicate node elimination by key('no-duplicates',...) and Muenchian grouping. <xsl:key name="no-duplicates" match="id" use="concat(gen/text(),substring (':',1+not(pre)),pre/text())"/> An id-node-set is just a node-set of id-nodes. Dereferencing an id-node-set is done this way, even without exslt:node-set() function. As a side-effect applying the key function sorts the nodes into document order (*)! <xsl:variable name="result-nodes" select=" key('nodes-by-genid',$ecids/id[not(pre)]/gen) | key('nodes-by-genid',$ecids/id[pre]/gen)/ namespace::*[concat(generate-id(..),':',name())=$ecids/id]"/> (*) not for Webkit based browsers Chrome and Safari. id-node-sets can just be copied. Because of the contained id-value the copies still reference the same nodes as before. While handling of a constant number of "pointers" could be achieved by <xsl:variable name="..." select="..."> there is no concept of set of references in XSLT, only a node-set. But passing node-set while calling a template would need to generate copies and cannot be used for representing pointers by itself. All what is needed for XSLT pointers is XSLT 1.0 plus the exslt:node-set() function. This has the advantage that XSLT pointers work on the following browsers: Chrome, Firefox(**), Internet Explorer(***), Opera, Safari. (**) Firefox does not support the namespace:: axis. (***) Support for Internet Explorer is provided by: <!-- from http://dpcarlisle.blogspot.com/2007/05/exslt-node-set-function.html --> <msxsl:script language="JScript" implements-prefix="exslt"> this['node-set'] = function (x) { return x; } </msxsl:script> id-node-sets can preferably be used to implement eg. a location step as part of dyn:evaluate() function within XSLT itself along the "words": A location step identifies a new node-set relative to the context node-set. The location step is evaluated against each node in the context node-set, and the union of the resulting node-sets becomes the context node-set for the next step. Find below a sequence of above operations with generated output. ... (B) <!-- XSLT pointers, v1.0, 9/21/2010 --> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:exslt="http://exslt.org/common" xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="exslt msxsl" > <xsl:output method="html"/> <xsl:include href="serialize.xsl"/> <!-- for id-node-set dereferencing --> <xsl:key name="nodes-by-genid" match="/ | node() | @*" use="generate-id()"/> <!-- for id-node-set duplicate node elimination --> <xsl:key name="no-duplicates" match="id" use="concat(gen/text(),substring(':',1+not(pre)),pre/text())"/> <!-- duplicate node elimination --> <xsl:template name="eliminate-duplicates"> <xsl:param name="ids"/> <xsl:for-each select="$ids"> <xsl:for-each select="id"> <xsl:if test="generate-id()=generate-id(key('no-duplicates',.)[1])"> <xsl:copy-of select="."/> </xsl:if> </xsl:for-each> </xsl:for-each> </xsl:template> <!-- create id-node-set referencing all nodes from $nodes --> <xsl:template name="ids-by-nodes"> <xsl:param name="nodes"/> <xsl:for-each select="$nodes"> <xsl:choose> <xsl:when test="count(.|../namespace::*)=count(../namespace::*)"> <id><gen><xsl:value-of select="generate-id(..)"/> </gen>:<pre><xsl:value-of select="name()"/></pre></id> </xsl:when> <xsl:otherwise> <id><gen><xsl:value-of select="generate-id(.)"/></gen></id> </xsl:otherwise> </xsl:choose> </xsl:for-each> </xsl:template> <xsl:template match="/"> <!-- sample id-node-set --> <xsl:variable name="ids"> <xsl:call-template name="ids-by-nodes"> <xsl:with-param name="nodes" select=" /comment() | /e1/@att | processing-instruction() | / | /e1/e2/e4/namespace::xml | /e1/e2 | /e1/text() | /e1/e2/*[local-name()='e3']/namespace::*[name()=''] | /e1/e2/namespace::m | /e1/e2/e4/namespace::ns | /e1/e2/namespace::ns"/> </xsl:call-template> </xsl:variable> <xsl:variable name="eids" select="exslt:node-set($ids)"/> <!-- intentionally create duplicates --> <xsl:variable name="dids"> <xsl:copy-of select="$eids"/> <xsl:copy-of select="$eids"/> </xsl:variable> <xsl:variable name="edids" select="exslt:node-set($dids)"/> <!-- eliminate duplicates --> <xsl:variable name="ndids"> <xsl:call-template name="eliminate-duplicates"> <xsl:with-param name="ids" select="$edids"/> </xsl:call-template> </xsl:variable> <xsl:variable name="endids" select="exslt:node-set($ndids)"/> <!-- copy id-node-set --> <xsl:variable name="cids"> <xsl:for-each select="$endids/id"> <xsl:sort order="descending"/> <xsl:copy-of select="."/> </xsl:for-each> </xsl:variable> <xsl:variable name="ecids" select="exslt:node-set($cids)"/> <!-- dereference id-node-set --> <xsl:variable name="result-nodes" select=" key('nodes-by-genid',$ecids/id[not(pre)]/gen) | key('nodes-by-genid',$ecids/id[pre]/gen)/ namespace::*[concat(generate-id(..),':',name())=$ecids/id]"/> <html> <body> <h3>XSLT pointers</h3> <pre> (Referencing) Every non-namespace node will be represented by: <id><gen><xsl:value-of select="generate-id()"/> </gen></id> This allows to dereference the pointers by key('nodes-by-genid',...). <br/> <xsl:for-each select="document('')/xsl:stylesheet/xsl:key [@name='nodes-by-genid']"> <xsl:call-template name="doOutput"/> </xsl:for-each> <p/> Namespace nodes cannot be matched by <xsl:key>. Therefore we represent its parent and its name. Both parts are seperated by colon character: <id><gen><xsl:value-of select="generate-id(..)"/> </gen>:<pre><xsl:value-of select="name()"/> </pre></id> The string value of this representation allows for duplicate node elimination by key('no-duplicates',...) and Muenchian grouping. <br/> <xsl:for-each select="document('')/xsl:stylesheet/xsl:key [@name='no-duplicates']"> <xsl:call-template name="doOutput"/> </xsl:for-each> <p/> An id-node-set is just a node-set of id-nodes. <br/> Dereferencing an id-node-set is done this way, even without exslt:node-set() function. As a side-effect applying the key function sorts the nodes into document order (*)! <br/> <xsl:for-each select="document ('')/xsl:stylesheet/xsl:template/xsl:variable[@name='result-nodes']"> <xsl:call-template name="doOutput"/> </xsl:for-each> <p/> (*) not for Webkit based browsers Chrome and Safari. id-node-sets can just be copied. Because of the contained id-value the copies still reference the same nodes as before. While handling of a constant number of "pointers" could be achieved by <xsl:variable name="..." select="..."> there is no concept of set of references in XSLT, only a node-set. But passing node-set while calling a template would need to generate copies and cannot be used for representing pointers by itself. All what is needed for XSLT pointers is XSLT 1.0 plus the exslt:node-set() function. This has the advantage that XSLT pointers work on the following browsers: Chrome, Firefox(**), Internet Explorer(***), Opera, Safari. (**) Firefox does not support the namespace:: axis. (***) Support for Internet Explorer is provided by: <xsl:for-each select="document('')/xsl:stylesheet/comment()[last()]"> <xsl:call-template name="doOutput"/> </xsl:for-each> <br/> <xsl:for-each select="document('')/xsl:stylesheet/msxsl:script"> <xsl:call-template name="doOutput"/> </xsl:for-each> id-node-sets can preferably be used to implement eg. a location step as part of dyn:evaluate() function within XSLT itself along the "words": A location step identifies a new node-set relative to the context node-set. The location step is evaluated against each node in the context node-set, and the union of the resulting node-sets becomes the context node-set for the next step. Find below a sequence of above operations with generated output. </pre> <h4>seventypes.xml (input)</h4> <pre> <xsl:for-each select="/"> <xsl:call-template name="doOutput"/> </xsl:for-each> </pre> <h4>determine id-node-set from node-set (reference)</h4> <pre> <xsl:for-each select="document('')/xsl:stylesheet/xsl:template [@match='/']/xsl:variable[@name='ids']"> <xsl:call-template name="doOutput"/> </xsl:for-each> <br/> <xsl:for-each select="document('')/xsl:stylesheet/xsl:template [@match='/']/xsl:variable[@name='eids']"> <xsl:call-template name="doOutput"/> </xsl:for-each> <p/> <xsl:for-each select="$eids/id"> <xsl:call-template name="doOutput"/> <xsl:text> </xsl:text> </xsl:for-each> </pre> <h4>intentionally generate id-node-set with duplicates</h4> <pre> <xsl:for-each select="document('')/xsl:stylesheet/xsl:template [@match='/']/xsl:variable[@name='dids']"> <xsl:call-template name="doOutput"/> </xsl:for-each> <br/> <xsl:for-each select="document('')/xsl:stylesheet/xsl:template [@match='/']/xsl:variable[@name='edids']"> <xsl:call-template name="doOutput"/> </xsl:for-each> <br/><br/> <xsl:for-each select="$edids/id"> <xsl:call-template name="doOutput"/> <xsl:text> </xsl:text> </xsl:for-each> </pre> <h4>eliminate duplicates in id-node-set</h4> <pre> <xsl:for-each select="document('')/xsl:stylesheet/xsl:template [@match='/']/xsl:variable[@name='ndids']"> <xsl:call-template name="doOutput"/> </xsl:for-each> <br/> <xsl:for-each select="document('')/xsl:stylesheet/xsl:template [@match='/']/xsl:variable[@name='endids']"> <xsl:call-template name="doOutput"/> </xsl:for-each> <br/><br/> <xsl:for-each select="$endids/id"> <xsl:call-template name="doOutput"/> <xsl:text> </xsl:text> </xsl:for-each> </pre> <h4>copy of id-node-set (different order)</h4> <pre> <xsl:for-each select="document('')/xsl:stylesheet/xsl:template [@match='/']/xsl:variable[@name='cids']"> <xsl:call-template name="doOutput"/> </xsl:for-each> <br/> <xsl:for-each select="document('')/xsl:stylesheet/xsl:template [@match='/']/xsl:variable[@name='ecids']"> <xsl:call-template name="doOutput"/> </xsl:for-each> <p/> <xsl:for-each select="$ecids/id"> <xsl:call-template name="doOutput"/> <xsl:text> </xsl:text> </xsl:for-each> </pre> <h4>determine node-set from id-node-set (dereference, document order (****))</h4> <pre> <xsl:for-each select="document('')/xsl:stylesheet/xsl:template [@match='/']//xsl:variable[@name='result-nodes']"> <xsl:call-template name="doOutput"/> </xsl:for-each> (****) not for Webkit based browsers Chrome and Safari </pre> <h4>output of $result-nodes</h4> <table border="1"> <xsl:for-each select="$result-nodes"> <tr> <td> <xsl:value-of select="position()"/> </td> <td> <xsl:text>/</xsl:text> <xsl:for-each select="ancestor::*"> <xsl:value-of select="name()"/> <xsl:text>/</xsl:text> </xsl:for-each> <br/><xsl:text> </xsl:text> <xsl:call-template name="doOutput"/> </td> </tr> </xsl:for-each> </table> </body> </html> </xsl:template> <!-- from http://dpcarlisle.blogspot.com/2007/05/exslt-node-set-function.html --> <msxsl:script language="JScript" implements-prefix="exslt"> this['node-set'] = function (x) { return x; } </msxsl:script> </xsl:stylesheet> Mit besten Gruessen / Best wishes, Hermann Stamm-Wilbrandt Developer, XML Compiler, L3 Fixpack team lead WebSphere DataPower SOA Appliances ---------------------------------------------------------------------- IBM Deutschland Research & Development GmbH Vorsitzender des Aufsichtsrats: Martin Jetter Geschaeftsfuehrung: Dirk Wittkopp Sitz der Gesellschaft: Boeblingen Registergericht: Amtsgericht Stuttgart, HRB 243294
Current Thread |
---|
|
<- Previous | Index | Next -> |
---|---|---|
Re: [xsl] Fwd: NLM3.0, Wendell Piez | Thread | Re: [xsl] XSLT pointers / questions, Michael Kay |
[xsl] Fwd: NLM3.0, Evan Leibovitch | Date | Re: [xsl] XSLT pointers / questions, Michael Kay |
Month |