How to rename multiple elements with different names in XSLT?

Viewed 74

I'm trying to rename elements with different element names using XSLT. My input XML is lengthy, so making it short below. I'm stuck in this part, can someone provide suggestions on this, provided link below:

<B> <A>
         <Year>9</Year>
         <Age>40</Age>
         <Value>32923.38</Value>
         <Value1>135223</Value1>
         <Value2>35</Value2>
         <Value3>114</Value3>
      </A>
      <A>
          <Year>19</Year>
         <Age>30</Age>
         <Value>42578.40</Value>
         <Value1>123</Value1>
         <Value2>70</Value2>
         <Value3>115</Value3>
      </A>
      </B>

Expected output:

<c>
      <A>
         <day>9</day>
         <type>40</type>
         <key>32923.38</key>
         <level>135223</level>
         <pay>35</pay>
         <terms>114</terms>
      </A>
      <A>
          <day>19</day>
         <type>30</type>
         <key>42578.40</key>
         <level>123</level>
         <pay>70</pay>
         <terms>115</terms>
      </A>    
      </c>

XSLT templates I tried:

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

Here is the actual XSLT which is doing work of split based on particular element but stuck for above scenario, not knowing how to apply in code and make it work.

I tried multiple ways but none of them working when applied to my code, here is the link of the code below: https://xsltfiddle.liberty-development.net/gVAkJ4Q/4

Updated the original XSLT, since actual source code is very lengthy added link above containing the source code and XSLT.

2 Answers

Here's a simple example you can use as your starting point:

XML

<B>
    <A>
        <Year>9</Year>
        <Age>40</Age>
        <Value>32923.38</Value>
    </A>
    <A>
        <Year>19</Year>
        <Age>30</Age>
        <Value>42578.40</Value>
    </A>
</B>

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="/B">
    <C>
        <xsl:apply-templates/>
    </C>
</xsl:template>

<xsl:template match="Year">
    <day>
        <xsl:apply-templates/>
    </day>
</xsl:template>

<xsl:template match="Age">
    <type>
        <xsl:apply-templates/>
    </type>
</xsl:template>

<xsl:template match="Value">
    <key>
        <xsl:apply-templates/>
    </key>
</xsl:template>

<!-- and so on ... -->

</xsl:stylesheet>

Result

<?xml version="1.0" encoding="UTF-8"?>
<C>
   <A>
      <day>9</day>
      <type>40</type>
      <key>32923.38</key>
   </A>
   <A>
      <day>19</day>
      <type>30</type>
      <key>42578.40</key>
   </A>
</C>

P.S. If you have a great many elements to rename, you might prefer to reduce the code to just:

<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="*">
    <xsl:variable name="new-name">
        <xsl:choose>
            <xsl:when test="name()='B'">C</xsl:when>
            <xsl:when test="name()='Year'">day</xsl:when>
            <xsl:when test="name()='Age'">type</xsl:when>
            <xsl:when test="name()='Value'">key</xsl:when>
            <!-- and so on ... -->
            <xsl:otherwise>
                <xsl:value-of select="name()"/>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:variable>
    <xsl:element name="{$new-name}">
        <xsl:apply-templates/>
    </xsl:element>
</xsl:template>

</xsl:stylesheet>

Here is a short and generic, pure XSLT 1.0 solution:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>
 <xsl:param name="pReps"
      select="'|Year+day|Age+type|Value+key|Value1+level|Value2+pay|Value3+terms|'"/>

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

  <xsl:template match="A/*">
    <xsl:variable name="vName">
        <xsl:choose>
            <xsl:when test="contains($pReps, concat('|', name(), '+'))">
              <xsl:value-of select=
              "substring-before(substring-after($pReps, concat('|', name(), '+')), '|')"/>
            </xsl:when>
            <xsl:otherwise><xsl:value-of select="name()"/></xsl:otherwise>
        </xsl:choose>
    </xsl:variable>
    <xsl:element name="{$vName}">
      <xsl:copy-of select="namespace::*"/>
      <xsl:apply-templates select="node()|@*"/>
    </xsl:element>
  </xsl:template>
</xsl:stylesheet>

When this transformation is applied on the provided XML document:

<B>
    <A>
        <Year>9</Year>
        <Age>40</Age>
        <Value>32923.38</Value>
        <Value1>135223</Value1>
        <Value2>35</Value2>
        <Value3>114</Value3>
    </A>
    <A>
        <Year>19</Year>
        <Age>30</Age>
        <Value>42578.40</Value>
        <Value1>123</Value1>
        <Value2>70</Value2>
        <Value3>115</Value3>
    </A>
</B>

the wanted, correct result is produced:

<B>
   <A>
      <day>9</day>
      <type>40</type>
      <key>32923.38</key>
      <level>135223</level>
      <pay>35</pay>
      <terms>114</terms>
   </A>
   <A>
      <day>19</day>
      <type>30</type>
      <key>42578.40</key>
      <level>123</level>
      <pay>70</pay>
      <terms>115</terms>
   </A>
</B>

Do note:

  1. This is a generic solution having only two simple templates. The element names and their replacements are not hardcoded in the transformation (hey, no "<!-- and so on ... -->" comments) , but are specified as the value of a global parameter $pReps, which can be passed externally to the transformation and can be different every time the same, unchanged transformation is executed. No need to edit the transformation when you have a new problem, with different (oldName, newName) set of values

  2. The "identity template" matches all nodes, whose names are not subject to renaming and shallow-copies them "as-is"

  3. Another template matches any element that is a child of A and whose name may be found in the externally passed parameter $pReps. This template, if it finds the current element name in the replacements in the parameter, locates the suggested replacement immediately after the delimited original name, extracts it and uses it in an <xsl:element> instruction to create an element that has the specified corresponding new name. Otherwise the original element name is used without being replaced.

  4. The so created newly named (or not) element preserves all of its namespace nodes as they are also copied. Thus this solution works correctly even if some/all elements are in a namespace including a default (non-empty) namespace.

  5. The format to represent the old element names and their suggested replacements is:

"|{oldName}+{newName}|"

Related