001 /* XmlReader.java --
002 Copyright (C) 1999,2000,2001 Free Software Foundation, Inc.
003
004 This file is part of GNU JAXP.
005
006 GNU JAXP is free software; you can redistribute it and/or modify
007 it under the terms of the GNU General Public License as published by
008 the Free Software Foundation; either version 2, or (at your option)
009 any later version.
010
011 GNU JAXP is distributed in the hope that it will be useful, but
012 WITHOUT ANY WARRANTY; without even the implied warranty of
013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014 General Public License for more details.
015
016 You should have received a copy of the GNU General Public License
017 along with GNU JAXP; see the file COPYING. If not, write to the
018 Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
019 02111-1307 USA.
020
021 Linking this library statically or dynamically with other modules is
022 making a combined work based on this library. Thus, the terms and
023 conditions of the GNU General Public License cover the whole
024 combination.
025
026 As a special exception, the copyright holders of this library give you
027 permission to link this library with independent modules to produce an
028 executable, regardless of the license terms of these independent
029 modules, and to copy and distribute the resulting executable under
030 terms of your choice, provided that you also meet, for each linked
031 independent module, the terms and conditions of the license of that
032 module. An independent module is a module which is not derived from
033 or based on this library. If you modify this library, you may extend
034 this exception to your version of the library, but you are not
035 obligated to do so. If you do not wish to do so, delete this
036 exception statement from your version. */
037
038 package fi.iki.hsivonen.gnu.xml.aelfred2;
039
040 import java.io.IOException;
041 import java.util.Locale;
042
043 import org.xml.sax.*;
044 import org.xml.sax.ext.*;
045
046 import gnu.xml.pipeline.EventFilter;
047 import gnu.xml.pipeline.ValidationConsumer;
048
049
050 /**
051 * This SAX2 parser optionally layers a validator over the Ælfred2
052 * SAX2 parser. While this will not evaluate every XML validity constraint,
053 * it does support all the validity constraints that are of any real utility
054 * outside the strict SGML-compatible world. See the documentation for the
055 * SAXDriver class for information about the SAX2 features and properties
056 * that are supported, and documentation for the ValidationConsumer for
057 * information about what validity constraints may not be supported.
058 * (Ælfred2 tests some of those, even in non-validating mode, to
059 * achieve better conformance.)
060 *
061 * <p> Note that due to its internal construction, you can't change most
062 * handlers until parse() returns. This diverges slightly from SAX, which
063 * expects later binding to be supported. Early binding involves less
064 * runtime overhead, which is an issue for event pipelines as used inside
065 * this parser. Rather than relying on the parser to handle late binding
066 * to your own handlers, do it yourself.
067 *
068 * @see SAXDriver
069 * @see gnu.xml.pipeline.ValidationConsumer
070 *
071 * @author David Brownell
072 */
073 public final class XmlReader
074 implements XMLReader
075 {
076
077 static class FatalErrorHandler
078 extends DefaultHandler2
079 {
080
081 public void error(SAXParseException e)
082 throws SAXException
083 {
084 throw e;
085 }
086
087 }
088
089 private SAXDriver aelfred2 = new SAXDriver();
090 private EventFilter filter = new EventFilter();
091 private boolean isValidating;
092 private boolean active;
093
094 /**
095 * Constructs a SAX Parser.
096 */
097 public XmlReader()
098 {
099 }
100
101 /**
102 * Constructs a SAX Parser, optionally treating validity errors
103 * as if they were fatal errors.
104 */
105 public XmlReader(boolean invalidIsFatal)
106 {
107 if (invalidIsFatal)
108 {
109 setErrorHandler(new FatalErrorHandler());
110 }
111 }
112
113 /**
114 * <b>SAX2</b>: Returns the object used to report the logical
115 * content of an XML document.
116 */
117 public ContentHandler getContentHandler()
118 {
119 return filter.getContentHandler();
120 }
121
122 /**
123 * <b>SAX2</b>: Assigns the object used to report the logical
124 * content of an XML document.
125 * @exception IllegalStateException if called mid-parse
126 */
127 public void setContentHandler(ContentHandler handler)
128 {
129 if (active)
130 {
131 throw new IllegalStateException("already parsing");
132 }
133 filter.setContentHandler(handler);
134 }
135
136 /**
137 * <b>SAX2</b>: Returns the object used to process declarations related
138 * to notations and unparsed entities.
139 */
140 public DTDHandler getDTDHandler()
141 {
142 return filter.getDTDHandler();
143 }
144
145 /**
146 * <b>SAX1</b> Assigns DTD handler
147 * @exception IllegalStateException if called mid-parse
148 */
149 public void setDTDHandler(DTDHandler handler)
150 {
151 if (active)
152 {
153 throw new IllegalStateException("already parsing");
154 }
155 filter.setDTDHandler(handler);
156 }
157
158 /**
159 * <b>SAX2</b>: Returns the object used when resolving external
160 * entities during parsing (both general and parameter entities).
161 */
162 public EntityResolver getEntityResolver()
163 {
164 return aelfred2.getEntityResolver();
165 }
166
167 /**
168 * <b>SAX1</b> Assigns parser's entity resolver
169 */
170 public void setEntityResolver(EntityResolver handler)
171 {
172 aelfred2.setEntityResolver(handler);
173 }
174
175 /**
176 * <b>SAX2</b>: Returns the object used to receive callbacks for XML
177 * errors of all levels (fatal, nonfatal, warning); this is never null;
178 */
179 public ErrorHandler getErrorHandler()
180 {
181 return aelfred2.getErrorHandler();
182 }
183
184 /**
185 * <b>SAX1</b> Assigns error handler
186 * @exception IllegalStateException if called mid-parse
187 */
188 public void setErrorHandler(ErrorHandler handler)
189 {
190 if (active)
191 {
192 throw new IllegalStateException("already parsing");
193 }
194 aelfred2.setErrorHandler(handler);
195 }
196
197 /**
198 * <b>SAX2</b>: Assigns the specified property.
199 * @exception IllegalStateException if called mid-parse
200 */
201 public void setProperty(String propertyId, Object value)
202 throws SAXNotRecognizedException, SAXNotSupportedException
203 {
204 if (active)
205 {
206 throw new IllegalStateException("already parsing");
207 }
208 if (getProperty(propertyId) != value)
209 {
210 filter.setProperty(propertyId, value);
211 }
212 }
213
214 /**
215 * <b>SAX2</b>: Returns the specified property.
216 */
217 public Object getProperty(String propertyId)
218 throws SAXNotRecognizedException
219 {
220 if ((SAXDriver.PROPERTY + "declaration-handler").equals(propertyId)
221 || (SAXDriver.PROPERTY + "lexical-handler").equals(propertyId))
222 {
223 return filter.getProperty(propertyId);
224 }
225 throw new SAXNotRecognizedException(propertyId);
226 }
227
228 private void forceValidating()
229 throws SAXNotRecognizedException, SAXNotSupportedException
230 {
231 aelfred2.setFeature(SAXDriver.FEATURE + "namespace-prefixes",
232 true);
233 aelfred2.setFeature(SAXDriver.FEATURE + "external-general-entities",
234 true);
235 aelfred2.setFeature(SAXDriver.FEATURE + "external-parameter-entities",
236 true);
237 }
238
239 /**
240 * <b>SAX2</b>: Sets the state of features supported in this parser.
241 * Note that this parser requires reporting of namespace prefixes when
242 * validating.
243 */
244 public void setFeature(String featureId, boolean state)
245 throws SAXNotRecognizedException, SAXNotSupportedException
246 {
247 boolean value = getFeature(featureId);
248
249 if (state == value)
250 {
251 return;
252 }
253
254 if ((SAXDriver.FEATURE + "validation").equals(featureId))
255 {
256 if (active)
257 {
258 throw new SAXNotSupportedException("already parsing");
259 }
260 if (state)
261 {
262 forceValidating();
263 }
264 isValidating = state;
265 }
266 else
267 {
268 aelfred2.setFeature(featureId, state);
269 }
270 }
271
272 /**
273 * <b>SAX2</b>: Tells whether this parser supports the specified feature.
274 * At this time, this directly parallels the underlying SAXDriver,
275 * except that validation is optionally supported.
276 *
277 * @see SAXDriver
278 */
279 public boolean getFeature(String featureId)
280 throws SAXNotRecognizedException, SAXNotSupportedException
281 {
282 if ((SAXDriver.FEATURE + "validation").equals(featureId))
283 {
284 return isValidating;
285 }
286
287 return aelfred2.getFeature(featureId);
288 }
289
290 /**
291 * <b>SAX1</b>: Sets the locale used for diagnostics; currently,
292 * only locales using the English language are supported.
293 * @param locale The locale for which diagnostics will be generated
294 */
295 public void setLocale(Locale locale)
296 throws SAXException
297 {
298 aelfred2.setLocale(locale);
299 }
300
301 /**
302 * <b>SAX1</b>: Preferred API to parse an XML document, using a
303 * system identifier (URI).
304 */
305 public void parse(String systemId)
306 throws SAXException, IOException
307 {
308 parse(new InputSource(systemId));
309 }
310
311 /**
312 * <b>SAX1</b>: Underlying API to parse an XML document, used
313 * directly when no URI is available. When this is invoked,
314 * and the parser is set to validate, some features will be
315 * automatically reset to appropriate values: for reporting
316 * namespace prefixes, and incorporating external entities.
317 *
318 * @param source The XML input source.
319 *
320 * @exception IllegalStateException if called mid-parse
321 * @exception SAXException The handlers may throw any SAXException,
322 * and the parser normally throws SAXParseException objects.
323 * @exception IOException IOExceptions are normally through through
324 * the parser if there are problems reading the source document.
325 */
326 public void parse(InputSource source)
327 throws SAXException, IOException
328 {
329 EventFilter next;
330 boolean nsdecls;
331
332 synchronized (aelfred2)
333 {
334 if (active)
335 {
336 throw new IllegalStateException("already parsing");
337 }
338 active = true;
339 }
340
341 // set up the output pipeline
342 if (isValidating)
343 {
344 forceValidating();
345 next = new ValidationConsumer(filter);
346 }
347 else
348 {
349 next = filter;
350 }
351
352 // connect pipeline and error handler
353 // don't let _this_ call to bind() affect xmlns* attributes
354 nsdecls = aelfred2.getFeature(SAXDriver.FEATURE + "namespace-prefixes");
355 EventFilter.bind(aelfred2, next);
356 if (!nsdecls)
357 {
358 aelfred2.setFeature(SAXDriver.FEATURE + "namespace-prefixes",
359 false);
360 }
361
362 // parse, clean up
363 try
364 {
365 aelfred2.parse(source);
366 }
367 finally
368 {
369 active = false;
370 }
371 }
372
373 }
374