9.2. Creating a Document with SAX Events

Another way of creating an XML document is to have a Transformer do it for us. Since the transformer must receive an XML document, we might think that this is pointless. However, a SAX transformer creates a document from SAX events as we saw in Section 8.2. So if we manage to send these types of events while reading our text file, we will obtain a new XML document. This illustrates a clever and efficient way to convert non-XML files into XML.

The main class for the SAX transformation (Example 9.3) is very simple: it creates a CompactReader that will read the file as an InputSource (line 20‑1); then it creates a transformer to process this source into an output stream (here System.out). All the magic of setting the input file and creating the document elements is done via the identity transformation process (line 25‑2).

Example 9.3. [SAXExpand.java]: XML document creation using SAX events

  1 import org.xml.sax.InputSource;
    import javax.xml.transform.sax.SAXSource;
    import javax.xml.transform.stream.StreamResult;
    import javax.xml.transform.Transformer;
  5 import javax.xml.transform.TransformerFactory;
    import javax.xml.transform.TransformerException;
    import javax.xml.transform.TransformerConfigurationException;
    
    import  java.io.BufferedReader;
 10 import  java.io.FileReader;
    import  java.io.IOException;
    
    public class SAXExpand {
        public static void main( String[] argv ) {
 15         try {
                InputSource inputSource = 
                    new InputSource(
                        new BufferedReader(new FileReader(argv[0])));
                CompactReader saxReader = new CompactReader();
 20             SAXSource source = new SAXSource(saxReader,inputSource); (1)
                StreamResult result = new StreamResult(System.out);
                
                TransformerFactory tFactory = 
                    TransformerFactory.newInstance();
 25             Transformer transformer = tFactory.newTransformer();     (2)
                transformer.transform(source,result);
            } catch (TransformerException ex){
                System.out.println("TransformerException"+ex);
                ex.printStackTrace();
 30         } catch (IOException ex) {
                System.out.println("IOException"+ex);
                ex.printStackTrace();
            }
        }
 35 }

1

Creates a Reader to be used as an InputSource.

2

Uses an identity transformer to copy the input to the output stream (here the standard output).


The reading of the file and generation of SAX events are performed by CompactReader (Example 9.4), a specialized XMLReader. Because CompactReader implements the XMLReader interface, it must define many methods but only a few of them are really important in this special case. This explains the many stub methods at the end of Example 9.4 (line 89‑18).

The SAX parsing events are sent to an event handler (line 18‑1) for which we define (get... and set...) accessor methods available to the user of the handler. The main method is parse (line 41‑5) that uses the same algorithm as the ones used for the DOM approach. parse uses a CompactTokenizer instance (line 39‑4) of the same class used with the DOM approach in Example 9.1. This method will generate SAX events as it goes through the text file. It first finds the name of the root element (line 400‑6) and then calls expand (line 50‑7) with an indentation level (used only to obtain a nicer output).

Most of the processing is done within the expand method that receives the name of the current element as parameter. It first creates an AttributesImpl data structure which is populated with the names and values of all attributes (line 64‑9). Once all the attributes have been gathered, we can send a startElement event to the handler (line 72‑11). We then process the content of the event either with a recursive call to expand (line 77‑13) or by creating a text element by sending a characters event to the handler (line 80‑15). Once all children elements have been processed, an endElement event is sent to the handler (line 85‑17).

The previous processing produces a valid XML file but one that is very hard to read because everything will appear on the same line. A way to produce a nicely formatted output is to send ignorable spacing to the handler. As the formatting rule we use is to always end the current line and add some indentation, we have defined an auxiliary method ignorableSpacing that manages an array of characters of the appropriate length. It is initialized with nine spaces but it expands when necessary. This method is called at the appropriate moment in the expansion process, i.e. before creating a new start (line 71‑10) or end element (line 84‑16) or before a new text node (line 79‑14).

