Overview
XML documents are hierarchical documents, where the same element names and namespaces might occur in several places, having different meaning, and in infinitive depth (recursive). As normal, the solution to big problems, is to divide them into small problems. In the context of XML parsing, this means parsing specific parts of XML in methods specific to that XML. For example, one piece of logic would parse an address:
<Address>
<Street>Odins vei</Street>
<Building>4</Building>
<Door>b</Door>
</Address>
i.e. you would have a method
AddressType parseAddress(...); // A
or
void parseAddress(...); // B
somewhere in your logic, taking XML inputs arguments and returning an object (result of B can be fetched from a field later).
SAX
SAX 'pushes' XML events, leaving it up to you to determine where the XML events belong in your program / data.
// method in stock SAX handler
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException
// .. your logic here for start element
}
In case of an 'Building' start element, you would need to determine that you are actually parsing an Address and then route the XML event to the method whose job it is to interpret Address.
StAX
StAX 'pulls' XML events, leaving it up to you to determine where in your program / data to receive the XML events.
// method in standard StAX reader
int event = reader.next();
if(event == XMLStreamConstants.START_ELEMENT) {
// .. your logic here for start element
}
Of course, you would always want to receive a 'Building' event in in the method whose job it is to interpret Address.
Discussion
The difference between SAX and StAX is that of push and pull. In both cases, the parse state must be handled somehow.
This translates to method B as typical for SAX, and method A for StAX. In addition, SAX must give B individual XML events, while StAX can give A multiple events (by passing an XMLStreamReader instance).
Thus B first check the previous state of the parsing and then handle each individual XML event and then store the state (in a field). Method A can just handle the XML events all at once by accessing the XMLStreamReader multiple times until satisfied.
Conclusion
StAX lets you structure your parsing (data-binding) code according to the XML structure; so in relation to SAX, the 'state' is implicit from the program flow for StAX, whereas in SAX, you always need to preserve some kind of state variable + route the flow according to that state, for most event calls.
I recommend StAX for all but the simplest documents. Rather move to SAX as an optimization later (but you'll probably want to go binary by then).
Follow this pattern when parsing using StAX:
public MyDataBindingObject parse(..) { // provide input stream, reader, etc
// set up parser
// read the root tag to get to level 1
XMLStreamReader reader = ....;
do {
int event = reader.next();
if(event == XMLStreamConstants.START_ELEMENT) {
// check if correct root tag
break;
}
// add check for document end if you want to
} while(reader.hasNext());
MyDataBindingObject object = new MyDataBindingObject();
// read root attributes if any
int level = 1; // we are at level 1, since we have read the document header
do {
int event = reader.next();
if(event == XMLStreamConstants.START_ELEMENT) {
level++;
// do stateful stuff here
// for child logic:
if(reader.getLocalName().equals("Whatever1")) {
WhateverObject child = parseSubTreeForWhatever(reader);
level --; // read from level 1 to 0 in submethod.
// do something with the result of subtree
object.setWhatever(child);
}
// alternatively, faster
if(level == 2) {
parseSubTreeForWhateverAtRelativeLevel2(reader);
level --; // read from level 1 to 0 in submethod.
// do something with the result of subtree
object.setWhatever(child);
}
} else if(event == XMLStreamConstants.END_ELEMENT) {
level--;
// do stateful stuff here, too
}
} while(level > 0);
return object;
}
So the submethod uses about the same approach, i.e. counting level:
private MySubTreeObject parseSubTree(XMLStreamReader reader) throws XMLStreamException {
MySubTreeObject object = new MySubTreeObject();
// read element attributes if any
int level = 1;
do {
int event = reader.next();
if(event == XMLStreamConstants.START_ELEMENT) {
level++;
// do stateful stuff here
// for child logic:
if(reader.getLocalName().equals("Whatever2")) {
MyWhateverObject child = parseMySubelementTree(reader);
level --; // read from level 1 to 0 in submethod.
// use subtree object somehow
object.setWhatever(child);
}
// alternatively, faster, but less strict
if(level == 2) {
MyWhateverObject child = parseMySubelementTree(reader);
level --; // read from level 1 to 0 in submethod.
// use subtree object somehow
object.setWhatever(child);
}
} else if(event == XMLStreamConstants.END_ELEMENT) {
level--;
// do stateful stuff here, too
}
} while(level > 0);
return object;
}
And then eventually you reach a level in which you will read the base types.
private MySetterGetterObject parseSubTree(XMLStreamReader reader) throws XMLStreamException {
MySetterGetterObject myObject = new MySetterGetterObject();
// read element attributes if any
int level = 1;
do {
int event = reader.next();
if(event == XMLStreamConstants.START_ELEMENT) {
level++;
// assume <FirstName>Thomas</FirstName>:
if(reader.getLocalName().equals("FirstName")) {
// read tag contents
String text = reader.getElementText()
if(text.length() > 0) {
myObject.setName(text)
}
level--;
} else if(reader.getLocalName().equals("LastName")) {
// etc ..
}
} else if(event == XMLStreamConstants.END_ELEMENT) {
level--;
// do stateful stuff here, too
}
} while(level > 0);
// verify that all required fields in myObject are present
return myObject;
}
This is quite straightforward and there is no room for misunderstandings. Just remember to decrement level correctly:
A. after you expected characters but got an END_ELEMENT in some tag which should contain chars (in the above pattern):
<Name>Thomas</Name>
was instead
<Name></Name>
The same is true for a missing subtree too, you get the idea.
B. after calling subparsing methods, which are called on start elements, and returns AFTER the corresponding end element, i.e. the parser is at one level lower than before the method call (the above pattern).
Note how this approach totally ignores 'ignorable' whitespace too, for more robust implementation.
Parsers
Go with Woodstox for most features or Aaalto-xml for speed.