com.gargoylesoftware.htmlunit.activex.javascript.msxml.XMLDOMNode 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-2022 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
* https://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.List;
import org.apache.commons.lang3.StringUtils;
import com.gargoylesoftware.htmlunit.SgmlPage;
import com.gargoylesoftware.htmlunit.html.DomCDataSection;
import com.gargoylesoftware.htmlunit.html.DomDocumentFragment;
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.configuration.JsxSetter;
import com.gargoylesoftware.htmlunit.xml.XmlPage;
import net.sourceforge.htmlunit.corejs.javascript.Context;
import net.sourceforge.htmlunit.corejs.javascript.Function;
import net.sourceforge.htmlunit.corejs.javascript.ScriptRuntime;
import net.sourceforge.htmlunit.corejs.javascript.Scriptable;
import net.sourceforge.htmlunit.corejs.javascript.Undefined;
/**
* A JavaScript object for MSXML's (ActiveX) XMLDOMNode.
* Extends the core node with support for data types, namespaces, document type definitions (DTDs), and schemas.
* @see MSDN documentation
*
* @author Mike Bowler
* @author David K. Taylor
* @author Barnaby Court
* @author Christian Sell
* @author George Murnock
* @author Chris Erskine
* @author Bruce Faulkner
* @author Ahmed Ashour
* @author Ronald Brill
* @author Frank Danek
*/
@JsxClass(IE)
public class XMLDOMNode extends MSXMLScriptable {
/** "Live" child nodes collection; has to be a member to have equality (==) working. */
private XMLDOMNodeList childNodes_;
/**
* Returns the list of attributes for this node.
* @return the list of attributes for this node
*/
@JsxGetter
public Object getAttributes() {
return null;
}
/**
* Returns the base name for the name qualified with the namespace.
* @return the base name for the name qualified with the namespace
*/
@JsxGetter
public String getBaseName() {
final DomNode domNode = getDomNodeOrDie();
final String baseName = domNode.getLocalName();
if (baseName == null) {
return "";
}
return baseName;
}
/**
* Returns a node list containing the child nodes.
* @return a node list containing the child nodes
*/
@JsxGetter
public XMLDOMNodeList getChildNodes() {
if (childNodes_ == null) {
final DomNode domNode = getDomNodeOrDie();
final boolean isXmlPage = domNode.getOwnerDocument() instanceof XmlPage;
final Boolean xmlSpaceDefault = isXMLSpaceDefault(domNode);
final boolean skipEmptyTextNode = isXmlPage && !Boolean.FALSE.equals(xmlSpaceDefault);
childNodes_ = new XMLDOMNodeList(domNode, false, "XMLDOMNode.childNodes") {
@Override
protected List computeElements() {
final List response = new ArrayList<>();
for (final DomNode child : domNode.getChildren()) {
//IE: XmlPage ignores all empty text nodes
if (skipEmptyTextNode && child instanceof DomText && !(child instanceof DomCDataSection)
&& StringUtils.isBlank(child.getNodeValue())) { //and 'xml:space' is 'default'
continue;
}
response.add(child);
}
return response;
}
};
}
return childNodes_;
}
/**
* Recursively checks whether "xml:space" attribute is set to "default".
* @param node node to start checking from
* @return {@link Boolean#TRUE} if "default" is set, {@link Boolean#FALSE} for other value,
* or null if nothing is set.
*/
private static Boolean isXMLSpaceDefault(DomNode node) {
for ( ; node instanceof DomElement; node = node.getParentNode()) {
final String value = ((DomElement) node).getAttribute("xml:space");
if (!value.isEmpty()) {
return "default".equals(value);
}
}
return null;
}
/**
* Returns the data type for this node.
* @return the data type for this node
*/
@JsxGetter
public String getDataType() {
return null;
}
/**
* Returns the definition of the node in the document type definition (DTD) or schema.
* @return the definition of the node in the document type definition (DTD) or schema
*/
@JsxGetter
public String getDefinition() {
return null;
}
/**
* Returns the first child of this node.
* @return the first child of this node
*/
@JsxGetter
public XMLDOMNode getFirstChild() {
final DomNode domNode = getDomNodeOrDie();
return getJavaScriptNode(domNode.getFirstChild());
}
/**
* Overwritten to throw also in non strict mode.
* @param ignored ignored param
*/
@JsxSetter
public void setFirstChild(final Object ignored) {
throw ScriptRuntime.typeError("Wrong number of arguments or invalid property assignment");
}
/**
* Returns the last child node.
* @return the last child node
*/
@JsxGetter
public XMLDOMNode getLastChild() {
final DomNode domNode = getDomNodeOrDie();
return getJavaScriptNode(domNode.getLastChild());
}
/**
* Overwritten to throw also in non strict mode.
* @param ignored ignored param
*/
@JsxSetter
public void setLastChild(final Object ignored) {
throw ScriptRuntime.typeError("Wrong number of arguments or invalid property assignment");
}
/**
* Returns the Uniform Resource Identifier (URI) for the namespace.
* @return the Uniform Resource Identifier (URI) for the namespace
*/
@JsxGetter
public String getNamespaceURI() {
final DomNode domNode = getDomNodeOrDie();
final String namespaceURI = domNode.getNamespaceURI();
if (namespaceURI == null) {
return "";
}
return namespaceURI;
}
/**
* Returns the next sibling of this node in the parent's child list.
* @return the next sibling of this node in the parent's child list
*/
@JsxGetter
public XMLDOMNode getNextSibling() {
final DomNode domNode = getDomNodeOrDie();
return getJavaScriptNode(domNode.getNextSibling());
}
/**
* Returns the qualified name for attribute, document type, element, entity, or notation nodes. Returns a fixed
* string for all other node types.
* @return the qualified name
*/
@JsxGetter
public String getNodeName() {
final DomNode domNode = getDomNodeOrDie();
return domNode.getNodeName();
}
/**
* Returns the XML Document Object Model (DOM) node type, which determines valid values and whether the node can
* have child nodes.
* @return the XML Document Object Model (DOM) node type
*/
@JsxGetter
public short getNodeType() {
final DomNode domNode = getDomNodeOrDie();
return domNode.getNodeType();
}
/**
* Returns the text associated with the node.
* @return the text associated with the node
*/
@JsxGetter
public String getNodeValue() {
final DomNode domNode = getDomNodeOrDie();
return domNode.getNodeValue();
}
/**
* Sets the text associated with the node.
* @param value the text associated with the node
*/
@JsxSetter
public void setNodeValue(final String value) {
if (value == null || "null".equals(value)) {
throw Context.reportRuntimeError("Type mismatch.");
}
final DomNode domNode = getDomNodeOrDie();
domNode.setNodeValue(value);
}
/**
* Returns the root of the document that contains the node.
* @return the root of the document that contains the node
*/
@JsxGetter
public Object getOwnerDocument() {
final DomNode domNode = getDomNodeOrDie();
final Object document = domNode.getOwnerDocument();
if (document == null) {
return null;
}
return ((SgmlPage) document).getScriptableObject();
}
/**
* Returns the parent node.
* @return the parent node
*/
@JsxGetter
public Object getParentNode() {
final DomNode domNode = getDomNodeOrDie();
return getJavaScriptNode(domNode.getParentNode());
}
/**
* Returns the namespace prefix.
* @return the namespace prefix
*/
@JsxGetter
public String getPrefix() {
final DomNode domNode = getDomNodeOrDie();
final String prefix = domNode.getPrefix();
if (prefix == null || domNode.getHtmlPageOrNull() != null) {
return "";
}
return prefix;
}
/**
* Returns the previous sibling of the node in the parent's child list.
* @return the previous sibling of the node in the parent's child list
*/
@JsxGetter
public XMLDOMNode getPreviousSibling() {
final DomNode domNode = getDomNodeOrDie();
return getJavaScriptNode(domNode.getPreviousSibling());
}
/**
* Returns the text content of the node or the concatenated text representing the node and its descendants.
* @return the text content of the node and its descendants
*/
@JsxGetter
public Object getText() {
final DomNode domNode = getDomNodeOrDie();
return domNode.getTextContent();
}
/**
* Replace all child elements of this element by the supplied text.
* @param text the new text of this node
*/
@JsxSetter
public void setText(final Object text) {
final DomNode domNode = getDomNodeOrDie();
domNode.setTextContent(text == null ? null : Context.toString(text));
}
/**
* Returns the XML representation of the node and all its descendants.
* @return the XML representation of the node and all its descendants
*/
@JsxGetter
public Object getXml() {
final DomNode domNode = getDomNodeOrDie();
String xml;
if (this instanceof XMLDOMElement) {
final boolean preserveWhiteSpace =
((XMLDOMDocument) getOwnerDocument()).isPreserveWhiteSpaceDuringLoad();
final XMLSerializer serializer = new XMLSerializer(preserveWhiteSpace);
xml = serializer.serializeToString(this);
}
else {
xml = domNode.asXml();
}
if (xml.endsWith("\r\n")) {
xml = xml.substring(0, xml.length() - 2);
}
return xml;
}
/**
* Appends a new child as the last child of the node.
* @param newChild the new child node to be appended at the end of the list of children belonging to this node
* @return the new child node successfully appended to the list
*/
@JsxFunction
public Object appendChild(final Object newChild) {
if (newChild == null || "null".equals(newChild)) {
throw Context.reportRuntimeError("Type mismatch.");
}
Object appendedChild = null;
if (newChild instanceof XMLDOMNode) {
final XMLDOMNode childNode = (XMLDOMNode) newChild;
// Get XML node for the DOM node passed in
final DomNode childDomNode = childNode.getDomNodeOrDie();
// Get the parent XML node that the child should be added to.
final DomNode parentNode = getDomNodeOrDie();
// Append the child to the parent node
parentNode.appendChild(childDomNode);
appendedChild = newChild;
// if the parentNode has null parentNode in IE,
// create a DocumentFragment to be the parentNode's parentNode.
if (!(parentNode instanceof SgmlPage)
&& !(this instanceof XMLDOMDocumentFragment) && parentNode.getParentNode() == null) {
final DomDocumentFragment fragment = parentNode.getPage().createDocumentFragment();
fragment.appendChild(parentNode);
}
}
return appendedChild;
}
/**
* Clones a new node.
* @param deep flag that indicates whether to recursively clone all nodes that are descendants of this node;
* if {@code true}, creates a clone of the complete tree below this node,
* if {@code false}, clones this node and its attributes only
* @return the newly created clone node
*/
@JsxFunction
public Object cloneNode(final boolean deep) {
final DomNode domNode = getDomNodeOrDie();
final DomNode clonedNode = domNode.cloneNode(deep);
return getJavaScriptNode(clonedNode);
}
/**
* Provides a fast way to determine whether a node has children.
* @return boolean {@code true} if this node has children
*/
@JsxFunction
public boolean hasChildNodes() {
final DomNode domNode = getDomNodeOrDie();
return domNode.getChildren().iterator().hasNext();
}
/**
* Inserts a child node to the left of the specified node, or at the end of the list.
* @param context the JavaScript context
* @param thisObj the scriptable
* @param args the arguments passed into the method
* @param function the function
* @return on success, returns the child node that was inserted
*/
@JsxFunction
public static Object insertBefore(
final Context context, final Scriptable thisObj, final Object[] args, final Function function) {
return ((XMLDOMNode) thisObj).insertBeforeImpl(args);
}
/**
* Inserts a child node to the left of the specified node, or at the end of the list.
* @param args the arguments
*
* - args[0]=newChild the new node to be inserted
*
- args[1]=refChild the reference node; the
newChild
parameter is inserted to the left
* of the refChild
parameter; if {@code null}, the newChild
parameter is inserted
* at the end of the child list
*
* @return on success, returns the child node that was inserted
*/
protected Object insertBeforeImpl(final Object[] args) {
final Object newChildObject = args[0];
final Object refChildObject;
if (args.length > 1) {
refChildObject = args[1];
}
else {
refChildObject = Undefined.instance;
}
Object appendedChild = null;
if (newChildObject instanceof XMLDOMNode) {
final XMLDOMNode newChild = (XMLDOMNode) newChildObject;
final DomNode newChildNode = newChild.getDomNodeOrDie();
if (newChildNode instanceof DomDocumentFragment) {
final DomDocumentFragment fragment = (DomDocumentFragment) newChildNode;
for (final DomNode child : fragment.getChildren()) {
insertBeforeImpl(new Object[] {child.getScriptableObject(), refChildObject});
}
return newChildObject;
}
final DomNode refChildNode;
// IE accepts non standard calls with only one arg
if (Undefined.isUndefined(refChildObject)) {
if (args.length > 1) {
throw Context.reportRuntimeError("Invalid argument.");
}
refChildNode = null;
}
else if (refChildObject == null) {
refChildNode = null;
}
else {
refChildNode = ((XMLDOMNode) refChildObject).getDomNodeOrDie();
}
final DomNode domNode = getDomNodeOrDie();
// Append the child to the parent node
if (refChildNode == null) {
domNode.appendChild(newChildNode);
}
else {
refChildNode.insertBefore(newChildNode);
}
appendedChild = newChildObject;
// if parentNode is null in IE, create a DocumentFragment to be the parentNode
if (domNode.getParentNode() == null) {
final DomDocumentFragment fragment = domNode.getPage().createDocumentFragment();
fragment.appendChild(domNode);
}
}
return appendedChild;
}
/**
* Removes the specified child node from the list of children and returns it.
* @param childNode the child node to be removed from the list of children of this node
* @return the removed child node
*/
@JsxFunction
public Object removeChild(final Object childNode) {
Object removedChild = null;
if (childNode instanceof XMLDOMNode) {
// Get XML node for the DOM node passed in
final DomNode childDomNode = ((XMLDOMNode) childNode).getDomNodeOrDie();
// Remove the child from the parent node
childDomNode.remove();
removedChild = childNode;
}
return removedChild;
}
/**
* Replaces the specified old child node with the supplied new child node.
* @param newChild the new child that is to replace the old child; if {@code null},
* oldChild
is removed without a replacement
* @param oldChild the old child that is to be replaced by the new child
* @return the old child that is replaced
*/
@JsxFunction
public Object replaceChild(final Object newChild, final Object oldChild) {
Object removedChild = null;
if (newChild instanceof XMLDOMDocumentFragment) {
final XMLDOMDocumentFragment fragment = (XMLDOMDocumentFragment) newChild;
XMLDOMNode firstNode = null;
final XMLDOMNode refChildObject = ((XMLDOMNode) oldChild).getNextSibling();
for (final DomNode node : fragment.getDomNodeOrDie().getChildren()) {
if (firstNode == null) {
replaceChild(node.getScriptableObject(), oldChild);
firstNode = node.getScriptableObject();
}
else {
insertBeforeImpl(new Object[] {node.getScriptableObject(), refChildObject});
}
}
if (firstNode == null) {
removeChild(oldChild);
}
removedChild = oldChild;
}
else if (newChild instanceof XMLDOMNode && oldChild instanceof XMLDOMNode) {
final XMLDOMNode newChildNode = (XMLDOMNode) newChild;
// Get XML nodes for the DOM nodes passed in
final DomNode newChildDomNode = newChildNode.getDomNodeOrDie();
final DomNode oldChildDomNode = ((XMLDOMNode) oldChild).getDomNodeOrDie();
// Replace the old child with the new child.
oldChildDomNode.replace(newChildDomNode);
removedChild = oldChild;
}
return removedChild;
}
/**
* Applies the specified pattern-matching operation to this node's context and returns the list of matching nodes.
* @param expression a string specifying an XPath expression
* @return the collection of nodes selected by applying the given pattern-matching operation;
* if no nodes are selected, returns an empty collection
*/
@JsxFunction
public XMLDOMSelection selectNodes(final String expression) {
final DomNode domNode = getDomNodeOrDie();
final boolean attributeChangeSensitive = expression.contains("@");
return new XMLDOMSelection(domNode, attributeChangeSensitive,
"XMLDOMNode.selectNodes('" + expression + "')") {
@Override
protected List computeElements() {
return new ArrayList<>(domNode.getByXPath(expression));
}
};
}
/**
* Applies the specified pattern-matching operation to this node's context and returns the first matching node.
* @param expression a string specifying an XPath expression
* @return the first node that matches the given pattern-matching operation;
* if no nodes match the expression, returns {@code null}
*/
@JsxFunction
public Object selectSingleNode(final String expression) {
final XMLDOMNodeList collection = selectNodes(expression);
if (collection.getLength() > 0) {
return collection.get(0, collection);
}
return null;
}
/**
* Gets the JavaScript node for a given {@link DomNode}.
* @param domNode the {@link DomNode}
* @return the JavaScript node or null if the {@link DomNode} was null
*/
protected XMLDOMNode getJavaScriptNode(final DomNode domNode) {
if (domNode == null) {
return null;
}
return (XMLDOMNode) getScriptableFor(domNode);
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy