Source code: jbreport/core/DefaultXMLParseHandler.java
1 /*
2 * $Id: DefaultXMLParseHandler.java,v 1.1 2000/08/31 13:53:17 grantfin Exp $
3 *
4 * jbReport - A reporting library for Java
5 * Copyright (C) 2000 Grant Finnemore <grantfin@users.sourceforge.net>
6 *
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2 of the License, or (at your option) any later version.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 */
21 package jbreport.core;
22
23 import java.util.ResourceBundle;
24 import org.xml.sax.Attributes;
25 import org.xml.sax.InputSource;
26 import org.xml.sax.XMLReader;
27 import org.xml.sax.SAXException;
28 import org.xml.sax.SAXParseException;
29 import org.xml.sax.helpers.DefaultHandler;
30 import org.xml.sax.helpers.XMLReaderFactory;
31
32 import jbreport.ReportElement;
33 import jbreport.ReportElementFactory;
34 import jbreport.ReportException;
35 import jbreport.ReportFacade;
36 import jbreport.util.Stack;
37
38 /**
39 * This is the default xml parser handling class. It is registered with the
40 * actual sax parser, and will receive the appropriate callbacks.
41 *
42 * <p> It should not know about concrete implementations of reporting elements,
43 * it should just make the appropriate calls to the ReportElementFactory
44 * instance.
45 *
46 * <p> The sequence of events when traversing the parse tree is the following.
47 * <ol>
48 * <li> On startElement, see if the top node in the stack knows how to deal
49 * with its own children,
50 * <ul>
51 * <li> [it does] then pass the element type, attributes and cdata to the
52 * top stack node while still in this node.
53 * <li> [it does not] then try to create the appropriate class using
54 * the ReportElementFactory instance.
55 * </ul>
56 * <li> Initialise the element with the type and attributes
57 * <li> Collect any cdata before the endElement method
58 * <li> On endElement, pass the cdata to the element, call end() on the
59 * element, endChild() on the parent, and pop the element off the stack.
60 * </ol>
61 *
62 * @author Grant Finnemore
63 * @version $Revision: 1.1 $
64 */
65 public
66 class DefaultXMLParseHandler extends DefaultHandler implements XMLHandler {
67
68 /** The bundle of resources that provide default settings to the parser,
69 * and also determine which parser will be used.
70 */
71 private static ResourceBundle bundle =
72 ResourceBundle.getBundle("jbreport.core.parse");
73
74 /** The current XML parser. Should we recognize it, an attempt will be made
75 * to re-use rather than re-create it.
76 */
77 private static XMLReader reader;
78
79 /** The stack that is used to determine the current state of parsing. It
80 * should be noted here that every xml element should have a mapping, or
81 * that its parent should implement the appropriate handling interface.
82 */
83 private Stack stack = new Stack();
84
85 /** The factory instance that will create the concrete element instances. */
86 private ReportElementFactory factory;
87
88 /** The current cdata that exists for this element */
89 private StringBuffer cdata;
90
91 //
92 // Constructors
93 //
94
95 private DefaultXMLParseHandler() throws ReportException {
96 factory = ReportFacade.fetchElementFactory();
97 }
98
99 //
100 // Factory methods
101 //
102
103 /**
104 * Parse the xml which is read in through the given InputSource instance.
105 */
106 public static void parseXML(InputSource input) throws ReportException {
107 // Ensure that a reader is available for use
108 initializeReader();
109 // Create a parse handler for use of the parser
110 DefaultHandler handler = new DefaultXMLParseHandler();
111 reader.setContentHandler(handler);
112 reader.setErrorHandler(handler);
113 // Do the parsing
114 try {
115 reader.parse(input);
116 }
117 catch(SAXParseException spe) {
118 spe.printStackTrace(System.err);
119 throw new ReportException(spe);
120 }
121 catch(SAXException se) {
122 if (se.getException() != null) {
123 se.getException().printStackTrace(System.err);
124 }
125 else {
126 se.printStackTrace(System.err);
127 }
128 throw new ReportException(se);
129 }
130 catch(Exception e) {
131 throw new ReportException(e);
132 }
133 }
134
135 /**
136 * Parse the xml which is contained by the given systemId.
137 */
138 public static void parseXML(String systemId) throws ReportException {
139 parseXML(new InputSource(systemId));
140 }
141
142 //
143 // Methods overloaded from HandlerBase
144 //
145
146 public void startElement(String uri, String localName,
147 String rawName, Attributes attributes) {
148 // System.err.println("> " + localName);
149 boolean done = false;
150 cdata = new StringBuffer(); // Re-initialize on every new element
151 if(!stackEmpty()) {
152 ReportElement top = peekElement();
153 if(top instanceof XMLParsingElement
154 && ((XMLParsingElement)top).xmlCanParse()) {
155 ((XMLParsingElement)top).xmlParse(this, localName, attributes);
156 done = true;
157 }
158 }
159 if(!done) {
160 try {
161 ReportElement elem = factory.createElement(localName);
162 // I don't want to do this, but I am more against putting the
163 // XMLHandler interface into the base package. This has the effect
164 // that we are probably not able to handle programatic failures
165 // to insert the group element, as there we don't necessarily have
166 // access to the stack, and, they might not even be using a stack.
167 if(elem instanceof AbstractReportElement) {
168 ((AbstractReportElement)elem).xmlHandler = this;
169 }
170 elem.xmlInitialize(localName, attributes);
171 pushElement(elem);
172 }
173 catch(ReportException e) {
174 handleErrorDuringParse(e, "startElement(" + uri + "," + localName
175 + "," + rawName + "," + attributes + ")");
176 }
177 }
178 }
179
180 public void endElement(String uri, String localName, String rawName) {
181 // System.err.println(">> " + localName);
182 try {
183 if(!stackEmpty()) {
184 ReportElement top = peekElement();
185 if(localName.equals(top.getType())) {
186 // Remove the element
187 top.xmlEnd(cdata.toString().trim());
188 // The stack might have changed in xmlEnd!
189 top = popElement();
190 if(!stackEmpty()) {
191 ReportElement parent = peekElement();
192 parent.xmlEndChild(top);
193 }
194 }
195 else if(top instanceof XMLParsingElement
196 && ((XMLParsingElement)top).xmlCanParse()) {
197 // Send it the appropriate data
198 XMLParsingElement elem = (XMLParsingElement)top;
199 elem.xmlCData(this, cdata.toString().trim());
200 // elem.xmlEnd(this);
201 }
202 }
203 }
204 catch(ReportException e) {
205 handleErrorDuringParse(e, "endElement(" + uri + "," + localName
206 + "," + rawName + ")");
207 }
208 }
209
210 public void characters(char ch[], int start, int length) {
211 for(int i = start; i < start + length; i++) {
212 if(ch[i] != '\n' && ch[i] != '\r') {
213 cdata.append(ch[i]);
214 }
215 }
216 // System.err.println("->" + cdata + "<-");
217 }
218
219 //
220 // Implementation of the XMLHandler interface
221 //
222
223 public void pushElement(ReportElement elem) {
224 stack.push(elem);
225 }
226
227 public ReportElement peekElement() {
228 return (ReportElement)stack.peek();
229 }
230
231 public ReportElement peekElement(int pos) {
232 return (ReportElement)stack.peek(pos);
233 }
234
235 public ReportElement popElement() {
236 return (ReportElement)stack.pop();
237 }
238
239 public boolean stackEmpty() {
240 return stack.empty();
241 }
242
243 //
244 // Implementation methods
245 //
246
247 /**
248 * This will check if a reader already exists, if not, it will create one.
249 * Should the reader already exist, then check to see if we recognize it. If
250 * so then try to reset it, otherwise just create a new one.
251 */
252 private static void initializeReader() throws ReportException {
253 if(reader == null) {
254 reader = createNewReader();
255 }
256 else {
257 boolean reset = false;
258 try {
259 // Ugh!!!
260 if(reader instanceof org.apache.xerces.framework.XMLParser) {
261 ((org.apache.xerces.framework.XMLParser)reader).reset();
262 reset = true;
263 }
264 }
265 catch(Exception e) {}
266 catch(NoClassDefFoundError e) {}
267 if(!reset) {
268 reader = createNewReader();
269 }
270 }
271 }
272
273 /**
274 * This will create a new reader. It should not modify the reader attribute
275 * of the class.
276 */
277 private static XMLReader createNewReader() throws ReportException {
278 try {
279 String className = bundle.getString("parser.class");
280 XMLReader result = null;
281 if(className != null) {
282 result = XMLReaderFactory.createXMLReader(className);
283 }
284 else {
285 result = XMLReaderFactory.createXMLReader();
286 }
287 return result;
288 }
289 catch(SAXException e) {
290 throw new ReportException(e);
291 }
292 }
293
294 /**
295 * This method will handle all report errors that occur during parsing in a
296 * consistent fashion.
297 */
298 private void handleErrorDuringParse(ReportException e, String msg) {
299 e.printStackTrace(System.err);
300 throw new RuntimeException(e.toString() + "::" + msg);
301 }
302
303 }