Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

XSLT - Problems changing the node context in conjunction with the identity pattern

Tags:

xslt

I have a given source XML document with a structure like this:

<?xml version="1.0" encoding="UTF-8"?>
<sample>
  <definition>
    <variable>
      <name>object01_ID_138368350261919620</name>
      <value>NUL</value>
    </variable>  
    <variable>
      <name>param01_ID_138368350261919621</name>
      <value>10</value>
    </variable>
    <variable>
      <name>param02_ID_138368350261919622</name>
      <value>100</value>
    </variable>
  </definition>
  <override>
    <assignment>
      <name>object01_ID_138368350261919620</name>
      <path>module01/object01</path>
    </assignment>
    <assignment>
      <name>param01_ID_138368350261919621</name>
      <path>module01/object01/param01</path>
    </assignment>
    <assignment>
      <name>param02_ID_138368350261919622</name>
      <path>module01/object01/param02</path>
    </assignment>
  </override>
</sample>

The characteristic of the source XML is:
Each <assignment> element within the <override> element corresponds to exactly one <variable> element within the <definition> element. This 1:1 relationship is established by the content of their <name> element.

The requirements of the transformation and the target XML are:
Depending on the pattern of the content of the <path> elements, within the <assignment> elements in an <override> element, I like to add a new <assignment> element. Important to note is, that an <assignment> element is the leading information. Therefore always at first, a new <assignment> element with its <path> and <name> content has to be created and in conjunction with that a corresponding new <variable> element with the same <name> content and a specific <value> content has to be created and inserted at last position in the <definition> element. For example, for adding param03, the right result should look like:

<?xml version="1.0" encoding="UTF-8"?>
<sample>
  <definition>
    <variable>
      <name>param00_ID_138368350261919620</name>
      <value>NUL</value>
    </variable>
    <variable>
      <name>param01_ID_138368350261919621</name>
      <value>10</value>
    </variable>
    <variable>
      <name>param02_ID_138368350261919622</name>
      <value>100</value>
    </variable>
    <variable>
      <name>Param03_ID_138368350261919623</name>
      <value>1000</value>
    </variable>
  </definition>
  <override>
    <assignment>
      <name>param00_ID_138368350261919620</name>
      <path>module01/object01</path>
    </assignment>
    <assignment>
      <name>param01_ID_138368350261919621</name>
      <path>module01/object01/param01</path>
    </assignment>
    <assignment>
      <name>param02_ID_138368350261919622</name>
      <path>module01/object01/param02</path>
    </assignment>
    <assignment>
      <name>Param03_ID_138368350261919623</name>
      <xpath>module01/object01/param03</xpath>
    </assignment>
  </override>
</sample>

My XSL 2.0 stylesheet for transformation:
For identity transformation, I have choosen to use the fine-grained control identity rule, recommended by [Dimitre Novatchev]. Applying the processing param03 template, I create a new <assignment> element with its specific <path> and <name> content. Within that template, I like to change the node context by using for-each, to the <definition> element and add at last position a new <variable> element with the corresponding <name> content and a specific <value> content. This stylesheet has been tested with Saxon HE 9.5.

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" 
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
  xmlns:fo="http://www.w3.org/1999/XSL/Format" 
  xmlns:xs="http://www.w3.org/2001/XMLSchema" 
  xmlns:fn="http://www.w3.org/2005/xpath-functions" 
  exclude-result-prefixes="fo xs fn">
  <!--
  global declarations ==========================================================
  -->
  <xsl:output indent="yes"/>
  <xsl:strip-space elements="*"/>
  <!-- randomid here is just a fake for sake of simplification -->
  <xsl:variable name="randomid" select="138368350261919623"/>
  <!--
  template - identity ==========================================================
  -->
  <xsl:template match="node()|@*" name="identity">
    <xsl:copy>
      <xsl:apply-templates select="@*|node()[1]"/>
    </xsl:copy>
    <xsl:apply-templates select="following-sibling::node()[1]"/>
  </xsl:template>
  <!--
  template - variable assignment ===============================================
  -->
  <xsl:template name="variable_assignment">
    <xsl:param name="value_node_name"/>
    <xsl:param name="value_node_path"/>
    <xsl:message select="'processing: variable assignment'"/>
    <xsl:message select="concat('applying name: ', $value_node_name)"/>
    <xsl:message select="concat('applying path: ', $value_node_path)"/>
    <xsl:call-template name="identity"/>
    <assignment>
      <name>
        <xsl:value-of select="$value_node_name"/>
      </name>
      <xpath>
        <xsl:value-of select="$value_node_path"/>
      </xpath>
    </assignment>
  </xsl:template>
  <!--
    template - processing param03 =============================================
  -->
  <xsl:template match="/sample/override[not(assignment
              /path[matches(text(), '.*/object01/param03$')])]
              /assignment[path[matches(text(), '.*/object01$')]]">
    <!-- setting params -->
    <xsl:param name="value_node_name_target">
      <xsl:value-of select="concat('Param03_ID', '_', $randomid)"/>
    </xsl:param>
    <xsl:param name="value_node_path_target">
      <xsl:value-of select="concat(./path, '/param03')"/>
    </xsl:param>
    <xsl:param name="value_node_value_target" select="'1000'"/>
    <!-- processing variable assignment -->
    <xsl:call-template name="variable_assignment">
      <xsl:with-param name="value_node_name" select="$value_node_name_target"/>
      <xsl:with-param name="value_node_path" select="$value_node_path_target"/>
    </xsl:call-template>
    <!-- processing variable definition -->
    <xsl:for-each select="/sample/definition/*[position()=last()]">
        <xsl:message select="'processing: variable definition'"/>
      <xsl:message select="concat('Here we are: ', .)"/>
      <xsl:message select="concat('applying name: ', $value_node_name_target)"/>
      <xsl:message select="concat('applying value: ', $value_node_value_target)"/>
      <xsl:call-template name="identity"/>
      <variable>
        <name>
          <xsl:value-of select="$value_node_name_target"/>
        </name>
        <value>
          <xsl:value-of select="$value_node_value_target"/>
        </value>
      </variable>
    </xsl:for-each>
  </xsl:template>
