SAX Building is where you stream through a very large XML dataset that could never be loaded into memory, and pick out certain elements to convert into Java objects in your own program.
Unlike DOM parsing, which builds a DOM-tree in memory, SAX building relies on you supplying a Builder to construct the Java objects of specific interest to your program.
SAX building uses a streaming push-parser, which invokes call-back methods on a builder in response to certain recognised events, such as the start or end of an XML tag. These are the main components:
XMLParser is the streaming push-parser that scans the XML dataset and makes call-backs to a builder;
Builder is an abstract interface, declaring the required call-back API of any builder;
BasicBuilder is a default implementation of the Builder interface, whose methods do nothing (they are null-ops).
The idea is that you provide your own subclass of BasicBuilder that overrides the necessary call-back methods to do whatever you want.
In addition, two more pre-defined Builders are supplied, which replicate the behaviour of other tools in the JAST toolset:
XMLBuilder is a builder that replicates the behaviour of XMLReader;
ASTBuilder is a builder that replicates the behaviour of ASTReader.
SAX building tools are all in the Java package:
uk.ac.sheffield.jast.build
See the Javadoc documentation for the API of these classes. See below for an example.
XMLParser is the parser to use for streaming an XML dataset. It invokes call-back methods on your own Builder. It has four main constructors:
XMLParser(File file, String encoding)
XMLParser(URL url, String encoding)
XMLParser(InputStream stream,
String encoding)
XMLParser(Reader reader,
String encoding)
These allow you to read from different kinds of source (from a file, from a web URL, or from another stream). They all require the character encoding (e.g. "UTF-8") as a String.
XMLParser is found in the package:
uk.ac.sheffield.jast.build
Reading from a local file is easily done using the following Java code:
Builder builder = ... // you supply
File file =
new File("my/xml/input.xml");
XMLParser reader =
new XMLParser(file, "UTF-8");
reader.setBuilder(builder);
Object result =
reader.readDocument();
reader.close();
You must install your own Builder, before reading the XML file. If no builder is installed, the parser uses a BasicBuilder, which ignores all events.
The result returned by your Builder could be any kind of Java Object, or null if nothing of interest was found. You will typically cast the type down to some known class in your program.
How you access the result depends on the API your own program provided.
Reading from a URL on the web is also easily done using the following Java code:
Builder builder = ... // you supply
URL url = new URL(
"https://www.my.site/input.xml");
XMLParser reader =
new XMLParser(url, "ISO-8859-1");
reader.setBuilder(builder);
Object result =
reader.readDocument();
reader.close();
You must install your own Builder, before reading the XML file. If no builder is installed, the parser uses a BasicBuilder, which ignores all events.
This shows that you can specify a different character encoding Latin-1, which used to be standard for the HTTP protocol (probably UTF-8 now). Note that JAST raises an exception if the expected and actual encodings do not match.
SAX Building has no equivalent symmetrical output operations. This is because what is built in memory is not a whole dataset, but only a small sample of the XML dataset.
Furthermore, since the streaming behaviour ignores much of the data, it would not be possible to try to insert modified values into this dataset while it is being streamed.
If you wish to load and modify an XML dataset, use the XMLReader and XMLWriter instead.
Builder is an interface that defines the API your own builder must supply. Its methods are called in response to events recognised by the XMLParser.
The following methods are triggered at the start or end of specific XML content.
public void startDocument();
public void endDocument();
public void startDeclaration(String target);
public void endDeclaration();
public void startInstruction(String target);
public void endInstruction();
public void startDoctype(String root);
public void endDoctype();
public void startElement(String identifier);
public void endElement();
For example, startElement(String) is called at the opening tag of an XML element, with the name of that element. Your builder could create some object at this point and push it onto a stack.
The corresponding method endElement() is called at the closing tag of an XML element. Your builder could pop the current object from the top of the stack.
The following methods are triggered in response to attribute, text or comment data:
public void addAttribute(String identifier, String value);
public void addComment(String text);
public void addEscapedData(String text);
public void addLayoutText(String text);
public void addPrintingText(String text);
Your builder may choose to provide versions of these that do something with relevant textual data, or simply ignore them, if not needed.
The following additional methods are provided for general access to the owning parser, and the lexicon for decoding XML entity references.
public Object getDocument();
public XMLParser getParser();
public void setParser(XMLParser parser);
public Lexicon getLexicon();
public void setLexicon(Lexicon lexicon);
If your builder is a subclass of BasicBuilder, then you only need to implement methods for the events that your application is interested in; and it will inherit null-op methods for the rest.
See the Javadoc documentation for further details about these operations.
Builder and BasicBuilder are in the Java package:
uk.ac.sheffield.jast.build
The following is a toy example file of XML data, stored in the file: Family.xml. A real dataset for SAX Building would be much larger.
<?xml version="1.0" encoding="UTF-8"?>
<Family>
<!-- The Smith family -->
<Person role="father" age="45">
John Smith
</Person>
<Person role="mother" age="41">
Mary Smith
</Person>
<Person role="son" age="16">
Ben Smith
</Person>
<Person role="daughter" age="14">
Alice Smith
</Person>
</Family>
Below, we show how to respond to specific events raised when streaming this XML file.
Assume that the goal of your application is to extract voting adults from the above dataset. Your Java application may have a class Voters that maintains a List of Person objects who are eligible to vote, if they are aged 18 or over.
public class Voters {
private List<Person> voters;
public void addVoter(Person voter) {
voters.add(voter);
}
public List<Voter> getVoters() {
return voters;
}
... // other methods
public Voters() {
voters = new ArrayList<Person>();
}
}
public class Person {
private String name;
private int age;
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setAge(int age) {
this.age = age;
}
public int getAge() {
return age;
}
... // other methods
public Person() {}
}
We have only sketched the code for these two classes. You may provide whatever API you want for the voting application.
The following is a design for your own builder, called VoterBuilder. We will explain this in easy stages. First, the builder must have fields to store its interim results:
public class VoterBuilder extends BasicBuilder {
private Voters voters = new Voters();
private Person person = null;
The variable voters stores an instance of your own Voters class to hold the results;
The variable person is a temporary placeholder for a possible Person of interest.
Next, we need to respond to events raised at the start and end of XML elements:
public void startElement(String identifier) {
if (identifer.equals("Person") {
person = new Person();
}
}
public void endElement() {
if (person != null) {
voters.addVoter(person);
person = null;
}
}
If the name of the XML element is "Person", then we create a temporary person. At the end of an XML element, if there exists a person, then we add this to the voters, and set the placeholder to null.
Next, we need to respond to events raised when an attribute with a value is detected:
public void addAttribute(String identifier, String value) {
if (person != null &&
identifier.equals("age")) {
try {
person.setAge(
Integer.parseInt(value));
if (person.getAge() < 18)
person = null;
}
catch(NumberFormatException ex){
person = null;
}
}
}
We are only interested if there is a current valid person, and if the attribute is called "age". We try to convert the String to an int. If this fails, or if the age is less than 18, we immediately discard the current person, which is no longer of interest.
Next, we want to capture printing text to store the name of a given Person:
public void addPrintingText(String text) {
if (person != null) {
person.setName(text);
}
}
Our VoterBuilder knows that if there is a current valid person, then any text must be the name.
Finally, we want to provide an access method to return the Voters object, once the XMLParser has finished:
public Voters getDocument() {
return voters;
}
} // end of class VoterBuilder
XMLParser's method readDocument() will call the method getDocument() on its builder, when it has finished scanning the XML dataset.
VoterBuilder will receive call-backs for every XML element, every attribute, and every piece of text. So it must be able to recognise only those data that are relevant to the application.
It ignores any element that is not called "Person";
It ignores any attribute that is not called "age";
If the age is less than 18, it immediately discards the person.
A simple placeholder variable person is sufficient for this dataset. In general, you may need to push and pop objects on a stack.
XMLParser raises certain Java exceptions if it encounters ill-formed XML or mis-matched character encodings. User programs must be prepared for these:
FileNotFoundException - if the requested file cannot be found;
UnsupportedEncodingException - if there is a mismatch between expected and actual character encodings;
IOException - if there is an underlying read or write failure while reading or writing a file;
SyntaxError - if XML data is syntactically incorrect, when reading a file;
SemanticError - if the construction rules for a DOM-tree are violated.
The last two are styled as fatal errors, rather than recoverable exceptions, since the W3C standard mandates this. A syntax error might be an XML opening tag with no matching closing tag. A semantic error might be trying to add a subnode to more than one parent node in a DOM-tree.