| /* |
| * 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> |
| * <feed xmlns='http://www.w3.org/2005/Atom'> |
| * <entry> |
| * <id>bob</id> |
| * </entry> |
| * </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--; |
| } |
| } |
| } |