views:

421

answers:

3

Ok, I've seen numerous variations on this question, but none exactly answer what I'm trying to solve and perhaps I'm just too dense to see how to apply one of the other answers to what I'm trying to do.

I have some XML that looks something like the following:

<?xml version="1.0" encoding="utf-8"?>
<message>
  <cmd id="api_info">
    <api-version>1.0</api-version>
    <api-build>1.0.0.0</api-build>
  </cmd>
</message>

Now I have an XSLT transform that I'm applying to this XML. The XSLT is similar to the following:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:fo="http://www.w3.org/1999/XSL/Format"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:fn="http://www.w3.org/2005/xpath-functions"
    version="2.0">

 <xsl:output method="xml" version="1.0" indent="yes"/>

 <xsl:template match="/">
  <xsl:apply-templates select="message"/>
 </xsl:template>

 <xsl:template match="message">
  <xsl:element name="message" xmlns="http://www.companyname.com/schemas/product/Version001"&gt;
   <xsl:apply-templates select="/message/cmd/@id"/>
  </xsl:element>
 </xsl:template>

 <xsl:template match="/message/cmd/@id">
  <xsl:variable name="_commandType" select="/message/cmd/@id"/>
  <xsl:element name="messageHeader">
            <xsl:element name="cmdType">
                <xsl:value-of select="$_commandType"/>
            </xsl:element>
  </xsl:element>

        <xsl:element name="messageBody">
            <xsl:choose>
                <xsl:when test="$_commandType = 'api_info'">
                    <xsl:element name="apiInfoBody">
                        <xsl:element name="apiVersion">
                            <xsl:value-of select="/message/cmd/api-version"/>
                        </xsl:element>
                        <xsl:element name="apiBuild">
                            <xsl:value-of select="/message/cmd/api-build"/>
                        </xsl:element>
                    </xsl:element>
                </xsl:when>
                <xsl:when test="$_commandType = 'communicationError'">
                    <xsl:element name="communicationErrorBody">
                        <xsl:element name="errorCode">
                            <xsl:value-of select="error-code"/>
                        </xsl:element>
                        <xsl:element name="badCmd">
                            <xsl:value-of select="bad-cmd"/>
                        </xsl:element>
                    </xsl:element>
                </xsl:when>
            </xsl:choose>
  </xsl:element>
 </xsl:template>
</xsl:stylesheet>

The output I get is basically what I want and looks like the following:

<?xml version="1.0" encoding="UTF-8"?>
<message xmlns="http://www.companyname.com/schemas/product/Version001"&gt;
 <messageHeader xmlns="">
  <cmdType>api_info</cmdType>
 </messageHeader>
 <messageBody xmlns="">
  <apiInfoBody>
   <apiVersion>1.0</apiVersion>
   <apiBuild>1.0.0.0</apiBuild>
  </apiInfoBody>
 </messageBody>
</message>

But what I don't want are the xmlns="" attributes in the <messageHeader> and <messageBody> elements.

Now I've found that if I explicitly specify the namespace in the XSLT for those elements, then the unwanted attribute gets pushed down one level to the children of those attributes.

I could just go through my entire XSLT and explicitly add the xmlns=""http://www.companyname.com/schemas/product/Version001" attribute to each of my xsl:element definitions, but I know that there must be a more elegant way. We programmers are far too lazy to not have a shortcut for this kind of nonsense. If my XSLT didn't consist of something as simple as the shortened example, I be tempted to do it that way. But I know there must be a better way.

Does anyone know what I'm missing here?

Thanks,

AlarmTripper

+2  A: 

Use exclude-result-prefixes on the xsl:stylesheet tag with the prefix "#default"

The reference in w3c for this is HERE

EDIT: OK, I should have studied your XSL more carefully. Move the xmlns on the message tag up to the stylesheet tag. This will put ALL the result elements in the same namespace and result in one namespace attribute on the message tag. I ran this in Oxygen/XML and got the output you want.

Jim Garrison
Did not change the output XML.
AlarmTripper
Which XSLT processor are you using?
Jim Garrison
Yes, I confirmed that your new suggestion works as well and is probably a better solution than mine since it keeps the xmlns assignment up in the stylesheet element. And the #default option keeps me from having to exclude each of the other namespace prefixes.Thanks a lot. I've got a lot to learn about this XSLT stuff and I think namespaces are probably one of the trickier aspects of it.
AlarmTripper
I'm using Altova XMLSpy Professional. Just purchased it today to help get through some of this stuff.
AlarmTripper
Welcome to XSLT! There is a lot to learn, but you will find that it is a rich environment (especially XSLT 2). If you haven't already done so, get a copy of Michael Kay's XSLT/XPATH 2.0 reference (ISBN 0470192747 4th edition April 2008). I use my copy all the time, and it goes beyond being just a reference. Also check out the XSLT spec on w3c.org. It can be intimidating but isn't all that hard to read and is the ultimate definition of what XSLT is.
Jim Garrison
Oh, and Michael Kay's book has a fairly extensive discussion of namespaces :-)
Jim Garrison
A: 

