Update nodes detail in XML using XSLT

149 Views Asked by At

I want to update some nodes in XML using XSLT. Like contact detail and Email. Currently I am using command like:

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

<xsl:template match="/Metadata/contact/node/Email">    

      <xsl:variable name="OName" select="/Metadata/contact/organisationName/CharacterString"/>
      <xsl:variable name="Email" select="/Metadata/contact/node/Email/CharacterString"/>

          <xsl:choose>
            <xsl:when test="contains($OName,'TestOrg')">
              <CharacterString>
                <xsl:value-of select="'[email protected]'"/>
              </CharacterString>
            </xsl:when>
            <xsl:otherwise>
              <CharacterString>
                <xsl:value-of select="$Email"/>
              </CharacterString>
            </xsl:otherwise>
          </xsl:choose>

  </xsl:template>

As Contact nodes are multiple and in each Contact node there are one organizaion name and email id. For ex of contact nodes are 3 and currently it fetch 3 values in $OName variable and $Email variable so nodes are not match. So how can I update only some nodes in xml using XSLT?

2

There are 2 best solutions below

0
On BEST ANSWER

The very first template you have is a so called "Identity template" and will loop through your complete source XML:

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

The second template you have will match on every /Metadata/contact/node/Email. I would write this template a little bit different. Instead of matching the absolute path, I think it is best to match the Email/CharacterString node and then perform your actions.

I would use a template like:

<xsl:template match="Email/CharacterString">
    <xsl:copy>
        <xsl:choose>
            <xsl:when test="contains(ancestor::contact/organisationName/CharacterString,'TestOrg')">
                <xsl:value-of select="'[email protected]'"/>
            </xsl:when>
            <xsl:otherwise>
                <xsl:value-of select="."/>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:copy>
</xsl:template>

Above template only matches the node you want to change (being Email/CharacterString). Once matches the xsl:copy copies the current selected node (CharacterString). The value will be filled depending on the value of ancestor::contact/organisationName/CharacterString. I am using the XPath Axe ancestor overhere. Which will be handy when changing the template.

If I would now change the template, for example only select the contact nodes that I would like to change, the second template can be written as:

<xsl:template match="contact[organisationRole/CharacterString = '1']/node/Email/CharacterString">
    <xsl:copy>
        <xsl:choose>
            <xsl:when test="contains(ancestor::contact/organisationName/CharacterString,'TestOrg')">
                <xsl:value-of select="'[email protected]'"/>
            </xsl:when>
            <xsl:otherwise>
                <xsl:value-of select="."/>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:copy>
</xsl:template>

Here I only apply the template to contact nodes where ([]) the organisationRole/CharacterString equals the value 1. Note that the body of the template did not change. Therefore you can see why the use of the XPath Axe is usefull.

Complete XSLT

<?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" version="1.0" encoding="UTF-8" indent="yes"/>

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

    <xsl:template match="contact[organisationRole/CharacterString = '1']/node/Email/CharacterString">
        <xsl:copy>
            <xsl:choose>
                <xsl:when test="contains(ancestor::contact/organisationName/CharacterString,'TestOrg')">
                    <xsl:value-of select="'[email protected]'"/>
                </xsl:when>
                <xsl:otherwise>
                    <xsl:value-of select="."/>
                </xsl:otherwise>
            </xsl:choose>
        </xsl:copy>
    </xsl:template>
</xsl:stylesheet>

Source XML

<?xml version="1.0" encoding="UTF-8"?>
<Metadata>
    <contact>
        <organisationRole>
            <CharacterString>1</CharacterString>
        </organisationRole>
        <organisationName>
            <CharacterString>TestOrg</CharacterString>
        </organisationName>
        <postalCode>
            <CharacterString>2020</CharacterString>
        </postalCode>
        <node>
            <Email>
                <CharacterString>[email protected]</CharacterString>
            </Email>
        </node>
    </contact>
    <contact>
        <organisationRole>
            <CharacterString>2</CharacterString>
        </organisationRole>
        <organisationName>
            <CharacterString>Example Org</CharacterString>
        </organisationName>
        <postalCode>
            <CharacterString>8080</CharacterString>
        </postalCode>
        <node>
            <Email>
                <CharacterString>[email protected]</CharacterString>
            </Email>
        </node>
    </contact>
    <contact>
        <organisationRole>
            <CharacterString>1</CharacterString>
        </organisationRole>
        <organisationName>
            <CharacterString>Real Org</CharacterString>
        </organisationName>
        <postalCode>
            <CharacterString>9050</CharacterString>
        </postalCode>
        <node>
            <Email>
                <CharacterString>[email protected]</CharacterString>
            </Email>
        </node>
    </contact>
</Metadata>

Produced output

<?xml version="1.0" encoding="UTF-8"?>
<Metadata>
    <contact>
        <organisationRole>
            <CharacterString>1</CharacterString>
        </organisationRole>
        <organisationName>
            <CharacterString>TestOrg</CharacterString>
        </organisationName>
        <postalCode>
            <CharacterString>2020</CharacterString>
        </postalCode>
        <node>
            <Email>
                <CharacterString>[email protected]</CharacterString>
            </Email>
        </node>
    </contact>
    <contact>
        <organisationRole>
            <CharacterString>2</CharacterString>
        </organisationRole>
        <organisationName>
            <CharacterString>Example Org</CharacterString>
        </organisationName>
        <postalCode>
            <CharacterString>8080</CharacterString>
        </postalCode>
        <node>
            <Email>
                <CharacterString>[email protected]</CharacterString>
            </Email>
        </node>
    </contact>
    <contact>
        <organisationRole>
            <CharacterString>1</CharacterString>
        </organisationRole>
        <organisationName>
            <CharacterString>Real Org</CharacterString>
        </organisationName>
        <postalCode>
            <CharacterString>9050</CharacterString>
        </postalCode>
        <node>
            <Email>
                <CharacterString>[email protected]</CharacterString>
            </Email>
        </node>
    </contact>
</Metadata>

EDIT

If you only want to change the Email/CharacterString from the contacts where the organisationName/CharacterString contains the text TestOrg the XSLT would look like this:

<?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" version="1.0" encoding="UTF-8" indent="yes"/>

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

    <xsl:template match="contact[contains(organisationName/CharacterString,'TestOrg')]/node/Email/CharacterString">
        <xsl:copy>
            <xsl:value-of select="'[email protected]'"/>
        </xsl:copy>
    </xsl:template>
</xsl:stylesheet> 
0
On

From your description it seems your variables selects need to be relative to the context node of your template:

<xsl:template match="/Metadata/contact/node/Email">    

  <xsl:variable name="OName" select="../../organisationName/CharacterString"/>
  <xsl:variable name="Email" select="CharacterString"/>

otherwise you will always start selecting from the root node for each match of the template and you will select all existing contact nodes every time.