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