| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You 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 org.apache.xerces.impl; |
| |
| import java.io.File; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.InputStreamReader; |
| import java.io.OutputStream; |
| import java.io.Reader; |
| import java.io.StringReader; |
| import java.net.HttpURLConnection; |
| import java.net.URL; |
| import java.net.URLConnection; |
| import java.security.AccessController; |
| import java.security.PrivilegedAction; |
| import java.util.Hashtable; |
| import java.util.Iterator; |
| import java.util.Locale; |
| import java.util.Map; |
| import java.util.Stack; |
| import java.util.StringTokenizer; |
| |
| import org.apache.xerces.impl.io.ASCIIReader; |
| import org.apache.xerces.impl.io.Latin1Reader; |
| import org.apache.xerces.impl.io.UCSReader; |
| import org.apache.xerces.impl.io.UTF16Reader; |
| import org.apache.xerces.impl.io.UTF8Reader; |
| import org.apache.xerces.impl.msg.XMLMessageFormatter; |
| import org.apache.xerces.impl.validation.ValidationManager; |
| import org.apache.xerces.util.AugmentationsImpl; |
| import org.apache.xerces.util.EncodingMap; |
| import org.apache.xerces.util.HTTPInputSource; |
| import org.apache.xerces.util.SecurityManager; |
| import org.apache.xerces.util.SymbolTable; |
| import org.apache.xerces.util.URI; |
| import org.apache.xerces.util.XMLChar; |
| import org.apache.xerces.util.XMLEntityDescriptionImpl; |
| import org.apache.xerces.util.XMLResourceIdentifierImpl; |
| import org.apache.xerces.xni.Augmentations; |
| import org.apache.xerces.xni.XMLResourceIdentifier; |
| import org.apache.xerces.xni.XNIException; |
| import org.apache.xerces.xni.parser.XMLComponent; |
| import org.apache.xerces.xni.parser.XMLComponentManager; |
| import org.apache.xerces.xni.parser.XMLConfigurationException; |
| import org.apache.xerces.xni.parser.XMLEntityResolver; |
| import org.apache.xerces.xni.parser.XMLInputSource; |
| import org.xml.sax.SAXException; |
| import org.xml.sax.SAXNotRecognizedException; |
| import org.xml.sax.SAXNotSupportedException; |
| |
| /** |
| * The entity manager handles the registration of general and parameter |
| * entities; resolves entities; and starts entities. The entity manager |
| * is a central component in a standard parser configuration and this |
| * class works directly with the entity scanner to manage the underlying |
| * xni. |
| * <p> |
| * This component requires the following features and properties from the |
| * component manager that uses it: |
| * <ul> |
| * <li>http://xml.org/sax/features/validation</li> |
| * <li>http://xml.org/sax/features/external-general-entities</li> |
| * <li>http://xml.org/sax/features/external-parameter-entities</li> |
| * <li>http://apache.org/xml/features/allow-java-encodings</li> |
| * <li>http://apache.org/xml/properties/internal/symbol-table</li> |
| * <li>http://apache.org/xml/properties/internal/error-reporter</li> |
| * <li>http://apache.org/xml/properties/internal/entity-resolver</li> |
| * </ul> |
| * |
| * @xerces.internal |
| * |
| * @author Andy Clark, IBM |
| * @author Arnaud Le Hors, IBM |
| * |
| * @version $Id$ |
| */ |
| public class XMLEntityManager |
| implements XMLComponent, XMLEntityResolver { |
| |
| // |
| // Constants |
| // |
| |
| /** Default buffer size (2048). */ |
| public static final int DEFAULT_BUFFER_SIZE = 2048; |
| |
| /** Default buffer size before we've finished with the XMLDecl: */ |
| public static final int DEFAULT_XMLDECL_BUFFER_SIZE = 64; |
| |
| /** Default internal entity buffer size (512). */ |
| public static final int DEFAULT_INTERNAL_BUFFER_SIZE = 512; |
| |
| // feature identifiers |
| |
| /** Feature identifier: validation. */ |
| protected static final String VALIDATION = |
| Constants.SAX_FEATURE_PREFIX + Constants.VALIDATION_FEATURE; |
| |
| /** Feature identifier: external general entities. */ |
| protected static final String EXTERNAL_GENERAL_ENTITIES = |
| Constants.SAX_FEATURE_PREFIX + Constants.EXTERNAL_GENERAL_ENTITIES_FEATURE; |
| |
| /** Feature identifier: external parameter entities. */ |
| protected static final String EXTERNAL_PARAMETER_ENTITIES = |
| Constants.SAX_FEATURE_PREFIX + Constants.EXTERNAL_PARAMETER_ENTITIES_FEATURE; |
| |
| /** Feature identifier: allow Java encodings. */ |
| protected static final String ALLOW_JAVA_ENCODINGS = |
| Constants.XERCES_FEATURE_PREFIX + Constants.ALLOW_JAVA_ENCODINGS_FEATURE; |
| |
| /** Feature identifier: warn on duplicate EntityDef */ |
| protected static final String WARN_ON_DUPLICATE_ENTITYDEF = |
| Constants.XERCES_FEATURE_PREFIX +Constants.WARN_ON_DUPLICATE_ENTITYDEF_FEATURE; |
| |
| /** Feature identifier: standard uri conformant */ |
| protected static final String STANDARD_URI_CONFORMANT = |
| Constants.XERCES_FEATURE_PREFIX +Constants.STANDARD_URI_CONFORMANT_FEATURE; |
| |
| protected static final String PARSER_SETTINGS = |
| Constants.XERCES_FEATURE_PREFIX + Constants.PARSER_SETTINGS; |
| |
| // property identifiers |
| |
| /** 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: ValidationManager |
| protected static final String VALIDATION_MANAGER = |
| Constants.XERCES_PROPERTY_PREFIX + Constants.VALIDATION_MANAGER_PROPERTY; |
| |
| /** property identifier: buffer size. */ |
| protected static final String BUFFER_SIZE = |
| Constants.XERCES_PROPERTY_PREFIX + Constants.BUFFER_SIZE_PROPERTY; |
| |
| /** property identifier: security manager. */ |
| protected static final String SECURITY_MANAGER = |
| Constants.XERCES_PROPERTY_PREFIX + Constants.SECURITY_MANAGER_PROPERTY; |
| |
| // recognized features and properties |
| |
| /** Recognized features. */ |
| private static final String[] RECOGNIZED_FEATURES = { |
| VALIDATION, |
| EXTERNAL_GENERAL_ENTITIES, |
| EXTERNAL_PARAMETER_ENTITIES, |
| ALLOW_JAVA_ENCODINGS, |
| WARN_ON_DUPLICATE_ENTITYDEF, |
| STANDARD_URI_CONFORMANT |
| }; |
| |
| /** Feature defaults. */ |
| private static final Boolean[] FEATURE_DEFAULTS = { |
| null, |
| Boolean.TRUE, |
| Boolean.TRUE, |
| Boolean.FALSE, |
| Boolean.FALSE, |
| Boolean.FALSE |
| }; |
| |
| /** Recognized properties. */ |
| private static final String[] RECOGNIZED_PROPERTIES = { |
| SYMBOL_TABLE, |
| ERROR_REPORTER, |
| ENTITY_RESOLVER, |
| VALIDATION_MANAGER, |
| BUFFER_SIZE, |
| SECURITY_MANAGER, |
| }; |
| |
| /** Property defaults. */ |
| private static final Object[] PROPERTY_DEFAULTS = { |
| null, |
| null, |
| null, |
| null, |
| new Integer(DEFAULT_BUFFER_SIZE), |
| null, |
| }; |
| |
| private static final String XMLEntity = "[xml]".intern(); |
| private static final String DTDEntity = "[dtd]".intern(); |
| |
| // debugging |
| |
| /** |
| * Debug printing of buffer. This debugging flag works best when you |
| * resize the DEFAULT_BUFFER_SIZE down to something reasonable like |
| * 64 characters. |
| */ |
| private static final boolean DEBUG_BUFFER = false; |
| |
| /** Debug some basic entities. */ |
| private static final boolean DEBUG_ENTITIES = false; |
| |
| /** Debug switching readers for encodings. */ |
| private static final boolean DEBUG_ENCODINGS = false; |
| |
| // should be diplayed trace resolving messages |
| private static final boolean DEBUG_RESOLVER = false; |
| |
| // |
| // Data |
| // |
| |
| // features |
| |
| /** |
| * Validation. This feature identifier is: |
| * http://xml.org/sax/features/validation |
| */ |
| protected boolean fValidation; |
| |
| /** |
| * External general entities. This feature identifier is: |
| * http://xml.org/sax/features/external-general-entities |
| */ |
| protected boolean fExternalGeneralEntities = true; |
| |
| /** |
| * External parameter entities. This feature identifier is: |
| * http://xml.org/sax/features/external-parameter-entities |
| */ |
| protected boolean fExternalParameterEntities = true; |
| |
| /** |
| * Allow Java encoding names. This feature identifier is: |
| * http://apache.org/xml/features/allow-java-encodings |
| */ |
| protected boolean fAllowJavaEncodings; |
| |
| /** warn on duplicate Entity declaration. |
| * http://apache.org/xml/features/warn-on-duplicate-entitydef |
| */ |
| protected boolean fWarnDuplicateEntityDef; |
| |
| /** |
| * standard uri conformant (strict uri). |
| * http://apache.org/xml/features/standard-uri-conformant |
| */ |
| protected boolean fStrictURI; |
| |
| // properties |
| |
| /** |
| * Symbol table. This property identifier is: |
| * http://apache.org/xml/properties/internal/symbol-table |
| */ |
| protected SymbolTable fSymbolTable; |
| |
| /** |
| * Error reporter. This property identifier is: |
| * http://apache.org/xml/properties/internal/error-reporter |
| */ |
| protected XMLErrorReporter fErrorReporter; |
| |
| /** |
| * Entity resolver. This property identifier is: |
| * http://apache.org/xml/properties/internal/entity-resolver |
| */ |
| protected XMLEntityResolver fEntityResolver; |
| |
| /** |
| * Validation manager. This property identifier is: |
| * http://apache.org/xml/properties/internal/validation-manager |
| */ |
| protected ValidationManager fValidationManager; |
| |
| // settings |
| |
| /** |
| * Buffer size. We get this value from a property. The default size |
| * is used if the input buffer size property is not specified. |
| * REVISIT: do we need a property for internal entity buffer size? |
| */ |
| protected int fBufferSize = DEFAULT_BUFFER_SIZE; |
| |
| // stores defaults for entity expansion limit if it has |
| // been set on the configuration. |
| protected SecurityManager fSecurityManager = null; |
| |
| /** |
| * True if the document entity is standalone. This should really |
| * only be set by the document source (e.g. XMLDocumentScanner). |
| */ |
| protected boolean fStandalone; |
| |
| /** |
| * True if the current document contains parameter entity references. |
| */ |
| protected boolean fHasPEReferences; |
| |
| // are the entities being parsed in the external subset? |
| // NOTE: this *is not* the same as whether they're external entities! |
| protected boolean fInExternalSubset = false; |
| |
| // handlers |
| |
| /** Entity handler. */ |
| protected XMLEntityHandler fEntityHandler; |
| |
| // scanner |
| |
| /** Current entity scanner. */ |
| protected XMLEntityScanner fEntityScanner; |
| |
| /** XML 1.0 entity scanner. */ |
| protected XMLEntityScanner fXML10EntityScanner; |
| |
| /** XML 1.1 entity scanner. */ |
| protected XMLEntityScanner fXML11EntityScanner; |
| |
| // entity expansion limit (contains useful data if and only if |
| // fSecurityManager is non-null) |
| protected int fEntityExpansionLimit = 0; |
| // entity currently being expanded: |
| protected int fEntityExpansionCount = 0; |
| |
| // entities |
| |
| /** Entities. */ |
| protected final Hashtable fEntities = new Hashtable(); |
| |
| /** Entity stack. */ |
| protected final Stack fEntityStack = new Stack(); |
| |
| /** Current entity. */ |
| protected ScannedEntity fCurrentEntity; |
| |
| // shared context |
| |
| /** Shared declared entities. */ |
| protected Hashtable fDeclaredEntities; |
| |
| // temp vars |
| |
| /** Resource identifier. */ |
| private final XMLResourceIdentifierImpl fResourceIdentifier = new XMLResourceIdentifierImpl(); |
| |
| /** Augmentations for entities. */ |
| private final Augmentations fEntityAugs = new AugmentationsImpl(); |
| |
| /** Pool of byte buffers for single byte and variable width encodings, such as US-ASCII and UTF-8. */ |
| private final ByteBufferPool fSmallByteBufferPool = new ByteBufferPool(fBufferSize); |
| |
| /** Pool of byte buffers for 2-byte encodings, such as UTF-16. **/ |
| private final ByteBufferPool fLargeByteBufferPool = new ByteBufferPool(fBufferSize << 1); |
| |
| /** Temporary storage for the current entity's byte buffer. */ |
| private byte[] fTempByteBuffer = null; |
| |
| /** Pool of character buffers. */ |
| private final CharacterBufferPool fCharacterBufferPool = new CharacterBufferPool(fBufferSize, DEFAULT_INTERNAL_BUFFER_SIZE); |
| |
| // |
| // Constructors |
| // |
| |
| /** Default constructor. */ |
| public XMLEntityManager() { |
| this(null); |
| } // <init>() |
| |
| /** |
| * Constructs an entity manager that shares the specified entity |
| * declarations during each parse. |
| * <p> |
| * <strong>REVISIT:</strong> We might want to think about the "right" |
| * way to expose the list of declared entities. For now, the knowledge |
| * how to access the entity declarations is implicit. |
| */ |
| public XMLEntityManager(XMLEntityManager entityManager) { |
| |
| // save shared entity declarations |
| fDeclaredEntities = entityManager != null |
| ? entityManager.getDeclaredEntities() : null; |
| |
| setScannerVersion(Constants.XML_VERSION_1_0); |
| } // <init>(XMLEntityManager) |
| |
| // |
| // Public methods |
| // |
| |
| /** |
| * Sets whether the document entity is standalone. |
| * |
| * @param standalone True if document entity is standalone. |
| */ |
| public void setStandalone(boolean standalone) { |
| fStandalone = standalone; |
| } // setStandalone(boolean) |
| |
| /** Returns true if the document entity is standalone. */ |
| public boolean isStandalone() { |
| return fStandalone; |
| } // isStandalone():boolean |
| |
| /** |
| * Notifies the entity manager that the current document |
| * being processed contains parameter entity references. |
| */ |
| final void notifyHasPEReferences() { |
| fHasPEReferences = true; |
| } // notifyHasPEReferences |
| |
| /** |
| * Returns true if the document contains parameter entity references. |
| */ |
| final boolean hasPEReferences() { |
| return fHasPEReferences; |
| } // hasPEReferences():boolean |
| |
| /** |
| * Sets the entity handler. When an entity starts and ends, the |
| * entity handler is notified of the change. |
| * |
| * @param entityHandler The new entity handler. |
| */ |
| public void setEntityHandler(XMLEntityHandler entityHandler) { |
| fEntityHandler = entityHandler; |
| } // setEntityHandler(XMLEntityHandler) |
| |
| // this simply returns the fResourceIdentifier object; |
| // this should only be used with caution by callers that |
| // carefully manage the entity manager's behaviour, so that |
| // this doesn't returning meaningless or misleading data. |
| // @return a reference to the current fResourceIdentifier object |
| public XMLResourceIdentifier getCurrentResourceIdentifier() { |
| return fResourceIdentifier; |
| } |
| |
| // this simply returns the fCurrentEntity object; |
| // this should only be used with caution by callers that |
| // carefully manage the entity manager's behaviour, so that |
| // this doesn't returning meaningless or misleading data. |
| // @return a reference to the current fCurrentEntity object |
| public ScannedEntity getCurrentEntity() { |
| return fCurrentEntity; |
| } |
| |
| /** |
| * Adds an internal entity declaration. |
| * <p> |
| * <strong>Note:</strong> This method ignores subsequent entity |
| * declarations. |
| * <p> |
| * <strong>Note:</strong> The name should be a unique symbol. The |
| * SymbolTable can be used for this purpose. |
| * |
| * @param name The name of the entity. |
| * @param text The text of the entity. |
| * @param paramEntityRefs Count of direct and indirect references to parameter entities in the value of the entity. |
| * |
| * @see SymbolTable |
| */ |
| public void addInternalEntity(String name, String text, int paramEntityRefs) { |
| if (!fEntities.containsKey(name)) { |
| Entity entity = new InternalEntity(name, text, fInExternalSubset, paramEntityRefs); |
| fEntities.put(name, entity); |
| } |
| else{ |
| if(fWarnDuplicateEntityDef){ |
| fErrorReporter.reportError(XMLMessageFormatter.XML_DOMAIN, |
| "MSG_DUPLICATE_ENTITY_DEFINITION", |
| new Object[]{ name }, |
| XMLErrorReporter.SEVERITY_WARNING ); |
| } |
| } |
| |
| } // addInternalEntity(String,String,int) |
| |
| /** |
| * Adds an internal entity declaration. |
| * <p> |
| * <strong>Note:</strong> This method ignores subsequent entity |
| * declarations. |
| * <p> |
| * <strong>Note:</strong> The name should be a unique symbol. The |
| * SymbolTable can be used for this purpose. |
| * |
| * @param name The name of the entity. |
| * @param text The text of the entity. |
| * |
| * @see SymbolTable |
| */ |
| public void addInternalEntity(String name, String text) { |
| addInternalEntity(name, text, 0); |
| } // addInternalEntity(String,String) |
| |
| /** |
| * Returns the number of direct and indirect references to parameter |
| * entities in the value of the entity. This value will only be |
| * non-zero for an internal parameter entity. |
| * |
| * @param entityName The name of the entity to check. |
| * @return Count of direct and indirect references to parameter entities in the value of the entity |
| */ |
| public int getParamEntityRefCount(String entityName) { |
| if (entityName != null && |
| entityName.length() > 0 && |
| entityName.charAt(0) == '%') { |
| final Entity entity = (Entity) fEntities.get(entityName); |
| if (entity != null && !entity.isExternal()) { |
| return ((InternalEntity) entity).paramEntityRefs; |
| } |
| } |
| return 0; |
| } // getParamEntityRefCount(String) |
| |
| /** |
| * Adds an external entity declaration. |
| * <p> |
| * <strong>Note:</strong> This method ignores subsequent entity |
| * declarations. |
| * <p> |
| * <strong>Note:</strong> The name should be a unique symbol. The |
| * SymbolTable can be used for this purpose. |
| * |
| * @param name The name of the entity. |
| * @param publicId The public identifier of the entity. |
| * @param literalSystemId The system identifier of the entity. |
| * @param baseSystemId The base system identifier of the entity. |
| * This is the system identifier of the entity |
| * where <em>the entity being added</em> and |
| * is used to expand the system identifier when |
| * the system identifier is a relative URI. |
| * When null the system identifier of the first |
| * external entity on the stack is used instead. |
| * |
| * @see SymbolTable |
| */ |
| public void addExternalEntity(String name, |
| String publicId, String literalSystemId, |
| String baseSystemId) throws IOException { |
| if (!fEntities.containsKey(name)) { |
| if (baseSystemId == null) { |
| // search for the first external entity on the stack |
| int size = fEntityStack.size(); |
| if (size == 0 && fCurrentEntity != null && fCurrentEntity.entityLocation != null) { |
| baseSystemId = fCurrentEntity.entityLocation.getExpandedSystemId(); |
| } |
| for (int i = size - 1; i >= 0 ; i--) { |
| ScannedEntity externalEntity = |
| (ScannedEntity)fEntityStack.elementAt(i); |
| if (externalEntity.entityLocation != null && externalEntity.entityLocation.getExpandedSystemId() != null) { |
| baseSystemId = externalEntity.entityLocation.getExpandedSystemId(); |
| break; |
| } |
| } |
| } |
| Entity entity = new ExternalEntity(name, |
| new XMLEntityDescriptionImpl(name, publicId, literalSystemId, baseSystemId, |
| expandSystemId(literalSystemId, baseSystemId, false)), null, fInExternalSubset); |
| fEntities.put(name, entity); |
| } |
| else{ |
| if(fWarnDuplicateEntityDef){ |
| fErrorReporter.reportError(XMLMessageFormatter.XML_DOMAIN, |
| "MSG_DUPLICATE_ENTITY_DEFINITION", |
| new Object[]{ name }, |
| XMLErrorReporter.SEVERITY_WARNING ); |
| } |
| } |
| |
| } // addExternalEntity(String,String,String,String) |
| |
| /** |
| * Checks whether an entity given by name is external. |
| * |
| * @param entityName The name of the entity to check. |
| * @return True if the entity is external, false otherwise |
| * (including when the entity is not declared). |
| */ |
| public boolean isExternalEntity(String entityName) { |
| |
| Entity entity = (Entity)fEntities.get(entityName); |
| if (entity == null) { |
| return false; |
| } |
| return entity.isExternal(); |
| } |
| |
| /** |
| * Checks whether the declaration of an entity given by name is |
| // in the external subset. |
| * |
| * @param entityName The name of the entity to check. |
| * @return True if the entity was declared in the external subset, false otherwise |
| * (including when the entity is not declared). |
| */ |
| public boolean isEntityDeclInExternalSubset(String entityName) { |
| |
| Entity entity = (Entity)fEntities.get(entityName); |
| if (entity == null) { |
| return false; |
| } |
| return entity.isEntityDeclInExternalSubset(); |
| } |
| |
| /** |
| * Adds an unparsed entity declaration. |
| * <p> |
| * <strong>Note:</strong> This method ignores subsequent entity |
| * declarations. |
| * <p> |
| * <strong>Note:</strong> The name should be a unique symbol. The |
| * SymbolTable can be used for this purpose. |
| * |
| * @param name The name of the entity. |
| * @param publicId The public identifier of the entity. |
| * @param systemId The system identifier of the entity. |
| * @param notation The name of the notation. |
| * |
| * @see SymbolTable |
| */ |
| public void addUnparsedEntity(String name, |
| String publicId, String systemId, |
| String baseSystemId, String notation) { |
| if (!fEntities.containsKey(name)) { |
| Entity entity = new ExternalEntity(name, |
| new XMLEntityDescriptionImpl(name, publicId, systemId, baseSystemId, null), |
| notation, fInExternalSubset); |
| fEntities.put(name, entity); |
| } |
| else{ |
| if(fWarnDuplicateEntityDef){ |
| fErrorReporter.reportError(XMLMessageFormatter.XML_DOMAIN, |
| "MSG_DUPLICATE_ENTITY_DEFINITION", |
| new Object[]{ name }, |
| XMLErrorReporter.SEVERITY_WARNING ); |
| } |
| } |
| } // addUnparsedEntity(String,String,String,String) |
| |
| /** |
| * Checks whether an entity given by name is unparsed. |
| * |
| * @param entityName The name of the entity to check. |
| * @return True if the entity is unparsed, false otherwise |
| * (including when the entity is not declared). |
| */ |
| public boolean isUnparsedEntity(String entityName) { |
| |
| Entity entity = (Entity)fEntities.get(entityName); |
| if (entity == null) { |
| return false; |
| } |
| return entity.isUnparsed(); |
| } |
| |
| /** |
| * Checks whether an entity given by name is declared. |
| * |
| * @param entityName The name of the entity to check. |
| * @return True if the entity is declared, false otherwise. |
| */ |
| public boolean isDeclaredEntity(String entityName) { |
| |
| Entity entity = (Entity)fEntities.get(entityName); |
| return entity != null; |
| } |
| |
| /** |
| * Resolves the specified public and system identifiers. This |
| * method first attempts to resolve the entity based on the |
| * EntityResolver registered by the application. If no entity |
| * resolver is registered or if the registered entity handler |
| * is unable to resolve the entity, then default entity |
| * resolution will occur. |
| * |
| * @param resourceIdentifier The XMLResourceIdentifier for the resource to resolve. |
| * |
| * @return Returns an input source that wraps the resolved entity. |
| * This method will never return null. |
| * |
| * @throws IOException Thrown on i/o error. |
| * @throws XNIException Thrown by entity resolver to signal an error. |
| */ |
| public XMLInputSource resolveEntity(XMLResourceIdentifier resourceIdentifier) |
| throws IOException, XNIException { |
| if(resourceIdentifier == null ) return null; |
| String publicId = resourceIdentifier.getPublicId(); |
| String literalSystemId = resourceIdentifier.getLiteralSystemId(); |
| String baseSystemId = resourceIdentifier.getBaseSystemId(); |
| String expandedSystemId = resourceIdentifier.getExpandedSystemId(); |
| // if no base systemId given, assume that it's relative |
| // to the systemId of the current scanned entity |
| // Sometimes the system id is not (properly) expanded. |
| // We need to expand the system id if: |
| // a. the expanded one was null; or |
| // b. the base system id was null, but becomes non-null from the current entity. |
| boolean needExpand = (expandedSystemId == null); |
| // REVISIT: why would the baseSystemId ever be null? if we |
| // didn't have to make this check we wouldn't have to reuse the |
| // fXMLResourceIdentifier object... |
| if (baseSystemId == null && fCurrentEntity != null && fCurrentEntity.entityLocation != null) { |
| baseSystemId = fCurrentEntity.entityLocation.getExpandedSystemId(); |
| if (baseSystemId != null) |
| needExpand = true; |
| } |
| |
| // give the entity resolver a chance |
| XMLInputSource xmlInputSource = null; |
| if (fEntityResolver != null) { |
| if (needExpand) { |
| expandedSystemId = expandSystemId(literalSystemId, baseSystemId, false); |
| } |
| resourceIdentifier.setBaseSystemId(baseSystemId); |
| resourceIdentifier.setExpandedSystemId(expandedSystemId); |
| xmlInputSource = fEntityResolver.resolveEntity(resourceIdentifier); |
| } |
| |
| // do default resolution |
| // REVISIT: what's the correct behavior if the user provided an entity |
| // resolver (fEntityResolver != null), but resolveEntity doesn't return |
| // an input source (xmlInputSource == null)? |
| // do we do default resolution, or do we just return null? -SG |
| if (xmlInputSource == null) { |
| // REVISIT: when systemId is null, I think we should return null. |
| // is this the right solution? -SG |
| //if (systemId != null) |
| xmlInputSource = new XMLInputSource(publicId, literalSystemId, baseSystemId); |
| } |
| |
| if (DEBUG_RESOLVER) { |
| System.err.println("XMLEntityManager.resolveEntity(" + publicId + ")"); |
| System.err.println(" = " + xmlInputSource); |
| } |
| |
| return xmlInputSource; |
| |
| } // resolveEntity(XMLResourceIdentifier):XMLInputSource |
| |
| /** |
| * Starts a named entity. |
| * |
| * @param entityName The name of the entity to start. |
| * @param literal True if this entity is started within a literal |
| * value. |
| * |
| * @throws IOException Thrown on i/o error. |
| * @throws XNIException Thrown by entity handler to signal an error. |
| */ |
| public void startEntity(String entityName, boolean literal) |
| throws IOException, XNIException { |
| |
| // was entity declared? |
| Entity entity = (Entity)fEntities.get(entityName); |
| if (entity == null) { |
| if (fEntityHandler != null) { |
| String encoding = null; |
| fResourceIdentifier.clear(); |
| fEntityAugs.removeAllItems(); |
| fEntityAugs.putItem(Constants.ENTITY_SKIPPED, Boolean.TRUE); |
| fEntityHandler.startEntity(entityName, fResourceIdentifier, encoding, fEntityAugs); |
| fEntityAugs.removeAllItems(); |
| fEntityAugs.putItem(Constants.ENTITY_SKIPPED, Boolean.TRUE); |
| fEntityHandler.endEntity(entityName, fEntityAugs); |
| } |
| return; |
| } |
| |
| // should we skip external entities? |
| boolean external = entity.isExternal(); |
| if (external && (fValidationManager == null || !fValidationManager.isCachedDTD())) { |
| boolean unparsed = entity.isUnparsed(); |
| boolean parameter = entityName.startsWith("%"); |
| boolean general = !parameter; |
| if (unparsed || (general && !fExternalGeneralEntities) || |
| (parameter && !fExternalParameterEntities)) { |
| if (fEntityHandler != null) { |
| fResourceIdentifier.clear(); |
| final String encoding = null; |
| ExternalEntity externalEntity = (ExternalEntity)entity; |
| //REVISIT: since we're storing expandedSystemId in the |
| // externalEntity, how could this have got here if it wasn't already |
| // expanded??? - neilg |
| String extLitSysId = (externalEntity.entityLocation != null ? externalEntity.entityLocation.getLiteralSystemId() : null); |
| String extBaseSysId = (externalEntity.entityLocation != null ? externalEntity.entityLocation.getBaseSystemId() : null); |
| String expandedSystemId = expandSystemId(extLitSysId, extBaseSysId, false); |
| fResourceIdentifier.setValues( |
| (externalEntity.entityLocation != null ? externalEntity.entityLocation.getPublicId() : null), |
| extLitSysId, extBaseSysId, expandedSystemId); |
| fEntityAugs.removeAllItems(); |
| fEntityAugs.putItem(Constants.ENTITY_SKIPPED, Boolean.TRUE); |
| fEntityHandler.startEntity(entityName, fResourceIdentifier, encoding, fEntityAugs); |
| fEntityAugs.removeAllItems(); |
| fEntityAugs.putItem(Constants.ENTITY_SKIPPED, Boolean.TRUE); |
| fEntityHandler.endEntity(entityName, fEntityAugs); |
| } |
| return; |
| } |
| } |
| |
| // is entity recursive? |
| int size = fEntityStack.size(); |
| for (int i = size; i >= 0; i--) { |
| Entity activeEntity = i == size |
| ? fCurrentEntity |
| : (Entity)fEntityStack.elementAt(i); |
| if (activeEntity.name == entityName) { |
| StringBuffer path = new StringBuffer(entityName); |
| for (int j = i + 1; j < size; j++) { |
| activeEntity = (Entity)fEntityStack.elementAt(j); |
| path.append(" -> "); |
| path.append(activeEntity.name); |
| } |
| path.append(" -> "); |
| path.append(fCurrentEntity.name); |
| path.append(" -> "); |
| path.append(entityName); |
| fErrorReporter.reportError(XMLMessageFormatter.XML_DOMAIN, |
| "RecursiveReference", |
| new Object[] { entityName, path.toString() }, |
| XMLErrorReporter.SEVERITY_FATAL_ERROR); |
| if (fEntityHandler != null) { |
| fResourceIdentifier.clear(); |
| final String encoding = null; |
| if (external) { |
| ExternalEntity externalEntity = (ExternalEntity)entity; |
| // REVISIT: for the same reason above... |
| String extLitSysId = (externalEntity.entityLocation != null ? externalEntity.entityLocation.getLiteralSystemId() : null); |
| String extBaseSysId = (externalEntity.entityLocation != null ? externalEntity.entityLocation.getBaseSystemId() : null); |
| String expandedSystemId = expandSystemId(extLitSysId, extBaseSysId, false); |
| fResourceIdentifier.setValues( |
| (externalEntity.entityLocation != null ? externalEntity.entityLocation.getPublicId() : null), |
| extLitSysId, extBaseSysId, expandedSystemId); |
| } |
| fEntityAugs.removeAllItems(); |
| fEntityAugs.putItem(Constants.ENTITY_SKIPPED, Boolean.TRUE); |
| fEntityHandler.startEntity(entityName, fResourceIdentifier, encoding, fEntityAugs); |
| fEntityAugs.removeAllItems(); |
| fEntityAugs.putItem(Constants.ENTITY_SKIPPED, Boolean.TRUE); |
| fEntityHandler.endEntity(entityName, fEntityAugs); |
| } |
| return; |
| } |
| } |
| |
| // resolve external entity |
| XMLInputSource xmlInputSource = null; |
| if (external) { |
| ExternalEntity externalEntity = (ExternalEntity)entity; |
| xmlInputSource = resolveEntity(externalEntity.entityLocation); |
| } |
| |
| // wrap internal entity |
| else { |
| InternalEntity internalEntity = (InternalEntity)entity; |
| Reader reader = new StringReader(internalEntity.text); |
| xmlInputSource = new XMLInputSource(null, null, null, reader, null); |
| } |
| |
| // start the entity |
| startEntity(entityName, xmlInputSource, literal, external); |
| |
| } // startEntity(String,boolean) |
| |
| /** |
| * Starts the document entity. The document entity has the "[xml]" |
| * pseudo-name. |
| * |
| * @param xmlInputSource The input source of the document entity. |
| * |
| * @throws IOException Thrown on i/o error. |
| * @throws XNIException Thrown by entity handler to signal an error. |
| */ |
| public void startDocumentEntity(XMLInputSource xmlInputSource) |
| throws IOException, XNIException { |
| startEntity(XMLEntity, xmlInputSource, false, true); |
| } // startDocumentEntity(XMLInputSource) |
| |
| /** |
| * Starts the DTD entity. The DTD entity has the "[dtd]" |
| * pseudo-name. |
| * |
| * @param xmlInputSource The input source of the DTD entity. |
| * |
| * @throws IOException Thrown on i/o error. |
| * @throws XNIException Thrown by entity handler to signal an error. |
| */ |
| public void startDTDEntity(XMLInputSource xmlInputSource) |
| throws IOException, XNIException { |
| startEntity(DTDEntity, xmlInputSource, false, true); |
| } // startDTDEntity(XMLInputSource) |
| |
| // indicate start of external subset so that |
| // location of entity decls can be tracked |
| public void startExternalSubset() { |
| fInExternalSubset = true; |
| } |
| |
| public void endExternalSubset() { |
| fInExternalSubset = false; |
| } |
| |
| /** |
| * Starts an entity. |
| * <p> |
| * This method can be used to insert an application defined XML |
| * entity stream into the parsing stream. |
| * |
| * @param name The name of the entity. |
| * @param xmlInputSource The input source of the entity. |
| * @param literal True if this entity is started within a |
| * literal value. |
| * @param isExternal whether this entity should be treated as an internal or external entity. |
| * |
| * @throws IOException Thrown on i/o error. |
| * @throws XNIException Thrown by entity handler to signal an error. |
| */ |
| public void startEntity(String name, |
| XMLInputSource xmlInputSource, |
| boolean literal, boolean isExternal) |
| throws IOException, XNIException { |
| |
| String encoding = setupCurrentEntity(name, xmlInputSource, literal, isExternal); |
| |
| // when entity expansion limit is set by the Application, we need to |
| // check for the entity expansion limit set by the parser, if number of entity |
| // expansions exceeds the entity expansion limit, parser will throw fatal error. |
| // Note that this is intentionally unbalanced; it counts |
| // the number of expansions *per document*. |
| if (fSecurityManager != null) { |
| fEntityExpansionCount += getParamEntityRefCount(name); |
| if (fEntityExpansionCount++ > fEntityExpansionLimit) { |
| fErrorReporter.reportError(XMLMessageFormatter.XML_DOMAIN, |
| "EntityExpansionLimitExceeded", |
| new Object[]{new Integer(fEntityExpansionLimit) }, |
| XMLErrorReporter.SEVERITY_FATAL_ERROR ); |
| // is there anything better to do than reset the counter? |
| // at least one can envision debugging applications where this might |
| // be useful... |
| fEntityExpansionCount = 0; |
| } |
| } |
| |
| // call handler |
| if (fEntityHandler != null) { |
| fEntityHandler.startEntity(name, fResourceIdentifier, encoding, null); |
| } |
| |
| } // startEntity(String,XMLInputSource) |
| |
| /** |
| * This method uses the passed-in XMLInputSource to make |
| * fCurrentEntity usable for reading. |
| * @param name name of the entity (XML is it's the document entity) |
| * @param xmlInputSource the input source, with sufficient information |
| * to begin scanning characters. |
| * @param literal True if this entity is started within a |
| * literal value. |
| * @param isExternal whether this entity should be treated as an internal or external entity. |
| * @throws IOException if anything can't be read |
| * XNIException If any parser-specific goes wrong. |
| * @return the encoding of the new entity or null if a character stream was employed |
| */ |
| public String setupCurrentEntity(String name, XMLInputSource xmlInputSource, |
| boolean literal, boolean isExternal) |
| throws IOException, XNIException { |
| // get information |
| |
| final String publicId = xmlInputSource.getPublicId(); |
| String literalSystemId = xmlInputSource.getSystemId(); |
| String baseSystemId = xmlInputSource.getBaseSystemId(); |
| String encoding = xmlInputSource.getEncoding(); |
| final boolean encodingExternallySpecified = (encoding != null); |
| Boolean isBigEndian = null; |
| fTempByteBuffer = null; |
| |
| // create reader |
| InputStream stream = null; |
| Reader reader = xmlInputSource.getCharacterStream(); |
| // First chance checking strict URI |
| String expandedSystemId = expandSystemId(literalSystemId, baseSystemId, fStrictURI); |
| if (baseSystemId == null) { |
| baseSystemId = expandedSystemId; |
| } |
| if (reader == null) { |
| stream = xmlInputSource.getByteStream(); |
| if (stream == null) { |
| URL location = new URL(expandedSystemId); |
| URLConnection connect = location.openConnection(); |
| if (!(connect instanceof HttpURLConnection)) { |
| stream = connect.getInputStream(); |
| } |
| else { |
| boolean followRedirects = true; |
| |
| // setup URLConnection if we have an HTTPInputSource |
| if (xmlInputSource instanceof HTTPInputSource) { |
| final HttpURLConnection urlConnection = (HttpURLConnection) connect; |
| final HTTPInputSource httpInputSource = (HTTPInputSource) xmlInputSource; |
| |
| // set request properties |
| Iterator propIter = httpInputSource.getHTTPRequestProperties(); |
| while (propIter.hasNext()) { |
| Map.Entry entry = (Map.Entry) propIter.next(); |
| urlConnection.setRequestProperty((String) entry.getKey(), (String) entry.getValue()); |
| } |
| |
| // set preference for redirection |
| followRedirects = httpInputSource.getFollowHTTPRedirects(); |
| if (!followRedirects) { |
| urlConnection.setInstanceFollowRedirects(followRedirects); |
| } |
| } |
| |
| stream = connect.getInputStream(); |
| |
| // REVISIT: If the URLConnection has external encoding |
| // information, we should be reading it here. It's located |
| // in the charset parameter of Content-Type. -- mrglavas |
| |
| if (followRedirects) { |
| String redirect = connect.getURL().toString(); |
| // E43: Check if the URL was redirected, and then |
| // update literal and expanded system IDs if needed. |
| if (!redirect.equals(expandedSystemId)) { |
| literalSystemId = redirect; |
| expandedSystemId = redirect; |
| } |
| } |
| } |
| } |
| // wrap this stream in RewindableInputStream |
| RewindableInputStream rewindableStream = new RewindableInputStream(stream); |
| stream = rewindableStream; |
| |
| // perform auto-detect of encoding if necessary |
| if (encoding == null) { |
| // read first four bytes and determine encoding |
| final byte[] b4 = new byte[4]; |
| int count = 0; |
| for (; count<4; count++ ) { |
| b4[count] = (byte)rewindableStream.readAndBuffer(); |
| } |
| if (count == 4) { |
| final EncodingInfo info = getEncodingInfo(b4, count); |
| encoding = info.autoDetectedEncoding; |
| final String readerEncoding = info.readerEncoding; |
| isBigEndian = info.isBigEndian; |
| stream.reset(); |
| if (info.hasBOM) { |
| // Special case UTF-8 files with BOM created by Microsoft |
| // tools. It's more efficient to consume the BOM than make |
| // the reader perform extra checks. -Ac |
| if (readerEncoding == "UTF-8") { |
| // UTF-8 BOM: 0xEF 0xBB 0xBF |
| stream.skip(3); |
| } |
| // It's also more efficient to consume the UTF-16 BOM. |
| else if (readerEncoding == "UTF-16") { |
| // UTF-16 BE BOM: 0xFE 0xFF |
| // UTF-16 LE BOM: 0xFF 0xFE |
| stream.skip(2); |
| } |
| } |
| reader = createReader(stream, readerEncoding, isBigEndian); |
| } |
| else { |
| reader = createReader(stream, encoding, isBigEndian); |
| } |
| } |
| |
| // use specified encoding |
| else { |
| encoding = encoding.toUpperCase(Locale.ENGLISH); |
| |
| // If encoding is UTF-8, consume BOM if one is present. |
| if (encoding.equals("UTF-8")) { |
| final int[] b3 = new int[3]; |
| int count = 0; |
| for (; count < 3; ++count) { |
| b3[count] = rewindableStream.readAndBuffer(); |
| if (b3[count] == -1) |
| break; |
| } |
| if (count == 3) { |
| if (b3[0] != 0xEF || b3[1] != 0xBB || b3[2] != 0xBF) { |
| // First three bytes are not BOM, so reset. |
| stream.reset(); |
| } |
| } |
| else { |
| stream.reset(); |
| } |
| reader = createReader(stream, "UTF-8", isBigEndian); |
| } |
| // If encoding is UTF-16, we still need to read the first |
| // four bytes, in order to discover the byte order. |
| else if (encoding.equals("UTF-16")) { |
| final int[] b4 = new int[4]; |
| int count = 0; |
| for (; count < 4; ++count) { |
| b4[count] = rewindableStream.readAndBuffer(); |
| if (b4[count] == -1) |
| break; |
| } |
| stream.reset(); |
| if (count >= 2) { |
| final int b0 = b4[0]; |
| final int b1 = b4[1]; |
| if (b0 == 0xFE && b1 == 0xFF) { |
| // UTF-16, big-endian |
| isBigEndian = Boolean.TRUE; |
| stream.skip(2); |
| } |
| else if (b0 == 0xFF && b1 == 0xFE) { |
| // UTF-16, little-endian |
| isBigEndian = Boolean.FALSE; |
| stream.skip(2); |
| } |
| else if (count == 4) { |
| final int b2 = b4[2]; |
| final int b3 = b4[3]; |
| if (b0 == 0x00 && b1 == 0x3C && b2 == 0x00 && b3 == 0x3F) { |
| // UTF-16, big-endian, no BOM |
| isBigEndian = Boolean.TRUE; |
| } |
| if (b0 == 0x3C && b1 == 0x00 && b2 == 0x3F && b3 == 0x00) { |
| // UTF-16, little-endian, no BOM |
| isBigEndian = Boolean.FALSE; |
| } |
| } |
| } |
| reader = createReader(stream, "UTF-16", isBigEndian); |
| } |
| // If encoding is UCS-4, we still need to read the first four bytes |
| // in order to discover the byte order. |
| else if (encoding.equals("ISO-10646-UCS-4")) { |
| final int[] b4 = new int[4]; |
| int count = 0; |
| for (; count < 4; ++count) { |
| b4[count] = rewindableStream.readAndBuffer(); |
| if (b4[count] == -1) |
| break; |
| } |
| stream.reset(); |
| |
| // Ignore unusual octet order for now. |
| if (count == 4) { |
| // UCS-4, big endian (1234) |
| if (b4[0] == 0x00 && b4[1] == 0x00 && b4[2] == 0x00 && b4[3] == 0x3C) { |
| isBigEndian = Boolean.TRUE; |
| } |
| // UCS-4, little endian (1234) |
| else if (b4[0] == 0x3C && b4[1] == 0x00 && b4[2] == 0x00 && b4[3] == 0x00) { |
| isBigEndian = Boolean.FALSE; |
| } |
| } |
| reader = createReader(stream, encoding, isBigEndian); |
| } |
| // If encoding is UCS-2, we still need to read the first four bytes |
| // in order to discover the byte order. |
| else if (encoding.equals("ISO-10646-UCS-2")) { |
| final int[] b4 = new int[4]; |
| int count = 0; |
| for (; count < 4; ++count) { |
| b4[count] = rewindableStream.readAndBuffer(); |
| if (b4[count] == -1) |
| break; |
| } |
| stream.reset(); |
| |
| if (count == 4) { |
| // UCS-2, big endian |
| if (b4[0] == 0x00 && b4[1] == 0x3C && b4[2] == 0x00 && b4[3] == 0x3F) { |
| isBigEndian = Boolean.TRUE; |
| } |
| // UCS-2, little endian |
| else if (b4[0] == 0x3C && b4[1] == 0x00 && b4[2] == 0x3F && b4[3] == 0x00) { |
| isBigEndian = Boolean.FALSE; |
| } |
| } |
| reader = createReader(stream, encoding, isBigEndian); |
| } |
| else { |
| reader = createReader(stream, encoding, isBigEndian); |
| } |
| } |
| |
| // read one character at a time so we don't jump too far |
| // ahead, converting characters from the byte stream in |
| // the wrong encoding |
| if (DEBUG_ENCODINGS) { |
| System.out.println("$$$ no longer wrapping reader in OneCharReader"); |
| } |
| //reader = new OneCharReader(reader); |
| } |
| |
| // We've seen a new Reader. |
| // Push it on the stack so we can close it later. |
| fReaderStack.push(reader); |
| |
| // push entity on stack |
| if (fCurrentEntity != null) { |
| fEntityStack.push(fCurrentEntity); |
| } |
| |
| // create entity |
| fCurrentEntity = new ScannedEntity(name, |
| new XMLResourceIdentifierImpl(publicId, literalSystemId, baseSystemId, expandedSystemId), |
| stream, reader, fTempByteBuffer, encoding, literal, false, isExternal); |
| fCurrentEntity.setEncodingExternallySpecified(encodingExternallySpecified); |
| fEntityScanner.setCurrentEntity(fCurrentEntity); |
| fResourceIdentifier.setValues(publicId, literalSystemId, baseSystemId, expandedSystemId); |
| return encoding; |
| } //setupCurrentEntity(String, XMLInputSource, boolean, boolean): String |
| |
| // set version of scanner to use |
| public void setScannerVersion(short version) { |
| if(version == Constants.XML_VERSION_1_0) { |
| if(fXML10EntityScanner == null) { |
| fXML10EntityScanner = new XMLEntityScanner(); |
| } |
| fXML10EntityScanner.reset(fSymbolTable, this, fErrorReporter); |
| fEntityScanner = fXML10EntityScanner; |
| fEntityScanner.setCurrentEntity(fCurrentEntity); |
| } else { |
| if(fXML11EntityScanner == null) { |
| fXML11EntityScanner = new XML11EntityScanner(); |
| } |
| fXML11EntityScanner.reset(fSymbolTable, this, fErrorReporter); |
| fEntityScanner = fXML11EntityScanner; |
| fEntityScanner.setCurrentEntity(fCurrentEntity); |
| } |
| } // setScannerVersion(short) |
| |
| /** Returns the entity scanner. */ |
| public XMLEntityScanner getEntityScanner() { |
| if(fEntityScanner == null) { |
| // default to 1.0 |
| if(fXML10EntityScanner == null) { |
| fXML10EntityScanner = new XMLEntityScanner(); |
| } |
| fXML10EntityScanner.reset(fSymbolTable, this, fErrorReporter); |
| fEntityScanner = fXML10EntityScanner; |
| } |
| return fEntityScanner; |
| } // getEntityScanner():XMLEntityScanner |
| |
| // A stack containing all the open readers |
| protected Stack fReaderStack = new Stack(); |
| |
| /** |
| * Close all opened InputStreams and Readers opened by this parser. |
| */ |
| public void closeReaders() { |
| // close all readers |
| for (int i = fReaderStack.size()-1; i >= 0; i--) { |
| try { |
| ((Reader)fReaderStack.pop()).close(); |
| } catch (IOException e) { |
| // ignore |
| } |
| } |
| } |
| |
| // |
| // XMLComponent methods |
| // |
| |
| /** |
| * Resets the component. The component can query the component manager |
| * about any features and properties that affect the operation of the |
| * component. |
| * |
| * @param componentManager The component manager. |
| * |
| * @throws SAXException Thrown by component on initialization error. |
| * For example, if a feature or property is |
| * required for the operation of the component, the |
| * component manager may throw a |
| * SAXNotRecognizedException or a |
| * SAXNotSupportedException. |
| */ |
| public void reset(XMLComponentManager componentManager) |
| throws XMLConfigurationException { |
| |
| boolean parser_settings; |
| try { |
| parser_settings = componentManager.getFeature(PARSER_SETTINGS); |
| } catch (XMLConfigurationException e) { |
| parser_settings = true; |
| } |
| |
| if (!parser_settings) { |
| // parser settings have not been changed |
| reset(); |
| return; |
| } |
| |
| // sax features |
| try { |
| fValidation = componentManager.getFeature(VALIDATION); |
| } |
| catch (XMLConfigurationException e) { |
| fValidation = false; |
| } |
| try { |
| fExternalGeneralEntities = componentManager.getFeature(EXTERNAL_GENERAL_ENTITIES); |
| } |
| catch (XMLConfigurationException e) { |
| fExternalGeneralEntities = true; |
| } |
| try { |
| fExternalParameterEntities = componentManager.getFeature(EXTERNAL_PARAMETER_ENTITIES); |
| } |
| catch (XMLConfigurationException e) { |
| fExternalParameterEntities = true; |
| } |
| |
| // xerces features |
| try { |
| fAllowJavaEncodings = componentManager.getFeature(ALLOW_JAVA_ENCODINGS); |
| } |
| catch (XMLConfigurationException e) { |
| fAllowJavaEncodings = false; |
| } |
| |
| try { |
| fWarnDuplicateEntityDef = componentManager.getFeature(WARN_ON_DUPLICATE_ENTITYDEF); |
| } |
| catch (XMLConfigurationException e) { |
| fWarnDuplicateEntityDef = false; |
| } |
| |
| try { |
| fStrictURI = componentManager.getFeature(STANDARD_URI_CONFORMANT); |
| } |
| catch (XMLConfigurationException e) { |
| fStrictURI = false; |
| } |
| |
| // xerces properties |
| fSymbolTable = (SymbolTable)componentManager.getProperty(SYMBOL_TABLE); |
| fErrorReporter = (XMLErrorReporter)componentManager.getProperty(ERROR_REPORTER); |
| try { |
| fEntityResolver = (XMLEntityResolver)componentManager.getProperty(ENTITY_RESOLVER); |
| } |
| catch (XMLConfigurationException e) { |
| fEntityResolver = null; |
| } |
| try { |
| fValidationManager = (ValidationManager)componentManager.getProperty(VALIDATION_MANAGER); |
| } |
| catch (XMLConfigurationException e) { |
| fValidationManager = null; |
| } |
| try { |
| fSecurityManager = (SecurityManager)componentManager.getProperty(SECURITY_MANAGER); |
| } |
| catch (XMLConfigurationException e) { |
| fSecurityManager = null; |
| } |
| |
| // reset general state |
| reset(); |
| |
| } // reset(XMLComponentManager) |
| |
| // reset general state. Should not be called other than by |
| // a class acting as a component manager but not |
| // implementing that interface for whatever reason. |
| public void reset() { |
| fEntityExpansionLimit = (fSecurityManager != null)?fSecurityManager.getEntityExpansionLimit():0; |
| |
| // initialize state |
| fStandalone = false; |
| fHasPEReferences = false; |
| fEntities.clear(); |
| fEntityStack.removeAllElements(); |
| fEntityExpansionCount = 0; |
| |
| fCurrentEntity = null; |
| // reset scanner |
| if(fXML10EntityScanner != null){ |
| fXML10EntityScanner.reset(fSymbolTable, this, fErrorReporter); |
| } |
| if(fXML11EntityScanner != null) { |
| fXML11EntityScanner.reset(fSymbolTable, this, fErrorReporter); |
| } |
| |
| // DEBUG |
| if (DEBUG_ENTITIES) { |
| addInternalEntity("text", "Hello, World."); |
| addInternalEntity("empty-element", "<foo/>"); |
| addInternalEntity("balanced-element", "<foo></foo>"); |
| addInternalEntity("balanced-element-with-text", "<foo>Hello, World</foo>"); |
| addInternalEntity("balanced-element-with-entity", "<foo>&text;</foo>"); |
| addInternalEntity("unbalanced-entity", "<foo>"); |
| addInternalEntity("recursive-entity", "<foo>&recursive-entity2;</foo>"); |
| addInternalEntity("recursive-entity2", "<bar>&recursive-entity3;</bar>"); |
| addInternalEntity("recursive-entity3", "<baz>&recursive-entity;</baz>"); |
| try { |
| addExternalEntity("external-text", null, "external-text.ent", "test/external-text.xml"); |
| addExternalEntity("external-balanced-element", null, "external-balanced-element.ent", "test/external-balanced-element.xml"); |
| addExternalEntity("one", null, "ent/one.ent", "test/external-entity.xml"); |
| addExternalEntity("two", null, "ent/two.ent", "test/ent/one.xml"); |
| } |
| catch (IOException ex) { |
| // should never happen |
| } |
| } |
| |
| // copy declared entities |
| if (fDeclaredEntities != null) { |
| Iterator entries = fDeclaredEntities.entrySet().iterator(); |
| while (entries.hasNext()) { |
| Map.Entry entry = (Map.Entry) entries.next(); |
| Object key = entry.getKey(); |
| Object value = entry.getValue(); |
| fEntities.put(key, value); |
| } |
| } |
| fEntityHandler = null; |
| |
| } // 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. |
| */ |
| 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. |
| * <p> |
| * <strong>Note:</strong> 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. |
| */ |
| public void setFeature(String featureId, boolean state) |
| throws XMLConfigurationException { |
| |
| // xerces features |
| if (featureId.startsWith(Constants.XERCES_FEATURE_PREFIX)) { |
| final int suffixLength = featureId.length() - Constants.XERCES_FEATURE_PREFIX.length(); |
| if (suffixLength == Constants.ALLOW_JAVA_ENCODINGS_FEATURE.length() && |
| featureId.endsWith(Constants.ALLOW_JAVA_ENCODINGS_FEATURE)) { |
| fAllowJavaEncodings = 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. |
| */ |
| 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. |
| * <p> |
| * <strong>Note:</strong> 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. |
| */ |
| public void setProperty(String propertyId, Object value) |
| throws XMLConfigurationException { |
| |
| // Xerces properties |
| if (propertyId.startsWith(Constants.XERCES_PROPERTY_PREFIX)) { |
| final int suffixLength = propertyId.length() - Constants.XERCES_PROPERTY_PREFIX.length(); |
| |
| if (suffixLength == Constants.SYMBOL_TABLE_PROPERTY.length() && |
| propertyId.endsWith(Constants.SYMBOL_TABLE_PROPERTY)) { |
| fSymbolTable = (SymbolTable)value; |
| return; |
| } |
| if (suffixLength == Constants.ERROR_REPORTER_PROPERTY.length() && |
| propertyId.endsWith(Constants.ERROR_REPORTER_PROPERTY)) { |
| fErrorReporter = (XMLErrorReporter)value; |
| return; |
| } |
| if (suffixLength == Constants.ENTITY_RESOLVER_PROPERTY.length() && |
| propertyId.endsWith(Constants.ENTITY_RESOLVER_PROPERTY)) { |
| fEntityResolver = (XMLEntityResolver)value; |
| return; |
| } |
| if (suffixLength == Constants.BUFFER_SIZE_PROPERTY.length() && |
| propertyId.endsWith(Constants.BUFFER_SIZE_PROPERTY)) { |
| Integer bufferSize = (Integer)value; |
| if (bufferSize != null && |
| bufferSize.intValue() > DEFAULT_XMLDECL_BUFFER_SIZE) { |
| fBufferSize = bufferSize.intValue(); |
| fEntityScanner.setBufferSize(fBufferSize); |
| fSmallByteBufferPool.setBufferSize(fBufferSize); |
| fLargeByteBufferPool.setBufferSize(fBufferSize << 1); |
| fCharacterBufferPool.setExternalBufferSize(fBufferSize); |
| } |
| } |
| if (suffixLength == Constants.SECURITY_MANAGER_PROPERTY.length() && |
| propertyId.endsWith(Constants.SECURITY_MANAGER_PROPERTY)) { |
| fSecurityManager = (SecurityManager)value; |
| fEntityExpansionLimit = (fSecurityManager != null)?fSecurityManager.getEntityExpansionLimit():0; |
| } |
| } |
| |
| } // 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 |
| */ |
| 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 |
| */ |
| 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 |
| |
| // |
| // Public static methods |
| // |
| |
| // current value of the "user.dir" property |
| private static String gUserDir; |
| // cached URI object for the current value of the escaped "user.dir" property stored as a URI |
| private static URI gUserDirURI; |
| // 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 { |
| for (int i = 0; i <= 0x1f; i++) { |
| gNeedEscaping[i] = true; |
| gAfterEscaping1[i] = gHexChs[i >> 4]; |
| gAfterEscaping2[i] = gHexChs[i & 0xf]; |
| } |
| gNeedEscaping[0x7f] = true; |
| gAfterEscaping1[0x7f] = '7'; |
| gAfterEscaping2[0x7f] = 'F'; |
| 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]; |
| } |
| } |
| |
| private static PrivilegedAction GET_USER_DIR_SYSTEM_PROPERTY = new PrivilegedAction() { |
| public Object run() { |
| return System.getProperty("user.dir"); |
| } |
| }; |
| |
| // To escape the "user.dir" system property, by using %HH to represent |
| // special ASCII characters: 0x00~0x1F, 0x7F, ' ', '<', '>', '#', '%' |
| // and '"'. It's a static method, so needs to be synchronized. |
| // this method looks heavy, but since the system property isn't expected |
| // to change often, so in most cases, we only need to return the URI |
| // that was escaped before. |
| // According to the URI spec, non-ASCII characters (whose value >= 128) |
| // need to be escaped too. |
| // REVISIT: don't know how to escape non-ASCII characters, especially |
| // which encoding to use. Leave them for now. |
| private static synchronized URI getUserDir() throws URI.MalformedURIException { |
| // get the user.dir property |
| String userDir = ""; |
| try { |
| userDir = (String) AccessController.doPrivileged(GET_USER_DIR_SYSTEM_PROPERTY); |
| } |
| catch (SecurityException se) {} |
| |
| // return empty string if property value is empty string. |
| if (userDir.length() == 0) |
| return new URI("file", "", "", null, null); |
| |
| // compute the new escaped value if the new property value doesn't |
| // match the previous one |
| if (gUserDirURI != null && userDir.equals(gUserDir)) { |
| return gUserDirURI; |
| } |
| |
| // record the new value as the global property value |
| gUserDir = userDir; |
| |
| char separator = java.io.File.separatorChar; |
| userDir = userDir.replace(separator, '/'); |
| |
| int len = userDir.length(), ch; |
| StringBuffer buffer = new StringBuffer(len*3); |
| // change C:/blah to /C:/blah |
| if (len >= 2 && userDir.charAt(1) == ':') { |
| ch = Character.toUpperCase(userDir.charAt(0)); |
| if (ch >= 'A' && ch <= 'Z') { |
| buffer.append('/'); |
| } |
| } |
| |
| // for each character in the path |
| int i = 0; |
| for (; i < len; i++) { |
| ch = userDir.charAt(i); |
| // if it's not an ASCII character, break here, and use UTF-8 encoding |
| if (ch >= 128) |
| break; |
| if (gNeedEscaping[ch]) { |
| buffer.append('%'); |
| buffer.append(gAfterEscaping1[ch]); |
| buffer.append(gAfterEscaping2[ch]); |
| // record the fact that it's escaped |
| } |
| else { |
| buffer.append((char)ch); |
| } |
| } |
| |
| // we saw some non-ascii character |
| if (i < len) { |
| // get UTF-8 bytes for the remaining sub-string |
| byte[] bytes = null; |
| byte b; |
| try { |
| bytes = userDir.substring(i).getBytes("UTF-8"); |
| } catch (java.io.UnsupportedEncodingException e) { |
| // should never happen |
| return new URI("file", "", userDir, null, null); |
| } |
| 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); |
| } |
| } |
| } |
| |
| // change blah/blah to blah/blah/ |
| if (!userDir.endsWith("/")) |
| buffer.append('/'); |
| |
| gUserDirURI = new URI("file", "", buffer.toString(), null, null); |
| |
| return gUserDirURI; |
| } |
| |
| /** |
| * Absolutizes a URI using the current value |
| * of the "user.dir" property as the base URI. If |
| * the URI is already absolute, this is a no-op. |
| * |
| * @param uri the URI to absolutize |
| */ |
| public static void absolutizeAgainstUserDir(URI uri) |
| throws URI.MalformedURIException { |
| uri.absolutize(getUserDir()); |
| } |
| |
| /** |
| * Expands a system id and returns the system id as a URI, if |
| * it can be expanded. A return value of null means that the |
| * identifier is already expanded. An exception thrown |
| * indicates a failure to expand the id. |
| * |
| * @param systemId The systemId to be expanded. |
| * |
| * @return Returns the URI string representing the expanded system |
| * identifier. A null value indicates that the given |
| * system identifier is already expanded. |
| * |
| */ |
| public static String expandSystemId(String systemId, String baseSystemId, |
| boolean strict) |
| throws URI.MalformedURIException { |
| |
| // check if there is a system id before |
| // trying to expand it. |
| if (systemId == null) { |
| return null; |
| } |
| |
| // system id has to be a valid URI |
| if (strict) { |
| return expandSystemIdStrictOn(systemId, baseSystemId); |
| } |
| |
| // Assume the URIs are well-formed. If it turns out they're not, try fixing them up. |
| try { |
| return expandSystemIdStrictOff(systemId, baseSystemId); |
| } |
| catch (URI.MalformedURIException e) { |
| // continue on... |
| } |
| |
| // check for bad parameters id |
| if (systemId.length() == 0) { |
| return systemId; |
| } |
| |
| // normalize id |
| String id = fixURI(systemId); |
| |
| // normalize base |
| URI base = null; |
| URI uri = null; |
| try { |
| if (baseSystemId == null || baseSystemId.length() == 0 || |
| baseSystemId.equals(systemId)) { |
| base = getUserDir(); |
| } |
| else { |
| try { |
| base = new URI(fixURI(baseSystemId).trim()); |
| } |
| catch (URI.MalformedURIException e) { |
| if (baseSystemId.indexOf(':') != -1) { |
| // for xml schemas we might have baseURI with |
| // a specified drive |
| base = new URI("file", "", fixURI(baseSystemId).trim(), null, null); |
| } |
| else { |
| base = new URI(getUserDir(), fixURI(baseSystemId)); |
| } |
| } |
| } |
| // expand id |
| uri = new URI(base, id.trim()); |
| } |
| catch (Exception e) { |
| // let it go through |
| |
| } |
| |
| if (uri == null) { |
| return systemId; |
| } |
| return uri.toString(); |
| |
| } // expandSystemId(String,String,boolean):String |
| |
| /** |
| * Helper method for expandSystemId(String,String,boolean):String |
| */ |
| private static String expandSystemIdStrictOn(String systemId, String baseSystemId) |
| throws URI.MalformedURIException { |
| |
| URI systemURI = new URI(systemId, true); |
| // If it's already an absolute one, return it |
| if (systemURI.isAbsoluteURI()) { |
| return systemId; |
| } |
| |
| // If there isn't a base URI, use the working directory |
| URI baseURI = null; |
| if (baseSystemId == null || baseSystemId.length() == 0) { |
| baseURI = getUserDir(); |
| } |
| else { |
| baseURI = new URI(baseSystemId, true); |
| if (!baseURI.isAbsoluteURI()) { |
| // assume "base" is also a relative uri |
| baseURI.absolutize(getUserDir()); |
| } |
| } |
| |
| // absolutize the system identifier using the base URI |
| systemURI.absolutize(baseURI); |
| |
| // return the string rep of the new uri (an absolute one) |
| return systemURI.toString(); |
| |
| // if any exception is thrown, it'll get thrown to the caller. |
| |
| } // expandSystemIdStrictOn(String,String):String |
| |
| /** |
| * Helper method for expandSystemId(String,String,boolean):String |
| */ |
| private static String expandSystemIdStrictOff(String systemId, String baseSystemId) |
| throws URI.MalformedURIException { |
| |
| URI systemURI = new URI(systemId, true); |
| // If it's already an absolute one, return it |
| if (systemURI.isAbsoluteURI()) { |
| if (systemURI.getScheme().length() > 1) { |
| return systemId; |
| } |
| /** |
| * If the scheme's length is only one character, |
| * it's likely that this was intended as a file |
| * path. Fixing this up in expandSystemId to |
| * maintain backwards compatibility. |
| */ |
| throw new URI.MalformedURIException(); |
| } |
| |
| // If there isn't a base URI, use the working directory |
| URI baseURI = null; |
| if (baseSystemId == null || baseSystemId.length() == 0) { |
| baseURI = getUserDir(); |
| } |
| else { |
| baseURI = new URI(baseSystemId, true); |
| if (!baseURI.isAbsoluteURI()) { |
| // assume "base" is also a relative uri |
| baseURI.absolutize(getUserDir()); |
| } |
| } |
| |
| // absolutize the system identifier using the base URI |
| systemURI.absolutize(baseURI); |
| |
| // return the string rep of the new uri (an absolute one) |
| return systemURI.toString(); |
| |
| // if any exception is thrown, it'll get thrown to the caller. |
| |
| } // expandSystemIdStrictOff(String,String):String |
| |
| public static OutputStream createOutputStream(String uri) throws IOException { |
| // URI was specified. Handle relative URIs. |
| final String expanded = XMLEntityManager.expandSystemId(uri, null, true); |
| final URL url = new URL(expanded != null ? expanded : uri); |
| OutputStream out = null; |
| String protocol = url.getProtocol(); |
| String host = url.getHost(); |
| // Use FileOutputStream if this URI is for a local file. |
| if (protocol.equals("file") |
| && (host == null || host.length() == 0 || host.equals("localhost"))) { |
| File file = new File(getPathWithoutEscapes(url.getPath())); |
| if (!file.exists()) { |
| File parent = file.getParentFile(); |
| if (parent != null && !parent.exists()) { |
| parent.mkdirs(); |
| } |
| } |
| out = new FileOutputStream(file); |
| } |
| // Try to write to some other kind of URI. Some protocols |
| // won't support this, though HTTP should work. |
| else { |
| URLConnection urlCon = url.openConnection(); |
| urlCon.setDoInput(false); |
| urlCon.setDoOutput(true); |
| urlCon.setUseCaches(false); // Enable tunneling. |
| if (urlCon instanceof HttpURLConnection) { |
| // The DOM L3 REC says if we are writing to an HTTP URI |
| // it is to be done with an HTTP PUT. |
| HttpURLConnection httpCon = (HttpURLConnection) urlCon; |
| httpCon.setRequestMethod("PUT"); |
| } |
| out = urlCon.getOutputStream(); |
| } |
| return out; |
| } |
| |
| private static String getPathWithoutEscapes(String origPath) { |
| if (origPath != null && origPath.length() != 0 && origPath.indexOf('%') != -1) { |
| // Locate the escape characters |
| StringTokenizer tokenizer = new StringTokenizer(origPath, "%"); |
| StringBuffer result = new StringBuffer(origPath.length()); |
| int size = tokenizer.countTokens(); |
| result.append(tokenizer.nextToken()); |
| for(int i = 1; i < size; ++i) { |
| String token = tokenizer.nextToken(); |
| // Decode the 2 digit hexadecimal number following % in '%nn' |
| result.append((char)Integer.valueOf(token.substring(0, 2), 16).intValue()); |
| result.append(token.substring(2)); |
| } |
| return result.toString(); |
| } |
| return origPath; |
| } |
| |
| // |
| // Protected methods |
| // |
| |
| /** |
| * Ends an entity. |
| * |
| * @throws XNIException Thrown by entity handler to signal an error. |
| */ |
| void endEntity() throws XNIException { |
| |
| // call handler |
| if (DEBUG_BUFFER) { |
| System.out.print("(endEntity: "); |
| print(fCurrentEntity); |
| System.out.println(); |
| } |
| if (fEntityHandler != null) { |
| fEntityHandler.endEntity(fCurrentEntity.name, null); |
| } |
| |
| // Close the reader for the current entity once we're |
| // done with it, and remove it from our stack. If parsing |
| // is halted at some point, the rest of the readers on |
| // the stack will be closed during cleanup. |
| try { |
| fCurrentEntity.reader.close(); |
| } |
| catch (IOException e) { |
| // ignore |
| } |
| // REVISIT: We should never encounter underflow if the calls |
| // to startEntity and endEntity are balanced, but guard |
| // against the EmptyStackException for now. -- mrglavas |
| if (!fReaderStack.isEmpty()) { |
| fReaderStack.pop(); |
| } |
| |
| // Release the character buffer back to the pool for reuse |
| fCharacterBufferPool.returnBuffer(fCurrentEntity.fCharacterBuffer); |
| |
| // Release the byte buffer back to the pool for reuse |
| if (fCurrentEntity.fByteBuffer != null) { |
| if (fCurrentEntity.fByteBuffer.length == fBufferSize) { |
| fSmallByteBufferPool.returnBuffer(fCurrentEntity.fByteBuffer); |
| } |
| else { |
| fLargeByteBufferPool.returnBuffer(fCurrentEntity.fByteBuffer); |
| } |
| } |
| |
| // Pop entity stack. |
| fCurrentEntity = fEntityStack.size() > 0 |
| ? (ScannedEntity)fEntityStack.pop() : null; |
| fEntityScanner.setCurrentEntity(fCurrentEntity); |
| if (DEBUG_BUFFER) { |
| System.out.print(")endEntity: "); |
| print(fCurrentEntity); |
| System.out.println(); |
| } |
| |
| } // endEntity() |
| |
| /** |
| * Returns the IANA encoding name that is auto-detected from |
| * the bytes specified, with the endian-ness of that encoding where appropriate. |
| * |
| * @param b4 The first four bytes of the input. |
| * @param count The number of bytes actually read. |
| * @return an instance of EncodingInfo which represents the auto-detected encoding. |
| */ |
| protected EncodingInfo getEncodingInfo(byte[] b4, int count) { |
| |
| if (count < 2) { |
| return EncodingInfo.UTF_8; |
| } |
| |
| // UTF-16, with BOM |
| int b0 = b4[0] & 0xFF; |
| int b1 = b4[1] & 0xFF; |
| if (b0 == 0xFE && b1 == 0xFF) { |
| // UTF-16, big-endian |
| return EncodingInfo.UTF_16_BIG_ENDIAN_WITH_BOM; |
| } |
| if (b0 == 0xFF && b1 == 0xFE) { |
| // UTF-16, little-endian |
| return EncodingInfo.UTF_16_LITTLE_ENDIAN_WITH_BOM; |
| } |
| |
| // default to UTF-8 if we don't have enough bytes to make a |
| // good determination of the encoding |
| if (count < 3) { |
| return EncodingInfo.UTF_8; |
| } |
| |
| // UTF-8 with a BOM |
| int b2 = b4[2] & 0xFF; |
| if (b0 == 0xEF && b1 == 0xBB && b2 == 0xBF) { |
| return EncodingInfo.UTF_8_WITH_BOM; |
| } |
| |
| // default to UTF-8 if we don't have enough bytes to make a |
| // good determination of the encoding |
| if (count < 4) { |
| return EncodingInfo.UTF_8; |
| } |
| |
| // other encodings |
| int b3 = b4[3] & 0xFF; |
| if (b0 == 0x00 && b1 == 0x00 && b2 == 0x00 && b3 == 0x3C) { |
| // UCS-4, big endian (1234) |
| return EncodingInfo.UCS_4_BIG_ENDIAN; |
| } |
| if (b0 == 0x3C && b1 == 0x00 && b2 == 0x00 && b3 == 0x00) { |
| // UCS-4, little endian (4321) |
| return EncodingInfo.UCS_4_LITTLE_ENDIAN; |
| } |
| if (b0 == 0x00 && b1 == 0x00 && b2 == 0x3C && b3 == 0x00) { |
| // UCS-4, unusual octet order (2143) |
| // REVISIT: What should this be? |
| return EncodingInfo.UCS_4_UNUSUAL_BYTE_ORDER; |
| } |
| if (b0 == 0x00 && b1 == 0x3C && b2 == 0x00 && b3 == 0x00) { |
| // UCS-4, unusual octect order (3412) |
| // REVISIT: What should this be? |
| return EncodingInfo.UCS_4_UNUSUAL_BYTE_ORDER; |
| } |
| if (b0 == 0x00 && b1 == 0x3C && b2 == 0x00 && b3 == 0x3F) { |
| // UTF-16, big-endian, no BOM |
| // (or could turn out to be UCS-2... |
| // REVISIT: What should this be? |
| return EncodingInfo.UTF_16_BIG_ENDIAN; |
| } |
| if (b0 == 0x3C && b1 == 0x00 && b2 == 0x3F && b3 == 0x00) { |
| // UTF-16, little-endian, no BOM |
| // (or could turn out to be UCS-2... |
| return EncodingInfo.UTF_16_LITTLE_ENDIAN; |
| } |
| if (b0 == 0x4C && b1 == 0x6F && b2 == 0xA7 && b3 == 0x94) { |
| // EBCDIC |
| // a la xerces1, return CP037 instead of EBCDIC here |
| return EncodingInfo.EBCDIC; |
| } |
| |
| // default encoding |
| return EncodingInfo.UTF_8; |
| |
| } // getEncodingName(byte[],int):Object[] |
| |
| /** |
| * Creates a reader capable of reading the given input stream in |
| * the specified encoding. |
| * |
| * @param inputStream The input stream. |
| * @param encoding The encoding name that the input stream is |
| * encoded using. If the user has specified that |
| * Java encoding names are allowed, then the |
| * encoding name may be a Java encoding name; |
| * otherwise, it is an ianaEncoding name. |
| * @param isBigEndian For encodings (like uCS-4), whose names cannot |
| * specify a byte order, this tells whether the order is bigEndian. Null means |
| * unknown or not relevant. |
| * |
| * @return Returns a reader. |
| */ |
| protected Reader createReader(InputStream inputStream, String encoding, Boolean isBigEndian) |
| throws IOException { |
| |
| // if the encoding is UTF-8 use the optimized UTF-8 reader |
| if (encoding == "UTF-8" || encoding == null) { |
| return createUTF8Reader(inputStream); |
| } |
| |
| // If the encoding is UTF-16 use the optimized UTF-16 reader |
| if (encoding == "UTF-16" && isBigEndian != null) { |
| return createUTF16Reader(inputStream, isBigEndian.booleanValue()); |
| } |
| |
| // try to use an optimized reader |
| String ENCODING = encoding.toUpperCase(Locale.ENGLISH); |
| if (ENCODING.equals("UTF-8")) { |
| return createUTF8Reader(inputStream); |
| } |
| if (ENCODING.equals("UTF-16BE")) { |
| return createUTF16Reader(inputStream, true); |
| } |
| if (ENCODING.equals("UTF-16LE")) { |
| return createUTF16Reader(inputStream, false); |
| } |
| if (ENCODING.equals("ISO-10646-UCS-4")) { |
| if(isBigEndian != null) { |
| boolean isBE = isBigEndian.booleanValue(); |
| if(isBE) { |
| return new UCSReader(inputStream, UCSReader.UCS4BE); |
| } else { |
| return new UCSReader(inputStream, UCSReader.UCS4LE); |
| } |
| } else { |
| fErrorReporter.reportError(XMLMessageFormatter.XML_DOMAIN, |
| "EncodingByteOrderUnsupported", |
| new Object[] { encoding }, |
| XMLErrorReporter.SEVERITY_FATAL_ERROR); |
| } |
| } |
| if (ENCODING.equals("ISO-10646-UCS-2")) { |
| if(isBigEndian != null) { // should never happen with this encoding... |
| boolean isBE = isBigEndian.booleanValue(); |
| if(isBE) { |
| return new UCSReader(inputStream, UCSReader.UCS2BE); |
| } else { |
| return new UCSReader(inputStream, UCSReader.UCS2LE); |
| } |
| } else { |
| fErrorReporter.reportError(XMLMessageFormatter.XML_DOMAIN, |
| "EncodingByteOrderUnsupported", |
| new Object[] { encoding }, |
| XMLErrorReporter.SEVERITY_FATAL_ERROR); |
| } |
| } |
| |
| // check for valid name |
| boolean validIANA = XMLChar.isValidIANAEncoding(encoding); |
| boolean validJava = XMLChar.isValidJavaEncoding(encoding); |
| if (!validIANA || (fAllowJavaEncodings && !validJava)) { |
| fErrorReporter.reportError(XMLMessageFormatter.XML_DOMAIN, |
| "EncodingDeclInvalid", |
| new Object[] { encoding }, |
| XMLErrorReporter.SEVERITY_FATAL_ERROR); |
| // NOTE: AndyH suggested that, on failure, we use ISO Latin 1 |
| // because every byte is a valid ISO Latin 1 character. |
| // It may not translate correctly but if we failed on |
| // the encoding anyway, then we're expecting the content |
| // of the document to be bad. This will just prevent an |
| // invalid UTF-8 sequence to be detected. This is only |
| // important when continue-after-fatal-error is turned |
| // on. -Ac |
| return createLatin1Reader(inputStream); |
| } |
| |
| // try to use a Java reader |
| String javaEncoding = EncodingMap.getIANA2JavaMapping(ENCODING); |
| if (javaEncoding == null) { |
| if (fAllowJavaEncodings) { |
| javaEncoding = encoding; |
| } |
| else { |
| fErrorReporter.reportError(XMLMessageFormatter.XML_DOMAIN, |
| "EncodingDeclInvalid", |
| new Object[] { encoding }, |
| XMLErrorReporter.SEVERITY_FATAL_ERROR); |
| // see comment above. |
| return createLatin1Reader(inputStream); |
| } |
| } |
| else if (javaEncoding.equals("ASCII")) { |
| return createASCIIReader(inputStream); |
| } |
| else if (javaEncoding.equals("ISO8859_1")) { |
| return createLatin1Reader(inputStream); |
| } |
| if (DEBUG_ENCODINGS) { |
| System.out.print("$$$ creating Java InputStreamReader: encoding="+javaEncoding); |
| if (javaEncoding == encoding) { |
| System.out.print(" (IANA encoding)"); |
| } |
| System.out.println(); |
| } |
| return new InputStreamReader(inputStream, javaEncoding); |
| |
| } // createReader(InputStream,String, Boolean): Reader |
| |
| /** Create a new UTF-8 reader from the InputStream. **/ |
| private Reader createUTF8Reader(InputStream stream) { |
| if (DEBUG_ENCODINGS) { |
| System.out.println("$$$ creating UTF8Reader"); |
| } |
| if (fTempByteBuffer == null) { |
| fTempByteBuffer = fSmallByteBufferPool.getBuffer(); |
| } |
| return new UTF8Reader(stream, |
| fTempByteBuffer, |
| fErrorReporter.getMessageFormatter(XMLMessageFormatter.XML_DOMAIN), |
| fErrorReporter.getLocale()); |
| } // createUTF8Reader(InputStream):Reader |
| |
| /** Create a new UTF-16 reader from the InputStream. **/ |
| private Reader createUTF16Reader(InputStream stream, boolean isBigEndian) { |
| if (DEBUG_ENCODINGS) { |
| System.out.println("$$$ creating UTF16Reader"); |
| } |
| if (fTempByteBuffer == null) { |
| fTempByteBuffer = fLargeByteBufferPool.getBuffer(); |
| } |
| // The cached buffer is too small, we need a larger one. |
| else if (fTempByteBuffer.length == fBufferSize) { |
| fSmallByteBufferPool.returnBuffer(fTempByteBuffer); |
| fTempByteBuffer = fLargeByteBufferPool.getBuffer(); |
| } |
| return new UTF16Reader(stream, |
| fTempByteBuffer, |
| isBigEndian, |
| fErrorReporter.getMessageFormatter(XMLMessageFormatter.XML_DOMAIN), |
| fErrorReporter.getLocale()); |
| } // createUTF16Reader(InputStream):Reader |
| |
| /** Create a new ASCII reader from the InputStream. **/ |
| private Reader createASCIIReader(InputStream stream) { |
| if (DEBUG_ENCODINGS) { |
| System.out.println("$$$ creating ASCIIReader"); |
| } |
| if (fTempByteBuffer == null) { |
| fTempByteBuffer = fSmallByteBufferPool.getBuffer(); |
| } |
| return new ASCIIReader(stream, |
| fTempByteBuffer, |
| fErrorReporter.getMessageFormatter(XMLMessageFormatter.XML_DOMAIN), |
| fErrorReporter.getLocale()); |
| } // createASCIIReader(InputStream):Reader |
| |
| /** Create a new ISO-8859-1 reader from the InputStream. **/ |
| private Reader createLatin1Reader(InputStream stream) { |
| if (DEBUG_ENCODINGS) { |
| System.out.println("$$$ creating Latin1Reader"); |
| } |
| if (fTempByteBuffer == null) { |
| fTempByteBuffer = fSmallByteBufferPool.getBuffer(); |
| } |
| return new Latin1Reader(stream, fTempByteBuffer); |
| } // createLatin1Reader(InputStream):Reader |
| |
| // |
| // Protected static methods |
| // |
| |
| /** |
| * Fixes a platform dependent filename to standard URI form. |
| * |
| * @param str The string to fix. |
| * |
| * @return Returns the fixed URI string. |
| */ |
| protected static String fixURI(String str) { |
| |
| // handle platform dependent strings |
| str = str.replace(java.io.File.separatorChar, '/'); |
| |
| StringBuffer sb = null; |
| |
| // Windows fix |
| if (str.length() >= 2) { |
| char ch1 = str.charAt(1); |
| // change "C:blah" to "file:///C:blah" |
| if (ch1 == ':') { |
| char ch0 = Character.toUpperCase(str.charAt(0)); |
| if (ch0 >= 'A' && ch0 <= 'Z') { |
| sb = new StringBuffer(str.length() + 8); |
| sb.append("file:///"); |
| } |
| } |
| // change "//blah" to "file://blah" |
| else if (ch1 == '/' && str.charAt(0) == '/') { |
| sb = new StringBuffer(str.length() + 5); |
| sb.append("file:"); |
| } |
| } |
| |
| int pos = str.indexOf(' '); |
| // there is no space in the string |
| // we just append "str" to the end of sb |
| if (pos < 0) { |
| if (sb != null) { |
| sb.append(str); |
| str = sb.toString(); |
| } |
| } |
| // otherwise, convert all ' ' to "%20". |
| // Note: the following algorithm might not be very performant, |
| // but people who want to use invalid URI's have to pay the price. |
| else { |
| if (sb == null) |
| sb = new StringBuffer(str.length()); |
| // put characters before ' ' into the string buffer |
| for (int i = 0; i < pos; i++) |
| sb.append(str.charAt(i)); |
| // and %20 for the space |
| sb.append("%20"); |
| // for the remamining part, also convert ' ' to "%20". |
| for (int i = pos+1; i < str.length(); i++) { |
| if (str.charAt(i) == ' ') |
| sb.append("%20"); |
| else |
| sb.append(str.charAt(i)); |
| } |
| str = sb.toString(); |
| } |
| |
| // done |
| return str; |
| |
| } // fixURI(String):String |
| |
| // |
| // Package visible methods |
| // |
| |
| /** |
| * Returns the hashtable of declared entities. |
| * <p> |
| * <strong>REVISIT:</strong> |
| * This should be done the "right" way by designing a better way to |
| * enumerate the declared entities. For now, this method is needed |
| * by the constructor that takes an XMLEntityManager parameter. |
| */ |
| Hashtable getDeclaredEntities() { |
| return fEntities; |
| } // getDeclaredEntities():Hashtable |
| |
| /** Prints the contents of the buffer. */ |
| static final void print(ScannedEntity currentEntity) { |
| if (DEBUG_BUFFER) { |
| if (currentEntity != null) { |
| System.out.print('['); |
| System.out.print(currentEntity.count); |
| System.out.print(' '); |
| System.out.print(currentEntity.position); |
| if (currentEntity.count > 0) { |
| System.out.print(" \""); |
| for (int i = 0; i < currentEntity.count; i++) { |
| if (i == currentEntity.position) { |
| System.out.print('^'); |
| } |
| char c = currentEntity.ch[i]; |
| switch (c) { |
| case '\n': { |
| System.out.print("\\n"); |
| break; |
| } |
| case '\r': { |
| System.out.print("\\r"); |
| break; |
| } |
| case '\t': { |
| System.out.print("\\t"); |
| break; |
| } |
| case '\\': { |
| System.out.print("\\\\"); |
| break; |
| } |
| default: { |
| System.out.print(c); |
| } |
| } |
| } |
| if (currentEntity.position == currentEntity.count) { |
| System.out.print('^'); |
| } |
| System.out.print('"'); |
| } |
| System.out.print(']'); |
| System.out.print(" @ "); |
| System.out.print(currentEntity.lineNumber); |
| System.out.print(','); |
| System.out.print(currentEntity.columnNumber); |
| } |
| else { |
| System.out.print("*NO CURRENT ENTITY*"); |
| } |
| } |
| } // print(ScannedEntity) |
| |
| // |
| // Classes |
| // |
| |
| /** |
| * Entity information. |
| * |
| * @xerces.internal |
| * |
| * @author Andy Clark, IBM |
| */ |
| public static abstract class Entity { |
| |
| // |
| // Data |
| // |
| |
| /** Entity name. */ |
| public String name; |
| |
| // whether this entity's declaration was found in the internal |
| // or external subset |
| public boolean inExternalSubset; |
| |
| // |
| // Constructors |
| // |
| |
| /** Default constructor. */ |
| public Entity() { |
| clear(); |
| } // <init>() |
| |
| /** Constructs an entity. */ |
| public Entity(String name, boolean inExternalSubset) { |
| this.name = name; |
| this.inExternalSubset = inExternalSubset; |
| } // <init>(String) |
| |
| // |
| // Public methods |
| // |
| |
| /** Returns true if this entity was declared in the external subset. */ |
| public boolean isEntityDeclInExternalSubset () { |
| return inExternalSubset; |
| } |
| |
| /** Returns true if this is an external entity. */ |
| public abstract boolean isExternal(); |
| |
| /** Returns true if this is an unparsed entity. */ |
| public abstract boolean isUnparsed(); |
| |
| /** Clears the entity. */ |
| public void clear() { |
| name = null; |
| inExternalSubset = false; |
| } // clear() |
| |
| /** Sets the values of the entity. */ |
| public void setValues(Entity entity) { |
| name = entity.name; |
| inExternalSubset = entity.inExternalSubset; |
| } // setValues(Entity) |
| |
| } // class Entity |
| |
| /** |
| * Internal entity. |
| * |
| * @xerces.internal |
| * |
| * @author Andy Clark, IBM |
| */ |
| protected static class InternalEntity |
| extends Entity { |
| |
| // |
| // Data |
| // |
| |
| /** Text value of entity. */ |
| public String text; |
| |
| /** Count of direct and indirect references to parameter entities in the value of the entity. */ |
| public int paramEntityRefs; |
| |
| // |
| // Constructors |
| // |
| |
| /** Default constructor. */ |
| public InternalEntity() { |
| clear(); |
| } // <init>() |
| |
| /** Constructs an internal entity. */ |
| public InternalEntity(String name, String text, boolean inExternalSubset) { |
| super(name,inExternalSubset); |
| this.text = text; |
| } // <init>(String,String) |
| |
| /** Constructs an internal entity. */ |
| public InternalEntity(String name, String text, boolean inExternalSubset, int paramEntityRefs) { |
| this(name, text, inExternalSubset); |
| this.paramEntityRefs = paramEntityRefs; |
| } // <init>(String,String,int) |
| |
| // |
| // Entity methods |
| // |
| |
| /** Returns true if this is an external entity. */ |
| public final boolean isExternal() { |
| return false; |
| } // isExternal():boolean |
| |
| /** Returns true if this is an unparsed entity. */ |
| public final boolean isUnparsed() { |
| return false; |
| } // isUnparsed():boolean |
| |
| /** Clears the entity. */ |
| public void clear() { |
| super.clear(); |
| text = null; |
| } // clear() |
| |
| /** Sets the values of the entity. */ |
| public void setValues(Entity entity) { |
| super.setValues(entity); |
| text = null; |
| } // setValues(Entity) |
| |
| /** Sets the values of the entity. */ |
| public void setValues(InternalEntity entity) { |
| super.setValues(entity); |
| text = entity.text; |
| } // setValues(InternalEntity) |
| |
| } // class InternalEntity |
| |
| /** |
| * External entity. |
| * |
| * @xerces.internal |
| * |
| * @author Andy Clark, IBM |
| */ |
| protected static class ExternalEntity |
| extends Entity { |
| |
| // |
| // Data |
| // |
| |
| /** container for all relevant entity location information. */ |
| public XMLResourceIdentifier entityLocation; |
| |
| /** Notation name for unparsed entity. */ |
| public String notation; |
| |
| // |
| // Constructors |
| // |
| |
| /** Default constructor. */ |
| public ExternalEntity() { |
| clear(); |
| } // <init>() |
| |
| /** Constructs an internal entity. */ |
| public ExternalEntity(String name, XMLResourceIdentifier entityLocation, |
| String notation, boolean inExternalSubset) { |
| super(name,inExternalSubset); |
| this.entityLocation = entityLocation; |
| this.notation = notation; |
| } // <init>(String,XMLResourceIdentifier, String) |
| |
| // |
| // Entity methods |
| // |
| |
| /** Returns true if this is an external entity. */ |
| public final boolean isExternal() { |
| return true; |
| } // isExternal():boolean |
| |
| /** Returns true if this is an unparsed entity. */ |
| public final boolean isUnparsed() { |
| return notation != null; |
| } // isUnparsed():boolean |
| |
| /** Clears the entity. */ |
| public void clear() { |
| super.clear(); |
| entityLocation = null; |
| notation = null; |
| } // clear() |
| |
| /** Sets the values of the entity. */ |
| public void setValues(Entity entity) { |
| super.setValues(entity); |
| entityLocation = null; |
| notation = null; |
| } // setValues(Entity) |
| |
| /** Sets the values of the entity. */ |
| public void setValues(ExternalEntity entity) { |
| super.setValues(entity); |
| entityLocation = entity.entityLocation; |
| notation = entity.notation; |
| } // setValues(ExternalEntity) |
| |
| } // class ExternalEntity |
| |
| /** |
| * Entity state. |
| * |
| * @xerces.internal |
| * |
| * @author Andy Clark, IBM |
| */ |
| public class ScannedEntity |
| extends Entity { |
| |
| // |
| // Data |
| // |
| |
| // i/o |
| |
| /** Input stream. */ |
| public InputStream stream; |
| |
| /** Reader. */ |
| public Reader reader; |
| |
| // locator information |
| |
| /** entity location information */ |
| public XMLResourceIdentifier entityLocation; |
| |
| /** Line number. */ |
| public int lineNumber = 1; |
| |
| /** Column number. */ |
| public int columnNumber = 1; |
| |
| // encoding |
| |
| /** Auto-detected encoding. */ |
| public String encoding; |
| |
| /** |
| * Encoding has been set externally, for example |
| * using a SAX InputSource or a DOM LSInput. |
| */ |
| boolean externallySpecifiedEncoding = false; |
| |
| // version |
| |
| /** XML version. **/ |
| public String xmlVersion = "1.0"; |
| |
| // status |
| |
| /** True if in a literal. */ |
| public boolean literal; |
| |
| // whether this is an external or internal scanned entity |
| public boolean isExternal; |
| |
| // buffer |
| |
| /** Character buffer. */ |
| public char[] ch = null; |
| |
| /** Position in character buffer. */ |
| public int position; |
| |
| /** Base character offset for computing absolute character offset. */ |
| public int baseCharOffset; |
| |
| /** Start position in character buffer. */ |
| public int startPosition; |
| |
| /** Count of characters in buffer. */ |
| public int count; |
| |
| // to allow the reader/inputStream to behave efficiently: |
| public boolean mayReadChunks; |
| |
| /** Character buffer container. */ |
| private CharacterBuffer fCharacterBuffer; |
| |
| /** Byte buffer. */ |
| private byte [] fByteBuffer; |
| |
| // |
| // Constructors |
| // |
| |
| /** Constructs a scanned entity. */ |
| public ScannedEntity(String name, |
| XMLResourceIdentifier entityLocation, |
| InputStream stream, Reader reader, byte [] byteBuffer, |
| String encoding, boolean literal, boolean mayReadChunks, boolean isExternal) { |
| super(name,XMLEntityManager.this.fInExternalSubset); |
| this.entityLocation = entityLocation; |
| this.stream = stream; |
| this.reader = reader; |
| this.encoding = encoding; |
| this.literal = literal; |
| this.mayReadChunks = mayReadChunks; |
| this.isExternal = isExternal; |
| this.fCharacterBuffer = fCharacterBufferPool.getBuffer(isExternal); |
| this.ch = fCharacterBuffer.ch; |
| this.fByteBuffer = byteBuffer; |
| } // <init>(StringXMLResourceIdentifier,InputStream,Reader,String,boolean, boolean) |
| |
| // |
| // Entity methods |
| // |
| |
| /** Returns true if this is an external entity. */ |
| public final boolean isExternal() { |
| return isExternal; |
| } // isExternal():boolean |
| |
| /** Returns true if this is an unparsed entity. */ |
| public final boolean isUnparsed() { |
| return false; |
| } // isUnparsed():boolean |
| |
| public void setReader(InputStream stream, String encoding, Boolean isBigEndian) throws IOException { |
| fTempByteBuffer = fByteBuffer; |
| reader = createReader(stream, encoding, isBigEndian); |
| fByteBuffer = fTempByteBuffer; |
| } |
| |
| // return the expanded system ID of the |
| // first external entity on the stack, null |
| // otherwise. |
| public String getExpandedSystemId() { |
| |
| // search for the first external entity on the stack |
| int size = fEntityStack.size(); |
| for (int i = size - 1; i >= 0; --i) { |
| ScannedEntity externalEntity = |
| (ScannedEntity)fEntityStack.elementAt(i); |
| |
| if (externalEntity.entityLocation != null && |
| externalEntity.entityLocation.getExpandedSystemId() != null) { |
| return externalEntity.entityLocation.getExpandedSystemId(); |
| } |
| } |
| return null; |
| } |
| |
| // return literal systemId of |
| // nearest external entity |
| public String getLiteralSystemId() { |
| // search for the first external entity on the stack |
| int size = fEntityStack.size(); |
| for (int i = size - 1; i >= 0; --i) { |
| ScannedEntity externalEntity = |
| (ScannedEntity)fEntityStack.elementAt(i); |
| |
| if (externalEntity.entityLocation != null && |
| externalEntity.entityLocation.getLiteralSystemId() != null) { |
| return externalEntity.entityLocation.getLiteralSystemId(); |
| } |
| } |
| return null; |
| } |
| |
| // return line number of position in most |
| // recent external entity |
| public int getLineNumber() { |
| // search for the first external entity on the stack |
| int size = fEntityStack.size(); |
| for (int i = size - 1; i >= 0 ; --i) { |
| ScannedEntity firstExternalEntity = (ScannedEntity)fEntityStack.elementAt(i); |
| if (firstExternalEntity.isExternal()) { |
| return firstExternalEntity.lineNumber; |
| } |
| } |
| return -1; |
| } |
| |
| // return column number of position in most |
| // recent external entity |
| public int getColumnNumber() { |
| // search for the first external entity on the stack |
| int size = fEntityStack.size(); |
| for (int i = size - 1; i >= 0; --i) { |
| ScannedEntity firstExternalEntity = (ScannedEntity)fEntityStack.elementAt(i); |
| if (firstExternalEntity.isExternal()) { |
| return firstExternalEntity.columnNumber; |
| } |
| } |
| return -1; |
| } |
| |
| // return character offset of position in most |
| // recent external entity |
| public int getCharacterOffset() { |
| // search for the first external entity on the stack |
| int size = fEntityStack.size(); |
| for (int i = size - 1; i >= 0; --i) { |
| ScannedEntity firstExternalEntity = (ScannedEntity)fEntityStack.elementAt(i); |
| if (firstExternalEntity.isExternal()) { |
| return firstExternalEntity.baseCharOffset + (firstExternalEntity.position - firstExternalEntity.startPosition); |
| } |
| } |
| return -1; |
| } |
| |
| // return encoding of most recent external entity |
| public String getEncoding() { |
| // search for the first external entity on the stack |
| int size = fEntityStack.size(); |
| for (int i = size - 1; i >= 0; --i) { |
| ScannedEntity firstExternalEntity = (ScannedEntity)fEntityStack.elementAt(i); |
| if (firstExternalEntity.isExternal()) { |
| return firstExternalEntity.encoding; |
| } |
| } |
| return null; |
| } |
| |
| // return xml version of most recent external entity |
| public String getXMLVersion() { |
| // search for the first external entity on the stack |
| int size = fEntityStack.size(); |
| for (int i = size - 1; i >= 0; --i) { |
| ScannedEntity firstExternalEntity = (ScannedEntity)fEntityStack.elementAt(i); |
| if (firstExternalEntity.isExternal()) { |
| return firstExternalEntity.xmlVersion; |
| } |
| } |
| return null; |
| } |
| |
| /** Returns whether the encoding of this entity was externally specified. **/ |
| public boolean isEncodingExternallySpecified() { |
| return externallySpecifiedEncoding; |
| } |
| |
| /** Sets whether the encoding of this entity was externally specified. **/ |
| public void setEncodingExternallySpecified(boolean value) { |
| externallySpecifiedEncoding = value; |
| } |
| |
| // |
| // Object methods |
| // |
| |
| /** Returns a string representation of this object. */ |
| public String toString() { |
| |
| StringBuffer str = new StringBuffer(); |
| str.append("name=\"").append(name).append('"'); |
| str.append(",ch="); |
| str.append(ch); |
| str.append(",position=").append(position); |
| str.append(",count=").append(count); |
| str.append(",baseCharOffset=").append(baseCharOffset); |
| str.append(",startPosition=").append(startPosition); |
| return str.toString(); |
| |
| } // toString():String |
| |
| } // class ScannedEntity |
| |
| /** |
| * Information about auto-detectable encodings. |
| * |
| * @xerces.internal |
| * |
| * @author Michael Glavassevich, IBM |
| */ |
| private static class EncodingInfo { |
| |
| /** UTF-8 **/ |
| public static final EncodingInfo UTF_8 = new EncodingInfo("UTF-8", null, false); |
| |
| /** UTF-8, with BOM **/ |
| public static final EncodingInfo UTF_8_WITH_BOM = new EncodingInfo("UTF-8", null, true); |
| |
| /** UTF-16, big-endian **/ |
| public static final EncodingInfo UTF_16_BIG_ENDIAN = new EncodingInfo("UTF-16BE", "UTF-16", Boolean.TRUE, false); |
| |
| /** UTF-16, big-endian with BOM **/ |
| public static final EncodingInfo UTF_16_BIG_ENDIAN_WITH_BOM = new EncodingInfo("UTF-16BE", "UTF-16", Boolean.TRUE, true); |
| |
| /** UTF-16, little-endian **/ |
| public static final EncodingInfo UTF_16_LITTLE_ENDIAN = new EncodingInfo("UTF-16LE", "UTF-16", Boolean.FALSE, false); |
| |
| /** UTF-16, little-endian with BOM **/ |
| public static final EncodingInfo UTF_16_LITTLE_ENDIAN_WITH_BOM = new EncodingInfo("UTF-16LE", "UTF-16", Boolean.FALSE, true); |
| |
| /** UCS-4, big-endian **/ |
| public static final EncodingInfo UCS_4_BIG_ENDIAN = new EncodingInfo("ISO-10646-UCS-4", Boolean.TRUE, false); |
| |
| /** UCS-4, little-endian **/ |
| public static final EncodingInfo UCS_4_LITTLE_ENDIAN = new EncodingInfo("ISO-10646-UCS-4", Boolean.FALSE, false); |
| |
| /** UCS-4, unusual byte-order (2143) or (3412) **/ |
| public static final EncodingInfo UCS_4_UNUSUAL_BYTE_ORDER = new EncodingInfo("ISO-10646-UCS-4", null, false); |
| |
| /** EBCDIC **/ |
| public static final EncodingInfo EBCDIC = new EncodingInfo("CP037", null, false); |
| |
| public final String autoDetectedEncoding; |
| public final String readerEncoding; |
| public final Boolean isBigEndian; |
| public final boolean hasBOM; |
| |
| private EncodingInfo(String autoDetectedEncoding, Boolean isBigEndian, boolean hasBOM) { |
| this(autoDetectedEncoding, autoDetectedEncoding, isBigEndian, hasBOM); |
| } // <init>(String,Boolean,boolean) |
| |
| private EncodingInfo(String autoDetectedEncoding, String readerEncoding, Boolean isBigEndian, boolean hasBOM) { |
| this.autoDetectedEncoding = autoDetectedEncoding; |
| this.readerEncoding = readerEncoding; |
| this.isBigEndian = isBigEndian; |
| this.hasBOM = hasBOM; |
| } // <init>(String,String,Boolean,boolean) |
| |
| } // class EncodingInfo |
| |
| /** |
| * Pool of byte buffers for the java.io.Readers. |
| * |
| * @xerces.internal |
| * |
| * @author Michael Glavassevich, IBM |
| */ |
| private static final class ByteBufferPool { |
| |
| private static final int DEFAULT_POOL_SIZE = 3; |
| |
| private int fPoolSize; |
| private int fBufferSize; |
| private byte[][] fByteBufferPool; |
| private int fDepth; |
| |
| public ByteBufferPool(int bufferSize) { |
| this(DEFAULT_POOL_SIZE, bufferSize); |
| } |
| |
| public ByteBufferPool(int poolSize, int bufferSize) { |
| fPoolSize = poolSize; |
| fBufferSize = bufferSize; |
| fByteBufferPool = new byte[fPoolSize][]; |
| fDepth = 0; |
| } |
| |
| /** Retrieves a byte buffer from the pool. **/ |
| public byte[] getBuffer() { |
| return (fDepth > 0) ? fByteBufferPool[--fDepth] : new byte[fBufferSize]; |
| } |
| |
| /** Returns byte buffer to pool. **/ |
| public void returnBuffer(byte[] buffer) { |
| if (fDepth < fByteBufferPool.length) { |
| fByteBufferPool[fDepth++] = buffer; |
| } |
| } |
| |
| /** Sets the size of the buffers and dumps the old pool. **/ |
| public void setBufferSize(int bufferSize) { |
| fBufferSize = bufferSize; |
| fByteBufferPool = new byte[fPoolSize][]; |
| fDepth = 0; |
| } |
| } |
| |
| /** |
| * Buffer used in entity manager to reuse character arrays instead |
| * of creating new ones every time. |
| * |
| * @xerces.internal |
| * |
| * @author Ankit Pasricha, IBM |
| */ |
| private static final class CharacterBuffer { |
| |
| /** character buffer */ |
| private final char[] ch; |
| |
| /** whether the buffer is for an external or internal scanned entity */ |
| private final boolean isExternal; |
| |
| public CharacterBuffer(boolean isExternal, int size) { |
| this.isExternal = isExternal; |
| ch = new char[size]; |
| } |
| } |
| |
| /** |
| * Stores a number of character buffers and provides it to the entity |
| * manager to use when an entity is seen. |
| * |
| * @xerces.internal |
| * |
| * @author Ankit Pasricha, IBM |
| */ |
| private static final class CharacterBufferPool { |
| |
| private static final int DEFAULT_POOL_SIZE = 3; |
| |
| private CharacterBuffer[] fInternalBufferPool; |
| private CharacterBuffer[] fExternalBufferPool; |
| |
| private int fExternalBufferSize; |
| private int fInternalBufferSize; |
| private int fPoolSize; |
| |
| private int fInternalTop; |
| private int fExternalTop; |
| |
| public CharacterBufferPool(int externalBufferSize, int internalBufferSize) { |
| this(DEFAULT_POOL_SIZE, externalBufferSize, internalBufferSize); |
| } |
| |
| public CharacterBufferPool(int poolSize, int externalBufferSize, int internalBufferSize) { |
| fExternalBufferSize = externalBufferSize; |
| fInternalBufferSize = internalBufferSize; |
| fPoolSize = poolSize; |
| init(); |
| } |
| |
| /** Initializes buffer pool. **/ |
| private void init() { |
| fInternalBufferPool = new CharacterBuffer[fPoolSize]; |
| fExternalBufferPool = new CharacterBuffer[fPoolSize]; |
| fInternalTop = -1; |
| fExternalTop = -1; |
| } |
| |
| /** Retrieves buffer from pool. **/ |
| public CharacterBuffer getBuffer(boolean external) { |
| if (external) { |
| if (fExternalTop > -1) { |
| return (CharacterBuffer)fExternalBufferPool[fExternalTop--]; |
| } |
| else { |
| return new CharacterBuffer(true, fExternalBufferSize); |
| } |
| } |
| else { |
| if (fInternalTop > -1) { |
| return (CharacterBuffer)fInternalBufferPool[fInternalTop--]; |
| } |
| else { |
| return new CharacterBuffer(false, fInternalBufferSize); |
| } |
| } |
| } |
| |
| /** Returns buffer to pool. **/ |
| public void returnBuffer(CharacterBuffer buffer) { |
| if (buffer.isExternal) { |
| if (fExternalTop < fExternalBufferPool.length - 1) { |
| fExternalBufferPool[++fExternalTop] = buffer; |
| } |
| } |
| else if (fInternalTop < fInternalBufferPool.length - 1) { |
| fInternalBufferPool[++fInternalTop] = buffer; |
| } |
| } |
| |
| /** Sets the size of external buffers and dumps the old pool. **/ |
| public void setExternalBufferSize(int bufferSize) { |
| fExternalBufferSize = bufferSize; |
| fExternalBufferPool = new CharacterBuffer[fPoolSize]; |
| fExternalTop = -1; |
| } |
| } |
| |
| /** |
| * This class wraps the byte inputstreams we're presented with. |
| * We need it because java.io.InputStreams don't provide |
| * functionality to reread processed bytes, and they have a habit |
| * of reading more than one character when you call their read() |
| * methods. This means that, once we discover the true (declared) |
| * encoding of a document, we can neither backtrack to read the |
| * whole doc again nor start reading where we are with a new |
| * reader. |
| * |
| * This class allows rewinding an inputStream by allowing a mark |
| * to be set, and the stream reset to that position. <strong>The |
| * class assumes that it needs to read one character per |
| * invocation when it's read() method is inovked, but uses the |
| * underlying InputStream's read(char[], offset length) method--it |
| * won't buffer data read this way!</strong> |
| * |
| * @xerces.internal |
| * |
| * @author Neil Graham, IBM |
| * @author Glenn Marcy, IBM |
| */ |
| protected final class RewindableInputStream extends InputStream { |
| |
| private InputStream fInputStream; |
| private byte[] fData; |
| private int fStartOffset; |
| private int fEndOffset; |
| private int fOffset; |
| private int fLength; |
| private int fMark; |
| |
| public RewindableInputStream(InputStream is) { |
| fData = new byte[DEFAULT_XMLDECL_BUFFER_SIZE]; |
| fInputStream = is; |
| fStartOffset = 0; |
| fEndOffset = -1; |
| fOffset = 0; |
| fLength = 0; |
| fMark = 0; |
| } |
| |
| public void setStartOffset(int offset) { |
| fStartOffset = offset; |
| } |
| |
| public void rewind() { |
| fOffset = fStartOffset; |
| } |
| |
| public int readAndBuffer() throws IOException { |
| if (fOffset == fData.length) { |
| byte[] newData = new byte[fOffset << 1]; |
| System.arraycopy(fData, 0, newData, 0, fOffset); |
| fData = newData; |
| } |
| final int b = fInputStream.read(); |
| if (b == -1) { |
| fEndOffset = fOffset; |
| return -1; |
| } |
| fData[fLength++] = (byte)b; |
| fOffset++; |
| return b & 0xff; |
| } |
| |
| public int read() throws IOException { |
| if (fOffset < fLength) { |
| return fData[fOffset++] & 0xff; |
| } |
| if (fOffset == fEndOffset) { |
| return -1; |
| } |
| if (fCurrentEntity.mayReadChunks) { |
| return fInputStream.read(); |
| } |
| return readAndBuffer(); |
| } |
| |
| public int read(byte[] b, int off, int len) throws IOException { |
| final int bytesLeft = fLength - fOffset; |
| if (bytesLeft == 0) { |
| if (fOffset == fEndOffset) { |
| return -1; |
| } |
| // better get some more for the voracious reader... |
| if (fCurrentEntity.mayReadChunks) { |
| return fInputStream.read(b, off, len); |
| } |
| int returnedVal = readAndBuffer(); |
| if (returnedVal == -1) { |
| fEndOffset = fOffset; |
| return -1; |
| } |
| b[off] = (byte)returnedVal; |
| return 1; |
| } |
| if (len < bytesLeft) { |
| if (len <= 0) { |
| return 0; |
| } |
| } |
| else { |
| len = bytesLeft; |
| } |
| if (b != null) { |
| System.arraycopy(fData, fOffset, b, off, len); |
| } |
| fOffset += len; |
| return len; |
| } |
| |
| public long skip(long n) |
| throws IOException |
| { |
| int bytesLeft; |
| if (n <= 0) { |
| return 0; |
| } |
| bytesLeft = fLength - fOffset; |
| if (bytesLeft == 0) { |
| if (fOffset == fEndOffset) { |
| return 0; |
| } |
| return fInputStream.skip(n); |
| } |
| if (n <= bytesLeft) { |
| fOffset += n; |
| return n; |
| } |
| fOffset += bytesLeft; |
| if (fOffset == fEndOffset) { |
| return bytesLeft; |
| } |
| n -= bytesLeft; |
| /* |
| * In a manner of speaking, when this class isn't permitting more |
| * than one byte at a time to be read, it is "blocking". The |
| * available() method should indicate how much can be read without |
| * blocking, so while we're in this mode, it should only indicate |
| * that bytes in its buffer are available; otherwise, the result of |
| * available() on the underlying InputStream is appropriate. |
| */ |
| return fInputStream.skip(n) + bytesLeft; |
| } |
| |
| public int available() throws IOException { |
| final int bytesLeft = fLength - fOffset; |
| if (bytesLeft == 0) { |
| if (fOffset == fEndOffset) { |
| return -1; |
| } |
| return fCurrentEntity.mayReadChunks ? fInputStream.available() |
| : 0; |
| } |
| return bytesLeft; |
| } |
| |
| public void mark(int howMuch) { |
| fMark = fOffset; |
| } |
| |
| public void reset() { |
| fOffset = fMark; |
| } |
| |
| public boolean markSupported() { |
| return true; |
| } |
| |
| public void close() throws IOException { |
| if (fInputStream != null) { |
| fInputStream.close(); |
| fInputStream = null; |
| } |
| } |
| } // end of RewindableInputStream class |
| |
| } // class XMLEntityManager |