1 /*
   2  * reserved comment block
   3  * DO NOT REMOVE OR ALTER!
   4  */
   5 /*
   6  * Copyright 1999-2002,2004 The Apache Software Foundation.
   7  *
   8  * Licensed under the Apache License, Version 2.0 (the "License");
   9  * you may not use this file except in compliance with the License.
  10  * You may obtain a copy of the License at
  11  *
  12  *      http://www.apache.org/licenses/LICENSE-2.0
  13  *
  14  * Unless required by applicable law or agreed to in writing, software
  15  * distributed under the License is distributed on an "AS IS" BASIS,
  16  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  17  * See the License for the specific language governing permissions and
  18  * limitations under the License.
  19  */
  20 
  21 package com.sun.org.apache.xerces.internal.dom;
  22 
  23 import org.w3c.dom.Attr;
  24 import org.w3c.dom.DOMException;
  25 import org.w3c.dom.Element;
  26 import org.w3c.dom.NamedNodeMap;
  27 import org.w3c.dom.Node;
  28 import org.w3c.dom.NodeList;
  29 import org.w3c.dom.Text;
  30 
  31 import org.w3c.dom.TypeInfo;
  32 import com.sun.org.apache.xerces.internal.util.URI;
  33 
  34 /**
  35  * Elements represent most of the "markup" and structure of the
  36  * document.  They contain both the data for the element itself
  37  * (element name and attributes), and any contained nodes, including
  38  * document text (as children).
  39  * <P>
  40  * Elements may have Attributes associated with them; the API for this is
  41  * defined in Node, but the function is implemented here. In general, XML
  42  * applications should retrive Attributes as Nodes, since they may contain
  43  * entity references and hence be a fairly complex sub-tree. HTML users will
  44  * be dealing with simple string values, and convenience methods are provided
  45  * to work in terms of Strings.
  46  * <P>
  47  * ElementImpl does not support Namespaces. ElementNSImpl, which inherits from
  48  * it, does.
  49  * @see ElementNSImpl
  50  *
  51  * @xerces.internal
  52  *
  53  * @author Arnaud  Le Hors, IBM
  54  * @author Joe Kesselman, IBM
  55  * @author Andy Clark, IBM
  56  * @author Ralf Pfeiffer, IBM
  57  * @since  PR-DOM-Level-1-19980818.
  58  */
  59 public class ElementImpl
  60     extends ParentNode
  61     implements Element, TypeInfo {
  62 
  63     //
  64     // Constants
  65     //
  66 
  67     /** Serialization version. */
  68     static final long serialVersionUID = 3717253516652722278L;
  69     //
  70     // Data
  71     //
  72 
  73     /** Element name. */
  74     protected String name;
  75 
  76     /** Attributes. */
  77     protected AttributeMap attributes;
  78 
  79     //
  80     // Constructors
  81     //
  82 
  83     /** Factory constructor. */
  84     public ElementImpl(CoreDocumentImpl ownerDoc, String name) {
  85         super(ownerDoc);
  86         this.name = name;
  87         needsSyncData(true);    // synchronizeData will initialize attributes
  88     }
  89 
  90     // for ElementNSImpl
  91     protected ElementImpl() {}
  92 
  93     // Support for DOM Level 3 renameNode method.
  94     // Note: This only deals with part of the pb. CoreDocumentImpl
  95     // does all the work.
  96     void rename(String name) {
  97         if (needsSyncData()) {
  98             synchronizeData();
  99         }
 100             this.name = name;
 101         reconcileDefaultAttributes();
 102     }
 103 
 104     //
 105     // Node methods
 106     //
 107 
 108 
 109     /**
 110      * A short integer indicating what type of node this is. The named
 111      * constants for this value are defined in the org.w3c.dom.Node interface.
 112      */
 113     public short getNodeType() {
 114         return Node.ELEMENT_NODE;
 115     }
 116 
 117     /**
 118      * Returns the element name
 119      */
 120     public String getNodeName() {
 121         if (needsSyncData()) {
 122             synchronizeData();
 123         }
 124         return name;
 125     }
 126 
 127     /**
 128      * Retrieve all the Attributes as a set. Note that this API is inherited
 129      * from Node rather than specified on Element; in fact only Elements will
 130      * ever have Attributes, but they want to allow folks to "blindly" operate
 131      * on the tree as a set of Nodes.
 132      */
 133     public NamedNodeMap getAttributes() {
 134 
 135         if (needsSyncData()) {
 136             synchronizeData();
 137         }
 138         if (attributes == null) {
 139             attributes = new AttributeMap(this, null);
 140         }
 141         return attributes;
 142 
 143     } // getAttributes():NamedNodeMap
 144 
 145     /**
 146      * Return a duplicate copy of this Element. Note that its children
 147      * will not be copied unless the "deep" flag is true, but Attributes
 148      * are <i>always</i> replicated.
 149      *
 150      * @see org.w3c.dom.Node#cloneNode(boolean)
 151      */
 152     public Node cloneNode(boolean deep) {
 153 
 154         ElementImpl newnode = (ElementImpl) super.cloneNode(deep);
 155         // Replicate NamedNodeMap rather than sharing it.
 156         if (attributes != null) {
 157             newnode.attributes = (AttributeMap) attributes.cloneMap(newnode);
 158         }
 159         return newnode;
 160 
 161     } // cloneNode(boolean):Node
 162 
 163    /**
 164      * DOM Level 3 WD - Experimental.
 165      * Retrieve baseURI
 166      */
 167     public String getBaseURI() {
 168 
 169         if (needsSyncData()) {
 170             synchronizeData();
 171         }
 172         // Absolute base URI is computed according to
 173         // XML Base (http://www.w3.org/TR/xmlbase/#granularity)
 174         // 1. The base URI specified by an xml:base attribute on the element,
 175         // if one exists
 176         if (attributes != null) {
 177             Attr attrNode = (Attr)attributes.getNamedItem("xml:base");
 178             if (attrNode != null) {
 179                 String uri =  attrNode.getNodeValue();
 180                 if (uri.length() != 0 ) {// attribute value is always empty string
 181                     try {
 182                        uri = new URI(uri).toString();
 183                     }
 184                     catch (com.sun.org.apache.xerces.internal.util.URI.MalformedURIException e) {
 185                         // This may be a relative URI.
 186 
 187                         // Make any parentURI into a URI object to use with the URI(URI, String) constructor
 188                         String parentBaseURI = (this.ownerNode != null) ? this.ownerNode.getBaseURI() : null;
 189                         if (parentBaseURI != null){
 190                             try{
 191                                 uri = new URI(new URI(parentBaseURI), uri).toString();
 192                             }
 193                             catch (com.sun.org.apache.xerces.internal.util.URI.MalformedURIException ex){
 194                                 // This should never happen: parent should have checked the URI and returned null if invalid.
 195                                 return null;
 196                             }
 197                             return uri;
 198                         }
 199                         return null;
 200                     }
 201                     return uri;
 202                 }
 203             }
 204         }
 205 
 206         // 2.the base URI of the element's parent element within the
 207         // document or external entity, if one exists
 208                 // 3. the base URI of the document entity or external entity
 209                 // containing the element
 210 
 211                 // ownerNode serves as a parent or as document
 212                 String baseURI = (this.ownerNode != null) ? this.ownerNode.getBaseURI() : null ;
 213         //base URI of parent element is not null
 214         if(baseURI != null){
 215             try {
 216                 //return valid absolute base URI
 217                return new URI(baseURI).toString();
 218             }
 219             catch (com.sun.org.apache.xerces.internal.util.URI.MalformedURIException e){
 220                 return null;
 221             }
 222         }
 223         return null;
 224     } //getBaseURI
 225 
 226 
 227 
 228     /**
 229      * NON-DOM
 230      * set the ownerDocument of this node, its children, and its attributes
 231      */
 232     void setOwnerDocument(CoreDocumentImpl doc) {
 233         super.setOwnerDocument(doc);
 234         if (attributes != null) {
 235             attributes.setOwnerDocument(doc);
 236         }
 237     }
 238 
 239     //
 240     // Element methods
 241     //
 242 
 243     /**
 244      * Look up a single Attribute by name. Returns the Attribute's
 245      * string value, or an empty string (NOT null!) to indicate that the
 246      * name did not map to a currently defined attribute.
 247      * <p>
 248      * Note: Attributes may contain complex node trees. This method
 249      * returns the "flattened" string obtained from Attribute.getValue().
 250      * If you need the structure information, see getAttributeNode().
 251      */
 252     public String getAttribute(String name) {
 253 
 254         if (needsSyncData()) {
 255             synchronizeData();
 256         }
 257         if (attributes == null) {
 258             return "";
 259         }
 260         Attr attr = (Attr)(attributes.getNamedItem(name));
 261         return (attr == null) ? "" : attr.getValue();
 262 
 263     } // getAttribute(String):String
 264 
 265 
 266     /**
 267      * Look up a single Attribute by name. Returns the Attribute Node,
 268      * so its complete child tree is available. This could be important in
 269      * XML, where the string rendering may not be sufficient information.
 270      * <p>
 271      * If no matching attribute is available, returns null.
 272      */
 273     public Attr getAttributeNode(String name) {
 274 
 275         if (needsSyncData()) {
 276             synchronizeData();
 277         }
 278         if (attributes == null) {
 279             return null;
 280         }
 281         return (Attr)attributes.getNamedItem(name);
 282 
 283     } // getAttributeNode(String):Attr
 284 
 285 
 286     /**
 287      * Returns a NodeList of all descendent nodes (children,
 288      * grandchildren, and so on) which are Elements and which have the
 289      * specified tag name.
 290      * <p>
 291      * Note: NodeList is a "live" view of the DOM. Its contents will
 292      * change as the DOM changes, and alterations made to the NodeList
 293      * will be reflected in the DOM.
 294      *
 295      * @param tagname The type of element to gather. To obtain a list of
 296      * all elements no matter what their names, use the wild-card tag
 297      * name "*".
 298      *
 299      * @see DeepNodeListImpl
 300      */
 301     public NodeList getElementsByTagName(String tagname) {
 302         return new DeepNodeListImpl(this,tagname);
 303     }
 304 
 305     /**
 306      * Returns the name of the Element. Note that Element.nodeName() is
 307      * defined to also return the tag name.
 308      * <p>
 309      * This is case-preserving in XML. HTML should uppercasify it on the
 310      * way in.
 311      */
 312     public String getTagName() {
 313         if (needsSyncData()) {
 314             synchronizeData();
 315         }
 316         return name;
 317     }
 318 
 319     /**
 320      * In "normal form" (as read from a source file), there will never be two
 321      * Text children in succession. But DOM users may create successive Text
 322      * nodes in the course of manipulating the document. Normalize walks the
 323      * sub-tree and merges adjacent Texts, as if the DOM had been written out
 324      * and read back in again. This simplifies implementation of higher-level
 325      * functions that may want to assume that the document is in standard form.
 326      * <p>
 327      * To normalize a Document, normalize its top-level Element child.
 328      * <p>
 329      * As of PR-DOM-Level-1-19980818, CDATA -- despite being a subclass of
 330      * Text -- is considered "markup" and will _not_ be merged either with
 331      * normal Text or with other CDATASections.
 332      */
 333     public void normalize() {
 334         // No need to normalize if already normalized.
 335         if (isNormalized()) {
 336             return;
 337         }
 338         if (needsSyncChildren()) {
 339             synchronizeChildren();
 340         }
 341         ChildNode kid, next;
 342         for (kid = firstChild; kid != null; kid = next) {
 343             next = kid.nextSibling;
 344 
 345             // If kid is a text node, we need to check for one of two
 346             // conditions:
 347             //   1) There is an adjacent text node
 348             //   2) There is no adjacent text node, but kid is
 349             //      an empty text node.
 350             if ( kid.getNodeType() == Node.TEXT_NODE )
 351             {
 352                 // If an adjacent text node, merge it with kid
 353                 if ( next!=null && next.getNodeType() == Node.TEXT_NODE )
 354                 {
 355                     ((Text)kid).appendData(next.getNodeValue());
 356                     removeChild( next );
 357                     next = kid; // Don't advance; there might be another.
 358                 }
 359                 else
 360                 {
 361                     // If kid is empty, remove it
 362                     if ( kid.getNodeValue() == null || kid.getNodeValue().length() == 0 ) {
 363                         removeChild( kid );
 364                     }
 365                 }
 366             }
 367 
 368             // Otherwise it might be an Element, which is handled recursively
 369             else if (kid.getNodeType() == Node.ELEMENT_NODE) {
 370                 kid.normalize();
 371             }
 372         }
 373 
 374         // We must also normalize all of the attributes
 375         if ( attributes!=null )
 376         {
 377             for( int i=0; i<attributes.getLength(); ++i )
 378             {
 379                 Node attr = attributes.item(i);
 380                 attr.normalize();
 381             }
 382         }
 383 
 384         // changed() will have occurred when the removeChild() was done,
 385         // so does not have to be reissued.
 386 
 387         isNormalized(true);
 388     } // normalize()
 389 
 390     /**
 391      * Remove the named attribute from this Element. If the removed
 392      * Attribute has a default value, it is immediately replaced thereby.
 393      * <P>
 394      * The default logic is actually implemented in NamedNodeMapImpl.
 395      * PR-DOM-Level-1-19980818 doesn't fully address the DTD, so some
 396      * of this behavior is likely to change in future versions. ?????
 397      * <P>
 398      * Note that this call "succeeds" even if no attribute by this name
 399      * existed -- unlike removeAttributeNode, which will throw a not-found
 400      * exception in that case.
 401      *
 402      * @throws DOMException(NO_MODIFICATION_ALLOWED_ERR) if the node is
 403      * readonly.
 404      */
 405     public void removeAttribute(String name) {
 406 
 407         if (ownerDocument.errorChecking && isReadOnly()) {
 408             String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NO_MODIFICATION_ALLOWED_ERR", null);
 409             throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, msg);
 410         }
 411 
 412         if (needsSyncData()) {
 413             synchronizeData();
 414         }
 415 
 416         if (attributes == null) {
 417             return;
 418         }
 419 
 420         attributes.safeRemoveNamedItem(name);
 421 
 422     } // removeAttribute(String)
 423 
 424 
 425     /**
 426      * Remove the specified attribute/value pair. If the removed
 427      * Attribute has a default value, it is immediately replaced.
 428      * <p>
 429      * NOTE: Specifically removes THIS NODE -- not the node with this
 430      * name, nor the node with these contents. If the specific Attribute
 431      * object passed in is not stored in this Element, we throw a
 432      * DOMException.  If you really want to remove an attribute by name,
 433      * use removeAttribute().
 434      *
 435      * @return the Attribute object that was removed.
 436      * @throws DOMException(NOT_FOUND_ERR) if oldattr is not an attribute of
 437      * this Element.
 438      * @throws DOMException(NO_MODIFICATION_ALLOWED_ERR) if the node is
 439      * readonly.
 440      */
 441     public Attr removeAttributeNode(Attr oldAttr)
 442         throws DOMException {
 443 
 444         if (ownerDocument.errorChecking && isReadOnly()) {
 445             String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NO_MODIFICATION_ALLOWED_ERR", null);
 446             throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, msg);
 447         }
 448 
 449         if (needsSyncData()) {
 450             synchronizeData();
 451         }
 452 
 453         if (attributes == null) {
 454             String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NOT_FOUND_ERR", null);
 455             throw new DOMException(DOMException.NOT_FOUND_ERR, msg);
 456         }
 457         return (Attr) attributes.removeItem(oldAttr, true);
 458 
 459     } // removeAttributeNode(Attr):Attr
 460 
 461 
 462     /**
 463      * Add a new name/value pair, or replace the value of the existing
 464      * attribute having that name.
 465      *
 466      * Note: this method supports only the simplest kind of Attribute,
 467      * one whose value is a string contained in a single Text node.
 468      * If you want to assert a more complex value (which XML permits,
 469      * though HTML doesn't), see setAttributeNode().
 470      *
 471      * The attribute is created with specified=true, meaning it's an
 472      * explicit value rather than inherited from the DTD as a default.
 473      * Again, setAttributeNode can be used to achieve other results.
 474      *
 475      * @throws DOMException(INVALID_NAME_ERR) if the name is not acceptable.
 476      * (Attribute factory will do that test for us.)
 477      *
 478      * @throws DOMException(NO_MODIFICATION_ALLOWED_ERR) if the node is
 479      * readonly.
 480      */
 481         public void setAttribute(String name, String value) {
 482 
 483                 if (ownerDocument.errorChecking && isReadOnly()) {
 484                         String msg =
 485                                 DOMMessageFormatter.formatMessage(
 486                                         DOMMessageFormatter.DOM_DOMAIN,
 487                                         "NO_MODIFICATION_ALLOWED_ERR",
 488                                         null);
 489                         throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, msg);
 490                 }
 491 
 492                 if (needsSyncData()) {
 493                         synchronizeData();
 494                 }
 495 
 496                 Attr newAttr = getAttributeNode(name);
 497                 if (newAttr == null) {
 498                         newAttr = getOwnerDocument().createAttribute(name);
 499 
 500                         if (attributes == null) {
 501                                 attributes = new AttributeMap(this, null);
 502                         }
 503 
 504                         newAttr.setNodeValue(value);
 505                         attributes.setNamedItem(newAttr);
 506                 }
 507                 else {
 508                         newAttr.setNodeValue(value);
 509                 }
 510 
 511         } // setAttribute(String,String)
 512 
 513     /**
 514      * Add a new attribute/value pair, or replace the value of the
 515      * existing attribute with that name.
 516      * <P>
 517      * This method allows you to add an Attribute that has already been
 518      * constructed, and hence avoids the limitations of the simple
 519      * setAttribute() call. It can handle attribute values that have
 520      * arbitrarily complex tree structure -- in particular, those which
 521      * had entity references mixed into their text.
 522      *
 523      * @throws DOMException(INUSE_ATTRIBUTE_ERR) if the Attribute object
 524      * has already been assigned to another Element.
 525      */
 526     public Attr setAttributeNode(Attr newAttr)
 527         throws DOMException
 528         {
 529 
 530         if (needsSyncData()) {
 531             synchronizeData();
 532         }
 533 
 534         if (ownerDocument.errorChecking) {
 535             if (isReadOnly()) {
 536                 String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NO_MODIFICATION_ALLOWED_ERR", null);
 537                 throw new DOMException(
 538                                      DOMException.NO_MODIFICATION_ALLOWED_ERR,
 539                                      msg);
 540             }
 541 
 542             if (newAttr.getOwnerDocument() != ownerDocument) {
 543                 String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "WRONG_DOCUMENT_ERR", null);
 544                     throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, msg);
 545             }
 546         }
 547 
 548         if (attributes == null) {
 549             attributes = new AttributeMap(this, null);
 550         }
 551         // This will throw INUSE if necessary
 552         return (Attr) attributes.setNamedItem(newAttr);
 553 
 554     } // setAttributeNode(Attr):Attr
 555 
 556     //
 557     // DOM2: Namespace methods
 558     //
 559 
 560     /**
 561      * Introduced in DOM Level 2. <p>
 562      *
 563      * Retrieves an attribute value by local name and namespace URI.
 564      *
 565      * @param namespaceURI
 566      *                      The namespace URI of the attribute to
 567      *                      retrieve.
 568      * @param localName     The local name of the attribute to retrieve.
 569      * @return String       The Attr value as a string, or empty string
 570      *                      if that attribute
 571      *                      does not have a specified or default value.
 572      * @since WD-DOM-Level-2-19990923
 573      */
 574     public String getAttributeNS(String namespaceURI, String localName) {
 575 
 576         if (needsSyncData()) {
 577             synchronizeData();
 578         }
 579 
 580         if (attributes == null) {
 581             return "";
 582         }
 583 
 584         Attr attr = (Attr)(attributes.getNamedItemNS(namespaceURI, localName));
 585         return (attr == null) ? "" : attr.getValue();
 586 
 587     } // getAttributeNS(String,String):String
 588 
 589     /**
 590      * Introduced in DOM Level 2. <p>
 591      *
 592      *  Adds a new attribute.
 593      *  If the given namespaceURI is null or an empty string and the
 594      *  qualifiedName has a prefix that is "xml", the new attribute is bound to
 595      *  the predefined namespace "http://www.w3.org/XML/1998/namespace"
 596      *  [Namespaces].  If an attribute with the same local name and namespace
 597      *  URI is already present on the element, its prefix is changed to be the
 598      *  prefix part of the qualifiedName, and its value is changed to be the
 599      *  value parameter. This value is a simple string, it is not parsed as it
 600      *  is being set. So any markup (such as syntax to be recognized as an
 601      *  entity reference) is treated as literal text, and needs to be
 602      *  appropriately escaped by the implementation when it is written out. In
 603      *  order to assign an attribute value that contains entity references, the
 604      *  user must create an Attr node plus any Text and EntityReference nodes,
 605      *  build the appropriate subtree, and use setAttributeNodeNS or
 606      *  setAttributeNode to assign it as the value of an attribute.
 607      *
 608      * @param namespaceURI      The namespace URI of the attribute to create
 609      *                          or alter.
 610      * @param qualifiedName     The qualified name of the attribute to create or
 611      *                          alter.
 612      * @param value             The value to set in string form.
 613      * @throws                  INVALID_CHARACTER_ERR: Raised if the specified
 614      *                          name contains an invalid character.
 615      *
 616      * @throws                  NO_MODIFICATION_ALLOWED_ERR: Raised if this
 617      *                          node is readonly.
 618      *
 619      * @throws                  NAMESPACE_ERR: Raised if the qualifiedName
 620      *                          has a prefix that is "xml" and the namespaceURI
 621      *                          is neither null nor an empty string nor
 622      *                          "http://www.w3.org/XML/1998/namespace", or if
 623      *                          the qualifiedName has a prefix that is "xmlns"
 624      *                          but the namespaceURI is neither null nor an
 625      *                          empty string, or if if the qualifiedName has a
 626      *                          prefix different from "xml" and "xmlns" and the
 627      *                          namespaceURI is null or an empty string.
 628      * @since WD-DOM-Level-2-19990923
 629      */
 630      public void setAttributeNS(String namespaceURI,String qualifiedName,
 631                                           String value) {
 632                 if (ownerDocument.errorChecking && isReadOnly()) {
 633                         String msg =
 634                                 DOMMessageFormatter.formatMessage(
 635                                         DOMMessageFormatter.DOM_DOMAIN,
 636                                         "NO_MODIFICATION_ALLOWED_ERR",
 637                                         null);
 638                         throw new DOMException(
 639                                 DOMException.NO_MODIFICATION_ALLOWED_ERR,
 640                                 msg);
 641                 }
 642                 if (needsSyncData()) {
 643                         synchronizeData();
 644                 }
 645                 int index = qualifiedName.indexOf(':');
 646                 String prefix, localName;
 647                 if (index < 0) {
 648                         prefix = null;
 649                         localName = qualifiedName;
 650                 }
 651                 else {
 652                         prefix = qualifiedName.substring(0, index);
 653                         localName = qualifiedName.substring(index + 1);
 654                 }
 655                 Attr newAttr = getAttributeNodeNS(namespaceURI, localName);
 656                 if (newAttr == null) {
 657             // REVISIT: this is not efficient, we are creating twice the same
 658             //          strings for prefix and localName.
 659                         newAttr = getOwnerDocument().createAttributeNS(
 660                                         namespaceURI,
 661                                         qualifiedName);
 662                         if (attributes == null) {
 663                                 attributes = new AttributeMap(this, null);
 664                         }
 665                         newAttr.setNodeValue(value);
 666                         attributes.setNamedItemNS(newAttr);
 667                 }
 668                 else {
 669             if (newAttr instanceof AttrNSImpl){
 670                 String origNodeName = ((AttrNSImpl) newAttr).name;
 671                 String newName = (prefix!=null) ? (prefix+":"+localName) : localName;
 672 
 673                 ((AttrNSImpl) newAttr).name = newName;
 674 
 675                 if (!newName.equals(origNodeName)) {
 676                     // Note: we can't just change the name of the attribute. Names have to be in sorted
 677                     // order in the attributes vector because a binary search is used to locate them.
 678                     // If the new name has a different prefix, the list may become unsorted.
 679                     // Maybe it would be better to resort the list, but the simplest
 680                     // fix seems to be to remove the old attribute and re-insert it.
 681                     // -- Norman.Walsh@Sun.COM, 2 Feb 2007
 682                     newAttr = (Attr) attributes.removeItem(newAttr, false);
 683                     attributes.addItem(newAttr);
 684                 }
 685             }
 686             else {
 687                 // This case may happen if user calls:
 688                 //      elem.setAttribute("name", "value");
 689                 //      elem.setAttributeNS(null, "name", "value");
 690                 // This case is not defined by the DOM spec, we choose
 691                 // to create a new attribute in this case and remove an old one from the tree
 692                 // note this might cause events to be propagated or user data to be lost
 693                 newAttr = new AttrNSImpl((CoreDocumentImpl)getOwnerDocument(), namespaceURI, qualifiedName, localName);
 694                 attributes.setNamedItemNS(newAttr);
 695             }
 696 
 697                         newAttr.setNodeValue(value);
 698                 }
 699 
 700     } // setAttributeNS(String,String,String)
 701 
 702 
 703     /**
 704      * Introduced in DOM Level 2. <p>
 705      *
 706      * Removes an attribute by local name and namespace URI. If the removed
 707      * attribute has a default value it is immediately replaced.
 708      * The replacing attribute has the same namespace URI and local name,
 709      * as well as the original prefix.<p>
 710      *
 711      * @param namespaceURI  The namespace URI of the attribute to remove.
 712      *
 713      * @param localName     The local name of the attribute to remove.
 714      * @throws                  NO_MODIFICATION_ALLOWED_ERR: Raised if this
 715      *                          node is readonly.
 716      * @since WD-DOM-Level-2-19990923
 717      */
 718     public void removeAttributeNS(String namespaceURI, String localName) {
 719 
 720         if (ownerDocument.errorChecking && isReadOnly()) {
 721             String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NO_MODIFICATION_ALLOWED_ERR", null);
 722             throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, msg);
 723         }
 724 
 725         if (needsSyncData()) {
 726             synchronizeData();
 727         }
 728 
 729         if (attributes == null) {
 730             return;
 731         }
 732 
 733         attributes.safeRemoveNamedItemNS(namespaceURI, localName);
 734 
 735     } // removeAttributeNS(String,String)
 736 
 737     /**
 738      * Retrieves an Attr node by local name and namespace URI.
 739      *
 740      * @param namespaceURI  The namespace URI of the attribute to
 741      *                      retrieve.
 742      * @param localName     The local name of the attribute to retrieve.
 743      * @return Attr         The Attr node with the specified attribute
 744      *                      local name and namespace
 745      *                      URI or null if there is no such attribute.
 746      * @since WD-DOM-Level-2-19990923
 747      */
 748     public Attr getAttributeNodeNS(String namespaceURI, String localName){
 749 
 750         if (needsSyncData()) {
 751             synchronizeData();
 752         }
 753         if (attributes == null) {
 754             return null;
 755         }
 756         return (Attr)attributes.getNamedItemNS(namespaceURI, localName);
 757 
 758     } // getAttributeNodeNS(String,String):Attr
 759 
 760     /**
 761      * Introduced in DOM Level 2. <p>
 762      *
 763      * Adds a new attribute. If an attribute with that local name and
 764      * namespace URI is already present in the element, it is replaced
 765      * by the new one.
 766      *
 767      * @param Attr      The Attr node to add to the attribute list. When
 768      *                  the Node has no namespaceURI, this method behaves
 769      *                  like setAttributeNode.
 770      * @return Attr     If the newAttr attribute replaces an existing attribute
 771      *                  with the same local name and namespace URI, the *
 772      *                  previously existing Attr node is returned, otherwise
 773      *                  null is returned.
 774      * @throws          WRONG_DOCUMENT_ERR: Raised if newAttr
 775      *                  was created from a different document than the one that
 776      *                  created the element.
 777      *
 778      * @throws          NO_MODIFICATION_ALLOWED_ERR: Raised if
 779      *                  this node is readonly.
 780      *
 781      * @throws          INUSE_ATTRIBUTE_ERR: Raised if newAttr is
 782      *                  already an attribute of another Element object. The
 783      *                  DOM user must explicitly clone Attr nodes to re-use
 784      *                  them in other elements.
 785      * @since WD-DOM-Level-2-19990923
 786      */
 787     public Attr setAttributeNodeNS(Attr newAttr)
 788         throws DOMException
 789         {
 790 
 791         if (needsSyncData()) {
 792             synchronizeData();
 793         }
 794         if (ownerDocument.errorChecking) {
 795             if (isReadOnly()) {
 796                 String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NO_MODIFICATION_ALLOWED_ERR", null);
 797                     throw new DOMException(
 798                                      DOMException.NO_MODIFICATION_ALLOWED_ERR,
 799                                      msg);
 800             }
 801             if (newAttr.getOwnerDocument() != ownerDocument) {
 802                 String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "WRONG_DOCUMENT_ERR", null);
 803                 throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, msg);
 804             }
 805         }
 806 
 807         if (attributes == null) {
 808             attributes = new AttributeMap(this, null);
 809         }
 810         // This will throw INUSE if necessary
 811         return (Attr) attributes.setNamedItemNS(newAttr);
 812 
 813     } // setAttributeNodeNS(Attr):Attr
 814 
 815     /**
 816       * NON-DOM: sets attribute node for this element
 817       */
 818     protected int setXercesAttributeNode (Attr attr){
 819 
 820         if (needsSyncData()) {
 821             synchronizeData();
 822         }
 823 
 824         if (attributes == null) {
 825             attributes = new AttributeMap(this, null);
 826         }
 827         return attributes.addItem(attr);
 828 
 829     }
 830 
 831     /**
 832       * NON-DOM: get inded of an attribute
 833       */
 834     protected int getXercesAttribute(String namespaceURI, String localName){
 835 
 836         if (needsSyncData()) {
 837             synchronizeData();
 838         }
 839         if (attributes == null) {
 840             return -1;
 841         }
 842         return attributes.getNamedItemIndex(namespaceURI, localName);
 843 
 844     }
 845 
 846     /**
 847      * Introduced in DOM Level 2.
 848      */
 849     public boolean hasAttributes() {
 850         if (needsSyncData()) {
 851             synchronizeData();
 852         }
 853         return (attributes != null && attributes.getLength() != 0);
 854     }
 855 
 856     /**
 857      * Introduced in DOM Level 2.
 858      */
 859     public boolean hasAttribute(String name) {
 860         return getAttributeNode(name) != null;
 861     }
 862 
 863     /**
 864      * Introduced in DOM Level 2.
 865      */
 866     public boolean hasAttributeNS(String namespaceURI, String localName) {
 867         return getAttributeNodeNS(namespaceURI, localName) != null;
 868     }
 869 
 870     /**
 871      * Introduced in DOM Level 2. <p>
 872      *
 873      * Returns a NodeList of all the Elements with a given local name and
 874      * namespace URI in the order in which they would be encountered in a
 875      * preorder traversal of the Document tree, starting from this node.
 876      *
 877      * @param namespaceURI The namespace URI of the elements to match
 878      *                     on. The special value "*" matches all
 879      *                     namespaces. When it is null or an empty
 880      *                     string, this method behaves like
 881      *                     getElementsByTagName.
 882      * @param localName    The local name of the elements to match on.
 883      *                     The special value "*" matches all local names.
 884      * @return NodeList    A new NodeList object containing all the matched
 885      *                     Elements.
 886      * @since WD-DOM-Level-2-19990923
 887      */
 888     public NodeList getElementsByTagNameNS(String namespaceURI,
 889                                            String localName) {
 890         return new DeepNodeListImpl(this, namespaceURI, localName);
 891     }
 892 
 893     /**
 894      * DOM Level 3 WD- Experimental.
 895      * Override inherited behavior from NodeImpl and ParentNode to check on
 896      * attributes
 897      */
 898     public boolean isEqualNode(Node arg) {
 899         if (!super.isEqualNode(arg)) {
 900             return false;
 901         }
 902         boolean hasAttrs = hasAttributes();
 903         if (hasAttrs != ((Element) arg).hasAttributes()) {
 904             return false;
 905         }
 906         if (hasAttrs) {
 907             NamedNodeMap map1 = getAttributes();
 908             NamedNodeMap map2 = ((Element) arg).getAttributes();
 909             int len = map1.getLength();
 910             if (len != map2.getLength()) {
 911                 return false;
 912             }
 913             for (int i = 0; i < len; i++) {
 914                 Node n1 = map1.item(i);
 915                 if (n1.getLocalName() == null) { // DOM Level 1 Node
 916                     Node n2 = map2.getNamedItem(n1.getNodeName());
 917                     if (n2 == null || !((NodeImpl) n1).isEqualNode(n2)) {
 918                         return false;
 919                     }
 920                 }
 921                 else {
 922                     Node n2 = map2.getNamedItemNS(n1.getNamespaceURI(),
 923                                                   n1.getLocalName());
 924                     if (n2 == null || !((NodeImpl) n1).isEqualNode(n2)) {
 925                         return false;
 926                     }
 927                 }
 928             }
 929         }
 930         return true;
 931     }
 932 
 933     /**
 934      * DOM Level 3: register the given attribute node as an ID attribute
 935      */
 936     public void setIdAttributeNode(Attr at, boolean makeId) {
 937         if (needsSyncData()) {
 938             synchronizeData();
 939         }
 940         if (ownerDocument.errorChecking) {
 941             if (isReadOnly()) {
 942                 String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NO_MODIFICATION_ALLOWED_ERR", null);
 943                 throw new DOMException(
 944                                      DOMException.NO_MODIFICATION_ALLOWED_ERR,
 945                                      msg);
 946             }
 947 
 948             if (at.getOwnerElement() != this) {
 949                 String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NOT_FOUND_ERR", null);
 950                 throw new DOMException(DOMException.NOT_FOUND_ERR, msg);
 951             }
 952         }
 953         ((AttrImpl) at).isIdAttribute(makeId);
 954         if (!makeId) {
 955             ownerDocument.removeIdentifier(at.getValue());
 956         }
 957         else {
 958             ownerDocument.putIdentifier(at.getValue(), this);
 959         }
 960     }
 961 
 962     /**
 963      * DOM Level 3: register the given attribute node as an ID attribute
 964      */
 965     public void setIdAttribute(String name, boolean makeId) {
 966         if (needsSyncData()) {
 967             synchronizeData();
 968         }
 969         Attr at = getAttributeNode(name);
 970 
 971                 if( at == null){
 972                 String msg = DOMMessageFormatter.formatMessage(
 973                                                                         DOMMessageFormatter.DOM_DOMAIN,
 974                                                                         "NOT_FOUND_ERR", null);
 975             throw new DOMException(DOMException.NOT_FOUND_ERR, msg);
 976                 }
 977 
 978                 if (ownerDocument.errorChecking) {
 979             if (isReadOnly()) {
 980                 String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NO_MODIFICATION_ALLOWED_ERR", null);
 981                 throw new DOMException(
 982                                      DOMException.NO_MODIFICATION_ALLOWED_ERR,
 983                                      msg);
 984             }
 985 
 986             if (at.getOwnerElement() != this) {
 987                 String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NOT_FOUND_ERR", null);
 988                 throw new DOMException(DOMException.NOT_FOUND_ERR, msg);
 989             }
 990         }
 991 
 992         ((AttrImpl) at).isIdAttribute(makeId);
 993         if (!makeId) {
 994             ownerDocument.removeIdentifier(at.getValue());
 995         }
 996         else {
 997             ownerDocument.putIdentifier(at.getValue(), this);
 998         }
 999     }
