How to change and delete attributes in an XML file using XSLT
Posted by Jim DeLaHunt on 31 Mar 2021 at 11:19 pm | Tagged as: robobait, software engineering
I recently had a need to modify a large XML file by changing attributes of one kind of element — changing the value of a certain attribute, and deleting another attribute — while preserving everything else: element text, surrounding elements, etc. I did not readily find code for modifying an attribute value in my searches. Here is what worked for me. Perhaps it will be helpful for someone else.
Given an XML file like this:
<?xml version="1.0" encoding="UTF-8"?>
<body><foo a1="wrong" a2="deleteme" a3="keepme">keep also</foo></body>
The objective is to come up with an XSLT stylesheet which changes attribute a1
from value "wrong"
to value "right"
, and which deletes attribute a2
entirely, while preserving everything else. In other words, it generates an XML file like:
<?xml version="1.0" encoding="UTF-8"?>
<body><foo a1="right" a3="keepme">keep also</foo></body>
(Note that the XML files are very stripped-down and stylised, to make the technique clearer. The same XSLT stylesheet also works on real, complex XML files.)
There are three basic XML principles involved in the solution:
- Use an identity template to copy everything, except where overridden.
- Delete attributes by writing a template which matches to the specific element and attributes, and then omits a copy directive. The matched attributes are deleted by omission.
- Change attribute values by deleting the attribute, then within that template, using
<xsl:attribute />
to generate a replacement attribute with the new value.
The following XSLT stylesheet performs the modification.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="no" encoding="UTF-8" />
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/body/foo[@a1='wrong'][@a2='deleteme']" >
<xsl:copy>
<xsl:attribute name="a1">right</xsl:attribute>
<xsl:apply-templates select="@*[name(.)!='a1' and name(.)!='a2']" />
<xsl:apply-templates select="node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
The identity template has a match string of "node()|@*"
. node()
means, all elements and element text. @*
means, attributes with any name. It contains an <xsl:copy> instruction, which recursively applies templates to everything. Thus, it will copy everything in the input file to the output, unless another, more specific template overrides it.
The second template is the more specific template which overrides the identity template. The expression "/body/foo"
matches elements named foo
within an element named body
under the root of the file. (Explaining match expressions is beyond the scope of this post. See an XSLT tutorial.) The expression "foo[@a1='wrong'][@a2='deleteme']"
matches only elements foo
, which have an attribute named a1
having the value 'wrong'
, and also an attribute named a2 having the value 'deleteme'
.
The second template has an <xsl:copy> instruction. Within it are elements which describe what to put in the output file in place of those matched "/body/foo"
elements.
The <xsl:attribute>
instructions sends an attribute a1
with the value 'right'
to the output.
The first <xsl:apply-templates>
instruction causes every attribute except the attributes named a1
and a2
to be processed, and thus copied to the output. The expression "@*[name(.)!='a1' and name(.)!='a2']"
matches attributes of any name, as long as the name is not 'a1'
and the name is not 'a2'
. Thus, this instruction deletes the attributesa1
and a2
by omission.
The second <xsl:apply-templates>
instruction causes the "/body/foo"
element itself, and any element text or contained elements, to be processed, and thus copied to the output.
It is possible to make this stylesheet more sophisticated and reusable. For instance one could extract the replacement element values into parameters to make reuse easier. However, my purpose here is to show clearly the principle behind changing attribute values and deleting attributes, using XSLT.
I acknowledge the help I got from StackOverflow answer to XSLT: How to change an attribute value during <xsl:copy>? in understanding this technique, and in making my own XSLT stylesheet. I hope this blog will help you, when you have a similar problem.
[Update 2021-04-01: corrected the example XML files, which had old content due to a copy/paste mistake. Thank you to the alert reader to reported this!]
Thank you so much!
This has been stumping me. When I use XSLT, I feel like I “get it” then suddenly I just hit a wall which proves I know nothing of how this works. 🙂
I was able to extrapolate your simple example and the clear explanations to solve my problem.