RE: snapshot() function - bug 29141

I know I was supposed to respond to this sooner, sorry about that, too many distractions. 

 

I tested below stylesheet with several of my existing scenarios with complex trees, attribute nodes, namespace nodes etc and couldn't get it to fail. However there's one editorial note:

 

    <!-- for a parentless node, return a deep copy -->
    
    <xsl:template match="/" mode="snapshot">
      <xsl:copy-of select="."/>
    </xsl:template>

 

I think this comment should be different. Besides, I think it should match against "root()" (now allowed in patterns) to capture snapshot invocation called with the root of a parentless node.

 

Cheers,

Abel

 

From: Michael Kay [mailto:mike@saxonica.com] 
Sent: Monday, October 12, 2015 6:10 PM
To: Abel Braaksma
Cc: Public XSLWG
Subject: Re: snapshot() function - bug 29141

 

I think there are still a few problems with this formulation. 

 

* name() and self::xxx throw type errors if the context item is not a node. (The type error might be ignored if the variable isn’t used, but you can’t rely on it)

 

* (@* | namespace::*)[name() = $name] doesn’t work if there’s an attribute and a namespace having the same name

 

* name() is comparing lexical QNames which, although it might work, seems dangerous

 

I did consider the approach of returning to the “node corresponding to the original node” by using a count of ancestors, but I think the approach of adding ancestors recursively and maintaining the result node on each recursive call feels nicer.

 

However, I think my solution is wrong as well. In graft-to-parent, the call on apply-templates will always return the root of the new tree, and the caller of graft-to-parent then only drops down one level. It seems that I only passed the test case (snapshot-0102a) by having the critical part of the test accidentally commented-out!

 

Getting this right was a fair bit of effort, but I think I’ve got it:

 

   <xsl:function name="f:snapshot" as="item()*">
      <xsl:param name="input" as="item()*"/>
      <xsl:apply-templates select="$input" mode="snapshot"/>
    </xsl:function>
    
    <!-- for atomic values and function items, return the item unchanged -->
    
    <xsl:template match="." mode="snapshot">
      <xsl:sequence select="."/>
    </xsl:template>
    
    <!-- for a parentless node, return a deep copy -->
    
    <xsl:template match="/" mode="snapshot">
      <xsl:copy-of select="."/>
    </xsl:template>
    
    <!-- for an element, comment, or processing instruction: -->
    
    <xsl:template match="node()" mode="snapshot" as="node()">
      <xsl:sequence select="f:graft-to-parent(., .., function($n){$n/child::node()})"/>
    </xsl:template>
    
    <!-- for an attribute with a parent, graft it to a copy of its parent;
         then return the corresponding attribute this copied parent. -->
    
    <xsl:template match="@*" mode="snapshot" as="attribute()">
      <xsl:variable name="name" select="node-name(.)"/>
      <xsl:sequence select="f:graft-to-parent(., .., function($n){$n/@*[node-name(.) = $name]})"/>
    </xsl:template>
    
    <!-- for a namespace node with a parent, graft it to a copy of its parent;
         then return the corresponding namespace node of this copied parent. -->
    
    <xsl:template match="namespace-node()" mode="snapshot" as="namespace-node()">
      <xsl:variable name="name" select="local-name(.)"/>
      <xsl:sequence select="f:graft-to-parent(., .., function($n){$n/namespace-node()[local-name(.) = $name]})"/>
    </xsl:template>
    
    <!-- make a copy of the parent of a supplied node, with a copy of the supplied node attached
         as a child/attribute/namespace of the new parent as appropriate -->
    
    <xsl:function name="f:graft-to-parent" as="node()">
      <xsl:param name="n" as="node()"/>
      <xsl:param name="original-parent" as="node()?"/>
      <xsl:param name="down-function" as="function(node()) as node()"/>
      <!--<xsl:message>graft {path($n)} original-parent: {path($original-parent)}</xsl:message>-->
      <xsl:choose>
        <xsl:when test="$original-parent">
          <xsl:variable name="p" as="node()">
            <xsl:copy select="$original-parent">
              <xsl:copy-of select="@*"/>
              <xsl:copy-of select="$n"/>
            </xsl:copy>
          </xsl:variable>
          <xsl:sequence select="$down-function(f:graft-to-parent($p, $original-parent/.., function($n){$n/child::node()}))"/>
        </xsl:when>
        <xsl:otherwise>
          <xsl:sequence select="$n"/>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:function>

 

