1 /* 2 * Copyright (C) 2007 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.sax; 18 19 import org.xml.sax.helpers.DefaultHandler; 20 import org.xml.sax.Attributes; 21 import org.xml.sax.SAXException; 22 import org.xml.sax.ContentHandler; 23 import org.xml.sax.Locator; 24 25 /** 26 * The root XML element. The entry point for this API. Not safe for concurrent 27 * use. 28 * 29 * <p>For example, passing this XML: 30 * 31 * <pre> 32 * <feed xmlns='http://www.w3.org/2005/Atom'> 33 * <entry> 34 * <id>bob</id> 35 * </entry> 36 * </feed> 37 * </pre> 38 * 39 * to this code: 40 * 41 * <pre> 42 * static final String ATOM_NAMESPACE = "http://www.w3.org/2005/Atom"; 43 * 44 * ... 45 * 46 * RootElement root = new RootElement(ATOM_NAMESPACE, "feed"); 47 * Element entry = root.getChild(ATOM_NAMESPACE, "entry"); 48 * entry.getChild(ATOM_NAMESPACE, "id").setEndTextElementListener( 49 * new EndTextElementListener() { 50 * public void end(String body) { 51 * System.out.println("Entry ID: " + body); 52 * } 53 * }); 54 * 55 * XMLReader reader = ...; 56 * reader.setContentHandler(root.getContentHandler()); 57 * reader.parse(...); 58 * </pre> 59 * 60 * would output: 61 * 62 * <pre> 63 * Entry ID: bob 64 * </pre> 65 */ 66 public class RootElement extends Element { 67 68 final Handler handler = new Handler(); 69 70 /** 71 * Constructs a new root element with the given name. 72 * 73 * @param uri the namespace 74 * @param localName the local name 75 */ RootElement(String uri, String localName)76 public RootElement(String uri, String localName) { 77 super(null, uri, localName, 0); 78 } 79 80 /** 81 * Constructs a new root element with the given name. Uses an empty string 82 * as the namespace. 83 * 84 * @param localName the local name 85 */ RootElement(String localName)86 public RootElement(String localName) { 87 this("", localName); 88 } 89 90 /** 91 * Gets the SAX {@code ContentHandler}. Pass this to your SAX parser. 92 */ getContentHandler()93 public ContentHandler getContentHandler() { 94 return this.handler; 95 } 96 97 class Handler extends DefaultHandler { 98 99 Locator locator; 100 int depth = -1; 101 Element current = null; 102 StringBuilder bodyBuilder = null; 103 104 @Override setDocumentLocator(Locator locator)105 public void setDocumentLocator(Locator locator) { 106 this.locator = locator; 107 } 108 109 @Override startElement(String uri, String localName, String qName, Attributes attributes)110 public void startElement(String uri, String localName, String qName, 111 Attributes attributes) throws SAXException { 112 int depth = ++this.depth; 113 114 if (depth == 0) { 115 // This is the root element. 116 startRoot(uri, localName, attributes); 117 return; 118 } 119 120 // Prohibit mixed text and elements. 121 if (bodyBuilder != null) { 122 throw new BadXmlException("Encountered mixed content" 123 + " within text element named " + current + ".", 124 locator); 125 } 126 127 // If we're one level below the current element. 128 if (depth == current.depth + 1) { 129 // Look for a child to push onto the stack. 130 Children children = current.children; 131 if (children != null) { 132 Element child = children.get(uri, localName); 133 if (child != null) { 134 start(child, attributes); 135 } 136 } 137 } 138 } 139 startRoot(String uri, String localName, Attributes attributes)140 void startRoot(String uri, String localName, Attributes attributes) 141 throws SAXException { 142 Element root = RootElement.this; 143 if (root.uri.compareTo(uri) != 0 144 || root.localName.compareTo(localName) != 0) { 145 throw new BadXmlException("Root element name does" 146 + " not match. Expected: " + root + ", Got: " 147 + Element.toString(uri, localName), locator); 148 } 149 150 start(root, attributes); 151 } 152 start(Element e, Attributes attributes)153 void start(Element e, Attributes attributes) { 154 // Push element onto the stack. 155 this.current = e; 156 157 if (e.startElementListener != null) { 158 e.startElementListener.start(attributes); 159 } 160 161 if (e.endTextElementListener != null) { 162 this.bodyBuilder = new StringBuilder(); 163 } 164 165 e.resetRequiredChildren(); 166 e.visited = true; 167 } 168 169 @Override characters(char[] buffer, int start, int length)170 public void characters(char[] buffer, int start, int length) 171 throws SAXException { 172 if (bodyBuilder != null) { 173 bodyBuilder.append(buffer, start, length); 174 } 175 } 176 177 @Override endElement(String uri, String localName, String qName)178 public void endElement(String uri, String localName, String qName) 179 throws SAXException { 180 Element current = this.current; 181 182 // If we've ended the current element... 183 if (depth == current.depth) { 184 current.checkRequiredChildren(locator); 185 186 // Invoke end element listener. 187 if (current.endElementListener != null) { 188 current.endElementListener.end(); 189 } 190 191 // Invoke end text element listener. 192 if (bodyBuilder != null) { 193 String body = bodyBuilder.toString(); 194 bodyBuilder = null; 195 196 // We can assume that this listener is present. 197 current.endTextElementListener.end(body); 198 } 199 200 // Pop element off the stack. 201 this.current = current.parent; 202 } 203 204 depth--; 205 } 206 } 207 } 208