Prajeesh Nair
Prajeesh Nair

Reputation: 41

changing a Node name which has values with Xslt

Sorry that i have kept very less information.

I am getting a XMl response which have nodes with same Node Name. I need to rename all the Node name if its repeated.

The XML response i an getting is like shown below.

<Envelope>-
  <Body>-
    <getItemResponse>-
      <status>
        <statusCode>Success</statusCode>
      </status>-
      <item>-
        <item>
           <originOfData>SME</originOfData>-
           <itemNumbers>
              <shortNumber>115632</shortNumber>
              <tssArticleNumber>PT0401450-T46N</tssArticleNumber>-
              <itemMainGroup>
                <code>P</code>
                <description>Piston Seals</description>
              </itemMainGroup>-
              <itemSubGroup>
                <code>PT</code>
                <description>Turcon Glyd Ring® T</description>
                </itemSubGroup>-
              <relatedItems>
                 <alternateItemsNumber>ORAR00428-N7083</alternateItemsNumber>
              </relatedItems>
           </itemNumbers>-
           <itemDrawing>
             <drawingNumber> </drawingNumber>
           </itemDrawing>
           <description1>PT0401450-T46N.</description1>
           <description2>Turcon Glyd Ring® T</description2>-
           <dimensions>
              <insideDiameter>124</insideDiameter>
              <outsideDiameter>145</outsideDiameter>
              <width>8.1</width>
           </dimensions>-
           <weight>-
             <uom>
                <code>KG</code>
                <name>Kilograms *</name>
             </uom>
           </weight>-
           <primaryUOM>
              <code>PC</code>
              <name>Pieces *</name>
           </primaryUOM>-
           <material>
             <materialCode>T46N</materialCode>
           </material>
        </item>
      </item>
    </getItemResponse>
  </Body>
</Envelope>

You can see the Nodes Item, code, Description are repeated more than once. I need to change the Node name if its repeated by adding a number to it. Like Item node to Item1. So the Nodes which are repeated will be changed to Item, Item1, code, code1, code2, code3. But the description need to change to description-1, since the description1 is already there. For this purpose i have used XSLT which transform the node to the required name. I am calling this function using three variables Root, OldNode and NewNode.

The function is called repeatedly with desired node name. The function looks like below.

RemoveGetItemNamespace(Source : DotNet "System.Xml.XmlDocument";VAR Destination : DotNet "System.Xml.XmlDocument")
XslTransform :=  XslTransform.XslTransform;
XMLStyleSheet := XMLStyleSheet.XmlDocument;
XMLStyleSheet.InnerXml(

'<?xml version="1.0" encoding="UTF-8"?>'+
'<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format" xmlns:msxml="urn:schemas-microsoft-com:xslt">'+
'<xsl:output method="xml" omit-xml-declaration="yes" version="1.0" encoding="UTF-8" indent="yes"/>' +
'<xsl:template match="@*|node()">'+
'<xsl:copy>'+
'<xsl:apply-templates select="@*|node()"/>'+
'</xsl:copy>'+
'</xsl:template>'+
'<xsl:template match="'+Root+'">'+
'<xsl:variable name="oldNode" select="'+OldNode+'"/>' +
'<xsl:variable name="newNodeXml">' +
'<xsl:element name="'+NewNode+'">' +
'<xsl:copy-of select="$oldNode/@*|node()"/>' +
'<xsl:copy-of select="$oldNode/child::*"/>' +
'</xsl:element>' +
'</xsl:variable>' +
'<xsl:copy-of select="msxml:node-set($newNodeXml)"/>' +
'</xsl:template>' +
'</xsl:stylesheet>'
);

XslTransform.Load(XMLStyleSheet);
writer := writer.StringWriter();
XslTransform.Transform(Source, nullXsltArgumentList, writer);
Destination := Destination.XmlDocument;
Destination.InnerXml(writer.ToString());

The have called the function to change the first item node to item1 like shown below.

//ChangeNodeName(XMLDotNet,XMLDotNet,'/Envelope/Body/getItemResponse/item','/Envelope/Body/getItemResponse/item','item1');

It was successfully changed the Node name to Item1.But I tried same function to change the code to code2 for the child of itemSubGroup. But it deleted the value of the code node. like shown below.

-<itemSubGroup>
  <code>PT</code>
  <description>Turcon Glyd Ring® T</description>
</itemSubGroup>

-<itemSubGroup>
  <code2/>
  <description>Turcon Glyd Ring® T</description>
</itemSubGroup>

i think i need to change the code some what to keep the value.

'<xsl:copy-of select="$oldNode/@*|node()"/>' +
'<xsl:copy-of select="$oldNode/child::*"/>' +

Instead of calling the function again and again. can you please guide me to create a function (XSLT code) to rename all the node at once. Please help me guys. Thanks

I have corrected the Code by giving an extra line

'<xsl:copy-of select="$oldNode/@*"/>' +

below the code