Michael Kay

Saxonica

 

 

On 11 Oct 2015, at 01:29, Abel Braaksma <abel.braaksma@xs4all.nl> wrote:

 

Hmm, I seem to have forgotten that the returned node must have the equivalent focus as the current node (the below function sets the focus at the root of the returned tree).

 

Here's an adjusted alternative, but it loses a bit (large bit) of its charm ;). The function f:copy-ancestors-then-self() was not changed. It is still streamable (the non-streamable version of the same was a bit terser). I am wondering if this can perhaps be more conveniently expressed in an accumulator? 

 

 

<xsl:function name="f:snapshot" as="item()*">
    <xsl:param name="items" as="item()*"/>
    <xsl:copy-of select="$items ! (
        let $name := name(), 
            $is-ns-or-at := self::namespace-node() or self::attribute(),
            $cnt := count(ancestor::node())
        return 
            if(. instance of node() and not(($is-ns-or-at) and not(.[..]))) 
            then 
                f:copy-ancestors-then-self(., 0)/descendant-or-self::node()[$cnt]/(
                if($is-ns-or-at) then (@* | namespace::*)[name() = $name]
                else ./node())
            else .)"/>
</xsl:function>

<xsl:function name="f:copy-ancestors-then-self" as="node()?">
    <xsl:param name="node" as="node()"/>
    <xsl:param name="ancestor-pos" as="xs:integer"/>
    <xsl:variable name="current-ancestor" select="count($node/ancestor::node()) - $ancestor-pos" />
    
    <!-- if the snapshot anchor element is a parentless root node, or the document-node -->
    <xsl:copy-of select="$node[root($node) is $node]"/>
    
    <!-- otherwise (the @select serves as an implicit if-statement) -->
    <xsl:copy select="$node/ancestor::node()[$current-ancestor]">
        <xsl:copy-of select="$node/ancestor::node()[$current-ancestor]/attribute::*"/>
        <xsl:sequence select="
            f:copy-ancestors-then-self($node, $ancestor-pos + 1),
            copy-of($node[count($node/ancestor::node()) = $ancestor-pos + 1])" />
    </xsl:copy>
</xsl:function>

 

From: Abel Braaksma [mailto:abel.braaksma@xs4all.nl] 
Sent: Saturday, October 10, 2015 6:32 PM
To: 'Michael Kay'; 'Public XSLWG'
Subject: RE: snapshot() function - bug 29141

 

