Source code: org/jdom/Attribute.java
1 /*--
2
3 $Id: Attribute.java,v 1.52 2004/03/01 23:58:28 jhunter Exp $
4
5 Copyright (C) 2000-2004 Jason Hunter & Brett McLaughlin.
6 All rights reserved.
7
8 Redistribution and use in source and binary forms, with or without
9 modification, are permitted provided that the following conditions
10 are met:
11
12 1. Redistributions of source code must retain the above copyright
13 notice, this list of conditions, and the following disclaimer.
14
15 2. Redistributions in binary form must reproduce the above copyright
16 notice, this list of conditions, and the disclaimer that follows
17 these conditions in the documentation and/or other materials
18 provided with the distribution.
19
20 3. The name "JDOM" must not be used to endorse or promote products
21 derived from this software without prior written permission. For
22 written permission, please contact <request_AT_jdom_DOT_org>.
23
24 4. Products derived from this software may not be called "JDOM", nor
25 may "JDOM" appear in their name, without prior written permission
26 from the JDOM Project Management <request_AT_jdom_DOT_org>.
27
28 In addition, we request (but do not require) that you include in the
29 end-user documentation provided with the redistribution and/or in the
30 software itself an acknowledgement equivalent to the following:
31 "This product includes software developed by the
32 JDOM Project (http://www.jdom.org/)."
33 Alternatively, the acknowledgment may be graphical using the logos
34 available at http://www.jdom.org/images/logos.
35
36 THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
37 WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
38 OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
39 DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT
40 CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
41 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
42 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
43 USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
44 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
45 OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
46 OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
47 SUCH DAMAGE.
48
49 This software consists of voluntary contributions made by many
50 individuals on behalf of the JDOM Project and was originally
51 created by Jason Hunter <jhunter_AT_jdom_DOT_org> and
52 Brett McLaughlin <brett_AT_jdom_DOT_org>. For more information
53 on the JDOM Project, please see <http://www.jdom.org/>.
54
55 */
56
57 package org.jdom;
58
59 import java.io.*;
60
61 /**
62 * An XML attribute. Methods allow the user to obtain the value of the attribute
63 * as well as namespace and type information.
64 *
65 * @version $Revision: 1.52 $, $Date: 2004/03/01 23:58:28 $
66 * @author Brett McLaughlin
67 * @author Jason Hunter
68 * @author Elliotte Rusty Harold
69 * @author Wesley Biggs
70 */
71 public class Attribute implements Serializable, Cloneable {
72
73 private static final String CVS_ID =
74 "@(#) $RCSfile: Attribute.java,v $ $Revision: 1.52 $ $Date: 2004/03/01 23:58:28 $ $Name: jdom_1_0 $";
75
76 /**
77 * Attribute type: the attribute has not been declared or type
78 * is unknown.
79 *
80 * @see #getAttributeType
81 */
82 public final static int UNDECLARED_TYPE = 0;
83
84 /**
85 * Attribute type: the attribute value is a string.
86 *
87 * @see #getAttributeType
88 */
89 public final static int CDATA_TYPE = 1;
90
91 /**
92 * Attribute type: the attribute value is a unique identifier.
93 *
94 * @see #getAttributeType
95 */
96 public final static int ID_TYPE = 2;
97
98 /**
99 * Attribute type: the attribute value is a reference to a
100 * unique identifier.
101 *
102 * @see #getAttributeType
103 */
104 public final static int IDREF_TYPE = 3;
105
106 /**
107 * Attribute type: the attribute value is a list of references to
108 * unique identifiers.
109 *
110 * @see #getAttributeType
111 */
112 public final static int IDREFS_TYPE = 4;
113
114 /**
115 * Attribute type: the attribute value is the name of an entity.
116 *
117 * @see #getAttributeType
118 */
119 public final static int ENTITY_TYPE = 5;
120
121 /**
122 * <p>
123 * Attribute type: the attribute value is a list of entity names.
124 * </p>
125 *
126 * @see #getAttributeType
127 */
128 public final static int ENTITIES_TYPE = 6;
129
130 /**
131 * Attribute type: the attribute value is a name token.
132 * <p>
133 * According to SAX 2.0 specification, attributes of enumerated
134 * types should be reported as "NMTOKEN" by SAX parsers. But the
135 * major parsers (Xerces and Crimson) provide specific values
136 * that permit to recognize them as {@link #ENUMERATED_TYPE}.
137 *
138 * @see #getAttributeType
139 */
140 public final static int NMTOKEN_TYPE = 7;
141
142 /**
143 * Attribute type: the attribute value is a list of name tokens.
144 *
145 * @see #getAttributeType
146 */
147 public final static int NMTOKENS_TYPE = 8;
148
149 /**
150 * Attribute type: the attribute value is the name of a notation.
151 *
152 * @see #getAttributeType
153 */
154 public final static int NOTATION_TYPE = 9;
155
156 /**
157 * Attribute type: the attribute value is a name token from an
158 * enumeration.
159 *
160 * @see #getAttributeType
161 */
162 public final static int ENUMERATED_TYPE = 10;
163
164 // Keep the old constant names for one beta cycle to help migration
165
166
167
168 /** The local name of the <code>Attribute</code> */
169 protected String name;
170
171 /** The <code>{@link Namespace}</code> of the <code>Attribute</code> */
172 protected transient Namespace namespace;
173
174 /** The value of the <code>Attribute</code> */
175 protected String value;
176
177 /** The type of the <code>Attribute</code> */
178 protected int type = UNDECLARED_TYPE;
179
180 /** Parent element, or null if none */
181 protected Object parent;
182
183 /**
184 * Default, no-args constructor for implementations to use if needed.
185 */
186 protected Attribute() {}
187
188 /**
189 * This will create a new <code>Attribute</code> with the
190 * specified (local) name and value, and in the provided
191 * <code>{@link Namespace}</code>.
192 *
193 * @param name <code>String</code> name of <code>Attribute</code>.
194 * @param value <code>String</code> value for new attribute.
195 * @param namespace <code>Namespace</code> namespace for new attribute.
196 * @throws IllegalNameException if the given name is illegal as an
197 * attribute name or if if the new namespace is the default
198 * namespace. Attributes cannot be in a default namespace.
199 * @throws IllegalDataException if the given attribute value is
200 * illegal character data (as determined by
201 * {@link org.jdom.Verifier#checkCharacterData}).
202 */
203 public Attribute(String name, String value, Namespace namespace) {
204 setName(name);
205 setValue(value);
206 setNamespace(namespace);
207 }
208
209 /**
210 * This will create a new <code>Attribute</code> with the
211 * specified (local) name, value, and type, and in the provided
212 * <code>{@link Namespace}</code>.
213 *
214 * @param name <code>String</code> name of <code>Attribute</code>.
215 * @param value <code>String</code> value for new attribute.
216 * @param type <code>int</code> type for new attribute.
217 * @param namespace <code>Namespace</code> namespace for new attribute.
218 * @throws IllegalNameException if the given name is illegal as an
219 * attribute name or if if the new namespace is the default
220 * namespace. Attributes cannot be in a default namespace.
221 * @throws IllegalDataException if the given attribute value is
222 * illegal character data (as determined by
223 * {@link org.jdom.Verifier#checkCharacterData}) or
224 * if the given attribute type is not one of the
225 * supported types.
226 */
227 public Attribute(String name, String value, int type, Namespace namespace) {
228 setName(name);
229 setValue(value);
230 setAttributeType(type);
231 setNamespace(namespace);
232 }
233
234 /**
235 * This will create a new <code>Attribute</code> with the
236 * specified (local) name and value, and does not place
237 * the attribute in a <code>{@link Namespace}</code>.
238 * <p>
239 * <b>Note</b>: This actually explicitly puts the
240 * <code>Attribute</code> in the "empty" <code>Namespace</code>
241 * (<code>{@link Namespace#NO_NAMESPACE}</code>).
242 *
243 * @param name <code>String</code> name of <code>Attribute</code>.
244 * @param value <code>String</code> value for new attribute.
245 * @throws IllegalNameException if the given name is illegal as an
246 * attribute name.
247 * @throws IllegalDataException if the given attribute value is
248 * illegal character data (as determined by
249 * {@link org.jdom.Verifier#checkCharacterData}).
250 */
251 public Attribute(String name, String value) {
252 this(name, value, UNDECLARED_TYPE, Namespace.NO_NAMESPACE);
253 }
254
255 /**
256 * This will create a new <code>Attribute</code> with the
257 * specified (local) name, value and type, and does not place
258 * the attribute in a <code>{@link Namespace}</code>.
259 * <p>
260 * <b>Note</b>: This actually explicitly puts the
261 * <code>Attribute</code> in the "empty" <code>Namespace</code>
262 * (<code>{@link Namespace#NO_NAMESPACE}</code>).
263 *
264 * @param name <code>String</code> name of <code>Attribute</code>.
265 * @param value <code>String</code> value for new attribute.
266 * @param type <code>int</code> type for new attribute.
267 * @throws IllegalNameException if the given name is illegal as an
268 * attribute name.
269 * @throws IllegalDataException if the given attribute value is
270 * illegal character data (as determined by
271 * {@link org.jdom.Verifier#checkCharacterData}) or
272 * if the given attribute type is not one of the
273 * supported types.
274 */
275 public Attribute(String name, String value, int type) {
276 this(name, value, type, Namespace.NO_NAMESPACE);
277 }
278
279 /**
280 * This will return the parent of this <code>Attribute</code>.
281 * If there is no parent, then this returns <code>null</code>.
282 *
283 * @return parent of this <code>Attribute</code>
284 */
285 public Element getParent() {
286 return (Element) parent;
287 }
288
289 /**
290 * This retrieves the owning <code>{@link Document}</code> for
291 * this Attribute, or null if not a currently a member of a
292 * <code>{@link Document}</code>.
293 *
294 * @return <code>Document</code> owning this Attribute, or null.
295 */
296 public Document getDocument() {
297 if (parent != null) {
298 return ((Element)parent).getDocument();
299 }
300 return null;
301 }
302
303 /**
304 * This will set the parent of this <code>Attribute</code>.
305 *
306 * @param parent <code>Element</code> to be new parent.
307 * @return this <code>Attribute</code> modified.
308 */
309 protected Attribute setParent(Element parent) {
310 this.parent = parent;
311 return this;
312 }
313
314 /**
315 * This detaches the <code>Attribute</code> from its parent, or does
316 * nothing if the <code>Attribute</code> has no parent.
317 *
318 * @return <code>Attribute</code> - this <code>Attribute</code> modified.
319 */
320 public Attribute detach() {
321 Element p = getParent();
322 if (p != null) {
323 p.removeAttribute(this.getName(), this.getNamespace());
324 }
325 return this;
326 }
327
328 /**
329 * This will retrieve the local name of the
330 * <code>Attribute</code>. For any XML attribute
331 * which appears as
332 * <code>[namespacePrefix]:[attributeName]</code>,
333 * the local name of the attribute would be
334 * <code>[attributeName]</code>. When the attribute
335 * has no namespace, the local name is simply the attribute
336 * name.
337 * <p>
338 * To obtain the namespace prefix for this
339 * attribute, the
340 * <code>{@link #getNamespacePrefix()}</code>
341 * method should be used.
342 *
343 * @return <code>String</code> - name of this attribute,
344 * without any namespace prefix.
345 */
346 public String getName() {
347 return name;
348 }
349
350 /**
351 * This sets the local name of the <code>Attribute</code>.
352 *
353 * @param name the new local name to set
354 * @return <code>Attribute</code> - the attribute modified.
355 * @throws IllegalNameException if the given name is illegal as an
356 * attribute name.
357 */
358 public Attribute setName(String name) {
359 String reason;
360 if ((reason = Verifier.checkAttributeName(name)) != null) {
361 throw new IllegalNameException(name, "attribute", reason);
362 }
363 this.name = name;
364 return this;
365 }
366
367 /**
368 * This will retrieve the qualified name of the <code>Attribute</code>.
369 * For any XML attribute whose name is
370 * <code>[namespacePrefix]:[elementName]</code>,
371 * the qualified name of the attribute would be
372 * everything (both namespace prefix and
373 * element name). When the attribute has no
374 * namespace, the qualified name is simply the attribute's
375 * local name.
376 * <p>
377 * To obtain the local name of the attribute, the
378 * <code>{@link #getName()}</code> method should be used.
379 * <p>
380 * To obtain the namespace prefix for this attribute,
381 * the <code>{@link #getNamespacePrefix()}</code>
382 * method should be used.
383 *
384 * @return <code>String</code> - full name for this element.
385 */
386 public String getQualifiedName() {
387 // Note: Any changes here should be reflected in
388 // XMLOutputter.printQualifiedName()
389 String prefix = namespace.getPrefix();
390 if ((prefix != null) && (!prefix.equals(""))) {
391 return new StringBuffer(prefix)
392 .append(':')
393 .append(getName())
394 .toString();
395 } else {
396 return getName();
397 }
398 }
399
400 /**
401 * This will retrieve the namespace prefix of the
402 * <code>Attribute</code>. For any XML attribute
403 * which appears as
404 * <code>[namespacePrefix]:[attributeName]</code>,
405 * the namespace prefix of the attribute would be
406 * <code>[namespacePrefix]</code>. When the attribute
407 * has no namespace, an empty <code>String</code> is returned.
408 *
409 * @return <code>String</code> - namespace prefix of this
410 * attribute.
411 */
412 public String getNamespacePrefix() {
413 return namespace.getPrefix();
414 }
415
416 /**
417 * This returns the URI mapped to this <code>Attribute</code>'s
418 * prefix. If no mapping is found, an empty <code>String</code> is
419 * returned.
420 *
421 * @return <code>String</code> - namespace URI for this <code>Attribute</code>.
422 */
423 public String getNamespaceURI() {
424 return namespace.getURI();
425 }
426
427 /**
428 * This will return this <code>Attribute</code>'s
429 * <code>{@link Namespace}</code>.
430 *
431 * @return <code>Namespace</code> - Namespace object for this <code>Attribute</code>
432 */
433 public Namespace getNamespace() {
434 return namespace;
435 }
436
437 /**
438 * This sets this <code>Attribute</code>'s <code>{@link Namespace}</code>.
439 * If the provided namespace is null, the attribute will have no namespace.
440 * The namespace must have a prefix.
441 *
442 * @param namespace the new namespace
443 * @return <code>Element</code> - the element modified.
444 * @throws IllegalNameException if the new namespace is the default
445 * namespace. Attributes cannot be in a default namespace.
446 */
447 public Attribute setNamespace(Namespace namespace) {
448 if (namespace == null) {
449 namespace = Namespace.NO_NAMESPACE;
450 }
451
452 // Verify the attribute isn't trying to be in a default namespace
453 // Attributes can't be in a default namespace
454 if (namespace != Namespace.NO_NAMESPACE &&
455 namespace.getPrefix().equals("")) {
456 throw new IllegalNameException("", "attribute namespace",
457 "An attribute namespace without a prefix can only be the " +
458 "NO_NAMESPACE namespace");
459 }
460 this.namespace = namespace;
461 return this;
462 }
463 /**
464 * This will return the actual textual value of this
465 * <code>Attribute</code>. This will include all text
466 * within the quotation marks.
467 *
468 * @return <code>String</code> - value for this attribute.
469 */
470 public String getValue() {
471 return value;
472 }
473
474 /**
475 * This will set the value of the <code>Attribute</code>.
476 *
477 * @param value <code>String</code> value for the attribute.
478 * @return <code>Attribute</code> - this Attribute modified.
479 * @throws IllegalDataException if the given attribute value is
480 * illegal character data (as determined by
481 * {@link org.jdom.Verifier#checkCharacterData}).
482 */
483 public Attribute setValue(String value) {
484 String reason = null;
485 if ((reason = Verifier.checkCharacterData(value)) != null) {
486 throw new IllegalDataException(value, "attribute", reason);
487 }
488 this.value = value;
489 return this;
490 }
491
492 /**
493 * This will return the actual declared type of this
494 * <code>Attribute</code>.
495 *
496 * @return <code>int</code> - type for this attribute.
497 */
498 public int getAttributeType() {
499 return type;
500 }
501
502 /**
503 * This will set the type of the <code>Attribute</code>.
504 *
505 * @param type <code>int</code> type for the attribute.
506 * @return <code>Attribute</code> - this Attribute modified.
507 * @throws IllegalDataException if the given attribute type is
508 * not one of the supported types.
509 */
510 public Attribute setAttributeType(int type) {
511 if ((type < UNDECLARED_TYPE) || (type > ENUMERATED_TYPE)) {
512 throw new IllegalDataException(String.valueOf(type),
513 "attribute", "Illegal attribute type");
514 }
515 this.type = type;
516 return this;
517 }
518
519 /**
520 * This returns a <code>String</code> representation of the
521 * <code>Attribute</code>, suitable for debugging.
522 *
523 * @return <code>String</code> - information about the
524 * <code>Attribute</code>
525 */
526 public String toString() {
527 return new StringBuffer()
528 .append("[Attribute: ")
529 .append(getQualifiedName())
530 .append("=\"")
531 .append(value)
532 .append("\"")
533 .append("]")
534 .toString();
535 }
536
537 /**
538 * This tests for equality of this <code>Attribute</code> to the supplied
539 * <code>Object</code>.
540 *
541 * @param ob <code>Object</code> to compare to.
542 * @return <code>boolean</code> - whether the <code>Attribute</code> is
543 * equal to the supplied <code>Object</code>.
544 */
545 public final boolean equals(Object ob) {
546 return (ob == this);
547 }
548
549 /**
550 * This returns the hash code for this <code>Attribute</code>.
551 *
552 * @return <code>int</code> - hash code.
553 */
554 public final int hashCode() {
555 return super.hashCode();
556 }
557
558 /**
559 * This will return a clone of this <code>Attribute</code>.
560 *
561 * @return <code>Object</code> - clone of this <code>Attribute</code>.
562 */
563 public Object clone() {
564 Attribute attribute = null;
565
566 try {
567 attribute = (Attribute) super.clone();
568 } catch(CloneNotSupportedException ce) {
569 // Won't happen
570 }
571
572 // Name, namespace, and value are references to imutable objects
573 // and are copied by super.clone() (aka Object.clone())
574
575 // super.clone() copies reference to set parent to null
576 attribute.parent = null;
577 return attribute;
578 }
579
580 /////////////////////////////////////////////////////////////////
581 // Convenience Methods below here
582 /////////////////////////////////////////////////////////////////
583
584 /**
585 * This gets the value of the attribute, in
586 * <code>int</code> form, and if no conversion
587 * can occur, throws a
588 * <code>{@link DataConversionException}</code>
589 *
590 * @return <code>int</code> value of attribute.
591 * @throws DataConversionException when conversion fails.
592 */
593 public int getIntValue() throws DataConversionException {
594 try {
595 return Integer.parseInt(value.trim());
596 } catch (NumberFormatException e) {
597 throw new DataConversionException(name, "int");
598 }
599 }
600
601 /**
602 * This gets the value of the attribute, in
603 * <code>long</code> form, and if no conversion
604 * can occur, throws a
605 * <code>{@link DataConversionException}</code>
606 *
607 * @return <code>long</code> value of attribute.
608 * @throws DataConversionException when conversion fails.
609 */
610 public long getLongValue() throws DataConversionException {
611 try {
612 return Long.parseLong(value.trim());
613 } catch (NumberFormatException e) {
614 throw new DataConversionException(name, "long");
615 }
616 }
617
618 /**
619 * This gets the value of the attribute, in
620 * <code>float</code> form, and if no conversion
621 * can occur, throws a
622 * <code>{@link DataConversionException}</code>
623 *
624 * @return <code>float</code> value of attribute.
625 * @throws DataConversionException when conversion fails.
626 */
627 public float getFloatValue() throws DataConversionException {
628 try {
629 // Avoid Float.parseFloat() to support JDK 1.1
630 return Float.valueOf(value.trim()).floatValue();
631 } catch (NumberFormatException e) {
632 throw new DataConversionException(name, "float");
633 }
634 }
635
636 /**
637 * This gets the value of the attribute, in
638 * <code>double</code> form, and if no conversion
639 * can occur, throws a
640 * <code>{@link DataConversionException}</code>
641 *
642 * @return <code>double</code> value of attribute.
643 * @throws DataConversionException when conversion fails.
644 */
645 public double getDoubleValue() throws DataConversionException {
646 try {
647 // Avoid Double.parseDouble() to support JDK 1.1
648 return Double.valueOf(value.trim()).doubleValue();
649 } catch (NumberFormatException e) {
650 throw new DataConversionException(name, "double");
651 }
652 }
653
654 /**
655 * This gets the effective boolean value of the attribute, or throws a
656 * <code>{@link DataConversionException}</code> if a conversion can't be
657 * performed. True values are: "true", "on", "1", and "yes". False
658 * values are: "false", "off", "0", and "no". Values are trimmed before
659 * comparison. Values other than those listed here throw the exception.
660 *
661 * @return <code>boolean</code> value of attribute.
662 * @throws DataConversionException when conversion fails.
663 */
664 public boolean getBooleanValue() throws DataConversionException {
665 String valueTrim = value.trim();
666 if ((valueTrim.equalsIgnoreCase("true")) ||
667 (valueTrim.equalsIgnoreCase("on")) ||
668 (valueTrim.equalsIgnoreCase("1")) ||
669 (valueTrim.equalsIgnoreCase("yes"))) {
670 return true;
671 } else if ((valueTrim.equalsIgnoreCase("false")) ||
672 (valueTrim.equalsIgnoreCase("off")) ||
673 (valueTrim.equalsIgnoreCase("0")) ||
674 (valueTrim.equalsIgnoreCase("no"))) {
675 return false;
676 } else {
677 throw new DataConversionException(name, "boolean");
678 }
679 }
680
681 // Support a custom Namespace serialization so no two namespace
682 // object instances may exist for the same prefix/uri pair
683 private void writeObject(ObjectOutputStream out) throws IOException {
684
685 out.defaultWriteObject();
686
687 // We use writeObject() and not writeUTF() to minimize space
688 // This allows for writing pointers to already written strings
689 out.writeObject(namespace.getPrefix());
690 out.writeObject(namespace.getURI());
691 }
692
693 private void readObject(ObjectInputStream in)
694 throws IOException, ClassNotFoundException {
695
696 in.defaultReadObject();
697
698 namespace = Namespace.getNamespace(
699 (String)in.readObject(), (String)in.readObject());
700 }
701 }