Robert Mark Bram
Robert Mark Bram

Reputation: 9745

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>

Upvotes: 0

Views: 99

Answers (2)

michael.hor257k
michael.hor257k

Reputation: 117102

I suspect all you need to do is:

<xsl:stylesheet version="3.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
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">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>

<xsl:template match="@* | node()">
    <xsl:copy copy-namespaces="no">
        <xsl:apply-templates select="@* | node()"/>
    </xsl:copy>
</xsl:template>
   
<xsl:template match="/*">
    <ubl:Invoice>
        <xsl:apply-templates/>
    </ubl:Invoice>
</xsl:template>

<xsl:template match="cbc:CustomizationID | cbc:ProfileID | cbc:IssueDate"/>

</xsl:stylesheet>

Demo here.

Upvotes: 3

Martin Honnen
Martin Honnen

Reputation: 167716

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.

Upvotes: 2

Related Questions