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 Saturday, 10 October 2015 16:32:56 UTC