XSLT identity transform produces wrong namespace - xslt

I have a custom XML schema that is evolving: elements are added, others deleted, and the namespace changes to reflect the new version (e.g., from "http://foo/1.0" to "http://foo/1.1"). I want to write an XSLT that converts XML documents from the old schema to the new one. My first attempt works, but it's verbose and unscalable, and I need help refining it.
Here's a sample document for the 1.0 schema:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<foo:rootElement xmlns:foo="http://foo/1.0">
<obsolete>something</obsolete>
<stuff>
<alpha>a</alpha>
<beta>b</beta>
</stuff>
</foo:rootElement>
In the 1.1 schema, the "obsolete" element goes away but everything else remains. So the XSLT needs to do two things:
Remove the tag
Change the namespace from http://foo/1.0 to http://foo/1.1
Here's my solution:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:foo1="http://foo/1.0"
xmlns:foo="http://foo/1.1"
exclude-result-prefixes="foo1">
<xsl:output method="xml" standalone="yes" indent="yes"/>
<!-- Remove the "obsolete" tag -->
<xsl:template match="foo1:rootElement/obsolete"/>
<!-- Copy the "alpha" tag -->
<xsl:template match="foo1:rootElement/stuff/alpha">
<alpha>
<xsl:apply-templates/>
</alpha>
</xsl:template>
<!-- Copy the "beta" tag -->
<xsl:template match="foo1:rootElement/stuff/beta">
<beta>
<xsl:apply-templates/>
</beta>
</xsl:template>
<!-- Copy the "stuff" tag -->
<xsl:template match="foo1:rootElement/stuff">
<stuff>
<xsl:apply-templates/>
</stuff>
</xsl:template>
<!-- Copy the "rootElement" tag -->
<xsl:template match="foo1:rootElement">
<foo:rootElement>
<xsl:apply-templates/>
</foo:rootElement>
</xsl:template>
</xsl:stylesheet>
Although this produces the output I want, notice that I have a copying template for every element in the schema: alpha, beta, etc. For complex schemas with hundreds of kinds of elements, I'd have to add hundreds of templates!
I thought I could eliminate this problem by reducing all those copying templates into a single identity transform, like this:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:foo1="http://foo/1.0"
xmlns:foo="http://foo/1.1"
exclude-result-prefixes="foo1">
<xsl:output method="xml" standalone="yes" indent="yes"/>
<xsl:template match="foo1:rootElement/obsolete"/>
<xsl:template match="node()|#*" name="identity">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="foo1:rootElement/stuff">
<xsl:copy>
<xsl:call-template name="identity"/>
</xsl:copy>
</xsl:template>
<xsl:template match="foo1:rootElement">
<foo:rootElement>
<xsl:apply-templates/>
</foo:rootElement>
</xsl:template>
</xsl:stylesheet>
But it produces:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<foo:rootElement xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:foo="http://foo/1.1">
<stuff xmlns:foo="http://foo/1.0">
<stuff>
<alpha>a</alpha>
<beta>b</beta>
</stuff>
</stuff>
</foo:rootElement>
The "stuff" element was copied, which is what I want, but it's wrapped in another "stuff" element, tagged with the old namespace. I don't understand why this is happening. I've tried many ways of fixing this but have been unsuccessful. Any suggestions? Thanks.