</xsl:stylesheet>

The resulting wrong XML is:

<?xml version="1.0" encoding="UTF-8"?>
<sample>
  <definition>
    <variable>
      <name>object01_ID_138368350261919620</name>
      <value>NUL</value>
    </variable>
    <variable>
      <name>param01_ID_138368350261919621</name>
      <value>10</value>
    </variable>
    <variable>
      <name>param02_ID_138368350261919622</name>
      <value>100</value>
    </variable>
  </definition>
  <override>
    <assignment>
      <name>object01_ID_138368350261919620</name>
      <path>module01/object01</path>
    </assignment>
    <assignment>
      <name>param01_ID_138368350261919621</name>
      <path>module01/object01/param01</path>
    </assignment>
    <assignment>
      <name>param02_ID_138368350261919622</name>
      <path>module01/object01/param02</path>
    </assignment>
    <assignment>
      <name>Param03_ID_138368350261919623</name>
      <xpath>module01/object01/param03</xpath>
    </assignment>
    <variable>
      <name>param02_ID_138368350261919622</name>
      <value>100</value>
    </variable>
    <variable>
      <name>Param03_ID_138368350261919623</name>
      <value>1000</value>
    </variable>
  </override>
</sample>

The problems I got are:

  1. The node context becomes not changed. The new <variable> element becomes added at last position into the <override> element, instead into the <definition> element, as wanted.
  2. Additionally the last <variable> element from <definition> element becomes copied into the <override> element. That is not what I want.

Help needed!
I really would appreciate if somebody could advice me, in which way I would have to adapt my XSLT in order to get rid of the problems and the right behavior as delineated above.

Many thanks.

