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