This transformation:
<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="pOldNS" select="'http://foo/1.0'"/>
<xsl:param name="pNewNS" select="'http://foo/1.1'"/>
<xsl:template match="/*">
<xsl:element name="{name()}" namespace="{$pNewNS}">
<xsl:apply-templates select="node()|#*"/>
</xsl:element>
</xsl:template>
<xsl:template match="*">
<xsl:element name="{name()}">
<xsl:copy-of select="namespace::*[not(.=$pOldNS)]"/>
<xsl:apply-templates select="node()|#*"/>
</xsl:element>
</xsl:template>
<xsl:template match="#*">
<xsl:choose>
<xsl:when test="namespace-uri()=$pOldNS">
<xsl:attribute name="{name()}" namespace="{$pNewNS}">
<xsl:value-of select="."/>
</xsl:attribute>
</xsl:when>
<xsl:otherwise>
<xsl:copy-of select="."/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="obsolete"/>
</xsl:stylesheet>
when applied on this XML document:
<foo:rootElement xmlns:foo="http://foo/1.0">
<obsolete>something</obsolete>
<stuff>
<alpha x="y" foo:t="z">a</alpha>
<beta>b</beta>
</stuff>
</foo:rootElement>
produces the wanted, correct result (obsolute elements deleted, namespace upgraded, attribute nodes correctly processed, including nodes that were in the old namespace):
<foo:rootElement xmlns:foo="http://foo/1.1">
<stuff>
<alpha x="y" foo:t="z">a</alpha>
<beta>b</beta>
</stuff>
</foo:rootElement>
Main advantages of this solution:
Works correctly when there are attributes, belonging to the old-schema namespace.
General, allows the old and new namespace to be passed as external parameters to the transformation.

While you might want to copy the attributes, elements need to be regenerated in the new namespace. So I'd suggest to do it like this:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:foo1="http://foo/1.0">
<xsl:template match="foo1:rootElement/obsolete"/>
<xsl:template match="attribute()">
<xsl:copy/>
</xsl:template>
<xsl:template match="element()">
<xsl:variable name="name" select="local-name()"/>
<xsl:element name="{$name}" namespace="http://foo/1.1">
<xsl:apply-templates select="node()|#*"/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>

Related

XSLT Transformation that add namespace prefix doesn't copy attributes

