0001: /* Element.java
0002:
0003: {{IS_NOTE
0004:
0005: Purpose:
0006: Description:
0007: History:
0008: 2001/10/22 17:10:29, Create, Tom M. Yeh
0009: }}IS_NOTE
0010:
0011: Copyright (C) 2001 Potix Corporation. All Rights Reserved.
0012:
0013: {{IS_RIGHT
0014: This program is distributed under GPL Version 2.0 in the hope that
0015: it will be useful, but WITHOUT ANY WARRANTY.
0016: }}IS_RIGHT
0017: */
0018: package org.zkoss.idom;
0019:
0020: import java.util.Map;
0021: import java.util.LinkedHashMap;
0022: import java.util.Collection;
0023: import java.util.AbstractCollection;
0024: import java.util.List;
0025: import java.util.Iterator;
0026: import java.util.LinkedList;
0027: import java.util.Collections;
0028: import java.util.NoSuchElementException;
0029: import java.util.regex.Pattern;
0030: import java.io.IOException;
0031: import java.io.ObjectInputStream;
0032: import java.io.ObjectOutputStream;
0033: import javax.xml.transform.TransformerException;
0034:
0035: import org.w3c.dom.Attr;
0036: import org.w3c.dom.Node;
0037: import org.w3c.dom.NodeList;
0038: import org.w3c.dom.NamedNodeMap;
0039: import org.w3c.dom.TypeInfo;
0040:
0041: import org.zkoss.mesg.Messages;
0042: import org.zkoss.mesg.MCommon;
0043: import org.zkoss.util.CollectionsX;
0044: import org.zkoss.util.CheckableTreeArray;
0045: import org.zkoss.xml.FacadeNodeList;
0046: import org.zkoss.idom.impl.*;
0047:
0048: /**
0049: * The iDOM element.
0050: *
0051: * @author tomyeh
0052: * @see Attribute
0053: */
0054: public class Element extends AbstractGroup implements Attributable,
0055: Namespaceable, org.w3c.dom.Element {
0056: /** The namespace. */
0057: protected Namespace _ns;
0058: /** The local name. */
0059: protected String _lname;
0060: /** The attributes. May be null. */
0061: protected List _attrs = null;
0062: /** Additional namespaces. May be null*/
0063: protected Map _addNamespaces = null;
0064: /** Whether it is aware of the attribute modification. */
0065: boolean _attrModAware = false;
0066:
0067: /**
0068: * Constructor.
0069: *
0070: * @param nsURI the namespace URI
0071: * @param tname the tag name
0072: */
0073: public Element(String nsURI, String tname) {
0074: int kp = tname.indexOf(':');
0075: String prefix = kp >= 0 ? tname.substring(0, kp) : "";
0076: String lname = kp >= 0 ? tname.substring(kp + 1) : tname;
0077: setNamespace(prefix, nsURI);
0078: setLocalName(lname);
0079: }
0080:
0081: /**
0082: * Constructor.
0083: *
0084: * @param ns the namespace; if null, the default namespace is assumed
0085: * (not necessary {@link Namespace#NO_NAMESPACE}).
0086: * @param lname the local name
0087: */
0088: public Element(Namespace ns, String lname) {
0089: setNamespace(ns);
0090: setLocalName(lname);
0091: }
0092:
0093: /**
0094: * Constructor without a namespace (i.e., {@link Namespace#NO_NAMESPACE}).
0095: *
0096: * @param lname the local name
0097: */
0098: public Element(String lname) {
0099: this (Namespace.NO_NAMESPACE, lname);
0100: }
0101:
0102: /**
0103: * Constructor.
0104: * Unlike other constructors, it doesn't set the modification flag.
0105: */
0106: protected Element() {
0107: _ns = Namespace.NO_NAMESPACE;
0108: }
0109:
0110: //-- Element extras --//
0111: /**
0112: * Tests whether this element is the root element of
0113: * the owning document.
0114: *
0115: * <p>Note: false is returned if it doesn't has any parent.
0116: */
0117: public final boolean isRootElement() {
0118: return getParent() instanceof Document;
0119: }
0120:
0121: /**
0122: * Returns the Namespace in scope on this element for the given
0123: * prefix (this involves searching up the tree, so the results depend
0124: * on the current location of the element), or null if not found.
0125: *
0126: * <p>If prefix is empty, it searches for the "default" namespace
0127: * in scope. Thus, to search for attribute's namespace, caller
0128: * have to skip this one and use NO_NAMESPACE.
0129: * (due XML, an attribute without prefix is NO_NAMESPACE)
0130: *
0131: * @param prefix namespace prefix to look up; null for empty
0132: */
0133: public final Namespace getNamespace(String prefix) {
0134: if (prefix == null)
0135: prefix = "";
0136:
0137: Namespace ns = Namespace.getSpecial(prefix);
0138: if (ns != null)
0139: return ns;
0140:
0141: ns = getNamespace(); //might be null if in constructor
0142: if (ns != null && prefix.equals(ns.getPrefix()))
0143: return ns;
0144:
0145: if (_addNamespaces != null) {
0146: ns = (Namespace) _addNamespaces.get(prefix);
0147: if (ns != null)
0148: return ns;
0149: }
0150:
0151: if (getParent() instanceof Element) //only Element implements it; not Namespaceable
0152: return ((Element) getParent()).getNamespace(prefix);
0153:
0154: return prefix.length() > 0 ? null : Namespace.NO_NAMESPACE;
0155: }
0156:
0157: /**
0158: * Returns namespace declared on this element.
0159: *
0160: * <p>It is <i>not</i> a "live" representation. Also, it is read-only.
0161: *
0162: * <p>Note: Namespace.equals compares namespace's URI. However,
0163: * the distinction here is the prefix, because it is mainly for
0164: * getNamespace(prefix).
0165: *
0166: * @return the namespace declarations.
0167: */
0168: public final Collection getDeclaredNamespaces() {
0169: return _addNamespaces == null ? Collections.EMPTY_LIST
0170: : _addNamespaces.values();
0171: }
0172:
0173: /**
0174: * Adds a namespace to the namespace declaration.
0175: *
0176: * @return true if the namespace is added
0177: * @exception DOMException if the name space with the same prefix
0178: * already exists but with different URI
0179: */
0180: public final boolean addDeclaredNamespace(Namespace ns) {
0181: if (_addNamespaces == null) {
0182: _addNamespaces = new LinkedHashMap(5);
0183: } else {
0184: final Namespace old = (Namespace) _addNamespaces.get(ns
0185: .getPrefix());
0186: if (old != null) {
0187: if (!old.equals(ns))
0188: throw new DOMException(DOMException.NAMESPACE_ERR,
0189: "Add a conflict namespace: " + ns
0190: + ", while " + old
0191: + " already exists");
0192: return false;
0193: }
0194: }
0195:
0196: _addNamespaces.put(ns.getPrefix(), ns);
0197: return true;
0198: }
0199:
0200: /**
0201: * Gets the content of this element.
0202: *
0203: * <p>The content of an element is the first Binary or Text child of
0204: * the element. Each element can has zero or one content.
0205: *
0206: * <p>Note: {@link #getText} returns the catenation of all Text
0207: * children, not just the first one.
0208: *
0209: * @return the content of this element; null if no such child
0210: * @see #getContent(String)
0211: */
0212: public final Object getContent() {
0213: for (final Iterator it = _children.iterator(); it.hasNext();) {
0214: Object o = it.next();
0215: if (o instanceof Text) {
0216: return ((Text) o).getText();
0217: } else if (o instanceof Binary) {
0218: return ((Binary) o).getValue();
0219: } else if (o instanceof CData) {
0220: return ((CData) o).getText();
0221: }
0222: }
0223: return null;
0224: }
0225:
0226: /**
0227: * Sets the content of this element.
0228: *
0229: * <p>All existent Binary or Text children of this element are removed
0230: * first. If the object is a String, a Text item is created to hold
0231: * it. Otherwise, a Binary item is created to hold it.
0232: *
0233: * <p>Non-Binary/Text children are preserved.
0234: *
0235: * <p>If obj is a {@link Item} or an array/collection of {@link Item},
0236: * this method will add them as child vertices rather than
0237: * being the content.
0238: * Moreever, if the first item of the array/collection is {@link Item},
0239: * it is assumed to be all valid component to being has valid vertices.
0240: * If not, an exception is thrown.
0241: *
0242: * <p>Thus, getContent might not return the object being set by setContent.
0243: *
0244: * @param obj the object to set; null is OK
0245: * @return the previous content
0246: * @see #getContent()
0247: */
0248: public final Object setContent(Object obj) {
0249: if (obj instanceof Item) {
0250: getChildren().add(obj);
0251: return null; //done
0252: }
0253: if (obj instanceof Collection) {
0254: final Collection c = (Collection) obj;
0255: final Iterator it = c.iterator();
0256: if (it.hasNext() && (it.next() instanceof Item)) {
0257: getChildren().addAll(c);
0258: return null; //done
0259: }
0260: } else if (obj instanceof Object[]) {
0261: Object[] ary = (Object[]) obj;
0262: if (ary.length > 0 && (ary[0] instanceof Item)) {
0263: for (int j = 0; j < ary.length; ++j)
0264: getChildren().add(ary[j]);
0265: return null; //done
0266: }
0267: }
0268:
0269: Object ret = null;
0270: boolean retFound = false;
0271: boolean bStr = obj instanceof String;
0272: for (final Iterator it = _children.iterator(); it.hasNext();) {
0273: Object o = it.next();
0274: if (o instanceof Text) {
0275: if (!retFound) {
0276: retFound = true;
0277: ret = ((Text) o).getText();
0278: }
0279: if (!bStr || obj == null) {
0280: it.remove();
0281: } else {
0282: ((Text) o).setText((String) obj);
0283: obj = null; //then, the following will be removed
0284: }
0285: } else if (o instanceof Binary) {
0286: if (!retFound) {
0287: retFound = true;
0288: ret = ((Binary) o).getValue();
0289: }
0290: if (bStr || obj == null) {
0291: it.remove();
0292: } else {
0293: ((Binary) o).setValue(obj);
0294: obj = null; //then, the following will be removed
0295: }
0296: } else if (o instanceof CData) {
0297: if (!retFound) {
0298: retFound = true;
0299: ret = ((CData) o).getText();
0300: }
0301: it.remove(); //always remove and add
0302: }
0303: }
0304: if (obj != null)
0305: _children.add(0, bStr ? (Object) new Text((String) obj)
0306: : (Object) new Binary(obj));
0307: return ret;
0308: }
0309:
0310: /**
0311: * Returns the content of the child element with the giving path, or
0312: * null if the content is null or the child element doesn't exist.
0313: *
0314: * <p>Note that there might be more than one child with the same path
0315: * in an idom tree; this method simply picks the first one that matchs and
0316: * returns its content. To access certain one, you might use [n] to
0317: * [@attr = value] specify which one to access.
0318: *
0319: *
0320: * <p>To know whether the child element exists or conent is null,
0321: * use {@link #hasContent}.
0322: *
0323: * <p>The content of an element is a special feature of iDOM.
0324: * Like a Map, it is designed to let developers use names (in a path-like
0325: * format) to access objects. See {@link #setContent(String, Object)}.
0326: *
0327: * <p>Like Unix path, the giving name could use '/' to catenate a series
0328: * of child elements.
0329: *
0330: * <p>An empty path denotes this element itself. Leading, ending
0331: * and consecutive '/' will be ignored.
0332: *
0333: * <p>Example:<br>
0334: * <code>Object o = element.getContent("abc/def");<br>
0335: * String s = Objects.toString(element.getContent("ab/cd"));<br>
0336: * element.setContent("t:ab/cd/f:ef", new Integer(10));</code>
0337: *
0338: * <p>TODO: support [n] and [@attr = value]
0339: *
0340: * @param path a path; e.g., "b", "a/b", "t:a/t:b"
0341: * @see #getContent()
0342: */
0343: public final Object getContent(String path) {
0344: Element e = this ;
0345: int j = 0;
0346: while (true) {
0347: int k = path.indexOf('/', j);
0348: String tname = k >= 0 ? path.substring(j, k) : path
0349: .substring(j);
0350: if (tname.length() > 0) {
0351: e = e.getElement(tname);
0352: if (e == null)
0353: return null;
0354: }
0355:
0356: if (k < 0)
0357: return e.getContent();
0358: j = k + 1;
0359: }
0360: }
0361:
0362: /**
0363: * Tests whether the child element with the giving path exists. Note that
0364: * there might be more than one child with the same path in an idom tree;
0365: * this method simply tell you that "yes", at least on such path exist.
0366: *
0367: * To get the content, use {@link #getContent(String)}.
0368: */
0369: public final boolean hasContent(String path) {
0370: Element e = this ;
0371: int j = 0;
0372: while (true) {
0373: int k = path.indexOf('/', j);
0374: String tname = k >= 0 ? path.substring(j, k) : path
0375: .substring(j);
0376: if (tname.length() > 0) {
0377: e = e.getElement(tname);
0378: if (e == null)
0379: return false;
0380: }
0381:
0382: if (k < 0)
0383: return true;
0384: j = k + 1;
0385: }
0386: }
0387:
0388: /**
0389: * Sets the content of the child element with the giving path.
0390: *
0391: * <p>Note that there might be more than one child with the same path
0392: * in an idom tree; this method simply pick one that matchs and set
0393: * its content (see {@link #setContent(Object)}).
0394: *
0395: * <p>The content of an element is a special feature of iDOM.
0396: * Like a Map, it is designed to let developers use names (in a path-like
0397: * format) to access objects. See {@link #getContent(String)}.
0398: *
0399: * <p>Like Unix path, the giving name could use '/' to catenate a series
0400: * of child elements.
0401: *
0402: * <p>An empty path denotes this element itself. Leading, ending
0403: * and consecutive '/' will be ignored.
0404: *
0405: * <p>If any element in the path is not found, it will be created
0406: * automatically.
0407: *
0408: * @param path a path; e.g., "b", "a/b", "t:a/t:b"
0409: * @param obj the object to set; null is acceptable
0410: * @return the previous content
0411: *
0412: * @see #setContent(Object)
0413: * @see #removeContent
0414: * @see #hasContent
0415: */
0416: public final Object setContent(String path, Object obj) {
0417: Element e = this ;
0418: int j = 0;
0419: while (true) {
0420: int k = path.indexOf('/', j);
0421: String tname = k >= 0 ? path.substring(j, k) : path
0422: .substring(j);
0423: if (tname.length() > 0) {
0424: Element e2 = e.getElement(tname);
0425: if (e2 == null) {
0426: e2 = new Element(e.getNamespace().getURI(), tname);
0427: e.getChildren().add(e2);
0428: }
0429: e = e2;
0430: }
0431:
0432: if (k < 0)
0433: return e.setContent(obj);
0434: j = k + 1;
0435: }
0436: }
0437:
0438: /**
0439: * Removes the content of the child element with the giving path,
0440: * and the child element itself if no other child.
0441: *
0442: * <p>Unlike {@link #setContent(String, Object)} with null,
0443: * the child element identified by path will be detached if it has no
0444: * other child (but the content). So does its parent
0445: * <i>excluding</i> this element. Thus, removeContent(path)
0446: * could undo setContent(path, v).
0447: *
0448: * @return the previous content
0449: * @see #setContent(String, Object)
0450: */
0451: public final Object removeContent(String path) {
0452: Element e = this ;
0453: int j = 0;
0454: while (true) {
0455: int k = path.indexOf('/', j);
0456: String tname = k >= 0 ? path.substring(j, k) : path
0457: .substring(j);
0458: if (tname.length() > 0) {
0459: e = e.getElement(tname);
0460: if (e == null)
0461: return null;
0462: }
0463:
0464: if (k < 0) {
0465: Object ret = e.setContent(null);
0466:
0467: //try to remove e; not including this
0468: for (Group group = e; group != this
0469: && group.getChildren().size() == 0;) {
0470: Group parent = group.getParent();
0471: group.detach();
0472: group = parent;
0473: }
0474: return ret;
0475: }
0476: j = k + 1;
0477: }
0478: }
0479:
0480: //-- utilities --//
0481: /** Returns the text of a child; never null. */
0482: private static final String getTextOfChild(Object o) {
0483: if (!(o instanceof AbstractTextual))
0484: return "";
0485: final AbstractTextual t = (AbstractTextual) o;
0486: return t.isPartOfParentText() ? t.getText() : "";
0487: }
0488:
0489: //-- Namespaceable --//
0490: /**
0491: * Sets the namespace.
0492: * If ns is null, the default namespace is assumed (not necessary
0493: * {@link Namespace#NO_NAMESPACE}.
0494: * <p>According W3C/DOM, unlike element, an attribute doesn't allow
0495: * a namespace that has an URI but without a prefix.
0496: */
0497: public final void setNamespace(Namespace ns) {
0498: checkWritable();
0499: if (ns == null) {
0500: if (_ns != null && _ns.getPrefix().length() == 0)
0501: return; //nothing to do
0502: ns = getNamespace("");
0503: if (ns == null)
0504: ns = Namespace.NO_NAMESPACE;
0505: }
0506: final Namespace old = getNamespace(ns.getPrefix());
0507: if (old != null && old.equals(ns))
0508: ns = old; //re-use if already defined
0509: _ns = ns;
0510: }
0511:
0512: /** Sets the namespace.
0513: */
0514: public final void setNamespace(String prefix, String nsURI) {
0515: if (nsURI == null)
0516: nsURI = "";
0517: final Namespace ns = getNamespace(prefix);
0518: if (ns != null) {
0519: if (ns.getURI().equals(nsURI)) {
0520: setNamespace(ns);
0521: return;
0522: }
0523: }
0524: setNamespace(new Namespace(prefix, nsURI));
0525: }
0526:
0527: public final Namespace getNamespace() {
0528: return _ns;
0529: }
0530:
0531: public final String getTagName() {
0532: return _ns.tagNameOf(_lname);
0533: }
0534:
0535: public final void setTagName(String tname) {
0536: checkWritable();
0537: int kp = tname.indexOf(':');
0538: String prefix = kp >= 0 ? tname.substring(0, kp) : "";
0539: String lname = kp >= 0 ? tname.substring(kp + 1) : tname;
0540: setPrefix(prefix);
0541: setLocalName(lname);
0542: }
0543:
0544: public final String getLocalName() {
0545: return _lname;
0546: }
0547:
0548: public final void setLocalName(String lname) {
0549: checkWritable();
0550: Verifier.checkElementName(lname, getLocator());
0551: _lname = lname;
0552: }
0553:
0554: //-- Item --//
0555: /** Clear the modification flag.
0556: * <p>Note: if both includingDescendant and
0557: * {@link #isAttributeModificationAware} are true, attributes'
0558: * modified flags are cleaned.
0559: */
0560: public void clearModified(boolean includingDescendant) {
0561: if (includingDescendant && _attrModAware) {
0562: for (final Iterator it = _attrs.iterator(); it.hasNext();)
0563: ((Item) it.next()).clearModified(true);
0564: }
0565: super .clearModified(includingDescendant);
0566: }
0567:
0568: public Item clone(boolean preserveModified) {
0569: Element elem = (Element) super .clone(preserveModified);
0570:
0571: if (_addNamespaces != null) {
0572: elem._addNamespaces = new LinkedHashMap();
0573: elem._addNamespaces.putAll(_addNamespaces);
0574: }
0575: if (_attrs != null) {
0576: elem._attrs = elem.newAttrArray();
0577: //NOTE: AttrArray is an inner class, so we must use the right
0578: //object to create the array. Here is 'elem'.
0579:
0580: for (final Iterator it = _attrs.iterator(); it.hasNext();) {
0581: Item v = ((Item) it.next()).clone(preserveModified);
0582: boolean bClearModified = !preserveModified
0583: || !v.isModified();
0584:
0585: elem._attrs.add(v); //v becomes modified (v.setParent is called)
0586:
0587: if (bClearModified)
0588: v.clearModified(false);
0589: }
0590: }
0591: elem._modified = preserveModified && _modified;
0592: return elem;
0593: }
0594:
0595: /**
0596: * Gets the tag name of the element -- the name with prefix.
0597: * To get the local name, use getLocalName.
0598: */
0599: public final String getName() {
0600: return getTagName();
0601: }
0602:
0603: /**
0604: * Sets the tag name of the element.
0605: * It will affect the local name and the namespace's prefix.
0606: */
0607: public final void setName(String tname) {
0608: setTagName(tname);
0609: }
0610:
0611: /** Returns the catenation of {@link Textual} children; never null.
0612: * Note: both <tag/> and <tag></tag> returns an
0613: * empty string. To tell the difference, check the number of children.
0614: * @see #getText(boolean)
0615: */
0616: public final String getText() {
0617: if (_children.size() == 1) //optimize this case
0618: return getTextOfChild(_children.get(0));
0619:
0620: final StringBuffer sb = new StringBuffer(256);
0621: for (final Iterator it = _children.iterator(); it.hasNext();)
0622: sb.append(getTextOfChild(it.next()));
0623: return sb.toString();
0624: }
0625:
0626: /** Returns the catenation of {@link Textual} children; never null.
0627: *
0628: * @param trim whether to trim before returning
0629: * @see #getText()
0630: */
0631: public final String getText(boolean trim) {
0632: String t = getText();
0633: return trim && t != null ? t.trim() : t;
0634: }
0635:
0636: //-- Attributable --//
0637: public final boolean isAttributeModificationAware() {
0638: return _attrModAware;
0639: }
0640:
0641: public final void setAttributeModificationAware(boolean aware) {
0642: _attrModAware = aware;
0643: }
0644:
0645: public final List getAttributeItems() {
0646: if (_attrs == null)
0647: _attrs = newAttrArray();
0648: return _attrs;
0649: }
0650:
0651: /** Creates an empty list of attributes.
0652: */
0653: protected List newAttrArray() {
0654: return new AttrArray();
0655: }
0656:
0657: public final int getAttributeIndex(int indexFrom, String namespace,
0658: String name, int mode) {
0659: if (_attrs == null || indexFrom < 0
0660: || indexFrom >= _attrs.size())
0661: return -1;
0662:
0663: final Pattern ptn = (mode & FIND_BY_REGEX) != 0 ? Pattern
0664: .compile(name) : null;
0665:
0666: final Iterator it = _attrs.listIterator(indexFrom);
0667: for (int j = indexFrom; it.hasNext(); ++j)
0668: if (match((Attribute) it.next(), namespace, name, ptn, mode))
0669: return j;
0670:
0671: return -1;
0672: }
0673:
0674: public final int getAttributeIndex(int indexFrom, String tname) {
0675: return getAttributeIndex(indexFrom, null, tname,
0676: FIND_BY_TAGNAME);
0677: }
0678:
0679: public final Attribute getAttributeItem(String namespace,
0680: String name, int mode) {
0681: int j = getAttributeIndex(0, namespace, name, mode);
0682: return j >= 0 ? (Attribute) _attrs.get(j) : null;
0683: }
0684:
0685: public final Attribute getAttributeItem(String tname) {
0686: int j = getAttributeIndex(0, tname);
0687: return j >= 0 ? (Attribute) _attrs.get(j) : null;
0688: }
0689:
0690: public final List getAttributes(String namespace, String name,
0691: int mode) {
0692: if (_attrs == null)
0693: return Collections.EMPTY_LIST;
0694:
0695: Pattern ptn = (mode & FIND_BY_REGEX) != 0 ? Pattern
0696: .compile(name) : null;
0697:
0698: final List list = new LinkedList();
0699: for (final Iterator it = _attrs.iterator(); it.hasNext();) {
0700: Attribute attr = (Attribute) it.next();
0701: if (match(attr, namespace, name, ptn, mode))
0702: list.add(attr);
0703: }
0704: return list;
0705: }
0706:
0707: public final Attribute setAttribute(Attribute attr) {
0708: checkWritable();
0709: int j = getAttributeIndex(0, attr.getTagName());
0710: if (j >= 0) {
0711: return (Attribute) getAttributeItems().set(j, attr);
0712: } else {
0713: getAttributeItems().add(attr);
0714: return null;
0715: }
0716: }
0717:
0718: public final String getAttributeValue(String namespace,
0719: String name, int mode) {
0720: final Attribute attr = getAttributeItem(namespace, name, mode);
0721: return attr != null ? attr.getValue() : null;
0722: }
0723:
0724: public final String getAttributeValue(String tname) {
0725: Attribute attr = getAttributeItem(tname);
0726: return attr != null ? attr.getValue() : null;
0727: }
0728:
0729: public final Attribute setAttributeValue(String tname, String value) {
0730: checkWritable();
0731: Attribute attr = getAttributeItem(tname);
0732: if (attr != null)
0733: attr.setValue(value);
0734: else
0735: getAttributeItems().add(new Attribute(tname, value));
0736: return attr;
0737: }
0738:
0739: //-- Node --//
0740: public final short getNodeType() {
0741: return ELEMENT_NODE;
0742: }
0743:
0744: /**
0745: * Always null. Unlike other nodes, it is not the same as getText.
0746: */
0747: public final String getNodeValue() {
0748: return null;
0749: }
0750:
0751: public final NamedNodeMap getAttributes() {
0752: return new AttrMap();
0753: }
0754:
0755: public final boolean hasAttributes() {
0756: return _attrs != null && !_attrs.isEmpty();
0757: }
0758:
0759: public final String getNamespaceURI() {
0760: return _ns.getURI();
0761: }
0762:
0763: public final String getPrefix() {
0764: return _ns.getPrefix();
0765: }
0766:
0767: public final void setPrefix(String prefix) {
0768: setNamespace(prefix, _ns.getURI());
0769: }
0770:
0771: //-- Element --//
0772: public final NodeList getElementsByTagName(String tname) {
0773: return new FacadeNodeList(getElements(null, tname,
0774: FIND_BY_TAGNAME | FIND_RECURSIVE));
0775: }
0776:
0777: public final NodeList getElementsByTagNameNS(String nsURI,
0778: String lname) {
0779: return new FacadeNodeList(getElements(nsURI, lname,
0780: FIND_RECURSIVE));
0781: }
0782:
0783: public final Attr getAttributeNode(String tname) {
0784: return getAttributeItem(tname);
0785: }
0786:
0787: public final Attr getAttributeNodeNS(String nsURI, String lname) {
0788: return getAttributeItem(nsURI, lname, 0);
0789: }
0790:
0791: public final String getAttribute(String tname) {
0792: String val = getAttributeValue(tname);
0793: return val != null ? val : ""; //w3c spec
0794: }
0795:
0796: public final String getAttributeNS(String nsURI, String lname) {
0797: Attribute attr = getAttributeItem(nsURI, lname, 0);
0798: return attr != null ? attr.getValue() : "";
0799: }
0800:
0801: public final void setAttribute(String tname, String value) {
0802: setAttributeValue(tname, value);
0803: }
0804:
0805: public final void setAttributeNS(String nsURI, String tname,
0806: String value) {
0807: checkWritable();
0808:
0809: int kp = tname.indexOf(':');
0810: String prefix = kp >= 0 ? tname.substring(0, kp) : "";
0811: String lname = kp >= 0 ? tname.substring(kp + 1) : tname;
0812: Attribute attr = getAttributeItem(nsURI, lname, 0);
0813: if (attr != null) {
0814: attr.setPrefix(prefix); //also change prefix
0815: attr.setValue(value);
0816: } else {
0817: getAttributeItems().add(new Attribute(nsURI, tname, value));
0818: }
0819: }
0820:
0821: public final Attr setAttributeNode(Attr newAttr) {
0822: return setAttribute((Attribute) newAttr);
0823: }
0824:
0825: public final Attr setAttributeNodeNS(Attr newAttr) {
0826: Attribute attr = (Attribute) newAttr;
0827: int j = getAttributeIndex(0, attr.getNamespace().getURI(), attr
0828: .getLocalName(), 0);
0829: if (j >= 0) {
0830: return (Attr) getAttributeItems().set(j, newAttr);
0831: } else {
0832: getAttributeItems().add(newAttr);
0833: return null;
0834: }
0835: }
0836:
0837: public final void removeAttribute(String tname) {
0838: int j = getAttributeIndex(0, tname);
0839: if (j >= 0)
0840: _attrs.remove(j);
0841: }
0842:
0843: public final void removeAttributeNS(String nsURI, String lname) {
0844: int j = getAttributeIndex(0, nsURI, lname, 0);
0845: if (j >= 0)
0846: _attrs.remove(j);
0847: }
0848:
0849: public final Attr removeAttributeNode(Attr oldAttr) {
0850: Attribute attr = (Attribute) oldAttr;
0851: int j = getAttributeIndex(0, attr.getTagName());
0852: if (j >= 0) {
0853: return (Attr) _attrs.remove(j);
0854: } else {
0855: throw new DOMException(DOMException.NOT_FOUND_ERR,
0856: getLocator());
0857: }
0858: }
0859:
0860: public final boolean hasAttribute(String tname) {
0861: return getAttributeIndex(0, tname) >= 0;
0862: }
0863:
0864: public final boolean hasAttributeNS(String nsURI, String lname) {
0865: return getAttributeIndex(0, nsURI, lname, 0) >= 0;
0866: }
0867:
0868: public final String toString() {
0869: StringBuffer sb = new StringBuffer(64).append("[Element: <")
0870: .append(getTagName());
0871:
0872: String uri = getNamespace().getURI();
0873: if (uri.length() != 0)
0874: sb.append(" [").append(uri).append(']');
0875:
0876: if (_attrs != null) {
0877: for (final Iterator it = _attrs.iterator(); it.hasNext();) {
0878: Attribute attr = (Attribute) it.next();
0879: sb.append(' ').append(attr.getTagName()).append("=\"")
0880: .append(attr.getValue()).append('"');
0881: }
0882: }
0883:
0884: return sb.append("/>]").toString();
0885: }
0886:
0887: public TypeInfo getSchemaTypeInfo() {
0888: throw new UnsupportedOperationException("DOM Level 3");
0889: }
0890:
0891: public void setIdAttribute(String name, boolean isId)
0892: throws DOMException {
0893: //Level 3 not yet
0894: }
0895:
0896: public void setIdAttributeNS(String namespaceURI, String localName,
0897: boolean isId) throws DOMException {
0898: //Level 3 not yet
0899: }
0900:
0901: public void setIdAttributeNode(Attr idAttr, boolean isId)
0902: throws DOMException {
0903: //Level 3 not yet
0904: }
0905:
0906: //-- AttrArray --//
0907: protected class AttrArray extends CheckableTreeArray {
0908: protected AttrArray() {
0909: }
0910:
0911: //-- utilities --//
0912: private Attribute checkAdd(Object newItem) {
0913: checkWritable();
0914:
0915: if (!(newItem instanceof Attribute))
0916: throw new DOMException(
0917: DOMException.HIERARCHY_REQUEST_ERR,
0918: "Invalid type", getLocator());
0919:
0920: Attribute attr = (Attribute) newItem;
0921: if (attr.getOwner() != null)
0922: throw new DOMException(
0923: DOMException.HIERARCHY_REQUEST_ERR,
0924: "Attribute, "
0925: + attr.toString()
0926: + ", owned by other; detach or clone it",
0927: getLocator());
0928:
0929: return attr;
0930: }
0931:
0932: //-- CheckableTreeArray --//
0933: protected void onAdd(Object newElement, Object followingElement) {
0934: checkAdd(newElement, followingElement, false);
0935: }
0936:
0937: protected void onSet(Object newElement, Object replaced) {
0938: assert (replaced != null);
0939: checkAdd(newElement, replaced, true);
0940: }
0941:
0942: private void checkAdd(Object newItem, Object other,
0943: boolean replace) {
0944: //first, remove any existent with the same uri and name
0945: Object attrRemoved = null;
0946: Attribute attr = checkAdd(newItem);
0947:
0948: int j = getAttributeIndex(0, attr.getTagName());
0949: if (j >= 0 && (!replace || get(j) != other))
0950: throw new DOMException(
0951: DOMException.HIERARCHY_REQUEST_ERR,
0952: "Attribute name, " + attr.getTagName()
0953: + ", is conflicts with existent one ("
0954: + j + ')', getLocator());
0955:
0956: try {
0957: if (replace)
0958: onRemove(other);
0959: attr.setOwner(Element.this );
0960: } catch (RuntimeException ex) {
0961: if (replace) {
0962: Attribute attrRep = (Attribute) other;
0963: if (attrRep.getOwner() == null)
0964: attrRep.setOwner(Element.this ); //restore it
0965: }
0966: if (attrRemoved != null)
0967: super .add(j, attrRemoved); //restore it
0968: throw ex;
0969: }
0970: }
0971:
0972: protected void onRemove(Object item) {
0973: checkWritable();
0974: ((Attribute) item).setOwner(null);
0975: }
0976:
0977: }
0978:
0979: protected class AttrMap implements NamedNodeMap {
0980: protected AttrMap() {
0981: }
0982:
0983: //-- NamedNodeMap --//
0984: public final int getLength() {
0985: return _attrs != null ? _attrs.size() : 0;
0986: }
0987:
0988: public final Node item(int index) {
0989: return index < 0 || index >= getLength() ? null
0990: : (Node) _attrs.get(index);
0991: }
0992:
0993: public final Node getNamedItem(String tname) {
0994: return getAttributeItem(tname);
0995: }
0996:
0997: public final Node getNamedItemNS(String nsURI, String lname) {
0998: return getAttributeItem(nsURI, lname, 0);
0999: }
1000:
1001: public final Node removeNamedItem(String tname) {
1002: int j = getAttributeIndex(0, tname);
1003: return j >= 0 ? (Node) _attrs.remove(j) : null;
1004: }
1005:
1006: public final Node removeNamedItemNS(String nsURI, String lname) {
1007: int j = getAttributeIndex(0, nsURI, lname, 0);
1008: return j >= 0 ? (Node) _attrs.remove(j) : null;
1009: }
1010:
1011: public final Node setNamedItem(Node node) {
1012: return setAttributeNode((Attr) node);
1013: }
1014:
1015: public final Node setNamedItemNS(Node node) {
1016: return setAttributeNodeNS((Attr) node);
1017: }
1018: }
1019: }
|