From: <rb...@us...> - 2013-12-04 18:43:51
|
Revision: 8816 http://sourceforge.net/p/htmlunit/code/8816 Author: rbri Date: 2013-12-04 18:43:47 +0000 (Wed, 04 Dec 2013) Log Message: ----------- XMLDocument.firstChild() is now enabled to support more nodes at the root level like ProcessingInstruction, Comment Modified Paths: -------------- trunk/htmlunit/src/changes/changes.xml trunk/htmlunit/src/main/java/com/gargoylesoftware/htmlunit/javascript/host/CharacterDataImpl.java trunk/htmlunit/src/main/java/com/gargoylesoftware/htmlunit/javascript/host/XSLTProcessor.java trunk/htmlunit/src/main/java/com/gargoylesoftware/htmlunit/xml/XmlPage.java trunk/htmlunit/src/main/java/com/gargoylesoftware/htmlunit/xml/XmlUtil.java trunk/htmlunit/src/test/java/com/gargoylesoftware/htmlunit/javascript/host/xml/XMLDocument2Test.java Modified: trunk/htmlunit/src/changes/changes.xml =================================================================== --- trunk/htmlunit/src/changes/changes.xml 2013-12-04 18:41:03 UTC (rev 8815) +++ trunk/htmlunit/src/changes/changes.xml 2013-12-04 18:43:47 UTC (rev 8816) @@ -8,8 +8,11 @@ <body> <release version="2.14" date="???" description="Bugfixes"> - DomCharacterData-deleteData <action type="fix" dev="rbri" due-to="Frank Danek"> + JavaScript: XMLDocument.firstChild() is now enabled to support more nodes at the + root level like ProcessingInstruction, Comment. + </action> + <action type="fix" dev="rbri" due-to="Frank Danek"> JavaScript: DomCharacterData.deleteData no longer wipes the string when called with an invalid offset. Also the error handling is fixed. </action> Modified: trunk/htmlunit/src/main/java/com/gargoylesoftware/htmlunit/javascript/host/CharacterDataImpl.java =================================================================== --- trunk/htmlunit/src/main/java/com/gargoylesoftware/htmlunit/javascript/host/CharacterDataImpl.java 2013-12-04 18:41:03 UTC (rev 8815) +++ trunk/htmlunit/src/main/java/com/gargoylesoftware/htmlunit/javascript/host/CharacterDataImpl.java 2013-12-04 18:43:47 UTC (rev 8816) @@ -14,6 +14,9 @@ */ package com.gargoylesoftware.htmlunit.javascript.host; +import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.JS_DOM_CDATA_DELETE_THROWS_NEGATIVE_COUNT; +import net.sourceforge.htmlunit.corejs.javascript.Context; + import com.gargoylesoftware.htmlunit.html.DomCharacterData; import com.gargoylesoftware.htmlunit.javascript.configuration.JsxClass; import com.gargoylesoftware.htmlunit.javascript.configuration.JsxFunction; @@ -83,7 +86,24 @@ */ @JsxFunction public void deleteData(final int offset, final int count) { + if (offset < 0) { + throw Context.reportRuntimeError("Provided offset: " + offset + " is less than zero."); + } + + if (getBrowserVersion().hasFeature(JS_DOM_CDATA_DELETE_THROWS_NEGATIVE_COUNT)) { + if (count < 0) { + throw Context.reportRuntimeError("Provided count: " + count + " is less than zero."); + } + if (count == 0) { + return; + } + } + final DomCharacterData domCharacterData = (DomCharacterData) getDomNodeOrDie(); + if (offset > domCharacterData.getLength()) { + throw Context.reportRuntimeError("Provided offset: " + offset + " is greater than length."); + } + domCharacterData.deleteData(offset, count); } Modified: trunk/htmlunit/src/main/java/com/gargoylesoftware/htmlunit/javascript/host/XSLTProcessor.java =================================================================== --- trunk/htmlunit/src/main/java/com/gargoylesoftware/htmlunit/javascript/host/XSLTProcessor.java 2013-12-04 18:41:03 UTC (rev 8815) +++ trunk/htmlunit/src/main/java/com/gargoylesoftware/htmlunit/javascript/host/XSLTProcessor.java 2013-12-04 18:43:47 UTC (rev 8816) @@ -177,7 +177,7 @@ final SgmlPage parentPage = parent.getPage(); final NodeList children = ((org.w3c.dom.Node) result).getChildNodes(); for (int i = 0; i < children.getLength(); i++) { - XmlUtil.appendChild(parentPage, parent, children.item(i)); + XmlUtil.appendChild(parentPage, parent, children.item(i), true); } } else { Modified: trunk/htmlunit/src/main/java/com/gargoylesoftware/htmlunit/xml/XmlPage.java =================================================================== --- trunk/htmlunit/src/main/java/com/gargoylesoftware/htmlunit/xml/XmlPage.java 2013-12-04 18:41:03 UTC (rev 8815) +++ trunk/htmlunit/src/main/java/com/gargoylesoftware/htmlunit/xml/XmlPage.java 2013-12-04 18:43:47 UTC (rev 8816) @@ -82,7 +82,7 @@ super(null, enclosingWindow); node_ = node; if (node_ != null) { - XmlUtil.appendChild(this, this, node_); + XmlUtil.appendChild(this, this, node_, true); } } @@ -98,11 +98,29 @@ */ public XmlPage(final WebResponse webResponse, final WebWindow enclosingWindow, final boolean ignoreSAXException) throws IOException { + this(webResponse, enclosingWindow, ignoreSAXException, true); + } + + /** + * Creates an instance. + * A warning is logged if an exception is thrown while parsing the XML content + * (for instance when the content is not a valid XML and can't be parsed). + * + * @param webResponse the response from the server + * @param enclosingWindow the window that holds the page + * @param ignoreSAXException Whether to ignore {@link SAXException} or throw it as {@link IOException} + * @param handleXHTMLAsHTML if true elements from the XHTML namespace are handled as HTML elements instead of + * DOM elements + * @throws IOException if the page could not be created + */ + public XmlPage(final WebResponse webResponse, final WebWindow enclosingWindow, final boolean ignoreSAXException, + final boolean handleXHTMLAsHTML) throws IOException { super(webResponse, enclosingWindow); try { try { - node_ = XmlUtil.buildDocument(webResponse).getDocumentElement(); + final Document document = XmlUtil.buildDocument(webResponse); + node_ = document.getFirstChild(); } catch (final SAXException e) { LOG.warn("Failed parsing XML document " + webResponse.getWebRequest().getUrl() @@ -122,8 +140,10 @@ } } - if (node_ != null) { - XmlUtil.appendChild(this, this, node_); + Node node = node_; + while (node != null) { + XmlUtil.appendChild(this, this, node, handleXHTMLAsHTML); + node = node.getNextSibling(); } } Modified: trunk/htmlunit/src/main/java/com/gargoylesoftware/htmlunit/xml/XmlUtil.java =================================================================== --- trunk/htmlunit/src/main/java/com/gargoylesoftware/htmlunit/xml/XmlUtil.java 2013-12-04 18:41:03 UTC (rev 8815) +++ trunk/htmlunit/src/main/java/com/gargoylesoftware/htmlunit/xml/XmlUtil.java 2013-12-04 18:43:47 UTC (rev 8816) @@ -18,7 +18,7 @@ import java.io.InputStreamReader; import java.io.Reader; import java.io.StringReader; -import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.Locale; import java.util.Map; @@ -195,30 +195,44 @@ * @param page the owner page of {@link DomElement}s to be created * @param parent the parent DomNode * @param child the child Node + * @param handleXHTMLAsHTML if true elements from the XHTML namespace are handled as HTML elements instead of + * DOM elements */ - public static void appendChild(final SgmlPage page, final DomNode parent, final Node child) { + public static void appendChild(final SgmlPage page, final DomNode parent, final Node child, + final boolean handleXHTMLAsHTML) { final DocumentType documentType = child.getOwnerDocument().getDoctype(); if (documentType != null && page instanceof XmlPage) { final DomDocumentType domDoctype = new DomDocumentType( page, documentType.getName(), documentType.getPublicId(), documentType.getSystemId()); ((XmlPage) page).setDocumentType(domDoctype); } - final DomNode childXml = createFrom(page, child); + final DomNode childXml = createFrom(page, child, handleXHTMLAsHTML); parent.appendChild(childXml); - copy(page, child, childXml); + copy(page, child, childXml, handleXHTMLAsHTML); } - private static DomNode createFrom(final SgmlPage page, final Node source) { + private static DomNode createFrom(final SgmlPage page, final Node source, final boolean handleXHTMLAsHTML) { if (source.getNodeType() == Node.TEXT_NODE) { return new DomText(page, source.getNodeValue()); } + if (source.getNodeType() == Node.PROCESSING_INSTRUCTION_NODE) { + return new DomProcessingInstruction(page, source.getNodeName(), source.getNodeValue()); + } + if (source.getNodeType() == Node.COMMENT_NODE) { + return new DomComment(page, source.getNodeValue()); + } + if (source.getNodeType() == Node.DOCUMENT_TYPE_NODE) { + final DocumentType documentType = (DocumentType) source; + return new DomDocumentType(page, documentType.getName(), documentType.getPublicId(), + documentType.getSystemId()); + } final String ns = source.getNamespaceURI(); String localName = source.getLocalName(); - if (HTMLParser.XHTML_NAMESPACE.equals(ns)) { + if (handleXHTMLAsHTML && HTMLParser.XHTML_NAMESPACE.equals(ns)) { final ElementFactory factory = HTMLParser.getFactory(localName); return factory.createElementNS(page, ns, localName, namedNodeMapToSaxAttributes(source.getAttributes())); } - final Map<String, DomAttr> attributes = new HashMap<String, DomAttr>(); + final Map<String, DomAttr> attributes = new LinkedHashMap<String, DomAttr>(); final NamedNodeMap nodeAttributes = source.getAttributes(); for (int i = 0; i < nodeAttributes.getLength(); i++) { final Attr attribute = (Attr) nodeAttributes.item(i); @@ -265,16 +279,19 @@ * @param page the page which the nodes belong to * @param source the node to copy from * @param dest the node to copy to + * @param handleXHTMLAsHTML if true elements from the XHTML namespace are handled as HTML elements instead of + * DOM elements */ - private static void copy(final SgmlPage page, final Node source, final DomNode dest) { + private static void copy(final SgmlPage page, final Node source, final DomNode dest, + final boolean handleXHTMLAsHTML) { final NodeList nodeChildren = source.getChildNodes(); for (int i = 0; i < nodeChildren.getLength(); i++) { final Node child = nodeChildren.item(i); switch (child.getNodeType()) { case Node.ELEMENT_NODE: - final DomNode childXml = createFrom(page, child); + final DomNode childXml = createFrom(page, child, handleXHTMLAsHTML); dest.appendChild(childXml); - copy(page, child, childXml); + copy(page, child, childXml, handleXHTMLAsHTML); break; case Node.TEXT_NODE: Modified: trunk/htmlunit/src/test/java/com/gargoylesoftware/htmlunit/javascript/host/xml/XMLDocument2Test.java =================================================================== --- trunk/htmlunit/src/test/java/com/gargoylesoftware/htmlunit/javascript/host/xml/XMLDocument2Test.java 2013-12-04 18:41:03 UTC (rev 8815) +++ trunk/htmlunit/src/test/java/com/gargoylesoftware/htmlunit/javascript/host/xml/XMLDocument2Test.java 2013-12-04 18:43:47 UTC (rev 8816) @@ -15,6 +15,7 @@ package com.gargoylesoftware.htmlunit.javascript.host.xml; import static com.gargoylesoftware.htmlunit.BrowserRunner.Browser.IE; +import static com.gargoylesoftware.htmlunit.BrowserRunner.Browser.IE8; import org.junit.Test; import org.junit.runner.RunWith; @@ -22,6 +23,7 @@ import com.gargoylesoftware.htmlunit.BrowserRunner; import com.gargoylesoftware.htmlunit.BrowserRunner.Alerts; import com.gargoylesoftware.htmlunit.BrowserRunner.Browsers; +import com.gargoylesoftware.htmlunit.BrowserRunner.NotYetImplemented; import com.gargoylesoftware.htmlunit.WebDriverTestCase; /** @@ -179,4 +181,276 @@ + "</body></html>"; loadPageWithAlerts2(html); } + + /** + * @throws Exception if the test fails + */ + @Test + @Alerts({ "foo", "foo" }) + public void firstChild_element() throws Exception { + final String html = "<html><head><title>foo</title><script>\n" + + " function test() {\n" + + " var doc = " + XMLDocumentTest.callLoadXMLDocumentFromFile("'" + URL_SECOND + "'") + ";\n" + + " alert(doc.firstChild.nodeName);\n" + + " alert(doc.documentElement.nodeName);\n" + + " }\n" + + XMLDocumentTest.LOAD_NATIVE_XML_DOCUMENT_FROM_FILE_FUNCTION + + "</script></head><body onload='test()'>\n" + + "</body></html>"; + + final String xml = + "<foo>\n" + + " <foofoo name='first'>something</foofoo>\n" + + " <foofoo name='second'>something else</foofoo>\n" + + "</foo>"; + + getMockWebConnection().setResponse(URL_SECOND, xml, "text/xml"); + loadPageWithAlerts2(html); + } + + /** + * @throws Exception if the test fails + */ + @Test + @Browsers(IE) + @Alerts({ "foo", "foo" }) + public void firstChild_element_activeX() throws Exception { + final String html = "<html><head><title>foo</title><script>\n" + + " function test() {\n" + + " var doc = " + XMLDocumentTest.callLoadXMLDocumentFromFile("'" + URL_SECOND + "'") + ";\n" + + " alert(doc.firstChild.nodeName);\n" + + " alert(doc.documentElement.nodeName);\n" + + " }\n" + + XMLDocumentTest.LOAD_ACTIVEX_XML_DOCUMENT_FROM_FILE_FUNCTION + + "</script></head><body onload='test()'>\n" + + "</body></html>"; + + final String xml = + "<foo>\n" + + " <foofoo name='first'>something</foofoo>\n" + + " <foofoo name='second'>something else</foofoo>\n" + + "</foo>"; + + getMockWebConnection().setResponse(URL_SECOND, xml, "text/xml"); + loadPageWithAlerts2(html); + } + + /** + * @throws Exception if the test fails + */ + @Test + @Alerts(DEFAULT = { "foo", "foo" }, + IE8 = { "xml", "foo" }) + @NotYetImplemented(IE8) + // Xerces does not offer any way to access the XML declaration + public void firstChild_xmlDeclaration() throws Exception { + final String html = "<html><head><title>foo</title><script>\n" + + " function test() {\n" + + " var doc = " + XMLDocumentTest.callLoadXMLDocumentFromFile("'" + URL_SECOND + "'") + ";\n" + + " alert(doc.firstChild.nodeName);\n" + + " alert(doc.documentElement.nodeName);\n" + + " }\n" + + XMLDocumentTest.LOAD_NATIVE_XML_DOCUMENT_FROM_FILE_FUNCTION + + "</script></head><body onload='test()'>\n" + + "</body></html>"; + + final String xml = + "<?xml version=\"1.0\"?>\n" + + "<foo>\n" + + " <foofoo name='first'>something</foofoo>\n" + + " <foofoo name='second'>something else</foofoo>\n" + + "</foo>"; + + getMockWebConnection().setResponse(URL_SECOND, xml, "text/xml"); + loadPageWithAlerts2(html); + } + + /** + * @throws Exception if the test fails + */ + @Test + @Browsers(IE) + @Alerts({ "xml", "foo" }) + @NotYetImplemented(IE) + // Xerces does not offer any way to access the XML declaration + public void firstChild_xmlDeclaration_activeX() throws Exception { + final String html = "<html><head><title>foo</title><script>\n" + + " function test() {\n" + + " var doc = " + XMLDocumentTest.callLoadXMLDocumentFromFile("'" + URL_SECOND + "'") + ";\n" + + " alert(doc.firstChild.nodeName);\n" + + " alert(doc.documentElement.nodeName);\n" + + " }\n" + + XMLDocumentTest.LOAD_ACTIVEX_XML_DOCUMENT_FROM_FILE_FUNCTION + + "</script></head><body onload='test()'>\n" + + "</body></html>"; + + final String xml = + "<?xml version=\"1.0\"?>\n" + + "<foo>\n" + + " <foofoo name='first'>something</foofoo>\n" + + " <foofoo name='second'>something else</foofoo>\n" + + "</foo>"; + + getMockWebConnection().setResponse(URL_SECOND, xml, "text/xml"); + loadPageWithAlerts2(html); + } + + /** + * @throws Exception if the test fails + */ + @Test + @Alerts({ "apache", "foo" }) + public void firstChild_processingInstruction() throws Exception { + final String html = "<html><head><title>foo</title><script>\n" + + " function test() {\n" + + " var doc = " + XMLDocumentTest.callLoadXMLDocumentFromFile("'" + URL_SECOND + "'") + ";\n" + + " alert(doc.firstChild.nodeName);\n" + + " alert(doc.documentElement.nodeName);\n" + + " }\n" + + XMLDocumentTest.LOAD_NATIVE_XML_DOCUMENT_FROM_FILE_FUNCTION + + "</script></head><body onload='test()'>\n" + + "</body></html>"; + + final String xml = + "<?apache include file=\"header.html\" ?>\n" + + "<foo>\n" + + " <foofoo name='first'>something</foofoo>\n" + + " <foofoo name='second'>something else</foofoo>\n" + + "</foo>"; + + getMockWebConnection().setResponse(URL_SECOND, xml, "text/xml"); + loadPageWithAlerts2(html); + } + + /** + * @throws Exception if the test fails + */ + @Test + @Browsers(IE) + @Alerts({ "apache", "foo" }) + public void firstChild_processingInstruction_activeX() throws Exception { + final String html = "<html><head><title>foo</title><script>\n" + + " function test() {\n" + + " var doc = " + XMLDocumentTest.callLoadXMLDocumentFromFile("'" + URL_SECOND + "'") + ";\n" + + " alert(doc.firstChild.nodeName);\n" + + " alert(doc.documentElement.nodeName);\n" + + " }\n" + + XMLDocumentTest.LOAD_ACTIVEX_XML_DOCUMENT_FROM_FILE_FUNCTION + + "</script></head><body onload='test()'>\n" + + "</body></html>"; + + final String xml = + "<?apache include file=\"header.html\" ?>\n" + + "<foo>\n" + + " <foofoo name='first'>something</foofoo>\n" + + " <foofoo name='second'>something else</foofoo>\n" + + "</foo>"; + + getMockWebConnection().setResponse(URL_SECOND, xml, "text/xml"); + loadPageWithAlerts2(html); + } + + /** + * @throws Exception if the test fails + */ + @Test + @Alerts({ "dtd", "a" }) + public void firstChild_documentType() throws Exception { + final String html = "<html><head><title>foo</title><script>\n" + + " function test() {\n" + + " var doc = " + XMLDocumentTest.callLoadXMLDocumentFromFile("'" + URL_SECOND + "'") + ";\n" + + " alert(doc.firstChild.nodeName);\n" + + " alert(doc.documentElement.nodeName);\n" + + " }\n" + + XMLDocumentTest.LOAD_NATIVE_XML_DOCUMENT_FROM_FILE_FUNCTION + + "</script></head><body onload='test()'>\n" + + "</body></html>"; + + final String xml = + "<!DOCTYPE dtd [ <!ELEMENT a (b+)> <!ELEMENT b (#PCDATA)> ]>\n" + + "<a><b>1</b><b>2</b></a>"; + + getMockWebConnection().setResponse(URL_SECOND, xml, "text/xml"); + loadPageWithAlerts2(html); + } + + /** + * @throws Exception if the test fails + */ + @Test + @Browsers(IE) + @Alerts({ "dtd", "a" }) + public void firstChild_documentType_activeX() throws Exception { + final String html = "<html><head><title>foo</title><script>\n" + + " function test() {\n" + + " var doc = " + XMLDocumentTest.callLoadXMLDocumentFromFile("'" + URL_SECOND + "'") + ";\n" + + " alert(doc.firstChild.nodeName);\n" + + " alert(doc.documentElement.nodeName);\n" + + " }\n" + + XMLDocumentTest.LOAD_ACTIVEX_XML_DOCUMENT_FROM_FILE_FUNCTION + + "</script></head><body onload='test()'>\n" + + "</body></html>"; + + final String xml = + "<!DOCTYPE dtd [ <!ELEMENT a (b+)> <!ELEMENT b (#PCDATA)> ]>\n" + + "<a><b>1</b><b>2</b></a>"; + + getMockWebConnection().setResponse(URL_SECOND, xml, "text/xml"); + loadPageWithAlerts2(html); + } + + /** + * @throws Exception if the test fails + */ + @Test + @Alerts({ "#comment", "foo" }) + public void firstChild_comment() throws Exception { + final String html = "<html><head><title>foo</title><script>\n" + + " function test() {\n" + + " var doc = " + XMLDocumentTest.callLoadXMLDocumentFromFile("'" + URL_SECOND + "'") + ";\n" + + " alert(doc.firstChild.nodeName);\n" + + " alert(doc.documentElement.nodeName);\n" + + " }\n" + + XMLDocumentTest.LOAD_NATIVE_XML_DOCUMENT_FROM_FILE_FUNCTION + + "</script></head><body onload='test()'>\n" + + "</body></html>"; + + final String xml = + "<!--comment-->\n" + + "<foo>\n" + + " <foofoo name='first'>something</foofoo>\n" + + " <foofoo name='second'>something else</foofoo>\n" + + "</foo>"; + + getMockWebConnection().setResponse(URL_SECOND, xml, "text/xml"); + loadPageWithAlerts2(html); + } + + /** + * @throws Exception if the test fails + */ + @Test + @Browsers(IE) + @Alerts({ "#comment", "foo" }) + public void firstChild_comment_activeX() throws Exception { + final String html = "<html><head><title>foo</title><script>\n" + + " function test() {\n" + + " var doc = " + XMLDocumentTest.callLoadXMLDocumentFromFile("'" + URL_SECOND + "'") + ";\n" + + " alert(doc.firstChild.nodeName);\n" + + " alert(doc.documentElement.nodeName);\n" + + " }\n" + + XMLDocumentTest.LOAD_ACTIVEX_XML_DOCUMENT_FROM_FILE_FUNCTION + + "</script></head><body onload='test()'>\n" + + "</body></html>"; + + final String xml = + "<!--comment-->\n" + + "<foo>\n" + + " <foofoo name='first'>something</foofoo>\n" + + " <foofoo name='second'>something else</foofoo>\n" + + "</foo>"; + + getMockWebConnection().setResponse(URL_SECOND, xml, "text/xml"); + loadPageWithAlerts2(html); + } } |