/* * GNU LESSER GENERAL PUBLIC LICENSE Copyright (C) 2006 The Lobo Project * * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * * Contact info: xamjadmin@users.sourceforge.net */ /* * Created on Oct 29, 2005 */ package org.cobra_grendel.html.domimpl; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import org.cobra_grendel.util.Objects; import org.w3c.dom.Attr; import org.w3c.dom.Comment; import org.w3c.dom.DOMException; import org.w3c.dom.Element; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.w3c.dom.Text; import org.w3c.dom.TypeInfo; public class ElementImpl extends NodeImpl implements Element { /** * */ private static final long serialVersionUID = 1L; protected static boolean isTagName(final Node node, final String name) { return node.getNodeName().equalsIgnoreCase(name); } private String id; private final String name; protected Map attributes; public ElementImpl(final String name, final int transactionId) { super(transactionId); this.name = name; } protected void appendInnerTextImpl(final StringBuffer buffer) { ArrayList nl = nodeList; if (nl == null) { return; } int size = nl.size(); if (size == 0) { return; } for (int i = 0; i < size; i++) { Node child = (Node) nl.get(i); if (child instanceof ElementImpl) { ((ElementImpl) child).appendInnerTextImpl(buffer); } if (child instanceof Comment) { // skip } else if (child instanceof Text) { buffer.append(((Text) child).getTextContent()); } } } protected void assignAttributeField(final String normalName, final String value) { // Note: overriders assume that processing here is only done after // checking attribute names, i.e. they may not call the super // implementation if an attribute is already taken care of. boolean isName = false; if ("id".equals(normalName) || (isName = "name".equals(normalName))) { // Note that the value of name is used // as an ID, but the value of ID is not // used as a name. String oldId = id; id = value; HTMLDocumentImpl document = (HTMLDocumentImpl) this.document; if (document != null) { if (oldId != null) { document.removeElementById(oldId); } document.setElementById(value, this); if (isName) { String oldName = getAttribute("name"); if (oldName != null) { document.removeNamedItem(oldName); } document.setNamedItem(value, this); } } } } @Override protected Node createSimilarNode() { HTMLDocumentImpl doc = (HTMLDocumentImpl) document; return doc == null ? null : doc.createElement(getTagName()); } @Override public boolean equalAttributes(final Node arg) { if (arg instanceof ElementImpl) { synchronized (this) { Map attrs1 = attributes; if (attrs1 == null) { attrs1 = Collections.EMPTY_MAP; } Map attrs2 = ((ElementImpl) arg).attributes; if (attrs2 == null) { attrs2 = Collections.EMPTY_MAP; } return Objects.equals(attrs1, attrs2); } } else { return false; } } // private String title; private Attr getAttr(final String normalName, final String value) { // TODO: "specified" attributes return new AttrImpl(normalName, value, true, this, "id".equals(normalName), transactionId); } @Override public final String getAttribute(final String name) { String normalName = normalizeAttributeName(name); synchronized (this) { Map attributes = this.attributes; return attributes == null ? null : (String) attributes.get(normalName); } } @Override public Attr getAttributeNode(final String name) { String normalName = normalizeAttributeName(name); synchronized (this) { Map attributes = this.attributes; String value = attributes == null ? null : (String) attributes.get(normalName); return value == null ? null : getAttr(normalName, value); } } @Override public Attr getAttributeNodeNS(final String namespaceURI, final String localName) throws DOMException { throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "Namespaces not supported"); } @Override public String getAttributeNS(final String namespaceURI, final String localName) throws DOMException { throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "Namespaces not supported"); } /* * (non-Javadoc) * * @see org.xamjwg.html.domimpl.NodeImpl#getattributes() */ @Override public NamedNodeMap getAttributes() { synchronized (this) { Map attrs = attributes; if (attrs == null) { attrs = new HashMap(); attributes = attrs; } return new NamedNodeMapImpl(this, attributes, transactionId); } } public String getDir() { return getAttribute("dir"); } @Override public NodeList getElementsByTagName(final String name) { boolean matchesAll = "*".equals(name); List descendents = new LinkedList(); synchronized (treeLock) { ArrayList nl = nodeList; if (nl != null) { Iterator i = nl.iterator(); while (i.hasNext()) { Object child = i.next(); if (child instanceof Element) { Element childElement = (Element) child; if (matchesAll || isTagName(childElement, name)) { descendents.add(child); } NodeList sublist = childElement.getElementsByTagName(name); int length = sublist.getLength(); for (int idx = 0; idx < length; idx++) { descendents.add(sublist.item(idx)); } } } } } return new NodeListImpl(descendents, transactionId); } @Override public NodeList getElementsByTagNameNS(final String namespaceURI, final String localName) throws DOMException { throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "Namespaces not supported"); } public String getId() { return id; } /** * Attempts to convert the subtree starting at this point to a close text representation. BR elements are converted to line breaks, and so forth. */ public String getInnerText() { StringBuffer buffer = new StringBuffer(); synchronized (treeLock) { appendInnerTextImpl(buffer); } return buffer.toString(); } public String getLang() { return getAttribute("lang"); } /* * (non-Javadoc) * * @see org.xamjwg.html.domimpl.NodeImpl#getLocalName() */ @Override public String getLocalName() { return getNodeName(); } /* * (non-Javadoc) * * @see org.xamjwg.html.domimpl.NodeImpl#getNodeName() */ @Override public String getNodeName() { return name; } /* * (non-Javadoc) * * @see org.xamjwg.html.domimpl.NodeImpl#getNodeType() */ @Override public short getNodeType() { return Node.ELEMENT_NODE; } /* * (non-Javadoc) * * @see org.xamjwg.html.domimpl.NodeImpl#getNodeValue() */ @Override public String getNodeValue() throws DOMException { return null; } /** * Gets inner text of the element, possibly including text in comments. This can be used to get Javascript code out of a SCRIPT element. * * @param includeComment */ protected String getRawInnerText(final boolean includeComment) { synchronized (treeLock) { ArrayList nl = nodeList; if (nl != null) { Iterator i = nl.iterator(); StringBuffer sb = null; while (i.hasNext()) { Object node = i.next(); if (node instanceof Text) { Text tn = (Text) node; String txt = tn.getNodeValue(); if (!"".equals(txt)) { if (sb == null) { sb = new StringBuffer(); } sb.append(txt); } } else if (node instanceof ElementImpl) { ElementImpl en = (ElementImpl) node; String txt = en.getRawInnerText(includeComment); if (!"".equals(txt)) { if (sb == null) { sb = new StringBuffer(); } sb.append(txt); } } else if (includeComment && node instanceof Comment) { Comment cn = (Comment) node; String txt = cn.getNodeValue(); if (!"".equals(txt)) { if (sb == null) { sb = new StringBuffer(); } sb.append(txt); } } } return sb == null ? "" : sb.toString(); } else { return ""; } } } @Override public TypeInfo getSchemaTypeInfo() { throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "Namespaces not supported"); } @Override public String getTagName() { return getNodeName(); } public String getTitle() { return getAttribute("title"); } @Override public boolean hasAttribute(final String name) { String normalName = normalizeAttributeName(name); synchronized (this) { Map attributes = this.attributes; return attributes == null ? false : attributes.containsKey(normalName); } } @Override public boolean hasAttributeNS(final String namespaceURI, final String localName) throws DOMException { throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "Namespaces not supported"); } @Override public boolean hasAttributes() { synchronized (this) { Map attrs = attributes; return attrs == null ? false : !attrs.isEmpty(); } } protected final String normalizeAttributeName(final String name) { return name.toLowerCase(); } @Override public void removeAttribute(final String name) throws DOMException { String normalName = normalizeAttributeName(name); synchronized (this) { Map attributes = this.attributes; if (attributes == null) { return; } attributes.remove(normalName); } } @Override public Attr removeAttributeNode(final Attr oldAttr) throws DOMException { String normalName = normalizeAttributeName(oldAttr.getName()); synchronized (this) { Map attributes = this.attributes; if (attributes == null) { return null; } String oldValue = (String) attributes.remove(normalName); // TODO: "specified" attributes return oldValue == null ? null : getAttr(normalName, oldValue); } } @Override public void removeAttributeNS(final String namespaceURI, final String localName) throws DOMException { throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "Namespaces not supported"); } @Override public void setAttribute(final String name, final String value) throws DOMException { String normalName = normalizeAttributeName(name); synchronized (this) { Map attribs = attributes; if (attribs == null) { attribs = new HashMap(2); attributes = attribs; } attribs.put(normalName, value); } assignAttributeField(normalName, value); } /** * Fast method to set attributes. It is not thread safe. Calling thread should hold a treeLock. */ public void setAttributeImpl(final String name, final String value) throws DOMException { String normalName = normalizeAttributeName(name); Map attribs = attributes; if (attribs == null) { attribs = new HashMap(2); attributes = attribs; } assignAttributeField(normalName, value); attribs.put(normalName, value); } @Override public Attr setAttributeNode(final Attr newAttr) throws DOMException { String normalName = normalizeAttributeName(newAttr.getName()); String value = newAttr.getValue(); synchronized (this) { if (attributes == null) { attributes = new HashMap(); } attributes.put(normalName, value); // this.setIdAttribute(normalName, newAttr.isId()); } assignAttributeField(normalName, value); return newAttr; } @Override public Attr setAttributeNodeNS(final Attr newAttr) throws DOMException { throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "Namespaces not supported"); } @Override public void setAttributeNS(final String namespaceURI, final String qualifiedName, final String value) throws DOMException { throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "Namespaces not supported"); } public void setDir(final String dir) { setAttribute("dir", dir); } public void setId(final String id) { setAttribute("id", id); } @Override public void setIdAttribute(final String name, final boolean isId) throws DOMException { String normalName = normalizeAttributeName(name); if (!"id".equals(normalName)) { throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "IdAttribute can't be anything other than ID"); } } @Override public void setIdAttributeNode(final Attr idAttr, final boolean isId) throws DOMException { String normalName = normalizeAttributeName(idAttr.getName()); if (!"id".equals(normalName)) { throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "IdAttribute can't be anything other than ID"); } } @Override public void setIdAttributeNS(final String namespaceURI, final String localName, final boolean isId) throws DOMException { throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "Namespaces not supported"); } public void setInnerText(final String newText) { org.w3c.dom.Document document = this.document; if (document == null) { this.warn("setInnerText(): Element " + this + " does not belong to a document."); return; } synchronized (treeLock) { ArrayList nl = nodeList; if (nl != null) { nl.clear(); } } // Create node and call appendChild outside of synchronized block. Node textNode = document.createTextNode(newText); appendChild(textNode); } public void setLang(final String lang) { setAttribute("lang", lang); } /* * (non-Javadoc) * * @see org.xamjwg.html.domimpl.NodeImpl#setNodeValue(java.lang.String) */ @Override public void setNodeValue(final String nodeValue) throws DOMException { // nop } public void setTitle(final String title) { setAttribute("title", title); } @Override public String toString() { StringBuffer sb = new StringBuffer(); sb.append(getNodeName()); sb.append(" ["); NamedNodeMap attribs = getAttributes(); int length = attribs.getLength(); for (int i = 0; i < length; i++) { Attr attr = (Attr) attribs.item(i); sb.append(attr.getNodeName()); sb.append('='); sb.append(attr.getNodeValue()); if (i + 1 < length) { sb.append(','); } } sb.append("]"); return sb.toString(); } }