001: /* Attribute.java
002:
003: {{IS_NOTE
004:
005: Purpose:
006: Description:
007: History:
008: 2001/10/22 17:11:10, Create, Tom M. Yeh.
009: }}IS_NOTE
010:
011: Copyright (C) 2001 Potix Corporation. All Rights Reserved.
012:
013: {{IS_RIGHT
014: This program is distributed under GPL Version 2.0 in the hope that
015: it will be useful, but WITHOUT ANY WARRANTY.
016: }}IS_RIGHT
017: */
018: package org.zkoss.idom;
019:
020: import java.io.IOException;
021: import java.io.ObjectInputStream;
022: import java.io.ObjectOutputStream;
023:
024: import org.w3c.dom.Attr;
025: import org.w3c.dom.TypeInfo;
026:
027: import org.zkoss.lang.Objects;
028: import org.zkoss.idom.impl.*;
029:
030: /**
031: * The iDOM attribute.
032: *
033: * <p>Design decision: Attribute is also a item. The reason is
034: * it simplifies the use of xpath. A xpath migt return either
035: * elements or attributes, so...
036: *
037: * @author tomyeh
038: * @see Element
039: */
040: public class Attribute extends AbstractItem implements Namespaceable,
041: Attr {
042: /** The namespace. */
043: protected Namespace _ns;
044: /** The owner item. */
045: protected Item _owner;
046: /** The local name. */
047: protected String _lname;
048: /** The value. */
049: protected String _value;
050:
051: /**
052: * Constructor.
053: *
054: * <p>Note: According to W3/DOM, the namespace of attributes must
055: * have a prefix if the uri is not empty.
056: *
057: * @param nsURI the namespace URI
058: * @param tname the tag name
059: */
060: public Attribute(String nsURI, String tname, String value) {
061: int kp = tname.indexOf(':');
062: String prefix = kp >= 0 ? tname.substring(0, kp) : "";
063: String lname = kp >= 0 ? tname.substring(kp + 1) : tname;
064: setNamespace(prefix, nsURI);
065: setLocalName(lname);
066: setValue(value);
067: }
068:
069: /**
070: * Constructor.
071: *
072: * @param ns the namespace
073: * @param lname the local name
074: */
075: public Attribute(Namespace ns, String lname, String value) {
076: setNamespace(ns);
077: setLocalName(lname);
078: setValue(value);
079: }
080:
081: /**
082: * Constructor.
083: */
084: public Attribute(String lname, String value) {
085: this (Namespace.NO_NAMESPACE, lname, value);
086: }
087:
088: /**
089: * Constructor.
090: */
091: protected Attribute() {
092: _ns = Namespace.NO_NAMESPACE;
093: }
094:
095: //-- Attribute extra --//
096: /**
097: * Gets the value of this attribute.
098: */
099: public final String getValue() {
100: return _value;
101: }
102:
103: /**
104: * Sets the value of this attribute.
105: * According to Section 3.3.3 of XML 1.0 spec, the value is always
106: * normalized. Whether to trim depends on whether an attribute is CDATA
107: * (default).
108: * In this version, we don't normalize or trim
109: * (i.e., consider it as CDATA).
110: *
111: * @param value the new value; null is considered as empty
112: */
113: public final void setValue(String value) {
114: checkWritable();
115:
116: if (value == null)
117: value = "";
118: // else
119: // value = value.trim();
120: //TODO: check whether the attribute has been declared to CDATA or not
121:
122: if (!Objects.equals(_value, value)) {
123: Verifier.checkCharacterData(value, getLocator());
124: _value = value;
125: setModified();
126: }
127: }
128:
129: /**
130: * Gets the item that owns this attribute.
131: */
132: public final Item getOwner() {
133: return _owner;
134: }
135:
136: /**
137: * Sets the item that owns this attribute.
138: *
139: * <p><b><i>DO NOT</i></b> call this method. It is used internally.
140: * For user's point of view, the owner item is maintained
141: * automatically, so user never needs to update it.
142: */
143: public final void setOwner(Item owner) {
144: checkWritable();
145: if (_owner != owner) {
146: _ns = reuseNamespace(owner, _ns); //check and reuse
147:
148: //Note: new owner and old owner's setModified must be called
149: if (_owner != null
150: && ((Attributable) _owner)
151: .isAttributeModificationAware())
152: _owner.setModified();
153:
154: _owner = owner;
155: setModified(); //then, owner.setModified is called
156: }
157: }
158:
159: /** Search for the namespace to see any possible to reuse.
160: */
161: private static Namespace reuseNamespace(Item owner, Namespace ns) {
162: if (ns.getPrefix().length() > 0 && (owner instanceof Element)) {
163: final Namespace found = ((Element) owner).getNamespace(ns
164: .getPrefix());
165: if (found == null)
166: throw new DOMException(DOMException.NAMESPACE_ERR,
167: "Attribute's namespace, " + ns
168: + ", not found in element");
169: if (!ns.equals(found))
170: throw new DOMException(DOMException.NAMESPACE_ERR,
171: "Attribute's namespace, " + ns
172: + ", conflicts with element's, "
173: + found);
174: return found;
175: }
176: return ns;
177: }
178:
179: //-- Namespaceable --//
180: /** Sets the namespace.
181: */
182: public final void setNamespace(String prefix, String nsURI) {
183: setNamespace((prefix == null || prefix.length() == 0)
184: && (nsURI == null || nsURI.length() == 0) ? null
185: : new Namespace(prefix, nsURI));
186: }
187:
188: /**
189: * Sets the namespace.
190: * <p>According W3C/DOM, unlike element, an attribute doesn't allow
191: * a namespace that has an URI but without a prefix.
192: */
193: public final void setNamespace(Namespace ns) {
194: checkWritable();
195:
196: if (ns != null && ns.getPrefix().length() == 0
197: && ns.getURI().length() != 0)
198: throw new DOMException(
199: DOMException.NAMESPACE_ERR,
200: "Attribute's namespace without a prefix cannot have URI",
201: getLocator());
202:
203: if (ns == null || ns.getPrefix().length() == 0)
204: ns = Namespace.NO_NAMESPACE;
205: else
206: ns = reuseNamespace(_owner, ns); //check and reuse
207:
208: if (!Objects.equals(_ns, ns)) {
209: _ns = ns;
210: setModified();
211: }
212: }
213:
214: public final Namespace getNamespace() {
215: return _ns;
216: }
217:
218: public final String getTagName() {
219: return _ns.tagNameOf(_lname);
220: }
221:
222: /**
223: * Sets the tag name.
224: *
225: * <p>Changing a name improperly might cause replicated attribute
226: * names which won't be detected by this method.
227: */
228: public final void setTagName(String tname) {
229: checkWritable();
230:
231: if (!Objects.equals(tname, getName())) {
232: int kp = tname.indexOf(':');
233: String prefix = kp >= 0 ? tname.substring(0, kp) : "";
234: String lname = kp >= 0 ? tname.substring(kp + 1) : tname;
235: setPrefix(prefix);
236: setLocalName(lname);
237: setModified();
238: }
239: }
240:
241: public final String getLocalName() {
242: return _lname;
243: }
244:
245: /**
246: * Sets the local name of this attribute.
247: *
248: * <p>Changing a name improperly might cause replicated attribute
249: * names which won't be detected by this method.
250: */
251: public final void setLocalName(String lname) {
252: checkWritable();
253: if (!Objects.equals(lname, getLocalName())) {
254: Verifier.checkAttributeName(lname, getLocator());
255: _lname = lname;
256: setModified();
257: }
258: }
259:
260: //-- Item --//
261: /**
262: * Tests whether this attribute is read-only.
263: * Note: An attribute is read-only if the read-only flag is set (setReadonly)
264: * or any of its owner item is read-only (getOwner().isReadonly()).
265: */
266: public final boolean isReadonly() {
267: return super .isReadonly()
268: || (_owner != null && _owner.isReadonly());
269: }
270:
271: public void setModified() {
272: assert (getParent() == null);
273: _modified = true;
274: if (_owner != null
275: && ((Attributable) _owner)
276: .isAttributeModificationAware())
277: _owner.setModified();
278: }
279:
280: public final String getName() {
281: return getTagName();
282: }
283:
284: public final void setName(String tname) {
285: setTagName(tname);
286: }
287:
288: public final String getText() {
289: return getValue();
290: }
291:
292: public final void setText(String text) {
293: setValue(text); //and checkWritable and setModified
294: }
295:
296: /**
297: * Gets the document that owns this attribute.
298: */
299: public final Document getDocument() {
300: return _owner != null ? _owner.getDocument() : null;
301: }
302:
303: /**
304: * Detach the attribute from its owner, if any.
305: * Only attributes that belongs to no item or the same item
306: * are allowed to be added to a item. So, detach is useful
307: * to move an attribute out from a item (and then you might
308: * add it to another item).
309: */
310: public Item detach() {
311: checkWritable();
312: if (_owner != null) {
313: ((Attributable) _owner).getAttributeItems().remove(this );
314: assert (_owner == null);
315: setModified();
316: }
317: return this ;
318: }
319:
320: public void setParent(Item parent) {
321: throw new DOMException(DOMException.INVALID_ACCESS_ERR,
322: "Attributes do not have parent", getLocator());
323: }
324:
325: //-- Node --//
326: public final short getNodeType() {
327: return ATTRIBUTE_NODE;
328: }
329:
330: public final org.w3c.dom.Document getOwnerDocument() {
331: return getDocument();
332: }
333:
334: public final String getNamespaceURI() {
335: return _ns.getURI();
336: }
337:
338: public final String getPrefix() {
339: return _ns.getPrefix();
340: }
341:
342: /**
343: * Sets the namespace prefix of this attribute.
344: *
345: * <p>Changing a prefix improperly might cause replicated attribute
346: * names which won't be detected by this method.
347: */
348: public final void setPrefix(String prefix) {
349: setNamespace(prefix, _ns.getURI());
350: }
351:
352: public TypeInfo getSchemaTypeInfo() {
353: throw new UnsupportedOperationException("DOM Level 3");
354: }
355:
356: public boolean isId() {
357: throw new UnsupportedOperationException("DOM Level 3");
358: }
359:
360: //-- Attr --//
361: public final boolean getSpecified() {
362: return false; //TODO
363: }
364:
365: public final org.w3c.dom.Element getOwnerElement() {
366: return (org.w3c.dom.Element) getOwner();
367: }
368:
369: //-- Object --//
370: public final String toString() {
371: return "[Attribute: " + getTagName() + "=\"" + _value + "\"]";
372: }
373:
374: public Item clone(boolean preserveModified) {
375: Attribute v = (Attribute) super.clone(preserveModified);
376: v._owner = null;
377: return v;
378: }
379: }
|