SAX

SAX

Elliotte Rusty Harold

Software Development 2000 East

Wednesday, Wednesday, November 1, 2000

elharo@metalab.unc.edu

http://metalab.unc.edu/xml/


Where we're going


SAX Requirements


Prerequisites


A simple example

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<?xml-stylesheet type="text/css" href="song.css"?>
<!DOCTYPE SONG SYSTEM "song.dtd">
<SONG xmlns="http://metalab.unc.edu/xml/namespace/song"
      xmlns:xlink="http://www.w3.org/1999/xlink">
  <TITLE>Hot Cop</TITLE>
  <PHOTO 
    xlink:type="simple" xlink:show="onLoad" xlink:href="hotcop.jpg"
    ALT="Victor Willis in Cop Outfit" WIDTH="100" HEIGHT="200"/>
  <COMPOSER>Jacques Morali</COMPOSER>
  <COMPOSER>Henri Belolo</COMPOSER>
  <COMPOSER>Victor Willis</COMPOSER>
  <PRODUCER>Jacques Morali</PRODUCER>
  <!-- The publisher is actually Polygram but I needed 
       an example of a general entity reference. -->
  <PUBLISHER xlink:type="simple" xlink:href="http://www.amrecords.com/">
    A &amp; M Records
  </PUBLISHER>
  <LENGTH>6:20</LENGTH>
  <YEAR>1978</YEAR>
  <ARTIST>Village People</ARTIST>
</SONG>
<!-- You can tell what album I was 
     listening to when I wrote this example -->
View in Browser

Markup and Character Data


Markup and Character Data Example

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<?xml-stylesheet type="text/css" href="song.css"?>
<!DOCTYPE SONG SYSTEM "song.dtd">
<SONG xmlns="http://metalab.unc.edu/xml/namespace/song"
      xmlns:xlink="http://www.w3.org/1999/xlink">
  <TITLE>Hot Cop</TITLE>
  <PHOTO 
    xlink:type="simple" xlink:show="onLoad" xlink:href="hotcop.jpg"
    ALT="Victor Willis in Cop Outfit" WIDTH="100" HEIGHT="200"/>
  <COMPOSER>Jacques Morali</COMPOSER>
  <COMPOSER>Henri Belolo</COMPOSER>
  <COMPOSER>Victor Willis</COMPOSER>
  <PRODUCER>Jacques Morali</PRODUCER>
  <!-- The publisher is actually Polygram but I needed 
       an example of a general entity reference. -->
  <PUBLISHER xlink:type="simple" xlink:href="http://www.amrecords.com/">
    A &amp; M Records
  </PUBLISHER>
  <LENGTH>6:20</LENGTH>
  <YEAR>1978</YEAR>
  <ARTIST>Village People</ARTIST>
</SONG>
<!-- You can tell what album I was 
     listening to when I wrote this example -->

Entities


Parsed Character Data


CDATA sections


Comments


Processing Instructions


The XML Declaration

<?xml version="1.0" encoding="UTF-8" standalone="no"?>

Document Type Declaration

<!DOCTYPE SONG SYSTEM "song.dtd">


Document Type Definition

<!ELEMENT SONG (TITLE, COMPOSER+, PRODUCER*, 
 PUBLISHER*, YEAR?, LENGTH?, ARTIST+)>

