package org.anodyneos.xpImpl;
import java.util.Enumeration;
import java.util.List;
import org.anodyneos.xp.XpContentHandler;
import org.anodyneos.xpImpl.runtime.NamespaceMappings;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;
/**
* XpContentHandler wraps a SAX ContentHandler and adds XP specific support. The
* following features are provided:
*
* 1. Attributes may be set using XpContentHandler any time after startElement
* is called, but before any other node is added.
*
* 2. setNamespacePrefixes() controls the passing of xmlns attributes to the
* wrapped content handler. This will normally be set by an XMLReader when the
* feature "http://xml.org/sax/features/namespaces" is set. This class will both
* filter attributes that are passed to it and generate new attributes as
* necessary. The default value is false (same as XMLReader's default.)
*
* 3. Start and end prefix mapping calls to the wrapped ContentHandler are
* managed by this class. Calls to startPrefixMapping may only be followed by
* additional calls to startPrefixMapping or startElement(). Calls to
* endPrefixMapping() are ignored - this class will track mappings and make the
* necessary calls to the wrapped ContentHandler.
*
* 4. Convenience methods such as characters(String s).
*
* 5. Tracking of prefix to namespace URI mappings.
*
* 6. Convenient StartElement and EndElement methods that take uri and qName. See
* comments for parameter rules and prefix calculation.
*
* @author John Vasileff
*
* TODO: implement text-only output stack.
*
* TODO: handle clearing of default namespace when non-XP code uses the contentHandler.
*
* TODO: provide runtime support for excludeResultPrefixes - all mappings should be tracked and available to
* the runtime code, but output for the excluded prefixes should be suppressed as best possbile. One implementation
* would be to use an XMLFilter in order to avoid this class becoming even harder to read.
*/
public final class XpContentHandlerImpl implements XpContentHandler {
private static final String NULL_STRING = "null";
private static final Log logger = LogFactory.getLog(XpContentHandlerImpl.class);
// instance variables to test for logging for performance.
private boolean logDebugEnabled = logger.isDebugEnabled();
/**
* current setting for the SAX feature "http://xml.org/sax/features/namespace-prefixes".
*/
private boolean namespacePrefixes = false;
/**
* tracks prefix to namespace URI mappings.
*/
private NamespaceMappings namespaceMappings = new NamespaceMappings();
private int lastEvent = -1;
/**
* holds values for the next element, set by startElement(). When flush() is called, these values
* are passed to the wrapped ContentHandler's startElemement() methods and then set to null.
*/
private String bufferedElLocalName;
private String bufferedElQName;
private String bufferedElNamespaceURI;
/**
* holds attributes relevent to the "nextEl". When nextElLocalName is null this object will be empty.
*/
private AttributesImpl bufferedElAttributes = new AttributesImpl();
private ContentHandler wrappedContentHandler;
private static final int EVENT_CHARACTERS = 0;
private static final int EVENT_END_DOCUMENT = 1;
private static final int EVENT_END_ELEMENT = 2;
private static final int EVENT_END_PREFIX_MAPPING = 3;
private static final int EVENT_IGNORABLE_WHITESPACE = 4;
private static final int EVENT_PROCESSING_INSTRUCTION = 5;
private static final int EVENT_SKIPPED_ENTITY = 6;
private static final int EVENT_START_DOCUMENT = 7;
private static final int EVENT_START_ELEMENT = 8;
private static final int EVENT_START_PREFIX_MAPPING = 9;
private static final int EVENT_PUSH_PHANTOM_PREFIX_MAPPING = 10;
private static final int EVENT_POP_PHANTOM_PREFIX_MAPPING = 11;
XpContentHandlerImpl(ContentHandler contentHandler) {
this.wrappedContentHandler = contentHandler;
}
XpContentHandlerImpl(ContentHandler contentHandler, boolean namespacePrefixes) {
this.wrappedContentHandler = contentHandler;
setNamespacePrefixes(namespacePrefixes);
}
////////////////////////////////////////////////////////////////////////////////
//
// phantom prefix push/pop
//
////////////////////////////////////////////////////////////////////////////////
public void pushPhantomPrefixMapping(String prefix, String uri) throws SAXException {
if(logDebugEnabled) {
logger.debug("pushPhantomPrefixMapping(\"" + prefix + "\", \"" + uri + "\") called");
}
flush(EVENT_PUSH_PHANTOM_PREFIX_MAPPING);
namespaceMappings.pushPhantomPrefix(prefix, uri);
}
public void popPhantomPrefixMapping() throws SAXException {
if(logDebugEnabled) {
logger.debug("popPhantomPrefixMapping() called");
}
flush(EVENT_POP_PHANTOM_PREFIX_MAPPING);
namespaceMappings.popPhantomPrefix();
}
////////////////////////////////////////////////////////////////////////////////
//
// SAX Methods (managed)
//
////////////////////////////////////////////////////////////////////////////////
/**
* startPrefixMapping() must be followed only by zero or more startPrefixMapping
* calls followed by a startElement() call
*/
public void startPrefixMapping(String prefix, String uri) throws SAXException {
if(logDebugEnabled) {
logger.debug("startPrefixMapping(\"" + prefix + "\", \"" + uri + "\") called");
}
flush(EVENT_START_PREFIX_MAPPING);
namespaceMappings.declarePrefix(prefix, uri);
}
public void startElement( String namespaceURI, String localName, String qName, Attributes atts)
throws SAXException {
// **** WARNING **** Any changes made here should also be made in the other startElement() method.
if(logDebugEnabled) {
logger.debug("startElement("
+ namespaceURI
+ ", " + localName
+ ", " + qName
+ ", " + atts
+ ") called");
}
flush(EVENT_START_ELEMENT);
// buffer this element to allow attributes to be added
bufferedElNamespaceURI = namespaceURI;
bufferedElLocalName = localName;
bufferedElQName = qName;
bufferedElAttributes.clear();
if (atts != null) {
bufferedElAttributes.setAttributes(atts);
}
}
public void endElement(String namespaceURI, String localName, String qName) throws SAXException {
if(logDebugEnabled) {
logger.debug("endElement("
+ namespaceURI
+ ", " + localName
+ ", " + qName
+ ") called");
}
flush(EVENT_END_ELEMENT);
// call endElement on the wrappedContentHandler
if(logDebugEnabled) {
logger.debug(" wrappedContentHandler.endElement("
+ namespaceURI + ", " + localName + ", " + qName + ")");
}
wrappedContentHandler.endElement(namespaceURI, localName, qName);
// endPrefixMapping calls for wrappedContentHandler
List prefixes = namespaceMappings.popContext2();
for (int i=0; i < prefixes.size();) {
String prefix = (String) prefixes.get(i++);
if(logDebugEnabled) {
logger.debug(" wrappedContentHandler.endPrefixMapping('" + prefix + "')");
}
wrappedContentHandler.endPrefixMapping(prefix);
}
}
public void endPrefixMapping(String prefix) throws SAXException {
if(logDebugEnabled) {
logger.debug("endPrefixMapping(\"" + prefix + "\") called");
}
flush(EVENT_END_PREFIX_MAPPING);
// no op: we handle endPrefixMapping calls to the wrapped contentHandler automatically
}
////////////////////////////////////////////////////////////////////////////////
//
// Convenience Methods (managed)
//
////////////////////////////////////////////////////////////////////////////////
/**
* This method tries to be forgiving within reason; the following rules apply:
*
* 1. When uri == null && qName has a prefix: The prefix must be in scope.
* The namespace URI in the output will be that of the namespace associated
* with the prefix. If the prefix is not in scope, a SAXException is thrown.
*
* 2. When uri == null && qName has no prefix: The qName is used as is with
* no namespace URI.
*
* 3. When uri == "": If a prefix exists, an exception is thrown. Otherwise,
* the attribute will have no namespace and be output without a prefix.
*
* 4. When uri == someURI: A prefix will be given to the attribute in the
* following priority:
*
* 4.A) the provided prefix if one was provided and it is currently mapped to
* the uri.
*
* 4.B) any prefix that is currently mapped to the URI.
*
* 4.C) the provided prefix if it is not currently mapped to some other URI.
*
* 4.D) a generated prefix. In the case of C or D, a new namespace mapping
* will be created.
*/
public void addAttribute(final String uri, final String qName, final String value)
throws SAXException {
// this method currently must be in this class as it violates the contract that startPrefixMapping() must
// not occur after the startElement() it applies to. This contract cannot be changed as it would break
// SAX compatibility. In addition, this method needs direct access to the internal bufferedAttributes
// structure.
if(logDebugEnabled) {
logger.debug("addAttribute("
+ uri
+ ", " + qName
+ ", value) called");
}
if (null == bufferedElLocalName) {
throw new SAXException("Cannot addAttribute() unless directly after startElement().");
} else if (qName.equals("xmlns") || qName.startsWith("xmlns:")) {
// flush() automatically handles namespace prefixes. We already know about the namespace
// from startPrefixMapping().
return;
} else {
// lets put this code here, not in flush(). This way the internal state is kept current and we get
// immediate feedback on errors.
final String myQName;
final String myURI;
final String prefix = parsePrefix(qName);
final String localName = parseLocalName(qName);
if (localName.length() == 0) {
throw new SAXException("Could not determine localName for attribute: '" + qName + "'.");
}
if (null == uri) {
if (prefix.length() == 0) {
myQName = localName;
myURI = "";
} else {
// a prefix was provided
// the prefix must be in scope to determine URI
// the prefix may be a phantom
String u = namespaceMappings.getURI(prefix, true);
if (null == u) {
throw new SAXException("Cannot find URI for '" + qName + "' and none was provided.");
} else {
String existingURI = namespaceMappings.getURI(prefix, false);
if (null == existingURI) {
myQName = prefix + ":" + localName;
myURI = u;
// declare the phantom prefix
namespaceMappings.declarePrefix(prefix, myURI);
} else if (existingURI.equals(u)) {
// no need to make new declaration
myQName = prefix + ":" + localName;
myURI = u;
} else {
// existingURI does not match calculated URI for provided prefix
String genP = genPrefix();
myQName = genP + ":" + localName;
myURI = u;
// declare the generated prefix
namespaceMappings.declarePrefix(genP, myURI);
}
}
}
} else if (uri.length() == 0) {
if (prefix.length() != 0) {
throw new SAXException("Prefix not allowed for '" + qName + "' when namespace URI = ''.");
}
// else, use "" URI and no prefix
myQName = localName;
myURI = "";
} else {
// we have a uri, lets find a good prefix
// don't search phantoms
if (prefix.length() != 0 && uri.equals(namespaceMappings.getURI(prefix, false))) {
// Case A: prefix was provided and uri matches
myQName = qName;
myURI = uri;
} else {
// don't search phantoms
final String existingPrefix = namespaceMappings.getPrefix(uri, false);
if (null != existingPrefix) {
// Case B: we already have a perfectly good prefix
myQName = existingPrefix + ":" + localName;
myURI = uri;
// don't search phantoms
} else if (prefix.length() != 0 && (null == namespaceMappings.getURI(prefix, false))) {
// Case C: the provided prefix will do; create new namespace mapping
if (logDebugEnabled) {
logger.debug(" addAttribute calls declarePrefix('" + prefix + "', '" + uri + "')");
}
namespaceMappings.declarePrefix(prefix, uri);
myQName = qName;
myURI = uri;
} else {
// Case D1: try a phantom prefix
// Case D2: or, generate a new prefix
String p = namespaceMappings.getPrefix(uri, true);
// p may be a phantom-prefix; if it conflicts with a non-phantom prefix, we can't use it
if (null == p || null != namespaceMappings.getURI(p, false)) {
p = genPrefix();
}
if (logDebugEnabled) {
logger.debug(" addAttribute calls declarePrefix('" + p + "', '" + uri + "')");
}
namespaceMappings.declarePrefix(p, uri);
myQName = p + ":" + localName;
myURI = uri;
}
}
}
bufferedElAttributes.addAttribute(myURI, localName, myQName, "CDATA", value);
}
}
/**
* This method tries to be forgiving, the following rules apply:
*
* 1. When uri == null && qName has a prefix: The prefix must be in scope.
* The namespace URI in the output will be that of the namespace associated
* with the prefix. If the prefix is not in scope, a SAXException is thrown.
*
* 2. When uri == null && qName has no prefix: The qName is used as is with
* the current default namespace URI.
*
* 3. When uri == "": The element will have no namespace and the default xmlns
* will be set to "".
*
* 4. When uri == someURI: A prefix will be given to the attribute in the
* following priority:
*
* 4.a) If the uri is the current default namespace, no prefix will be used.
*
* 4.b) the provided prefix if one was provided and it is currently mapped to
* the uri.
*
* 4.c) a prefix that is currently mapped to the URI.
*
* 4.d) the provided prefix if it is not currently mapped to another URI. A new
* mapping will be created.
*
* 4.e) a generated prefix. In the case of D or E, a new namespace mapping
* will be created.
*/
public void startElement(String uri, String qName) throws SAXException {
// we don't have to call flush() as long as we let other methods do the SAX specific work.
if(logDebugEnabled) {
logger.debug("startElement(" + uri + ", " + qName + ") called");
}
String[] elData;
elData = resolveElementPrefix(uri, qName);
String myURI = elData[0];
String localName = elData[1];
String myQName = elData[2];
String prefix = parsePrefix(myQName);
// this will flush() and take care of the mapping
startPrefixMapping(prefix, myURI);
// this takes care of buffering the element.
startElement(myURI, localName, myQName, null);
}
/**
* This method corresponds to startElement(uri, qName);
*
* @param uri
* @param qName
*/
public void endElement(String uri, String qName) throws SAXException {
// TODO: make sure qName is the same as what was used for startElement... currently this is buggy.
// possible fixes include writing a ns mapper like NamespaceHelper that can make a guarantee on
// getPrefix(uri), perhaps using a TreeMap to store namespace -> uri. Otherwise, we'll simply have to
// maintain a stack of qNames for start/end element.
String[] elData;
elData = resolveElementPrefix(uri, qName);
String myURI = elData[0];
String localName = elData[1];
String myQName = elData[2];
endElement(myURI, localName, myQName);
}
// this method may return undeclared or phantom prefixes
private String[] resolveElementPrefix(final String uri, final String qName) throws SAXException {
final String myQName;
final String myURI;
final String localName = parseLocalName(qName);
final String prefix = parsePrefix(qName);
if (localName.length() == 0) {
throw new SAXException("Could not determine localName for element: '" + qName + "'.");
}
if (null == uri) {
if (prefix.length() == 0) {
myQName = localName;
String u = namespaceMappings.getURI("", true);
if (null == u) {
u = "";
}
myURI = u;
} else {
// FIXME: this is ok for startElement, but are there any issues for endElement? Really endElement
// needs to be improved anyway to make sure the same prefix is used on both ends.
myURI = namespaceMappings.getURI(prefix, true);
if (null == myURI) {
throw new SAXException("Cannot find URI for '" + qName + "' and none was provided.");
}
myQName = qName;
}
} else if (uri.length() == 0) {
// use "" URI and no prefix
myQName = localName;
myURI = "";
} else {
// we have a uri, lets find a good prefix
if (uri.equals(namespaceMappings.getURI("", true))) {
// Case A: the uri is currently the default namespace; don't use a prefix
myQName = localName;
myURI = uri;
} else if (prefix.length() != 0 && uri.equals(namespaceMappings.getURI(prefix, true))) {
// Case B: prefix was provided and uri matches
myQName = qName;
myURI = uri;
} else {
String p = namespaceMappings.getPrefix(uri, true);
if (null != p) {
// Case C: we already have a perfectly good prefix
myQName = p + ":" + localName;
myURI = uri;
} else if (prefix.length() != 0 && (null == namespaceMappings.getURI(prefix, true))) {
// Case D: the provided prefix will do; create new namespace mapping
myQName = qName;
myURI = uri;
} else {
// Case E: punt... generate a new prefix for the attribute
p = genPrefix();
myQName = p + ":" + localName;
myURI = uri;
}
}
}
return new String[] {myURI, localName, myQName};
}
////////////////////////////////////////////////////////////////////////////////
//
// SAX Methods (simple pass through)
//
////////////////////////////////////////////////////////////////////////////////
public void characters(char[] ch, int start, int length) throws SAXException {
flush(EVENT_CHARACTERS);
if (ch != null) {
wrappedContentHandler.characters(ch, start, length);
}
}
public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException {
flush(EVENT_IGNORABLE_WHITESPACE);
wrappedContentHandler.ignorableWhitespace(ch, start, length);
}
public void processingInstruction(String target, String data) throws SAXException {
flush(EVENT_PROCESSING_INSTRUCTION);
wrappedContentHandler.processingInstruction(target, data);
}
public void skippedEntity(String name) throws SAXException {
flush(EVENT_SKIPPED_ENTITY);
wrappedContentHandler.skippedEntity(name);
}
public void setDocumentLocator(Locator locator) {
wrappedContentHandler.setDocumentLocator(locator);
}
public void endDocument() throws SAXException {
flush(EVENT_END_DOCUMENT);
// should calls to this method be ignored?
}
public void startDocument() throws SAXException {
flush(EVENT_START_DOCUMENT);
// should calls to this method be ignored?
}
////////////////////////////////////////////////////////////////////////////////
//
// Xp specific getters/setters
//
////////////////////////////////////////////////////////////////////////////////
/**
* This method should be used carefully; output should not be made directly
* to the wrapped <code>ContentHandler</code>.
*
* @return the wrapped <code>ContentHandler</code>
*/
public ContentHandler getWrappedContentHandler() {
return wrappedContentHandler;
}
public boolean isNamespacePrefixes() {
return namespacePrefixes;
}
public void setNamespacePrefixes(boolean namespacePrefixes) {
this.namespacePrefixes = namespacePrefixes;
}
////////////////////////////////////////////////////////////////////////////////
//
// private utility methods
//
////////////////////////////////////////////////////////////////////////////////
private void flush(final int event) throws SAXException {
// NOTE: to handle http://xml.org/sax/features/namespace-prefixes, we will first
// remove all "xmlns" and xmlns:xxx" attributes, then add required attributes from
// namespaceSupport if the feature is set to "true"
// This method is called at the start of _every_ event except addAttribute.
// There are two main concerns:
//
// 1. if bufferedElLocalName != null we need to declare prefixes and output
// the element with accumulated attributes.
//
// 2. if another element has come or is about to come: We need to pushContext()
// if we haven't yet, but wait until we process bufferedEl if it exits.
try {
if (EVENT_START_PREFIX_MAPPING == lastEvent &&
EVENT_START_PREFIX_MAPPING != event && EVENT_START_ELEMENT != event) {
throw new IllegalStateException("Only startPrefixMapping() or startElement()" +
" SAX events may follow startPrefixMapping()");
}
if (EVENT_PUSH_PHANTOM_PREFIX_MAPPING == event || EVENT_POP_PHANTOM_PREFIX_MAPPING == event) {
// do nothing; these can be called at any time except between startPrefixMapping and startElement
return;
}
// Note: flush() is called by startElement PRIOR to setting bufferedElLocalName. So, this test is for a
// a bufferedElLocalName set by a previous startElement call.
if (null != bufferedElLocalName) {
if(logDebugEnabled) {
logger.debug(" outputing bufferd element");
}
for (int i = 0; i < bufferedElAttributes.getLength(); i++) {
String qName = bufferedElAttributes.getQName(i);
if (qName.equals("xmlns") || qName.startsWith("xmlns:")) {
bufferedElAttributes.removeAttribute(i);
}
}
// start prefix mappings
// namespaceMappings.push() was already called by the startElement()
// that saved the bufferedElLocalName we are about to output
// we don't want to output new phantoms; since push() was already called, phantoms
// that are relevent to bufferedEl have already been "promoted" to declared status
Enumeration e = namespaceMappings.getDeclaredPrefixes(false);
while (e.hasMoreElements()) {
String prefix = (String) e.nextElement();
String uri = namespaceMappings.getURI(prefix, false);
if (null == uri) {
uri = "";
}
if(logDebugEnabled) {
logger.debug(" wrappedContentHandler.startPrefixMapping("
+ "'" + prefix + "'"
+ ", '" + uri + "'"
+ ")");
}
wrappedContentHandler.startPrefixMapping(prefix, uri);
if (namespacePrefixes) {
String qName;
if (prefix.length() == 0) {
qName = "xmlns";
} else {
qName = "xmlns:" + prefix;
}
if(logDebugEnabled) {
logger.debug(" adding namespace-prefix attribute " + qName + "= '" + uri + "')");
}
bufferedElAttributes.addAttribute("", "", qName, "CDATA", uri);
}
}
if(logDebugEnabled) {
logger.debug(" wrappedContentHandler.startElement("
+ "'" + bufferedElNamespaceURI + "'"
+ ", '" + bufferedElLocalName + "'"
+ ", '" + bufferedElQName + "'"
+ ", bufferedElAttributes"
+ ")");
}
wrappedContentHandler.startElement(bufferedElNamespaceURI, bufferedElLocalName,
bufferedElQName, bufferedElAttributes);
bufferedElNamespaceURI = null;
bufferedElLocalName = null;
bufferedElQName = null;
bufferedElAttributes.clear();
}
switch (event) {
case EVENT_START_PREFIX_MAPPING:
if (EVENT_START_PREFIX_MAPPING != lastEvent) {
namespaceMappings.pushContext();
}
break;
case EVENT_START_ELEMENT:
if (EVENT_START_PREFIX_MAPPING != lastEvent) {
namespaceMappings.pushContext();
}
break;
}
} finally {
lastEvent = event;
}
}
private static final String parsePrefix(String qName) {
if (null == qName || qName.length() == 0) {
return "";
} else {
int colon = qName.indexOf(':');
if (-1 == colon) {
return "";
} else {
return qName.substring(0, colon);
}
}
}
private static final String parseLocalName(String qName) {
if (null == qName || qName.length() == 0) {
return "";
} else {
int colon = qName.indexOf(':');
if (-1 == colon) {
return qName;
} else {
return qName.substring(colon + 1);
}
}
}
private int prefixNum = 100;
private String genPrefix() {
String prefix;
do {
// comment this out. Better to be repeatable.
//prefix = "n" + Integer.toString((int) (Math.random() *
// Integer.MAX_VALUE), 36);
prefix = "n" + prefixNum++;
// we may as well consider phantoms here in order to keep them around (not mask them) in case someone cares
} while (null != namespaceMappings.getURI(prefix, true));
return prefix;
}
////////////////////////////////////////////////////////////////////////////////
//
// characters(xxx) convenience methods
//
////////////////////////////////////////////////////////////////////////////////
public void characters(String s) throws SAXException {
if (null != s) {
characters(s.toCharArray(), 0, s.length());
} else {
characters(NULL_STRING.toCharArray(), 0, NULL_STRING.length());
}
}
public void characters(Object x) throws SAXException {
if (null != x) {
characters(x.toString());
} else {
characters((String) null);
}
}
public void characters(char x) throws SAXException {
characters(String.valueOf(x));
}
public void characters(byte x) throws SAXException {
characters(String.valueOf(x));
}
public void characters(boolean x) throws SAXException {
characters(String.valueOf(x));
}
public void characters(int x) throws SAXException {
characters(String.valueOf(x));
}
public void characters(long x) throws SAXException {
characters(String.valueOf(x));
}
public void characters(float x) throws SAXException {
characters(String.valueOf(x));
}
public void characters(double x) throws SAXException {
characters(String.valueOf(x));
}
////////////////////////////////////////////////////////////////////////////////
//
// methods for XpNamespaceMappings
//
// These methods ALWAYS include phantoms since they will be used by code
// that cares about phantoms to do things like EL prefix resolution.
//
////////////////////////////////////////////////////////////////////////////////
public String getPrefix(String uri) {
return namespaceMappings.getPrefix(uri, true);
}
public Enumeration getPrefixes() {
return namespaceMappings.getPrefixes(true);
}
public Enumeration getPrefixes(String uri) {
return namespaceMappings.getPrefixes(uri, true);
}
public String getURI(String prefix) {
return namespaceMappings.getURI(prefix, true);
}
////////////////////////////////////////////////////////////////////////////////
//
// our namespace mappings
//
////////////////////////////////////////////////////////////////////////////////
public boolean isNamespaceContextCompatible(XpContentHandler ch, boolean parentElClosed, int contextVersion,
int ancestorsWithPrefixMasking, int phantomPrefixCount) {
if (logDebugEnabled) {
logger.debug("isNamespaceContextCompatible() called");
}
if (this != ch) {
// no quick way to tell since our namespace mapping did not produce these values.
if (logDebugEnabled) {
logger.debug(" namespace is not compatible: original XpCH != current XpCH");
}
return false;
} else if (phantomPrefixCount != getPhantomPrefixCount()) {
// don't bother trying anything else if phantomPrefixCounts don't match.
if (logDebugEnabled) {
logger.debug(" namespace is not compatible: phantom prefix count doesn't match (short-circuit.)");
}
return false;
} else if (contextVersion == getContextVersion() && phantomPrefixCount != getPhantomPrefixCount()) {
// they are _exactly_ the same
return true;
} else if (parentElClosed) {
// exact version comparison is the only we can detect compatibility when we
// are no longer a decendent of the fragment's parent.
if (logDebugEnabled) {
logger.debug(" namespace is not compatible: versions not the same and parentElClosed");
}
return false;
} else if (ancestorsWithPrefixMasking == getAncestorsWithPrefixMasking()) {
// current context is a descendent of the target's parent and no prefixes have been masked
return true;
}
if (logDebugEnabled) {
logger.debug(" namespace is not compatible: prefixes have been masked");
}
return false;
}
public int getContextVersion() {
return namespaceMappings.getContextVersion();
}
public int getAncestorsWithPrefixMasking() {
return namespaceMappings.getAncestorsWithPrefixMasking();
}
public int getPhantomPrefixCount() {
return namespaceMappings.getPhantomPrefixCount();
}
}