SAX, the power API

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

Return to article

Listing 4. BestDeal.java
/*
 * XML By Example, chapter 8: SAX
 */

package com.psol.xbe2;

import java.util.*;
import org.xml.sax.*;
import java.io.IOException;
import org.xml.sax.helpers.*;
import java.text.MessageFormat;

/**
 * This class receives events from the SAX2Internal adapter
 * and does the comparison required.
 * This class holds the "business logic."
 * SAX event handling is done in an inner class.
 */

public class BestDeal
{
   /**
    * SAX event handler to adapt from the SAX interface to
    * best deal data structure.
    */

   protected class SAX2BestDeal
      extends DefaultHandler
   {
      /**
       * constants
       */

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

      /**
       * the current state
       */
      protected int state = START;

      /**
       * current leaf element and current vendor
       */
      protected String vendorName = null;
      protected StringBuffer buffer = null;
      protected int delivery = Integer.MAX_VALUE;

      /**
       * startElement event
       * @param uri namespace URI
       * @param name local name
       * @param qualifiedName qualified name (with prefix)
       * @param attributes attributes list
       */
      public void startElement(String uri,
                               String name,
                               String qualifiedName,
                               Attributes attributes)
         throws SAXException
      {
         if(!uri.equals(NAMESPACE_URI))
            return;
         // this accept many combinations of elements
         // it would work if new elements where being added, etc.
         // this ensures maximal flexibility: if the document
         // has to be validated, use a validating parser
         switch(state)
         {
            case START:
               if(name.equals("price-list"))
                  state = PRICE_LIST;
               break;
            case PRICE_LIST:
               if(name.equals("name"))
               {
                  state = PRICE_LIST_NAME;
                  buffer = new StringBuffer();
               }
               if(name.equals("vendor"))
                  state = VENDOR;
               break;
            case VENDOR:
               if(name.equals("name"))
               {
                  state = VENDOR_NAME;
                  buffer = new StringBuffer();
               }
               if(name.equals("price-quote"))
               {
                  state = VENDOR_PRICE_QUOTE;
                  String st = attributes.getValue("","delivery");
                  delivery = Integer.parseInt(st);
                  buffer = new StringBuffer();
               }
               break;
         }
      }

      /**
       * content of the element
       * @param chars documents characters
       * @param start first character in the content
       * @param length last character in the content
       */
      public void characters(char[] chars,int start,int length)
      {
         switch(state)
         {
            case PRICE_LIST_NAME:
            case VENDOR_NAME:
            case VENDOR_PRICE_QUOTE:
               buffer.append(chars,start,length);
               break;
         }
      }

      /**
       * endElement event
       * @param uri namespace URI
       * @param name local name
       * @param qualifiedName qualified name (with prefix)
       */
      public void endElement(String uri,
                             String name,
                             String qualifiedName)
      {
         if(!uri.equals(NAMESPACE_URI))
            return;
         switch(state)
         {
            case PRICE_LIST_NAME:
               if(name.equals("name"))
               {
                  state = PRICE_LIST;
                  setProductName(buffer.toString());
                  buffer = null;
               }
               break;
            case VENDOR_NAME:
               if(name.equals("name"))
               {
                  state = VENDOR;
                  vendorName = buffer.toString();
                  buffer = null;
               }
               break;
            case VENDOR_PRICE_QUOTE:
               if(name.equals("price-quote"))
               {
                  state = VENDOR;
                  double price = 0.0;
                  Double stringDouble =
                     Double.valueOf(buffer.toString());
                  if(null != stringDouble)
                     price = stringDouble.doubleValue();
                  compare(vendorName,price,delivery);
                  delivery = Integer.MAX_VALUE;
                  buffer = null;
               }
               break;
            case VENDOR:
               if(name.equals("vendor"))
               {
                  state = PRICE_LIST;
                  vendorName = null;
               }
               break;
            case PRICE_LIST:
               if(name.equals("price-list"))
                  state = START;
               break;
         }
      }
   }

   /**
    * constant
    */
   protected static final String
      MESSAGE =
         "The best deal is proposed by {0}. " +
         "A(n) {1} delivered in {2,number,integer} days for " +
         "{3,number,currency}",
      NAMESPACE_URI = "http://www.psol.com/xbe2/listing8.3",
      PARSER_NAME = "org.apache.xerces.parsers.SAXParser";

   /**
    * properties we are collecting: best price, delivery time,
    * product and vendor names
    */
   public double price = Double.MAX_VALUE;
   public int delivery = Integer.MAX_VALUE;
   public String product = null,
                 vendor = null;

   /**
    * target delivery value (refuse elements above this target)
    */
   protected int targetDelivery;

   /**
    * creates a BestDeal
    * @param td the target for delivery
    */
   public BestDeal(int td)
   {
      targetDelivery = td;
   }

   /**
    * called by SAX2Internal when it has found the product name
    * @param name the product name
    */
   public void setProductName(String name)
   {
      product = name;
   }

   /**
    * called by SAX2Internal when it has found a price
    * @param vendor vendor's name
    * @param price price proposal
    * @param delivery delivery time proposal
    */
   public void compare(String vendor,double price,int delivery)
   {
      if(delivery <= targetDelivery)
      {
         if(this.price > price)
         {
            this.price = price;
            this.vendor = vendor;
            this.delivery = delivery;
         }
      }
   }

   /**
    * return a ContentHandler that populates this object
    * @return the ContentHandler
    */
   public ContentHandler getContentHandler()
   {
      return new SAX2BestDeal();
   }

   /**
    * main() method
    * decodes command-line parameters and invoke the parser
    * @param args command-line argument
    * @throw Exception catch-all for underlying exceptions
    */
   public static void main(String[] args)
      throws IOException, SAXException
   {
      if(args.length < 2)
      {
         System.out.println(
            "java com.psol.xbe2.BestDeal file delivery");
         return;
      }

      BestDeal bestDeal = new BestDeal(Integer.parseInt(args[1]));

      XMLReader parser =
         XMLReaderFactory.createXMLReader(PARSER_NAME);
      parser.setContentHandler(bestDeal.getContentHandler());
      parser.parse(args[0]);

      Object[] objects = new Object[]
      {
          bestDeal.vendor,
          bestDeal.product,
          new Integer(bestDeal.delivery),
          new Double(bestDeal.price)
      };
      System.out.println(MessageFormat.format(MESSAGE,objects));
   }
}

Return to article