I have a requirement to add a namespace prefix to all my xml elements.
I have a working xslt for this but unfortunately it doesn't copy the attributes. What do I need to change to also copy these attributes.
This my code:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:ns0="http://ns.hr-xml.org/2007-04-15" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" />
<xsl:template match="*">
<xsl:element name="ns0:{local-name()}">
<xsl:apply-templates select="*|node()" />
</xsl:element>
</xsl:template>
</xsl:stylesheet>
The input XML is this:
<HumanResource schemaLocation="http://ns.hr-xml.org/2007-04-15 ../HumanResource.xsd">
<HumanResourceId idOwner="StaffingCompany">
<IdValue>123COMP</IdValue>
</HumanResourceId>
<HumanResourceId idOwner="StaffingCustomer">
<IdValue>456CUST</IdValue>
<!--double hre-id, due fms wnr-code swap-->
</HumanResourceId>
<HumanResourceStatus status="x:Assigned"/>
<ReferenceInformation>
<StaffingSupplierId idOwner="StaffingCustomer">
<IdValue>105964</IdValue>
</StaffingSupplierId>
<StaffingCustomerId idOwner="StaffingCompany">
<IdValue>MIN0000612</IdValue>
</StaffingCustomerId>
<StaffingCustomerId idOwner="StaffingCustomer">
<IdValue>ELI</IdValue>
</StaffingCustomerId>
<StaffingSupplierOrgUnitId idOwner="StaffingCompany">
<IdValue>2606379</IdValue>
</StaffingSupplierOrgUnitId>
<StaffingCustomerOrgUnitId idOwner="StaffingCustomer">
<IdValue>ELI</IdValue>
</StaffingCustomerOrgUnitId>
</ReferenceInformation>
<ResourceInformation>
<PersonName>
<FormattedName>J</FormattedName>
<LegalName>C</LegalName>
<GivenName>J</GivenName>
<PreferredGivenName>J</PreferredGivenName>
<MiddleName>C.J.</MiddleName>
<FamilyName primary="true"></FamilyName>
</PersonName>
<EntityContactInfo>
<EntityName></EntityName>
<PersonName>
<FormattedName>Liesbeth Kramer</FormattedName>
</PersonName>
<ContactMethod>
<Telephone>
<FormattedNumber>070-3102150</FormattedNumber>
</Telephone>
</ContactMethod>
</EntityContactInfo>
<PostalAddress>
<CountryCode>NL</CountryCode>
<PostalCode></PostalCode>
<Municipality></Municipality>
<DeliveryAddress>
<AddressLine></AddressLine>
<StreetName></StreetName>
<BuildingNumber></BuildingNumber>
</DeliveryAddress>
</PostalAddress>
</ResourceInformation>
<Profile/>
<Preferences/>
<UserArea>
<ns2:HumanResourceAdditionalNL xmlns:ns2="http://ns.setu.nl/2008-01">
<ns2:BirthDate>1961-07-13</ns2:BirthDate>
<ns2:Sex>female</ns2:Sex>
</ns2:HumanResourceAdditionalNL>
</UserArea>
</HumanResource>
My result with my code looks like this:
<?xml version='1.0' ?>
<ns0:HumanResource xmlns:ns0="http://ns.hr-xml.org/2007-04-15">
<ns0:HumanResourceId>
<ns0:IdValue>123COMP</ns0:IdValue>
</ns0:HumanResourceId>
<ns0:HumanResourceId>
<ns0:IdValue>456CUST</ns0:IdValue>
</ns0:HumanResourceId>
<ns0:HumanResourceStatus/>
<ns0:ReferenceInformation>
As you can see, the ns0: is added ok but I lost the attributes.
You have lost the attributes because you have no code to select them. You currently do <xsl:apply-templates select="*|node()" />, but * select elements, and node() selects elements, text nodes, comments and processing instructions. You need to change it to this...
<xsl:apply-templates select="#*|node()" />
You would then need a separate template to match attributes and copy them.
<xsl:template match="#*">
<xsl:copy />
</xsl:template>
Try this XSLT
<xsl:stylesheet version="1.0" xmlns:ns0="http://ns.hr-xml.org/2007-04-15" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" />
<xsl:template match="*">
<xsl:element name="ns0:{local-name()}">
<xsl:apply-templates select="#*|node()" />
</xsl:element>
</xsl:template>
<xsl:template match="#*">
<xsl:copy />
</xsl:template>
</xsl:stylesheet>
Alternatively, you could use xsl:copy-of to copy the attributes. Try this XSLT too
<xsl:stylesheet version="1.0" xmlns:ns0="http://ns.hr-xml.org/2007-04-15" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" />
<xsl:template match="*">
<xsl:element name="ns0:{local-name()}">
<xsl:copy-of select="#*" />
<xsl:apply-templates />
</xsl:element>
</xsl:template>
</xsl:stylesheet>
Note that <xsl:apply-templates /> is short-hand for <xsl:apply-templates select="node()" />

XSLT 1.0, copy all child nodes except some

