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><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("&"); 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 }