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




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>
                         <xsd:element ref="common:MyCommonElement"/>
                         <xsd:element ref="custom:MyCustomExtension"/>
                    </xsd:complexType>
               </xsd:element>
          </xsd:schema>
     </xsl:template>
</xsl:stylesheet>

I was able to draw upon the issue of dynamic namespace in the result document as discussed on the XSL list - and here is a thread. Once again Jeni Tennison is on the case.

http://www.biglist.com/lists/xsl-list/archives/200206/msg01350.html

http://www.biglist.com/lists/xsl-list/archives/200206/msg01352.html

http://www.biglist.com/lists/xsl-list/archives/200206/msg01353.html




Click here to visit the Radio UserLand website. © Copyright 2007 Paul Kiel.
Last update: 9/22/2007; 4:15:38 PM.