1 /* Copyright 2002-2006, 2011, 2013 Elliotte Rusty Harold
2 
3    This library is free software; you can redistribute it and/or modify
4    it under the terms of version 2.1 of the GNU Lesser General Public
5    License as published by the Free Software Foundation.
6 
7    This library is distributed in the hope that it will be useful,
8    but WITHOUT ANY WARRANTY; without even the implied warranty of
9    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10    GNU Lesser General Public License for more details.
11 
12    You should have received a copy of the GNU Lesser General Public
13    License along with this library; if not, write to the
14    Free Software Foundation, Inc., 59 Temple Place, Suite 330,
15    Boston, MA 02111-1307  USA
16 
17    You can contact Elliotte Rusty Harold by sending e-mail to
18    elharo@ibiblio.org. Please include the word "XOM" in the
19    subject line. The XOM home page is located at http://www.xom.nu/
20 */
21 
22 package nu.xom;
23 
24 import java.util.HashMap;
25 import java.util.HashSet;
26 import java.util.Iterator;
27 import java.util.Map;
28 import java.util.NoSuchElementException;
29 import java.util.Set;
30 import java.util.LinkedHashSet;
31 
32 /**
33  * <p>
34  * This class represents an XML element. Each
35  * element has the following properties:
36  * </p>
37  *
38  * <ul>
39  *   <li>Local name</li>
40  *   <li>Prefix (which may be null or the empty string) </li>
41  *   <li>Namespace URI (which may be null or the empty string) </li>
42  *   <li>A list of attributes</li>
43  *   <li>A list of namespace declarations for this element
44  *       (not including those inherited from its parent)</li>
45  *   <li>A list of child nodes</li>
46  * </ul>
47  *
48  * @author Elliotte Rusty Harold
49  * @version 1.2.10
50  *
51  */
52 public class Element extends ParentNode {
53 
54     private String localName;
55     private String prefix;
56     private String URI;
57 
58     private Attribute[] attributes = null;
59     private int         numAttributes = 0;
60             Namespaces  namespaces = null;
61 
62     /**
63      * <p>
64      * Creates a new element in no namespace.
65      * </p>
66      *
67      * @param name the name of the element
68      *
69      * @throws IllegalNameException if <code>name</code>
70      *     is not a legal XML 1.0 non-colonized name
71      */
Element(String name)72     public Element(String name) {
73         this(name, "");
74     }
75 
76 
77     /**
78      * <p>
79      * Creates a new element in a namespace.
80      * </p>
81      *
82      * @param name the qualified name of the element
83      * @param uri the namespace URI of the element
84      *
85      * @throws IllegalNameException if <code>name</code>
86      *     is not a legal XML 1.0 name
87      * @throws NamespaceConflictException if <code>name</code>'s prefix
88      *     cannot be used with <code>uri</code>
89      * @throws MalformedURIException if <code>uri</code>
90      *     is not an RFC 3986 absolute URI reference
91      */
Element(String name, String uri)92     public Element(String name, String uri) {
93 
94         // The shadowing is important here.
95         // I don't want to set the prefix field just yet.
96         String prefix = "";
97         String localName = name;
98         int colon = name.indexOf(':');
99         if (colon > 0) {
100             prefix = name.substring(0, colon);
101             localName = name.substring(colon + 1);
102         }
103 
104         // The order of these next two calls
105         // matters a great deal.
106         _setNamespacePrefix(prefix);
107         _setNamespaceURI(uri);
108         try {
109             _setLocalName(localName);
110         }
111         catch (IllegalNameException ex) {
112             ex.setData(name);
113             throw ex;
114         }
115 
116     }
117 
118 
Element()119     private Element() {}
120 
121 
build(String name, String uri, String localName)122     static Element build(String name, String uri, String localName) {
123 
124         Element result = new Element();
125         String prefix = "";
126         int colon = name.indexOf(':');
127         if (colon >= 0) {
128             prefix = name.substring(0, colon);
129         }
130         result.prefix = prefix;
131         result.localName = localName;
132         // We do need to verify the URI here because parsers are
133         // allowing relative URIs which XOM forbids, for reasons
134         // of canonical XML if nothing else. But we only have to verify
135         // that it's an absolute base URI. I don't have to verify
136         // no conflicts.
137         if (! "".equals(uri)) Verifier.checkAbsoluteURIReference(uri);
138         result.URI = uri;
139         return result;
140 
141     }
142 
143 
144     /**
145      * <p>
146      * Creates a deep copy of an element.
147      * The copy is disconnected from the tree, and does not
148      * have a parent.
149      * </p>
150      *
151      * @param element the element to copy
152      *
153      */
Element(Element element)154     public Element(Element element) {
155 
156         this.prefix = element.prefix;
157         this.localName = element.localName;
158         this.URI = element.URI;
159 
160         // Attach additional namespaces
161         if (element.namespaces != null) {
162             this.namespaces = element.namespaces.copy();
163         }
164 
165         // Attach clones of attributes
166         if (element.attributes != null) {
167             this.attributes = element.copyAttributes(this);
168             this.numAttributes = element.numAttributes;
169         }
170 
171         this.actualBaseURI = element.findActualBaseURI();
172 
173         copyChildren(element, this);
174 
175     }
176 
177 
copyAttributes(Element newParent)178     private Attribute[] copyAttributes(Element newParent) {
179 
180         if (numAttributes == 0) return null;
181         Attribute[] copy = new Attribute[numAttributes];
182         for (int i = 0; i < numAttributes; i++) {
183             copy[i] = (Attribute) attributes[i].copy();
184             copy[i].setParent(newParent);
185         }
186         return copy;
187 
188     }
189 
190 
copyTag(final Element source)191     private static Element copyTag(final Element source) {
192 
193         Element result = source.shallowCopy();
194 
195         // Attach additional namespaces
196         if (source.namespaces != null) {
197             result.namespaces = source.namespaces.copy();
198         }
199 
200         // Attach clones of attributes
201         if (source.attributes != null) {
202             result.attributes = source.copyAttributes(result);
203             result.numAttributes = source.numAttributes;
204         }
205 
206         result.actualBaseURI = source.findActualBaseURI();
207 
208         return result;
209 
210     }
211 
212 
copyChildren(final Element sourceElement, Element resultElement)213     private static void copyChildren(final Element sourceElement,
214       Element resultElement) {
215 
216         if (sourceElement.getChildCount() == 0) return;
217         ParentNode resultParent = resultElement;
218         Node sourceCurrent = sourceElement;
219         int index = 0;
220         int[] indexes = new int[10];
221         int top = 0;
222         indexes[0] = 0;
223 
224         // true if processing the element for the 2nd time;
225         // i.e. the element's end-tag
226         boolean endTag = false;
227 
228         while (true) {
229             if (!endTag && sourceCurrent.getChildCount() > 0) {
230                sourceCurrent = sourceCurrent.getChild(0);
231                index = 0;
232                top++;
233                indexes = grow(indexes, top);
234                indexes[top] = 0;
235             }
236             else {
237                 endTag = false;
238                 ParentNode sourceParent = sourceCurrent.getParent();
239                 if (sourceParent.getChildCount() - 1 == index) {
240                     sourceCurrent = sourceParent;
241                     top--;
242                     if (sourceCurrent == sourceElement) break;
243                     // switch parent up
244                     resultParent = (Element) resultParent.getParent();
245                     index = indexes[top];
246                     endTag = true;
247                     continue;
248                 }
249                 else {
250                     index++;
251                     indexes[top] = index;
252                     sourceCurrent = sourceParent.getChild(index);
253                 }
254             }
255 
256             if (sourceCurrent.isElement()) {
257                 Element child = copyTag((Element) sourceCurrent);
258                 resultParent.appendChild(child);
259                 if (sourceCurrent.getChildCount() > 0) {
260                     resultParent = child;
261                 }
262             }
263             else {
264                 Node child = sourceCurrent.copy();
265                 resultParent.appendChild(child);
266             }
267 
268         }
269 
270     }
271 
272 
grow(int[] indexes, int top)273     private static int[] grow(int[] indexes, int top) {
274 
275         if (top < indexes.length) return indexes;
276         int[] result = new int[indexes.length*2];
277         System.arraycopy(indexes, 0, result, 0, indexes.length);
278         return result;
279 
280     }
281 
282 
283     /**
284      * <p>
285      * Returns a list of the child elements of
286      * this element with the specified name in no namespace.
287      * The elements returned are in document order.
288      * </p>
289      *
290      * @param name the name of the elements included in the list
291      *
292      * @return a comatose list containing the child elements of this
293      *     element with the specified name
294      */
getChildElements(String name)295     public final Elements getChildElements(String name) {
296         return getChildElements(name, "");
297     }
298 
299 
300     /**
301      * <p>
302      * Returns a list of the immediate child elements of this
303      * element with the specified local name and namespace URI.
304      * Passing the empty string or null as the local name
305      * returns all elements in the specified namespace.
306      * Passing null or the empty string as the namespace URI
307      * returns elements with the specified name in no namespace.
308      * The elements returned are in document order.
309      * </p>
310      *
311      * @param localName the name of the elements included in the list
312      * @param namespaceURI the namespace URI of the elements included
313      *     in the list
314      *
315      * @return a comatose list containing the child
316      *    elements of this element with the specified
317      *    name in the specified namespace
318      */
getChildElements(String localName, String namespaceURI)319     public final Elements getChildElements(String localName,
320      String namespaceURI) {
321 
322         if (namespaceURI == null) namespaceURI = "";
323         if (localName == null) localName = "";
324 
325         Elements elements = new Elements();
326         for (int i = 0; i < getChildCount(); i++) {
327             Node child = getChild(i);
328             if (child.isElement()) {
329                 Element element = (Element) child;
330                 if ((localName.equals(element.getLocalName())
331                   || localName.length() == 0)
332                   && namespaceURI.equals(element.getNamespaceURI())) {
333                     elements.add(element);
334                 }
335             }
336         }
337         return elements;
338 
339     }
340 
341 
342     /**
343      * <p>
344      * Returns a list of all the child elements
345      * of this element in document order.
346      * </p>
347      *
348      * @return a comatose list containing all
349      *    child elements of this element
350      */
getChildElements()351     public final Elements getChildElements() {
352 
353         Elements elements = new Elements();
354         for (int i = 0; i < getChildCount(); i++) {
355             Node child = getChild(i);
356             if (child.isElement()) {
357                 Element element = (Element) child;
358                 elements.add(element);
359             }
360         }
361         return elements;
362 
363     }
364 
365 
366     /**
367      * <p>
368      * Returns the first child
369      * element with the specified name in no namespace.
370      * If there is no such element, it returns null.
371      * </p>
372      *
373      * @param name the name of the element to return
374      *
375      * @return the first child element with the specified local name
376      *    in no namespace or null if there is no such element
377      */
getFirstChildElement(String name)378     public final Element getFirstChildElement(String name) {
379         return getFirstChildElement(name, "");
380     }
381 
382 
383     /**
384      * <p>
385      * Returns the first child
386      * element with the specified local name and namespace URI.
387      * If there is no such element, it returns null.
388      * </p>
389      *
390      * @param localName the local name of the element to return
391      * @param namespaceURI the namespace URI of the element to return
392      *
393      * @return the first child with the specified local name in the
394      *      specified namespace, or null if there is no such element
395      */
getFirstChildElement(String localName, String namespaceURI)396     public final Element getFirstChildElement(String localName,
397      String namespaceURI) {
398 
399         for (int i = 0; i < getChildCount(); i++) {
400             Node child = getChild(i);
401             if (child.isElement()) {
402                 Element element = (Element) child;
403                 if (localName.equals(element.getLocalName())
404                   && namespaceURI.equals(element.getNamespaceURI())) {
405                     return element;
406                 }
407             }
408         }
409         return null;
410 
411     }
412 
413 
414     /**
415      * <p>
416      * Adds an attribute to this element, replacing any existing
417      * attribute with the same local name and namespace URI.
418      * </p>
419      *
420      * @param attribute the attribute to add
421      *
422      * @throws MultipleParentException if the attribute is already
423      *      attached to an element
424      * @throws NamespaceConflictException if the attribute's prefix
425      *      is mapped to a different namespace URI than the same prefix
426      *      is mapped to by this element, another attribute of
427      *      this element, or an additional namespace declaration
428      *      of this element
429      */
addAttribute(Attribute attribute)430     public void addAttribute(Attribute attribute) {
431 
432         if (attribute.getParent() != null) {
433             throw new MultipleParentException(
434               "Attribute already has a parent");
435         }
436 
437         // check for namespace conflicts
438         String attPrefix = attribute.getNamespacePrefix();
439         if (attPrefix.length() != 0 && !"xml".equals(attPrefix)) {
440             if (prefix.equals(attribute.getNamespacePrefix())
441               && !(getNamespaceURI()
442                .equals(attribute.getNamespaceURI()))) {
443                 throw new NamespaceConflictException("Prefix of "
444                   + attribute.getQualifiedName()
445                   + " conflicts with element prefix " + prefix);
446             }
447             // check for conflicts with additional namespaces
448             if (namespaces != null) {
449                 String existing
450                  = namespaces.getURI(attribute.getNamespacePrefix());
451                 if (existing != null
452                   && !existing.equals(attribute.getNamespaceURI())) {
453                     throw new NamespaceConflictException("Attribute prefix  "
454                       + attPrefix
455                       + " conflicts with namespace declaration.");
456                 }
457             }
458 
459         }
460 
461         if (attributes == null) attributes = new Attribute[1];
462         checkPrefixConflict(attribute);
463 
464         // Is there already an attribute with this local name
465         // and namespace? If so, remove it.
466         Attribute oldAttribute = getAttribute(attribute.getLocalName(),
467           attribute.getNamespaceURI());
468         if (oldAttribute != null) remove(oldAttribute);
469 
470         add(attribute);
471         attribute.setParent(this);
472 
473     }
474 
475 
add(Attribute attribute)476     private void add(Attribute attribute) {
477 
478         if (numAttributes == attributes.length) {
479             Attribute[] newAttributes = new Attribute[attributes.length * 2];
480             System.arraycopy(attributes, 0, newAttributes, 0, numAttributes);
481             this.attributes = newAttributes;
482         }
483         attributes[numAttributes] = attribute;
484         numAttributes++;
485 
486     }
487 
488 
remove(Attribute attribute)489     private boolean remove(Attribute attribute) {
490 
491         int index = -1;
492         for (int i = 0; i < attributes.length; i++) {
493             if (attributes[i] == attribute) {
494                 index = i;
495                 break;
496             }
497         }
498 
499         if (index == -1) return false;
500 
501         int toCopy = numAttributes-index-1;
502         if (toCopy > 0) {
503             System.arraycopy(attributes, index+1, attributes, index, toCopy);
504         }
505         numAttributes--;
506         attributes[numAttributes] = null;
507         return true;
508 
509     }
510 
511 
fastAddAttribute(Attribute attribute)512     void fastAddAttribute(Attribute attribute) {
513         if (attributes == null) attributes = new Attribute[1];
514         add(attribute);
515         attribute.setParent(this);
516     }
517 
518 
519     /**
520      * <p>
521      * Removes an attribute from this element.
522      * </p>
523      *
524      * @param attribute the attribute to remove
525      *
526      * @return the attribute that was removed
527      *
528      * @throws NoSuchAttributeException if this element is not the
529      *     parent of attribute
530      *
531      */
removeAttribute(Attribute attribute)532     public Attribute removeAttribute(Attribute attribute) {
533 
534         if (attributes == null) {
535             throw new NoSuchAttributeException(
536               "Tried to remove attribute "
537               + attribute.getQualifiedName()
538               + " from non-parent element");
539         }
540         if (attribute == null) {
541             throw new NullPointerException(
542               "Tried to remove null attribute");
543         }
544         if (remove(attribute)) {
545             attribute.setParent(null);
546             return attribute;
547         }
548         else {
549             throw new NoSuchAttributeException(
550               "Tried to remove attribute "
551               + attribute.getQualifiedName()
552               + " from non-parent element");
553         }
554 
555     }
556 
557 
558     /**
559      * <p>
560      * Returns the attribute with the specified name in no namespace,
561      * or null if this element does not have an attribute
562      * with that name in no namespace.
563      * </p>
564      *
565      * @param name the name of the attribute
566      *
567      * @return the attribute of this element with the specified name
568      */
getAttribute(String name)569     public final Attribute getAttribute(String name) {
570         return getAttribute(name, "");
571     }
572 
573 
574     /**
575      * <p>
576      * Returns the attribute with the specified name and namespace URI,
577      * or null if this element does not have an attribute
578      * with that name in that namespace.
579      * </p>
580      *
581      * @param localName the local name of the attribute
582      * @param namespaceURI the namespace of the attribute
583      *
584      * @return the attribute of this element
585      *     with the specified name and namespace
586      */
getAttribute(String localName, String namespaceURI)587     public final Attribute getAttribute(String localName,
588       String namespaceURI) {
589 
590         if (attributes == null) return null;
591         for (int i = 0; i < numAttributes; i++) {
592             Attribute a = attributes[i];
593             if (a.getLocalName().equals(localName)
594              && a.getNamespaceURI().equals(namespaceURI)) {
595                 return a;
596             }
597         }
598 
599         return null;
600 
601     }
602 
603 
604     /**
605      * <p>
606      * Returns the value of the attribute with the specified
607      * name in no namespace,
608      * or null if this element does not have an attribute
609      * with that name.
610      * </p>
611      *
612      * @param name the name of the attribute
613      *
614      * @return the value of the attribute of this element
615      *     with the specified name
616      */
getAttributeValue(String name)617     public final String getAttributeValue(String name) {
618         return getAttributeValue(name, "");
619     }
620 
621 
622     /**
623      *
624      * <p>
625      * Returns the number of attributes of this <code>Element</code>,
626      * not counting namespace declarations.
627      * This is always a non-negative number.
628      * </p>
629      *
630      * @return the number of attributes in the container
631      */
getAttributeCount()632     public final int getAttributeCount() {
633         return numAttributes;
634     }
635 
636 
637     /**
638      *
639      * <p>
640      * Selects an attribute by index.
641      * The index is purely for convenience and has no particular
642      * meaning. In particular, it is <em>not</em> necessarily the
643      * position of this attribute in the original document from
644      * which this <code>Element</code> object was read.
645      * As with most lists in Java, attributes are numbered
646      * from 0 to one less than the length of the list.
647      * </p>
648      *
649      * <p>
650      * In general, you should not add attributes to or remove
651      * attributes from the list while iterating across it.
652      * Doing so will change the indexes of the other attributes in
653      * the list. it is, however, safe to remove an attribute from
654      * either end of the list (0 or <code>getAttributeCount()-1</code>)
655      * until there are no attributes left.
656      * </p>
657      *
658      * @param index the attribute to return
659      *
660      * @return the index<sup>th</sup> attribute of this element
661      *
662      * @throws IndexOutofBoundsException if the index is negative
663      *   or greater than or equal to the number of attributes
664      *   of this element
665      *
666      */
getAttribute(int index)667     public final Attribute getAttribute(int index) {
668 
669         if (attributes == null) {
670             throw new IndexOutOfBoundsException(
671               "Element does not have any attributes"
672             );
673         }
674         return attributes[index];
675 
676     }
677 
678 
679     /**
680      * <p>
681      * Returns the value of the attribute with the
682      * specified name and namespace URI,
683      * or null if this element does not have such an attribute.
684      * </p>
685      *
686      * @param localName the name of the attribute
687      * @param namespaceURI the namespace of the attribute
688      *
689      * @return the value of the attribute of this element
690      *     with the specified name and namespace
691      */
getAttributeValue(String localName, String namespaceURI)692     public final String getAttributeValue(String localName,
693                                           String namespaceURI) {
694 
695         Attribute attribute = getAttribute(localName, namespaceURI);
696         if (attribute == null) return null;
697         else return attribute.getValue();
698 
699     }
700 
701 
702     /**
703      * <p>
704      * Returns the local name of this element, not including the
705      * namespace prefix or colon.
706      * </p>
707      *
708      * @return the local name of this element
709      */
getLocalName()710     public final String getLocalName() {
711         return localName;
712     }
713 
714 
715     /**
716      * <p>
717      * Returns the complete name of this element, including the
718      * namespace prefix if this element has one.
719      * </p>
720      *
721      * @return the qualified name of this element
722      */
getQualifiedName()723     public final String getQualifiedName() {
724         if (prefix.length() == 0) return localName;
725         else return prefix + ":" + localName;
726     }
727 
728 
729     /**
730      * <p>
731      * Returns the prefix of this element, or the empty string
732      * if this element does not have a prefix.
733      * </p>
734      *
735      * @return the prefix of this element
736      */
getNamespacePrefix()737     public final String getNamespacePrefix() {
738         return prefix;
739     }
740 
741 
742     /**
743      * <p>
744      * Returns the namespace URI of this element,
745      * or the empty string if this element is not
746      * in a namespace.
747      * </p>
748      *
749      * @return  the namespace URI of this element
750      */
getNamespaceURI()751     public final String getNamespaceURI() {
752         return URI;
753     }
754 
755 
756     /**
757      * <p>
758      * Returns the namespace URI mapped to the specified
759      * prefix within this element. Returns null if this prefix
760      * is not associated with a URI.
761      * </p>
762      *
763      * @param prefix the namespace prefix whose URI is desired
764      *
765      * @return the namespace URI mapped to <code>prefix</code>
766      */
getNamespaceURI(String prefix)767     public final String getNamespaceURI(String prefix) {
768 
769         Element current = this;
770         String result = getLocalNamespaceURI(prefix);
771         while (result == null) {
772             ParentNode parent = current.getParent();
773             if (parent == null || parent.isDocument()) break;
774             current = (Element) parent;
775             result = current.getLocalNamespaceURI(prefix);
776         }
777         if (result == null && "".equals(prefix)) result = "";
778         return result;
779 
780     }
781 
782 
getLocalNamespaceURI(String prefix)783     final String getLocalNamespaceURI(String prefix) {
784 
785         if (prefix.equals(this.prefix)) return this.URI;
786 
787         if ("xml".equals(prefix)) {
788             return "http://www.w3.org/XML/1998/namespace";
789         }
790         // This next line uses the original Namespaces 1.0
791         // specification rules.
792         // Namespaces 1.0 + errata is different
793         if ("xmlns".equals(prefix)) return "";
794         // Look in the additional namespace declarations
795         if (namespaces != null) {
796             String result = namespaces.getURI(prefix);
797             if (result != null) return result;
798         }
799         // Look in the attributes
800         if (prefix.length() != 0 && attributes != null) {
801             for (int i = 0; i < numAttributes; i++) {
802                 Attribute a = attributes[i];
803                 if (a.getNamespacePrefix().equals(prefix)) {
804                     return a.getNamespaceURI();
805                 }
806             }
807         }
808 
809         return null;
810 
811     }
812 
813 
814     /**
815      * <p>
816      * Sets the local name of this element.
817      * </p>
818      *
819      * @param localName the new local name
820      *
821      * @throws IllegalNameException if <code>localName</code> is not
822      *     a legal, non-colonized name
823      */
setLocalName(String localName)824     public void setLocalName(String localName) {
825         _setLocalName(localName);
826     }
827 
828 
_setLocalName(String localName)829     private void _setLocalName(String localName) {
830         Verifier.checkNCName(localName);
831         this.localName = localName;
832     }
833 
834 
835     /**
836      * <p>
837      * Sets the namespace URI of this element.
838      * </p>
839      *
840      * @param uri the new namespace URI
841      *
842      * @throws MalformedURIException if <code>uri</code>
843      *     is not an absolute RFC 3986 URI reference
844      * @throws NamespaceException if this element has a prefix
845      *     and <code>uri</code> is null or the empty string;
846      *     or if the element's prefix is shared by an attribute
847      *     or additional namespace
848      */
setNamespaceURI(String uri)849     public void setNamespaceURI(String uri) {
850         _setNamespaceURI(uri);
851     }
852 
853 
_setNamespaceURI(String uri)854     private void _setNamespaceURI(String uri) {
855 
856         if (uri == null) uri = "";
857         // Next line is needed to avoid unintentional
858         // exceptions below when checking for conflicts
859         if (uri.equals(this.URI)) return;
860         if (uri.length() == 0) { // faster than "".equals(uri)
861             if (prefix.length() != 0) {
862                 throw new NamespaceConflictException(
863                  "Prefixed elements must have namespace URIs."
864                 );
865             }
866         }
867         else Verifier.checkAbsoluteURIReference(uri);
868         // Make sure this doesn't conflict with any local
869         // attribute prefixes or additional namespace declarations
870         // Note that if the prefix equals the prefix, then the
871         // URI must equal the old URI, so the URI can't easily be
872         // changed. (you'd need to detach everything first;
873         // change the URIs, then put it all back together
874         if (namespaces != null) {
875         String result = namespaces.getURI(prefix);
876             if (result != null) {
877                 throw new NamespaceConflictException(
878                   "new URI conflicts with existing prefix"
879                 );
880             }
881         }
882         // Look in the attributes
883         if (uri.length() > 0 && attributes != null) {
884             for (int i = 0; i < numAttributes; i++) {
885                 Attribute a = attributes[i];
886                 String attPrefix = a.getNamespacePrefix();
887                 if (attPrefix.length() == 0) continue;
888                 if (a.getNamespacePrefix().equals(prefix)) {
889                     throw new NamespaceConflictException(
890                       "new element URI " + uri
891                       + " conflicts with attribute "
892                       + a.getQualifiedName()
893                     );
894                 }
895             }
896         }
897 
898         if ("http://www.w3.org/XML/1998/namespace".equals(uri)
899           && ! "xml".equals(prefix)) {
900             throw new NamespaceConflictException(
901               "Wrong prefix " + prefix +
902               " for the http://www.w3.org/XML/1998/namespace namespace URI"
903             );
904         }
905         else if ("xml".equals(prefix) &&
906           !"http://www.w3.org/XML/1998/namespace".equals(uri)) {
907             throw new NamespaceConflictException(
908               "Wrong namespace URI " + uri + " for the xml prefix"
909             );
910         }
911 
912         this.URI = uri;
913 
914     }
915 
916 
917     /**
918      * <p>
919      * Sets the namespace prefix of this element.
920      * You can pass null or the empty string to remove the prefix.
921      * </p>
922      *
923      * @param prefix the new namespace prefix
924      *
925      * @throws IllegalNameException if <code>prefix</code> is
926      *     not a legal XML non-colonized name
927      * @throws NamespaceConflictException if <code>prefix</code> is
928      *     already in use by an attribute or additional
929      *     namespace with a different URI than the element
930      *     itself
931      */
setNamespacePrefix(String prefix)932     public void setNamespacePrefix(String prefix) {
933         _setNamespacePrefix(prefix);
934     }
935 
936 
_setNamespacePrefix(String prefix)937     private void _setNamespacePrefix(String prefix) {
938 
939         if (prefix == null) prefix = "";
940 
941         if (prefix.length() != 0) Verifier.checkNCName(prefix);
942 
943         // Check how this affects or conflicts with
944         // attribute namespaces and additional
945         // namespace declarations.
946         String uri = getLocalNamespaceURI(prefix);
947         if (uri != null) {
948             if (!uri.equals(this.URI) && !"xml".equals(prefix)) {
949                 throw new NamespaceConflictException(prefix
950                  + " conflicts with existing prefix");
951             }
952         }
953         else if ("".equals(this.URI) && !"".equals(prefix)) {
954             throw new NamespaceConflictException(
955               "Cannot assign prefix to element in no namespace");
956         }
957 
958         this.prefix = prefix;
959 
960     }
961 
962 
963     /**
964      * <p>
965      * Inserts a child node at the specified position.
966      * Inserting at position 0 makes the child the first child
967      * of this node. Inserting at the position
968      * <code>getChildCount()</code>
969      * makes the child the last child of the node.
970      * </p>
971      *
972      * <p>
973      * All the other methods that add a node to the tree,
974      * invoke this method ultimately.
975      * </p>
976      *
977      * @param position where to insert the child
978      * @param child the node to insert
979      *
980      * @throws IllegalAddException if <code>child</code>
981      *   is a <code>Document</code>
982      * @throws MultipleParentException if <code>child</code>
983      *   already has a parent
984      * @throws NullPointerException if <code>child</code> is null
985      * @throws IndexOutOfBoundsException if the position is negative
986      *     or greater than the number of children of this element.
987      */
insertionAllowed(Node child, int position)988     void insertionAllowed(Node child, int position) {
989 
990         if (child == null) {
991             throw new NullPointerException(
992              "Tried to insert a null child in the tree");
993         }
994         else if (child.getParent() != null) {
995             throw new MultipleParentException(child.toString()
996               + " child already has a parent.");
997         }
998         else if (child.isElement()) {
999             checkCycle(child, this);
1000             return;
1001         }
1002         else if (child.isText()
1003           || child.isProcessingInstruction()
1004           || child.isComment()) {
1005             return;
1006         }
1007         else {
1008             throw new IllegalAddException("Cannot add a "
1009              + child.getClass().getName() + " to an Element.");
1010         }
1011 
1012     }
1013 
1014 
checkCycle(Node child, ParentNode parent)1015     private static void checkCycle(Node child, ParentNode parent) {
1016 
1017         if (child == parent) {
1018             throw new CycleException("Cannot add a node to itself");
1019         }
1020         if (child.getChildCount() == 0) return;
1021         while ((parent = parent.getParent()) != null) {
1022             if (parent == child) {
1023                 throw new CycleException(
1024                   "Cannot add an ancestor as a child");
1025             }
1026         }
1027 
1028     }
1029 
1030 
1031     /**
1032      * <p>
1033      * Converts a string to a text node and inserts that
1034      * node at the specified position.
1035      * </p>
1036      *
1037      * @param position where to insert the child
1038      * @param text the string to convert to a text node and insert
1039      *
1040      * @throws NullPointerException if text is null
1041      * @throws IndexOutOfBoundsException if the position is negative
1042      *     or greater than the number of children of the node
1043      */
insertChild(String text, int position)1044     public void insertChild(String text, int position) {
1045 
1046        if (text == null) {
1047            throw new NullPointerException("Inserted null string");
1048        }
1049        super.fastInsertChild(new Text(text), position);
1050 
1051     }
1052 
1053 
1054     /**
1055      * <p>
1056      * Converts a string to a text node
1057      * and appends that node to the children of this node.
1058      * </p>
1059      *
1060      * @param text String to add to this node
1061      *
1062      * @throws IllegalAddException if this node cannot
1063      *     have children of this type
1064      * @throws NullPointerException if <code>text</code> is null
1065      */
appendChild(String text)1066     public void appendChild(String text) {
1067         insertChild(new Text(text), getChildCount());
1068     }
1069 
1070 
1071     /**
1072      * <p>
1073      * Detaches all children from this node.
1074      * </p>
1075      *
1076      * <p>
1077      * Subclassers should note that the default implementation of this
1078      * method does <strong>not</strong> call <code>removeChild</code>
1079      * or <code>detach</code>. If you override
1080      * <code>removeChild</code>, you'll probably need to override this
1081      * method as well.
1082      * </p>
1083      *
1084      * @return a list of all the children removed in the order they
1085      *     appeared in the element
1086      */
removeChildren()1087     public Nodes removeChildren() {
1088 
1089         int length = this.getChildCount();
1090         Nodes result = new Nodes();
1091         for (int i = 0; i < length; i++) {
1092             Node child = getChild(i);
1093             if (child.isElement()) fillInBaseURI((Element) child);
1094             child.setParent(null);
1095             result.append(child);
1096         }
1097         this.children = null;
1098         this.childCount = 0;
1099 
1100         return result;
1101 
1102     }
1103 
1104 
1105     /**
1106      * <p>
1107      * Declares a namespace prefix. This is only necessary when
1108      * prefixes are used in element content and attribute values,
1109      * as in XSLT and the W3C XML Schema Language. Do not use
1110      * this method to declare prefixes for element and attribute
1111      * names.
1112      * </p>
1113      *
1114      * <p>
1115      * You can supply an empty string for the prefix to declare a
1116      * default namespace, provided the element itself has a prefix.
1117      * </p>
1118      *
1119      * <p>
1120      * If you do redeclare a prefix that is already used
1121      * by an element or attribute name, the additional
1122      * namespace is added if and only if the URI is the same.
1123      * Conflicting namespace declarations will throw an exception.
1124      * </p>
1125      *
1126      * @param prefix the prefix to declare
1127      * @param uri the absolute URI reference to map the prefix to
1128      *
1129      * @throws MalformedURIException if <code>URI</code>
1130      *      is not an RFC 3986 URI reference
1131      * @throws IllegalNameException  if <code>prefix</code> is not
1132      *      a legal XML non-colonized name or the empty string
1133      * @throws NamespaceConflictException if the mapping conflicts
1134      *     with an existing element, attribute,
1135      *     or additional namespace declaration
1136      */
addNamespaceDeclaration(String prefix, String uri)1137     public void addNamespaceDeclaration(String prefix, String uri) {
1138 
1139         if (prefix == null) prefix = "";
1140         if (uri == null) uri = "";
1141 
1142         // check to see if this is the xmlns or xml prefix
1143         if (prefix.equals("xmlns")) {
1144             if (uri.equals("")) {
1145                 // This is done automatically
1146                 return;
1147             }
1148             throw new NamespaceConflictException(
1149              "The xmlns prefix cannot bound to any URI");
1150         }
1151         else if (prefix.equals("xml")) {
1152             if (uri.equals("http://www.w3.org/XML/1998/namespace")) {
1153                 // This is done automatically
1154                 return;
1155             }
1156             throw new NamespaceConflictException(
1157               "Wrong namespace URI for xml prefix: " + uri);
1158         }
1159         else if (uri.equals("http://www.w3.org/XML/1998/namespace")) {
1160             throw new NamespaceConflictException(
1161                "Wrong prefix for http://www.w3.org/XML/1998/namespace namespace: "
1162                + prefix);
1163         }
1164 
1165         if (prefix.length() != 0) {
1166             Verifier.checkNCName(prefix);
1167             Verifier.checkAbsoluteURIReference(uri);
1168         }
1169         else if (uri.length() != 0) {
1170             // Make sure we're not trying to undeclare
1171             // the default namespace; this is legal.
1172             Verifier.checkAbsoluteURIReference(uri);
1173         }
1174 
1175         String currentBinding = getLocalNamespaceURI(prefix);
1176         if (currentBinding != null && !currentBinding.equals(uri)) {
1177 
1178             String message;
1179             if (prefix.equals("")) {
1180                 message =  "Additional namespace " + uri
1181                   + " conflicts with existing default namespace "
1182                   + currentBinding;
1183             }
1184             else {
1185                 message = "Additional namespace " + uri
1186                   + " for the prefix " + prefix
1187                   + " conflicts with existing namespace binding "
1188                   + currentBinding;
1189             }
1190             throw new NamespaceConflictException(message);
1191         }
1192 
1193         if (namespaces == null) namespaces = new Namespaces();
1194         namespaces.put(prefix, uri);
1195 
1196     }
1197 
1198 
1199     /**
1200      * <p>
1201      * Removes the mapping of the specified prefix. This method only
1202      * removes additional namespaces added with
1203      * <code>addNamespaceDeclaration</code>.
1204      * It has no effect on namespaces of elements and attributes.
1205      * If the prefix is not used on this element, this method
1206      * does nothing.
1207      * </p>
1208      *
1209      * @param prefix the prefix whose declaration should be removed
1210      */
removeNamespaceDeclaration(String prefix)1211     public void removeNamespaceDeclaration(String prefix) {
1212 
1213         if (namespaces != null) {
1214             namespaces.remove(prefix);
1215         }
1216 
1217     }
1218 
1219 
1220     /**
1221      * <p>
1222      * Returns the number of namespace declarations on this
1223      * element. This counts the namespace of the element
1224      * itself (which may be the empty string), the namespace
1225      * of each attribute, and each namespace added
1226      * by <code>addNamespaceDeclaration</code>.
1227      * However, prefixes used multiple times are only counted
1228      * once; and the <code>xml</code> prefix used for
1229      * <code>xml:base</code>, <code>xml:lang</code>, and
1230      * <code>xml:space</code> is not counted even if one of these
1231      * attributes is present on the element.
1232      * </p>
1233      *
1234      * <p>
1235      * The return value is almost always positive. It can be zero
1236      * if and only if the element itself has the prefix
1237      * <code>xml</code>; e.g. <code>&lt;xml:space /></code>.
1238      * This is not endorsed by the XML specification. The prefix
1239      * <code>xml</code> is reserved for use by the W3C, which has only
1240      * used it for attributes to date. You really shouldn't do this.
1241      * Nonetheless, this is not malformed so XOM allows it.
1242      * </p>
1243      *
1244      * @return the number of namespaces declared by this element
1245      */
getNamespaceDeclarationCount()1246     public final int getNamespaceDeclarationCount() {
1247 
1248         // This seems to be a hot spot for DOM conversion.
1249         // I'm trying to avoid the overhead of creating and adding
1250         // to a HashSet for the simplest case of an element, none
1251         // of whose attributes are in namespaces, and which has no
1252         // additional namespace declarations. In this case, the
1253         // namespace count is exactly one, which is here indicated
1254         // by a null prefix set.
1255         Set allPrefixes = null;
1256         if (namespaces != null) {
1257             allPrefixes = new HashSet(namespaces.getPrefixes());
1258             allPrefixes.add(prefix);
1259         }
1260         if ("xml".equals(prefix)) allPrefixes = new HashSet();
1261         // add attribute prefixes
1262         int count = getAttributeCount();
1263         for (int i = 0; i < count; i++) {
1264             Attribute att = attributes[i];
1265             String attPrefix = att.getNamespacePrefix();
1266             if (attPrefix.length() != 0 && !"xml".equals(attPrefix)) {
1267                 if (allPrefixes == null) {
1268                     allPrefixes = new HashSet();
1269                     allPrefixes.add(prefix);
1270                 }
1271                 allPrefixes.add(attPrefix);
1272             }
1273         }
1274 
1275         if (allPrefixes == null) return 1;
1276         return allPrefixes.size();
1277 
1278     }
1279 
1280 
1281     // Used for XPath and serialization
getNamespacePrefixesInScope()1282     Map getNamespacePrefixesInScope() {
1283 
1284         HashMap namespaces = new HashMap();
1285 
1286         Element current = this;
1287         while (true) {
1288 
1289             if (!("xml".equals(current.prefix))) {
1290                 addPrefixIfNotAlreadyPresent(namespaces, current, current.prefix);
1291             }
1292 
1293             // add attribute prefixes
1294             if (current.attributes != null) {
1295                 int count = current.numAttributes;
1296                 for (int i = 0; i < count; i++) {
1297                     Attribute att = current.attributes[i];
1298                     String attPrefix = att.getNamespacePrefix();
1299                     if (attPrefix.length() != 0 && !("xml".equals(attPrefix))) {
1300                         addPrefixIfNotAlreadyPresent(namespaces, current, attPrefix);
1301                     }
1302                 }
1303             }
1304 
1305             // add additional namespace prefixes
1306             // ???? should add more methods to Namespaces to avoid access to private data
1307             if (current.namespaces != null) {
1308                 int namespaceCount = current.namespaces.size();
1309                 for (int i = 0; i < namespaceCount; i++) {
1310                     String nsPrefix = current.namespaces.getPrefix(i);
1311                     addPrefixIfNotAlreadyPresent(namespaces, current, nsPrefix);
1312                 }
1313             }
1314 
1315             ParentNode parent = current.getParent();
1316             if (parent == null || parent.isDocument() || parent.isDocumentFragment()) {
1317                 break;
1318             }
1319             current = (Element) parent;
1320         }
1321 
1322         return namespaces;
1323 
1324     }
1325 
1326 
addPrefixIfNotAlreadyPresent(HashMap namespaces, Element current, String prefix)1327     private void addPrefixIfNotAlreadyPresent(HashMap namespaces, Element current, String prefix) {
1328         if (!namespaces.containsKey(prefix)) {
1329             namespaces.put(prefix, current.getLocalNamespaceURI(prefix));
1330         }
1331     }
1332 
1333 
1334     /**
1335      * <p>
1336      * Returns the index<sup>th</sup> namespace prefix <em>declared</em> on
1337      * this element. Namespaces inherited from ancestors are not included.
1338      * The index is purely for convenience, and has no
1339      * meaning in itself. This includes the namespaces of the element
1340      * name and of all attributes' names (except for those with the
1341      * prefix <code>xml</code> such as <code>xml:space</code>) as well
1342      * as additional declarations made for attribute values and element
1343      * content. However, prefixes used multiple times (e.g. on several
1344      * attribute values) are only reported once. The default
1345      * namespace is reported with an empty string prefix if present.
1346      * Like most lists in Java, the first prefix is at index 0.
1347      * </p>
1348      *
1349      * <p>
1350      * If the namespaces on the element change for any reason
1351      * (adding or removing an attribute in a namespace, adding
1352      * or removing a namespace declaration, changing the prefix
1353      * of an element, etc.) then this method may skip or repeat
1354      * prefixes. Don't change the prefixes of an element while
1355      * iterating across them.
1356      * </p>
1357      *
1358      * @param index the prefix to return
1359      *
1360      * @return the prefix
1361      *
1362      * @throws IndexOutOfBoundsException if <code>index</code> is
1363      *     negative or greater than or equal to the number of
1364      *     namespaces declared by this element.
1365      *
1366      */
getNamespacePrefix(int index)1367     public final String getNamespacePrefix(int index) {
1368 
1369         // XXX can I reuse set? or not use a Set at all?
1370         // suppose the zeroth prefix is the namespace prefix is the first prefix
1371         // found for the element, attributes, and namespace declarations, in that order.
1372         // The second prefix is the next and so on.
1373 
1374         if (index < 0) {
1375             throw new IndexOutOfBoundsException(
1376               "Negative prefix number " + index);
1377         }
1378         else if (index == 0) {
1379             if (!("xml".equals(prefix))) return prefix;
1380         }
1381 
1382 
1383         Set allPrefixes = getNamespacePrefixes();
1384         Iterator iterator = allPrefixes.iterator();
1385         try {
1386             for (int i = 0; i < index; i++) {
1387                 iterator.next();
1388             }
1389             return (String) iterator.next();
1390         }
1391         catch (NoSuchElementException ex) {
1392             throw new IndexOutOfBoundsException(
1393               // ???? fix to use 3rd, 2nd, 1st as appropriate
1394               "No " + index + "th namespace");
1395         }
1396 
1397     }
1398 
1399 
getNamespacePrefixes()1400     private Set getNamespacePrefixes() {
1401 
1402         Set allPrefixes = new LinkedHashSet();
1403         if (!("xml".equals(prefix))) allPrefixes.add(prefix);
1404         if (namespaces != null) {
1405             allPrefixes.addAll(namespaces.getPrefixes());
1406         }
1407 
1408         if (attributes != null) {
1409             int count = getAttributeCount();
1410             for (int i = 0; i < count; i++) {
1411                 Attribute att = attributes[i];
1412                 String attPrefix = att.getNamespacePrefix();
1413                 if (attPrefix.length() != 0 && !("xml".equals(attPrefix))) {
1414                     allPrefixes.add(attPrefix);
1415                 }
1416             }
1417         }
1418         return allPrefixes;
1419 
1420     }
1421 
1422 
1423     /**
1424      *
1425      * <p>
1426      * Sets the URI from which this element was loaded,
1427      * and against which relative URLs in this element will be
1428      * resolved, unless an <code>xml:base</code> attribute overrides
1429      * this. Setting the base URI to null or the empty string removes
1430      * any existing base URI.
1431      * </p>
1432      *
1433      * @param URI the new base URI for this element
1434      *
1435      * @throws MalformedURIException if <code>URI</code> is
1436      *     not a legal RFC 3986 absolute URI
1437      */
setBaseURI(String URI)1438     public void setBaseURI(String URI) {
1439         setActualBaseURI(URI);
1440     }
1441 
1442 
1443     /**
1444      *
1445      * <p>
1446      * Returns the absolute base URI against which relative URIs in
1447      * this element should be resolved. <code>xml:base</code>
1448      * attributes <em>in the same entity</em> take precedence over the
1449      * actual base URI of the document where the element was found
1450      * or which was set by <code>setBaseURI</code>.
1451      * </p>
1452      *
1453      * <p>
1454      * This URI is made absolute before it is returned
1455      * by resolving the information in this element against the
1456      * information in its parent element and document entity.
1457      * However, it is not always possible to fully absolutize the
1458      * URI in all circumstances. In this case, this method returns the
1459      * empty string to indicate the base URI of the current entity.
1460      * </p>
1461      *
1462      * <p>
1463      * If the element's <code>xml:base</code> attribute contains a
1464      * value that is a syntactically illegal URI (e.g. %GF.html"),
1465      * then the base URI is application dependent. XOM's choice is
1466      * to behave as if the element did not have an <code>xml:base</code>
1467      * attribute.
1468      * </p>
1469      *
1470      * @return the base URI of this element
1471      */
getBaseURI()1472      public String getBaseURI() {
1473 
1474         String baseURI = "";
1475         String sourceEntity = this.getActualBaseURI();
1476 
1477         ParentNode current = this;
1478 
1479         while (true) {
1480             String currentEntity = current.getActualBaseURI();
1481             if (sourceEntity.length() != 0
1482               && ! sourceEntity.equals(currentEntity)) {
1483                 baseURI = URIUtil.absolutize(sourceEntity, baseURI);
1484                 break;
1485             }
1486 
1487             if (current.isDocument()) {
1488                 baseURI = URIUtil.absolutize(currentEntity, baseURI);
1489                 break;
1490             }
1491             Attribute baseAttribute = ((Element) current).getAttribute("base",
1492               "http://www.w3.org/XML/1998/namespace");
1493             if (baseAttribute != null) {
1494                 String baseIRI = baseAttribute.getValue();
1495                 // The base attribute contains an IRI, not a URI.
1496                 // Thus the first thing we have to do is escape it
1497                 // to convert illegal characters to hexadecimal escapes.
1498                 String base = URIUtil.toURI(baseIRI);
1499                 if ("".equals(base)) {
1500                     baseURI = getEntityURI();
1501                     break;
1502                 }
1503                 else if (legalURI(base)) {
1504                     if ("".equals(baseURI)) baseURI = base;
1505                     else if (URIUtil.isOpaque(base)) break;
1506                     else baseURI = URIUtil.absolutize(base, baseURI);
1507                     if (URIUtil.isAbsolute(base)) break;  // ???? base or baseURI
1508                 }
1509             }
1510             current = current.getParent();
1511             if (current == null) {
1512                 baseURI = URIUtil.absolutize(currentEntity, baseURI);
1513                 break;
1514             }
1515         }
1516 
1517         if (URIUtil.isAbsolute(baseURI)) return baseURI;
1518         return "";
1519 
1520     }
1521 
1522 
getEntityURI()1523     private String getEntityURI() {
1524 
1525         ParentNode current = this;
1526         while (current != null) {
1527             if (current.actualBaseURI != null
1528               && current.actualBaseURI.length() != 0) {
1529                 return current.actualBaseURI;
1530             }
1531             current = current.getParent();
1532         }
1533         return "";
1534 
1535     }
1536 
1537 
legalURI(String base)1538     private boolean legalURI(String base) {
1539 
1540         try {
1541             Verifier.checkURIReference(base);
1542             return true;
1543         }
1544         catch (MalformedURIException ex) {
1545             return false;
1546         }
1547 
1548     }
1549 
1550 
1551     /**
1552      * <p>
1553      * Returns a string containing the XML serialization of this
1554      * element. This includes the element and all its attributes
1555      * and descendants. However, it does not contain namespace
1556      * declarations for namespaces inherited from ancestor elements.
1557      * </p>
1558      *
1559      * @return the XML representation of this element
1560      *
1561      */
toXML()1562     public final String toXML() {
1563 
1564         StringBuffer result = new StringBuffer(1024);
1565         Node current = this;
1566         boolean endTag = false;
1567         int index = -1;
1568         int[] indexes = new int[10];
1569         int top = 0;
1570         indexes[0] = -1;
1571 
1572         while (true) {
1573 
1574             if (!endTag && current.getChildCount() > 0) {
1575                writeStartTag((Element) current, result);
1576                current = current.getChild(0);
1577                index = 0;
1578                top++;
1579                indexes = grow(indexes, top);
1580                indexes[top] = 0;
1581             }
1582             else {
1583               if (endTag) {
1584                  writeEndTag((Element) current, result);
1585                  if (current == this) break;
1586               }
1587               else if (current.isElement()) {
1588                  writeStartTag((Element) current, result);
1589                  if (current == this) break;
1590               }
1591               else {
1592                   result.append(current.toXML());
1593               }
1594               endTag = false;
1595               ParentNode parent = current.getParent();
1596               if (parent.getChildCount() - 1 == index) {
1597                 current = parent;
1598                 top--;
1599                 if (current != this) {
1600                     index = indexes[top];
1601                 }
1602                 endTag = true;
1603               }
1604               else {
1605                  index++;
1606                  indexes[top] = index;
1607                  current = parent.getChild(index);
1608               }
1609 
1610             }
1611 
1612         }
1613 
1614         return result.toString();
1615 
1616     }
1617 
1618 
writeStartTag(Element element, StringBuffer result)1619     private static void writeStartTag(Element element, StringBuffer result) {
1620 
1621         result.append('<');
1622         result.append(element.getQualifiedName());
1623 
1624         ParentNode parentNode = element.getParent();
1625         for (int i = 0; i < element.getNamespaceDeclarationCount(); i++) {
1626             String additionalPrefix = element.getNamespacePrefix(i);
1627             String uri = element.getNamespaceURI(additionalPrefix);
1628             if (parentNode != null && parentNode.isElement()) {
1629                Element parentElement = (Element) parentNode;
1630                if (uri.equals(
1631                  parentElement.getNamespaceURI(additionalPrefix))) {
1632                    continue;
1633                }
1634             }
1635             else if (uri.length() == 0) {
1636                 continue; // no need to say xmlns=""
1637             }
1638 
1639             result.append(" xmlns");
1640             if (additionalPrefix.length() > 0) {
1641                 result.append(':');
1642                 result.append(additionalPrefix);
1643             }
1644             result.append("=\"");
1645             result.append(escape(uri));
1646             result.append('"');
1647         }
1648 
1649         // attributes
1650         if (element.attributes != null) {
1651             for (int i = 0; i < element.numAttributes; i++) {
1652                 Attribute attribute = element.attributes[i];
1653                 result.append(' ');
1654                 result.append(attribute.toXML());
1655             }
1656         }
1657 
1658         if (element.getChildCount() > 0) {
1659             result.append('>');
1660         }
1661         else {
1662             result.append(" />");
1663         }
1664 
1665     }
1666 
1667 
escape(String s)1668     private static String escape(String s) {
1669 
1670         int length = s.length();
1671         // Give the string buffer enough room for a couple of escaped characters
1672         StringBuffer result = new StringBuffer(length+12);
1673         for (int i = 0; i < length; i++) {
1674             char c = s.charAt(i);
1675             if (c == '&') result.append("&amp;");
1676             else result.append(c);
1677         }
1678         return result.toString();
1679 
1680     }
1681 
1682 
writeEndTag( Element element, StringBuffer result)1683     private static void writeEndTag(
1684       Element element, StringBuffer result) {
1685 
1686         result.append("</");
1687         result.append(element.getQualifiedName());
1688         result.append('>');
1689 
1690     }
1691 
1692 
1693     /**
1694      * <p>
1695      * Returns the value of the element as defined by XPath 1.0.
1696      * This is the complete PCDATA content of the element, without
1697      * any tags, comments, or processing instructions after all
1698      * entity and character references have been resolved.
1699      * </p>
1700      *
1701      * @return XPath string value of this element
1702      *
1703      */
getValue()1704     public final String getValue() {
1705 
1706         // non-recursive algorithm avoids stack size limitations
1707         int childCount = this.getChildCount();
1708         if (childCount == 0) return "";
1709 
1710         Node current = this.getChild(0);
1711         // optimization for common case where element
1712         // has a single text node child
1713         if (childCount == 1 && current.isText()) {
1714             return current.getValue();
1715         }
1716 
1717         StringBuffer result = new StringBuffer();
1718         int index = 0;
1719         int[] indexes = new int[10];
1720         int top = 0;
1721         indexes[0] = 0;
1722 
1723         boolean endTag = false;
1724         while (true) {
1725             if (!endTag && current.getChildCount() > 0) {
1726                current = current.getChild(0);
1727                index = 0;
1728                top++;
1729                indexes = grow(indexes, top);
1730                indexes[top] = 0;            }
1731             else {
1732                 endTag = false;
1733                 if (current.isText()) result.append(current.getValue());
1734                 ParentNode parent = current.getParent();
1735                 if (parent.getChildCount() - 1 == index) {
1736                     current = parent;
1737                     top--;
1738                     if (current == this) break;
1739                     index = indexes[top];
1740                     endTag = true;
1741                 }
1742                 else {
1743                     index++;
1744                     indexes[top] = index;
1745                     current = parent.getChild(index);
1746                 }
1747             }
1748         }
1749 
1750         return result.toString();
1751 
1752     }
1753 
1754     /**
1755      * <p>
1756      * Creates a deep copy of this element with no parent,
1757      * that can be added to this document or a different one.
1758      * </p>
1759      *
1760      * <p>
1761      * Subclassers should be wary. Implementing this method is trickier
1762      * than it might seem, especially if you wish to avoid potential
1763      * stack overflows in deep documents. In particular, you should not
1764      * rely on the obvious recursive algorithm. Most subclasses should
1765      * override the {@link nu.xom.Element#shallowCopy() shallowCopy}
1766      * method instead.
1767      * </p>
1768      *
1769      * @return a deep copy of this element with no parent
1770      */
copy()1771     public Node copy() {
1772         Element result = copyTag(this);
1773         copyChildren(this, result);
1774         return result;
1775     }
1776 
1777 
1778     /**
1779      * <p>
1780      * Creates a very shallow copy of the element with the same name
1781      * and namespace URI, but no children, attributes, base URI, or
1782      * namespace declaration. This method is invoked as necessary
1783      * by the {@link nu.xom.Element#copy() copy} method
1784      * and the {@link nu.xom.Element#Element(nu.xom.Element)
1785      * copy constructor}.
1786      * </p>
1787      *
1788      * <p>
1789      * Subclasses should override this method so that it
1790      * returns an instance of the subclass so that types
1791      * are preserved when copying. This method should not add any
1792      * attributes, namespace declarations, or children to the
1793      * shallow copy. Any such items will be overwritten.
1794      * </p>
1795      *
1796      * @return an empty element with the same name and
1797      *     namespace as this element
1798      */
shallowCopy()1799     protected Element shallowCopy() {
1800         return new Element(getQualifiedName(), getNamespaceURI());
1801     }
1802 
1803 
1804     /**
1805      * <p>
1806      * Returns a string representation of this element suitable
1807      * for debugging and diagnosis. This is <em>not</em>
1808      * the XML representation of the element.
1809      * </p>
1810      *
1811      * @return a non-XML string representation of this element
1812      */
toString()1813     public String toString() {
1814         return
1815           "[" + getClass().getName() + ": " + getQualifiedName() + "]";
1816     }
1817 
1818 
isElement()1819     boolean isElement() {
1820         return true;
1821     }
1822 
1823 
checkPrefixConflict(Attribute attribute)1824     private void checkPrefixConflict(Attribute attribute) {
1825 
1826         String prefix = attribute.getNamespacePrefix();
1827         String namespaceURI = attribute.getNamespaceURI();
1828 
1829         // Look for conflicts
1830         for (int i = 0; i < numAttributes; i++) {
1831             Attribute a = attributes[i];
1832             if (a.getNamespacePrefix().equals(prefix)) {
1833               if (a.getNamespaceURI().equals(namespaceURI)) return;
1834               throw new NamespaceConflictException(
1835                 "Prefix of " + attribute.getQualifiedName()
1836                 + " conflicts with " + a.getQualifiedName());
1837             }
1838         }
1839 
1840     }
1841 
1842 
1843     private class AttributeIterator implements Iterator {
1844 
1845       private int next = 0;
1846 
hasNext()1847       public boolean hasNext() {
1848           return next < numAttributes;
1849       }
1850 
next()1851       public Object next() throws NoSuchElementException {
1852 
1853           if (hasNext()) {
1854               Attribute a = attributes[next];
1855               next++;
1856               return a;
1857           }
1858           throw new NoSuchElementException("No such attribute");
1859 
1860       }
1861 
remove()1862       public void remove() {
1863           throw new UnsupportedOperationException();
1864       }
1865     }
1866 
attributeIterator()1867     Iterator attributeIterator() {
1868 
1869         return new AttributeIterator();
1870     }
1871 
1872 
1873 }