Add attribute to node

I am trying to add an attribute to node if the child value of node is equal to some string.

I have a main.xml file

<Employees> <Employee> <countryid>32</countryid> <id name="id">1</id> <firstname >ABC</firstname> <lastname >XYZ</lastname> </Employee> <Employee> <countryid>100</countryid> <id name="id">2</id> <firstname >ddd</firstname> <lastname >ggg</lastname> </Employee> </Employees> 

So, let's say if the country id is 32, then it should add the country = 32 attribute to the Employee node. The result should look like this:

output.xml

  <Employees> <Employee countryid="32"> <countryid>32</countryid> <id name="id">1</id> <firstname >ABC</firstname> <lastname >XYZ</lastname> </Employee> <Employee> <countryid>100</countryid> <id name="id">2</id> <firstname >ddd</firstname> <lastname >ggg</lastname> </Employee> </Employees> 

I am using the following script, but I get an error that the node attribute cannot be created after the children of the containing element .:

Transform.xsl


 <xsl:template match="/"> <xsl:apply-templates /> </xsl:template> <xsl:template match="Employees/Employee/countryid[.=32']"> <xsl:attribute name="countryid">32</xsl:attribute> <xsl:copy> <xsl:apply-templates select="@* | node()"/> </xsl:copy> </xsl:template> 

Any help would be appreciated. We can also pass countryid as a comma to pass 32100, and then add the attribute to all the relevant nodes.

Thanks.

+8
source share
2 answers

In addition to Dimitre's good answer, XSLT 2.0 stylesheet:

 <xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:param name="pCountry" select="'32,100'"/> <xsl:template match="node()|@*"> <xsl:copy> <xsl:apply-templates select="node()|@*"/> </xsl:copy> </xsl:template> <xsl:template match="Employee[countryid = tokenize($pCountry,',')]"> <Employee countryid="{countryid}"> <xsl:apply-templates select="@*|node()"/> </Employee> </xsl:template> </xsl:stylesheet> 

Output:

 <Employees> <Employee countryid="32"> <countryid>32</countryid> <id name="id">1</id> <firstname>ABC</firstname> <lastname>XYZ</lastname> </Employee> <Employee countryid="100"> <countryid>100</countryid> <id name="id">2</id> <firstname>ddd</firstname> <lastname>ggg</lastname> </Employee> </Employees> 

Note Experimental comparison with sequence, param / variable reference in templates.

Another approach, assuming countryid , is always the first descendant:

 <xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:strip-space elements="*"/> <xsl:param name="pCountry" select="'32,100'"/> <xsl:template match="node()|@*" name="identity"> <xsl:copy> <xsl:apply-templates select="node()|@*"/> </xsl:copy> </xsl:template> <xsl:template match="countryid[. = tokenize($pCountry,',')]"> <xsl:attribute name="countryid"> <xsl:value-of select="."/> </xsl:attribute> <xsl:call-template name="identity"/> </xsl:template> </xsl:stylesheet> 

Note : now the xsl:strip-space command is important (avoids the output of node text before the attribute)

+5
source share

Part 1

So, let's say if the country identifier is 32, then it should add the attribute country = 32 for the node employee.

This conversion is :

 <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:template match="node()|@*"> <xsl:copy> <xsl:apply-templates select="node()|@*"/> </xsl:copy> </xsl:template> <xsl:template match="Employee[countryid=32]"> <Employee countryid="{countryid}"> <xsl:apply-templates select="@*|node()"/> </Employee> </xsl:template> </xsl:stylesheet> 

when applied to the provided XML document :

 <Employees> <Employee> <countryid>32</countryid> <id name="id">1</id> <firstname >ABC</firstname> <lastname >XYZ</lastname> </Employee> <Employee> <countryid>100</countryid> <id name="id">2</id> <firstname >ddd</firstname> <lastname >ggg</lastname> </Employee> </Employees> 

creates the desired, correct result :

 <Employees> <Employee countryid="32"> <countryid>32</countryid> <id name="id">1</id> <firstname>ABC</firstname> <lastname>XYZ</lastname> </Employee> <Employee> <countryid>100</countryid> <id name="id">2</id> <firstname>ddd</firstname> <lastname>ggg</lastname> </Employee> </Employees> 

Explanation

  • An identification rule is used to copy each node as-is . Using and redefining an identity rule (template) is the most fundamental and powerful XSLT design pattern.

  • There is only one template that overrides the identification rule for specific nodes - Employee , which has a child countryid with a string value (converted to a number) 32. This template adds the countryid attribute to the Employee element and applies templates to renew the identity rule and copy everything else as it is.

Part 2.

We can also skip countryid as a comma-separated values ​​so that I can pass 32 100, and then we should add an attribute for all matching nodes

This conversion is :

 <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="pIds" select="'32,100'"/> <xsl:template match="node()|@*"> <xsl:copy> <xsl:apply-templates select="node()|@*"/> </xsl:copy> </xsl:template> <xsl:template match="Employee"> <Employee> <xsl:if test= "contains(concat(',',$pIds,','), concat(',',countryid,',') )"> <xsl:attribute name="countryid"> <xsl:value-of select="countryid"/> </xsl:attribute> </xsl:if> <xsl:apply-templates select="@*|node()"/> </Employee> </xsl:template> </xsl:stylesheet> 

when applied to the same XML document (above), it creates the desired, correct result :

 <Employees> <Employee countryid="32"> <countryid>32</countryid> <id name="id">1</id> <firstname>ABC</firstname> <lastname>XYZ</lastname> </Employee> <Employee countryid="100"> <countryid>100</countryid> <id name="id">2</id> <firstname>ddd</firstname> <lastname>ggg</lastname> </Employee> </Employees> 
+10
source share

All Articles