org.htmlunit.activex.javascript.msxml.XMLDOMDocument Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of xlt Show documentation
Show all versions of xlt Show documentation
XLT (Xceptance LoadTest) is an extensive load and performance test tool developed and maintained by Xceptance.
The newest version!
/*
* Copyright (c) 2002-2024 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 org.htmlunit.activex.javascript.msxml;
import static org.htmlunit.javascript.configuration.SupportedBrowser.IE;
import java.io.IOException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.htmlunit.ElementNotFoundException;
import org.htmlunit.SgmlPage;
import org.htmlunit.StringWebResponse;
import org.htmlunit.WebRequest;
import org.htmlunit.WebResponse;
import org.htmlunit.WebWindow;
import org.htmlunit.html.DomAttr;
import org.htmlunit.html.DomComment;
import org.htmlunit.html.DomDocumentFragment;
import org.htmlunit.html.DomElement;
import org.htmlunit.html.DomNode;
import org.htmlunit.html.DomProcessingInstruction;
import org.htmlunit.html.DomText;
import org.htmlunit.html.Html;
import org.htmlunit.html.HtmlElement;
import org.htmlunit.html.HtmlPage;
import org.htmlunit.javascript.HtmlUnitScriptable;
import org.htmlunit.javascript.JavaScriptEngine;
import org.htmlunit.javascript.configuration.JsxClass;
import org.htmlunit.javascript.configuration.JsxFunction;
import org.htmlunit.javascript.configuration.JsxGetter;
import org.htmlunit.javascript.configuration.JsxSetter;
import org.htmlunit.xml.XmlPage;
import org.w3c.dom.CDATASection;
import org.w3c.dom.Node;
/**
* A JavaScript object for MSXML's (ActiveX) XMLDOMDocument.
* Represents the top level of the XML source. Includes members for retrieving and creating all other XML objects.
* @see MSDN documentation
*
* @author Ahmed Ashour
* @author Marc Guillemot
* @author Sudhan Moghe
* @author Ronald Brill
* @author Chuck Dumont
* @author Frank Danek
*/
@JsxClass(IE)
public class XMLDOMDocument extends XMLDOMNode {
private static final Log LOG = LogFactory.getLog(XMLDOMDocument.class);
private boolean async_ = true;
private XMLDOMImplementation implementation_;
private boolean preserveWhiteSpace_;
private boolean preserveWhiteSpaceDuringLoad_ = true;
private XMLDOMParseError parseError_;
private final Map properties_ = new HashMap<>();
private String url_ = "";
/**
* Creates a new instance.
*/
public XMLDOMDocument() {
this(null);
}
/**
* Creates a new instance, with associated {@link XmlPage}.
* @param enclosingWindow the window
*/
public XMLDOMDocument(final WebWindow enclosingWindow) {
if (enclosingWindow != null) {
try {
final XmlPage page = new XmlPage(null, enclosingWindow, true, false);
setDomNode(page);
}
catch (final IOException e) {
throw JavaScriptEngine.reportRuntimeError("IOException: " + e);
}
}
}
/**
* Returns if asynchronous download is permitted.
* @return if asynchronous download is permitted
*/
@JsxGetter
public boolean isAsync() {
return async_;
}
/**
* Sets if asynchronous download is permitted.
* @param async if asynchronous download is permitted
*/
@JsxSetter
public void setAsync(final boolean async) {
async_ = async;
}
/**
* Returns the document type node that specifies the DTD for this document.
* @return the document type node that specifies the DTD for this document
*/
@JsxGetter
public XMLDOMDocumentType getDoctype() {
final Object documentType = getPage().getDoctype();
if (documentType == null) {
return null;
}
return (XMLDOMDocumentType) getScriptableFor(documentType);
}
/**
* Overwritten to throw also in non strict mode.
* @param ignored ignored param
*/
@JsxSetter
public void setDoctype(final Object ignored) {
throw JavaScriptEngine.typeError("Wrong number of arguments or invalid property assignment");
}
/**
* Returns the root element of the document.
* @return the root element of the document
*/
@JsxGetter
public XMLDOMElement getDocumentElement() {
final Object documentElement = getPage().getDocumentElement();
if (documentElement == null) {
// for instance with an XML document with parsing error
return null;
}
return (XMLDOMElement) getScriptableFor(documentElement);
}
/**
* Sets the root element of the document.
* @param element the root element of the document
*/
@JsxSetter
public void setDocumentElement(final XMLDOMElement element) {
if (element == null) {
throw JavaScriptEngine.reportRuntimeError("Type mismatch.");
}
final XMLDOMElement documentElement = getDocumentElement();
if (documentElement != null) {
documentElement.getDomNodeOrDie().remove();
}
appendChild(element);
}
/**
* Returns the implementation object for the document.
* @return the implementation object for the document
*/
@JsxGetter
public XMLDOMImplementation getImplementation() {
if (implementation_ == null) {
implementation_ = new XMLDOMImplementation();
implementation_.setParentScope(getWindow());
implementation_.setPrototype(getPrototype(implementation_.getClass()));
}
return implementation_;
}
/**
* Overwritten to throw also in non strict mode.
* @param ignored ignored param
*/
@JsxSetter
public void setImplementation(final Object ignored) {
throw JavaScriptEngine.typeError("Wrong number of arguments or invalid property assignment");
}
/**
* Attempting to set the value of documents generates an error.
* @param value the new value to set
*/
@Override
public void setNodeValue(final String value) {
if (value == null || "null".equals(value)) {
throw JavaScriptEngine.reportRuntimeError("Type mismatch.");
}
throw JavaScriptEngine.reportRuntimeError("This operation cannot be performed with a node of type DOCUMENT.");
}
/**
* {@inheritDoc}
*/
@Override
public HtmlUnitScriptable getOwnerDocument() {
return null;
}
/**
* Returns a parse error object that contains information about the last parsing error.
* @return a parse error object
*/
@JsxGetter
public XMLDOMParseError getParseError() {
if (parseError_ == null) {
parseError_ = new XMLDOMParseError();
parseError_.setParentScope(getParentScope());
parseError_.setPrototype(getPrototype(parseError_.getClass()));
}
return parseError_;
}
/**
* Overwritten to throw also in non strict mode.
* @param ignored ignored param
*/
@JsxSetter
public void setParseError(final Object ignored) {
throw JavaScriptEngine.typeError("Wrong number of arguments or invalid property assignment");
}
/**
* Returns the default white space handling.
* @return the default white space handling
*/
@JsxGetter
public boolean isPreserveWhiteSpace() {
return preserveWhiteSpace_;
}
/**
* Set the default white space handling.
* @param preserveWhiteSpace the default white space handling
*/
@JsxSetter
public void setPreserveWhiteSpace(final boolean preserveWhiteSpace) {
preserveWhiteSpace_ = preserveWhiteSpace;
}
/**
* {@inheritDoc}
*/
@Override
public Object getText() {
final XMLDOMElement element = getDocumentElement();
if (element == null) {
return "";
}
return element.getText();
}
/**
* Attempting to set the text of documents generates an error.
* @param text the new text of this node
*/
@Override
public void setText(final Object text) {
if (text == null || "null".equals(text)) {
throw JavaScriptEngine.reportRuntimeError("Type mismatch.");
}
throw JavaScriptEngine.reportRuntimeError("This operation cannot be performed with a node of type DOCUMENT.");
}
/**
* Returns the URL for the last loaded XML document.
* @return the URL for the last loaded XML document
*/
@JsxGetter
public String getUrl() {
return url_;
}
/**
* Returns the XML representation of the node and all its descendants.
* @return an XML representation of this node and all its descendants
*/
@Override
@JsxGetter
public String getXml() {
final XMLSerializer seralizer = new XMLSerializer(preserveWhiteSpaceDuringLoad_);
return seralizer.serializeToString(getDocumentElement());
}
/**
* {@inheritDoc}
*/
@Override
public Object appendChild(final Object newChild) {
verifyChild(newChild);
return super.appendChild(newChild);
}
private void verifyChild(final Object newChild) {
if (!(newChild instanceof XMLDOMNode)) {
throw JavaScriptEngine.reportRuntimeError("Type mismatch.");
}
if (newChild instanceof XMLDOMCDATASection) {
throw JavaScriptEngine.reportRuntimeError("This operation cannot be performed with a node of type CDATA.");
}
if (newChild instanceof XMLDOMText) {
throw JavaScriptEngine.reportRuntimeError("This operation cannot be performed with a node of type TEXT.");
}
if (newChild instanceof XMLDOMElement && getDocumentElement() != null) {
throw JavaScriptEngine.reportRuntimeError("Only one top level element is allowed in an XML document.");
}
if (newChild instanceof XMLDOMDocumentFragment) {
boolean elementFound = false;
XMLDOMNode child = ((XMLDOMDocumentFragment) newChild).getFirstChild();
while (child != null) {
if (child instanceof XMLDOMCDATASection) {
throw JavaScriptEngine.reportRuntimeError(
"This operation cannot be performed with a node of type CDATA.");
}
if (child instanceof XMLDOMText) {
throw JavaScriptEngine.reportRuntimeError(
"This operation cannot be performed with a node of type TEXT.");
}
if (child instanceof XMLDOMElement) {
if (elementFound) {
throw JavaScriptEngine.reportRuntimeError(
"Only one top level element is allowed in an XML document.");
}
elementFound = true;
}
child = child.getNextSibling();
}
}
}
/**
* Creates a new attribute with the specified name.
*
* @param name the name of the new attribute object
* @return the new attribute object
*/
@JsxFunction
public Object createAttribute(final String name) {
if (name == null || "null".equals(name)) {
throw JavaScriptEngine.reportRuntimeError("Type mismatch.");
}
if (StringUtils.isBlank(name) || name.indexOf('<') >= 0 || name.indexOf('>') >= 0) {
throw JavaScriptEngine.reportRuntimeError("To create a node of type ATTR a valid name must be given.");
}
final DomAttr domAttr = getPage().createAttribute(name);
return getScriptableFor(domAttr);
}
/**
* Creates a CDATA section node that contains the supplied data.
* @param data the value to be supplied to the new CDATA section object's nodeValue
property
* @return the new CDATA section object
*/
@JsxFunction
public Object createCDATASection(final String data) {
final CDATASection domCDATASection = getPage().createCDATASection(data);
return getScriptableFor(domCDATASection);
}
/**
* Creates a comment node that contains the supplied data.
* @param data the value to be supplied to the new comment object's nodeValue
property
* @return the new comment object
*/
@JsxFunction
public Object createComment(final String data) {
final DomComment domComment = new DomComment(getPage(), data);
return getScriptableFor(domComment);
}
/**
* Creates an empty document fragment object.
* @return the new document fragment object
*/
@JsxFunction
public Object createDocumentFragment() {
final DomDocumentFragment domDocumentFragment = new DomDocumentFragment(getPage());
return getScriptableFor(domDocumentFragment);
}
/**
* Creates an element node using the specified name.
* @param tagName the name for the new element node
* @return the new element object or NOT_FOUND
if the tag is not supported
*/
@JsxFunction
public Object createElement(final String tagName) {
if (tagName == null || "null".equals(tagName)) {
throw JavaScriptEngine.reportRuntimeError("Type mismatch.");
}
if (StringUtils.isBlank(tagName) || tagName.indexOf('<') >= 0 || tagName.indexOf('>') >= 0) {
throw JavaScriptEngine.reportRuntimeError("To create a node of type ELEMENT a valid name must be given.");
}
try {
final DomElement domElement = (DomElement) getPage().createElement(tagName);
final Object jsElement = getScriptableFor(domElement);
if (jsElement == NOT_FOUND) {
if (LOG.isDebugEnabled()) {
LOG.debug("createElement(" + tagName
+ ") cannot return a result as there isn't a JavaScript object for the element "
+ domElement.getClass().getName());
}
}
return jsElement;
}
catch (final ElementNotFoundException e) {
// Just fall through - result is already set to NOT_FOUND
}
return NOT_FOUND;
}
/**
* Creates a node using the supplied type, name, and namespace.
* @param type a value that uniquely identifies the node type
* @param name the value for the new node's nodeName
property
* @param namespaceURI the namespace URI.
* If specified, the node is created in the context of the namespaceURI parameter
* with the prefix specified on the node name.
* If the name parameter does not have a prefix, this is treated as the default namespace.
* @return the newly created node
*/
@JsxFunction
public Object createNode(final Object type, final String name, final Object namespaceURI) {
switch ((short) JavaScriptEngine.toNumber(type)) {
case Node.ELEMENT_NODE:
return createElementNS((String) namespaceURI, name);
case Node.ATTRIBUTE_NODE:
return createAttribute(name);
default:
throw JavaScriptEngine.reportRuntimeError("xmlDoc.createNode(): Unsupported type "
+ (short) JavaScriptEngine.toNumber(type));
}
}
/**
* Creates a new HTML element with the given tag name, and name.
* @param namespaceURI the URI that identifies an XML namespace
* @param qualifiedName the qualified name of the element type to instantiate
* @return the new element or NOT_FOUND if the tag is not supported
*/
private Object createElementNS(final String namespaceURI, final String qualifiedName) {
final org.w3c.dom.Element element;
if ("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul".equals(namespaceURI)) {
throw JavaScriptEngine.reportRuntimeError("XUL not available");
}
else if (Html.XHTML_NAMESPACE.equals(namespaceURI)
|| Html.SVG_NAMESPACE.equals(namespaceURI)) {
element = getPage().createElementNS(namespaceURI, qualifiedName);
}
else {
element = new DomElement(namespaceURI, qualifiedName, getPage(), null);
}
return getScriptableFor(element);
}
/**
* Creates a processing instruction node that contains the supplied target and data.
* @param target the target part of the processing instruction
* @param data the rest of the processing instruction preceding the closing ?> characters
* @return the new processing instruction object
*/
@JsxFunction
public Object createProcessingInstruction(final String target, final String data) {
final DomProcessingInstruction domProcessingInstruction =
((XmlPage) getPage()).createProcessingInstruction(target, data);
return getScriptableFor(domProcessingInstruction);
}
/**
* Creates a text node that contains the supplied data.
* @param data the value to be supplied to the new text object's nodeValue
property
* @return the new text object or NOT_FOUND
if there is an error
*/
@JsxFunction
public Object createTextNode(final String data) {
try {
final DomText domText = new DomText(getPage(), data);
final HtmlUnitScriptable jsElement = makeScriptableFor(domText);
if (jsElement == NOT_FOUND) {
if (LOG.isDebugEnabled()) {
LOG.debug("createTextNode(" + data
+ ") cannot return a result as there isn't a JavaScript object for the DOM node "
+ domText.getClass().getName());
}
}
return jsElement;
}
catch (final ElementNotFoundException e) {
// Just fall through - result is already set to NOT_FOUND
}
return NOT_FOUND;
}
/**
* Returns a collection of elements that have the specified name.
* @param tagName the element name to find; the tagName
value '*' returns all elements in the document
* @return a collection of elements that match the specified name
*/
@JsxFunction
public XMLDOMNodeList getElementsByTagName(final String tagName) {
final DomNode firstChild = getDomNodeOrDie().getFirstChild();
if (firstChild == null) {
return XMLDOMNodeList.emptyCollection(this);
}
return new XMLDOMNodeList(getDomNodeOrDie(), false, "XMLDOMDocument.getElementsByTagName") {
@Override
protected boolean isMatching(final DomNode node) {
return node.getNodeName().equals(tagName);
}
};
}
/**
* Retrieves the value of one of the second-level properties that are set either by default or using the
* {@link #setProperty(String, String)} method.
* @param name the name of the property
* @return the property value
*/
@JsxFunction
public String getProperty(final String name) {
return properties_.get(name);
}
/**
* {@inheritDoc}
*/
@Override
protected Object insertBeforeImpl(final Object[] args) {
final Object newChild = args[0];
verifyChild(newChild);
if (args.length != 2) {
throw JavaScriptEngine.reportRuntimeError("Wrong number of arguments or invalid property assignment.");
}
return super.insertBeforeImpl(args);
}
/**
* Loads an XML document from the specified location.
* @param xmlSource a URL that specifies the location of the XML file
* @return {@code true} if the load succeeded; {@code false} if the load failed
*/
@JsxFunction
public boolean load(final String xmlSource) {
if (async_ && LOG.isDebugEnabled()) {
LOG.debug("XMLDOMDocument.load(): 'async' is true, currently treated as false.");
}
try {
final WebWindow ww = getWindow().getWebWindow();
final HtmlPage htmlPage = (HtmlPage) ww.getEnclosedPage();
final URL fullyQualifiedURL = htmlPage.getFullyQualifiedUrl(xmlSource);
final WebRequest request = new WebRequest(fullyQualifiedURL);
final WebResponse webResponse = ww.getWebClient().loadWebResponse(request);
final XmlPage page = new XmlPage(webResponse, ww, false, false);
setDomNode(page);
preserveWhiteSpaceDuringLoad_ = preserveWhiteSpace_;
url_ = fullyQualifiedURL.toExternalForm();
return true;
}
catch (final IOException e) {
final XMLDOMParseError parseError = getParseError();
parseError.setErrorCode(-1);
parseError.setFilepos(1);
parseError.setLine(1);
parseError.setLinepos(1);
parseError.setReason(e.getMessage());
parseError.setSrcText("xml");
parseError.setUrl(xmlSource);
if (LOG.isDebugEnabled()) {
LOG.debug("Error parsing XML from '" + xmlSource + "'", e);
}
return false;
}
}
/**
* Loads an XML document using the supplied string.
* @param strXML the XML string to load into this XML document object;
* this string can contain an entire XML document or a well-formed fragment
* @return {@code true} if the load succeeded; {@code false} if the load failed
*/
@JsxFunction
public boolean loadXML(final String strXML) {
try {
final WebWindow webWindow = getWindow().getWebWindow();
final WebResponse webResponse = new StringWebResponse(strXML, webWindow.getEnclosedPage().getUrl());
final XmlPage page = new XmlPage(webResponse, webWindow, false, false);
setDomNode(page);
preserveWhiteSpaceDuringLoad_ = preserveWhiteSpace_;
url_ = "";
return true;
}
catch (final IOException e) {
final XMLDOMParseError parseError = getParseError();
parseError.setErrorCode(-1);
parseError.setFilepos(1);
parseError.setLine(1);
parseError.setLinepos(1);
parseError.setReason(e.getMessage());
parseError.setSrcText("xml");
parseError.setUrl("");
if (LOG.isDebugEnabled()) {
LOG.debug("Error parsing XML\n" + strXML, e);
}
return false;
}
}
/**
* Returns the node that matches the ID attribute.
* @param id the value of the ID to match
* @return since we are not processing DTD, this method always returns {@code null}
*/
@JsxFunction
public Object nodeFromID(final String id) {
return null;
}
/**
* This method is used to set second-level properties on the DOM object.
* @param name the name of the property to be set
* @param value the value of the specified property
*/
@JsxFunction
public void setProperty(final String name, final String value) {
properties_.put(name, value);
}
/**
* @return the preserveWhiteSpaceDuringLoad
*/
public boolean isPreserveWhiteSpaceDuringLoad() {
return preserveWhiteSpaceDuringLoad_;
}
/**
* @return the page that this document is modeling
*/
protected SgmlPage getPage() {
return (SgmlPage) getDomNodeOrDie();
}
/**
* {@inheritDoc}
*/
@Override
public MSXMLScriptable makeScriptableFor(final DomNode domNode) {
final MSXMLScriptable scriptable;
if (domNode instanceof DomElement && !(domNode instanceof HtmlElement)) {
scriptable = new XMLDOMElement();
}
else if (domNode instanceof DomAttr) {
scriptable = new XMLDOMAttribute();
}
else {
return (MSXMLScriptable) super.makeScriptableFor(domNode);
}
scriptable.setParentScope(this);
scriptable.setPrototype(getPrototype(scriptable.getClass()));
scriptable.setDomNode(domNode);
return scriptable;
}
/**
* {@inheritDoc}
*/
@Override
protected void initParentScope(final DomNode domNode, final HtmlUnitScriptable scriptable) {
scriptable.setParentScope(getParentScope());
}
}