add namespace prefix to element and subnodes with xslt - xslt

I'd like to copy only header element with all subnodes and add to every subnode prefix "v11"(including header element)
Source xml:
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<ns3:createReservationRequest xmlns:ns3="ns3URL" xmlns:ns2="ns2URL">
<header>
<language isoCountryCode="US" isoLanguageCode="en"/>
<channel name="DT">
<subChannel name="WEBWB">
<subChannel name="WEBWB">
<subChannel name="Functester">
<subChannel name="ecom"/>
</subChannel>
</subChannel>
</subChannel>
</channel>
</header>
<ns3:agentInfo>
<ns2:agentDutyCode>PR</ns2:agentDutyCode>
</ns3:agentInfo>
</ns3:createReservationRequest>
</soap:Body>
</soap:Envelope>
Desired result xml:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:v1="v1URL"
xmlns:v11="v11URL">
<soapenv:Body>
<v1:createBookerEventRequest>
<v11:header>
<v11:channel name="DT">
<v11:subChannel name="WEBWB">
<v11:subChannel name="WEBWB">
<v11:subChannel name="Functester">
<v11:subChannel name="ecom"/>
</v11:subChannel>
</v11:subChannel>
</v11:subChannel>
</v11:channel>
</v11:header>
</v1:createBookerEventRequest>
</soapenv:Body>
</soapenv:Envelope>
I've tried to implement this using example from here . I've written the following xsl:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:v11="v11URL">
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
<xsl:template match="/">
<xsl:copy>
<xsl:apply-templates select="//*[local-name()='header']/*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="//*[local-name()='header']/*">
<xsl:element name="v11:{name()}" inherit-namespaces="no">
<xsl:apply-templates select="node()|#*"/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
But it doesn't copy subchannels into result xml. And also adds unwanted "xmlns:v11="http://example.com/schema/common/ATPCommonServiceTypes/v1" attribute to header subnodes. Any help is appreciated

