SaxParser.java
001 /* This file is part of the project "Hilbert II" - http://www.qedeq.org
002  *
003  * Copyright 2000-2011,  Michael Meyling <mime@qedeq.org>.
004  *
005  * "Hilbert II" is free software; you can redistribute
006  * it and/or modify it under the terms of the GNU General Public
007  * License as published by the Free Software Foundation; either
008  * version 2 of the License, or (at your option) any later version.
009  *
010  * This program is distributed in the hope that it will be useful,
011  * but WITHOUT ANY WARRANTY; without even the implied warranty of
012  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
013  * GNU General Public License for more details.
014  */
015 package org.qedeq.kernel.xml.parser;
016 
017 import java.io.File;
018 import java.io.FileInputStream;
019 import java.io.IOException;
020 import java.io.InputStream;
021 import java.util.MissingResourceException;
022 
023 import javax.xml.parsers.ParserConfigurationException;
024 import javax.xml.parsers.SAXParser;
025 import javax.xml.parsers.SAXParserFactory;
026 
027 import org.qedeq.base.trace.Trace;
028 import org.qedeq.kernel.se.common.DefaultSourceFileExceptionList;
029 import org.qedeq.kernel.se.common.Plugin;
030 import org.qedeq.kernel.se.common.SourceFileException;
031 import org.qedeq.kernel.se.common.SourceFileExceptionList;
032 import org.qedeq.kernel.xml.common.XmlSyntaxException;
033 import org.qedeq.kernel.xml.handler.common.SaxDefaultHandler;
034 import org.qedeq.kernel.xml.handler.common.SimpleHandler;
035 import org.xml.sax.InputSource;
036 import org.xml.sax.SAXException;
037 import org.xml.sax.SAXNotRecognizedException;
038 import org.xml.sax.XMLReader;
039 
040 
041 /**
042  * Parser for XML files. This class uses features specific for Xerces.
043  *
044  @author Michael Meyling
045  */
046 public final class SaxParser {
047 
048     /** This class. */
049     private static final Class CLASS = SaxParser.class;
050 
051     /** Namespaces feature id (http://xml.org/sax/features/namespaces). */
052     private static final String NAMESPACES_FEATURE_ID = "http://xml.org/sax/features/namespaces";
053 
054     /** Validation feature id (http://xml.org/sax/features/validation). */
055     private static final String VALIDATION_FEATURE_ID = "http://xml.org/sax/features/validation";
056 
057     /** Schema validation feature id (http://apache.org/xml/features/validation/schema). */
058     private static final String SCHEMA_VALIDATION_FEATURE_ID
059         "http://apache.org/xml/features/validation/schema";
060 
061     /** Schema full checking feature id
062      * (http://apache.org/xml/features/validation/schema-full-checking). */
063     protected static final String SCHEMA_FULL_CHECKING_FEATURE_ID
064         "http://apache.org/xml/features/validation/schema-full-checking";
065 
066     /** Handler which deals with the XML contents. */
067     private SaxDefaultHandler handler;
068 
069     /** SAX parser. */
070     private XMLReader reader;
071 
072     /** Simple handler for validation purpose only. */
073     private final SimpleHandler deflt;
074 
075     /** Saved errors of parsing. */
076     private DefaultSourceFileExceptionList exceptionList;
077 
078     /** Plugin we work for. */
079     private Plugin plugin;
080 
081     /**
082      * Constructor.
083      *
084      @param   plugin    We work for this plugin.
085      @param   handler   Default handler for this application.
086      @throws  ParserConfigurationException    Severe parser configuration problem.
087      @throws  SAXException                    Option not recognized or supported.
088      */
089     public SaxParser(final Plugin plugin, final SaxDefaultHandler handler)
090             throws ParserConfigurationException, SAXException {
091         super();
092 
093         this.handler = handler;
094         this.deflt = new SimpleHandler();
095         this.plugin = plugin;
096 
097         final String factoryImpl = System.getProperty("javax.xml.parsers.SAXParserFactory");
098         if (factoryImpl == null) {
099             System.setProperty("javax.xml.parsers.SAXParserFactory",
100                 "org.apache.xerces.jaxp.SAXParserFactoryImpl");
101         }
102         SAXParserFactory factory = SAXParserFactory.newInstance();
103         factory.setNamespaceAware(true);
104         factory.setValidating(true);
105 
106         factory.setFeature(NAMESPACES_FEATURE_ID, true);
107         factory.setFeature(VALIDATION_FEATURE_ID, true);
108 
109         try {
110             factory.setFeature(SCHEMA_VALIDATION_FEATURE_ID, true);
111         catch (SAXNotRecognizedException e) {
112             Trace.trace(CLASS, this, "constructor", e);
113             // ignore
114         }
115         try {
116             factory.setFeature(SCHEMA_FULL_CHECKING_FEATURE_ID, true);
117         catch (SAXNotRecognizedException e) {
118             Trace.trace(CLASS, this, "constructor", e);
119             // ignore
120         }
121 
122         final SAXParser parser = factory.newSAXParser();
123         if (!parser.isNamespaceAware()) {
124             throw new ParserConfigurationException(
125                 "Current XML parser doesn't support namespaces.");
126         }
127         if (!parser.isValidating()) {
128             throw new ParserConfigurationException(
129                 "Current XML parser doesn't support schema validation.");
130         }
131 
132         reader = parser.getXMLReader();
133         reader.setEntityResolver(new SaxEntityResolver(handler));
134 
135         // set parser features
136         reader.setFeature(NAMESPACES_FEATURE_ID, true);
137         reader.setFeature(VALIDATION_FEATURE_ID, true);
138         try {
139             reader.setFeature(SCHEMA_VALIDATION_FEATURE_ID, true);
140         catch (SAXNotRecognizedException e) {
141             Trace.trace(CLASS, this, "constructor", e);
142             // ignore
143         }
144         try {
145             reader.setFeature(SCHEMA_FULL_CHECKING_FEATURE_ID, true);
146         catch (SAXNotRecognizedException e) {
147             Trace.trace(CLASS, this, "constructor", e);
148             // ignore
149         }
150 
151     }
152 
153     /**
154      * Parse input source.
155      @param   in              Parse data from this file source.
156      @param   validateOnly    validate with {@link #deflt} or parse with {@link #handler}.
157      @param   original        Original URL for the file. If this is <code>null</code> same as
158      *                          file name.
159      *
160      @throws  SourceFileExceptionList    Loading failed.
161      */
162     private void parse(final File in, final boolean validateOnly, final String original)
163             throws SourceFileExceptionList {
164         final String method = "parse(URL, boolean, InputStream)";
165         InputStream stream = null;
166         exceptionList = new DefaultSourceFileExceptionList();
167         try {
168             stream = new FileInputStream(in);
169             final InputSource input = new InputSource(stream);
170             reader.setErrorHandler(new SaxErrorHandler(plugin, original, exceptionList));
171             handler.setUrl(original);
172             deflt.setUrl(original);
173             if (validateOnly) {
174                 try {
175                     reader.setContentHandler(deflt);
176                     reader.parse(input);
177                 catch (MissingResourceException ex) {
178                     throw new SAXException("For " + ex.getClassName() " we searched for value"
179                         " of " + ex.getKey(), ex);
180                 }
181             else {
182                 handler.setExceptionList(exceptionList);
183                 reader.setContentHandler(handler);
184                 reader.parse(input);
185             }
186         catch (SAXException e) {
187             if (exceptionList.size() <= 0) {    // do we have already exceptions?
188                 // no, we must add this one
189                 final XmlSyntaxException xml = XmlSyntaxException.createBySAXException(e);
190                 exceptionList.add(new SourceFileException(plugin, xml, handler.createSourceArea()null));
191             }
192             throw exceptionList;
193         catch (IOException e) {
194             final XmlSyntaxException xml = XmlSyntaxException.createByIOException(e);
195             exceptionList.add(new SourceFileException(plugin, xml, handler.createSourceArea()null));
196             throw exceptionList;
197         finally {
198             if (stream != null) {
199                 try {
200                     stream.close();
201                 catch (Exception e) {
202                     Trace.trace(CLASS, this, method, e);
203                 }
204             }
205         }
206         if (exceptionList.size() 0) {
207             throw exceptionList;
208         }
209     }
210 
211     /**
212      * Parses XML file.
213      *
214      @param   fileName        File name.
215      @param   original        Original URL for the file. If this is <code>null</code> same as
216      *                          file name.
217      @throws  SourceFileExceptionList    Loading failed.
218      */
219     public final void parse(final String fileName, final String original)
220             throws SourceFileExceptionList {
221         final File file = new File(fileName);
222         parse(file.getAbsoluteFile(), original);
223     }
224 
225     /**
226      * Parses the XML file.
227      *
228      @param   file            File to parse.
229      @param   original        Original URL for the file. If this is <code>null</code> same as
230      *                          file.
231      @throws  SourceFileExceptionList    Loading failed.
232      */
233     public final void parse(final File file, final String originalthrows SourceFileExceptionList {
234         String org = original;
235         if (org == null) {
236             org = "" + file;
237         }
238         parse(file, true, org);
239         parse(file, false, org);
240     }
241 
242     /**
243      * Get errors that occurred during last parsing.
244      *
245      @return  List with collected Exceptions.
246      */
247     public DefaultSourceFileExceptionList getExceptionList() {
248         return exceptionList;
249     }
250 
251     /**
252      * Get encoding of XML document. This value is set during parsing the document.
253      *
254      @return  Encoding. Maybe <code>null</code>.
255      */
256     public String getEncoding() {
257         return deflt.getEncoding();
258     }
259 
260 }