Home | History | Annotate | Download | only in xml
      1 /*
      2  * Copyright (C) 2010 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package tests.xml;
     18 
     19 import dalvik.annotation.KnownFailure;
     20 import junit.framework.TestCase;
     21 import org.w3c.dom.CDATASection;
     22 import org.w3c.dom.Comment;
     23 import org.w3c.dom.DOMConfiguration;
     24 import org.w3c.dom.DOMError;
     25 import org.w3c.dom.DOMErrorHandler;
     26 import org.w3c.dom.DOMException;
     27 import org.w3c.dom.Document;
     28 import org.w3c.dom.Element;
     29 import org.w3c.dom.Node;
     30 import org.w3c.dom.NodeList;
     31 import org.w3c.dom.ProcessingInstruction;
     32 import org.w3c.dom.Text;
     33 import org.xml.sax.InputSource;
     34 
     35 import javax.xml.parsers.DocumentBuilderFactory;
     36 import javax.xml.transform.OutputKeys;
     37 import javax.xml.transform.Transformer;
     38 import javax.xml.transform.TransformerException;
     39 import javax.xml.transform.TransformerFactory;
     40 import javax.xml.transform.dom.DOMSource;
     41 import javax.xml.transform.stream.StreamResult;
     42 import java.io.StringReader;
     43 import java.io.StringWriter;
     44 import java.util.ArrayList;
     45 import java.util.Arrays;
     46 import java.util.Collections;
     47 import java.util.List;
     48 
     49 /**
     50  * Tests the acceptance of various parameters on the DOM configuration. This
     51  * test assumes the same set of parameters as the RI version 1.5. Perfectly
     52  * correct DOM implementations may fail this test because it assumes certain
     53  * parameters will be unsupported.
     54  */
     55 public class NormalizeTest extends TestCase {
     56 
     57     private Document document;
     58     private DOMConfiguration domConfiguration;
     59 
     60     String[] infosetImpliesFalse = {
     61             "validate-if-schema", "entities", "datatype-normalization", "cdata-sections" };
     62     String[] infosetImpliesTrue = { "namespace-declarations", "well-formed",
     63             "element-content-whitespace", "comments", "namespaces" };
     64 
     65     @Override protected void setUp() throws Exception {
     66         document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
     67         domConfiguration = document.getDomConfig();
     68     }
     69 
     70     public void testCanonicalForm() {
     71         assertEquals(false, domConfiguration.getParameter("canonical-form"));
     72         assertSupported("canonical-form", false);
     73         assertUnsupported("canonical-form", true);
     74     }
     75 
     76     public void testCdataSections() {
     77         assertEquals(true, domConfiguration.getParameter("cdata-sections"));
     78         assertSupported("cdata-sections", false);
     79         assertSupported("cdata-sections", true);
     80     }
     81 
     82     public void testCheckCharacterNormalization() {
     83         assertEquals(false, domConfiguration.getParameter("check-character-normalization"));
     84         assertSupported("check-character-normalization", false);
     85         assertUnsupported("check-character-normalization", true);
     86     }
     87 
     88     public void testComments() {
     89         assertEquals(true, domConfiguration.getParameter("comments"));
     90         assertSupported("comments", false);
     91         assertSupported("comments", true);
     92     }
     93 
     94     public void testDatatypeNormalization() {
     95         assertEquals(false, domConfiguration.getParameter("datatype-normalization"));
     96         assertSupported("datatype-normalization", false);
     97         assertSupported("datatype-normalization", true);
     98 
     99         // setting this parameter to true should set validate to true...
    100         domConfiguration.setParameter("validate", false);
    101         domConfiguration.setParameter("datatype-normalization", true);
    102         assertEquals(true, domConfiguration.getParameter("validate"));
    103 
    104         // ...but the negative case isn't so
    105         domConfiguration.setParameter("datatype-normalization", false);
    106         assertEquals(true, domConfiguration.getParameter("validate"));
    107     }
    108 
    109     public void testElementContentWhitespace() {
    110         assertEquals(true, domConfiguration.getParameter("element-content-whitespace"));
    111         assertUnsupported("element-content-whitespace", false);
    112         assertSupported("element-content-whitespace", true);
    113     }
    114 
    115     public void testEntities() {
    116         assertEquals(true, domConfiguration.getParameter("entities"));
    117         assertSupported("entities", false);
    118         assertSupported("entities", true);
    119     }
    120 
    121     public void testErrorHandler() {
    122         assertEquals(null, domConfiguration.getParameter("error-handler"));
    123         assertSupported("error-handler", null);
    124         assertSupported("error-handler", new DOMErrorHandler() {
    125             public boolean handleError(DOMError error) {
    126                 return true;
    127             }
    128         });
    129     }
    130 
    131     public void testInfoset() {
    132         assertEquals(false, domConfiguration.getParameter("infoset"));
    133         assertSupported("infoset", false);
    134         assertSupported("infoset", true);
    135     }
    136 
    137     public void testSettingInfosetUpdatesImplied() {
    138         // first clear those other parameters
    139         for (String name : infosetImpliesFalse) {
    140             if (domConfiguration.canSetParameter(name, true)) {
    141                 domConfiguration.setParameter(name, true);
    142             }
    143         }
    144         for (String name : infosetImpliesTrue) {
    145             if (domConfiguration.canSetParameter(name, false)) {
    146                 domConfiguration.setParameter(name, false);
    147             }
    148         }
    149 
    150         // set infoset
    151         domConfiguration.setParameter("infoset", true);
    152 
    153         // now the parameters should all match what infoset implies
    154         for (String name : infosetImpliesFalse) {
    155             assertEquals(false, domConfiguration.getParameter(name));
    156         }
    157         for (String name : infosetImpliesTrue) {
    158             assertEquals(true, domConfiguration.getParameter(name));
    159         }
    160     }
    161 
    162     public void testSettingImpliedUpdatesInfoset() {
    163         for (String name : infosetImpliesFalse) {
    164             domConfiguration.setParameter("infoset", true);
    165             if (domConfiguration.canSetParameter(name, true)) {
    166                 domConfiguration.setParameter(name, true);
    167                 assertEquals(false, domConfiguration.getParameter("infoset"));
    168             }
    169         }
    170 
    171         for (String name : infosetImpliesTrue) {
    172             domConfiguration.setParameter("infoset", true);
    173             if (domConfiguration.canSetParameter(name, false)) {
    174                 domConfiguration.setParameter(name, false);
    175                 assertEquals(false, domConfiguration.getParameter("infoset"));
    176             }
    177         }
    178     }
    179 
    180     public void testNamespaces() {
    181         assertEquals(true, domConfiguration.getParameter("namespaces"));
    182         assertSupported("namespaces", false);
    183         assertSupported("namespaces", true);
    184     }
    185 
    186     public void testNamespaceDeclarations() {
    187         assertEquals(true, domConfiguration.getParameter("namespace-declarations"));
    188         assertUnsupported("namespace-declarations", false); // supported in RI 6
    189         assertSupported("namespace-declarations", true);
    190     }
    191 
    192     public void testNormalizeCharacters() {
    193         assertEquals(false, domConfiguration.getParameter("normalize-characters"));
    194         assertSupported("normalize-characters", false);
    195         assertUnsupported("normalize-characters", true);
    196     }
    197 
    198     public void testSchemaLocation() {
    199         assertEquals(null, domConfiguration.getParameter("schema-location"));
    200         assertSupported("schema-location", "http://foo");
    201         assertSupported("schema-location", null);
    202     }
    203 
    204     /**
    205      * This fails under the RI because setParameter() succeeds even though
    206      * canSetParameter() returns false.
    207      */
    208     @KnownFailure("Dalvik doesn't honor the schema-type parameter")
    209     public void testSchemaTypeDtd() {
    210         assertUnsupported("schema-type", "http://www.w3.org/TR/REC-xml"); // supported in RI v6
    211     }
    212 
    213     public void testSchemaTypeXmlSchema() {
    214         assertEquals(null, domConfiguration.getParameter("schema-type"));
    215         assertSupported("schema-type", null);
    216         assertSupported("schema-type", "http://www.w3.org/2001/XMLSchema");
    217     }
    218 
    219     public void testSplitCdataSections() {
    220         assertEquals(true, domConfiguration.getParameter("split-cdata-sections"));
    221         assertSupported("split-cdata-sections", false);
    222         assertSupported("split-cdata-sections", true);
    223     }
    224 
    225     public void testValidate() {
    226         assertEquals(false, domConfiguration.getParameter("validate"));
    227         assertSupported("validate", false);
    228         assertSupported("validate", true);
    229     }
    230 
    231     public void testValidateIfSchema() {
    232         assertEquals(false, domConfiguration.getParameter("validate-if-schema"));
    233         assertSupported("validate-if-schema", false);
    234         assertUnsupported("validate-if-schema", true);
    235     }
    236 
    237     public void testWellFormed() {
    238         assertEquals(true, domConfiguration.getParameter("well-formed"));
    239         assertSupported("well-formed", false);
    240         assertSupported("well-formed", true);
    241     }
    242 
    243     public void testMissingParameter() {
    244         assertFalse(domConfiguration.canSetParameter("foo", true));
    245         try {
    246             domConfiguration.getParameter("foo");
    247             fail();
    248         } catch (DOMException e) {
    249         }
    250         try {
    251             domConfiguration.setParameter("foo", true);
    252             fail();
    253         } catch (DOMException e) {
    254         }
    255     }
    256 
    257     public void testNullKey() {
    258         try {
    259             domConfiguration.canSetParameter(null, true);
    260             fail();
    261         } catch (NullPointerException e) {
    262         }
    263         try {
    264             domConfiguration.getParameter(null);
    265             fail();
    266         } catch (NullPointerException e) {
    267         }
    268         try {
    269             domConfiguration.setParameter(null, true);
    270             fail();
    271         } catch (NullPointerException e) {
    272         }
    273     }
    274 
    275     public void testNullValue() {
    276         String message = "This implementation's canSetParameter() disagrees"
    277                 + " with its setParameter()";
    278         try {
    279             domConfiguration.setParameter("well-formed", null);
    280             fail(message);
    281         } catch (DOMException e) {
    282         }
    283         assertEquals(message, false, domConfiguration.canSetParameter("well-formed", null));
    284     }
    285 
    286     public void testTypeMismatch() {
    287         assertEquals(false, domConfiguration.canSetParameter("well-formed", "true"));
    288         try {
    289             domConfiguration.setParameter("well-formed", "true");
    290             fail();
    291         } catch (DOMException e) {
    292         }
    293 
    294         assertEquals(false, domConfiguration.canSetParameter("well-formed", new Object()));
    295         try {
    296             domConfiguration.setParameter("well-formed", new Object());
    297             fail();
    298         } catch (DOMException e) {
    299         }
    300     }
    301 
    302     private void assertUnsupported(String name, Object value) {
    303         String message = "This implementation's setParameter() supports an unexpected value: "
    304                 + name + "=" + value;
    305         assertFalse(message, domConfiguration.canSetParameter(name, value));
    306         try {
    307             domConfiguration.setParameter(name, value);
    308             fail(message);
    309         } catch (DOMException e) {
    310             assertEquals(DOMException.NOT_SUPPORTED_ERR, e.code);
    311         }
    312         try {
    313             domConfiguration.setParameter(name.toUpperCase(), value);
    314             fail(message);
    315         } catch (DOMException e) {
    316             assertEquals(DOMException.NOT_SUPPORTED_ERR, e.code);
    317         }
    318         assertFalse(value.equals(domConfiguration.getParameter(name)));
    319     }
    320 
    321     private void assertSupported(String name, Object value) {
    322         String message = "This implementation's canSetParameter() disagrees"
    323                 + " with its setParameter() for " + name + "=" + value;
    324         try {
    325             domConfiguration.setParameter(name, value);
    326         } catch (DOMException e) {
    327             if (domConfiguration.canSetParameter(name, value)) {
    328                 fail(message);
    329             } else {
    330                 fail("This implementation's setParameter() doesn't support: "
    331                         + name + "=" + value);
    332             }
    333         }
    334         assertTrue(message, domConfiguration.canSetParameter(name.toUpperCase(), value));
    335         assertTrue(message, domConfiguration.canSetParameter(name, value));
    336         assertEquals(value, domConfiguration.getParameter(name));
    337         domConfiguration.setParameter(name.toUpperCase(), value);
    338         assertEquals(value, domConfiguration.getParameter(name.toUpperCase()));
    339     }
    340 
    341     public void testCdataSectionsNotHonoredByNodeNormalize() throws Exception {
    342         String xml = "<foo>ABC<![CDATA[DEF]]>GHI</foo>";
    343         parse(xml);
    344         domConfiguration.setParameter("cdata-sections", true);
    345         document.getDocumentElement().normalize();
    346         assertEquals(xml, domToString(document));
    347 
    348         parse(xml);
    349         domConfiguration.setParameter("cdata-sections", false);
    350         document.getDocumentElement().normalize();
    351         assertEquals(xml, domToString(document));
    352     }
    353 
    354     public void testCdataSectionsHonoredByDocumentNormalize() throws Exception {
    355         String xml = "<foo>ABC<![CDATA[DEF]]>GHI</foo>";
    356         parse(xml);
    357         domConfiguration.setParameter("cdata-sections", true);
    358         document.normalizeDocument();
    359         assertEquals(xml, domToString(document));
    360 
    361         parse(xml);
    362         domConfiguration.setParameter("cdata-sections", false);
    363         document.normalizeDocument();
    364         String expected = xml.replace("<![CDATA[DEF]]>", "DEF");
    365         assertEquals(expected, domToString(document));
    366     }
    367 
    368     public void testMergeAdjacentTextNodes() throws Exception {
    369         document = createDocumentWithAdjacentTexts("abc", "def");
    370         document.getDocumentElement().normalize();
    371         assertChildren(document.getDocumentElement(), "abcdef");
    372     }
    373 
    374     public void testMergeAdjacentEmptyTextNodes() throws Exception {
    375         document = createDocumentWithAdjacentTexts("", "", "");
    376         document.getDocumentElement().normalize();
    377         assertChildren(document.getDocumentElement());
    378     }
    379 
    380     public void testMergeAdjacentNodesWithNonTextSiblings() throws Exception {
    381         document = createDocumentWithAdjacentTexts("abc", "def", "<br>", "ghi", "jkl");
    382         document.getDocumentElement().normalize();
    383         assertChildren(document.getDocumentElement(), "abcdef", "<br>", "ghijkl");
    384     }
    385 
    386     public void testMergeAdjacentNodesEliminatesEmptyTexts() throws Exception {
    387         document = createDocumentWithAdjacentTexts("", "", "<br>", "", "", "<br>", "", "<br>", "");
    388         document.getDocumentElement().normalize();
    389         assertChildren(document.getDocumentElement(), "<br>", "<br>", "<br>");
    390     }
    391 
    392     public void testRetainingComments() throws Exception {
    393         String xml = "<foo>ABC<!-- bar -->DEF<!-- baz -->GHI</foo>";
    394         parse(xml);
    395         domConfiguration.setParameter("comments", true);
    396         document.normalizeDocument();
    397         assertEquals(xml, domToString(document));
    398     }
    399 
    400     public void testCommentContainingDoubleDash() throws Exception {
    401         ErrorRecorder errorRecorder = new ErrorRecorder();
    402         domConfiguration.setParameter("error-handler", errorRecorder);
    403         domConfiguration.setParameter("namespaces", false);
    404         Element root = document.createElement("foo");
    405         document.appendChild(root);
    406         root.appendChild(document.createComment("ABC -- DEF"));
    407         document.normalizeDocument();
    408         errorRecorder.assertAllErrors(DOMError.SEVERITY_ERROR, "wf-invalid-character");
    409     }
    410 
    411     public void testStrippingComments() throws Exception {
    412         String xml = "<foo>ABC<!-- bar -->DEF<!-- baz -->GHI</foo>";
    413         parse(xml);
    414         domConfiguration.setParameter("comments", false);
    415         document.normalizeDocument();
    416         assertChildren(document.getDocumentElement(), "ABCDEFGHI");
    417     }
    418 
    419     public void testSplittingCdataSectionsSplit() throws Exception {
    420         ErrorRecorder errorRecorder = new ErrorRecorder();
    421         domConfiguration.setParameter("split-cdata-sections", true);
    422         domConfiguration.setParameter("error-handler", errorRecorder);
    423         domConfiguration.setParameter("namespaces", false);
    424         Element root = document.createElement("foo");
    425         document.appendChild(root);
    426         root.appendChild(document.createCDATASection("ABC]]>DEF]]>GHI"));
    427         document.normalizeDocument();
    428         errorRecorder.assertAllErrors(DOMError.SEVERITY_WARNING, "cdata-sections-splitted");
    429         assertChildren(root, "<![CDATA[ABC]]]]>", "<![CDATA[>DEF]]]]>", "<![CDATA[>GHI]]>");
    430     }
    431 
    432     public void testSplittingCdataSectionsReportError() throws Exception {
    433         ErrorRecorder errorRecorder = new ErrorRecorder();
    434         domConfiguration.setParameter("split-cdata-sections", false);
    435         domConfiguration.setParameter("error-handler", errorRecorder);
    436         domConfiguration.setParameter("namespaces", false);
    437         Element root = document.createElement("foo");
    438         document.appendChild(root);
    439         root.appendChild(document.createCDATASection("ABC]]>DEF"));
    440         document.normalizeDocument();
    441         errorRecorder.assertAllErrors(DOMError.SEVERITY_ERROR, "wf-invalid-character");
    442     }
    443 
    444     public void testInvalidCharactersCdata() throws Exception {
    445         ErrorRecorder errorRecorder = new ErrorRecorder();
    446         domConfiguration.setParameter("cdata-sections", true);
    447         domConfiguration.setParameter("error-handler", errorRecorder);
    448         domConfiguration.setParameter("namespaces", false);
    449         Element root = document.createElement("foo");
    450         document.appendChild(root);
    451         CDATASection cdata = document.createCDATASection("");
    452         root.appendChild(cdata);
    453 
    454         for (int c = 0; c <= Character.MAX_VALUE; c++) {
    455             cdata.setData(new String(new char[]{ 'A', 'B', (char) c }));
    456             document.normalizeDocument();
    457             if (isValid((char) c)) {
    458                 assertEquals(Collections.<DOMError>emptyList(), errorRecorder.errors);
    459             } else {
    460                 errorRecorder.assertAllErrors("For character " + c,
    461                         DOMError.SEVERITY_ERROR, "wf-invalid-character");
    462             }
    463         }
    464     }
    465 
    466     public void testInvalidCharactersText() throws Exception {
    467         ErrorRecorder errorRecorder = new ErrorRecorder();
    468         domConfiguration.setParameter("error-handler", errorRecorder);
    469         domConfiguration.setParameter("namespaces", false);
    470         Element root = document.createElement("foo");
    471         document.appendChild(root);
    472         Text text = document.createTextNode("");
    473         root.appendChild(text);
    474 
    475         for (int c = 0; c <= Character.MAX_VALUE; c++) {
    476             text.setData(new String(new char[]{ 'A', 'B', (char) c }));
    477             document.normalizeDocument();
    478             if (isValid((char) c)) {
    479                 assertEquals(Collections.<DOMError>emptyList(), errorRecorder.errors);
    480             } else {
    481                 errorRecorder.assertAllErrors("For character " + c,
    482                         DOMError.SEVERITY_ERROR, "wf-invalid-character");
    483             }
    484         }
    485     }
    486 
    487     public void testInvalidCharactersAttribute() throws Exception {
    488         ErrorRecorder errorRecorder = new ErrorRecorder();
    489         domConfiguration.setParameter("error-handler", errorRecorder);
    490         domConfiguration.setParameter("namespaces", false);
    491         Element root = document.createElement("foo");
    492         document.appendChild(root);
    493 
    494         for (int c = 0; c <= Character.MAX_VALUE; c++) {
    495             root.setAttribute("bar", new String(new char[] { 'A', 'B', (char) c}));
    496             document.normalizeDocument();
    497             if (isValid((char) c)) {
    498                 assertEquals(Collections.<DOMError>emptyList(), errorRecorder.errors);
    499             } else {
    500                 errorRecorder.assertAllErrors("For character " + c,
    501                         DOMError.SEVERITY_ERROR, "wf-invalid-character");
    502             }
    503         }
    504     }
    505 
    506     public void testInvalidCharactersComment() throws Exception {
    507         ErrorRecorder errorRecorder = new ErrorRecorder();
    508         domConfiguration.setParameter("error-handler", errorRecorder);
    509         domConfiguration.setParameter("namespaces", false);
    510         Element root = document.createElement("foo");
    511         document.appendChild(root);
    512         Comment comment = document.createComment("");
    513         root.appendChild(comment);
    514 
    515         for (int c = 0; c <= Character.MAX_VALUE; c++) {
    516             comment.setData(new String(new char[] { 'A', 'B', (char) c}));
    517             document.normalizeDocument();
    518             if (isValid((char) c)) {
    519                 assertEquals(Collections.<DOMError>emptyList(), errorRecorder.errors);
    520             } else {
    521                 errorRecorder.assertAllErrors("For character " + c,
    522                         DOMError.SEVERITY_ERROR, "wf-invalid-character");
    523             }
    524         }
    525     }
    526 
    527     public void testInvalidCharactersProcessingInstructionData() throws Exception {
    528         ErrorRecorder errorRecorder = new ErrorRecorder();
    529         domConfiguration.setParameter("error-handler", errorRecorder);
    530         domConfiguration.setParameter("namespaces", false);
    531         Element root = document.createElement("foo");
    532         document.appendChild(root);
    533         ProcessingInstruction pi = document.createProcessingInstruction("foo", "");
    534         root.appendChild(pi);
    535 
    536         for (int c = 0; c <= Character.MAX_VALUE; c++) {
    537             pi.setData(new String(new char[] { 'A', 'B', (char) c}));
    538             document.normalizeDocument();
    539             if (isValid((char) c)) {
    540                 assertEquals(Collections.<DOMError>emptyList(), errorRecorder.errors);
    541             } else {
    542                 errorRecorder.assertAllErrors("For character " + c,
    543                         DOMError.SEVERITY_ERROR, "wf-invalid-character");
    544             }
    545         }
    546     }
    547 
    548     // TODO: test for surrogates
    549 
    550     private boolean isValid(char c) {
    551         // as defined by http://www.w3.org/TR/REC-xml/#charsets.
    552         return c == 0x9 || c == 0xA || c == 0xD || (c >= 0x20 && c <= 0xd7ff)
    553                 || (c >= 0xe000 && c <= 0xfffd);
    554     }
    555 
    556     private Document createDocumentWithAdjacentTexts(String... texts) throws Exception {
    557         Document result = DocumentBuilderFactory.newInstance()
    558                 .newDocumentBuilder().newDocument();
    559         Element root = result.createElement("foo");
    560         result.appendChild(root);
    561         for (String text : texts) {
    562             if (text.equals("<br>")) {
    563                 root.appendChild(result.createElement("br"));
    564             } else {
    565                 root.appendChild(result.createTextNode(text));
    566             }
    567         }
    568         return result;
    569     }
    570 
    571     private void assertChildren(Element element, String... texts) {
    572         List<String> actual = new ArrayList<String>();
    573         NodeList nodes = element.getChildNodes();
    574         for (int i = 0; i < nodes.getLength(); i++) {
    575             Node node = nodes.item(i);
    576             if (node.getNodeType() == Node.TEXT_NODE) {
    577                 actual.add(((Text) node).getData());
    578             } else if (node.getNodeType() == Node.CDATA_SECTION_NODE) {
    579                 actual.add("<![CDATA[" + ((CDATASection) node).getData() + "]]>");
    580             } else {
    581                 actual.add("<" + node.getNodeName() + ">");
    582             }
    583         }
    584         assertEquals(Arrays.asList(texts), actual);
    585     }
    586 
    587     private void parse(String xml) throws Exception {
    588         document = DocumentBuilderFactory.newInstance().newDocumentBuilder()
    589                 .parse(new InputSource(new StringReader(xml)));
    590         domConfiguration = document.getDomConfig();
    591     }
    592 
    593     private String domToString(Document document) throws TransformerException {
    594         StringWriter writer = new StringWriter();
    595         Transformer transformer = TransformerFactory.newInstance() .newTransformer();
    596         transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
    597         transformer.transform(new DOMSource(document), new StreamResult(writer));
    598         return writer.toString();
    599     }
    600 
    601     private class ErrorRecorder implements DOMErrorHandler {
    602         private final List<DOMError> errors = new ArrayList<DOMError>();
    603 
    604         public boolean handleError(DOMError error) {
    605             errors.add(error);
    606             return true;
    607         }
    608 
    609         public void assertAllErrors(int severity, String type) {
    610             assertAllErrors("Expected one or more " + type + " errors", severity, type);
    611         }
    612 
    613         public void assertAllErrors(String message, int severity, String type) {
    614             assertFalse(message, errors.isEmpty());
    615             for (DOMError error : errors) {
    616                 assertEquals(message, severity, error.getSeverity());
    617                 assertEquals(message, type, error.getType());
    618             }
    619             errors.clear();
    620         }
    621     }
    622 }
    623