All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.xerces.impl.XMLEntityManager Maven / Gradle / Ivy

/*
 * 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;

/**
 * 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.
 * 

* This component requires the following features and properties from the * component manager that uses it: *

    *
  • http://xml.org/sax/features/validation
  • *
  • http://xml.org/sax/features/external-general-entities
  • *
  • http://xml.org/sax/features/external-parameter-entities
  • *
  • http://apache.org/xml/features/allow-java-encodings
  • *
  • http://apache.org/xml/properties/internal/symbol-table
  • *
  • http://apache.org/xml/properties/internal/error-reporter
  • *
  • http://apache.org/xml/properties/internal/entity-resolver
  • *
* * @xerces.internal * * @author Andy Clark, IBM * @author Arnaud Le Hors, IBM * * @version $Id: XMLEntityManager.java 1513560 2013-08-13 15:42:54Z mrglavas $ */ 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); } // () /** * Constructs an entity manager that shares the specified entity * declarations during each parse. *

* REVISIT: 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); } // (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. *

* Note: This method ignores subsequent entity * declarations. *

* Note: 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. *

* Note: This method ignores subsequent entity * declarations. *

* Note: 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. *

* Note: This method ignores subsequent entity * declarations. *

* Note: 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 the entity being added 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. *

* Note: This method ignores subsequent entity * declarations. *

* Note: 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. *

* 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", ""); addInternalEntity("balanced-element", ""); addInternalEntity("balanced-element-with-text", "Hello, World"); addInternalEntity("balanced-element-with-entity", "&text;"); addInternalEntity("unbalanced-entity", ""); addInternalEntity("recursive-entity", "&recursive-entity2;"); addInternalEntity("recursive-entity2", "&recursive-entity3;"); addInternalEntity("recursive-entity3", "&recursive-entity;"); 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. *

* Note: Components should silently ignore features * that do not affect the operation of the component. * * @param featureId The feature identifier. * @param state The state of the feature. * * @throws SAXNotRecognizedException The component should not throw * this exception. * @throws SAXNotSupportedException The component should not throw * this exception. */ 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. *

* Note: Components should silently ignore properties * that do not affect the operation of the component. * * @param propertyId The property identifier. * @param value The value of the property. * * @throws SAXNotRecognizedException The component should not throw * this exception. * @throws SAXNotSupportedException The component should not throw * this exception. */ 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. *

* REVISIT: * 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(); } // () /** Constructs an entity. */ public Entity(String name, boolean inExternalSubset) { this.name = name; this.inExternalSubset = inExternalSubset; } // (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(); } // () /** Constructs an internal entity. */ public InternalEntity(String name, String text, boolean inExternalSubset) { super(name,inExternalSubset); this.text = text; } // (String,String) /** Constructs an internal entity. */ public InternalEntity(String name, String text, boolean inExternalSubset, int paramEntityRefs) { this(name, text, inExternalSubset); this.paramEntityRefs = paramEntityRefs; } // (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(); } // () /** Constructs an internal entity. */ public ExternalEntity(String name, XMLResourceIdentifier entityLocation, String notation, boolean inExternalSubset) { super(name,inExternalSubset); this.entityLocation = entityLocation; this.notation = notation; } // (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; } // (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); } // (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; } // (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. 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! * * @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





© 2015 - 2024 Weber Informatics LLC | Privacy Policy