Receiving Namespace Mappings

By default namespace declaration attributes such as xmlns="http://ns.cafeconleche.org/Orders/" and xmlns:xlink="http://www.w3.org/1999/xlink" are not included in the list of attributes passed to startElement(). Instead each such namespace declaration attribute is signaled by a call to startPrefixMapping() immediately before the startElement() call corresponding to the start-tag of the element where the declaration appears. Furthermore, the endElement() call corresponding to the end tag of that element is immediately followed by an endPrefixMapping() call.

public void startPrefixMapping(String prefix, String uri)
    throws SAXException;

public void endPrefixMapping(String prefix)
    throws SAXException;

90% of the time you can ignore these events. As long as you only care about URIs and prefixes on element and attribute names, then there’s really no need to pay attention to these methods. You simply inspect the URIs and prefixes passed in the arguments to the startElement() method. However, in a few applications, including XSLT, schemas, and SOAP, prefixes appear in attribute values and even element content. If so, you need to keep track of which URIs particular prefixes are bound to where. This is complicated by the fact that, while many documents declare all namespaces on the root element and most documents map each prefix to exactly one URI, it is possible for namespaces to be declared elsewhere than the root and for one prefix to match different URIs in different parts of the same document. Conversely, one URI can be associated with several prefixes, even in the same part of a document.

The proper way to manage prefix mappings is to keep the prefixes and URIs on a per-document stack. startPrefixMapping() pushes the pair onto the stack. endPrefixMapping() pops them off.[2] If at any time while parsing you need to know the current binding of a prefix you’ve encountered, simply search down the stack starting with the top. The first binding you encounter for the prefix is the one you want. The org.xml.sax.helpers.NamespaceSupport class implements this logic. It’s summarized in Example 6.12.

Example 6.12. The NamespaceSupport class

package org.xml.sax.helpers;


public class NamespaceSupport {

  public final static String XMLNS 
   = "http://www.w3.org/XML/1998/namespace";

  public NamespaceSupport();
  
  public void        reset();
  public void        pushContext();
  public void        popContext();
  public boolean     declarePrefix(String prefix, String uri);
  public String[]    processName(String qualifedName, 
   String parts[], boolean isAttribute);
  public String      getURI(String prefix);
  public Enumeration getPrefixes();
  public String      getPrefix(String uri);
  public Enumeration getPrefixes(String uri);
  public Enumeration getDeclaredPrefixes();

}

To use this class, you simply call pushContext() in the first startPrefixMapping() event before each startElement(), or at the beginning of the startElement() method if that event is not preceded by any startPrefixMapping() events. Then call popContext() at the end of every endElement() event.

You add namespace declarations to a context by passing their prefix and URI as strings to declarePrefix() inside each startPrefixMapping() call. Use the empty string as the prefix to declare the default namespace. Here’s the cookbook code you can paste into your content handler to maintain a stack of namespaces:

  private NamespaceSupport namespaces;
  private boolean needNewContext = true;

  public void startDocument() {
    namespaces = new NamespaceSupport(); 
  }
  
  public void startPrefixMapping(String prefix, String uri)
   throws SAXException {
   
    if (needNewContext) {
      namespaces.pushContext();
      needNewContext = false;
    }
    namespaces.declarePrefix(prefix, uri);
    
  }
   
  public void endPrefixMapping(String prefix) 
   throws SAXException {
   
    // add additional endElement() code...
    
    namespaces.popContext();
  }

   public void startElement(String namespaceURI, 
    String localName, String qualifiedName, Attributes atts)
    throws SAXException {
   
     if (needNewContext) namespaces.pushContext();

     // add additional startElement() code...
     
     needNewContext = true;
  }   
    
  }

The getDeclaredPrefixes(), getPrefix(), getPrefixes(), and getURI() methods all return various information about the namespaces in scope in the current context. For example, this startElement() method prints the namespace URI for the values of type attributes, such as might be found in a schema:

  public void startElement(String namespaceURI, String localName,
   String qualifiedName, Attributes atts) throws SAXException {
  
    String value = atts.getValue("type");
    if (value != null) {
      String prefix = "";
      if (value.indexOf(':') >= 0) {
        prefix = value.substring(0, value.indexOf(':')); 
      }
      String uri = namespaces.getURI(prefix);
    }
   
  }

That point’s easy to miss so let me make it again: the getURI() method is being used here to get the namespace URI for the attribute value, not the attribute itself. The attribute in this example is in no namespace at all. NamespaceSupport and startPrefixMapping()/endPrefixMapping() are only relevant to namespace prefixes in attribute values and element content, not to namespace prefixes used on attribute and element names. If you only care about namespace prefixes in element and attribute names, then you can ignore this section completely.

If you want to reuse a NamespaceSupport for a new document, call reset(), probably from the startDocument() method.

Note

The Namespaces in XML specification explicitly states that the xml prefix is always bound to the URL http://www.w3.org/XML/1998/namespace. Even if this is explicitly declared with an xmlns:xml="http://www.w3.org/XML/1998/namespace" attribute, startPrefixMapping() and endPrefixMapping() are never invoked for the xml prefix. However, this binding is automatically included in all NamespaceSupport objects regardless of context.

Also according to Namespaces in XML, “the prefix xmlns is used only for namespace bindings and is not itself bound to any namespace name.” Thus it too does not cause startPrefixMapping() and endPrefixMapping() invocations.



[2] SAX does state that “start/endPrefixMapping events are not guaranteed to be properly nested relative to each other”. Thus you might pop mappings off in a different order than you pushed them, and thus pop the wrong mapping at the wrong time. However, as long as you push and pop all mappings as soon as you see a start/endPrefixMapping event, you’ll always pop the same set of mappings you pushed, even if in a different order. Thus this turns out not to matter in practice.


Copyright 2001, 2002 Elliotte Rusty Haroldelharo@metalab.unc.eduLast Modified December 10, 2002
Up To Cafe con Leche