summaryrefslogtreecommitdiffstats
path: root/sax
diff options
context:
space:
mode:
Diffstat (limited to 'sax')
-rw-r--r--sax/java/android/sax/BadXmlException.java34
-rw-r--r--sax/java/android/sax/Children.java97
-rw-r--r--sax/java/android/sax/Element.java204
-rw-r--r--sax/java/android/sax/ElementListener.java23
-rw-r--r--sax/java/android/sax/EndElementListener.java28
-rw-r--r--sax/java/android/sax/EndTextElementListener.java30
-rw-r--r--sax/java/android/sax/RootElement.java207
-rw-r--r--sax/java/android/sax/StartElementListener.java32
-rw-r--r--sax/java/android/sax/TextElementListener.java23
-rw-r--r--sax/java/android/sax/package.html5
10 files changed, 683 insertions, 0 deletions
diff --git a/sax/java/android/sax/BadXmlException.java b/sax/java/android/sax/BadXmlException.java
new file mode 100644
index 0000000..dd324fd
--- /dev/null
+++ b/sax/java/android/sax/BadXmlException.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.sax;
+
+import org.xml.sax.SAXParseException;
+import org.xml.sax.Locator;
+
+/**
+ * An XML parse exception which includes the line number in the message.
+ */
+class BadXmlException extends SAXParseException {
+
+ public BadXmlException(String message, Locator locator) {
+ super(message, locator);
+ }
+
+ public String getMessage() {
+ return "Line " + getLineNumber() + ": " + super.getMessage();
+ }
+}
diff --git a/sax/java/android/sax/Children.java b/sax/java/android/sax/Children.java
new file mode 100644
index 0000000..7aa8dcf
--- /dev/null
+++ b/sax/java/android/sax/Children.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.sax;
+
+/**
+ * Contains element children. Using this class instead of HashMap results in
+ * measurably better performance.
+ */
+class Children {
+
+ Child[] children = new Child[16];
+
+ /**
+ * Looks up a child by name and creates a new one if necessary.
+ */
+ Element getOrCreate(Element parent, String uri, String localName) {
+ int hash = uri.hashCode() * 31 + localName.hashCode();
+ int index = hash & 15;
+
+ Child current = children[index];
+ if (current == null) {
+ // We have no children in this bucket yet.
+ current = new Child(parent, uri, localName, parent.depth + 1, hash);
+ children[index] = current;
+ return current;
+ } else {
+ // Search this bucket.
+ Child previous;
+ do {
+ if (current.hash == hash
+ && current.uri.compareTo(uri) == 0
+ && current.localName.compareTo(localName) == 0) {
+ // We already have a child with that name.
+ return current;
+ }
+
+ previous = current;
+ current = current.next;
+ } while (current != null);
+
+ // Add a new child to the bucket.
+ current = new Child(parent, uri, localName, parent.depth + 1, hash);
+ previous.next = current;
+ return current;
+ }
+ }
+
+ /**
+ * Looks up a child by name.
+ */
+ Element get(String uri, String localName) {
+ int hash = uri.hashCode() * 31 + localName.hashCode();
+ int index = hash & 15;
+
+ Child current = children[index];
+ if (current == null) {
+ return null;
+ } else {
+ do {
+ if (current.hash == hash
+ && current.uri.compareTo(uri) == 0
+ && current.localName.compareTo(localName) == 0) {
+ return current;
+ }
+ current = current.next;
+ } while (current != null);
+
+ return null;
+ }
+ }
+
+ static class Child extends Element {
+
+ final int hash;
+ Child next;
+
+ Child(Element parent, String uri, String localName, int depth,
+ int hash) {
+ super(parent, uri, localName, depth);
+ this.hash = hash;
+ }
+ }
+}
diff --git a/sax/java/android/sax/Element.java b/sax/java/android/sax/Element.java
new file mode 100644
index 0000000..8c8334c
--- /dev/null
+++ b/sax/java/android/sax/Element.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.sax;
+
+import org.xml.sax.Locator;
+import org.xml.sax.SAXParseException;
+
+import java.util.ArrayList;
+
+import android.util.Log;
+
+/**
+ * An XML element. Provides access to child elements and hooks to listen
+ * for events related to this element.
+ *
+ * @see RootElement
+ */
+public class Element {
+
+ final String uri;
+ final String localName;
+ final int depth;
+ final Element parent;
+
+ Children children;
+ ArrayList<Element> requiredChilden;
+
+ boolean visited;
+
+ StartElementListener startElementListener;
+ EndElementListener endElementListener;
+ EndTextElementListener endTextElementListener;
+
+ Element(Element parent, String uri, String localName, int depth) {
+ this.parent = parent;
+ this.uri = uri;
+ this.localName = localName;
+ this.depth = depth;
+ }
+
+ /**
+ * Gets the child element with the given name. Uses an empty string as the
+ * namespace.
+ */
+ public Element getChild(String localName) {
+ return getChild("", localName);
+ }
+
+ /**
+ * Gets the child element with the given name.
+ */
+ public Element getChild(String uri, String localName) {
+ if (endTextElementListener != null) {
+ throw new IllegalStateException("This element already has an end"
+ + " text element listener. It cannot have children.");
+ }
+
+ if (children == null) {
+ children = new Children();
+ }
+
+ return children.getOrCreate(this, uri, localName);
+ }
+
+ /**
+ * Gets the child element with the given name. Uses an empty string as the
+ * namespace. We will throw a {@link org.xml.sax.SAXException} at parsing
+ * time if the specified child is missing. This helps you ensure that your
+ * listeners are called.
+ */
+ public Element requireChild(String localName) {
+ return requireChild("", localName);
+ }
+
+ /**
+ * Gets the child element with the given name. We will throw a
+ * {@link org.xml.sax.SAXException} at parsing time if the specified child
+ * is missing. This helps you ensure that your listeners are called.
+ */
+ public Element requireChild(String uri, String localName) {
+ Element child = getChild(uri, localName);
+
+ if (requiredChilden == null) {
+ requiredChilden = new ArrayList<Element>();
+ requiredChilden.add(child);
+ } else {
+ if (!requiredChilden.contains(child)) {
+ requiredChilden.add(child);
+ }
+ }
+
+ return child;
+ }
+
+ /**
+ * Sets start and end element listeners at the same time.
+ */
+ public void setElementListener(ElementListener elementListener) {
+ setStartElementListener(elementListener);
+ setEndElementListener(elementListener);
+ }
+
+ /**
+ * Sets start and end text element listeners at the same time.
+ */
+ public void setTextElementListener(TextElementListener elementListener) {
+ setStartElementListener(elementListener);
+ setEndTextElementListener(elementListener);
+ }
+
+ /**
+ * Sets a listener for the start of this element.
+ */
+ public void setStartElementListener(
+ StartElementListener startElementListener) {
+ if (this.startElementListener != null) {
+ throw new IllegalStateException(
+ "Start element listener has already been set.");
+ }
+ this.startElementListener = startElementListener;
+ }
+
+ /**
+ * Sets a listener for the end of this element.
+ */
+ public void setEndElementListener(EndElementListener endElementListener) {
+ if (this.endElementListener != null) {
+ throw new IllegalStateException(
+ "End element listener has already been set.");
+ }
+ this.endElementListener = endElementListener;
+ }
+
+ /**
+ * Sets a listener for the end of this text element.
+ */
+ public void setEndTextElementListener(
+ EndTextElementListener endTextElementListener) {
+ if (this.endTextElementListener != null) {
+ throw new IllegalStateException(
+ "End text element listener has already been set.");
+ }
+
+ if (children != null) {
+ throw new IllegalStateException("This element already has children."
+ + " It cannot have an end text element listener.");
+ }
+
+ this.endTextElementListener = endTextElementListener;
+ }
+
+ @Override
+ public String toString() {
+ return toString(uri, localName);
+ }
+
+ static String toString(String uri, String localName) {
+ return "'" + (uri.equals("") ? localName : uri + ":" + localName) + "'";
+ }
+
+ /**
+ * Clears flags on required children.
+ */
+ void resetRequiredChildren() {
+ ArrayList<Element> requiredChildren = this.requiredChilden;
+ if (requiredChildren != null) {
+ for (int i = requiredChildren.size() - 1; i >= 0; i--) {
+ requiredChildren.get(i).visited = false;
+ }
+ }
+ }
+
+ /**
+ * Throws an exception if a required child was not present.
+ */
+ void checkRequiredChildren(Locator locator) throws SAXParseException {
+ ArrayList<Element> requiredChildren = this.requiredChilden;
+ if (requiredChildren != null) {
+ for (int i = requiredChildren.size() - 1; i >= 0; i--) {
+ Element child = requiredChildren.get(i);
+ if (!child.visited) {
+ throw new BadXmlException(
+ "Element named " + this + " is missing required"
+ + " child element named "
+ + child + ".", locator);
+ }
+ }
+ }
+ }
+}
diff --git a/sax/java/android/sax/ElementListener.java b/sax/java/android/sax/ElementListener.java
new file mode 100644
index 0000000..5b3f5be
--- /dev/null
+++ b/sax/java/android/sax/ElementListener.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.sax;
+
+/**
+ * Listens for the beginning and ending of elements.
+ */
+public interface ElementListener extends StartElementListener,
+ EndElementListener {}
diff --git a/sax/java/android/sax/EndElementListener.java b/sax/java/android/sax/EndElementListener.java
new file mode 100644
index 0000000..b00a006
--- /dev/null
+++ b/sax/java/android/sax/EndElementListener.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.sax;
+
+/**
+ * Listens for the end of elements.
+ */
+public interface EndElementListener {
+
+ /**
+ * Invoked at the end of an element.
+ */
+ void end();
+}
diff --git a/sax/java/android/sax/EndTextElementListener.java b/sax/java/android/sax/EndTextElementListener.java
new file mode 100644
index 0000000..6d87d0e
--- /dev/null
+++ b/sax/java/android/sax/EndTextElementListener.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.sax;
+
+/**
+ * Listens for the end of text elements.
+ */
+public interface EndTextElementListener {
+
+ /**
+ * Invoked at the end of a text element with the body of the element.
+ *
+ * @param body of the element
+ */
+ void end(String body);
+}
diff --git a/sax/java/android/sax/RootElement.java b/sax/java/android/sax/RootElement.java
new file mode 100644
index 0000000..4fd6f6b
--- /dev/null
+++ b/sax/java/android/sax/RootElement.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.sax;
+
+import org.xml.sax.helpers.DefaultHandler;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.Locator;
+
+/**
+ * The root XML element. The entry point for this API. Not safe for concurrent
+ * use.
+ *
+ * <p>For example, passing this XML:
+ *
+ * <pre>
+ * &lt;feed xmlns='http://www.w3.org/2005/Atom'>
+ * &lt;entry>
+ * &lt;id>bob&lt;/id>
+ * &lt;/entry>
+ * &lt;/feed>
+ * </pre>
+ *
+ * to this code:
+ *
+ * <pre>
+ * static final String ATOM_NAMESPACE = "http://www.w3.org/2005/Atom";
+ *
+ * ...
+ *
+ * RootElement root = new RootElement(ATOM_NAMESPACE, "feed");
+ * Element entry = root.getChild(ATOM_NAMESPACE, "entry");
+ * entry.getChild(ATOM_NAMESPACE, "id").setEndTextElementListener(
+ * new EndTextElementListener() {
+ * public void end(String body) {
+ * System.out.println("Entry ID: " + body);
+ * }
+ * });
+ *
+ * XMLReader reader = ...;
+ * reader.setContentHandler(root.getContentHandler());
+ * reader.parse(...);
+ * </pre>
+ *
+ * would output:
+ *
+ * <pre>
+ * Entry ID: bob
+ * </pre>
+ */
+public class RootElement extends Element {
+
+ final Handler handler = new Handler();
+
+ /**
+ * Constructs a new root element with the given name.
+ *
+ * @param uri the namespace
+ * @param localName the local name
+ */
+ public RootElement(String uri, String localName) {
+ super(null, uri, localName, 0);
+ }
+
+ /**
+ * Constructs a new root element with the given name. Uses an empty string
+ * as the namespace.
+ *
+ * @param localName the local name
+ */
+ public RootElement(String localName) {
+ this("", localName);
+ }
+
+ /**
+ * Gets the SAX {@code ContentHandler}. Pass this to your SAX parser.
+ */
+ public ContentHandler getContentHandler() {
+ return this.handler;
+ }
+
+ class Handler extends DefaultHandler {
+
+ Locator locator;
+ int depth = -1;
+ Element current = null;
+ StringBuilder bodyBuilder = null;
+
+ @Override
+ public void setDocumentLocator(Locator locator) {
+ this.locator = locator;
+ }
+
+ @Override
+ public void startElement(String uri, String localName, String qName,
+ Attributes attributes) throws SAXException {
+ int depth = ++this.depth;
+
+ if (depth == 0) {
+ // This is the root element.
+ startRoot(uri, localName, attributes);
+ return;
+ }
+
+ // Prohibit mixed text and elements.
+ if (bodyBuilder != null) {
+ throw new BadXmlException("Encountered mixed content"
+ + " within text element named " + current + ".",
+ locator);
+ }
+
+ // If we're one level below the current element.
+ if (depth == current.depth + 1) {
+ // Look for a child to push onto the stack.
+ Children children = current.children;
+ if (children != null) {
+ Element child = children.get(uri, localName);
+ if (child != null) {
+ start(child, attributes);
+ }
+ }
+ }
+ }
+
+ void startRoot(String uri, String localName, Attributes attributes)
+ throws SAXException {
+ Element root = RootElement.this;
+ if (root.uri.compareTo(uri) != 0
+ || root.localName.compareTo(localName) != 0) {
+ throw new BadXmlException("Root element name does"
+ + " not match. Expected: " + root + ", Got: "
+ + Element.toString(uri, localName), locator);
+ }
+
+ start(root, attributes);
+ }
+
+ void start(Element e, Attributes attributes) {
+ // Push element onto the stack.
+ this.current = e;
+
+ if (e.startElementListener != null) {
+ e.startElementListener.start(attributes);
+ }
+
+ if (e.endTextElementListener != null) {
+ this.bodyBuilder = new StringBuilder();
+ }
+
+ e.resetRequiredChildren();
+ e.visited = true;
+ }
+
+ @Override
+ public void characters(char[] buffer, int start, int length)
+ throws SAXException {
+ if (bodyBuilder != null) {
+ bodyBuilder.append(buffer, start, length);
+ }
+ }
+
+ @Override
+ public void endElement(String uri, String localName, String qName)
+ throws SAXException {
+ Element current = this.current;
+
+ // If we've ended the current element...
+ if (depth == current.depth) {
+ current.checkRequiredChildren(locator);
+
+ // Invoke end element listener.
+ if (current.endElementListener != null) {
+ current.endElementListener.end();
+ }
+
+ // Invoke end text element listener.
+ if (bodyBuilder != null) {
+ String body = bodyBuilder.toString();
+ bodyBuilder = null;
+
+ // We can assume that this listener is present.
+ current.endTextElementListener.end(body);
+ }
+
+ // Pop element off the stack.
+ this.current = current.parent;
+ }
+
+ depth--;
+ }
+ }
+}
diff --git a/sax/java/android/sax/StartElementListener.java b/sax/java/android/sax/StartElementListener.java
new file mode 100644
index 0000000..d935fed
--- /dev/null
+++ b/sax/java/android/sax/StartElementListener.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.sax;
+
+import org.xml.sax.Attributes;
+
+/**
+ * Listens for the beginning of elements.
+ */
+public interface StartElementListener {
+
+ /**
+ * Invoked at the beginning of an element.
+ *
+ * @param attributes from the element
+ */
+ void start(Attributes attributes);
+}
diff --git a/sax/java/android/sax/TextElementListener.java b/sax/java/android/sax/TextElementListener.java
new file mode 100644
index 0000000..6f3c31d
--- /dev/null
+++ b/sax/java/android/sax/TextElementListener.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.sax;
+
+/**
+ * Listens for the beginning and ending of text elements.
+ */
+public interface TextElementListener extends StartElementListener,
+ EndTextElementListener {}
diff --git a/sax/java/android/sax/package.html b/sax/java/android/sax/package.html
new file mode 100644
index 0000000..93747e9
--- /dev/null
+++ b/sax/java/android/sax/package.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+A framework that makes it easy to write efficient and robust SAX handlers.
+</body>
+</html>