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

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.

Related

How to remove the ns0 prefix from the xml output file with out removing the namespace declaration

i want to remove "nso" prefix from the output xml file with out removing the namespace declaration but in the declaration part also i need to remove "ns0:" & ":ns0".
`
<?xml version="1.0" encoding="UTF-8"?>
<ns0:Document xmlns:ns0="urn:iso:std:iso:20022:tech:xsd:abcd">**<!-- I do not want to remove this declaration line from the output only needs to remove "ns0:" & ":ns0" from the declaration-->**
<ns0:CstmrCdtTrfInitn>
<ns0:GrpHdr>
<ns0:MsgId>abcd</ns0:MsgId>
<ns0:CreDtTm>2023-01-24T14:47:17Z</ns0:CreDtTm>
<ns0:NbOfTxs>2 </ns0:NbOfTxs>
<ns0:CtrlSum>580000.00</ns0:CtrlSum>
<ns0:InitgPty>
<ns0:Nm>abcd</ns0:Nm>
<ns0:CtryOfRes>IN</ns0:CtryOfRes>
</ns0:InitgPty>
</ns0:GrpHdr>
</ns0:CstmrCdtTrfInitn>
</ns0:Document>`
`
<?xml version="1.0" encoding="UTF-8"?>
<Document xmlns ="urn:iso:std:iso:20022:tech:xsd:abcd">
<CstmrCdtTrfInitn>
<GrpHdr>
<MsgId>abcd</MsgId>
<CreDtTm>2023-01-24T14:47:17Z</CreDtTm>
<NbOfTxs>2 </NbOfTxs>
<CtrlSum>580000.00</CtrlSum>
<InitgPty>
<Nm>abcd</Nm>
<:CtryOfRes>IN</CtryOfRes>
</InitgPty>
</GrpHdr>
</CstmrCdtTrfInitn>
</Document>`
Please help me on this requirment.
Thanks.
i used below code but it is removing all the nso prefix's along with namespace declarations but i want to remove only nso prefix's for individual xml tags from the element "ns0:CstmrCdtTrfInitn" before this element i want to remove "ns0:" & ":ns0" and keep the declaration with out "ns0".
`
<?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" />
<xsl:template match="/|comment()|processing-instruction()">
<xsl:copy>
<xsl:apply-templates />
</xsl:copy>
</xsl:template>
<xsl:template match="*">
<xsl:element name="{local-name()}">
<xsl:apply-templates select="#*|node()" />
</xsl:element>
</xsl:template>
<xsl:template match="#*">
<xsl:attribute name="{local-name()}">
<xsl:value-of select="." />
</xsl:attribute>
</xsl:template>
</xsl:stylesheet>
Change
<xsl:template match="*">
<xsl:element name="{local-name()}">
<xsl:apply-templates select="#*|node()" />
</xsl:element>
</xsl:template>
to
<xsl:template match="/*//*">
<xsl:element name="{local-name()}">
<xsl:apply-templates select="#*|node()" />
</xsl:element>
</xsl:template>
and add a template
<xsl:template match="/*">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
--- edited in view of changed requirement ---
To remove the ns0 prefix from all elements in the input XML, while keeping them in the original namespace, do:
XSLT 1.0
<xsl:stylesheet version="1.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="*"/>
<xsl:template match="*">
<xsl:element name="{local-name()}" namespace="{namespace-uri()}">
<xsl:apply-templates/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>

xslt for converting one element to attribute among multiple

I have an xml with subelements and need to change one of the subelement to attribute using XSLT 1.0
<TransportationRequest>
<actionCode>01</actionCode>
<ContractConditionCode>DC</ContractConditionCode>
<ShippingTypeCode>17</ShippingTypeCode>
<MovementTypeCode>3</MovementTypeCode>
<DangerousGoodsIndicator>false</DangerousGoodsIndicator>
<DefaultCurrencyCode>SAR</DefaultCurrencyCode>
The Expected xml is as below using the XSLT code:
<TransportationRequest actionCode="01">
<ContractConditionCode>DC</ContractConditionCode>
<ShippingTypeCode>17</ShippingTypeCode>
<MovementTypeCode>3</MovementTypeCode>
<DangerousGoodsIndicator>false</DangerousGoodsIndicator>
<DefaultCurrencyCode>SAR</DefaultCurrencyCode>
First, close the root tag on your xml:
<TransportationRequest>
<actionCode>01</actionCode>
<ContractConditionCode>DC</ContractConditionCode>
<ShippingTypeCode>17</ShippingTypeCode>
<MovementTypeCode>3</MovementTypeCode>
<DangerousGoodsIndicator>false</DangerousGoodsIndicator>
<DefaultCurrencyCode>SAR</DefaultCurrencyCode>
</TransportationRequest>
then this xsl will do what you asked for:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" encoding="UTF-8" />
<xsl:template match="/">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="#*|node()">
<!-- Copy every node as is -->
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="TransportationRequest">
<!-- transform the TransportationRequest node in a special way -->
<xsl:element name="TransportationRequest">
<xsl:attribute name="actionCode"><xsl:value-of select="actionCode" /></xsl:attribute>
<!-- don't transform the actionCode node (is in the attribute) -->
<xsl:apply-templates select="#*|node()[name()!='actionCode']"/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
You could to it like this :
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="TransportationRequest">
<xsl:copy>
<xsl:attribute name="actionCode">
<xsl:value-of select="actionCode"/>
</xsl:attribute>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:template match="actionCode"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
See it working here : https://xsltfiddle.liberty-development.net/naZYrpW/1

XSLT Tags attributes are getting lost

Input XML
<?xml version="1.0" encoding="UTF-8"?>
<web-inf metadata-complete="true">
<A>
<A1>DGDDG</A1>
<A1>TYTY</A1>
</A>
</web-inf>
When i am applying my transforms then the O/P XML is just dumping the <web-inf> tag without the metadata-complete="true" i.e as below
<?xml version="1.0" encoding="UTF-8"?>
<web-inf>
<A>
<A1>DGDDG</A1>
<A1>TYTY</A1>
</A>
</web-inf>
My XSLT Transform file has below in the beginning.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="yes" />
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="web-inf[not(A/A1='hello')]">
<xsl:copy>
<xsl:call-template name="XXX"/>
<xsl:apply-templates />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Not sure what going wrong here.
Any suggestions?
<xsl:copy> copies only the current node, but not any attributes or child nodes. You are already catering for the child nodes with <xsl:apply-templates /> (which is equivalent to <xsl:apply-templates select="node()" />), but you also need to handle selecting attributes separately.
<xsl:template match="web-inf[not(A/A1='hello')]">
<xsl:copy>
<xsl:apply-templates select="#*" />
<xsl:call-template name="XXX"/>
<xsl:apply-templates />
</xsl:copy>
</xsl:template>

xsl:strip-space combined with xsl:text messes up automatic indentation

Using XSLT 1.0 I would like to comment out certain XML elements and replace other XML elements, while keeping the XML nicely formatted.
For example, the following XML document
<doc>
<e1>foo</e1>
<e2>bar</e2>
</doc>
should be converted to
<doc>
<!--<e1>foo</e1>-->
<e3>foobar</e3>
<e4>foobar</e4>
</doc>
I am using the following XSL transformation and xsltproc for testing it:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:strip-space elements="*" />
<xsl:output indent="yes" />
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*" />
</xsl:copy>
</xsl:template>
<xsl:template match="e1">
<xsl:text disable-output-escaping="yes"><!--</xsl:text> <!--*-->
<xsl:copy>
<xsl:apply-templates />
</xsl:copy>
<xsl:text disable-output-escaping="yes">--></xsl:text> <!--*-->
</xsl:template>
<xsl:template match="e2">
<e3>foobar</e3><e4>foobar</e4>
</xsl:template>
</xsl:stylesheet>
But what I get is this:
<doc><!--<e1>foo</e1>--><e3>foobar</e3><e4>foobar</e4></doc>
The problem seems to be caused by the lines marked with '*' in my transformation; more specifically from inserting <!-- and -->. When I remove these two elements, the result is indented as expected.
Is there a way to wrap elements in comments while still keeping the output document nicely formatted?
Try whether outputting a comment with the serialization of the element, as in
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:import href="http://lenzconsulting.com/xml-to-string/xml-to-string.xsl"/>
<xsl:strip-space elements="*"/>
<xsl:output indent="yes"/>
<xsl:template match="/">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*" />
</xsl:copy>
</xsl:template>
<xsl:template match="e1">
<xsl:comment>
<xsl:call-template name="xml-to-string"></xsl:call-template>
</xsl:comment>
</xsl:template>
<xsl:template match="e2">
<e3>foobar</e3><e4>foobar</e4>
</xsl:template>
</xsl:stylesheet>
gives you a better result.

How to remove the rootnodes using XSLT?

Input file:
<?xml version="1.0" encoding="UTF-8"?>
<ns0:root xmlns:ns0="http://xyz.com/separate">
<ns0:root1>
<ns3:Detail xmlns:ns3="http://POProject/Details">
<DetailLines>
<ItemID>
<ItemDescription/>
</DetailLines>
</ns3:Detail>
</ns0:root1>
</ns0:root>
Output file:
<?xml version="1.0" encoding="UTF-8"?>
<ns0:Detail xmlns:ns0="http://POProject/Details">
<DetailLines>
<ItemID>
<ItemDescription/>
</DetailLines>
</ns0:Detail>
Question: I have to remove the root1 and root nodes and need to do small
changes in Detail node. How to write a xslt code to achieve this?
This...
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ns0="http://xyz.com/separate"
xmlns:ns3="http://POProject/Details">
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*" />
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/">
<xsl:apply-templates select="*/*/ns3:Detail" />
</xsl:template>
<xsl:template match="ns3:Detail">
<xsl:apply-templates select="." mode="copy-sans-namespace" />
</xsl:template>
<xsl:template match="*" mode="copy-sans-namespace">
<xsl:element name="{name()}" namespace="{namespace-uri()}">
<xsl:copy-of select="#*"/>
<xsl:apply-templates mode="copy-sans-namespace" />
</xsl:element>
</xsl:template>
</xsl:stylesheet>
...will yield this...
<?xml version="1.0" encoding="utf-8"?>
<ns3:Detail xmlns:ns3="http://POProject/Details">
<DetailLines>
<ItemID />
<ItemDescription />
</DetailLines>
</ns3:Detail>
I'm not sure it is possible to control the prefix. The XDM data model does not consider it to be significant information.
UDPATE
To get the prefix rename, I thought you would have to go to an XML 1.1 supporting XSLT processor (allowing prefix undefine), but I found a way to do it with XML 1.0 . Try this ...
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ns0="http://xyz.com/separate">
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*" />
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/" xmlns:ns3="http://POProject/Details">
<xsl:apply-templates select="*/*/ns3:Detail" />
</xsl:template>
<xsl:template match="ns0:Detail" xmlns:ns0="http://POProject/Details">
<ns0:Detail xmlns:ns0="http://POProject/Details">
<xsl:apply-templates select="*" mode="copy-sans-namespace" />
</ns0:Detail>
</xsl:template>
<xsl:template match="*" mode="copy-sans-namespace">
<xsl:element name="{name()}" namespace="{namespace-uri()}">
<xsl:copy-of select="#*"/>
<xsl:apply-templates mode="copy-sans-namespace" />
</xsl:element>
</xsl:template>
</xsl:stylesheet>