1 /* 2 * Copyright (C) 2011 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 com.android.util; 18 19 import com.android.util.PositionXmlParser.Position; 20 21 import org.w3c.dom.Attr; 22 import org.w3c.dom.Document; 23 import org.w3c.dom.Element; 24 import org.w3c.dom.NodeList; 25 import org.w3c.dom.Text; 26 27 import java.io.BufferedOutputStream; 28 import java.io.BufferedWriter; 29 import java.io.File; 30 import java.io.FileInputStream; 31 import java.io.FileOutputStream; 32 import java.io.FileWriter; 33 import java.io.OutputStreamWriter; 34 import java.io.Writer; 35 36 import junit.framework.TestCase; 37 38 @SuppressWarnings("javadoc") 39 public class PositionXmlParserTest extends TestCase { 40 public void test() throws Exception { 41 String xml = 42 "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" + 43 "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" + 44 " android:layout_width=\"match_parent\"\n" + 45 " android:layout_height=\"wrap_content\"\n" + 46 " android:orientation=\"vertical\" >\n" + 47 "\n" + 48 " <Button\n" + 49 " android:id=\"@+id/button1\"\n" + 50 " android:layout_width=\"wrap_content\"\n" + 51 " android:layout_height=\"wrap_content\"\n" + 52 " android:text=\"Button\" />\n" + 53 "\n" + 54 " <Button\n" + 55 " android:id=\"@+id/button2\"\n" + 56 " android:layout_width=\"wrap_content\"\n" + 57 " android:layout_height=\"wrap_content\"\n" + 58 " android:text=\"Button\" />\n" + 59 "\n" + 60 "</LinearLayout>\n"; 61 PositionXmlParser parser = new PositionXmlParser(); 62 File file = File.createTempFile("parsertest", ".xml"); 63 Writer fw = new BufferedWriter(new FileWriter(file)); 64 fw.write(xml); 65 fw.close(); 66 Document document = parser.parse(new FileInputStream(file)); 67 assertNotNull(document); 68 69 // Basic parsing heart beat tests 70 Element linearLayout = (Element) document.getElementsByTagName("LinearLayout").item(0); 71 assertNotNull(linearLayout); 72 NodeList buttons = document.getElementsByTagName("Button"); 73 assertEquals(2, buttons.getLength()); 74 final String ANDROID_URI = "http://schemas.android.com/apk/res/android"; 75 assertEquals("wrap_content", 76 linearLayout.getAttributeNS(ANDROID_URI, "layout_height")); 77 78 // Check attribute positions 79 Attr attr = linearLayout.getAttributeNodeNS(ANDROID_URI, "layout_width"); 80 assertNotNull(attr); 81 Position start = parser.getPosition(attr); 82 Position end = start.getEnd(); 83 assertEquals(2, start.getLine()); 84 assertEquals(4, start.getColumn()); 85 assertEquals(xml.indexOf("android:layout_width"), start.getOffset()); 86 assertEquals(2, end.getLine()); 87 String target = "android:layout_width=\"match_parent\""; 88 assertEquals(xml.indexOf(target) + target.length(), end.getOffset()); 89 90 // Check element positions 91 Element button = (Element) buttons.item(0); 92 start = parser.getPosition(button); 93 end = start.getEnd(); 94 assertNull(end.getEnd()); 95 assertEquals(6, start.getLine()); 96 assertEquals(4, start.getColumn()); 97 assertEquals(xml.indexOf("<Button"), start.getOffset()); 98 assertEquals(xml.indexOf("/>") + 2, end.getOffset()); 99 assertEquals(10, end.getLine()); 100 int button1End = end.getOffset(); 101 102 Element button2 = (Element) buttons.item(1); 103 start = parser.getPosition(button2); 104 end = start.getEnd(); 105 assertEquals(12, start.getLine()); 106 assertEquals(4, start.getColumn()); 107 assertEquals(xml.indexOf("<Button", button1End), start.getOffset()); 108 assertEquals(xml.indexOf("/>", start.getOffset()) + 2, end.getOffset()); 109 assertEquals(16, end.getLine()); 110 111 file.delete(); 112 } 113 114 public void testText() throws Exception { 115 String xml = 116 "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" + 117 "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" + 118 " android:layout_width=\"match_parent\"\n" + 119 " android:layout_height=\"wrap_content\"\n" + 120 " android:orientation=\"vertical\" >\n" + 121 "\n" + 122 " <Button\n" + 123 " android:id=\"@+id/button1\"\n" + 124 " android:layout_width=\"wrap_content\"\n" + 125 " android:layout_height=\"wrap_content\"\n" + 126 " android:text=\"Button\" />\n" + 127 " some text\n" + 128 "\n" + 129 "</LinearLayout>\n"; 130 PositionXmlParser parser = new PositionXmlParser(); 131 File file = File.createTempFile("parsertest", ".xml"); 132 file.deleteOnExit(); 133 Writer fw = new BufferedWriter(new FileWriter(file)); 134 fw.write(xml); 135 fw.close(); 136 Document document = parser.parse(new FileInputStream(file)); 137 assertNotNull(document); 138 139 // Basic parsing heart beat tests 140 Element linearLayout = (Element) document.getElementsByTagName("LinearLayout").item(0); 141 assertNotNull(linearLayout); 142 NodeList buttons = document.getElementsByTagName("Button"); 143 assertEquals(1, buttons.getLength()); 144 final String ANDROID_URI = "http://schemas.android.com/apk/res/android"; 145 assertEquals("wrap_content", 146 linearLayout.getAttributeNS(ANDROID_URI, "layout_height")); 147 148 // Check text positions 149 Element button = (Element) buttons.item(0); 150 Text text = (Text) button.getNextSibling(); 151 assertNotNull(text); 152 153 // Check attribute positions 154 Position start = parser.getPosition(text); 155 assertEquals(11, start.getLine()); 156 assertEquals(10, start.getColumn()); 157 assertEquals(xml.indexOf("some text"), start.getOffset()); 158 } 159 160 public void testLineEndings() throws Exception { 161 // Test for http://code.google.com/p/android/issues/detail?id=22925 162 String xml = 163 "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n" + 164 "<LinearLayout>\r\n" + 165 "\r" + 166 "<LinearLayout></LinearLayout>\r\n" + 167 "</LinearLayout>\r\n"; 168 PositionXmlParser parser = new PositionXmlParser(); 169 File file = File.createTempFile("parsertest2", ".xml"); 170 Writer fw = new BufferedWriter(new FileWriter(file)); 171 fw.write(xml); 172 fw.close(); 173 Document document = parser.parse(new FileInputStream(file)); 174 assertNotNull(document); 175 176 file.delete(); 177 } 178 179 private static void checkEncoding(String encoding, boolean writeBom, boolean writeEncoding, 180 String lineEnding) 181 throws Exception { 182 // Norwegian extra vowel characters such as "latin small letter a with ring above" 183 String value = "\u00e6\u00d8\u00e5"; 184 StringBuilder sb = new StringBuilder(); 185 186 sb.append("<?xml version=\"1.0\""); 187 if (writeEncoding) { 188 sb.append(" encoding=\""); 189 sb.append(encoding); 190 sb.append("\""); 191 } 192 sb.append("?>"); 193 sb.append(lineEnding); 194 sb.append( 195 "<!-- This is a " + lineEnding + 196 " multiline comment" + lineEnding + 197 "-->" + lineEnding + 198 "<foo "); 199 int startAttrOffset = sb.length(); 200 sb.append("attr=\""); 201 sb.append(value); 202 sb.append("\""); 203 sb.append(">" + lineEnding + 204 lineEnding + 205 "<bar></bar>" + lineEnding + 206 "</foo>" + lineEnding); 207 PositionXmlParser parser = new PositionXmlParser(); 208 File file = File.createTempFile("parsertest" + encoding + writeBom + writeEncoding, 209 ".xml"); 210 BufferedOutputStream stream = new BufferedOutputStream(new FileOutputStream(file)); 211 OutputStreamWriter writer = new OutputStreamWriter(stream, encoding); 212 213 if (writeBom) { 214 String normalized = encoding.toLowerCase().replace("-", "_"); 215 if (normalized.equals("utf_8")) { 216 stream.write(0xef); 217 stream.write(0xbb); 218 stream.write(0xbf); 219 } else if (normalized.equals("utf_16")) { 220 stream.write(0xfe); 221 stream.write(0xff); 222 } else if (normalized.equals("utf_16le")) { 223 stream.write(0xff); 224 stream.write(0xfe); 225 } else if (normalized.equals("utf_32")) { 226 stream.write(0x0); 227 stream.write(0x0); 228 stream.write(0xfe); 229 stream.write(0xff); 230 } else if (normalized.equals("utf_32le")) { 231 stream.write(0xff); 232 stream.write(0xfe); 233 stream.write(0x0); 234 stream.write(0x0); 235 } else { 236 fail("Can't write BOM for encoding " + encoding); 237 } 238 } 239 240 writer.write(sb.toString()); 241 writer.close(); 242 243 Document document = parser.parse(new FileInputStream(file)); 244 assertNotNull(document); 245 Element root = document.getDocumentElement(); 246 assertEquals(file.getPath(), value, root.getAttribute("attr")); 247 assertEquals(4, parser.getPosition(root).getLine()); 248 249 Attr attribute = root.getAttributeNode("attr"); 250 assertNotNull(attribute); 251 Position position = parser.getPosition(attribute); 252 assertNotNull(position); 253 assertEquals(4, position.getLine()); 254 assertEquals(startAttrOffset, position.getOffset()); 255 256 file.delete(); 257 } 258 259 public void testEncoding() throws Exception { 260 checkEncoding("utf-8", false /*bom*/, true /*encoding*/, "\n"); 261 checkEncoding("UTF-8", false /*bom*/, true /*encoding*/, "\n"); 262 checkEncoding("UTF_16", false /*bom*/, true /*encoding*/, "\n"); 263 checkEncoding("UTF-16", false /*bom*/, true /*encoding*/, "\n"); 264 checkEncoding("UTF_16LE", false /*bom*/, true /*encoding*/, "\n"); 265 checkEncoding("UTF_32", false /*bom*/, true /*encoding*/, "\n"); 266 checkEncoding("UTF_32LE", false /*bom*/, true /*encoding*/, "\n"); 267 checkEncoding("windows-1252", false /*bom*/, true /*encoding*/, "\n"); 268 checkEncoding("MacRoman", false /*bom*/, true /*encoding*/, "\n"); 269 checkEncoding("ISO-8859-1", false /*bom*/, true /*encoding*/, "\n"); 270 checkEncoding("iso-8859-1", false /*bom*/, true /*encoding*/, "\n"); 271 272 // Try BOM's (with no encoding specified) 273 checkEncoding("utf-8", true /*bom*/, false /*encoding*/, "\n"); 274 checkEncoding("UTF-8", true /*bom*/, false /*encoding*/, "\n"); 275 checkEncoding("UTF_16", true /*bom*/, false /*encoding*/, "\n"); 276 checkEncoding("UTF-16", true /*bom*/, false /*encoding*/, "\n"); 277 checkEncoding("UTF_16LE", true /*bom*/, false /*encoding*/, "\n"); 278 checkEncoding("UTF_32", true /*bom*/, false /*encoding*/, "\n"); 279 checkEncoding("UTF_32LE", true /*bom*/, false /*encoding*/, "\n"); 280 281 // Try default encodings (only defined for utf-8 and utf-16) 282 checkEncoding("utf-8", false /*bom*/, false /*encoding*/, "\n"); 283 checkEncoding("UTF-8", false /*bom*/, false /*encoding*/, "\n"); 284 checkEncoding("UTF_16", false /*bom*/, false /*encoding*/, "\n"); 285 checkEncoding("UTF-16", false /*bom*/, false /*encoding*/, "\n"); 286 checkEncoding("UTF_16LE", false /*bom*/, false /*encoding*/, "\n"); 287 288 // Try BOM's (with explicit encoding specified) 289 checkEncoding("utf-8", true /*bom*/, true /*encoding*/, "\n"); 290 checkEncoding("UTF-8", true /*bom*/, true /*encoding*/, "\n"); 291 checkEncoding("UTF_16", true /*bom*/, true /*encoding*/, "\n"); 292 checkEncoding("UTF-16", true /*bom*/, true /*encoding*/, "\n"); 293 checkEncoding("UTF_16LE", true /*bom*/, true /*encoding*/, "\n"); 294 checkEncoding("UTF_32", true /*bom*/, true /*encoding*/, "\n"); 295 checkEncoding("UTF_32LE", true /*bom*/, true /*encoding*/, "\n"); 296 297 // Make sure this works for \r and \r\n as well 298 checkEncoding("UTF-16", false /*bom*/, true /*encoding*/, "\r"); 299 checkEncoding("UTF_16LE", false /*bom*/, true /*encoding*/, "\r"); 300 checkEncoding("UTF_32", false /*bom*/, true /*encoding*/, "\r"); 301 checkEncoding("UTF_32LE", false /*bom*/, true /*encoding*/, "\r"); 302 checkEncoding("windows-1252", false /*bom*/, true /*encoding*/, "\r"); 303 checkEncoding("MacRoman", false /*bom*/, true /*encoding*/, "\r"); 304 checkEncoding("ISO-8859-1", false /*bom*/, true /*encoding*/, "\r"); 305 checkEncoding("iso-8859-1", false /*bom*/, true /*encoding*/, "\r"); 306 307 checkEncoding("UTF-16", false /*bom*/, true /*encoding*/, "\r\n"); 308 checkEncoding("UTF_16LE", false /*bom*/, true /*encoding*/, "\r\n"); 309 checkEncoding("UTF_32", false /*bom*/, true /*encoding*/, "\r\n"); 310 checkEncoding("UTF_32LE", false /*bom*/, true /*encoding*/, "\r\n"); 311 checkEncoding("windows-1252", false /*bom*/, true /*encoding*/, "\r\n"); 312 checkEncoding("MacRoman", false /*bom*/, true /*encoding*/, "\r\n"); 313 checkEncoding("ISO-8859-1", false /*bom*/, true /*encoding*/, "\r\n"); 314 checkEncoding("iso-8859-1", false /*bom*/, true /*encoding*/, "\r\n"); 315 } 316 } 317