package org.codehaus.staxmate.in;
import java.io.IOException;
import java.io.Writer;
import javax.xml.namespace.QName;
import javax.xml.stream.Location;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader; // for javadocs
import org.codehaus.stax2.DTDInfo;
import org.codehaus.stax2.LocationInfo;
import org.codehaus.stax2.XMLStreamReader2;
import org.codehaus.staxmate.util.DataUtil;
/**
* Base class for reader-side cursors that form the main input-side
* abstraction offered by StaxMate.
*<p>
* Implementation Note: since cursors are thin wrappers around
* {@link XMLStreamReader2},
* and since not all Stax implementations implement
* {@link XMLStreamReader2}, some wrapping may be involved in exposing
* basic Stax 1.0 stream readers as Stax2 stream readers.
* Without native support, not all stax2 features may be available,
* but cursors will try to limit their usage to known working subset.
*
* @author Tatu Saloranta
*/
public abstract class SMInputCursor
extends CursorBase
{
/*
////////////////////////////////////////////
// Constants, tracking
////////////////////////////////////////////
*/
// // // Constants for element tracking:
/**
* Different tracking behaviors available for cursors.
* Tracking is a feature that can be used to store
* information about traversed sub-trees, to allow for a limited
* access to information that is not limited to ancestor stack.
* Using tracking will consume more memory, but generally less
* than constructing a full in-memory tree object model (such
* as DOM), since it the represenation is compact, read-only,
* and only subset of a full tree. Size (and hence memory overhead)
* of that sub-tree depends on tracking settings.
*/
public enum Tracking
{
/**
* Value that indicates that no element state information should
* be tracked. This means that {@link #getTrackedElement} will always
* return null for this element, as well as that if immediate child
* cursors do have tracking enabled, element states it saves have
* no parent element information available.
*/
NONE,
/**
* Value that indicates that element basic state information should
* be tracked, including linkage to the parent element (but only
* if the parent cursor was tracking elements).
* This means that {@link #getTrackedElement} will return non-null
* values, as soon as this cursor has been advanced over its first
* element node. However, element will return null from its
* {@link SMElementInfo#getPreviousSibling} since sibling information
* is not tracked.
*/
PARENTS,
/**
* Value that indicates full element state information should
* be tracked for all "visible" elements: visible meaning that element
* node was accepted by the filter this cursor uses.
* This means that {@link #getTrackedElement} will return non-null
* values, as soon as this cursor has been advanced over its first
* element node, and that element will return non-null from its
* {@link SMElementInfo#getPreviousSibling} unless it's the first element
* iterated by this cursor.
*/
VISIBLE_SIBLINGS,
/**
* Value that indicates full element state information should
* be tracked for ALL elements (including ones not visible to the
* caller via {@link #getNext} method).
* This means that {@link #getTrackedElement} will return non-null
* values, as soon as this cursor has been advanced over its first
* element node, and that element will return non-null from its
* {@link SMElementInfo#getPreviousSibling} unless it's the first element
* iterated by this cursor.
*/
ALL_SIBLINGS
}
/*
////////////////////////////////////////////
// Configuration
////////////////////////////////////////////
*/
/**
* Optional filter object that can be used to filter out events of
* types caller is not interested in.
*/
protected SMFilter mFilter = null;
/**
* Whether element information is to be tracked or not, and if it is,
* how much of it will be stored. See {@link Tracking} for details.
*/
protected Tracking mElemTracking = Tracking.NONE;
/**
* Optional factory instance that is used to create element info
* objects if element tracking is enabled. If null, will use default
* generation mechanism, implemented by SMInputCursor itself.
*<p>
* Note that by default, this factory will be passed down to child
* and descendant cursors this cursor creates, so usually one
* only needs to set the factory of the root cursor.
*/
protected ElementInfoFactory mElemInfoFactory;
/*
////////////////////////////////////////////
// Additional data
////////////////////////////////////////////
*/
/**
* Non-typesafe payload data that applications can use, to pass
* an extra argument along with cursors. Not used by the framework
* itself for anything.
*/
protected Object mData;
/*
////////////////////////////////////////////
// Life cycle, configuration
////////////////////////////////////////////
*/
public SMInputCursor(SMInputCursor parent, XMLStreamReader2 sr, SMFilter filter)
{
super(sr, (parent == null) ? 0 : sr.getDepth());
mFilter = filter;
/* By default, we use parent cursor's element tracking setting;
* or "no tracking" if we have no parent
*/
if (parent == null) {
mElemTracking = Tracking.NONE;
mParentTrackedElement = null;
mElemInfoFactory = null;
} else {
mElemTracking = parent.getElementTracking();
mParentTrackedElement = parent.getTrackedElement();
mElemInfoFactory = parent.getElementInfoFactory();
}
}
/**
* Method for setting filter used for selecting which events
* are to be returned to the caller when {@link #getNext}
* is called.
*/
public final void setFilter(SMFilter f) {
mFilter = f;
}
/**
* Changes tracking mode of this cursor to the new specified
* mode. Default mode for cursors is the one their parent uses;
* {@link Tracking#NONE} for root cursors with no parent.
*<p>
* See also {@link #getPathDesc} for information on how
* to display tracked path/element information.
*/
public final void setElementTracking(Tracking tracking) {
mElemTracking = tracking;
}
public final Tracking getElementTracking() {
return mElemTracking;
}
/**
* Set element info factory used for constructing
* {@link SMElementInfo} instances during traversal for this
* cursor, as well as all of its children.
*/
public final void setElementInfoFactory(ElementInfoFactory f) {
mElemInfoFactory = f;
}
public final ElementInfoFactory getElementInfoFactory() {
return mElemInfoFactory;
}
/*
///////////////////////////////////////////////////
// Public API, accessing cursor state information
///////////////////////////////////////////////////
*/
/**
* Method to access number of nodes cursor has traversed
* (including ones that were filtered out, if any).
* Starts with 0, and is incremented each time
* underlying stream reader's {@link XMLStreamReader#next} method
* is called, but not counting child cursors' node counts.
*
* @return Number of nodes (events) cursor has traversed
*/
public int getNodeCount() {
return mNodeCount;
}
/**
* Method to access number of start elements cursor has traversed
* (including ones that were filtered out, if any).
* Starts with 0, and is incremented each time
* underlying stream reader's {@link XMLStreamReader#next} method
* is called and has moved over a start element, but not counting
* child cursors' element counts.
*
* @return Number of start elements cursor has traversed
*/
public int getElementCount() {
return mElemCount;
}
/**
* @deprecated Use {@link #getParentCount()} instead
*/
@Deprecated
public final int getDepth() {
return getParentCount();
}
/**
* Number of parent elements that the token/event cursor points to has,
* if it points to one. If not, either most recent valid parent
* count (if cursor is closed), or the depth that it will have
* once is is advanced. One practical result is that a nested
* cursor instance will always have a single constant value it
* returns, whereas flattening cursors can return different
* values during traversal. Another thing to notice that matching
* START_ELEMENT and END_ELEMENT will always correspond to the
* same parent count.
*<p>
* For example, here are expected return values
* for an example XML document:
*<pre>
* <!-- Comment outside tree --> [0]
* <root> [0]
* Text [1]
* <branch> [1]
* Inner text [2]
* <child /> [2]/[2]
* </branch> [1]
* </root> [0]
*</pre>
* Numbers in bracket are depths that would be returned when the
* cursor points to the node.
*<p>
* Note: depths are different from what many other xml processing
* APIs (such as Stax and XmlPull)return.
*
* @return Number of enclosing nesting levels, ie. number of parent
* start elements for the node that cursor currently points to (or,
* in case of initial state, that it will point to if scope has
* node(s)).
*/
public abstract int getParentCount();
/**
* Returns the type of event this cursor either currently points to
* (if in valid state), or pointed to (if ever iterated forward), or
* null if just created.
*
* @return Type of event this cursor points to, if it currently points
* to one, or last one it pointed to otherwise (if ever pointed to
* a valid event), or null if neither.
*/
public SMEvent getCurrEvent() {
return mCurrEvent;
}
/**
* Convenience method doing
*/
public int getCurrEventCode() {
return (mCurrEvent == null) ? 0 : mCurrEvent.getEventCode();
}
/**
* @return True if this cursor iterates over root level of
* the underlying stream reader
*/
public final boolean isRootCursor() {
return (mBaseDepth == 0);
}
/*
////////////////////////////////////////////
// Public API, accessing tracked elements
////////////////////////////////////////////
*/
/**
* @return Information about last "tracked" element; element we have
* last iterated over when tracking has been enabled.
*/
public SMElementInfo getTrackedElement() {
return mTrackedElement;
}
/**
* @return Information about the tracked element the parent cursor
* had, if parent cursor existed and was tracking element
* information.
*/
public SMElementInfo getParentTrackedElement() {
return mParentTrackedElement;
}
/*
////////////////////////////////////////////////
// Public API, accessing current document state
////////////////////////////////////////////////
*/
/**
* Method that can be used to check whether this cursor is
* currently valid; that is, it is the cursor that points
* to the event underlying stream is at. Only one cursor
* at any given time is valid in this sense, although other
* cursors may be made valid by advancing them (and by process
* invalidating the cursor that was valid at that point).
* It is also possible that none of cursors is valid at
* some point: this is the case when formerly valid cursor
* reached end of its contet (END_ELEMENT).
*
* @return True if the cursor is currently valid; false if not
*/
public final boolean readerAccessible() {
return (mState == State.ACTIVE);
}
/**
* Method that can be used to get direct access to the underlying
* stream reader. Custom sub-classed versions (which can be constructed
* by overriding this classes factory methods) can choose to block
* such access, but the default implementation does allow access
* to it.
*<p>
* Note that this method should not be needed (or extensively used)
* for regular StaxMate usage, because direct access to the stream
* may cause cursor's understanding of stream reader state to be
* incompatible with its actual state.
*
* @return Stream reader the cursor uses for getting XML events
*/
public XMLStreamReader2 getStreamReader() {
return _getStreamReader();
}
/**
* Method to access starting Location of event (as defined by Stax
* specification)
* that this cursor points to.
* Method can only be called if the
* cursor is valid (as per {@link #readerAccessible}); if not,
* an exception is thrown
*
* @return Location of the event this cursor points to
*/
public Location getCursorLocation()
throws XMLStreamException
{
if (!readerAccessible()) {
throw _notAccessible("getLocation");
}
// Let's try to get actual exact location via Stax2 first:
LocationInfo li = mStreamReader.getLocationInfo();
if (li != null) {
Location loc = li.getStartLocation();
if (loc != null) {
return loc;
}
}
// If not, fall back to regular method
return mStreamReader.getLocation();
}
/**
* Method to access Location that the underlying stream reader points
* to.
*
* @return Location of the event the underlying stream reader points
* to (independent of whether this cursor points to that event)
*/
public Location getStreamLocation()
{
// Let's try to get actual exact location via Stax2 first:
LocationInfo li = mStreamReader.getLocationInfo();
if (li != null) {
Location loc = li.getCurrentLocation();
if (loc != null) {
return loc;
}
}
// If not, fall back to regular method
return mStreamReader.getLocation();
}
/**
* Same as calling {@link #getCursorLocation}
*
* @deprecated
*/
@Deprecated
public Location getLocation()
throws XMLStreamException
{
if (!readerAccessible()) {
throw _notAccessible("getLocation");
}
return mStreamReader.getLocation();
}
/*
////////////////////////////////////////////////
// Public API, accessing document text content
////////////////////////////////////////////////
*/
/**
* Method that can be used when this cursor points to a textual
* event; something for which {@link XMLStreamReader#getText} can
* be called. Note that it does not advance the cursor, or combine
* multiple textual events.
*
* @return Textual content of the current event that this cursor
* points to, if any
*
* @throws XMLStreamException if either the underlying parser has
* problems (possibly including event type not being of textual
* type, see Stax 1.0 specs for details); or if this cursor does
* not currently point to an event.
*/
public String getText()
throws XMLStreamException
{
if (!readerAccessible()) {
throw _notAccessible("getText");
}
return mStreamReader.getText();
}
/**
* Method that can collect all text contained within START_ELEMENT
* currently pointed by this cursor. Collection is done recursively
* through all descendant text (CHARACTER, CDATA; optionally SPACE) nodes,
* ignoring nodes of other types. After collecting text, cursor
* will be positioned at the END_ELEMENT matching initial START_ELEMENT
* and thus needs to be advanced to access the next sibling event.
*
* @param includeIgnorable Whether text for events of type SPACE should
* be ignored in the results or not. If false, SPACE events will be
* skipped; if true, white space will be included in results.
*/
public String collectDescendantText(boolean includeIgnorable)
throws XMLStreamException
{
if (!readerAccessible()) {
throw _notAccessible("getText");
}
if (getCurrEvent() != SMEvent.START_ELEMENT) {
throw constructStreamException("Can not call 'getText()' when cursor is not positioned over START_ELEMENT (current event "+currentEventStr()+")");
}
SMFilter f = includeIgnorable
? SMFilterFactory.getTextOnlyFilter()
: SMFilterFactory.getNonIgnorableTextFilter();
SMInputCursor childIt = descendantCursor(f);
/* Cursor should only return actual text nodes, so no type
* checks are needed, except for checks for EOF. But we can
* also slightly optimize things, by avoiding StringBuilder
* construction if there's just one node.
*/
if (childIt.getNext() == null) {
return "";
}
String text = childIt.getText(); // has to be a text event
if (childIt.getNext() == null) {
return text;
}
XMLStreamReader2 sr = childIt._getStreamReader();
int size = text.length() + sr.getTextLength()+ 20;
StringBuffer sb = new StringBuffer(Math.max(size, 100));
sb.append(text);
do {
// Let's assume char array access is more efficient...
sb.append(sr.getTextCharacters(), sr.getTextStart(),
sr.getTextLength());
} while (childIt.getNext() != null);
return sb.toString();
}
/**
* Method similar to {@link #collectDescendantText}, but will write
* the text to specified Writer instead of collecting it into a
* String.
*
* @param w Writer to use for outputting text found
* @param includeIgnorable Whether text for events of type SPACE should
* be ignored in the results or not. If false, SPACE events will be
* skipped; if true, white space will be included in results.
*/
public void processDescendantText(Writer w, boolean includeIgnorable)
throws IOException, XMLStreamException
{
SMFilter f = includeIgnorable
? SMFilterFactory.getTextOnlyFilter()
: SMFilterFactory.getNonIgnorableTextFilter();
SMInputCursor childIt = descendantCursor(f);
// Any text in there?
XMLStreamReader2 sr = childIt._getStreamReader();
while (childIt.getNext() != null) {
/* 'true' indicates that we are not to lose the text contained
* (can call getText() multiple times, idempotency). While this
* may not be as efficient as allowing content to be discarded,
* let's play it safe. Another method could be added for
* the alternative (fast but dangerous) behaviour as needed.
*/
sr.getText(w, true);
}
}
/*
////////////////////////////////////////////////////
// Public API, accessing current element information
////////////////////////////////////////////////////
*/
public QName getQName()
throws XMLStreamException
{
if (!readerAccessible()) {
throw _notAccessible("getName");
}
return mStreamReader.getName();
}
/**
* For events with fully qualified names (START_ELEMENT, END_ELEMENT,
* ATTRIBUTE, NAMESPACE) returns the local component of the full
* name; for events with only non-qualified name (PROCESSING_INSTRUCTION,
* entity and notation declarations, references) returns the name, and
* for other events, returns null.
*
* @return Local component of the name
*/
public String getLocalName()
throws XMLStreamException
{
if (!readerAccessible()) {
throw _notAccessible("getLocalName");
}
switch (getCurrEventCode()) {
case XMLStreamConstants.START_ELEMENT:
case XMLStreamConstants.END_ELEMENT:
case XMLStreamConstants.ENTITY_REFERENCE:
return mStreamReader.getLocalName();
case XMLStreamConstants.PROCESSING_INSTRUCTION:
return mStreamReader.getPITarget();
case XMLStreamConstants.DTD:
{
DTDInfo dtd = mStreamReader.getDTDInfo();
return (dtd == null) ? null : dtd.getDTDRootName();
}
}
return null;
}
/**
* Method for accessing namespace prefix of the START_ELEMENT this
* cursor points to.
*
* @return Prefix of currently pointed-to START_ELEMENT,
* if it has one; "" if none
*
* @throws XMLStreamException if cursor does not point to START_ELEMENT
*/
public String getPrefix()
throws XMLStreamException
{
if (!readerAccessible()) {
throw _notAccessible("getPrefix");
}
String prefix = mStreamReader.getPrefix();
// some impls may return null instead, let's convert
return (prefix == null) ? "" : prefix;
}
/**
* Method for accessing namespace URI of the START_ELEMENT this
* cursor points to.
*
* @return Namespace URI of currently pointed-to START_ELEMENT,
* if it has one; "" if none
*
* @throws XMLStreamException if cursor does not point to START_ELEMENT
*/
public String getNsUri()
throws XMLStreamException
{
if (!readerAccessible()) {
throw _notAccessible("getNsUri");
}
String uri = mStreamReader.getNamespaceURI();
// some impls may return null instead, let's convert
return (uri == null) ? "" : uri;
}
/**
* Returns a String representation of either the fully-qualified name
* (if the event has full name) or the local name (if event does not
* have full name but has local name); or if no name available, throws
* stream exception.
*/
public String getPrefixedName()
throws XMLStreamException
{
if (!readerAccessible()) {
throw _notAccessible("getPrefixedName");
}
return mStreamReader.getPrefixedName();
}
/**
* Method for verifying whether current named event (one for which
* {@link #getLocalName} can be called)
* has the specified local name or not.
*
* @return True if the local name associated with the event is
* as expected
*/
public boolean hasLocalName(String expName)
throws XMLStreamException
{
if (!readerAccessible()) {
throw _notAccessible("hasName");
}
if (expName == null) {
throw new IllegalArgumentException("Can not pass null name to method");
}
String name = getLocalName();
return (name != null) && expName.equals(name);
}
/**
* Method for verifying whether current named event (one for which
* {@link #getLocalName} can be called) has the specified
* fully-qualified name or not.
* Both namespace URI and local name must match for the result
* to be true.
*
* @return True if the fully-qualified name associated with the event is
* as expected
*/
public boolean hasName(String expNsURI, String expLN)
throws XMLStreamException
{
if (!readerAccessible()) {
throw _notAccessible("hasName");
}
int type = getCurrEventCode();
String uri;
String ln;
switch (type) {
case XMLStreamConstants.START_ELEMENT:
case XMLStreamConstants.END_ELEMENT:
ln = mStreamReader.getLocalName();
uri = mStreamReader.getNamespaceURI();
break;
case XMLStreamConstants.ENTITY_REFERENCE:
ln = mStreamReader.getLocalName();
uri = null;
break;
case XMLStreamConstants.PROCESSING_INSTRUCTION:
ln = mStreamReader.getPITarget();
uri = null;
break;
case XMLStreamConstants.DTD:
{
DTDInfo dtd = mStreamReader.getDTDInfo();
ln = (dtd == null) ? null : dtd.getDTDRootName();
}
uri = null;
break;
default:
return false;
}
if (ln == null || !ln.equals(expLN)) {
return false;
}
if (expNsURI == null || expNsURI.length() == 0) { // no namespace
return (uri == null) || (uri.length() == 0);
}
return (uri != null) && expNsURI.equals(uri);
}
/*
////////////////////////////////////////////////////
// Public API, accessing current element's attribute
// information
////////////////////////////////////////////////////
*/
/**
* Method that can be called when this cursor points to START_ELEMENT,
* and which will return number of attributes with values for the
* start element. This includes both explicit attribute values and
* possible implied default values (when DTD support is enabled
* by the underlying stream reader).
*
* @throws XMLStreamException if either the underlying parser has
* problems (cursor not valid or not pointing to START_ELEMENT)
*/
public int getAttrCount()
throws XMLStreamException
{
if (!readerAccessible()) {
throw _notAccessible("getAttrCount");
}
return mStreamReader.getAttributeCount();
}
/**
* Method that can be called when this cursor points to START_ELEMENT,
* and which will return index of specified attribute, if it
* exists for this element. If not, -1 is returned to denote "not found".
*
* @throws XMLStreamException if either the underlying parser has
* problems (cursor not valid or not pointing to START_ELEMENT)
*/
public int findAttrIndex(String uri, String localName)
throws XMLStreamException
{
if (!readerAccessible()) {
throw _notAccessible("getAttrCount");
}
return mStreamReader.getAttributeInfo().findAttributeIndex(uri, localName);
}
/**
* Method that can be called when this cursor points to START_ELEMENT,
* and returns fully qualified name
* of the attribute at specified index.
* Index has to be between [0, {@link #getAttrCount}[; otherwise
* {@link IllegalArgumentException} will be thrown.
*
* @param index Index of the attribute
*
* @throws XMLStreamException if either the underlying parser has
* problems (cursor not valid or not pointing to START_ELEMENT),
* or if invalid attribute
* @throws IllegalArgumentException if attribute index is invalid
* (less than 0 or greater than the last valid index
* [getAttributeCount()-1])
*/
public QName getAttrName(int index)
throws XMLStreamException
{
if (!readerAccessible()) {
throw _notAccessible("getAttrName");
}
return mStreamReader.getAttributeName(index);
}
/**
* Method that can be called when this cursor points to START_ELEMENT,
* and returns local name
* of the attribute at specified index.
* Index has to be between [0, {@link #getAttrCount}[; otherwise
* {@link IllegalArgumentException} will be thrown.
*
* @param index Index of the attribute
*
* @throws XMLStreamException if either the underlying parser has
* problems (cursor not valid or not pointing to START_ELEMENT),
* or if invalid attribute
* @throws IllegalArgumentException if attribute index is invalid
* (less than 0 or greater than the last valid index
* [getAttributeCount()-1])
*/
public String getAttrLocalName(int index)
throws XMLStreamException
{
if (!readerAccessible()) {
throw _notAccessible("getAttrLocalName");
}
return mStreamReader.getAttributeLocalName(index);
}
/**
* Method that can be called when this cursor points to START_ELEMENT,
* and returns namespace prefix
* of the attribute at specified index (if it has any), or
* empty String if attribute has no prefix (does not belong to
* a namespace).
* Index has to be between [0, {@link #getAttrCount}[; otherwise
* {@link IllegalArgumentException} will be thrown.
*
* @param index Index of the attribute
*
* @throws XMLStreamException if either the underlying parser has
* problems (cursor not valid or not pointing to START_ELEMENT),
* or if invalid attribute
* @throws IllegalArgumentException if attribute index is invalid
* (less than 0 or greater than the last valid index
* [getAttributeCount()-1])
*/
public String getAttrPrefix(int index)
throws XMLStreamException
{
if (!readerAccessible()) {
throw _notAccessible("getAttrPrefix");
}
String prefix = mStreamReader.getAttributePrefix(index);
// some impls may return null instead, let's convert
return (prefix == null) ? "" : prefix;
}
/**
* Method that can be called when this cursor points to START_ELEMENT,
* and returns namespace URI
* of the attribute at specified index (non-empty String if it has
* one, and empty String if attribute does not belong to a namespace)
* Index has to be between [0, {@link #getAttrCount}[; otherwise
* {@link IllegalArgumentException} will be thrown.
*
* @param index Index of the attribute
*
* @throws XMLStreamException if either the underlying parser has
* problems (cursor not valid or not pointing to START_ELEMENT),
* or if invalid attribute
* @throws IllegalArgumentException if attribute index is invalid
* (less than 0 or greater than the last valid index
* [getAttributeCount()-1])
*/
public String getAttrNsUri(int index)
throws XMLStreamException
{
if (!readerAccessible()) {
throw _notAccessible("getAttrNsUri");
}
String uri = mStreamReader.getAttributeNamespace(index);
// some impls may return null instead, let's convert
return (uri == null) ? "" : uri;
}
/**
* Method that can be called when this cursor points to START_ELEMENT,
* and returns unmodified textual value
* of the attribute at specified index (non-empty String if it has
* one, and empty String if attribute does not belong to a namespace)
* Index has to be between [0, {@link #getAttrCount}[; otherwise
* {@link IllegalArgumentException} will be thrown.
*
* @param index Index of the attribute
*
* @throws XMLStreamException if either the underlying parser has
* problems (cursor not valid or not pointing to START_ELEMENT),
* or if invalid attribute
* @throws IllegalArgumentException if attribute index is invalid
* (less than 0 or greater than the last valid index
* [getAttributeCount()-1])
*/
public String getAttrValue(int index)
throws XMLStreamException
{
if (!readerAccessible()) {
throw _notAccessible("getAttributeValue");
}
return mStreamReader.getAttributeValue(index);
}
/**
* Convenience accessor method to access an attribute that is
* not in a namespace (has no prefix). Equivalent to
* calling {@link #getAttrValue(String,String)} with
* 'null' for 'namespace URI' argument
*/
public String getAttrValue(String localName)
throws XMLStreamException
{
if (!readerAccessible()) {
throw _notAccessible("getAttributeValue");
}
/* If we are to believe StAX specs, null would mean "do not
* check namespace" -- that's pretty much never what anyone
* really wants (or, at least should use), so let's pass
* "" to indicate "no namespace"
*/
/* 16-Jun-2008, tatu: Alas, Sun sjsxp doesn't seem to work
* well if we do pass "" instead of null! Since Woodstox
* works ok with both, let's use null -- specs are irrelevant
* if no implementation follows this particular quirk.
*/
//return mStreamReader.getAttributeValue("", localName);
return mStreamReader.getAttributeValue(null, localName);
}
/**
* Method that can be called when this cursor points to START_ELEMENT,
* and returns unmodified textual value
* of the specified attribute (if element has it), or null if
* element has no value for such attribute.
*
* @param namespaceURI Namespace URI for the attribute, if any;
* empty String or null if none.
* @param localName Local name of the attribute to access (in
* namespace-aware mode: in non-namespace-aware mode, needs to
* be the full name)
*
* @throws XMLStreamException if either the underlying parser has
* problems (cursor not valid or not pointing to START_ELEMENT),
* or if invalid attribute
*/
public String getAttrValue(String namespaceURI, String localName)
throws XMLStreamException
{
if (!readerAccessible()) {
throw _notAccessible("getAttrValue");
}
return mStreamReader.getAttributeValue(namespaceURI, localName);
}
/*
////////////////////////////////////////////////////
// Public API, Typed Access API for attributes
////////////////////////////////////////////////////
*/
/**
* Method for accessing value of specified attribute as boolean.
* Method will only succeed if the attribute value is a valid
* boolean, as specified by XML Schema specification (and hence
* is accessible via Stax2 Typed Access API).
*
* @param index Index of attribute to access
*
* @throws XMLStreamException If specified attribute can not be
* accessed (due to cursor state), or if attribute value
* is not a valid textual representation of boolean
* @throws IllegalArgumentException If given attribute index is invalid
*/
public boolean getAttrBooleanValue(int index)
throws NumberFormatException, XMLStreamException
{
if (!readerAccessible()) {
throw _notAccessible("getAttrBooleanValue");
}
/* For now, let's just get it as String and convert: in future,
* may be able to use more efficient access method(s)
*/
String value = mStreamReader.getAttributeValue(index);
try {
return DataUtil.parseBoolean(value);
} catch (IllegalArgumentException iae) {
throw constructStreamException("Attribute #"+index+" value not numeric: "+iae.getMessage());
}
}
/**
* Method for accessing value of specified attribute as boolean.
* If attribute value is not a valid boolean
* (as specified by XML Schema specification), will instead
* return specified "default value".
*
* @param index Index of attribute to access
* @param defValue Value to return if attribute value exists but
* is not a valid boolean value
*
* @throws XMLStreamException If specified attribute can not be
* accessed (due to cursor state), or if attribute value
* is not a valid textual representation of boolean.
* @throws IllegalArgumentException If given attribute index
* is invalid
*/
public boolean getAttrBooleanValue(int index, boolean defValue)
throws NumberFormatException, XMLStreamException
{
if (!readerAccessible()) {
throw _notAccessible("getAttrBooleanValue");
}
/* For now, let's just get it as String and convert: in future,
* may be able to use more efficient access method(s)
*/
return DataUtil.parseBoolean(mStreamReader.getAttributeValue(index), defValue);
}
/**
* Method for accessing value of specified attribute as integer.
* Method will only succeed if the attribute value is a valid
* integer, as specified by XML Schema specification (and hence
* is accessible via Stax2 Typed Access API).
*
* @param index Index of attribute to access
*
* @throws XMLStreamException If specified attribute can not be
* accessed (due to cursor state), or if attribute value
* is not a valid textual representation of integer.
* @throws IllegalArgumentException If given attribute index
* is invalid
*/
public int getAttrIntValue(int index)
throws NumberFormatException, XMLStreamException
{
if (!readerAccessible()) {
throw _notAccessible("getAttrIntValue");
}
/* For now, let's just get it as String and convert: in future,
* may be able to use more efficient access method(s)
*/
String value = mStreamReader.getAttributeValue(index);
try {
return DataUtil.parseInt(value);
} catch (IllegalArgumentException iae) {
throw constructStreamException("Attribute #"+index+" value not numeric: "+iae.getMessage());
}
}
/**
* Method for accessing value of specified attribute as integer.
* If attribute value is not a valid integer
* (as specified by XML Schema specification), will instead
* return specified "default value".
*
* @param index Index of attribute to access
* @param defValue Value to return if attribute value exists but
* is not a valid integer value
*
* @throws XMLStreamException If specified attribute can not be
* accessed (due to cursor state), or if attribute value
* is not a valid textual representation of integer.
* @throws IllegalArgumentException If given attribute index
* is invalid
*/
public int getAttrIntValue(int index, int defValue)
throws NumberFormatException, XMLStreamException
{
if (!readerAccessible()) {
throw _notAccessible("getAttrIntValue");
}
/* For now, let's just get it as String and convert: in future,
* may be able to use more efficient access method(s)
*/
return DataUtil.parseInt(mStreamReader.getAttributeValue(index), defValue);
}
/**
* Method for accessing value of specified attribute as long.
* Method will only succeed if the attribute value is a valid
* long, as specified by XML Schema specification (and hence
* is accessible via Stax2 Typed Access API).
*
* @param index Index of attribute to access
*
* @throws XMLStreamException If specified attribute can not be
* accessed (due to cursor state), or if attribute value
* is not a valid textual representation of long.
* @throws IllegalArgumentException If given attribute index
* is invalid
*/
public long getAttrLongValue(int index)
throws NumberFormatException, XMLStreamException
{
if (!readerAccessible()) {
throw _notAccessible("getAttrLongValue");
}
/* For now, let's just get it as String and convert: in future,
* may be able to use more efficient access method(s)
*/
String value = mStreamReader.getAttributeValue(index);
try {
return DataUtil.parseLong(value);
} catch (IllegalArgumentException iae) {
throw constructStreamException("Attribute #"+index+" value not numeric: "+iae.getMessage());
}
}
/**
* Method for accessing value of specified attribute as long.
* If attribute value is not a valid long
* (as specified by XML Schema specification), will instead
* return specified "default value".
*
* @param index Index of attribute to access
* @param defValue Value to return if attribute value exists but
* is not a valid long value
*
* @throws XMLStreamException If specified attribute can not be
* accessed (due to cursor state), or if attribute value
* is not a valid textual representation of long.
* @throws IllegalArgumentException If given attribute index
* is invalid
*/
public long getAttrLongValue(int index, long defValue)
throws NumberFormatException, XMLStreamException
{
if (!readerAccessible()) {
throw _notAccessible("getAttrLongValue");
}
/* For now, let's just get it as String and convert: in future,
* may be able to use more efficient access method(s)
*/
return DataUtil.parseLong(mStreamReader.getAttributeValue(index), defValue);
}
/*
////////////////////////////////////////////////////
// Deprecated data access
////////////////////////////////////////////////////
*/
/**
* @deprecated Use combination of {@link #findAttrIndex} and
* {@link #getAttrIntValue(int)} instead.
*/
@Deprecated
public int getAttrIntValue(String uri, String localName)
throws XMLStreamException
{
if (!readerAccessible()) {
throw _notAccessible("getAttrIntValue");
}
/* For now, let's just get it as String and convert: in future,
* may be able to use more efficient access method(s)
*/
String value = mStreamReader.getAttributeValue(uri, localName);
return DataUtil.parseInt(value);
}
/**
* @deprecated Use combination of {@link #findAttrIndex} and
* {@link #getAttrIntValue(int,int)} instead.
*/
@Deprecated
public int getAttrIntValue(String uri, String localName, int defValue)
throws XMLStreamException
{
if (!readerAccessible()) {
throw _notAccessible("getAttrIntValue");
}
/* For now, let's just get it as String and convert: in future,
* may be able to use more efficient access method(s)
*/
String valueStr = mStreamReader.getAttributeValue(uri, localName);
return DataUtil.parseInt(valueStr, defValue);
}
/*
////////////////////////////////////////////////////
// Public API, Typed Access API for element values
////////////////////////////////////////////////////
*/
/**
* Method that can collect text <b>directly</b> contained within
* START_ELEMENT currently pointed by this cursor.
* This is different from {@link #collectDescendantText} in that
* it does NOT work for mixed content
* (child elements are not allowed:
* comments and processing instructions are allowed and ignored
* if encountered).
* If any ignorable white space (as per schema, dtd or so) is encountered,
* it will be ignored.
*<p>
* The main technical difference to {@link #collectDescendantText} is
* that this method tries to make use of Stax2 v3.0 Typed Access API,
* if available, and can thus be more efficient.
*
* @throws XMLStreamException if content is not accessible; may also
* be thrown if child elements are encountered.
*/
public String getElemStringValue()
throws XMLStreamException
{
if (!readerAccessible()) {
throw _notAccessible("getElemStringValue");
}
if (getCurrEvent() != SMEvent.START_ELEMENT) {
throw _wrongState("getElemStringValue", SMEvent.START_ELEMENT);
}
/* !!! 02-Sep-2008, tatus: In future, should convert to using
* Stax2 v3.0 Typed Access API. But that's only available
* for StaxMate 2.0 and above.
*/
SMInputCursor childIt = childCursor(SMFilterFactory.getNonIgnorableTextFilter());
if (childIt.getNext() == null) {
return "";
}
String text = childIt.getText(); // has to be a text event
if (childIt.getNext() == null) {
return text;
}
XMLStreamReader2 sr = childIt._getStreamReader();
int size = text.length() + sr.getTextLength()+ 20;
StringBuffer sb = new StringBuffer(Math.max(size, 100));
sb.append(text);
do {
// Let's assume char array access is more efficient...
sb.append(sr.getTextCharacters(), sr.getTextStart(),
sr.getTextLength());
} while (childIt.getNext() != null);
return sb.toString();
}
/**
* Method that can collect text <b>directly</b> contained within
* START_ELEMENT currently pointed by this cursor and convert
* it to a boolean value.
* For method to work, the value must be legal textual representation of
*<b>boolean</b>
* data type as specified by W3C Schema (as well as Stax2 Typed
* Access API).
* Element also can not contain mixed content (child elements;
* comments and processing instructions are allowed and ignored
* if encountered).
*
* @throws XMLStreamException if content is not accessible or
* convertible to required return type
*/
public boolean getElemBooleanValue()
throws XMLStreamException
{
if (!readerAccessible()) {
throw _notAccessible("getElemBooleanValue");
}
if (getCurrEvent() != SMEvent.START_ELEMENT) {
throw _wrongState("getElemBooleanValue", SMEvent.START_ELEMENT);
}
/* !!! 02-Sep-2008, tatus: In future, should convert to using
* Stax2 v3.0 Typed Access API. But that's only available
* for StaxMate 2.0 and above.
*/
try {
return DataUtil.parseBoolean(getElemStringValue());
} catch (IllegalArgumentException iae) {
throw constructStreamException("Element value not boolean: "+iae.getMessage());
}
}
/**
* Similar to {@link #getElemBooleanValue()}, but instead of failing
* on invalid value, returns given default value.
*/
public boolean getElemBooleanValue(boolean defValue)
throws XMLStreamException
{
if (!readerAccessible()) {
throw _notAccessible("getElemBooleanValue");
}
if (getCurrEvent() != SMEvent.START_ELEMENT) {
throw _wrongState("getElemBooleanValue", SMEvent.START_ELEMENT);
}
String value = getElemStringValue();
if (value.length() > 0) {
value = value.trim();
if (value.length() > 0) {
try {
return DataUtil.parseBoolean(value);
} catch (IllegalArgumentException iae) { }
}
}
return defValue;
}
/**
* Method that can collect text <b>directly</b> contained within
* START_ELEMENT currently pointed by this cursor and convert
* it to a int value.
* For method to work, the value must be legal textual representation of
*<b>int</b>
* data type as specified by W3C Schema (as well as Stax2 Typed
* Access API).
* Element also can not contain mixed content (child elements;
* comments and processing instructions are allowed and ignored
* if encountered).
*
* @throws XMLStreamException if content is not accessible or
* convertible to required return type
*/
public int getElemIntValue()
throws XMLStreamException
{
if (!readerAccessible()) {
throw _notAccessible("getElemIntValue");
}
if (getCurrEvent() != SMEvent.START_ELEMENT) {
throw _wrongState("getElemIntValue", SMEvent.START_ELEMENT);
}
/* !!! 02-Sep-2008, tatus: In future, should convert to using
* Stax2 v3.0 Typed Access API. But that's only available
* for StaxMate 2.0 and above.
*/
try {
return DataUtil.parseInt(getElemStringValue());
} catch (IllegalArgumentException iae) {
throw constructStreamException("Element value not int: "+iae.getMessage());
}
}
/**
* Similar to {@link #getElemIntValue()}, but instead of failing
* on invalid value, returns given default value.
*/
public int getElemIntValue(int defValue)
throws XMLStreamException
{
if (!readerAccessible()) {
throw _notAccessible("getElemIntValue");
}
if (getCurrEvent() != SMEvent.START_ELEMENT) {
throw _wrongState("getElemIntValue", SMEvent.START_ELEMENT);
}
String value = getElemStringValue();
if (value.length() > 0) {
value = value.trim();
if (value.length() > 0) {
try {
return DataUtil.parseInt(value);
} catch (IllegalArgumentException iae) { }
}
}
return defValue;
}
/**
* Method that can collect text <b>directly</b> contained within
* START_ELEMENT currently pointed by this cursor and convert
* it to a long value.
* For method to work, the value must be legal textual representation of
*<b>long</b>
* data type as specified by W3C Schema (as well as Stax2 Typed
* Access API).
* Element also can not contain mixed content (child elements;
* comments and processing instructions are allowed and ignored
* if encountered).
*
* @throws XMLStreamException if content is not accessible or
* convertible to required return type
*/
public long getElemLongValue()
throws XMLStreamException
{
if (!readerAccessible()) {
throw _notAccessible("getElemLongValue");
}
if (getCurrEvent() != SMEvent.START_ELEMENT) {
throw _wrongState("getElemLongValue", SMEvent.START_ELEMENT);
}
/* !!! 02-Sep-2008, tatus: In future, should convert to using
* Stax2 v3.0 Typed Access API. But that's only available
* for StaxMate 2.0 and above.
*/
try {
return DataUtil.parseLong(getElemStringValue());
} catch (IllegalArgumentException iae) {
throw constructStreamException("Element value not long: "+iae.getMessage());
}
}
/**
* Similar to {@link #getElemLongValue()}, but instead of failing
* on invalid value, returns given default value.
*/
public long getElemLongValue(long defValue)
throws XMLStreamException
{
if (!readerAccessible()) {
throw _notAccessible("getElemLongValue");
}
if (getCurrEvent() != SMEvent.START_ELEMENT) {
throw _wrongState("getElemLongValue", SMEvent.START_ELEMENT);
}
String value = getElemStringValue();
if (value.length() > 0) {
value = value.trim();
if (value.length() > 0) {
try {
return DataUtil.parseLong(value);
} catch (IllegalArgumentException iae) { }
}
}
return defValue;
}
/*
////////////////////////////////////////////////
// Public API, accessing extra application data
////////////////////////////////////////////////
*/
/**
* Method for accessing application-provided data set previously
* by a {@link #setData} call.
*/
public Object getData() {
return mData;
}
/**
* Method for assigning per-cursor application-managed data,
* readable using {@link #getData}.
*/
public void setData(Object o) {
mData = o;
}
/*
////////////////////////////////////////////
// Public API, iteration
////////////////////////////////////////////
*/
/**
* Main iterating method.
*
* @return Type of event (from {@link XMLStreamConstants}, such as
* {@link XMLStreamConstants#START_ELEMENT}, if a new node was
* iterated over; <code>null</code> when there are no more
* nodes this cursor can iterate over.
*/
public abstract SMEvent getNext()
throws XMLStreamException;
/**
* Method that will create a new nested cursor for iterating
* over all (immediate) child nodes of the start element this cursor
* currently points to that are passed by the specified filter.
* If cursor does not point to a start element,
* it will throw {@link IllegalStateException}; if it does not support
* concept of child cursors, it will throw
* {@link UnsupportedOperationException}
*
* @param f Filter child cursor is to use for filtering out
* 'unwanted' nodes; may be null if no filtering is to be done
*
* @throws IllegalStateException If cursor can not be created due
* to the state cursor is in.
* @throws UnsupportedOperationException If cursor does not allow
* creation of child cursors.
*/
public SMInputCursor childCursor(SMFilter f)
throws XMLStreamException
{
if (mState != State.ACTIVE) {
if (mState == State.HAS_CHILD) {
throw new IllegalStateException("Child cursor already requested.");
}
throw new IllegalStateException("Can not iterate children: cursor does not point to a start element (state "+getStateDesc()+")");
}
if (mCurrEvent != SMEvent.START_ELEMENT) {
throw new IllegalStateException("Can not iterate children: cursor does not point to a start element (pointing to "+mCurrEvent+")");
}
mChildCursor = constructChildCursor(f);
mState = State.HAS_CHILD;
return mChildCursor;
}
/**
* Method that will create a new nested cursor for iterating
* over all (immediate) child nodes of the start element this cursor
* currently points to.
* If cursor does not point to a start element,
* it will throw {@link IllegalStateException}; if it does not support
* concept of child cursors, it will throw
* {@link UnsupportedOperationException}
*
* @throws IllegalStateException If cursor can not be created due
* to the state cursor is in.
* @throws UnsupportedOperationException If cursor does not allow
* creation of child cursors.
*/
public final SMInputCursor childCursor()
throws XMLStreamException
{
return childCursor(null);
}
/**
* Method that will create a new nested cursor for iterating
* over all the descendant (children and grandchildren) nodes of
* the start element this cursor currently points to
* that are accepted by the specified filter.
* If cursor does not point to a start element,
* it will throw {@link IllegalStateException}; if it does not support
* concept of descendant cursors, it will throw
* {@link UnsupportedOperationException}
*
*
* @param f Filter child cursor is to use for filtering out
* 'unwanted' nodes; may be null if no filtering is to be done
*
* @throws IllegalStateException If cursor can not be created due
* to the state cursor is in (or for some cursors, if they never
* allow creating such cursors)
* @throws UnsupportedOperationException If cursor does not allow
* creation of descendant cursors.
*/
public SMInputCursor descendantCursor(SMFilter f)
throws XMLStreamException
{
if (mState != State.ACTIVE) {
if (mState == State.HAS_CHILD) {
throw new IllegalStateException("Child cursor already requested.");
}
throw new IllegalStateException("Can not iterate children: cursor does not point to a start element (state "+getStateDesc()+")");
}
if (mCurrEvent != SMEvent.START_ELEMENT) {
throw new IllegalStateException("Can not iterate children: cursor does not point to a start element (pointing to "+mCurrEvent+")");
}
mChildCursor = constructDescendantCursor(f);
mState = State.HAS_CHILD;
return mChildCursor;
}
/**
* Method that will create a new nested cursor for iterating
* over all the descendant (children and grandchildren) nodes of
* the start element this cursor currently points to.
* If cursor does not point to a start element,
* it will throw {@link IllegalStateException}; if it does not support
* concept of descendant cursors, it will throw
* {@link UnsupportedOperationException}
*
* @throws IllegalStateException If cursor can not be created due
* to the state cursor is in (or for some cursors, if they never
* allow creating such cursors)
* @throws UnsupportedOperationException If cursor does not allow
* creation of descendant cursors.
*/
public final SMInputCursor descendantCursor()
throws XMLStreamException
{
return descendantCursor(null);
}
/**
* Convenience method; equivalent to
*<code>childCursor(SMFilterFactory.getElementOnlyFilter());</code>
*/
public final SMInputCursor childElementCursor()
throws XMLStreamException
{
return childCursor(SMFilterFactory.getElementOnlyFilter());
}
/**
* Convenience method; equivalent to
*<code>childCursor(SMFilterFactory.getElementOnlyFilter(elemName));</code>
* Will only return START_ELEMENT and END_ELEMENT events, whose element
* name matches given qname.
*/
public final SMInputCursor childElementCursor(QName elemName)
throws XMLStreamException
{
return childCursor(SMFilterFactory.getElementOnlyFilter(elemName));
}
/**
* Convenience method; equivalent to
*<code>childCursor(SMFilterFactory.getElementOnlyFilter(elemName));</code>
* Will only return START_ELEMENT and END_ELEMENT events, whose element's
* local name matches given local name, and that does not belong to
* a namespace.
*/
public final SMInputCursor childElementCursor(String elemLocalName)
throws XMLStreamException
{
return childCursor(SMFilterFactory.getElementOnlyFilter(elemLocalName));
}
/**
* Convenience method; equivalent to
*<code>descendantCursor(SMFilterFactory.getElementOnlyFilter());</code>
*/
public final SMInputCursor descendantElementCursor()
throws XMLStreamException
{
return descendantCursor(SMFilterFactory.getElementOnlyFilter());
}
/**
* Convenience method; equivalent to
*<code>descendantCursor(SMFilterFactory.getElementOnlyFilter(elemName));</code>
* Will only return START_ELEMENT and END_ELEMENT events, whose element
* name matches given qname.
*/
public final SMInputCursor descendantElementCursor(QName elemName)
throws XMLStreamException
{
return descendantCursor(SMFilterFactory.getElementOnlyFilter(elemName));
}
/**
* Convenience method; equivalent to
*<code>descendantCursor(SMFilterFactory.getElementOnlyFilter(elemLocalName));</code>.
* Will only return START_ELEMENT and END_ELEMENT events, whose element
* local name matches given local name, and that do not belong to a
* namespace
*/
public final SMInputCursor descendantElementCursor(String elemLocalName)
throws XMLStreamException
{
return descendantCursor(SMFilterFactory.getElementOnlyFilter(elemLocalName));
}
/**
* Convenience method; equivalent to
*<code>childCursor(SMFilterFactory.getMixedFilter());</code>
*/
public final SMInputCursor childMixedCursor()
throws XMLStreamException
{
return childCursor(SMFilterFactory.getMixedFilter());
}
/**
* Convenience method; equivalent to
*<code>descendantCursor(SMFilterFactory.getMixedFilter());</code>
*/
public final SMInputCursor descendantMixedCursor()
throws XMLStreamException
{
return descendantCursor(SMFilterFactory.getMixedFilter());
}
/*
/////////////////////////////////////////////////////////////
// Public API, convenience methods for exception construction
/////////////////////////////////////////////////////////////
*/
/**
* Method for constructing stream exception with given message,
* and location that matches that of the underlying stream
*<b>regardless of whether this cursor is valid</b> (that is,
* will indicate location of the stream which may differ from
* where this cursor was last valid)
*/
public XMLStreamException constructStreamException(String msg)
{
// !!! TODO: use StaxMate-specific sub-classes of XMLStreamException?
return new XMLStreamException(msg, getStreamLocation());
}
/**
* Method for constructing and throwing stream exception with given
* message. Equivalent to throwing exception that
* {@link #constructStreamException} constructs and returns.
*/
public void throwStreamException(String msg)
throws XMLStreamException
{
throw constructStreamException(msg);
}
/*
/////////////////////////////////////////////////////////////
// Public API, dev-readable descs
/////////////////////////////////////////////////////////////
*/
/**
* Method that generates developer-readable description of
* the logical path of the event this cursor points to,
* assuming that <b>element tracking</b> is enabled.
* If it is, a path description will be constructed; if not,
* result will be "." ("unspecified current location").
*<p>
* Note: while results look similar to XPath expressions,
* they are not accurate (or even valid) XPath.
* This is partly because of filtering, and partly because
* of differences between element/node index calculation.
* The idea is to make it easier to get reasonable idea
* of logical location, in addition to physical input location.
*/
public String getPathDesc()
{
/* Need to start with parent, since current element may
* or may not exist (depeneding on traversal)?
*/
SMElementInfo parent = getParentTrackedElement();
// Not tracking, or not just yet advanced?
if (parent == null && getElementTracking() == Tracking.NONE) {
return ".";
}
StringBuilder sb = new StringBuilder(100);
appendPathDesc(sb, parent, true);
/* Let's indicate index of the current node; whether to indicate
* via element or node index depend on whether it's a start/end
* element (and one for which we have info) or not
*/
SMElementInfo curr = getTrackedElement();
if (curr != null && getCurrEvent() == SMEvent.START_ELEMENT) {
appendPathDesc(sb, mTrackedElement, false);
} else {
sb.append("/*[n").append(getNodeCount()).append(']');
}
return sb.toString();
}
private static void appendPathDesc(StringBuilder sb, SMElementInfo info,
boolean recursive)
{
if (info == null) {
return;
}
if (recursive) {
appendPathDesc(sb, info.getParent(), true);
}
sb.append('/');
String prefix = info.getPrefix();
if (prefix != null && prefix.length() > 0) {
sb.append(prefix);
sb.append(':');
}
sb.append(info.getLocalName());
// and let's indicate relative element-index of the element
sb.append("[e").append(info.getElementIndex()).append(']');
}
protected String getCurrEventDesc() {
return mCurrEvent.toString();
}
/**
* Overridden implementation will just display description of
* the event this cursor points to (or last pointed to, if not
* valid)
*/
@Override
public String toString() {
return "[Cursor that point(s/ed) to: "+getCurrEventDesc()+"]";
}
/*
////////////////////////////////////////////
// Methods sub-classes need or can override
// to customize behaviour:
////////////////////////////////////////////
*/
/**
* Method cursor calls when it needs to track element state information;
* if so, it calls this method to take a snapshot of the element.
*<p>
* Note caller already suppresses calls so that this method is only
* called when information needs to be preserved. Further, previous
* element is only passed if such linkage is to be preserved (reason
* for not always doing it is the increased memory usage).
*<p>
* Finally, note that this method does NOT implement
* {@link ElementInfoFactory}, as its signature does not include the
* cursor argument, as that's passed as this pointer already.
*/
protected SMElementInfo constructElementInfo(SMElementInfo parent,
SMElementInfo prevSibling)
throws XMLStreamException
{
if (mElemInfoFactory != null) {
return mElemInfoFactory.constructElementInfo(this, parent, prevSibling);
}
XMLStreamReader2 sr = mStreamReader;
return new DefaultElementInfo(parent, prevSibling,
sr.getPrefix(), sr.getNamespaceURI(), sr.getLocalName(),
mNodeCount-1, mElemCount-1, getDepth());
}
/**
* Abstract method that concrete sub-classes implement, and is used
* for all instantiation of child cursors by this cursor instance.
*<p>
* Note that custom cursor implementations can be used by overriding
* this method.
*/
protected abstract SMInputCursor constructChildCursor(SMFilter f)
throws XMLStreamException;
/**
* Abstract method that concrete sub-classes implement, and is used
* for all instantiation of descendant cursors by this cursor instance.
*<p>
* Note that custom cursor implementations can be used by overriding
* this method.
*/
protected abstract SMInputCursor constructDescendantCursor(SMFilter f)
throws XMLStreamException;
}