SAX NamespaceSupport and Order of Operations

Paul Brown @ 2004-10-15T22:18:27Z

Update: I thought about this a little bit more (and did some experimentation), and some of what I said below is wrong. See a more recent entry for an update.

You never know which SAX you're going to get. For example, on my PowerBook, I get the version from the 1.4.2 JDK by default, and that (according to the source, at least) is SAX 2.0r2pre. Using Xerces 2.6.2, I get SAX 2.0.1.

The difference is an IllegalStateException if a NamespaceSupport instance is used incorrectly, but proper hygeine ensures portable (and correct) behavior. A short digression on how SAX deals with namespaces is in order.

No matter how you set the SAX namespace properties, if namespaces are enabled, you'll get startPrefixMapping and endPrefixMapping events. The startPrefixMapping events occur immediately before a startElement event, and endPrefixMapping events occur immediately after a endElement event. The startPrefixMapping and endPrefixMapping events don't necessarily occur in a particular order or in a nested pattern. (By the way, you care about prefix mappings separately from element and attribute names if you're working with QNames in attribute values or element content, e.g., in many XML schema-typed documents.)

In a nutshell, the guideline for NamespaceSupport is:

Namespaces should only be declared on a fresh stack frame. Specifically, the order of operations for a NamespaceSupport instance is declarePrefix invocations, processName (or other) invocations, and finally pushContext.

Here's why. If you put some code like this in your ContentHandler implementation:

private NamespaceSupport _nss;

public void startPrefixMapping(String prefix, String uri)
    throws SAXException
{
  _nss.declarePrefix(prefix,uri);
}

You'll be pushing namespace bindings onto the current stack frame within the NamespaceSupport instance, and all of this will happen before the startElement event.

SAX 2.0r2pre was more permissive, but SAX 2.0.1 "locks" the NamespaceSupport instance once it is used to resolve a name through the processName() method. So, if you call pushContext() too soon and someone calls processName(), the stack frame will be "locked" when the next startPrefixMapping event shows up, resulting in an IllegalStateException. Thus, in the code for the startElement event, the last thing to touch the NamespaceSupport should be _nss.pushContext() to create a fresh frame for the next round of startPrefixMapping events.

This is perfectly reasonable, since adding prefix bindings might change the way a qualified name's parts would resolve.

By the way, you don't really need to pay attention to the endPrefixMapping events unless you want to check up on the parser or event generator. Instead, just implement the endPrefixMapping method with an empty body and ensure that a _nss.popContext() is the last thing in the endElement implementation that touches the NamespaceSupport.

Meta

No tags.

(comment bubbles) 0 comments
358 direct views