~ubuntu-branches/ubuntu/precise/xom/precise

« back to all changes in this revision

Viewing changes to src/nu/xom/Attribute.java

  • Committer: Bazaar Package Importer
  • Author(s): Varun Hiremath
  • Date: 2007-11-25 15:50:40 UTC
  • Revision ID: james.westby@ubuntu.com-20071125155040-r75ikcqf1vu0cei7
Tags: upstream-1.1
ImportĀ upstreamĀ versionĀ 1.1

Show diffs side-by-side

added added

removed removed

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