I am trying to copy all child nodes to a specific node, except a few. Haven't been able to get this to work? Any pointers of what I am doing wrong?
Using this XML:
<ns0:Envelope xmlns:ns0="http://schemas.xmlsoap.org/soap/envelope/">
<ns0:Header>
<wsse:Sec xmlns:wsse="http://docs.x.org/wsse/">
<saml:Ass xmlns:saml="http://docs.x.org/saml/">
<ds:Sign xmlns:ds="http://docs.x.org/ds/">
<ds:SignVal>SignatureValue</ds:SignVal>
</ds:Sign>
<saml:subj>SubjectValue</saml:subj>
</saml:Ass>
</wsse:Sec>
<To>http://localhost:8080/Test/</To>
<Action>SendTest</Action>
</ns0:Header>
<ns0:Body>...</ns0:Body>
</ns0:Envelope>
The wanted result is to just get the Sec tag and all children:
<wsse:Sec xmlns:wsse="http://docs.x.org/wsse/">
<saml:Ass xmlns:saml="http://docs.x.org/saml/">
<ds:Sign xmlns:ds="http://docs.x.org/ds/">
<ds:SignVal>SignatureValue</ds:SignVal>
</ds:Sign>
<saml:subj>SubjectValue</saml:subj>
</saml:Ass>
</wsse:Sec>
I have tried numerous XSL including this:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" encoding="utf-8" indent="yes"/>
<xsl:template match="Header">
<xsl:copy-of select="*"/>
</xsl:template>
<!-- Exclude these -->
<xsl:template match="To" />
<xsl:template match="Action" />
</xsl:stylesheet>
The result is I get values but no tags...
You have not accounted for namespaces in your XSLT. In your XML, Header is in namespace http://schemas.xmlsoap.org/soap/envelope/, but your XSLT is trying to match a Header in no namespace.
You need to declare the namespaces in your XSLT, and use them in the template matches
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ns0="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:wsse="http://docs.x.org/wsse/">
<xsl:output method="xml" encoding="utf-8" indent="yes"/>
<xsl:template match="ns0:Header">
<xsl:copy-of select="wsse:Sec"/>
</xsl:template>
<xsl:template match="ns0:Body" />
</xsl:stylesheet>
Note this XSLT doesn't need templates matching "To" and "Action" because of the explicit copy of wsse:Sec using this approach. However, you do need to template to ensure any test within ns0:Body isn't picked up.
Another approach is to use the identity template, and then you would have the templates to exclude To and Action (and Body)
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ns0="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:wsse="http://docs.x.org/wsse/">
<xsl:output method="xml" encoding="utf-8" indent="yes"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="ns0:Envelope|ns0:Header">
<xsl:apply-templates />
</xsl:template>
<!-- Exclude these -->
<xsl:template match="ns0:Body|To|Action" />
</xsl:stylesheet>
Note there is a template matching ns0:Envelope and ns0:Header as although you don't want these elements themselves, you do need to process the child nodes.
You would need to use XSLT 2 or 3 with
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:wsse="http://docs.x.org/wsse/"
exclude-result-prefixes="#all"
version="3.0">
<xsl:template match="/">
<xsl:copy-of select="//wsse:Sec" copy-namespaces="no"/>
</xsl:template>
</xsl:stylesheet>
to get the posted result with a simple copy instruction: https://xsltfiddle.liberty-development.net/bnnZVw
In XSLT 1 the copy will always copy the in-scope namespace xmlns:ns0="http://schemas.xmlsoap.org/soap/envelope/" so to remove it from the result you would need to run your code through some kind of transformation stripping in-scope namespaces (other than the one of the element itself):
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:wsse="http://docs.x.org/wsse/"
exclude-result-prefixes="wsse"
version="1.0">
<xsl:template match="#*">
<xsl:attribute name="{name()}" namespace="{namespace-uri()}">
<xsl:value-of select="."/>
</xsl:attribute>
</xsl:template>
<xsl:template match="*">
<xsl:element name="{name()}" namespace="{namespace-uri()}">
<xsl:apply-templates select="#* | node()"/>
</xsl:element>
</xsl:template>
<xsl:template match="/">
<xsl:apply-templates select="//wsse:Sec"/>
</xsl:template>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/bnnZVw/1

XSLT- Creating Parent XML element

