StAX, like SAX, is stream-oriented but in StAX, the programmer is in control using a simple API based on the notion of a cursor that walks the XML document from beginning to end. The cursor can only move forward and points to a single point in the XML file[4]. Only a small part of the whole XML document needs to reside in memory at any single time, so this approach is quite memory-efficient, as it is for SAX. But as the programmer controls which methods are called when a given token is at the cursor, it is possible to add contextual information to each call for dealing with a token without the need of global variables as in SAX.
Creating a StAX parser is done via a factory in much the
same way as we have shown in the previous sections for DOM or SAX
parsers. Example 8.5 describes the main procedure which creates a
factory (line 25‑).
Then we configure the parser by setting some properties: here we want entities to be
interpreted by the parser instead of being passed directly to our program and we would
like to receive consecutive character nodes as a single concatenation of all these nodes.
Parsing is done using an
XMLStreamReader
created (line 28‑) from the
XMLInputFactory
. This reader will be an iterator-like object
that gets a new token at each call to next()
which returns its type
as an integer. This type can then be checked for equality with predefined constants in
the XMLEvent
class but, in our program, we prefer to use parser utility functions
such as isStartElement()
or isCharacters()
that check the type
of the current token.
In Example 8.5, we only deal with the start or end of an element or
with its character content.
Similarly to the DOM approach in Example 8.1, the compacting process
follows recursively the tree structure. After skipping what comes before the first element, we call
the compact
method (line 31‑)with
the current state of the XML stream which will process the current element and
its children before returning.
Within compact
(line 47‑), the type of
current token is checked and if it is the starting tag of an element (line 49‑
) then the name of the element is printed and
the indentation is updated. Then attributes and values are output properly indented
(line 53‑
). Each non empty children is
then printed by a recursive call to compact (line 65‑
). A closing bracket is output
when the end element tag is encountered which terminates the loop. The character content
(line 68‑
) is simply printed as is.
Example 8.5. [StAXCompact.java
] Text compaction of the cellar book (Example 2.2) with Java using the StAX model
1 import java.io.FileInputStream; import java.io.IOException; import javax.swing.JTree; 5 import javax.swing.tree.DefaultTreeModel; import javax.xml.stream.XMLInputFactory; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamReader; 10 public class StAXCompact { // production of string of spaces with a lazy StringBuffer private static StringBuffer blanks = new StringBuffer(); private static String blanks(int n){ for(int i=blanks.length();i<n;i++) 15 blanks.append(' '); return blanks.substring(0,n); } public static void main(String[] argv) {20 if (argv.length != 1) { System.out.println("Usage: java StAXCompact file"); return; } try { 25 XMLInputFactory xmlif = XMLInputFactory.newInstance();
xmlif.setProperty(XMLInputFactory.IS_REPLACING_ENTITY_REFERENCES, true); xmlif.setProperty(XMLInputFactory.IS_COALESCING,true); XMLStreamReader xmlsr =
xmlif.createXMLStreamReader(argv[0], new FileInputStream(argv[0])); 30 while(!xmlsr.isStartElement())xmlsr.next();
compact(xmlsr,"");
System.out.println(); // end last line xmlsr.close();
// restart to create the JTree 35 xmlsr=xmlif.createXMLStreamReader(argv[0], new FileInputStream(argv[0])); while(!xmlsr.isStartElement())xmlsr.next(); new TreeViewer(new JTree( new DefaultTreeModel(TreeViewer.jTreeBuild(xmlsr))
)).setVisible(true); 40 } catch (XMLStreamException ex) {
System.out.println(ex.getMessage()); } catch (IOException e) { System.out.println("IO Error on "+argv[0]); } 45 } private static void compact(XMLStreamReader xmlsr,String indent)
throws XMLStreamException{ if(xmlsr.isStartElement()){
50 String localName = xmlsr.getLocalName(); System.out.print(localName+'['); indent += blanks(localName.length()+1); int count = xmlsr.getAttributeCount(); // attributes
for (int i = 0; i < count; i++) { 55 if(i>0)System.out.print('\n'+indent); System.out.print("@"+xmlsr.getAttributeLocalName(i)+ '['+xmlsr.getAttributeValue(i)+']'); } boolean first=count==0; 60 while (true){
do {xmlsr.next();} while(xmlsr.isWhiteSpace()); if (xmlsr.isEndElement()) break; if (first) first=false; else System.out.print('\n'+indent); 65 compact(xmlsr,indent);
} System.out.print(']'); } else if (xmlsr.isCharacters()){
System.out.print(xmlsr.getText()); 70 } } }
|
Main method that first checks if a file name has been given as an argument to the program. |
|
Creates an input factory. |
|
Creates a new stream parser. |
|
Ignores the tokens that come before the first element (e.g. processing instructions or DTD). |
|
Calls the compacting process with the current token and then prints a newline to flush the content of the last line. |
|
Releases the data structure of the last streaming operation before opening the file for the second time, to build the JTree. |
|
Handles exceptions that can occur in both parsing and input file processing. |
|
Method to compact from the current token. |
|
If it is a start element tag, outputs the name of the element followed by an opening bracket and update the current indentation. |
|
Outputs each attribute name and value all indented except the first one. |
|
Loops on children nodes that are not whitespace and compacts each of them with the correct indentation. |
|
Recursive call to the compacting process. |
|
Prints the character content. |
[4] There is also a StAX iterator based API that represents the XML document as a set of discrete events that are pulled by the application in the order in which they show up in the document, but we will not consider this approach here.