I am trying to convert an XML document to one with a new root element, but which shares many other common element namespaces. I am having troubles with the namespace transformation.
This is the input XML:
<CreditNote
xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2"
xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2"
xmlns="urn:oasis:names:specification:ubl:schema:xsd:CreditNote-2">
<cbc:CustomizationID>12345</cbc:CustomizationID>
<cbc:ProfileID>67890</cbc:ProfileID>
<cbc:ID>abcdef</cbc:ID>
<cac:BillingReference>
<cac:InvoiceDocumentReference>
<cbc:ID>ghijk</cbc:ID>
<cbc:IssueDate>2024-08-05</cbc:IssueDate>
</cac:InvoiceDocumentReference>
</cac:BillingReference>
</CreditNote>
This is the output XML I want:
<?xml version="1.0" encoding="UTF-8"?>
<ubl:Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2"
xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2"
xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2"
xmlns:ubl="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2">
<cbc:ID>abcdef</cbc:ID>
<cac:BillingReference>
<cac:InvoiceDocumentReference>
<cbc:ID>ghijk</cbc:ID>
</cac:InvoiceDocumentReference>
</cac:BillingReference>
</ubl:Invoice>
The root node (or namespace) is meant to switch from "urn:oasis:names:specification:ubl:schema:xsd:CreditNote-2" to "urn:oasis:names:specification:ubl:schema:xsd:Invoice-2", while sharing the common namespaces: cac
and cbc
.
I am using XSLT 3.0 and SAXON-HE 12.4. This is my XLST. I took some hints from this post: XSLT to rename qualified root element, keep other namespaces
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="3.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2"
xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2"
xmlns:cn="urn:oasis:names:specification:ubl:schema:xsd:CreditNote-2"
xmlns:ubl="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2"
xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2"
exclude-result-prefixes="cn">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/cn:CreditNote">
<ubl:Invoice
xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2"
xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2"
xmlns:ubl="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2"
xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2">
<xsl:apply-templates select="cbc:ID"/>
<xsl:apply-templates select="cac:BillingReference"/>
</ubl:Invoice>
</xsl:template>
<!--
Identity template, provides default behavior that copies all content into
the output Do not copy namespace attributes into elements.
-->
<xsl:template match="@* | node()">
<xsl:copy copy-namespaces="no">
<xsl:apply-templates select="@* | node()"/>
</xsl:copy>
</xsl:template>
<!--
Copy all elements from the CreditNote namespace, but change the namespace
to the default namespace (Invoice).
-->
<xsl:template match="cn:*">
<xsl:element name="{local-name()}">
<xsl:apply-templates select="@*|node()"/>
</xsl:element>
</xsl:template>
<xsl:template match="*/cac:BillingReference">
<xsl:copy>
<xsl:copy-of select="namespace::*[not(. = ('urn:oasis:names:specification:ubl:schema:xsd:CreditNote-2'))]"/>
<xsl:apply-templates select="cac:InvoiceDocumentReference"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
This is the output XML I get:
<?xml version="1.0" encoding="UTF-8"?>
<ubl:Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2"
xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2"
xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2"
xmlns:ubl="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2">
<cbc:ID>abcdef</cbc:ID>
<cac:BillingReference xmlns="urn:oasis:names:specification:ubl:schema:xsd:CreditNote-2">
<cac:InvoiceDocumentReference>
<cbc:ID>ghijk</cbc:ID>
<cbc:IssueDate>2024-08-05</cbc:IssueDate>
</cac:InvoiceDocumentReference>
</cac:BillingReference>
</ubl:Invoice>
I get that the XSLT is trying to preserve the source namespaces, but I am not clear on why my XSLT has not resolved this.
xmlns="urn:oasis:names:specification:ubl:schema:xsd:CreditNote-2"
on the templates it selected?cac:BillingReference
template being matched? (It should have trimmed cbc:IssueDate
.)Update - Thursday 29 August 2024, 04:53:09 PM
A note for anyone else who finds this post helpful: don't use xsl:copy
when trying to output the same element but also changing source namespaces. The XSLT below still outputs the old namespace. XSLT:
<xsl:template match="cac:BillingReference">
<xsl:copy>
<xsl:apply-templates select="cac:InvoiceDocumentReference"/>
</xsl:copy>
</xsl:template>
Output contains the old namespace:
<cac:BillingReference xmlns="urn:oasis:names:specification:ubl:schema:xsd:CreditNote-2">
<cac:AdditionalDocumentReference>
<cbc:ID>ghijk</cbc:ID>
</cac:AdditionalDocumentReference>
</cac:BillingReference>
So I need to output the XML element name directly or use some other XSL directives to do the job.
<xsl:template match="cac:BillingReference">
<cac:BillingReference>
<xsl:apply-templates select="*"/>
</cac:BillingReference>
</xsl:template>
xsl:element
is a very good tool for templates where you are matching multiple input elements so you don't necessarily know the element name.
<xsl:template match="cac:PostalAddress|cac:Address">
<xsl:element name="{name()}">
<xsl:apply-templates select="*"/>
</xsl:element>
</xsl:template>
I think the following suffices:
<xsl:stylesheet version="3.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2"
xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2"
xmlns:cn="urn:oasis:names:specification:ubl:schema:xsd:CreditNote-2"
xmlns:ubl="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2"
xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2"
exclude-result-prefixes="cn">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/cn:CreditNote">
<ubl:Invoice
xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2"
xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2"
xmlns:ubl="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2"
xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2">
<xsl:apply-templates select="cbc:ID"/>
<xsl:apply-templates select="cac:BillingReference"/>
</ubl:Invoice>
</xsl:template>
<!--
Identity template, provides default behavior that copies all content into
the output Do not copy namespace attributes into elements.
-->
<xsl:template match="@* | node()">
<xsl:copy copy-namespaces="no">
<xsl:apply-templates select="@* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="cbc:IssueDate"/>
</xsl:stylesheet>