XML.com: XML From the Inside Out
oreilly.comSafari Bookshelf.Conferences.

advertisement

Namespaces and Stylesheet Logic

May 02, 2001

In last month's "Transforming XML" column, we saw how to control the namespaces that are declared and referenced in your result document. If your stylesheet needs to know details about which namespaces are used in your source document, and to perform tasks based on which namespaces certain elements or attributes belong to, XSLT offers a variety of ways to find out. This month we look at them.

To experiment, we'll use the following document. It has one title element and one verse element from the http://www.snee.com/red namespace, two verse elements from the http://www.snee.com/blue namespace, and one verse element from the default namespace.

<poem xmlns:red="http://www.snee.com/red"
         xmlns:blue="http://www.snee.com/blue">
<red:title>From Book IV</red:title>
<blue:verse>The way he went, and on the Assyrian mount</blue:verse>
<red:verse>Saw him disfigured, more then could befall</red:verse>
<blue:verse>Spirit of happy sort: his gestures fierce</blue:verse>
<verse>He marked and mad demeanor, then alone</verse>
</poem>

Sample documents and stylesheets are available in this zip file. Our first stylesheet has template rules that act on element nodes based on various conditions. Each adds a text node to the result tree about what it found (for example, "Found a red node:") followed by information about the node it found. Note that for the http://www.snee.com/blue namespace, the stylesheet uses a prefix that is different from the one that the document above uses — instead of "blue", it uses the German word for "blue": "blau".

<!-- xq255.xsl: converts xq254.xml into xq256.txt -->
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:red="http://www.snee.com/red"
                xmlns:blau="http://www.snee.com/blue"
                version="1.0">
<xsl:output method="text"/>

  <xsl:template match="poem">
   Namespace nodes:
    <xsl:for-each select="namespace::*"> 
      <xsl:value-of select="name()"/><xsl:text> </xsl:text>
    </xsl:for-each>
    <xsl:apply-templates/>
  </xsl:template>

  <xsl:template match="blau:verse">
   Found a blue verse.
   name          <xsl:value-of select="name()"/>
   local-name    <xsl:value-of select="local-name()"/>
   namespace-uri <xsl:value-of select="namespace-uri()"/>
   contents      <xsl:apply-templates/>
  </xsl:template>

  <xsl:template match="red:*">
   Found a red node:
   name          <xsl:value-of select="name(.)"/>
   local-name    <xsl:value-of select="local-name(.)"/>
   namespace-uri <xsl:value-of select="namespace-uri(.)"/>
   contents      <xsl:apply-templates/>
  </xsl:template>

  <xsl:template match="verse">
   Found a verse element from the default namespace:
   name          <xsl:value-of select="name(.)"/>
   local-name    <xsl:value-of select="local-name(.)"/>
   namespace-uri <xsl:value-of select="namespace-uri(.)"/>
   contents      <xsl:apply-templates/>
  </xsl:template>

  <xsl:template match="*"/>

</xsl:stylesheet>

Let's look at the result of applying this stylesheet to the document above, before we talk about how it does what it does:

   
Namespace nodes:
    xml blue red 

   Found a red node:
   name          red:title
   local-name    title
   namespace-uri http://www.snee.com/red
   contents      From Book IV

   Found a blue verse.
   name          blue:verse
   local-name    verse
   namespace-uri http://www.snee.com/blue
   contents      The way he went, and on the Assyrian mount

   Found a red node:
   name          red:verse
   local-name    verse
   namespace-uri http://www.snee.com/red
   contents      Saw him disfigured, more then could befall

   Found a blue verse.
   name          blue:verse
   local-name    verse
   namespace-uri http://www.snee.com/blue
   contents      Spirit of happy sort: his gestures fierce

   Found a verse element from the default namespace:
   name          verse
   local-name    verse
   namespace-uri 
   contents      He marked and mad demeanor, then alone

When the first template rule finds a poem element, it lists all the namespace nodes for that element. It does this by using an xsl:for-each instruction to count through the names in the namespace axis with any name ("*"), adding each one's name to the result tree by calling the name() function in an xsl:value-of instruction's select attribute. It then puts a single space after each name with an xsl:text element, so that they don't run together. In addition to the "blue" and "red" namespaces declared in the poem element's start-tag, note the "xml" namespace that starts the list in the result; an XSLT processor assumes that this was implicitly declared. (It's supposed to, but they don't all actually do so.)

The second template rule looks for verse elements in the "blau" namespace. Remember, "blau" isn't really the namespace name — as we can see in the xsl:stylesheet start-tag, it's the prefix assigned in the stylesheet to refer to the namespace that's really called http://www.snee.com/blue. The sample source document has two verse elements from the http://www.snee.com/blue namespace, and even though the stylesheet and source documents refer to this namespace with different prefixes, they're still referring to the same namespace -- so the XSLT processor recognizes them and adds two "Found a blue verse" text nodes to the result tree.

Each of these result tree sections has four lines to tell us about the element node that the template processed:

  • The first line uses the name() function to show us the element's full name. For all the verse elements from the http://www.snee.com/blue namespace, this name is blue:verse. (The stylesheet's first template rule used the same function to retrieve namespace prefix names, and not element names, because namespace nodes were the type of node being handed to the name() function inside the xsl:for-each element that was counting through the namespace nodes.) A template rule looking for "verse" elements from the http://www.snee.com/blue namespace hands this function element nodes, not namespace nodes, so it adds the element names to the result tree.

  • The second line uses the local-name() function to show us the local part of the element's name—that is, that name that identifies it within that particular namespace. For an element with a full name of blue:verse, the local name is "blue".

  • The third line uses the namespace-uri() function to get the full URI of the element's namespace. As we saw with the http://www.snee.com/blue namespace, documents may assign any prefix to a namespace; it's the corresponding URI that really identifies the namespace. For example, you can use "xsl" or "potrzebie" or "blue" as the prefix for your stylesheet's XSLT instructions, as long as the prefix is declared with the "http://www.w3.org/1999/XSL/Transform" URI so that your XSLT processor recognizes those elements as the special ones from the XSLT namespace.

  • The fourth line shows the contents of the selected element node with an xsl:apply-templates instruction.

    