I've fairly recently started learning XSLT after getting to grips with XML and XPath; I'm trying to complete a practice exercise; I think I'm nearly there but I've ran into a problem. So I have the following XML document:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<a>
<b></b>
<b></b>
<b></b>
<b></b>
</a>
</root>
And I'd like to surround the elements with a pair of parent elements (to output the following):
<?xml version="1.0" encoding="UTF-8"?>
<root>
<a>
<b-group>
<b></b>
<b></b>
<b></b>
<b></b>
<b-group>
</a>
Here is my XSLT:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs"
version="2.0">
<xsl:template match="root">
<xsl:element name="root">
<xsl:apply-templates select="#*|node()"/>
</xsl:element>
</xsl:template>
<xsl:template match="a">
<xsl:element name="a">
<xsl:apply-templates select="#*|node()"/>
</xsl:element>
</xsl:template>
<xsl:template match="b">
<xsl:element name="b-group">
<xsl:apply-templates select="#*|node()"/>
</xsl:element>
</xsl:template>
<xsl:template match="b">
<xsl:element name="b">
<xsl:apply-templates select="#*|node()"/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
Despite making several other attempts, I'm having difficulty creating the pair of elements that surround the elements; could anyone point me in the right direction of how to do this please?
You need to do this in the template matching a, for example:
<xsl:template match="a">
<a>
<b-group>
<xsl:apply-templates select="b"/>
</b-group>
</a>
</xsl:template>
Additional notes:
Use a literal result element to create an element whose name is known, instead of xsl:element.
Most of your templates do the same thing: create an element with the same name as the one being matched, and apply templates to its children. Thus they could be consolidated into one. A template like this is known as the identity transform template and it is commonly used when most of the document needs to preserved as is, with only a few modifications. This would reduce your entire stylesheet to just:
<xsl:stylesheet version="2.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="*"/>
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="a">
<a>
<b-group>
<xsl:apply-templates select="b"/>
</b-group>
</a>
</xsl:template>
</xsl:stylesheet>

how to remove a part of attribut to a node in xml file using xslt

I have the following xml code:
<OML>
<bg-def xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="EX1"/>
</OML>
I want to remove the attribute xmlns:xsi and its value using XSLT, so that the result will look like this:
<OML>
<bg-def name="EX1"/>
</OML>
I tryied to do this with the following XSLT code:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
xmlns:ex="http://exslt.org/dates-and-times" extension-element-prefixes="ex">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="no" xml:space="preserve"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="bg-def|# xmlns:xsi"/>
</xsl:transform>
Before I finished to write my code, my editor warned me that:
"W Namespace prefix xmlns has not been declared".
When I remove the expression :xsi and just write xmlns, there is no warning more. But when I compile and execute my program, nothing happens and I don't get the expected output.
I try also to change the last line of my xslt file with this:
<xsl:template match="bg-def|# name"/>
then the result is looking like this:
<OML>
<bg-def xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/>
</OML>
That means, the attribute name has been removed very well. But I want to do this with the attribute xmlns:xsi.
Can someone help me to do this please?
Thanks for any help.
Franky
Use following template for bd-def node:
<xsl:template match="bg-def">
<xsl:element name="{local-name()}">
<xsl:apply-templates select="node()|#*"/>
</xsl:element>
</xsl:template>
Instead of
<xsl:template match="bg-def|# name"/>
This template will create node bg-def and copy all it context nodes and attributes, but not namespaces
Check similar question:
remove namespace for a perticular element
Update:
Source file:
<OML>
<bg-def xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="EX1"/>
</OML>
Stylesheet:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
xmlns:ex="http://exslt.org/dates-and-times" extension-element-prefixes="ex">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="no" xml:space="preserve"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="bg-def">
<xsl:element name="{local-name()}">
<xsl:apply-templates select="node()|#*"/>
</xsl:element>
</xsl:template>
</xsl:transform>
Transformation result (Saxon 6.5.5 - Xslt 1.0):
<?xml version="1.0" encoding="UTF-8"?><OML>
<bg-def name="EX1"/>
</OML>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml"/>
<xsl:template match="*">
<xsl:element name="{local-name()}">
<xsl:apply-templates select="#*"/>
<xsl:apply-templates/>
</xsl:element>
</xsl:template>
<xsl:template match="#*">
<xsl:attribute name="{local-name()}">
<xsl:value-of select="."/>
</xsl:attribute>
</xsl:template>
</xsl:stylesheet>

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

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".
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>
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.
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>
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.
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']
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>
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>
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.