Example 9.4. [CompactReader.java]: Compact form parsing to generate SAX events

  1 import org.xml.sax.XMLReader;
    import org.xml.sax.ContentHandler;
    import org.xml.sax.DTDHandler;
    import org.xml.sax.EntityResolver;
  5 import org.xml.sax.ErrorHandler;
    import org.xml.sax.InputSource;
    import org.xml.sax.Attributes;
    import org.xml.sax.SAXException;
    import org.xml.sax.helpers.AttributesImpl;
 10 
    import java.io.IOException;
    import java.io.StreamTokenizer;
    import java.util.Arrays;
    
 15 public class CompactReader implements XMLReader{
        
        private String nsu = ""; // no namespace URI
        private ContentHandler handler;                                  (1)
    
 20     private static char[] blanks = "\n         ".toCharArray();      (2)
        
        private void ignorableSpacing(int nb) throws SAXException {      (3)
            if(nb>blanks.length){// extend the length of space array 
                blanks = new char[nb];
 25             blanks[0]='\n';
                Arrays.fill(blanks,1,blanks.length,' ');
            }
            handler.ignorableWhitespace(blanks,0,nb);
        }
 30             
        //Return the current content handler.
        public ContentHandler   getContentHandler(){return handler;}
        //Allow an application to register a content event handler.
        public void setContentHandler(ContentHandler handler){
 35         this.handler=handler;
        }
        
        //Parse an XML document.
        private CompactTokenizer st;                                     (4)
 40     
        public void parse(InputSource input){                            (5)
            try{
                String rootName = "dummyRoot";
                st = new CompactTokenizer(input.getCharacterStream());
 45             // ignore everything preceding the word before the first "["
                while(!st.nextToken().equals("[")){
                    rootName=st.getToken();
                }
                handler.startDocument();
 50             expand(rootName,1);                                      (7)
                ignorableSpacing(1);
                handler.endDocument();
            } catch (SAXException e){
                System.out.println(e.getMessage());
 55         } catch (IOException e) {
                System.out.println("IO Error:"+e);
            }
        } 
        
 60     void expand(String elementName,int indent)                       (8)
            throws IOException,SAXException {
            AttributesImpl attrs = new AttributesImpl();
            st.skip("["); 
            while(st.getToken().equals("@")) {// process attributes      (9)
 65             String attName = st.nextToken();
                st.nextToken();
                attrs.addAttribute(nsu,attName,attName,"CDATA",st.skip("["));
                st.nextToken();
                st.skip("]");
 70         }
            ignorableSpacing(indent);                                    (10)
            handler.startElement(nsu,elementName,elementName,attrs);     (11)
            while(!st.getToken().equals("]")){ // process content        (12)
                String s = st.getToken().trim();
 75             st.nextToken();
                if(st.getToken().equals("["))
                    expand(s,indent+3);                                  (13)
                else {
                    ignorableSpacing(indent+3);                          (14)
 80                 handler.characters(s.toCharArray(),0,s.length());    (15)
                }
            }
            st.skip("]");
            ignorableSpacing(indent);                                    (16)
 85         handler.endElement(nsu,elementName,elementName);             (17)
        }
        
        // dummy definitions...
        public DTDHandler getDTDHandler(){return null;}                  (18)
 90     public EntityResolver getEntityResolver(){return null;}
        public ErrorHandler getErrorHandler() {return null;} 
        public boolean getFeature(String name) {return false;} 
        public Object getProperty(String name) {return null;}
        public void parse(String systemId){} 
 95     public void setDTDHandler(DTDHandler handler){}
        public void setEntityResolver(EntityResolver resolver){} 
        public void setErrorHandler(ErrorHandler handler){} 
        public void setFeature(String name, boolean value){} 
        public void setProperty(String name, Object value){} 
100 }
    

1

Private variable for keeping track of the document handler.

2

Character array containing a carriage return followed by a certain number of blanks that will used for formatting.

3

Method for returning blanks that will be ignored by the parser.

4

Variable for the tokenizer that handles the compact output.

5

Starting point of the parsing process. Creates the tokenizer for the input file and generates a startDocument event.

6

Finds the name of the root node.

7

Starts the recursive processing of the content of an element with the name of the root node.

8

Recursive processing of an element content.

9

Loops over the content of all attributes that are inserted into a single AttributesImpl structure.

10

Generate spacing for pretty-printing the output structure.

11

Generates a startElement event with the name of the element and the structure containing the attributes.

12

Loops over all children nodes in order to process either element or text nodes.

13

If it is an element node, calls expand recursively but with more indentation.

15

If it is a text node, generates a characters event with an appropriate indentation

14

Indentation before a text node.

16

Indentation before closing an element

17

Indicates the end of an element.

18

Empty definitions for handlers required by the XMLReader interface but not used in this example.