Re: [xsl] Namespace de-duplication for dynamically generated elements

Subject: Re: [xsl] Namespace de-duplication for dynamically generated elements
From: Florent Georges <lists@xxxxxxxxxxxx>
Date: Tue, 9 Sep 2008 10:50:04 +0200 (CEST)
Ian Stokes-Rees wrote:

  Hi

> We want a single XSLT which will handle these without a priori
> knowledge of the QNames or namespace *inside* the @name attribute.

  First of all, rather than dealing with parsing the QName
myself, I would use resolve-QName():

    <xsl:template match="foo">
       <xsl:variable name="qn" select="resolve-QName(@name, .)"/>
       <xsl:element name="{ $qn }"
                    namespace="{ namespace-uri-from-QName($qn) }">
          <xsl:apply-templates select="@*|node()"/>
       </xsl:element>
    </xsl:template>

  I find that weird that when you have a QName (so a local
name, a prefix and a ns URI,) you can't just use:

    <xsl:element name="{ $qn }"/>

  Then what you describe doesn't match the output you
showed.  You should have got (without prefixes):

    <zip> apple </zip>
    <zap> orange </zap>

  Then what you described (having the ns declarations on the
subelements only) can be because you create a new element
instead of copying the initial order element.  What you want
is to ensure that the output element has the correct binding,
and you can do that copying the namespaces of order:

    <xsl:template match="order">
       <output>
          <xsl:copy-of select="namespace::*"/>
          <xsl:apply-templates select="@*|node()"/>
       </output>
    </xsl:template>

  But the namespace axis is deprecated in XPath 2.0.  So you
might want to use the following instead:

    <!--
        Same as <xsl:copy-of select="$context/namespace::*"/>,
        but without using the deprecated axis namespace::.
    -->
    <xsl:function name="fg:copy-namespaces">
       <xsl:param name="context" as="element()"/>
       <xsl:for-each select="in-scope-prefixes($context)">
          <xsl:namespace name="{ . }" select="
              namespace-uri-for-prefix(., $context)"/>
       </xsl:for-each>
    </xsl:function>

    <xsl:template match="order">
       <output>
          <xsl:sequence select="fg:copy-namespaces(.)"/>
          <xsl:apply-templates select="@*|node()"/>
       </output>
    </xsl:template>

  So here is a sample of transform that gives you what you
are after I think, for your sample:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform";
                xmlns:fg="http://www.fgeorges.org/xslt/functions";
                version="2.0">

   <!-- Modified Identity Transform pattern. -->
   <xsl:template match="@*|node()">
      <xsl:copy>
         <xsl:apply-templates select="@*|node()"/>
      </xsl:copy>
   </xsl:template>

   <!-- Copy initial ns bindings. -->
   <xsl:template match="order">
      <output>
         <xsl:copy-of select="namespace::*"/>
         <xsl:apply-templates select="@*|node()"/>
      </output>
   </xsl:template>

   <!-- Throw the @name away. -->
   <xsl:template match="foo/@name"/>

   <!-- Change the name of the element regarding @name. -->
   <xsl:template match="foo">
      <xsl:variable name="qn" select="resolve-QName(@name, .)"/>
      <xsl:element name="{ $qn }"
                   namespace="{ namespace-uri-from-QName($qn) }">
         <xsl:apply-templates select="@*|node()"/>
      </xsl:element>
   </xsl:template>

   <!--
       Same as <xsl:copy-of select="$context/namespace::*"/>,
       but without using the deprecated namespace:: axis.
   -->
   <xsl:function name="fg:copy-namespaces">
      <xsl:param name="context" as="element()"/>
      <xsl:for-each select="in-scope-prefixes($context)">
         <xsl:namespace name="{ . }" select="
             namespace-uri-for-prefix(., $context)"/>
      </xsl:for-each>
   </xsl:function>

</xsl:stylesheet>

  Regards,

--drkm

Current Thread