1/*
2 * reserved comment block
3 * DO NOT REMOVE OR ALTER!
4 */
5/*
6 * Licensed to the Apache Software Foundation (ASF) under one or more
7 * contributor license agreements.  See the NOTICE file distributed with
8 * this work for additional information regarding copyright ownership.
9 * The ASF licenses this file to You under the Apache License, Version 2.0
10 * (the "License"); you may not use this file except in compliance with
11 * the License.  You may obtain a copy of the License at
12 *
13 *      http://www.apache.org/licenses/LICENSE-2.0
14 *
15 * Unless required by applicable law or agreed to in writing, software
16 * distributed under the License is distributed on an "AS IS" BASIS,
17 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18 * See the License for the specific language governing permissions and
19 * limitations under the License.
20 */
21
22package com.sun.org.apache.xerces.internal.impl.dtd;
23
24import java.io.EOFException;
25import java.io.IOException;
26import java.io.StringReader;
27import java.util.Locale;
28
29import com.sun.org.apache.xerces.internal.impl.Constants;
30import com.sun.org.apache.xerces.internal.impl.XMLDTDScannerImpl;
31import com.sun.org.apache.xerces.internal.impl.XMLErrorReporter;
32import com.sun.org.apache.xerces.internal.impl.XMLEntityManager;
33import com.sun.org.apache.xerces.internal.impl.msg.XMLMessageFormatter;
34
35import com.sun.org.apache.xerces.internal.util.Status;
36import com.sun.org.apache.xerces.internal.util.SymbolTable;
37import com.sun.org.apache.xerces.internal.util.DefaultErrorHandler;
38
39import com.sun.org.apache.xerces.internal.xni.XNIException;
40import com.sun.org.apache.xerces.internal.xni.grammars.XMLGrammarPool;
41import com.sun.org.apache.xerces.internal.xni.grammars.XMLGrammarLoader;
42import com.sun.org.apache.xerces.internal.xni.grammars.Grammar;
43import com.sun.org.apache.xerces.internal.xni.parser.XMLConfigurationException;
44import com.sun.org.apache.xerces.internal.xni.parser.XMLErrorHandler;
45import com.sun.org.apache.xerces.internal.xni.parser.XMLEntityResolver;
46import com.sun.org.apache.xerces.internal.xni.parser.XMLInputSource;
47
48
49/**
50 * The DTD loader. The loader knows how to build grammars from XMLInputSources.
51 * It extends the DTD processor in order to do this; it's
52 * a separate class because DTD processors don't need to know how
53 * to talk to the outside world in their role as instance-document
54 * helpers.
55 * <p>
56 * This component requires the following features and properties.  It
57 * know ho to set them if no one else does:from the
58 * <ul>
59 *  <li>http://xml.org/sax/features/namespaces</li>
60 *  <li>http://apache.org/xml/properties/internal/symbol-table</li>
61 *  <li>http://apache.org/xml/properties/internal/error-reporter</li>
62 *  <li>http://apache.org/xml/properties/internal/grammar-pool</li>
63 *  <li>http://apache.org/xml/properties/internal/datatype-validator-factory</li>
64 * </ul>
65 *
66 * @xerces.internal
67 *
68 * @author Neil Graham, IBM
69 * @author Michael Glavassevich, IBM
70 *
71 */
72public class XMLDTDLoader
73        extends XMLDTDProcessor
74        implements XMLGrammarLoader {
75
76    //
77    // Constants
78    //
79
80    // feature identifiers
81
82    /** Feature identifier: standard uri conformant feature. */
83    protected static final String STANDARD_URI_CONFORMANT_FEATURE =
84        Constants.XERCES_FEATURE_PREFIX + Constants.STANDARD_URI_CONFORMANT_FEATURE;
85
86    /** Feature identifier: balance syntax trees. */
87    protected static final String BALANCE_SYNTAX_TREES =
88        Constants.XERCES_FEATURE_PREFIX + Constants.BALANCE_SYNTAX_TREES;
89
90    // recognized features:
91    private static final String[] LOADER_RECOGNIZED_FEATURES = {
92        VALIDATION,
93        WARN_ON_DUPLICATE_ATTDEF,
94        WARN_ON_UNDECLARED_ELEMDEF,
95        NOTIFY_CHAR_REFS,
96        STANDARD_URI_CONFORMANT_FEATURE,
97        BALANCE_SYNTAX_TREES
98    };
99
100    // property identifiers
101
102    /** Property identifier: error handler. */
103    protected static final String ERROR_HANDLER =
104        Constants.XERCES_PROPERTY_PREFIX + Constants.ERROR_HANDLER_PROPERTY;
105
106    /** Property identifier: entity resolver. */
107    public static final String ENTITY_RESOLVER =
108        Constants.XERCES_PROPERTY_PREFIX + Constants.ENTITY_RESOLVER_PROPERTY;
109
110    /** Property identifier: locale. */
111    public static final String LOCALE =
112        Constants.XERCES_PROPERTY_PREFIX + Constants.LOCALE_PROPERTY;
113
114    /** Recognized properties. */
115    private static final String[] LOADER_RECOGNIZED_PROPERTIES = {
116        SYMBOL_TABLE,
117        ERROR_REPORTER,
118        ERROR_HANDLER,
119        ENTITY_RESOLVER,
120        GRAMMAR_POOL,
121        DTD_VALIDATOR,
122        LOCALE
123    };
124
125    // enforcing strict uri?
126    private boolean fStrictURI = false;
127
128    /** Controls whether the DTD grammar produces balanced syntax trees. */
129    private boolean fBalanceSyntaxTrees = false;
130
131    /** Entity resolver . */
132    protected XMLEntityResolver fEntityResolver;
133
134    // the scanner we use to actually read the DTD
135    protected XMLDTDScannerImpl fDTDScanner;
136
137    // the entity manager the scanner needs.
138    protected XMLEntityManager fEntityManager;
139
140    // what's our Locale?
141    protected Locale fLocale;
142
143    //
144    // Constructors
145    //
146
147    /** Deny default construction; we need a SymtolTable! */
148    public XMLDTDLoader() {
149        this(new SymbolTable());
150    } // <init>()
151
152    public XMLDTDLoader(SymbolTable symbolTable) {
153        this(symbolTable, null);
154    } // init(SymbolTable)
155
156    public XMLDTDLoader(SymbolTable symbolTable,
157                XMLGrammarPool grammarPool) {
158        this(symbolTable, grammarPool, null, new XMLEntityManager());
159    } // init(SymbolTable, XMLGrammarPool)
160
161    XMLDTDLoader(SymbolTable symbolTable,
162                XMLGrammarPool grammarPool, XMLErrorReporter errorReporter,
163                XMLEntityResolver entityResolver) {
164        fSymbolTable = symbolTable;
165        fGrammarPool = grammarPool;
166        if(errorReporter == null) {
167            errorReporter = new XMLErrorReporter();
168            errorReporter.setProperty(ERROR_HANDLER, new DefaultErrorHandler());
169        }
170        fErrorReporter = errorReporter;
171        // Add XML message formatter if there isn't one.
172        if (fErrorReporter.getMessageFormatter(XMLMessageFormatter.XML_DOMAIN) == null) {
173            XMLMessageFormatter xmft = new XMLMessageFormatter();
174            fErrorReporter.putMessageFormatter(XMLMessageFormatter.XML_DOMAIN, xmft);
175            fErrorReporter.putMessageFormatter(XMLMessageFormatter.XMLNS_DOMAIN, xmft);
176        }
177        fEntityResolver = entityResolver;
178        if(fEntityResolver instanceof XMLEntityManager) {
179            fEntityManager = (XMLEntityManager)fEntityResolver;
180        } else {
181            fEntityManager = new XMLEntityManager();
182        }
183        fEntityManager.setProperty(Constants.XERCES_PROPERTY_PREFIX + Constants.ERROR_REPORTER_PROPERTY, errorReporter);
184        fDTDScanner = createDTDScanner(fSymbolTable, fErrorReporter, fEntityManager);
185        fDTDScanner.setDTDHandler(this);
186        fDTDScanner.setDTDContentModelHandler(this);
187        reset();
188    } // init(SymbolTable, XMLGrammarPool, XMLErrorReporter, XMLEntityResolver)
189
190    // XMLGrammarLoader methods
191
192    /**
193     * Returns a list of feature identifiers that are recognized by
194     * this component. This method may return null if no features
195     * are recognized by this component.
196     */
197    public String[] getRecognizedFeatures() {
198        return (String[])(LOADER_RECOGNIZED_FEATURES.clone());
199    } // getRecognizedFeatures():String[]
200
201    /**
202     * Sets the state of a feature. This method is called by the component
203     * manager any time after reset when a feature changes state.
204     * <p>
205     * <strong>Note:</strong> Components should silently ignore features
206     * that do not affect the operation of the component.
207     *
208     * @param featureId The feature identifier.
209     * @param state     The state of the feature.
210     *
211     * @throws SAXNotRecognizedException The component should not throw
212     *                                   this exception.
213     * @throws SAXNotSupportedException The component should not throw
214     *                                  this exception.
215     */
216    public void setFeature(String featureId, boolean state)
217            throws XMLConfigurationException {
218        if (featureId.equals(VALIDATION)) {
219            fValidation = state;
220        }
221        else if (featureId.equals(WARN_ON_DUPLICATE_ATTDEF)) {
222            fWarnDuplicateAttdef = state;
223        }
224        else if (featureId.equals(WARN_ON_UNDECLARED_ELEMDEF)) {
225            fWarnOnUndeclaredElemdef = state;
226        }
227        else if (featureId.equals(NOTIFY_CHAR_REFS)) {
228            fDTDScanner.setFeature(featureId, state);
229        }
230        else if (featureId.equals(STANDARD_URI_CONFORMANT_FEATURE)) {
231            fStrictURI = state;
232        }
233        else if (featureId.equals(BALANCE_SYNTAX_TREES)) {
234            fBalanceSyntaxTrees = state;
235        }
236        else {
237            throw new XMLConfigurationException(Status.NOT_RECOGNIZED, featureId);
238        }
239    } // setFeature(String,boolean)
240
241    /**
242     * Returns a list of property identifiers that are recognized by
243     * this component. This method may return null if no properties
244     * are recognized by this component.
245     */
246    public String[] getRecognizedProperties() {
247        return (String[])(LOADER_RECOGNIZED_PROPERTIES.clone());
248    } // getRecognizedProperties():String[]
249
250    /**
251     * Returns the state of a property.
252     *
253     * @param propertyId The property identifier.
254     *
255     * @throws XMLConfigurationException Thrown on configuration error.
256     */
257    public Object getProperty(String propertyId)
258            throws XMLConfigurationException {
259        if (propertyId.equals(SYMBOL_TABLE)) {
260            return fSymbolTable;
261        }
262        else if (propertyId.equals(ERROR_REPORTER)) {
263            return fErrorReporter;
264        }
265        else if (propertyId.equals(ERROR_HANDLER)) {
266            return fErrorReporter.getErrorHandler();
267        }
268        else if (propertyId.equals(ENTITY_RESOLVER)) {
269            return fEntityResolver;
270        }
271        else if (propertyId.equals(LOCALE)) {
272            return getLocale();
273        }
274        else if (propertyId.equals(GRAMMAR_POOL)) {
275            return fGrammarPool;
276        }
277        else if (propertyId.equals(DTD_VALIDATOR)) {
278            return fValidator;
279        }
280        throw new XMLConfigurationException(Status.NOT_RECOGNIZED, propertyId);
281    } // getProperty(String):  Object
282
283    /**
284     * Sets the value of a property. This method is called by the component
285     * manager any time after reset when a property changes value.
286     * <p>
287     * <strong>Note:</strong> Components should silently ignore properties
288     * that do not affect the operation of the component.
289     *
290     * @param propertyId The property identifier.
291     * @param value      The value of the property.
292     *
293     * @throws SAXNotRecognizedException The component should not throw
294     *                                   this exception.
295     * @throws SAXNotSupportedException The component should not throw
296     *                                  this exception.
297     */
298    public void setProperty(String propertyId, Object value)
299            throws XMLConfigurationException {
300        if (propertyId.equals(SYMBOL_TABLE)) {
301            fSymbolTable = (SymbolTable)value;
302            fDTDScanner.setProperty(propertyId, value);
303            fEntityManager.setProperty(propertyId, value);
304        }
305        else if(propertyId.equals(ERROR_REPORTER)) {
306            fErrorReporter = (XMLErrorReporter)value;
307            // Add XML message formatter if there isn't one.
308            if (fErrorReporter.getMessageFormatter(XMLMessageFormatter.XML_DOMAIN) == null) {
309                XMLMessageFormatter xmft = new XMLMessageFormatter();
310                fErrorReporter.putMessageFormatter(XMLMessageFormatter.XML_DOMAIN, xmft);
311                fErrorReporter.putMessageFormatter(XMLMessageFormatter.XMLNS_DOMAIN, xmft);
312            }
313            fDTDScanner.setProperty(propertyId, value);
314            fEntityManager.setProperty(propertyId, value);
315        }
316        else if (propertyId.equals(ERROR_HANDLER)) {
317            fErrorReporter.setProperty(propertyId, value);
318        }
319        else if (propertyId.equals(ENTITY_RESOLVER)) {
320            fEntityResolver = (XMLEntityResolver)value;
321            fEntityManager.setProperty(propertyId, value);
322        }
323        else if (propertyId.equals(LOCALE)) {
324            setLocale((Locale) value);
325        }
326        else if(propertyId.equals(GRAMMAR_POOL)) {
327            fGrammarPool = (XMLGrammarPool)value;
328        }
329        else {
330            throw new XMLConfigurationException(Status.NOT_RECOGNIZED, propertyId);
331        }
332    } // setProperty(String,Object)
333
334    /**
335     * Returns the state of a feature.
336     *
337     * @param featureId The feature identifier.
338     *
339     * @throws XMLConfigurationException Thrown on configuration error.
340     */
341    public boolean getFeature(String featureId)
342            throws XMLConfigurationException {
343        if (featureId.equals(VALIDATION)) {
344            return fValidation;
345        }
346        else if (featureId.equals(WARN_ON_DUPLICATE_ATTDEF)) {
347            return fWarnDuplicateAttdef;
348        }
349        else if (featureId.equals(WARN_ON_UNDECLARED_ELEMDEF)) {
350            return fWarnOnUndeclaredElemdef;
351        }
352        else if (featureId.equals(NOTIFY_CHAR_REFS)) {
353            return fDTDScanner.getFeature(featureId);
354        }
355        else if (featureId.equals(STANDARD_URI_CONFORMANT_FEATURE)) {
356            return fStrictURI;
357        }
358        else if (featureId.equals(BALANCE_SYNTAX_TREES)) {
359            return fBalanceSyntaxTrees;
360        }
361        throw new XMLConfigurationException(Status.NOT_RECOGNIZED, featureId);
362    } //getFeature(String):  boolean
363
364    /**
365     * Set the locale to use for messages.
366     *
367     * @param locale The locale object to use for localization of messages.
368     *
369     * @exception XNIException Thrown if the parser does not support the
370     *                         specified locale.
371     */
372    public void setLocale(Locale locale) {
373        fLocale = locale;
374        fErrorReporter.setLocale(locale);
375    } // setLocale(Locale)
376
377    /** Return the Locale the XMLGrammarLoader is using. */
378    public Locale getLocale() {
379        return fLocale;
380    } // getLocale():  Locale
381
382
383    /**
384     * Sets the error handler.
385     *
386     * @param errorHandler The error handler.
387     */
388    public void setErrorHandler(XMLErrorHandler errorHandler) {
389        fErrorReporter.setProperty(ERROR_HANDLER, errorHandler);
390    } // setErrorHandler(XMLErrorHandler)
391
392    /** Returns the registered error handler.  */
393    public XMLErrorHandler getErrorHandler() {
394        return fErrorReporter.getErrorHandler();
395    } // getErrorHandler():  XMLErrorHandler
396
397    /**
398     * Sets the entity resolver.
399     *
400     * @param entityResolver The new entity resolver.
401     */
402    public void setEntityResolver(XMLEntityResolver entityResolver) {
403        fEntityResolver = entityResolver;
404        fEntityManager.setProperty(ENTITY_RESOLVER, entityResolver);
405    } // setEntityResolver(XMLEntityResolver)
406
407    /** Returns the registered entity resolver.  */
408    public XMLEntityResolver getEntityResolver() {
409        return fEntityResolver;
410    } // getEntityResolver():  XMLEntityResolver
411
412    /**
413     * Returns a Grammar object by parsing the contents of the
414     * entity pointed to by source.
415     *
416     * @param source        the location of the entity which forms
417     *                          the starting point of the grammar to be constructed.
418     * @throws IOException      When a problem is encountered reading the entity
419     *          XNIException    When a condition arises (such as a FatalError) that requires parsing
420     *                              of the entity be terminated.
421     */
422    public Grammar loadGrammar(XMLInputSource source)
423            throws IOException, XNIException {
424        reset();
425        // First chance checking strict URI
426        String eid = XMLEntityManager.expandSystemId(source.getSystemId(), source.getBaseSystemId(), fStrictURI);
427        XMLDTDDescription desc = new XMLDTDDescription(source.getPublicId(), source.getSystemId(), source.getBaseSystemId(), eid, null);
428        if (!fBalanceSyntaxTrees) {
429            fDTDGrammar = new DTDGrammar(fSymbolTable, desc);
430        }
431        else {
432            fDTDGrammar = new BalancedDTDGrammar(fSymbolTable, desc);
433        }
434        fGrammarBucket = new DTDGrammarBucket();
435        fGrammarBucket.setStandalone(false);
436        fGrammarBucket.setActiveGrammar(fDTDGrammar);
437        // no reason to use grammar bucket's "put" method--we
438        // know which grammar it is, and we don't know the root name anyway...
439
440        // actually start the parsing!
441        try {
442            fDTDScanner.setInputSource(source);
443            fDTDScanner.scanDTDExternalSubset(true);
444        } catch (EOFException e) {
445            // expected behaviour...
446        }
447        finally {
448            // Close all streams opened by the parser.
449            fEntityManager.closeReaders();
450        }
451        if(fDTDGrammar != null && fGrammarPool != null) {
452            fGrammarPool.cacheGrammars(XMLDTDDescription.XML_DTD, new Grammar[] {fDTDGrammar});
453        }
454        return fDTDGrammar;
455    } // loadGrammar(XMLInputSource):  Grammar
456
457    /**
458     * Parse a DTD internal and/or external subset and insert the content
459     * into the existing DTD grammar owned by the given DTDValidator.
460     */
461    public void loadGrammarWithContext(XMLDTDValidator validator, String rootName,
462            String publicId, String systemId, String baseSystemId, String internalSubset)
463        throws IOException, XNIException {
464        final DTDGrammarBucket grammarBucket = validator.getGrammarBucket();
465        final DTDGrammar activeGrammar = grammarBucket.getActiveGrammar();
466        if (activeGrammar != null && !activeGrammar.isImmutable()) {
467            fGrammarBucket = grammarBucket;
468            fEntityManager.setScannerVersion(getScannerVersion());
469            reset();
470            try {
471                // process internal subset
472                if (internalSubset != null) {
473                    // To get the DTD scanner to end at the right place we have to fool
474                    // it into thinking that it reached the end of the internal subset
475                    // in a real document.
476                    StringBuffer buffer = new StringBuffer(internalSubset.length() + 2);
477                    buffer.append(internalSubset).append("]>");
478                    XMLInputSource is = new XMLInputSource(null, baseSystemId,
479                            null, new StringReader(buffer.toString()), null);
480                    fEntityManager.startDocumentEntity(is);
481                    fDTDScanner.scanDTDInternalSubset(true, false, systemId != null);
482                }
483                // process external subset
484                if (systemId != null) {
485                    XMLDTDDescription desc = new XMLDTDDescription(publicId, systemId, baseSystemId, null, rootName);
486                    XMLInputSource source = fEntityManager.resolveEntity(desc);
487                    fDTDScanner.setInputSource(source);
488                    fDTDScanner.scanDTDExternalSubset(true);
489                }
490            }
491            catch (EOFException e) {
492                // expected behaviour...
493            }
494            finally {
495                // Close all streams opened by the parser.
496                fEntityManager.closeReaders();
497            }
498        }
499    } // loadGrammarWithContext(XMLDTDValidator, String, String, String, String, String)
500
501    // reset all the components that we rely upon
502    protected void reset() {
503        super.reset();
504        fDTDScanner.reset();
505        fEntityManager.reset();
506        fErrorReporter.setDocumentLocator(fEntityManager.getEntityScanner());
507    }
508
509    protected XMLDTDScannerImpl createDTDScanner(SymbolTable symbolTable,
510            XMLErrorReporter errorReporter, XMLEntityManager entityManager) {
511        return new XMLDTDScannerImpl(symbolTable, errorReporter, entityManager);
512    } // createDTDScanner(SymbolTable, XMLErrorReporter, XMLEntityManager) : XMLDTDScannerImpl
513
514    protected short getScannerVersion() {
515        return Constants.XML_VERSION_1_0;
516    } // getScannerVersion() : short
517
518} // class XMLDTDLoader
519