/* GNU GENERAL LICENSE Copyright (C) 2006 The Lobo Project. Copyright (C) 2014 - 2017 Lobo Evolution This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either verion 3 of the License, or (at your option) any later version. This program 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 General License for more details. You should have received a copy of the GNU General Public along with this program. If not, see <http://www.gnu.org/licenses/>. Contact info: lobochief@users.sourceforge.net; ivan.difrancesco@yahoo.it */ /* * Created on Oct 29, 2005 */ package org.lobobrowser.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.lobobrowser.html.HtmlAttributeProperties; import org.lobobrowser.html.style.HtmlValues; import org.lobobrowser.util.Objects; import org.lobobrowser.util.Strings; import org.lobobrowser.w3c.html.HTMLMenuElement; 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; /** * The Class DOMElementImpl. */ public class DOMElementImpl extends DOMNodeImpl implements Element { /** The name. */ private final String name; /** The id. */ private String id; /** * Instantiates a new DOM element impl. * * @param name * the name */ public DOMElementImpl(String name) { super(); this.name = name; } /** The attributes. */ protected Map<String, String> attributes; /* * (non-Javadoc) * * @see org.lobobrowser.html.dombl.DOMNodeImpl#getattributes() */ @Override public NamedNodeMap getAttributes() { synchronized (this) { Map<String, String> attrs = this.attributes; if (attrs == null) { attrs = new HashMap<String, String>(); this.attributes = attrs; } return new DOMAttrMapImpl(this, this.attributes); } } /* * (non-Javadoc) * * @see org.lobobrowser.html.domimpl.DOMNodeImpl#hasAttributes() */ @Override public boolean hasAttributes() { synchronized (this) { Map<String, String> attrs = this.attributes; return attrs == null ? false : !attrs.isEmpty(); } } /* * (non-Javadoc) * * @see * org.lobobrowser.html.domimpl.DOMNodeImpl#equalAttributes(org.w3c.dom. * Node) */ @Override public boolean equalAttributes(Node arg) { if (arg instanceof DOMElementImpl) { synchronized (this) { Map<String, String> attrs1 = this.attributes; if (attrs1 == null) { attrs1 = Collections.emptyMap(); } Map<String, String> attrs2 = ((DOMElementImpl) arg).attributes; if (attrs2 == null) { attrs2 = Collections.emptyMap(); } return Objects.equals(attrs1, attrs2); } } else { return false; } } /** * Gets the id. * * @return the id */ public String getId() { String id = this.id; return id == null ? "" : id; } /** * Sets the id. * * @param id * the new id */ public void setId(String id) { this.setAttribute(HtmlAttributeProperties.ID, id); } /** * Gets the title. * * @return the title */ public String getTitle() { return this.getAttribute(HtmlAttributeProperties.TITLE); } /** * Sets the title. * * @param title * the new title */ public void setTitle(String title) { this.setAttribute(HtmlAttributeProperties.TITLE, title); } /** * Gets the lang. * * @return the lang */ public String getLang() { return this.getAttribute(HtmlAttributeProperties.LANG); } /** * Sets the lang. * * @param lang * the new lang */ public void setLang(String lang) { this.setAttribute(HtmlAttributeProperties.LANG, lang); } /** * Gets the dir. * * @return the dir */ public String getDir() { return this.getAttribute(HtmlAttributeProperties.DIR); } /** * Sets the dir. * * @param dir * the new dir */ public void setDir(String dir) { this.setAttribute(HtmlAttributeProperties.DIR, dir); } /** * Gets the hidden. * * @return the hidden */ public boolean getHidden() { return this.getAttribute(HtmlAttributeProperties.HIDDEN) == null ? true : false; } /** * Gets the content editable. * * @return the content editable */ public String getContentEditable() { return this.getAttribute(HtmlAttributeProperties.CONTENTEDITABLE); } /** * Sets the content editable. * * @param contenteditable * the new content editable */ public void setContentEditable(String contenteditable) { this.setAttribute(HtmlAttributeProperties.CONTENTEDITABLE, contenteditable); } /** * Gets the spellcheck. * * @return the spellcheck */ public String getSpellcheck() { return this.getAttribute(HtmlAttributeProperties.SPELLCHECK); } /** * Sets the spellcheck. * * @param spellcheck * the new spellcheck */ public void setSpellcheck(String spellcheck) { this.setAttribute(HtmlAttributeProperties.SPELLCHECK, spellcheck); } /** * Gets the draggable. * * @return the draggable */ public boolean getDraggable() { String draggable = this.getAttribute(HtmlAttributeProperties.DRAGGABLE); if (draggable == null) { return false; } else { return new Boolean(draggable); } } /** * Sets the draggable. * * @param draggable * the new draggable */ public void setDraggable(boolean draggable) { this.setAttribute(HtmlAttributeProperties.DRAGGABLE, String.valueOf(draggable)); } /** * Gets the checks if is content editable. * * @return the checks if is content editable */ public boolean getIsContentEditable() { String content = getAttribute(HtmlAttributeProperties.CONTENTEDITABLE); if (content == null) { return false; } else { return new Boolean(content); } } /** * Gets the disabled. * * @return the disabled */ public boolean getDisabled() { return this.getAttribute(HtmlAttributeProperties.DISABLE) == null ? true : false; } /** * Gets the checked. * * @return the checked */ public boolean getChecked() { return this.getAttribute(HtmlAttributeProperties.CHECKED) == null ? true : false; } /** * Gets the item scope. * * @return the item scope */ public boolean getItemScope() { String itemscope = this.getAttribute(HtmlAttributeProperties.ITEMSCOPE); if (itemscope == null) { return false; } else { return new Boolean(itemscope); } } /** * Sets the item scope. * * @param itemscope * the new item scope */ public void setItemScope(boolean itemscope) { this.setAttribute(HtmlAttributeProperties.ITEMSCOPE, String.valueOf(itemscope)); } /** * Gets the item type. * * @return the item type */ public String getItemType() { return this.getAttribute(HtmlAttributeProperties.ITEMTYPE); } /** * Sets the item type. * * @param itemType * the new item type */ public void setItemType(String itemType) { this.setAttribute(HtmlAttributeProperties.ITEMTYPE, itemType); } /** * Gets the item id. * * @return the item id */ public String getItemId() { return this.getAttribute(HtmlAttributeProperties.ITEMID); } /** * Sets the item id. * * @param itemId * the new item id */ public void setItemId(String itemId) { this.setAttribute(HtmlAttributeProperties.ITEMID, itemId); } /** * Gets the tab index. * * @return the tab index */ public int getTabIndex() { String valueText = this.getAttribute(HtmlAttributeProperties.TABINDEX); return HtmlValues.getPixelSize(valueText, this.getRenderState(), 0); } /** * Sets the tab index. * * @param tabIndex * the new tab index */ public void setTabIndex(int tabIndex) { this.setAttribute(HtmlAttributeProperties.TABINDEX, String.valueOf(tabIndex)); } /** * Gets the access key. * * @return the access key */ public String getAccessKey() { return this.getAttribute(HtmlAttributeProperties.ACCESSKEY); } /** * Sets the access key. * * @param accessKey * the new access key */ public void setAccessKey(String accessKey) { this.setAttribute(HtmlAttributeProperties.ACCESSKEY, accessKey); } /** * Gets the context menu. * * @return the context menu */ public HTMLMenuElement getContextMenu() { // TODO Auto-generated method stub return null; } /** * Sets the context menu. * * @param contextMenu * the new context menu */ public void setContextMenu(HTMLMenuElement contextMenu) { // TODO Auto-generated method stub } /* * (non-Javadoc) * * @see org.w3c.dom.Element#getAttribute(java.lang.String) */ @Override public final String getAttribute(String name) { String normalName = this.normalizeAttributeName(name); synchronized (this) { Map<String, String> attributes = this.attributes; return attributes == null ? null : (String) attributes.get(normalName); } } /** * Gets the attr. * * @param normalName * the normal name * @param value * the value * @return the attr */ private Attr getAttr(String normalName, String value) { // TODO: "specified" attributes return new DOMAttrImpl(normalName, value, true, this, HtmlAttributeProperties.ID.equals(normalName)); } /* * (non-Javadoc) * * @see org.w3c.dom.Element#getAttributeNode(java.lang.String) */ @Override public Attr getAttributeNode(String name) { String normalName = this.normalizeAttributeName(name); synchronized (this) { Map<String, String> attributes = this.attributes; String value = attributes == null ? null : (String) attributes.get(normalName); return value == null ? null : this.getAttr(normalName, value); } } /* * (non-Javadoc) * * @see org.w3c.dom.Element#getAttributeNodeNS(java.lang.String, * java.lang.String) */ @Override public Attr getAttributeNodeNS(String namespaceURI, String localName) throws DOMException { throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "Namespaces not supported"); } /* * (non-Javadoc) * * @see org.w3c.dom.Element#getAttributeNS(java.lang.String, * java.lang.String) */ @Override public String getAttributeNS(String namespaceURI, String localName) throws DOMException { throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "Namespaces not supported"); } /** * Checks if is tag name. * * @param node * the node * @param name * the name * @return true, if is tag name */ protected static boolean isTagName(Node node, String name) { return node.getNodeName().equalsIgnoreCase(name); } /* * (non-Javadoc) * * @see org.w3c.dom.Element#getElementsByTagName(java.lang.String) */ @Override public NodeList getElementsByTagName(String name) { boolean matchesAll = "*".equals(name); List<Object> descendents = new LinkedList<Object>(); synchronized (this.getTreeLock()) { ArrayList<Node> nl = this.nodeList; if (nl != null) { Iterator<Node> 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 DOMNodeListImpl(descendents); } /* * (non-Javadoc) * * @see org.w3c.dom.Element#getElementsByTagNameNS(java.lang.String, * java.lang.String) */ @Override public NodeList getElementsByTagNameNS(String namespaceURI, String localName) throws DOMException { throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "Namespaces not supported"); } /* * (non-Javadoc) * * @see org.w3c.dom.Element#getSchemaTypeInfo() */ @Override public TypeInfo getSchemaTypeInfo() { throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "Namespaces not supported"); } /* * (non-Javadoc) * * @see org.w3c.dom.Element#getTagName() */ @Override public String getTagName() { return this.getNodeName(); } /* * (non-Javadoc) * * @see org.w3c.dom.Element#hasAttribute(java.lang.String) */ @Override public boolean hasAttribute(String name) { String normalName = this.normalizeAttributeName(name); synchronized (this) { Map<String, String> attributes = this.attributes; return attributes == null ? false : attributes.containsKey(normalName); } } /* * (non-Javadoc) * * @see org.w3c.dom.Element#hasAttributeNS(java.lang.String, * java.lang.String) */ @Override public boolean hasAttributeNS(String namespaceURI, String localName) throws DOMException { throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "Namespaces not supported"); } /* * (non-Javadoc) * * @see org.w3c.dom.Element#removeAttribute(java.lang.String) */ @Override public void removeAttribute(String name) throws DOMException { String normalName = this.normalizeAttributeName(name); synchronized (this) { Map<String, String> attributes = this.attributes; if (attributes == null) { return; } attributes.remove(normalName); } } /* * (non-Javadoc) * * @see org.w3c.dom.Element#removeAttributeNode(org.w3c.dom.Attr) */ @Override public Attr removeAttributeNode(Attr oldAttr) throws DOMException { String normalName = this.normalizeAttributeName(oldAttr.getName()); synchronized (this) { Map<String, String> attributes = this.attributes; if (attributes == null) { return null; } String oldValue = attributes.remove(normalName); // TODO: "specified" attributes return oldValue == null ? null : this.getAttr(normalName, oldValue); } } /* * (non-Javadoc) * * @see org.w3c.dom.Element#removeAttributeNS(java.lang.String, * java.lang.String) */ @Override public void removeAttributeNS(String namespaceURI, String localName) throws DOMException { throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "Namespaces not supported"); } /** * Assign attribute field. * * @param normalName * the normal name * @param value * the value */ 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 (HtmlAttributeProperties.ID.equals(normalName) || (isName = HtmlAttributeProperties.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(HtmlAttributeProperties.NAME); if (oldName != null) { document.removeNamedItem(oldName); } document.setNamedItem(value, this); } } } } /** * Normalize attribute name. * * @param name * the name * @return the string */ protected final String normalizeAttributeName(String name) { return name.toLowerCase(); } /* * (non-Javadoc) * * @see org.w3c.dom.Element#setAttribute(java.lang.String, java.lang.String) */ @Override public void setAttribute(String name, String value) throws DOMException { String normalName = this.normalizeAttributeName(name); synchronized (this) { Map<String, String> attribs = this.attributes; if (attribs == null) { attribs = new HashMap<String, String>(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. * * @param name * the name * @param value * the value * @throws DOMException * the DOM exception */ public void setAttributeImpl(String name, String value) throws DOMException { String normalName = this.normalizeAttributeName(name); Map<String, String> attribs = this.attributes; if (attribs == null) { attribs = new HashMap<String, String>(2); this.attributes = attribs; } this.assignAttributeField(normalName, value); attribs.put(normalName, value); } /* * (non-Javadoc) * * @see org.w3c.dom.Element#setAttributeNode(org.w3c.dom.Attr) */ @Override 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<String, String>(); } this.attributes.put(normalName, value); // this.setIdAttribute(normalName, newAttr.isId()); } this.assignAttributeField(normalName, value); return newAttr; } /* * (non-Javadoc) * * @see org.w3c.dom.Element#setAttributeNodeNS(org.w3c.dom.Attr) */ @Override public Attr setAttributeNodeNS(Attr newAttr) throws DOMException { throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "Namespaces not supported"); } /* * (non-Javadoc) * * @see org.w3c.dom.Element#setAttributeNS(java.lang.String, * java.lang.String, java.lang.String) */ @Override public void setAttributeNS(String namespaceURI, String qualifiedName, String value) throws DOMException { throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "Namespaces not supported"); } /* * (non-Javadoc) * * @see org.w3c.dom.Element#setIdAttribute(java.lang.String, boolean) */ @Override public void setIdAttribute(String name, boolean isId) throws DOMException { String normalName = this.normalizeAttributeName(name); if (!HtmlAttributeProperties.ID.equals(normalName)) { throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "IdAttribute can't be anything other than ID"); } } /* * (non-Javadoc) * * @see org.w3c.dom.Element#setIdAttributeNode(org.w3c.dom.Attr, boolean) */ @Override public void setIdAttributeNode(Attr idAttr, boolean isId) throws DOMException { String normalName = this.normalizeAttributeName(idAttr.getName()); if (!HtmlAttributeProperties.ID.equals(normalName)) { throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "IdAttribute can't be anything other than ID"); } } /* * (non-Javadoc) * * @see org.w3c.dom.Element#setIdAttributeNS(java.lang.String, * java.lang.String, boolean) */ @Override 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.lobobrowser.html.dombl.DOMNodeImpl#getLocalName() */ @Override public String getLocalName() { return this.getNodeName(); } /* * (non-Javadoc) * * @see org.lobobrowser.html.dombl.DOMNodeImpl#getNodeName() */ @Override public String getNodeName() { return this.name; } /* * (non-Javadoc) * * @see org.lobobrowser.html.dombl.DOMNodeImpl#getNodeType() */ @Override public short getNodeType() { return Node.ELEMENT_NODE; } /* * (non-Javadoc) * * @see org.lobobrowser.html.dombl.DOMNodeImpl#getNodeValue() */ @Override public String getNodeValue() throws DOMException { return null; } /* * (non-Javadoc) * * @see org.lobobrowser.html.dombl.DOMNodeImpl#setNodeValue(String) */ @Override 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 * the include comment * @return the raw inner text */ protected String getRawInnerText(boolean includeComment) { synchronized (this.getTreeLock()) { ArrayList<Node> nl = this.nodeList; if (nl != null) { Iterator<Node> 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 DOMElementImpl) { DOMElementImpl en = (DOMElementImpl) 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 ""; } } } /* * (non-Javadoc) * * @see org.lobobrowser.html.domimpl.DOMNodeImpl#toString() */ @Override public String toString() { StringBuffer sb = new StringBuffer(); 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(); } /** * Sets the inner text. * * @param newText * the new inner text */ public void setInnerText(String newText) { org.w3c.dom.Document document = this.document; if (document == null) { logger.error("setInnerText(): Element " + this + " does not belong to a document."); return; } synchronized (this.getTreeLock()) { ArrayList<Node> 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); } /* * (non-Javadoc) * * @see org.lobobrowser.html.domimpl.DOMNodeImpl#createSimilarNode() */ @Override protected Node createSimilarNode() { HTMLDocumentImpl doc = (HTMLDocumentImpl) this.document; return doc == null ? null : doc.createElement(this.getTagName()); } /* * (non-Javadoc) * * @see * org.lobobrowser.html.domimpl.DOMNodeImpl#htmlEncodeChildText(java.lang. * String ) */ @Override protected String htmlEncodeChildText(String text) { if (org.lobobrowser.html.parser.HtmlParser.isDecodeEntities(this.name)) { return Strings.strictHtmlEncode(text, false); } else { return text; } } }