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 &amp; 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 &amp; 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 &amp; 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 &amp; 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 &amp; 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("&#x09;");
627                     break;
628                 case '\n':
629                     result.append("&#x0A;");
630                     break;
631                 case 11:
632                     // impossible
633                     break;
634                 case 12:
635                     // impossible
636                     break;
637                 case '\r':
638                     result.append("&#x0D;");
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("&quot;");
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("&amp;");
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("&lt;");
780                     break;
781                 case '=':
782                     result.append('=');
783                     break;
784                 case '>':
785                     result.append("&gt;");
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