/* * Copyright (c) 2011-2013, Oracle and/or its affiliates. All rights reserved. * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. */ /* * Copyright 2003-2005 The Apache Software Foundation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.sun.org.apache.xerces.internal.xinclude; import java.io.CharConversionException; import java.io.IOException; import java.util.ArrayList; import java.util.Enumeration; import java.util.Locale; import java.util.Stack; import java.util.StringTokenizer; import javax.xml.XMLConstants; import com.sun.org.apache.xerces.internal.impl.Constants; import com.sun.org.apache.xerces.internal.impl.XMLEntityManager; import com.sun.org.apache.xerces.internal.impl.XMLErrorReporter; import com.sun.org.apache.xerces.internal.impl.io.MalformedByteSequenceException; import com.sun.org.apache.xerces.internal.impl.msg.XMLMessageFormatter; import com.sun.org.apache.xerces.internal.util.AugmentationsImpl; import com.sun.org.apache.xerces.internal.util.HTTPInputSource; import com.sun.org.apache.xerces.internal.util.IntStack; import com.sun.org.apache.xerces.internal.util.ParserConfigurationSettings; import com.sun.org.apache.xerces.internal.util.SymbolTable; import com.sun.org.apache.xerces.internal.util.URI; import com.sun.org.apache.xerces.internal.util.XMLAttributesImpl; import com.sun.org.apache.xerces.internal.util.XMLResourceIdentifierImpl; import com.sun.org.apache.xerces.internal.util.XMLChar; import com.sun.org.apache.xerces.internal.util.XMLSymbols; import com.sun.org.apache.xerces.internal.util.URI.MalformedURIException; import com.sun.org.apache.xerces.internal.utils.XMLSecurityManager; import com.sun.org.apache.xerces.internal.xni.Augmentations; import com.sun.org.apache.xerces.internal.xni.NamespaceContext; import com.sun.org.apache.xerces.internal.xni.QName; import com.sun.org.apache.xerces.internal.xni.XMLAttributes; import com.sun.org.apache.xerces.internal.xni.XMLDTDHandler; import com.sun.org.apache.xerces.internal.xni.XMLDocumentHandler; import com.sun.org.apache.xerces.internal.xni.XMLLocator; import com.sun.org.apache.xerces.internal.xni.XMLResourceIdentifier; import com.sun.org.apache.xerces.internal.xni.XMLString; import com.sun.org.apache.xerces.internal.xni.XNIException; import com.sun.org.apache.xerces.internal.xni.parser.XMLComponent; import com.sun.org.apache.xerces.internal.xni.parser.XMLComponentManager; import com.sun.org.apache.xerces.internal.xni.parser.XMLConfigurationException; import com.sun.org.apache.xerces.internal.xni.parser.XMLDTDFilter; import com.sun.org.apache.xerces.internal.xni.parser.XMLDTDSource; import com.sun.org.apache.xerces.internal.xni.parser.XMLDocumentFilter; import com.sun.org.apache.xerces.internal.xni.parser.XMLDocumentSource; import com.sun.org.apache.xerces.internal.xni.parser.XMLEntityResolver; import com.sun.org.apache.xerces.internal.xni.parser.XMLInputSource; import com.sun.org.apache.xerces.internal.xni.parser.XMLParserConfiguration; import com.sun.org.apache.xerces.internal.xpointer.XPointerHandler; import com.sun.org.apache.xerces.internal.xpointer.XPointerProcessor; import com.sun.org.apache.xerces.internal.utils.ObjectFactory; import com.sun.org.apache.xerces.internal.utils.Objects; import com.sun.org.apache.xerces.internal.utils.XMLSecurityPropertyManager; /** *
* This is a pipeline component which performs XInclude handling, according to the * W3C specification for XML Inclusions. *
*
* This component analyzes each event in the pipeline, looking for <include>
* elements. An <include> element is one which has a namespace of
* http://www.w3.org/2001/XInclude
and a localname of include
.
* When it finds an <include> element, it attempts to include the file specified
* in the href
attribute of the element. If inclusion succeeds, all
* children of the <include> element are ignored (with the exception of
* checking for invalid children as outlined in the specification). If the inclusion
* fails, the <fallback> child of the <include> element is processed.
*
* See the XInclude specification for * more information on how XInclude is to be used. *
** This component requires the following features and properties from the * component manager that uses it: *
NamespaceContext
used in the pipeline is required
* to be an instance of XIncludeNamespaceSupport
.
*
* * Currently, this implementation has only partial support for the XInclude specification. * Specifically, it is missing support for XPointer document fragments. Thus, only whole * documents can be included using this component in the pipeline. *
* * @author Peter McCracken, IBM * @author Michael Glavassevich, IBM * * @version $Id: XIncludeHandler.java,v 1.7 2010-11-01 04:40:18 joehw Exp $ * * @see XIncludeNamespaceSupport */ public class XIncludeHandler implements XMLComponent, XMLDocumentFilter, XMLDTDFilter { public final static String XINCLUDE_DEFAULT_CONFIGURATION = "com.sun.org.apache.xerces.internal.parsers.XIncludeParserConfiguration"; public final static String HTTP_ACCEPT = "Accept"; public final static String HTTP_ACCEPT_LANGUAGE = "Accept-Language"; public final static String XPOINTER = "xpointer"; public final static String XINCLUDE_NS_URI = "http://www.w3.org/2001/XInclude".intern(); public final static String XINCLUDE_INCLUDE = "include".intern(); public final static String XINCLUDE_FALLBACK = "fallback".intern(); public final static String XINCLUDE_PARSE_XML = "xml".intern(); public final static String XINCLUDE_PARSE_TEXT = "text".intern(); public final static String XINCLUDE_ATTR_HREF = "href".intern(); public final static String XINCLUDE_ATTR_PARSE = "parse".intern(); public final static String XINCLUDE_ATTR_ENCODING = "encoding".intern(); public final static String XINCLUDE_ATTR_ACCEPT = "accept".intern(); public final static String XINCLUDE_ATTR_ACCEPT_LANGUAGE = "accept-language".intern(); // Top Level Information Items have [included] property in infoset public final static String XINCLUDE_INCLUDED = "[included]".intern(); /** The identifier for the Augmentation that contains the current base URI */ public final static String CURRENT_BASE_URI = "currentBaseURI"; // used for adding [base URI] attributes public final static String XINCLUDE_BASE = "base".intern(); public final static QName XML_BASE_QNAME = new QName( XMLSymbols.PREFIX_XML, XINCLUDE_BASE, (XMLSymbols.PREFIX_XML + ":" + XINCLUDE_BASE).intern(), NamespaceContext.XML_URI); // used for adding [language] attributes public final static String XINCLUDE_LANG = "lang".intern(); public final static QName XML_LANG_QNAME = new QName( XMLSymbols.PREFIX_XML, XINCLUDE_LANG, (XMLSymbols.PREFIX_XML + ":" + XINCLUDE_LANG).intern(), NamespaceContext.XML_URI); public final static QName NEW_NS_ATTR_QNAME = new QName( XMLSymbols.PREFIX_XMLNS, "", XMLSymbols.PREFIX_XMLNS + ":", NamespaceContext.XMLNS_URI); // Processing States private final static int STATE_NORMAL_PROCESSING = 1; // we go into this state after a successful include (thus we ignore the children // of the include) or after a fallback private final static int STATE_IGNORE = 2; // we go into this state after a failed include. If we don't encounter a fallback // before we reach the end include tag, it's a fatal error private final static int STATE_EXPECT_FALLBACK = 3; // recognized features and properties /** Feature identifier: validation. */ protected static final String VALIDATION = Constants.SAX_FEATURE_PREFIX + Constants.VALIDATION_FEATURE; /** Feature identifier: schema validation. */ protected static final String SCHEMA_VALIDATION = Constants.XERCES_FEATURE_PREFIX + Constants.SCHEMA_VALIDATION_FEATURE; /** Feature identifier: dynamic validation. */ protected static final String DYNAMIC_VALIDATION = Constants.XERCES_FEATURE_PREFIX + Constants.DYNAMIC_VALIDATION_FEATURE; /** Feature identifier: allow notation and unparsed entity events to be sent out of order. */ protected static final String ALLOW_UE_AND_NOTATION_EVENTS = Constants.SAX_FEATURE_PREFIX + Constants.ALLOW_DTD_EVENTS_AFTER_ENDDTD_FEATURE; /** Feature identifier: fixup base URIs. */ protected static final String XINCLUDE_FIXUP_BASE_URIS = Constants.XERCES_FEATURE_PREFIX + Constants.XINCLUDE_FIXUP_BASE_URIS_FEATURE; /** Feature identifier: fixup language. */ protected static final String XINCLUDE_FIXUP_LANGUAGE = Constants.XERCES_FEATURE_PREFIX + Constants.XINCLUDE_FIXUP_LANGUAGE_FEATURE; /** Property identifier: symbol table. */ protected static final String SYMBOL_TABLE = Constants.XERCES_PROPERTY_PREFIX + Constants.SYMBOL_TABLE_PROPERTY; /** Property identifier: error reporter. */ protected static final String ERROR_REPORTER = Constants.XERCES_PROPERTY_PREFIX + Constants.ERROR_REPORTER_PROPERTY; /** Property identifier: entity resolver. */ protected static final String ENTITY_RESOLVER = Constants.XERCES_PROPERTY_PREFIX + Constants.ENTITY_RESOLVER_PROPERTY; /** property identifier: security manager. */ protected static final String SECURITY_MANAGER = Constants.XERCES_PROPERTY_PREFIX + Constants.SECURITY_MANAGER_PROPERTY; /** property identifier: buffer size. */ public static final String BUFFER_SIZE = Constants.XERCES_PROPERTY_PREFIX + Constants.BUFFER_SIZE_PROPERTY; protected static final String PARSER_SETTINGS = Constants.XERCES_FEATURE_PREFIX + Constants.PARSER_SETTINGS; /** property identifier: XML security property manager. */ protected static final String XML_SECURITY_PROPERTY_MANAGER = Constants.XML_SECURITY_PROPERTY_MANAGER; /** Recognized features. */ private static final String[] RECOGNIZED_FEATURES = { ALLOW_UE_AND_NOTATION_EVENTS, XINCLUDE_FIXUP_BASE_URIS, XINCLUDE_FIXUP_LANGUAGE }; /** Feature defaults. */ private static final Boolean[] FEATURE_DEFAULTS = { Boolean.TRUE, Boolean.TRUE, Boolean.TRUE }; /** Recognized properties. */ private static final String[] RECOGNIZED_PROPERTIES = { ERROR_REPORTER, ENTITY_RESOLVER, SECURITY_MANAGER, BUFFER_SIZE }; /** Property defaults. */ private static final Object[] PROPERTY_DEFAULTS = { null, null, null, new Integer(XMLEntityManager.DEFAULT_BUFFER_SIZE) }; // instance variables // for XMLDocumentFilter protected XMLDocumentHandler fDocumentHandler; protected XMLDocumentSource fDocumentSource; // for XMLDTDFilter protected XMLDTDHandler fDTDHandler; protected XMLDTDSource fDTDSource; // for XIncludeHandler protected XIncludeHandler fParentXIncludeHandler; // for buffer size in XIncludeTextReader protected int fBufferSize = XMLEntityManager.DEFAULT_BUFFER_SIZE; // It "feels wrong" to store this value here. However, // calculating it can be time consuming, so we cache it. // It's never going to change in the lifetime of this XIncludeHandler protected String fParentRelativeURI; // we cache the child parser configuration, so we don't have to re-create // the objects when the parser is re-used protected XMLParserConfiguration fChildConfig; // The cached child parser configuration, may contain a // XInclude or XPointer Handler. Cache both these protected XMLParserConfiguration fXIncludeChildConfig; protected XMLParserConfiguration fXPointerChildConfig; // The XPointerProcessor protected XPointerProcessor fXPtrProcessor = null; protected XMLLocator fDocLocation; protected XIncludeMessageFormatter fXIncludeMessageFormatter = new XIncludeMessageFormatter(); protected XIncludeNamespaceSupport fNamespaceContext; protected SymbolTable fSymbolTable; protected XMLErrorReporter fErrorReporter; protected XMLEntityResolver fEntityResolver; protected XMLSecurityManager fSecurityManager; protected XMLSecurityPropertyManager fSecurityPropertyMgr; // these are needed for text include processing protected XIncludeTextReader fXInclude10TextReader; protected XIncludeTextReader fXInclude11TextReader; // these are needed for XML Base processing protected XMLResourceIdentifier fCurrentBaseURI; protected IntStack fBaseURIScope; protected Stack fBaseURI; protected Stack fLiteralSystemID; protected Stack fExpandedSystemID; // these are needed for Language Fixup protected IntStack fLanguageScope; protected Stack fLanguageStack; protected String fCurrentLanguage; // used for passing features on to child XIncludeHandler objects protected ParserConfigurationSettings fSettings; // The current element depth. We start at depth 0 (before we've reached any elements). // The first element is at depth 1. private int fDepth; // The current element depth of the result infoset. private int fResultDepth; // this value must be at least 1 private static final int INITIAL_SIZE = 8; // Used to ensure that fallbacks are always children of include elements, // and that include elements are never children of other include elements. // An index contains true if the ancestor of the current element which resides // at that depth was an include element. private boolean[] fSawInclude = new boolean[INITIAL_SIZE]; // Ensures that only one fallback element can be at a single depth. // An index contains true if we have seen any fallback elements at that depth, // and it is only reset to false when the end tag of the parent is encountered. private boolean[] fSawFallback = new boolean[INITIAL_SIZE]; // The state of the processor at each given depth. private int[] fState = new int[INITIAL_SIZE]; // buffering the necessary DTD events private ArrayList fNotations; private ArrayList fUnparsedEntities; // flags which control whether base URI or language fixup is performed. private boolean fFixupBaseURIs = true; private boolean fFixupLanguage = true; // for SAX compatibility. // Has the value of the ALLOW_UE_AND_NOTATION_EVENTS feature private boolean fSendUEAndNotationEvents; // track the version of the document being parsed private boolean fIsXML11; // track whether a DTD is being parsed private boolean fInDTD; // track whether the root element of the result infoset has been processed private boolean fSeenRootElement; // track whether the child config needs its features refreshed private boolean fNeedCopyFeatures = true; // Constructors public XIncludeHandler() { fDepth = 0; fSawFallback[fDepth] = false; fSawInclude[fDepth] = false; fState[fDepth] = STATE_NORMAL_PROCESSING; fNotations = new ArrayList(); fUnparsedEntities = new ArrayList(); fBaseURIScope = new IntStack(); fBaseURI = new Stack(); fLiteralSystemID = new Stack(); fExpandedSystemID = new Stack(); fCurrentBaseURI = new XMLResourceIdentifierImpl(); fLanguageScope = new IntStack(); fLanguageStack = new Stack(); fCurrentLanguage = null; } // XMLComponent methods @Override public void reset(XMLComponentManager componentManager) throws XNIException { fNamespaceContext = null; fDepth = 0; fResultDepth = isRootDocument() ? 0 : fParentXIncludeHandler.getResultDepth(); fNotations.clear(); fUnparsedEntities.clear(); fParentRelativeURI = null; fIsXML11 = false; fInDTD = false; fSeenRootElement = false; fBaseURIScope.clear(); fBaseURI.clear(); fLiteralSystemID.clear(); fExpandedSystemID.clear(); fLanguageScope.clear(); fLanguageStack.clear(); // REVISIT: Find a better method for maintaining // the state of the XInclude processor. These arrays // can potentially grow quite large. Cleaning them // out on reset may be very time consuming. -- mrglavas // // clear the previous settings from the arrays for (int i = 0; i < fState.length; ++i) { fState[i] = STATE_NORMAL_PROCESSING; } for (int i = 0; i < fSawFallback.length; ++i) { fSawFallback[i] = false; } for (int i = 0; i < fSawInclude.length; ++i) { fSawInclude[i] = false; } try { if (!componentManager.getFeature(PARSER_SETTINGS)) { // if parser settings have not changed return. return; } } catch (XMLConfigurationException e) {} // parser settings changed. Need to refresh features on child config. fNeedCopyFeatures = true; try { fSendUEAndNotationEvents = componentManager.getFeature(ALLOW_UE_AND_NOTATION_EVENTS); if (fChildConfig != null) { fChildConfig.setFeature( ALLOW_UE_AND_NOTATION_EVENTS, fSendUEAndNotationEvents); } } catch (XMLConfigurationException e) { } try { fFixupBaseURIs = componentManager.getFeature(XINCLUDE_FIXUP_BASE_URIS); if (fChildConfig != null) { fChildConfig.setFeature( XINCLUDE_FIXUP_BASE_URIS, fFixupBaseURIs); } } catch (XMLConfigurationException e) { fFixupBaseURIs = true; } try { fFixupLanguage = componentManager.getFeature(XINCLUDE_FIXUP_LANGUAGE); if (fChildConfig != null) { fChildConfig.setFeature( XINCLUDE_FIXUP_LANGUAGE, fFixupLanguage); } } catch (XMLConfigurationException e) { fFixupLanguage = true; } // Get symbol table. try { SymbolTable value = (SymbolTable)componentManager.getProperty(SYMBOL_TABLE); if (value != null) { fSymbolTable = value; if (fChildConfig != null) { fChildConfig.setProperty(SYMBOL_TABLE, value); } } } catch (XMLConfigurationException e) { fSymbolTable = null; } // Get error reporter. try { XMLErrorReporter value = (XMLErrorReporter)componentManager.getProperty(ERROR_REPORTER); if (value != null) { setErrorReporter(value); if (fChildConfig != null) { fChildConfig.setProperty(ERROR_REPORTER, value); } } } catch (XMLConfigurationException e) { fErrorReporter = null; } // Get entity resolver. try { XMLEntityResolver value = (XMLEntityResolver)componentManager.getProperty( ENTITY_RESOLVER); if (value != null) { fEntityResolver = value; if (fChildConfig != null) { fChildConfig.setProperty(ENTITY_RESOLVER, value); } } } catch (XMLConfigurationException e) { fEntityResolver = null; } // Get security manager. try { XMLSecurityManager value = (XMLSecurityManager)componentManager.getProperty( SECURITY_MANAGER); if (value != null) { fSecurityManager = value; if (fChildConfig != null) { fChildConfig.setProperty(SECURITY_MANAGER, value); } } } catch (XMLConfigurationException e) { fSecurityManager = null; } fSecurityPropertyMgr = (XMLSecurityPropertyManager) componentManager.getProperty(Constants.XML_SECURITY_PROPERTY_MANAGER); // Get buffer size. try { Integer value = (Integer)componentManager.getProperty( BUFFER_SIZE); if (value != null && value.intValue() > 0) { fBufferSize = value.intValue(); if (fChildConfig != null) { fChildConfig.setProperty(BUFFER_SIZE, value); } } else { fBufferSize = ((Integer)getPropertyDefault(BUFFER_SIZE)).intValue(); } } catch (XMLConfigurationException e) { fBufferSize = ((Integer)getPropertyDefault(BUFFER_SIZE)).intValue(); } // Reset XML 1.0 text reader. if (fXInclude10TextReader != null) { fXInclude10TextReader.setBufferSize(fBufferSize); } // Reset XML 1.1 text reader. if (fXInclude11TextReader != null) { fXInclude11TextReader.setBufferSize(fBufferSize); } fSettings = new ParserConfigurationSettings(); copyFeatures(componentManager, fSettings); // We don't want a schema validator on the new pipeline, // so if it was enabled, we set the feature to false. If // the validation feature was also enabled we turn on // dynamic validation, so that DTD validation is performed // on the included documents only if they have a DOCTYPE. // This is consistent with the behaviour on the main pipeline. try { if (componentManager.getFeature(SCHEMA_VALIDATION)) { fSettings.setFeature(SCHEMA_VALIDATION, false); if (componentManager.getFeature(VALIDATION)) { fSettings.setFeature(DYNAMIC_VALIDATION, true); } } } catch (XMLConfigurationException e) {} // Don't reset fChildConfig -- we don't want it to share the same components. // It will be reset when it is actually used to parse something. } // reset(XMLComponentManager) /** * Returns a list of feature identifiers that are recognized by * this component. This method may return null if no features * are recognized by this component. */ @Override public String[] getRecognizedFeatures() { return (String[])(RECOGNIZED_FEATURES.clone()); } // getRecognizedFeatures():String[] /** * Sets the state of a feature. This method is called by the component * manager any time after reset when a feature changes state. ** Note: Components should silently ignore features * that do not affect the operation of the component. * * @param featureId The feature identifier. * @param state The state of the feature. * * @throws SAXNotRecognizedException The component should not throw * this exception. * @throws SAXNotSupportedException The component should not throw * this exception. */ @Override public void setFeature(String featureId, boolean state) throws XMLConfigurationException { if (featureId.equals(ALLOW_UE_AND_NOTATION_EVENTS)) { fSendUEAndNotationEvents = state; } if (fSettings != null) { fNeedCopyFeatures = true; fSettings.setFeature(featureId, state); } } // setFeature(String,boolean) /** * Returns a list of property identifiers that are recognized by * this component. This method may return null if no properties * are recognized by this component. */ @Override public String[] getRecognizedProperties() { return (String[])(RECOGNIZED_PROPERTIES.clone()); } // getRecognizedProperties():String[] /** * Sets the value of a property. This method is called by the component * manager any time after reset when a property changes value. *
* Note: Components should silently ignore properties * that do not affect the operation of the component. * * @param propertyId The property identifier. * @param value The value of the property. * * @throws SAXNotRecognizedException The component should not throw * this exception. * @throws SAXNotSupportedException The component should not throw * this exception. */ @Override public void setProperty(String propertyId, Object value) throws XMLConfigurationException { if (propertyId.equals(SYMBOL_TABLE)) { fSymbolTable = (SymbolTable)value; if (fChildConfig != null) { fChildConfig.setProperty(propertyId, value); } return; } if (propertyId.equals(ERROR_REPORTER)) { setErrorReporter((XMLErrorReporter)value); if (fChildConfig != null) { fChildConfig.setProperty(propertyId, value); } return; } if (propertyId.equals(ENTITY_RESOLVER)) { fEntityResolver = (XMLEntityResolver)value; if (fChildConfig != null) { fChildConfig.setProperty(propertyId, value); } return; } if (propertyId.equals(SECURITY_MANAGER)) { fSecurityManager = (XMLSecurityManager)value; if (fChildConfig != null) { fChildConfig.setProperty(propertyId, value); } return; } if (propertyId.equals(XML_SECURITY_PROPERTY_MANAGER)) { fSecurityPropertyMgr = (XMLSecurityPropertyManager)value; if (fChildConfig != null) { fChildConfig.setProperty(XML_SECURITY_PROPERTY_MANAGER, value); } return; } if (propertyId.equals(BUFFER_SIZE)) { Integer bufferSize = (Integer) value; if (fChildConfig != null) { fChildConfig.setProperty(propertyId, value); } if (bufferSize != null && bufferSize.intValue() > 0) { fBufferSize = bufferSize.intValue(); // Reset XML 1.0 text reader. if (fXInclude10TextReader != null) { fXInclude10TextReader.setBufferSize(fBufferSize); } // Reset XML 1.1 text reader. if (fXInclude11TextReader != null) { fXInclude11TextReader.setBufferSize(fBufferSize); } } return; } } // setProperty(String,Object) /** * Returns the default state for a feature, or null if this * component does not want to report a default value for this * feature. * * @param featureId The feature identifier. * * @since Xerces 2.2.0 */ @Override public Boolean getFeatureDefault(String featureId) { for (int i = 0; i < RECOGNIZED_FEATURES.length; i++) { if (RECOGNIZED_FEATURES[i].equals(featureId)) { return FEATURE_DEFAULTS[i]; } } return null; } // getFeatureDefault(String):Boolean /** * Returns the default state for a property, or null if this * component does not want to report a default value for this * property. * * @param propertyId The property identifier. * * @since Xerces 2.2.0 */ @Override public Object getPropertyDefault(String propertyId) { for (int i = 0; i < RECOGNIZED_PROPERTIES.length; i++) { if (RECOGNIZED_PROPERTIES[i].equals(propertyId)) { return PROPERTY_DEFAULTS[i]; } } return null; } // getPropertyDefault(String):Object @Override public void setDocumentHandler(XMLDocumentHandler handler) { fDocumentHandler = handler; } @Override public XMLDocumentHandler getDocumentHandler() { return fDocumentHandler; } // XMLDocumentHandler methods /** * Event sent at the start of the document. * * A fatal error will occur here, if it is detected that this document has been processed * before. * * This event is only passed on to the document handler if this is the root document. */ @Override public void startDocument( XMLLocator locator, String encoding, NamespaceContext namespaceContext, Augmentations augs) throws XNIException { // we do this to ensure that the proper location is reported in errors // otherwise, the locator from the root document would always be used fErrorReporter.setDocumentLocator(locator); if (!isRootDocument() && fParentXIncludeHandler.searchForRecursiveIncludes(locator)) { reportFatalError( "RecursiveInclude", new Object[] { locator.getExpandedSystemId()}); } if (!(namespaceContext instanceof XIncludeNamespaceSupport)) { reportFatalError("IncompatibleNamespaceContext"); } fNamespaceContext = (XIncludeNamespaceSupport)namespaceContext; fDocLocation = locator; // initialize the current base URI fCurrentBaseURI.setBaseSystemId(locator.getBaseSystemId()); fCurrentBaseURI.setExpandedSystemId(locator.getExpandedSystemId()); fCurrentBaseURI.setLiteralSystemId(locator.getLiteralSystemId()); saveBaseURI(); if (augs == null) { augs = new AugmentationsImpl(); } augs.putItem(CURRENT_BASE_URI, fCurrentBaseURI); // initialize the current language fCurrentLanguage = XMLSymbols.EMPTY_STRING; saveLanguage(fCurrentLanguage); if (isRootDocument() && fDocumentHandler != null) { fDocumentHandler.startDocument( locator, encoding, namespaceContext, augs); } } @Override public void xmlDecl( String version, String encoding, String standalone, Augmentations augs) throws XNIException { fIsXML11 = "1.1".equals(version); if (isRootDocument() && fDocumentHandler != null) { fDocumentHandler.xmlDecl(version, encoding, standalone, augs); } } @Override public void doctypeDecl( String rootElement, String publicId, String systemId, Augmentations augs) throws XNIException { if (isRootDocument() && fDocumentHandler != null) { fDocumentHandler.doctypeDecl(rootElement, publicId, systemId, augs); } } @Override public void comment(XMLString text, Augmentations augs) throws XNIException { if (!fInDTD) { if (fDocumentHandler != null && getState() == STATE_NORMAL_PROCESSING) { fDepth++; augs = modifyAugmentations(augs); fDocumentHandler.comment(text, augs); fDepth--; } } else if (fDTDHandler != null) { fDTDHandler.comment(text, augs); } } @Override public void processingInstruction( String target, XMLString data, Augmentations augs) throws XNIException { if (!fInDTD) { if (fDocumentHandler != null && getState() == STATE_NORMAL_PROCESSING) { // we need to change the depth like this so that modifyAugmentations() works fDepth++; augs = modifyAugmentations(augs); fDocumentHandler.processingInstruction(target, data, augs); fDepth--; } } else if (fDTDHandler != null) { fDTDHandler.processingInstruction(target, data, augs); } } @Override public void startElement( QName element, XMLAttributes attributes, Augmentations augs) throws XNIException { fDepth++; int lastState = getState(fDepth - 1); // If the last two states were fallback then this must be a descendant of an include // child which isn't a fallback. The specification says we should ignore such elements // and their children. if (lastState == STATE_EXPECT_FALLBACK && getState(fDepth - 2) == STATE_EXPECT_FALLBACK) { setState(STATE_IGNORE); } else { setState(lastState); } // we process the xml:base and xml:lang attributes regardless // of what type of element it is. processXMLBaseAttributes(attributes); if (fFixupLanguage) { processXMLLangAttributes(attributes); } if (isIncludeElement(element)) { boolean success = this.handleIncludeElement(attributes); if (success) { setState(STATE_IGNORE); } else { setState(STATE_EXPECT_FALLBACK); } } else if (isFallbackElement(element)) { this.handleFallbackElement(); } else if (hasXIncludeNamespace(element)) { if (getSawInclude(fDepth - 1)) { reportFatalError( "IncludeChild", new Object[] { element.rawname }); } if (getSawFallback(fDepth - 1)) { reportFatalError( "FallbackChild", new Object[] { element.rawname }); } if (getState() == STATE_NORMAL_PROCESSING) { if (fResultDepth++ == 0) { checkMultipleRootElements(); } if (fDocumentHandler != null) { augs = modifyAugmentations(augs); attributes = processAttributes(attributes); fDocumentHandler.startElement(element, attributes, augs); } } } else if (getState() == STATE_NORMAL_PROCESSING) { if (fResultDepth++ == 0) { checkMultipleRootElements(); } if (fDocumentHandler != null) { augs = modifyAugmentations(augs); attributes = processAttributes(attributes); fDocumentHandler.startElement(element, attributes, augs); } } } @Override public void emptyElement( QName element, XMLAttributes attributes, Augmentations augs) throws XNIException { fDepth++; int lastState = getState(fDepth - 1); // If the last two states were fallback then this must be a descendant of an include // child which isn't a fallback. The specification says we should ignore such elements // and their children. if (lastState == STATE_EXPECT_FALLBACK && getState(fDepth - 2) == STATE_EXPECT_FALLBACK) { setState(STATE_IGNORE); } else { setState(lastState); } // we process the xml:base and xml:lang attributes regardless // of what type of element it is. processXMLBaseAttributes(attributes); if (fFixupLanguage) { processXMLLangAttributes(attributes); } if (isIncludeElement(element)) { boolean success = this.handleIncludeElement(attributes); if (success) { setState(STATE_IGNORE); } else { reportFatalError("NoFallback", new Object[] { attributes.getValue(null, "href") }); } } else if (isFallbackElement(element)) { this.handleFallbackElement(); } else if (hasXIncludeNamespace(element)) { if (getSawInclude(fDepth - 1)) { reportFatalError( "IncludeChild", new Object[] { element.rawname }); } if (getSawFallback(fDepth - 1)) { reportFatalError( "FallbackChild", new Object[] { element.rawname }); } if (getState() == STATE_NORMAL_PROCESSING) { if (fResultDepth == 0) { checkMultipleRootElements(); } if (fDocumentHandler != null) { augs = modifyAugmentations(augs); attributes = processAttributes(attributes); fDocumentHandler.emptyElement(element, attributes, augs); } } } else if (getState() == STATE_NORMAL_PROCESSING) { if (fResultDepth == 0) { checkMultipleRootElements(); } if (fDocumentHandler != null) { augs = modifyAugmentations(augs); attributes = processAttributes(attributes); fDocumentHandler.emptyElement(element, attributes, augs); } } // reset the out of scope stack elements setSawFallback(fDepth + 1, false); setSawInclude(fDepth, false); // check if an xml:base has gone out of scope if (fBaseURIScope.size() > 0 && fDepth == fBaseURIScope.peek()) { // pop the values from the stack restoreBaseURI(); } fDepth--; } @Override public void endElement(QName element, Augmentations augs) throws XNIException { if (isIncludeElement(element)) { // if we're ending an include element, and we were expecting a fallback // we check to see if the children of this include element contained a fallback if (getState() == STATE_EXPECT_FALLBACK && !getSawFallback(fDepth + 1)) { reportFatalError("NoFallback", new Object[] { "unknown" }); } } if (isFallbackElement(element)) { // the state would have been set to normal processing if we were expecting the fallback element // now that we're done processing it, we should ignore all the other children of the include element if (getState() == STATE_NORMAL_PROCESSING) { setState(STATE_IGNORE); } } else if (getState() == STATE_NORMAL_PROCESSING) { --fResultDepth; if (fDocumentHandler != null) { fDocumentHandler.endElement(element, augs); } } // reset the out of scope stack elements setSawFallback(fDepth + 1, false); setSawInclude(fDepth, false); // check if an xml:base has gone out of scope if (fBaseURIScope.size() > 0 && fDepth == fBaseURIScope.peek()) { // pop the values from the stack restoreBaseURI(); } // check if an xml:lang has gone out of scope if (fLanguageScope.size() > 0 && fDepth == fLanguageScope.peek()) { // pop the language from the stack fCurrentLanguage = restoreLanguage(); } fDepth--; } @Override public void startGeneralEntity( String name, XMLResourceIdentifier resId, String encoding, Augmentations augs) throws XNIException { if (getState() == STATE_NORMAL_PROCESSING) { if (fResultDepth == 0) { if (augs != null && Boolean.TRUE.equals(augs.getItem(Constants.ENTITY_SKIPPED))) { reportFatalError("UnexpandedEntityReferenceIllegal"); } } else if (fDocumentHandler != null) { fDocumentHandler.startGeneralEntity(name, resId, encoding, augs); } } } @Override public void textDecl(String version, String encoding, Augmentations augs) throws XNIException { if (fDocumentHandler != null && getState() == STATE_NORMAL_PROCESSING) { fDocumentHandler.textDecl(version, encoding, augs); } } @Override public void endGeneralEntity(String name, Augmentations augs) throws XNIException { if (fDocumentHandler != null && getState() == STATE_NORMAL_PROCESSING && fResultDepth != 0) { fDocumentHandler.endGeneralEntity(name, augs); } } @Override public void characters(XMLString text, Augmentations augs) throws XNIException { if (getState() == STATE_NORMAL_PROCESSING) { if (fResultDepth == 0) { checkWhitespace(text); } else if (fDocumentHandler != null) { // we need to change the depth like this so that modifyAugmentations() works fDepth++; augs = modifyAugmentations(augs); fDocumentHandler.characters(text, augs); fDepth--; } } } @Override public void ignorableWhitespace(XMLString text, Augmentations augs) throws XNIException { if (fDocumentHandler != null && getState() == STATE_NORMAL_PROCESSING && fResultDepth != 0) { fDocumentHandler.ignorableWhitespace(text, augs); } } @Override public void startCDATA(Augmentations augs) throws XNIException { if (fDocumentHandler != null && getState() == STATE_NORMAL_PROCESSING && fResultDepth != 0) { fDocumentHandler.startCDATA(augs); } } @Override public void endCDATA(Augmentations augs) throws XNIException { if (fDocumentHandler != null && getState() == STATE_NORMAL_PROCESSING && fResultDepth != 0) { fDocumentHandler.endCDATA(augs); } } @Override public void endDocument(Augmentations augs) throws XNIException { if (isRootDocument()) { if (!fSeenRootElement) { reportFatalError("RootElementRequired"); } if (fDocumentHandler != null) { fDocumentHandler.endDocument(augs); } } } @Override public void setDocumentSource(XMLDocumentSource source) { fDocumentSource = source; } @Override public XMLDocumentSource getDocumentSource() { return fDocumentSource; } // DTDHandler methods // We are only interested in the notation and unparsed entity declarations, // the rest we just pass on /* (non-Javadoc) * @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) */ @Override public void attributeDecl( String elementName, String attributeName, String type, String[] enumeration, String defaultType, XMLString defaultValue, XMLString nonNormalizedDefaultValue, Augmentations augmentations) throws XNIException { if (fDTDHandler != null) { fDTDHandler.attributeDecl( elementName, attributeName, type, enumeration, defaultType, defaultValue, nonNormalizedDefaultValue, augmentations); } } /* (non-Javadoc) * @see com.sun.org.apache.xerces.internal.xni.XMLDTDHandler#elementDecl(java.lang.String, java.lang.String, com.sun.org.apache.xerces.internal.xni.Augmentations) */ @Override public void elementDecl( String name, String contentModel, Augmentations augmentations) throws XNIException { if (fDTDHandler != null) { fDTDHandler.elementDecl(name, contentModel, augmentations); } } /* (non-Javadoc) * @see com.sun.org.apache.xerces.internal.xni.XMLDTDHandler#endAttlist(com.sun.org.apache.xerces.internal.xni.Augmentations) */ @Override public void endAttlist(Augmentations augmentations) throws XNIException { if (fDTDHandler != null) { fDTDHandler.endAttlist(augmentations); } } /* (non-Javadoc) * @see com.sun.org.apache.xerces.internal.xni.XMLDTDHandler#endConditional(com.sun.org.apache.xerces.internal.xni.Augmentations) */ @Override public void endConditional(Augmentations augmentations) throws XNIException { if (fDTDHandler != null) { fDTDHandler.endConditional(augmentations); } } /* (non-Javadoc) * @see com.sun.org.apache.xerces.internal.xni.XMLDTDHandler#endDTD(com.sun.org.apache.xerces.internal.xni.Augmentations) */ @Override public void endDTD(Augmentations augmentations) throws XNIException { if (fDTDHandler != null) { fDTDHandler.endDTD(augmentations); } fInDTD = false; } /* (non-Javadoc) * @see com.sun.org.apache.xerces.internal.xni.XMLDTDHandler#endExternalSubset(com.sun.org.apache.xerces.internal.xni.Augmentations) */ @Override public void endExternalSubset(Augmentations augmentations) throws XNIException { if (fDTDHandler != null) { fDTDHandler.endExternalSubset(augmentations); } } /* (non-Javadoc) * @see com.sun.org.apache.xerces.internal.xni.XMLDTDHandler#endParameterEntity(java.lang.String, com.sun.org.apache.xerces.internal.xni.Augmentations) */ @Override public void endParameterEntity(String name, Augmentations augmentations) throws XNIException { if (fDTDHandler != null) { fDTDHandler.endParameterEntity(name, augmentations); } } /* (non-Javadoc) * @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) */ @Override public void externalEntityDecl( String name, XMLResourceIdentifier identifier, Augmentations augmentations) throws XNIException { if (fDTDHandler != null) { fDTDHandler.externalEntityDecl(name, identifier, augmentations); } } /* (non-Javadoc) * @see com.sun.org.apache.xerces.internal.xni.XMLDTDHandler#getDTDSource() */ @Override public XMLDTDSource getDTDSource() { return fDTDSource; } /* (non-Javadoc) * @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) */ @Override public void ignoredCharacters(XMLString text, Augmentations augmentations) throws XNIException { if (fDTDHandler != null) { fDTDHandler.ignoredCharacters(text, augmentations); } } /* (non-Javadoc) * @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) */ @Override public void internalEntityDecl( String name, XMLString text, XMLString nonNormalizedText, Augmentations augmentations) throws XNIException { if (fDTDHandler != null) { fDTDHandler.internalEntityDecl( name, text, nonNormalizedText, augmentations); } } /* (non-Javadoc) * @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) */ @Override public void notationDecl( String name, XMLResourceIdentifier identifier, Augmentations augmentations) throws XNIException { this.addNotation(name, identifier, augmentations); if (fDTDHandler != null) { fDTDHandler.notationDecl(name, identifier, augmentations); } } /* (non-Javadoc) * @see com.sun.org.apache.xerces.internal.xni.XMLDTDHandler#setDTDSource(com.sun.org.apache.xerces.internal.xni.parser.XMLDTDSource) */ @Override public void setDTDSource(XMLDTDSource source) { fDTDSource = source; } /* (non-Javadoc) * @see com.sun.org.apache.xerces.internal.xni.XMLDTDHandler#startAttlist(java.lang.String, com.sun.org.apache.xerces.internal.xni.Augmentations) */ @Override public void startAttlist(String elementName, Augmentations augmentations) throws XNIException { if (fDTDHandler != null) { fDTDHandler.startAttlist(elementName, augmentations); } } /* (non-Javadoc) * @see com.sun.org.apache.xerces.internal.xni.XMLDTDHandler#startConditional(short, com.sun.org.apache.xerces.internal.xni.Augmentations) */ @Override public void startConditional(short type, Augmentations augmentations) throws XNIException { if (fDTDHandler != null) { fDTDHandler.startConditional(type, augmentations); } } /* (non-Javadoc) * @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) */ @Override public void startDTD(XMLLocator locator, Augmentations augmentations) throws XNIException { fInDTD = true; if (fDTDHandler != null) { fDTDHandler.startDTD(locator, augmentations); } } /* (non-Javadoc) * @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) */ @Override public void startExternalSubset( XMLResourceIdentifier identifier, Augmentations augmentations) throws XNIException { if (fDTDHandler != null) { fDTDHandler.startExternalSubset(identifier, augmentations); } } /* (non-Javadoc) * @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) */ @Override public void startParameterEntity( String name, XMLResourceIdentifier identifier, String encoding, Augmentations augmentations) throws XNIException { if (fDTDHandler != null) { fDTDHandler.startParameterEntity( name, identifier, encoding, augmentations); } } /* (non-Javadoc) * @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) */ @Override public void unparsedEntityDecl( String name, XMLResourceIdentifier identifier, String notation, Augmentations augmentations) throws XNIException { this.addUnparsedEntity(name, identifier, notation, augmentations); if (fDTDHandler != null) { fDTDHandler.unparsedEntityDecl( name, identifier, notation, augmentations); } } /* (non-Javadoc) * @see com.sun.org.apache.xerces.internal.xni.parser.XMLDTDSource#getDTDHandler() */ @Override public XMLDTDHandler getDTDHandler() { return fDTDHandler; } /* (non-Javadoc) * @see com.sun.org.apache.xerces.internal.xni.parser.XMLDTDSource#setDTDHandler(com.sun.org.apache.xerces.internal.xni.XMLDTDHandler) */ @Override public void setDTDHandler(XMLDTDHandler handler) { fDTDHandler = handler; } // XIncludeHandler methods private void setErrorReporter(XMLErrorReporter reporter) { fErrorReporter = reporter; if (fErrorReporter != null) { fErrorReporter.putMessageFormatter( XIncludeMessageFormatter.XINCLUDE_DOMAIN, fXIncludeMessageFormatter); // this ensures the proper location is displayed in error messages if (fDocLocation != null) { fErrorReporter.setDocumentLocator(fDocLocation); } } } protected void handleFallbackElement() { if (!getSawInclude(fDepth - 1)) { if (getState() == STATE_IGNORE) { return; } reportFatalError("FallbackParent"); } setSawInclude(fDepth, false); fNamespaceContext.setContextInvalid(); if (getSawFallback(fDepth)) { reportFatalError("MultipleFallbacks"); } else { setSawFallback(fDepth, true); } // Either the state is STATE_EXPECT_FALLBACK or it's STATE_IGNORE. // If we're ignoring, we want to stay ignoring. But if we're expecting this fallback element, // we want to signal that we should process the children. if (getState() == STATE_EXPECT_FALLBACK) { setState(STATE_NORMAL_PROCESSING); } } protected boolean handleIncludeElement(XMLAttributes attributes) throws XNIException { if (getSawInclude(fDepth - 1)) { reportFatalError("IncludeChild", new Object[] { XINCLUDE_INCLUDE }); } if (getState() == STATE_IGNORE) { return true; } setSawInclude(fDepth, true); fNamespaceContext.setContextInvalid(); // TODO: does Java use IURIs by default? // [Definition: An internationalized URI reference, or IURI, is a URI reference that directly uses [Unicode] characters.] // TODO: figure out what section 4.1.1 of the XInclude spec is talking about // has to do with disallowed ASCII character escaping // this ties in with the above IURI section, but I suspect Java already does it String href = attributes.getValue(XINCLUDE_ATTR_HREF); String parse = attributes.getValue(XINCLUDE_ATTR_PARSE); String xpointer = attributes.getValue(XPOINTER); String accept = attributes.getValue(XINCLUDE_ATTR_ACCEPT); String acceptLanguage = attributes.getValue(XINCLUDE_ATTR_ACCEPT_LANGUAGE); if (parse == null) { parse = XINCLUDE_PARSE_XML; } if (href == null) { href = XMLSymbols.EMPTY_STRING; } if (href.length() == 0 && XINCLUDE_PARSE_XML.equals(parse)) { if (xpointer == null) { reportFatalError("XpointerMissing"); } else { // When parse="xml" and an xpointer is specified treat // all absences of the href attribute as a resource error. Locale locale = (fErrorReporter != null) ? fErrorReporter.getLocale() : null; String reason = fXIncludeMessageFormatter.formatMessage(locale, "XPointerStreamability", null); reportResourceError("XMLResourceError", new Object[] { href, reason }); return false; } } URI hrefURI = null; // Check whether href is correct and perform escaping as per section 4.1.1 of the XInclude spec. // Report fatal error if the href value contains a fragment identifier or if the value after // escaping is a syntactically invalid URI or IRI. try { hrefURI = new URI(href, true); if (hrefURI.getFragment() != null) { reportFatalError("HrefFragmentIdentifierIllegal", new Object[] {href}); } } catch (URI.MalformedURIException exc) { String newHref = escapeHref(href); if (href != newHref) { href = newHref; try { hrefURI = new URI(href, true); if (hrefURI.getFragment() != null) { reportFatalError("HrefFragmentIdentifierIllegal", new Object[] {href}); } } catch (URI.MalformedURIException exc2) { reportFatalError("HrefSyntacticallyInvalid", new Object[] {href}); } } else { reportFatalError("HrefSyntacticallyInvalid", new Object[] {href}); } } // Verify that if an accept and/or an accept-language attribute exist // that the value(s) don't contain disallowed characters. if (accept != null && !isValidInHTTPHeader(accept)) { reportFatalError("AcceptMalformed", null); accept = null; } if (acceptLanguage != null && !isValidInHTTPHeader(acceptLanguage)) { reportFatalError("AcceptLanguageMalformed", null); acceptLanguage = null; } XMLInputSource includedSource = null; if (fEntityResolver != null) { try { XMLResourceIdentifier resourceIdentifier = new XMLResourceIdentifierImpl( null, href, fCurrentBaseURI.getExpandedSystemId(), XMLEntityManager.expandSystemId( href, fCurrentBaseURI.getExpandedSystemId(), false)); includedSource = fEntityResolver.resolveEntity(resourceIdentifier); if (includedSource != null && !(includedSource instanceof HTTPInputSource) && (accept != null || acceptLanguage != null) && includedSource.getCharacterStream() == null && includedSource.getByteStream() == null) { includedSource = createInputSource(includedSource.getPublicId(), includedSource.getSystemId(), includedSource.getBaseSystemId(), accept, acceptLanguage); } } catch (IOException e) { reportResourceError( "XMLResourceError", new Object[] { href, e.getMessage()}); return false; } } if (includedSource == null) { // setup an HTTPInputSource if either of the content negotation attributes were specified. if (accept != null || acceptLanguage != null) { includedSource = createInputSource(null, href, fCurrentBaseURI.getExpandedSystemId(), accept, acceptLanguage); } else { includedSource = new XMLInputSource(null, href, fCurrentBaseURI.getExpandedSystemId()); } } if (parse.equals(XINCLUDE_PARSE_XML)) { // Instead of always creating a new configuration, the first one can be reused if ((xpointer != null && fXPointerChildConfig == null) || (xpointer == null && fXIncludeChildConfig == null) ) { String parserName = XINCLUDE_DEFAULT_CONFIGURATION; if (xpointer != null) parserName = "com.sun.org.apache.xerces.internal.parsers.XPointerParserConfiguration"; fChildConfig = (XMLParserConfiguration)ObjectFactory.newInstance( parserName, true); // use the same symbol table, error reporter, entity resolver, security manager and buffer size. if (fSymbolTable != null) fChildConfig.setProperty(SYMBOL_TABLE, fSymbolTable); if (fErrorReporter != null) fChildConfig.setProperty(ERROR_REPORTER, fErrorReporter); if (fEntityResolver != null) fChildConfig.setProperty(ENTITY_RESOLVER, fEntityResolver); fChildConfig.setProperty(SECURITY_MANAGER, fSecurityManager); fChildConfig.setProperty(XML_SECURITY_PROPERTY_MANAGER, fSecurityPropertyMgr); fChildConfig.setProperty(BUFFER_SIZE, new Integer(fBufferSize)); // features must be copied to child configuration fNeedCopyFeatures = true; // use the same namespace context fChildConfig.setProperty( Constants.XERCES_PROPERTY_PREFIX + Constants.NAMESPACE_CONTEXT_PROPERTY, fNamespaceContext); fChildConfig.setFeature( XINCLUDE_FIXUP_BASE_URIS, fFixupBaseURIs); fChildConfig.setFeature( XINCLUDE_FIXUP_LANGUAGE, fFixupLanguage); // If the xpointer attribute is present if (xpointer != null ) { XPointerHandler newHandler = (XPointerHandler)fChildConfig.getProperty( Constants.XERCES_PROPERTY_PREFIX + Constants.XPOINTER_HANDLER_PROPERTY); fXPtrProcessor = newHandler; // ??? ((XPointerHandler)fXPtrProcessor).setProperty( Constants.XERCES_PROPERTY_PREFIX + Constants.NAMESPACE_CONTEXT_PROPERTY, fNamespaceContext); ((XPointerHandler)fXPtrProcessor).setProperty(XINCLUDE_FIXUP_BASE_URIS, fFixupBaseURIs); ((XPointerHandler)fXPtrProcessor).setProperty( XINCLUDE_FIXUP_LANGUAGE, fFixupLanguage); if (fErrorReporter != null) ((XPointerHandler)fXPtrProcessor).setProperty(ERROR_REPORTER, fErrorReporter); // ??? newHandler.setParent(this); newHandler.setDocumentHandler(this.getDocumentHandler()); fXPointerChildConfig = fChildConfig; } else { XIncludeHandler newHandler = (XIncludeHandler)fChildConfig.getProperty( Constants.XERCES_PROPERTY_PREFIX + Constants.XINCLUDE_HANDLER_PROPERTY); newHandler.setParent(this); newHandler.setDocumentHandler(this.getDocumentHandler()); fXIncludeChildConfig = fChildConfig; } } // If an xpointer attribute is present if (xpointer != null ) { fChildConfig = fXPointerChildConfig ; // Parse the XPointer expression try { ((XPointerProcessor)fXPtrProcessor).parseXPointer(xpointer); } catch (XNIException ex) { // report the XPointer error as a resource error reportResourceError( "XMLResourceError", new Object[] { href, ex.getMessage()}); return false; } } else { fChildConfig = fXIncludeChildConfig; } // set all features on parserConfig to match this parser configuration if (fNeedCopyFeatures) { copyFeatures(fSettings, fChildConfig); } fNeedCopyFeatures = false; try { fNamespaceContext.pushScope(); fChildConfig.parse(includedSource); // necessary to make sure proper location is reported in errors if (fErrorReporter != null) { fErrorReporter.setDocumentLocator(fDocLocation); } // If the xpointer attribute is present if (xpointer != null ) { // and it was not resolved if (!((XPointerProcessor)fXPtrProcessor).isXPointerResolved()) { Locale locale = (fErrorReporter != null) ? fErrorReporter.getLocale() : null; String reason = fXIncludeMessageFormatter.formatMessage(locale, "XPointerResolutionUnsuccessful", null); reportResourceError("XMLResourceError", new Object[] {href, reason}); // use the fallback return false; } } } catch (XNIException e) { // necessary to make sure proper location is reported in errors if (fErrorReporter != null) { fErrorReporter.setDocumentLocator(fDocLocation); } reportFatalError("XMLParseError", new Object[] { href, e.getMessage() }); } catch (IOException e) { // necessary to make sure proper location is reported in errors if (fErrorReporter != null) { fErrorReporter.setDocumentLocator(fDocLocation); } // An IOException indicates that we had trouble reading the file, not // that it was an invalid XML file. So we send a resource error, not a // fatal error. reportResourceError( "XMLResourceError", new Object[] { href, e.getMessage()}); return false; } finally { fNamespaceContext.popScope(); } } else if (parse.equals(XINCLUDE_PARSE_TEXT)) { // we only care about encoding for parse="text" String encoding = attributes.getValue(XINCLUDE_ATTR_ENCODING); includedSource.setEncoding(encoding); XIncludeTextReader textReader = null; try { // Setup the appropriate text reader. if (!fIsXML11) { if (fXInclude10TextReader == null) { fXInclude10TextReader = new XIncludeTextReader(includedSource, this, fBufferSize); } else { fXInclude10TextReader.setInputSource(includedSource); } textReader = fXInclude10TextReader; } else { if (fXInclude11TextReader == null) { fXInclude11TextReader = new XInclude11TextReader(includedSource, this, fBufferSize); } else { fXInclude11TextReader.setInputSource(includedSource); } textReader = fXInclude11TextReader; } textReader.setErrorReporter(fErrorReporter); textReader.parse(); } // encoding errors catch (MalformedByteSequenceException ex) { fErrorReporter.reportError(ex.getDomain(), ex.getKey(), ex.getArguments(), XMLErrorReporter.SEVERITY_FATAL_ERROR); } catch (CharConversionException e) { fErrorReporter.reportError(XMLMessageFormatter.XML_DOMAIN, "CharConversionFailure", null, XMLErrorReporter.SEVERITY_FATAL_ERROR); } catch (IOException e) { reportResourceError( "TextResourceError", new Object[] { href, e.getMessage()}); return false; } finally { if (textReader != null) { try { textReader.close(); } catch (IOException e) { reportResourceError( "TextResourceError", new Object[] { href, e.getMessage()}); return false; } } } } else { reportFatalError("InvalidParseValue", new Object[] { parse }); } return true; } /** * Returns true if the element has the namespace "http://www.w3.org/2001/XInclude" * @param element the element to check * @return true if the element has the namespace "http://www.w3.org/2001/XInclude" */ protected boolean hasXIncludeNamespace(QName element) { // REVISIT: The namespace of this element should be bound // already. Why are we looking it up from the namespace // context? -- mrglavas return element.uri == XINCLUDE_NS_URI || fNamespaceContext.getURI(element.prefix) == XINCLUDE_NS_URI; } /** * Checks if the element is an <include> element. The element must have * the XInclude namespace, and a local name of "include". * * @param element the element to check * @return true if the element is an <include> element * @see #hasXIncludeNamespace(QName) */ protected boolean isIncludeElement(QName element) { return element.localpart.equals(XINCLUDE_INCLUDE) && hasXIncludeNamespace(element); } /** * Checks if the element is an <fallback> element. The element must have * the XInclude namespace, and a local name of "fallback". * * @param element the element to check * @return true if the element is an <fallback; element * @see #hasXIncludeNamespace(QName) */ protected boolean isFallbackElement(QName element) { return element.localpart.equals(XINCLUDE_FALLBACK) && hasXIncludeNamespace(element); } /** * Returns true if the current [base URI] is the same as the [base URI] that * was in effect on the include parent. This method should only be called * when the current element is a top level included element, i.e. the direct child * of a fallback element, or the root elements in an included document. * The "include parent" is the element which, in the result infoset, will be the * direct parent of the current element. * @return true if the [base URIs] are the same string */ protected boolean sameBaseURIAsIncludeParent() { String parentBaseURI = getIncludeParentBaseURI(); String baseURI = fCurrentBaseURI.getExpandedSystemId(); // REVISIT: should we use File#sameFile() ? // I think the benefit of using it is that it resolves host names // instead of just doing a string comparison. // TODO: [base URI] is still an open issue with the working group. // They're deciding if xml:base should be added if the [base URI] is different in terms // of resolving relative references, or if it should be added if they are different at all. // Revisit this after a final decision has been made. // The decision also affects whether we output the file name of the URI, or just the path. return parentBaseURI != null && parentBaseURI.equals(baseURI); } /** * Returns true if the current [language] is equivalent to the [language] that * was in effect on the include parent, taking case-insensitivity into account * as per [RFC 3066]. This method should only be called when the * current element is a top level included element, i.e. the direct child * of a fallback element, or the root elements in an included document. * The "include parent" is the element which, in the result infoset, will be the * direct parent of the current element. * * @return true if the [language] properties have the same value * taking case-insensitivity into account as per [RFC 3066]. */ protected boolean sameLanguageAsIncludeParent() { String parentLanguage = getIncludeParentLanguage(); return parentLanguage != null && parentLanguage.equalsIgnoreCase(fCurrentLanguage); } /** * Checks if the file indicated by the given XMLLocator has already been included * in the current stack. * @param includedSource the source to check for inclusion * @return true if the source has already been included */ protected boolean searchForRecursiveIncludes(XMLLocator includedSource) { String includedSystemId = includedSource.getExpandedSystemId(); if (includedSystemId == null) { try { includedSystemId = XMLEntityManager.expandSystemId( includedSource.getLiteralSystemId(), includedSource.getBaseSystemId(), false); } catch (MalformedURIException e) { reportFatalError("ExpandedSystemId"); } } if (includedSystemId.equals(fCurrentBaseURI.getExpandedSystemId())) { return true; } if (fParentXIncludeHandler == null) { return false; } return fParentXIncludeHandler.searchForRecursiveIncludes( includedSource); } /** * Returns true if the current element is a top level included item. This means * it's either the child of a fallback element, or the top level item in an * included document * @return true if the current element is a top level included item */ protected boolean isTopLevelIncludedItem() { return isTopLevelIncludedItemViaInclude() || isTopLevelIncludedItemViaFallback(); } protected boolean isTopLevelIncludedItemViaInclude() { return fDepth == 1 && !isRootDocument(); } protected boolean isTopLevelIncludedItemViaFallback() { // Technically, this doesn't check if the parent was a fallback, it also // would return true if any of the parent's sibling elements were fallbacks. // However, this doesn't matter, since we will always be ignoring elements // whose parent's siblings were fallbacks. return getSawFallback(fDepth - 1); } /** * Processes the XMLAttributes object of startElement() calls. Performs the following tasks: *
force
* is true, or if the current element is a top level included item.
* @param augs the Augmentations to modify.
* @param force whether to force modification
* @return the modified Augmentations
*/
protected Augmentations modifyAugmentations(
Augmentations augs,
boolean force) {
if (force || isTopLevelIncludedItem()) {
if (augs == null) {
augs = new AugmentationsImpl();
}
augs.putItem(XINCLUDE_INCLUDED, Boolean.TRUE);
}
return augs;
}
protected int getState(int depth) {
return fState[depth];
}
protected int getState() {
return fState[fDepth];
}
protected void setState(int state) {
if (fDepth >= fState.length) {
int[] newarray = new int[fDepth * 2];
System.arraycopy(fState, 0, newarray, 0, fState.length);
fState = newarray;
}
fState[fDepth] = state;
}
/**
* Records that an <fallback> was encountered at the specified depth,
* as an ancestor of the current element, or as a sibling of an ancestor of the
* current element.
*
* @param depth
* @param val
*/
protected void setSawFallback(int depth, boolean val) {
if (depth >= fSawFallback.length) {
boolean[] newarray = new boolean[depth * 2];
System.arraycopy(fSawFallback, 0, newarray, 0, fSawFallback.length);
fSawFallback = newarray;
}
fSawFallback[depth] = val;
}
/**
* Returns whether an <fallback> was encountered at the specified depth,
* as an ancestor of the current element, or as a sibling of an ancestor of the
* current element.
*
* @param depth
*/
protected boolean getSawFallback(int depth) {
if (depth >= fSawFallback.length) {
return false;
}
return fSawFallback[depth];
}
/**
* Records that an <include> was encountered at the specified depth,
* as an ancestor of the current item.
*
* @param depth
* @param val
*/
protected void setSawInclude(int depth, boolean val) {
if (depth >= fSawInclude.length) {
boolean[] newarray = new boolean[depth * 2];
System.arraycopy(fSawInclude, 0, newarray, 0, fSawInclude.length);
fSawInclude = newarray;
}
fSawInclude[depth] = val;
}
/**
* Return whether an <include> was encountered at the specified depth,
* as an ancestor of the current item.
*
* @param depth
* @return
*/
protected boolean getSawInclude(int depth) {
if (depth >= fSawInclude.length) {
return false;
}
return fSawInclude[depth];
}
protected void reportResourceError(String key) {
this.reportFatalError(key, null);
}
protected void reportResourceError(String key, Object[] args) {
this.reportError(key, args, XMLErrorReporter.SEVERITY_WARNING);
}
protected void reportFatalError(String key) {
this.reportFatalError(key, null);
}
protected void reportFatalError(String key, Object[] args) {
this.reportError(key, args, XMLErrorReporter.SEVERITY_FATAL_ERROR);
}
private void reportError(String key, Object[] args, short severity) {
if (fErrorReporter != null) {
fErrorReporter.reportError(
XIncludeMessageFormatter.XINCLUDE_DOMAIN,
key,
args,
severity);
}
// we won't worry about when error reporter is null, since there should always be
// at least the default error reporter
}
/**
* Set the parent of this XIncludeHandler in the tree
* @param parent
*/
protected void setParent(XIncludeHandler parent) {
fParentXIncludeHandler = parent;
}
// used to know whether to pass declarations to the document handler
protected boolean isRootDocument() {
return fParentXIncludeHandler == null;
}
/**
* Caches an unparsed entity.
* @param name the name of the unparsed entity
* @param identifier the location of the unparsed entity
* @param augmentations any Augmentations that were on the original unparsed entity declaration
*/
protected void addUnparsedEntity(
String name,
XMLResourceIdentifier identifier,
String notation,
Augmentations augmentations) {
UnparsedEntity ent = new UnparsedEntity();
ent.name = name;
ent.systemId = identifier.getLiteralSystemId();
ent.publicId = identifier.getPublicId();
ent.baseURI = identifier.getBaseSystemId();
ent.expandedSystemId = identifier.getExpandedSystemId();
ent.notation = notation;
ent.augmentations = augmentations;
fUnparsedEntities.add(ent);
}
/**
* Caches a notation.
* @param name the name of the notation
* @param identifier the location of the notation
* @param augmentations any Augmentations that were on the original notation declaration
*/
protected void addNotation(
String name,
XMLResourceIdentifier identifier,
Augmentations augmentations) {
Notation not = new Notation();
not.name = name;
not.systemId = identifier.getLiteralSystemId();
not.publicId = identifier.getPublicId();
not.baseURI = identifier.getBaseSystemId();
not.expandedSystemId = identifier.getExpandedSystemId();
not.augmentations = augmentations;
fNotations.add(not);
}
/**
* Checks if an UnparsedEntity with the given name was declared in the DTD of the document
* for the current pipeline. If so, then the notation for the UnparsedEntity is checked.
* If that turns out okay, then the UnparsedEntity is passed to the root pipeline to
* be checked for conflicts, and sent to the root DTDHandler.
*
* @param entName the name of the UnparsedEntity to check
*/
protected void checkUnparsedEntity(String entName) {
UnparsedEntity ent = new UnparsedEntity();
ent.name = entName;
int index = fUnparsedEntities.indexOf(ent);
if (index != -1) {
ent = (UnparsedEntity)fUnparsedEntities.get(index);
// first check the notation of the unparsed entity
checkNotation(ent.notation);
checkAndSendUnparsedEntity(ent);
}
}
/**
* Checks if a Notation with the given name was declared in the DTD of the document
* for the current pipeline. If so, that Notation is passed to the root pipeline to
* be checked for conflicts, and sent to the root DTDHandler
*
* @param notName the name of the Notation to check
*/
protected void checkNotation(String notName) {
Notation not = new Notation();
not.name = notName;
int index = fNotations.indexOf(not);
if (index != -1) {
not = (Notation)fNotations.get(index);
checkAndSendNotation(not);
}
}
/**
* The purpose of this method is to check if an UnparsedEntity conflicts with a previously
* declared entity in the current pipeline stack. If there is no conflict, the
* UnparsedEntity is sent by the root pipeline.
*
* @param ent the UnparsedEntity to check for conflicts
*/
protected void checkAndSendUnparsedEntity(UnparsedEntity ent) {
if (isRootDocument()) {
int index = fUnparsedEntities.indexOf(ent);
if (index == -1) {
// There is no unparsed entity with the same name that we have sent.
// Calling unparsedEntityDecl() will add the entity to our local store,
// and also send the unparsed entity to the DTDHandler
XMLResourceIdentifier id =
new XMLResourceIdentifierImpl(
ent.publicId,
ent.systemId,
ent.baseURI,
ent.expandedSystemId);
addUnparsedEntity(
ent.name,
id,
ent.notation,
ent.augmentations);
if (fSendUEAndNotationEvents && fDTDHandler != null) {
fDTDHandler.unparsedEntityDecl(
ent.name,
id,
ent.notation,
ent.augmentations);
}
}
else {
UnparsedEntity localEntity =
(UnparsedEntity)fUnparsedEntities.get(index);
if (!ent.isDuplicate(localEntity)) {
reportFatalError(
"NonDuplicateUnparsedEntity",
new Object[] { ent.name });
}
}
}
else {
fParentXIncludeHandler.checkAndSendUnparsedEntity(ent);
}
}
/**
* The purpose of this method is to check if a Notation conflicts with a previously
* declared notation in the current pipeline stack. If there is no conflict, the
* Notation is sent by the root pipeline.
*
* @param not the Notation to check for conflicts
*/
protected void checkAndSendNotation(Notation not) {
if (isRootDocument()) {
int index = fNotations.indexOf(not);
if (index == -1) {
// There is no notation with the same name that we have sent.
XMLResourceIdentifier id =
new XMLResourceIdentifierImpl(
not.publicId,
not.systemId,
not.baseURI,
not.expandedSystemId);
addNotation(not.name, id, not.augmentations);
if (fSendUEAndNotationEvents && fDTDHandler != null) {
fDTDHandler.notationDecl(not.name, id, not.augmentations);
}
}
else {
Notation localNotation = (Notation)fNotations.get(index);
if (!not.isDuplicate(localNotation)) {
reportFatalError(
"NonDuplicateNotation",
new Object[] { not.name });
}
}
}
else {
fParentXIncludeHandler.checkAndSendNotation(not);
}
}
/**
* Checks whether the string only contains white space characters.
*
* @param value the text to check
*/
private void checkWhitespace(XMLString value) {
int end = value.offset + value.length;
for (int i = value.offset; i < end; ++i) {
if (!XMLChar.isSpace(value.ch[i])) {
reportFatalError("ContentIllegalAtTopLevel");
return;
}
}
}
/**
* Checks whether the root element has already been processed.
*/
private void checkMultipleRootElements() {
if (getRootElementProcessed()) {
reportFatalError("MultipleRootElements");
}
setRootElementProcessed(true);
}
/**
* Sets whether the root element has been processed.
*/
private void setRootElementProcessed(boolean seenRoot) {
if (isRootDocument()) {
fSeenRootElement = seenRoot;
return;
}
fParentXIncludeHandler.setRootElementProcessed(seenRoot);
}
/**
* Returns whether the root element has been processed.
*/
private boolean getRootElementProcessed() {
return isRootDocument() ? fSeenRootElement : fParentXIncludeHandler.getRootElementProcessed();
}
// It would be nice if we didn't have to repeat code like this, but there's no interface that has
// setFeature() and addRecognizedFeatures() that the objects have in common.
protected void copyFeatures(
XMLComponentManager from,
ParserConfigurationSettings to) {
Enumeration features = Constants.getXercesFeatures();
copyFeatures1(features, Constants.XERCES_FEATURE_PREFIX, from, to);
features = Constants.getSAXFeatures();
copyFeatures1(features, Constants.SAX_FEATURE_PREFIX, from, to);
}
protected void copyFeatures(
XMLComponentManager from,
XMLParserConfiguration to) {
Enumeration features = Constants.getXercesFeatures();
copyFeatures1(features, Constants.XERCES_FEATURE_PREFIX, from, to);
features = Constants.getSAXFeatures();
copyFeatures1(features, Constants.SAX_FEATURE_PREFIX, from, to);
}
private void copyFeatures1(
Enumeration features,
String featurePrefix,
XMLComponentManager from,
ParserConfigurationSettings to) {
while (features.hasMoreElements()) {
String featureId = featurePrefix + (String)features.nextElement();
to.addRecognizedFeatures(new String[] { featureId });
try {
to.setFeature(featureId, from.getFeature(featureId));
}
catch (XMLConfigurationException e) {
// componentManager doesn't support this feature,
// so we won't worry about it
}
}
}
private void copyFeatures1(
Enumeration features,
String featurePrefix,
XMLComponentManager from,
XMLParserConfiguration to) {
while (features.hasMoreElements()) {
String featureId = featurePrefix + (String)features.nextElement();
boolean value = from.getFeature(featureId);
try {
to.setFeature(featureId, value);
}
catch (XMLConfigurationException e) {
// componentManager doesn't support this feature,
// so we won't worry about it
}
}
}
// This is a storage class to hold information about the notations.
// We're not using XMLNotationDecl because we don't want to lose the augmentations.
protected static class Notation {
public String name;
public String systemId;
public String baseURI;
public String publicId;
public String expandedSystemId;
public Augmentations augmentations;
// equals() returns true if two Notations have the same name.
// Useful for searching Vectors for notations with the same name
@Override
public boolean equals(Object obj) {
return obj == this || obj instanceof Notation
&& Objects.equals(name, ((Notation)obj).name);
}
@Override
public int hashCode() {
return Objects.hashCode(name);
}
// from 4.5.2
// Notation items with the same [name], [system identifier],
// [public identifier], and [declaration base URI] are considered
// to be duplicate. An application may also be able to detect that
// notations are duplicate through other means. For instance, the URI
// resulting from combining the system identifier and the declaration
// base URI is the same.
public boolean isDuplicate(Object obj) {
if (obj != null && obj instanceof Notation) {
Notation other = (Notation)obj;
return Objects.equals(name, other.name)
&& Objects.equals(publicId, other.publicId)
&& Objects.equals(expandedSystemId, other.expandedSystemId);
}
return false;
}
}
// This is a storage class to hold information about the unparsed entities.
// We're not using XMLEntityDecl because we don't want to lose the augmentations.
protected static class UnparsedEntity {
public String name;
public String systemId;
public String baseURI;
public String publicId;
public String expandedSystemId;
public String notation;
public Augmentations augmentations;
// equals() returns true if two UnparsedEntities have the same name.
// Useful for searching Vectors for entities with the same name
@Override
public boolean equals(Object obj) {
return obj == this || obj instanceof UnparsedEntity
&& Objects.equals(name, ((UnparsedEntity)obj).name);
}
@Override
public int hashCode() {
return Objects.hashCode(name);
}
// from 4.5.1:
// Unparsed entity items with the same [name], [system identifier],
// [public identifier], [declaration base URI], [notation name], and
// [notation] are considered to be duplicate. An application may also
// be able to detect that unparsed entities are duplicate through other
// means. For instance, the URI resulting from combining the system
// identifier and the declaration base URI is the same.
public boolean isDuplicate(Object obj) {
if (obj != null && obj instanceof UnparsedEntity) {
UnparsedEntity other = (UnparsedEntity)obj;
return Objects.equals(name, other.name)
&& Objects.equals(publicId, other.publicId)
&& Objects.equals(expandedSystemId, other.expandedSystemId)
&& Objects.equals(notation, other.notation);
}
return false;
}
}
// The following methods are used for XML Base processing
/**
* Saves the current base URI to the top of the stack.
*/
protected void saveBaseURI() {
fBaseURIScope.push(fDepth);
fBaseURI.push(fCurrentBaseURI.getBaseSystemId());
fLiteralSystemID.push(fCurrentBaseURI.getLiteralSystemId());
fExpandedSystemID.push(fCurrentBaseURI.getExpandedSystemId());
}
/**
* Discards the URIs at the top of the stack, and restores the ones beneath it.
*/
protected void restoreBaseURI() {
fBaseURI.pop();
fLiteralSystemID.pop();
fExpandedSystemID.pop();
fBaseURIScope.pop();
fCurrentBaseURI.setBaseSystemId((String)fBaseURI.peek());
fCurrentBaseURI.setLiteralSystemId((String)fLiteralSystemID.peek());
fCurrentBaseURI.setExpandedSystemId((String)fExpandedSystemID.peek());
}
// The following methods are used for language processing
/**
* Saves the given language on the top of the stack.
*
* @param lanaguage the language to push onto the stack.
*/
protected void saveLanguage(String language) {
fLanguageScope.push(fDepth);
fLanguageStack.push(language);
}
/**
* Discards the language at the top of the stack, and returns the one beneath it.
*/
public String restoreLanguage() {
fLanguageStack.pop();
fLanguageScope.pop();
return (String) fLanguageStack.peek();
}
/**
* Gets the base URI that was in use at that depth
* @param depth
* @return the base URI
*/
public String getBaseURI(int depth) {
int scope = scopeOfBaseURI(depth);
return (String)fExpandedSystemID.elementAt(scope);
}
/**
* Gets the language that was in use at that depth.
* @param depth
* @return the language
*/
public String getLanguage(int depth) {
int scope = scopeOfLanguage(depth);
return (String)fLanguageStack.elementAt(scope);
}
/**
* Returns a relative URI, which when resolved against the base URI at the
* specified depth, will create the current base URI.
* This is accomplished by merged the literal system IDs.
* @param depth the depth at which to start creating the relative URI
* @return a relative URI to convert the base URI at the given depth to the current
* base URI
*/
public String getRelativeURI(int depth) throws MalformedURIException {
// The literal system id at the location given by "start" is *in focus* at
// the given depth. So we need to adjust it to the next scope, so that we
// only process out of focus literal system ids
int start = scopeOfBaseURI(depth) + 1;
if (start == fBaseURIScope.size()) {
// If that is the last system id, then we don't need a relative URI
return "";
}
URI uri = new URI("file", (String)fLiteralSystemID.elementAt(start));
for (int i = start + 1; i < fBaseURIScope.size(); i++) {
uri = new URI(uri, (String)fLiteralSystemID.elementAt(i));
}
return uri.getPath();
}
// We need to find two consecutive elements in the scope stack,
// such that the first is lower than 'depth' (or equal), and the
// second is higher.
private int scopeOfBaseURI(int depth) {
for (int i = fBaseURIScope.size() - 1; i >= 0; i--) {
if (fBaseURIScope.elementAt(i) <= depth)
return i;
}
// we should never get here, because 0 was put on the stack in startDocument()
return -1;
}
private int scopeOfLanguage(int depth) {
for (int i = fLanguageScope.size() - 1; i >= 0; i--) {
if (fLanguageScope.elementAt(i) <= depth)
return i;
}
// we should never get here, because 0 was put on the stack in startDocument()
return -1;
}
/**
* Search for a xml:base attribute, and if one is found, put the new base URI into
* effect.
*/
protected void processXMLBaseAttributes(XMLAttributes attributes) {
String baseURIValue =
attributes.getValue(NamespaceContext.XML_URI, "base");
if (baseURIValue != null) {
try {
String expandedValue =
XMLEntityManager.expandSystemId(
baseURIValue,
fCurrentBaseURI.getExpandedSystemId(),
false);
fCurrentBaseURI.setLiteralSystemId(baseURIValue);
fCurrentBaseURI.setBaseSystemId(
fCurrentBaseURI.getExpandedSystemId());
fCurrentBaseURI.setExpandedSystemId(expandedValue);
// push the new values on the stack
saveBaseURI();
}
catch (MalformedURIException e) {
// REVISIT: throw error here
}
}
}
/**
* Search for a xml:lang attribute, and if one is found, put the new
* [language] into effect.
*/
protected void processXMLLangAttributes(XMLAttributes attributes) {
String language = attributes.getValue(NamespaceContext.XML_URI, "lang");
if (language != null) {
fCurrentLanguage = language;
saveLanguage(fCurrentLanguage);
}
}
/**
* Returns true
if the given string
* would be valid in an HTTP header.
*
* @param value string to check
* @return true
if the given string
* would be valid in an HTTP header
*/
private boolean isValidInHTTPHeader (String value) {
char ch;
for (int i = value.length() - 1; i >= 0; --i) {
ch = value.charAt(i);
if (ch < 0x20 || ch > 0x7E) {
return false;
}
}
return true;
}
/**
* Returns a new XMLInputSource
from the given parameters.
*/
private XMLInputSource createInputSource(String publicId,
String systemId, String baseSystemId,
String accept, String acceptLanguage) {
HTTPInputSource httpSource = new HTTPInputSource(publicId, systemId, baseSystemId);
if (accept != null && accept.length() > 0) {
httpSource.setHTTPRequestProperty(XIncludeHandler.HTTP_ACCEPT, accept);
}
if (acceptLanguage != null && acceptLanguage.length() > 0) {
httpSource.setHTTPRequestProperty(XIncludeHandler.HTTP_ACCEPT_LANGUAGE, acceptLanguage);
}
return httpSource;
}
// which ASCII characters need to be escaped
private static final boolean gNeedEscaping[] = new boolean[128];
// the first hex character if a character needs to be escaped
private static final char gAfterEscaping1[] = new char[128];
// the second hex character if a character needs to be escaped
private static final char gAfterEscaping2[] = new char[128];
private static final char[] gHexChs = {'0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
// initialize the above 3 arrays
static {
char[] escChs = {' ', '<', '>', '"', '{', '}', '|', '\\', '^', '`'};
int len = escChs.length;
char ch;
for (int i = 0; i < len; i++) {
ch = escChs[i];
gNeedEscaping[ch] = true;
gAfterEscaping1[ch] = gHexChs[ch >> 4];
gAfterEscaping2[ch] = gHexChs[ch & 0xf];
}
}
//
// Escape an href value according to (4.1.1):
//
// To convert the value of the href attribute to an IRI reference, the following characters must be escaped:
// space #x20
// the delimiters < #x3C, > #x3E and " #x22
// the unwise characters { #x7B, } #x7D, | #x7C, \ #x5C, ^ #x5E and ` #x60
//
// To convert an IRI reference to a URI reference, the following characters must also be escaped:
// the Unicode plane 0 characters #xA0 - #xD7FF, #xF900-#xFDCF, #xFDF0-#xFFEF
// the Unicode plane 1-14 characters #x10000-#x1FFFD ... #xE0000-#xEFFFD
//
private String escapeHref(String href) {
int len = href.length();
int ch;
final StringBuilder buffer = new StringBuilder(len*3);
// for each character in the href
int i = 0;
for (; i < len; i++) {
ch = href.charAt(i);
// if it's not an ASCII character (excluding 0x7F), break here, and use UTF-8 encoding
if (ch > 0x7E) {
break;
}
// abort: href does not allow this character
if (ch < 0x20) {
return href;
}
if (gNeedEscaping[ch]) {
buffer.append('%');
buffer.append(gAfterEscaping1[ch]);
buffer.append(gAfterEscaping2[ch]);
}
else {
buffer.append((char)ch);
}
}
// we saw some non-ascii character
if (i < len) {
// check if remainder of href contains any illegal characters before proceeding
for (int j = i; j < len; ++j) {
ch = href.charAt(j);
if ((ch >= 0x20 && ch <= 0x7E) ||
(ch >= 0xA0 && ch <= 0xD7FF) ||
(ch >= 0xF900 && ch <= 0xFDCF) ||
(ch >= 0xFDF0 && ch <= 0xFFEF)) {
continue;
}
if (XMLChar.isHighSurrogate(ch) && ++j < len) {
int ch2 = href.charAt(j);
if (XMLChar.isLowSurrogate(ch2)) {
ch2 = XMLChar.supplemental((char)ch, (char)ch2);
if (ch2 < 0xF0000 && (ch2 & 0xFFFF) <= 0xFFFD) {
continue;
}
}
}
// abort: href does not allow this character
return href;
}
// get UTF-8 bytes for the remaining sub-string
byte[] bytes = null;
byte b;
try {
bytes = href.substring(i).getBytes("UTF-8");
} catch (java.io.UnsupportedEncodingException e) {
// should never happen
return href;
}
len = bytes.length;
// for each byte
for (i = 0; i < len; i++) {
b = bytes[i];
// for non-ascii character: make it positive, then escape
if (b < 0) {
ch = b + 256;
buffer.append('%');
buffer.append(gHexChs[ch >> 4]);
buffer.append(gHexChs[ch & 0xf]);
}
else if (gNeedEscaping[b]) {
buffer.append('%');
buffer.append(gAfterEscaping1[b]);
buffer.append(gAfterEscaping2[b]);
}
else {
buffer.append((char)b);
}
}
}
// If escaping happened, create a new string;
// otherwise, return the orginal one.
if (buffer.length() != len) {
return buffer.toString();
}
else {
return href;
}
}
}