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.Locator;
20 import org.xml.sax.SAXParseException;
21 
22 import java.util.ArrayList;
23 
24 /**
25  * An XML element. Provides access to child elements and hooks to listen
26  * for events related to this element.
27  *
28  * @see RootElement
29  */
30 public class Element {
31 
32     final String uri;
33     final String localName;
34     final int depth;
35     final Element parent;
36 
37     Children children;
38     ArrayList<Element> requiredChilden;
39 
40     boolean visited;
41 
42     StartElementListener startElementListener;
43     EndElementListener endElementListener;
44     EndTextElementListener endTextElementListener;
45 
Element(Element parent, String uri, String localName, int depth)46     Element(Element parent, String uri, String localName, int depth) {
47         this.parent = parent;
48         this.uri = uri;
49         this.localName = localName;
50         this.depth = depth;
51     }
52 
53     /**
54      * Gets the child element with the given name. Uses an empty string as the
55      * namespace.
56      */
getChild(String localName)57     public Element getChild(String localName) {
58         return getChild("", localName);
59     }
60 
61     /**
62      * Gets the child element with the given name.
63      */
getChild(String uri, String localName)64     public Element getChild(String uri, String localName) {
65         if (endTextElementListener != null) {
66             throw new IllegalStateException("This element already has an end"
67                     + " text element listener. It cannot have children.");
68         }
69 
70         if (children == null) {
71             children = new Children();
72         }
73 
74         return children.getOrCreate(this, uri, localName);
75     }
76 
77     /**
78      * Gets the child element with the given name. Uses an empty string as the
79      * namespace. We will throw a {@link org.xml.sax.SAXException} at parsing
80      * time if the specified child is missing. This helps you ensure that your
81      * listeners are called.
82      */
requireChild(String localName)83     public Element requireChild(String localName) {
84         return requireChild("", localName);
85     }
86 
87     /**
88      * Gets the child element with the given name. We will throw a
89      * {@link org.xml.sax.SAXException} at parsing time if the specified child
90      * is missing. This helps you ensure that your listeners are called.
91      */
requireChild(String uri, String localName)92     public Element requireChild(String uri, String localName) {
93         Element child = getChild(uri, localName);
94 
95         if (requiredChilden == null) {
96             requiredChilden = new ArrayList<Element>();
97             requiredChilden.add(child);
98         } else {
99             if (!requiredChilden.contains(child)) {
100                 requiredChilden.add(child);
101             }
102         }
103 
104         return child;
105     }
106 
107     /**
108      * Sets start and end element listeners at the same time.
109      */
setElementListener(ElementListener elementListener)110     public void setElementListener(ElementListener elementListener) {
111         setStartElementListener(elementListener);
112         setEndElementListener(elementListener);
113     }
114 
115     /**
116      * Sets start and end text element listeners at the same time.
117      */
setTextElementListener(TextElementListener elementListener)118     public void setTextElementListener(TextElementListener elementListener) {
119         setStartElementListener(elementListener);
120         setEndTextElementListener(elementListener);
121     }
122 
123     /**
124      * Sets a listener for the start of this element.
125      */
setStartElementListener( StartElementListener startElementListener)126     public void setStartElementListener(
127             StartElementListener startElementListener) {
128         if (this.startElementListener != null) {
129             throw new IllegalStateException(
130                     "Start element listener has already been set.");
131         }
132         this.startElementListener = startElementListener;
133     }
134 
135     /**
136      * Sets a listener for the end of this element.
137      */
setEndElementListener(EndElementListener endElementListener)138     public void setEndElementListener(EndElementListener endElementListener) {
139         if (this.endElementListener != null) {
140             throw new IllegalStateException(
141                     "End element listener has already been set.");
142         }
143         this.endElementListener = endElementListener;
144     }
145 
146     /**
147      * Sets a listener for the end of this text element.
148      */
setEndTextElementListener( EndTextElementListener endTextElementListener)149     public void setEndTextElementListener(
150             EndTextElementListener endTextElementListener) {
151         if (this.endTextElementListener != null) {
152             throw new IllegalStateException(
153                     "End text element listener has already been set.");
154         }
155 
156         if (children != null) {
157             throw new IllegalStateException("This element already has children."
158                     + " It cannot have an end text element listener.");
159         }
160 
161         this.endTextElementListener = endTextElementListener;
162     }
163 
164     @Override
toString()165     public String toString() {
166         return toString(uri, localName);
167     }
168 
toString(String uri, String localName)169     static String toString(String uri, String localName) {
170         return "'" + (uri.equals("") ? localName : uri + ":" + localName) + "'";
171     }
172 
173     /**
174      * Clears flags on required children.
175      */
resetRequiredChildren()176     void resetRequiredChildren() {
177         ArrayList<Element> requiredChildren = this.requiredChilden;
178         if (requiredChildren != null) {
179             for (int i = requiredChildren.size() - 1; i >= 0; i--) {
180                 requiredChildren.get(i).visited = false;
181             }
182         }
183     }
184 
185     /**
186      * Throws an exception if a required child was not present.
187      */
checkRequiredChildren(Locator locator)188     void checkRequiredChildren(Locator locator) throws SAXParseException {
189         ArrayList<Element> requiredChildren = this.requiredChilden;
190         if (requiredChildren != null) {
191             for (int i = requiredChildren.size() - 1; i >= 0; i--) {
192                 Element child = requiredChildren.get(i);
193                 if (!child.visited) {
194                     throw new BadXmlException(
195                             "Element named " + this + " is missing required"
196                                     + " child element named "
197                                     + child + ".", locator);
198                 }
199             }
200         }
201     }
202 }
203