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
17package android.sax;
18
19import org.xml.sax.helpers.DefaultHandler;
20import org.xml.sax.Attributes;
21import org.xml.sax.SAXException;
22import org.xml.sax.ContentHandler;
23import 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 */
66public 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     */
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     */
86    public RootElement(String localName) {
87        this("", localName);
88    }
89
90    /**
91     * Gets the SAX {@code ContentHandler}. Pass this to your SAX parser.
92     */
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
105        public void setDocumentLocator(Locator locator) {
106            this.locator = locator;
107        }
108
109        @Override
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
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
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
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
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