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