xmlxsltnamespacesxml-namespacesxslt-3.0

Use XSLT to change root element and namespace but keep other common namespaces


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.

  1. How do I prevent it from outputting xmlns="urn:oasis:names:specification:ubl:schema:xsd:CreditNote-2" on the templates it selected?
  2. Why isn't the 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>

Solution

  • 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>
    

    Example Saxon HE fiddle.