ElementImpl.java revision 790:c10ec627fad5
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 */
21package com.sun.org.apache.xerces.internal.dom;
22
23import org.w3c.dom.Attr;
24import org.w3c.dom.DOMException;
25import org.w3c.dom.Element;
26import org.w3c.dom.ElementTraversal;
27import org.w3c.dom.NamedNodeMap;
28import org.w3c.dom.Node;
29import org.w3c.dom.NodeList;
30import org.w3c.dom.Text;
31import org.w3c.dom.TypeInfo;
32import 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 */
59public 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                    // -- Norman.Walsh@Sun.COM, 2 Feb 2007
686                    newAttr = (Attr) attributes.removeItem(newAttr, false);
687                    attributes.addItem(newAttr);
688                }
689            }
690            else {
691                // This case may happen if user calls:
692                //      elem.setAttribute("name", "value");
693                //      elem.setAttributeNS(null, "name", "value");
694                // This case is not defined by the DOM spec, we choose
695                // to create a new attribute in this case and remove an old one from the tree
696                // note this might cause events to be propagated or user data to be lost
697                newAttr = new AttrNSImpl((CoreDocumentImpl)getOwnerDocument(), namespaceURI, qualifiedName, localName);
698                attributes.setNamedItemNS(newAttr);
699            }
700
701            newAttr.setNodeValue(value);
702        }
703
704    } // setAttributeNS(String,String,String)
705
706    /**
707     * Introduced in DOM Level 2.
708     * <p>
709     *
710     * Removes an attribute by local name and namespace URI. If the removed
711     * attribute has a default value it is immediately replaced. The replacing
712     * attribute has the same namespace URI and local name, as well as the
713     * original prefix.<p>
714     *
715     * @param namespaceURI The namespace URI of the attribute to remove.
716     *
717     * @param localName The local name of the attribute to remove.
718     * @throws NO_MODIFICATION_ALLOWED_ERR: Raised if this node is readonly.
719     * @since WD-DOM-Level-2-19990923
720     */
721    public void removeAttributeNS(String namespaceURI, String localName) {
722
723        if (ownerDocument.errorChecking && isReadOnly()) {
724            String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NO_MODIFICATION_ALLOWED_ERR", null);
725            throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, msg);
726        }
727
728        if (needsSyncData()) {
729            synchronizeData();
730        }
731
732        if (attributes == null) {
733            return;
734        }
735
736        attributes.safeRemoveNamedItemNS(namespaceURI, localName);
737
738    } // removeAttributeNS(String,String)
739
740    /**
741     * Retrieves an Attr node by local name and namespace URI.
742     *
743     * @param namespaceURI The namespace URI of the attribute to retrieve.
744     * @param localName The local name of the attribute to retrieve.
745     * @return Attr The Attr node with the specified attribute local name and
746     * namespace URI or null if there is no such attribute.
747     * @since WD-DOM-Level-2-19990923
748     */
749    public Attr getAttributeNodeNS(String namespaceURI, String localName) {
750
751        if (needsSyncData()) {
752            synchronizeData();
753        }
754        if (attributes == null) {
755            return null;
756        }
757        return (Attr) attributes.getNamedItemNS(namespaceURI, localName);
758
759    } // getAttributeNodeNS(String,String):Attr
760
761    /**
762     * Introduced in DOM Level 2.
763     * <p>
764     *
765     * Adds a new attribute. If an attribute with that local name and namespace
766     * URI is already present in the element, it is replaced by the new one.
767     *
768     * @param newAttr The Attr node to add to the attribute list. When the Node
769     * has no namespaceURI, this method behaves like setAttributeNode.
770     * @return Attr If the newAttr attribute replaces an existing attribute with
771     * the same local name and namespace URI, the * previously existing Attr
772     * node is returned, otherwise null is returned.
773     * @throws WRONG_DOCUMENT_ERR: Raised if newAttr was created from a
774     * different document than the one that created the element.
775     *
776     * @throws NO_MODIFICATION_ALLOWED_ERR: Raised if this node is readonly.
777     *
778     * @throws INUSE_ATTRIBUTE_ERR: Raised if newAttr is already an attribute of
779     * another Element object. The DOM user must explicitly clone Attr nodes to
780     * re-use them in other elements.
781     * @since WD-DOM-Level-2-19990923
782     */
783    public Attr setAttributeNodeNS(Attr newAttr)
784            throws DOMException {
785
786        if (needsSyncData()) {
787            synchronizeData();
788        }
789        if (ownerDocument.errorChecking) {
790            if (isReadOnly()) {
791                String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NO_MODIFICATION_ALLOWED_ERR", null);
792                throw new DOMException(
793                        DOMException.NO_MODIFICATION_ALLOWED_ERR,
794                        msg);
795            }
796            if (newAttr.getOwnerDocument() != ownerDocument) {
797                String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "WRONG_DOCUMENT_ERR", null);
798                throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, msg);
799            }
800        }
801
802        if (attributes == null) {
803            attributes = new AttributeMap(this, null);
804        }
805        // This will throw INUSE if necessary
806        return (Attr) attributes.setNamedItemNS(newAttr);
807
808    } // setAttributeNodeNS(Attr):Attr
809
810    /**
811     * NON-DOM: sets attribute node for this element
812     */
813    protected int setXercesAttributeNode(Attr attr) {
814
815        if (needsSyncData()) {
816            synchronizeData();
817        }
818
819        if (attributes == null) {
820            attributes = new AttributeMap(this, null);
821        }
822        return attributes.addItem(attr);
823
824    }
825
826    /**
827     * NON-DOM: get inded of an attribute
828     */
829    protected int getXercesAttribute(String namespaceURI, String localName) {
830
831        if (needsSyncData()) {
832            synchronizeData();
833        }
834        if (attributes == null) {
835            return -1;
836        }
837        return attributes.getNamedItemIndex(namespaceURI, localName);
838
839    }
840
841    /**
842     * Introduced in DOM Level 2.
843     */
844    public boolean hasAttributes() {
845        if (needsSyncData()) {
846            synchronizeData();
847        }
848        return (attributes != null && attributes.getLength() != 0);
849    }
850
851    /**
852     * Introduced in DOM Level 2.
853     */
854    public boolean hasAttribute(String name) {
855        return getAttributeNode(name) != null;
856    }
857
858    /**
859     * Introduced in DOM Level 2.
860     */
861    public boolean hasAttributeNS(String namespaceURI, String localName) {
862        return getAttributeNodeNS(namespaceURI, localName) != null;
863    }
864
865    /**
866     * Introduced in DOM Level 2.
867     * <p>
868     *
869     * Returns a NodeList of all the Elements with a given local name and
870     * namespace URI in the order in which they would be encountered in a
871     * preorder traversal of the Document tree, starting from this node.
872     *
873     * @param namespaceURI The namespace URI of the elements to match on. The
874     * special value "*" matches all namespaces. When it is null or an empty
875     * string, this method behaves like getElementsByTagName.
876     * @param localName The local name of the elements to match on. The special
877     * value "*" matches all local names.
878     * @return NodeList A new NodeList object containing all the matched
879     * Elements.
880     * @since WD-DOM-Level-2-19990923
881     */
882    public NodeList getElementsByTagNameNS(String namespaceURI,
883            String localName) {
884        return new DeepNodeListImpl(this, namespaceURI, localName);
885    }
886
887    /**
888     * DOM Level 3 WD- Experimental. Override inherited behavior from NodeImpl
889     * and ParentNode to check on attributes
890     */
891    public boolean isEqualNode(Node arg) {
892        if (!super.isEqualNode(arg)) {
893            return false;
894        }
895        boolean hasAttrs = hasAttributes();
896        if (hasAttrs != ((Element) arg).hasAttributes()) {
897            return false;
898        }
899        if (hasAttrs) {
900            NamedNodeMap map1 = getAttributes();
901            NamedNodeMap map2 = ((Element) arg).getAttributes();
902            int len = map1.getLength();
903            if (len != map2.getLength()) {
904                return false;
905            }
906            for (int i = 0; i < len; i++) {
907                Node n1 = map1.item(i);
908                if (n1.getLocalName() == null) { // DOM Level 1 Node
909                    Node n2 = map2.getNamedItem(n1.getNodeName());
910                    if (n2 == null || !((NodeImpl) n1).isEqualNode(n2)) {
911                        return false;
912                    }
913                } else {
914                    Node n2 = map2.getNamedItemNS(n1.getNamespaceURI(),
915                            n1.getLocalName());
916                    if (n2 == null || !((NodeImpl) n1).isEqualNode(n2)) {
917                        return false;
918                    }
919                }
920            }
921        }
922        return true;
923    }
924
925    /**
926     * DOM Level 3: register the given attribute node as an ID attribute
927     */
928    public void setIdAttributeNode(Attr at, boolean makeId) {
929        if (needsSyncData()) {
930            synchronizeData();
931        }
932        if (ownerDocument.errorChecking) {
933            if (isReadOnly()) {
934                String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NO_MODIFICATION_ALLOWED_ERR", null);
935                throw new DOMException(
936                        DOMException.NO_MODIFICATION_ALLOWED_ERR,
937                        msg);
938            }
939
940            if (at.getOwnerElement() != this) {
941                String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NOT_FOUND_ERR", null);
942                throw new DOMException(DOMException.NOT_FOUND_ERR, msg);
943            }
944        }
945        ((AttrImpl) at).isIdAttribute(makeId);
946        if (!makeId) {
947            ownerDocument.removeIdentifier(at.getValue());
948        } else {
949            ownerDocument.putIdentifier(at.getValue(), this);
950        }
951    }
952
953    /**
954     * DOM Level 3: register the given attribute node as an ID attribute
955     */
956    public void setIdAttribute(String name, boolean makeId) {
957        if (needsSyncData()) {
958            synchronizeData();
959        }
960        Attr at = getAttributeNode(name);
961
962        if (at == null) {
963            String msg = DOMMessageFormatter.formatMessage(
964                    DOMMessageFormatter.DOM_DOMAIN,
965                    "NOT_FOUND_ERR", null);
966            throw new DOMException(DOMException.NOT_FOUND_ERR, msg);
967        }
968
969        if (ownerDocument.errorChecking) {
970            if (isReadOnly()) {
971                String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NO_MODIFICATION_ALLOWED_ERR", null);
972                throw new DOMException(
973                        DOMException.NO_MODIFICATION_ALLOWED_ERR,
974                        msg);
975            }
976
977            if (at.getOwnerElement() != this) {
978                String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NOT_FOUND_ERR", null);
979                throw new DOMException(DOMException.NOT_FOUND_ERR, msg);
980            }
981        }
982
983        ((AttrImpl) at).isIdAttribute(makeId);
984        if (!makeId) {
985            ownerDocument.removeIdentifier(at.getValue());
986        } else {
987            ownerDocument.putIdentifier(at.getValue(), this);
988        }
989    }
990
991    /**
992     * DOM Level 3: register the given attribute node as an ID attribute
993     */
994    public void setIdAttributeNS(String namespaceURI, String localName,
995            boolean makeId) {
996        if (needsSyncData()) {
997            synchronizeData();
998        }
999        //if namespace uri is empty string, set it to 'null'
1000        if (namespaceURI != null) {
1001            namespaceURI = (namespaceURI.length() == 0) ? null : namespaceURI;
1002        }
1003        Attr at = getAttributeNodeNS(namespaceURI, localName);
1004
1005        if (at == null) {
1006            String msg = DOMMessageFormatter.formatMessage(
1007                    DOMMessageFormatter.DOM_DOMAIN,
1008                    "NOT_FOUND_ERR", null);
1009            throw new DOMException(DOMException.NOT_FOUND_ERR, msg);
1010        }
1011
1012        if (ownerDocument.errorChecking) {
1013            if (isReadOnly()) {
1014                String msg = DOMMessageFormatter.formatMessage(
1015                        DOMMessageFormatter.DOM_DOMAIN, "NO_MODIFICATION_ALLOWED_ERR", null);
1016                throw new DOMException(
1017                        DOMException.NO_MODIFICATION_ALLOWED_ERR,
1018                        msg);
1019            }
1020
1021            if (at.getOwnerElement() != this) {
1022                String msg = DOMMessageFormatter.formatMessage(
1023                        DOMMessageFormatter.DOM_DOMAIN, "NOT_FOUND_ERR", null);
1024                throw new DOMException(DOMException.NOT_FOUND_ERR, msg);
1025            }
1026        }
1027        ((AttrImpl) at).isIdAttribute(makeId);
1028        if (!makeId) {
1029            ownerDocument.removeIdentifier(at.getValue());
1030        } else {
1031            ownerDocument.putIdentifier(at.getValue(), this);
1032        }
1033    }
1034
1035    /**
1036     * @see org.w3c.dom.TypeInfo#getTypeName()
1037     */
1038    public String getTypeName() {
1039        return null;
1040    }
1041
1042    /**
1043     * @see org.w3c.dom.TypeInfo#getTypeNamespace()
1044     */
1045    public String getTypeNamespace() {
1046        return null;
1047    }
1048
1049    /**
1050     * Introduced in DOM Level 3.
1051     * <p>
1052     * Checks if a type is derived from another by restriction. See:
1053     * http://www.w3.org/TR/DOM-Level-3-Core/core.html#TypeInfo-isDerivedFrom
1054     *
1055     * @param typeNamespaceArg The namspace of the ancestor type declaration
1056     * @param typeNameArg The name of the ancestor type declaration
1057     * @param derivationMethod The derivation method
1058     *
1059     * @return boolean True if the type is derived by restriction for the
1060     * reference type
1061     */
1062    public boolean isDerivedFrom(String typeNamespaceArg,
1063            String typeNameArg,
1064            int derivationMethod) {
1065
1066        return false;
1067    }
1068
1069    /**
1070     * Method getSchemaTypeInfo.
1071     *
1072     * @return TypeInfo
1073     */
1074    public TypeInfo getSchemaTypeInfo() {
1075        if (needsSyncData()) {
1076            synchronizeData();
1077        }
1078        return this;
1079    }
1080
1081    //
1082    // Public methods
1083    //
1084    /**
1085     * NON-DOM: Subclassed to flip the attributes' readonly switch as well.
1086     *
1087     * @see NodeImpl#setReadOnly
1088     */
1089    public void setReadOnly(boolean readOnly, boolean deep) {
1090        super.setReadOnly(readOnly, deep);
1091        if (attributes != null) {
1092            attributes.setReadOnly(readOnly, true);
1093        }
1094    }
1095
1096    //
1097    // Protected methods
1098    //
1099    /**
1100     * Synchronizes the data (name and value) for fast nodes.
1101     */
1102    protected void synchronizeData() {
1103
1104        // no need to sync in the future
1105        needsSyncData(false);
1106
1107        // we don't want to generate any event for this so turn them off
1108        boolean orig = ownerDocument.getMutationEvents();
1109        ownerDocument.setMutationEvents(false);
1110
1111        // attributes
1112        setupDefaultAttributes();
1113
1114        // set mutation events flag back to its original value
1115        ownerDocument.setMutationEvents(orig);
1116
1117    } // synchronizeData()
1118
1119    // support for DOM Level 3 renameNode method
1120    // @param el The element from which to take the attributes
1121    void moveSpecifiedAttributes(ElementImpl el) {
1122        if (needsSyncData()) {
1123            synchronizeData();
1124        }
1125        if (el.hasAttributes()) {
1126            if (attributes == null) {
1127                attributes = new AttributeMap(this, null);
1128            }
1129            attributes.moveSpecifiedAttributes(el.attributes);
1130        }
1131    }
1132
1133    /**
1134     * Setup the default attributes.
1135     */
1136    protected void setupDefaultAttributes() {
1137        NamedNodeMapImpl defaults = getDefaultAttributes();
1138        if (defaults != null) {
1139            attributes = new AttributeMap(this, defaults);
1140        }
1141    }
1142
1143    /**
1144     * Reconcile default attributes.
1145     */
1146    protected void reconcileDefaultAttributes() {
1147        if (attributes != null) {
1148            NamedNodeMapImpl defaults = getDefaultAttributes();
1149            attributes.reconcileDefaults(defaults);
1150        }
1151    }
1152
1153    /**
1154     * Get the default attributes.
1155     */
1156    protected NamedNodeMapImpl getDefaultAttributes() {
1157
1158        DocumentTypeImpl doctype
1159                = (DocumentTypeImpl) ownerDocument.getDoctype();
1160        if (doctype == null) {
1161            return null;
1162        }
1163        ElementDefinitionImpl eldef
1164                = (ElementDefinitionImpl) doctype.getElements()
1165                .getNamedItem(getNodeName());
1166        if (eldef == null) {
1167            return null;
1168        }
1169        return (NamedNodeMapImpl) eldef.getAttributes();
1170
1171    } // getDefaultAttributes()
1172
1173    //
1174    // ElementTraversal methods
1175    //
1176    /**
1177     * @see <a
1178     * href="http://www.w3.org/TR/2008/REC-ElementTraversal-20081222/#attribute-childElementCount">
1179     * Element Traversal Specification</a>
1180     */
1181    @Override
1182    public final int getChildElementCount() {
1183        int count = 0;
1184        Element child = getFirstElementChild();
1185        while (child != null) {
1186            ++count;
1187            child = ((ElementImpl) child).getNextElementSibling();
1188        }
1189        return count;
1190    } // getChildElementCount():int
1191
1192    /**
1193     * @see <a
1194     * href="http://www.w3.org/TR/2008/REC-ElementTraversal-20081222/#attribute-firstElementChild">
1195     * Element Traversal Specification</a>
1196     */
1197    @Override
1198    public final Element getFirstElementChild() {
1199        Node n = getFirstChild();
1200        while (n != null) {
1201            switch (n.getNodeType()) {
1202                case Node.ELEMENT_NODE:
1203                    return (Element) n;
1204                case Node.ENTITY_REFERENCE_NODE:
1205                    final Element e = getFirstElementChild(n);
1206                    if (e != null) {
1207                        return e;
1208                    }
1209                    break;
1210            }
1211            n = n.getNextSibling();
1212        }
1213        return null;
1214    } // getFirstElementChild():Element
1215
1216    /**
1217     * @see <a
1218     * href="http://www.w3.org/TR/2008/REC-ElementTraversal-20081222/#attribute-lastElementChild">
1219     * Element Traversal Specification</a>
1220     */
1221    @Override
1222    public final Element getLastElementChild() {
1223        Node n = getLastChild();
1224        while (n != null) {
1225            switch (n.getNodeType()) {
1226                case Node.ELEMENT_NODE:
1227                    return (Element) n;
1228                case Node.ENTITY_REFERENCE_NODE:
1229                    final Element e = getLastElementChild(n);
1230                    if (e != null) {
1231                        return e;
1232                    }
1233                    break;
1234            }
1235            n = n.getPreviousSibling();
1236        }
1237        return null;
1238    } // getLastElementChild():Element
1239
1240    /**
1241     * @see <a
1242     * href="http://www.w3.org/TR/2008/REC-ElementTraversal-20081222/#attribute-nextElementSibling">
1243     * Element Traversal Specification</a>
1244     */
1245    @Override
1246    public final Element getNextElementSibling() {
1247        Node n = getNextLogicalSibling(this);
1248        while (n != null) {
1249            switch (n.getNodeType()) {
1250                case Node.ELEMENT_NODE:
1251                    return (Element) n;
1252                case Node.ENTITY_REFERENCE_NODE:
1253                    final Element e = getFirstElementChild(n);
1254                    if (e != null) {
1255                        return e;
1256                    }
1257                    break;
1258            }
1259            n = getNextLogicalSibling(n);
1260        }
1261        return null;
1262    } // getNextElementSibling():Element
1263
1264    /**
1265     * @see <a
1266     * href="http://www.w3.org/TR/2008/REC-ElementTraversal-20081222/#attribute-previousElementSibling">
1267     * Element Traversal Specification</a>
1268     */
1269    @Override
1270    public final Element getPreviousElementSibling() {
1271        Node n = getPreviousLogicalSibling(this);
1272        while (n != null) {
1273            switch (n.getNodeType()) {
1274                case Node.ELEMENT_NODE:
1275                    return (Element) n;
1276                case Node.ENTITY_REFERENCE_NODE:
1277                    final Element e = getLastElementChild(n);
1278                    if (e != null) {
1279                        return e;
1280                    }
1281                    break;
1282            }
1283            n = getPreviousLogicalSibling(n);
1284        }
1285        return null;
1286    } // getPreviousElementSibling():Element
1287
1288    // Returns the first element node found from a
1289    // non-recursive in order traversal of the given node.
1290    private Element getFirstElementChild(Node n) {
1291        final Node top = n;
1292        while (n != null) {
1293            if (n.getNodeType() == Node.ELEMENT_NODE) {
1294                return (Element) n;
1295            }
1296            Node next = n.getFirstChild();
1297            while (next == null) {
1298                if (top == n) {
1299                    break;
1300                }
1301                next = n.getNextSibling();
1302                if (next == null) {
1303                    n = n.getParentNode();
1304                    if (n == null || top == n) {
1305                        return null;
1306                    }
1307                }
1308            }
1309            n = next;
1310        }
1311        return null;
1312    } // getFirstElementChild(Node):Element
1313
1314    // Returns the first element node found from a
1315    // non-recursive reverse order traversal of the given node.
1316    private Element getLastElementChild(Node n) {
1317        final Node top = n;
1318        while (n != null) {
1319            if (n.getNodeType() == Node.ELEMENT_NODE) {
1320                return (Element) n;
1321            }
1322            Node next = n.getLastChild();
1323            while (next == null) {
1324                if (top == n) {
1325                    break;
1326                }
1327                next = n.getPreviousSibling();
1328                if (next == null) {
1329                    n = n.getParentNode();
1330                    if (n == null || top == n) {
1331                        return null;
1332                    }
1333                }
1334            }
1335            n = next;
1336        }
1337        return null;
1338    } // getLastElementChild(Node):Element
1339
1340    // Returns the next logical sibling with respect to the given node.
1341    private Node getNextLogicalSibling(Node n) {
1342        Node next = n.getNextSibling();
1343        // If "n" has no following sibling and its parent is an entity reference node we
1344        // need to continue the search through the following siblings of the entity
1345        // reference as these are logically siblings of the given node.
1346        if (next == null) {
1347            Node parent = n.getParentNode();
1348            while (parent != null && parent.getNodeType() == Node.ENTITY_REFERENCE_NODE) {
1349                next = parent.getNextSibling();
1350                if (next != null) {
1351                    break;
1352                }
1353                parent = parent.getParentNode();
1354            }
1355        }
1356        return next;
1357    } // getNextLogicalSibling(Node):Node
1358
1359    // Returns the previous logical sibling with respect to the given node.
1360    private Node getPreviousLogicalSibling(Node n) {
1361        Node prev = n.getPreviousSibling();
1362        // If "n" has no previous sibling and its parent is an entity reference node we
1363        // need to continue the search through the previous siblings of the entity
1364        // reference as these are logically siblings of the given node.
1365        if (prev == null) {
1366            Node parent = n.getParentNode();
1367            while (parent != null && parent.getNodeType() == Node.ENTITY_REFERENCE_NODE) {
1368                prev = parent.getPreviousSibling();
1369                if (prev != null) {
1370                    break;
1371                }
1372                parent = parent.getParentNode();
1373            }
1374        }
1375        return prev;
1376    } // getPreviousLogicalSibling(Node):Node
1377} // class ElementImpl
1378