The XSLT 2.0 proposed by you, adapted by me:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:fn="http://www.w3.org/2005/xpath-functions" exclude-result-prefixes="fo xs fn">
  <!--
  global declarations ==========================================================
  -->
  <xsl:output indent="yes"/>
  <xsl:strip-space elements="*"/>
  <!-- baserandom here is just a fake for sake of simplification -->
  <xsl:param name="baserandom" select="138368350261919623"/>
  <!--MOVED PARAMS FROM ORIGINAL TEMPLATE HERE SO THEY CAN BE USED BY MULTIPLE TEMPLATES -->
  <!--xsl:param name="value_node_path"-->
  <!--I LEFT THE PREDICATE BECAUSE IT APPEARS THAT THERE COULD BE MORE THAN ONE override ELEMENT.-->
  <!--xsl:value-of select="concat(/sample/override[not(assignment/path[matches(text(), '.*/object01/param03$')])]
            /assignment[1]/path, '/param03')"/>
  </xsl:param>
  <xsl:param name="value_node_value" select="'1000'"/-->
  <!--
  template - identity ==========================================================
  -->
  <xsl:template match="node()|@*" name="identity">
    <xsl:copy>
      <xsl:apply-templates select="@*|node()[1]"/>
    </xsl:copy>
    <xsl:apply-templates select="following-sibling::node()[1]"/>
  </xsl:template>
  <!--
  template - definition ========================================================
  -->
  <!--REPLACES THE xsl:for-each THAT PROCESSES THE VARIABLE DEFINITION-->
  <xsl:template match="definition/*[last()]">
    <xsl:param name="value_node_name"/>
    <xsl:param name="value_node_value"/>
    <xsl:call-template name="identity"/>
    <xsl:if test="$value_node_name">
      <xsl:message select="'processing: variable definition'"/>
      <xsl:message select="concat('Here we are: ', .)"/>
      <xsl:message select="concat('applying name: ', $value_node_name)"/>
      <xsl:message select="concat('applying value: ', $value_node_value)"/>
      <variable>
        <name>
          <xsl:value-of select="$value_node_name"/>
        </name>
        <value>
          <xsl:value-of select="$value_node_value"/>
        </value>
      </variable>
    </xsl:if>
  </xsl:template>
  <!--
    template - processing param03 =============================================
  -->
  <xsl:template match="/sample/override[not(assignment/path[matches(text(), '.*/object01/param03$')])]
        /assignment[path[matches(text(), '.*/object01$')]]">
    <!-- name -->
    <xsl:param name="value_node_name">
      <xsl:value-of select="concat('param03_ID', '_', $baserandom)"/>
    </xsl:param>
    <!-- path -->
    <xsl:param name="value_node_path">
      <xsl:value-of select="concat(./path, '/param03')"/>
    </xsl:param>
    <!-- value -->
    <xsl:param name="value_node_value" select="'1000'"/>
    <!-- processing definition -->
    <xsl:apply-templates select="/sample/definition/*[last()]">
      <xsl:with-param name="value_node_name" select="$value_node_name"/>
      <xsl:with-param name="value_node_value" select="$value_node_value"/>
    </xsl:apply-templates>
    <!-- processing assignment -->
    <xsl:message select="'processing: variable assignment'"/>
    <xsl:message select="concat('applying name: ', $value_node_name)"/>
    <xsl:message select="concat('applying path: ', $value_node_path)"/>
    <xsl:call-template name="identity"/>
    <assignment>
      <name>
        <xsl:value-of select="$value_node_name"/>
      </name>
      <path>
        <xsl:value-of select="$value_node_path"/>
      </path>
    </assignment>
  </xsl:template>
</xsl:stylesheet>

The resulting XML (still wrong):

<?xml version="1.0" encoding="UTF-8"?>
<sample>
  <definition>
    <variable>
      <name>object01_ID_138368350261919620</name>
      <value>NUL</value>
    </variable>
    <variable>
      <name>param01_ID_138368350261919621</name>
      <value>10</value>
    </variable>
    <variable>
      <name>param02_ID_138368350261919622</name>
      <value>100</value>
    </variable>
  </definition>
  <override>
    <variable>
      <name>param02_ID_138368350261919622</name>
      <value>100</value>
    </variable>
    <variable>
      <name>param03_ID_138368350261919623</name>
      <value>1000</value>
    </variable>
    <assignment>
      <name>object01_ID_138368350261919620</name>
      <path>module01/object01</path>
    </assignment>
    <assignment>
      <name>param01_ID_138368350261919621</name>
      <path>module01/object01/param01</path>
    </assignment>
    <assignment>
      <name>param02_ID_138368350261919622</name>
      <path>module01/object01/param02</path>
    </assignment>
    <assignment>
      <name>param03_ID_138368350261919623</name>
      <path>module01/object01/param03</path>
    </assignment>
  </override>
</sample>
like image 899
Stephan Rudolph Avatar asked Nov 21 '13 14:11

Stephan Rudolph


People also ask

What are the two 2 inputs of an XSLT processor?

The XSLT processor operates on two inputs: the XML document to transform, and the XSLT stylesheet that is used to apply transformations on the XML. Each of these two can actually be multiple inputs.

Can we reassign a value to variable in XSLT?

Variables in XSLT are not really variables, as their values cannot be changed. They resemble constants from conventional programming languages. The only way in which a variable can be changed is by declaring it inside a for-each loop, in which case its value will be updated for every iteration.

Is XSLT still relevant?

XSLT has become the language of choice for a very wide range of XML applications. It is of course still used to produce XSL-FO documents for printing, but it is also used to integrate back-end software for Web sites.

Which activity allows you to transform an input XML document into the output specified by the given XSLT file?

Using XSLTRANSFORM You can use the XSLTRANSFORM function to apply XSLT stylesheets to XML data. If you supply the function with the name of an XML document and an XSLT stylesheet, the function will apply the stylesheet to the document and return the result.


1 Answers

The issue you are having is that xsl:for-each isn't changing the output context. It's only changing the iteration context.

You are still outputting in the context of assignment (template match) when you're iterating over xsl:for-each select="/sample/definition/*[position()=last()].

You'll need to output the new variable from the context of definition.

I've modified your XSLT to produce what you want. It might not be the final solution, but should get you closer. I added comments (all uppercase) to try to explain what I changed. Please let me know if there are questions.

Modified XSLT 2.0

