Source code: com/gersonworks/xml/util/XMLPropertiesHandler.java
1 /*
2 * XMLProperties library - A utility class which reads property lists from
3 * an XML document
4 * Copyright (C) 21 January 2004 Gerson Galang
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 * For any questions or suggestions, you can email me at :
19 * Email: gerson721@gawab.com
20 */
21
22 package com.gersonworks.xml.util;
23
24 import java.io.PrintWriter;
25 import java.io.File;
26 import java.io.InputStream;
27
28 import org.xml.sax.*;
29 import org.xml.sax.helpers.DefaultHandler;
30
31 import javax.xml.parsers.SAXParserFactory;
32 import javax.xml.parsers.ParserConfigurationException;
33 import javax.xml.parsers.SAXParser;
34
35 import java.util.Map;
36 import java.util.HashMap;
37
38 /**
39 * The XMLPropertiesHandler class acts as a handler to the XMLProperties file
40 * while it is being parsed by the SAX Parser. Most of the methods provided
41 * by this class only handles events wherein the SAX parser has entered and
42 * left the property and value elements.
43 * <p>
44 * This is not a public class because only the XMLProperties object directly
45 * accesses it.
46 *
47 * <p>Copyright: Copyright (c) 21 January 2004</p>
48 * @author Gerson Galang
49 * @version 1.1, 29 January 2004
50 */
51 class XMLPropertiesHandler extends DefaultHandler {
52
53 // the map where the property list is going to be stored
54 private Map xmlProperties;
55
56 // temporary holder of property values
57 private XMLPropertyValues propertyValues;
58
59 // true if inside the value element
60 private boolean insideValueElement = false;
61
62 // where the logs are going to written
63 private PrintWriter log = new PrintWriter(System.out);
64
65 private String propertyKey = null; // temporary property key
66 private String propertyValue = null; // temporary property value
67
68 private boolean verbose = false; // used for debugging purposes
69
70 /**
71 * Constructs an XMLPropertiesHandler for the given File instance of the XML
72 * document to be parsed.
73 *
74 * @param file the file going to be parsed by the SAXParser.
75 */
76 public XMLPropertiesHandler(File file) {
77 constructXMLPropertiesHandler(file);
78 }
79
80 /**
81 * Constructs an XMLPropertiesHandler for the URI of the XML document to be
82 * parsed.
83 *
84 * @param uri the URI of the XML document which is going to be parsed by the
85 * SAXParser.
86 */
87 public XMLPropertiesHandler(String uri) {
88 constructXMLPropertiesHandler(uri);
89 }
90
91 /**
92 * Constructs an XMLPropertiesHandler for the given InputStream as the XML
93 * document to be parsed.
94 *
95 * @param inStream the input stream containing the XML document to be parsed.
96 * @since XMLProperties version 1.1
97 */
98 public XMLPropertiesHandler(InputStream inStream) {
99 constructXMLPropertiesHandler(inStream);
100 }
101
102 /**
103 * A special purpose method to lessen the number of lines I need to write
104 * especially if the only difference between the two contructors is the way
105 * the SAXParser.parse() method is called.
106 *
107 * @param o either a file or a string
108 */
109 private void constructXMLPropertiesHandler(Object o) {
110 // Use the default (non-validating) parser
111 SAXParserFactory factory = SAXParserFactory.newInstance();
112 xmlProperties = new HashMap();
113 try {
114 // Parse the input
115 SAXParser saxParser = factory.newSAXParser();
116
117 // get the xmlReader wrapped by the saxParser object so that this class
118 // may be able to handle errors generated by the saxParser.
119 org.xml.sax.XMLReader xmlReader = saxParser.getXMLReader();
120 xmlReader.setErrorHandler(this);
121
122 //xmlReader.setContentHandler(this);
123 //xmlReader.parse(
124 // new org.xml.sax.InputSource(new java.io.FileReader(file)));
125
126 // the code below are the only
127 if (o instanceof File) {
128 File file = (File) o;
129 verbosePrintln("Parsing file: " + file.getAbsolutePath());
130 saxParser.parse(file, this);
131
132 }
133 else if (o instanceof String) {
134 String uri = o.toString(); // get the URI string
135 verbosePrintln("Parsing uri: " + uri);
136 saxParser.parse(uri, this);
137 }
138 else if (o instanceof InputStream) {
139 InputStream inStream = (InputStream) o; // get the input stream
140 verbosePrintln("Parsing inputStream... ");
141 saxParser.parse(inStream, this);
142 }
143 else {
144 // this should never be thrown because the user of this library doesn't
145 // have an access to this class.
146 throw new XMLPropertiesException(
147 "XMLPropertiesHandler.constructXMLPropertiesHandler()'s parameter "
148 + "is not of type File or String or InputStream");
149 }
150 }
151 catch (Throwable t) {
152 System.err.println(t.getMessage());
153 t.printStackTrace();
154 } finally {
155 log.flush();
156 }
157 }
158
159 /**
160 * Returns the property list (key-values pair) generated by this handler.
161 *
162 * @return the property list (key-values pair) generated by this handler.
163 */
164 public Map getProperties() {
165 return xmlProperties;
166 }
167
168 //===========================================================
169 // SAX DocumentHandler methods
170 //===========================================================
171
172 /**
173 * Used only for debugging purposes
174 *
175 * @throws SAXException
176 */
177 public void startDocument() throws SAXException {
178 verbosePrintln("Entering properties document.");
179 }
180
181 /**
182 * Used only for debugging purposes
183 *
184 * @throws SAXException
185 */
186 public void endDocument() throws SAXException {
187 verbosePrintln("Leaving properties document.");
188 }
189
190 /**
191 * Overrides the startElement() method of the DefaultHandler class.
192 *
193 * @param namespaceURI
194 * @param sName
195 * @param qName
196 * @param attrs
197 * @throws XMLPropertiesFormatException thrown when an unrecognized element
198 * has been inserted into the XML properties document. Or, the key attribute
199 * is missing from the property element.
200 */
201 public void startElement(String namespaceURI,
202 String sName, // simple name
203 String qName, // qualified name
204 Attributes attrs) throws SAXException {
205
206 // if inside the property element, instantiate a new XMLPropertyValues
207 // object
208 if (sName.equals("property") || qName.equals("property")) {
209 propertyKey = attrs.getValue("key");
210 if (propertyKey == null) {
211 throw new XMLPropertiesFormatException("Key attribute missing in " +
212 "property element.", null);
213 }
214 verbosePrintln("Entering property element: " + propertyKey);
215 propertyValues = new XMLPropertyValues();
216 }
217 else if (sName.equals("value") || qName.equals("value")) {
218 insideValueElement = true;
219 verbosePrint("Entering value element: ");
220 }
221 else if (sName.equals("properties") || qName.equals("properties")) {
222 // do nothing
223 }
224 else {
225 // the checking of the XML document should be hard-coded in here to avoid
226 // the trouble of using a DTD or an XML Schema. the format of the XML
227 // document the XMLProperties class is very simple so there is no need to
228 // use a DTD or an XML schema.
229 throw new XMLPropertiesFormatException("Unrecognized <" +
230 (sName.length() > 1 ? sName : qName) +"> element " +
231 "found.", null);
232 }
233 }
234
235 /**
236 * Overrides the endElement() method of the DefaultHandler class.
237 *
238 * @param namespaceURI
239 * @param sName
240 * @param qName
241 * @throws XMLPropertiesFormatException thrown when the value element
242 * has not been found inside the property element.
243 */
244 public void endElement(String namespaceURI,
245 String sName, // simple name
246 String qName // qualified name
247 ) throws SAXException {
248
249 // if leaving the property element, don't forget to reset the temporary
250 // property key and temporary property values
251 if (sName.equals("property") || qName.equals("property")) {
252
253 // don't allow a property element not to have a property value element.
254 // it is important that the user, types-in the correct format of the
255 // properties file.
256 if (propertyKey != null && propertyValues.isEmpty()) {
257 throw new XMLPropertiesFormatException(
258 "Missing values for property element with propertyKey=\"" +
259 propertyKey + "\".", null);
260 }
261 xmlProperties.put(propertyKey, propertyValues);
262 verbosePrintln("Leaving property element: " + propertyKey);
263
264 // just to be sure... set these variables' values to null
265 propertyKey = null;
266 propertyValues = null;
267 }
268
269 if (sName.equals("value") || qName.equals("value")) {
270 verbosePrintln(propertyValue);
271
272 // modified the following line in version 1.2. i was having some problems
273 // with xml property files having this format:
274 // <value>
275 // value1
276 // </value>
277 // the old line did not remove the extra CR/LF and spaces so if value1 is
278 // converted to an integer or a double, a NumberFormatException will be
279 // thrown
280 propertyValues.addValue(propertyValue.trim());
281
282 verbosePrintln("Leaving value element: " + propertyValue);
283
284 // need to set this to false so that characters outside this element
285 // will not be allowed.
286 insideValueElement = false;
287
288 propertyValue = null; // reset the propertyValue to null just to be safe
289 }
290 }
291
292 /**
293 * Creates a string from the text found inside the value element.
294 *
295 * @param ch
296 * @param start
297 * @param length
298 * @throws XMLPropertiesFormatException thrown if normal texts and not
299 * comments are found inside the property element.
300 */
301 public void characters(char ch[], int start, int length) throws SAXException {
302 propertyValue = new String(ch, start, length);
303 if (!insideValueElement && !propertyValue.trim().equals("")) {
304 String errorMsg = null;
305 if (propertyKey != null) {
306 errorMsg = "the property element with propertyKey=\""
307 + propertyKey + "\".";
308 }
309 else {
310 errorMsg = "the properties element.";
311 }
312
313 throw new XMLPropertiesFormatException(
314 "Illegal \""+ propertyValue.trim() +"\" characters found inside the "
315 + errorMsg, null);
316 }
317 }
318
319 /**
320 * Outputs a detailed warning message.
321 *
322 * @param ex
323 */
324 public void warning(SAXParseException ex)
325 throws XMLPropertiesFormatException {
326 throw new XMLPropertiesFormatException("Warning: " + ex.getMessage() +
327 " at line " + ex.getLineNumber() + " column " +
328 ex.getColumnNumber() + detailedMessage(), null);
329
330 }
331
332 /**
333 * Outputs a detailed error message.
334 *
335 * @param ex
336 */
337 public void error(SAXParseException ex)
338 throws XMLPropertiesFormatException {
339 throw new XMLPropertiesFormatException("Error: " + ex.getMessage() +
340 " at line " + ex.getLineNumber() + " column " +
341 ex.getColumnNumber() + detailedMessage(), null);
342 }
343
344 /**
345 * Outputs a detailed fatal error message.
346 *
347 * @param ex
348 */
349 public void fatalError(SAXParseException ex)
350 throws XMLPropertiesFormatException {
351
352 throw new XMLPropertiesFormatException("Fatal error: " + ex.getMessage() +
353 " at line " + ex.getLineNumber() + " column " +
354 ex.getColumnNumber() + detailedMessage(), null);
355 }
356
357 /**
358 * Returns the key-value pair when the error occured.
359 *
360 * @return
361 */
362 public String detailedMessage() {
363 return "\nPropertyKey = " + propertyKey
364 + "\nPropertyValue = " + propertyValue;
365 }
366
367 /**
368 * For debugging purposes.
369 *
370 * @param str
371 */
372 private void verbosePrint(String str) {
373 if (verbose) {
374 log.print(str);
375 }
376 }
377
378 /**
379 * For debugging purposes.
380 *
381 * @param str
382 */
383 private void verbosePrintln(String str) {
384 if (verbose) {
385 log.println(str);
386 }
387 }
388
389 }