Reputation: 67
As an input xml example I have
<Elem1>
<Obj name="1">
<Obj name="2">
<Obj name="3">
</Elem1>
Using a xslt rule I would like to obtain something like below
<Elem1>
<Obj1 name="1">
<Obj2 name="2">
<Obj3 name="3">
</Elem1>
Basically if the name of the child elements are the same I would like to append to the name the name of the first attribute.
I found a lot of examples where you can match this but only when you know the name of the element. Is there a way to match this kind of scenario without knowing the name of the element in advance For example:
<Elem1>
<Second name="1">
<Second name="2">
<Second name="3">
</Elem1>
will also return
<Elem1>
<Second1 name="1">
<Second2 name="2">
<Second3 name="3">
</Elem1>
Thanks in advance for you help.
Upvotes: 3
Views: 2530
Reputation: 461
<xsl:template match="Elem1">
<xsl:element name="Elem1">
<xsl:for-each select="Obj">
<xsl:element name="{concat(local-name(),position())}">
<xsl:apply-templates select="@*"/>
</xsl:element>
</xsl:for-each>
</xsl:element>
</xsl:template>
You may use this xslt also
Upvotes: 0
Reputation: 167756
Refining Rupesh's fine answer with a key in XSLT 2 or 3 you can use
<xsl:key name="group-by-name" match="*[@name]" use="node-name(.)"/>
<xsl:template match="*[@name and key('group-by-name', node-name(.), ..)[2]]">
<xsl:element name="{name()}{@name}" namespace="{namespace-uri()}">
<xsl:apply-templates select="@* | node()"/>
</xsl:element>
</xsl:template>
https://xsltfiddle.liberty-development.net/eiZQaF4/1
Although pondering it a bit longer it could give differents results than the accepted answer if there is an element that has children and grandchildren or further descendants of the same name as the third argument to the key
function only allows restricting the search to a subtree but not to a certain level of a subtree.
Upvotes: 3
Reputation: 12456
Rupesh_Kr answer is very general and works in any situation.
However, if you want to use a specific xpath
under which you only want to change the nodes based on your rule, then you can use the following stylesheet
INPUT:
<?xml version="1.0"?>
<Elems>
<Elem>
<Obj1 name="1"/>
<Obj2 name="2"/>
<Obj3 name="3"/>
</Elem>
<Elem>
<Second name="1"/>
<Second name="2"/>
<Second name="3"/>
</Elem>
</Elems>
STYLESHEET:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:redirect="http://xml.apache.org/xalan/redirect" xmlns:xalan="http://xml.apache.org/xslt" version="1.0" extension-element-prefixes="redirect">
<xsl:output method="xml" indent="yes" xalan:indent-amount="4"/>
<xsl:strip-space elements="*"/>
<xsl:template match="node() | @*">
<xsl:copy>
<xsl:apply-templates select="node() | @*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/Elems/Elem"> <!-- only for those nodes you do a specific process -->
<!-- variable to store the name of the first child -->
<xsl:variable name="subNodeName" select="local-name(./*[1])"/>
<!-- variable to store the number of children -->
<xsl:variable name="countNodes" select="count(./*)"/>
<xsl:choose>
<xsl:when test="$countNodes=count(./*[local-name()=$subNodeName])"><!--when all children have the same name -->
<Elem>
<xsl:for-each select="./*">
<xsl:element name="{name()}{@name}">
<xsl:apply-templates select="node() | @*"/>
</xsl:element>
</xsl:for-each>
</Elem>
</xsl:when>
<xsl:otherwise>
<Elem>
<xsl:apply-templates select="node() | @*"/>
</Elem>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
OUTPUT:
<?xml version="1.0"?>
<Elems>
<Elem>
<Obj1 name="1"/>
<Obj2 name="2"/>
<Obj3 name="3"/>
</Elem>
<Elem>
<Second1 name="1"/>
<Second2 name="2"/>
<Second3 name="3"/>
</Elem>
</Elems>
Upvotes: 2
Reputation: 3445
Try this
<xsl:template match="*[@name][count(../*[name() = current()/name()]) > 1]">
<xsl:element name="{name()}{@name}">
<xsl:apply-templates select="node()|@*"/>
</xsl:element>
</xsl:template>
For copy other data use identity transform as
<xsl:template match="node() | @*">
<xsl:copy>
<xsl:apply-templates select="node() | @*"/>
</xsl:copy>
</xsl:template>
Upvotes: 3