Working with XML Namespaces in XSL Stylesheets

One of the things I like about working on a larger team is that there is plenty of opportunity to bounce questions off of co-workers and, as anyone who has ever answered a question on a forum or mailing list can tell you, you often learn as much trying to answer a question as you do by asking one.

This happened to me last week when a co-worker came to me with a question about XSLT. He had an XML document that looked something like the following:

<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" SOAP-ENV:encodingStyle="">
<SOAP-ENV:Header xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
...
</SOAP-ENV:Header>
<SOAP-ENV:Body xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<QueryResults xmlns="http://schemas.mischefamily.com">
<People xmlns="http://schemas.mischefamily.com">
<Person personId="12345" xmlns="http://schemas.mischefamily.com">
<PersonalData>
<Contact>
<StructuredName>
<GivenName>Nathan</GivenName>
<MiddleName></MiddleName>
<FamilyName>Mische</FamilyName>
</StructuredName>
</Contact>
</PersonalData>
</Person>
</People>
</QueryResults>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>

He wanted to transform it into:

<People>
<Person>
<FirstName>Nathan</FirstName>
<LastName>Mische</LastName>   
</Person>
</People>

He had the beginnings of an XSL stylesheet which had the SOAP-ENV namespace declared in the xsl:styleheet root element, but his search sting, "//SOAP-ENV:Body/QueryResults/People", wasn't returning any results. Just looking at the original XML document I knew the problem had something to do with namespaces, but I wasn't quite sure how to handle the namespaces defined in the QueryResults, People, and Person nodes. Turns out that the answer was fairly simple; we just needed to declare another prefixed namespace in the stylesheet root element pointing to http://schemas.mischefamily.com.

<?xml version="1.0" encoding='utf-8' ?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:MISCHE="http://schemas.mischefamily.com">

<xsl:output method="xml" indent="yes"/>
<xsl:template match="/">
<People xsl:exclude-result-prefixes="SOAP-ENV MISCHE">
<xsl:for-each select="//SOAP-ENV:Body/MISCHE:QueryResults/MISCHE:People"> <Person>
<FirstName><xsl:value-of select="./MISCHE:Person/MISCHE:PersonalData
/MISCHE:Contact/MISCHE:StructuredName/MISCHE:GivenName"
/>
</FirstName>
<LastName><xsl:value-of select="./MISCHE:Person/MISCHE:PersonalData
/MISCHE:Contact/MISCHE:StructuredName/MISCHE:FamilyName"
/>
</LastName>
</Person>
</xsl:for-each>
</People>
</xsl:template>
</xsl:stylesheet>

There were a few things, however, that threw me for a loop while coming up with this stylesheet. First, I originally thought that PersonalData and all the nodes below it were in the local namespace because everything above that node (QueryResults, People, Person) had a xmlns attribute defined. Based on this assumption my first xsl:value-of select statement for the transformed FirstName node looked like "./MISCHE:Person/PersonalData/Contact/StructuredName/GivenName". The problem was this statement didn't return anything. After some quick research I realized this was because the namespace declared in the xmlns attribute is inherited. This meant that all of the elements below the Person element in the source XML were also in the http://schemas.mischefamily.com namespace and needed to be prefixed with MISCHE in the select statements of my stylesheet. As a side note, to make my original query work you would need to force child nodes back into the local namespace, which you can do if you can give the xmlns attribute an empty value like so:

...
<QueryResults xmlns="http://schemas.mischefamily.com">
<People xmlns="http://schemas.mischefamily.com">
<Person personId="12345" xmlns="http://schemas.mischefamily.com">
<PersonalData xmlns="">
<Contact>
<StructuredName>
<GivenName>Nathan</GivenName>
<MiddleName></MiddleName>
<FamilyName>Mische</FamilyName>
</StructuredName>
</Contact>
</PersonalData>
</Person>
</People>
</QueryResults>
...

The second issue I had was that my original stylesheet was returning the SOAP-ENV and MISCHE namespace declarations in the root node of my result. To suppress the namespaces in my results I had to add the exclude-result-prefixes attribute to the Person element in my XSL stylesheet.

Anyway, these were just some things I though I'd share in case you find yourself trying to transform XML with a different namespaces defined.

Comments
Doug Setzer's Gravatar Thanks for the blog entry - it's helped save me a bunch of time!
# Posted By Doug Setzer | 3/5/07 4:00 PM
BlogCFC was created by Raymond Camden. This blog is running version 5.8.001.