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