SAX, the power API

In this preview from XML by Example, compare DOM and SAX and then put SAX to work

This preview of the second edition of XML by Example by Benoit Marchal gives a solid introduction to SAX, the event-based API for processing XML that has become a de facto standard. This preview tells when to use SAX instead of DOM, gives an overview of commonly used SAX interfaces, and provides detailed examples in a Java-based application with many code samples.
Used with permission of Que Publishing, a division of Pearson Technology Group.

Share:

Benoit Marchal (bmarchal@pineapplesoft.com), Consultant, Pineapplesoft

Benoit Marchal Benoît Marchal is a consultant and writer based in Namur, Belgium. He is the author of XML by Example (about to be revised in a second edition), Applied XML Solutions, and XML and the Enterprise. He writes the developerWorks XML zone column Working XML as well as a column for Gamelan.
Details on his latest projects are at www.marchal.com.



01 August 2001

Also available in Japanese

This article, adapted from a chapter in the forthcoming second edition of XML by Example, serves as an introduction to SAX, the event-based API for processing XML that complements the Document Object Model, or DOM, the object-based API for XML parsers published by the W3C.

You will see that SAX:

  • Is an event-based API.
  • Operates at a lower level than DOM.
  • Gives you more control than DOM.
  • Is almost always more efficient than DOM.
  • But, unfortunately, requires more work than DOM.

Why Another API?

Don't be fooled by the name. SAX may be the Simple API for XML, but it requires more work than DOM. The reward -- tighter code -- is well worth the effort.

Figure 1 shows the two components of a typical XML program:

  • The parser, a software component that decodes XML files on behalf of the application. Parsers effectively shield developers from the intricacies of the XML syntax.
  • The application, which consumes the file content.
Figure 1. Architecture of an XML program
Flow chart of the architecture of an XML application

Obviously, the application can be simple (such as an application to convert prices between Euros and Dollars) or very complex, such as a distributed e-commerce application to order goods over the Internet.

This chapter concentrates on the dotted line in Figure 1 -- the interface, or API (Application Programming Interface), between the parser and the application.


Object-Based and Event-Based Interfaces

You probably already know that there are two classes of interfaces for parsers: object-based and event-based interfaces.

DOM, the standard API for object-based parsers that was developed and published by the W3C, is discussed at length in another chapter of my book. This brief summary of the DOM serves as context for understanding how SAX fits into the whole picture.

As an object-based interface, DOM communicates with the application by explicitly building a tree of objects in memory. The tree of objects is an exact map of the tree of elements in the XML file.

DOM is simple to learn and use because it closely matches the underlying XML document. It is also ideal for what I call XML-centric applications, such as browsers and editors. XML-centric applications manipulate XML documents for the sake of manipulating XML documents.

