<?xml version="1.0" encoding="UTF-8"?>
<!-- Copyright (c) 2010 DeltaXML Ltd.  All rights reserved. -->
<!-- This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License version 3 only,
    as published by the Free Software Foundation.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License version 3 for more details
    (a copy is included in the LICENSE-LGPL.txt file that accompanied this code).

    You should have received a copy of the GNU Lesser General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/> -->
<!-- $Id: rollback-one-change.xsl 6781 2010-07-21 16:33:00Z tristanm $ -->
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
                xmlns:xs="http://www.w3.org/2001/XMLSchema" 
                xmlns:func="namespace-for-functions"
                xmlns:delta="http://www.deltaxml.com/ns/track-changes/delta-namespace"
                xmlns:ac="http://www.deltaxml.com/ns/track-changes/attribute-change-namespace"
                xmlns:split="http://www.deltaxml.com/ns/track-changes/split-namespace"
                xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0"
                exclude-result-prefixes="xs delta ac split office" 
                version="2.0">
  
  <xsl:variable name="ac-namespace" select="'http://www.deltaxml.com/ns/track-changes/attribute-change-namespace'"/>
  
  <xsl:key name="splits" match="*[@delta:split-id]" use="@delta:split-id"/>
  
  <!-- the processing is performed in two passes - 
       the first pass removes items that have been added, 
       the second pass recreates elements that were deleted -->
  <xsl:template match="/">
    <xsl:variable name="pass1">
      <xsl:apply-templates mode="pass1"/>
    </xsl:variable>
    <!--<xsl:if test="$input-doc='content'">
    <xsl:message>PASS1:
<xsl:copy-of select="$pass1" copy-namespaces="no"/>
    </xsl:message>
    </xsl:if>-->
    <xsl:apply-templates select="$pass1" mode="pass2"/>
  </xsl:template>
  
  <!-- identity transform -->
  <xsl:template match="node() | @*" mode="#all">
    <xsl:copy>
      <xsl:apply-templates select="@* | node()" mode="#current"/>
    </xsl:copy>
  </xsl:template>
  
  <xsl:variable name="input-doc">
    <xsl:choose>
      <xsl:when test="office:document-content"><xsl:value-of select="'content'"/></xsl:when>
      <xsl:when test="/office:document-styles"><xsl:value-of select="'styles'"/></xsl:when>
    </xsl:choose>
  </xsl:variable>
  
  <xsl:variable name="content-document" select="if($input-doc = 'content') then /* else document('content.xml', /*)"/>
  
  <xsl:variable name="last-change" select="$content-document//delta:tracked-changes/*[last()]/@delta:change-id"/>
  
  <xsl:variable name="insert-around-content" select="'insert-around-content'"/>
