diff options
author | Tor Norbye <tnorbye@google.com> | 2012-01-05 15:47:09 -0800 |
---|---|---|
committer | Tor Norbye <tnorbye@google.com> | 2012-01-05 21:25:47 -0800 |
commit | 581ca2e75fd638f6e95570ac54ff9e6b7b7cde84 (patch) | |
tree | 130b1ad476c0230db414fdd03cfe4efaac7dadbc /common/tests/src | |
parent | c000db8d112f4143249fa79a24c6ddb2ffc122f3 (diff) | |
download | sdk-581ca2e75fd638f6e95570ac54ff9e6b7b7cde84.zip sdk-581ca2e75fd638f6e95570ac54ff9e6b7b7cde84.tar.gz sdk-581ca2e75fd638f6e95570ac54ff9e6b7b7cde84.tar.bz2 |
Extract positional XML parser into common and fix encoding issues
The XML DOM parser used by the lint CLI driver (which tracks
positions) is needed outside of lint, so pull it out of the lint/cli
project, and refactor it such that it does not directly reference the
lint Position APIs (but can utilize them when subclassed in lint).
In addition, handle non-UTF-8 file encodings. XML files can be encoded
in other character sets, and can specify this via the encoding
attribute in the XML prologue. Until now, the CLI lint runner would
just read the XML file contents in using the default encoding and
parse this. Now there's a new utility method which takes a byte[] and
infers the desired encoding and uses that to convert the byte[] into a
string using the correct encoding. (We can't just pass an InputStream
and let the SAX parser handle this on its own because the XML parser
needs to access the character stream in order to assign correct node
offsets.) This code now also handles the byte order mark more
cleanly.
There are some new unit tests too to check the new encoding, BOM and
offset handling.
Change-Id: Ib0badbbe72172e3408c6d5af2413be51280a7724
Diffstat (limited to 'common/tests/src')
-rw-r--r-- | common/tests/src/com/android/util/PositionXmlParserTest.java | 243 |
1 files changed, 243 insertions, 0 deletions
diff --git a/common/tests/src/com/android/util/PositionXmlParserTest.java b/common/tests/src/com/android/util/PositionXmlParserTest.java new file mode 100644 index 0000000..9f87252 --- /dev/null +++ b/common/tests/src/com/android/util/PositionXmlParserTest.java @@ -0,0 +1,243 @@ +/* + * Copyright (C) 2011 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 com.android.util; + +import com.android.util.PositionXmlParser.Position; + +import org.w3c.dom.Attr; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; + +import java.io.BufferedOutputStream; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.FileWriter; +import java.io.OutputStreamWriter; +import java.io.Writer; + +import junit.framework.TestCase; + +@SuppressWarnings("javadoc") +public class PositionXmlParserTest extends TestCase { + public void test() throws Exception { + String xml = + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" + + "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" + + " android:layout_width=\"match_parent\"\n" + + " android:layout_height=\"wrap_content\"\n" + + " android:orientation=\"vertical\" >\n" + + "\n" + + " <Button\n" + + " android:id=\"@+id/button1\"\n" + + " android:layout_width=\"wrap_content\"\n" + + " android:layout_height=\"wrap_content\"\n" + + " android:text=\"Button\" />\n" + + "\n" + + " <Button\n" + + " android:id=\"@+id/button2\"\n" + + " android:layout_width=\"wrap_content\"\n" + + " android:layout_height=\"wrap_content\"\n" + + " android:text=\"Button\" />\n" + + "\n" + + "</LinearLayout>\n"; + PositionXmlParser parser = new PositionXmlParser(); + File file = File.createTempFile("parsertest", ".xml"); + Writer fw = new BufferedWriter(new FileWriter(file)); + fw.write(xml); + fw.close(); + Document document = parser.parse(new FileInputStream(file)); + assertNotNull(document); + + // Basic parsing heart beat tests + Element linearLayout = (Element) document.getElementsByTagName("LinearLayout").item(0); + assertNotNull(linearLayout); + NodeList buttons = document.getElementsByTagName("Button"); + assertEquals(2, buttons.getLength()); + final String ANDROID_URI = "http://schemas.android.com/apk/res/android"; + assertEquals("wrap_content", + linearLayout.getAttributeNS(ANDROID_URI, "layout_height")); + + // Check attribute positions + Attr attr = linearLayout.getAttributeNodeNS(ANDROID_URI, "layout_width"); + assertNotNull(attr); + Position start = parser.getPosition(attr); + Position end = start.getEnd(); + assertEquals(2, start.getLine()); + assertEquals(xml.indexOf("android:layout_width"), start.getOffset()); + assertEquals(2, end.getLine()); + String target = "android:layout_width=\"match_parent\""; + assertEquals(xml.indexOf(target) + target.length(), end.getOffset()); + + // Check element positions + Element button = (Element) buttons.item(0); + start = parser.getPosition(button); + end = start.getEnd(); + assertNull(end.getEnd()); + assertEquals(6, start.getLine()); + assertEquals(xml.indexOf("<Button"), start.getOffset()); + assertEquals(xml.indexOf("/>") + 2, end.getOffset()); + assertEquals(10, end.getLine()); + int button1End = end.getOffset(); + + Element button2 = (Element) buttons.item(1); + start = parser.getPosition(button2); + end = start.getEnd(); + assertEquals(12, start.getLine()); + assertEquals(xml.indexOf("<Button", button1End), start.getOffset()); + assertEquals(xml.indexOf("/>", start.getOffset()) + 2, end.getOffset()); + assertEquals(16, end.getLine()); + + file.delete(); + } + + public void testLineEndings() throws Exception { + // Test for http://code.google.com/p/android/issues/detail?id=22925 + String xml = + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n" + + "<LinearLayout>\r\n" + + "\r" + + "<LinearLayout></LinearLayout>\r\n" + + "</LinearLayout>\r\n"; + PositionXmlParser parser = new PositionXmlParser(); + File file = File.createTempFile("parsertest2", ".xml"); + Writer fw = new BufferedWriter(new FileWriter(file)); + fw.write(xml); + fw.close(); + Document document = parser.parse(new FileInputStream(file)); + assertNotNull(document); + + file.delete(); + } + + private static void checkEncoding(String encoding, boolean writeBom, boolean writeEncoding) + throws Exception { + String value = "¾¿Œ"; + StringBuilder sb = new StringBuilder(); + + sb.append("<?xml version=\"1.0\""); + if (writeEncoding) { + sb.append(" encoding=\""); + sb.append(encoding); + sb.append("\""); + } + sb.append("?>\n" + + "<!-- This is a \n" + + " multiline comment\n" + + "-->\n" + + "<foo "); + int startAttrOffset = sb.length(); + sb.append("attr=\""); + sb.append(value); + sb.append("\""); + sb.append(">\n" + + "\n" + + "<bar></bar>\n" + + "</foo>\n"); + PositionXmlParser parser = new PositionXmlParser(); + File file = File.createTempFile("parsertest" + encoding + writeBom + writeEncoding, + ".xml"); + BufferedOutputStream stream = new BufferedOutputStream(new FileOutputStream(file)); + OutputStreamWriter writer = new OutputStreamWriter(stream, encoding); + + if (writeBom) { + String normalized = encoding.toLowerCase().replace("-", "_"); + if (normalized.equals("utf_8")) { + stream.write(0xef); + stream.write(0xbb); + stream.write(0xbf); + } else if (normalized.equals("utf_16")) { + stream.write(0xfe); + stream.write(0xff); + } else if (normalized.equals("utf_16le")) { + stream.write(0xff); + stream.write(0xfe); + } else if (normalized.equals("utf_32")) { + stream.write(0x0); + stream.write(0x0); + stream.write(0xfe); + stream.write(0xff); + } else if (normalized.equals("utf_32le")) { + stream.write(0xff); + stream.write(0xfe); + stream.write(0x0); + stream.write(0x0); + } else { + fail("Can't write BOM for encoding " + encoding); + } + } + + writer.write(sb.toString()); + writer.close(); + + Document document = parser.parse(new FileInputStream(file)); + assertNotNull(document); + Element root = document.getDocumentElement(); + assertEquals(file.getPath(), value, root.getAttribute("attr")); + assertEquals(4, parser.getPosition(root).getLine()); + + Attr attribute = root.getAttributeNode("attr"); + assertNotNull(attribute); + Position position = parser.getPosition(attribute); + assertNotNull(position); + assertEquals(4, position.getLine()); + assertEquals(startAttrOffset, position.getOffset()); + + file.delete(); + } + + public void testEncoding() throws Exception { + checkEncoding("utf-8", false /*bom*/, true /*encoding*/); + checkEncoding("UTF-8", false /*bom*/, true /*encoding*/); + checkEncoding("UTF_16", false /*bom*/, true /*encoding*/); + checkEncoding("UTF-16", false /*bom*/, true /*encoding*/); + checkEncoding("UTF_16LE", false /*bom*/, true /*encoding*/); + checkEncoding("UTF_32", false /*bom*/, true /*encoding*/); + checkEncoding("UTF_32LE", false /*bom*/, true /*encoding*/); + checkEncoding("windows-1252", false /*bom*/, true /*encoding*/); + checkEncoding("MacRoman", false /*bom*/, true /*encoding*/); + checkEncoding("ISO-8859-1", false /*bom*/, true /*encoding*/); + checkEncoding("iso-8859-1", false /*bom*/, true /*encoding*/); + + // Try BOM's (with no encoding specified) + checkEncoding("utf-8", true /*bom*/, false /*encoding*/); + checkEncoding("UTF-8", true /*bom*/, false /*encoding*/); + checkEncoding("UTF_16", true /*bom*/, false /*encoding*/); + checkEncoding("UTF-16", true /*bom*/, false /*encoding*/); + checkEncoding("UTF_16LE", true /*bom*/, false /*encoding*/); + checkEncoding("UTF_32", true /*bom*/, false /*encoding*/); + checkEncoding("UTF_32LE", true /*bom*/, false /*encoding*/); + + // Try default encodings (only defined for utf-8 and utf-16) + checkEncoding("utf-8", false /*bom*/, false /*encoding*/); + checkEncoding("UTF-8", false /*bom*/, false /*encoding*/); + checkEncoding("UTF_16", false /*bom*/, false /*encoding*/); + checkEncoding("UTF-16", false /*bom*/, false /*encoding*/); + checkEncoding("UTF_16LE", false /*bom*/, false /*encoding*/); + + // Try BOM's (with explicit encoding specified) + checkEncoding("utf-8", true /*bom*/, true /*encoding*/); + checkEncoding("UTF-8", true /*bom*/, true /*encoding*/); + checkEncoding("UTF_16", true /*bom*/, true /*encoding*/); + checkEncoding("UTF-16", true /*bom*/, true /*encoding*/); + checkEncoding("UTF_16LE", true /*bom*/, true /*encoding*/); + checkEncoding("UTF_32", true /*bom*/, true /*encoding*/); + checkEncoding("UTF_32LE", true /*bom*/, true /*encoding*/); + } +} |