For most applications, however, processing XML documents is just one task among many others. For example, an accounting package might import XML invoices, but that is not its primary activity. Balancing accounts, tracking expenditures, and matching payments against invoices are. Chances are the accounting package already has a data structure, most likely a database. The DOM model is ill suited to the accounting application, in that case, as the application would have to maintain two copies of the data in memory (one in the DOM tree and one in the application's own structure). At the very least, maintaining the data in memory twice is inefficient. It might not be a major problem for desktop applications, but it can bring a server to its knees.

SAX is the sensible choice for non-XML-centric applications. Indeed SAX does not explicitly build the document tree in memory. It enables the application to store the data in the most efficient way.

Figure 2 illustrates how an application can map between an XML tree and its own data structure.

Figure 2. Mapping the XML structure to the application structure
Diagram depicts how the XML structure maps to the application structure

Event-Based Interfaces

As the name implies, an event-based parser sends events to the application. The events are similar to user-interface events such as ONCLICK (in a browser) or AWT/Swing events (in Java).

Events alert the application that something happened and the application needs to react. In a browser, events are typically generated in response to user actions: a button fires an ONCLICK event when the user clicks.

With an XML parser, events are related not to user actions but to elements in the XML document being read. There are events for:

  • Element opening and closing tags
  • Content of elements
  • Entities
  • Parsing errors

Figure 3 shows how the parser generates events as it reads the document.

Figure 3. The parser generates events
Diagram shows how the parser generates events

Listing 1 shows a price list in XML. It details the prices charged by various companies for XML training. Figure 4 shows the structure of the price list document

Listing 1. pricelist.xml
<?xml version="1.0"?>
<xbe:price-list xmlns:xbe="http://www.psol.com/xbe2/listing8.1">
   <xbe:product>XML Training</xbe:product>
   <xbe:price-quote price="999.00"  vendor="Playfield Training"/>
   <xbe:price-quote price="699.00"  vendor="XMLi"/>
   <xbe:price-quote price="799.00"  vendor="WriteIT"/>
   <xbe:price-quote price="1999.00" vendor="Emailaholic"/>
</xbe:price-list>
Figure 4. The structure of the price list
Diagram of the structure of the price list

The XML parser reads this document and interprets it. Whenever it recognizes something in the document, it generates an event.

When reading Listing 1, the parser first reads the XML declaration and generates an event for the beginning of the document. When it encounters the first opening tag, <xbe:price-list>, the parser generates its second event to notify the application that it has encountered the starting tag for a price-list element.

Next, the parser sees the opening tag for the product element (for simplicity, I'll ignore the namespaces and indenting whitespaces in the rest of this discussion) and it generates its third event.

After the opening tag, the parser sees the content of the product element: XML Training, which results in yet another event.

The next event indicates the closing tag for the product element. The parser has completely parsed the product element. It has fired five events so far: three events for the product element, one event for the beginning of document, and one for price-list opening tag.

The parser now moves to the first price-quote element. It generates two events for each price-quote element: one event for the opening tag and one event for the closing tag.

Yes, even though the closing tag is reduced to the / character in the opening tag, the parser still generates a closing event.

There are four price-quote elements, so the parser generates eight events as it parses them. Finally, the parser meets price-list's closing tag and it generates its two last events: closing price-list and end of document.

As Figure 5 illustrates, taken together, the events describe the document tree to the application. An opening tag event means "going one level down in the tree" whereas a closing tag element means "going one level up in the tree."

Figure 5. How the parser builds the tree implicitly
Diagram depicts how a SAX parser builds a DOM tree implicitly

Note that the parser passes enough information to build the document tree of the XML documents but, unlike a DOM parser, it does not explicitly build the tree.

Why Use Event-Based Interfaces?

Now I'm sure you're confused. Which type of API should you use and when should you use it -- SAX or DOM? Unfortunately, there is no clear-cut answer to this question. Neither is any of the two APIs intrinsically better; they serve different needs.

The rule of thumb is to use SAX when you need more control and DOM when you want increased convenience. For example, DOM is popular with scripting languages.

Note: A natural

An event-based interface is the most natural interface for a parser: It simply has to report what it sees.

The main reason to adopt SAX is efficiency. SAX does fewer things than DOM, but it gives you more control over the parsing. Of course, if the parser does less work, it means you (the developer) have more work to do.

Furthermore, as already discussed, SAX consumes fewer resources than DOM, simply because it does not need to build the document tree.

In the early days of XML, DOM benefited from being the official, W3C-approved API. Increasingly, developers trade convenience for power and turn to SAX.

The major limitation of SAX is that it is not possible to navigate backwards in the document. Indeed, after firing an event, the parser forgets about it. As you will see, the application must explicitly buffer those events it is interested in.

Note: The tree that SAX built

If needed, the application can build a DOM tree from the events it receives from the parser. In fact, several DOM parsers are built on top of a SAX parser.

Of course, whether it implements the SAX or DOM API, the parser does a lot of useful work: It reads the document, enforces the XML syntax, and resolves entities -- to name just a few. A validating parser also enforces the document schema.

There are many reasons to use a parser, and you should master APIs, SAX, and DOM. It gives you the flexibility to choose the best API depending on the task at hand. Fortunately, modern parsers support both APIs.


SAX, the Power API

SAX was developed by the members of the XML-DEV mailing list as a standard and simple API for event-based parsers. SAX is short for the Simple API for XML.

SAX was originally defined for Java, but it is also available for Python, Perl, C++, and COM (Windows objects). More language bindings are sure to follow. Furthermore, through COM, SAX parsers are available to all Windows programming languages, including Visual Basic and Delphi.

Unlike DOM, SAX is not endorsed by an official standardization body, but it is widely used and is considered a de facto standard. (Currently SAX is edited by David Megginson, but he has announced that he will retire.)

As you have seen, in a browser DOM is preferred API. Therefore, the examples in this chapter are written in Java. (If you feel you need a crash course on Java, turn to Appendix A of my book or the Education section of the developerWorks Java zone.)

Some parsers that support SAX include Xerces, the Apache parser -- formerly the IBM parser; MSXML, the Microsoft parser; and XDK, the Oracle parser. These parsers are the most flexible because they also support DOM.

A few parsers offer only SAX, such as James Clark's XP, Ælfred, and ActiveSAX from Vivid Creations (see Resources).


Getting Started with SAX

Listing 2 is a Java application that finds the cheapest offering in Listing 1. The application prints the best price and the name of the vendor.

Compiling the Example

To compile this application, you need a Java Development Kit (JDK) for your platform (see Resources). For this example, the Java Runtime is not enough.

Caution

Java has difficulty with paths containing spaces. If Cheapest complains that it cannot find the file, check the directory for an errant space.

Download the listings for this excerpt from the XBE2 page on the author's Web site. The download includes Xerces. If you have problems with a listing, visit the author's Web site for updates.

Save Listing 2 in a file called Cheapest.java. Go to the DOS prompt, change to the directory where you saved Cheapest.java and compile by issuing the following commands at the DOS prompt:

mkdir classes
set classpath=classes;lib\xerces.jar  
javac -d classes src\Cheapest.java

The compilation will install the Java program in the classes directory. These commands assume that you have installed Xerces in the lib directory and Listing 2 in the src directory. You might have to adapt the classpath (second command) if you installed the parser in a different directory.

To run the application against the price list, issue the following command:

java com.psol.xbe2.Cheapest data\pricelist.xml

The result should be:

The cheapest offer is from XMLi ($699.00)

This command assumes that Listing 1 is in a file called data\pricelist.xml. Again, you might need to adapt the path to your system.

Tip: handler

An event handler does not call the parser. In fact, it's just the opposite: the parser calls it. Confused? Think of AWT events. An event handler attached to, say, a button does not call the button. It waits for the button to be clicked.

The Event Handler Step by Step

Events in SAX are defined as methods attached to specific Java interfaces. This section will review Listing 2 step by step. The following section gives you more information on the main SAX interfaces.

The easiest solution to declare an event handler is to inherit from the SAX-provided DefaultHandler:

public class Cheapest
   extends DefaultHandler

This application implements only one event handler, startElement(), which the parser calls when it encounters a start tag. The parser will call startElement() for every start tag in the document: <xbe:price-list>, <xbe:product> and <xbe:price-quote>.

In Listing 2, the event handler is only interested in price-quote, so it tests for it. The handler does nothing with events for other elements:

if(uri.equals(NAMESPACE_URI) && name.equals("price-quote"))
{
   // ...
}

When it finds a price-quote element, the event handler extracts the vendor name and the price from the list of attributes. Armed with this information, finding the cheapest product is a simple comparison:

String attribute =
   attributes.getValue("","price");
if(null != attribute)
{
   double price = toDouble(attribute);
   if(min > price)
   {
      min = price;
      vendor = attributes.getValue("","vendor");
   }
}

Notice that the event handler receives the element name, namespace and attribute lists as parameters from the parser.

Let's now turn our attention to the main() method. It creates an event-handler object and a parser object:

Cheapest cheapest = new Cheapest();
XMLReader parser =
   XMLReaderFactory.createXMLReader(PARSER_NAME);

XMLReader and XMLReaderFactory are defined by SAX. An XMLReader is a SAX parser. The factory is a helper class to create XMLReaders.

main() sets a parser feature to request namespace processing and it registers the event handler with the parser. Finally, main() calls the parse() method with the URI to the XML file:

parser.setFeature("http://xml.org/sax/features/namespaces",true);
parser.setContentHandler(cheapest);
parser.parse(args[0]);

Tip: Namespaces

http://xml.org/sax/features/namespaces is set to true by default, but setting it to true explicitly makes the code more readable.

The innocent-looking parse() method triggers parsing of the XML document which, in turn, calls the event handler. It is during the execution of this method that our startElement() method will be called. There's a lot happening behind the call to parse()!

Last but not least, main() prints the result:

Object[] objects = new Object[]
{
   cheapest.vendor,
   new Double(cheapest.min)
};
System.out.println(MessageFormat.format(MESSAGE,objects));

Wait! When do Cheapest.vendor and Cheapest.min acquire their values? We don't set them explicitly in main()! True; it's the event handler's job. And the event handler is ultimately called by parse(). That's the beauty of event processing.

Caution

Remember that you cannot compile the examples unless you have installed a Java Development Kit.

Finally, an error like:

src\Cheapest.java:7: Package org.xml.sax 
  not found in import.
import org.xml.sax.*;

or

Can't find class com/psol/xbe2/Cheapest 
   or something it requires

is most likely one of following:

  • The classpath (second command, classes;lib\xerces.jar) is incorrect.
  • You entered an incorrect class name in the last command (com.psol.xbe2.Cheapest).

Commonly Used SAX Interfaces and Classes

Note: SAX versions

There have been two versions of SAX so far: SAX1 and SAX2. This chapter introduces the SAX2 API only. SAX1 is very similar to SAX2, but it lacks namespace handling.

SAX groups its events in a few interfaces:

  • ContentHandler defines events related to the document itself (such as opening and closing tags). Most applications register for these events.
  • DTDHandler defines events related to the DTD. However it does not define enough events to completely report on the DTD. If you need to parse a DTD, use the optional DeclHandler. DeclHandler is an extension to SAX and it is not supported by all parsers.
  • EntityResolver defines events related to loading entities. Few applications register for these events.
  • ErrorHandler defines error events. Many applications register for these events to report errors in their own way.

Note: SAX's greatest hits

This section is not a comprehensive reference to SAX. Instead it concentrates on the most frequently used classes.

To simplify work, SAX provides a default implementation for these interfaces in the DefaultHandler class. In most cases, it is easier to extend DefaultHandler and override the methods that are relevant for the application rather than to implement an interface directly.

XMLReader

To register event handlers and to start parsing, the application uses the XMLReader interface. As we have seen, parse(), a method of XMLReader, starts the parsing:

parser.parse(args[0]);

XMLReader's main methods are:

  • parse() parses an XML document. There are two versions of parse(); one accepts a filename or a URL, the other an InputSource object (see the section "InputSource").
  • setContentHandler(), setDTDHandler(), setEntityResolver(), and setErrorHandler() let the application register event handlers.
  • setFeature() and setProperty() control how the parser work. They take a property or feature identifier, which is a URI -- similar to namespaces -- and a value. Features take Boolean values, while properties take Objects.

XMLReaderFactory's most commonly used features are:

  • http:// xml.org/sax/features/namespaces, which all SAX parsers recognize. When set to true (the default), the parser recognizes namespaces and resolves prefix when calling ContentHandler's methods.
  • http://xml.org/sax/features/validation, which is optional. If it is set to true, a validating parser validates the document. Nonvalidating parsers ignore this feature.

XMLReaderFactory

XMLReaderFactory creates the parser object. It defines two versions of createXMLReader(): one takes the class name for the parser as a parameter, the second obtains the class name from the org.xml.sax.driver system property.

For Xerces, the class is org.apache.xerces.parsers.SAXParser. You should use XMLReaderFactory because it makes it easy to switch to another SAX parser. Indeed, it only requires changing one line and recompiling:

XMLReader parser = XMLReaderFactory.createXMLReader(
                       "org.apache.xerces.parsers.SAXParser");

For more flexibility, the application can read the class name from the command line or use the parameterless createXMLReader(). It is, therefore, possible to change the parser without even recompiling.

InputSource

InputSource controls how the parser reads files, including XML documents and entities.

In most cases, documents are loaded from a URL. However, applications with special needs can override InputSource. This is used, for example, to load documents from databases.

ContentHandler

ContentHandler is the most commonly used SAX interface because it defines events for the XML document.

As you have seen, Listing 2 implements startElement(), an event defined in ContentHandler. It registers the ContentHandler with the parser:

Cheapest cheapest = new Cheapest();
// ...
parser.setContentHandler(cheapest);

ContentHandler declares the following events:

  • startDocument()/endDocument() notify the application of the document's beginning or ending.
  • startElement()/endElement() notifies the application of an opening or closing tag. Attributes are passed as an Attributes parameter (see the following section, Attributes). Empty elements (for example, <img href="logo.gif"/>) generate both startElement() and endElement(),even though there is only one tag.
  • startPrefixMapping()/endPrefixMapping() notifies the application of a namespace scope. You seldom need this information because the parser already resolves namespaces when the http://xml.org/sax/features/namespaces is true.
  • characters()/ignorableWhitespace() notify the application when the parser finds text (parsed character data) in an element. Beware, the parser is entitled to spread the text across several events (to better manage its buffer). The ignorableWhitespace event is used for ignorable spaces as defined by the XML standard.
  • processingInstruction() notifies the application of processing instructions.
  • skippedEntity() notifies the application that an entity has been skipped (that is, when a parser has not seen the entity declaration in the DTD/schema).
  • setDocumentLocator() passes a Locator object to the application; see the section Locator that follows. Note that the SAX parser is not required to supply a Locator, but if it does, it must fire this event before any other event.

Attributes
In the startElement() event, the application receives the list of attributes in an Attributes parameter:

String attribute = attributes.getValue("","price");

Attributes defines the following methods:

  • getValue(i)/getValue(qName) /getValue(uri,localName) returns the value of the ith attribute or the value of an attribute whose name is given.
  • getLength() returns the number of attributes.
  • getQName(i)/getLocalName(i)/getURI(i) returns the qualified name (with the prefix), local name (without the prefix) and namespace URI of the ith attribute.
  • getType(i)/getType(qName)/getType(uri,localName) returns the type of the ith attribute or the type of the attribute whose name is given. The type is a string, as used in the DTD: "CDATA", "ID", "IDREF", "IDREFS", "NMTOKEN", "NMTOKENS", "ENTITY", "ENTITIES", or "NOTATION".

Caution

The Attributes parameter is available only during the startElement() event. If you need it between events, make a copy with AttributesImpl.

Locator

A Locator provides line and column positions to the application. The parser is not required to provide a Locator object.

Locator defines the following methods:

  • getColumnNumber() returns the column where the current event ends. In a endElement() event, it would return the last column of the end tag.
  • getLineNumber() returns the line in which the current event ends. In a endElement() event, it would return the line of the end tag.
  • getPublicId() returns the public identifier for the current document event.
  • getSystemId() returns the system identifier for the current document event.

DTDHandler

DTDHandler declares two events related to parsing the DTD:

  • notationDecl() notifies the application that a notation has been declared.
  • nparsedEntityDecl() notifies the application that an unparsed entity declaration has been found.

EntityResolver

The EntityResolver interface defines only one event, resolveEntity(), which returns an InputSource (covered in another chapter).

Because the SAX parser can resolve most URLs already, few applications implement EntityResolver. The exception is catalog files (covered in a different chapter), which resolve public identifiers to system identifiers. If you need catalog files in your application, download Norman Walsh's catalog package (see Resources).

ErrorHandler

The ErrorHandler interface defines events for errors. Applications that handle these events can provide custom error processing.

After a custom error handler is installed, the parser doesn't throw exceptions anymore. Throwing exceptions is the responsibility of the event handlers.

The interface defines three methods that correspond to three levels or gravity of errors:

  • warning() signals problems that are not errors as defined by the XML specification. For example, some parsers issue a warning when there is no XML declaration. It is not an error (because the declaration is optional), but it might be worth noting.
  • error() signals errors as defined by the XML specification.
  • fatalError() signals fatal errors, as defined by the XML specification.

SAXException

Most methods defined by the SAX standard can throw SAXException. A SAXException signals an error while parsing the XML document.

The error can either be a parsing error or an error in an event handler. To report other exceptions from the event handler, it is possible to wrap exceptions in SAXException.

Example: Suppose an event handler catches an IndexOutOfBoundsException while processing the startElement event. The event handler can wrap the IndexOutOfBoundsException in a SAXException:

public void startElement(String uri,
                         String name,
                         String qualifiedName,
                         Attributes attributes)
{
   try
   {
      // the code may throw an IndexOutOfBoundsException
   }
   catch(IndexOutOfBounds e)
   {
      throw new SAXException(e);
   }
}

The SAXException flows all the way up to the parse() method where it is caught and interpreted:

try
{
   parser.parse(uri);
}
catch(SAXException e)
{
   Exception x = e.getException();
   if(null != x)
      if(x instanceof IndexOutOfBoundsException)
         // process the IndexOutOfBoundsException
}

Maintaining the State

Listing 1 is convenient for a SAX parser because the information is stored as attributes of price elements. The application had to register only for startElement().

Example Listing 3 is more complex because the information is scattered across several elements. Specifically, vendors have different prices depending on the delivery delay. If the user is willing to wait, he or she may get a better price. Figure 6 illustrates the structure of the document.

Listing 3. xtpricelist.xml
<?xml version="1.0"?>
<xbe:price-list xmlns:xbe="http://www.psol.com/xbe2/listing8.3">
   <xbe:name>XML Training</xbe:name>
   <xbe:vendor>
      <xbe:name>Playfield Training</xbe:name>
      <xbe:price-quote delivery="5">999.00</xbe:price-quote>
      <xbe:price-quote delivery="15">899.00</xbe:price-quote>
   </xbe:vendor>
   <xbe:vendor>
      <xbe:name>XMLi</xbe:name>
      <xbe:price-quote delivery="3">2999.00</xbe:price-quote>
      <xbe:price-quote delivery="30">1499.00</xbe:price-quote>
      <xbe:price-quote delivery="45">699.00</xbe:price-quote>
   </xbe:vendor>
   <xbe:vendor>
      <xbe:name>WriteIT</xbe:name>
      <xbe:price-quote delivery="5">799.00</xbe:price-quote>
      <xbe:price-quote delivery="15">899.00</xbe:price-quote>
   </xbe:vendor>
   <xbe:vendor>
      <xbe:name>Emailaholic</xbe:name>
      <xbe:price-quote delivery="1">1999.00</xbe:price-quote>
   </xbe:vendor>
</xbe:price-list>
Figure 6. Price list structure
Diagram of the price list structure

To find the best deal, the application must collect information from several elements. However, the parser can generate up to three events for each element -- startElement(), characters(), and endElement(). The application must somehow relate events and elements.

Listing 4 is a new Java application that looks for the best deal in the price list. When looking for the best deal, it takes the urgency into consideration. Indeed, from Listing 3, the cheapest vendor (XMLi) is also the slowest. On the other hand, Emailaholic is expensive, but it delivers in two days.

You compile and run this application like the Cheapest application introduced previously. The results depend on the urgency of the delivery. You will notice that this program takes two parameters: the filename and the longest delay one is willing to wait.

java com.psol.xbe2.BestDeal data/xtpricelist.xml 60

returns:

The best deal is proposed by XMLi. A(n) XML Training delivered[ccc]
in 45 days for $699.00

whereas:

java com.psol.xbe2.BestDeal data/xtpricelist.xml 3

returns:

The best deal is proposed by Emailaholic. A(n) XML Training[ccc]
delivered in 1 days for $1,999.00

A Layered Architecture

Listing 4 is the most complex application you have seen so far. It's not abnormal: The SAX parser is very low level, so the application has to take over a lot of the work that a DOM parser would do.

The application is organized around two classes: SAX2BestDeal and BestDeal. SAX2BestDeal manages the interface with the SAX parser. It manages the state and groups events in a coherent way.

BestDeal has the logic to perform price comparison. It also maintains information in a structure that is optimized for the application, not XML. The architecture for this application is illustrated in Figure 7. Figure 8 shows the class diagram in UML.

Figure 7. The architecture for the application
Diagram of the Best Deal application architecture
Figure 8. The class diagram for the application
Class diagram for the Best Deal application

SAX2BestDeal handles several events: startElement(), endElement(), and characters(). All along, SAX2BestDeal tracks its position in the document tree.

For example, in a characters() event, SAX2BestDeal needs to know whether the text is a name, the price, or whitespaces that can be ignored. Furthermore, there are two name elements: the price-list's name and the vendor's name.

States

A SAX parser, unlike a DOM parser, does not provide state information. The application is responsible for tracking its own state. There are several options for this. Listing 4 identified the meaningful states and the transitions between them. It's not difficult to derive this information from the document structure in Figure 6.

It is obvious that the application will first encounter a price-list tag. The first state should, therefore, be within a price-list. From there, the application will reach a name. The second state is therefore within a name in the price-list.

The next element has to be a vendor, so the third state is within a vendor in the price-list. The fourth state is within a name in a vendor in a price-list, because a name follows the vendor.

The name is followed by a price-quote element and the corresponding state is in a price in a vendor in a price-list. Afterwards, the parser encounters either a price-quote or a vendor for which there are already states.

It's easier to visualize this concept on a graph with states and transitions, such as the one shown in Figure 9. Note that there are two different states related to two different name elements, depending on whether you are dealing with the price-list/name or price-list/vendor/name.

Figure 9. State transition diagram
State transition diagram

In Listing 4, the state variable stores the current state:

final protected int START = 0,
                    PRICE_LIST = 1,
                    PRICE_LIST_NAME = 2,
                    VENDOR = 3,
                    VENDOR_NAME = 4,
                    VENDOR_PRICE_QUOTE = 5;
protected int state = START;

Transitions

Transition 1 The value of the state variable changes in response to events. In the example, elementStart() updates the state:

ifswitch(state)
{
   case START:
      if(name.equals("price-list"))
         state = PRICE_LIST;
      break;
   case PRICE_LIST:
      if(name.equals("name"))
         state = PRICE_LIST_NAME;
         // ...
      if(name.equals("vendor"))
         state = VENDOR;
         break;
   case VENDOR:
      if(name.equals("name"))
         state = VENDOR_NAME;
         // ...
      if(name.equals("price-quote"))
         state = VENDOR_PRICE_QUOTE;
         // ...
      break;
}

SAX2BestDeal has a few instance variables to store the content of the current name and price-quote. In effect, it maintains a small subset of the tree. Note that, unlike DOM, it never has the entire tree because it discards the name and price-quote when the application has used them.

This is very efficient memorywise. In fact, you could process a file of several gigabytes because, at any point, there's only a small subset in memory.

Transition 2 The parser calls characters() for every character data in the document, including indenting. It makes sense to record text only in name and price-quote, so the event handler uses the state.

switch(state)
{
   case PRICE_LIST_NAME:
   case VENDOR_NAME:
   case VENDOR_PRICE_QUOTE:
      buffer.append(chars,start,length);
      break;
}

Transition 3 The event handler for endElement() updates the state and calls BestDeal to process the current element: switch(state)

{
   case PRICE_LIST_NAME:
      if(name.equals("name"))
      {
         state = PRICE_LIST;
         setProductName(buffer.toString());
         // ...
      }
      break;
   case VENDOR_NAME:
      if(name.equals("name"))
         state = VENDOR;
         // ...
      break;
   case VENDOR_PRICE_QUOTE:
      if(name.equals("price-quote"))
      {
         state = VENDOR;
         // ...
         compare(vendorName,price,delivery);
         // ...
      }
      break;
   case VENDOR:
      if(name.equals("vendor"))
         state = PRICE_LIST;
         // ...
      break;
   case PRICE_LIST:
      if(name.equals("price-list"))
         state = START;
      break;
}

Lessons Learned

Listing 4 is typical for a SAX application. There's a SAX event handler (SAX2BestDeal), which packages the events in the format most suitable for the application.

Note: State or Stack?

An alternative to using a state variable, is to use a Stack. Push the element name (or another identifier) in startElement() and pop it in endElement().

The application logic (in BestDeal) is kept separate from the event handler. In fact, in many cases, the application logic will be written independently of XML.

The layered approach establishes a clean-cut separation between the application logic and the parsing.

The example also clearly illustrates that SAX is more efficient than DOM but that it requires more work from the programmer. In particular, the programmer has to explicitly manage states and transitions between states. (In DOM, the state is implicit in the recursive walk of the tree.)


Flexibility

XML is a very flexible standard. However, in practice, XML applications are only as flexible as you, the programmer, make them. This section offers some tips to ensure that your applications exploit XML flexibility.

Building for Flexibility

The BestDeal application puts very few constraints on the structure of the XML document. Add elements in the XML document and they are simply ignored. For example, BestDeal would accept the following vendor element:

<xbe:vendor>
   <xbe:name>Playfield Training</xbe:name>
   <xbe:contact>John Doe</xbe:contact>
   <xbe:price-quote delivery="5">999.00</xbe:price-quote>
   <xbe:price-quote delivery="15">899.00</xbe:price-quote>
</xbe:vendor>

but would ignore the contact information. In general, it's a good idea to simply ignore unknown elements -- as HTML browsers have always done.

Enforce a Structure

However it's not difficult to validate their structure from the event handler. The following code snippet (adapted from startElement()) checks the structure and throws a SAXException if a vendor element contains anything but name or price elements.

case VENDOR:
   if(name.equals("name"))
   {
      state = VENDOR_NAME;
      buffer = new StringBuffer();
   }
   else if(name.equals("price-quote"))
   {
      state = VENDOR_PRICE_QUOTE;
      String st = attributes.getValue("","delivery");
      delivery = Integer.parseInt(st);
      buffer = new StringBuffer();
   }
   else
      throw new SAXException("Expecting <xbe:name> or <xbe:price-quote>");
   break;

Given the listing with a contact element, it reports that:

org.xml.sax.SAXException: Expecting <xbe:name> or <xbe:price-quote>

However, in practice, if your application is really dependent on the structure of the document, it is best to write a schema and use a validating parser.


What's Next?

In this excerpt from the second edition of XML by Example, you learned how to read XML documents. The rest of the book takes you through the entire process of learning how to write documents, thereby closing the loop. Or you can refer to online tutorials and articles to complete your education. (Of course, I hope that you will opt for my book.)

Resources

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into XML on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=XML, Java technology
ArticleID=12024
ArticleTitle=SAX, the power API
publish-date=08012001