blob: 5d2bb0be9a51a2e8221c241eaf6953c8fd79a0fd [file] [log] [blame]
/*
* Copyright (C) 2010 The Android Open Source Project
*
* 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 tests.xml;
import dalvik.annotation.KnownFailure;
import junit.framework.TestCase;
import org.w3c.dom.CDATASection;
import org.w3c.dom.Comment;
import org.w3c.dom.DOMConfiguration;
import org.w3c.dom.DOMError;
import org.w3c.dom.DOMErrorHandler;
import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.ProcessingInstruction;
import org.w3c.dom.Text;
import org.xml.sax.InputSource;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* Tests the acceptance of various parameters on the DOM configuration. This
* test assumes the same set of parameters as the RI version 1.5. Perfectly
* correct DOM implementations may fail this test because it assumes certain
* parameters will be unsupported.
*/
public class NormalizeTest extends TestCase {
private Document document;
private DOMConfiguration domConfiguration;
String[] infosetImpliesFalse = {
"validate-if-schema", "entities", "datatype-normalization", "cdata-sections" };
String[] infosetImpliesTrue = { "namespace-declarations", "well-formed",
"element-content-whitespace", "comments", "namespaces" };
@Override protected void setUp() throws Exception {
document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
domConfiguration = document.getDomConfig();
}
public void testCanonicalForm() {
assertEquals(false, domConfiguration.getParameter("canonical-form"));
assertSupported("canonical-form", false);
assertUnsupported("canonical-form", true);
}
public void testCdataSections() {
assertEquals(true, domConfiguration.getParameter("cdata-sections"));
assertSupported("cdata-sections", false);
assertSupported("cdata-sections", true);
}
public void testCheckCharacterNormalization() {
assertEquals(false, domConfiguration.getParameter("check-character-normalization"));
assertSupported("check-character-normalization", false);
assertUnsupported("check-character-normalization", true);
}
public void testComments() {
assertEquals(true, domConfiguration.getParameter("comments"));
assertSupported("comments", false);
assertSupported("comments", true);
}
public void testDatatypeNormalization() {
assertEquals(false, domConfiguration.getParameter("datatype-normalization"));
assertSupported("datatype-normalization", false);
assertSupported("datatype-normalization", true);
// setting this parameter to true should set validate to true...
domConfiguration.setParameter("validate", false);
domConfiguration.setParameter("datatype-normalization", true);
assertEquals(true, domConfiguration.getParameter("validate"));
// ...but the negative case isn't so
domConfiguration.setParameter("datatype-normalization", false);
assertEquals(true, domConfiguration.getParameter("validate"));
}
public void testElementContentWhitespace() {
assertEquals(true, domConfiguration.getParameter("element-content-whitespace"));
assertUnsupported("element-content-whitespace", false);
assertSupported("element-content-whitespace", true);
}
public void testEntities() {
assertEquals(true, domConfiguration.getParameter("entities"));
assertSupported("entities", false);
assertSupported("entities", true);
}
public void testErrorHandler() {
assertEquals(null, domConfiguration.getParameter("error-handler"));
assertSupported("error-handler", null);
assertSupported("error-handler", new DOMErrorHandler() {
public boolean handleError(DOMError error) {
return true;
}
});
}
public void testInfoset() {
assertEquals(false, domConfiguration.getParameter("infoset"));
assertSupported("infoset", false);
assertSupported("infoset", true);
}
public void testSettingInfosetUpdatesImplied() {
// first clear those other parameters
for (String name : infosetImpliesFalse) {
if (domConfiguration.canSetParameter(name, true)) {
domConfiguration.setParameter(name, true);
}
}
for (String name : infosetImpliesTrue) {
if (domConfiguration.canSetParameter(name, false)) {
domConfiguration.setParameter(name, false);
}
}
// set infoset
domConfiguration.setParameter("infoset", true);
// now the parameters should all match what infoset implies
for (String name : infosetImpliesFalse) {
assertEquals(false, domConfiguration.getParameter(name));
}
for (String name : infosetImpliesTrue) {
assertEquals(true, domConfiguration.getParameter(name));
}
}
public void testSettingImpliedUpdatesInfoset() {
for (String name : infosetImpliesFalse) {
domConfiguration.setParameter("infoset", true);
if (domConfiguration.canSetParameter(name, true)) {
domConfiguration.setParameter(name, true);
assertEquals(false, domConfiguration.getParameter("infoset"));
}
}
for (String name : infosetImpliesTrue) {
domConfiguration.setParameter("infoset", true);
if (domConfiguration.canSetParameter(name, false)) {
domConfiguration.setParameter(name, false);
assertEquals(false, domConfiguration.getParameter("infoset"));
}
}
}
public void testNamespaces() {
assertEquals(true, domConfiguration.getParameter("namespaces"));
assertSupported("namespaces", false);
assertSupported("namespaces", true);
}
public void testNamespaceDeclarations() {
assertEquals(true, domConfiguration.getParameter("namespace-declarations"));
assertUnsupported("namespace-declarations", false); // supported in RI 6
assertSupported("namespace-declarations", true);
}
public void testNormalizeCharacters() {
assertEquals(false, domConfiguration.getParameter("normalize-characters"));
assertSupported("normalize-characters", false);
assertUnsupported("normalize-characters", true);
}
public void testSchemaLocation() {
assertEquals(null, domConfiguration.getParameter("schema-location"));
assertSupported("schema-location", "http://foo");
assertSupported("schema-location", null);
}
/**
* This fails under the RI because setParameter() succeeds even though
* canSetParameter() returns false.
*/
@KnownFailure("Dalvik doesn't honor the schema-type parameter")
public void testSchemaTypeDtd() {
assertUnsupported("schema-type", "http://www.w3.org/TR/REC-xml"); // supported in RI v6
}
public void testSchemaTypeXmlSchema() {
assertEquals(null, domConfiguration.getParameter("schema-type"));
assertSupported("schema-type", null);
assertSupported("schema-type", "http://www.w3.org/2001/XMLSchema");
}
public void testSplitCdataSections() {
assertEquals(true, domConfiguration.getParameter("split-cdata-sections"));
assertSupported("split-cdata-sections", false);
assertSupported("split-cdata-sections", true);
}
public void testValidate() {
assertEquals(false, domConfiguration.getParameter("validate"));
assertSupported("validate", false);
assertSupported("validate", true);
}
public void testValidateIfSchema() {
assertEquals(false, domConfiguration.getParameter("validate-if-schema"));
assertSupported("validate-if-schema", false);
assertUnsupported("validate-if-schema", true);
}
public void testWellFormed() {
assertEquals(true, domConfiguration.getParameter("well-formed"));
assertSupported("well-formed", false);
assertSupported("well-formed", true);
}
public void testMissingParameter() {
assertFalse(domConfiguration.canSetParameter("foo", true));
try {
domConfiguration.getParameter("foo");
fail();
} catch (DOMException e) {
}
try {
domConfiguration.setParameter("foo", true);
fail();
} catch (DOMException e) {
}
}
public void testNullKey() {
try {
domConfiguration.canSetParameter(null, true);
fail();
} catch (NullPointerException e) {
}
try {
domConfiguration.getParameter(null);
fail();
} catch (NullPointerException e) {
}
try {
domConfiguration.setParameter(null, true);
fail();
} catch (NullPointerException e) {
}
}
public void testNullValue() {
String message = "This implementation's canSetParameter() disagrees"
+ " with its setParameter()";
try {
domConfiguration.setParameter("well-formed", null);
fail(message);
} catch (DOMException e) {
}
assertEquals(message, false, domConfiguration.canSetParameter("well-formed", null));
}
public void testTypeMismatch() {
assertEquals(false, domConfiguration.canSetParameter("well-formed", "true"));
try {
domConfiguration.setParameter("well-formed", "true");
fail();
} catch (DOMException e) {
}
assertEquals(false, domConfiguration.canSetParameter("well-formed", new Object()));
try {
domConfiguration.setParameter("well-formed", new Object());
fail();
} catch (DOMException e) {
}
}
private void assertUnsupported(String name, Object value) {
String message = "This implementation's setParameter() supports an unexpected value: "
+ name + "=" + value;
assertFalse(message, domConfiguration.canSetParameter(name, value));
try {
domConfiguration.setParameter(name, value);
fail(message);
} catch (DOMException e) {
assertEquals(DOMException.NOT_SUPPORTED_ERR, e.code);
}
try {
domConfiguration.setParameter(name.toUpperCase(), value);
fail(message);
} catch (DOMException e) {
assertEquals(DOMException.NOT_SUPPORTED_ERR, e.code);
}
assertFalse(value.equals(domConfiguration.getParameter(name)));
}
private void assertSupported(String name, Object value) {
String message = "This implementation's canSetParameter() disagrees"
+ " with its setParameter() for " + name + "=" + value;
try {
domConfiguration.setParameter(name, value);
} catch (DOMException e) {
if (domConfiguration.canSetParameter(name, value)) {
fail(message);
} else {
fail("This implementation's setParameter() doesn't support: "
+ name + "=" + value);
}
}
assertTrue(message, domConfiguration.canSetParameter(name.toUpperCase(), value));
assertTrue(message, domConfiguration.canSetParameter(name, value));
assertEquals(value, domConfiguration.getParameter(name));
domConfiguration.setParameter(name.toUpperCase(), value);
assertEquals(value, domConfiguration.getParameter(name.toUpperCase()));
}
public void testCdataSectionsNotHonoredByNodeNormalize() throws Exception {
String xml = "<foo>ABC<![CDATA[DEF]]>GHI</foo>";
parse(xml);
domConfiguration.setParameter("cdata-sections", true);
document.getDocumentElement().normalize();
assertEquals(xml, domToString(document));
parse(xml);
domConfiguration.setParameter("cdata-sections", false);
document.getDocumentElement().normalize();
assertEquals(xml, domToString(document));
}
public void testCdataSectionsHonoredByDocumentNormalize() throws Exception {
String xml = "<foo>ABC<![CDATA[DEF]]>GHI</foo>";
parse(xml);
domConfiguration.setParameter("cdata-sections", true);
document.normalizeDocument();
assertEquals(xml, domToString(document));
parse(xml);
domConfiguration.setParameter("cdata-sections", false);
document.normalizeDocument();
String expected = xml.replace("<![CDATA[DEF]]>", "DEF");
assertEquals(expected, domToString(document));
}
public void testMergeAdjacentTextNodes() throws Exception {
document = createDocumentWithAdjacentTexts("abc", "def");
document.getDocumentElement().normalize();
assertChildren(document.getDocumentElement(), "abcdef");
}
public void testMergeAdjacentEmptyTextNodes() throws Exception {
document = createDocumentWithAdjacentTexts("", "", "");
document.getDocumentElement().normalize();
assertChildren(document.getDocumentElement());
}
public void testMergeAdjacentNodesWithNonTextSiblings() throws Exception {
document = createDocumentWithAdjacentTexts("abc", "def", "<br>", "ghi", "jkl");
document.getDocumentElement().normalize();
assertChildren(document.getDocumentElement(), "abcdef", "<br>", "ghijkl");
}
public void testMergeAdjacentNodesEliminatesEmptyTexts() throws Exception {
document = createDocumentWithAdjacentTexts("", "", "<br>", "", "", "<br>", "", "<br>", "");
document.getDocumentElement().normalize();
assertChildren(document.getDocumentElement(), "<br>", "<br>", "<br>");
}
public void testRetainingComments() throws Exception {
String xml = "<foo>ABC<!-- bar -->DEF<!-- baz -->GHI</foo>";
parse(xml);
domConfiguration.setParameter("comments", true);
document.normalizeDocument();
assertEquals(xml, domToString(document));
}
public void testCommentContainingDoubleDash() throws Exception {
ErrorRecorder errorRecorder = new ErrorRecorder();
domConfiguration.setParameter("error-handler", errorRecorder);
domConfiguration.setParameter("namespaces", false);
Element root = document.createElement("foo");
document.appendChild(root);
root.appendChild(document.createComment("ABC -- DEF"));
document.normalizeDocument();
errorRecorder.assertAllErrors(DOMError.SEVERITY_ERROR, "wf-invalid-character");
}
public void testStrippingComments() throws Exception {
String xml = "<foo>ABC<!-- bar -->DEF<!-- baz -->GHI</foo>";
parse(xml);
domConfiguration.setParameter("comments", false);
document.normalizeDocument();
assertChildren(document.getDocumentElement(), "ABCDEFGHI");
}
public void testSplittingCdataSectionsSplit() throws Exception {
ErrorRecorder errorRecorder = new ErrorRecorder();
domConfiguration.setParameter("split-cdata-sections", true);
domConfiguration.setParameter("error-handler", errorRecorder);
domConfiguration.setParameter("namespaces", false);
Element root = document.createElement("foo");
document.appendChild(root);
root.appendChild(document.createCDATASection("ABC]]>DEF]]>GHI"));
document.normalizeDocument();
errorRecorder.assertAllErrors(DOMError.SEVERITY_WARNING, "cdata-sections-splitted");
assertChildren(root, "<![CDATA[ABC]]]]>", "<![CDATA[>DEF]]]]>", "<![CDATA[>GHI]]>");
}
public void testSplittingCdataSectionsReportError() throws Exception {
ErrorRecorder errorRecorder = new ErrorRecorder();
domConfiguration.setParameter("split-cdata-sections", false);
domConfiguration.setParameter("error-handler", errorRecorder);
domConfiguration.setParameter("namespaces", false);
Element root = document.createElement("foo");
document.appendChild(root);
root.appendChild(document.createCDATASection("ABC]]>DEF"));
document.normalizeDocument();
errorRecorder.assertAllErrors(DOMError.SEVERITY_ERROR, "wf-invalid-character");
}
public void testInvalidCharactersCdata() throws Exception {
ErrorRecorder errorRecorder = new ErrorRecorder();
domConfiguration.setParameter("cdata-sections", true);
domConfiguration.setParameter("error-handler", errorRecorder);
domConfiguration.setParameter("namespaces", false);
Element root = document.createElement("foo");
document.appendChild(root);
CDATASection cdata = document.createCDATASection("");
root.appendChild(cdata);
for (int c = 0; c <= Character.MAX_VALUE; c++) {
cdata.setData(new String(new char[]{ 'A', 'B', (char) c }));
document.normalizeDocument();
if (isValid((char) c)) {
assertEquals(Collections.<DOMError>emptyList(), errorRecorder.errors);
} else {
errorRecorder.assertAllErrors("For character " + c,
DOMError.SEVERITY_ERROR, "wf-invalid-character");
}
}
}
public void testInvalidCharactersText() throws Exception {
ErrorRecorder errorRecorder = new ErrorRecorder();
domConfiguration.setParameter("error-handler", errorRecorder);
domConfiguration.setParameter("namespaces", false);
Element root = document.createElement("foo");
document.appendChild(root);
Text text = document.createTextNode("");
root.appendChild(text);
for (int c = 0; c <= Character.MAX_VALUE; c++) {
text.setData(new String(new char[]{ 'A', 'B', (char) c }));
document.normalizeDocument();
if (isValid((char) c)) {
assertEquals(Collections.<DOMError>emptyList(), errorRecorder.errors);
} else {
errorRecorder.assertAllErrors("For character " + c,
DOMError.SEVERITY_ERROR, "wf-invalid-character");
}
}
}
public void testInvalidCharactersAttribute() throws Exception {
ErrorRecorder errorRecorder = new ErrorRecorder();
domConfiguration.setParameter("error-handler", errorRecorder);
domConfiguration.setParameter("namespaces", false);
Element root = document.createElement("foo");
document.appendChild(root);
for (int c = 0; c <= Character.MAX_VALUE; c++) {
root.setAttribute("bar", new String(new char[] { 'A', 'B', (char) c}));
document.normalizeDocument();
if (isValid((char) c)) {
assertEquals(Collections.<DOMError>emptyList(), errorRecorder.errors);
} else {
errorRecorder.assertAllErrors("For character " + c,
DOMError.SEVERITY_ERROR, "wf-invalid-character");
}
}
}
public void testInvalidCharactersComment() throws Exception {
ErrorRecorder errorRecorder = new ErrorRecorder();
domConfiguration.setParameter("error-handler", errorRecorder);
domConfiguration.setParameter("namespaces", false);
Element root = document.createElement("foo");
document.appendChild(root);
Comment comment = document.createComment("");
root.appendChild(comment);
for (int c = 0; c <= Character.MAX_VALUE; c++) {
comment.setData(new String(new char[] { 'A', 'B', (char) c}));
document.normalizeDocument();
if (isValid((char) c)) {
assertEquals(Collections.<DOMError>emptyList(), errorRecorder.errors);
} else {
errorRecorder.assertAllErrors("For character " + c,
DOMError.SEVERITY_ERROR, "wf-invalid-character");
}
}
}
public void testInvalidCharactersProcessingInstructionData() throws Exception {
ErrorRecorder errorRecorder = new ErrorRecorder();
domConfiguration.setParameter("error-handler", errorRecorder);
domConfiguration.setParameter("namespaces", false);
Element root = document.createElement("foo");
document.appendChild(root);
ProcessingInstruction pi = document.createProcessingInstruction("foo", "");
root.appendChild(pi);
for (int c = 0; c <= Character.MAX_VALUE; c++) {
pi.setData(new String(new char[] { 'A', 'B', (char) c}));
document.normalizeDocument();
if (isValid((char) c)) {
assertEquals(Collections.<DOMError>emptyList(), errorRecorder.errors);
} else {
errorRecorder.assertAllErrors("For character " + c,
DOMError.SEVERITY_ERROR, "wf-invalid-character");
}
}
}
// TODO: test for surrogates
private boolean isValid(char c) {
// as defined by http://www.w3.org/TR/REC-xml/#charsets.
return c == 0x9 || c == 0xA || c == 0xD || (c >= 0x20 && c <= 0xd7ff)
|| (c >= 0xe000 && c <= 0xfffd);
}
private Document createDocumentWithAdjacentTexts(String... texts) throws Exception {
Document result = DocumentBuilderFactory.newInstance()
.newDocumentBuilder().newDocument();
Element root = result.createElement("foo");
result.appendChild(root);
for (String text : texts) {
if (text.equals("<br>")) {
root.appendChild(result.createElement("br"));
} else {
root.appendChild(result.createTextNode(text));
}
}
return result;
}
private void assertChildren(Element element, String... texts) {
List<String> actual = new ArrayList<String>();
NodeList nodes = element.getChildNodes();
for (int i = 0; i < nodes.getLength(); i++) {
Node node = nodes.item(i);
if (node.getNodeType() == Node.TEXT_NODE) {
actual.add(((Text) node).getData());
} else if (node.getNodeType() == Node.CDATA_SECTION_NODE) {
actual.add("<![CDATA[" + ((CDATASection) node).getData() + "]]>");
} else {
actual.add("<" + node.getNodeName() + ">");
}
}
assertEquals(Arrays.asList(texts), actual);
}
private void parse(String xml) throws Exception {
document = DocumentBuilderFactory.newInstance().newDocumentBuilder()
.parse(new InputSource(new StringReader(xml)));
domConfiguration = document.getDomConfig();
}
private String domToString(Document document) throws TransformerException {
StringWriter writer = new StringWriter();
Transformer transformer = TransformerFactory.newInstance() .newTransformer();
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
transformer.transform(new DOMSource(document), new StreamResult(writer));
return writer.toString();
}
private class ErrorRecorder implements DOMErrorHandler {
private final List<DOMError> errors = new ArrayList<DOMError>();
public boolean handleError(DOMError error) {
errors.add(error);
return true;
}
public void assertAllErrors(int severity, String type) {
assertAllErrors("Expected one or more " + type + " errors", severity, type);
}
public void assertAllErrors(String message, int severity, String type) {
assertFalse(message, errors.isEmpty());
for (DOMError error : errors) {
assertEquals(message, severity, error.getSeverity());
assertEquals(message, type, error.getType());
}
errors.clear();
}
}
}