8.3. Stream API for XML (StAX)

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 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‑2). 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‑3) 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‑5)with the current state of the stream which will process the current element and its children before returning.

Within compact (line 47‑9), the type of current token is checked and if it is the starting tag of an element (line 49‑10) then the name of the element is printed and the indentation is updated. Then attributes and values are output properly indented (line 53‑11). Each non empty children is then printed by a recursive call to compact (line 65‑13). A closing bracket is output when the end element tag is encountered which terminates the loop. The character content (line 68‑14) 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) {                         (1)
 20         if (argv.length != 1) {
                System.out.println("Usage: java StAXCompact file");
                return;
            }
            try {
 25             XMLInputFactory xmlif = XMLInputFactory.newInstance();   (2)
                xmlif.setProperty(XMLInputFactory.IS_REPLACING_ENTITY_REFERENCES, true);
                xmlif.setProperty(XMLInputFactory.IS_COALESCING,true);
                XMLStreamReader xmlsr =                                  (3)
                    xmlif.createXMLStreamReader(argv[0], new FileInputStream(argv[0]));
 30             while(!xmlsr.isStartElement())xmlsr.next();              (4)
                compact(xmlsr,"");                                       (5)
                System.out.println(); // end last line
                xmlsr.close();                                           (6)
                // 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)) (7)
                                         )).setVisible(true);
 40         } catch (XMLStreamException ex) {                            (8)
                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) (9)
               throws XMLStreamException{
            if(xmlsr.isStartElement()){                                  (10)
 50             String localName = xmlsr.getLocalName();
                System.out.print(localName+'[');
                indent += blanks(localName.length()+1);
                int count = xmlsr.getAttributeCount();   // attributes   (11)
                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){                                            (12)
                    do {xmlsr.next();} while(xmlsr.isWhiteSpace());
                    if (xmlsr.isEndElement()) break;
                    if (first) first=false;
                    else System.out.print('\n'+indent);
 65                 compact(xmlsr,indent);                               (13)
                }
                System.out.print(']');
            } else if (xmlsr.isCharacters()){                            (14)
                System.out.print(xmlsr.getText());
 70         }
        }
    }
    

1

Main method that first checks if a file name has been given as an argument to the program.

2

Creates an input factory.

3

Creates a new stream parser.

4

Ignores the tokens that come before the first element (e.g. processing instructions or DTD).

5

Calls the compacting process with the current token and then prints a newline to flush the content of the last line.

6

Releases the data structure of the last streaming operation before opening the file for the second time, to build the JTree.

8

Handles exceptions that can occur in both parsing and input file processing.

9

Method to compact from the current token.

10

If it is a start element tag, outputs the name of the element followed by an opening bracket and update the current indentation.

11

Outputs each attribute name and value all indented except the first one.

12

Loops on children nodes that are not whitespace and compacts each of them with the correct indentation.

13

Recursive call to the compacting process.

14

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.