package com.skatestown.invoice; import java.io.File; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import org.xml.sax.helpers.DefaultHandler; /** * Check Skatestown invoice totals using a SAX parser */ public class InvoiceCheckerSAX extends DefaultHandler implements InvoiceChecker { // Class-level data double runningTotal = 0.0; double total = 0.0; // invoice total // Utility for extracting amounts from content boolean isMoneyContent = false; double amount = 0.0; /** * Check invoice totals * @param inoviceXML Invoice XML document * @exception Exception any exception returned during processing */ public void checkInvoice(File invoiceXML) throws Exception { // Use default (non-validating) parser SAXParserFactory factory = SAXParserFactory.newInstance(); SAXParser saxParser = factory.newSAXParser(); // Parse the input, we are the handler of SAX events saxParser.parse(invoiceXML, this); } // SAX DocumentHandler methods public void startDocument() throws SAXException { runningTotal = 0.0; total = 0.0; isMoneyContent = false; } public void endDocument() throws SAXException { // Use delta equality to prevent cumulative binary arithmetic errors. // Choose delta = 1/2 of one cent if (Math.abs(runningTotal - total) >= .005) { throw new SAXException( "Invoice error: total is " + Double.toString(total) + " while our calculation shows a total of " + Double.toString(Math.round(runningTotal * 100) / 100.0)); } } public void startElement(String namespaceURI, String localName, String qualifiedName, Attributes attrs) throws SAXException { if (localName.equals("item")) { // Find item subtotal; add it to running total runningTotal += Integer.valueOf(attrs.getValue(namespaceURI, "quantity")).intValue() * Double.valueOf(attrs.getValue(namespaceURI, "unitPrice")).doubleValue(); } else if (localName.equals("tax") || (localName.equals("shippingAndHandling")) || (localName.equals("totalCost"))) { isMoneyContent = true; // prepare to extract money amount } } public void startElement(String namespaceURI, String localName, String qualifiedName) throws SAXException { if (isMoneyContent) { if (localName.equals("totalCost")) { total = amount; } else { // It must be tax or shipping and handling runningTotal += amount; } isMoneyContent = false; } } public void characters(char buf[], int offset, int len) throws SAXException { if (isMoneyContent) { String value = new String(buf, offset, len); amount = Double.valueOf(value).doubleValue(); } } }