<!ELEMENT TITLE (#PCDATA)>

<!ELEMENT COMPOSER (#PCDATA)>
<!ELEMENT PRODUCER (#PCDATA)>
<!ELEMENT PUBLISHER (#PCDATA)>
<!ELEMENT LENGTH (#PCDATA)>
<!-- This should be a four digit year like "1999",
     not a two-digit year like "99" -->
<!ELEMENT YEAR (#PCDATA)>

<!ELEMENT ARTIST (#PCDATA)>

XML Names


XML Namespaces


Namespace Syntax


Namespace URIs


Binding Prefixes to Namespace URIs


The Default Namespace


How Parsers Handle Namespaces


Canonical XML


Part II: Reading XML Documents with SAX


Parser APIs


SAX


SAX Parsers for Java

Parser URL Validating Namespaces DOM1 DOM2 SAX1 SAX2 License
Apache XML Project's Xerces Java http://xml.apache.org/xerces-j/index.html X X X X X X Apache Software License, Version 1.1
IBM's XML for Java http://www.alphaworks.ibm.com/formula/xml X X X X X X License
James Clark's XP http://www.jclark.com/xml/xp/index.html      X  Modified BSD
Microstar's Ælfred http://home.pacbell.net/david-b/xml/ Namespaces DOM1DOM2SAX1SAX2open source
Silfide's SXP http://www.loria.fr/projets/XSilfide/EN/sxp/    X   X  Non-GPL viral open source license
Sun's Java API for XML http://java.sun.com/products/xml X X X   X   free beer
Oracle's XML Parser for Java http://technet.oracle.com/ X X X   X  free beer

What SAX1 doesn't do


SAX2


The SAX2 Process


Making an XMLReader


Parsing a Document with XMLReader

import org.xml.sax.*;
import org.xml.sax.helpers.*;
import java.io.*;


public class SAX2Checker {

  public static void main(String[] args) {
    
    if (args.length == 0) {
      System.out.println("Usage: java SAX2Checker URL1 URL2..."); 
    } 
    
    // set up the parser 
    XMLReader parser;
    try {
      parser = XMLReaderFactory.createXMLReader();
    } 
    catch (SAXException e) {
      try {
        parser = XMLReaderFactory.createXMLReader("org.apache.xerces.parsers.SAXParser");
      }
      catch (SAXException e2) {
        System.err.println("Error: could not locate a parser.");
        return;
      }
    }
     
    // start parsing... 
    for (int i = 0; i < args.length; i++) {
      
      // command line should offer URIs or file names
      try {
        parser.parse(args[i]);
        // If there are no well-formedness errors
        // then no exception is thrown
        System.out.println(args[i] + " is well formed.");
      }
      catch (SAXParseException e) { // well-formedness error
        System.out.println(args[i] + " is not well formed.");
        System.out.println(e.getMessage()
         + " at line " + e.getLineNumber() 
         + ", column " + e.getColumnNumber());
      }
      catch (SAXException e) { // some other kind of error
        System.out.println(e.getMessage());
      }
      catch (IOException e) {
        System.out.println("Could not check " + args[i] 
         + " because of the IOException " + e);
      }
      
    }  
  
  }

}

Sample Output from SAX2Checker

C:\>java SAX2Checker http://metalab.unc.edu/xml/
http://metalab.unc.edu/xml/ is not well formed.
The element type "dt" must be terminated by the 
matching end-tag "</dt>". 
at line 186, column 5

The ContentHandler interface

package org.xml.sax;


public interface ContentHandler {

    public void setDocumentLocator(Locator locator);
    
    public void startDocument() throws SAXException;
    
    public void endDocument()	throws SAXException;
    
    public void startPrefixMapping(String prefix, String uri) 
     throws SAXException;

    public void endPrefixMapping(String prefix) throws SAXException;

    public void startElement(String namespaceURI, String localName,
		 String qualifiedName, Attributes atts) throws SAXException;

    public void endElement(String namespaceURI, String localName,
     String qualifiedName) throws SAXException;

    public void characters(char[] ch, int start, int length) 
     throws SAXException;

    public void ignorableWhitespace(char ch[], int start, int length)
     throws SAXException;

    public void processingInstruction(String target, String data)
     throws SAXException;

    public void skippedEntity(String name) throws SAXException;
     
}

SAX2 Event Reporter

import org.xml.sax.*;
import org.xml.sax.helpers.*;
import java.io.*;

public class EventReporter implements ContentHandler {

  public void setDocumentLocator(Locator locator) {
    System.out.println("setDocumentLocator(" + locator + ")");         
  }
  
  public void startDocument() throws SAXException {
    System.out.println("startDocument()"); 
  }

  public void endDocument() throws SAXException {
    System.out.println("endDocument()"); 
  }
  
  public void startElement(String namespaceURI, String localName, String qName, Attributes atts)
   throws SAXException {
    namespaceURI = '"' + namespaceURI + '"';
    localName = '"' + localName + '"';
    qName = '"' + qName + '"';
    String attributeString = "{";
    for (int i = 0; i < atts.getLength(); i++) {
      attributeString += atts.getQName(i) + "=\"" + atts.getValue(i) + "\"";
      if (i != atts.getLength()-1) attributeString += ", ";
    }
    attributeString += "}";
    System.out.println("startElement(" + namespaceURI + ", " + localName + ", " 
    + qName + ", " + attributeString + ")"); 
  }
  
  public void endElement(String namespaceURI, String localName, String qName) 
   throws SAXException {
    namespaceURI = '"' + namespaceURI + '"';
    localName = '"' + localName + '"';
    qName = '"' + qName + '"';
    System.out.println("endElement(" + namespaceURI + ", " + localName + ", " 
    + qName + ")"); 
  }
  
  public void characters(char[] text, int start, int length) 
   throws SAXException {
    String textString = "[" + new String(text) + "]";
    System.out.println("characters(" + textString + ", " + start + ", " +  length + ")"); 
  }
  
  public void ignorableWhitespace(char[] text, int start, int length)
   throws SAXException {
    System.out.println("ignorableWhitespace()"); 
  }
  
  public void processingInstruction(String target, String data)
   throws SAXException {
    System.out.println("processingInstruction(" + target + ", " + data + ")"); 
  }

  public void startPrefixMapping(String prefix, String uri) 
   throws SAXException {
    System.out.println("startPrefixMapping(\"" + prefix + "\", \"" + uri + "\")");         
  }
  
  public void endPrefixMapping(String prefix) throws SAXException {
    System.out.println("startPrefixMapping(\"" + prefix + "\")");                 
  }

  public void skippedEntity(String name) throws SAXException {
    System.out.println("skippedEntity(" + name + ")");                         
  }

  // Could easily have put main() method in a separate class
  public static void main(String[] args) {
    
    XMLReader parser;
    try {
     parser = XMLReaderFactory.createXMLReader();
    }
    catch (Exception e) {
      // fall back on Xerces parser by name
      try {
        parser = XMLReaderFactory.createXMLReader(
         "org.apache.xerces.parsers.SAXParser");
      }
      catch (Exception ee) {
        System.err.println("Couldn't locate a SAX parser");
        return;          
      }
    }

     
    if (args.length == 0) {
      System.out.println(
       "Usage: java EventReporter URL1 URL2..."); 
    } 
      
    // Install the Document Handler      
    parser.setContentHandler(new EventReporter());
    
    // start parsing... 
    for (int i = 0; i < args.length; i++) {
      
      // command line should offer URIs or file names
      try {
        parser.parse(args[i]);
      }
      catch (SAXParseException e) { // well-formedness error
        System.out.println(args[i] + " is not well formed.");
        System.out.println(e.getMessage()
         + " at line " + e.getLineNumber() 
         + ", column " + e.getColumnNumber());
      }
      catch (SAXException e) { // some other kind of error
        System.out.println(e.getMessage());
      }
      catch (IOException e) {
        System.out.println("Could not report on " + args[i] 
         + " because of the IOException " + e);
      }
      
    }  
  
  }

}

Event Reporter Output



UserLand's RSS based list of Web logs

Full list

Goal: Return a list of all the URLs in this list as java.net.URL objects

Design Decisions


SAX Design


User Interface Class

import org.xml.sax.*;
import org.xml.sax.helpers.*;
import java.util.*;
import java.io.*;

public class Weblogs {
     
  public static List listChannels() 
   throws IOException, SAXException {
    return listChannels(
     "http://static.userland.com/weblogMonitor/logs.xml"); 
  }
  
  public static List listChannels(String uri) 
   throws IOException, SAXException {
    
    // set up the parser 
    XMLReader parser;
    try {
      parser = XMLReaderFactory.createXMLReader();
    } 
    catch (SAXException e) {
      try {
        parser = XMLReaderFactory.createXMLReader("org.apache.xerces.parsers.SAXParser");
      }
      catch (SAXException e2) {
        System.err.println("Error: could not locate a parser.");
        return null;
      }
    }
    
    Vector urls = new Vector(1000);
    URIGrabber u = new URIGrabber(urls);
    parser.setContentHandler(u);
    parser.parse(uri);
    return urls;
    
  }
  
  public static void main(String[] args) {
   
    try {
      List urls;
      if (args.length > 0) urls = listChannels(args[0]);
      else urls = listChannels();
      Iterator iterator = urls.iterator();
      while (iterator.hasNext()) {
        System.out.println(iterator.next()); 
      }
    }
    catch (IOException e) {
      System.err.println(e); 
    }
    catch (SAXParseException e) {
      System.err.println(e); 
      System.err.println("at line " + e.getLineNumber() 
       + ", column " + e.getColumnNumber()); 
    }
    catch (SAXException e) {
      System.err.println(e); 
    }
    catch (/* Unexpected */ Exception e) {
      e.printStackTrace(); 
    }
    
  }
  
}


  

ContentHandler Class

import org.xml.sax.*;
import java.net.*;
import java.util.Vector;

             // conflicts with java.net.ContentHandler
class URIGrabber implements org.xml.sax.ContentHandler {
    
  private Vector urls;
     
  URIGrabber(Vector urls) {
    this.urls = urls;
  }
    
  // do nothing methods  
  public void setDocumentLocator(Locator locator) {}
  public void startDocument() throws SAXException {}
  public void endDocument() throws SAXException {}
  public void startPrefixMapping(String prefix, String uri) 
   throws SAXException {}
  public void endPrefixMapping(String prefix) throws SAXException {}
  public void skippedEntity(String name) throws SAXException {}  
  public void ignorableWhitespace(char[] text, int start, int length)
   throws SAXException {}
  public void processingInstruction(String target, String data)
   throws SAXException {}
  
  
  // Remember, there's no guarantee all the text of the
  // url element will be returned in a single call to characters
  private StringBuffer urlBuffer;
  private boolean collecting = false;
  
  public void startElement(String namespaceURI, String localName,
   String rawName, Attributes atts) throws SAXException {
	  
    if (rawName.equals("url")) {
      collecting = true;
      urlBuffer = new StringBuffer();
    } 
    
  }
  
  public void characters(char[] text, int start, int length) 
   throws SAXException {
    
    if (collecting) {
      urlBuffer.append(text, start, length);
    } 
    
  }
  
  public void endElement(String namespaceURI, String localName,
   String rawName) throws SAXException {
	  
    if (rawName.equals("url")) {
      collecting = false;
      String url = urlBuffer.toString();
      try {
        urls.addElement(new URL(url));
      }
      catch (MalformedURLException e) {
        // skip this url
      }
    }
    
  } 
    
}

Weblogs Output

% java Weblogs shortlogs.xml
http://www.mozillazine.org
http://www.salonherringwiredfool.com/
http://www.scripting.com/
http://www.slashdot.org/

Features and Properties


Feature/Property SAXExceptions


Required Features


Core Features

adapted from SAX2 documentation by David Megginson


Turning on Validation


Three Levels of Errors


The ErrorHandler interface

package org.xml.sax;

public interface ErrorHandler {
 
  public void warning(SAXParseException exception)
   throws SAXException;

  public void error(SAXParseException exception)
   throws SAXException;
    
  public void fatalError(SAXParseException exception)
   throws SAXException;
    
}

An ErrorHandler for Reporting Validity Errors

import org.xml.sax.*;
import java.io.*;


public class ValidityErrorReporter implements ErrorHandler {
 
  Writer out;
 
  public ValidityErrorReporter(Writer out) {
    this.out = out;
  }
 
  public ValidityErrorReporter() {
    this(new OutputStreamWriter(System.out));
  }
 
  public void warning(SAXParseException ex)
   throws SAXException {

    try {
      out.write(ex.getMessage() + "\r\n");
      out.write(" at line " + ex.getLineNumber() + ", column " 
       + ex.getColumnNumber() + "\r\n");
      out.flush();
    }
    catch (IOException e) {
      throw new SAXException(e); 
    }
    
  }

  public void error(SAXParseException ex)
   throws SAXException {
    
    try {
      out.write(ex.getMessage() + "\r\n");
      out.write(" at line " + ex.getLineNumber() + ", column " 
       + ex.getColumnNumber() + "\r\n");
      out.flush();
    }
    catch (IOException e) {
      throw new SAXException(e); 
    }
    
  }
    
  public void fatalError(SAXParseException ex)
   throws SAXException {
    
    try {
      out.write(ex.getMessage() + "\r\n");
      out.write(" at line " + ex.getLineNumber() + ", column " 
       + ex.getColumnNumber() + "\r\n");
      out.flush();
    }
    catch (IOException e) {
      throw new SAXException(e); 
    }
    
  }
    
}

Validating

import org.xml.sax.*;
import org.xml.sax.helpers.*;
import java.io.*;


public class Validator {

  public static void main(String[] args) {

    XMLReader parser = XMLReaderFactory.createXMLReader();

    // turn on validation
    try {
      parser.setFeature(
       "http://xml.org/sax/features/validation", true);
      parser.setErrorHandler(new ValidityErrorReporter());
    }
    catch (SAXNotRecognizedException e) {
      System.err.println(
       "Installed XML parser cannot validate;"
       + " checking for well-formedness instead...");
    }
    catch (SAXNotSupportedException e) {
      System.err.println(
       "Cannot turn on validation here; "
       + "checking for well-formedness instead...");
    }

    if (args.length == 0) {
      System.out.println("Usage: java Validator URL1 URL2...");
    }

    // start parsing...
    for (int i = 0; i < args.length; i++) {

      // command line should offer URIs or file names
      try {
        parser.parse(args[i]);
        // If there are no well-formedness errors,
        // then no exception is thrown
        System.out.println(args[i] + " is well formed.");
      }
      catch (SAXParseException e) { // well-formedness error
        System.out.println(args[i] + " is not well formed.");
        System.out.println(e.getMessage()
         + " at line " + e.getLineNumber()
         + ", column " + e.getColumnNumber());
      }
      catch (SAXException e) { // some other kind of error
        System.out.println(e.getMessage());
      }
      catch (IOException e) {
        System.out.println("Could not check " + args[i]
         + " because of the IOException " + e);
      }

    }

  }

}

Schemas


Schema for Songs

<xsd:schema xmlns:xsd="http://www.w3.org/1999/XMLSchema">
 
  <xsd:element name="SONG" type="SongType"/>

  <xsd:complexType name="SongType">
  
    <xsd:element name="TITLE"     type="xsd:string" minOccurs="1" maxOccurs="1"/>
    <xsd:element name="COMPOSER"  type="xsd:string" minOccurs="1" maxOccurs="unbounded"/>
    <xsd:element name="PRODUCER"  type="xsd:string" minOccurs="0" maxOccurs="unbounded"/>
    <xsd:element name="PUBLISHER" type="xsd:string" minOccurs="0" maxOccurs="1"/>
  
    <xsd:element name="LENGTH" type="xsd:timeDuration" minOccurs="1" maxOccurs="1"/>
    <xsd:element name="YEAR"   type="xsd:year" minOccurs="1" maxOccurs="1"/>

    <xsd:element name="ARTIST" type="xsd:string" minOccurs="0" maxOccurs="unbounded"/>
    
  </xsd:complexType>

</xsd:schema>

Core Properties

adapted from SAX2 documentation by David Megginson


Nonstandard Features in Xerces


Nonstandard Properties in Xerces


Properties for Extension Handlers


Handling Attributes in SAX2


Attributes Example

import org.xml.sax.*;
import org.apache.xerces.parsers.*;
import java.io.*;
import java.util.*;
import org.xml.sax.helpers.*;


public class XLinkSpider extends DefaultHandler {

  public static Enumeration listURIs(String systemId) 
   throws SAXException, IOException {
    
    // set up the parser 
    XMLReader parser;
    try {
      parser = XMLReaderFactory.createXMLReader();
    } 
    catch (SAXException e) {
      try {
        parser = XMLReaderFactory.createXMLReader("org.apache.xerces.parsers.SAXParser");
      }
      catch (SAXException e2) {
        System.err.println("Error: could not locate a parser.");
        return null;
      }
    }
      
    // Install the Content Handler   
    XLinkSpider spider = new XLinkSpider();   
    parser.setContentHandler(spider);
    parser.parse(systemId);
    return spider.uris.elements();
      
  }
  
  private Vector uris = new Vector();

  public void startElement(String namespaceURI, String localName, 
   String rawName, Attributes atts) throws SAXException {
    
     String uri = atts.getValue("http://www.w3.org/1999/xlink", "href");
     if (uri != null) uris.addElement(uri);
    
  }
  

  public static void main(String[] args) {
    
    if (args.length == 0) {
      System.out.println("Usage: java XLinkSpider URL1 URL2..."); 
    } 
      
    // start parsing... 
    for (int i = 0; i < args.length; i++) {
      
      try {
        Enumeration uris = listURIs(args[i]);
        while (uris.hasMoreElements()) {
          String s = (String) uris.nextElement();
          System.out.println(s);
        }
      }
      catch (Exception e) {
        System.err.println(e);
        e.printStackTrace(); 
      }
      
    } // end for
  
  } // end main

} // end XLinkSpider

Resolving Entities


EntityResolver Example

import org.xml.sax.*;

public class RSSResolver implements EntityResolver {

  public InputSource resolveEntity(String publicId, String systemId) {

    if (publicId.equals("-//Netscape Communications//DTD RSS 0.91//EN")
     || systemId.equals("http://my.netscape.com/publish/formats/rss-0.91.dtd")) {
      return new InputSource("http://metalab.unc.edu/xml/dtds/rss.dtd");
    } 
    else {
      // use the default behaviour
      return null;
    }
    
  }
   
}
 

Handling DTDs


DTDHandler Example


TextEntityReplacer

import org.xml.sax.*;
import java.util.*;
import java.net.*;
import java.io.*;


public class TextEntityReplacer implements DTDHandler {

  /* This class stores the notation and entity declarations 
     for a single document. It is not designed to be reused
     for multiple parses, though that would be straightforward
     extension. The public and system IDs of the document
     being parsed are set in the constructor.    
  */ 
  
  private URL systemID;
  private String publicID;
  
  public TextEntityReplacer(String publicID, String systemID) 
   throws MalformedURLException {
    System.err.println("created");
    this.publicID = publicID;
    this.systemID = new URL(systemID);
  }

  // store all notations in a hashtable. We'll need them later
  private Hashtable notations = new Hashtable();

  // for the DTDHandler interface
  public void notationDecl(String name, String publicID, String systemID)
   throws SAXException {
    
    Notation n = new Notation(name, publicID, systemID);
    notations.put(name, n);
    
  }
  
  private class Notation {
    
    String name;
    String publicID;
    String systemID;
    
    Notation(String name, String publicID, String systemID) {
      this.name = name;
      this.publicID = publicID;
      this.systemID = systemID;
    } 
    
  }
 
   
  // store all unparsed entities in a hashtable. We'll need them later
  private Hashtable unparsedEntities = new Hashtable();

  // for the DTDHandler interface
  public void unparsedEntityDecl(String name, String publicID, 
   String systemID, String notationName) throws SAXException {
    
    UnparsedEntity e = new UnparsedEntity(name, publicID, systemID, notationName);
    unparsedEntities.put(name, e);
    
  }    

  private class UnparsedEntity {
    
    String name;
    String publicID;
    String systemID;
    String notationName;
    
    UnparsedEntity(String name, String publicID, String systemID, String notationName) {
      this.name = name;
      this.notationName = notationName;
      this.publicID = publicID;
      this.systemID = systemID;
    } 
    
  }


  public boolean isText(String notationName) {
    
    Object o = notations.get(notationName);
    if (o == null) return false;
    Notation n = (Notation) o;
    if (n.systemID.startsWith("text/")) return true;
    return false;
    
  }
  
  public String getText(String entityName) throws IOException {
    
    Object o = unparsedEntities.get(entityName);
    if (o == null) return "";
    UnparsedEntity entity = (UnparsedEntity) o;
    if (!isText(entity.notationName)) {
      return " binary data "; // could throw an exception instead
    }
    
    URL source;
    try {
      source = new URL(systemID, entity.systemID);     
    }
    catch (Exception e) {
      return " unresolvable entity "; // could throw an exception instead
    }
    
    // I'm not really handling characetr encodings here. 
    // A more detailed look at the MIME type would allow that.
    Reader in = new BufferedReader(new InputStreamReader(source.openStream()));
    StringBuffer result = new StringBuffer();
    int c;
    while ((c = in.read()) != -1) {
      // Is this necessaary or will parser escape string automatically????
   /*   switch (c) {
        case '<': 
          result.append("&lt;");
          break;
        case '>': 
          result.append("&gt;");
          break;
        case '"': 
          result.append("&quot;");
          break;
        case '\'': 
          result.append("&apos;");
          break;
        case '&': 
          result.append("&amp;");
          break;
        default:
          result.append((char) c); 
      }*/
      result.append((char) c);
    }
    
    return result.toString();
    
  }

}

Handling Declarations


The DeclHandler interface:

package org.xml.sax.ext;

import org.xml.sax.SAXException;


public interface DeclHandler {

  public void elementDecl(String name, String model)
   throws SAXException;

  public void attributeDecl(String elementName, String attributeName, 
   String type, String defaultValue, String value) throws SAXException;

  public void internalEntityDecl(String name, String value)
   throws SAXException;

  public void externalEntityDecl(String name, String publicId,
   String systemId) throws SAXException;

}

Handling Lexical Events


The LexicalHandler interface

package org.xml.sax.ext;

import org.xml.sax.SAXException;


public interface LexicalHandler {

  public void startDTD(String name, String publicId, String systemId)
     throws SAXException;
  public void endDTD() throws SAXException;
  public void startEntity(String name) throws SAXException;
  public void endEntity(String name) throws SAXException;
  public void startCDATA() throws SAXException;
  public void endCDATA() throws SAXException;
  public void comment (char[] text, int start, int length) 
   throws SAXException;

}

LexicalHandler Example

import org.xml.sax.*;
import org.xml.sax.ext.*;
import org.xml.sax.helpers.*;
import java.io.IOException;


public class SAXCommentReader implements LexicalHandler {

  public void startDTD(String name, String publicId, String systemId)
   throws SAXException {}
  public void endDTD() throws SAXException {}
  public void startEntity(String name) throws SAXException {}
  public void endEntity(String name) throws SAXException {}
  public void startCDATA() throws SAXException {}
  public void endCDATA() throws SAXException {}

  public void comment (char[] text, int start, int length)
   throws SAXException {

    String comment = new String(text, start, length);
    System.out.println(comment);

  }

  public static void main(String[] args) {

    // set up the parser
    XMLReader parser;
    try {
      parser = XMLReaderFactory.createXMLReader();
    }
    catch (SAXException e) {
      try {
        parser = XMLReaderFactory.createXMLReader("org.apache.xerces.parsers.SAXParser");
      }
      catch (SAXException e2) {
        System.err.println("Error: could not locate a parser.");
        return;
      }
    }

    // turn on comment handling
    try {
      parser.setProperty("http://xml.org/sax/properties/lexical-handler",
       new SAXCommentReader());
    }
    catch (SAXNotRecognizedException e) {
      System.err.println(
       "Installed XML parser does not provide lexical events...");
      return;
    }
    catch (SAXNotSupportedException e) {
      System.err.println(
       "Cannot turn on comment processing here");
      return;
    }

    if (args.length == 0) {
      System.out.println("Usage: java SAXCommentReader URL1 URL2...");
    }

    // start parsing...
    for (int i = 0; i < args.length; i++) {

      try {
        parser.parse(args[i]);
      }
      catch (SAXParseException e) { // well-formedness error
        System.out.println(args[i] + " is not well formed.");
        System.out.println(e.getMessage()
         + " at line " + e.getLineNumber()
         + ", column " + e.getColumnNumber());
      }
      catch (SAXException e) { // some other kind of error
        System.out.println(e.getMessage());
      }
      catch (IOException e) {
        System.out.println("Could not check " + args[i]
         + " because of the IOException " + e);
      }

    }

  }

}

The Locator interface


Locator Example

import org.xml.sax.*;
import org.apache.xerces.parsers.*; 
import java.io.*;


public class LocationReporter implements ContentHandler {

  Locator locator = null;

  public void setDocumentLocator(Locator locator) {
    this.locator = locator;  
  }
  
  private String reportPosition() {
    
    if (locator != null) {
      
      String publicID = locator.getPublicId();
      String systemID = locator.getSystemId();
      int line        = locator.getLineNumber();
      int column      = locator.getColumnNumber();
      
      String name;
      if (publicID != null) name = publicID;
      else name = systemID;
      
      return " in " + name + " at line " + line 
       + ", column " + column;
    }
    return "";
    
  }
  
  public void startDocument() throws SAXException {
    System.out.println("Document started" + reportPosition()); 
  }

  public void endDocument() throws SAXException {
    System.out.println("Document ended" + reportPosition()); 
  }
  
  public void characters(char[] text, int start, int length) 
   throws SAXException {
    System.out.println("Got some characters" + reportPosition()); 
  }
  
  public void ignorableWhitespace(char[] text, int start, int length)
   throws SAXException {
    System.out.println("Got some ignorable white space" 
     + reportPosition()); 
  }
  
  public void processingInstruction(String target, String data)
   throws SAXException {
    System.out.println("Got a processing instruction" 
     + reportPosition()); 
  }
  
  // Changed methods for SAX2
  public void startElement(String namespaceURI, String localName,
	 String rawName, Attributes atts) throws SAXException {
    System.out.println("Element " + rawName + " started" 
     + reportPosition()); 
  }
  
  public void endElement(String namespaceURI, String localName,
	 String rawName) throws SAXException {
    System.out.println("Element " + rawName + " ended" 
     + reportPosition()); 
  } 

  // new methods for SAX2
  public void startPrefixMapping(String prefix, String uri) 
   throws SAXException {
    System.out.println("Started mapping prefix " + prefix + " to URI " 
     + uri + reportPosition());     
  }

  public void endPrefixMapping(String prefix) throws SAXException {
    System.out.println("Stopped mapping prefix " 
     + prefix + reportPosition());         
  }

  public void skippedEntity(String name) throws SAXException {
    System.out.println("Skipped entity " + name + reportPosition());         
  }  

  // Could easily have put main() method in a separate class
  public static void main(String[] args) {
    
    XMLReader parser = new SAXParser();
     
    if (args.length == 0) {
      System.out.println(
       "Usage: java LocationReporter URL1 URL2..."); 
    } 
      
    // Install the Content Handler      
    parser.setContentHandler(new LocationReporter());
    
    // start parsing... 
    for (int i = 0; i < args.length; i++) {
      
      // command line should offer URIs or file names
      try {
        parser.parse(args[i]);
      }
      catch (SAXParseException e) { // well-formedness error
        System.out.println(args[i] + " is not well formed.");
        System.out.println(e.getMessage()
         + " at line " + e.getLineNumber() 
         + ", column " + e.getColumnNumber());
      }
      catch (SAXException e) { // some other kind of error
        System.out.println(e.getMessage());
      }
      catch (IOException e) {
        System.out.println("Could not report on " + args[i] 
         + " because of the IOException " + e);
      }
      
    }  
  
  }

}
View Output

The DefaultHandler class


The NamespaceSupport class


Filtering XML


XMLFilter Example

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


public class UnparsedTextFilter extends XMLFilterImpl {

  private TextEntityReplacer replacer;

  public UnparsedTextFilter(XMLReader parent) {
    super(parent);
    System.err.println("created UnparsedTextFilter");
  }

  public void parse(InputSource input) throws IOException, SAXException {
    System.err.println("parsing");
    replacer = new TextEntityReplacer(input.getPublicId(), input.getSystemId());
    this.setDTDHandler(replacer); 
  }
  // The other parse() method just calls this one 

  public void parse(String systemId) throws IOException, SAXException {
    parse(new InputSource(systemId)); 
  }

  public void startElement(String uri, String localName, 
   String rawName, Attributes attributes) throws SAXException {
    
    Vector extraText = new Vector();

    // Are there any unparsed entities in the attributes?
    for (int i = 0; i < attributes.getLength(); i++) {
      if (attributes.getType(i).equals("ENTITY")) {
        try {
          System.out.println("replacing");
          String s = replacer.getText(attributes.getValue(i));
          if (s != null) extraText.addElement(s);
        }
        catch (IOException e) {
          System.err.println(e); 
        }
      } 
      
    }    

    super.startElement(uri, localName, rawName, attributes);
    
    // Now spew out the values of the unparsed entities:
    Enumeration e = extraText.elements();
    while (e.hasMoreElements()) {
      Object o = e.nextElement();
      String s = (String) o;
      super.characters(s.toCharArray(), 0, s.length()); 
    }
    
  }

}

TextMerger

import org.xml.sax.*;
import org.xml.sax.helpers.*;
import java.util.*;
import java.io.IOException;
import org.apache.xerces.parsers.*; 
import org.apache.xml.serialize.*;


public class TextMerger {

  public static void main(String[] args) {
  
    System.err.println("starting");
    XMLReader parser = new UnparsedTextFilter(new SAXParser());
    
    //essentially a pretty printer
    XMLSerializer printer 
     = new XMLSerializer(System.out, new OutputFormat());
    
    parser.setContentHandler(printer);
    
    for (int i = 0; i < args.length; i++) {
      try {
        parser.parse(args[i]);
      }
      catch (SAXParseException e) { // well-formedness error
        System.out.println(args[i] + " is not well formed.");
        System.out.println(e.getMessage()
         + " at line " + e.getLineNumber() 
         + ", column " + e.getColumnNumber());
      }
      catch (SAXException e) { // some other kind of error
        System.out.println(e.getMessage());
      }
      catch (IOException e) {
        System.out.println("Could not report on " + args[i] 
         + " because of the IOException " + e);
      }      
    }
  
  }

}

InputSource


The InputSource interface

package org.xml.sax;

import java.io.*;

public class InputSource {

  public InputSource() 
  public InputSource(String systemID) 
  public InputSource(InputStream in)
  public InputSource(Reader in)

  public void setPublicId(String publicID)
  public String getPublicId()
  public void setSystemId(String systemID)
  public String getSystemId()

  public void setByteStream(InputStream byteStream)
  public InputStream getByteStream()
  public void setEncoding(String encoding)
  public String getEncoding()
  public void setCharacterStream(Reader characterStream)
  public Reader getCharacterStream()

}

Example of InputSource

import org.xml.sax;
import java.io.*;
import java.util.zip.*;
...
try {

  URL u = new URL("http://metalab.unc.edu/xml/examples/1998validstats.xml.gz"); 
  InputStream raw = u.openStream();
  InputStream decompressed = new GZIPInputStream(in);
  InputSource in = new InputSource(decompressed);
  // read the document... 

}
catch (IOException e) {
  System.err.println(e);
}
catch (SAXException e) {
  System.err.println(e);
}

What SAX2 doesn't do


Event Based API Caveats


To Learn More


Questions?


Index | Cafe con Leche

Copyright 2000 Elliotte Rusty Harold
elharo@metalab.unc.edu
Last Modified November 8, 2000