<?xml version="1.0" encoding="UTF-8"?>
<!--
   Copyright 2014 IBM Corp.
  
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at
  
   http://www.apache.org/licenses/LICENSE-2.0
  
   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
-->

<!-- 
  This stylesheet provides utility functions to merge XML into a template.
-->
<xsl:stylesheet version="1.0" 
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
  xmlns:xsd="http://www.w3.org/2001/XMLSchema"
  xmlns:dcm="urn:datapower:configuration:manager"
  xmlns:date="http://exslt.org/dates-and-times"
  xmlns:dtat="urn:local-function:definition-to-ant-tasks"
  xmlns:dyn="http://exslt.org/dynamic"
  xmlns:exslt="http://exslt.org/common"
  xmlns:func="http://exslt.org/functions"
  xmlns:local="urn:local:function"
  xmlns:mgmt="http://www.datapower.com/schemas/management"
  xmlns:str="http://exslt.org/strings"
  extension-element-prefixes="date dyn exslt func str" 
  exclude-result-prefixes="date dyn exslt func str">
  

  <xsl:include href="util-debug.xsl"/>
  
  
  <!-- 
    This function merges the supplied template with the generated XML, as described in the comments
    at the top of the default template.

    This ant script template is used to produce an ant script that implements a DCM policy
    combined with a set of DCM operations (e.g. some policy plus "import, save").
    
    The generate-script.xsl stylesheet generates a big wad of XML that contains
    bits of ant scripting.  It then makes a pass through this template, replacing special
    markers with bits of that big wad of XML.
    
    The markers are simple.  To insert elements into the generated ant script:
    
      <replace-here xpath="..."/>
      
    The @xpath is an xpath expression into that big wad of XML mentioned earlier.
    
    To replace or modify the content of an attribute, insert the same xpath, but enclosed in
    double curly braces.  For example:
    
      <project name="{{/generated/project-name}}" ...>
    
    The results of evaluating the xpath expression against the big wad of XML are treated as a
    string for the {{...}} case, and as a list of nodes for the <replace-here> case.
    
    This comment is not included in the final ant script by virtue of generate-script.xsl
    dropping the first comment in the script.  Don't combine this comment with the following
    comment that describes the ant script that results from generate-script.xsl
    
    This ant script ...
    
    This script relies on a number of externally-defined properties.  This script can be run
    immediately after it is generated by DCM, or it can be run later.  You have the opportunity
    (and responsibility) to define these properties according to the environment at the time
    the script is run, if you move this script to another machine.  For example, you may generate
    this script on some machine and store it in source control.  The script is then run later on
    a completely different machine.  In this case you will need to define these properties to
    fit the new machine.
    
    dcm.jar - location of DCM jar file
    work.dir - location of DCM temp directory
    dcm.dir - location of DCM directory
    schema.dir - location of directory used by DCM to cache internally-generated schemas 

  -->
  <func:function name="dtat:mergeTemplate">
    <xsl:param name="tmpl"/>
    <xsl:param name="generated"/>
    <func:result>
      <xsl:apply-templates select="$tmpl" mode="dtat:mergeTemplate">
        <xsl:with-param name="generated" select="$generated"/>
      </xsl:apply-templates>
    </func:result>
  </func:function>
  
  
  <xsl:template match="/" mode="dtat:mergeTemplate">
    <xsl:param name="generated"/>
    <xsl:copy>
      <xsl:apply-templates select="node()" mode="dtat:mergeTemplate">
        <xsl:with-param name="generated" select="$generated"/>
      </xsl:apply-templates>
    </xsl:copy>
  </xsl:template>
  
  <xsl:template match="replace-here[@xpath]" mode="dtat:mergeTemplate">
    <xsl:param name="generated"/>
    <!-- Replace this element with the specified portion (see @xpath) of the generated XML. -->
    <xsl:variable name="xpath" select="@xpath"/>
    <xsl:for-each select="$generated">
      <xsl:variable name="result" select="dyn:evaluate($xpath)"/>
      <!-- <xsl:message>### xpath=<xsl:value-of select="$xpath"/>, $generated contains <xsl:value-of select="count($generated//node())"/> nodes, $result contains <xsl:value-of select="count($result//node())"/> nodes.</xsl:message> -->
      <xsl:copy-of select="$result"/>
    </xsl:for-each>
  </xsl:template>
  
  <xsl:template match="*" mode="dtat:mergeTemplate">
    <xsl:param name="generated"/>
    <xsl:copy>
      <xsl:apply-templates select="@* | node()" mode="dtat:mergeTemplate">
        <xsl:with-param name="generated" select="$generated"/>
      </xsl:apply-templates>
    </xsl:copy>
  </xsl:template>
  
  <xsl:template match="/comment()[1]" mode="dtat:mergeTemplate"/>
  
  <xsl:template match="@*" mode="dtat:mergeTemplate">
    <xsl:param name="generated"/>
    <xsl:choose>
      <xsl:when test="contains(., '{{') and contains(., '}}')">

        <!-- This attribute may contain a {{...}} token -->

        <xsl:attribute name="{name()}"><xsl:value-of select="dtat:replaceExpressionXML(., $generated)"/></xsl:attribute>
        
      </xsl:when>
      <xsl:otherwise>

        <!-- This attribute doesn't contain a {{...}} token, so just copy it unchanged. -->
        <xsl:copy-of select="."/>
        
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>
  
  <xsl:template match="text()" mode="dtat:mergeTemplate">
    <xsl:param name="generated"/>
    <xsl:choose>
      <xsl:when test="contains(., '{{') and contains(., '}}')">
        
        <!-- This text may contain a {{...}} token -->
        
        <xsl:value-of select="dtat:replaceExpressionXML(., $generated)"/>
        
      </xsl:when>
      <xsl:otherwise>
        
        <!-- This text doesn't contain a {{...}} token, so just copy it unchanged. -->
        <xsl:value-of select="."/>
        
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>
  
  <xsl:template match="comment() | processing-instruction()" mode="dtat:mergeTemplate">
    <xsl:param name="generated"/>
    <xsl:copy-of select="."/>
  </xsl:template>
  
  
  <!-- 
    This function takes a string containing an '{{'...'}}' expression and replaces that
    expression with the results of an xpath expression.  It recurses until no more
    replacements remain to be done, or until some arbitrary limit is reached.
  -->
  <func:function name="dtat:replaceExpressionXML">
    <xsl:param name="in"/> <!-- string containing a '{{'...'}}' expression -->
    <xsl:param name="xml"/> <!-- XML to run the xpath against -->
    <xsl:param name="depth" select="1"/> <!-- current recursion depth. -->
    <func:result>
      
      <xsl:choose>
        <xsl:when test="$depth &lt; 20">
          
          <!-- Extract the '{{' ... '}}' token from the rest of the string. -->
          <xsl:variable name="before" select="substring-before($in, '{{')"/>
          <xsl:variable name="remaining1" select="substring-after($in, '{{')"/>
          <xsl:variable name="middle" select="substring-before($remaining1, '}}')"/>
          <xsl:variable name="after" select="substring-after($remaining1, '}}')"/>
          
          <!-- The middle part of the '{{' ... '}}' token is an xpath to be applied against the supplied XML. -->
          <xsl:variable name="replacement">
            <xsl:for-each select="$xml">
              <xsl:value-of select="dyn:evaluate($middle)"/>
            </xsl:for-each>
          </xsl:variable>
          
          <!-- Paste the string back together, replacing the '{{' ... '}}' token with the results of evaluating the xpath. -->
          <xsl:variable name="result" select="concat($before, $replacement, $after)"/>
          <!-- <xsl:message>in=<xsl:value-of select="$in"/>, before='<xsl:value-of select="$before"/>', middle='<xsl:value-of select="$middle"/>', after='<xsl:value-of select="$after"/>', replacement='<xsl:value-of select="$replacement"/>', result='<xsl:value-of select="$result"/>'</xsl:message> -->
          
          <!-- Recurse or just return the final value. -->
          <xsl:choose>
            <xsl:when test="contains($result, '{{') and contains($result, '}}')">
              
              <xsl:value-of select="dtat:replaceExpressionXML($result, $xml, $depth + 1)"/>
              
            </xsl:when>
            <xsl:otherwise>
              
              <xsl:value-of select="$result"/>
              
            </xsl:otherwise>
          </xsl:choose>
        </xsl:when>
        <xsl:otherwise>
          
          <xsl:message terminate="yes">dtat:replaceExpressionXML exceeded max depth, probably due to a recursive substitution!</xsl:message>
          
        </xsl:otherwise>
      </xsl:choose>
    </func:result>
  </func:function>
  
  
  <!-- 
    This function replaces specific tokens in the nodeset (e.g. {{hostname}}).
  -->
  <func:function name="dtat:replaceSpecificTokens">
    <xsl:param name="xml"/>
    <xsl:param name="replacements"/>
    <func:result>
      <xsl:apply-templates select="$xml" mode="dtat:replaceSpecificTokens">
        <xsl:with-param name="replacements" select="$replacements"/>
      </xsl:apply-templates>
    </func:result>
  </func:function>
  
  
  <xsl:template match="/" mode="dtat:replaceSpecificTokens">
    <xsl:param name="replacements"/>
    <xsl:copy>
      <xsl:apply-templates select="node()" mode="dtat:replaceSpecificTokens">
        <xsl:with-param name="replacements" select="$replacements"/>
      </xsl:apply-templates>
    </xsl:copy>
  </xsl:template>
  
  <xsl:template match="*" mode="dtat:replaceSpecificTokens">
    <xsl:param name="replacements"/>
    <xsl:copy>
      <xsl:apply-templates select="@* | node()" mode="dtat:replaceSpecificTokens">
        <xsl:with-param name="replacements" select="$replacements"/>
      </xsl:apply-templates>
    </xsl:copy>
  </xsl:template>
  
  <xsl:template match="@*" mode="dtat:replaceSpecificTokens">
    <xsl:param name="replacements"/>
    <xsl:choose>
      <xsl:when test="contains(., '{{') and contains(., '}}')">
        
        <!-- This attribute may contain a {{...}} token -->
        <xsl:attribute name="{name()}"><xsl:value-of select="dtat:replaceExpression(., $replacements)"/></xsl:attribute>
        
      </xsl:when>
      <xsl:otherwise>
        
        <!-- This attribute doesn't contain a {{...}} token, so just copy it unchanged. -->
        <xsl:copy-of select="."/>
        
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>
  
  <xsl:template match="text()" mode="dtat:replaceSpecificTokens">
    <xsl:param name="replacements"/>
    <xsl:choose>
      <xsl:when test="contains(., '{{') and contains(., '}}')">
        
        <!-- This text may contain a {{...}} token -->
        <xsl:value-of select="dtat:replaceExpression(., $replacements)"/>
        
      </xsl:when>
      <xsl:otherwise>
        
        <!-- This text doesn't contain a {{...}} token, so just copy it unchanged. -->
        <xsl:value-of select="."/>
        
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>
  
  <xsl:template match="comment() | processing-instruction()" mode="dtat:replaceSpecificTokens">
    <xsl:param name="replacements"/>
    <xsl:copy-of select="."/>
  </xsl:template>
  
  
  <!-- 
    This function takes a string containing an '{{...}}' expression and replaces 
    the contents with one of the strings from the supplied set.  The $xml parameter
    has this form:
    <replacements>
      <replace this="..." withthis="..."/>
    </replace>
  -->
  <func:function name="dtat:replaceExpression">
    <xsl:param name="in"/> <!-- string containing a '{{'...'}}' expression -->
    <xsl:param name="replacements"/> <!-- XML listing the various replacement values -->
    <xsl:param name="depth" select="1"/> <!-- current recursion depth. -->
    <func:result>
      
      <xsl:choose>
        <xsl:when test="$depth &lt; 6">
          
          <!-- Extract the '{{' ... '}}' token from the rest of the string. -->
          <xsl:variable name="before" select="substring-before($in, '{{')"/>
          <xsl:variable name="remaining1" select="substring-after($in, '{{')"/>
          <xsl:variable name="middle" select="substring-before($remaining1, '}}')"/>
          <xsl:variable name="after" select="substring-after($remaining1, '}}')"/>

          <!-- Map the token to a replacement value. -->
          <xsl:variable name="replace" select="$replacements/replacements/replace[@this = $middle]"/>
          <xsl:choose>
            <xsl:when test="$replace">

              <!-- Paste the string back together, replacing the '{{' ... '}}' token with the results of evaluating the xpath. -->
              <xsl:variable name="result" select="concat($before, $replace/@withthis, $after)"/>
              <!-- <xsl:message>depth=<xsl:value-of select="$depth"/>, in=<xsl:value-of select="$in"/>, before='<xsl:value-of select="$before"/>', middle='<xsl:value-of select="$middle"/>', after='<xsl:value-of select="$after"/>', replace='<xsl:value-of select="$replace/@withthis"/>', result='<xsl:value-of select="$result"/>'</xsl:message> -->
              
              <!-- Recurse or just return the final value. -->
              <xsl:choose>
                <xsl:when test="contains($result, '{{') and contains($result, '}}')">
                  
                  <xsl:value-of select="dtat:replaceExpression($result, $replacements, $depth + 1)"/>
                  
                </xsl:when>
                <xsl:otherwise>
                  
                  <xsl:value-of select="$result"/> <!-- No reason to recurse, nothing left to replace. -->
                  
                </xsl:otherwise>
              </xsl:choose>
              
            </xsl:when>
            <xsl:otherwise>

              <!-- No replacement found for this expression, so leave it alone and bail. -->
              <xsl:value-of select="$in"/>
              
            </xsl:otherwise>
          </xsl:choose>
          
        </xsl:when>
        <xsl:otherwise>
          
          <xsl:message terminate="yes">dtat:replaceExpression exceeded max depth, probably due to a recursive substitution!</xsl:message>
          
        </xsl:otherwise>
      </xsl:choose>
    </func:result>
  </func:function>
  
</xsl:stylesheet>
