Paul Kiel's Data Integration Blog
Data integration using Xml / Xslt and anything else...






Subscribe to "Paul Kiel's Data Integration Blog" in Radio UserLand.

Click to see the XML version of this web page.

Click here to send an email to the editor of this weblog..

dynamic namespaces with XSLT

I had an interesting problem to solve recently regarding Xml Schemas, XSLT and namespaces. The task was to use XSLT to create Xml Schema dynamically. This was something I had done before and figured I would pull upon previous work to give me a head start. The interesting part was the requirements around creating namespaces. Dynamically created namespaces can be tricky enough, especially if you are not yet using XSLT 2.0 as I was. The added feature was that I needed to use pre-defined namespace prefixes on those dynamically generated namespaces.

In short, I was tasked with generating namespaces in a resulting Xml Schema, based on data dynamically created at processing time, and with namespace prefixes predefined.

The tempation is to try something like this:

<xsl:attribute name="xmlns"><xsl:value-of select="$xmlns"/></xsl:attribute>

But that doesn't work. The solution is to create a dummy attribute and copy it to the result via the namespace axis and local-name.

In the scenario here, I want to create 3 namespaces, one for the default and target, a second for "common" components that are to be imported, and a third for "custom" components that are used for extensions.

<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://xmlns.myexample.com/version3" targetNamespace="http://xmlns.myexample.com/version3" xmlns:common="http://xmlns.myexample.com/common/version3" xmlns:custom="http://xmlns.myexample.com/custom/version3"

elementFormDefault ="qualified"
attributeFormDefault="unqualified">
 
<xsd:import
schemaLocation="common.xsd"/>
 
<xsd:import
schemaLocation="custom.xsd"/>
 
</xsd:schema>
 
Here is the XSLT.  I used result tree fragments here, so make sure your processor supports it.
 
<xsl:stylesheet version="1.1" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
     <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
     <xsl:template match="/">
          <!-- the version of the resulting schema -->
          <xsl:variable name="version">version3</xsl:variable>
          <!-- the version of the common components imported schema -->
          <xsl:variable name="commonVersion">version2</xsl:variable>
          <!-- the version of the custom components imported schema -->
          <xsl:variable name="customVersion">version1</xsl:variable>
          <!-- the targetNamespace -->
          <xsl:variable name="target">http://xmlns.myexample.com/<xsl:value-of select="$version"/>
          </xsl:variable>
          <!-- the common components namespace -->
          <xsl:variable name="common">http://xmlns.myexample.com/common/<xsl:value-of select="$commonVersion"/>
          </xsl:variable>
          <!-- the custom components namespace -->
          <xsl:variable name="custom">http://xmlns.myexample.com/custom/<xsl:value-of select="$customVersion"/>
          </xsl:variable>
          <!-- we cannot do this:
          ="http://xmlns.myexample.com/{$commonVersion}"
          we must add the namespaces generated above to dummy attributes -->
          <xsl:variable name="default-ns-node">
               <xsl:element name="default-ns-element" namespace="{$target}"/>
          </xsl:variable>
          <xsl:variable name="common-ns-node">
               <xsl:element name="common-ns-element" namespace="{$common}">
                    <xsl:attribute name="common:dummy" namespace="{$common}"/>
               </xsl:element>
          </xsl:variable>
          <xsl:variable name="custom-ns-node">
               <xsl:element name="custom-ns-element" namespace="{$common}">
                    <xsl:attribute name="custom:dummy" namespace="{$custom}"/>
               </xsl:element>
          </xsl:variable>
          <!-- ========================================== -->
          <xsd:schema>
               <!-- since we cannot do xsl:copy, we simply copy the namespace axis referring to the local-name only -->
               <xsl:copy-of select="$default-ns-node/*/namespace::*[local-name()='']"/>
               <xsl:copy-of select="$common-ns-node/*/namespace::*[local-name()='common']"/>
               <xsl:copy-of select="$custom-ns-node/*/namespace::*[local-name()='custom']"/>
               <!-- form defaults and targetNamespace attributes don't need special treatment, so they can be simply added with xsl:attribute -->
               <xsl:attribute name="elementFormDefault">qualified</xsl:attribute>
               <xsl:attribute name="attributeFormDefault">unqualified</xsl:attribute>
               <xsl:attribute name="targetNamespace"><xsl:value-of select="$target"/></xsl:attribute>
               <!-- ========================================== -->
               <!-- the namespace attribute in import statements can use variables directly -->
               <xsd:import namespace="http://xmlns.myexample.com/common/{$commonVersion}" schemaLocation="components.xsd"/>
               <xsd:import namespace="http://xmlns.myexample.com/custom/{$customVersion}" schemaLocation="custom.xsd"/>
               <xsd:element name="root">
                    <xsd:complexType>