1000 
1001     /**
1002      * DOM Level 3: register the given attribute node as an ID attribute
1003      */
1004     public void setIdAttributeNS(String namespaceURI, String localName,
1005                                     boolean makeId) {
1006         if (needsSyncData()) {
1007             synchronizeData();
1008         }
1009         //if namespace uri is empty string, set it to 'null'
1010         if (namespaceURI != null) {
1011             namespaceURI = (namespaceURI.length() == 0)? null : namespaceURI;
1012         }
1013         Attr at = getAttributeNodeNS(namespaceURI, localName);
1014 
1015                 if( at == null){
1016                 String msg = DOMMessageFormatter.formatMessage(
1017                                                                         DOMMessageFormatter.DOM_DOMAIN,
1018                                                                         "NOT_FOUND_ERR", null);
1019             throw new DOMException(DOMException.NOT_FOUND_ERR, msg);
1020                 }
1021 
1022                 if (ownerDocument.errorChecking) {
1023             if (isReadOnly()) {
1024                 String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NO_MODIFICATION_ALLOWED_ERR", null);
1025                 throw new DOMException(
1026                                      DOMException.NO_MODIFICATION_ALLOWED_ERR,
1027                                      msg);
1028             }
1029 
1030             if (at.getOwnerElement() != this) {
1031                 String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NOT_FOUND_ERR", null);
1032                 throw new DOMException(DOMException.NOT_FOUND_ERR, msg);
1033             }
1034         }
1035         ((AttrImpl) at).isIdAttribute(makeId);
1036         if (!makeId) {
1037             ownerDocument.removeIdentifier(at.getValue());
1038         }
1039         else {
1040             ownerDocument.putIdentifier(at.getValue(), this);
1041         }
1042    }
1043 
1044     /**
1045      * @see org.w3c.dom.TypeInfo#getTypeName()
1046      */
1047      public String getTypeName() {
1048         return null;
1049      }
1050 
1051     /**
1052      * @see org.w3c.dom.TypeInfo#getTypeNamespace()
1053      */
1054     public String getTypeNamespace() {
1055         return null;
1056     }
1057 
1058     /**
1059      * Introduced in DOM Level 3. <p>
1060      * Checks if a type is derived from another by restriction. See:
1061      * http://www.w3.org/TR/DOM-Level-3-Core/core.html#TypeInfo-isDerivedFrom
1062      *
1063      * @param ancestorNS
1064      *        The namspace of the ancestor type declaration
1065      * @param ancestorName
1066      *        The name of the ancestor type declaration
1067      * @param type
1068      *        The reference type definition
1069      *
1070      * @return boolean True if the type is derived by restriciton for the
1071      *         reference type
1072      */
1073     public boolean isDerivedFrom(String typeNamespaceArg,
1074                                  String typeNameArg,
1075                                  int derivationMethod) {
1076 
1077         return false;
1078     }
1079 
1080         /**
1081          * Method getSchemaTypeInfo.
1082          * @return TypeInfo
1083          */
1084     public TypeInfo getSchemaTypeInfo(){
1085         if(needsSyncData()) {
1086             synchronizeData();
1087         }
1088         return this;
1089     }
1090 
1091     //
1092     // Public methods
1093     //
1094 
1095     /**
1096      * NON-DOM: Subclassed to flip the attributes' readonly switch as well.
1097      * @see NodeImpl#setReadOnly
1098      */
1099     public void setReadOnly(boolean readOnly, boolean deep) {
1100         super.setReadOnly(readOnly,deep);
1101         if (attributes != null) {
1102             attributes.setReadOnly(readOnly,true);
1103         }
1104     }
1105 
1106 
1107 
1108     //
1109     // Protected methods
1110     //
1111 
1112     /** Synchronizes the data (name and value) for fast nodes. */
1113     protected void synchronizeData() {
1114 
1115         // no need to sync in the future
1116         needsSyncData(false);
1117 
1118         // we don't want to generate any event for this so turn them off
1119         boolean orig = ownerDocument.getMutationEvents();
1120         ownerDocument.setMutationEvents(false);
1121 
1122         // attributes
1123         setupDefaultAttributes();
1124 
1125         // set mutation events flag back to its original value
1126         ownerDocument.setMutationEvents(orig);
1127 
1128     } // synchronizeData()
1129 
1130     // support for DOM Level 3 renameNode method
1131     // @param el The element from which to take the attributes
1132     void moveSpecifiedAttributes(ElementImpl el) {
1133         if (needsSyncData()) {
1134             synchronizeData();
1135         }
1136         if (el.hasAttributes()) {
1137             if (attributes == null) {
1138                 attributes = new AttributeMap(this, null);
1139             }
1140             attributes.moveSpecifiedAttributes(el.attributes);
1141         }
1142     }
1143 
1144     /** Setup the default attributes. */
1145     protected void setupDefaultAttributes() {
1146         NamedNodeMapImpl defaults = getDefaultAttributes();
1147         if (defaults != null) {
1148             attributes = new AttributeMap(this, defaults);
1149         }
1150     }
1151 
1152     /** Reconcile default attributes. */
1153     protected void reconcileDefaultAttributes() {
1154         if (attributes != null) {
1155             NamedNodeMapImpl defaults = getDefaultAttributes();
1156             attributes.reconcileDefaults(defaults);
1157         }
1158     }
1159 
1160     /** Get the default attributes. */
1161     protected NamedNodeMapImpl getDefaultAttributes() {
1162 
1163         DocumentTypeImpl doctype =
1164             (DocumentTypeImpl) ownerDocument.getDoctype();
1165         if (doctype == null) {
1166             return null;
1167         }
1168         ElementDefinitionImpl eldef =
1169             (ElementDefinitionImpl)doctype.getElements()
1170                                                .getNamedItem(getNodeName());
1171         if (eldef == null) {
1172             return null;
1173         }
1174         return (NamedNodeMapImpl) eldef.getAttributes();
1175 
1176     } // getDefaultAttributes()
1177 
1178 } // class ElementImpl