org.apache.xml.dtm.ref.sax2dtm.SAX2DTM 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.
*/
/*
* $Id: SAX2DTM.java 468653 2006-10-28 07:07:05Z minchau $
*/
package org.apache.xml.dtm.ref.sax2dtm;
import java.util.Hashtable;
import java.util.Vector;
import javax.xml.transform.Source;
import javax.xml.transform.SourceLocator;
import org.apache.xml.dtm.*;
import org.apache.xml.dtm.ref.*;
import org.apache.xml.utils.StringVector;
import org.apache.xml.utils.IntVector;
import org.apache.xml.utils.FastStringBuffer;
import org.apache.xml.utils.IntStack;
import org.apache.xml.utils.SuballocatedIntVector;
import org.apache.xml.utils.SystemIDResolver;
import org.apache.xml.utils.WrappedRuntimeException;
import org.apache.xml.utils.XMLString;
import org.apache.xml.utils.XMLStringFactory;
import org.apache.xml.res.XMLErrorResources;
import org.apache.xml.res.XMLMessages;
import org.xml.sax.*;
import org.xml.sax.ext.*;
/**
* This class implements a DTM that tends to be optimized more for speed than
* for compactness, that is constructed via SAX2 ContentHandler events.
*/
public class SAX2DTM extends DTMDefaultBaseIterators
implements EntityResolver, DTDHandler, ContentHandler, ErrorHandler,
DeclHandler, LexicalHandler
{
/** Set true to monitor SAX events and similar diagnostic info. */
private static final boolean DEBUG = false;
/**
* If we're building the model incrementally on demand, we need to
* be able to tell the source when to send us more data.
*
* Note that if this has not been set, and you attempt to read ahead
* of the current build point, we'll probably throw a null-pointer
* exception. We could try to wait-and-retry instead, as a very poor
* fallback, but that has all the known problems with multithreading
* on multiprocessors and we Don't Want to Go There.
*
* @see setIncrementalSAXSource
*/
private IncrementalSAXSource m_incrementalSAXSource = null;
/**
* All the character content, including attribute values, are stored in
* this buffer.
*
* %REVIEW% Should this have an option of being shared across DTMs?
* Sequentially only; not threadsafe... Currently, I think not.
*
* %REVIEW% Initial size was pushed way down to reduce weight of RTFs.
* pending reduction in number of RTF DTMs. Now that we're sharing a DTM
* between RTFs, and tail-pruning... consider going back to the larger/faster.
*
* Made protected rather than private so SAX2RTFDTM can access it.
*/
//private FastStringBuffer m_chars = new FastStringBuffer(13, 13);
protected FastStringBuffer m_chars;
/** This vector holds offset and length data.
*/
protected SuballocatedIntVector m_data;
/** The parent stack, needed only for construction.
* Made protected rather than private so SAX2RTFDTM can access it.
*/
transient protected IntStack m_parents;
/** The current previous node, needed only for construction time.
* Made protected rather than private so SAX2RTFDTM can access it.
*/
transient protected int m_previous = 0;
/** Namespace support, only relevent at construction time.
* Made protected rather than private so SAX2RTFDTM can access it.
*/
transient protected java.util.Vector m_prefixMappings =
new java.util.Vector();
/** Namespace support, only relevent at construction time.
* Made protected rather than private so SAX2RTFDTM can access it.
*/
transient protected IntStack m_contextIndexes;
/** Type of next characters() event within text block in prgress. */
transient protected int m_textType = DTM.TEXT_NODE;
/**
* Type of coalesced text block. See logic in the characters()
* method.
*/
transient protected int m_coalescedTextType = DTM.TEXT_NODE;
/** The SAX Document locator */
transient protected Locator m_locator = null;
/** The SAX Document system-id */
transient private String m_systemId = null;
/** We are inside the DTD. This is used for ignoring comments. */
transient protected boolean m_insideDTD = false;
/** Tree Walker for dispatchToEvents. */
protected DTMTreeWalker m_walker = new DTMTreeWalker();
/** pool of string values that come as strings. */
protected DTMStringPool m_valuesOrPrefixes;
/** End document has been reached.
* Made protected rather than private so SAX2RTFDTM can access it.
*/
protected boolean m_endDocumentOccured = false;
/** Data or qualified name values, one array element for each node. */
protected SuballocatedIntVector m_dataOrQName;
/**
* This table holds the ID string to node associations, for
* XML IDs.
*/
protected Hashtable m_idAttributes = new Hashtable();
/**
* fixed dom-style names.
*/
private static final String[] m_fixednames = { null,
null, // nothing, Element
null, "#text", // Attr, Text
"#cdata_section", null, // CDATA, EntityReference
null, null, // Entity, PI
"#comment", "#document", // Comment, Document
null, "#document-fragment", // Doctype, DocumentFragment
null }; // Notation
/**
* Vector of entities. Each record is composed of four Strings:
* publicId, systemID, notationName, and name.
*/
private Vector m_entities = null;
/** m_entities public ID offset. */
private static final int ENTITY_FIELD_PUBLICID = 0;
/** m_entities system ID offset. */
private static final int ENTITY_FIELD_SYSTEMID = 1;
/** m_entities notation name offset. */
private static final int ENTITY_FIELD_NOTATIONNAME = 2;
/** m_entities name offset. */
private static final int ENTITY_FIELD_NAME = 3;
/** Number of entries per record for m_entities. */
private static final int ENTITY_FIELDS_PER = 4;
/**
* The starting offset within m_chars for the text or
* CDATA_SECTION node currently being acumulated,
* or -1 if there is no text node in progress
*/
protected int m_textPendingStart = -1;
/**
* Describes whether information about document source location
* should be maintained or not.
*
* Made protected for access by SAX2RTFDTM.
*/
protected boolean m_useSourceLocationProperty = false;
/** Made protected for access by SAX2RTFDTM.
*/
protected StringVector m_sourceSystemId;
/** Made protected for access by SAX2RTFDTM.
*/
protected IntVector m_sourceLine;
/** Made protected for access by SAX2RTFDTM.
*/
protected IntVector m_sourceColumn;
/**
* Construct a SAX2DTM object using the default block size.
*
* @param mgr The DTMManager who owns this DTM.
* @param source the JAXP 1.1 Source object for this DTM.
* @param dtmIdentity The DTM identity ID for this DTM.
* @param whiteSpaceFilter The white space filter for this DTM, which may
* be null.
* @param xstringfactory XMLString factory for creating character content.
* @param doIndexing true if the caller considers it worth it to use
* indexing schemes.
*/
public SAX2DTM(DTMManager mgr, Source source, int dtmIdentity,
DTMWSFilter whiteSpaceFilter,
XMLStringFactory xstringfactory,
boolean doIndexing)
{
this(mgr, source, dtmIdentity, whiteSpaceFilter,
xstringfactory, doIndexing, DEFAULT_BLOCKSIZE, true, false);
}
/**
* Construct a SAX2DTM object ready to be constructed from SAX2
* ContentHandler events.
*
* @param mgr The DTMManager who owns this DTM.
* @param source the JAXP 1.1 Source object for this DTM.
* @param dtmIdentity The DTM identity ID for this DTM.
* @param whiteSpaceFilter The white space filter for this DTM, which may
* be null.
* @param xstringfactory XMLString factory for creating character content.
* @param doIndexing true if the caller considers it worth it to use
* indexing schemes.
* @param blocksize The block size of the DTM.
* @param usePrevsib true if we want to build the previous sibling node array.
* @param newNameTable true if we want to use a new ExpandedNameTable for this DTM.
*/
public SAX2DTM(DTMManager mgr, Source source, int dtmIdentity,
DTMWSFilter whiteSpaceFilter,
XMLStringFactory xstringfactory,
boolean doIndexing,
int blocksize,
boolean usePrevsib,
boolean newNameTable)
{
super(mgr, source, dtmIdentity, whiteSpaceFilter,
xstringfactory, doIndexing, blocksize, usePrevsib, newNameTable);
// %OPT% Use smaller sizes for all internal storage units when
// the blocksize is small. This reduces the cost of creating an RTF.
if (blocksize <= 64)
{
m_data = new SuballocatedIntVector(blocksize, DEFAULT_NUMBLOCKS_SMALL);
m_dataOrQName = new SuballocatedIntVector(blocksize, DEFAULT_NUMBLOCKS_SMALL);
m_valuesOrPrefixes = new DTMStringPool(16);
m_chars = new FastStringBuffer(7, 10);
m_contextIndexes = new IntStack(4);
m_parents = new IntStack(4);
}
else
{
m_data = new SuballocatedIntVector(blocksize, DEFAULT_NUMBLOCKS);
m_dataOrQName = new SuballocatedIntVector(blocksize, DEFAULT_NUMBLOCKS);
m_valuesOrPrefixes = new DTMStringPool();
m_chars = new FastStringBuffer(10, 13);
m_contextIndexes = new IntStack();
m_parents = new IntStack();
}
// %REVIEW% Initial size pushed way down to reduce weight of RTFs
// (I'm not entirely sure 0 would work, so I'm playing it safe for now.)
//m_data = new SuballocatedIntVector(doIndexing ? (1024*2) : 512, 1024);
//m_data = new SuballocatedIntVector(blocksize);
m_data.addElement(0); // Need placeholder in case index into here must be <0.
//m_dataOrQName = new SuballocatedIntVector(blocksize);
// m_useSourceLocationProperty=org.apache.xalan.processor.TransformerFactoryImpl.m_source_location;
m_useSourceLocationProperty = mgr.getSource_location();
m_sourceSystemId = (m_useSourceLocationProperty) ? new StringVector() : null;
m_sourceLine = (m_useSourceLocationProperty) ? new IntVector() : null;
m_sourceColumn = (m_useSourceLocationProperty) ? new IntVector() : null;
}
/**
* Set whether information about document source location
* should be maintained or not.
*/
public void setUseSourceLocation(boolean useSourceLocation)
{
m_useSourceLocationProperty = useSourceLocation;
}
/**
* Get the data or qualified name for the given node identity.
*
* @param identity The node identity.
*
* @return The data or qualified name, or DTM.NULL.
*/
protected int _dataOrQName(int identity)
{
if (identity < m_size)
return m_dataOrQName.elementAt(identity);
// Check to see if the information requested has been processed, and,
// if not, advance the iterator until we the information has been
// processed.
while (true)
{
boolean isMore = nextNode();
if (!isMore)
return NULL;
else if (identity < m_size)
return m_dataOrQName.elementAt(identity);
}
}
/**
* Ask the CoRoutine parser to doTerminate and clear the reference.
*/
public void clearCoRoutine()
{
clearCoRoutine(true);
}
/**
* Ask the CoRoutine parser to doTerminate and clear the reference. If
* the CoRoutine parser has already been cleared, this will have no effect.
*
* @param callDoTerminate true of doTerminate should be called on the
* coRoutine parser.
*/
public void clearCoRoutine(boolean callDoTerminate)
{
if (null != m_incrementalSAXSource)
{
if (callDoTerminate)
m_incrementalSAXSource.deliverMoreNodes(false);
m_incrementalSAXSource = null;
}
}
/**
* Bind a IncrementalSAXSource to this DTM. If we discover we need nodes
* that have not yet been built, we will ask this object to send us more
* events, and it will manage interactions with its data sources.
*
* Note that we do not actually build the IncrementalSAXSource, since we don't
* know what source it's reading from, what thread that source will run in,
* or when it will run.
*
* @param incrementalSAXSource The parser that we want to recieve events from
* on demand.
*/
public void setIncrementalSAXSource(IncrementalSAXSource incrementalSAXSource)
{
// Establish coroutine link so we can request more data
//
// Note: It's possible that some versions of IncrementalSAXSource may
// not actually use a CoroutineManager, and hence may not require
// that we obtain an Application Coroutine ID. (This relies on the
// coroutine transaction details having been encapsulated in the
// IncrementalSAXSource.do...() methods.)
m_incrementalSAXSource = incrementalSAXSource;
// Establish SAX-stream link so we can receive the requested data
incrementalSAXSource.setContentHandler(this);
incrementalSAXSource.setLexicalHandler(this);
incrementalSAXSource.setDTDHandler(this);
// Are the following really needed? incrementalSAXSource doesn't yet
// support them, and they're mostly no-ops here...
//incrementalSAXSource.setErrorHandler(this);
//incrementalSAXSource.setDeclHandler(this);
}
/**
* getContentHandler returns "our SAX builder" -- the thing that
* someone else should send SAX events to in order to extend this
* DTM model.
*
* %REVIEW% Should this return null if constrution already done/begun?
*
* @return null if this model doesn't respond to SAX events,
* "this" if the DTM object has a built-in SAX ContentHandler,
* the IncrementalSAXSource if we're bound to one and should receive
* the SAX stream via it for incremental build purposes...
*/
public ContentHandler getContentHandler()
{
if (m_incrementalSAXSource instanceof IncrementalSAXSource_Filter)
return (ContentHandler) m_incrementalSAXSource;
else
return this;
}
/**
* Return this DTM's lexical handler.
*
* %REVIEW% Should this return null if constrution already done/begun?
*
* @return null if this model doesn't respond to lexical SAX events,
* "this" if the DTM object has a built-in SAX ContentHandler,
* the IncrementalSAXSource if we're bound to one and should receive
* the SAX stream via it for incremental build purposes...
*/
public LexicalHandler getLexicalHandler()
{
if (m_incrementalSAXSource instanceof IncrementalSAXSource_Filter)
return (LexicalHandler) m_incrementalSAXSource;
else
return this;
}
/**
* Return this DTM's EntityResolver.
*
* @return null if this model doesn't respond to SAX entity ref events.
*/
public EntityResolver getEntityResolver()
{
return this;
}
/**
* Return this DTM's DTDHandler.
*
* @return null if this model doesn't respond to SAX dtd events.
*/
public DTDHandler getDTDHandler()
{
return this;
}
/**
* Return this DTM's ErrorHandler.
*
* @return null if this model doesn't respond to SAX error events.
*/
public ErrorHandler getErrorHandler()
{
return this;
}
/**
* Return this DTM's DeclHandler.
*
* @return null if this model doesn't respond to SAX Decl events.
*/
public DeclHandler getDeclHandler()
{
return this;
}
/**
* @return true iff we're building this model incrementally (eg
* we're partnered with a IncrementalSAXSource) and thus require that the
* transformation and the parse run simultaneously. Guidance to the
* DTMManager.
*/
public boolean needsTwoThreads()
{
return null != m_incrementalSAXSource;
}
/**
* Directly call the
* characters method on the passed ContentHandler for the
* string-value of the given node (see http://www.w3.org/TR/xpath#data-model
* for the definition of a node's string-value). Multiple calls to the
* ContentHandler's characters methods may well occur for a single call to
* this method.
*
* @param nodeHandle The node ID.
* @param ch A non-null reference to a ContentHandler.
* @param normalize true if the content should be normalized according to
* the rules for the XPath
* normalize-space
* function.
*
* @throws SAXException
*/
public void dispatchCharactersEvents(int nodeHandle, ContentHandler ch,
boolean normalize)
throws SAXException
{
int identity = makeNodeIdentity(nodeHandle);
if (identity == DTM.NULL)
return;
int type = _type(identity);
if (isTextType(type))
{
int dataIndex = m_dataOrQName.elementAt(identity);
int offset = m_data.elementAt(dataIndex);
int length = m_data.elementAt(dataIndex + 1);
if(normalize)
m_chars.sendNormalizedSAXcharacters(ch, offset, length);
else
m_chars.sendSAXcharacters(ch, offset, length);
}
else
{
int firstChild = _firstch(identity);
if (DTM.NULL != firstChild)
{
int offset = -1;
int length = 0;
int startNode = identity;
identity = firstChild;
do {
type = _type(identity);
if (isTextType(type))
{
int dataIndex = _dataOrQName(identity);
if (-1 == offset)
{
offset = m_data.elementAt(dataIndex);
}
length += m_data.elementAt(dataIndex + 1);
}
identity = getNextNodeIdentity(identity);
} while (DTM.NULL != identity && (_parent(identity) >= startNode));
if (length > 0)
{
if(normalize)
m_chars.sendNormalizedSAXcharacters(ch, offset, length);
else
m_chars.sendSAXcharacters(ch, offset, length);
}
}
else if(type != DTM.ELEMENT_NODE)
{
int dataIndex = _dataOrQName(identity);
if (dataIndex < 0)
{
dataIndex = -dataIndex;
dataIndex = m_data.elementAt(dataIndex + 1);
}
String str = m_valuesOrPrefixes.indexToString(dataIndex);
if(normalize)
FastStringBuffer.sendNormalizedSAXcharacters(str.toCharArray(),
0, str.length(), ch);
else
ch.characters(str.toCharArray(), 0, str.length());
}
}
}
/**
* Given a node handle, return its DOM-style node name. This will
* include names such as #text or #document.
*
* @param nodeHandle the id of the node.
* @return String Name of this node, which may be an empty string.
* %REVIEW% Document when empty string is possible...
* %REVIEW-COMMENT% It should never be empty, should it?
*/
public String getNodeName(int nodeHandle)
{
int expandedTypeID = getExpandedTypeID(nodeHandle);
// If just testing nonzero, no need to shift...
int namespaceID = m_expandedNameTable.getNamespaceID(expandedTypeID);
if (0 == namespaceID)
{
// Don't retrieve name until/unless needed
// String name = m_expandedNameTable.getLocalName(expandedTypeID);
int type = getNodeType(nodeHandle);
if (type == DTM.NAMESPACE_NODE)
{
if (null == m_expandedNameTable.getLocalName(expandedTypeID))
return "xmlns";
else
return "xmlns:" + m_expandedNameTable.getLocalName(expandedTypeID);
}
else if (0 == m_expandedNameTable.getLocalNameID(expandedTypeID))
{
return m_fixednames[type];
}
else
return m_expandedNameTable.getLocalName(expandedTypeID);
}
else
{
int qnameIndex = m_dataOrQName.elementAt(makeNodeIdentity(nodeHandle));
if (qnameIndex < 0)
{
qnameIndex = -qnameIndex;
qnameIndex = m_data.elementAt(qnameIndex);
}
return m_valuesOrPrefixes.indexToString(qnameIndex);
}
}
/**
* Given a node handle, return the XPath node name. This should be
* the name as described by the XPath data model, NOT the DOM-style
* name.
*
* @param nodeHandle the id of the node.
* @return String Name of this node, which may be an empty string.
*/
public String getNodeNameX(int nodeHandle)
{
int expandedTypeID = getExpandedTypeID(nodeHandle);
int namespaceID = m_expandedNameTable.getNamespaceID(expandedTypeID);
if (0 == namespaceID)
{
String name = m_expandedNameTable.getLocalName(expandedTypeID);
if (name == null)
return "";
else
return name;
}
else
{
int qnameIndex = m_dataOrQName.elementAt(makeNodeIdentity(nodeHandle));
if (qnameIndex < 0)
{
qnameIndex = -qnameIndex;
qnameIndex = m_data.elementAt(qnameIndex);
}
return m_valuesOrPrefixes.indexToString(qnameIndex);
}
}
/**
* 5. [specified] A flag indicating whether this attribute was actually
* specified in the start-tag of its element, or was defaulted from the
* DTD.
*
* @param attributeHandle Must be a valid handle to an attribute node.
* @return true
if the attribute was specified;
* false
if it was defaulted.
*/
public boolean isAttributeSpecified(int attributeHandle)
{
// I'm not sure if I want to do anything with this...
return true; // ??
}
/**
* A document type declaration information item has the following properties:
*
* 1. [system identifier] The system identifier of the external subset, if
* it exists. Otherwise this property has no value.
*
* @return the system identifier String object, or null if there is none.
*/
public String getDocumentTypeDeclarationSystemIdentifier()
{
/** @todo: implement this org.apache.xml.dtm.DTMDefaultBase abstract method */
error(XMLMessages.createXMLMessage(XMLErrorResources.ER_METHOD_NOT_SUPPORTED, null));//"Not yet supported!");
return null;
}
/**
* Get the next node identity value in the list, and call the iterator
* if it hasn't been added yet.
*
* @param identity The node identity (index).
* @return identity+1, or DTM.NULL.
*/
protected int getNextNodeIdentity(int identity)
{
identity += 1;
while (identity >= m_size)
{
if (null == m_incrementalSAXSource)
return DTM.NULL;
nextNode();
}
return identity;
}
/**
* Directly create SAX parser events from a subtree.
*
* @param nodeHandle The node ID.
* @param ch A non-null reference to a ContentHandler.
*
* @throws org.xml.sax.SAXException
*/
public void dispatchToEvents(int nodeHandle, org.xml.sax.ContentHandler ch)
throws org.xml.sax.SAXException
{
DTMTreeWalker treeWalker = m_walker;
ContentHandler prevCH = treeWalker.getcontentHandler();
if (null != prevCH)
{
treeWalker = new DTMTreeWalker();
}
treeWalker.setcontentHandler(ch);
treeWalker.setDTM(this);
try
{
treeWalker.traverse(nodeHandle);
}
finally
{
treeWalker.setcontentHandler(null);
}
}
/**
* Get the number of nodes that have been added.
*
* @return The number of that are currently in the tree.
*/
public int getNumberOfNodes()
{
return m_size;
}
/**
* This method should try and build one or more nodes in the table.
*
* @return The true if a next node is found or false if
* there are no more nodes.
*/
protected boolean nextNode()
{
if (null == m_incrementalSAXSource)
return false;
if (m_endDocumentOccured)
{
clearCoRoutine();
return false;
}
Object gotMore = m_incrementalSAXSource.deliverMoreNodes(true);
// gotMore may be a Boolean (TRUE if still parsing, FALSE if
// EOF) or an exception if IncrementalSAXSource malfunctioned
// (code error rather than user error).
//
// %REVIEW% Currently the ErrorHandlers sketched herein are
// no-ops, so I'm going to initially leave this also as a
// no-op.
if (!(gotMore instanceof Boolean))
{
if(gotMore instanceof RuntimeException)
{
throw (RuntimeException)gotMore;
}
else if(gotMore instanceof Exception)
{
throw new WrappedRuntimeException((Exception)gotMore);
}
// for now...
clearCoRoutine();
return false;
// %TBD%
}
if (gotMore != Boolean.TRUE)
{
// EOF reached without satisfying the request
clearCoRoutine(); // Drop connection, stop trying
// %TBD% deregister as its listener?
}
return true;
}
/**
* Bottleneck determination of text type.
*
* @param type oneof DTM.XXX_NODE.
*
* @return true if this is a text or cdata section.
*/
private final boolean isTextType(int type)
{
return (DTM.TEXT_NODE == type || DTM.CDATA_SECTION_NODE == type);
}
// /**
// * Ensure that the size of the information arrays can hold another entry
// * at the given index.
// *
// * @param on exit from this function, the information arrays sizes must be
// * at least index+1.
// *
// * NEEDSDOC @param index
// */
// protected void ensureSize(int index)
// {
// // dataOrQName is an SuballocatedIntVector and hence self-sizing.
// // But DTMDefaultBase may need fixup.
// super.ensureSize(index);
// }
/**
* Construct the node map from the node.
*
* @param type raw type ID, one of DTM.XXX_NODE.
* @param expandedTypeID The expended type ID.
* @param parentIndex The current parent index.
* @param previousSibling The previous sibling index.
* @param dataOrPrefix index into m_data table, or string handle.
* @param canHaveFirstChild true if the node can have a first child, false
* if it is atomic.
*
* @return The index identity of the node that was added.
*/
protected int addNode(int type, int expandedTypeID,
int parentIndex, int previousSibling,
int dataOrPrefix, boolean canHaveFirstChild)
{
// Common to all nodes:
int nodeIndex = m_size++;
// Have we overflowed a DTM Identity's addressing range?
if(m_dtmIdent.size() == (nodeIndex>>>DTMManager.IDENT_DTM_NODE_BITS))
{
addNewDTMID(nodeIndex);
}
m_firstch.addElement(canHaveFirstChild ? NOTPROCESSED : DTM.NULL);
m_nextsib.addElement(NOTPROCESSED);
m_parent.addElement(parentIndex);
m_exptype.addElement(expandedTypeID);
m_dataOrQName.addElement(dataOrPrefix);
if (m_prevsib != null) {
m_prevsib.addElement(previousSibling);
}
if (DTM.NULL != previousSibling) {
m_nextsib.setElementAt(nodeIndex,previousSibling);
}
if (m_locator != null && m_useSourceLocationProperty) {
setSourceLocation();
}
// Note that nextSibling is not processed until charactersFlush()
// is called, to handle successive characters() events.
// Special handling by type: Declare namespaces, attach first child
switch(type)
{
case DTM.NAMESPACE_NODE:
declareNamespaceInContext(parentIndex,nodeIndex);
break;
case DTM.ATTRIBUTE_NODE:
break;
default:
if (DTM.NULL == previousSibling && DTM.NULL != parentIndex) {
m_firstch.setElementAt(nodeIndex,parentIndex);
}
break;
}
return nodeIndex;
}
/**
* Get a new DTM ID beginning at the specified node index.
* @param nodeIndex The node identity at which the new DTM ID will begin
* addressing.
*/
protected void addNewDTMID(int nodeIndex) {
try
{
if(m_mgr==null)
throw new ClassCastException();
// Handle as Extended Addressing
DTMManagerDefault mgrD=(DTMManagerDefault)m_mgr;
int id=mgrD.getFirstFreeDTMID();
mgrD.addDTM(this,id,nodeIndex);
m_dtmIdent.addElement(id<
*
* @param nodeHandle The node id.
* @return String Value of this node, or null if not
* meaningful for this node type.
*/
public String getNodeValue(int nodeHandle)
{
int identity = makeNodeIdentity(nodeHandle);
int type = _type(identity);
if (isTextType(type))
{
int dataIndex = _dataOrQName(identity);
int offset = m_data.elementAt(dataIndex);
int length = m_data.elementAt(dataIndex + 1);
// %OPT% We should cache this, I guess.
return m_chars.getString(offset, length);
}
else if (DTM.ELEMENT_NODE == type || DTM.DOCUMENT_FRAGMENT_NODE == type
|| DTM.DOCUMENT_NODE == type)
{
return null;
}
else
{
int dataIndex = _dataOrQName(identity);
if (dataIndex < 0)
{
dataIndex = -dataIndex;
dataIndex = m_data.elementAt(dataIndex + 1);
}
return m_valuesOrPrefixes.indexToString(dataIndex);
}
}
/**
* Given a node handle, return its XPath-style localname.
* (As defined in Namespaces, this is the portion of the name after any
* colon character).
*
* @param nodeHandle the id of the node.
* @return String Local name of this node.
*/
public String getLocalName(int nodeHandle)
{
return m_expandedNameTable.getLocalName(_exptype(makeNodeIdentity(nodeHandle)));
}
/**
* The getUnparsedEntityURI function returns the URI of the unparsed
* entity with the specified name in the same document as the context
* node (see [3.3 Unparsed Entities]). It returns the empty string if
* there is no such entity.
*
* XML processors may choose to use the System Identifier (if one
* is provided) to resolve the entity, rather than the URI in the
* Public Identifier. The details are dependent on the processor, and
* we would have to support some form of plug-in resolver to handle
* this properly. Currently, we simply return the System Identifier if
* present, and hope that it a usable URI or that our caller can
* map it to one.
* TODO: Resolve Public Identifiers... or consider changing function name.
*
* If we find a relative URI
* reference, XML expects it to be resolved in terms of the base URI
* of the document. The DOM doesn't do that for us, and it isn't
* entirely clear whether that should be done here; currently that's
* pushed up to a higher level of our application. (Note that DOM Level
* 1 didn't store the document's base URI.)
* TODO: Consider resolving Relative URIs.
*
* (The DOM's statement that "An XML processor may choose to
* completely expand entities before the structure model is passed
* to the DOM" refers only to parsed entities, not unparsed, and hence
* doesn't affect this function.)
*
* @param name A string containing the Entity Name of the unparsed
* entity.
*
* @return String containing the URI of the Unparsed Entity, or an
* empty string if no such entity exists.
*/
public String getUnparsedEntityURI(String name)
{
String url = "";
if (null == m_entities)
return url;
int n = m_entities.size();
for (int i = 0; i < n; i += ENTITY_FIELDS_PER)
{
String ename = (String) m_entities.elementAt(i + ENTITY_FIELD_NAME);
if (null != ename && ename.equals(name))
{
String nname = (String) m_entities.elementAt(i
+ ENTITY_FIELD_NOTATIONNAME);
if (null != nname)
{
// The draft says: "The XSLT processor may use the public
// identifier to generate a URI for the entity instead of the URI
// specified in the system identifier. If the XSLT processor does
// not use the public identifier to generate the URI, it must use
// the system identifier; if the system identifier is a relative
// URI, it must be resolved into an absolute URI using the URI of
// the resource containing the entity declaration as the base
// URI [RFC2396]."
// So I'm falling a bit short here.
url = (String) m_entities.elementAt(i + ENTITY_FIELD_SYSTEMID);
if (null == url)
{
url = (String) m_entities.elementAt(i + ENTITY_FIELD_PUBLICID);
}
}
break;
}
}
return url;
}
/**
* Given a namespace handle, return the prefix that the namespace decl is
* mapping.
* Given a node handle, return the prefix used to map to the namespace.
*
*
%REVIEW% Are you sure you want "" for no prefix?
* %REVIEW-COMMENT% I think so... not totally sure. -sb
*
* @param nodeHandle the id of the node.
* @return String prefix of this node's name, or "" if no explicit
* namespace prefix was given.
*/
public String getPrefix(int nodeHandle)
{
int identity = makeNodeIdentity(nodeHandle);
int type = _type(identity);
if (DTM.ELEMENT_NODE == type)
{
int prefixIndex = _dataOrQName(identity);
if (0 == prefixIndex)
return "";
else
{
String qname = m_valuesOrPrefixes.indexToString(prefixIndex);
return getPrefix(qname, null);
}
}
else if (DTM.ATTRIBUTE_NODE == type)
{
int prefixIndex = _dataOrQName(identity);
if (prefixIndex < 0)
{
prefixIndex = m_data.elementAt(-prefixIndex);
String qname = m_valuesOrPrefixes.indexToString(prefixIndex);
return getPrefix(qname, null);
}
}
return "";
}
/**
* Retrieves an attribute node by by qualified name and namespace URI.
*
* @param nodeHandle int Handle of the node upon which to look up this attribute..
* @param namespaceURI The namespace URI of the attribute to
* retrieve, or null.
* @param name The local name of the attribute to
* retrieve.
* @return The attribute node handle with the specified name (
* nodeName
) or DTM.NULL
if there is no such
* attribute.
*/
public int getAttributeNode(int nodeHandle, String namespaceURI,
String name)
{
for (int attrH = getFirstAttribute(nodeHandle); DTM.NULL != attrH;
attrH = getNextAttribute(attrH))
{
String attrNS = getNamespaceURI(attrH);
String attrName = getLocalName(attrH);
boolean nsMatch = namespaceURI == attrNS
|| (namespaceURI != null
&& namespaceURI.equals(attrNS));
if (nsMatch && name.equals(attrName))
return attrH;
}
return DTM.NULL;
}
/**
* Return the public identifier of the external subset,
* normalized as described in 4.2.2 External Entities [XML]. If there is
* no external subset or if it has no public identifier, this property
* has no value.
*
* @return the public identifier String object, or null if there is none.
*/
public String getDocumentTypeDeclarationPublicIdentifier()
{
/** @todo: implement this org.apache.xml.dtm.DTMDefaultBase abstract method */
error(XMLMessages.createXMLMessage(XMLErrorResources.ER_METHOD_NOT_SUPPORTED, null));//"Not yet supported!");
return null;
}
/**
* Given a node handle, return its DOM-style namespace URI
* (As defined in Namespaces, this is the declared URI which this node's
* prefix -- or default in lieu thereof -- was mapped to.)
*
* %REVIEW% Null or ""? -sb
*
* @param nodeHandle the id of the node.
* @return String URI value of this node's namespace, or null if no
* namespace was resolved.
*/
public String getNamespaceURI(int nodeHandle)
{
return m_expandedNameTable.getNamespace(_exptype(makeNodeIdentity(nodeHandle)));
}
/**
* Get the string-value of a node as a String object
* (see http://www.w3.org/TR/xpath#data-model
* for the definition of a node's string-value).
*
* @param nodeHandle The node ID.
*
* @return A string object that represents the string-value of the given node.
*/
public XMLString getStringValue(int nodeHandle)
{
int identity = makeNodeIdentity(nodeHandle);
int type;
if(identity==DTM.NULL) // Separate lines because I wanted to breakpoint it
type = DTM.NULL;
else
type= _type(identity);
if (isTextType(type))
{
int dataIndex = _dataOrQName(identity);
int offset = m_data.elementAt(dataIndex);
int length = m_data.elementAt(dataIndex + 1);
return m_xstrf.newstr(m_chars, offset, length);
}
else
{
int firstChild = _firstch(identity);
if (DTM.NULL != firstChild)
{
int offset = -1;
int length = 0;
int startNode = identity;
identity = firstChild;
do {
type = _type(identity);
if (isTextType(type))
{
int dataIndex = _dataOrQName(identity);
if (-1 == offset)
{
offset = m_data.elementAt(dataIndex);
}
length += m_data.elementAt(dataIndex + 1);
}
identity = getNextNodeIdentity(identity);
} while (DTM.NULL != identity && (_parent(identity) >= startNode));
if (length > 0)
{
return m_xstrf.newstr(m_chars, offset, length);
}
}
else if(type != DTM.ELEMENT_NODE)
{
int dataIndex = _dataOrQName(identity);
if (dataIndex < 0)
{
dataIndex = -dataIndex;
dataIndex = m_data.elementAt(dataIndex + 1);
}
return m_xstrf.newstr(m_valuesOrPrefixes.indexToString(dataIndex));
}
}
return m_xstrf.emptystr();
}
/**
* Determine if the string-value of a node is whitespace
*
* @param nodeHandle The node Handle.
*
* @return Return true if the given node is whitespace.
*/
public boolean isWhitespace(int nodeHandle)
{
int identity = makeNodeIdentity(nodeHandle);
int type;
if(identity==DTM.NULL) // Separate lines because I wanted to breakpoint it
type = DTM.NULL;
else
type= _type(identity);
if (isTextType(type))
{
int dataIndex = _dataOrQName(identity);
int offset = m_data.elementAt(dataIndex);
int length = m_data.elementAt(dataIndex + 1);
return m_chars.isWhitespace(offset, length);
}
return false;
}
/**
* Returns the Element
whose ID
is given by
* elementId
. If no such element exists, returns
* DTM.NULL
. Behavior is not defined if more than one element
* has this ID
. Attributes (including those
* with the name "ID") are not of type ID unless so defined by DTD/Schema
* information available to the DTM implementation.
* Implementations that do not know whether attributes are of type ID or
* not are expected to return DTM.NULL
.
*
* %REVIEW% Presumably IDs are still scoped to a single document,
* and this operation searches only within a single document, right?
* Wouldn't want collisions between DTMs in the same process.
*
* @param elementId The unique id
value for an element.
* @return The handle of the matching element.
*/
public int getElementById(String elementId)
{
Integer intObj;
boolean isMore = true;
do
{
intObj = (Integer) m_idAttributes.get(elementId);
if (null != intObj)
return makeNodeHandle(intObj.intValue());
if (!isMore || m_endDocumentOccured)
break;
isMore = nextNode();
}
while (null == intObj);
return DTM.NULL;
}
/**
* Get a prefix either from the qname or from the uri mapping, or just make
* one up!
*
* @param qname The qualified name, which may be null.
* @param uri The namespace URI, which may be null.
*
* @return The prefix if there is one, or null.
*/
public String getPrefix(String qname, String uri)
{
String prefix;
int uriIndex = -1;
if (null != uri && uri.length() > 0)
{
do
{
uriIndex = m_prefixMappings.indexOf(uri, ++uriIndex);
} while ( (uriIndex & 0x01) == 0);
if (uriIndex >= 0)
{
prefix = (String) m_prefixMappings.elementAt(uriIndex - 1);
}
else if (null != qname)
{
int indexOfNSSep = qname.indexOf(':');
if (qname.equals("xmlns"))
prefix = "";
else if (qname.startsWith("xmlns:"))
prefix = qname.substring(indexOfNSSep + 1);
else
prefix = (indexOfNSSep > 0)
? qname.substring(0, indexOfNSSep) : null;
}
else
{
prefix = null;
}
}
else if (null != qname)
{
int indexOfNSSep = qname.indexOf(':');
if (indexOfNSSep > 0)
{
if (qname.startsWith("xmlns:"))
prefix = qname.substring(indexOfNSSep + 1);
else
prefix = qname.substring(0, indexOfNSSep);
}
else
{
if (qname.equals("xmlns"))
prefix = "";
else
prefix = null;
}
}
else
{
prefix = null;
}
return prefix;
}
/**
* Get a prefix either from the uri mapping, or just make
* one up!
*
* @param uri The namespace URI, which may be null.
*
* @return The prefix if there is one, or null.
*/
public int getIdForNamespace(String uri)
{
return m_valuesOrPrefixes.stringToIndex(uri);
}
/**
* Get a prefix either from the qname or from the uri mapping, or just make
* one up!
*
* @return The prefix if there is one, or null.
*/
public String getNamespaceURI(String prefix)
{
String uri = "";
int prefixIndex = m_contextIndexes.peek() - 1 ;
if(null == prefix)
prefix = "";
do
{
prefixIndex = m_prefixMappings.indexOf(prefix, ++prefixIndex);
} while ( (prefixIndex >= 0) && (prefixIndex & 0x01) == 0x01);
if (prefixIndex > -1)
{
uri = (String) m_prefixMappings.elementAt(prefixIndex + 1);
}
return uri;
}
/**
* Set an ID string to node association in the ID table.
*
* @param id The ID string.
* @param elem The associated element handle.
*/
public void setIDAttribute(String id, int elem)
{
m_idAttributes.put(id, new Integer(elem));
}
/**
* Check whether accumulated text should be stripped; if not,
* append the appropriate flavor of text/cdata node.
*/
protected void charactersFlush()
{
if (m_textPendingStart >= 0) // -1 indicates no-text-in-progress
{
int length = m_chars.size() - m_textPendingStart;
boolean doStrip = false;
if (getShouldStripWhitespace())
{
doStrip = m_chars.isWhitespace(m_textPendingStart, length);
}
if (doStrip) {
m_chars.setLength(m_textPendingStart); // Discard accumulated text
} else {
// Guard against characters/ignorableWhitespace events that
// contained no characters. They should not result in a node.
if (length > 0) {
int exName = m_expandedNameTable.getExpandedTypeID(DTM.TEXT_NODE);
int dataIndex = m_data.size();
m_previous = addNode(m_coalescedTextType, exName,
m_parents.peek(), m_previous, dataIndex, false);
m_data.addElement(m_textPendingStart);
m_data.addElement(length);
}
}
// Reset for next text block
m_textPendingStart = -1;
m_textType = m_coalescedTextType = DTM.TEXT_NODE;
}
}
////////////////////////////////////////////////////////////////////
// Implementation of the EntityResolver interface.
////////////////////////////////////////////////////////////////////
/**
* Resolve an external entity.
*
* Always return null, so that the parser will use the system
* identifier provided in the XML document. This method implements
* the SAX default behaviour: application writers can override it
* in a subclass to do special translations such as catalog lookups
* or URI redirection.
*
* @param publicId The public identifer, or null if none is
* available.
* @param systemId The system identifier provided in the XML
* document.
* @return The new input source, or null to require the
* default behaviour.
* @throws SAXException Any SAX exception, possibly
* wrapping another exception.
* @see org.xml.sax.EntityResolver#resolveEntity
*
* @throws SAXException
*/
public InputSource resolveEntity(String publicId, String systemId)
throws SAXException
{
return null;
}
////////////////////////////////////////////////////////////////////
// Implementation of DTDHandler interface.
////////////////////////////////////////////////////////////////////
/**
* Receive notification of a notation declaration.
*
* By default, do nothing. Application writers may override this
* method in a subclass if they wish to keep track of the notations
* declared in a document.
*
* @param name The notation name.
* @param publicId The notation public identifier, or null if not
* available.
* @param systemId The notation system identifier.
* @throws SAXException Any SAX exception, possibly
* wrapping another exception.
* @see org.xml.sax.DTDHandler#notationDecl
*
* @throws SAXException
*/
public void notationDecl(String name, String publicId, String systemId)
throws SAXException
{
// no op
}
/**
* Receive notification of an unparsed entity declaration.
*
* By default, do nothing. Application writers may override this
* method in a subclass to keep track of the unparsed entities
* declared in a document.
*
* @param name The entity name.
* @param publicId The entity public identifier, or null if not
* available.
* @param systemId The entity system identifier.
* @param notationName The name of the associated notation.
* @throws SAXException Any SAX exception, possibly
* wrapping another exception.
* @see org.xml.sax.DTDHandler#unparsedEntityDecl
*
* @throws SAXException
*/
public void unparsedEntityDecl(
String name, String publicId, String systemId, String notationName)
throws SAXException
{
if (null == m_entities)
{
m_entities = new Vector();
}
try
{
systemId = SystemIDResolver.getAbsoluteURI(systemId,
getDocumentBaseURI());
}
catch (Exception e)
{
throw new org.xml.sax.SAXException(e);
}
// private static final int ENTITY_FIELD_PUBLICID = 0;
m_entities.addElement(publicId);
// private static final int ENTITY_FIELD_SYSTEMID = 1;
m_entities.addElement(systemId);
// private static final int ENTITY_FIELD_NOTATIONNAME = 2;
m_entities.addElement(notationName);
// private static final int ENTITY_FIELD_NAME = 3;
m_entities.addElement(name);
}
////////////////////////////////////////////////////////////////////
// Implementation of ContentHandler interface.
////////////////////////////////////////////////////////////////////
/**
* Receive a Locator object for document events.
*
* By default, do nothing. Application writers may override this
* method in a subclass if they wish to store the locator for use
* with other document events.
*
* @param locator A locator for all SAX document events.
* @see org.xml.sax.ContentHandler#setDocumentLocator
* @see org.xml.sax.Locator
*/
public void setDocumentLocator(Locator locator)
{
m_locator = locator;
m_systemId = locator.getSystemId();
}
/**
* Receive notification of the beginning of the document.
*
* @throws SAXException Any SAX exception, possibly
* wrapping another exception.
* @see org.xml.sax.ContentHandler#startDocument
*/
public void startDocument() throws SAXException
{
if (DEBUG)
System.out.println("startDocument");
int doc = addNode(DTM.DOCUMENT_NODE,
m_expandedNameTable.getExpandedTypeID(DTM.DOCUMENT_NODE),
DTM.NULL, DTM.NULL, 0, true);
m_parents.push(doc);
m_previous = DTM.NULL;
m_contextIndexes.push(m_prefixMappings.size()); // for the next element.
}
/**
* Receive notification of the end of the document.
*
* @throws SAXException Any SAX exception, possibly
* wrapping another exception.
* @see org.xml.sax.ContentHandler#endDocument
*/
public void endDocument() throws SAXException
{
if (DEBUG)
System.out.println("endDocument");
charactersFlush();
m_nextsib.setElementAt(NULL,0);
if (m_firstch.elementAt(0) == NOTPROCESSED)
m_firstch.setElementAt(NULL,0);
if (DTM.NULL != m_previous)
m_nextsib.setElementAt(DTM.NULL,m_previous);
m_parents = null;
m_prefixMappings = null;
m_contextIndexes = null;
m_endDocumentOccured = true;
// Bugzilla 4858: throw away m_locator. we cache m_systemId
m_locator = null;
}
/**
* Receive notification of the start of a Namespace mapping.
*
* By default, do nothing. Application writers may override this
* method in a subclass to take specific actions at the start of
* each Namespace prefix scope (such as storing the prefix mapping).
*
* @param prefix The Namespace prefix being declared.
* @param uri The Namespace URI mapped to the prefix.
* @throws SAXException Any SAX exception, possibly
* wrapping another exception.
* @see org.xml.sax.ContentHandler#startPrefixMapping
*/
public void startPrefixMapping(String prefix, String uri)
throws SAXException
{
if (DEBUG)
System.out.println("startPrefixMapping: prefix: " + prefix + ", uri: "
+ uri);
if(null == prefix)
prefix = "";
m_prefixMappings.addElement(prefix); // JDK 1.1.x compat -sc
m_prefixMappings.addElement(uri); // JDK 1.1.x compat -sc
}
/**
* Receive notification of the end of a Namespace mapping.
*
* By default, do nothing. Application writers may override this
* method in a subclass to take specific actions at the end of
* each prefix mapping.
*
* @param prefix The Namespace prefix being declared.
* @throws SAXException Any SAX exception, possibly
* wrapping another exception.
* @see org.xml.sax.ContentHandler#endPrefixMapping
*/
public void endPrefixMapping(String prefix) throws SAXException
{
if (DEBUG)
System.out.println("endPrefixMapping: prefix: " + prefix);
if(null == prefix)
prefix = "";
int index = m_contextIndexes.peek() - 1;
do
{
index = m_prefixMappings.indexOf(prefix, ++index);
} while ( (index >= 0) && ((index & 0x01) == 0x01) );
if (index > -1)
{
m_prefixMappings.setElementAt("%@$#^@#", index);
m_prefixMappings.setElementAt("%@$#^@#", index + 1);
}
// no op
}
/**
* Check if a declaration has already been made for a given prefix.
*
* @param prefix non-null prefix string.
*
* @return true if the declaration has already been declared in the
* current context.
*/
protected boolean declAlreadyDeclared(String prefix)
{
int startDecls = m_contextIndexes.peek();
java.util.Vector prefixMappings = m_prefixMappings;
int nDecls = prefixMappings.size();
for (int i = startDecls; i < nDecls; i += 2)
{
String prefixDecl = (String) prefixMappings.elementAt(i);
if (prefixDecl == null)
continue;
if (prefixDecl.equals(prefix))
return true;
}
return false;
}
boolean m_pastFirstElement=false;
/**
* Receive notification of the start of an element.
*
* By default, do nothing. Application writers may override this
* method in a subclass to take specific actions at the start of
* each element (such as allocating a new tree node or writing
* output to a file).
*
* @param uri The Namespace URI, or the empty string if the
* element has no Namespace URI or if Namespace
* processing is not being performed.
* @param localName The local name (without prefix), or the
* empty string if Namespace processing is not being
* performed.
* @param qName The qualified name (with prefix), or the
* empty string if qualified names are not available.
* @param attributes The specified or defaulted attributes.
* @throws SAXException Any SAX exception, possibly
* wrapping another exception.
* @see org.xml.sax.ContentHandler#startElement
*/
public void startElement(
String uri, String localName, String qName, Attributes attributes)
throws SAXException
{
if (DEBUG)
{
System.out.println("startElement: uri: " + uri + ", localname: "
+ localName + ", qname: "+qName+", atts: " + attributes);
boolean DEBUG_ATTRS=true;
if(DEBUG_ATTRS & attributes!=null)
{
int n = attributes.getLength();
if(n==0)
System.out.println("\tempty attribute list");
else for (int i = 0; i < n; i++)
System.out.println("\t attr: uri: " + attributes.getURI(i) +
", localname: " + attributes.getLocalName(i) +
", qname: " + attributes.getQName(i) +
", type: " + attributes.getType(i) +
", value: " + attributes.getValue(i)
);
}
}
charactersFlush();
int exName = m_expandedNameTable.getExpandedTypeID(uri, localName, DTM.ELEMENT_NODE);
String prefix = getPrefix(qName, uri);
int prefixIndex = (null != prefix)
? m_valuesOrPrefixes.stringToIndex(qName) : 0;
int elemNode = addNode(DTM.ELEMENT_NODE, exName,
m_parents.peek(), m_previous, prefixIndex, true);
if(m_indexing)
indexNode(exName, elemNode);
m_parents.push(elemNode);
int startDecls = m_contextIndexes.peek();
int nDecls = m_prefixMappings.size();
int prev = DTM.NULL;
if(!m_pastFirstElement)
{
// SPECIAL CASE: Implied declaration at root element
prefix="xml";
String declURL = "http://www.w3.org/XML/1998/namespace";
exName = m_expandedNameTable.getExpandedTypeID(null, prefix, DTM.NAMESPACE_NODE);
int val = m_valuesOrPrefixes.stringToIndex(declURL);
prev = addNode(DTM.NAMESPACE_NODE, exName, elemNode,
prev, val, false);
m_pastFirstElement=true;
}
for (int i = startDecls; i < nDecls; i += 2)
{
prefix = (String) m_prefixMappings.elementAt(i);
if (prefix == null)
continue;
String declURL = (String) m_prefixMappings.elementAt(i + 1);
exName = m_expandedNameTable.getExpandedTypeID(null, prefix, DTM.NAMESPACE_NODE);
int val = m_valuesOrPrefixes.stringToIndex(declURL);
prev = addNode(DTM.NAMESPACE_NODE, exName, elemNode,
prev, val, false);
}
int n = attributes.getLength();
for (int i = 0; i < n; i++)
{
String attrUri = attributes.getURI(i);
String attrQName = attributes.getQName(i);
String valString = attributes.getValue(i);
prefix = getPrefix(attrQName, attrUri);
int nodeType;
String attrLocalName = attributes.getLocalName(i);
if ((null != attrQName)
&& (attrQName.equals("xmlns")
|| attrQName.startsWith("xmlns:")))
{
if (declAlreadyDeclared(prefix))
continue; // go to the next attribute.
nodeType = DTM.NAMESPACE_NODE;
}
else
{
nodeType = DTM.ATTRIBUTE_NODE;
if (attributes.getType(i).equalsIgnoreCase("ID"))
setIDAttribute(valString, elemNode);
}
// Bit of a hack... if somehow valString is null, stringToIndex will
// return -1, which will make things very unhappy.
if(null == valString)
valString = "";
int val = m_valuesOrPrefixes.stringToIndex(valString);
//String attrLocalName = attributes.getLocalName(i);
if (null != prefix)
{
prefixIndex = m_valuesOrPrefixes.stringToIndex(attrQName);
int dataIndex = m_data.size();
m_data.addElement(prefixIndex);
m_data.addElement(val);
val = -dataIndex;
}
exName = m_expandedNameTable.getExpandedTypeID(attrUri, attrLocalName, nodeType);
prev = addNode(nodeType, exName, elemNode, prev, val,
false);
}
if (DTM.NULL != prev)
m_nextsib.setElementAt(DTM.NULL,prev);
if (null != m_wsfilter)
{
short wsv = m_wsfilter.getShouldStripSpace(makeNodeHandle(elemNode), this);
boolean shouldStrip = (DTMWSFilter.INHERIT == wsv)
? getShouldStripWhitespace()
: (DTMWSFilter.STRIP == wsv);
pushShouldStripWhitespace(shouldStrip);
}
m_previous = DTM.NULL;
m_contextIndexes.push(m_prefixMappings.size()); // for the children.
}
/**
* Receive notification of the end of an element.
*
* By default, do nothing. Application writers may override this
* method in a subclass to take specific actions at the end of
* each element (such as finalising a tree node or writing
* output to a file).
*
* @param uri The Namespace URI, or the empty string if the
* element has no Namespace URI or if Namespace
* processing is not being performed.
* @param localName The local name (without prefix), or the
* empty string if Namespace processing is not being
* performed.
* @param qName The qualified XML 1.0 name (with prefix), or the
* empty string if qualified names are not available.
* @throws SAXException Any SAX exception, possibly
* wrapping another exception.
* @see org.xml.sax.ContentHandler#endElement
*/
public void endElement(String uri, String localName, String qName)
throws SAXException
{
if (DEBUG)
System.out.println("endElement: uri: " + uri + ", localname: "
+ localName + ", qname: "+qName);
charactersFlush();
// If no one noticed, startPrefixMapping is a drag.
// Pop the context for the last child (the one pushed by startElement)
m_contextIndexes.quickPop(1);
// Do it again for this one (the one pushed by the last endElement).
int topContextIndex = m_contextIndexes.peek();
if (topContextIndex != m_prefixMappings.size()) {
m_prefixMappings.setSize(topContextIndex);
}
int lastNode = m_previous;
m_previous = m_parents.pop();
// If lastNode is still DTM.NULL, this element had no children
if (DTM.NULL == lastNode)
m_firstch.setElementAt(DTM.NULL,m_previous);
else
m_nextsib.setElementAt(DTM.NULL,lastNode);
popShouldStripWhitespace();
}
/**
* Receive notification of character data inside an element.
*
* By default, do nothing. Application writers may override this
* method to take specific actions for each chunk of character data
* (such as adding the data to a node or buffer, or printing it to
* a file).
*
* @param ch The characters.
* @param start The start position in the character array.
* @param length The number of characters to use from the
* character array.
* @throws SAXException Any SAX exception, possibly
* wrapping another exception.
* @see org.xml.sax.ContentHandler#characters
*/
public void characters(char ch[], int start, int length) throws SAXException
{
if (m_textPendingStart == -1) // First one in this block
{
m_textPendingStart = m_chars.size();
m_coalescedTextType = m_textType;
}
// Type logic: If all adjacent text is CDATASections, the
// concatentated text is treated as a single CDATASection (see
// initialization above). If any were ordinary Text, the whole
// thing is treated as Text. This may be worth %REVIEW%ing.
else if (m_textType == DTM.TEXT_NODE)
{
m_coalescedTextType = DTM.TEXT_NODE;
}
m_chars.append(ch, start, length);
}
/**
* Receive notification of ignorable whitespace in element content.
*
* By default, do nothing. Application writers may override this
* method to take specific actions for each chunk of ignorable
* whitespace (such as adding data to a node or buffer, or printing
* it to a file).
*
* @param ch The whitespace characters.
* @param start The start position in the character array.
* @param length The number of characters to use from the
* character array.
* @throws SAXException Any SAX exception, possibly
* wrapping another exception.
* @see org.xml.sax.ContentHandler#ignorableWhitespace
*/
public void ignorableWhitespace(char ch[], int start, int length)
throws SAXException
{
// %OPT% We can probably take advantage of the fact that we know this
// is whitespace.
characters(ch, start, length);
}
/**
* Receive notification of a processing instruction.
*
* By default, do nothing. Application writers may override this
* method in a subclass to take specific actions for each
* processing instruction, such as setting status variables or
* invoking other methods.
*
* @param target The processing instruction target.
* @param data The processing instruction data, or null if
* none is supplied.
* @throws SAXException Any SAX exception, possibly
* wrapping another exception.
* @see org.xml.sax.ContentHandler#processingInstruction
*/
public void processingInstruction(String target, String data)
throws SAXException
{
if (DEBUG)
System.out.println("processingInstruction: target: " + target +", data: "+data);
charactersFlush();
int exName = m_expandedNameTable.getExpandedTypeID(null, target,
DTM.PROCESSING_INSTRUCTION_NODE);
int dataIndex = m_valuesOrPrefixes.stringToIndex(data);
m_previous = addNode(DTM.PROCESSING_INSTRUCTION_NODE, exName,
m_parents.peek(), m_previous,
dataIndex, false);
}
/**
* Receive notification of a skipped entity.
*
* By default, do nothing. Application writers may override this
* method in a subclass to take specific actions for each
* processing instruction, such as setting status variables or
* invoking other methods.
*
* @param name The name of the skipped entity.
* @throws SAXException Any SAX exception, possibly
* wrapping another exception.
* @see org.xml.sax.ContentHandler#processingInstruction
*/
public void skippedEntity(String name) throws SAXException
{
// %REVIEW% What should be done here?
// no op
}
////////////////////////////////////////////////////////////////////
// Implementation of the ErrorHandler interface.
////////////////////////////////////////////////////////////////////
/**
* Receive notification of a parser warning.
*
* The default implementation does nothing. Application writers
* may override this method in a subclass to take specific actions
* for each warning, such as inserting the message in a log file or
* printing it to the console.
*
* @param e The warning information encoded as an exception.
* @throws SAXException Any SAX exception, possibly
* wrapping another exception.
* @see org.xml.sax.ErrorHandler#warning
* @see org.xml.sax.SAXParseException
*/
public void warning(SAXParseException e) throws SAXException
{
// %REVIEW% Is there anyway to get the JAXP error listener here?
System.err.println(e.getMessage());
}
/**
* Receive notification of a recoverable parser error.
*
* The default implementation does nothing. Application writers
* may override this method in a subclass to take specific actions
* for each error, such as inserting the message in a log file or
* printing it to the console.
*
* @param e The warning information encoded as an exception.
* @throws SAXException Any SAX exception, possibly
* wrapping another exception.
* @see org.xml.sax.ErrorHandler#warning
* @see org.xml.sax.SAXParseException
*/
public void error(SAXParseException e) throws SAXException
{
throw e;
}
/**
* Report a fatal XML parsing error.
*
* The default implementation throws a SAXParseException.
* Application writers may override this method in a subclass if
* they need to take specific actions for each fatal error (such as
* collecting all of the errors into a single report): in any case,
* the application must stop all regular processing when this
* method is invoked, since the document is no longer reliable, and
* the parser may no longer report parsing events.
*
* @param e The error information encoded as an exception.
* @throws SAXException Any SAX exception, possibly
* wrapping another exception.
* @see org.xml.sax.ErrorHandler#fatalError
* @see org.xml.sax.SAXParseException
*/
public void fatalError(SAXParseException e) throws SAXException
{
throw e;
}
////////////////////////////////////////////////////////////////////
// Implementation of the DeclHandler interface.
////////////////////////////////////////////////////////////////////
/**
* Report an element type declaration.
*
* The content model will consist of the string "EMPTY", the
* string "ANY", or a parenthesised group, optionally followed
* by an occurrence indicator. The model will be normalized so
* that all whitespace is removed,and will include the enclosing
* parentheses.
*
* @param name The element type name.
* @param model The content model as a normalized string.
* @throws SAXException The application may raise an exception.
*/
public void elementDecl(String name, String model) throws SAXException
{
// no op
}
/**
* Report an attribute type declaration.
*
* Only the effective (first) declaration for an attribute will
* be reported. The type will be one of the strings "CDATA",
* "ID", "IDREF", "IDREFS", "NMTOKEN", "NMTOKENS", "ENTITY",
* "ENTITIES", or "NOTATION", or a parenthesized token group with
* the separator "|" and all whitespace removed.
*
* @param eName The name of the associated element.
* @param aName The name of the attribute.
* @param type A string representing the attribute type.
* @param valueDefault A string representing the attribute default
* ("#IMPLIED", "#REQUIRED", or "#FIXED") or null if
* none of these applies.
* @param value A string representing the attribute's default value,
* or null if there is none.
* @throws SAXException The application may raise an exception.
*/
public void attributeDecl(
String eName, String aName, String type, String valueDefault, String value)
throws SAXException
{
// no op
}
/**
* Report an internal entity declaration.
*
* Only the effective (first) declaration for each entity
* will be reported.
*
* @param name The name of the entity. If it is a parameter
* entity, the name will begin with '%'.
* @param value The replacement text of the entity.
* @throws SAXException The application may raise an exception.
* @see #externalEntityDecl
* @see org.xml.sax.DTDHandler#unparsedEntityDecl
*/
public void internalEntityDecl(String name, String value)
throws SAXException
{
// no op
}
/**
* Report a parsed external entity declaration.
*
* Only the effective (first) declaration for each entity
* will be reported.
*
* @param name The name of the entity. If it is a parameter
* entity, the name will begin with '%'.
* @param publicId The declared public identifier of the entity, or
* null if none was declared.
* @param systemId The declared system identifier of the entity.
* @throws SAXException The application may raise an exception.
* @see #internalEntityDecl
* @see org.xml.sax.DTDHandler#unparsedEntityDecl
*/
public void externalEntityDecl(
String name, String publicId, String systemId) throws SAXException
{
// no op
}
////////////////////////////////////////////////////////////////////
// Implementation of the LexicalHandler interface.
////////////////////////////////////////////////////////////////////
/**
* Report the start of DTD declarations, if any.
*
* Any declarations are assumed to be in the internal subset
* unless otherwise indicated by a {@link #startEntity startEntity}
* event.
*
* Note that the start/endDTD events will appear within
* the start/endDocument events from ContentHandler and
* before the first startElement event.
*
* @param name The document type name.
* @param publicId The declared public identifier for the
* external DTD subset, or null if none was declared.
* @param systemId The declared system identifier for the
* external DTD subset, or null if none was declared.
* @throws SAXException The application may raise an
* exception.
* @see #endDTD
* @see #startEntity
*/
public void startDTD(String name, String publicId, String systemId)
throws SAXException
{
m_insideDTD = true;
}
/**
* Report the end of DTD declarations.
*
* @throws SAXException The application may raise an exception.
* @see #startDTD
*/
public void endDTD() throws SAXException
{
m_insideDTD = false;
}
/**
* Report the beginning of an entity in content.
*
* NOTE: entity references in attribute
* values -- and the start and end of the document entity --
* are never reported.
*
* The start and end of the external DTD subset are reported
* using the pseudo-name "[dtd]". All other events must be
* properly nested within start/end entity events.
*
* Note that skipped entities will be reported through the
* {@link org.xml.sax.ContentHandler#skippedEntity skippedEntity}
* event, which is part of the ContentHandler interface.
*
* @param name The name of the entity. If it is a parameter
* entity, the name will begin with '%'.
* @throws SAXException The application may raise an exception.
* @see #endEntity
* @see org.xml.sax.ext.DeclHandler#internalEntityDecl
* @see org.xml.sax.ext.DeclHandler#externalEntityDecl
*/
public void startEntity(String name) throws SAXException
{
// no op
}
/**
* Report the end of an entity.
*
* @param name The name of the entity that is ending.
* @throws SAXException The application may raise an exception.
* @see #startEntity
*/
public void endEntity(String name) throws SAXException
{
// no op
}
/**
* Report the start of a CDATA section.
*
* The contents of the CDATA section will be reported through
* the regular {@link org.xml.sax.ContentHandler#characters
* characters} event.
*
* @throws SAXException The application may raise an exception.
* @see #endCDATA
*/
public void startCDATA() throws SAXException
{
m_textType = DTM.CDATA_SECTION_NODE;
}
/**
* Report the end of a CDATA section.
*
* @throws SAXException The application may raise an exception.
* @see #startCDATA
*/
public void endCDATA() throws SAXException
{
m_textType = DTM.TEXT_NODE;
}
/**
* Report an XML comment anywhere in the document.
*
* This callback will be used for comments inside or outside the
* document element, including comments in the external DTD
* subset (if read).
*
* @param ch An array holding the characters in the comment.
* @param start The starting position in the array.
* @param length The number of characters to use from the array.
* @throws SAXException The application may raise an exception.
*/
public void comment(char ch[], int start, int length) throws SAXException
{
if (m_insideDTD) // ignore comments if we're inside the DTD
return;
charactersFlush();
int exName = m_expandedNameTable.getExpandedTypeID(DTM.COMMENT_NODE);
// For now, treat comments as strings... I guess we should do a
// seperate FSB buffer instead.
int dataIndex = m_valuesOrPrefixes.stringToIndex(new String(ch, start,
length));
m_previous = addNode(DTM.COMMENT_NODE, exName,
m_parents.peek(), m_previous, dataIndex, false);
}
/**
* Set a run time property for this DTM instance.
*
* %REVIEW% Now that we no longer use this method to support
* getSourceLocatorFor, can we remove it?
*
* @param property a String
value
* @param value an Object
value
*/
public void setProperty(String property, Object value)
{
}
/** Retrieve the SourceLocator associated with a specific node.
* This is only meaningful if the XalanProperties.SOURCE_LOCATION flag was
* set True using setProperty; if it was never set, or was set false, we
* will return null.
*
* (We _could_ return a locator with the document's base URI and bogus
* line/column information. Trying that; see the else clause.)
* */
public SourceLocator getSourceLocatorFor(int node)
{
if (m_useSourceLocationProperty)
{
node = makeNodeIdentity(node);
return new NodeLocator(null,
m_sourceSystemId.elementAt(node),
m_sourceLine.elementAt(node),
m_sourceColumn.elementAt(node));
}
else if(m_locator!=null)
{
return new NodeLocator(null,m_locator.getSystemId(),-1,-1);
}
else if(m_systemId!=null)
{
return new NodeLocator(null,m_systemId,-1,-1);
}
return null;
}
public String getFixedNames(int type){
return m_fixednames[type];
}
}