1/*
2 * Copyright (C) 2009 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
17package libcore.xml;
18
19import java.io.ByteArrayOutputStream;
20import java.io.IOException;
21import java.io.StringWriter;
22import junit.framework.TestCase;
23import org.kxml2.io.KXmlSerializer;
24import org.w3c.dom.Document;
25import org.w3c.dom.Node;
26import org.w3c.dom.NodeList;
27import org.w3c.dom.Text;
28import org.xmlpull.v1.XmlSerializer;
29import static tests.support.Support_Xml.domOf;
30
31public final class KxmlSerializerTest extends TestCase {
32    private static final String NAMESPACE = null;
33
34    public void testWhitespaceInAttributeValue() throws Exception {
35        StringWriter stringWriter = new StringWriter();
36        XmlSerializer serializer = new KXmlSerializer();
37        serializer.setOutput(stringWriter);
38        serializer.startDocument("UTF-8", null);
39        serializer.startTag(NAMESPACE, "a");
40        serializer.attribute(NAMESPACE, "cr", "\r");
41        serializer.attribute(NAMESPACE, "lf", "\n");
42        serializer.attribute(NAMESPACE, "tab", "\t");
43        serializer.attribute(NAMESPACE, "space", " ");
44        serializer.endTag(NAMESPACE, "a");
45        serializer.endDocument();
46        assertXmlEquals("<a cr=\"&#13;\" lf=\"&#10;\" tab=\"&#9;\" space=\" \" />",
47                stringWriter.toString());
48    }
49
50    public void testWriteDocument() throws Exception {
51        StringWriter stringWriter = new StringWriter();
52        XmlSerializer serializer = new KXmlSerializer();
53        serializer.setOutput(stringWriter);
54        serializer.startDocument("UTF-8", null);
55        serializer.startTag(NAMESPACE, "foo");
56        serializer.attribute(NAMESPACE, "quux", "abc");
57        serializer.startTag(NAMESPACE, "bar");
58        serializer.endTag(NAMESPACE, "bar");
59        serializer.startTag(NAMESPACE, "baz");
60        serializer.endTag(NAMESPACE, "baz");
61        serializer.endTag(NAMESPACE, "foo");
62        serializer.endDocument();
63        assertXmlEquals("<foo quux=\"abc\"><bar /><baz /></foo>", stringWriter.toString());
64    }
65
66    // http://code.google.com/p/android/issues/detail?id=21250
67    public void testWriteSpecialCharactersInText() throws Exception {
68        StringWriter stringWriter = new StringWriter();
69        XmlSerializer serializer = new KXmlSerializer();
70        serializer.setOutput(stringWriter);
71        serializer.startDocument("UTF-8", null);
72        serializer.startTag(NAMESPACE, "foo");
73        serializer.text("5'8\", 5 < 6 & 7 > 3!");
74        serializer.endTag(NAMESPACE, "foo");
75        serializer.endDocument();
76        assertXmlEquals("<foo>5'8\", 5 &lt; 6 &amp; 7 &gt; 3!</foo>", stringWriter.toString());
77    }
78
79    private void assertXmlEquals(String expectedXml, String actualXml) throws Exception {
80        String declaration = "<?xml version='1.0' encoding='UTF-8' ?>";
81        assertEquals(declaration + expectedXml, actualXml);
82    }
83
84    private static XmlSerializer newSerializer() throws IOException {
85        ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
86        XmlSerializer serializer = new KXmlSerializer();
87        serializer.setOutput(bytesOut, "UTF-8");
88        serializer.startDocument("UTF-8", null);
89        return serializer;
90    }
91
92    public String fromCodePoint(int codePoint) {
93        if (codePoint > Character.MAX_VALUE) {
94            return new String(Character.toChars(codePoint));
95        }
96        return Character.toString((char) codePoint);
97    }
98
99    // http://b/17960630
100    public void testSpeakNoEvilMonkeys() throws Exception {
101        StringWriter stringWriter = new StringWriter();
102        XmlSerializer serializer = new KXmlSerializer();
103        serializer.setOutput(stringWriter);
104        serializer.startDocument("UTF-8", null);
105        serializer.startTag(NAMESPACE, "tag");
106        serializer.attribute(NAMESPACE, "attr", "a\ud83d\ude4ab");
107        serializer.text("c\ud83d\ude4ad");
108        serializer.cdsect("e\ud83d\ude4af");
109        serializer.endTag(NAMESPACE, "tag");
110        serializer.endDocument();
111        assertXmlEquals("<tag attr=\"a&#128586;b\">" +
112                        "c&#128586;d" +
113                        "<![CDATA[e]]>&#128586;<![CDATA[f]]>" +
114                        "</tag>", stringWriter.toString());
115
116        // Check we can parse what we just output.
117        Document doc = domOf(stringWriter.toString());
118        Node root = doc.getDocumentElement();
119        assertEquals("a\ud83d\ude4ab", root.getAttributes().getNamedItem("attr").getNodeValue());
120        Text text = (Text) root.getFirstChild();
121        assertEquals("c\ud83d\ude4ade\ud83d\ude4af", text.getNodeValue());
122    }
123
124    public void testBadSurrogates() throws Exception {
125        StringWriter stringWriter = new StringWriter();
126        XmlSerializer serializer = new KXmlSerializer();
127        serializer.setOutput(stringWriter);
128        serializer.startDocument("UTF-8", null);
129        serializer.startTag(NAMESPACE, "tag");
130        try {
131            serializer.attribute(NAMESPACE, "attr", "a\ud83d\u0040b");
132        } catch (IllegalArgumentException expected) {
133        }
134        try {
135            serializer.text("c\ud83d\u0040d");
136        } catch (IllegalArgumentException expected) {
137        }
138        try {
139            serializer.cdsect("e\ud83d\u0040f");
140        } catch (IllegalArgumentException expected) {
141        }
142    }
143
144    // Cover all the BMP code points plus a few that require us to use surrogates.
145    private static int MAX_TEST_CODE_POINT = 0x10008;
146
147    public void testInvalidCharactersInText() throws IOException {
148        XmlSerializer serializer = newSerializer();
149        serializer.startTag(NAMESPACE, "root");
150        for (int c = 0; c <= MAX_TEST_CODE_POINT; ++c) {
151            final String s = fromCodePoint(c);
152            if (isValidXmlCodePoint(c)) {
153                serializer.text("a" + s + "b");
154            } else {
155                try {
156                    serializer.text("a" + s + "b");
157                    fail(s);
158                } catch (IllegalArgumentException expected) {
159                }
160            }
161        }
162        serializer.endTag(NAMESPACE, "root");
163    }
164
165    public void testInvalidCharactersInAttributeValues() throws IOException {
166        XmlSerializer serializer = newSerializer();
167        serializer.startTag(NAMESPACE, "root");
168        for (int c = 0; c <= MAX_TEST_CODE_POINT; ++c) {
169            final String s = fromCodePoint(c);
170            if (isValidXmlCodePoint(c)) {
171                serializer.attribute(NAMESPACE, "a", "a" + s + "b");
172            } else {
173                try {
174                    serializer.attribute(NAMESPACE, "a", "a" + s + "b");
175                    fail(s);
176                } catch (IllegalArgumentException expected) {
177                }
178            }
179        }
180        serializer.endTag(NAMESPACE, "root");
181    }
182
183    public void testInvalidCharactersInCdataSections() throws IOException {
184        XmlSerializer serializer = newSerializer();
185        serializer.startTag(NAMESPACE, "root");
186        for (int c = 0; c <= MAX_TEST_CODE_POINT; ++c) {
187            final String s = fromCodePoint(c);
188            if (isValidXmlCodePoint(c)) {
189                serializer.cdsect("a" + s + "b");
190            } else {
191                try {
192                    serializer.cdsect("a" + s + "b");
193                    fail(s);
194                } catch (IllegalArgumentException expected) {
195                }
196            }
197        }
198        serializer.endTag(NAMESPACE, "root");
199    }
200
201    public void testCdataWithTerminatorInside() throws Exception {
202        StringWriter writer = new StringWriter();
203        XmlSerializer serializer = new KXmlSerializer();
204        serializer.setOutput(writer);
205        serializer.startDocument("UTF-8", null);
206        serializer.startTag(NAMESPACE, "p");
207        serializer.cdsect("a]]>b");
208        serializer.endTag(NAMESPACE, "p");
209        serializer.endDocument();
210        // Adjacent CDATA sections aren't merged, so let's stick them together ourselves...
211        Document doc = domOf(writer.toString());
212        NodeList children = doc.getFirstChild().getChildNodes();
213        String text = "";
214        for (int i = 0; i < children.getLength(); ++i) {
215            text += children.item(i).getNodeValue();
216        }
217        assertEquals("a]]>b", text);
218    }
219
220    private static boolean isValidXmlCodePoint(int c) {
221        // http://www.w3.org/TR/REC-xml/#charsets
222        return (c >= 0x20 && c <= 0xd7ff) || (c == 0x9) || (c == 0xa) || (c == 0xd) ||
223                (c >= 0xe000 && c <= 0xfffd) || (c >= 0x10000 && c <= 0x10ffff);
224    }
225}
226