/* Attribute.java
Purpose:
Description:
History:
2001/10/22 17:11:10, Create, Tom M. Yeh.
Copyright (C) 2001 Potix Corporation. All Rights Reserved.
{{IS_RIGHT
This program is distributed under LGPL Version 2.1 in the hope that
it will be useful, but WITHOUT ANY WARRANTY.
}}IS_RIGHT
*/
package org.zkoss.idom;
import org.w3c.dom.Attr;
import org.w3c.dom.TypeInfo;
import org.zkoss.lang.Objects;
import org.zkoss.idom.impl.*;
/**
* The iDOM attribute.
*
* <p>Design decision: Attribute is also a item. The reason is
* it simplifies the use of XPath. An XPath might return either
* elements or attributes, so...
*
* @author tomyeh
* @see Element
*/
public class Attribute extends AbstractItem implements Namespaceable, Attr {
/** The namespace. */
protected Namespace _ns;
/** The owner item. */
protected Item _owner;
/** The local name. */
protected String _lname;
/** The value. */
protected String _value;
/**
* Constructor.
*
* <p>Note: According to W3/DOM, the namespace of attributes must
* have a prefix if the uri is not empty.
*
* @param nsURI the namespace URI
* @param tname the tag name
*/
public Attribute(String nsURI, String tname, String value) {
int kp = tname.indexOf(':');
String prefix = kp >= 0 ? tname.substring(0, kp): "";
String lname = kp >= 0 ? tname.substring(kp + 1): tname;
setNamespace(prefix, nsURI);
setLocalName(lname);
setValue(value);
}
/**
* Constructor.
*
* @param ns the namespace
* @param lname the local name
*/
public Attribute(Namespace ns, String lname, String value) {
setNamespace(ns);
setLocalName(lname);
setValue(value);
}
/**
* Constructor.
*/
public Attribute(String lname, String value) {
this(Namespace.NO_NAMESPACE, lname, value);
}
/**
* Constructor.
*/
protected Attribute() {
_ns = Namespace.NO_NAMESPACE;
}
//-- Attribute extra --//
/**
* Gets the value of this attribute.
*/
public final String getValue() {
return _value;
}
/**
* Sets the value of this attribute.
* According to Section 3.3.3 of XML 1.0 spec, the value is always
* normalized. Whether to trim depends on whether an attribute is CDATA
* (default).
* In this version, we don't normalize or trim
* (i.e., consider it as CDATA).
*
* @param value the new value; null is considered as empty
*/
public final void setValue(String value) {
if (value == null)
value = "";
// else
// value = value.trim();
//TODO: check whether the attribute has been declared to CDATA or not
if (!Objects.equals(_value, value)) {
Verifier.checkCharacterData(value, getLocator());
_value = value;
}
}
/**
* Gets the item that owns this attribute.
*/
public final Item getOwner() {
return _owner;
}
/**
* Sets the item that owns this attribute.
*
* <p><b><i>DO NOT</i></b> call this method. It is used internally.
* For user's point of view, the owner item is maintained
* automatically, so user never needs to update it.
*/
public final void setOwner(Item owner) {
if (_owner != owner) {
_ns = reuseNamespace(owner, _ns); //check and reuse
_owner = owner;
}
}
/** Search for the namespace to see any possible to reuse.
*/
private static Namespace reuseNamespace(Item owner, Namespace ns) {
if (ns.getPrefix().length() > 0 && (owner instanceof Element)) {
final Namespace found =
((Element)owner).getNamespace(ns.getPrefix());
if (found == null)
throw new DOMException(DOMException.NAMESPACE_ERR,
"Attribute's namespace, "+ns+", not found in element");
if (!ns.equals(found))
throw new DOMException(DOMException.NAMESPACE_ERR,
"Attribute's namespace, "+ns+", conflicts with element's, "+found);
return found;
}
return ns;
}
//-- Namespaceable --//
/** Sets the namespace.
*/
public final void setNamespace(String prefix, String nsURI) {
setNamespace(
(prefix == null || prefix.length() == 0)
&& (nsURI == null || nsURI.length() ==0 ) ?
null: new Namespace(prefix, nsURI));
}
/**
* Sets the namespace.
* <p>According W3C/DOM, unlike element, an attribute doesn't allow
* a namespace that has an URI but without a prefix.
*/
public final void setNamespace(Namespace ns) {
if (ns != null && ns.getPrefix().length() == 0
&& ns.getURI().length() != 0)
throw new DOMException(DOMException.NAMESPACE_ERR,
"Attribute's namespace without a prefix cannot have URI", getLocator());
if (ns == null || ns.getPrefix().length() == 0)
ns = Namespace.NO_NAMESPACE;
else
ns = reuseNamespace(_owner, ns); //check and reuse
_ns = ns;
}
public final Namespace getNamespace() {
return _ns;
}
public final String getTagName() {
return _ns.tagNameOf(_lname);
}
/**
* Sets the tag name.
*
* <p>Changing a name improperly might cause replicated attribute
* names which won't be detected by this method.
*/
public final void setTagName(String tname) {
if (!Objects.equals(tname, getName())) {
int kp = tname.indexOf(':');
String prefix = kp >= 0 ? tname.substring(0, kp): "";
String lname = kp >= 0 ? tname.substring(kp + 1): tname;
setPrefix(prefix);
setLocalName(lname);
}
}
public final String getLocalName() {
return _lname;
}
/**
* Sets the local name of this attribute.
*
* <p>Changing a name improperly might cause replicated attribute
* names which won't be detected by this method.
*/
public final void setLocalName(String lname) {
if (!Objects.equals(lname, getLocalName())) {
Verifier.checkAttributeName(lname, getLocator());
_lname = lname;
}
}
//-- Item --//
public final String getName() {
return getTagName();
}
public final void setName(String tname) {
setTagName(tname);
}
public final String getText() {
return getValue();
}
public final void setText(String text) {
setValue(text);
}
/**
* Gets the document that owns this attribute.
*/
public final Document getDocument() {
return _owner != null ? _owner.getDocument(): null;
}
/**
* Detach the attribute from its owner, if any.
* Only attributes that belongs to no item or the same item
* are allowed to be added to a item. So, detach is useful
* to move an attribute out from a item (and then you might
* add it to another item).
*/
public Item detach() {
if (_owner != null) {
((Attributable)_owner).getAttributeItems().remove(this);
assert(_owner == null);
}
return this;
}
public void setParent(Item parent) {
throw new DOMException(DOMException.INVALID_ACCESS_ERR,
"Attributes do not have parent", getLocator());
}
//-- Node --//
public final short getNodeType() {
return ATTRIBUTE_NODE;
}
public final org.w3c.dom.Document getOwnerDocument() {
return getDocument();
}
public final String getNamespaceURI() {
return _ns.getURI();
}
public final String getPrefix() {
return _ns.getPrefix();
}
/**
* Sets the namespace prefix of this attribute.
*
* <p>Changing a prefix improperly might cause replicated attribute
* names which won't be detected by this method.
*/
public final void setPrefix(String prefix) {
setNamespace(prefix, _ns.getURI());
}
public TypeInfo getSchemaTypeInfo() {
throw new UnsupportedOperationException("DOM Level 3");
}
public boolean isId() {
throw new UnsupportedOperationException("DOM Level 3");
}
//-- Attr --//
public final boolean getSpecified() {
return false; //TODO
}
public final org.w3c.dom.Element getOwnerElement() {
return (org.w3c.dom.Element)getOwner();
}
//-- Object --//
public final String toString() {
return "[Attribute: " + getTagName() + "=\"" + _value + "\"]";
}
public Object clone() {
Attribute v = (Attribute)super.clone();
v._owner = null;
return v;
}
}