Re: [xsl] [XSLT 1.0] Strip unused namespaces, but retain namespace declarations for elements with QName values?

Subject: Re: [xsl] [XSLT 1.0] Strip unused namespaces, but retain namespace declarations for elements with QName values?
From: "G. Ken Holman" <gkholman@xxxxxxxxxxxxxxxxxxxx>
Date: Wed, 30 Mar 2011 15:03:11 -0400
At 2011-03-30 13:50 -0400, Costello, Roger L. wrote:
Hi Folks,

I want to transform this:

--------------------------------------------
<t:Test xmlns:t="http://www.test.org";
        xmlns:unused="http://www.unused.org";
        xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/";>

<t:faultcode>SOAP:client</t:faultcode>

</t:Test>
--------------------------------------------

to this:

------------------------------------
<Test xmlns="http://www.test.org";
      xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/";>

<faultcode>SOAP:client</faultcode>

</Test>
------------------------------------

That is, I want to:

1. Replace namespace-qualified elements with a default namespace, e.g.,

Change this: <t:Test xmlns:t="..."

to this: <Test xmlns="..."


2. Delete unused namespace declarations, e.g.,


Delete this: xmlns:unused="..."


3. Retain a namespace declaration if it declares a namespace prefix that is used in an element QName value, e.g.,


Retain this: xmlns:soap="..."

because the prefix is used in this element's value: <faultcode>SOAP:client</faultcode>


The following template rule does (1) and (2) but fails to implement (3):


<xsl:template match="*[namespace-uri()]">

        <xsl:element name="{local-name()}"
                     namespace="{namespace-uri()}">
            <xsl:apply-templates select="@* | node()"/>
        </xsl:element>

</xsl:template>

Any ideas? I am using XSLT 1.0

First, you don't need to just match on elements with a namespace-uri() because if there isn't one the code still works.


Second, it will slow down your transformation, and it isn't elegant at all, but all you have to do is copy all descendent namespace nodes that are not being used. The first step is to do this for elements that are using the namespace except those using the parent's namespace:

<xsl:template match="*">
  <xsl:element name="{local-name()}" namespace="{namespace-uri()}">
    <xsl:for-each select=".//namespace::*">
       <xsl:if test="(..//*)[namespace-uri()=current() and
                             namespace-uri()!=namespace-uri(current()/..)]
         <xsl:copy-of select="."/>
       </xsl:if>
    </xsl:for-each>
    <xsl:apply-templates select="@*|node()"/>
  </xsl:element>
</xsl:template>

Then you have to modify that to include whatever test you need for checking the name. You don't say how specific you need to be, whether you are checking only <t:faultcode> elements or are you checking all elements. The following checks all elements for starting with the prefix ... I have no idea how specific you need to be for poorly formatted text content (e.g. leading spaces):

<xsl:template match="*">
  <xsl:element name="{local-name()}" namespace="{namespace-uri()}">
    <xsl:for-each select=".//namespace::*">
      <!-- ( ( find an element using the namespace but
                               not using the parent's namespace ) or
             ( find text content beginning with the namespace prefix ) )-->
       <xsl:if test="(..//*)[namespace-uri()=current() and
                             namespace-uri()!=namespace-uri(current()/..)] or
                     (..|..//*)[starts-with(.,concat(name(current()),':'))]">
         <xsl:copy-of select="."/>
       </xsl:if>
    </xsl:for-each>
    <xsl:apply-templates select="@*|node()"/>
  </xsl:element>
</xsl:template>

Let the processor worry about cleaning up namespace declarations in the result.

BTW, although your input is XML well-formed, the prefix you use in your text does not match the SOAP namespace declaration. I'll assume that is an oversight and that you don't want to do case conversion and that you would rather have the text as the same case as the namespace prefix.

And, why are you bothered about superfluous namespaces? There is no XML processing reason for getting rid of them.

And why restrict yourself to XSLT 1 when XSLT 2 can make this more succinct?

I hope this helps.

. . . . . . . . . Ken

~/t/ftemp $ cat roger.xml
<t:Test xmlns:t="http://www.test.org";
        xmlns:unused="http://www.unused.org";
        xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/";>

<t:faultcode>soap:client</t:faultcode>

</t:Test>
~/t/ftemp $ xslt roger.xml roger.xsl
<?xml version="1.0" encoding="utf-8"?><Test xmlns="http://www.test.org"; xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/";>


<faultcode>soap:client</faultcode>

</Test>~/t/ftemp $
~/t/ftemp $ cat roger.xsl
<?xml version="1.0" encoding="US-ASCII"?>
<?xml-stylesheeta href="abc.xsl" type="text/xsl"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform";
                version="1.0">

<xsl:template match="@*|node()"><!--identity for all other nodes-->
  <xsl:copy>
    <xsl:copy-of select=".//namespace::*"/>
    <xsl:apply-templates select="@*|node()"/>
  </xsl:copy>
</xsl:template>

<xsl:template match="*" priority="1">
  <xsl:element name="{local-name()}" namespace="{namespace-uri()}">
    <xsl:for-each select=".//namespace::*">
      <!-- ( ( find an element using the namespace but
                               not using the parent's namespace ) or
             ( find text content beginning with the namespace prefix ) )-->
       <xsl:if test="(..//*)[namespace-uri()=current() and
                             namespace-uri()!=namespace-uri(current()/..)] or
                     (..|..//*)[starts-with(.,concat(name(current()),':'))]">
         <xsl:copy-of select="."/>
       </xsl:if>
    </xsl:for-each>
    <xsl:apply-templates select="@*|node()"/>
  </xsl:element>
</xsl:template>

</xsl:stylesheet>~/t/ftemp $


-- Contact us for world-wide XML consulting & instructor-led training Crane Softwrights Ltd. http://www.CraneSoftwrights.com/s/ G. Ken Holman mailto:gkholman@xxxxxxxxxxxxxxxxxxxx Legal business disclaimers: http://www.CraneSoftwrights.com/legal

Current Thread