|  | /* | 
|  | * 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--; | 
|  | } | 
|  | } | 
|  | } |