- Parsing XML Documents with DOM Level 2
- DOM Example: Representing an XML Document as a JTree
- Parsing XML Documents with SAX 2.0
- SAX Example 1: Printing the Outline of an XML Document
- SAX Example 2: Counting Book Orders
- Transforming XML with XSLT
- XSLT Example 1: XSLT Document Editor
- XSLT Example 2: Custom JSP Tag
- Summary
23.5 SAX Example 2: Counting Book Orders
One of the advantages of SAX over DOM is that SAX does not require you to process and store the entire document; you can quickly skip over the parts that do not interest you. The following example looks for sections of an XML file that look like this:
<orders> ... <count>23</count> <book> <isbn>0130897930</isbn> ... </book> ... </orders>
The idea is that the program will count up how many copies of Core Web Pro_gramming Second Edition (you did recognize that ISBN number, right?) are contained in a set of orders. Thus, it can skip over most elements. Since SAX, unlike DOM, does not store anything automatically, we need to take care of storing the pieces of data that are of interest. The one difficulty in that regard is that the isbn element comes after the count element. So, we need to record every count temporarily but only add the temporary value to the running total when the ISBN number matches. To accomplish this task, the content handler (Listing 23.10) overrides the following four methods:
startElement
This method checks whether the name of the element is either count or isbn. If so, it sets a flag that tells the characters method to be on the lookout.
endElement
This method checks whether the name of the element is either count or isbn. If so, it turns off the flag that the characters method watches.
characters
If the count flag is set, this method parses the tag body as an int and records the result in a temporary location. If the isbn flag is set, the method reads the tag body and compares it to the ISBN number of the second edition of Core Web Programming. If this comparison results in a match, then the temporary count is added to the running count.
endDocument
This method prints out the running count. If the number of copies is less than 250 (a real slacker!), it urges the user to buy more copies in the future.
The CountBooks class (Listing 23.11) invokes a user-specifiable parser on an XML file with CountHandler as the parser's content handler. Figure 235 shows the initial result and Figure 236 shows the final result, after orders.xml (Listing 23.9) is used as input.
Figure 235 Interactively selecting the orders.xml file.
Figure 236 Result of running CountBooks on orders.xml.
Listing 23.10 CountHandler.java
import org.xml.sax.*; import org.xml.sax.helpers.*; import java.util.StringTokenizer; import javax.swing.*; /** A SAX parser handler that keeps track of the number * of copies of Core Web Programming ordered. Entries * that look like this will be recorded: * <XMP> * ... * <count>23</count> * <book> * <isbn>0130897930</isbn> * ... * </book> * </XMP> * All other entries will be ignored -- different books, * orders for yachts, things that are not even orders, etc. */ public class CountHandler extends DefaultHandler { private boolean collectCount = false; private boolean collectISBN = false; private int currentCount = 0; private int totalCount = 0; /** If you start the "count" or "isbn" elements, * set a flag so that the characters method can check * the value of the tag body. */ public void startElement(String namespaceUri, String localName, String qualifiedName, Attributes attributes) throws SAXException { if (qualifiedName.equals("count")) { collectCount = true; currentCount = 0; } else if (qualifiedName.equals("isbn")) { collectISBN = true; } } /** If you end the "count" or "isbn" elements, * set a flag so that the characters method will no * longer check the value of the tag body. */ public void endElement(String namespaceUri, String localName, String qualifiedName) throws SAXException { if (qualifiedName.equals("count")) { collectCount = false; } else if (qualifiedName.equals("isbn")) { collectISBN = false; } } /** Since the "count" entry comes before the "book" * entry (which contains "isbn"), we have to temporarily * record all counts we see. Later, if we find a * matching "isbn" entry, we will record that temporary * count. */ public void characters(char[] chars, int startIndex, int endIndex) { if (collectCount || collectISBN) { String dataString = new String(chars, startIndex, endIndex).trim(); if (collectCount) { try { currentCount = Integer.parseInt(dataString); } catch(NumberFormatException nfe) { System.err.println("Ignoring malformed count: " + dataString); } } else if (collectISBN) { if (dataString.equals("0130897930")) { totalCount = totalCount + currentCount; } } } } /** Report the total number of copies ordered. * Gently chide underachievers. */ public void endDocument() throws SAXException { String message = "You ordered " + totalCount + " copies of \n" + "Core Web Programming Second Edition.\n"; if (totalCount < 250) { message = message + "Please order more next time!"; } else { message = message + "Thanks for your order."; } JOptionPane.showMessageDialog(null, message); } }
Listing 23.11 CountBooks.java
import javax.xml.parsers.*; import org.xml.sax.*; import org.xml.sax.helpers.*; /** A program using SAX to keep track of the number * of copies of Core Web Programming ordered. Entries * that look like this will be recorded:<XMP> * ... * <count>23</count> * <book> * <isbn>0130897930</isbn> * ... * </book> * * </XMP>All other entries will be ignored -- different books, * orders for yachts, things that are not even orders, etc. */ public class CountBooks { public static void main(String[] args) { String jaxpPropertyName = "javax.xml.parsers.SAXParserFactory"; // Pass the parser factory in on the command line with // -D to override the use of the Apache parser. if (System.getProperty(jaxpPropertyName) == null) { String apacheXercesPropertyValue = "org.apache.xerces.jaxp.SAXParserFactoryImpl"; System.setProperty(jaxpPropertyName, apacheXercesPropertyValue); } String filename; if (args.length > 0) { filename = args[0]; } else { String[] extensions = { "xml" }; WindowUtilities.setNativeLookAndFeel(); filename = ExtensionFileFilter.getFileName(".", "XML Files", extensions); if (filename == null) { filename = "orders.xml"; } } countBooks(filename); System.exit(0); } private static void countBooks(String filename) { DefaultHandler handler = new CountHandler(); SAXParserFactory factory = SAXParserFactory.newInstance(); try { SAXParser parser = factory.newSAXParser(); parser.parse(filename, handler); } catch(Exception e) { String errorMessage = "Error parsing " + filename + ": " + e; System.err.println(errorMessage); e.printStackTrace(); } } }