-// Copyright (C) 2003 Adam Megacz <adam@ibex.org> all rights reserved.
-//
-// You may modify, copy, and redistribute this code under the terms of
-// the GNU Library Public License version 2.1, with the exception of
-// the portion of clause 6a after the semicolon (aka the "obnoxious
-// relink clause")
+// Copyright 2000-2005 the Contributors, as shown in the revision logs.
+// Licensed under the Apache Public Source License 2.0 ("the License").
+// You may not use this file except in compliance with the License.
package org.ibex.util;
-import java.io.Reader;
-import java.io.IOException;
import java.io.EOFException;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.Reader;
+import java.io.Writer;
+import java.io.Serializable;
/**
- * An Event-Driving, Non-Validating XML Parser with Namespace support.
+ * An non-validating XML Parser with Namespace support.
+ *
+ * <h3>SAX-like usage</h3>
*
- * A subclass can implement the abstract functions for receiving details
- * about an xml file as it is parsed. To initate a parse, use the parse()
- * function.
+ * <p>Subclass XML and implement the four abstract functions. Call
+ * <tt>parse()</tt> to begin synchronously processing reader input.
+ * Any number of documents may be <tt>parse()</tt>ed.</p>
+ *
+ * <h3>DOM-like usage</h3>
+ *
+ * <p>Instansiate <tt>XML.Document</tt> and call <tt>parse()</tt>. The
+ * root of the document tree can be accessed by calling <tt>getRoot()</tt>.
+ * See the public interface <tt>XML.Element</tt> for tree traversal.</p>
+ *
+ * <p>Only one document may be <tt>parse()</tt>ed per <tt>XML.Document</tt>
+ * instance.</p>
*
* <h3>Implementation Notes</h3>
- * <p>As the parser traverses into an element, it adds it to the linked list
- * called <tt>elements</tt>. However, <tt>elements</tt> has been pre-filled
- * with instances of the Element inner class. So in the vast majority of
- * cases, the pointer current is moved along one, and the values for the
- * new element are filled into the current object.</p>
*
* <p>This parser supports all the unicode ranges required by the XML
* Specification. However, it is optimised for well-formed ASCII documents.
* Violation and is not recoverable</li>
* </ul>
*
- * @author David Crawshaw
+ * @author crawshaw@ibex.org
* @see <a href="http://w3.org/TR/REC-xml">XML Specification</a>
* @see <a href="http://w3.org/TR/REC-xml-names">XML Namespaces</a>
*/
public abstract class XML
{
- /////////////////////////////////////////////////////////////////////////////////////////////
- // XML Parser
- /////////////////////////////////////////////////////////////////////////////////////////////
+ // XML Parser /////////////////////////////////////////////////////////////
+
+ /** Default initial buffer size. */
+ public static final int BUFFER_SIZE = 256;
- public static final int BUFFER_SIZE = 255;
+ private static final int DEFAULT_ATTR_COUNT = 2;
+ private static final int DEFAULT_PFX_COUNT = 2;
- /** static pool of XML.Element instances shared by all XML Parsers. */
- private static final Queue elements = new Queue(30);
+ /** static pool of XML.Elem instances shared by all XML Parsers. */
+ private static final Basket.List elements = new Basket.Array();
private static final char[] single_amp = new char[] { '&' };
private static final char[] single_apos = new char[] { '\'' };
private static final char[] single_lt = new char[] { '<' };
private static final char[] single_quot = new char[] { '"' };
+ private final boolean poolElements;
+
private int line;
private int col;
private int base; // base+off == distance into the stream
private int len;
- private Element current;
+ private Elem current;
// used in readEntity() to process a single character without creating a new array
private char[] singlechar = new char[1];
- public XML() { this(BUFFER_SIZE); }
+ /** Creates a new XML parser with that has a default initial
+ * buffer size and reuses its signal objects. */
+ protected XML() { this(BUFFER_SIZE, true); }
- public XML(int bSize) {
+ /** Creates a new XML parser.
+ * @param bSize initial buffer size.
+ * @param poolElements if true the objects passed to the signal functions are reused.
+ */
+ protected XML(int bSize, boolean poolElements) {
buf = new char[bSize];
+ this.poolElements = poolElements;
- current = (Element)elements.remove(false);
- if (current == null) current = new Element();
+ current = element();
}
/** Returns the line number at the beginning of the last process call. */
/** Returns the global file offset at the beginning of the last process call. */
public int getGlobalOffset() { return base + off; }
- /**
- * Parse given input and call the abstract event functions.
- *
- * Careful with threading, as this function is not synchronized.
- */
+ /** Set the reader used as a data source by the XML parser. */
+ public void setReader(Reader reader) { in = reader; }
+
+ /** Parse given input and call the abstract event functions.
+ * Equivalent to calling <tt>setReader(reader); parse();</tt>. */
public final void parse(Reader reader) throws IOException, Exn {
- in = reader;
+ setReader(reader); parse();
+ }
+
+ /** Parse given input and call the abstract event functions.
+ *
+ * <p>This function is synchronous with event functions, meaning it
+ * will only return <i>after</i> it has finished calling all signal
+ * functions.</p>
+ */
+ public final void parse() throws IOException, Exn {
off = len = 0;
line = col = 1;
-
- clear(); // clean up possible mid-way linked-list element
+ current = null;
try {
// process the stream
while (true) {
if (!buffer(1)) {
- if (current.qName == null) break;
- throw new Exn("reached eof without closing <"+current.qName+"> element", Exn.WFC, getLine(), getCol());
+ if (current == null) break;
+ throw new Exn("reached eof without closing <"+current.qName+"> element",
+ Exn.WFC, getLine(), getCol());
}
- if (buf[off] == '<') readTag();
- readChars(current.qName != null);
+ if (buf[off] == '<') {
+ if (current == null) current = element();
+ readTag();
+ }
+ readChars(current != null);
}
} finally { clear(); } // clean up elements
}
- /** remove any leftover elements from the linked list and queue them */
- private final void clear() {
- for (Element last = current; current.parent != null; ) {
- current = current.parent;
- last.clear();
- elements.append(last);
+ /** Parses the next tag or block of character data, calling the
+ * abstract event functions to process the data.
+ *
+ * @return True if successfully processed a block of data.
+ */
+ public boolean parseNext() throws IOException, Exn {
+ if (!buffer(1)) {
+ if (current == null) return false;
+ throw new Exn("reached eof without closing <"+current.qName+"> element",
+ Exn.WFC, getLine(), getCol());
+ }
+
+ // move through meaningless data
+ if (current != null) readChars(false);
+
+ if (buf[off] == '<') {
+ // proecess and return a tag
+ if (current == null) current = element();
+ readTag();
+ } else {
+ // processes a block of character data
+ readChars(true);
+ }
+
+ return true;
+ }
+
+ /** Returns the current Tree.Element, or null if outside the root node. */
+ public Tree.Element current() { return current; }
+
+ /** Empty the linked list. */
+ private void clear() {
+ while (current != null) {
+ Elem l = current;
+ current = (Elem)current.parent;
+ element(l);
}
- current.clear();
}
- /** reads in a tag. expects <tt>buf[off] == '<'</tt> */
- private final void readTag() throws IOException, Exn {
+ /** Provides a fresh element. */
+ private Elem element() {
+ Elem e = null;
+ if (poolElements) synchronized (elements) {
+ if (elements.size() > 0) e = (Elem)elements.remove(elements.size() - 1);
+ }
+ if (e == null) e = new Elem();
+ return e;
+ }
+
+ /** Frees a used element. */
+ private void element(Elem e) {
+ if (e == null || !poolElements) return;
+ e.clear(); synchronized (elements) { elements.add(e); }
+ }
+
+
+ /** Reads in a tag. Expects <tt>buf[off] == '<'</tt>. */
+ private void readTag() throws IOException, Exn {
// Start Tag '<' Name (S Attribute)* S? '>'
boolean starttag = true;
// create the in-memory element representation of this beast
// if current.qName == null then this is the root element we're dealing with
if (current.qName != null) {
- Element next = (Element)elements.remove(false);
- if (next == null) next = new Element();
- //next.clear(); // TODO: remove as elements now checked as they're added to the queue
+ Elem next = element();
next.parent = current;
current = next;
}
}
// work out the uri of this element
- current.uri = current.getUri(current.getPrefix());
- if (current.getUri().equals("") && current.getPrefix() != null)
- current.addError(new Exn("undefined prefix '"+current.getPrefix()+"'", Exn.NC, getLine(), getCol()));
+ String p = current.getPrefix();
+ String uri = current.uri(p);
+ if (uri == null && p != null && !p.equals("")) error(new Exn("undefined prefix '"+current.getPrefix()+"'", Exn.NC, getLine(), getCol()));
+ else current.uri = uri;
} else {
// this is an end-of-element tag
if (endtag) {
endElement(current);
- // we just closed an element, so remove it from the element 'stack'
- if (current.getParent() == null) {
- // we just finished the root element
- current.clear();
- } else {
- Element last = current;
- current = current.parent;
- last.clear();
- elements.append(last);
- }
+ Elem l = current;
+ current = (Elem)current.parent;
+ element(l);
}
}
}
// process attribute
if (p != null && p.equals("xmlns")) {
- current.addUri(n, v);
+ current.addPrefix(n, v);
} else if (n.equals("xmlns")) {
- if (current.getUri().equals("")) {
- current.addUri("", v);
+ if (current.getUri() == null || current.getUri().equals("")) {
+ current.addPrefix("", v);
} else {
- current.addError(new Exn("default namespace definition repeated", Exn.NC, getLine(), getCol()));
+ error(new Exn("default namespace definition repeated", Exn.NC, getLine(), getCol()));
}
} else {
// find attribute uri
- u = current.getUri(p);
- if (p != null && u.equals("")) current.addError(new Exn("undefined attribute prefix '"+p+"'", Exn.NC, getLine(), getCol()));
+ u = current.uri(p);
+ if (u == null && p != null) error(new Exn("undefined attribute prefix '"+p+"'", Exn.NC, getLine(), getCol()));
// check to see if attribute is a repeat
- for (int i=0; current.len > i; i++) if (n.equals(current.getAttrKey(i)) && u.equals(current.getAttrUri(i))) throw new Exn(
+ for (int i=0; current.attrSize() > i; i++) if (n.equals(current.getKey(i)) && u.equals(current.getUri(i))) throw new Exn(
"attribute name '"+n+"' may not appear more than once in the same element tag", Exn.WFC, getLine(), getCol()
);
- current.addAttr(n, v, u);
+ current.addAttr(n, v, u, p);
}
}
}
/**
- * reads until a <tt><</tt> symbol is encountered
+ * Reads until a <tt><</tt> symbol is encountered.
* @param p If true call the characters(char[],int,int) funciton for the processed characters
*/
private final void readChars(boolean p) throws IOException, Exn {
+ boolean lastWhite = false;
int ref;
for (boolean more = true; more;) {
if (!buffer(1)) return;
+ boolean readWhite = false;
buf: for (ref = 0; ref < len; ref++) {
+
switch (buf[off+ref]) {
case '\r': // windows or macos9 newline
+ if (lastWhite) { readWhite = true; break buf; }
+ lastWhite = true;
+
// normalise and process
buf[off+ref] = '\n'; ref++;
if (p) characters(buf, off, ref);
break;
case '\n': // unix newline
+ if (lastWhite) { readWhite = true; break buf; }
+ lastWhite = true;
+
ref++;
if (p) characters(buf, off, ref);
off += ref; len -= ref; ref = -1;
line++; col = 1;
+ if (buffer(1) && S(buf[off])) {
+ readWhite = true; break buf;
+ }
break;
+ case ' ':
+ case '\t':
+ if (lastWhite) { readWhite = true; break buf; }
+ lastWhite = true;
+ break;
+
+ case '<': // end of chars section
+ more = false;
+ break buf;
+
case '&': // entity
if (p) {
if (ref > 0) characters(buf, off, ref);
off += ref; len -= ref; ref = -1;
readEntity();
}
- break;
-
- case '<': // end of chars section
- more = false;
- break buf;
+ default:
+ lastWhite = false;
}
}
- if (p && ref > 0) characters(buf, off, ref);
- off += ref; len -= ref; col += ref;
+ if (ref > 0) {
+ if (p) characters(buf, off, ref);
+ off += ref; len -= ref; col += ref;
+ }
+
+ if (readWhite) {
+ readWhitespace();
+ more = buffer(1) && !(buf[off] == '<');
+ readWhite = false;
+ }
}
}
line++; col = 1;
// windows double-char newline; skip the next char
- if (!buffer(1)) return;
- if (buf[off] == '\n') { off++; len--; }
+ if (buffer(1) && buf[off] == '\n') { off++; len--; }
break;
case '\n': // unix newline
}
}
- off += ref; len -= ref; col += ref;
+ if (ref > 0) {
+ whitespace(buf, off, ref);
+ off += ref; len -= ref; col += ref;
+ }
}
}
/**
- * attempt to fill the buffer.
+ * Attempt to fill the buffer.
*
* @param min Minimum number of characters to read (even if we have to block to do it).
* @return return false if min can't be reached.
}
- /////////////////////////////////////////////////////////////////////////////////////////////
- // Abstract SAX-Like Interface
- /////////////////////////////////////////////////////////////////////////////////////////////
+ // SAX-like Interface /////////////////////////////////////////////////////
- /**
- * Called when the start of an element is processed.
+ /** Called when the start of an element is processed.
*
- * <p><b>DO NOT</b> store a reference to the Element object, as
- * they are reused by XML Parser.</p>
+ * <p>If poolElements == true (default), <b>DO NOT</b> store a
+ * reference to the Element object, as they are reused by
+ * XML Parser.</p>
*/
- public abstract void startElement(Element e) throws Exn;
+ public abstract void startElement(Tree.Element e) throws Exn;
- /**
- * Represents up to a line of character data.
+ /** Called when up to a line of character data is processed.
*
* <p>Newlines are all normalised to the Unix \n as per the XML Spec,
* and a newline will only appear as the last character in the passed
*/
public abstract void characters(char[] ch, int start, int length) throws Exn, IOException;
- /** Represents up to a line of ignorable whitespace. */
- public abstract void whitespace(char[] ch, int start, int length) throws Exn, IOException;
+ /** Called when the end of an Tree.Element is processed. */
+ public abstract void endElement(Tree.Element e) throws Exn, IOException;
- /** Represents the end of an Element. */
- public abstract void endElement(Element e) throws Exn, IOException;
+ /** Optional callback; called when when up to a line of ignorable whitespace is processed. */
+ public void whitespace(char[] ch, int start, int length) throws Exn, IOException {}
+ /** Optonal callback; called when a recoverable parsing error has been encountered. */
+ public void error(Exn e) throws Exn, IOException {}
- /////////////////////////////////////////////////////////////////////////////////////////////
- // Inner Classes for Parser Support
- /////////////////////////////////////////////////////////////////////////////////////////////
+ // DOM-like Interface /////////////////////////////////////////////////////
- /**
- * Represents an element in an XML document. Stores a reference to its
- * parent, forming a one-way linked list.
+ /** A Document Object Model extension to the XML Parser.
*
- * Element objects are reused, so client code making use of them must
- * drop their references after the specific element process function
- * has returned.
+ * <p>To use, instaniate XML.Document and call parse(Reader).The
+ * full Block tree can then be accessed starting from the root
+ * element by calling getRoot().</p>
*/
- public static final class Element {
+ public static class Document {
+ private final DXML xml;
+ private Tree.Element root = null;
- private static final int DEFAULT_ATTR_SIZE = 10;
+ /** Creates a new XML.Document. Default initial buffer size is used. */
+ public Document() { this(BUFFER_SIZE); }
- protected Element parent = null;
+ /** Creates a new XML.Document with a sepcified initial buffer size. */
+ public Document(int bSize) { xml = new DXML(bSize, false); }
- protected String uri = null;
- protected String localName = null;
- protected String qName = null;
- protected String prefix = null;
+ /** Returns the root Tree.Element of the parsed xml document. */
+ public Tree.Element getRoot() { return root; }
- protected Hash urimap = new Hash(3,3);
+ /** Sets the root element of this document. */
+ public void setRoot(Tree.Element e) { root = e; }
- protected String[] keys = new String[DEFAULT_ATTR_SIZE];
- protected String[] vals = new String[DEFAULT_ATTR_SIZE];
- protected String[] uris = new String[DEFAULT_ATTR_SIZE];
- protected int len = 0;
+ /** Parse given input create the document model. */
+ public void parse(Reader r) throws IOException, Exn { xml.parse(r); }
- protected Exn[] errors = new Exn[] {};
+ /** Returns a character representation of this document. */
+ /*public String toXML() throws IOException { FIXME
+ StringWriter w = new StringWriter(); toXML(w); return w.toString();
+ }*/
+
+ /** Writes the character representation of this document to
+ * the given writer. Calls <tt>root.toXML(Writer)</tt>.*/
+ //public void toXML(Writer w) throws IOException { if (root == null) return; root.toXML(w); }
- /** Parent of current element. */
- public Element getParent() { return parent; }
-
- /** Qualified Name of current element. XML Namespace Spec 14-Jan-1999 [6] */
- public String getQName() { return qName; }
-
- /** LocalPart of current element. XML Namespace Spec 14-Jan-1999 [8] */
- public String getLocalName() { return localName; }
+ /** Used to hide implementation from public interface. */
+ private final class DXML extends XML {
+ private StringBuffer chars = null;
- /** Prefix of current element. Substring of qName. XML Namespace Spec 14-Jan-1999 [7] */
- public String getPrefix() { return prefix; }
+ private DXML(int b, boolean r) { super(b, r); }
- // HACK
- public Hash getUriMap() {
- Hash map = new Hash();
- for (Element e = this; e != null; e = e.getParent()) {
- java.util.Enumeration en = e.urimap.keys();
- while(en.hasMoreElements()) {
- String key = (String)en.nextElement();
- String val = getUri(key);
- map.put(key, val);
+ public void startElement(Tree.Element e) {
+ if (root == null) root = e;
+ else {
+ if (chars != null) addText((Tree.Element)e.getParent());
+ e.getParent().getChildren().add(e);
}
}
- return map;
+ public void characters(char[] ch, int s, int l) {
+ if (chars == null) chars = new StringBuffer();
+ chars.append(ch, s, l);
+ }
+ public void endElement(Tree.Element e) { if (chars != null) addText(e); }
+
+ private void addText(Tree.Element e) {
+ e.getChildren().add(new Text(e, chars.toString())); chars = null;
+ }
+ }
+ }
+
+ // Pull Interface /////////////////////////////////////////////////////////
+
+ public static class Stream implements Tree.Stream {
+ private final SXML xml;
+ private int depth = -1;
+
+ /** Creates a new XML.Stream. Default initial buffer size is used. */
+ public Stream() { this(null, BUFFER_SIZE); }
+
+ /** Creates a new XML.Stram with a sepcified character source and
+ * initial buffer size. */
+ public Stream(Reader in, int bSize) {
+ xml = new SXML(bSize); setReader(in);
}
- /** URI of current tag. XML Namespace Spec 14-Jan-1999 section 1 */
- public String getUri() { return getUri(prefix); }
+ public int available() { return 0; }
+ public int getDepth() { return depth; }
+
+ /** Set the current character source. */
+ public void setReader(Reader in) { xml.setReader(in); }
+
+ public Tree.Leaf next() throws IOException { return xml.next(); }
- /** URI of a given prefix. Never returns null, instead gives "". */
- public String getUri(String p) {
- String ret = null;
- for (Element e = this; e != null && ret == null; e = e.getParent()) {
- ret = (String)e.urimap.get(p == null ? "" : p);
+ public void skip() throws IOException {
+ int d = depth;
+ do { next(); } while (d != depth);
+ }
+
+ /** Used to hide implementation from public interface. */
+ private final class SXML extends XML {
+ private StringBuffer chars = new StringBuffer();
+ private Tree.Element element = null;
+
+ private SXML(int b) { super(b, false); }
+ public void startElement(Tree.Element e) { depth++; element = e; }
+ public void endElement(Tree.Element e) { depth--; element = e; }
+ public void characters(char[] ch, int s, int l) { chars.append(ch, s, l); }
+ private Tree.Leaf next() throws IOException, Exn {
+ if (!parseNext()) return null;
+ if (element != null) {
+ Tree.Element e = element; element = null; return e;
+ } else if (chars.length() > 0) {
+ Tree.Leaf l = new Text(current(), chars.toString());
+ chars = new StringBuffer(); return l;
+ }
+ return null;
}
- return ret == null ? "" : ret;
}
+ }
- /** An array of attribute names. */
- public String getAttrKey(int pos) { return len > pos ? keys[pos] : null; }
+ // Public Interfaces //////////////////////////////////////////////////////
- /** An array of attribute values. */
- public String getAttrVal(int pos) { return len > pos ? vals[pos] : null; }
- /** An array of attribute uris. */
- public String getAttrUri(int pos) { return len > pos ? uris[pos] : null; }
+ // Support Classes ////////////////////////////////////////////////////////
- /** Current number of attributes in the element. */
- public int getAttrLen() { return len; }
+ /** Represents a block of text in an XML.Document model. */
+ private static final class Text implements Tree.Leaf, Serializable {
+ private Tree.Node p;
+ private String t;
- /** Poor performance, but easier to use when speed is not a concern */
- public Hash getAttrHash() {
- Hash ret = new Hash(getAttrLen() * 2, 3);
- for(int i=0; i<len; i++)
- ret.put(getAttrKey(i), getAttrVal(i));
- return ret;
- }
+ Text() {}
+ Text(Tree.Node p, String t) { this.t = t; this.p = p; }
- /** Poor performance, but easier to use */
- public String getAttrVal(String key) {
- for(int i=0; i<len; i++) if (keys[i].equals(key)) return vals[i];
- return null;
- }
+ public Tree.Node getParent() { return p; }
+ public void setParent(Tree.Node parent) { p = parent; }
+ public void out(Writer out) throws IOException { out.write(t); }
+ public void out(OutputStream out) { throw new UnsupportedOperationException(); }
+ }
+
+ private static final class Elem
+ implements Tree.Element, Tree.Attributes, Tree.Prefixes, Serializable {
+ private String uri = null;
+ private String localName = null;
+ private String qName = null;
+ private String prefix = null;
+
+ private Tree.Node parent = null;
+ private Basket.List children = null;
+
+ private Tree.Attributes a = this;
+ private Tree.Prefixes p = this;
+
+ Elem() { }
+
+ public Tree.Node getParent() { return parent; }
+ public void setParent(Tree.Node p) { parent = p; }
+ public Basket.List getChildren() { return children == null ? children = new Basket.Array() : children; }
- /** An array of non-fatal errors related to this element. */
- public Exn[] getErrors() { return errors; }
+ public Tree.Attributes getAttributes() { return a; }
+ public Tree.Prefixes getPrefixes() { return p; }
- protected Element() { }
+ public void setAttributes(Tree.Attributes a) { this.a = a; }
+ public void setPrefixes(Tree.Prefixes p) { this.p = p; }
- /** Add (replace if exists in current element) a Namespace prefix/uri map. */
- public void addUri(String name, String value) {
- urimap.put(name, value);
+ public String getQName() { return qName; }
+ public String getLocalName() { return localName; }
+ public String getPrefix() { return prefix; }
+ public String getUri() { return uri; }
+
+ // Attributes
+
+ /** attr[index + 0] // localName
+ * attr[index + 1] // value
+ * attr[index + 2] // uri
+ * attr[index + 3] // prefix */
+ private String[] attr = null;
+ private int attrSize = 0;
+
+ void addAttr(String key, String val, String uri, String pfx) {
+ if (attr == null) {
+ attr = new String[DEFAULT_ATTR_COUNT * 4];
+ } else if (attrSize == attr.length / 4) {
+ String[] newattr = new String[attr.length * 2];
+ System.arraycopy(attr, 0, newattr, 0, attr.length);
+ attr = newattr;
+ }
+
+ attr[attrSize * 4 + 0] = key;
+ attr[attrSize * 4 + 1] = val;
+ attr[attrSize * 4 + 2] = uri;
+ attr[attrSize * 4 + 3] = pfx;
+ attrSize++;
+ }
+
+ public int getIndex(String qName) {
+ if (qName == null || qName.length() == 0) return -1;
+ int pos = qName.indexOf(':');
+ if (pos < 1) return getIndex(null, qName);
+ return getIndex(uri(qName.substring(0, pos)), qName.substring(pos+1));
+ }
+
+ public int getIndex(String uri, String key) {
+ if (attr != null) for(int i=0; i < attrSize; i++)
+ if (attr[i*4].equals(key) && attr[i*4+1].equals(uri)) return i;
+ return -1;
}
- /** Add an attribute. */
- protected void addAttr(String key, String val, String uri) {
- if (len == keys.length) {
- // increase the size of the attributes arrays
- String[] newkeys = new String[keys.length*2];
- String[] newvals = new String[vals.length*2];
- String[] newuris = new String[uris.length*2];
- System.arraycopy(keys, 0, newkeys, 0, keys.length);
- System.arraycopy(vals, 0, newvals, 0, vals.length);
- System.arraycopy(uris, 0, newuris, 0, uris.length);
- keys = newkeys; vals = newvals; uris = newuris;
+ public String getKey(int pos) { return attr != null && attrSize > pos && pos >= 0 ? attr[pos * 4] : null; }
+ public String getVal(int pos) { return attr != null && attrSize > pos && pos >= 0 ? attr[pos * 4 + 1] : null; }
+ public String getUri(int pos) { return attr != null && attrSize > pos && pos >= 0 ? attr[pos * 4 + 2] : null; }
+ public String getPrefix(int pos) { return attr != null && attrSize > pos && pos >= 0 ? attr[pos * 4 + 3] : null; }
+ public String getQName(int pos) { String pfx = getPrefix(pos); return (pfx != null ? pfx + ":" : "") + getKey(pos); }
+ public int attrSize() { return attrSize; }
+
+ // Prefixes
+
+ /** pfx[index + 0] // prefix
+ * pfx[index + 1] // uri */
+ private String[] pfx = null;
+ private int pfxSize = 0;
+
+ void addPrefix(String prefix, String uri) {
+ if (pfx == null) {
+ pfx = new String[DEFAULT_PFX_COUNT * 2];
+ } else if (pfxSize == pfx.length / 4) {
+ String[] newpfx = new String[pfx.length * 2];
+ System.arraycopy(pfx, 0, newpfx, 0, pfx.length);
+ pfx = newpfx;
}
- keys[len] = key;
- vals[len] = val;
- uris[len] = uri;
- len++;
+ pfx[pfxSize * 2 + 0] = prefix == null ? "" : prefix;
+ pfx[pfxSize * 2 + 1] = uri == null ? "" : uri;
+ pfxSize++;
}
- /** Add an error. */
- protected void addError(Exn e) {
- // it doesn't really matter about continually expanding the array, as this case is quite rare
- Exn[] newe = new Exn[errors.length+1];
- System.arraycopy(errors, 0, newe, 0, errors.length);
- newe[errors.length] = e;
- errors = newe;
+ public int getPrefixIndexKey(String p) {
+ if (p == null) p = ""; for (int i=0; i < pfxSize; i++) if (p.equals(pfx[i * 2])) return i;
+ return -1;
+ }
+ public int getPrefixIndexVal(String u) {
+ if (u == null) u = ""; for (int i=0; i < pfxSize; i++) if (u.equals(pfx[i * 2 + 1])) return i;
+ return -1;
+ }
+ public String getPrefixKey(int i) {
+ if (i >= pfxSize) throw new IndexOutOfBoundsException(
+ "index "+i+" exceeds boundary "+pfxSize);
+ return pfx[i * 2];
+ }
+ public String getPrefixVal(int i) {
+ if (i >= pfxSize) throw new IndexOutOfBoundsException(
+ "index "+i+" exceeds boundary "+pfxSize);
+ return pfx[i * 2 + 1];
}
+ public int pfxSize() { return pfxSize; }
- /** Empty out all the data from the Element. */
- protected void clear() {
- parent = null;
- uri = localName = qName = prefix = null;
- urimap.clear();
+ // Output
+
+ public void out(OutputStream out) throws IOException { throw new UnsupportedOperationException(); }
+ public void out(Writer w) throws IOException {
+ Tree.Attributes a = getAttributes();
+ Tree.Prefixes p = getPrefixes();
+
+ w.write('<'); w.write(getQName());
- if (keys.length != vals.length || vals.length != uris.length) {
- keys = new String[DEFAULT_ATTR_SIZE];
- vals = new String[DEFAULT_ATTR_SIZE];
- uris = new String[DEFAULT_ATTR_SIZE];
+ for (int i=0; i < p.pfxSize(); i++) {
+ w.write(" xmlns");
+ String pfx = p.getPrefixKey(i);
+ if (pfx != null && pfx.length() > 0) {
+ w.write(':'); w.write(pfx);
+ }
+ w.write("=\"");
+ w.write(p.getPrefixVal(i));
+ w.write('\"');
+ }
+
+ for (int i=0; i < a.attrSize(); i++) {
+ w.write(' ');
+ String pfx = a.getPrefix(i);
+ if (pfx != null && pfx.length() > 0) {
+ w.write(pfx); w.write(':');
+ }
+ w.write(a.getKey(i));
+ w.write("=\"");
+ w.write(a.getVal(i));
+ w.write('\"');
+ }
+
+ Basket.List c = getChildren();
+ if (c == null || c.size() == 0) {
+ w.write(" />");
} else {
- for (int i=0; keys.length > i; i++) { keys[i] = null; vals[i] = null; uris[i] = null; };
+ w.write('>');
+
+ for (int i=0; i < c.size(); i++) ((Tree.Leaf)c.get(i)).out(w);
+
+ w.write("</");
+ w.write(getQName());
+ w.write('>');
}
- len = 0;
+ }
+
+ // Helper
+
+ void clear() {
+ if (children != null) children.clear();
+ if (attrSize > 0) { attr = null; attrSize = 0; }
+ if (pfxSize > 0) { pfx = null; pfxSize = 0; }
+
+ uri = localName = qName = prefix = null;
+ parent = null;
+ children = null;
+ }
- errors = new Exn[] {};
+ String uri(String prefix) {
+ String uri = null;
+ for (Tree.Element e = this; e != null; e = (Tree.Element)e.getParent()) {
+ int u = e.getPrefixes().getPrefixIndexKey(prefix);
+ if (u >= 0) { uri = e.getPrefixes().getPrefixVal(u); break; }
+ }
+ return uri;
}
}
/** Parse or Structural Error */
- public static class Exn extends Exception {
+ public static class Exn extends IOException implements Serializable {
/** Violation of Markup restrictions in XML Specification - Fatal Error */
public static final int MARKUP = 1;
this.col = col;
}
- public int getType() { return this.type; }
- public int getLine() { return this.line; }
- public int getCol() { return this.col; }
- public String getMessage() { return this.error + (line >= 0 && col >= 0 ? " at " + line + ":" + col: ""); }
+ public String getError() { return error; }
+ public int getType() { return type; }
+ public int getLine() { return line; }
+ public int getCol() { return col; }
+ public String getMessage() { return error + (line >= 0 && col >= 0 ? " at " + line + ":" + col: ""); }
+ public String toString() { return "XML.Exn: " + getMessage(); }
}
- /////////////////////////////////////////////////////////////////////////////////////////////
- // Static Support Functions for the XML Specification
- /////////////////////////////////////////////////////////////////////////////////////////////
+ // Static Support Functions for the XML Specification /////////////////////
- // attempt to avoid these functions unless you *expect* the input to fall in the given range.
+ // For effeciency's sake, attempt to avoid these functions unless you
+ // *expect* the input to fall in the given range. Thanks to unicode,
+ // even range matching can result in hundreds of comparisons to return
+ // a negative.
/** First Character of Name - XML Specification 1.0 [5] */
private static final boolean Name(char c) {