📄 element.java
字号:
/* Element.java{{IS_NOTE Purpose: Description: History: 2001/10/22 17:10:29, Create, Tom M. Yeh}}IS_NOTECopyright (C) 2001 Potix Corporation. All Rights Reserved.{{IS_RIGHT This program is distributed under GPL Version 2.0 in the hope that it will be useful, but WITHOUT ANY WARRANTY.}}IS_RIGHT*/package org.zkoss.idom;import java.util.Map;import java.util.LinkedHashMap;import java.util.Collection;import java.util.AbstractCollection;import java.util.List;import java.util.Iterator;import java.util.LinkedList;import java.util.Collections;import java.util.NoSuchElementException;import java.util.regex.Pattern;import java.io.IOException;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import javax.xml.transform.TransformerException;import org.w3c.dom.Attr;import org.w3c.dom.Node;import org.w3c.dom.NodeList;import org.w3c.dom.NamedNodeMap;import org.w3c.dom.TypeInfo;import org.zkoss.mesg.Messages;import org.zkoss.mesg.MCommon;import org.zkoss.util.CollectionsX;import org.zkoss.util.CheckableTreeArray;import org.zkoss.xml.FacadeNodeList;import org.zkoss.idom.impl.*;/** * The iDOM element. * * @author tomyeh * @see Attribute */public class Element extends AbstractGroupimplements Attributable, Namespaceable, org.w3c.dom.Element { /** The namespace. */ protected Namespace _ns; /** The local name. */ protected String _lname; /** The attributes. May be null. */ protected List _attrs = null; /** Additional namespaces. May be null*/ protected Map _addNamespaces = null; /** Whether it is aware of the attribute modification. */ boolean _attrModAware = false; /** * Constructor. * * @param nsURI the namespace URI * @param tname the tag name */ public Element(String nsURI, String tname) { 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); } /** * Constructor. * * @param ns the namespace; if null, the default namespace is assumed * (not necessary {@link Namespace#NO_NAMESPACE}). * @param lname the local name */ public Element(Namespace ns, String lname) { setNamespace(ns); setLocalName(lname); } /** * Constructor without a namespace (i.e., {@link Namespace#NO_NAMESPACE}). * * @param lname the local name */ public Element(String lname) { this(Namespace.NO_NAMESPACE, lname); } /** * Constructor. * Unlike other constructors, it doesn't set the modification flag. */ protected Element() { _ns = Namespace.NO_NAMESPACE; } //-- Element extras --// /** * Tests whether this element is the root element of * the owning document. * * <p>Note: false is returned if it doesn't has any parent. */ public final boolean isRootElement() { return getParent() instanceof Document; } /** * Returns the Namespace in scope on this element for the given * prefix (this involves searching up the tree, so the results depend * on the current location of the element), or null if not found. * * <p>If prefix is empty, it searches for the "default" namespace * in scope. Thus, to search for attribute's namespace, caller * have to skip this one and use NO_NAMESPACE. * (due XML, an attribute without prefix is NO_NAMESPACE) * * @param prefix namespace prefix to look up; null for empty */ public final Namespace getNamespace(String prefix) { if (prefix == null) prefix = ""; Namespace ns = Namespace.getSpecial(prefix); if (ns != null) return ns; ns = getNamespace(); //might be null if in constructor if (ns != null && prefix.equals(ns.getPrefix())) return ns; if (_addNamespaces != null) { ns = (Namespace)_addNamespaces.get(prefix); if (ns != null) return ns; } if (getParent() instanceof Element) //only Element implements it; not Namespaceable return ((Element)getParent()).getNamespace(prefix); return prefix.length() > 0 ? null: Namespace.NO_NAMESPACE; } /** * Returns namespace declared on this element. * * <p>It is <i>not</i> a "live" representation. Also, it is read-only. * * <p>Note: Namespace.equals compares namespace's URI. However, * the distinction here is the prefix, because it is mainly for * getNamespace(prefix). * * @return the namespace declarations. */ public final Collection getDeclaredNamespaces() { return _addNamespaces == null ? Collections.EMPTY_LIST: _addNamespaces.values(); } /** * Adds a namespace to the namespace declaration. * * @return true if the namespace is added * @exception DOMException if the name space with the same prefix * already exists but with different URI */ public final boolean addDeclaredNamespace(Namespace ns) { if (ns.equalsAll(_ns)) //throws DOMException if... return false; //exists if (_addNamespaces == null) { _addNamespaces = new LinkedHashMap(5); } else { final Namespace old = (Namespace)_addNamespaces.get(ns.getPrefix()); if (old != null) { if (!old.equals(ns)) throw new DOMException(DOMException.NAMESPACE_ERR, "Add a conflict namespace: "+ns+", while "+old+" already exists"); return false; } } _addNamespaces.put(ns.getPrefix(), ns); return true; } /** * Gets the content of this element. * * <p>The content of an element is the first Binary or Text child of * the element. Each element can has zero or one content. * * <p>Note: {@link #getText} returns the catenation of all Text * children, not just the first one. * * @return the content of this element; null if no such child * @see #getContent(String) */ public final Object getContent() { for (final Iterator it = _children.iterator(); it.hasNext();) { Object o = it.next(); if (o instanceof Text) { return ((Text)o).getText(); } else if (o instanceof Binary) { return ((Binary)o).getValue(); } else if (o instanceof CData) { return ((CData)o).getText(); } } return null; } /** * Sets the content of this element. * * <p>All existent Binary or Text children of this element are removed * first. If the object is a String, a Text item is created to hold * it. Otherwise, a Binary item is created to hold it. * * <p>Non-Binary/Text children are preserved. * * <p>If obj is a {@link Item} or an array/collection of {@link Item}, * this method will add them as child vertices rather than * being the content. * Moreever, if the first item of the array/collection is {@link Item}, * it is assumed to be all valid component to being has valid vertices. * If not, an exception is thrown. * * <p>Thus, getContent might not return the object being set by setContent. * * @param obj the object to set; null is OK * @return the previous content * @see #getContent() */ public final Object setContent(Object obj) { if (obj instanceof Item) { getChildren().add(obj); return null; //done } if (obj instanceof Collection) { final Collection c = (Collection)obj; final Iterator it = c.iterator(); if (it.hasNext() && (it.next() instanceof Item)) { getChildren().addAll(c); return null; //done } } else if (obj instanceof Object[]) { Object[] ary = (Object[])obj; if (ary.length > 0 && (ary[0] instanceof Item)) { for (int j = 0; j < ary.length; ++j) getChildren().add(ary[j]); return null; //done } } Object ret = null; boolean retFound = false; boolean bStr = obj instanceof String; for (final Iterator it = _children.iterator(); it.hasNext();) { Object o = it.next(); if (o instanceof Text) { if (!retFound) { retFound = true; ret = ((Text)o).getText(); } if (!bStr || obj == null) { it.remove(); } else { ((Text)o).setText((String)obj); obj = null; //then, the following will be removed } } else if (o instanceof Binary) { if (!retFound) { retFound = true; ret = ((Binary)o).getValue(); } if (bStr || obj == null) { it.remove(); } else { ((Binary)o).setValue(obj); obj = null; //then, the following will be removed } } else if (o instanceof CData) { if (!retFound) { retFound = true; ret = ((CData)o).getText(); } it.remove(); //always remove and add } } if (obj != null) _children.add(0, bStr ? (Object)new Text((String)obj): (Object)new Binary(obj)); return ret; } /** * Returns the content of the child element with the giving path, or * null if the content is null or the child element doesn't exist. * * <p>Note that there might be more than one child with the same path * in an idom tree; this method simply picks the first one that matchs and * returns its content. To access certain one, you might use [n] to * [@attr = value] specify which one to access. * * * <p>To know whether the child element exists or conent is null, * use {@link #hasContent}. * * <p>The content of an element is a special feature of iDOM. * Like a Map, it is designed to let developers use names (in a path-like * format) to access objects. See {@link #setContent(String, Object)}. * * <p>Like Unix path, the giving name could use '/' to catenate a series * of child elements. * * <p>An empty path denotes this element itself. Leading, ending * and consecutive '/' will be ignored. * * <p>Example:<br> * <code>Object o = element.getContent("abc/def");<br> * String s = Objects.toString(element.getContent("ab/cd"));<br> * element.setContent("t:ab/cd/f:ef", new Integer(10));</code> * * <p>TODO: support [n] and [@attr = value] * * @param path a path; e.g., "b", "a/b", "t:a/t:b" * @see #getContent() */ public final Object getContent(String path) { Element e = this; int j = 0; while (true) { int k = path.indexOf('/', j); String tname = k >= 0 ? path.substring(j, k): path.substring(j); if (tname.length() > 0) { e = e.getElement(tname); if (e == null) return null; } if (k < 0) return e.getContent(); j = k + 1; } } /** * Tests whether the child element with the giving path exists. Note that * there might be more than one child with the same path in an idom tree; * this method simply tell you that "yes", at least on such path exist. * * To get the content, use {@link #getContent(String)}. */ public final boolean hasContent(String path) { Element e = this; int j = 0; while (true) { int k = path.indexOf('/', j); String tname = k >= 0 ? path.substring(j, k): path.substring(j); if (tname.length() > 0) { e = e.getElement(tname); if (e == null) return false; } if (k < 0) return true; j = k + 1; } } /** * Sets the content of the child element with the giving path. * * <p>Note that there might be more than one child with the same path * in an idom tree; this method simply pick one that matchs and set * its content (see {@link #setContent(Object)}). * * <p>The content of an element is a special feature of iDOM. * Like a Map, it is designed to let developers use names (in a path-like * format) to access objects. See {@link #getContent(String)}. * * <p>Like Unix path, the giving name could use '/' to catenate a series * of child elements. * * <p>An empty path denotes this element itself. Leading, ending * and consecutive '/' will be ignored. * * <p>If any element in the path is not found, it will be created * automatically. * * @param path a path; e.g., "b", "a/b", "t:a/t:b" * @param obj the object to set; null is acceptable * @return the previous content * * @see #setContent(Object) * @see #removeContent * @see #hasContent */ public final Object setContent(String path, Object obj) { Element e = this; int j = 0; while (true) { int k = path.indexOf('/', j); String tname = k >= 0 ? path.substring(j, k): path.substring(j); if (tname.length() > 0) { Element e2 = e.getElement(tname); if (e2 == null) { e2 = new Element(e.getNamespace().getURI(), tname); e.getChildren().add(e2); } e = e2; } if (k < 0) return e.setContent(obj); j = k + 1; } } /** * Removes the content of the child element with the giving path, * and the child element itself if no other child. * * <p>Unlike {@link #setContent(String, Object)} with null, * the child element identified by path will be detached if it has no * other child (but the content). So does its parent * <i>excluding</i> this element. Thus, removeContent(path) * could undo setContent(path, v). * * @return the previous content * @see #setContent(String, Object) */ public final Object removeContent(String path) { Element e = this; int j = 0; while (true) { int k = path.indexOf('/', j); String tname = k >= 0 ? path.substring(j, k): path.substring(j); if (tname.length() > 0) { e = e.getElement(tname); if (e == null) return null; } if (k < 0) { Object ret = e.setContent(null); //try to remove e; not including this for (Group group = e; group != this && group.getChildren().size() == 0;) { Group parent = group.getParent(); group.detach(); group = parent; } return ret; } j = k + 1; } } //-- utilities --// /** Returns the text of a child; never null. */ private static final String getTextOfChild(Object o) { if (!(o instanceof AbstractTextual)) return ""; final AbstractTextual t = (AbstractTextual)o;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -