1

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.

Lorentz
  • 181
  • 1
  • 3
  • 13

2 Answers2

3

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>
michael.hor257k
  • 113,275
  • 6
  • 33
  • 51
2

You are not far off, but there are two main problems:

  • you use xsl:copy inside the second, literal product element, which results in an extra product element in the output
  • you need a way to find all child elements of a product element, output them again and prepend "NEW_" to their text content.

Are you sure that the version attribute should be set to "2.0"? Also, I am not sure what the point of this exercise is...

XSLT Stylesheet

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

  <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 id="{concat('NEW_',@id)}">
      <xsl:for-each select="*">
          <xsl:copy>
              <xsl:value-of select="concat('NEW_',.)"/>
          </xsl:copy>
      </xsl:for-each>
    </product>
  </xsl:template>

</xsl:stylesheet>

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>
Mathias Müller
  • 22,203
  • 13
  • 58
  • 75
  • Thank you for the answer. I learned that using templates was the way to go when transforming XML - I see use of the for-each syntax. – Lorentz Apr 14 '15 at 18:28
  • @Lorentz Both `for-each` and templates have their place - the important thing is that you do not use one when the other would be better. And it's true that `xsl:for-each` is a heavily-misused construct, mostly by beginners. Please do not forget to [accept this answer](http://stackoverflow.com/help/someone-answers) if it solved your problem. Thanks! – Mathias Müller Apr 14 '15 at 18:30
  • Duly noted on the use of 'for-each' - I will keep your comments in mind as I move forward. This exercise is part of a bigger project - I just needed to get to the heart of duplicating nodes with new data. Will this transform allow for recursion to deeper levels in the XML input file? Thanks in advance. – Lorentz Apr 14 '15 at 18:36
  • @Lorentz No, currently, it will not. In general, people on Stackoverflow can only work with the XML shown to them. You would need to _show_ those deep structures and _explain_ in what way you would like your code to behave recursively. – Mathias Müller Apr 14 '15 at 18:38