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