org.apache.xerces.impl.XMLEntityManager Maven / Gradle / Ivy
Show all versions of cookcc Show documentation
/*
* 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 944964 2010-05-17 04:36:47Z 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.
*
* @see SymbolTable
*/
public void addInternalEntity(String name, String text) {
if (!fEntities.containsKey(name)) {
Entity entity = new InternalEntity(name, text, fInExternalSubset);
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)
/**
* 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++ > 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) {
EncodingInfo info = getEncodingInfo(b4, count);
encoding = info.encoding;
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 (encoding == "UTF-8") {
// UTF-8 BOM: 0xEF 0xBB 0xBF
stream.skip(3);
}
// It's also more efficient to consume the UTF-16 BOM.
else if (encoding == "UTF-16") {
// UTF-16 BE BOM: 0xFE 0xFF
// UTF-16 LE BOM: 0xFF 0xFE
stream.skip(2);
}
}
reader = createReader(stream, encoding, 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;
//
// 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)
//
// 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-16", Boolean.TRUE, false);
/** UTF-16, big-endian with BOM **/
public static final EncodingInfo UTF_16_BIG_ENDIAN_WITH_BOM = new EncodingInfo("UTF-16", Boolean.TRUE, true);
/** UTF-16, little-endian **/
public static final EncodingInfo UTF_16_LITTLE_ENDIAN = new EncodingInfo("UTF-16", Boolean.FALSE, false);
/** UTF-16, little-endian with BOM **/
public static final EncodingInfo UTF_16_LITTLE_ENDIAN_WITH_BOM = new EncodingInfo("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 encoding;
public final Boolean isBigEndian;
public final boolean hasBOM;
private EncodingInfo(String encoding, Boolean isBigEndian, boolean hasBOM) {
this.encoding = encoding;
this.isBigEndian = isBigEndian;
this.hasBOM = hasBOM;
} // (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