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