XSLT: How to change an attribute value during <xsl:copy>?

XsltXslt 1.0Xslt 2.0

Xslt Problem Overview


I have an XML document, and I want to change the values for one of the attributes.

First I copied everything from input to output using:

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

And now I want to change the value of the attribute "type" in any element named "property".

Xslt Solutions


Solution 1 - Xslt

This problem has a classical solution: Using and overriding the identity template is one of the most fundamental and powerful XSLT design patterns:

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

	<xsl:param name="pNewType" select="'myNewType'"/>

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

	<xsl:template match="property/@type">
		<xsl:attribute name="type">
			<xsl:value-of select="$pNewType"/>
		</xsl:attribute>
	</xsl:template>
</xsl:stylesheet>

When applied on this XML document:

<t>
  <property>value1</property>
  <property type="old">value2</property>
</t>

the wanted result is produced:

<t>
  <property>value1</property>
  <property type="myNewType">value2</property>
</t>

Solution 2 - Xslt

Tested on a simple example, works fine:

<xsl:template match="@*|node()">
  <xsl:copy>
    <xsl:apply-templates select="@*|node()"/>
  </xsl:copy>
</xsl:template>
<xsl:template match="@type[parent::property]">
  <xsl:attribute name="type">
    <xsl:value-of select="'your value here'"/>
  </xsl:attribute>
</xsl:template>

Edited to include Tomalak's suggestion.

Solution 3 - Xslt

The top two answers will not work if there is a xmlns definition in the root element:

<?xml version="1.0"?>
<html xmlns="http://www.w3.org/1999/xhtml">
    <property type="old"/>
</html>

All of the solutions will not work for the above xml.

The possible solution is like:

<?xml version="1.0"?> 

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
                
  <xsl:output omit-xml-declaration="yes" indent="yes"/>
  <xsl:template match="node()[local-name()='property']/@*[local-name()='type']">
	  <xsl:attribute name="{name()}" namespace="{namespace-uri()}">
                some new value here
          </xsl:attribute>
  </xsl:template>
     
  <xsl:template match="@*|node()|comment()|processing-instruction()|text()">
	  <xsl:copy>
		  <xsl:apply-templates select="@*|node()|comment()|processing-instruction()|text()"/>
	  </xsl:copy>
  </xsl:template>
</xsl:stylesheet>

Solution 4 - Xslt

You need a template that will match your target attribute, and nothing else.

<xsl:template match='XPath/@myAttr'>
  <xsl:attribute name='myAttr'>This is the value</xsl:attribute>
</xsl:template>

This is in addition to the "copy all" you already have (and is actually always present by default in XSLT). Having a more specific match it will be used in preference.

Solution 5 - Xslt

I had a similar case where I wanted to delete one attribute from a simple node, and couldn't figure out what axis would let me read the attribute name. In the end, all I had to do was use

@*[name(.)!='AttributeNameToDelete']

Solution 6 - Xslt

I also came across same issue and i solved it as follows:

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

<!-- copy property element while only changing its type attribute -->
<xsl:template match="property">
  <xsl:copy>
    <xsl:attribute name="type">
      <xsl:value-of select="'your value here'"/>
    </xsl:attribute>
    <xsl:apply-templates select="@*[not(local-name()='type')]|node()"/>
  </xsl:copy>
</xsl:template>

Solution 7 - Xslt

For the following XML:

<?xml version="1.0" encoding="utf-8"?>
<root>
	<property type="foo"/>
	<node id="1"/>
	<property type="bar">
		<sub-property/>
	</property>
</root>

I was able to get it to work with the following XSLT:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
	<xsl:template match="@*|node()">
		<xsl:copy>
			<xsl:apply-templates select="@*|node()"/>
		</xsl:copy>
	</xsl:template>
	<xsl:template match="//property">
		<xsl:copy>
			<xsl:attribute name="type">
				<xsl:value-of select="@type"/>
				<xsl:text>-added</xsl:text>
			</xsl:attribute>
			<xsl:copy-of select="child::*"/>
		</xsl:copy>
	</xsl:template>
</xsl:stylesheet>

Solution 8 - Xslt

If your source XML document has its own namespace, you need to declare the namespace in your stylesheet, assign it a prefix, and use that prefix when referring to the elements of the source XML - for example:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xhtml="http://www.w3.org/1999/xhtml">

<xsl:output method="xml" encoding="utf-8" indent="yes" omit-xml-declaration="yes" />

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

<!-- exception-->    
<xsl:template match="xhtml:property/@type">
	<xsl:attribute name="type">
		<xsl:text>some new value</xsl:text>	
	</xsl:attribute>
</xsl:template>
    
</xsl:stylesheet>

Or, if you prefer:

...
<!-- exception-->    
<xsl:template match="@type[parent::xhtml:property]">
  <xsl:attribute name="type">
		<xsl:text>some new value</xsl:text>	
  </xsl:attribute>
</xsl:template>
...

ADDENDUM: In the highly unlikely case where the XML namespace is not known beforehand, you could do:

<?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" encoding="utf-8" indent="yes" omit-xml-declaration="yes" />

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

<!-- exception -->
<xsl:template match="*[local-name()='property']/@type">
	<xsl:attribute name="type">
		<xsl:text>some new value</xsl:text>	
	</xsl:attribute>
</xsl:template>

Of course, it's very difficult to imagine a scenario where you would know in advance that the source XML document contains an element named "property", with an attribute named "type" that needs replacing - but still not know the namespace of the document. I have added this mainly to show how your own solution could be streamlined.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestiontomatoView Question on Stackoverflow
Solution 1 - XsltDimitre NovatchevView Answer on Stackoverflow
Solution 2 - XsltWelbogView Answer on Stackoverflow
Solution 3 - XsltastoniaView Answer on Stackoverflow
Solution 4 - XsltRichardView Answer on Stackoverflow
Solution 5 - XsltrwrobsonView Answer on Stackoverflow
Solution 6 - XsltAshish LahotiView Answer on Stackoverflow
Solution 7 - XsltAndrew HareView Answer on Stackoverflow
Solution 8 - Xsltmichael.hor257kView Answer on Stackoverflow