XIncludeHandler.java revision 628:2bfaf29cc90b
1/*
2 * reserved comment block
3 * DO NOT REMOVE OR ALTER!
4 */
5/*
6 * Copyright 2003-2005 The Apache Software Foundation.
7 *
8 * Licensed under the Apache License, Version 2.0 (the "License");
9 * you may not use this file except in compliance with the License.
10 * You may obtain a copy of the License at
11 *
12 *      http://www.apache.org/licenses/LICENSE-2.0
13 *
14 * Unless required by applicable law or agreed to in writing, software
15 * distributed under the License is distributed on an "AS IS" BASIS,
16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 * See the License for the specific language governing permissions and
18 * limitations under the License.
19 */
20package com.sun.org.apache.xerces.internal.xinclude;
21
22import java.io.CharConversionException;
23import java.io.IOException;
24import java.util.ArrayList;
25import java.util.Enumeration;
26import java.util.Locale;
27import java.util.Stack;
28import java.util.StringTokenizer;
29import javax.xml.XMLConstants;
30
31import com.sun.org.apache.xerces.internal.impl.Constants;
32import com.sun.org.apache.xerces.internal.impl.XMLEntityManager;
33import com.sun.org.apache.xerces.internal.impl.XMLErrorReporter;
34import com.sun.org.apache.xerces.internal.impl.io.MalformedByteSequenceException;
35import com.sun.org.apache.xerces.internal.impl.msg.XMLMessageFormatter;
36import com.sun.org.apache.xerces.internal.util.AugmentationsImpl;
37import com.sun.org.apache.xerces.internal.util.HTTPInputSource;
38import com.sun.org.apache.xerces.internal.util.IntStack;
39import com.sun.org.apache.xerces.internal.util.ParserConfigurationSettings;
40import com.sun.org.apache.xerces.internal.util.SymbolTable;
41import com.sun.org.apache.xerces.internal.util.URI;
42import com.sun.org.apache.xerces.internal.util.XMLAttributesImpl;
43import com.sun.org.apache.xerces.internal.util.XMLResourceIdentifierImpl;
44import com.sun.org.apache.xerces.internal.util.XMLChar;
45import com.sun.org.apache.xerces.internal.util.XMLSymbols;
46import com.sun.org.apache.xerces.internal.util.URI.MalformedURIException;
47import com.sun.org.apache.xerces.internal.utils.XMLSecurityManager;
48import com.sun.org.apache.xerces.internal.xni.Augmentations;
49import com.sun.org.apache.xerces.internal.xni.NamespaceContext;
50import com.sun.org.apache.xerces.internal.xni.QName;
51import com.sun.org.apache.xerces.internal.xni.XMLAttributes;
52import com.sun.org.apache.xerces.internal.xni.XMLDTDHandler;
53import com.sun.org.apache.xerces.internal.xni.XMLDocumentHandler;
54import com.sun.org.apache.xerces.internal.xni.XMLLocator;
55import com.sun.org.apache.xerces.internal.xni.XMLResourceIdentifier;
56import com.sun.org.apache.xerces.internal.xni.XMLString;
57import com.sun.org.apache.xerces.internal.xni.XNIException;
58import com.sun.org.apache.xerces.internal.xni.parser.XMLComponent;
59import com.sun.org.apache.xerces.internal.xni.parser.XMLComponentManager;
60import com.sun.org.apache.xerces.internal.xni.parser.XMLConfigurationException;
61import com.sun.org.apache.xerces.internal.xni.parser.XMLDTDFilter;
62import com.sun.org.apache.xerces.internal.xni.parser.XMLDTDSource;
63import com.sun.org.apache.xerces.internal.xni.parser.XMLDocumentFilter;
64import com.sun.org.apache.xerces.internal.xni.parser.XMLDocumentSource;
65import com.sun.org.apache.xerces.internal.xni.parser.XMLEntityResolver;
66import com.sun.org.apache.xerces.internal.xni.parser.XMLInputSource;
67import com.sun.org.apache.xerces.internal.xni.parser.XMLParserConfiguration;
68import com.sun.org.apache.xerces.internal.xpointer.XPointerHandler;
69import com.sun.org.apache.xerces.internal.xpointer.XPointerProcessor;
70import com.sun.org.apache.xerces.internal.utils.ObjectFactory;
71import com.sun.org.apache.xerces.internal.utils.XMLSecurityPropertyManager;
72import java.util.Objects;
73
74/**
75 * <p>
76 * This is a pipeline component which performs XInclude handling, according to the
77 * W3C specification for XML Inclusions.
78 * </p>
79 * <p>
80 * This component analyzes each event in the pipeline, looking for &lt;include&gt;
81 * elements. An &lt;include&gt; element is one which has a namespace of
82 * <code>http://www.w3.org/2001/XInclude</code> and a localname of <code>include</code>.
83 * When it finds an &lt;include&gt; element, it attempts to include the file specified
84 * in the <code>href</code> attribute of the element.  If inclusion succeeds, all
85 * children of the &lt;include&gt; element are ignored (with the exception of
86 * checking for invalid children as outlined in the specification).  If the inclusion
87 * fails, the &lt;fallback&gt; child of the &lt;include&gt; element is processed.
88 * </p>
89 * <p>
90 * See the <a href="http://www.w3.org/TR/xinclude/">XInclude specification</a> for
91 * more information on how XInclude is to be used.
92 * </p>
93 * <p>
94 * This component requires the following features and properties from the
95 * component manager that uses it:
96 * <ul>
97 *  <li>http://xml.org/sax/features/allow-dtd-events-after-endDTD</li>
98 *  <li>http://apache.org/xml/properties/internal/error-reporter</li>
99 *  <li>http://apache.org/xml/properties/internal/entity-resolver</li>
100 * </ul>
101 * Optional property:
102 * <ul>
103 *  <li>http://apache.org/xml/properties/input-buffer-size</li>
104 * </ul>
105 *
106 * Furthermore, the <code>NamespaceContext</code> used in the pipeline is required
107 * to be an instance of <code>XIncludeNamespaceSupport</code>.
108 * </p>
109 * <p>
110 * Currently, this implementation has only partial support for the XInclude specification.
111 * Specifically, it is missing support for XPointer document fragments.  Thus, only whole
112 * documents can be included using this component in the pipeline.
113 * </p>
114 *
115 * @author Peter McCracken, IBM
116 * @author Michael Glavassevich, IBM
117 *
118 *
119 * @see XIncludeNamespaceSupport
120 */
121public class XIncludeHandler
122    implements XMLComponent, XMLDocumentFilter, XMLDTDFilter {
123
124    public final static String XINCLUDE_DEFAULT_CONFIGURATION =
125        "com.sun.org.apache.xerces.internal.parsers.XIncludeParserConfiguration";
126    public final static String HTTP_ACCEPT = "Accept";
127    public final static String HTTP_ACCEPT_LANGUAGE = "Accept-Language";
128    public final static String XPOINTER = "xpointer";
129
130    public final static String XINCLUDE_NS_URI =
131        "http://www.w3.org/2001/XInclude".intern();
132    public final static String XINCLUDE_INCLUDE = "include".intern();
133    public final static String XINCLUDE_FALLBACK = "fallback".intern();
134
135    public final static String XINCLUDE_PARSE_XML = "xml".intern();
136    public final static String XINCLUDE_PARSE_TEXT = "text".intern();
137
138    public final static String XINCLUDE_ATTR_HREF = "href".intern();
139    public final static String XINCLUDE_ATTR_PARSE = "parse".intern();
140    public final static String XINCLUDE_ATTR_ENCODING = "encoding".intern();
141    public final static String XINCLUDE_ATTR_ACCEPT = "accept".intern();
142    public final static String XINCLUDE_ATTR_ACCEPT_LANGUAGE = "accept-language".intern();
143
144    // Top Level Information Items have [included] property in infoset
145    public final static String XINCLUDE_INCLUDED = "[included]".intern();
146
147    /** The identifier for the Augmentation that contains the current base URI */
148    public final static String CURRENT_BASE_URI = "currentBaseURI";
149
150    // used for adding [base URI] attributes
151    public final static String XINCLUDE_BASE = "base".intern();
152    public final static QName XML_BASE_QNAME =
153        new QName(
154            XMLSymbols.PREFIX_XML,
155            XINCLUDE_BASE,
156            (XMLSymbols.PREFIX_XML + ":" + XINCLUDE_BASE).intern(),
157            NamespaceContext.XML_URI);
158
159    // used for adding [language] attributes
160    public final static String XINCLUDE_LANG = "lang".intern();
161    public final static QName XML_LANG_QNAME =
162        new QName(
163            XMLSymbols.PREFIX_XML,
164            XINCLUDE_LANG,
165            (XMLSymbols.PREFIX_XML + ":" + XINCLUDE_LANG).intern(),
166            NamespaceContext.XML_URI);
167
168    public final static QName NEW_NS_ATTR_QNAME =
169        new QName(
170            XMLSymbols.PREFIX_XMLNS,
171            "",
172            XMLSymbols.PREFIX_XMLNS + ":",
173            NamespaceContext.XMLNS_URI);
174
175    // Processing States
176    private final static int STATE_NORMAL_PROCESSING = 1;
177    // we go into this state after a successful include (thus we ignore the children
178    // of the include) or after a fallback
179    private final static int STATE_IGNORE = 2;
180    // we go into this state after a failed include.  If we don't encounter a fallback
181    // before we reach the end include tag, it's a fatal error
182    private final static int STATE_EXPECT_FALLBACK = 3;
183
184    // recognized features and properties
185
186    /** Feature identifier: validation. */
187    protected static final String VALIDATION =
188        Constants.SAX_FEATURE_PREFIX + Constants.VALIDATION_FEATURE;
189
190    /** Feature identifier: schema validation. */
191    protected static final String SCHEMA_VALIDATION =
192        Constants.XERCES_FEATURE_PREFIX + Constants.SCHEMA_VALIDATION_FEATURE;
193
194    /** Feature identifier: dynamic validation. */
195    protected static final String DYNAMIC_VALIDATION =
196        Constants.XERCES_FEATURE_PREFIX + Constants.DYNAMIC_VALIDATION_FEATURE;
197
198    /** Feature identifier: allow notation and unparsed entity events to be sent out of order. */
199    protected static final String ALLOW_UE_AND_NOTATION_EVENTS =
200        Constants.SAX_FEATURE_PREFIX
201            + Constants.ALLOW_DTD_EVENTS_AFTER_ENDDTD_FEATURE;
202
203    /** Feature identifier: fixup base URIs. */
204    protected static final String XINCLUDE_FIXUP_BASE_URIS =
205        Constants.XERCES_FEATURE_PREFIX + Constants.XINCLUDE_FIXUP_BASE_URIS_FEATURE;
206
207    /** Feature identifier: fixup language. */
208    protected static final String XINCLUDE_FIXUP_LANGUAGE =
209        Constants.XERCES_FEATURE_PREFIX + Constants.XINCLUDE_FIXUP_LANGUAGE_FEATURE;
210
211    /** Property identifier: symbol table. */
212    protected static final String SYMBOL_TABLE =
213        Constants.XERCES_PROPERTY_PREFIX + Constants.SYMBOL_TABLE_PROPERTY;
214
215    /** Property identifier: error reporter. */
216    protected static final String ERROR_REPORTER =
217        Constants.XERCES_PROPERTY_PREFIX + Constants.ERROR_REPORTER_PROPERTY;
218
219    /** Property identifier: entity resolver. */
220    protected static final String ENTITY_RESOLVER =
221        Constants.XERCES_PROPERTY_PREFIX + Constants.ENTITY_RESOLVER_PROPERTY;
222
223    /** property identifier: security manager. */
224    protected static final String SECURITY_MANAGER =
225        Constants.XERCES_PROPERTY_PREFIX + Constants.SECURITY_MANAGER_PROPERTY;
226
227    /** property identifier: buffer size. */
228    public static final String BUFFER_SIZE =
229        Constants.XERCES_PROPERTY_PREFIX + Constants.BUFFER_SIZE_PROPERTY;
230
231    protected static final String PARSER_SETTINGS =
232        Constants.XERCES_FEATURE_PREFIX + Constants.PARSER_SETTINGS;
233
234    /** property identifier: XML security property manager. */
235    protected static final String XML_SECURITY_PROPERTY_MANAGER =
236            Constants.XML_SECURITY_PROPERTY_MANAGER;
237
238    /** Recognized features. */
239    private static final String[] RECOGNIZED_FEATURES =
240        { ALLOW_UE_AND_NOTATION_EVENTS, XINCLUDE_FIXUP_BASE_URIS, XINCLUDE_FIXUP_LANGUAGE };
241
242    /** Feature defaults. */
243    private static final Boolean[] FEATURE_DEFAULTS = { Boolean.TRUE, Boolean.TRUE, Boolean.TRUE };
244
245    /** Recognized properties. */
246    private static final String[] RECOGNIZED_PROPERTIES =
247        { ERROR_REPORTER, ENTITY_RESOLVER, SECURITY_MANAGER, BUFFER_SIZE };
248
249    /** Property defaults. */
250    private static final Object[] PROPERTY_DEFAULTS = { null, null, null, new Integer(XMLEntityManager.DEFAULT_BUFFER_SIZE) };
251
252    // instance variables
253
254    // for XMLDocumentFilter
255    protected XMLDocumentHandler fDocumentHandler;
256    protected XMLDocumentSource fDocumentSource;
257
258    // for XMLDTDFilter
259    protected XMLDTDHandler fDTDHandler;
260    protected XMLDTDSource fDTDSource;
261
262    // for XIncludeHandler
263    protected XIncludeHandler fParentXIncludeHandler;
264
265    // for buffer size in XIncludeTextReader
266    protected int fBufferSize = XMLEntityManager.DEFAULT_BUFFER_SIZE;
267
268    // It "feels wrong" to store this value here.  However,
269    // calculating it can be time consuming, so we cache it.
270    // It's never going to change in the lifetime of this XIncludeHandler
271    protected String fParentRelativeURI;
272
273    // we cache the child parser configuration, so we don't have to re-create
274    // the objects when the parser is re-used
275    protected XMLParserConfiguration fChildConfig;
276
277    // The cached child parser configuration, may contain a
278    // XInclude or XPointer Handler.  Cache both these
279    protected XMLParserConfiguration fXIncludeChildConfig;
280    protected XMLParserConfiguration fXPointerChildConfig;
281
282    // The XPointerProcessor
283    protected XPointerProcessor fXPtrProcessor = null;
284
285    protected XMLLocator fDocLocation;
286    protected XIncludeMessageFormatter fXIncludeMessageFormatter = new XIncludeMessageFormatter();
287    protected XIncludeNamespaceSupport fNamespaceContext;
288    protected SymbolTable fSymbolTable;
289    protected XMLErrorReporter fErrorReporter;
290    protected XMLEntityResolver fEntityResolver;
291    protected XMLSecurityManager fSecurityManager;
292    protected XMLSecurityPropertyManager fSecurityPropertyMgr;
293
294    // these are needed for text include processing
295    protected XIncludeTextReader fXInclude10TextReader;
296    protected XIncludeTextReader fXInclude11TextReader;
297
298    // these are needed for XML Base processing
299    protected XMLResourceIdentifier fCurrentBaseURI;
300    protected IntStack fBaseURIScope;
301    protected Stack fBaseURI;
302    protected Stack fLiteralSystemID;
303    protected Stack fExpandedSystemID;
304
305    // these are needed for Language Fixup
306    protected IntStack fLanguageScope;
307    protected Stack fLanguageStack;
308    protected String fCurrentLanguage;
309
310    // used for passing features on to child XIncludeHandler objects
311    protected ParserConfigurationSettings fSettings;
312
313    // The current element depth.  We start at depth 0 (before we've reached any elements).
314    // The first element is at depth 1.
315    private int fDepth;
316
317    // The current element depth of the result infoset.
318    private int fResultDepth;
319
320    // this value must be at least 1
321    private static final int INITIAL_SIZE = 8;
322
323    // Used to ensure that fallbacks are always children of include elements,
324    // and that include elements are never children of other include elements.
325    // An index contains true if the ancestor of the current element which resides
326    // at that depth was an include element.
327    private boolean[] fSawInclude = new boolean[INITIAL_SIZE];
328
329    // Ensures that only one fallback element can be at a single depth.
330    // An index contains true if we have seen any fallback elements at that depth,
331    // and it is only reset to false when the end tag of the parent is encountered.
332    private boolean[] fSawFallback = new boolean[INITIAL_SIZE];
333
334    // The state of the processor at each given depth.
335    private int[] fState = new int[INITIAL_SIZE];
336
337    // buffering the necessary DTD events
338    private ArrayList fNotations;
339    private ArrayList fUnparsedEntities;
340
341    // flags which control whether base URI or language fixup is performed.
342    private boolean fFixupBaseURIs = true;
343    private boolean fFixupLanguage = true;
344
345    // for SAX compatibility.
346    // Has the value of the ALLOW_UE_AND_NOTATION_EVENTS feature
347    private boolean fSendUEAndNotationEvents;
348
349    // track the version of the document being parsed
350    private boolean fIsXML11;
351
352    // track whether a DTD is being parsed
353    private boolean fInDTD;
354
355    // track whether the root element of the result infoset has been processed
356    private boolean fSeenRootElement;
357
358    // track whether the child config needs its features refreshed
359    private boolean fNeedCopyFeatures = true;
360
361    // Constructors
362
363    public XIncludeHandler() {
364        fDepth = 0;
365
366        fSawFallback[fDepth] = false;
367        fSawInclude[fDepth] = false;
368        fState[fDepth] = STATE_NORMAL_PROCESSING;
369        fNotations = new ArrayList();
370        fUnparsedEntities = new ArrayList();
371
372        fBaseURIScope = new IntStack();
373        fBaseURI = new Stack();
374        fLiteralSystemID = new Stack();
375        fExpandedSystemID = new Stack();
376        fCurrentBaseURI = new XMLResourceIdentifierImpl();
377
378        fLanguageScope = new IntStack();
379        fLanguageStack = new Stack();
380        fCurrentLanguage = null;
381    }
382
383    // XMLComponent methods
384
385    @Override
386    public void reset(XMLComponentManager componentManager)
387        throws XNIException {
388        fNamespaceContext = null;
389        fDepth = 0;
390        fResultDepth = isRootDocument() ? 0 : fParentXIncludeHandler.getResultDepth();
391        fNotations.clear();
392        fUnparsedEntities.clear();
393        fParentRelativeURI = null;
394        fIsXML11 = false;
395        fInDTD = false;
396        fSeenRootElement = false;
397
398        fBaseURIScope.clear();
399        fBaseURI.clear();
400        fLiteralSystemID.clear();
401        fExpandedSystemID.clear();
402        fLanguageScope.clear();
403        fLanguageStack.clear();
404
405        // REVISIT: Find a better method for maintaining
406        // the state of the XInclude processor. These arrays
407        // can potentially grow quite large. Cleaning them
408        // out on reset may be very time consuming. -- mrglavas
409        //
410        // clear the previous settings from the arrays
411        for (int i = 0; i < fState.length; ++i) {
412            fState[i] = STATE_NORMAL_PROCESSING;
413        }
414        for (int i = 0; i < fSawFallback.length; ++i) {
415            fSawFallback[i] = false;
416        }
417        for (int i = 0; i < fSawInclude.length; ++i) {
418            fSawInclude[i] = false;
419        }
420
421        try {
422            if (!componentManager.getFeature(PARSER_SETTINGS)) {
423                // if parser settings have not changed return.
424                return;
425            }
426        }
427        catch (XMLConfigurationException e) {}
428
429        // parser settings changed. Need to refresh features on child config.
430        fNeedCopyFeatures = true;
431
432        try {
433            fSendUEAndNotationEvents =
434                componentManager.getFeature(ALLOW_UE_AND_NOTATION_EVENTS);
435            if (fChildConfig != null) {
436                fChildConfig.setFeature(
437                    ALLOW_UE_AND_NOTATION_EVENTS,
438                    fSendUEAndNotationEvents);
439            }
440        }
441        catch (XMLConfigurationException e) {
442        }
443
444        try {
445            fFixupBaseURIs =
446                componentManager.getFeature(XINCLUDE_FIXUP_BASE_URIS);
447            if (fChildConfig != null) {
448                fChildConfig.setFeature(
449                    XINCLUDE_FIXUP_BASE_URIS,
450                    fFixupBaseURIs);
451            }
452        }
453        catch (XMLConfigurationException e) {
454            fFixupBaseURIs = true;
455        }
456
457        try {
458            fFixupLanguage =
459                componentManager.getFeature(XINCLUDE_FIXUP_LANGUAGE);
460            if (fChildConfig != null) {
461                fChildConfig.setFeature(
462                    XINCLUDE_FIXUP_LANGUAGE,
463                    fFixupLanguage);
464            }
465        }
466        catch (XMLConfigurationException e) {
467            fFixupLanguage = true;
468        }
469
470        // Get symbol table.
471        try {
472            SymbolTable value =
473                (SymbolTable)componentManager.getProperty(SYMBOL_TABLE);
474            if (value != null) {
475                fSymbolTable = value;
476                if (fChildConfig != null) {
477                    fChildConfig.setProperty(SYMBOL_TABLE, value);
478                }
479            }
480        }
481        catch (XMLConfigurationException e) {
482            fSymbolTable = null;
483        }
484
485        // Get error reporter.
486        try {
487            XMLErrorReporter value =
488                (XMLErrorReporter)componentManager.getProperty(ERROR_REPORTER);
489            if (value != null) {
490                setErrorReporter(value);
491                if (fChildConfig != null) {
492                    fChildConfig.setProperty(ERROR_REPORTER, value);
493                }
494            }
495        }
496        catch (XMLConfigurationException e) {
497            fErrorReporter = null;
498        }
499
500        // Get entity resolver.
501        try {
502            XMLEntityResolver value =
503                (XMLEntityResolver)componentManager.getProperty(
504                    ENTITY_RESOLVER);
505
506            if (value != null) {
507                fEntityResolver = value;
508                if (fChildConfig != null) {
509                    fChildConfig.setProperty(ENTITY_RESOLVER, value);
510                }
511            }
512        }
513        catch (XMLConfigurationException e) {
514            fEntityResolver = null;
515        }
516
517        // Get security manager.
518        try {
519            XMLSecurityManager value =
520                (XMLSecurityManager)componentManager.getProperty(
521                    SECURITY_MANAGER);
522
523            if (value != null) {
524                fSecurityManager = value;
525                if (fChildConfig != null) {
526                    fChildConfig.setProperty(SECURITY_MANAGER, value);
527                }
528            }
529        }
530        catch (XMLConfigurationException e) {
531            fSecurityManager = null;
532        }
533
534        fSecurityPropertyMgr = (XMLSecurityPropertyManager)
535                componentManager.getProperty(Constants.XML_SECURITY_PROPERTY_MANAGER);
536
537        // Get buffer size.
538        try {
539            Integer value =
540                (Integer)componentManager.getProperty(
541                    BUFFER_SIZE);
542
543            if (value != null && value.intValue() > 0) {
544                fBufferSize = value.intValue();
545                if (fChildConfig != null) {
546                    fChildConfig.setProperty(BUFFER_SIZE, value);
547                }
548            }
549            else {
550                fBufferSize = ((Integer)getPropertyDefault(BUFFER_SIZE)).intValue();
551            }
552        }
553        catch (XMLConfigurationException e) {
554                fBufferSize = ((Integer)getPropertyDefault(BUFFER_SIZE)).intValue();
555        }
556
557        // Reset XML 1.0 text reader.
558        if (fXInclude10TextReader != null) {
559                fXInclude10TextReader.setBufferSize(fBufferSize);
560        }
561        // Reset XML 1.1 text reader.
562        if (fXInclude11TextReader != null) {
563            fXInclude11TextReader.setBufferSize(fBufferSize);
564        }
565
566        fSettings = new ParserConfigurationSettings();
567        copyFeatures(componentManager, fSettings);
568
569        // We don't want a schema validator on the new pipeline,
570        // so if it was enabled, we set the feature to false. If
571        // the validation feature was also enabled we turn on
572        // dynamic validation, so that DTD validation is performed
573        // on the included documents only if they have a DOCTYPE.
574        // This is consistent with the behaviour on the main pipeline.
575        try {
576            if (componentManager.getFeature(SCHEMA_VALIDATION)) {
577                fSettings.setFeature(SCHEMA_VALIDATION, false);
578                if (componentManager.getFeature(VALIDATION)) {
579                    fSettings.setFeature(DYNAMIC_VALIDATION, true);
580                }
581            }
582        }
583        catch (XMLConfigurationException e) {}
584
585        // Don't reset fChildConfig -- we don't want it to share the same components.
586        // It will be reset when it is actually used to parse something.
587    } // reset(XMLComponentManager)
588
589    /**
590     * Returns a list of feature identifiers that are recognized by
591     * this component. This method may return null if no features
592     * are recognized by this component.
593     */
594    @Override
595    public String[] getRecognizedFeatures() {
596        return (String[])(RECOGNIZED_FEATURES.clone());
597    } // getRecognizedFeatures():String[]
598
599    /**
600     * Sets the state of a feature. This method is called by the component
601     * manager any time after reset when a feature changes state.
602     * <p>
603     * <strong>Note:</strong> Components should silently ignore features
604     * that do not affect the operation of the component.
605     *
606     * @param featureId The feature identifier.
607     * @param state     The state of the feature.
608     *
609     * @throws SAXNotRecognizedException The component should not throw
610     *                                   this exception.
611     * @throws SAXNotSupportedException The component should not throw
612     *                                  this exception.
613     */
614    @Override
615    public void setFeature(String featureId, boolean state)
616        throws XMLConfigurationException {
617        if (featureId.equals(ALLOW_UE_AND_NOTATION_EVENTS)) {
618            fSendUEAndNotationEvents = state;
619        }
620        if (fSettings != null) {
621            fNeedCopyFeatures = true;
622            fSettings.setFeature(featureId, state);
623        }
624    } // setFeature(String,boolean)
625
626    /**
627     * Returns a list of property identifiers that are recognized by
628     * this component. This method may return null if no properties
629     * are recognized by this component.
630     */
631    @Override
632    public String[] getRecognizedProperties() {
633        return (String[])(RECOGNIZED_PROPERTIES.clone());
634    } // getRecognizedProperties():String[]
635
636    /**
637     * Sets the value of a property. This method is called by the component
638     * manager any time after reset when a property changes value.
639     * <p>
640     * <strong>Note:</strong> Components should silently ignore properties
641     * that do not affect the operation of the component.
642     *
643     * @param propertyId The property identifier.
644     * @param value      The value of the property.
645     *
646     * @throws SAXNotRecognizedException The component should not throw
647     *                                   this exception.
648     * @throws SAXNotSupportedException The component should not throw
649     *                                  this exception.
650     */
651    @Override
652    public void setProperty(String propertyId, Object value)
653        throws XMLConfigurationException {
654        if (propertyId.equals(SYMBOL_TABLE)) {
655            fSymbolTable = (SymbolTable)value;
656            if (fChildConfig != null) {
657                fChildConfig.setProperty(propertyId, value);
658            }
659            return;
660        }
661        if (propertyId.equals(ERROR_REPORTER)) {
662            setErrorReporter((XMLErrorReporter)value);
663            if (fChildConfig != null) {
664                fChildConfig.setProperty(propertyId, value);
665            }
666            return;
667        }
668        if (propertyId.equals(ENTITY_RESOLVER)) {
669            fEntityResolver = (XMLEntityResolver)value;
670            if (fChildConfig != null) {
671                fChildConfig.setProperty(propertyId, value);
672            }
673            return;
674        }
675        if (propertyId.equals(SECURITY_MANAGER)) {
676            fSecurityManager = (XMLSecurityManager)value;
677            if (fChildConfig != null) {
678                fChildConfig.setProperty(propertyId, value);
679            }
680            return;
681        }
682        if (propertyId.equals(XML_SECURITY_PROPERTY_MANAGER)) {
683            fSecurityPropertyMgr = (XMLSecurityPropertyManager)value;
684
685            if (fChildConfig != null) {
686                fChildConfig.setProperty(XML_SECURITY_PROPERTY_MANAGER, value);
687            }
688
689            return;
690        }
691
692        if (propertyId.equals(BUFFER_SIZE)) {
693            Integer bufferSize = (Integer) value;
694            if (fChildConfig != null) {
695                fChildConfig.setProperty(propertyId, value);
696            }
697            if (bufferSize != null && bufferSize.intValue() > 0) {
698                fBufferSize = bufferSize.intValue();
699                // Reset XML 1.0 text reader.
700                if (fXInclude10TextReader != null) {
701                    fXInclude10TextReader.setBufferSize(fBufferSize);
702                }
703                // Reset XML 1.1 text reader.
704                if (fXInclude11TextReader != null) {
705                    fXInclude11TextReader.setBufferSize(fBufferSize);
706                }
707            }
708            return;
709        }
710
711    } // setProperty(String,Object)
712
713    /**
714     * Returns the default state for a feature, or null if this
715     * component does not want to report a default value for this
716     * feature.
717     *
718     * @param featureId The feature identifier.
719     *
720     * @since Xerces 2.2.0
721     */
722    @Override
723    public Boolean getFeatureDefault(String featureId) {
724        for (int i = 0; i < RECOGNIZED_FEATURES.length; i++) {
725            if (RECOGNIZED_FEATURES[i].equals(featureId)) {
726                return FEATURE_DEFAULTS[i];
727            }
728        }
729        return null;
730    } // getFeatureDefault(String):Boolean
731
732    /**
733     * Returns the default state for a property, or null if this
734     * component does not want to report a default value for this
735     * property.
736     *
737     * @param propertyId The property identifier.
738     *
739     * @since Xerces 2.2.0
740     */
741    @Override
742    public Object getPropertyDefault(String propertyId) {
743        for (int i = 0; i < RECOGNIZED_PROPERTIES.length; i++) {
744            if (RECOGNIZED_PROPERTIES[i].equals(propertyId)) {
745                return PROPERTY_DEFAULTS[i];
746            }
747        }
748        return null;
749    } // getPropertyDefault(String):Object
750
751    @Override
752    public void setDocumentHandler(XMLDocumentHandler handler) {
753        fDocumentHandler = handler;
754    }
755
756    @Override
757    public XMLDocumentHandler getDocumentHandler() {
758        return fDocumentHandler;
759    }
760
761    // XMLDocumentHandler methods
762
763    /**
764     * Event sent at the start of the document.
765     *
766     * A fatal error will occur here, if it is detected that this document has been processed
767     * before.
768     *
769     * This event is only passed on to the document handler if this is the root document.
770     */
771    @Override
772    public void startDocument(
773        XMLLocator locator,
774        String encoding,
775        NamespaceContext namespaceContext,
776        Augmentations augs)
777        throws XNIException {
778
779        // we do this to ensure that the proper location is reported in errors
780        // otherwise, the locator from the root document would always be used
781        fErrorReporter.setDocumentLocator(locator);
782
783        if (!isRootDocument()
784            && fParentXIncludeHandler.searchForRecursiveIncludes(locator)) {
785            reportFatalError(
786                "RecursiveInclude",
787                new Object[] { locator.getExpandedSystemId()});
788        }
789
790        if (!(namespaceContext instanceof XIncludeNamespaceSupport)) {
791            reportFatalError("IncompatibleNamespaceContext");
792        }
793        fNamespaceContext = (XIncludeNamespaceSupport)namespaceContext;
794        fDocLocation = locator;
795
796        // initialize the current base URI
797        fCurrentBaseURI.setBaseSystemId(locator.getBaseSystemId());
798        fCurrentBaseURI.setExpandedSystemId(locator.getExpandedSystemId());
799        fCurrentBaseURI.setLiteralSystemId(locator.getLiteralSystemId());
800        saveBaseURI();
801        if (augs == null) {
802            augs = new AugmentationsImpl();
803        }
804        augs.putItem(CURRENT_BASE_URI, fCurrentBaseURI);
805
806        // initialize the current language
807        fCurrentLanguage = XMLSymbols.EMPTY_STRING;
808        saveLanguage(fCurrentLanguage);
809
810        if (isRootDocument() && fDocumentHandler != null) {
811            fDocumentHandler.startDocument(
812                locator,
813                encoding,
814                namespaceContext,
815                augs);
816        }
817    }
818
819    @Override
820    public void xmlDecl(
821        String version,
822        String encoding,
823        String standalone,
824        Augmentations augs)
825        throws XNIException {
826        fIsXML11 = "1.1".equals(version);
827        if (isRootDocument() && fDocumentHandler != null) {
828            fDocumentHandler.xmlDecl(version, encoding, standalone, augs);
829        }
830    }
831
832    @Override
833    public void doctypeDecl(
834        String rootElement,
835        String publicId,
836        String systemId,
837        Augmentations augs)
838        throws XNIException {
839        if (isRootDocument() && fDocumentHandler != null) {
840            fDocumentHandler.doctypeDecl(rootElement, publicId, systemId, augs);
841        }
842    }
843
844    @Override
845    public void comment(XMLString text, Augmentations augs)
846        throws XNIException {
847        if (!fInDTD) {
848            if (fDocumentHandler != null
849                && getState() == STATE_NORMAL_PROCESSING) {
850                fDepth++;
851                augs = modifyAugmentations(augs);
852                fDocumentHandler.comment(text, augs);
853                fDepth--;
854            }
855        }
856        else if (fDTDHandler != null) {
857            fDTDHandler.comment(text, augs);
858        }
859    }
860
861    @Override
862    public void processingInstruction(
863        String target,
864        XMLString data,
865        Augmentations augs)
866        throws XNIException {
867        if (!fInDTD) {
868            if (fDocumentHandler != null
869                && getState() == STATE_NORMAL_PROCESSING) {
870                // we need to change the depth like this so that modifyAugmentations() works
871                fDepth++;
872                augs = modifyAugmentations(augs);
873                fDocumentHandler.processingInstruction(target, data, augs);
874                fDepth--;
875            }
876        }
877        else if (fDTDHandler != null) {
878            fDTDHandler.processingInstruction(target, data, augs);
879        }
880    }
881
882    @Override
883    public void startElement(
884        QName element,
885        XMLAttributes attributes,
886        Augmentations augs)
887        throws XNIException {
888        fDepth++;
889        int lastState = getState(fDepth - 1);
890        // If the last two states were fallback then this must be a descendant of an include
891        // child which isn't a fallback. The specification says we should ignore such elements
892        // and their children.
893        if (lastState == STATE_EXPECT_FALLBACK && getState(fDepth - 2) == STATE_EXPECT_FALLBACK) {
894            setState(STATE_IGNORE);
895        }
896        else {
897            setState(lastState);
898        }
899
900        // we process the xml:base and xml:lang attributes regardless
901        // of what type of element it is.
902        processXMLBaseAttributes(attributes);
903        if (fFixupLanguage) {
904            processXMLLangAttributes(attributes);
905        }
906
907        if (isIncludeElement(element)) {
908            boolean success = this.handleIncludeElement(attributes);
909            if (success) {
910                setState(STATE_IGNORE);
911            }
912            else {
913                setState(STATE_EXPECT_FALLBACK);
914            }
915        }
916        else if (isFallbackElement(element)) {
917            this.handleFallbackElement();
918        }
919        else if (hasXIncludeNamespace(element)) {
920            if (getSawInclude(fDepth - 1)) {
921                reportFatalError(
922                    "IncludeChild",
923                    new Object[] { element.rawname });
924            }
925            if (getSawFallback(fDepth - 1)) {
926                reportFatalError(
927                    "FallbackChild",
928                    new Object[] { element.rawname });
929            }
930            if (getState() == STATE_NORMAL_PROCESSING) {
931                if (fResultDepth++ == 0) {
932                    checkMultipleRootElements();
933                }
934                if (fDocumentHandler != null) {
935                    augs = modifyAugmentations(augs);
936                    attributes = processAttributes(attributes);
937                    fDocumentHandler.startElement(element, attributes, augs);
938                }
939            }
940        }
941        else if (getState() == STATE_NORMAL_PROCESSING) {
942            if (fResultDepth++ == 0) {
943                checkMultipleRootElements();
944            }
945            if (fDocumentHandler != null) {
946                augs = modifyAugmentations(augs);
947                attributes = processAttributes(attributes);
948                fDocumentHandler.startElement(element, attributes, augs);
949            }
950        }
951    }
952
953    @Override
954    public void emptyElement(
955        QName element,
956        XMLAttributes attributes,
957        Augmentations augs)
958        throws XNIException {
959        fDepth++;
960        int lastState = getState(fDepth - 1);
961        // If the last two states were fallback then this must be a descendant of an include
962        // child which isn't a fallback. The specification says we should ignore such elements
963        // and their children.
964        if (lastState == STATE_EXPECT_FALLBACK && getState(fDepth - 2) == STATE_EXPECT_FALLBACK) {
965            setState(STATE_IGNORE);
966        }
967        else {
968            setState(lastState);
969        }
970
971        // we process the xml:base and xml:lang attributes regardless
972        // of what type of element it is.
973        processXMLBaseAttributes(attributes);
974        if (fFixupLanguage) {
975            processXMLLangAttributes(attributes);
976        }
977
978        if (isIncludeElement(element)) {
979            boolean success = this.handleIncludeElement(attributes);
980            if (success) {
981                setState(STATE_IGNORE);
982            }
983            else {
984                reportFatalError("NoFallback",
985                    new Object[] { attributes.getValue(null, "href") });
986            }
987        }
988        else if (isFallbackElement(element)) {
989            this.handleFallbackElement();
990        }
991        else if (hasXIncludeNamespace(element)) {
992            if (getSawInclude(fDepth - 1)) {
993                reportFatalError(
994                    "IncludeChild",
995                    new Object[] { element.rawname });
996            }
997            if (getSawFallback(fDepth - 1)) {
998                reportFatalError(
999                    "FallbackChild",
1000                    new Object[] { element.rawname });
1001            }
1002            if (getState() == STATE_NORMAL_PROCESSING) {
1003                if (fResultDepth == 0) {
1004                    checkMultipleRootElements();
1005                }
1006                if (fDocumentHandler != null) {
1007                    augs = modifyAugmentations(augs);
1008                    attributes = processAttributes(attributes);
1009                    fDocumentHandler.emptyElement(element, attributes, augs);
1010                }
1011            }
1012        }
1013        else if (getState() == STATE_NORMAL_PROCESSING) {
1014            if (fResultDepth == 0) {
1015                checkMultipleRootElements();
1016            }
1017            if (fDocumentHandler != null) {
1018                augs = modifyAugmentations(augs);
1019                attributes = processAttributes(attributes);
1020                fDocumentHandler.emptyElement(element, attributes, augs);
1021            }
1022        }
1023        // reset the out of scope stack elements
1024        setSawFallback(fDepth + 1, false);
1025        setSawInclude(fDepth, false);
1026
1027        // check if an xml:base has gone out of scope
1028        if (fBaseURIScope.size() > 0 && fDepth == fBaseURIScope.peek()) {
1029            // pop the values from the stack
1030            restoreBaseURI();
1031        }
1032        fDepth--;
1033    }
1034
1035    @Override
1036    public void endElement(QName element, Augmentations augs)
1037        throws XNIException {
1038
1039        if (isIncludeElement(element)) {
1040            // if we're ending an include element, and we were expecting a fallback
1041            // we check to see if the children of this include element contained a fallback
1042            if (getState() == STATE_EXPECT_FALLBACK
1043                && !getSawFallback(fDepth + 1)) {
1044                reportFatalError("NoFallback",
1045                    new Object[] { "unknown" });
1046            }
1047        }
1048        if (isFallbackElement(element)) {
1049            // the state would have been set to normal processing if we were expecting the fallback element
1050            // now that we're done processing it, we should ignore all the other children of the include element
1051            if (getState() == STATE_NORMAL_PROCESSING) {
1052                setState(STATE_IGNORE);
1053            }
1054        }
1055        else if (getState() == STATE_NORMAL_PROCESSING) {
1056            --fResultDepth;
1057            if (fDocumentHandler != null) {
1058                fDocumentHandler.endElement(element, augs);
1059            }
1060        }
1061
1062        // reset the out of scope stack elements
1063        setSawFallback(fDepth + 1, false);
1064        setSawInclude(fDepth, false);
1065
1066        // check if an xml:base has gone out of scope
1067        if (fBaseURIScope.size() > 0 && fDepth == fBaseURIScope.peek()) {
1068            // pop the values from the stack
1069            restoreBaseURI();
1070        }
1071
1072        // check if an xml:lang has gone out of scope
1073        if (fLanguageScope.size() > 0 && fDepth == fLanguageScope.peek()) {
1074            // pop the language from the stack
1075            fCurrentLanguage = restoreLanguage();
1076        }
1077
1078        fDepth--;
1079    }
1080
1081    @Override
1082    public void startGeneralEntity(
1083        String name,
1084        XMLResourceIdentifier resId,
1085        String encoding,
1086        Augmentations augs)
1087        throws XNIException {
1088        if (getState() == STATE_NORMAL_PROCESSING) {
1089            if (fResultDepth == 0) {
1090                if (augs != null && Boolean.TRUE.equals(augs.getItem(Constants.ENTITY_SKIPPED))) {
1091                    reportFatalError("UnexpandedEntityReferenceIllegal");
1092                }
1093            }
1094            else if (fDocumentHandler != null) {
1095                fDocumentHandler.startGeneralEntity(name, resId, encoding, augs);
1096            }
1097        }
1098    }
1099
1100    @Override
1101    public void textDecl(String version, String encoding, Augmentations augs)
1102        throws XNIException {
1103        if (fDocumentHandler != null
1104            && getState() == STATE_NORMAL_PROCESSING) {
1105            fDocumentHandler.textDecl(version, encoding, augs);
1106        }
1107    }
1108
1109    @Override
1110    public void endGeneralEntity(String name, Augmentations augs)
1111        throws XNIException {
1112        if (fDocumentHandler != null
1113            && getState() == STATE_NORMAL_PROCESSING
1114            && fResultDepth != 0) {
1115            fDocumentHandler.endGeneralEntity(name, augs);
1116        }
1117    }
1118
1119    @Override
1120    public void characters(XMLString text, Augmentations augs)
1121        throws XNIException {
1122        if (getState() == STATE_NORMAL_PROCESSING) {
1123            if (fResultDepth == 0) {
1124                checkWhitespace(text);
1125            }
1126            else if (fDocumentHandler != null) {
1127                // we need to change the depth like this so that modifyAugmentations() works
1128                fDepth++;
1129                augs = modifyAugmentations(augs);
1130                fDocumentHandler.characters(text, augs);
1131                fDepth--;
1132            }
1133        }
1134    }
1135
1136    @Override
1137    public void ignorableWhitespace(XMLString text, Augmentations augs)
1138        throws XNIException {
1139        if (fDocumentHandler != null
1140            && getState() == STATE_NORMAL_PROCESSING
1141            && fResultDepth != 0) {
1142            fDocumentHandler.ignorableWhitespace(text, augs);
1143        }
1144    }
1145
1146    @Override
1147    public void startCDATA(Augmentations augs) throws XNIException {
1148        if (fDocumentHandler != null
1149            && getState() == STATE_NORMAL_PROCESSING
1150            && fResultDepth != 0) {
1151            fDocumentHandler.startCDATA(augs);
1152        }
1153    }
1154
1155    @Override
1156    public void endCDATA(Augmentations augs) throws XNIException {
1157        if (fDocumentHandler != null
1158            && getState() == STATE_NORMAL_PROCESSING
1159            && fResultDepth != 0) {
1160            fDocumentHandler.endCDATA(augs);
1161        }
1162    }
1163
1164    @Override
1165    public void endDocument(Augmentations augs) throws XNIException {
1166        if (isRootDocument()) {
1167            if (!fSeenRootElement) {
1168                reportFatalError("RootElementRequired");
1169            }
1170            if (fDocumentHandler != null) {
1171                fDocumentHandler.endDocument(augs);
1172            }
1173        }
1174    }
1175
1176    @Override
1177    public void setDocumentSource(XMLDocumentSource source) {
1178        fDocumentSource = source;
1179    }
1180
1181    @Override
1182    public XMLDocumentSource getDocumentSource() {
1183        return fDocumentSource;
1184    }
1185
1186    // DTDHandler methods
1187    // We are only interested in the notation and unparsed entity declarations,
1188    // the rest we just pass on
1189
1190    /* (non-Javadoc)
1191     * @see com.sun.org.apache.xerces.internal.xni.XMLDTDHandler#attributeDecl(java.lang.String, java.lang.String, java.lang.String, java.lang.String[], java.lang.String, com.sun.org.apache.xerces.internal.xni.XMLString, com.sun.org.apache.xerces.internal.xni.XMLString, com.sun.org.apache.xerces.internal.xni.Augmentations)
1192     */
1193    @Override
1194    public void attributeDecl(
1195        String elementName,
1196        String attributeName,
1197        String type,
1198        String[] enumeration,
1199        String defaultType,
1200        XMLString defaultValue,
1201        XMLString nonNormalizedDefaultValue,
1202        Augmentations augmentations)
1203        throws XNIException {
1204        if (fDTDHandler != null) {
1205            fDTDHandler.attributeDecl(
1206                elementName,
1207                attributeName,
1208                type,
1209                enumeration,
1210                defaultType,
1211                defaultValue,
1212                nonNormalizedDefaultValue,
1213                augmentations);
1214        }
1215    }
1216
1217    /* (non-Javadoc)
1218     * @see com.sun.org.apache.xerces.internal.xni.XMLDTDHandler#elementDecl(java.lang.String, java.lang.String, com.sun.org.apache.xerces.internal.xni.Augmentations)
1219     */
1220    @Override
1221    public void elementDecl(
1222        String name,
1223        String contentModel,
1224        Augmentations augmentations)
1225        throws XNIException {
1226        if (fDTDHandler != null) {
1227            fDTDHandler.elementDecl(name, contentModel, augmentations);
1228        }
1229    }
1230
1231    /* (non-Javadoc)
1232     * @see com.sun.org.apache.xerces.internal.xni.XMLDTDHandler#endAttlist(com.sun.org.apache.xerces.internal.xni.Augmentations)
1233     */
1234    @Override
1235    public void endAttlist(Augmentations augmentations) throws XNIException {
1236        if (fDTDHandler != null) {
1237            fDTDHandler.endAttlist(augmentations);
1238        }
1239    }
1240
1241    /* (non-Javadoc)
1242     * @see com.sun.org.apache.xerces.internal.xni.XMLDTDHandler#endConditional(com.sun.org.apache.xerces.internal.xni.Augmentations)
1243     */
1244    @Override
1245    public void endConditional(Augmentations augmentations)
1246        throws XNIException {
1247        if (fDTDHandler != null) {
1248            fDTDHandler.endConditional(augmentations);
1249        }
1250    }
1251
1252    /* (non-Javadoc)
1253     * @see com.sun.org.apache.xerces.internal.xni.XMLDTDHandler#endDTD(com.sun.org.apache.xerces.internal.xni.Augmentations)
1254     */
1255    @Override
1256    public void endDTD(Augmentations augmentations) throws XNIException {
1257        if (fDTDHandler != null) {
1258            fDTDHandler.endDTD(augmentations);
1259        }
1260        fInDTD = false;
1261    }
1262
1263    /* (non-Javadoc)
1264     * @see com.sun.org.apache.xerces.internal.xni.XMLDTDHandler#endExternalSubset(com.sun.org.apache.xerces.internal.xni.Augmentations)
1265     */
1266    @Override
1267    public void endExternalSubset(Augmentations augmentations)
1268        throws XNIException {
1269        if (fDTDHandler != null) {
1270            fDTDHandler.endExternalSubset(augmentations);
1271        }
1272    }
1273
1274    /* (non-Javadoc)
1275     * @see com.sun.org.apache.xerces.internal.xni.XMLDTDHandler#endParameterEntity(java.lang.String, com.sun.org.apache.xerces.internal.xni.Augmentations)
1276     */
1277    @Override
1278    public void endParameterEntity(String name, Augmentations augmentations)
1279        throws XNIException {
1280        if (fDTDHandler != null) {
1281            fDTDHandler.endParameterEntity(name, augmentations);
1282        }
1283    }
1284
1285    /* (non-Javadoc)
1286     * @see com.sun.org.apache.xerces.internal.xni.XMLDTDHandler#externalEntityDecl(java.lang.String, com.sun.org.apache.xerces.internal.xni.XMLResourceIdentifier, com.sun.org.apache.xerces.internal.xni.Augmentations)
1287     */
1288    @Override
1289    public void externalEntityDecl(
1290        String name,
1291        XMLResourceIdentifier identifier,
1292        Augmentations augmentations)
1293        throws XNIException {
1294        if (fDTDHandler != null) {
1295            fDTDHandler.externalEntityDecl(name, identifier, augmentations);
1296        }
1297    }
1298
1299    /* (non-Javadoc)
1300     * @see com.sun.org.apache.xerces.internal.xni.XMLDTDHandler#getDTDSource()
1301     */
1302    @Override
1303    public XMLDTDSource getDTDSource() {
1304        return fDTDSource;
1305    }
1306
1307    /* (non-Javadoc)
1308     * @see com.sun.org.apache.xerces.internal.xni.XMLDTDHandler#ignoredCharacters(com.sun.org.apache.xerces.internal.xni.XMLString, com.sun.org.apache.xerces.internal.xni.Augmentations)
1309     */
1310    @Override
1311    public void ignoredCharacters(XMLString text, Augmentations augmentations)
1312        throws XNIException {
1313        if (fDTDHandler != null) {
1314            fDTDHandler.ignoredCharacters(text, augmentations);
1315        }
1316    }
1317
1318    /* (non-Javadoc)
1319     * @see com.sun.org.apache.xerces.internal.xni.XMLDTDHandler#internalEntityDecl(java.lang.String, com.sun.org.apache.xerces.internal.xni.XMLString, com.sun.org.apache.xerces.internal.xni.XMLString, com.sun.org.apache.xerces.internal.xni.Augmentations)
1320     */
1321    @Override
1322    public void internalEntityDecl(
1323        String name,
1324        XMLString text,
1325        XMLString nonNormalizedText,
1326        Augmentations augmentations)
1327        throws XNIException {
1328        if (fDTDHandler != null) {
1329            fDTDHandler.internalEntityDecl(
1330                name,
1331                text,
1332                nonNormalizedText,
1333                augmentations);
1334        }
1335    }
1336
1337    /* (non-Javadoc)
1338     * @see com.sun.org.apache.xerces.internal.xni.XMLDTDHandler#notationDecl(java.lang.String, com.sun.org.apache.xerces.internal.xni.XMLResourceIdentifier, com.sun.org.apache.xerces.internal.xni.Augmentations)
1339     */
1340    @Override
1341    public void notationDecl(
1342        String name,
1343        XMLResourceIdentifier identifier,
1344        Augmentations augmentations)
1345        throws XNIException {
1346        this.addNotation(name, identifier, augmentations);
1347        if (fDTDHandler != null) {
1348            fDTDHandler.notationDecl(name, identifier, augmentations);
1349        }
1350    }
1351
1352    /* (non-Javadoc)
1353     * @see com.sun.org.apache.xerces.internal.xni.XMLDTDHandler#setDTDSource(com.sun.org.apache.xerces.internal.xni.parser.XMLDTDSource)
1354     */
1355    @Override
1356    public void setDTDSource(XMLDTDSource source) {
1357        fDTDSource = source;
1358    }
1359
1360    /* (non-Javadoc)
1361     * @see com.sun.org.apache.xerces.internal.xni.XMLDTDHandler#startAttlist(java.lang.String, com.sun.org.apache.xerces.internal.xni.Augmentations)
1362     */
1363    @Override
1364    public void startAttlist(String elementName, Augmentations augmentations)
1365        throws XNIException {
1366        if (fDTDHandler != null) {
1367            fDTDHandler.startAttlist(elementName, augmentations);
1368        }
1369    }
1370
1371    /* (non-Javadoc)
1372     * @see com.sun.org.apache.xerces.internal.xni.XMLDTDHandler#startConditional(short, com.sun.org.apache.xerces.internal.xni.Augmentations)
1373     */
1374    @Override
1375    public void startConditional(short type, Augmentations augmentations)
1376        throws XNIException {
1377        if (fDTDHandler != null) {
1378            fDTDHandler.startConditional(type, augmentations);
1379        }
1380    }
1381
1382    /* (non-Javadoc)
1383     * @see com.sun.org.apache.xerces.internal.xni.XMLDTDHandler#startDTD(com.sun.org.apache.xerces.internal.xni.XMLLocator, com.sun.org.apache.xerces.internal.xni.Augmentations)
1384     */
1385    @Override
1386    public void startDTD(XMLLocator locator, Augmentations augmentations)
1387        throws XNIException {
1388        fInDTD = true;
1389        if (fDTDHandler != null) {
1390            fDTDHandler.startDTD(locator, augmentations);
1391        }
1392    }
1393
1394    /* (non-Javadoc)
1395     * @see com.sun.org.apache.xerces.internal.xni.XMLDTDHandler#startExternalSubset(com.sun.org.apache.xerces.internal.xni.XMLResourceIdentifier, com.sun.org.apache.xerces.internal.xni.Augmentations)
1396     */
1397    @Override
1398    public void startExternalSubset(
1399        XMLResourceIdentifier identifier,
1400        Augmentations augmentations)
1401        throws XNIException {
1402        if (fDTDHandler != null) {
1403            fDTDHandler.startExternalSubset(identifier, augmentations);
1404        }
1405    }
1406
1407    /* (non-Javadoc)
1408     * @see com.sun.org.apache.xerces.internal.xni.XMLDTDHandler#startParameterEntity(java.lang.String, com.sun.org.apache.xerces.internal.xni.XMLResourceIdentifier, java.lang.String, com.sun.org.apache.xerces.internal.xni.Augmentations)
1409     */
1410    @Override
1411    public void startParameterEntity(
1412        String name,
1413        XMLResourceIdentifier identifier,
1414        String encoding,
1415        Augmentations augmentations)
1416        throws XNIException {
1417        if (fDTDHandler != null) {
1418            fDTDHandler.startParameterEntity(
1419                name,
1420                identifier,
1421                encoding,
1422                augmentations);
1423        }
1424    }
1425
1426    /* (non-Javadoc)
1427     * @see com.sun.org.apache.xerces.internal.xni.XMLDTDHandler#unparsedEntityDecl(java.lang.String, com.sun.org.apache.xerces.internal.xni.XMLResourceIdentifier, java.lang.String, com.sun.org.apache.xerces.internal.xni.Augmentations)
1428     */
1429    @Override
1430    public void unparsedEntityDecl(
1431        String name,
1432        XMLResourceIdentifier identifier,
1433        String notation,
1434        Augmentations augmentations)
1435        throws XNIException {
1436        this.addUnparsedEntity(name, identifier, notation, augmentations);
1437        if (fDTDHandler != null) {
1438            fDTDHandler.unparsedEntityDecl(
1439                name,
1440                identifier,
1441                notation,
1442                augmentations);
1443        }
1444    }
1445
1446    /* (non-Javadoc)
1447     * @see com.sun.org.apache.xerces.internal.xni.parser.XMLDTDSource#getDTDHandler()
1448     */
1449    @Override
1450    public XMLDTDHandler getDTDHandler() {
1451        return fDTDHandler;
1452    }
1453
1454    /* (non-Javadoc)
1455     * @see com.sun.org.apache.xerces.internal.xni.parser.XMLDTDSource#setDTDHandler(com.sun.org.apache.xerces.internal.xni.XMLDTDHandler)
1456     */
1457    @Override
1458    public void setDTDHandler(XMLDTDHandler handler) {
1459        fDTDHandler = handler;
1460    }
1461
1462    // XIncludeHandler methods
1463
1464    private void setErrorReporter(XMLErrorReporter reporter) {
1465        fErrorReporter = reporter;
1466        if (fErrorReporter != null) {
1467            fErrorReporter.putMessageFormatter(
1468                XIncludeMessageFormatter.XINCLUDE_DOMAIN, fXIncludeMessageFormatter);
1469            // this ensures the proper location is displayed in error messages
1470            if (fDocLocation != null) {
1471                fErrorReporter.setDocumentLocator(fDocLocation);
1472            }
1473        }
1474    }
1475
1476    protected void handleFallbackElement() {
1477        if (!getSawInclude(fDepth - 1)) {
1478            if (getState() == STATE_IGNORE) {
1479                return;
1480            }
1481            reportFatalError("FallbackParent");
1482        }
1483
1484        setSawInclude(fDepth, false);
1485        fNamespaceContext.setContextInvalid();
1486
1487        if (getSawFallback(fDepth)) {
1488            reportFatalError("MultipleFallbacks");
1489        }
1490        else {
1491            setSawFallback(fDepth, true);
1492        }
1493
1494        // Either the state is STATE_EXPECT_FALLBACK or it's STATE_IGNORE.
1495        // If we're ignoring, we want to stay ignoring. But if we're expecting this fallback element,
1496        // we want to signal that we should process the children.
1497        if (getState() == STATE_EXPECT_FALLBACK) {
1498            setState(STATE_NORMAL_PROCESSING);
1499        }
1500    }
1501
1502    protected boolean handleIncludeElement(XMLAttributes attributes)
1503        throws XNIException {
1504        if (getSawInclude(fDepth - 1)) {
1505            reportFatalError("IncludeChild", new Object[] { XINCLUDE_INCLUDE });
1506        }
1507        if (getState() == STATE_IGNORE) {
1508            return true;
1509        }
1510        setSawInclude(fDepth, true);
1511        fNamespaceContext.setContextInvalid();
1512
1513        // TODO: does Java use IURIs by default?
1514        //       [Definition: An internationalized URI reference, or IURI, is a URI reference that directly uses [Unicode] characters.]
1515        // TODO: figure out what section 4.1.1 of the XInclude spec is talking about
1516        //       has to do with disallowed ASCII character escaping
1517        //       this ties in with the above IURI section, but I suspect Java already does it
1518
1519        String href = attributes.getValue(XINCLUDE_ATTR_HREF);
1520        String parse = attributes.getValue(XINCLUDE_ATTR_PARSE);
1521        String xpointer =  attributes.getValue(XPOINTER);
1522        String accept = attributes.getValue(XINCLUDE_ATTR_ACCEPT);
1523        String acceptLanguage = attributes.getValue(XINCLUDE_ATTR_ACCEPT_LANGUAGE);
1524
1525        if (parse == null) {
1526            parse = XINCLUDE_PARSE_XML;
1527        }
1528        if (href == null) {
1529            href = XMLSymbols.EMPTY_STRING;
1530        }
1531        if (href.length() == 0 && XINCLUDE_PARSE_XML.equals(parse)) {
1532            if (xpointer == null) {
1533                reportFatalError("XpointerMissing");
1534            }
1535            else {
1536                // When parse="xml" and an xpointer is specified treat
1537                // all absences of the href attribute as a resource error.
1538                Locale locale = (fErrorReporter != null) ? fErrorReporter.getLocale() : null;
1539                String reason = fXIncludeMessageFormatter.formatMessage(locale, "XPointerStreamability", null);
1540                reportResourceError("XMLResourceError", new Object[] { href, reason });
1541                return false;
1542            }
1543        }
1544
1545        URI hrefURI = null;
1546
1547        // Check whether href is correct and perform escaping as per section 4.1.1 of the XInclude spec.
1548        // Report fatal error if the href value contains a fragment identifier or if the value after
1549        // escaping is a syntactically invalid URI or IRI.
1550        try {
1551            hrefURI = new URI(href, true);
1552            if (hrefURI.getFragment() != null) {
1553                reportFatalError("HrefFragmentIdentifierIllegal", new Object[] {href});
1554            }
1555        }
1556        catch (URI.MalformedURIException exc) {
1557            String newHref = escapeHref(href);
1558            if (href != newHref) {
1559                href = newHref;
1560                try {
1561                    hrefURI = new URI(href, true);
1562                    if (hrefURI.getFragment() != null) {
1563                        reportFatalError("HrefFragmentIdentifierIllegal", new Object[] {href});
1564                    }
1565                }
1566                catch (URI.MalformedURIException exc2) {
1567                    reportFatalError("HrefSyntacticallyInvalid", new Object[] {href});
1568                }
1569            }
1570            else {
1571                reportFatalError("HrefSyntacticallyInvalid", new Object[] {href});
1572            }
1573        }
1574
1575        // Verify that if an accept and/or an accept-language attribute exist
1576        // that the value(s) don't contain disallowed characters.
1577        if (accept != null && !isValidInHTTPHeader(accept)) {
1578            reportFatalError("AcceptMalformed", null);
1579            accept = null;
1580        }
1581        if (acceptLanguage != null && !isValidInHTTPHeader(acceptLanguage)) {
1582            reportFatalError("AcceptLanguageMalformed", null);
1583            acceptLanguage = null;
1584        }
1585
1586        XMLInputSource includedSource = null;
1587        if (fEntityResolver != null) {
1588            try {
1589                XMLResourceIdentifier resourceIdentifier =
1590                    new XMLResourceIdentifierImpl(
1591                        null,
1592                        href,
1593                        fCurrentBaseURI.getExpandedSystemId(),
1594                        XMLEntityManager.expandSystemId(
1595                            href,
1596                            fCurrentBaseURI.getExpandedSystemId(),
1597                            false));
1598
1599                includedSource =
1600                    fEntityResolver.resolveEntity(resourceIdentifier);
1601
1602                if (includedSource != null &&
1603                    !(includedSource instanceof HTTPInputSource) &&
1604                    (accept != null || acceptLanguage != null) &&
1605                    includedSource.getCharacterStream() == null &&
1606                    includedSource.getByteStream() == null) {
1607
1608                    includedSource = createInputSource(includedSource.getPublicId(), includedSource.getSystemId(),
1609                        includedSource.getBaseSystemId(), accept, acceptLanguage);
1610                }
1611            }
1612            catch (IOException e) {
1613                reportResourceError(
1614                    "XMLResourceError",
1615                    new Object[] { href, e.getMessage()});
1616                return false;
1617            }
1618        }
1619
1620        if (includedSource == null) {
1621            // setup an HTTPInputSource if either of the content negotation attributes were specified.
1622            if (accept != null || acceptLanguage != null) {
1623                includedSource = createInputSource(null, href, fCurrentBaseURI.getExpandedSystemId(), accept, acceptLanguage);
1624            }
1625            else {
1626                includedSource = new XMLInputSource(null, href, fCurrentBaseURI.getExpandedSystemId());
1627            }
1628        }
1629
1630        if (parse.equals(XINCLUDE_PARSE_XML)) {
1631            // Instead of always creating a new configuration, the first one can be reused
1632            if ((xpointer != null && fXPointerChildConfig == null)
1633                        || (xpointer == null && fXIncludeChildConfig == null) ) {
1634
1635                String parserName = XINCLUDE_DEFAULT_CONFIGURATION;
1636                if (xpointer != null)
1637                        parserName = "com.sun.org.apache.xerces.internal.parsers.XPointerParserConfiguration";
1638
1639                fChildConfig =
1640                    (XMLParserConfiguration)ObjectFactory.newInstance(
1641                        parserName,
1642                        true);
1643
1644                // use the same symbol table, error reporter, entity resolver, security manager and buffer size.
1645                if (fSymbolTable != null) fChildConfig.setProperty(SYMBOL_TABLE, fSymbolTable);
1646                if (fErrorReporter != null) fChildConfig.setProperty(ERROR_REPORTER, fErrorReporter);
1647                if (fEntityResolver != null) fChildConfig.setProperty(ENTITY_RESOLVER, fEntityResolver);
1648                fChildConfig.setProperty(SECURITY_MANAGER, fSecurityManager);
1649                fChildConfig.setProperty(XML_SECURITY_PROPERTY_MANAGER, fSecurityPropertyMgr);
1650                fChildConfig.setProperty(BUFFER_SIZE, new Integer(fBufferSize));
1651
1652                // features must be copied to child configuration
1653                fNeedCopyFeatures = true;
1654
1655                // use the same namespace context
1656                fChildConfig.setProperty(
1657                    Constants.XERCES_PROPERTY_PREFIX
1658                        + Constants.NAMESPACE_CONTEXT_PROPERTY,
1659                    fNamespaceContext);
1660
1661                fChildConfig.setFeature(
1662                            XINCLUDE_FIXUP_BASE_URIS,
1663                            fFixupBaseURIs);
1664
1665                fChildConfig.setFeature(
1666                            XINCLUDE_FIXUP_LANGUAGE,
1667                            fFixupLanguage);
1668
1669
1670                // If the xpointer attribute is present
1671                if (xpointer != null ) {
1672
1673                    XPointerHandler newHandler =
1674                        (XPointerHandler)fChildConfig.getProperty(
1675                            Constants.XERCES_PROPERTY_PREFIX
1676                                + Constants.XPOINTER_HANDLER_PROPERTY);
1677
1678                        fXPtrProcessor = newHandler;
1679
1680                        // ???
1681                        ((XPointerHandler)fXPtrProcessor).setProperty(
1682                            Constants.XERCES_PROPERTY_PREFIX
1683                            + Constants.NAMESPACE_CONTEXT_PROPERTY,
1684                        fNamespaceContext);
1685
1686                    ((XPointerHandler)fXPtrProcessor).setProperty(XINCLUDE_FIXUP_BASE_URIS,
1687                            fFixupBaseURIs);
1688
1689                    ((XPointerHandler)fXPtrProcessor).setProperty(
1690                            XINCLUDE_FIXUP_LANGUAGE, fFixupLanguage);
1691
1692                    if (fErrorReporter != null)
1693                        ((XPointerHandler)fXPtrProcessor).setProperty(ERROR_REPORTER, fErrorReporter);
1694                        // ???
1695
1696                    newHandler.setParent(this);
1697                    newHandler.setDocumentHandler(this.getDocumentHandler());
1698                    fXPointerChildConfig = fChildConfig;
1699                } else {
1700                    XIncludeHandler newHandler =
1701                        (XIncludeHandler)fChildConfig.getProperty(
1702                            Constants.XERCES_PROPERTY_PREFIX
1703                                + Constants.XINCLUDE_HANDLER_PROPERTY);
1704
1705                        newHandler.setParent(this);
1706                    newHandler.setDocumentHandler(this.getDocumentHandler());
1707                    fXIncludeChildConfig = fChildConfig;
1708                }
1709            }
1710
1711            // If an xpointer attribute is present
1712            if (xpointer != null ) {
1713                fChildConfig = fXPointerChildConfig ;
1714
1715                // Parse the XPointer expression
1716                try {
1717                    ((XPointerProcessor)fXPtrProcessor).parseXPointer(xpointer);
1718
1719                } catch (XNIException ex) {
1720                    // report the XPointer error as a resource error
1721                    reportResourceError(
1722                            "XMLResourceError",
1723                            new Object[] { href, ex.getMessage()});
1724                        return false;
1725                }
1726            } else {
1727                fChildConfig = fXIncludeChildConfig;
1728            }
1729
1730            // set all features on parserConfig to match this parser configuration
1731            if (fNeedCopyFeatures) {
1732                copyFeatures(fSettings, fChildConfig);
1733            }
1734            fNeedCopyFeatures = false;
1735
1736            try {
1737                fNamespaceContext.pushScope();
1738
1739                fChildConfig.parse(includedSource);
1740                // necessary to make sure proper location is reported in errors
1741                if (fErrorReporter != null) {
1742                    fErrorReporter.setDocumentLocator(fDocLocation);
1743                }
1744
1745                // If the xpointer attribute is present
1746                if (xpointer != null ) {
1747                        // and it was not resolved
1748                        if (!((XPointerProcessor)fXPtrProcessor).isXPointerResolved()) {
1749                        Locale locale = (fErrorReporter != null) ? fErrorReporter.getLocale() : null;
1750                        String reason = fXIncludeMessageFormatter.formatMessage(locale, "XPointerResolutionUnsuccessful", null);
1751                        reportResourceError("XMLResourceError", new Object[] {href, reason});
1752                                // use the fallback
1753                                return false;
1754                        }
1755                }
1756            }
1757            catch (XNIException e) {
1758                // necessary to make sure proper location is reported in errors
1759                if (fErrorReporter != null) {
1760                    fErrorReporter.setDocumentLocator(fDocLocation);
1761                }
1762                reportFatalError("XMLParseError", new Object[] { href, e.getMessage() });
1763            }
1764            catch (IOException e) {
1765                // necessary to make sure proper location is reported in errors
1766                if (fErrorReporter != null) {
1767                    fErrorReporter.setDocumentLocator(fDocLocation);
1768                }
1769                // An IOException indicates that we had trouble reading the file, not
1770                // that it was an invalid XML file.  So we send a resource error, not a
1771                // fatal error.
1772                reportResourceError(
1773                    "XMLResourceError",
1774                    new Object[] { href, e.getMessage()});
1775                return false;
1776            }
1777            finally {
1778                fNamespaceContext.popScope();
1779            }
1780        }
1781        else if (parse.equals(XINCLUDE_PARSE_TEXT)) {
1782            // we only care about encoding for parse="text"
1783            String encoding = attributes.getValue(XINCLUDE_ATTR_ENCODING);
1784            includedSource.setEncoding(encoding);
1785            XIncludeTextReader textReader = null;
1786
1787            try {
1788                // Setup the appropriate text reader.
1789                if (!fIsXML11) {
1790                    if (fXInclude10TextReader == null) {
1791                        fXInclude10TextReader = new XIncludeTextReader(includedSource, this, fBufferSize);
1792                    }
1793                    else {
1794                        fXInclude10TextReader.setInputSource(includedSource);
1795                    }
1796                    textReader = fXInclude10TextReader;
1797                }
1798                else {
1799                    if (fXInclude11TextReader == null) {
1800                        fXInclude11TextReader = new XInclude11TextReader(includedSource, this, fBufferSize);
1801                    }
1802                    else {
1803                        fXInclude11TextReader.setInputSource(includedSource);
1804                    }
1805                    textReader = fXInclude11TextReader;
1806                }
1807                textReader.setErrorReporter(fErrorReporter);
1808                textReader.parse();
1809            }
1810            // encoding errors
1811            catch (MalformedByteSequenceException ex) {
1812                fErrorReporter.reportError(ex.getDomain(), ex.getKey(),
1813                    ex.getArguments(), XMLErrorReporter.SEVERITY_FATAL_ERROR);
1814            }
1815            catch (CharConversionException e) {
1816                fErrorReporter.reportError(XMLMessageFormatter.XML_DOMAIN,
1817                    "CharConversionFailure", null, XMLErrorReporter.SEVERITY_FATAL_ERROR);
1818            }
1819            catch (IOException e) {
1820                reportResourceError(
1821                    "TextResourceError",
1822                    new Object[] { href, e.getMessage()});
1823                return false;
1824            }
1825            finally {
1826                if (textReader != null) {
1827                    try {
1828                        textReader.close();
1829                    }
1830                    catch (IOException e) {
1831                        reportResourceError(
1832                            "TextResourceError",
1833                            new Object[] { href, e.getMessage()});
1834                        return false;
1835                    }
1836                }
1837            }
1838        }
1839        else {
1840            reportFatalError("InvalidParseValue", new Object[] { parse });
1841        }
1842        return true;
1843    }
1844
1845    /**
1846     * Returns true if the element has the namespace "http://www.w3.org/2001/XInclude"
1847     * @param element the element to check
1848     * @return true if the element has the namespace "http://www.w3.org/2001/XInclude"
1849     */
1850    protected boolean hasXIncludeNamespace(QName element) {
1851        // REVISIT: The namespace of this element should be bound
1852        // already. Why are we looking it up from the namespace
1853        // context? -- mrglavas
1854        return element.uri == XINCLUDE_NS_URI
1855            || fNamespaceContext.getURI(element.prefix) == XINCLUDE_NS_URI;
1856    }
1857
1858    /**
1859     * Checks if the element is an &lt;include&gt; element.  The element must have
1860     * the XInclude namespace, and a local name of "include".
1861     *
1862     * @param element the element to check
1863     * @return true if the element is an &lt;include&gt; element
1864     * @see #hasXIncludeNamespace(QName)
1865     */
1866    protected boolean isIncludeElement(QName element) {
1867        return element.localpart.equals(XINCLUDE_INCLUDE) &&
1868            hasXIncludeNamespace(element);
1869    }
1870
1871    /**
1872     * Checks if the element is an &lt;fallback&gt; element.  The element must have
1873     * the XInclude namespace, and a local name of "fallback".
1874     *
1875     * @param element the element to check
1876     * @return true if the element is an &lt;fallback; element
1877     * @see #hasXIncludeNamespace(QName)
1878     */
1879    protected boolean isFallbackElement(QName element) {
1880        return element.localpart.equals(XINCLUDE_FALLBACK) &&
1881            hasXIncludeNamespace(element);
1882    }
1883
1884    /**
1885     * Returns true if the current [base URI] is the same as the [base URI] that
1886     * was in effect on the include parent.  This method should <em>only</em> be called
1887     * when the current element is a top level included element, i.e. the direct child
1888     * of a fallback element, or the root elements in an included document.
1889     * The "include parent" is the element which, in the result infoset, will be the
1890     * direct parent of the current element.
1891     * @return true if the [base URIs] are the same string
1892     */
1893    protected boolean sameBaseURIAsIncludeParent() {
1894        String parentBaseURI = getIncludeParentBaseURI();
1895        String baseURI = fCurrentBaseURI.getExpandedSystemId();
1896        // REVISIT: should we use File#sameFile() ?
1897        //          I think the benefit of using it is that it resolves host names
1898        //          instead of just doing a string comparison.
1899        // TODO: [base URI] is still an open issue with the working group.
1900        //       They're deciding if xml:base should be added if the [base URI] is different in terms
1901        //       of resolving relative references, or if it should be added if they are different at all.
1902        //       Revisit this after a final decision has been made.
1903        //       The decision also affects whether we output the file name of the URI, or just the path.
1904        return parentBaseURI != null && parentBaseURI.equals(baseURI);
1905    }
1906
1907    /**
1908     * Returns true if the current [language] is equivalent to the [language] that
1909     * was in effect on the include parent, taking case-insensitivity into account
1910     * as per [RFC 3066].  This method should <em>only</em> be called when the
1911     * current element is a top level included element, i.e. the direct child
1912     * of a fallback element, or the root elements in an included document.
1913     * The "include parent" is the element which, in the result infoset, will be the
1914     * direct parent of the current element.
1915     *
1916     * @return true if the [language] properties have the same value
1917     * taking case-insensitivity into account as per [RFC 3066].
1918     */
1919    protected boolean sameLanguageAsIncludeParent() {
1920        String parentLanguage = getIncludeParentLanguage();
1921        return parentLanguage != null && parentLanguage.equalsIgnoreCase(fCurrentLanguage);
1922    }
1923
1924    /**
1925     * Checks if the file indicated by the given XMLLocator has already been included
1926     * in the current stack.
1927     * @param includedSource the source to check for inclusion
1928     * @return true if the source has already been included
1929     */
1930    protected boolean searchForRecursiveIncludes(XMLLocator includedSource) {
1931        String includedSystemId = includedSource.getExpandedSystemId();
1932
1933        if (includedSystemId == null) {
1934            try {
1935                includedSystemId =
1936                    XMLEntityManager.expandSystemId(
1937                        includedSource.getLiteralSystemId(),
1938                        includedSource.getBaseSystemId(),
1939                        false);
1940            }
1941            catch (MalformedURIException e) {
1942                reportFatalError("ExpandedSystemId");
1943            }
1944        }
1945
1946        if (includedSystemId.equals(fCurrentBaseURI.getExpandedSystemId())) {
1947            return true;
1948        }
1949
1950        if (fParentXIncludeHandler == null) {
1951            return false;
1952        }
1953        return fParentXIncludeHandler.searchForRecursiveIncludes(
1954            includedSource);
1955    }
1956
1957    /**
1958     * Returns true if the current element is a top level included item.  This means
1959     * it's either the child of a fallback element, or the top level item in an
1960     * included document
1961     * @return true if the current element is a top level included item
1962     */
1963    protected boolean isTopLevelIncludedItem() {
1964        return isTopLevelIncludedItemViaInclude()
1965            || isTopLevelIncludedItemViaFallback();
1966    }
1967
1968    protected boolean isTopLevelIncludedItemViaInclude() {
1969        return fDepth == 1 && !isRootDocument();
1970    }
1971
1972    protected boolean isTopLevelIncludedItemViaFallback() {
1973        // Technically, this doesn't check if the parent was a fallback, it also
1974        // would return true if any of the parent's sibling elements were fallbacks.
1975        // However, this doesn't matter, since we will always be ignoring elements
1976        // whose parent's siblings were fallbacks.
1977        return getSawFallback(fDepth - 1);
1978    }
1979
1980    /**
1981     * Processes the XMLAttributes object of startElement() calls.  Performs the following tasks:
1982     * <ul>
1983     * <li> If the element is a top level included item whose [base URI] is different from the
1984     * [base URI] of the include parent, then an xml:base attribute is added to specify the
1985     * true [base URI]
1986     * <li> For all namespace prefixes which are in-scope in an included item, but not in scope
1987     * in the include parent, a xmlns:prefix attribute is added
1988     * <li> For all attributes with a type of ENTITY, ENTITIES or NOTATIONS, the notations and
1989     * unparsed entities are processed as described in the spec, sections 4.5.1 and 4.5.2
1990     * </ul>
1991     * @param attributes
1992     * @return
1993     */
1994    protected XMLAttributes processAttributes(XMLAttributes attributes) {
1995        if (isTopLevelIncludedItem()) {
1996            // Modify attributes to fix the base URI (spec 4.5.5).
1997            // We only do it to top level included elements, which have a different
1998            // base URI than their include parent.
1999            if (fFixupBaseURIs && !sameBaseURIAsIncludeParent()) {
2000                if (attributes == null) {
2001                    attributes = new XMLAttributesImpl();
2002                }
2003
2004                // This causes errors with schema validation, if the schema doesn't
2005                // specify that these elements can have an xml:base attribute
2006                String uri = null;
2007                try {
2008                    uri = this.getRelativeBaseURI();
2009                }
2010                catch (MalformedURIException e) {
2011                    // this shouldn't ever happen, since by definition, we had to traverse
2012                    // the same URIs to even get to this place
2013                    uri = fCurrentBaseURI.getExpandedSystemId();
2014                }
2015                int index =
2016                    attributes.addAttribute(
2017                        XML_BASE_QNAME,
2018                        XMLSymbols.fCDATASymbol,
2019                        uri);
2020                attributes.setSpecified(index, true);
2021            }
2022
2023            // Modify attributes to perform language-fixup (spec 4.5.6).
2024            // We only do it to top level included elements, which have a different
2025            // [language] than their include parent.
2026            if (fFixupLanguage && !sameLanguageAsIncludeParent()) {
2027                if (attributes == null) {
2028                    attributes = new XMLAttributesImpl();
2029                }
2030                int index =
2031                    attributes.addAttribute(
2032                        XML_LANG_QNAME,
2033                        XMLSymbols.fCDATASymbol,
2034                        fCurrentLanguage);
2035                attributes.setSpecified(index, true);
2036            }
2037
2038            // Modify attributes of included items to do namespace-fixup. (spec 4.5.4)
2039            Enumeration inscopeNS = fNamespaceContext.getAllPrefixes();
2040            while (inscopeNS.hasMoreElements()) {
2041                String prefix = (String)inscopeNS.nextElement();
2042                String parentURI =
2043                    fNamespaceContext.getURIFromIncludeParent(prefix);
2044                String uri = fNamespaceContext.getURI(prefix);
2045                if (parentURI != uri && attributes != null) {
2046                    if (prefix == XMLSymbols.EMPTY_STRING) {
2047                        if (attributes
2048                            .getValue(
2049                                NamespaceContext.XMLNS_URI,
2050                                XMLSymbols.PREFIX_XMLNS)
2051                            == null) {
2052                            if (attributes == null) {
2053                                attributes = new XMLAttributesImpl();
2054                            }
2055
2056                            QName ns = (QName)NEW_NS_ATTR_QNAME.clone();
2057                            ns.prefix = null;
2058                            ns.localpart = XMLSymbols.PREFIX_XMLNS;
2059                            ns.rawname = XMLSymbols.PREFIX_XMLNS;
2060                            int index =
2061                                attributes.addAttribute(
2062                                    ns,
2063                                    XMLSymbols.fCDATASymbol,
2064                                    uri != null ? uri : XMLSymbols.EMPTY_STRING);
2065                            attributes.setSpecified(index, true);
2066                            // Need to re-declare this prefix in the current context
2067                            // in order for the SAX parser to report the appropriate
2068                            // start and end prefix mapping events. -- mrglavas
2069                            fNamespaceContext.declarePrefix(prefix, uri);
2070                        }
2071                    }
2072                    else if (
2073                        attributes.getValue(NamespaceContext.XMLNS_URI, prefix)
2074                            == null) {
2075                        if (attributes == null) {
2076                            attributes = new XMLAttributesImpl();
2077                        }
2078
2079                        QName ns = (QName)NEW_NS_ATTR_QNAME.clone();
2080                        ns.localpart = prefix;
2081                        ns.rawname += prefix;
2082                        ns.rawname = (fSymbolTable != null) ?
2083                            fSymbolTable.addSymbol(ns.rawname) :
2084                            ns.rawname.intern();
2085                        int index =
2086                            attributes.addAttribute(
2087                                ns,
2088                                XMLSymbols.fCDATASymbol,
2089                                uri != null ? uri : XMLSymbols.EMPTY_STRING);
2090                        attributes.setSpecified(index, true);
2091                        // Need to re-declare this prefix in the current context
2092                        // in order for the SAX parser to report the appropriate
2093                        // start and end prefix mapping events. -- mrglavas
2094                        fNamespaceContext.declarePrefix(prefix, uri);
2095                    }
2096                }
2097            }
2098        }
2099
2100        if (attributes != null) {
2101            int length = attributes.getLength();
2102            for (int i = 0; i < length; i++) {
2103                String type = attributes.getType(i);
2104                String value = attributes.getValue(i);
2105                if (type == XMLSymbols.fENTITYSymbol) {
2106                    this.checkUnparsedEntity(value);
2107                }
2108                if (type == XMLSymbols.fENTITIESSymbol) {
2109                    // 4.5.1 - Unparsed Entities
2110                    StringTokenizer st = new StringTokenizer(value);
2111                    while (st.hasMoreTokens()) {
2112                        String entName = st.nextToken();
2113                        this.checkUnparsedEntity(entName);
2114                    }
2115                }
2116                else if (type == XMLSymbols.fNOTATIONSymbol) {
2117                    // 4.5.2 - Notations
2118                    this.checkNotation(value);
2119                }
2120                /* We actually don't need to do anything for 4.5.3, because at this stage the
2121                 * value of the attribute is just a string. It will be taken care of later
2122                 * in the pipeline, when the IDREFs are actually resolved against IDs.
2123                 *
2124                 * if (type == XMLSymbols.fIDREFSymbol || type == XMLSymbols.fIDREFSSymbol) { }
2125                 */
2126            }
2127        }
2128
2129        return attributes;
2130    }
2131
2132    /**
2133     * Returns a URI, relative to the include parent's base URI, of the current
2134     * [base URI].  For instance, if the current [base URI] was "dir1/dir2/file.xml"
2135     * and the include parent's [base URI] was "dir/", this would return "dir2/file.xml".
2136     * @return the relative URI
2137     */
2138    protected String getRelativeBaseURI() throws MalformedURIException {
2139        int includeParentDepth = getIncludeParentDepth();
2140        String relativeURI = this.getRelativeURI(includeParentDepth);
2141        if (isRootDocument()) {
2142            return relativeURI;
2143        }
2144        else {
2145            if (relativeURI.equals("")) {
2146                relativeURI = fCurrentBaseURI.getLiteralSystemId();
2147            }
2148
2149            if (includeParentDepth == 0) {
2150                if (fParentRelativeURI == null) {
2151                    fParentRelativeURI =
2152                        fParentXIncludeHandler.getRelativeBaseURI();
2153                }
2154                if (fParentRelativeURI.equals("")) {
2155                    return relativeURI;
2156                }
2157
2158                URI base = new URI(fParentRelativeURI, true);
2159                URI uri = new URI(base, relativeURI);
2160
2161                /** Check whether the scheme components are equal. */
2162                final String baseScheme = base.getScheme();
2163                final String literalScheme = uri.getScheme();
2164                if (!Objects.equals(baseScheme, literalScheme)) {
2165                    return relativeURI;
2166                }
2167
2168                /** Check whether the authority components are equal. */
2169                final String baseAuthority = base.getAuthority();
2170                final String literalAuthority = uri.getAuthority();
2171                if (!Objects.equals(baseAuthority, literalAuthority)) {
2172                    return uri.getSchemeSpecificPart();
2173                }
2174
2175                /**
2176                 * The scheme and authority components are equal,
2177                 * return the path and the possible query and/or
2178                 * fragment which follow.
2179                 */
2180                final String literalPath = uri.getPath();
2181                final String literalQuery = uri.getQueryString();
2182                final String literalFragment = uri.getFragment();
2183                if (literalQuery != null || literalFragment != null) {
2184                    final StringBuilder buffer = new StringBuilder();
2185                    if (literalPath != null) {
2186                        buffer.append(literalPath);
2187                    }
2188                    if (literalQuery != null) {
2189                        buffer.append('?');
2190                        buffer.append(literalQuery);
2191                    }
2192                    if (literalFragment != null) {
2193                        buffer.append('#');
2194                        buffer.append(literalFragment);
2195                    }
2196                    return buffer.toString();
2197                }
2198                return literalPath;
2199            }
2200            else {
2201                return relativeURI;
2202            }
2203        }
2204    }
2205
2206    /**
2207     * Returns the [base URI] of the include parent.
2208     * @return the base URI of the include parent.
2209     */
2210    private String getIncludeParentBaseURI() {
2211        int depth = getIncludeParentDepth();
2212        if (!isRootDocument() && depth == 0) {
2213            return fParentXIncludeHandler.getIncludeParentBaseURI();
2214        }
2215        else {
2216            return this.getBaseURI(depth);
2217        }
2218    }
2219
2220    /**
2221     * Returns the [language] of the include parent.
2222     *
2223     * @return the language property of the include parent.
2224     */
2225    private String getIncludeParentLanguage() {
2226        int depth = getIncludeParentDepth();
2227        if (!isRootDocument() && depth == 0) {
2228            return fParentXIncludeHandler.getIncludeParentLanguage();
2229        }
2230        else {
2231            return getLanguage(depth);
2232        }
2233    }
2234
2235    /**
2236     * Returns the depth of the include parent.  Here, the include parent is
2237     * calculated as the last non-include or non-fallback element. It is assumed
2238     * this method is called when the current element is a top level included item.
2239     * Returning 0 indicates that the top level element in this document
2240     * was an include element.
2241     * @return the depth of the top level include element
2242     */
2243    private int getIncludeParentDepth() {
2244        // We don't start at fDepth, since it is either the top level included item,
2245        // or an include element, when this method is called.
2246        for (int i = fDepth - 1; i >= 0; i--) {
2247            // This technically might not always return the first non-include/fallback
2248            // element that it comes to, since sawFallback() returns true if a fallback
2249            // was ever encountered at that depth.  However, if a fallback was encountered
2250            // at that depth, and it wasn't the direct descendant of the current element
2251            // then we can't be in a situation where we're calling this method (because
2252            // we'll always be in STATE_IGNORE)
2253            if (!getSawInclude(i) && !getSawFallback(i)) {
2254                return i;
2255            }
2256        }
2257        // shouldn't get here, since depth 0 should never have an include element or
2258        // a fallback element
2259        return 0;
2260    }
2261
2262    /**
2263     * Returns the current element depth of the result infoset.
2264     */
2265    private int getResultDepth() {
2266        return fResultDepth;
2267    }
2268
2269    /**
2270     * Modify the augmentations.  Add an [included] infoset item, if the current
2271     * element is a top level included item.
2272     * @param augs the Augmentations to modify.
2273     * @return the modified Augmentations
2274     */
2275    protected Augmentations modifyAugmentations(Augmentations augs) {
2276        return modifyAugmentations(augs, false);
2277    }
2278
2279    /**
2280     * Modify the augmentations.  Add an [included] infoset item, if <code>force</code>
2281     * is true, or if the current element is a top level included item.
2282     * @param augs the Augmentations to modify.
2283     * @param force whether to force modification
2284     * @return the modified Augmentations
2285     */
2286    protected Augmentations modifyAugmentations(
2287        Augmentations augs,
2288        boolean force) {
2289        if (force || isTopLevelIncludedItem()) {
2290            if (augs == null) {
2291                augs = new AugmentationsImpl();
2292            }
2293            augs.putItem(XINCLUDE_INCLUDED, Boolean.TRUE);
2294        }
2295        return augs;
2296    }
2297
2298    protected int getState(int depth) {
2299        return fState[depth];
2300    }
2301
2302    protected int getState() {
2303        return fState[fDepth];
2304    }
2305
2306    protected void setState(int state) {
2307        if (fDepth >= fState.length) {
2308            int[] newarray = new int[fDepth * 2];
2309            System.arraycopy(fState, 0, newarray, 0, fState.length);
2310            fState = newarray;
2311        }
2312        fState[fDepth] = state;
2313    }
2314
2315    /**
2316     * Records that an &lt;fallback&gt; was encountered at the specified depth,
2317     * as an ancestor of the current element, or as a sibling of an ancestor of the
2318     * current element.
2319     *
2320     * @param depth
2321     * @param val
2322     */
2323    protected void setSawFallback(int depth, boolean val) {
2324        if (depth >= fSawFallback.length) {
2325            boolean[] newarray = new boolean[depth * 2];
2326            System.arraycopy(fSawFallback, 0, newarray, 0, fSawFallback.length);
2327            fSawFallback = newarray;
2328        }
2329        fSawFallback[depth] = val;
2330    }
2331
2332    /**
2333     * Returns whether an &lt;fallback&gt; was encountered at the specified depth,
2334     * as an ancestor of the current element, or as a sibling of an ancestor of the
2335     * current element.
2336     *
2337     * @param depth
2338     */
2339    protected boolean getSawFallback(int depth) {
2340        if (depth >= fSawFallback.length) {
2341            return false;
2342        }
2343        return fSawFallback[depth];
2344    }
2345
2346    /**
2347     * Records that an &lt;include&gt; was encountered at the specified depth,
2348     * as an ancestor of the current item.
2349     *
2350     * @param depth
2351     * @param val
2352     */
2353    protected void setSawInclude(int depth, boolean val) {
2354        if (depth >= fSawInclude.length) {
2355            boolean[] newarray = new boolean[depth * 2];
2356            System.arraycopy(fSawInclude, 0, newarray, 0, fSawInclude.length);
2357            fSawInclude = newarray;
2358        }
2359        fSawInclude[depth] = val;
2360    }
2361
2362    /**
2363     * Return whether an &lt;include&gt; was encountered at the specified depth,
2364     * as an ancestor of the current item.
2365     *
2366     * @param depth
2367     * @return
2368     */
2369    protected boolean getSawInclude(int depth) {
2370        if (depth >= fSawInclude.length) {
2371            return false;
2372        }
2373        return fSawInclude[depth];
2374    }
2375
2376    protected void reportResourceError(String key) {
2377        this.reportFatalError(key, null);
2378    }
2379
2380    protected void reportResourceError(String key, Object[] args) {
2381        this.reportError(key, args, XMLErrorReporter.SEVERITY_WARNING);
2382    }
2383
2384    protected void reportFatalError(String key) {
2385        this.reportFatalError(key, null);
2386    }
2387
2388    protected void reportFatalError(String key, Object[] args) {
2389        this.reportError(key, args, XMLErrorReporter.SEVERITY_FATAL_ERROR);
2390    }
2391
2392    private void reportError(String key, Object[] args, short severity) {
2393        if (fErrorReporter != null) {
2394            fErrorReporter.reportError(
2395                XIncludeMessageFormatter.XINCLUDE_DOMAIN,
2396                key,
2397                args,
2398                severity);
2399        }
2400        // we won't worry about when error reporter is null, since there should always be
2401        // at least the default error reporter
2402    }
2403
2404    /**
2405     * Set the parent of this XIncludeHandler in the tree
2406     * @param parent
2407     */
2408    protected void setParent(XIncludeHandler parent) {
2409        fParentXIncludeHandler = parent;
2410    }
2411
2412    // used to know whether to pass declarations to the document handler
2413    protected boolean isRootDocument() {
2414        return fParentXIncludeHandler == null;
2415    }
2416
2417    /**
2418     * Caches an unparsed entity.
2419     * @param name the name of the unparsed entity
2420     * @param identifier the location of the unparsed entity
2421     * @param augmentations any Augmentations that were on the original unparsed entity declaration
2422     */
2423    protected void addUnparsedEntity(
2424        String name,
2425        XMLResourceIdentifier identifier,
2426        String notation,
2427        Augmentations augmentations) {
2428        UnparsedEntity ent = new UnparsedEntity();
2429        ent.name = name;
2430        ent.systemId = identifier.getLiteralSystemId();
2431        ent.publicId = identifier.getPublicId();
2432        ent.baseURI = identifier.getBaseSystemId();
2433        ent.expandedSystemId = identifier.getExpandedSystemId();
2434        ent.notation = notation;
2435        ent.augmentations = augmentations;
2436        fUnparsedEntities.add(ent);
2437    }
2438
2439    /**
2440     * Caches a notation.
2441     * @param name the name of the notation
2442     * @param identifier the location of the notation
2443     * @param augmentations any Augmentations that were on the original notation declaration
2444     */
2445    protected void addNotation(
2446        String name,
2447        XMLResourceIdentifier identifier,
2448        Augmentations augmentations) {
2449        Notation not = new Notation();
2450        not.name = name;
2451        not.systemId = identifier.getLiteralSystemId();
2452        not.publicId = identifier.getPublicId();
2453        not.baseURI = identifier.getBaseSystemId();
2454        not.expandedSystemId = identifier.getExpandedSystemId();
2455        not.augmentations = augmentations;
2456        fNotations.add(not);
2457    }
2458
2459    /**
2460     * Checks if an UnparsedEntity with the given name was declared in the DTD of the document
2461     * for the current pipeline.  If so, then the notation for the UnparsedEntity is checked.
2462     * If that turns out okay, then the UnparsedEntity is passed to the root pipeline to
2463     * be checked for conflicts, and sent to the root DTDHandler.
2464     *
2465     * @param entName the name of the UnparsedEntity to check
2466     */
2467    protected void checkUnparsedEntity(String entName) {
2468        UnparsedEntity ent = new UnparsedEntity();
2469        ent.name = entName;
2470        int index = fUnparsedEntities.indexOf(ent);
2471        if (index != -1) {
2472            ent = (UnparsedEntity)fUnparsedEntities.get(index);
2473            // first check the notation of the unparsed entity
2474            checkNotation(ent.notation);
2475            checkAndSendUnparsedEntity(ent);
2476        }
2477    }
2478
2479    /**
2480     * Checks if a Notation with the given name was declared in the DTD of the document
2481     * for the current pipeline.  If so, that Notation is passed to the root pipeline to
2482     * be checked for conflicts, and sent to the root DTDHandler
2483     *
2484     * @param notName the name of the Notation to check
2485     */
2486    protected void checkNotation(String notName) {
2487        Notation not = new Notation();
2488        not.name = notName;
2489        int index = fNotations.indexOf(not);
2490        if (index != -1) {
2491            not = (Notation)fNotations.get(index);
2492            checkAndSendNotation(not);
2493        }
2494    }
2495
2496    /**
2497     * The purpose of this method is to check if an UnparsedEntity conflicts with a previously
2498     * declared entity in the current pipeline stack.  If there is no conflict, the
2499     * UnparsedEntity is sent by the root pipeline.
2500     *
2501     * @param ent the UnparsedEntity to check for conflicts
2502     */
2503    protected void checkAndSendUnparsedEntity(UnparsedEntity ent) {
2504        if (isRootDocument()) {
2505            int index = fUnparsedEntities.indexOf(ent);
2506            if (index == -1) {
2507                // There is no unparsed entity with the same name that we have sent.
2508                // Calling unparsedEntityDecl() will add the entity to our local store,
2509                // and also send the unparsed entity to the DTDHandler
2510                XMLResourceIdentifier id =
2511                    new XMLResourceIdentifierImpl(
2512                        ent.publicId,
2513                        ent.systemId,
2514                        ent.baseURI,
2515                        ent.expandedSystemId);
2516                addUnparsedEntity(
2517                    ent.name,
2518                    id,
2519                    ent.notation,
2520                    ent.augmentations);
2521                if (fSendUEAndNotationEvents && fDTDHandler != null) {
2522                    fDTDHandler.unparsedEntityDecl(
2523                        ent.name,
2524                        id,
2525                        ent.notation,
2526                        ent.augmentations);
2527                }
2528            }
2529            else {
2530                UnparsedEntity localEntity =
2531                    (UnparsedEntity)fUnparsedEntities.get(index);
2532                if (!ent.isDuplicate(localEntity)) {
2533                    reportFatalError(
2534                        "NonDuplicateUnparsedEntity",
2535                        new Object[] { ent.name });
2536                }
2537            }
2538        }
2539        else {
2540            fParentXIncludeHandler.checkAndSendUnparsedEntity(ent);
2541        }
2542    }
2543
2544    /**
2545     * The purpose of this method is to check if a Notation conflicts with a previously
2546     * declared notation in the current pipeline stack.  If there is no conflict, the
2547     * Notation is sent by the root pipeline.
2548     *
2549     * @param not the Notation to check for conflicts
2550     */
2551    protected void checkAndSendNotation(Notation not) {
2552        if (isRootDocument()) {
2553            int index = fNotations.indexOf(not);
2554            if (index == -1) {
2555                // There is no notation with the same name that we have sent.
2556                XMLResourceIdentifier id =
2557                    new XMLResourceIdentifierImpl(
2558                        not.publicId,
2559                        not.systemId,
2560                        not.baseURI,
2561                        not.expandedSystemId);
2562                addNotation(not.name, id, not.augmentations);
2563                if (fSendUEAndNotationEvents && fDTDHandler != null) {
2564                    fDTDHandler.notationDecl(not.name, id, not.augmentations);
2565                }
2566            }
2567            else {
2568                Notation localNotation = (Notation)fNotations.get(index);
2569                if (!not.isDuplicate(localNotation)) {
2570                    reportFatalError(
2571                        "NonDuplicateNotation",
2572                        new Object[] { not.name });
2573                }
2574            }
2575        }
2576        else {
2577            fParentXIncludeHandler.checkAndSendNotation(not);
2578        }
2579    }
2580
2581    /**
2582     * Checks whether the string only contains white space characters.
2583     *
2584     * @param value the text to check
2585     */
2586    private void checkWhitespace(XMLString value) {
2587        int end = value.offset + value.length;
2588        for (int i = value.offset; i < end; ++i) {
2589            if (!XMLChar.isSpace(value.ch[i])) {
2590                reportFatalError("ContentIllegalAtTopLevel");
2591                return;
2592            }
2593        }
2594    }
2595
2596    /**
2597     * Checks whether the root element has already been processed.
2598     */
2599    private void checkMultipleRootElements() {
2600        if (getRootElementProcessed()) {
2601            reportFatalError("MultipleRootElements");
2602        }
2603        setRootElementProcessed(true);
2604    }
2605
2606    /**
2607     * Sets whether the root element has been processed.
2608     */
2609    private void setRootElementProcessed(boolean seenRoot) {
2610        if (isRootDocument()) {
2611            fSeenRootElement = seenRoot;
2612            return;
2613        }
2614        fParentXIncludeHandler.setRootElementProcessed(seenRoot);
2615    }
2616
2617    /**
2618     * Returns whether the root element has been processed.
2619     */
2620    private boolean getRootElementProcessed() {
2621        return isRootDocument() ? fSeenRootElement : fParentXIncludeHandler.getRootElementProcessed();
2622    }
2623
2624    // It would be nice if we didn't have to repeat code like this, but there's no interface that has
2625    // setFeature() and addRecognizedFeatures() that the objects have in common.
2626    protected void copyFeatures(
2627        XMLComponentManager from,
2628        ParserConfigurationSettings to) {
2629        Enumeration features = Constants.getXercesFeatures();
2630        copyFeatures1(features, Constants.XERCES_FEATURE_PREFIX, from, to);
2631        features = Constants.getSAXFeatures();
2632        copyFeatures1(features, Constants.SAX_FEATURE_PREFIX, from, to);
2633    }
2634
2635    protected void copyFeatures(
2636        XMLComponentManager from,
2637        XMLParserConfiguration to) {
2638        Enumeration features = Constants.getXercesFeatures();
2639        copyFeatures1(features, Constants.XERCES_FEATURE_PREFIX, from, to);
2640        features = Constants.getSAXFeatures();
2641        copyFeatures1(features, Constants.SAX_FEATURE_PREFIX, from, to);
2642    }
2643
2644    private void copyFeatures1(
2645        Enumeration features,
2646        String featurePrefix,
2647        XMLComponentManager from,
2648        ParserConfigurationSettings to) {
2649        while (features.hasMoreElements()) {
2650            String featureId = featurePrefix + (String)features.nextElement();
2651
2652            to.addRecognizedFeatures(new String[] { featureId });
2653
2654            try {
2655                to.setFeature(featureId, from.getFeature(featureId));
2656            }
2657            catch (XMLConfigurationException e) {
2658                // componentManager doesn't support this feature,
2659                // so we won't worry about it
2660            }
2661        }
2662    }
2663
2664    private void copyFeatures1(
2665        Enumeration features,
2666        String featurePrefix,
2667        XMLComponentManager from,
2668        XMLParserConfiguration to) {
2669        while (features.hasMoreElements()) {
2670            String featureId = featurePrefix + (String)features.nextElement();
2671            boolean value = from.getFeature(featureId);
2672
2673            try {
2674                to.setFeature(featureId, value);
2675            }
2676            catch (XMLConfigurationException e) {
2677                // componentManager doesn't support this feature,
2678                // so we won't worry about it
2679            }
2680        }
2681    }
2682
2683    // This is a storage class to hold information about the notations.
2684    // We're not using XMLNotationDecl because we don't want to lose the augmentations.
2685    protected static class Notation {
2686        public String name;
2687        public String systemId;
2688        public String baseURI;
2689        public String publicId;
2690        public String expandedSystemId;
2691        public Augmentations augmentations;
2692
2693        // equals() returns true if two Notations have the same name.
2694        // Useful for searching Vectors for notations with the same name
2695        @Override
2696        public boolean equals(Object obj) {
2697            return obj == this || obj instanceof Notation
2698                    && Objects.equals(name, ((Notation)obj).name);
2699        }
2700
2701        @Override
2702        public int hashCode() {
2703            return Objects.hashCode(name);
2704        }
2705
2706        // from 4.5.2
2707        // Notation items with the same [name], [system identifier],
2708        // [public identifier], and [declaration base URI] are considered
2709        // to be duplicate. An application may also be able to detect that
2710        // notations are duplicate through other means. For instance, the URI
2711        // resulting from combining the system identifier and the declaration
2712        // base URI is the same.
2713        public boolean isDuplicate(Object obj) {
2714            if (obj != null && obj instanceof Notation) {
2715                Notation other = (Notation)obj;
2716                return Objects.equals(name, other.name)
2717                && Objects.equals(publicId, other.publicId)
2718                && Objects.equals(expandedSystemId, other.expandedSystemId);
2719            }
2720            return false;
2721        }
2722    }
2723
2724    // This is a storage class to hold information about the unparsed entities.
2725    // We're not using XMLEntityDecl because we don't want to lose the augmentations.
2726    protected static class UnparsedEntity {
2727        public String name;
2728        public String systemId;
2729        public String baseURI;
2730        public String publicId;
2731        public String expandedSystemId;
2732        public String notation;
2733        public Augmentations augmentations;
2734
2735        // equals() returns true if two UnparsedEntities have the same name.
2736        // Useful for searching Vectors for entities with the same name
2737        @Override
2738        public boolean equals(Object obj) {
2739            return obj == this || obj instanceof UnparsedEntity
2740                    && Objects.equals(name, ((UnparsedEntity)obj).name);
2741        }
2742
2743        @Override
2744        public int hashCode() {
2745            return Objects.hashCode(name);
2746        }
2747
2748        // from 4.5.1:
2749        // Unparsed entity items with the same [name], [system identifier],
2750        // [public identifier], [declaration base URI], [notation name], and
2751        // [notation] are considered to be duplicate. An application may also
2752        // be able to detect that unparsed entities are duplicate through other
2753        // means. For instance, the URI resulting from combining the system
2754        // identifier and the declaration base URI is the same.
2755        public boolean isDuplicate(Object obj) {
2756            if (obj != null && obj instanceof UnparsedEntity) {
2757                UnparsedEntity other = (UnparsedEntity)obj;
2758                return Objects.equals(name, other.name)
2759                && Objects.equals(publicId, other.publicId)
2760                && Objects.equals(expandedSystemId, other.expandedSystemId)
2761                && Objects.equals(notation, other.notation);
2762            }
2763            return false;
2764        }
2765    }
2766
2767    // The following methods are used for XML Base processing
2768
2769    /**
2770     * Saves the current base URI to the top of the stack.
2771     */
2772    protected void saveBaseURI() {
2773        fBaseURIScope.push(fDepth);
2774        fBaseURI.push(fCurrentBaseURI.getBaseSystemId());
2775        fLiteralSystemID.push(fCurrentBaseURI.getLiteralSystemId());
2776        fExpandedSystemID.push(fCurrentBaseURI.getExpandedSystemId());
2777    }
2778
2779    /**
2780     * Discards the URIs at the top of the stack, and restores the ones beneath it.
2781     */
2782    protected void restoreBaseURI() {
2783        fBaseURI.pop();
2784        fLiteralSystemID.pop();
2785        fExpandedSystemID.pop();
2786        fBaseURIScope.pop();
2787        fCurrentBaseURI.setBaseSystemId((String)fBaseURI.peek());
2788        fCurrentBaseURI.setLiteralSystemId((String)fLiteralSystemID.peek());
2789        fCurrentBaseURI.setExpandedSystemId((String)fExpandedSystemID.peek());
2790    }
2791
2792    // The following methods are used for language processing
2793
2794    /**
2795     * Saves the given language on the top of the stack.
2796     *
2797     * @param lanaguage the language to push onto the stack.
2798     */
2799    protected void saveLanguage(String language) {
2800        fLanguageScope.push(fDepth);
2801        fLanguageStack.push(language);
2802    }
2803
2804    /**
2805     * Discards the language at the top of the stack, and returns the one beneath it.
2806     */
2807    public String restoreLanguage() {
2808        fLanguageStack.pop();
2809        fLanguageScope.pop();
2810        return (String) fLanguageStack.peek();
2811    }
2812
2813    /**
2814     * Gets the base URI that was in use at that depth
2815     * @param depth
2816     * @return the base URI
2817     */
2818    public String getBaseURI(int depth) {
2819        int scope = scopeOfBaseURI(depth);
2820        return (String)fExpandedSystemID.elementAt(scope);
2821    }
2822
2823    /**
2824     * Gets the language that was in use at that depth.
2825     * @param depth
2826     * @return the language
2827     */
2828    public String getLanguage(int depth) {
2829        int scope = scopeOfLanguage(depth);
2830        return (String)fLanguageStack.elementAt(scope);
2831    }
2832
2833    /**
2834     * Returns a relative URI, which when resolved against the base URI at the
2835     * specified depth, will create the current base URI.
2836     * This is accomplished by merged the literal system IDs.
2837     * @param depth the depth at which to start creating the relative URI
2838     * @return a relative URI to convert the base URI at the given depth to the current
2839     *         base URI
2840     */
2841    public String getRelativeURI(int depth) throws MalformedURIException {
2842        // The literal system id at the location given by "start" is *in focus* at
2843        // the given depth. So we need to adjust it to the next scope, so that we
2844        // only process out of focus literal system ids
2845        int start = scopeOfBaseURI(depth) + 1;
2846        if (start == fBaseURIScope.size()) {
2847            // If that is the last system id, then we don't need a relative URI
2848            return "";
2849        }
2850        URI uri = new URI("file", (String)fLiteralSystemID.elementAt(start));
2851        for (int i = start + 1; i < fBaseURIScope.size(); i++) {
2852            uri = new URI(uri, (String)fLiteralSystemID.elementAt(i));
2853        }
2854        return uri.getPath();
2855    }
2856
2857    // We need to find two consecutive elements in the scope stack,
2858    // such that the first is lower than 'depth' (or equal), and the
2859    // second is higher.
2860    private int scopeOfBaseURI(int depth) {
2861        for (int i = fBaseURIScope.size() - 1; i >= 0; i--) {
2862            if (fBaseURIScope.elementAt(i) <= depth)
2863                return i;
2864        }
2865        // we should never get here, because 0 was put on the stack in startDocument()
2866        return -1;
2867    }
2868
2869    private int scopeOfLanguage(int depth) {
2870        for (int i = fLanguageScope.size() - 1; i >= 0; i--) {
2871            if (fLanguageScope.elementAt(i) <= depth)
2872                return i;
2873        }
2874        // we should never get here, because 0 was put on the stack in startDocument()
2875        return -1;
2876    }
2877
2878    /**
2879     * Search for a xml:base attribute, and if one is found, put the new base URI into
2880     * effect.
2881     */
2882    protected void processXMLBaseAttributes(XMLAttributes attributes) {
2883        String baseURIValue =
2884            attributes.getValue(NamespaceContext.XML_URI, "base");
2885        if (baseURIValue != null) {
2886            try {
2887                String expandedValue =
2888                    XMLEntityManager.expandSystemId(
2889                        baseURIValue,
2890                        fCurrentBaseURI.getExpandedSystemId(),
2891                        false);
2892                fCurrentBaseURI.setLiteralSystemId(baseURIValue);
2893                fCurrentBaseURI.setBaseSystemId(
2894                    fCurrentBaseURI.getExpandedSystemId());
2895                fCurrentBaseURI.setExpandedSystemId(expandedValue);
2896
2897                // push the new values on the stack
2898                saveBaseURI();
2899            }
2900            catch (MalformedURIException e) {
2901                // REVISIT: throw error here
2902            }
2903        }
2904    }
2905
2906    /**
2907     * Search for a xml:lang attribute, and if one is found, put the new
2908     * [language] into effect.
2909     */
2910    protected void processXMLLangAttributes(XMLAttributes attributes) {
2911        String language = attributes.getValue(NamespaceContext.XML_URI, "lang");
2912        if (language != null) {
2913            fCurrentLanguage = language;
2914            saveLanguage(fCurrentLanguage);
2915        }
2916    }
2917
2918    /**
2919     * Returns <code>true</code> if the given string
2920     * would be valid in an HTTP header.
2921     *
2922     * @param value string to check
2923     * @return <code>true</code> if the given string
2924     * would be valid in an HTTP header
2925     */
2926    private boolean isValidInHTTPHeader (String value) {
2927        char ch;
2928        for (int i = value.length() - 1; i >= 0; --i) {
2929            ch = value.charAt(i);
2930            if (ch < 0x20 || ch > 0x7E) {
2931                return false;
2932            }
2933        }
2934        return true;
2935    }
2936
2937    /**
2938     * Returns a new <code>XMLInputSource</code> from the given parameters.
2939     */
2940    private XMLInputSource createInputSource(String publicId,
2941            String systemId, String baseSystemId,
2942            String accept, String acceptLanguage) {
2943
2944        HTTPInputSource httpSource = new HTTPInputSource(publicId, systemId, baseSystemId);
2945        if (accept != null && accept.length() > 0) {
2946            httpSource.setHTTPRequestProperty(XIncludeHandler.HTTP_ACCEPT, accept);
2947        }
2948        if (acceptLanguage != null && acceptLanguage.length() > 0) {
2949            httpSource.setHTTPRequestProperty(XIncludeHandler.HTTP_ACCEPT_LANGUAGE, acceptLanguage);
2950        }
2951        return httpSource;
2952    }
2953
2954    // which ASCII characters need to be escaped
2955    private static final boolean gNeedEscaping[] = new boolean[128];
2956    // the first hex character if a character needs to be escaped
2957    private static final char gAfterEscaping1[] = new char[128];
2958    // the second hex character if a character needs to be escaped
2959    private static final char gAfterEscaping2[] = new char[128];
2960    private static final char[] gHexChs = {'0', '1', '2', '3', '4', '5', '6', '7',
2961                                     '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
2962    // initialize the above 3 arrays
2963    static {
2964        char[] escChs = {' ', '<', '>', '"', '{', '}', '|', '\\', '^', '`'};
2965        int len = escChs.length;
2966        char ch;
2967        for (int i = 0; i < len; i++) {
2968            ch = escChs[i];
2969            gNeedEscaping[ch] = true;
2970            gAfterEscaping1[ch] = gHexChs[ch >> 4];
2971            gAfterEscaping2[ch] = gHexChs[ch & 0xf];
2972        }
2973    }
2974
2975    //
2976    // Escape an href value according to (4.1.1):
2977    //
2978    // To convert the value of the href attribute to an IRI reference, the following characters must be escaped:
2979    // space #x20
2980    // the delimiters < #x3C, > #x3E and " #x22
2981    // the unwise characters { #x7B, } #x7D, | #x7C, \ #x5C, ^ #x5E and ` #x60
2982    //
2983    // To convert an IRI reference to a URI reference, the following characters must also be escaped:
2984    // the Unicode plane 0 characters #xA0 - #xD7FF, #xF900-#xFDCF, #xFDF0-#xFFEF
2985    // the Unicode plane 1-14 characters #x10000-#x1FFFD ... #xE0000-#xEFFFD
2986    //
2987    private String escapeHref(String href) {
2988        int len = href.length();
2989        int ch;
2990        final StringBuilder buffer = new StringBuilder(len*3);
2991
2992        // for each character in the href
2993        int i = 0;
2994        for (; i < len; i++) {
2995            ch = href.charAt(i);
2996            // if it's not an ASCII character (excluding 0x7F), break here, and use UTF-8 encoding
2997            if (ch > 0x7E) {
2998                break;
2999            }
3000            // abort: href does not allow this character
3001            if (ch < 0x20) {
3002                return href;
3003            }
3004            if (gNeedEscaping[ch]) {
3005                buffer.append('%');
3006                buffer.append(gAfterEscaping1[ch]);
3007                buffer.append(gAfterEscaping2[ch]);
3008            }
3009            else {
3010                buffer.append((char)ch);
3011            }
3012        }
3013
3014        // we saw some non-ascii character
3015        if (i < len) {
3016            // check if remainder of href contains any illegal characters before proceeding
3017            for (int j = i; j < len; ++j) {
3018                ch = href.charAt(j);
3019                if ((ch >= 0x20 && ch <= 0x7E) ||
3020                    (ch >= 0xA0 && ch <= 0xD7FF) ||
3021                    (ch >= 0xF900 && ch <= 0xFDCF) ||
3022                    (ch >= 0xFDF0 && ch <= 0xFFEF)) {
3023                    continue;
3024                }
3025                if (XMLChar.isHighSurrogate(ch) && ++j < len) {
3026                    int ch2 = href.charAt(j);
3027                    if (XMLChar.isLowSurrogate(ch2)) {
3028                        ch2 = XMLChar.supplemental((char)ch, (char)ch2);
3029                        if (ch2 < 0xF0000 && (ch2 & 0xFFFF) <= 0xFFFD) {
3030                            continue;
3031                        }
3032                    }
3033                }
3034                // abort: href does not allow this character
3035                return href;
3036            }
3037
3038            // get UTF-8 bytes for the remaining sub-string
3039            byte[] bytes = null;
3040            byte b;
3041            try {
3042                bytes = href.substring(i).getBytes("UTF-8");
3043            } catch (java.io.UnsupportedEncodingException e) {
3044                // should never happen
3045                return href;
3046            }
3047            len = bytes.length;
3048
3049            // for each byte
3050            for (i = 0; i < len; i++) {
3051                b = bytes[i];
3052                // for non-ascii character: make it positive, then escape
3053                if (b < 0) {
3054                    ch = b + 256;
3055                    buffer.append('%');
3056                    buffer.append(gHexChs[ch >> 4]);
3057                    buffer.append(gHexChs[ch & 0xf]);
3058                }
3059                else if (gNeedEscaping[b]) {
3060                    buffer.append('%');
3061                    buffer.append(gAfterEscaping1[b]);
3062                    buffer.append(gAfterEscaping2[b]);
3063                }
3064                else {
3065                    buffer.append((char)b);
3066                }
3067            }
3068        }
3069
3070        // If escaping happened, create a new string;
3071        // otherwise, return the orginal one.
3072        if (buffer.length() != len) {
3073            return buffer.toString();
3074        }
3075        else {
3076            return href;
3077        }
3078    }
3079}
3080