com.gargoylesoftware.htmlunit.activex.javascript.msxml.XMLDOMElement Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of htmlunit Show documentation
Show all versions of htmlunit Show documentation
A headless browser intended for use in testing web-based applications.
/*
* Copyright (c) 2002-2018 Gargoyle Software Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.gargoylesoftware.htmlunit.activex.javascript.msxml;
import static com.gargoylesoftware.htmlunit.javascript.configuration.SupportedBrowser.IE;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import com.gargoylesoftware.htmlunit.html.DomAttr;
import com.gargoylesoftware.htmlunit.html.DomElement;
import com.gargoylesoftware.htmlunit.html.DomNode;
import com.gargoylesoftware.htmlunit.html.DomText;
import com.gargoylesoftware.htmlunit.javascript.configuration.JsxClass;
import com.gargoylesoftware.htmlunit.javascript.configuration.JsxFunction;
import com.gargoylesoftware.htmlunit.javascript.configuration.JsxGetter;
import com.gargoylesoftware.htmlunit.javascript.host.dom.Node;
import net.sourceforge.htmlunit.corejs.javascript.Context;
/**
* A JavaScript object for MSXML's (ActiveX) XMLDOMElement.
* Represents the element object.
* @see MSDN documentation
*
* @author Ahmed Ashour
* @author Marc Guillemot
* @author Sudhan Moghe
* @author Ronald Brill
* @author Frank Danek
*/
@JsxClass(domClass = DomElement.class, value = IE)
public class XMLDOMElement extends XMLDOMNode {
private XMLDOMNamedNodeMap attributes_;
private Map elementsByTagName_; // for performance and for equality (==)
/**
* Returns the list of attributes for this element.
* @return the list of attributes for this element
*/
@Override
public Object getAttributes() {
if (attributes_ == null) {
attributes_ = createAttributesObject();
}
return attributes_;
}
/**
* Creates the JS object for the property attributes. This object will the be cached.
* @return the JS object
*/
protected XMLDOMNamedNodeMap createAttributesObject() {
return new XMLDOMNamedNodeMap(getDomNodeOrDie());
}
/**
* Attempting to set the value of elements generates an error.
* @param newValue the new value to set
*/
@Override
public void setNodeValue(final String newValue) {
if (newValue == null || "null".equals(newValue)) {
throw Context.reportRuntimeError("Type mismatch.");
}
throw Context.reportRuntimeError("This operation cannot be performed with a node of type ELEMENT.");
}
/**
* Returns the element name.
* @return the element name
*/
@JsxGetter
public String getTagName() {
return getNodeName();
}
/**
* Returns a string that represents the element content. This will also include the text content from all child
* elements, concatenated in document order.
* @return a string that represents the element content
*/
@Override
public String getText() {
final StringBuilder builder = new StringBuilder();
toText(getDomNodeOrDie(), builder);
if (builder.length() > 0 && builder.charAt(builder.length() - 1) == '\n') {
return builder.substring(0, builder.length() - 1);
}
return builder.toString();
}
/**
* Replaces all children of this element with the supplied value.
* @param value the new value for the contents of this node
*/
@Override
public void setText(final Object value) {
if (value == null || "null".equals(value)) {
throw Context.reportRuntimeError("Type mismatch.");
}
super.setText(value);
}
private void toText(final DomNode node, final StringBuilder builder) {
switch (node.getNodeType()) {
case Node.DOCUMENT_TYPE_NODE:
case Node.NOTATION_NODE:
return;
case Node.TEXT_NODE:
case Node.CDATA_SECTION_NODE:
case Node.COMMENT_NODE:
case Node.PROCESSING_INSTRUCTION_NODE:
builder.append(node.getNodeValue());
break;
default:
}
boolean lastWasElement = false;
for (final DomNode child : node.getChildren()) {
switch (child.getNodeType()) {
case Node.ELEMENT_NODE:
lastWasElement = true;
toText(child, builder);
break;
case Node.TEXT_NODE:
case Node.CDATA_SECTION_NODE:
if (StringUtils.isBlank(child.getNodeValue())) {
if (lastWasElement) {
builder.append(' ');
}
lastWasElement = false;
break;
}
lastWasElement = false;
builder.append(child.getNodeValue());
break;
default:
lastWasElement = false;
}
}
}
/**
* Returns the value of the attribute.
* @param name the name of the attribute to return
* @return the value of the specified attribute, {@code null} if the named attribute does not have a
* specified value
*/
@JsxFunction
public Object getAttribute(final String name) {
if (name == null || "null".equals(name)) {
throw Context.reportRuntimeError("Type mismatch.");
}
if (StringUtils.isEmpty(name)) {
throw Context.reportRuntimeError("The empty string '' is not a valid name.");
}
final String value = getDomNodeOrDie().getAttribute(name);
if (value == DomElement.ATTRIBUTE_NOT_DEFINED) {
return null;
}
return value;
}
/**
* Returns the attribute node.
* @param name the name of the attribute to return
* @return the attribute node with the supplied name, {@code null} if the named attribute cannot be found
* on this element
*/
@JsxFunction
public Object getAttributeNode(final String name) {
if (name == null || "null".equals(name)) {
throw Context.reportRuntimeError("Type mismatch.");
}
if (StringUtils.isEmpty(name)) {
throw Context.reportRuntimeError("The empty string '' is not a valid name.");
}
final Map attributes = getDomNodeOrDie().getAttributesMap();
for (final DomAttr attr : attributes.values()) {
if (attr.getName().equals(name)) {
return attr.getScriptableObject();
}
}
return null;
}
/**
* Removes the named attribute.
* @param name the name of the attribute to be removed
*/
@JsxFunction
public void removeAttribute(final String name) {
if (name == null || "null".equals(name)) {
throw Context.reportRuntimeError("Type mismatch.");
}
if (StringUtils.isEmpty(name)) {
throw Context.reportRuntimeError("The empty string '' is not a valid name.");
}
getDomNodeOrDie().removeAttribute(name);
delete(name);
}
/**
* Removes the specified attribute from this element.
* @param att the attribute to be removed from this element
* @return the removed attribute
*/
@JsxFunction
public XMLDOMAttribute removeAttributeNode(final XMLDOMAttribute att) {
if (att == null) {
throw Context.reportRuntimeError("Type mismatch.");
}
final String name = att.getName();
final XMLDOMNamedNodeMap nodes = (XMLDOMNamedNodeMap) getAttributes();
final XMLDOMAttribute removedAtt = (XMLDOMAttribute) nodes.getNamedItemWithoutSyntheticClassAttr(name);
if (removedAtt != null) {
removedAtt.detachFromParent();
}
removeAttribute(name);
return removedAtt;
}
/**
* Sets the value of the named attribute.
*
* @param name the name of the attribute; if the attribute with that name already exists, its value is changed
* @param value the value for the named attribute
*/
@JsxFunction
public void setAttribute(final String name, final String value) {
if (name == null || "null".equals(name)) {
throw Context.reportRuntimeError("Type mismatch.");
}
if (StringUtils.isEmpty(name)) {
throw Context.reportRuntimeError("The empty string '' is not a valid name.");
}
if (value == null || "null".equals(value)) {
throw Context.reportRuntimeError("Type mismatch.");
}
getDomNodeOrDie().setAttribute(name, value);
}
/**
* Sets or updates the supplied attribute node on this element.
* @param newAtt the attribute node to be associated with this element
* @return the replaced attribute node, if any, {@code null} otherwise
*/
@JsxFunction
public XMLDOMAttribute setAttributeNode(final XMLDOMAttribute newAtt) {
if (newAtt == null) {
throw Context.reportRuntimeError("Type mismatch.");
}
final String name = newAtt.getBaseName();
final XMLDOMNamedNodeMap nodes = (XMLDOMNamedNodeMap) getAttributes();
final XMLDOMAttribute replacedAtt = (XMLDOMAttribute) nodes.getNamedItemWithoutSyntheticClassAttr(name);
if (replacedAtt != null) {
replacedAtt.detachFromParent();
}
final DomAttr newDomAttr = newAtt.getDomNodeOrDie();
getDomNodeOrDie().setAttributeNode(newDomAttr);
return replacedAtt;
}
/**
* Returns a list of all descendant elements that match the supplied name.
* @param tagName the name of the element to find; the tagName value '*' matches all descendant elements of this
* element
* @return a list containing all elements that match the supplied name
*/
@JsxFunction
public XMLDOMNodeList getElementsByTagName(final String tagName) {
if (tagName == null || "null".equals(tagName)) {
throw Context.reportRuntimeError("Type mismatch.");
}
final String tagNameTrimmed = tagName.trim();
if (elementsByTagName_ == null) {
elementsByTagName_ = new HashMap<>();
}
XMLDOMNodeList collection = elementsByTagName_.get(tagNameTrimmed);
if (collection != null) {
return collection;
}
final DomNode node = getDomNodeOrDie();
final String description = "XMLDOMElement.getElementsByTagName('" + tagNameTrimmed + "')";
if ("*".equals(tagNameTrimmed)) {
collection = new XMLDOMNodeList(node, false, description) {
@Override
protected boolean isMatching(final DomNode domNode) {
return true;
}
};
}
else if ("".equals(tagNameTrimmed)) {
collection = new XMLDOMNodeList(node, false, description) {
@Override
protected List computeElements() {
final List response = new ArrayList<>();
final DomNode domNode = getDomNodeOrNull();
if (domNode == null) {
return response;
}
for (final DomNode candidate : getCandidates()) {
if (candidate instanceof DomText) {
final DomText domText = (DomText) candidate;
if (!StringUtils.isBlank(domText.getWholeText())) {
response.add(candidate);
}
}
else {
response.add(candidate);
}
}
return response;
}
};
}
else {
collection = new XMLDOMNodeList(node, false, description) {
@Override
protected boolean isMatching(final DomNode domNode) {
return tagNameTrimmed.equals(domNode.getNodeName());
}
};
}
elementsByTagName_.put(tagName, collection);
return collection;
}
/**
* Normalizes all descendant elements by combining two or more adjacent text nodes into one unified text node.
*/
@JsxFunction
public void normalize() {
final DomElement domElement = getDomNodeOrDie();
domElement.normalize();
// normalize all descendants
normalize(domElement);
}
private void normalize(final DomElement domElement) {
for (DomNode domNode : domElement.getChildren()) {
if (domNode instanceof DomElement) {
domNode.normalize();
normalize((DomElement) domNode);
}
}
}
/**
* {@inheritDoc}
*/
@Override
public DomElement getDomNodeOrDie() {
return (DomElement) super.getDomNodeOrDie();
}
}