[xsl] XSLT pointers / questions

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="&#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