While I agree that your approach looks simpler to what is currently present in the spec, it is not streamable anymore (you apply templates to the parent axis). I gave it a try myself and came up with what I think is a simpler approach (if not simpler, at least it's shorter). It seems to work with a bunch of test cases, including namespace and attribute nodes as starting points, and with non-nodes. Since it uses xsl:copy and xsl:copy-of, their defaults for copying namespaces work with the description of fn:snapshot. We could add copy-accumulators="true" (btw, it is only available on xsl:copy-of, not on xsl:copy), even though the current prose already says that this should be assumed.

 

In addition, this solution is streamable (but only by virtue of implementation optimization, it does not fit the streamability rules we have, which will "think" that there are two downward expressions below and won't statically detect that there aren't; it can be rewritten in such a way without much trouble, if we'd want that).

 

<xsl:function name="f:snapshot" as="item()*">
    <xsl:param name="items" as="item()*"/>
    <xsl:copy-of select="$items ! (if(. instance of node()) then f:copy-ancestors-then-self(., 0) else .)"/>
</xsl:function>

<xsl:function name="f:copy-ancestors-then-self" as="node()?">
    <xsl:param name="node" as="node()"/>
    <xsl:param name="ancestor-pos" as="xs:integer"/>
    <xsl:variable name="current-ancestor" select="count($node/ancestor::node()) - $ancestor-pos" />
    
    <!-- if the snapshot anchor element is a parentless root node, or the document-node -->
    <xsl:copy-of select="$node[root($node) is $node]"/>
    
    <!-- otherwise (the @select serves as an implicit if-statement) -->
    <xsl:copy select="$node/ancestor::node()[$current-ancestor]">
        <xsl:copy-of select="$node/ancestor::node()[$current-ancestor]/attribute::*"/>
        <xsl:sequence select="
            f:copy-ancestors-then-self($node, $ancestor-pos + 1),
            copy-of($node[count($node/ancestor::node()) = $ancestor-pos + 1])" />
    </xsl:copy>
</xsl:function>

 

Thanks,

Abel

 

From: Michael Kay [mailto:mike@saxonica.com] 
Sent: Friday, October 09, 2015 7:47 PM
To: Public XSLWG
Subject: snapshot() function - bug 29141

 

In implementing the changes for the snapshot() function (making it apply to any kind of item and clarifying that it snapshots every item in a sequence), I found myself feeling that there must be a more elegant XSLT implementation of the function than the one we currently publish. This is what I came up with. It avoids the horrible search of the result tree to find the node that corresponds to the node where you started, doing this by always returning the relevant node after adding each new ancestor. It appears to work! Like the existing code, I’m not sure it captures what happens with accumulators. It’s horribly inefficient (in Saxon) because it makes a new copy of the whole tree once for each ancestor that gets added, but who cares? Comments?

 

   <xsl:function name="f:snapshot" as="item()*">
      <xsl:param name="input" as="item()*"/>
      <xsl:apply-templates select="$input" mode="snapshot"/>
    </xsl:function>
    
    <!-- for atomic values and function items, return the item unchanged -->
    
    <xsl:template match="." mode="snapshot">
      <xsl:sequence select="."/>
    </xsl:template>
    
    <!-- for a parentless node, return a deep copy -->
    
    <xsl:template match="(/ | node() | @* | namespace-node())[not(..)]" mode="snapshot">
      <xsl:copy-of select="."/>
    </xsl:template>
    
    <!-- for an element with a parent, graft it to a copy of its parent;
         then return the only child element of this copied parent. -->
    
    <xsl:template match="*[..]" mode="snapshot" as="element()">
      <xsl:sequence select="f:graft-to-parent(.)/*"/>
    </xsl:template>
    
    <!-- for an attribute with a parent, graft it to a copy of its parent;
         then return the corresponding attribute of this copied parent. -->
    
    <xsl:template match="@*[..]" mode="snapshot" as="attribute()">
      <xsl:sequence select="f:graft-to-parent(.)/@*[node-name(.) = node-name(current())]"/>
    </xsl:template>
    
    <!-- for a namespace node with a parent, graft it to a copy of its parent;
         then return the corresponding namespace node of this copied parent. -->
    
    <xsl:template match="namespace-node()[..]" mode="snapshot" as="namespace-node()">
      <xsl:sequence select="f:graft-to-parent(.)/namespace-node()[local-name(.) = local-name(current())]"/>
    </xsl:template>
    
    <!-- make a copy of the parent of a supplied node, with a copy of the supplied node attached
         as a child/attribute/namespace of the new parent as appropriate -->
    
    <xsl:function name="f:graft-to-parent" as="node()">
      <xsl:param name="n" as="node()"/>
      <xsl:apply-templates select="$n/.." mode="snapshot-ancestor">
        <xsl:with-param name="child" select="$n"/>
      </xsl:apply-templates>
    </xsl:function>
    
    <!-- make a copy of the parent of a supplied node, with a copy of the supplied node attached
         as a child/attribute/namespace of the new parent as appropriate, grafted on to a copy
         of its own parent, recursively -->
    
    <xsl:template match="node()[..]" mode="snapshot-ancestor">
      <xsl:param name="child" required="yes"/>
      <xsl:apply-templates select=".." mode="snapshot-ancestor">
        <xsl:with-param name="child" as="node()">
          <xsl:copy>
            <xsl:copy-of select="@*"/>
            <xsl:copy-of select="$child"/>
          </xsl:copy>
        </xsl:with-param>
      </xsl:apply-templates>
    </xsl:template>
    
    <!-- make a copy of the parent of a supplied node, with a copy of the supplied node attached
         as a child/attribute/namespace of the new parent as appropriate, in the case where the
         parent is the root of the tree -->
    
    <xsl:template match="/ | node()[not(..)]" mode="snapshot-ancestor">
      <xsl:param name="child" required="yes"/>
      <xsl:copy>
        <xsl:copy-of select="@*"/>
        <xsl:copy-of select="$child"/>
      </xsl:copy>
    </xsl:template>
    


</xsl:stylesheet>

 

Received on Thursday, 22 October 2015 15:53:01 UTC