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  * &lt;feed xmlns='http://www.w3.org/2005/Atom'>
33  *   &lt;entry>
34  *     &lt;id>bob&lt;/id>
35  *   &lt;/entry>
36  * &lt;/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