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