Ok, I figured it out.

The XSLT I want looks like this:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:fo="http://www.w3.org/1999/XSL/Format"
                xmlns:xs="http://www.w3.org/2001/XMLSchema"
                xmlns:fn="http://www.w3.org/2005/xpath-functions"
                exclude-result-prefixes="xsl fo xs fn">

    <xsl:output method="xml" version="1.0" indent="yes"/>

    <xsl:template match="/">
        <xsl:apply-templates select="message"/>
    </xsl:template>

    <xsl:template match="message">
        <message namespace="http://www.companyname.com/schemas/product/Version001"&gt;
            <xsl:apply-templates select="/message/cmd/@id"/>
        </message>
    </xsl:template>

    <xsl:template match="/message/cmd/@id">
        <xsl:variable name="_commandType" select="/message/cmd/@id"/>

        <xsl:element name="messageHeader">
            <xsl:element name="cmdType">
                <xsl:value-of select="$_commandType"/>
            </xsl:element>
        </xsl:element>

        <xsl:element name="messageBody">
            <xsl:choose>
                <xsl:when test="$_commandType = 'api_info'">
                    <xsl:element name="apiInfoBody">
                        <xsl:element name="apiVersion">
                            <xsl:value-of select="/message/cmd/api-version"/>
                        </xsl:element>
                        <xsl:element name="apiBuild">
                            <xsl:value-of select="/message/cmd/api-build"/>
                        </xsl:element>
                    </xsl:element>
                </xsl:when>
                <xsl:when test="$_commandType = 'communicationError'">
                    <xsl:element name="communicationErrorBody">
                        <xsl:element name="errorCode">
                            <xsl:value-of select="error-code"/>
                        </xsl:element>
                        <xsl:element name="badCmd">
                            <xsl:value-of select="bad-cmd"/>
                        </xsl:element>
                    </xsl:element>
                </xsl:when>
            </xsl:choose>
        </xsl:element>
    </xsl:template>
</xsl:stylesheet>

The change that fixed it was to add the exclude-result-prefixes attribute to the <xsl:stylesheet> element and to change section that was:

    <xsl:template match="message">
        <xsl:element name="message" xmlns="http://www.companyname.com/schemas/product/Version001"&gt;
            <xsl:apply-templates select="/message/cmd/@id"/>
        </xsl:element>
    </xsl:template>

to be the following instead:

    <xsl:template match="message">
        <message namespace="http://www.companyname.com/schemas/product/Version001"&gt;
            <xsl:apply-templates select="/message/cmd/@id"/>
        </message>
    </xsl:template>

And now I'm happy again.

There are probably better ways to do it, but this is working for me. Any future suggestions are still welcome.

AlarmTripper
A: 

Jim Garrison's (updated) answer is all you should have needed. But the updated stylesheet you posted outputs a "namespace" attribute in the result, so I'm not sure what good that does you.

The key thing to understand is how the default namespace works. Don't think of xmlns as an attribute that you can output to the result. Instead, think of it as a lexical detail of your stylesheet, whose purpose is to set the default namespace for everything beneath it (the element itself and every one of its descendants until overridden). (In turn, it has the same function in the result XML, which has a very different structure than the stylesheet itself.)

Literal result elements in your stylesheet (e.g., <message>) as well as <xsl:element> instructions (e.g., <xsl:element name="message">) both take on the default namespace when the name supplied doesn't use a prefix. If you want all of them to take on that same namespace, then you'll need to put a default namespace at the top of your stylesheet, as Jim Garrison suggested.

Otherwise, you'll end up with some elements that aren't in that namespace, which is why the result contained xmlns="" (unsetting the namespace for those elements).

Evan Lenz
Thanks Evan! I appreciate all the help I can get here. I did read that 'xmlns' looks like an attrib, but it's not. I did go back and change the code so it matched what Jim suggested as well as added a namespace qualifier (terminology?) so that my namespace tag is now up in my 'stylesheet' definition and looks like 'xmlns:grf="http://www.blahblah.com" and then went back through to be sure that my elements are prefixed with "grf:".The help from you experts on here to be invaluable since no one else around my company has done much with XSLT or XPATH. I'm really enjoying learning it though!
AlarmTripper