Search code examples
xmlxsltxslt-2.0node-set

XSLT Duplicating node & all child nodes & attributes with new values


Good day .... I am trying to duplicate nodes with updated/new element text and/or attribute values.

My input XML file:

<?xml version="1.0"?>
<products author="Jesper">
  <product id="p1">
    <name>Delta</name>
    <price>800</price>
    <stock>4</stock>
    <country>Denmark</country>
  </product>
</products>

The desired XML output:

<?xml version="1.0" encoding="utf-8"?>
<products author="Jesper">
  <product id="p1">
    <name>Delta</name>
    <price>800</price>
    <stock>4</stock>
    <country>Denmark</country>
  </product>
  <product id="NEW_p1">
    <name>NEW_Delta</name>
    <price>NEW_800</price>
    <stock>NEW_4</stock>
    <country>NEW_Denmark</country>
  </product>
</products>

After some time, the XSLT that I currently have is as follows:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="2.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 ="product">
    <xsl:copy>
      <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
    <product>
      <xsl:attribute name ="id">
        <xsl:value-of select ="concat('NEW_',@id"/>
      </xsl:attribute>
      <xsl:copy>
        <xsl:apply-templates select="node()"/>
      </xsl:copy>
    </product>
  </xsl:template>

However, using the above transform, I get the following XML output:

<?xml version="1.0" encoding="utf-8"?>
<products author="Jesper">
  <product id="p1">
    <name>Delta</name>
    <price>800</price>
    <stock>4</stock>
    <country>Denmark</country>
  </product>
  <product id="NEW_p1"><product>
    <name>Delta</name>
    <price>800</price>
    <stock>4</stock>
    <country>Denmark</country>
  </product></product>
</products>

As you can see, the product element is added whilst I declared a new product element with new @id value. Since I use to process child nodes I believe this processes the product element again.

Also, I need help in updating the child node's values (prepending 'NEW_' to each value). Searching the vast questions on this site, I believe I need a template like so:

<xsl:template match="*">
  <xsl:element name ="{local-name()}">
    <!--for all attributes-->
    <xsl:copy-of select ="@*"/>
    <xsl:value-of select = "."/>
  </xsl:element>
</xsl:template>

Thank you in advance for any suggestions/ideas with my issue.

UPDATED Thank you @Mathias for your answer to my initial question. The answer provided brought one more question involving recursion to deeper levels of an XML structure.

Input XML file:

    <products author="Jesper">
      <product id="p1">
        <name>Delta
          <innerName>MiddleDelta
            <baseName>FinalDelta</baseName>
          </innerName>
        </name>
        <price>800</price>
        <stock>4</stock>
        <country>Denmark
          <city>Copenhagen</city>
        </country>
      </product>
    </products>

And the Updated desire output file is as such:

<?xml version="1.0" encoding="utf-8"?>
<products author="Jesper">
  <product id="p1">
    <name>Delta
      <innerName>MiddleDelta
        <baseName>FinalDelta</baseName>
      </innerName>
    </name>
    <price>800</price>
    <stock>4</stock>
    <country>Denmark
      <city>Copenhagen</city>
    </country>
  </product>
  <product id="NEW_p1">
    <name>NEW_Delta
      <innerName>NEW_MiddleDelta
        <baseName>NEW_FinalDelta</baseName>
      </innerName>
    </name>
    <price>NEW_800</price>
    <stock>NEW_4</stock>
    <country>NEW_Denmark
      <city>NEW_Copenhagen</city>
    </country>
  </product>
</products>

I can only guess that using templates would work as seeing each node has varying level child nodes. Thank you in advance for ideas/suggestions in this.


Solution

  • This is in response to your updated question (which IMHO should have been asked as a new question):

    XSLT 1.0

    <xsl:stylesheet version="1.0" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
    <xsl:strip-space elements="*"/>
    
    <xsl:template match="/products">
        <xsl:copy>
            <xsl:copy-of select="product"/>
            <xsl:apply-templates select="product"/>
        </xsl:copy>
    </xsl:template>
    
    <!-- modified identity transform -->
    <xsl:template match="*">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>
    
    <xsl:template match="@*">
        <xsl:attribute name="{name()}">
            <xsl:value-of select="concat('NEW_', .)"/>
        </xsl:attribute>
    </xsl:template>
    
    <xsl:template match="text()">
        <xsl:value-of select="concat('NEW_', .)"/>
    </xsl:template>
    
    </xsl:stylesheet>