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