--- /dev/null
+/*
+ * 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.
+ }
+ };
+ }
+}
--- /dev/null
+/*
+ * 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[^?]*\\?>", "");
+ }
+}