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‑); 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‑).
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);StreamResult result = new StreamResult(System.out); TransformerFactory tFactory = TransformerFactory.newInstance(); 25 Transformer transformer = tFactory.newTransformer();
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 }
|
Creates a |
|
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‑).
The SAX parsing events
are sent to an event handler (line 18‑) for which we
define (
get...
and set...
) accessor methods available to the
user of the handler. The main method is parse
(line 41‑) that uses the same algorithm as the ones used for
the DOM approach.
parse
uses a CompactTokenizer
instance
(line 39‑) 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‑
) and then calls
expand
(line 50‑) 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‑). Once all the attributes have been
gathered, we can send a
startElement
event to the handler (line 72‑). We then process the content of the event
either with a recursive call to
expand
(line 77‑) or by creating a text element by sending
a
characters
event to the handler (line 80‑). Once all children elements have been
processed, an
endElement
event is sent to the handler (line 85‑).
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‑) or end
element (line 84‑
) or before a new text
node (line 79‑
).
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;20 private static char[] blanks = "\n ".toCharArray();
private void ignorableSpacing(int nb) throws SAXException {
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;
40 public void parse(InputSource input){
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);
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)
throws IOException,SAXException { AttributesImpl attrs = new AttributesImpl(); st.skip("["); while(st.getToken().equals("@")) {// process attributes
65 String attName = st.nextToken(); st.nextToken(); attrs.addAttribute(nsu,attName,attName,"CDATA",st.skip("[")); st.nextToken(); st.skip("]"); 70 } ignorableSpacing(indent);
handler.startElement(nsu,elementName,elementName,attrs);
while(!st.getToken().equals("]")){ // process content
String s = st.getToken().trim(); 75 st.nextToken(); if(st.getToken().equals("[")) expand(s,indent+3);
else { ignorableSpacing(indent+3);
80 handler.characters(s.toCharArray(),0,s.length());
} } st.skip("]"); ignorableSpacing(indent);
85 handler.endElement(nsu,elementName,elementName);
} // dummy definitions... public DTDHandler getDTDHandler(){return null;}
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 }
|
Private variable for keeping track of the document handler. |
|
Character array containing a carriage return followed by a certain number of blanks that will used for formatting. |
|
Method for returning blanks that will be ignored by the parser. |
|
Variable for the tokenizer that handles the compact output. |
|
Starting point of the parsing process. Creates the tokenizer for the
input file and generates a |
|
Finds the name of the root node. |
|
Starts the recursive processing of the content of an element with the name of the root node. |
|
Recursive processing of an element content. |
|
Loops over the content of all attributes that are inserted into a
single |
|
Generate spacing for pretty-printing the output structure. |
|
Generates a |
|
Loops over all children nodes in order to process either element or text nodes. |
|
If it is an element node, calls |
|
If it is a text node, generates a |
|
Indentation before a text node. |
|
Indentation before closing an element |
|
Indicates the end of an element. |
|
Empty definitions for handlers required by the |