'</xsl:template>'+
'<xsl:template match="'+Root+'">'+
'<xsl:variable name="oldNode" select="'+OldNode+'"/>' +
'<xsl:variable name="newNodeXml">' +
'<xsl:element name="'+NewNode+'">' +
'<xsl:copy-of select="$oldNode/@*|node()"/>' +
'<xsl:copy-of select="$oldNode/child::*"/>' +
'<xsl:copy-of select="$oldNode/@*"/>' +
'</xsl:element>' +
'</xsl:variable>' +
'<xsl:copy-of select="msxml:node-set($newNodeXml)"/>' +
'</xsl:template>' +

But i need one function to change the repeated name space at once. Please help me.

Upvotes: 0

Views: 1196

Answers (2)

michael.hor257k
michael.hor257k

Reputation: 117073

I need to change the Node name if its repeated by adding a number to it. Like Item node to Item1. So the Nodes which are repeated will be changed to Item, Item1, code, code1, code2, code3.

First of all, this is a really bad idea. Alike items names should be identical, otherwise processing them becomes very difficult, if not impossible. If you need to add sequential numbering, use an attribute - that's what they are for.

Now, to perform this task purely in XSLT 1.0, you could do something like this:

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:key name="same-name" match="*" use="name()" />

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

<xsl:template match="*[count(key('same-name', name())) > 1]">
    <xsl:copy>
        <xsl:attribute name="num">
            <xsl:call-template name="index-of">
                <xsl:with-param name="node-set" select="key('same-name', name())"/>
                <xsl:with-param name="node" select="."/>
            </xsl:call-template>
        </xsl:attribute>
        <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
</xsl:template>

<!-- proc template -->
<xsl:template name="index-of">
    <xsl:param name="node-set"/>
    <xsl:param name="node"/>
    <xsl:param name="i" select="1"/>
    <xsl:choose>
        <xsl:when test="count($node|$node-set[$i]) = 1">
            <xsl:value-of select="$i" />
        </xsl:when>
        <xsl:when test="count($node-set) > $i">
            <!-- recursive call -->
            <xsl:call-template name="index-of">
                <xsl:with-param name="node-set" select="$node-set"/>
                <xsl:with-param name="node" select="$node"/>
                <xsl:with-param name="i" select="$i + 1"/>
            </xsl:call-template>
        </xsl:when>
     </xsl:choose>
</xsl:template>

</xsl:stylesheet>

When applied to the following test input:

<items>
    <item>
        <item/>
        <item>
            <property/>
            <property/>
            <property/>
        </item>
    </item>       
    <item>
        <item/>
        <not-item/>
        <item>
            <property/>
            <property/>
            <non-property/>
            <property/>
        </item>
        <item>
            <property/>
            <property/>
        </item>
    </item>       
</items>

the result will be:

<?xml version="1.0" encoding="UTF-8"?>
<items>
   <item num="1">
      <item num="2"/>
      <item num="3">
         <property num="1"/>
         <property num="2"/>
         <property num="3"/>
      </item>
   </item>
   <item num="4">
      <item num="5"/>
      <not-item/>
      <item num="6">
         <property num="4"/>
         <property num="5"/>
         <non-property/>
         <property num="6"/>
      </item>
      <item num="7">
         <property num="7"/>
         <property num="8"/>
      </item>
   </item>
</items>

--

Note: Subject to your processor's support, this could be streamlined by using some EXSLT functions.


Edit:

I am trying to import the xml to my application. In my application two element cannot have same node name.

If that is the real problem here, it can be solved much more simply and efficiently by attaching a unique id to all element names - without considering if the element is "repeating" or what is its position in document order:

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="*">
    <xsl:element name="{concat(name(), '-', generate-id())}">
        <xsl:copy-of select="@*"/>
        <xsl:apply-templates/>
    </xsl:element>
</xsl:template>

</xsl:stylesheet>

The exact result is somewhat processor-dependent. When applied to the above test input, Saxon 6.5 will return:

<?xml version="1.0" encoding="UTF-8"?>
<items-d0e1>
   <item-d0e2>
      <item-d0e3/>
      <item-d0e4>
         <property-d0e5/>
         <property-d0e6/>
         <property-d0e7/>
      </item-d0e4>
   </item-d0e2>
   <item-d0e8>
      <item-d0e9/>
      <not-item-d0e10/>
      <item-d0e11>
         <property-d0e12/>
         <property-d0e13/>
         <non-property-d0e14/>
         <property-d0e15/>
      </item-d0e11>
      <item-d0e16>
         <property-d0e17/>
         <property-d0e18/>
      </item-d0e16>
   </item-d0e8>
</items-d0e1>

Other processors may generate ids in a different format, but they all will be unique within the processed document scope.

Upvotes: 0

Tim C
Tim C

Reputation: 70638

I see your XSLT already makes use of the Identity Template, which is good as that means you only need to add templates for the elements you do wish to transform in some way. I am not entirely clear what elements you do wish to transform, but you mention changing "Item" to "Item1". In this case, just add the following template

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

And similarly for changing "code" to "code2" you would add this

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

Try this XSLT

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
    <xsl:output method="xml" indent="yes" />

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

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

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

Of course, if you really did want to build your XSLT up dynamically via a string so you can have different element names, it should not be hard to do, although there are other more 'friendly' ways of doing this.....

Upvotes: 1

Related Questions