<!--  <xsl:variable name="remove-leaving-content" select="'remove-leaving-content'"/>-->
  
  <!-- PASS 1 TEMPLATES (removal of insertions) -->
  
  <!-- delete the change transaction -->
  <xsl:template match="delta:change-transaction[@delta:change-id=$last-change]" mode="pass1"/>
  
  <!-- roll back a text insertion -->
  <xsl:template match="text()[preceding-sibling::delta:inserted-text-start[@delta:insertion-change-idref = $last-change]]
                             [following-sibling::delta:inserted-text-end[@delta:inserted-text-end-id = preceding-sibling::delta:inserted-text-start[@delta:insertion-change-idref=$last-change]/@delta:inserted-text-end-idref]]"
                mode="pass1"/>
  <!-- the delta:inserted-text-start will be deleted by the general element insertion template. The end marker needs manual deletion -->
  <xsl:template match="delta:inserted-text-end[@delta:inserted-text-end-id=preceding-sibling::delta:inserted-text-start[@delta:insertion-change-idref=$last-change]/@delta:inserted-text-end-idref]"
                mode="pass1"/>
  
  <!-- roll back an element insertion -->
  <xsl:template match="*[@delta:insertion-change-idref=$last-change]" mode="pass1">
    <xsl:if test="@delta:insertion-type=$insert-around-content">
      <xsl:apply-templates select="node()" mode="#current"/>
    </xsl:if>
  </xsl:template>
  
  <!-- roll back a split-start (the end is rolled back by the general element insertion template) -->
  <xsl:template match="*[@split:*][key('splits', @split:*)/@delta:insertion-change-idref=$last-change]" mode="pass1">
    <xsl:variable name="split-end-element" select="key('splits', @split:*)[@delta:insertion-change-idref=$last-change]"/>
    <xsl:variable name="this-ct-split-id" select="$split-end-element/@delta:split-id"/>
    <xsl:variable name="split-att" as="attribute()" select="@split:*[. = $this-ct-split-id]"/>
    <!--<xsl:message>This ct split id: <xsl:value-of select="$this-ct-split-id"/></xsl:message>
    <xsl:message>split-att: <xsl:copy-of select="$split-att" copy-namespaces="no"/></xsl:message>-->
    <xsl:copy>
      <xsl:apply-templates select="@* except $split-att, node(), $split-end-element/node()" mode="#current"/>
    </xsl:copy>
  </xsl:template>
  
  <!-- roll back an attribute change -->
  <xsl:template match="*[exists(@ac:*[func:att-changed-in-last-transaction(.)])]" mode="pass1">
    <xsl:copy>
      <xsl:call-template name="output-attributes">
        <xsl:with-param name="atts" select="@*"/>
        <xsl:with-param name="context-node" select="."/>
      </xsl:call-template>
      <xsl:apply-templates select="node()" mode="#current"/>
    </xsl:copy>
  </xsl:template>
  
  <!-- PASS 2 TEMPLATES (recreates removed elements) -->
  
  <!-- roll back content removal -->
  <xsl:template match="delta:removed-content[@delta:removal-change-idref=$last-change]" mode="pass2">
    <!-- don't copy the deletion element, just output its content -->
    <xsl:apply-templates select="node()" mode="#current"/>
  </xsl:template>
  
  <!-- roll back a remove-leaving-content -->
  <xsl:template match="*[delta:remove-leaving-content-start[@delta:removal-change-idref=$last-change]]" mode="pass2">
    <!-- the flattened element can be reconstructed using grouping -->
    <xsl:copy>
      <xsl:apply-templates select="@*" mode="#current"/>
      <xsl:call-template name="recreate-rlcs">
        <xsl:with-param name="sequence" select="node()"/>
      </xsl:call-template>
    </xsl:copy>
  </xsl:template>
  
  <!-- roll back a merge -->
  <xsl:template match="*[delta:merge[@delta:removal-change-idref=$last-change]]" mode="pass2">
    <xsl:variable name="merge" select="delta:merge[@delta:removal-change-idref=$last-change]"/>
    <xsl:copy>
      <xsl:apply-templates select="@*, node()[. &lt;&lt; $merge]" mode="#current"/>
      <xsl:apply-templates select="$merge/delta:leading-partial-content/node()" mode="#current"/>
    </xsl:copy>
    <xsl:apply-templates select="$merge/delta:intermediate-content/node()" mode="#current"/>
    <xsl:element namespace="{namespace-uri($merge/delta:trailing-partial-content/*[1])}" name="{local-name($merge/delta:trailing-partial-content/*[1])}">
      <xsl:apply-templates select="$merge/delta:trailing-partial-content/*[1]/@*, $merge/delta:trailing-partial-content/*[1]/node()" mode="#current"/>
      <xsl:apply-templates select="node()[. >> $merge]" mode="#current"/>
    </xsl:element>
  </xsl:template>
  
  <!-- NAMED TEMPLATES -->
  <xsl:template name="output-attributes">
    <xsl:param name="atts" as="attribute()*"/>
    <xsl:param name="context-node" as="element()"/>
    
    <xsl:variable name="added-atts" as="element()*">
      <xsl:for-each select="$atts[namespace-uri(.) = $ac-namespace]">
        <xsl:variable name="tokens" select="tokenize(., ',')"/>
        <xsl:if test="$tokens[1] = $last-change and $tokens[2] = 'insert'">
          <att name="{$tokens[3]}"/>
        </xsl:if>
      </xsl:for-each>
    </xsl:variable>
    
    <xsl:variable name="deleted-atts" as="element()*">
      <xsl:for-each select="$atts[namespace-uri(.) = $ac-namespace]">
        <xsl:variable name="tokens" select="tokenize(., ',')"/>
        <xsl:if test="$tokens[1] = $last-change and $tokens[2] = 'remove'">
          <att name="{$tokens[3]}" value="{string-join(for $i in 4 to count($tokens) return $tokens[$i], ',')}"/>
        </xsl:if>
      </xsl:for-each>
    </xsl:variable>
    
    <xsl:variable name="modified-atts" as="element()*">
      <xsl:for-each select="$atts[namespace-uri(.) = $ac-namespace]">
        <xsl:variable name="tokens" select="tokenize(., ',')"/>
        <xsl:if test="$tokens[1] = $last-change and $tokens[2] = 'modify'">
          <att name="{$tokens[3]}" value="{string-join(for $i in 4 to count($tokens) return $tokens[$i], ',')}"/>
        </xsl:if>
      </xsl:for-each>
    </xsl:variable>
    
    <xsl:variable name="att-changes-from-other-cts" as="attribute()*">
      <xsl:for-each select="$atts[namespace-uri(.) = $ac-namespace]">
        <xsl:variable name="tokens" select="tokenize(., ',')"/>
        <xsl:if test="$tokens[1] != $last-change">
          <xsl:copy-of select="."/>
        </xsl:if>
      </xsl:for-each>
    </xsl:variable>
    
    <!--<xsl:message>Added atts: <xsl:copy-of select="$added-atts" copy-namespaces="no"></xsl:copy-of></xsl:message>
    <xsl:message>Deleted atts: <xsl:copy-of select="$deleted-atts" copy-namespaces="no"></xsl:copy-of></xsl:message>
    <xsl:message>Modified atts: <xsl:copy-of select="$modified-atts" copy-namespaces="no"></xsl:copy-of></xsl:message>-->
    
    <xsl:for-each select="$atts[not(namespace-uri(.) = $ac-namespace)]">
