blob: 859cf36f508a0a11c19296b19681beff36af871a [file] [log] [blame]
/*
* 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.utils;
import com.android.ide.common.blame.SourcePosition;
import junit.framework.TestCase;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;
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;
@SuppressWarnings({"javadoc", "IOResourceOpenedButNotSafelyClosed"})
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";
File file = File.createTempFile("parsertest", ".xml");
Writer fw = new BufferedWriter(new FileWriter(file));
fw.write(xml);
fw.close();
Document document = PositionXmlParser.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);
SourcePosition position = PositionXmlParser.getPosition(attr);
assertEquals(2, position.getStartLine());
assertEquals(4, position.getStartColumn());
assertEquals(xml.indexOf("android:layout_width"), position.getStartOffset());
assertEquals(2, position.getEndLine());
String target = "android:layout_width=\"match_parent\"";
assertEquals(xml.indexOf(target) + target.length(), position.getEndOffset());
// Check element positions
Element button = (Element) buttons.item(0);
position = PositionXmlParser.getPosition(button);
assertEquals(6, position.getStartLine());
assertEquals(4, position.getStartColumn());
assertEquals(xml.indexOf("<Button"), position.getStartOffset());
assertEquals(xml.indexOf("/>") + 2, position.getEndOffset());
assertEquals(10, position.getEndLine());
int button1End = position.getEndOffset();
Element button2 = (Element) buttons.item(1);
position = PositionXmlParser.getPosition(button2);
assertEquals(12, position.getStartLine());
assertEquals(4, position.getStartColumn());
assertEquals(xml.indexOf("<Button", button1End), position.getStartOffset());
assertEquals(xml.indexOf("/>", position.getStartOffset()) + 2, position.getEndOffset());
assertEquals(16, position.getEndLine());
file.delete();
}
public static void testText() 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" +
" some text\n" +
"\n" +
"</LinearLayout>\n";
File file = File.createTempFile("parsertest", ".xml");
file.deleteOnExit();
Writer fw = new BufferedWriter(new FileWriter(file));
fw.write(xml);
fw.close();
Document document = PositionXmlParser.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(1, buttons.getLength());
final String ANDROID_URI = "http://schemas.android.com/apk/res/android";
assertEquals("wrap_content",
linearLayout.getAttributeNS(ANDROID_URI, "layout_height"));
// Check text positions
Element button = (Element) buttons.item(0);
Text text = (Text) button.getNextSibling();
assertNotNull(text);
// Check attribute positions
SourcePosition start = PositionXmlParser.getPosition(text);
assertEquals(11, start.getStartLine());
assertEquals(10, start.getStartColumn());
assertEquals(xml.indexOf("some text"), start.getStartOffset());
// Check attribute positions with text node offsets
start = PositionXmlParser.getPosition(text, 13, 15);
assertEquals(11, start.getStartLine());
assertEquals(12, start.getStartColumn());
assertEquals(xml.indexOf("me"), start.getStartOffset());
}
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";
File file = File.createTempFile("parsertest2", ".xml");
Writer fw = new BufferedWriter(new FileWriter(file));
fw.write(xml);
fw.close();
Document document = PositionXmlParser.parse(new FileInputStream(file));
assertNotNull(document);
file.delete();
}
private static void checkEncoding(String encoding, boolean writeBom, boolean writeEncoding,
String lineEnding)
throws Exception {
// Norwegian extra vowel characters such as "latin small letter a with ring above"
String value = "\u00e6\u00d8\u00e5";
StringBuilder sb = new StringBuilder();
sb.append("<?xml version=\"1.0\"");
if (writeEncoding) {
sb.append(" encoding=\"");
sb.append(encoding);
sb.append("\"");
}
sb.append("?>");
sb.append(lineEnding);
sb.append(
"<!-- This is a " + lineEnding +
" multiline comment" + lineEnding +
"-->" + lineEnding +
"<foo ");
int startAttrOffset = sb.length();
sb.append("attr=\"");
sb.append(value);
sb.append("\"");
sb.append(">" + lineEnding +
lineEnding +
"<bar></bar>" + lineEnding +
"</foo>" + lineEnding);
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 = PositionXmlParser.parse(new FileInputStream(file));
assertNotNull(document);
Element root = document.getDocumentElement();
assertEquals(file.getPath(), value, root.getAttribute("attr"));
assertEquals(4, PositionXmlParser.getPosition(root).getStartLine());
Attr attribute = root.getAttributeNode("attr");
assertNotNull(attribute);
SourcePosition position = PositionXmlParser.getPosition(attribute);
assertNotNull(position);
assertEquals(4, position.getStartLine());
assertEquals(startAttrOffset, position.getStartOffset());
file.delete();
}
public void testEncoding() throws Exception {
checkEncoding("utf-8", false /*bom*/, true /*encoding*/, "\n");
checkEncoding("UTF-8", false /*bom*/, true /*encoding*/, "\n");
checkEncoding("UTF_16", false /*bom*/, true /*encoding*/, "\n");
checkEncoding("UTF-16", false /*bom*/, true /*encoding*/, "\n");
checkEncoding("UTF_16LE", false /*bom*/, true /*encoding*/, "\n");
checkEncoding("UTF_32", false /*bom*/, true /*encoding*/, "\n");
checkEncoding("UTF_32LE", false /*bom*/, true /*encoding*/, "\n");
checkEncoding("windows-1252", false /*bom*/, true /*encoding*/, "\n");
checkEncoding("MacRoman", false /*bom*/, true /*encoding*/, "\n");
checkEncoding("ISO-8859-1", false /*bom*/, true /*encoding*/, "\n");
checkEncoding("iso-8859-1", false /*bom*/, true /*encoding*/, "\n");
// Try BOM's (with no encoding specified)
checkEncoding("utf-8", true /*bom*/, false /*encoding*/, "\n");
checkEncoding("UTF-8", true /*bom*/, false /*encoding*/, "\n");
checkEncoding("UTF_16", true /*bom*/, false /*encoding*/, "\n");
checkEncoding("UTF-16", true /*bom*/, false /*encoding*/, "\n");
checkEncoding("UTF_16LE", true /*bom*/, false /*encoding*/, "\n");
checkEncoding("UTF_32", true /*bom*/, false /*encoding*/, "\n");
checkEncoding("UTF_32LE", true /*bom*/, false /*encoding*/, "\n");
// Try default encodings (only defined for utf-8 and utf-16)
checkEncoding("utf-8", false /*bom*/, false /*encoding*/, "\n");
checkEncoding("UTF-8", false /*bom*/, false /*encoding*/, "\n");
checkEncoding("UTF_16", false /*bom*/, false /*encoding*/, "\n");
checkEncoding("UTF-16", false /*bom*/, false /*encoding*/, "\n");
checkEncoding("UTF_16LE", false /*bom*/, false /*encoding*/, "\n");
// Try BOM's (with explicit encoding specified)
checkEncoding("utf-8", true /*bom*/, true /*encoding*/, "\n");
checkEncoding("UTF-8", true /*bom*/, true /*encoding*/, "\n");
checkEncoding("UTF_16", true /*bom*/, true /*encoding*/, "\n");
checkEncoding("UTF-16", true /*bom*/, true /*encoding*/, "\n");
checkEncoding("UTF_16LE", true /*bom*/, true /*encoding*/, "\n");
checkEncoding("UTF_32", true /*bom*/, true /*encoding*/, "\n");
checkEncoding("UTF_32LE", true /*bom*/, true /*encoding*/, "\n");
// Make sure this works for \r and \r\n as well
checkEncoding("UTF-16", false /*bom*/, true /*encoding*/, "\r");
checkEncoding("UTF_16LE", false /*bom*/, true /*encoding*/, "\r");
checkEncoding("UTF_32", false /*bom*/, true /*encoding*/, "\r");
checkEncoding("UTF_32LE", false /*bom*/, true /*encoding*/, "\r");
checkEncoding("windows-1252", false /*bom*/, true /*encoding*/, "\r");
checkEncoding("MacRoman", false /*bom*/, true /*encoding*/, "\r");
checkEncoding("ISO-8859-1", false /*bom*/, true /*encoding*/, "\r");
checkEncoding("iso-8859-1", false /*bom*/, true /*encoding*/, "\r");
checkEncoding("UTF-16", false /*bom*/, true /*encoding*/, "\r\n");
checkEncoding("UTF_16LE", false /*bom*/, true /*encoding*/, "\r\n");
checkEncoding("UTF_32", false /*bom*/, true /*encoding*/, "\r\n");
checkEncoding("UTF_32LE", false /*bom*/, true /*encoding*/, "\r\n");
checkEncoding("windows-1252", false /*bom*/, true /*encoding*/, "\r\n");
checkEncoding("MacRoman", false /*bom*/, true /*encoding*/, "\r\n");
checkEncoding("ISO-8859-1", false /*bom*/, true /*encoding*/, "\r\n");
checkEncoding("iso-8859-1", false /*bom*/, true /*encoding*/, "\r\n");
}
public void testOneLineComment() 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" +
" <!-- this is a comment ! -->\n" +
" <Button\n" +
" android:id=\"@+id/button1\"\n" +
" android:layout_width=\"wrap_content\"\n" +
" android:layout_height=\"wrap_content\"\n" +
" android:text=\"Button\" />\n" +
" some text\n" +
"\n" +
"</LinearLayout>\n";
File file = File.createTempFile("parsertest", ".xml");
file.deleteOnExit();
Writer fw = new BufferedWriter(new FileWriter(file));
fw.write(xml);
fw.close();
Document document = PositionXmlParser.parse(new FileInputStream(file));
assertNotNull(document);
// Basic parsing heart beat tests
Element linearLayout = (Element) document.getElementsByTagName("LinearLayout").item(0);
assertNotNull(linearLayout);
// first child is a comment.
org.w3c.dom.Node commentNode = linearLayout.getFirstChild().getNextSibling();
assertEquals(Node.COMMENT_NODE, commentNode.getNodeType());
SourcePosition position = PositionXmlParser.getPosition(commentNode);
assertNotNull(position);
assertEquals(6, position.getStartLine());
assertEquals(4, position.getStartColumn());
assertEquals(xml.indexOf("<!--"), position.getStartOffset());
// ensure that the next siblings' position start at the right location.
Element button = (Element) document.getElementsByTagName("Button").item(0);
SourcePosition buttonPosition = PositionXmlParser.getPosition(button);
assertNotNull(buttonPosition);
assertEquals(7, buttonPosition.getStartLine());
assertEquals(4, buttonPosition.getStartColumn());
}
public void testMultipleLineComment() 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" +
" <!-- this is a more complicated \n" +
" which spans on multiple lines\n" +
" in the source xml -->\n" +
" <Button\n" +
" android:id=\"@+id/button1\"\n" +
" android:layout_width=\"wrap_content\"\n" +
" android:layout_height=\"wrap_content\"\n" +
" android:text=\"Button\" />\n" +
" some text\n" +
"\n" +
"</LinearLayout>\n";
File file = File.createTempFile("parsertest", ".xml");
file.deleteOnExit();
Writer fw = new BufferedWriter(new FileWriter(file));
fw.write(xml);
fw.close();
Document document = PositionXmlParser.parse(new FileInputStream(file));
assertNotNull(document);
// Basic parsing heart beat tests
Element linearLayout = (Element) document.getElementsByTagName("LinearLayout").item(0);
assertNotNull(linearLayout);
// first child is a comment.
Node commentNode = linearLayout.getFirstChild().getNextSibling();
assertEquals(Node.COMMENT_NODE, commentNode.getNodeType());
SourcePosition position = PositionXmlParser.getPosition(commentNode);
assertNotNull(position);
assertEquals(6, position.getStartLine());
assertEquals(4, position.getStartColumn());
// ensure that the next siblings' position start at the right location.
Element button = (Element) document.getElementsByTagName("Button").item(0);
SourcePosition buttonPosition = PositionXmlParser.getPosition(button);
assertNotNull(buttonPosition);
assertEquals(9, buttonPosition.getStartLine());
assertEquals(4, buttonPosition.getStartColumn());
}
public void testAttributeWithoutNamespace() throws Exception {
// Search for attribute with different prefixes and with no prefix.
// Make sure we find it regardless of which one comes first (which is why we
// list all 3 orders here and search for all attributes in all 3.)
String xml = ""
+ "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ "<LinearLayout\n"
+ " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ " xmlns:other=\"http://foo.bar\">\n"
+ " <Button\n"
+ " android:id=\"@+id/button1\"\n"
+ " android:orientation=\"vertical\"\n"
+ " other:orientation=\"vertical\"\n"
+ " orientation=\"true\"/>\n"
+ " <Button\n"
+ " android:id=\"@+id/button2\"\n"
+ " other:orientation=\"vertical\"\n"
+ " android:orientation=\"vertical\"\n"
+ " orientation=\"true\"/>\n"
+ " <Button\n"
+ " android:id=\"@+id/button3\"\n"
+ " orientation=\"true\"\n"
+ " android:orientation=\"vertical\"\n"
+ " other:orientation=\"vertical\"/>\n"
+ "</LinearLayout>\n"
+ "\n";
File file = File.createTempFile("parsertest", ".xml");
file.deleteOnExit();
Writer fw = new BufferedWriter(new FileWriter(file));
fw.write(xml);
fw.close();
Document document = PositionXmlParser.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(3, buttons.getLength());
// Check attribute positions
for (int i = 0, n = buttons.getLength(); i < n; i++) {
Element button = (Element)buttons.item(i);
Attr attr;
SourcePosition position;
attr = button.getAttributeNode("orientation");
position = PositionXmlParser.getPosition(attr);
assertNotNull(position);
assertEquals(" orientation=\"true\"",
xml.substring(position.getStartOffset() - 1, position.getEndOffset()));
attr = button.getAttributeNodeNS("http://schemas.android.com/apk/res/android",
"orientation");
position = PositionXmlParser.getPosition(attr);
assertNotNull(position);
assertEquals("android:orientation=\"vertical\"",
xml.substring(position.getStartOffset(), position.getEndOffset()));
attr = button.getAttributeNodeNS("http://foo.bar", "orientation");
position = PositionXmlParser.getPosition(attr);
assertNotNull(position);
assertEquals("other:orientation=\"vertical\"",
xml.substring(position.getStartOffset(), position.getEndOffset()));
}
}
public void testTagNamespace() throws Exception {
final String NAMESPACE_URL = "http://example.org/path";
final String XML =
"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
"<ns:SomeTag xmlns:ns=\"" + NAMESPACE_URL + "\">\n" +
" <ns:SubTag\n" +
" ns:text=\"Button\" />\n" +
"</ns:SomeTag>\n";
File file = File.createTempFile("parsertest", ".xml");
Writer fw = new BufferedWriter(new FileWriter(file));
fw.write(XML);
fw.close();
Document document = PositionXmlParser.parse(new FileInputStream(file));
assertNotNull(document);
Element e = document.getDocumentElement();
assertNotNull(e);
assertEquals("http://example.org/path", e.getNamespaceURI());
assertEquals("ns:SomeTag", e.getTagName());
assertEquals("ns:SomeTag", e.getNodeName());
assertEquals("SomeTag",e.getLocalName());
NodeList subTags = e.getElementsByTagNameNS(NAMESPACE_URL, "SubTag");
assertEquals(1, subTags.getLength());
Node subTagNode = subTags.item(0);
assertEquals(Node.ELEMENT_NODE, subTagNode.getNodeType());
Element subTag = (Element) subTagNode;
assertEquals("ns:SubTag", subTag.getTagName());
assertEquals("ns:SubTag", subTag.getNodeName());
assertEquals("SubTag", subTag.getLocalName());
Attr attr = subTag.getAttributeNodeNS(NAMESPACE_URL, "text");
assertNotNull(attr);
SourcePosition position = PositionXmlParser.getPosition(attr);
assertNotNull(position);
assertEquals("ns:text=\"Button\"",
XML.substring(position.getStartOffset(), position.getEndOffset()));
assertEquals("Button", subTag.getAttributeNS(NAMESPACE_URL, "text"));
assertEquals(NAMESPACE_URL, subTag.getNamespaceURI());
}
}