Search code examples
xslttransformapply-templates

How to prevent a value being overwritten?


I’m having trouble transforming some elements of a XML message. I need to swap some values, but after I copied the the value of the last node to the first, I’m unable to reach the original value of the first node.

I’ve been searching for hours and I tried several variations within my XSL but none of them gave a satisfying result. I’m not a very experienced XSL programmer, but I believe the solution can’t be that difficult. I simplified the original message to explain the problem and I hope one of you can help me out with this one. Let me explain by the following example:

Source:

<ProcessMessages>
  <Message>
    <SomeNode>Val</SomeNode>
    <SomeNode>Val</SomeNode>
    <Number>100000</Number>
    <ExternalRefID>EXX12345600000001</ExternalRefID>
    <SomeNode>Val</SomeNode>
    <SomeNode>Val</SomeNode>
  </Message>
  <RelatedMessages>
    <Message>     
      <SomeNode>Val</SomeNode>
      <Number>200000</Number>
      <ExternalRefID>EXX12345600000002</ExternalRefID>
      <SomeNode>Val</SomeNode>
    </Message>
    <Message>
      <SomeNode>Val</SomeNode>
      <Number>300000</Number>
      <ExternalRefID>EXX12345600000003</ExternalRefID>
      <SomeNode>Val</SomeNode>
    </Message>
  </RelatedMessages>
</ProcessMessages>

Desired transformation:

<ProcessMessages>
  <Message>
    <SomeNode>Val</SomeNode>
    <SomeNode>Val</SomeNode>
    <ExternalRefID>EXX12345600000003</ExternalRefID>
    <SomeNode>Val</SomeNode>
    <SomeNode>Val</SomeNode>
  </Message>
  <RelatedMessages>
    <Message>     
      <Number>200000</Number>
      <ExternalRefID>EXX12345600000002</ExternalRefID>
    </Message>
    <Message>
      <Number>100000</Number>
      <ExternalRefID>EXX12345600000001</ExternalRefID>
    </Message>
  </RelatedMessages>
</ProcessMessages>

My XSLT:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl"
>
    <xsl:output method="xml" indent="yes"/>

    <xsl:template match="@* | node()">
        <xsl:copy>
            <xsl:apply-templates select="@* | node()"/>
        </xsl:copy>
    </xsl:template>

<xsl:template match="//ProcessMessages/Message/ExternalRefID">
    <xsl:apply-templates select="//ProcessMessages/RelatedMessages/Message[last()]/ExternalRefID"/>
  </xsl:template>
  <xsl:template match="//ProcessMessages/Message/Number">
    <xsl:apply-templates select="//ProcessMessages/RelatedMessages/Message[last()]/Number"/>
  </xsl:template>

  <xsl:template match="//ProcessMessages/RelatedMessages/Message[1]">
    <xsl:element name="Message">
      <xsl:apply-templates select="ExternalRefID"/>
      <xsl:apply-templates select="Number"/>
    </xsl:element>
  </xsl:template>
  <xsl:template match="//ProcessMessages/RelatedMessages/Message[2]">
    <xsl:element name="Message">
      <xsl:apply-templates select="//ProcessMessages/Message/ExternalRefID"/>
      <xsl:apply-templates select="//ProcessMessages/Message/Number"/>
    </xsl:element>
  </xsl:template>  
</xsl:stylesheet>

Current result

<ProcessMessages>
  <Message>
    <SomeNode>Val</SomeNode>
    <SomeNode>Val</SomeNode>
    <Number>100000</Number>
    <ExternalRefID>EXX12345600000001</ExternalRefID>
    <SomeNode>Val</SomeNode>
    <SomeNode>Val</SomeNode>
  </Message>
  <RelatedMessages>
    <Message>
      <ExternalRefID>EXX12345600000002</ExternalRefID>
      <Number>200000</Number>
    </Message>
    <Message>
      <ExternalRefID>EXX12345600000001</ExternalRefID>
      <Number>100000</Number>
    </Message>
  </RelatedMessages>
</ProcessMessages>

Note that after copying the values of ProcessMessages/RelatedMessages/Message into ProcessMessages/Message I'm unable to reach the default value of ProcessMessages/Message because it seems to be overwritten.

Any ideas? I would be very grateful if someone can help me out!


Solution

  • No, you are not overwriting anything in the input. The output tree of an XSLT transformation is a separate object from the input, and the input is not mutable. Nothing in the input is ever overwritten or changed.

    And yes, you are right: your problem has a simple solution.

    The problem is that your attempt to copy the ExternalRefID and Number of /ProcessMessages/Message onto the end of the RelatedMessages element takes the form:

    <xsl:apply-templates 
      select="//ProcessMessages
               /Message/ExternalRefID"/>
    <xsl:apply-templates 
      select="//ProcessMessages
               /Message/Number"/>
    

    The stylesheet engine does as you ask, and seeks templates for those elements. It finds the second and third templates in your sample code, the ones which seek out and copy of values from the last Message element in Related Messages.

    It's not entirely clear to me exactly how to define the transformation you're trying to build, but if what you're trying to do is (1) move the final message of RelatedMessages to be the first child of ProcessMessages, and (2) move the first child of ProcessMessages to the end of RelatedMessages, then something like this would work:

    <xsl:template match="@* | node()">
      <xsl:copy>
        <xsl:apply-templates select="@* | node()"/>
      </xsl:copy>
    </xsl:template>
    
    <xsl:template match="ProcessMessages">
      <xsl:copy-of 
        select="RelatedMessages/Message[last()]"/>
      <xsl:apply-templates select="RelatedMessages"/>
    </xsl:template>
    
    <xsl:template 
      match="RelatedMessages/Message[last()]">
      <xsl:copy-of 
        select="../../self::ProcessMessages
                /Message[1]"/>
    </xsl:template>  
    

    If you want only to change the Number and ExternalRefID, then you'll need to modify this code appropriately.