<!--      <xsl:message>Attribute name(): <xsl:value-of select="name(.)"/></xsl:message>-->
      <xsl:choose>
        <xsl:when test="name(.) = $added-atts//@name">
          <!-- do nothing, this att was added in the latest ct and should be removed -->
        </xsl:when>
        <xsl:when test="name(.) = $modified-atts//@name">
          <!-- output the old version of the attribute -->
          <xsl:variable name="modified-att" select="$modified-atts[@name=name(current())]"/>
          <!--<xsl:message>Modified att - <xsl:copy-of select="$modified-att" copy-namespaces="no"/></xsl:message>
          <xsl:message>Modified att value <xsl:value-of select="$modified-att/@value"></xsl:value-of></xsl:message>-->
          <xsl:attribute namespace="{namespace-uri(.)}" name="{local-name(.)}" select="$modified-att/@value"/>
        </xsl:when>
        <xsl:otherwise>
          <xsl:copy/>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:for-each>
    
    <xsl:copy-of select="$att-changes-from-other-cts"/>
    
    <xsl:for-each select="$deleted-atts">
      <xsl:variable name="prefix" select="if (contains(@name, ':')) then substring-before(@name, ':') else ''"/>
      <xsl:variable name="local-name" select="if (contains(@name, ':')) then substring-after(@name, ':') else @name"/>
      <xsl:attribute namespace="{if (string-length($prefix) != 0) then namespace-uri-for-prefix($prefix, $context-node) else ''}" name="{$local-name}" select="@value"/>
    </xsl:for-each>
    
  </xsl:template>
  
  
  <!-- two-level grouping as discussed:
    
    <p>T1 <span>T2<span>T3</span><span>T4</span></span><span>T5</span>T6</p>
    
                prev-starts  prev-ends     equal?   diff
    T1             0             0           T        0
    === split of first level grouping
    start          1             0           F        1  * Group start on second grouping
    T2             1             0           F        1
    start          2             0           F        2
    T3             2             0           F        2
    end            2             0           F        2
    start          3             1           F        2
    T4             3             1           F        2
    end            3             1           F        2
    end            3             2           F        1
    start          4             3           F        1  * Group start on second grouping
    T5             4             3           F        1
    end            4             3           F        1
    === split of first level grouping
    T6             4             4           T        0
    
    -->
  
  <xsl:template name="recreate-rlcs">
    <xsl:param name="sequence"/>
    
    <xsl:for-each-group select="$sequence" group-adjacent="func:previous-rlc-starts($sequence, current()) = func:previous-rlc-ends($sequence, current())">
<!--      <xsl:message>GROUPING A: <xsl:copy-of select="current-group()" copy-namespaces="no"/></xsl:message>-->
      <xsl:choose>
        <xsl:when test="current-grouping-key() = true()">
<!--          <xsl:message>COPYING</xsl:message>-->
          <!-- we are not inside any flattened content, output the group -->
          <xsl:apply-templates select="current-group()" mode="pass2"/>
        </xsl:when>
        <xsl:otherwise>
<!--          <xsl:message>CREATE AND RECURSE</xsl:message>-->
          <xsl:for-each-group select="current-group()" group-starting-with="*[func:is-current-rlc-start(.) and func:previous-rlc-starts($sequence, .) -  func:previous-rlc-ends($sequence, .) = 1]">
