From 9066cfe9886ac131c34d59ed0e2d287b0e3c0087 Mon Sep 17 00:00:00 2001 From: The Android Open Source Project Date: Tue, 3 Mar 2009 19:31:44 -0800 Subject: auto import from //depot/cupcake/@135843 --- sax/java/android/sax/BadXmlException.java | 34 ++++ sax/java/android/sax/Children.java | 97 +++++++++++ sax/java/android/sax/Element.java | 204 ++++++++++++++++++++++ sax/java/android/sax/ElementListener.java | 23 +++ sax/java/android/sax/EndElementListener.java | 28 +++ sax/java/android/sax/EndTextElementListener.java | 30 ++++ sax/java/android/sax/RootElement.java | 207 +++++++++++++++++++++++ sax/java/android/sax/StartElementListener.java | 32 ++++ sax/java/android/sax/TextElementListener.java | 23 +++ sax/java/android/sax/package.html | 5 + 10 files changed, 683 insertions(+) create mode 100644 sax/java/android/sax/BadXmlException.java create mode 100644 sax/java/android/sax/Children.java create mode 100644 sax/java/android/sax/Element.java create mode 100644 sax/java/android/sax/ElementListener.java create mode 100644 sax/java/android/sax/EndElementListener.java create mode 100644 sax/java/android/sax/EndTextElementListener.java create mode 100644 sax/java/android/sax/RootElement.java create mode 100644 sax/java/android/sax/StartElementListener.java create mode 100644 sax/java/android/sax/TextElementListener.java create mode 100644 sax/java/android/sax/package.html (limited to 'sax') 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 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(); + 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 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 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. + * + *

For example, passing this XML: + * + *

+ * <feed xmlns='http://www.w3.org/2005/Atom'>
+ *   <entry>
+ *     <id>bob</id>
+ *   </entry>
+ * </feed>
+ * 
+ * + * to this code: + * + *
+ * 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(...);
+ * 
+ * + * would output: + * + *
+ * Entry ID: bob
+ * 
+ */ +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 @@ + + +A framework that makes it easy to write efficient and robust SAX handlers. + + -- cgit v1.1