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@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 /** 25 * <p> 26 * This class represents an attribute such as 27 * <code>type="empty"</code> or 28 * <code>xlink:href="http://www.example.com"</code>. 29 * </p> 30 * 31 * <p> 32 * Attributes that declare namespaces such as 33 * <code>xmlns="http://www.w3.org/TR/1999/xhtml"</code> 34 * or <code>xmlns:xlink="http://www.w3.org/TR/1999/xlink"</code> 35 * are stored separately on the elements where they 36 * appear. They are never represented as <code>Attribute</code> 37 * objects. 38 * </p> 39 * 40 * @author Elliotte Rusty Harold 41 * @version 1.2b2 42 * 43 */ 44 public class Attribute extends Node { 45 46 private String localName; 47 private String prefix; 48 private String URI; 49 private String value = ""; 50 private Type type; 51 52 53 /** 54 * <p> 55 * Creates a new attribute in no namespace with the 56 * specified name and value and undeclared type. 57 * </p> 58 * 59 * @param localName the unprefixed attribute name 60 * @param value the attribute value 61 * 62 * @throws IllegalNameException if the local name is not 63 * a namespace well-formed, non-colonized name 64 * @throws IllegalDataException if the value contains characters 65 * which are not legal in XML such as vertical tab or a null. 66 * Characters such as " and & are legal, but will be 67 * automatically escaped when the attribute is serialized. 68 */ Attribute(String localName, String value)69 public Attribute(String localName, String value) { 70 this(localName, "", value, Type.UNDECLARED); 71 } 72 73 74 /** 75 * <p> 76 * Creates a new attribute in no namespace with the 77 * specified name, value, and type. 78 * </p> 79 * 80 * @param localName the unprefixed attribute name 81 * @param value the attribute value 82 * @param type the attribute type 83 * 84 * @throws IllegalNameException if the local name is 85 * not a namespace well-formed non-colonized name 86 * @throws IllegalDataException if the value contains 87 * characters which are not legal in 88 * XML such as vertical tab or a null. Note that 89 * characters such as " and & are legal, 90 * but will be automatically escaped when the 91 * attribute is serialized. 92 */ Attribute(String localName, String value, Type type)93 public Attribute(String localName, String value, Type type) { 94 this(localName, "", value, type); 95 } 96 97 98 /** 99 * <p> 100 * Creates a new attribute in the specified namespace with the 101 * specified name and value and undeclared type. 102 * </p> 103 * 104 * @param name the prefixed attribute name 105 * @param URI the namespace URI 106 * @param value the attribute value 107 * 108 * @throws IllegalNameException if the name is not a namespace 109 * well-formed name 110 * @throws IllegalDataException if the value contains characters 111 * which are not legal in XML such as vertical tab or a null. 112 * Note that characters such as " and & are legal, but will 113 * be automatically escaped when the attribute is serialized. 114 * @throws MalformedURIException if <code>URI</code> is not 115 * an RFC 3986 URI reference 116 * @throws NamespaceConflictException if there's no prefix, 117 * but the URI is not the empty string, or the prefix is 118 * <code>xml</code> and the URI is not 119 * http://www.w3.org/XML/1998/namespace 120 */ Attribute(String name, String URI, String value)121 public Attribute(String name, String URI, String value) { 122 this(name, URI, value, Type.UNDECLARED); 123 } 124 125 126 /** 127 * <p> 128 * Creates a new attribute in the specified namespace with the 129 * specified name, value, and type. 130 * </p> 131 * 132 * @param name the prefixed attribute name 133 * @param URI the namespace URI 134 * @param value the attribute value 135 * @param type the attribute type 136 * 137 * @throws IllegalNameException if the name is not a namespace 138 * well-formed prefixed name 139 * @throws IllegalDataException if the value contains 140 * characters which are not legal in XML such as 141 * vertical tab or a null. Note that characters such as 142 * " and & are legal, but will be automatically escaped 143 * when the attribute is serialized. 144 * @throws MalformedURIException if <code>URI</code> is not 145 * an RFC 3986 absolute URI reference 146 */ Attribute( String name, String URI, String value, Type type)147 public Attribute( 148 String name, String URI, String value, Type type) { 149 150 prefix = ""; 151 String localName = name; 152 int prefixPosition = name.indexOf(':'); 153 if (prefixPosition > 0) { 154 prefix = name.substring(0, prefixPosition); 155 localName = name.substring(prefixPosition + 1); 156 } 157 158 try { 159 _setLocalName(localName); 160 } 161 catch (IllegalNameException ex) { 162 ex.setData(name); 163 throw ex; 164 } 165 _setNamespace(prefix, URI); 166 _setValue(value); 167 if (isXMLID()) { 168 _setType(Attribute.Type.ID); 169 } 170 else { 171 _setType(type); 172 } 173 174 } 175 176 177 /** 178 * <p> 179 * Creates a copy of the specified attribute. 180 * </p> 181 * 182 * @param attribute the attribute to copy 183 * 184 */ Attribute(Attribute attribute)185 public Attribute(Attribute attribute) { 186 187 // These are all immutable types 188 this.localName = attribute.localName; 189 this.prefix = attribute.prefix; 190 this.URI = attribute.URI; 191 this.value = attribute.value; 192 this.type = attribute.type; 193 194 } 195 196 Attribute()197 private Attribute() {} 198 build( String qualifiedName, String URI, String value, Type type, String localName)199 static Attribute build( 200 String qualifiedName, String URI, String value, Type type, String localName) { 201 202 Attribute result = new Attribute(); 203 String prefix = ""; 204 int prefixPosition = qualifiedName.indexOf(':'); 205 if (prefixPosition >= 0) { 206 prefix = qualifiedName.substring(0, prefixPosition); 207 if ("xml:id".equals(qualifiedName)) { 208 type = Attribute.Type.ID; 209 value = normalize(value); 210 } 211 } 212 213 result.localName = localName; 214 result.prefix = prefix; 215 result.type = type; 216 result.URI = URI; 217 result.value = value; 218 219 return result; 220 221 } 222 223 normalize(String s)224 private static String normalize(String s) { 225 226 int length = s.length(); 227 int pos = 0; 228 while (pos < length && s.charAt(pos) == ' ') pos++; 229 s = s.substring(pos); 230 int end = s.length()-1; 231 while (end > 0 && s.charAt(end) == ' ') end--; 232 s = s.substring(0, end+1); 233 234 length = s.length(); 235 StringBuffer sb = new StringBuffer(length); 236 boolean wasSpace = false; 237 for (int i = 0; i < length; i++) { 238 char c = s.charAt(i); 239 if (c == ' ') { 240 if (wasSpace) continue; 241 sb.append(' '); 242 wasSpace = true; 243 } 244 else { 245 sb.append(c); 246 wasSpace = false; 247 } 248 } 249 return sb.toString(); 250 251 } 252 253 254 /** 255 * <p> 256 * Returns the DTD type of this attribute. 257 * If this attribute does not have a type, then 258 * <code>Type.UNDECLARED</code> is returned. 259 * </p> 260 * 261 * @return the DTD type of this attribute 262 */ getType()263 public final Type getType() { 264 return type; 265 } 266 267 268 /** 269 * <p> 270 * Sets the type of this attribute to one of the ten 271 * DTD types or <code>Type.UNDECLARED</code>. 272 * </p> 273 * 274 * @param type the DTD type of this attribute 275 * @throws NullPointerException if <code>type</code> is null 276 * @throws IllegalDataException if this is an <code>xml:id</code> 277 * attribute and the <code>type</code> is not ID 278 */ setType(Type type)279 public void setType(Type type) { 280 281 if (type == null) { 282 throw new NullPointerException("Null attribute type"); 283 } 284 if (isXMLID() && ! Type.ID.equals(type)) { 285 throw new IllegalDataException( 286 "Can't change type of xml:id attribute to " + type); 287 } 288 _setType(type); 289 290 } 291 292 isXMLID()293 private boolean isXMLID() { 294 return "xml".equals(this.prefix) && "id".equals(this.localName); 295 } 296 297 _setType(Type type)298 private void _setType(Type type) { 299 this.type = type; 300 } 301 302 303 /** 304 * <p> 305 * Returns the attribute value. If the attribute was 306 * originally created by a parser, it will have been 307 * normalized according to its type. 308 * However, attributes created in memory are not normalized. 309 * </p> 310 * 311 * @return the value of the attribute 312 * 313 */ getValue()314 public final String getValue() { 315 return value; 316 } 317 318 319 /** 320 * <p> 321 * Sets the attribute's value to the specified string, 322 * replacing any previous value. The value is not normalized 323 * automatically. 324 * </p> 325 * 326 * @param value the value assigned to the attribute 327 * 328 * @throws IllegalDataException if the value contains characters 329 * which are not legal in XML such as vertical tab or a null. 330 * Characters such as " and & are legal, but will be 331 * automatically escaped when the attribute is serialized. 332 */ setValue(String value)333 public void setValue(String value) { 334 _setValue(value); 335 } 336 337 _setValue(String value)338 private void _setValue(String value) { 339 Verifier.checkPCDATA(value); 340 if (this.isXMLID()) { 341 value = normalize(value); 342 } 343 this.value = value; 344 } 345 346 347 /** 348 * <p> 349 * Returns the local name of this attribute, 350 * not including the prefix. 351 * </p> 352 * 353 * @return the attribute's local name 354 */ getLocalName()355 public final String getLocalName() { 356 return localName; 357 } 358 359 360 /** 361 * <p> 362 * Sets the local name of the attribute. 363 * </p> 364 * 365 * @param localName the new local name 366 * 367 * @throws IllegalNameException if <code>localName</code> 368 * is not a namespace well-formed, non-colonized name 369 * 370 */ setLocalName(String localName)371 public void setLocalName(String localName) { 372 373 if ("id".equals(localName) && 374 "http://www.w3.org/XML/1998/namespace".equals(this.URI)) { 375 Verifier.checkNCName(this.value); 376 } 377 _setLocalName(localName); 378 if (isXMLID()) { 379 this.setType(Attribute.Type.ID); 380 } 381 382 } 383 384 _setLocalName(String localName)385 private void _setLocalName(String localName) { 386 Verifier.checkNCName(localName); 387 if (localName.equals("xmlns")) { 388 throw new IllegalNameException("The Attribute class is not" 389 + " used for namespace declaration attributes."); 390 } 391 this.localName = localName; 392 } 393 394 395 /** 396 * <p> 397 * Returns the qualified name of this attribute, 398 * including the prefix if this attribute is in a namespace. 399 * </p> 400 * 401 * @return the attribute's qualified name 402 */ getQualifiedName()403 public final String getQualifiedName() { 404 if (prefix.length() == 0) return localName; 405 else return prefix + ":" + localName; 406 } 407 408 409 /** 410 * <p> 411 * Returns the namespace URI of this attribute, or the empty string 412 * if this attribute is not in a namespace. 413 * </p> 414 * 415 * @return the attribute's namespace URI 416 */ getNamespaceURI()417 public final String getNamespaceURI() { 418 return URI; 419 } 420 421 422 /** 423 * <p> 424 * Returns the prefix of this attribute, 425 * or the empty string if this attribute 426 * is not in a namespace. 427 * </p> 428 * 429 * @return the attribute's prefix 430 */ getNamespacePrefix()431 public final String getNamespacePrefix() { 432 return prefix; 433 } 434 435 436 /** 437 * <p> 438 * Sets the attribute's namespace prefix and URI. 439 * Because attributes must be prefixed in order to have a 440 * namespace URI (and vice versa) this must be done 441 * simultaneously. 442 * </p> 443 * 444 * @param prefix the new namespace prefix 445 * @param URI the new namespace URI 446 * 447 * @throws MalformedURIException if <code>URI</code> is 448 * not an RFC 3986 URI reference 449 * @throws IllegalNameException if 450 * <ul> 451 * <li>The prefix is <code>xmlns</code>.</li> 452 * <li>The prefix is null or the empty string.</li> 453 * <li>The URI is null or the empty string.</li> 454 * </ul> 455 * @throws NamespaceConflictException if 456 * <ul> 457 * <li>The prefix is <code>xml</code> and the namespace URI is 458 * not <code>http://www.w3.org/XML/1998/namespace</code>.</li> 459 * <li>The prefix conflicts with an existing declaration 460 * on the attribute's parent element.</li> 461 * </ul> 462 */ setNamespace(String prefix, String URI)463 public void setNamespace(String prefix, String URI) { 464 465 _setNamespace(prefix, URI); 466 if (isXMLID()) { 467 this.setType(Attribute.Type.ID); 468 } 469 470 } 471 472 _setNamespace(String prefix, String URI)473 private void _setNamespace(String prefix, String URI) { 474 475 if (URI == null) URI = ""; 476 if (prefix == null) prefix = ""; 477 478 if (prefix.equals("xmlns")) { 479 throw new IllegalNameException( 480 "Attribute objects are not used to represent " 481 + " namespace declarations"); 482 } 483 else if (prefix.equals("xml") 484 && !(URI.equals("http://www.w3.org/XML/1998/namespace"))) { 485 throw new NamespaceConflictException( 486 "Wrong namespace URI for xml prefix: " + URI); 487 } 488 else if (URI.equals("http://www.w3.org/XML/1998/namespace") 489 && !prefix.equals("xml")) { 490 throw new NamespaceConflictException( 491 "Wrong prefix for the XML namespace: " + prefix); 492 } 493 else if (prefix.length() == 0) { 494 if (URI.length() == 0) { 495 this.prefix = ""; 496 this.URI = ""; 497 return; 498 } 499 else { 500 throw new NamespaceConflictException( 501 "Unprefixed attribute " + this.localName 502 + " cannot be in default namespace " + URI); 503 } 504 } 505 else if (URI.length() == 0) { 506 throw new NamespaceConflictException( 507 "Attribute prefixes must be declared."); 508 } 509 510 ParentNode parent = this.getParent(); 511 if (parent != null) { 512 // test for namespace conflicts 513 Element element = (Element) parent; 514 String currentURI = element.getLocalNamespaceURI(prefix); 515 if (currentURI != null && !currentURI.equals(URI)) { 516 throw new NamespaceConflictException( 517 "New prefix " + prefix 518 + "conflicts with existing namespace declaration" 519 ); 520 } 521 } 522 523 524 Verifier.checkAbsoluteURIReference(URI); 525 Verifier.checkNCName(prefix); 526 527 this.URI = URI; 528 this.prefix = prefix; 529 530 } 531 532 533 /** 534 * <p> 535 * Throws <code>IndexOutOfBoundsException</code> 536 * because attributes do not have children. 537 * </p> 538 * 539 * @param position the child to return 540 * 541 * @return nothing. This method always throws an exception. 542 * 543 * @throws IndexOutOfBoundsException because attributes do 544 * not have children 545 */ getChild(int position)546 public final Node getChild(int position) { 547 throw new IndexOutOfBoundsException( 548 "Attributes do not have children" 549 ); 550 } 551 552 553 /** 554 * <p> 555 * Returns 0 because attributes do not have children. 556 * </p> 557 * 558 * @return zero 559 */ getChildCount()560 public final int getChildCount() { 561 return 0; 562 } 563 564 565 /** 566 * <p> 567 * Creates a deep copy of this attribute that 568 * is not attached to an element. 569 * </p> 570 * 571 * @return a copy of this attribute 572 * 573 */ copy()574 public Node copy() { 575 return new Attribute(this); 576 } 577 578 579 /** 580 * <p> 581 * Returns a string representation of the attribute 582 * that is a well-formed XML attribute. 583 * </p> 584 * 585 * @return a string containing the XML form of this attribute 586 */ toXML()587 public final String toXML() { 588 // It's a common belief that methods like this one should be 589 // implemented using StringBuffers rather than String 590 // concatenation for maximum performance. However, 591 // disassembling the code shows that today's compilers are 592 // smart enough to figure this out for themselves. The compiled 593 // version of this class only uses a single StringBuffer. No 594 // benefit would be gained by making the code more opaque here. 595 return getQualifiedName() + "=\"" + escapeText(value) + "\""; 596 } 597 598 599 /** 600 * <p> 601 * Returns a string representation of the attribute suitable for 602 * debugging and diagnosis. However, this is not necessarily 603 * a well-formed XML attribute. 604 * </p> 605 * 606 * @return a non-XML string representation of this attribute 607 * 608 * @see java.lang.Object#toString() 609 */ toString()610 public final String toString() { 611 return "[" + getClass().getName() + ": " 612 + getQualifiedName() + "=\"" 613 + Text.escapeLineBreaksAndTruncate(getValue()) + "\"]"; 614 } 615 616 escapeText(String s)617 private static String escapeText(String s) { 618 619 int length = s.length(); 620 // Give the string buffer enough room for a couple of escaped characters 621 StringBuffer result = new StringBuffer(length+12); 622 for (int i = 0; i < length; i++) { 623 char c = s.charAt(i); 624 switch (c) { 625 case '\t': 626 result.append("	"); 627 break; 628 case '\n': 629 result.append("
"); 630 break; 631 case 11: 632 // impossible 633 break; 634 case 12: 635 // impossible 636 break; 637 case '\r': 638 result.append("
"); 639 break; 640 case 14: 641 // impossible 642 break; 643 case 15: 644 // impossible 645 break; 646 case 16: 647 // impossible 648 break; 649 case 17: 650 // impossible 651 break; 652 case 18: 653 // impossible 654 break; 655 case 19: 656 // impossible 657 break; 658 case 20: 659 // impossible 660 break; 661 case 21: 662 // impossible 663 break; 664 case 22: 665 // impossible 666 break; 667 case 23: 668 // impossible 669 break; 670 case 24: 671 // impossible 672 break; 673 case 25: 674 // impossible 675 break; 676 case 26: 677 // impossible 678 break; 679 case 27: 680 // impossible 681 break; 682 case 28: 683 // impossible 684 break; 685 case 29: 686 // impossible 687 break; 688 case 30: 689 // impossible 690 break; 691 case 31: 692 // impossible 693 break; 694 case ' ': 695 result.append(' '); 696 break; 697 case '!': 698 result.append('!'); 699 break; 700 case '"': 701 result.append("""); 702 break; 703 case '#': 704 result.append('#'); 705 break; 706 case '$': 707 result.append('$'); 708 break; 709 case '%': 710 result.append('%'); 711 break; 712 case '&': 713 result.append("&"); 714 break; 715 case '\'': 716 result.append('\''); 717 break; 718 case '(': 719 result.append('('); 720 break; 721 case ')': 722 result.append(')'); 723 break; 724 case '*': 725 result.append('*'); 726 break; 727 case '+': 728 result.append('+'); 729 break; 730 case ',': 731 result.append(','); 732 break; 733 case '-': 734 result.append('-'); 735 break; 736 case '.': 737 result.append('.'); 738 break; 739 case '/': 740 result.append('/'); 741 break; 742 case '0': 743 result.append('0'); 744 break; 745 case '1': 746 result.append('1'); 747 break; 748 case '2': 749 result.append('2'); 750 break; 751 case '3': 752 result.append('3'); 753 break; 754 case '4': 755 result.append('4'); 756 break; 757 case '5': 758 result.append('5'); 759 break; 760 case '6': 761 result.append('6'); 762 break; 763 case '7': 764 result.append('7'); 765 break; 766 case '8': 767 result.append('8'); 768 break; 769 case '9': 770 result.append('9'); 771 break; 772 case ':': 773 result.append(':'); 774 break; 775 case ';': 776 result.append(';'); 777 break; 778 case '<': 779 result.append("<"); 780 break; 781 case '=': 782 result.append('='); 783 break; 784 case '>': 785 result.append(">"); 786 break; 787 default: 788 result.append(c); 789 } 790 } 791 return result.toString(); 792 793 } 794 795 isAttribute()796 boolean isAttribute() { 797 return true; 798 } 799 800 801 /** 802 * <p> 803 * Uses the type-safe enumeration 804 * design pattern to represent attribute types, 805 * as specified by XML DTDs. 806 * </p> 807 * 808 * <p> 809 * XOM enforces well-formedness, but it does not enforce 810 * validity. Thus it is possible for a single element to have 811 * multiple ID type attributes, or ID type attributes 812 * on different elements to have the same value, 813 * or NMTOKEN type attributes that don't contain legal 814 * XML name tokens, and so forth. 815 * </p> 816 * 817 * @author Elliotte Rusty Harold 818 * @version 1.0 819 * 820 */ 821 public static final class Type { 822 823 /** 824 * <p> 825 * The type of attributes declared to have type CDATA 826 * in the DTD. The most general attribute type. 827 * All well-formed attribute values are valid for 828 * attributes of type CDATA. 829 * </p> 830 */ 831 public static final Type CDATA = new Type(1); 832 833 /** 834 * <p> 835 * The type of attributes declared to have type ID 836 * in the DTD. In order to be valid, an ID type attribute 837 * must contain an XML name which is unique among other 838 * ID type attributes in the document. 839 * Furthermore, each element may contain no more than one 840 * ID type attribute. However, XOM does not enforce 841 * such validity constraints. 842 * </p> 843 */ 844 public static final Type ID = new Type(2); 845 846 /** 847 * <p> 848 * The type of attributes declared to have type IDREF 849 * in the DTD. In order to be valid, an IDREF type attribute 850 * must contain an XML name which is also the value of 851 * ID type attribute of some element in the document. 852 * However, XOM does not enforce such validity constraints. 853 * </p> 854 * 855 */ 856 public static final Type IDREF = new Type(3); 857 858 /** 859 * <p> 860 * The type of attributes declared to have type IDREFS 861 * in the DTD. In order to be valid, an IDREFS type attribute 862 * must contain a white space separated list of 863 * XML names, each of which is also the value of 864 * ID type attribute of some element in the document. 865 * However, XOM does not enforce such validity constraints. 866 * </p> 867 * 868 */ 869 public static final Type IDREFS = new Type(4); 870 871 /** 872 * <p> 873 * The type of attributes declared to have type NMTOKEN 874 * in the DTD. In order to be valid, a NMTOKEN type 875 * attribute must contain a single XML name token. However, 876 * XOM does not enforce such validity constraints. 877 * </p> 878 * 879 */ 880 public static final Type NMTOKEN = new Type(5); 881 882 /** 883 * <p> 884 * The type of attributes declared to have type NMTOKENS 885 * in the DTD. In order to be valid, a NMTOKENS type attribute 886 * must contain a white space separated list of XML name 887 * tokens. However, XOM does not enforce such validity 888 * constraints. 889 * </p> 890 * 891 */ 892 public static final Type NMTOKENS = new Type(6); 893 894 895 /** 896 * <p> 897 * The type of attributes declared to have type NOTATION 898 * in the DTD. In order to be valid, a NOTATION type 899 * attribute must contain the name of a notation declared 900 * in the DTD. However, XOM does not enforce such 901 * validity constraints. 902 * </p> 903 * 904 */ 905 public static final Type NOTATION = new Type(7); 906 907 /** 908 * <p> 909 * The type of attributes declared to have type ENTITY 910 * in the DTD. In order to be valid, a ENTITY type attribute 911 * must contain the name of an unparsed entity declared in 912 * the DTD. However, XOM does not enforce such 913 * validity constraints. 914 * </p> 915 * 916 */ 917 public static final Type ENTITY = new Type(8); 918 919 /** 920 * <p> 921 * The type of attributes declared to have type ENTITIES 922 * in the DTD. In order to be valid, an ENTITIES type 923 * attribute must contain a white space separated list of 924 * names of unparsed entities declared in the DTD. 925 * However, XOM does not enforce such validity constraints. 926 * </p> 927 * 928 */ 929 public static final Type ENTITIES = new Type(9); 930 931 /** 932 * <p> 933 * The type of attributes declared by an enumeration 934 * in the DTD. In order to be valid, a enumeration type 935 * attribute must contain exactly one of the names given 936 * in the enumeration in the DTD. However, XOM does not 937 * enforce such validity constraints. 938 * </p> 939 * 940 * <p> 941 * Most parsers report attributes of type enumeration as 942 * having type NMTOKEN. In this case, XOM will not 943 * distinguish NMTOKEN and enumerated attributes. 944 * </p> 945 * 946 */ 947 public static final Type ENUMERATION = new Type(10); 948 949 /** 950 * <p> 951 * The type of attributes not declared in the DTD. 952 * This type only appears in invalid documents. 953 * This is the default type for all attributes in 954 * documents without DTDs. 955 * </p> 956 * 957 * <p> 958 * Most parsers report attributes of undeclared 959 * type as having type CDATA. In this case, XOM 960 * will not distinguish CDATA and undeclared attributes. 961 * </p> 962 */ 963 public static final Type UNDECLARED = new Type(0); 964 965 966 /** 967 * <p> 968 * Returns the string name of this type as might 969 * be used in a DTD; for example, "ID", "CDATA", etc. 970 * </p> 971 * 972 * @return an XML string representation of this type 973 */ getName()974 public String getName() { 975 976 switch (type) { 977 case 0: 978 return "UNDECLARED"; 979 case 1: 980 return "CDATA"; 981 case 2: 982 return "ID"; 983 case 3: 984 return "IDREF"; 985 case 4: 986 return "IDREFS"; 987 case 5: 988 return "NMTOKEN"; 989 case 6: 990 return "NMTOKENS"; 991 case 7: 992 return "NOTATION"; 993 case 8: 994 return "ENTITY"; 995 case 9: 996 return "ENTITIES"; 997 case 10: 998 return "ENUMERATION"; 999 default: 1000 throw new RuntimeException( 1001 "Bug in XOM: unexpected attribute type: " + type); 1002 } 1003 1004 } 1005 1006 1007 private final int type; 1008 Type(int type)1009 private Type(int type) { 1010 this.type = type; 1011 } 1012 1013 1014 /** 1015 * <p> 1016 * Returns a unique identifier for this type. 1017 * </p> 1018 * 1019 * @return a unique identifier for this type 1020 * 1021 * @see java.lang.Object#hashCode() 1022 */ hashCode()1023 public int hashCode() { 1024 return this.type; 1025 } 1026 1027 1028 /** 1029 * <p> 1030 * Tests for type equality. This is only necessary, 1031 * to handle the case where two <code>Type</code> objects 1032 * are loaded by different class loaders. 1033 * </p> 1034 * 1035 * @param o the object compared for equality to this type 1036 * 1037 * @return true if and only if <code>o</code> represents 1038 * the same type 1039 * 1040 * @see java.lang.Object#equals(Object) 1041 */ equals(Object o)1042 public boolean equals(Object o) { 1043 1044 if (o == this) return true; 1045 if (o == null) return false; 1046 if (this.hashCode() != o.hashCode()) return false; 1047 if (!o.getClass().getName().equals("nu.xom.Attribute.Type")) { 1048 return false; 1049 } 1050 return true; 1051 1052 } 1053 1054 1055 /** 1056 * <p> 1057 * Returns a string representation of the type 1058 * suitable for debugging and diagnosis. 1059 * </p> 1060 * 1061 * @return a non-XML string representation of this type 1062 * 1063 * @see java.lang.Object#toString() 1064 */ toString()1065 public String toString() { 1066 1067 StringBuffer result 1068 = new StringBuffer("[Attribute.Type: "); 1069 result.append(getName()); 1070 result.append(']'); 1071 return result.toString(); 1072 1073 } 1074 1075 1076 } 1077 1078 1079 } 1080