<xsl:stylesheet version="2.0" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
    xmlns:fo="http://www.w3.org/1999/XSL/Format" 
    xmlns:xs="http://www.w3.org/2001/XMLSchema" 
    xmlns:fn="http://www.w3.org/2005/xpath-functions" 
    exclude-result-prefixes="fo xs fn">

    <!--
  global declarations ==========================================================
  -->
    <xsl:output indent="yes"/>
    <xsl:strip-space elements="*"/>

    <!-- randomid here is just a fake for sake of simplification -->
    <xsl:param name="randomid" select="138368350261919623"/>
    <!--MOVED PARAMS FROM ORIGINAL TEMPLATE HERE SO THEY CAN BE USED BY MULTIPLE TEMPLATES -->
    <xsl:param name="value_node_name_target">
        <xsl:value-of select="concat('Param03_ID', '_', $randomid)"/>
    </xsl:param>
    <xsl:param name="value_node_path_target">
        <!--I LEFT THE PREDICATE BECAUSE IT APPEARS THAT THERE COULD BE MORE THAN ONE override ELEMENT.-->
        <xsl:value-of select="concat(/sample/override[not(assignment/path[matches(text(), '.*/object01/param03$')])]
            /assignment[1]/path, '/param03')"/>
    </xsl:param>
    <xsl:param name="value_node_value_target" select="'1000'"/>

    <!--
  template - identity ==========================================================
  -->
    <xsl:template match="node()|@*" name="identity">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()[1]"/>
        </xsl:copy>
        <xsl:apply-templates select="following-sibling::node()[1]"/>
    </xsl:template>

    <!--
  template - variable assignment ===============================================
  -->
    <!--MOVED CODE FROM THIS TEMPLATE INTO THE assignment TEMPLATE-->

    <!--REPLACES THE xsl:for-each THAT PROCESSES THE VARIABLE DEFINITION-->
    <xsl:template match="definition[../override[not(assignment/path[matches(text(), '.*/object01/param03$')])]
        /assignment[path[matches(text(), '.*/object01$')]]]/*[last()]">
        <xsl:message select="'processing: variable definition'"/>
        <xsl:message select="concat('Here we are: ', .)"/>
        <xsl:message select="concat('applying name: ', $value_node_name_target)"/>
        <xsl:message select="concat('applying value: ', $value_node_value_target)"/>
        <xsl:call-template name="identity"/>
        <variable>
            <name>
                <xsl:value-of select="$value_node_name_target"/>
            </name>
            <value>
                <xsl:value-of select="$value_node_value_target"/>
            </value>
        </variable>
    </xsl:template>

    <!--
    template - processing param03 =============================================
  -->
    <xsl:template match="/sample/override[not(assignment/path[matches(text(), '.*/object01/param03$')])]
        /assignment[path[matches(text(), '.*/object01$')]]">
        <!-- setting params -->
        <!--MOVED TEMPLATE PARAMS TO GLOBAL PARAMS-->
        <!-- processing variable assignment -->
        <!--REPLACED UNNECESSARY xsl:call-template WITH ACTUAL CODE-->
        <xsl:message select="'processing: variable assignment'"/>
        <xsl:message select="concat('applying name: ', $value_node_name_target)"/>
        <xsl:message select="concat('applying path: ', $value_node_path_target)"/>
        <xsl:call-template name="identity"/>
        <assignment>
            <name>
                <xsl:value-of select="$value_node_name_target"/>
            </name>
            <!--CHANGED FROM xpath TO path (APPEARED TO BE A TYPO)-->
            <path>
                <xsl:value-of select="$value_node_path_target"/>
            </path>
        </assignment>       <!-- processing variable definition -->
        <!--THIS IS NOW DONE BY A SEPARATE MATCHING TEMPLATE-->
    </xsl:template>
</xsl:stylesheet>

Output

<sample>
   <definition>
      <variable>
         <name>object01_ID_138368350261919620</name>
         <value>NUL</value>
      </variable>
      <variable>
         <name>param01_ID_138368350261919621</name>
         <value>10</value>
      </variable>
      <variable>
         <name>param02_ID_138368350261919622</name>
         <value>100</value>
      </variable>
      <variable>
         <name>Param03_ID_138368350261919623</name>
         <value>1000</value>
      </variable>
   </definition>
   <override>
      <assignment>
         <name>object01_ID_138368350261919620</name>
         <path>module01/object01</path>
      </assignment>
      <assignment>
         <name>param01_ID_138368350261919621</name>
         <path>module01/object01/param01</path>
      </assignment>
      <assignment>
         <name>param02_ID_138368350261919622</name>
         <path>module01/object01/param02</path>
      </assignment>
      <assignment>
         <name>Param03_ID_138368350261919623</name>
         <path>module01/object01/param03</path>
      </assignment>
   </override>
</sample>
like image 87
Daniel Haley Avatar answered Oct 14 '22 04:10

Daniel Haley