Also in Transforming XML

Automating Stylesheet Creation

Appreciating Libxslt

Push, Pull, Next!

Seeking Equality

The Path of Control

The stylesheet's third template rule looks for any element in the "red" namespace and adds the same information to the result tree that the blue:verse template rule added. Because the source document included both a title element and a verse element from the http://www.snee.com/red namespace, both get a four-line report in the result. Their corresponding element type names show up in their "name" and "local-name" parts of the result tree. The stylesheet's final template rule suppresses any elements not accounted for in the first three template rules.

We've seen how a template can select all the elements with a specific name from a specific namespace (in the example above, the verse elements from the http://www.snee.com/blue namespace) and how it can select all the elements, regardless of their names, from a particular namespace (in the example, those from the http://www.snee.com/red namespace). The next template shows how to select all the elements of a particular name regardless of their namespace: it has a match pattern for all the verse elements from any namespace.

<!-- xq257.xsl: converts xq254.xml into xq258.txt -->

  <xsl:template match="*[local-name()='verse']">
    Found a verse:
    name          <xsl:value-of select="name()"/>
    local-name    <xsl:value-of select="local-name()"/>
    namespace-uri <xsl:value-of select="namespace-uri()"/>
    contents      <xsl:apply-templates/>
  </xsl:template>

Technically speaking, it's really matching all the elements for which the local part of their name is "verse". This match pattern uses it to look for elements of any name ("*") that meet the condition in the predicate: the local-name() function must return a value of "verse". When we apply this stylesheet to the document used in the earlier examples, the result shows two verse elements from the "blue" namespace, one from the "red" namespace, and one from the default namespace (that is, one with no specific namespace assigned to it—the last verse element in the source document).

    Found a verse:
    name          blue:verse
    local-name    verse
    namespace-uri http://www.snee.com/blue
    contents      The way he went, and on the Assyrian mount

    Found a verse:
    name          red:verse
    local-name    verse
    namespace-uri http://www.snee.com/red
    contents      Saw him disfigured, more then could befall

    Found a verse:
    name          blue:verse
    local-name    verse
    namespace-uri http://www.snee.com/blue
    contents      Spirit of happy sort: his gestures fierce

    Found a verse:
    name          verse
    local-name    verse
    namespace-uri 
    contents      He marked and mad demeanor, then alone

For a more realistic example, we'll convert certain elements of an XLink document, regardless of their element names, to HTML. The first template rule in the following stylesheet applies to elements with any name ("*") that meet both of the two conditions in the predicate:

  • They must have a type attribute in the XLink namespace with a value of "simple".

  • They must have an href attribute in the XLink namespace. The value of this attribute doesn't affect whether the XSLT processor applies this template to the node.

<!-- xq259.xsl: converts xq260.xml into xq261.html -->
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:xlink="http://www.w3.org/1999/xlink"
                exclude-result-prefixes="xlink"           
                version="1.0">
  <xsl:output method="html"/>

  <xsl:template match="*[@xlink:type = 'simple' and @xlink:href]">
   <a href="{@xlink:href}"><xsl:apply-templates/></a>
  </xsl:template>

  <xsl:template match="recipe">
   <html><body>
    <xsl:apply-templates/>
   </body></html>
  </xsl:template>

</xsl:stylesheet>

If both of these conditions are true, the element—regardless of its element name—gets converted to an HTML a element in the result tree with the source tree XLink element's href attribute value used for the HTML href attribute in the result tree version. The template rule will do this to both the author and ingredient elements of the following document:

<recipe xmlns:xlink="http://www.w3.org/1999/xlink">
  <author xlink:href="http:/www.jcookie.com"
          xlink:type="simple">Joe "Cookie" Jones</author>
  <ingredients>
    <ingredient xlink:href="http:/www.snee.com/food/flour.html"
                xlink:type="simple">flour</ingredient>
    <ingredient xlink:href="http:/www.snee.com/food/sugar.html"
                xlink:type="simple">sugar</ingredient>
  </ingredients>
  <steps/>
</recipe>

Because of the example's simplicity, the result won't look fancy in a browser, but it does demonstrate how the two different element types can both be converted to HTML a elements with one template rule because of the namespace of their attributes:

<html>
   <body>
      <a href="http:/www.jcookie.com">Joe "Cookie" Jones</a>
      
      <a href="http:/www.snee.com/food/flour.html">flour</a>
      <a href="http:/www.snee.com/food/sugar.html">sugar</a>
      
      
      
   </body>
</html>

It also demonstrates the use of the exclude-result-prefixes attribute in the xsl:stylesheet element that I mentioned in last month's column. This attribute tells the XSLT processor to keep the original elements' namespace declaration and prefixes out of the result, which helps to make the result something that any web browser would understand.

XSLT's ability to base processing logic on namespace values makes it a great tool for developing XLink applications. As you use XSLT to process more and more XML documents with elements from specialized namespaces—for example, SOAP envelopes, XHTML elements, or XSL formatting objects—you'll find these techniques invaluable for keeping track of which elements come from where so that you can take the appropriate action with them.