<!--            <xsl:message>GROUPING B: <xsl:copy-of select="current-group()" copy-namespaces="no"/></xsl:message>-->
            <xsl:variable name="elem-to-reconstruct" select="current-group()[1]/*[1]"/>
            <xsl:element namespace="{namespace-uri($elem-to-reconstruct)}" name="{local-name($elem-to-reconstruct)}">
              <xsl:apply-templates select="$elem-to-reconstruct/@*" mode="pass2"/>
              <!-- we may have nested flattened elements, recurse -->
              <xsl:call-template name="recreate-rlcs">
              <xsl:with-param name="sequence" select="subsequence(current-group(), 2, count(current-group())-2)"/>
              </xsl:call-template>
              </xsl:element>
          </xsl:for-each-group>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:for-each-group>
    
  </xsl:template>
  
  
  <!-- FUNCTIONS -->
  <xsl:function name="func:att-changed-in-last-transaction" as="xs:boolean">
    <xsl:param name="att" as="attribute()"/>
    
    <xsl:variable name="ct" select="substring-before($att, ',')"/>
    <xsl:value-of select="$ct = $last-change"/>
    
  </xsl:function>
  
  <!-- 
    This function takes a sequence of nodes ($sequence) and a single node that is in the sequence ($item)
    It returns the position of the node in the sequence.
    WARNING: The return value is undefined if $item is not in $sequence!
  -->
  <xsl:function name="func:position" as="xs:integer">
    <xsl:param name="sequence"/>
    <xsl:param name="item"/>
    <xsl:for-each select="$sequence">
      <xsl:if test="current() is $item">
        <xsl:value-of select="position()"/>
      </xsl:if>
    </xsl:for-each>
  </xsl:function>
  
  <!-- 
    This function takes a sequence of nodes ($sequence) and a single node that is in the sequence ($item)
    It returns the number of remove-leaving-content elements in the sequence that occur BEFORE $item. If $item is
    itself a remove-leaving-content element, it will be included in that count. This ensures that when grouping occurs,
    the remove-leaving-content element is in the same group as its content.
    WARNING: The return value is undefined if $item is not in $sequence!
  -->
  <xsl:function name="func:previous-rlc-starts"  as="xs:integer">
    <xsl:param name="sequence"/>
    <xsl:param name="item"/>
    <xsl:variable name="item-position" select="func:position($sequence, $item)" as="xs:integer"/>
    <xsl:value-of select="count($sequence[self::*[func:is-current-rlc-start(.)][func:position($sequence, .) le $item-position]])"/>
  </xsl:function>
  
  <!-- 
    This function takes a sequence of nodes ($sequence) and a single node that is in the sequence ($item)
    It returns the number of delta:end-element elements in the sequence that occur BEFORE $item. If $item is
    itself a delta:end-element element, it will NOT be included in that count. This ensures that when grouping occurs,
    the delta:end-element element is in the same group as its content.
    WARNING: The return value is undefined if $item is not in $sequence!
  -->
  <xsl:function name="func:previous-rlc-ends"  as="xs:integer">
    <xsl:param name="sequence"/>
    <xsl:param name="item"/>
    <xsl:variable name="item-position" select="func:position($sequence, $item)" as="xs:integer"/>
    <xsl:value-of select="count($sequence[self::*[func:is-current-rlc-end(.)][func:position($sequence, .) lt $item-position]])"/>
  </xsl:function>
  
  <!-- this function states whether the passed element is a remove-leaving-content element from the last change transaction --> 
  <xsl:function name="func:is-current-rlc-start" as="xs:boolean">
    <xsl:param name="elem" as="node()"/>
    <xsl:value-of select="$elem/self::delta:remove-leaving-content-start and $elem/@delta:removal-change-idref=$last-change"/>
  </xsl:function>
  
  <!-- this function states whether the passed element is a remove-leaving-content end element from the last change transaction --> 
  <xsl:function name="func:is-current-rlc-end" as="xs:boolean">
    <xsl:param name="elem" as="node()"/>
    <xsl:variable name="matching-start" select="$elem/preceding-sibling::*[@delta:end-element-idref=$elem/@delta:end-element-id]"/>
    <xsl:value-of select="$elem/self::delta:remove-leaving-content-end and func:is-current-rlc-start($matching-start)"/>
  </xsl:function>
  
  
</xsl:stylesheet>
