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 org.apache.harmony.xml.dom;
18 
19 import java.util.ArrayList;
20 import java.util.List;
21 import libcore.util.Objects;
22 import org.w3c.dom.Attr;
23 import org.w3c.dom.DOMException;
24 import org.w3c.dom.Element;
25 import org.w3c.dom.NamedNodeMap;
26 import org.w3c.dom.Node;
27 import org.w3c.dom.NodeList;
28 import org.w3c.dom.TypeInfo;
29 
30 /**
31  * Provides a straightforward implementation of the corresponding W3C DOM
32  * interface. The class is used internally only, thus only notable members that
33  * are not in the original interface are documented (the W3C docs are quite
34  * extensive). Hope that's ok.
35  * <p>
36  * Some of the fields may have package visibility, so other classes belonging to
37  * the DOM implementation can easily access them while maintaining the DOM tree
38  * structure.
39  */
40 public class ElementImpl extends InnerNodeImpl implements Element {
41 
42     boolean namespaceAware;
43     String namespaceURI;
44     String prefix;
45     String localName;
46 
47     private List<AttrImpl> attributes = new ArrayList<AttrImpl>();
48 
ElementImpl(DocumentImpl document, String namespaceURI, String qualifiedName)49     ElementImpl(DocumentImpl document, String namespaceURI, String qualifiedName) {
50         super(document);
51         setNameNS(this, namespaceURI, qualifiedName);
52     }
53 
ElementImpl(DocumentImpl document, String name)54     ElementImpl(DocumentImpl document, String name) {
55         super(document);
56         setName(this, name);
57     }
58 
indexOfAttribute(String name)59     private int indexOfAttribute(String name) {
60         for (int i = 0; i < attributes.size(); i++) {
61             AttrImpl attr = attributes.get(i);
62             if (Objects.equal(name, attr.getNodeName())) {
63                 return i;
64             }
65         }
66 
67         return -1;
68     }
69 
indexOfAttributeNS(String namespaceURI, String localName)70     private int indexOfAttributeNS(String namespaceURI, String localName) {
71         for (int i = 0; i < attributes.size(); i++) {
72             AttrImpl attr = attributes.get(i);
73             if (Objects.equal(namespaceURI, attr.getNamespaceURI())
74                     && Objects.equal(localName, attr.getLocalName())) {
75                 return i;
76             }
77         }
78 
79         return -1;
80     }
81 
getAttribute(String name)82     public String getAttribute(String name) {
83         Attr attr = getAttributeNode(name);
84 
85         if (attr == null) {
86             return "";
87         }
88 
89         return attr.getValue();
90     }
91 
getAttributeNS(String namespaceURI, String localName)92     public String getAttributeNS(String namespaceURI, String localName) {
93         Attr attr = getAttributeNodeNS(namespaceURI, localName);
94 
95         if (attr == null) {
96             return "";
97         }
98 
99         return attr.getValue();
100     }
101 
getAttributeNode(String name)102     public AttrImpl getAttributeNode(String name) {
103         int i = indexOfAttribute(name);
104 
105         if (i == -1) {
106             return null;
107         }
108 
109         return attributes.get(i);
110     }
111 
getAttributeNodeNS(String namespaceURI, String localName)112     public AttrImpl getAttributeNodeNS(String namespaceURI, String localName) {
113         int i = indexOfAttributeNS(namespaceURI, localName);
114 
115         if (i == -1) {
116             return null;
117         }
118 
119         return attributes.get(i);
120     }
121 
122     @Override
getAttributes()123     public NamedNodeMap getAttributes() {
124         return new ElementAttrNamedNodeMapImpl();
125     }
126 
127     /**
128      * This implementation walks the entire document looking for an element
129      * with the given ID attribute. We should consider adding an index to speed
130      * navigation of large documents.
131      */
getElementById(String name)132     Element getElementById(String name) {
133         for (Attr attr : attributes) {
134             if (attr.isId() && name.equals(attr.getValue())) {
135                 return this;
136             }
137         }
138 
139         /*
140          * TODO: Remove this behavior.
141          * The spec explicitly says that this is a bad idea. From
142          * Document.getElementById(): "Attributes with the name "ID"
143          * or "id" are not of type ID unless so defined.
144          */
145         if (name.equals(getAttribute("id"))) {
146             return this;
147         }
148 
149         for (NodeImpl node : children) {
150             if (node.getNodeType() == Node.ELEMENT_NODE) {
151                 Element element = ((ElementImpl) node).getElementById(name);
152                 if (element != null) {
153                     return element;
154                 }
155             }
156         }
157 
158         return null;
159     }
160 
getElementsByTagName(String name)161     public NodeList getElementsByTagName(String name) {
162         NodeListImpl result = new NodeListImpl();
163         getElementsByTagName(result, name);
164         return result;
165     }
166 
getElementsByTagNameNS(String namespaceURI, String localName)167     public NodeList getElementsByTagNameNS(String namespaceURI, String localName) {
168         NodeListImpl result = new NodeListImpl();
169         getElementsByTagNameNS(result, namespaceURI, localName);
170         return result;
171     }
172 
173     @Override
getLocalName()174     public String getLocalName() {
175         return namespaceAware ? localName : null;
176     }
177 
178     @Override
getNamespaceURI()179     public String getNamespaceURI() {
180         return namespaceURI;
181     }
182 
183     @Override
getNodeName()184     public String getNodeName() {
185         return getTagName();
186     }
187 
getNodeType()188     public short getNodeType() {
189         return Node.ELEMENT_NODE;
190     }
191 
192     @Override
getPrefix()193     public String getPrefix() {
194         return prefix;
195     }
196 
getTagName()197     public String getTagName() {
198         return prefix != null
199                 ? prefix + ":" + localName
200                 : localName;
201     }
202 
hasAttribute(String name)203     public boolean hasAttribute(String name) {
204         return indexOfAttribute(name) != -1;
205     }
206 
hasAttributeNS(String namespaceURI, String localName)207     public boolean hasAttributeNS(String namespaceURI, String localName) {
208         return indexOfAttributeNS(namespaceURI, localName) != -1;
209     }
210 
211     @Override
hasAttributes()212     public boolean hasAttributes() {
213         return !attributes.isEmpty();
214     }
215 
removeAttribute(String name)216     public void removeAttribute(String name) throws DOMException {
217         int i = indexOfAttribute(name);
218 
219         if (i != -1) {
220             attributes.remove(i);
221         }
222     }
223 
removeAttributeNS(String namespaceURI, String localName)224     public void removeAttributeNS(String namespaceURI, String localName)
225             throws DOMException {
226         int i = indexOfAttributeNS(namespaceURI, localName);
227 
228         if (i != -1) {
229             attributes.remove(i);
230         }
231     }
232 
removeAttributeNode(Attr oldAttr)233     public Attr removeAttributeNode(Attr oldAttr) throws DOMException {
234         AttrImpl oldAttrImpl = (AttrImpl) oldAttr;
235 
236         if (oldAttrImpl.getOwnerElement() != this) {
237             throw new DOMException(DOMException.NOT_FOUND_ERR, null);
238         }
239 
240         attributes.remove(oldAttrImpl);
241         oldAttrImpl.ownerElement = null;
242 
243         return oldAttrImpl;
244     }
245 
setAttribute(String name, String value)246     public void setAttribute(String name, String value) throws DOMException {
247         Attr attr = getAttributeNode(name);
248 
249         if (attr == null) {
250             attr = document.createAttribute(name);
251             setAttributeNode(attr);
252         }
253 
254         attr.setValue(value);
255     }
256 
setAttributeNS(String namespaceURI, String qualifiedName, String value)257     public void setAttributeNS(String namespaceURI, String qualifiedName,
258             String value) throws DOMException {
259         Attr attr = getAttributeNodeNS(namespaceURI, qualifiedName);
260 
261         if (attr == null) {
262             attr = document.createAttributeNS(namespaceURI, qualifiedName);
263             setAttributeNodeNS(attr);
264         }
265 
266         attr.setValue(value);
267     }
268 
setAttributeNode(Attr newAttr)269     public Attr setAttributeNode(Attr newAttr) throws DOMException {
270         AttrImpl newAttrImpl = (AttrImpl) newAttr;
271 
272         if (newAttrImpl.document != this.document) {
273             throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, null);
274         }
275 
276         if (newAttrImpl.getOwnerElement() != null) {
277             throw new DOMException(DOMException.INUSE_ATTRIBUTE_ERR, null);
278         }
279 
280         AttrImpl oldAttrImpl = null;
281 
282         int i = indexOfAttribute(newAttr.getName());
283         if (i != -1) {
284             oldAttrImpl = attributes.get(i);
285             attributes.remove(i);
286         }
287 
288         attributes.add(newAttrImpl);
289         newAttrImpl.ownerElement = this;
290 
291         return oldAttrImpl;
292     }
293 
setAttributeNodeNS(Attr newAttr)294     public Attr setAttributeNodeNS(Attr newAttr) throws DOMException {
295         AttrImpl newAttrImpl = (AttrImpl) newAttr;
296 
297         if (newAttrImpl.document != this.document) {
298             throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, null);
299         }
300 
301         if (newAttrImpl.getOwnerElement() != null) {
302             throw new DOMException(DOMException.INUSE_ATTRIBUTE_ERR, null);
303         }
304 
305         AttrImpl oldAttrImpl = null;
306 
307         int i = indexOfAttributeNS(newAttr.getNamespaceURI(), newAttr.getLocalName());
308         if (i != -1) {
309             oldAttrImpl = attributes.get(i);
310             attributes.remove(i);
311         }
312 
313         attributes.add(newAttrImpl);
314         newAttrImpl.ownerElement = this;
315 
316         return oldAttrImpl;
317     }
318 
319     @Override
setPrefix(String prefix)320     public void setPrefix(String prefix) {
321         this.prefix = validatePrefix(prefix, namespaceAware, namespaceURI);
322     }
323 
324     public class ElementAttrNamedNodeMapImpl implements NamedNodeMap {
325 
getLength()326         public int getLength() {
327             return ElementImpl.this.attributes.size();
328         }
329 
indexOfItem(String name)330         private int indexOfItem(String name) {
331             return ElementImpl.this.indexOfAttribute(name);
332         }
333 
indexOfItemNS(String namespaceURI, String localName)334         private int indexOfItemNS(String namespaceURI, String localName) {
335             return ElementImpl.this.indexOfAttributeNS(namespaceURI, localName);
336         }
337 
getNamedItem(String name)338         public Node getNamedItem(String name) {
339             return ElementImpl.this.getAttributeNode(name);
340         }
341 
getNamedItemNS(String namespaceURI, String localName)342         public Node getNamedItemNS(String namespaceURI, String localName) {
343             return ElementImpl.this.getAttributeNodeNS(namespaceURI, localName);
344         }
345 
item(int index)346         public Node item(int index) {
347             return ElementImpl.this.attributes.get(index);
348         }
349 
removeNamedItem(String name)350         public Node removeNamedItem(String name) throws DOMException {
351             int i = indexOfItem(name);
352 
353             if (i == -1) {
354                 throw new DOMException(DOMException.NOT_FOUND_ERR, null);
355             }
356 
357             return ElementImpl.this.attributes.remove(i);
358         }
359 
removeNamedItemNS(String namespaceURI, String localName)360         public Node removeNamedItemNS(String namespaceURI, String localName)
361                 throws DOMException {
362             int i = indexOfItemNS(namespaceURI, localName);
363 
364             if (i == -1) {
365                 throw new DOMException(DOMException.NOT_FOUND_ERR, null);
366             }
367 
368             return ElementImpl.this.attributes.remove(i);
369         }
370 
setNamedItem(Node arg)371         public Node setNamedItem(Node arg) throws DOMException {
372             if (!(arg instanceof Attr)) {
373                 throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR, null);
374             }
375 
376             return ElementImpl.this.setAttributeNode((Attr)arg);
377         }
378 
setNamedItemNS(Node arg)379         public Node setNamedItemNS(Node arg) throws DOMException {
380             if (!(arg instanceof Attr)) {
381                 throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR, null);
382             }
383 
384             return ElementImpl.this.setAttributeNodeNS((Attr)arg);
385         }
386     }
387 
getSchemaTypeInfo()388     public TypeInfo getSchemaTypeInfo() {
389         // TODO: populate this when we support XML Schema
390         return NULL_TYPE_INFO;
391     }
392 
setIdAttribute(String name, boolean isId)393     public void setIdAttribute(String name, boolean isId) throws DOMException {
394         AttrImpl attr = getAttributeNode(name);
395         if (attr == null) {
396             throw new DOMException(DOMException.NOT_FOUND_ERR,
397                     "No such attribute: " + name);
398         }
399         attr.isId = isId;
400     }
401 
setIdAttributeNS(String namespaceURI, String localName, boolean isId)402     public void setIdAttributeNS(String namespaceURI, String localName,
403             boolean isId) throws DOMException {
404         AttrImpl attr = getAttributeNodeNS(namespaceURI, localName);
405         if (attr == null) {
406             throw new DOMException(DOMException.NOT_FOUND_ERR,
407                     "No such attribute: " + namespaceURI +  " " + localName);
408         }
409         attr.isId = isId;
410     }
411 
setIdAttributeNode(Attr idAttr, boolean isId)412     public void setIdAttributeNode(Attr idAttr, boolean isId) throws DOMException {
413         ((AttrImpl) idAttr).isId = isId;
414     }
415 }
416