Here is my (edited) suggestion:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:v1="v1URL"
xmlns:v11="v11URL"
xmlns:ns3="ns3URL"
exclude-result-prefixes="soap ns3">
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* , node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/*">
<xsl:copy>
<xsl:copy-of select="document('')/xsl:stylesheet/namespace::*[local-name() = ('v1', 'v11')]"/>
<xsl:apply-templates select="#* , node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*[not(namespace-uri())]">
<xsl:element name="v11:{local-name()}">
<xsl:apply-templates select="#* , node()"/>
</xsl:element>
</xsl:template>
<xsl:template match="ns3:createReservationRequest">
<v1:createBookerEventRequest>
<xsl:apply-templates select="#* , node()"/>
</v1:createBookerEventRequest>
</xsl:template>
<xsl:template match="ns3:agentInfo"/>
</xsl:stylesheet>

I added a xmlns declaration to agentDutyCode since it is missing a namespace declaration:
<ns2:agentDutyCode xmlns:ns2="ns2URL">PR</ns2:agentDutyCode>
Using the source with this stylesheet (templates explained in the comments):
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:v11="v11URL"
xmlns:v1="v1URL"
xmlns:ns3="ns3URL"
exclude-result-prefixes="ns3">
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
<xsl:strip-space elements="*"/>
<!-- Copies Envelope and Body preserving their namespace -->
<xsl:template match="soap:Envelope | soap:Body">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<!-- Creates the createReservationRequest element -->
<xsl:template match="ns3:createReservationRequest">
<v1:createBookerEventRequest>
<xsl:apply-templates/>
</v1:createBookerEventRequest>
</xsl:template>
<!-- Ignores language and agentInfo subtrees -->
<xsl:template match="language"/>
<xsl:template match="ns3:agentInfo"/>
<!-- Matches all other elements -->
<xsl:template match="*">
<xsl:element name="v11:{local-name()}" inherit-namespaces="no">
<xsl:apply-templates select="node()|#*"/>
</xsl:element>
</xsl:template>
<!-- Copies attributes -->
<xsl:template match="#*">
<xsl:copy>
<xsl:value-of select="."/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
You will have this result:
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<v1:createBookerEventRequest xmlns:v11="v11URL" xmlns:v1="v1URL">
<v11:header>
<v11:channel name="DT">
<v11:subChannel name="WEBWB">
<v11:subChannel name="WEBWB">
<v11:subChannel name="Functester">
<v11:subChannel name="ecom"/>
</v11:subChannel>
</v11:subChannel>
</v11:subChannel>
</v11:channel>
</v11:header>
</v1:createBookerEventRequest>
</soap:Body>
</soap:Envelope>
Here is an XSLT Fiddle where you can see the result.

Related

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

How to remove the name space from the XML tags

Could you please tell me how could I remove only the name space xmlns="http://ws.apache.org/ns/synapse" from the XML tags?
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:urn="urn:sap-com:document:sap:rfc:functions">
<soapenv:Body>
<urn:BAPI_QM_DEFECT_RECORDING>
<AMOUNT xmlns="http://ws.apache.org/ns/synapse">1</AMOUNT>
<DEFECT_CODE xmlns="http://ws.apache.org/ns/synapse">393</DEFECT_CODE>
<DEFECT_DESC xmlns="http://ws.apache.org/ns/synapse">393</DEFECT_DESC>
<DEFECT_PID xmlns="http://ws.apache.org/ns/synapse">601000</DEFECT_PID>
<INSPID xmlns="http://ws.apache.org/ns/synapse"/>
<ORDER xmlns="http://ws.apache.org/ns/synapse">20262950</ORDER>
<ORIGIN_PID xmlns="http://ws.apache.org/ns/synapse">600000</ORIGIN_PID>
<OVER_CONSUMP xmlns="http://ws.apache.org/ns/synapse">text</OVER_CONSUMP>
</urn:BAPI_QM_DEFECT_RECORDING></soapenv:Body></soapenv:Envelope>
you can use below code by creating element use local-name which will take name without namespaces:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:urn="urn:sap-com:document:sap:rfc:functions">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="urn:BAPI_QM_DEFECT_RECORDING/*">
<xsl:element name="{local-name(.)}">
<xsl:apply-templates/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>

XSLT copy without segment name

I have the following XML:
<segment>
<personal_information>
<birth_name>xxx</birth_name>
<created_by>yyy</created_by>
<created_on_timestamp>2018-08-06T06:41:07.000Z</created_on_timestamp>
</personal_information>
<segment>
I want to copy the entire personal_information segment with all elements and sub-segments while adding a new field. I tried this with:
<segment>
<personal_information>
<action>DELETE</action>
<xsl:copy>
<xsl:apply-templates select="child::node()"/>
</xsl:copy>
</personal_information>
</segment>
But this results in the following:
<segment>
<personal_information>
<action>DELETE</action>
<personal_information>
<birth_name>xxx</birth_name>
<created_by>yyy</created_by>
<created_on_timestamp>2018-08-06T06:41:07.000Z</created_on_timestamp>
</personal_information>
</personal_information>
</segment>
Would would be the XSLT code to achieve this as a result:
<segment>
<personal_information>
<action>DELETE</action>
<birth_name>xxx</birth_name>
<created_by>yyy</created_by>
<created_on_timestamp>2018-08-06T06:41:07.000Z</created_on_timestamp>
</personal_information>
</segment>
I do not want to copy all fields one by one.
Instead of using <xsl:copy>, use <xsl:copy-of>, which accepts an XPath expression for the nodes to be included in the copy; in this case only the inner childnodes.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="segment">
<segment>
<personal_information>
<action>DELETE</action>
<xsl:copy-of select="personal_information/*" />
</personal_information>
</segment>
</xsl:template>
</xsl:stylesheet>
You need a identity template and one personal_information with adding one action element:
<?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:output indent="yes"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="personal_information">
<xsl:copy>
<action>DELETE</action>
<xsl:apply-templates select="node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Updated as per new requirement :
<?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:output indent="yes"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="personal_information">
<action>DELETE</action>
<xsl:apply-templates select="node()"/>
</xsl:template>
</xsl:stylesheet>

XSLT - transform several sibling tags, but keep the rest intact

I'm trying to transform my input XML:
<?xml version="1.0" encoding="utf-8"?>
<request>
<customer_data>
<contact_info>
<name>john doe</name>
<dob_year>1984</dob_year>
<dob_month>09</dob_month>
<dob_date>14</dob_date>
<gender>m</gender>
</contact_info>
</customer_data>
</request>
so that it looks like this:
<?xml version="1.0" encoding="utf-8"?>
<request>
<customer_data>
<contact_info>
<name>john doe</name>
<dob>1984-09-14</dob>
<gender>m</gender>
</contact_info>
</customer_data>
</request>
Here is the XSLT, that I'm using:
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="2.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="contact_info">
<xsl:copy>
<dob>
<xsl:value-of select="(dob_year, dob_month, dob_date)" separator="-"/>
</dob>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
And here is the result that I get:
<?xml version="1.0" encoding="utf-8"?><request>
<customer_data>
<contact_info><dob>1984-09-14</dob></contact_info>
</customer_data>
</request>
How do I transform the dob_* tags, keeping the rest of contact_info intact?
UPDATE
Based, on the answers below, my current solution is
<xsl:template match="contact_info">
<xsl:copy>
<xsl:apply-templates select="*[not(starts-with(name(), 'dob'))]"/>
<dob>
<xsl:value-of select="(dob_year, dob_month, dob_date)" separator="-"/>
</dob>
</xsl:copy>
</xsl:template>
This works for me, but isn't there more elegant way to express "apply transformation to given elements, and apply-templates to the rest"? Now, I'm kind of stuck with this expression *[not(starts-with(name(), 'dob'))] which is not that bad, but if the names of "DOB" attributes change, I'll have to fix this too.
Here's one way you could look at it:
<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="dob_year">
<dob>
<xsl:value-of select="(., ../dob_month, ../dob_date)" separator="-"/>
</dob>
</xsl:template>
<xsl:template match="dob_month|dob_date"/>
</xsl:stylesheet>
Here's another:
<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="contact_info">
<xsl:copy>
<xsl:copy-of select="name|gender"/>
<dob>
<xsl:value-of select="(dob_year, dob_month, dob_date)" separator="-"/>
</dob>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Edit:
In order to enumerate only the DOB elements and to do so only once, you could do:
<xsl:template match="contact_info">
<xsl:variable name="dob-fields" select="(dob_year, dob_month, dob_date)" />
<xsl:copy>
<xsl:apply-templates select="* except $dob-fields"/>
<dob>
<xsl:value-of select="$dob-fields" separator="-"/>
</dob>
</xsl:copy>
</xsl:template>
Change
<xsl:template match="contact_info">
<xsl:copy>
<dob>
<xsl:value-of select="(dob_year, dob_month, dob_date)" separator="-"/>
</dob>
</xsl:copy>
</xsl:template>
to
<xsl:template match="contact_info">
<xsl:copy>
<xsl:apply-templates select="dob_year/preceding-sibling::node()"/>
<dob>
<xsl:value-of select="(dob_year, dob_month, dob_date)" separator="-"/>
</dob>
<xsl:apply-templates select="dob_date/following-sibling::node()"/>
</xsl:copy>
</xsl:template>

Getting prefix of a namespace

I'm developing an application and I need to add several elements into the header of soap message I receive. The problem is I don't know which prefix the namespace I'm using for adding these elements is using, but it's sure there will be several elements in the body using this prefix, so the namespace is already declared in the message.
For example, I'm receiving this message:
<soapenv:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:urn="urn:enterprise.soap.sforce.com" xmlns:urn1="urn:sobject.enterprise.soap.sforce.com">
<soapenv:Header/>
<soapenv:Body>
<urn:operation>
</urn:operation>
</soapenv:Body>
</soapenv:Envelope>
And the xpath expression I use for adding these elements in the header is:
<xsl:stylesheet version="1.0" exclude-result-prefixes="xsi xsl" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<xsl:output method="xml" encoding="UTF-8" indent="yes" omit-xml-declaration="yes"/>
<xsl:param name="Getsed">aBcDeFgHiJkLmNñOpQrStUvWxYz</xsl:param>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="//*[local-name()='Header']">
<xsl:copy>
<urn:SH xmlns:urn="urn:enterprise.soap.sforce.com">
<urn:sed><xsl:value-of select="$Getsed"/></urn:sed>
</urn:SH>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
I would like to use the prefix (urn) already declared for the namespace urn:enterprise.soap.sforce.com.
Could you please give me a hand?
I believe a silly workaround like <xsl:value-of select="substring-before(name(//*[namespace-uri() = 'urn:enterprise.soap.sforce.com']), ':')" /> will fit your needs.
Thus your template can be rewrited that way:
<xsl:template match="*[local-name()='Header']">
<!-- this will retrieve the namespace prefix in source document -->
<xsl:variable name="ns-sforce">
<xsl:value-of select="substring-before(name(//*[namespace-uri() = 'urn:enterprise.soap.sforce.com']), ':')" />
</xsl:variable>
<xsl:copy>
<!-- create prefixed elements with the same value as before -->
<xsl:element name="{$ns-sforce}:SH" namespace="urn:enterprise.soap.sforce.com">
<xsl:element name="{$ns-sforce}:sed" namespace="urn:enterprise.soap.sforce.com">
<xsl:value-of select="$Getsed"/>
</xsl:element>
</xsl:element>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
Note: <xsl:template match="//*[local-name()='Header']"> can be replaced by <xsl:template match="*[local-name()='Header']">