/* * 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: lobochief@users.sourceforge.net */ /* * Created on Oct 29, 2005 */ package com.nvarghese.beowulf.common.cobra.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.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; import com.nvarghese.beowulf.common.cobra.util.Objects; import com.nvarghese.beowulf.common.cobra.util.Strings; public class ElementImpl extends NodeImpl implements Element { private final String name; public ElementImpl(String name) { super(); this.name = name; } protected Map attributes; /* * (non-Javadoc) * * @see org.xamjwg.html.domimpl.NodeImpl#getattributes() */ public NamedNodeMap getAttributes() { synchronized (this) { Map attrs = this.attributes; if (attrs == null) { attrs = new HashMap(); this.attributes = attrs; } return new NamedNodeMapImpl(this, this.attributes); } } public boolean hasAttributes() { synchronized (this) { Map attrs = this.attributes; return attrs == null ? false : !attrs.isEmpty(); } } public boolean equalAttributes(Node arg) { if (arg instanceof ElementImpl) { synchronized (this) { Map attrs1 = this.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 id; public String getId() { String id = this.id; return id == null ? "" : id; } public void setId(String id) { this.setAttribute("id", id); } // private String title; public String getTitle() { return this.getAttribute("title"); } public void setTitle(String title) { this.setAttribute("title", title); } public String getLang() { return this.getAttribute("lang"); } public void setLang(String lang) { this.setAttribute("lang", lang); } public String getDir() { return this.getAttribute("dir"); } public void setDir(String dir) { this.setAttribute("dir", dir); } public final String getAttribute(String name) { String normalName = this.normalizeAttributeName(name); synchronized (this) { Map attributes = this.attributes; return attributes == null ? null : (String) attributes.get(normalName); } } private Attr getAttr(String normalName, String value) { // TODO: "specified" attributes return new AttrImpl(normalName, value, true, this, "id".equals(normalName)); } public Attr getAttributeNode(String name) { String normalName = this.normalizeAttributeName(name); synchronized (this) { Map attributes = this.attributes; String value = attributes == null ? null : (String) attributes.get(normalName); return value == null ? null : this.getAttr(normalName, value); } } public Attr getAttributeNodeNS(String namespaceURI, String localName) throws DOMException { throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "Namespaces not supported"); } public String getAttributeNS(String namespaceURI, String localName) throws DOMException { throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "Namespaces not supported"); } protected static boolean isTagName(Node node, String name) { return node.getNodeName().equalsIgnoreCase(name); } public NodeList getElementsByTagName(String name) { boolean matchesAll = "*".equals(name); List descendents = new LinkedList(); synchronized (this.treeLock) { ArrayList nl = this.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); } public NodeList getElementsByTagNameNS(String namespaceURI, String localName) throws DOMException { throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "Namespaces not supported"); } public TypeInfo getSchemaTypeInfo() { throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "Namespaces not supported"); } public String getTagName() { return this.getNodeName(); } public boolean hasAttribute(String name) { String normalName = this.normalizeAttributeName(name); synchronized (this) { Map attributes = this.attributes; return attributes == null ? false : attributes.containsKey(normalName); } } public boolean hasAttributeNS(String namespaceURI, String localName) throws DOMException { throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "Namespaces not supported"); } public void removeAttribute(String name) throws DOMException { String normalName = this.normalizeAttributeName(name); synchronized (this) { Map attributes = this.attributes; if (attributes == null) { return; } attributes.remove(normalName); } } public Attr removeAttributeNode(Attr oldAttr) throws DOMException { String normalName = this.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 : this.getAttr(normalName, oldValue); } } public void removeAttributeNS(String namespaceURI, String localName) throws DOMException { throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "Namespaces not supported"); } protected void assignAttributeField(String normalName, 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. if (!isName) { this.id = value; } HTMLDocumentImpl document = (HTMLDocumentImpl) this.document; if (document != null) { // // Do not remove old ID. Consider scenario where both // // name and ID are provided in an element. // if (oldId != null) { // document.removeElementById(oldId); // } document.setElementById(value, this); if (isName) { String oldName = this.getAttribute("name"); if (oldName != null) { document.removeNamedItem(oldName); } document.setNamedItem(value, this); } } } } protected final String normalizeAttributeName(String name) { return name.toLowerCase(); } public void setAttribute(String name, String value) throws DOMException { String normalName = this.normalizeAttributeName(name); synchronized (this) { Map attribs = this.attributes; if (attribs == null) { attribs = new HashMap(2); this.attributes = attribs; } attribs.put(normalName, value); } this.assignAttributeField(normalName, value); } /** * Fast method to set attributes. It is not thread safe. Calling thread * should hold a treeLock. */ public void setAttributeImpl(String name, String value) throws DOMException { String normalName = this.normalizeAttributeName(name); Map attribs = this.attributes; if (attribs == null) { attribs = new HashMap(2); this.attributes = attribs; } this.assignAttributeField(normalName, value); attribs.put(normalName, value); } public Attr setAttributeNode(Attr newAttr) throws DOMException { String normalName = this.normalizeAttributeName(newAttr.getName()); String value = newAttr.getValue(); synchronized (this) { if (this.attributes == null) { this.attributes = new HashMap(); } this.attributes.put(normalName, value); // this.setIdAttribute(normalName, newAttr.isId()); } this.assignAttributeField(normalName, value); return newAttr; } public Attr setAttributeNodeNS(Attr newAttr) throws DOMException { throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "Namespaces not supported"); } public void setAttributeNS(String namespaceURI, String qualifiedName, String value) throws DOMException { throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "Namespaces not supported"); } public void setIdAttribute(String name, boolean isId) throws DOMException { String normalName = this.normalizeAttributeName(name); if (!"id".equals(normalName)) { throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "IdAttribute can't be anything other than ID"); } } public void setIdAttributeNode(Attr idAttr, boolean isId) throws DOMException { String normalName = this.normalizeAttributeName(idAttr.getName()); if (!"id".equals(normalName)) { throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "IdAttribute can't be anything other than ID"); } } public void setIdAttributeNS(String namespaceURI, String localName, boolean isId) throws DOMException { throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "Namespaces not supported"); } /* * (non-Javadoc) * * @see org.xamjwg.html.domimpl.NodeImpl#getLocalName() */ public String getLocalName() { return this.getNodeName(); } /* * (non-Javadoc) * * @see org.xamjwg.html.domimpl.NodeImpl#getNodeName() */ public String getNodeName() { return this.name; } /* * (non-Javadoc) * * @see org.xamjwg.html.domimpl.NodeImpl#getNodeType() */ public short getNodeType() { return Node.ELEMENT_NODE; } /* * (non-Javadoc) * * @see org.xamjwg.html.domimpl.NodeImpl#getNodeValue() */ public String getNodeValue() throws DOMException { return null; } /* * (non-Javadoc) * * @see org.xamjwg.html.domimpl.NodeImpl#setNodeValue(java.lang.String) */ public void setNodeValue(String nodeValue) throws DOMException { // nop } /** * 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(boolean includeComment) { synchronized (this.treeLock) { ArrayList nl = this.nodeList; if (nl != null) { Iterator i = nl.iterator(); StringBuilder 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 StringBuilder(); } 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 StringBuilder(); } 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 StringBuilder(); } sb.append(txt); } } } return sb == null ? "" : sb.toString(); } else { return ""; } } } public String toString() { StringBuilder sb = new StringBuilder(); sb.append(this.getNodeName()); sb.append(" ["); NamedNodeMap attribs = this.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(); } public void setInnerText(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 (this.treeLock) { ArrayList nl = this.nodeList; if (nl != null) { nl.clear(); } } // Create node and call appendChild outside of synchronized block. Node textNode = document.createTextNode(newText); this.appendChild(textNode); } protected Node createSimilarNode() { HTMLDocumentImpl doc = (HTMLDocumentImpl) this.document; return doc == null ? null : doc.createElement(this.getTagName()); } protected String htmlEncodeChildText(String text) { if (com.nvarghese.beowulf.common.cobra.html.parser.HtmlParser.isDecodeEntities(this.name)) { return Strings.strictHtmlEncode(text, false); } else { return text; } } /** * 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() { StringBuilder buffer = new StringBuilder(); synchronized (treeLock) { appendInnerTextImpl(buffer); } return buffer.toString(); } protected void appendInnerTextImpl(StringBuilder 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()); } } } }