diff options
author | Jesse Wilson <jessewilson@google.com> | 2010-03-01 10:26:05 -0800 |
---|---|---|
committer | Android Git Automerger <android-git-automerger@android.com> | 2010-03-01 10:26:05 -0800 |
commit | e2ef3a27af881d400d26c374e51201e3224705b3 (patch) | |
tree | 983722d1d173e824fcdec20ec2f2be534bfad555 /xml/src | |
parent | 2cf62473cac4347ffa293cb5f0dea4326b777509 (diff) | |
parent | f06779ef253298411e2151a990d6f14b2cb42ce3 (diff) | |
download | libcore-e2ef3a27af881d400d26c374e51201e3224705b3.zip libcore-e2ef3a27af881d400d26c374e51201e3224705b3.tar.gz libcore-e2ef3a27af881d400d26c374e51201e3224705b3.tar.bz2 |
am ad71aa20: Merge "New implementation for DOMConfiguration."
Merge commit 'ad71aa2046502db784a8c861b30b11ddf88044f1' into dalvik-dev
* commit 'ad71aa2046502db784a8c861b30b11ddf88044f1':
New implementation for DOMConfiguration.
Diffstat (limited to 'xml/src')
5 files changed, 808 insertions, 21 deletions
diff --git a/xml/src/main/java/org/apache/harmony/xml/dom/DOMConfigurationImpl.java b/xml/src/main/java/org/apache/harmony/xml/dom/DOMConfigurationImpl.java new file mode 100644 index 0000000..2f57a4c --- /dev/null +++ b/xml/src/main/java/org/apache/harmony/xml/dom/DOMConfigurationImpl.java @@ -0,0 +1,371 @@ +/* + * 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 org.apache.harmony.xml.dom; + +import org.w3c.dom.DOMConfiguration; +import org.w3c.dom.DOMErrorHandler; +import org.w3c.dom.DOMException; +import org.w3c.dom.DOMStringList; + +import java.util.Map; +import java.util.TreeMap; + +/** + * A minimal implementation of DOMConfiguration. This implementation uses inner + * parameter instances to centralize each parameter's behaviour. + */ +public final class DOMConfigurationImpl implements DOMConfiguration { + + private static final Map<String, Parameter> PARAMETERS + = new TreeMap<String, Parameter>(String.CASE_INSENSITIVE_ORDER); + + static { + /* + * True to canonicalize the document (unsupported). This includes + * removing DocumentType nodes from the tree and removing unused + * namespace declarations. Setting this to true also sets these + * parameters: + * entities = false + * normalize-characters = false + * cdata-sections = false + * namespaces = true + * namespace-declarations = true + * well-formed = true + * element-content-whitespace = true + * Setting these parameters to another value shall revert the canonical + * form to false. + */ + PARAMETERS.put("canonical-form", new FixedParameter(false)); + + /* + * True to keep existing CDATA nodes; false to replace them/merge them + * into adjacent text nodes. + */ + PARAMETERS.put("cdata-sections", new BooleanParameter() { + public Object get(DOMConfigurationImpl config) { + return config.cdataSections; + } + public void set(DOMConfigurationImpl config, Object value) { + config.cdataSections = (Boolean) value; + } + }); + + /* + * True to check character normalization (unsupported). + */ + PARAMETERS.put("check-character-normalization", new FixedParameter(false)); + + /* + * True to keep comments in the document; false to discard them. + */ + PARAMETERS.put("comments", new BooleanParameter() { + public Object get(DOMConfigurationImpl config) { + return config.comments; + } + public void set(DOMConfigurationImpl config, Object value) { + config.comments = (Boolean) value; + } + }); + + /* + * True to expose schema normalized values. Setting this to true sets + * the validate parameter to true. Has no effect when validate is false. + */ + PARAMETERS.put("datatype-normalization", new BooleanParameter() { + public Object get(DOMConfigurationImpl config) { + return config.datatypeNormalization; + } + public void set(DOMConfigurationImpl config, Object value) { + if ((Boolean) value) { + config.datatypeNormalization = true; + config.validate = true; + } else { + config.datatypeNormalization = false; + } + } + }); + + /* + * True to keep whitespace elements in the document; false to discard + * them (unsupported). + */ + PARAMETERS.put("element-content-whitespace", new FixedParameter(true)); + + /* + * True to keep entity references in the document; false to expand them. + */ + PARAMETERS.put("entities", new BooleanParameter() { + public Object get(DOMConfigurationImpl config) { + return config.entities; + } + public void set(DOMConfigurationImpl config, Object value) { + config.entities = (Boolean) value; + } + }); + + /* + * Handler to be invoked when errors are encountered. + */ + PARAMETERS.put("error-handler", new Parameter() { + public Object get(DOMConfigurationImpl config) { + return config.errorHandler; + } + public void set(DOMConfigurationImpl config, Object value) { + config.errorHandler = (DOMErrorHandler) value; + } + public boolean canSet(DOMConfigurationImpl config, Object value) { + return value == null || value instanceof DOMErrorHandler; + } + }); + + /* + * Bulk alias to set the following parameter values: + * validate-if-schema = false + * entities = false + * datatype-normalization = false + * cdata-sections = false + * namespace-declarations = true + * well-formed = true + * element-content-whitespace = true + * comments = true + * namespaces = true. + * Querying this returns true if all of the above parameters have the + * listed values; false otherwise. + */ + PARAMETERS.put("infoset", new BooleanParameter() { + public Object get(DOMConfigurationImpl config) { + // validate-if-schema is always false + // element-content-whitespace is always true + // namespace-declarations is always true + return !config.entities + && !config.datatypeNormalization + && !config.cdataSections + && config.wellFormed + && config.comments + && config.namespaces; + } + public void set(DOMConfigurationImpl config, Object value) { + if ((Boolean) value) { + // validate-if-schema is always false + // element-content-whitespace is always true + // namespace-declarations is always true + config.entities = false; + config.datatypeNormalization = false; + config.cdataSections = false; + config.wellFormed = true; + config.comments = true; + config.namespaces = true; + } + } + }); + + /* + * True to perform namespace processing; false for none. + */ + PARAMETERS.put("namespaces", new BooleanParameter() { + public Object get(DOMConfigurationImpl config) { + return config.namespaces; + } + public void set(DOMConfigurationImpl config, Object value) { + config.namespaces = (Boolean) value; + } + }); + + /** + * True to include namespace declarations; false to discard them + * (unsupported). Even when namespace declarations are discarded, + * prefixes are retained. + * + * Has no effect if namespaces is false. + */ + PARAMETERS.put("namespace-declarations", new FixedParameter(true)); + + /* + * True to fully normalize characters (unsupported). + */ + PARAMETERS.put("normalize-characters", new FixedParameter(false)); + + /* + * A list of whitespace-separated URIs representing the schemas to validate + * against. Has no effect if schema-type is null. + */ + PARAMETERS.put("schema-location", new Parameter() { + public Object get(DOMConfigurationImpl config) { + return config.schemaLocation; + } + public void set(DOMConfigurationImpl config, Object value) { + config.schemaLocation = (String) value; + } + public boolean canSet(DOMConfigurationImpl config, Object value) { + return value == null || value instanceof String; + } + }); + + /* + * URI representing the type of schema language, such as + * "http://www.w3.org/2001/XMLSchema" or "http://www.w3.org/TR/REC-xml". + */ + PARAMETERS.put("schema-type", new Parameter() { + public Object get(DOMConfigurationImpl config) { + return config.schemaType; + } + public void set(DOMConfigurationImpl config, Object value) { + config.schemaType = (String) value; + } + public boolean canSet(DOMConfigurationImpl config, Object value) { + return value == null || value instanceof String; + } + }); + + /* + * True to split CDATA sections containing "]]>"; false to signal an + * error instead. + */ + PARAMETERS.put("split-cdata-sections", new BooleanParameter() { + public Object get(DOMConfigurationImpl config) { + return config.splitCdataSections; + } + public void set(DOMConfigurationImpl config, Object value) { + config.splitCdataSections = (Boolean) value; + } + }); + + /* + * True to require validation against a schema or DTD. Validation will + * recompute element content whitespace, ID and schema type data. + * + * Setting this unsets validate-if-schema. + */ + PARAMETERS.put("validate", new BooleanParameter() { + public Object get(DOMConfigurationImpl config) { + return config.validate; + } + public void set(DOMConfigurationImpl config, Object value) { + // validate-if-schema is always false + config.validate = (Boolean) value; + } + }); + + /* + * True to validate if a schema was declared (unsupported). Setting this + * unsets validate. + */ + PARAMETERS.put("validate-if-schema", new FixedParameter(false)); + + /* + * True to report invalid characters in node names, attributes, elements, + * comments, text, CDATA sections and processing instructions. + */ + PARAMETERS.put("well-formed", new BooleanParameter() { + public Object get(DOMConfigurationImpl config) { + return config.wellFormed; + } + public void set(DOMConfigurationImpl config, Object value) { + config.wellFormed = (Boolean) value; + } + }); + + // TODO add "resource-resolver" property for use with LS feature... + } + + private boolean cdataSections = true; + private boolean comments = true; + private boolean datatypeNormalization = false; + private boolean entities = true; + private DOMErrorHandler errorHandler; + private boolean namespaces = true; + private String schemaLocation; + private String schemaType; + private boolean splitCdataSections = true; + private boolean validate = false; + private boolean wellFormed = true; + + interface Parameter { + Object get(DOMConfigurationImpl config); + void set(DOMConfigurationImpl config, Object value); + boolean canSet(DOMConfigurationImpl config, Object value); + } + + static class FixedParameter implements Parameter { + final Object onlyValue; + FixedParameter(Object onlyValue) { + this.onlyValue = onlyValue; + } + public Object get(DOMConfigurationImpl config) { + return onlyValue; + } + public void set(DOMConfigurationImpl config, Object value) { + if (!onlyValue.equals(value)) { + throw new DOMException(DOMException.NOT_SUPPORTED_ERR, + "Unsupported value: " + value); + } + } + public boolean canSet(DOMConfigurationImpl config, Object value) { + return onlyValue.equals(value); + } + } + + static abstract class BooleanParameter implements Parameter { + public boolean canSet(DOMConfigurationImpl config, Object value) { + return value instanceof Boolean; + } + } + + public boolean canSetParameter(String name, Object value) { + Parameter parameter = PARAMETERS.get(name); + return parameter != null && parameter.canSet(this, value); + } + + public void setParameter(String name, Object value) throws DOMException { + Parameter parameter = PARAMETERS.get(name); + if (parameter == null) { + throw new DOMException(DOMException.NOT_FOUND_ERR, "No such parameter: " + name); + } + try { + parameter.set(this, value); + } catch (NullPointerException e) { + throw new DOMException(DOMException.TYPE_MISMATCH_ERR, + "Null not allowed for " + name); + } catch (ClassCastException e) { + throw new DOMException(DOMException.TYPE_MISMATCH_ERR, + "Invalid type for " + name + ": " + value.getClass()); + } + } + + public Object getParameter(String name) throws DOMException { + Parameter parameter = PARAMETERS.get(name); + if (parameter == null) { + throw new DOMException(DOMException.NOT_FOUND_ERR, "No such parameter: " + name); + } + return parameter.get(this); + } + + public DOMStringList getParameterNames() { + final String[] result = PARAMETERS.keySet().toArray(new String[PARAMETERS.size()]); + return new DOMStringList() { + public String item(int index) { + return index < result.length ? result[index] : null; + } + public int getLength() { + return result.length; + } + public boolean contains(String str) { + return PARAMETERS.containsKey(str); // case-insensitive. + } + }; + } +} diff --git a/xml/src/main/java/org/apache/harmony/xml/dom/DocumentImpl.java b/xml/src/main/java/org/apache/harmony/xml/dom/DocumentImpl.java index e297280..035e1bb 100644 --- a/xml/src/main/java/org/apache/harmony/xml/dom/DocumentImpl.java +++ b/xml/src/main/java/org/apache/harmony/xml/dom/DocumentImpl.java @@ -47,6 +47,7 @@ import org.w3c.dom.Text; public class DocumentImpl extends InnerNodeImpl implements Document { private DOMImplementation domImplementation; + private DOMConfiguration domConfiguration; /* * The default values of these fields are specified by the Document @@ -361,7 +362,10 @@ public class DocumentImpl extends InnerNodeImpl implements Document { } public DOMConfiguration getDomConfig() { - throw new UnsupportedOperationException(); // TODO + if (domConfiguration == null) { + domConfiguration = new DOMConfigurationImpl(); + } + return domConfiguration; } public void normalizeDocument() { diff --git a/xml/src/main/java/org/apache/harmony/xml/dom/InnerNodeImpl.java b/xml/src/main/java/org/apache/harmony/xml/dom/InnerNodeImpl.java index 275bbf3..9cee352 100644 --- a/xml/src/main/java/org/apache/harmony/xml/dom/InnerNodeImpl.java +++ b/xml/src/main/java/org/apache/harmony/xml/dom/InnerNodeImpl.java @@ -148,28 +148,35 @@ public abstract class InnerNodeImpl extends LeafNodeImpl { return false; } + /** + * Normalize the text nodes within this subtree. Although named similarly, + * this method is unrelated to Document.normalize. + */ @Override - public void normalize() { - Node nextNode = null; - + public final void normalize() { + Text next = null; // null if next doesn't exist or is not a TEXT_NODE for (int i = children.size() - 1; i >= 0; i--) { - Node thisNode = children.get(i); - - thisNode.normalize(); - - if (thisNode.getNodeType() == Node.TEXT_NODE) { - if (nextNode != null && nextNode.getNodeType() == Node.TEXT_NODE) { - ((Text)thisNode).setData(thisNode.getNodeValue() + nextNode.getNodeValue()); - removeChild(nextNode); - } - - if ("".equals(thisNode.getNodeValue())) { - removeChild(thisNode); - nextNode = null; - } else { - nextNode = thisNode; - } + Node node = children.get(i); + node.normalize(); + + if (node.getNodeType() != Node.TEXT_NODE) { + next = null; + continue; } + + Text text = (Text) node; + + if (text.getLength() == 0) { + removeChild(text); + continue; + } + + if (next != null) { + text.appendData(next.getData()); + removeChild(next); + } + + next = text; } } diff --git a/xml/src/test/java/tests/xml/AllTests.java b/xml/src/test/java/tests/xml/AllTests.java index aba4a13..9be812d 100644 --- a/xml/src/test/java/tests/xml/AllTests.java +++ b/xml/src/test/java/tests/xml/AllTests.java @@ -29,7 +29,8 @@ public class AllTests { suite.addTestSuite(SimpleParserTest.class); suite.addTestSuite(SimpleBuilderTest.class); suite.addTestSuite(NodeTest.class); - + suite.addTestSuite(NormalizeTest.class); + //suite.addTest(tests.org.w3c.dom.AllTests.suite()); suite.addTest(tests.api.javax.xml.parsers.AllTests.suite()); diff --git a/xml/src/test/java/tests/xml/NormalizeTest.java b/xml/src/test/java/tests/xml/NormalizeTest.java new file mode 100644 index 0000000..b10ea9c --- /dev/null +++ b/xml/src/test/java/tests/xml/NormalizeTest.java @@ -0,0 +1,404 @@ +/* + * 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 junit.framework.TestCase; +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.Text; +import org.xml.sax.InputSource; + +import javax.xml.parsers.DocumentBuilderFactory; +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.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() { + assertSupported("canonical-form", false); + assertUnsupported("canonical-form", true); + } + + public void testCdataSections() { + assertSupported("cdata-sections", false); + assertSupported("cdata-sections", true); + } + + public void testCheckCharacterNormalization() { + assertSupported("check-character-normalization", false); + assertUnsupported("check-character-normalization", true); + } + + public void testComments() { + assertSupported("comments", false); + assertSupported("comments", true); + } + + public void testDatatypeNormalization() { + 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() { + assertUnsupported("element-content-whitespace", false); + assertSupported("element-content-whitespace", true); + } + + public void testEntities() { + assertSupported("entities", false); + assertSupported("entities", true); + } + + public void testErrorHandler() { + assertSupported("error-handler", null); + assertSupported("error-handler", new DOMErrorHandler() { + public boolean handleError(DOMError error) { + return true; + } + }); + } + + public void testInfoset() { + 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() { + assertSupported("namespaces", false); + assertSupported("namespaces", true); + } + + public void testNamespaceDeclarations() { + assertUnsupported("namespace-declarations", false); // supported in RI 6 + assertSupported("namespace-declarations", true); + } + + public void testNormalizeCharacters() { + assertSupported("normalize-characters", false); + assertUnsupported("normalize-characters", true); + } + + public void testSchemaLocation() { + assertSupported("schema-location", "http://foo"); + assertSupported("schema-location", null); + } + + /** + * This fails under the RI because setParameter() succeeds even though + * canSetParameter() returns false. + */ + public void testSchemaTypeDtd() { + assertUnsupported("schema-type", "http://www.w3.org/TR/REC-xml"); // supported in RI v6 + } + + public void testSchemaTypeXmlSchema() { + assertSupported("schema-type", null); + assertSupported("schema-type", "http://www.w3.org/2001/XMLSchema"); + } + + public void testSplitCdataSections() { + assertSupported("split-cdata-sections", false); + assertSupported("split-cdata-sections", true); + } + + public void testValidate() { + assertSupported("validate", false); + assertSupported("validate", true); + } + + public void testValidateIfSchema() { + assertSupported("validate-if-schema", false); + assertUnsupported("validate-if-schema", true); + } + + public void testWellFormed() { + 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>"; + document = DocumentBuilderFactory.newInstance().newDocumentBuilder() + .parse(new InputSource(new StringReader(xml))); + document.getDomConfig().setParameter("cdata-sections", true); + document.getDocumentElement().normalize(); + assertEquals(xml, domToString(document)); + + document = DocumentBuilderFactory.newInstance().newDocumentBuilder() + .parse(new InputSource(new StringReader(xml))); + document.getDomConfig().setParameter("cdata-sections", false); + document.getDocumentElement().normalize(); + assertEquals(xml, domToString(document)); + } + + public void testCdataSectionsHonoredByDocumentNormalize() throws Exception { + String xml = "<foo>ABC<![CDATA[DEF]]>GHI</foo>"; + document = DocumentBuilderFactory.newInstance().newDocumentBuilder() + .parse(new InputSource(new StringReader(xml))); + document.getDomConfig().setParameter("cdata-sections", true); + document.normalizeDocument(); + assertEquals(xml, domToString(document)); + + document = DocumentBuilderFactory.newInstance().newDocumentBuilder() + .parse(new InputSource(new StringReader(xml))); + document.getDomConfig().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>"); + } + + 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); + actual.add(node.getNodeType() == Node.TEXT_NODE + ? ((Text) node).getData() + : "<" + node.getNodeName() + ">"); + } + assertEquals(Arrays.asList(texts), actual); + } + + private String domToString(Document document) throws TransformerException { + StringWriter writer = new StringWriter(); + TransformerFactory.newInstance().newTransformer() + .transform(new DOMSource(document), new StreamResult(writer)); + String xml = writer.toString(); + return xml.replaceFirst("<\\?xml[^?]*\\?>", ""); + } +} |