[XSL-LIST Mailing List Archive Home] [By Thread] [By Date]

Re: [xsl] XSLT pointers / questions


Subject: Re: [xsl] XSLT pointers / questions
From: Michael Kay <mike@xxxxxxxxxxxx>
Date: Tue, 21 Sep 2010 22:38:27 +0100

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

Dangerous terminology here. We're talking XSLT 1.0. So $result-nodes is a node-set - a set of nodes - it has no order. But the semantics of xsl:for-each produce an ordered output which is equivalent to the order obtained by processing the nodes of the input node-set in document order. In fact, pretty well anything you can do with a node-set processes the nodes in document order, which means it's convenient to think of the nodes as actually being in document order, and it's convenient for the implementation to keep them in document order.


If a processor implements xsl:for-each without respecting this rule, then it is non-conformant. (I would hazard a guess that the bug is not in the xsl:for-each instruction, however, but in the key() function; the processor is probably assuming that node-sets are always held internally in document order, and the key() function is [perhaps] failing to respect this assumption.)

As for xsl:copy-of, the XSLT 1.0 spec says "When the result [of the select expression] is a node-set, all the nodes in the set are copied in document order into the result tree." Clearly, if a processor breaks the rule for xsl:for-each then there is no way of predicting how conformant it will be with other constructs.

Michael Kay
Saxonica



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="&#10;
           /comment() |&#10;
           /e1/@att |&#10;
           processing-instruction() |&#10;
           / |&#10;
           /e1/e2/e4/namespace::xml |&#10;
           /e1/e2 |&#10;
           /e1/text() |&#10;
           /e1/e2/*[local-name()='e3']/namespace::*[name()=''] |&#10;
           /e1/e2/namespace::m |&#10;
           /e1/e2/e4/namespace::ns |&#10;
           /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="&#10;
       key('nodes-by-genid',$ecids/id[not(pre)]/gen) |&#10;
       key('nodes-by-genid',$ecids/id[pre]/gen)/&#10;
       namespace::*[concat(generate-id(..),':',name())=$ecids/id]"/>


<html> <body>


<h3>XSLT pointers</h3>

     <pre>
     (Referencing) Every non-namespace node will be represented by:
       &lt;id>&lt;gen>&lt;xsl:value-of select="generate-id()"/>
&lt;/gen>&lt;/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&lt;xsl:key>.
     Therefore we represent its parent and its name.
     Both parts are seperated by colon character:
       &lt;id>&lt;gen>&lt;xsl:value-of select="generate-id(..)"/>
         &lt;/gen>:&lt;pre>&lt;xsl:value-of select="name()"/>
&lt;/pre>&lt;/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
     &lt;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>&#10;</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>&#10;</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>&#10;</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>&#10;</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>&#10;</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
Keywords