Paweł Ruta
Paweł Ruta

Reputation: 13

How can I duplicate node and change a value of their (original and duplicate) nested child nodes using XSL?

I have just started with XSL and I need help from someone experienced.

I have a node A in an XML with a lot of content. I want to duplicate this node identifying it by the value of a nested tag inside of it.

After creating node A' that is identical to node A I want to change the value of a tag that I identified node A with for both: node A and node A'. New values will be different for both nodes.

Here is my source XML:

<businessFields>
      <businessField>
            <businessFieldID>
               <namespace>
                  <name>foo</name>
               </namespace>
               <name>foobar</name>
            </businessFieldID>
            <datatype>java.lang.Boolean</datatype>
            <value>false</value>
      </businessField>
      <businessField>
         <businessFieldID>
            <namespace>
               <name>bar</name>
            </namespace>
            <name>foobar3</name>
         </businessFieldID>
         <datatype>java.lang.String</datatype>
         <value>No</value>
      </businessField>

    <!-- some more businessField nodes-->
</businessFields>

I need to duplicate the node with foobar value in /businessFields/businessField/businessFieldID/name tag and I want to change this value for both nodes.

My XSL looks like that:

<xsl:stylesheet version="1.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="//businessField[businessFieldID/name='foobar']">
        <xsl:copy-of select="."/>
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
   </xsl:template>
   
   
    <xsl:template match="//businessField[businessFieldID/name='foobar']/businessFieldID/name/text()">
        <xsl:value-of select="'foobar1'"/>
    </xsl:template>
  
</xsl:stylesheet>

So basically I am able to copy the node I am interested in and change the value of desired name tag but only for one of those nodes.

Here is desired output:

<?xml version="1.0" encoding="UTF-8"?>
<businessFields>
      <businessField>
            <businessFieldID>
               <namespace>
                  <name>foo</name>
               </namespace>
               <name>foobar1</name>
            </businessFieldID>
            <datatype>java.lang.Boolean</datatype>
            <value>false</value>
      </businessField>
   <businessField>
            <businessFieldID>
               <namespace>
                  <name>foo</name>
               </namespace>
               <name>foobar2</name>
            </businessFieldID>
            <datatype>java.lang.Boolean</datatype>
            <value>false</value>
      </businessField>
      <businessField>
         <businessFieldID>
            <namespace>
               <name>bar</name>
            </namespace>
            <name>foobar3</name>
         </businessFieldID>
         <datatype>java.lang.String</datatype>
         <value>No</value>
      </businessField>
</businessFields>

I tried using <xsl:for-each> to iterate all nodes that have foobar as a value and change the value depending on the position() but I messed up something and it just changed it for the same name for all of the nodes.

It's my first question on stackoverflow and English is not my native language so sorry if it's not clear. Please let me know what can I do to make my question better and thanks for help.

Upvotes: 1

Views: 339

Answers (2)

Martin Honnen
Martin Honnen

Reputation: 167716

Saxon in 2021 sounds like XSLT 3 to me so perhaps

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    exclude-result-prefixes="#all"
    version="3.0">
    
  <xsl:param name="name-to-check" as="xs:string">foobar</xsl:param>
  
  <xsl:param name="new-value" as="xs:string">foobar</xsl:param>

  <xsl:mode on-no-match="shallow-copy"/>

  <xsl:template match="businessField[businessFieldID/name = $name-to-check]">
      <xsl:copy>
          <xsl:apply-templates>
              <xsl:with-param name="suffix" tunnel="yes" select="1"/>
          </xsl:apply-templates>
      </xsl:copy>
      <xsl:copy>
          <xsl:apply-templates>
              <xsl:with-param name="suffix" tunnel="yes" select="2"/>
          </xsl:apply-templates>
      </xsl:copy>
  </xsl:template>
  
  <xsl:template match="businessField/businessFieldID/name[. = $name-to-check]/text()">
      <xsl:param name="suffix" tunnel="yes"/>
      <xsl:value-of select="$new-value || $suffix"/>
  </xsl:template>
  
</xsl:stylesheet>

suffices., or even

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    exclude-result-prefixes="#all"
    version="3.0">
    
  <xsl:param name="name-to-check" as="xs:string">foobar</xsl:param>
  
  <xsl:param name="new-value" as="xs:string">foobar</xsl:param>

  <xsl:mode on-no-match="shallow-copy"/>

  <xsl:template match="businessField[businessFieldID/name = $name-to-check]">
      <xsl:apply-templates select="., ." mode="change"/>
  </xsl:template>
  
  <xsl:template match="businessField" mode="change">
      <xsl:copy>
          <xsl:apply-templates mode="#default">
              <xsl:with-param name="suffix" tunnel="yes" select="position()"/>
          </xsl:apply-templates>
      </xsl:copy>
  </xsl:template>
  
  <xsl:template match="businessField/businessFieldID/name[. = $name-to-check]/text()">
      <xsl:param name="suffix" tunnel="yes"/>
      <xsl:value-of select="$new-value || $suffix"/>
  </xsl:template>
  
</xsl:stylesheet>

Upvotes: 0

michael.hor257k
michael.hor257k

Reputation: 117140

If I understand correctly (which is not at all certain), you want to do:

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="*"/>

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

<xsl:template match="businessField[businessFieldID/name='foobar']">
    <!-- original -->
    <xsl:copy>
        <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
    <!-- duplicate -->
    <xsl:copy>
        <xsl:apply-templates select="@*|node()" mode="duplicate"/>
    </xsl:copy>
</xsl:template>

<!-- identity transform for duplicates -->
<xsl:template match="@*|node()" mode="duplicate">
    <xsl:copy>
        <xsl:apply-templates select="@*|node()" mode="duplicate"/>
    </xsl:copy>
</xsl:template>
     
<!-- modify original -->
<xsl:template match="businessField[businessFieldID/name='foobar']/businessFieldID/name/text()">
    <xsl:text>foobar1</xsl:text>
</xsl:template>

<!-- modify duplicate -->
<xsl:template match="businessField[businessFieldID/name='foobar']/businessFieldID/name/text()" mode="duplicate">
    <xsl:text>foobar2</xsl:text>
</xsl:template>
  
</xsl:stylesheet>

Upvotes: 1

Related Questions