0001: // Copyright (c) 2001, 2002, 2003, 2006 Per M.A. Bothner.
0002: // This is free software; for terms and warranty disclaimer see ./COPYING.
0003:
0004: package gnu.xml;
0005:
0006: import gnu.lists.*;
0007: import gnu.text.*;
0008: import gnu.mapping.Symbol; /* #ifdef SAX2 */
0009: import org.xml.sax.*;
0010: import gnu.kawa.sax.*; /* #endif */
0011: import gnu.expr.Keyword; // FIXME - bad cross-package dependency.
0012:
0013: /** Fixup XML input events.
0014: * Handles namespace resolution, and adds "namespace nodes" if needed.
0015: * Does various error checking.
0016: * This wrapper should be used when creating a NodeTree,
0017: * as is done for XQuery node constructor expressions.
0018: * Can also be called directly from XMLParser, in which case we use a slightly
0019: * lower-level interface where we array char array segments rather than
0020: * Strings. This is to avoid duplicate String allocation and interning.
0021: * The combination XMLParser+XMLFilter+NodeTree makes for a fast and
0022: * compact way to read an XML file into a DOM.
0023: */
0024:
0025: public class XMLFilter implements
0026: /* #ifdef SAX2 */
0027: DocumentHandler, ContentHandler,
0028: /* #endif */
0029: gnu.text.SourceLocator, XConsumer, PositionConsumer {
0030: /** This is where we save attributes while processing a begin element.
0031: * It may be the final output if {@code out instanceof NodeTree}. */
0032: TreeList tlist;
0033:
0034: /** The specified target Consumer that accepts the output.
0035: * In contrast, base may be either {@code ==out} or {@code ==tlist}. */
0036: public Consumer out;
0037:
0038: Consumer base;
0039:
0040: public static final int COPY_NAMESPACES_PRESERVE = 1;
0041: public static final int COPY_NAMESPACES_INHERIT = 2;
0042: public transient int copyNamespacesMode = COPY_NAMESPACES_PRESERVE;
0043:
0044: /** A helper stack.
0045: * This is logically multiple separate stacks, but we combine them into
0046: * a single array. While this makes the code a little harder to read,
0047: * it reduces memory overhead and (more importantly) should improve locality.
0048: * For each nested document or element there is the saved value of
0049: * namespaceBindings followed by a either a MappingInfo or Symbol
0050: * from the emitBeginElement/startElement. This is followed by a MappingInfo
0051: * or Symbol for each attribute we seen for the current element. */
0052: Object[] workStack;
0053: NamespaceBinding namespaceBindings;
0054:
0055: private SourceMessages messages;
0056: SourceLocator locator;
0057: LineBufferedReader in;
0058:
0059: public void setSourceLocator(LineBufferedReader in) {
0060: this .in = in;
0061: this .locator = this ;
0062: }
0063:
0064: public void setSourceLocator(SourceLocator locator) {
0065: this .locator = locator;
0066: }
0067:
0068: public void setMessages(SourceMessages messages) {
0069: this .messages = messages;
0070: }
0071:
0072: /** Twice the number of active startElement and startDocument calls. */
0073: protected int nesting;
0074:
0075: int previous = 0;
0076: private static final int SAW_KEYWORD = 1;
0077: private static final int SAW_WORD = 2;
0078:
0079: /** If {@code stringizingLevel > 0} then stringize rather than copy nodes.
0080: * It counts the number of nested startAttributes that are active.
0081: * (In the future it should also count begun comment and
0082: * processing-instruction constructors, when those support nesting.) */
0083: protected int stringizingLevel;
0084: /** Value of {@code nesting} just before outermost startElement
0085: * while {@code stringizingLevel > 0}.
0086: * I.e. if we're nested inside a element nested inside an attribute
0087: * then {@code stringizingElementNesting >= 0},
0088: * otherwise {@code stringizingElementNesting == -1}. */
0089: protected int stringizingElementNesting = -1;
0090: /** Postive if all output should be ignored.
0091: * This happens if we're inside an attribute value inside an element which
0092: * is stringized because it is in turm inside an outer attribute. Phew.
0093: * If gets increment by nested attributes so we can tell when to stop. */
0094: protected int ignoringLevel;
0095:
0096: // List of indexes in tlist.data of begin of attribute.
0097: int[] startIndexes = null;
0098:
0099: /** The number of attributes.
0100: * Zero means we've seen an element start tag.
0101: * Gets reset to -1 when we no longer in the element header. */
0102: int attrCount = -1;
0103:
0104: boolean inStartTag;
0105:
0106: /** The local name if currently processing an attribute value. */
0107: String attrLocalName;
0108: String attrPrefix;
0109:
0110: /** Non-null if we're processing a namespace declaration attribute.
0111: * In that case it is the prefix we're defining,
0112: * or {@code ""} in the case of a default namespace. */
0113: String currentNamespacePrefix;
0114:
0115: /** True if namespace declarations should be passed through as attributes.
0116: * Like SAX2's http://xml.org/features/namespace-prefixes. */
0117: public boolean namespacePrefixes = false;
0118:
0119: /** Map either lexical-QName or expanded-QName to a MappingInfo.
0120: * This is conceptually three hash tables merged into a single data structure.
0121: * (1) when we first see a tag (a QName as a lexical form before namespace
0122: * resolution), we map the tag String to an preliminary info entry that
0123: * has a null qname field.
0124: * (2) After see the namespace declaration, we use the same table and keys,
0125: * but name the uri and qtype.namespaceNodes also have to match.
0126: * (3) Used for hash-consing NamespaceBindings.
0127: */
0128: MappingInfo[] mappingTable = new MappingInfo[128];
0129: int mappingTableMask = mappingTable.length - 1;
0130:
0131: boolean mismatchReported;
0132:
0133: /** Functionally equivalent to
0134: * {@code new NamespaceBinding(prefix, uri, oldBindings},
0135: * but uses "hash consing".
0136: */
0137: public NamespaceBinding findNamespaceBinding(String prefix,
0138: String uri, NamespaceBinding oldBindings) {
0139: int hash = uri == null ? 0 : uri.hashCode();
0140: if (prefix != null)
0141: hash ^= prefix.hashCode();
0142: int bucket = hash & mappingTableMask;
0143: MappingInfo info = mappingTable[bucket];
0144: for (;; info = info.nextInBucket) {
0145: if (info == null) {
0146: info = new MappingInfo();
0147: info.nextInBucket = mappingTable[bucket];
0148: mappingTable[bucket] = info;
0149: info.tagHash = hash;
0150: info.prefix = prefix;
0151: info.local = uri;
0152: info.uri = uri;
0153: if (uri == "")
0154: uri = null;
0155: NamespaceBinding namespaces = new NamespaceBinding(
0156: prefix, uri, oldBindings);
0157: info.namespaces = namespaces;
0158: return info.namespaces;
0159: }
0160: NamespaceBinding namespaces;
0161: if (info.tagHash == hash && info.prefix == prefix
0162: && (namespaces = info.namespaces) != null
0163: && namespaces.getNext() == namespaceBindings
0164: && namespaces.getPrefix() == prefix
0165: && info.uri == uri) {
0166: return info.namespaces;
0167: }
0168: }
0169: }
0170:
0171: /** Return a MappingInfo containing a match namespaces.
0172: * Specifically, return a {@code MappingInfo info} is such that
0173: * {@code info.namespaces} is equal to
0174: * {@code new NamespaceBinding(prefix, uri, oldBindings)}, where {@code uri}
0175: * is {@code new String(uriChars, uriStart, uriLength).intern())}.
0176: */
0177: public MappingInfo lookupNamespaceBinding(String prefix,
0178: char[] uriChars, int uriStart, int uriLength, int uriHash,
0179: NamespaceBinding oldBindings) {
0180: int hash = prefix == null ? uriHash : prefix.hashCode()
0181: ^ uriHash;
0182: // Search for a matching already-seen NamespaceBinding list.
0183: // We hash these to so we can share lists that are equal but
0184: // appear multiple times in the same XML file, as sometimes happens.
0185: // This not only saves memory, but keeps hash bucket chains short,
0186: // which is important since we don't resize the table.
0187: int bucket = hash & mappingTableMask;
0188: MappingInfo info = mappingTable[bucket];
0189: for (;; info = info.nextInBucket) {
0190: if (info == null) {
0191: info = new MappingInfo();
0192: info.nextInBucket = mappingTable[bucket];
0193: mappingTable[bucket] = info;
0194: String uri = new String(uriChars, uriStart, uriLength)
0195: .intern();
0196: // We re-use the same MappingInfo table that is mainly used
0197: // for tag lookup, but re-interpreting the meaning of the
0198: // various fields. Since MappingInfo hashes on prefix^local,
0199: // we must do the same here.
0200: info.tagHash = hash;
0201: info.prefix = prefix;
0202: info.local = uri;
0203: info.uri = uri;
0204: if (uri == "")
0205: uri = null;
0206: NamespaceBinding namespaces = new NamespaceBinding(
0207: prefix, uri, oldBindings);
0208: info.namespaces = namespaces;
0209: return info;
0210: }
0211: NamespaceBinding namespaces;
0212: if (info.tagHash == hash
0213: && info.prefix == prefix
0214: && (namespaces = info.namespaces) != null
0215: && namespaces.getNext() == namespaceBindings
0216: && namespaces.getPrefix() == prefix
0217: && MappingInfo.equals(info.uri, uriChars, uriStart,
0218: uriLength)) {
0219: return info;
0220: }
0221: }
0222: }
0223:
0224: public void endAttribute() {
0225: if (attrLocalName == null)
0226: return;
0227: if (previous == SAW_KEYWORD) {
0228: previous = 0;
0229: return;
0230: }
0231: if (stringizingElementNesting >= 0)
0232: ignoringLevel--;
0233: if (--stringizingLevel == 0) {
0234: if (attrLocalName == "id" && attrPrefix == "xml") {
0235: // Need to normalize xml:id attributes.
0236: int valStart = startIndexes[attrCount - 1]
0237: + TreeList.BEGIN_ATTRIBUTE_LONG_SIZE;
0238: int valEnd = tlist.gapStart;
0239: char[] data = tlist.data;
0240: for (int i = valStart;;) {
0241: if (i >= valEnd) {
0242: // It's normalized. Nothing to do.
0243: break;
0244: }
0245: char datum = data[i++];
0246: if (((datum & 0xFFFF) > TreeList.MAX_CHAR_SHORT)
0247: || datum == '\t'
0248: || datum == '\r'
0249: || datum == '\n'
0250: || (datum == ' ' && (i == valEnd || data[i] == ' '))) {
0251: // It's either not normalized, or the value contains
0252: // chars values above MAX_CHAR_SHORT or non-chars.
0253: // We could try to normalize in place but why bother?
0254: // I'm assuming xml:id are going be normalized already.
0255: // The exception is characters above MAX_CHAR_SHORT, but
0256: // let's defer that until TreeList gets re-written.
0257: StringBuffer sbuf = new StringBuffer();
0258: tlist.stringValue(valStart, valEnd, sbuf);
0259: tlist.gapStart = valStart;
0260: tlist.write(TextUtils.replaceWhitespace(sbuf
0261: .toString(), true));
0262: break;
0263: }
0264: }
0265: }
0266:
0267: attrLocalName = null;
0268: attrPrefix = null;
0269: if (currentNamespacePrefix == null || namespacePrefixes)
0270: tlist.endAttribute();
0271: if (currentNamespacePrefix != null) {
0272: // Handle raw namespace attribute from parser.
0273: int attrStart = startIndexes[attrCount - 1];
0274: int uriStart = attrStart;
0275: int uriEnd = tlist.gapStart;
0276: int uriLength = uriEnd - uriStart;
0277: char[] data = tlist.data;
0278:
0279: // Check that the namespace attribute value is plain characters
0280: // so we an use the in-place buffer.
0281: // Calculate hash while we're at it.
0282: int uriHash = 0;
0283: for (int i = uriStart; i < uriEnd; i++) {
0284: char datum = data[i];
0285: if ((datum & 0xFFFF) > TreeList.MAX_CHAR_SHORT) {
0286: StringBuffer sbuf = new StringBuffer();
0287: tlist.stringValue(uriStart, uriEnd, sbuf);
0288: uriHash = sbuf.hashCode();
0289: uriStart = 0;
0290: uriEnd = uriLength = sbuf.length();
0291: data = new char[sbuf.length()];
0292: sbuf.getChars(0, uriEnd, data, 0);
0293: break;
0294: }
0295: uriHash = 31 * uriHash + datum;
0296: }
0297: tlist.gapStart = attrStart;
0298:
0299: String prefix = currentNamespacePrefix == "" ? null
0300: : currentNamespacePrefix;
0301: MappingInfo info = lookupNamespaceBinding(prefix, data,
0302: uriStart, uriLength, uriHash, namespaceBindings);
0303: namespaceBindings = info.namespaces;
0304:
0305: currentNamespacePrefix = null;
0306: }
0307: }
0308: }
0309:
0310: private String resolve(String prefix, boolean isAttribute) {
0311: if (isAttribute && prefix == null)
0312: return "";
0313: String uri = namespaceBindings.resolve(prefix);
0314: if (uri != null)
0315: return uri;
0316: if (prefix != null)
0317: error('e', "unknown namespace prefix '" + prefix + '\'');
0318: return "";
0319: }
0320:
0321: void closeStartTag() {
0322: if (attrCount < 0 || stringizingLevel > 0)
0323: return;
0324: inStartTag = false;
0325: previous = 0;
0326:
0327: if (attrLocalName != null) // Should only happen on erroneous input.
0328: endAttribute();
0329: NamespaceBinding outer = nesting == 0 ? NamespaceBinding.predefinedXML
0330: : (NamespaceBinding) workStack[nesting - 2];
0331:
0332: NamespaceBinding bindings = namespaceBindings;
0333:
0334: // This first pass is to check that there are namespace declarations for
0335: // each Symbol.
0336: for (int i = 0; i <= attrCount; i++) {
0337: Object saved = workStack[nesting + i - 1];
0338: if (saved instanceof Symbol) {
0339: Symbol sym = (Symbol) saved;
0340: String prefix = sym.getPrefix();
0341: if (prefix == "")
0342: prefix = null;
0343: String uri = sym.getNamespaceURI();
0344: if (uri == "")
0345: uri = null;
0346: if (i > 0 && prefix == null && uri == null)
0347: continue;
0348: boolean isOuter = false;
0349: for (NamespaceBinding ns = bindings;; ns = ns.next) {
0350: if (ns == outer)
0351: isOuter = true;
0352: if (ns == null) {
0353: if (prefix != null || uri != null)
0354: bindings = findNamespaceBinding(prefix,
0355: uri, bindings);
0356: break;
0357: }
0358: if (ns.prefix == prefix) {
0359: if (ns.uri != uri) {
0360: if (isOuter)
0361: bindings = findNamespaceBinding(prefix,
0362: uri, bindings);
0363: else {
0364: // Try to find an alternative existing prefix:
0365: String nprefix;
0366: for (NamespaceBinding ns2 = bindings;; ns2 = ns2.next) {
0367: if (ns2 == null) {
0368: // We have to generate a new prefix.
0369: for (int j = 1;; j++) {
0370: nprefix = ("_ns_" + j)
0371: .intern();
0372: if (bindings
0373: .resolve(nprefix) == null)
0374: break;
0375: }
0376: break;
0377: }
0378: if (ns2.uri == uri) {
0379: nprefix = ns2.prefix;
0380: if (bindings.resolve(nprefix) == uri)
0381: break;
0382: }
0383: }
0384: bindings = findNamespaceBinding(
0385: nprefix, uri, bindings);
0386: String local = sym.getLocalName();
0387: if (uri == null)
0388: uri = "";
0389: workStack[nesting + i - 1] = Symbol
0390: .make(uri, local, nprefix);
0391: }
0392: }
0393: break;
0394: }
0395: }
0396: }
0397:
0398: }
0399:
0400: for (int i = 0; i <= attrCount; i++) {
0401: Object saved = workStack[nesting + i - 1];
0402: MappingInfo info;
0403: boolean isNsNode = false;
0404: String prefix, uri, local;
0405: if (saved instanceof MappingInfo || out == tlist) {
0406: if (saved instanceof MappingInfo) {
0407: info = (MappingInfo) saved;
0408: prefix = info.prefix;
0409: local = info.local;
0410: if (i > 0
0411: && ((prefix == null && local == "xmlns") || prefix == "xmlns")) {
0412: isNsNode = true;
0413: uri = "(namespace-node)";
0414: } else
0415: uri = resolve(prefix, i > 0);
0416: } else {
0417: Symbol symbol = (Symbol) saved;
0418: info = lookupTag(symbol);
0419: prefix = info.prefix;
0420: local = info.local;
0421: uri = symbol.getNamespaceURI();
0422: }
0423: int hash = info.tagHash;
0424: int bucket = hash & mappingTableMask;
0425:
0426: info = mappingTable[bucket];
0427: MappingInfo tagMatch = null;
0428: Object type;
0429: for (;; info = info.nextInBucket) {
0430: if (info == null) {
0431: info = tagMatch;
0432: info = new MappingInfo();
0433: info.tagHash = hash;
0434: info.prefix = prefix;
0435: info.local = local;
0436: info.nextInBucket = mappingTable[bucket];
0437: mappingTable[bucket] = info;
0438: info.uri = uri;
0439: info.qname = Symbol.make(uri, local, prefix);
0440: if (i == 0) {
0441: XName xname = new XName(info.qname,
0442: bindings);
0443: type = xname;
0444: info.type = xname;
0445: info.namespaces = bindings;
0446: }
0447: break;
0448: }
0449: if (info.tagHash == hash && info.local == local
0450: && info.prefix == prefix) {
0451: if (info.uri == null) {
0452: info.uri = uri;
0453: info.qname = Symbol
0454: .make(uri, local, prefix);
0455: } else if (info.uri != uri)
0456: continue;
0457: else if (info.qname == null)
0458: info.qname = Symbol
0459: .make(uri, local, prefix);
0460: if (i == 0) {
0461: if (info.namespaces == bindings
0462: || info.namespaces == null) {
0463: type = info.type;
0464: info.namespaces = bindings;
0465: if (type == null) {
0466: XName xname = new XName(info.qname,
0467: bindings);
0468: type = xname;
0469: info.type = xname;
0470: }
0471: break;
0472: }
0473: } else {
0474: type = info.qname;
0475: break;
0476: }
0477: }
0478: }
0479: workStack[nesting + i - 1] = info;
0480: } else {
0481: Symbol sym = (Symbol) saved;
0482: uri = sym.getNamespaceURI();
0483: local = sym.getLocalName();
0484: info = null;
0485: }
0486:
0487: // Check for duplicated attribute names.
0488: for (int j = 1; j < i; j++) {
0489: Object other = workStack[nesting + j - 1];
0490: Symbol osym;
0491: if (other instanceof Symbol)
0492: osym = (Symbol) other;
0493: else if (other instanceof MappingInfo)
0494: osym = ((MappingInfo) other).qname;
0495: else
0496: continue;
0497: if (local == osym.getLocalPart()
0498: && uri == osym.getNamespaceURI()) {
0499: Object tag = workStack[nesting - 1];
0500: if (tag instanceof MappingInfo)
0501: tag = ((MappingInfo) tag).qname;
0502: error('e', XMLFilter.duplicateAttributeMessage(
0503: osym, tag));
0504: }
0505: }
0506:
0507: if (out == tlist) {
0508: Object type = i == 0 ? info.type : info.qname;
0509: int index = info.index;
0510: if (index <= 0 || tlist.objects[index] != type) {
0511: index = tlist.find(type);
0512: info.index = index;
0513: }
0514: if (i == 0)
0515: tlist.setElementName(tlist.gapEnd, index);
0516: else if (!isNsNode || namespacePrefixes)
0517: tlist.setAttributeName(startIndexes[i - 1], index);
0518: } else {
0519: Object type = info == null ? saved : i == 0 ? info.type
0520: : info.qname;
0521: if (i == 0)
0522: out.startElement(type);
0523: else if (!isNsNode || namespacePrefixes) {
0524: out.startAttribute(type);
0525: int start = startIndexes[i - 1];
0526: int end = i < attrCount ? startIndexes[i]
0527: : tlist.gapStart;
0528: tlist.consumeIRange(start
0529: + TreeList.BEGIN_ATTRIBUTE_LONG_SIZE, end
0530: - TreeList.END_ATTRIBUTE_SIZE, out);
0531: out.endAttribute();
0532: }
0533: }
0534: }
0535:
0536: /* #ifdef SAX2 */
0537: if (out instanceof ContentConsumer)
0538: ((ContentConsumer) out).endStartTag();
0539: /* #endif */
0540:
0541: for (int i = 1; i <= attrCount; i++)
0542: workStack[nesting + i - 1] = null; // For GC.
0543: if (out != tlist) {
0544: base = out;
0545: // Remove temporarily stored attributes.
0546: tlist.clear();
0547: }
0548: attrCount = -1;
0549: }
0550:
0551: protected boolean checkWriteAtomic() {
0552: previous = 0;
0553: if (ignoringLevel > 0)
0554: return false;
0555: closeStartTag();
0556: return true;
0557: }
0558:
0559: public void write(int v) {
0560: if (checkWriteAtomic())
0561: base.write(v);
0562: }
0563:
0564: public void writeBoolean(boolean v) {
0565: if (checkWriteAtomic())
0566: base.writeBoolean(v);
0567: }
0568:
0569: public void writeFloat(float v) {
0570: if (checkWriteAtomic())
0571: base.writeFloat(v);
0572: }
0573:
0574: public void writeDouble(double v) {
0575: if (checkWriteAtomic())
0576: base.writeDouble(v);
0577: }
0578:
0579: public void writeInt(int v) {
0580: if (checkWriteAtomic())
0581: base.writeInt(v);
0582: }
0583:
0584: public void writeLong(long v) {
0585: if (checkWriteAtomic())
0586: base.writeLong(v);
0587: }
0588:
0589: public void writeDocumentUri(Object uri) {
0590: if (nesting == 2 && base instanceof TreeList)
0591: ((TreeList) base).writeDocumentUri(uri);
0592: }
0593:
0594: public void consume(SeqPosition position) {
0595: writePosition(position.sequence, position.ipos);
0596: }
0597:
0598: public void writePosition(AbstractSequence seq, int ipos) {
0599: if (ignoringLevel > 0)
0600: return;
0601: if (stringizingLevel > 0 && previous == SAW_WORD) {
0602: if (stringizingElementNesting < 0)
0603: write(' ');
0604: previous = 0;
0605: }
0606: seq.consumeNext(ipos, this );
0607: if (stringizingLevel > 0 && stringizingElementNesting < 0)
0608: previous = SAW_WORD;
0609: }
0610:
0611: /** If v is a node, make a copy of it. */
0612: public void writeObject(Object v) {
0613: if (ignoringLevel > 0)
0614: return;
0615: if (v instanceof SeqPosition) {
0616: SeqPosition pos = (SeqPosition) v;
0617: writePosition(pos.sequence, pos.getPos());
0618: } else if (v instanceof TreeList)
0619: ((TreeList) v).consume(this );
0620: else if (v instanceof Keyword) {
0621: Keyword k = (Keyword) v;
0622: startAttribute(k.asSymbol());
0623: previous = SAW_KEYWORD;
0624: } else {
0625: closeStartTag();
0626: if (v instanceof UnescapedData) {
0627: base.writeObject(v);
0628: previous = 0;
0629: } else {
0630: if (previous == SAW_WORD)
0631: write(' ');
0632: TextUtils.textValue(v, this ); // Atomize.
0633: previous = SAW_WORD;
0634: }
0635: }
0636: }
0637:
0638: public XMLFilter(Consumer out) {
0639: this .base = out;
0640: this .out = out;
0641: if (out instanceof NodeTree)
0642: this .tlist = (NodeTree) out;
0643: else
0644: tlist = new TreeList(); // just for temporary storage
0645:
0646: namespaceBindings = NamespaceBinding.predefinedXML;
0647: }
0648:
0649: /** Process raw text. */
0650: public void write(char[] data, int start, int length) {
0651: if (length == 0)
0652: writeJoiner();
0653: else if (checkWriteAtomic())
0654: base.write(data, start, length);
0655: }
0656:
0657: public void write(String str) {
0658: write(str, 0, str.length());
0659: }
0660:
0661: /* #ifdef use:java.lang.CharSequence */
0662: public void write(CharSequence str, int start, int length)
0663: /* #else */
0664: // public void write(String str, int start, int length)
0665: /* #endif */
0666: {
0667: if (length == 0)
0668: writeJoiner();
0669: else if (checkWriteAtomic())
0670: base.write(str, start, length);
0671: }
0672:
0673: final boolean inElement() {
0674: int i = nesting;
0675: // Don't count nested document nodes.
0676: while (i > 0 && workStack[i - 1] == null)
0677: i -= 2;
0678: return i != 0;
0679: }
0680:
0681: public void linefeedFromParser() {
0682: if (inElement() && checkWriteAtomic())
0683: base.write('\n');
0684: }
0685:
0686: public void textFromParser(char[] data, int start, int length) {
0687: // We skip whitespace not in an element.
0688: if (!inElement()) {
0689: for (int i = 0;; i++) {
0690: if (i == length)
0691: return;
0692: if (!Character.isWhitespace(data[start + i])) {
0693: error('e', "text at document level");
0694: break;
0695: }
0696: }
0697: } else if (length > 0) {
0698: if (!checkWriteAtomic())
0699: return;
0700:
0701: base.write(data, start, length);
0702: }
0703: }
0704:
0705: protected void writeJoiner() {
0706: previous = 0;
0707: if (ignoringLevel == 0)
0708: ((TreeList) base).writeJoiner();
0709: }
0710:
0711: /** Process a CDATA section.
0712: * The data (starting at start for length char).
0713: * Does not include the delimiters (i.e. {@code "<![CDATA["}
0714: * and {@code "]]>"} are excluded). */
0715: public void writeCDATA(char[] data, int start, int length) {
0716: if (checkWriteAtomic()) {
0717: if (base instanceof XConsumer)
0718: ((XConsumer) base).writeCDATA(data, start, length);
0719: else
0720: write(data, start, length);
0721: }
0722: }
0723:
0724: protected void startElementCommon() {
0725: closeStartTag();
0726: if (stringizingLevel == 0) {
0727: ensureSpaceInWorkStack(nesting);
0728: workStack[nesting] = namespaceBindings;
0729: tlist.startElement(0);
0730: base = tlist;
0731: attrCount = 0;
0732: } else {
0733: if (previous == SAW_WORD && stringizingElementNesting < 0)
0734: write(' ');
0735: previous = 0;
0736: if (stringizingElementNesting < 0)
0737: stringizingElementNesting = nesting;
0738: }
0739: nesting += 2;
0740: }
0741:
0742: /** Process a start tag, with the given element name. */
0743: public void emitStartElement(char[] data, int start, int count) {
0744: closeStartTag();
0745: MappingInfo info = lookupTag(data, start, count);
0746: startElementCommon();
0747: ensureSpaceInWorkStack(nesting - 1);
0748: workStack[nesting - 1] = info;
0749: }
0750:
0751: public void startElement(Object type) {
0752: startElementCommon();
0753: if (stringizingLevel == 0) {
0754: ensureSpaceInWorkStack(nesting - 1);
0755: workStack[nesting - 1] = type;
0756: if (copyNamespacesMode == 0)
0757: namespaceBindings = NamespaceBinding.predefinedXML;
0758: else if (copyNamespacesMode == COPY_NAMESPACES_PRESERVE
0759: || nesting == 2)
0760: namespaceBindings = (type instanceof XName ? ((XName) type)
0761: .getNamespaceNodes()
0762: : NamespaceBinding.predefinedXML);
0763: else {
0764: NamespaceBinding inherited;
0765: // Start at 2, since workStack[0] just saves the predefinedXML.
0766: for (int i = 2;; i += 2) {
0767: if (i == nesting) {
0768: inherited = null;
0769: break;
0770: }
0771: if (workStack[i + 1] != null) { // Found an element, as opposed to a document.
0772: inherited = (NamespaceBinding) workStack[i];
0773: break;
0774: }
0775: }
0776: if (inherited == null) {
0777: // This is the outer-most element.
0778: namespaceBindings = (type instanceof XName ? ((XName) type)
0779: .getNamespaceNodes()
0780: : NamespaceBinding.predefinedXML);
0781: } else if (copyNamespacesMode == COPY_NAMESPACES_INHERIT)
0782: namespaceBindings = inherited;
0783: else if (type instanceof XName) {
0784: NamespaceBinding preserved = ((XName) type)
0785: .getNamespaceNodes();
0786: NamespaceBinding join = NamespaceBinding
0787: .commonAncestor(inherited, preserved);
0788: if (join == inherited)
0789: namespaceBindings = preserved;
0790: else
0791: namespaceBindings = mergeHelper(inherited,
0792: preserved);
0793: } else
0794: namespaceBindings = inherited;
0795: }
0796: }
0797: }
0798:
0799: private NamespaceBinding mergeHelper(NamespaceBinding list,
0800: NamespaceBinding node) {
0801: if (node == NamespaceBinding.predefinedXML)
0802: return list;
0803: list = mergeHelper(list, node.next);
0804: String uri = node.uri;
0805: if (list == null) {
0806: if (uri == null)
0807: return list;
0808: list = NamespaceBinding.predefinedXML;
0809: }
0810: String prefix = node.prefix;
0811: String found = list.resolve(prefix);
0812: if (found == null ? uri == null : found.equals(uri))
0813: return list;
0814: return findNamespaceBinding(prefix, uri, list);
0815: }
0816:
0817: private boolean startAttributeCommon() {
0818: if (stringizingElementNesting >= 0)
0819: ignoringLevel++;
0820: if (stringizingLevel++ > 0)
0821: return false;
0822:
0823: if (attrCount < 0) // A disembodied attribute.
0824: attrCount = 0;
0825: ensureSpaceInWorkStack(nesting + attrCount);
0826: ensureSpaceInStartIndexes(attrCount);
0827: startIndexes[attrCount] = tlist.gapStart;
0828: attrCount++;
0829: return true;
0830: }
0831:
0832: public void startAttribute(Object attrType) {
0833: previous = 0;
0834: if (attrType instanceof Symbol) {
0835: Symbol sym = (Symbol) attrType;
0836: String local = sym.getLocalPart();
0837: attrLocalName = local;
0838: attrPrefix = sym.getPrefix();
0839: String uri = sym.getNamespaceURI();
0840: if (uri == "http://www.w3.org/2000/xmlns/"
0841: || (uri == "" && local == "xmlns"))
0842: error('e',
0843: "arttribute name cannot be 'xmlns' or in xmlns namespace");
0844: }
0845: if (nesting == 2 && workStack[1] == null)
0846: error('e', "attribute not allowed at document level");
0847: if (attrCount < 0 && nesting > 0)
0848: error('e', "attribute '" + attrType
0849: + "' follows non-attribute content");
0850: if (!startAttributeCommon())
0851: return;
0852: workStack[nesting + attrCount - 1] = attrType;
0853: if (nesting == 0)
0854: base.startAttribute(attrType);
0855: else
0856: tlist.startAttribute(0);
0857: }
0858:
0859: /** Process an attribute, with the given attribute name.
0860: * The attribute value is given using {@code write}.
0861: * The value is terminated by either another emitStartAttribute
0862: * or an emitEndAttributes.
0863: */
0864: public void emitStartAttribute(char[] data, int start, int count) {
0865: if (attrLocalName != null)
0866: endAttribute();
0867: if (!startAttributeCommon())
0868: return;
0869:
0870: MappingInfo info = lookupTag(data, start, count);
0871: workStack[nesting + attrCount - 1] = info;
0872: String prefix = info.prefix;
0873: String local = info.local;
0874: attrLocalName = local;
0875: attrPrefix = prefix;
0876: if (prefix != null) {
0877: if (prefix == "xmlns") {
0878: currentNamespacePrefix = local;
0879: }
0880: } else {
0881: if (local == "xmlns" && prefix == null) {
0882: currentNamespacePrefix = "";
0883: }
0884: }
0885: if (currentNamespacePrefix == null || namespacePrefixes)
0886: tlist.startAttribute(0);
0887: }
0888:
0889: /** Process the end of a start tag.
0890: * There are no more attributes. */
0891: public void emitEndAttributes() {
0892: if (attrLocalName != null)
0893: endAttribute();
0894: closeStartTag();
0895: }
0896:
0897: /** Process an end tag.
0898: * An abbreviated tag (such as {@code '<br/>'}) has a name==null.
0899: */
0900: public void emitEndElement(char[] data, int start, int length) {
0901: if (attrLocalName != null) {
0902: error('e', "unclosed attribute"); // FIXME
0903: endAttribute();
0904: }
0905: if (!inElement()) {
0906: error('e', "unmatched end element"); // FIXME
0907: return;
0908: }
0909: if (data != null) {
0910: MappingInfo info = lookupTag(data, start, length);
0911: Object old = workStack[nesting - 1];
0912: if (old instanceof MappingInfo && !mismatchReported) {
0913: MappingInfo mold = (MappingInfo) old;
0914: if (info.local != mold.local
0915: || info.prefix != mold.prefix) {
0916: StringBuffer sbuf = new StringBuffer("</");
0917: sbuf.append(data, start, length);
0918: sbuf.append("> matching <");
0919: String oldPrefix = mold.prefix;
0920: if (oldPrefix != null) {
0921: sbuf.append(oldPrefix);
0922: sbuf.append(':');
0923: }
0924: sbuf.append(mold.local);
0925: sbuf.append('>');
0926: error('e', sbuf.toString());
0927: mismatchReported = true;
0928: }
0929: }
0930: }
0931: closeStartTag();
0932: if (nesting <= 0)
0933: return; // Only if error.
0934: endElement();
0935: }
0936:
0937: public void endElement() {
0938: closeStartTag();
0939: nesting -= 2;
0940: previous = 0;
0941: if (stringizingLevel == 0) {
0942: namespaceBindings = (NamespaceBinding) workStack[nesting];
0943: workStack[nesting] = null;
0944: workStack[nesting + 1] = null;
0945: base.endElement();
0946: } else if (stringizingElementNesting == nesting) {
0947: stringizingElementNesting = -1;
0948: previous = SAW_WORD;
0949: }
0950: /*
0951: if (nesting == 0)
0952: {
0953: workStack = null;
0954: attrIndexes = null;
0955: }
0956: */
0957: }
0958:
0959: /** Process an entity reference.
0960: * The entity name is given.
0961: * This handles the predefined entities, such as "<" and """.
0962: */
0963: public void emitEntityReference(char[] name, int start, int length) {
0964: char c0 = name[start];
0965: char ch = '?';
0966: if (length == 2 && name[start + 1] == 't') {
0967:
0968: if (c0 == 'l')
0969: ch = '<';
0970: else if (c0 == 'g')
0971: ch = '>';
0972: } else if (length == 3) {
0973: if (c0 == 'a' && name[start + 1] == 'm'
0974: && name[start + 2] == 'p')
0975: ch = '&';
0976: } else if (length == 4) {
0977: char c1 = name[start + 1];
0978: char c2 = name[start + 2];
0979: char c3 = name[start + 3];
0980: if (c0 == 'q' && c1 == 'u' && c2 == 'o' && c3 == 't')
0981: ch = '"';
0982: else if (c0 == 'a' && c1 == 'p' && c2 == 'o' && c3 == 's')
0983: ch = '\'';
0984: }
0985: write(ch);
0986: }
0987:
0988: /** Process a character entity reference.
0989: * The string encoding of the character (e.g. "xFF" or "255") is given,
0990: * as well as the character value. */
0991: public void emitCharacterReference(int value, char[] name,
0992: int start, int length) {
0993: if (value >= 0x10000)
0994: Char.print(value, this );
0995: else
0996: write(value);
0997: }
0998:
0999: protected void checkValidComment(char[] chars, int offset,
1000: int length) {
1001: int i = length;
1002: boolean sawHyphen = true;
1003: while (--i >= 0) {
1004: boolean curHyphen = chars[offset + i] == '-';
1005: if (sawHyphen && curHyphen) {
1006: error('e', "consecutive or final hyphen in XML comment");
1007: break;
1008: }
1009: sawHyphen = curHyphen;
1010: }
1011: }
1012:
1013: /** Process a comment.
1014: * The data (starting at start for length chars).
1015: * Does not include the delimiters (i.e. "<!--" and "-->" are excluded). */
1016: public void writeComment(char[] chars, int start, int length) {
1017: checkValidComment(chars, start, length);
1018: commentFromParser(chars, start, length);
1019: }
1020:
1021: /** Process a comment, when called from an XML parser.
1022: * The data (starting at start for length chars).
1023: * Does not include the delimiters (i.e. "<!--" and "-->" are excluded). */
1024: public void commentFromParser(char[] chars, int start, int length) {
1025: if (stringizingLevel == 0) {
1026: closeStartTag();
1027: if (base instanceof XConsumer)
1028: ((XConsumer) base).writeComment(chars, start, length);
1029: } else if (stringizingElementNesting < 0)
1030: base.write(chars, start, length);
1031: }
1032:
1033: public void writeProcessingInstruction(String target,
1034: char[] content, int offset, int length) {
1035: target = TextUtils.replaceWhitespace(target, true);
1036: for (int i = offset + length; --i >= offset;) {
1037: char ch = content[i];
1038: while (ch == '>' && --i >= offset) {
1039: ch = content[i];
1040: if (ch == '?') {
1041: error('e',
1042: "'?>' is not allowed in a processing-instruction");
1043: break;
1044: }
1045: }
1046: }
1047:
1048: if ("xml".equalsIgnoreCase(target))
1049: error('e',
1050: "processing-instruction target may not be 'xml' (ignoring case)");
1051: if (!XName.isNCName(target))
1052: error('e', "processing-instruction target '" + target
1053: + "' is not a valid Name");
1054:
1055: processingInstructionCommon(target, content, offset, length);
1056: }
1057:
1058: void processingInstructionCommon(String target, char[] content,
1059: int offset, int length) {
1060: if (stringizingLevel == 0) {
1061: closeStartTag();
1062: if (base instanceof XConsumer)
1063: ((XConsumer) base).writeProcessingInstruction(target,
1064: content, offset, length);
1065: } else if (stringizingElementNesting < 0)
1066: base.write(content, offset, length);
1067: }
1068:
1069: /** Process a processing instruction. */
1070: public void processingInstructionFromParser(char[] buffer,
1071: int tstart, int tlength, int dstart, int dlength) {
1072: // Skip XML declaration.
1073: if (tlength == 3 && !inElement() && buffer[tstart] == 'x'
1074: && buffer[tstart + 1] == 'm'
1075: && buffer[tstart + 2] == 'l')
1076: return;
1077: String target = new String(buffer, tstart, tlength);
1078: processingInstructionCommon(target, buffer, dstart, dlength);
1079: }
1080:
1081: public void startDocument() {
1082: closeStartTag();
1083: if (stringizingLevel > 0)
1084: writeJoiner();
1085: // We need to increment nesting so that endDocument can decrement it.
1086: else {
1087: if (nesting == 0)
1088: base.startDocument();
1089: else
1090: writeJoiner();
1091: ensureSpaceInWorkStack(nesting);
1092: workStack[nesting] = namespaceBindings;
1093: // The following should be redundant, but just in case ...
1094: // Having workStack[nesting+1] be null identifies that nesting
1095: // level as being a document rather than an element.
1096: workStack[nesting + 1] = null;
1097: nesting += 2;
1098: }
1099: }
1100:
1101: public void endDocument() {
1102: if (stringizingLevel > 0) {
1103: writeJoiner();
1104: return;
1105: }
1106: nesting -= 2;
1107: namespaceBindings = (NamespaceBinding) workStack[nesting];
1108: workStack[nesting] = null;
1109: workStack[nesting + 1] = null;
1110: if (nesting == 0)
1111: base.endDocument();
1112: else
1113: writeJoiner();
1114: /*
1115: if (nesting == 0)
1116: {
1117: workStack = null;
1118: attrIndexes = null;
1119: }
1120: */
1121: }
1122:
1123: /** Process a DOCTYPE declaration. */
1124: public void emitDoctypeDecl(char[] buffer, int target, int tlength,
1125: int data, int dlength) {
1126: // FIXME?
1127: }
1128:
1129: public void beginEntity(Object baseUri) {
1130: if (base instanceof XConsumer)
1131: ((XConsumer) base).beginEntity(baseUri);
1132: }
1133:
1134: public void endEntity() {
1135: if (base instanceof XConsumer)
1136: ((XConsumer) base).endEntity();
1137: }
1138:
1139: /* #ifdef JAVA5 */
1140: // public XMLFilter append (char c)
1141: // {
1142: // write(c);
1143: // return this;
1144: // }
1145: // public XMLFilter append (CharSequence csq)
1146: // {
1147: // if (csq == null)
1148: // csq = "null";
1149: // append(csq, 0, csq.length());
1150: // return this;
1151: // }
1152: // public XMLFilter append (CharSequence csq, int start, int end)
1153: // {
1154: // if (csq == null)
1155: // csq = "null";
1156: // write(csq, start, end-start);
1157: // return this;
1158: // }
1159: /* #endif */
1160:
1161: MappingInfo lookupTag(Symbol qname) {
1162: String local = qname.getLocalPart();
1163: String prefix = qname.getPrefix();
1164: if (prefix == "")
1165: prefix = null;
1166: String uri = qname.getNamespaceURI();
1167: int hash = MappingInfo.hash(prefix, local);
1168: int index = hash & mappingTableMask;
1169: MappingInfo first = mappingTable[index];
1170: MappingInfo info = first;
1171: for (;;) {
1172: if (info == null) {
1173: // No match found - create a new MappingInfo and Strings.
1174: info = new MappingInfo();
1175: info.qname = qname;
1176: info.prefix = prefix;
1177: info.uri = uri;
1178: info.local = local;
1179: info.tagHash = hash;
1180: info.nextInBucket = first;
1181: mappingTable[index] = first;
1182: return info;
1183: }
1184: if (qname == info.qname)
1185: return info;
1186: if (local == info.local && info.qname == null
1187: && (uri == info.uri || info.uri == null)
1188: && prefix == info.prefix) {
1189: info.uri = uri;
1190: info.qname = qname;
1191: return info;
1192: }
1193: info = info.nextInBucket;
1194: }
1195: }
1196:
1197: /** Look up an attribute/element tag (a QName as a lexical string
1198: * before namespace resolution), and return a MappingInfo with the
1199: * tagHash, prefix, and local fields set.
1200: * The trick is to avoid allocating a new String for each element or
1201: * attribute node we see, but only allocate a new String when we see a
1202: * tag we haven't seen. So we calculate the hash code using the
1203: * characters in the array, rather than using String's hashCode.
1204: */
1205: MappingInfo lookupTag(char[] data, int start, int length) {
1206: // Calculate hash code. Also note presence+position of ':'.
1207: int hash = 0;
1208: int prefixHash = 0;
1209: int colon = -1;
1210: for (int i = 0; i < length; i++) {
1211: char ch = data[start + i];
1212: if (ch == ':' && colon < 0) {
1213: colon = i;
1214: prefixHash = hash;
1215: hash = 0;
1216: } else
1217: hash = 31 * hash + ch;
1218: }
1219: hash = prefixHash ^ hash;
1220: int index = hash & mappingTableMask;
1221: MappingInfo first = mappingTable[index];
1222: MappingInfo info = first;
1223: for (;;) {
1224: if (info == null) {
1225: // No match found - create a new MappingInfo and Strings.
1226: info = new MappingInfo();
1227: info.tagHash = hash;
1228: if (colon >= 0) {
1229: info.prefix = new String(data, start, colon)
1230: .intern();
1231: colon++;
1232: int lstart = start + colon;
1233: info.local = new String(data, lstart, length
1234: - colon).intern();
1235: } else {
1236: info.prefix = null;
1237: info.local = new String(data, start, length)
1238: .intern();
1239: }
1240: info.nextInBucket = first;
1241: mappingTable[index] = first;
1242: return info;
1243: }
1244: if (hash == info.tagHash && info.match(data, start, length))
1245: return info;
1246: info = info.nextInBucket;
1247: }
1248: }
1249:
1250: private void ensureSpaceInWorkStack(int oldSize) {
1251: if (workStack == null) {
1252: workStack = new Object[20];
1253: } else if (oldSize >= workStack.length) {
1254: Object[] tmpn = new Object[2 * workStack.length];
1255: System.arraycopy(workStack, 0, tmpn, 0, oldSize);
1256: workStack = tmpn;
1257: }
1258: }
1259:
1260: private void ensureSpaceInStartIndexes(int oldSize) {
1261: if (startIndexes == null) {
1262: startIndexes = new int[20];
1263: } else if (oldSize >= startIndexes.length) {
1264: int[] tmpn = new int[2 * startIndexes.length];
1265: System.arraycopy(startIndexes, 0, tmpn, 0, oldSize);
1266: startIndexes = tmpn;
1267: }
1268: }
1269:
1270: public static String duplicateAttributeMessage(Symbol attrSymbol,
1271: Object elementName) {
1272: StringBuffer sbuf = new StringBuffer("duplicate attribute: ");
1273: String uri = attrSymbol.getNamespaceURI();
1274: if (uri != null && uri.length() > 0) {
1275: sbuf.append('{');
1276: sbuf.append('}');
1277: sbuf.append(uri);
1278: }
1279: sbuf.append(attrSymbol.getLocalPart());
1280: if (elementName != null) {
1281: sbuf.append(" in <");
1282: sbuf.append(elementName);
1283: sbuf.append('>');
1284: }
1285: return sbuf.toString();
1286: }
1287:
1288: public void error(char severity, String message) {
1289: if (messages == null)
1290: throw new RuntimeException(message);
1291: else if (locator != null)
1292: messages.error(severity, locator, message);
1293: else
1294: messages.error(severity, message);
1295: }
1296:
1297: public boolean ignoring() {
1298: return ignoringLevel > 0;
1299: }
1300:
1301: /* #ifdef SAX2 */
1302: public void setDocumentLocator(Locator locator) {
1303: if (locator instanceof SourceLocator)
1304: this .locator = (SourceLocator) locator;
1305: else
1306: ; // create SourceLocator that indirects to given locator. FIXME.
1307: }
1308:
1309: public void startElement(String namespaceURI, String localName,
1310: String qName, Attributes atts) {
1311: startElement(Symbol.make(namespaceURI, localName));
1312: int numAttributes = atts.getLength();
1313: for (int i = 0; i < numAttributes; i++) {
1314: startAttribute(Symbol.make(atts.getURI(i), atts
1315: .getLocalName(i)));
1316: write(atts.getValue(i));
1317: endAttribute();
1318: }
1319: }
1320:
1321: public void endElement(String namespaceURI, String localName,
1322: String qName) {
1323: endElement();
1324: }
1325:
1326: public void startElement(String name, AttributeList atts) {
1327: name = name.intern(); // ???
1328: startElement(name); // FIXME
1329: int attrLength = atts.getLength();
1330: for (int i = 0; i < attrLength; i++) {
1331: name = atts.getName(i);
1332: name = name.intern(); // ?
1333: String type = atts.getType(i);
1334: String value = atts.getValue(i);
1335: startAttribute(name); // FIXME
1336: write(value);
1337: endAttribute();
1338: }
1339: }
1340:
1341: public void endElement(String name) throws SAXException {
1342: endElement();
1343: }
1344:
1345: public void characters(char ch[], int start, int length)
1346: throws SAXException {
1347: write(ch, start, length);
1348: }
1349:
1350: public void ignorableWhitespace(char ch[], int start, int length)
1351: throws SAXException {
1352: write(ch, start, length); // FIXME
1353: }
1354:
1355: /* #endif */
1356:
1357: public void processingInstruction(String target, String data) {
1358: // FIXME - this could be done more efficiently by copying the
1359: // string into tlist.
1360: char[] chars = data.toCharArray();
1361: processingInstructionCommon(target, chars, 0, chars.length);
1362: }
1363:
1364: public void startPrefixMapping(String prefix, String uri) {
1365: namespaceBindings = findNamespaceBinding(prefix.intern(), uri
1366: .intern(), namespaceBindings);
1367: }
1368:
1369: public void endPrefixMapping(String prefix) {
1370: namespaceBindings = namespaceBindings.getNext();
1371: }
1372:
1373: public void skippedEntity(String name) {
1374: // FIXME
1375: }
1376:
1377: public String getPublicId() {
1378: return null;
1379: }
1380:
1381: public String getSystemId() {
1382: return in == null ? null : in.getName();
1383: }
1384:
1385: public String getFileName() {
1386: return in == null ? null : in.getName();
1387: }
1388:
1389: public int getLineNumber() {
1390: if (in == null)
1391: return -1;
1392: int line = in.getLineNumber();
1393: return line < 0 ? -1 : line + 1;
1394: }
1395:
1396: public int getColumnNumber() {
1397: int col;
1398: return in != null && (col = in.getColumnNumber()) > 0 ? col
1399: : -1;
1400: }
1401:
1402: public boolean isStableSourceLocation() {
1403: return false;
1404: }
1405: }
1406:
1407: final class MappingInfo {
1408: /** Next in same hash bucket. */
1409: MappingInfo nextInBucket;
1410:
1411: // maybe future: MappingInfo prevInBucket;
1412: // maybe future: MappingInfo nextForPrefix;
1413:
1414: /** The cached value of {@code hash(prefix, local)}. */
1415: int tagHash;
1416:
1417: /** The prefix part of tag: - the part before the colon.
1418: * It is null if there is no colon in tag. Otherwise it is interned. */
1419: String prefix;
1420:
1421: /** The local name part of tag: - the part after the colon.
1422: * It is interned. */
1423: String local;
1424:
1425: /** The namespace URI.
1426: * The value null means "unknown". */
1427: String uri;
1428:
1429: /** The Symbol for the resolved QName.
1430: * If non-null, it must be the case that {@code uri!= null}, and
1431: * {@code qname==Symbol.make(uri, local, prefix==null?"":prefix)}.
1432: */
1433: Symbol qname;
1434:
1435: NamespaceBinding namespaces;
1436:
1437: /** An XName matching the other fields.
1438: * If non-null, we must have {@code qname!=null}, {@code namespaces!=null},
1439: * {@code type.namespaceNodes == namespaces}, and
1440: * {@code type.equals(qname)}. */
1441: XName type;
1442:
1443: /** If non-negative: An index into a TreeList objects array. */
1444: int index = -1;
1445:
1446: static int hash(String prefix, String local) {
1447: int hash = local.hashCode();
1448: if (prefix != null)
1449: hash ^= prefix.hashCode();
1450: return hash;
1451: }
1452:
1453: /** Hash a QName, handling an optional prefix+colon. */
1454: static int hash(char[] data, int start, int length) {
1455: int hash = 0;
1456: int prefixHash = 0;
1457: int colonPos = -1;
1458: for (int i = 0; i < length; i++) {
1459: char ch = data[start + i];
1460: if (ch == ':' && colonPos < 0) {
1461: colonPos = i;
1462: prefixHash = hash;
1463: hash = 0;
1464: } else
1465: hash = 31 * hash + ch;
1466: }
1467: return prefixHash ^ hash;
1468: }
1469:
1470: /** Match {@code "[prefix:]length"} against {@code new String(data, start, next)}. */
1471: boolean match(char[] data, int start, int length) {
1472: if (prefix != null) {
1473: int localLength = local.length();
1474: int prefixLength = prefix.length();
1475: return length == prefixLength + 1 + localLength
1476: && data[prefixLength] == ':'
1477: && equals(prefix, data, start, prefixLength)
1478: && equals(local, data, start + prefixLength + 1,
1479: localLength);
1480: } else
1481: return equals(local, data, start, length);
1482: }
1483:
1484: /** An optimization of {@code sbuf.toString().equals(tag)}.
1485: */
1486: static boolean equals(String tag, StringBuffer sbuf) {
1487: int length = sbuf.length();
1488: if (tag.length() != length)
1489: return false;
1490: for (int i = 0; i < length; i++)
1491: if (sbuf.charAt(i) != tag.charAt(i))
1492: return false;
1493: return true;
1494: }
1495:
1496: static boolean equals(String tag, char[] data, int start, int length) {
1497: if (tag.length() != length)
1498: return false;
1499: for (int i = 0; i < length; i++)
1500: if (data[start + i] != tag.charAt(i))
1501: return false;
1502: return true;
1503: }
1504: }
|