Merge "AAPT2: Fix processing of quotes in XML" into oc-dr1-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
16cfd497f5
@@ -37,6 +37,7 @@ class BasicStringPiece {
|
||||
public:
|
||||
using const_iterator = const TChar*;
|
||||
using difference_type = size_t;
|
||||
using size_type = size_t;
|
||||
|
||||
// End of string marker.
|
||||
constexpr static const size_t npos = static_cast<size_t>(-1);
|
||||
|
||||
@@ -257,9 +257,11 @@ class XmlFlattenerVisitor : public xml::Visitor {
|
||||
|
||||
// Process plain strings to make sure they get properly escaped.
|
||||
StringPiece raw_value = xml_attr->value;
|
||||
util::StringBuilder str_builder;
|
||||
|
||||
util::StringBuilder str_builder(true /*preserve_spaces*/);
|
||||
str_builder.Append(xml_attr->value);
|
||||
|
||||
if (!options_.keep_raw_values) {
|
||||
str_builder.Append(xml_attr->value);
|
||||
raw_value = str_builder.ToString();
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,13 @@
|
||||
#include "util/BigBuffer.h"
|
||||
#include "util/Util.h"
|
||||
|
||||
using android::StringPiece16;
|
||||
using ::aapt::test::StrEq;
|
||||
using ::android::StringPiece16;
|
||||
using ::testing::Eq;
|
||||
using ::testing::Ge;
|
||||
using ::testing::IsNull;
|
||||
using ::testing::Ne;
|
||||
using ::testing::NotNull;
|
||||
|
||||
namespace aapt {
|
||||
|
||||
@@ -72,163 +78,138 @@ class XmlFlattenerTest : public ::testing::Test {
|
||||
};
|
||||
|
||||
TEST_F(XmlFlattenerTest, FlattenXmlWithNoCompiledAttributes) {
|
||||
std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"EOF(
|
||||
<View xmlns:test="http://com.test"
|
||||
attr="hey">
|
||||
<Layout test:hello="hi" />
|
||||
<Layout>Some text\\</Layout>
|
||||
</View>)EOF");
|
||||
std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"(
|
||||
<View xmlns:test="http://com.test" attr="hey">
|
||||
<Layout test:hello="hi" />
|
||||
<Layout>Some text\\</Layout>
|
||||
</View>)");
|
||||
|
||||
android::ResXMLTree tree;
|
||||
ASSERT_TRUE(Flatten(doc.get(), &tree));
|
||||
|
||||
ASSERT_EQ(android::ResXMLTree::START_NAMESPACE, tree.next());
|
||||
ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::START_NAMESPACE));
|
||||
|
||||
size_t len;
|
||||
const char16_t* namespace_prefix = tree.getNamespacePrefix(&len);
|
||||
EXPECT_EQ(StringPiece16(u"test"), StringPiece16(namespace_prefix, len));
|
||||
EXPECT_THAT(tree.getNamespacePrefix(&len), StrEq(u"test"));
|
||||
EXPECT_THAT(tree.getNamespaceUri(&len), StrEq(u"http://com.test"));
|
||||
|
||||
const char16_t* namespace_uri = tree.getNamespaceUri(&len);
|
||||
ASSERT_EQ(StringPiece16(u"http://com.test"), StringPiece16(namespace_uri, len));
|
||||
ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::START_TAG));
|
||||
EXPECT_THAT(tree.getElementNamespace(&len), IsNull());
|
||||
EXPECT_THAT(tree.getElementName(&len), StrEq(u"View"));
|
||||
|
||||
ASSERT_EQ(android::ResXMLTree::START_TAG, tree.next());
|
||||
ASSERT_THAT(tree.getAttributeCount(), Eq(1u));
|
||||
EXPECT_THAT(tree.getAttributeNamespace(0, &len), IsNull());
|
||||
EXPECT_THAT(tree.getAttributeName(0, &len), StrEq(u"attr"));
|
||||
|
||||
ASSERT_EQ(nullptr, tree.getElementNamespace(&len));
|
||||
const char16_t* tag_name = tree.getElementName(&len);
|
||||
EXPECT_EQ(StringPiece16(u"View"), StringPiece16(tag_name, len));
|
||||
const StringPiece16 kAttr(u"attr");
|
||||
EXPECT_THAT(tree.indexOfAttribute(nullptr, 0, kAttr.data(), kAttr.size()), Eq(0));
|
||||
|
||||
ASSERT_EQ(1u, tree.getAttributeCount());
|
||||
ASSERT_EQ(nullptr, tree.getAttributeNamespace(0, &len));
|
||||
const char16_t* attr_name = tree.getAttributeName(0, &len);
|
||||
EXPECT_EQ(StringPiece16(u"attr"), StringPiece16(attr_name, len));
|
||||
ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::START_TAG));
|
||||
EXPECT_THAT(tree.getElementNamespace(&len), IsNull());
|
||||
EXPECT_THAT(tree.getElementName(&len), StrEq(u"Layout"));
|
||||
|
||||
EXPECT_EQ(0, tree.indexOfAttribute(nullptr, 0, u"attr", StringPiece16(u"attr").size()));
|
||||
ASSERT_THAT(tree.getAttributeCount(), Eq(1u));
|
||||
EXPECT_THAT(tree.getAttributeNamespace(0, &len), StrEq(u"http://com.test"));
|
||||
EXPECT_THAT(tree.getAttributeName(0, &len), StrEq(u"hello"));
|
||||
|
||||
ASSERT_EQ(android::ResXMLTree::START_TAG, tree.next());
|
||||
ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::END_TAG));
|
||||
ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::START_TAG));
|
||||
|
||||
ASSERT_EQ(nullptr, tree.getElementNamespace(&len));
|
||||
tag_name = tree.getElementName(&len);
|
||||
EXPECT_EQ(StringPiece16(u"Layout"), StringPiece16(tag_name, len));
|
||||
EXPECT_THAT(tree.getElementNamespace(&len), IsNull());
|
||||
EXPECT_THAT(tree.getElementName(&len), StrEq(u"Layout"));
|
||||
ASSERT_THAT(tree.getAttributeCount(), Eq(0u));
|
||||
|
||||
ASSERT_EQ(1u, tree.getAttributeCount());
|
||||
const char16_t* attr_namespace = tree.getAttributeNamespace(0, &len);
|
||||
EXPECT_EQ(StringPiece16(u"http://com.test"), StringPiece16(attr_namespace, len));
|
||||
ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::TEXT));
|
||||
EXPECT_THAT(tree.getText(&len), StrEq(u"Some text\\"));
|
||||
|
||||
attr_name = tree.getAttributeName(0, &len);
|
||||
EXPECT_EQ(StringPiece16(u"hello"), StringPiece16(attr_name, len));
|
||||
ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::END_TAG));
|
||||
EXPECT_THAT(tree.getElementNamespace(&len), IsNull());
|
||||
EXPECT_THAT(tree.getElementName(&len), StrEq(u"Layout"));
|
||||
|
||||
ASSERT_EQ(android::ResXMLTree::END_TAG, tree.next());
|
||||
ASSERT_EQ(android::ResXMLTree::START_TAG, tree.next());
|
||||
ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::END_TAG));
|
||||
EXPECT_THAT(tree.getElementNamespace(&len), IsNull());
|
||||
EXPECT_THAT(tree.getElementName(&len), StrEq(u"View"));
|
||||
|
||||
ASSERT_EQ(nullptr, tree.getElementNamespace(&len));
|
||||
tag_name = tree.getElementName(&len);
|
||||
EXPECT_EQ(StringPiece16(u"Layout"), StringPiece16(tag_name, len));
|
||||
ASSERT_EQ(0u, tree.getAttributeCount());
|
||||
ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::END_NAMESPACE));
|
||||
EXPECT_THAT(tree.getNamespacePrefix(&len), StrEq(u"test"));
|
||||
EXPECT_THAT(tree.getNamespaceUri(&len), StrEq(u"http://com.test"));
|
||||
|
||||
ASSERT_EQ(android::ResXMLTree::TEXT, tree.next());
|
||||
const char16_t* text = tree.getText(&len);
|
||||
EXPECT_EQ(StringPiece16(u"Some text\\"), StringPiece16(text, len));
|
||||
|
||||
ASSERT_EQ(android::ResXMLTree::END_TAG, tree.next());
|
||||
ASSERT_EQ(nullptr, tree.getElementNamespace(&len));
|
||||
tag_name = tree.getElementName(&len);
|
||||
EXPECT_EQ(StringPiece16(u"Layout"), StringPiece16(tag_name, len));
|
||||
|
||||
ASSERT_EQ(android::ResXMLTree::END_TAG, tree.next());
|
||||
ASSERT_EQ(nullptr, tree.getElementNamespace(&len));
|
||||
tag_name = tree.getElementName(&len);
|
||||
EXPECT_EQ(StringPiece16(u"View"), StringPiece16(tag_name, len));
|
||||
|
||||
ASSERT_EQ(android::ResXMLTree::END_NAMESPACE, tree.next());
|
||||
namespace_prefix = tree.getNamespacePrefix(&len);
|
||||
EXPECT_EQ(StringPiece16(u"test"), StringPiece16(namespace_prefix, len));
|
||||
|
||||
namespace_uri = tree.getNamespaceUri(&len);
|
||||
ASSERT_EQ(StringPiece16(u"http://com.test"), StringPiece16(namespace_uri, len));
|
||||
|
||||
ASSERT_EQ(android::ResXMLTree::END_DOCUMENT, tree.next());
|
||||
ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::END_DOCUMENT));
|
||||
}
|
||||
|
||||
TEST_F(XmlFlattenerTest, FlattenCompiledXmlAndStripOnlyTools) {
|
||||
std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"EOF(
|
||||
<View xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:foo="http://schemas.android.com/foo"
|
||||
foo:bar="Foo"
|
||||
tools:ignore="MissingTranslation"/>)EOF");
|
||||
std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"(
|
||||
<View xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:foo="http://schemas.android.com/foo"
|
||||
foo:bar="Foo"
|
||||
tools:ignore="MissingTranslation"/>)");
|
||||
|
||||
android::ResXMLTree tree;
|
||||
ASSERT_TRUE(Flatten(doc.get(), &tree));
|
||||
|
||||
ASSERT_EQ(tree.next(), android::ResXMLTree::START_NAMESPACE);
|
||||
ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::START_NAMESPACE));
|
||||
|
||||
size_t len;
|
||||
const char16_t* namespace_prefix = tree.getNamespacePrefix(&len);
|
||||
EXPECT_EQ(StringPiece16(namespace_prefix, len), u"foo");
|
||||
EXPECT_THAT(tree.getNamespacePrefix(&len), StrEq(u"foo"));
|
||||
EXPECT_THAT(tree.getNamespaceUri(&len), StrEq(u"http://schemas.android.com/foo"));
|
||||
ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::START_TAG));
|
||||
|
||||
const char16_t* namespace_uri = tree.getNamespaceUri(&len);
|
||||
ASSERT_EQ(StringPiece16(namespace_uri, len),
|
||||
u"http://schemas.android.com/foo");
|
||||
|
||||
ASSERT_EQ(tree.next(), android::ResXMLTree::START_TAG);
|
||||
|
||||
EXPECT_EQ(tree.indexOfAttribute("http://schemas.android.com/tools", "ignore"),
|
||||
android::NAME_NOT_FOUND);
|
||||
EXPECT_GE(tree.indexOfAttribute("http://schemas.android.com/foo", "bar"), 0);
|
||||
EXPECT_THAT(tree.indexOfAttribute("http://schemas.android.com/tools", "ignore"),
|
||||
Eq(android::NAME_NOT_FOUND));
|
||||
EXPECT_THAT(tree.indexOfAttribute("http://schemas.android.com/foo", "bar"), Ge(0));
|
||||
}
|
||||
|
||||
TEST_F(XmlFlattenerTest, AssignSpecialAttributeIndices) {
|
||||
std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"EOF(
|
||||
<View xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@id/id"
|
||||
class="str"
|
||||
style="@id/id"/>)EOF");
|
||||
std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"(
|
||||
<View xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@id/id"
|
||||
class="str"
|
||||
style="@id/id"/>)");
|
||||
|
||||
android::ResXMLTree tree;
|
||||
ASSERT_TRUE(Flatten(doc.get(), &tree));
|
||||
|
||||
while (tree.next() != android::ResXMLTree::START_TAG) {
|
||||
ASSERT_NE(tree.getEventType(), android::ResXMLTree::BAD_DOCUMENT);
|
||||
ASSERT_NE(tree.getEventType(), android::ResXMLTree::END_DOCUMENT);
|
||||
ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::BAD_DOCUMENT));
|
||||
ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::END_DOCUMENT));
|
||||
}
|
||||
|
||||
EXPECT_EQ(tree.indexOfClass(), 0);
|
||||
EXPECT_EQ(tree.indexOfStyle(), 1);
|
||||
EXPECT_THAT(tree.indexOfClass(), Eq(0));
|
||||
EXPECT_THAT(tree.indexOfStyle(), Eq(1));
|
||||
}
|
||||
|
||||
// The device ResXMLParser in libandroidfw differentiates between empty namespace and null
|
||||
// namespace.
|
||||
TEST_F(XmlFlattenerTest, NoNamespaceIsNotTheSameAsEmptyNamespace) {
|
||||
std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom("<View package=\"android\"/>");
|
||||
std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"(<View package="android"/>)");
|
||||
|
||||
android::ResXMLTree tree;
|
||||
ASSERT_TRUE(Flatten(doc.get(), &tree));
|
||||
|
||||
while (tree.next() != android::ResXMLTree::START_TAG) {
|
||||
ASSERT_NE(tree.getEventType(), android::ResXMLTree::BAD_DOCUMENT);
|
||||
ASSERT_NE(tree.getEventType(), android::ResXMLTree::END_DOCUMENT);
|
||||
ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::BAD_DOCUMENT));
|
||||
ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::END_DOCUMENT));
|
||||
}
|
||||
|
||||
const StringPiece16 kPackage = u"package";
|
||||
EXPECT_GE(tree.indexOfAttribute(nullptr, 0, kPackage.data(), kPackage.size()), 0);
|
||||
EXPECT_THAT(tree.indexOfAttribute(nullptr, 0, kPackage.data(), kPackage.size()), Ge(0));
|
||||
}
|
||||
|
||||
TEST_F(XmlFlattenerTest, EmptyStringValueInAttributeIsNotNull) {
|
||||
std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom("<View package=\"\"/>");
|
||||
std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"(<View package=""/>)");
|
||||
|
||||
android::ResXMLTree tree;
|
||||
ASSERT_TRUE(Flatten(doc.get(), &tree));
|
||||
|
||||
while (tree.next() != android::ResXMLTree::START_TAG) {
|
||||
ASSERT_NE(tree.getEventType(), android::ResXMLTree::BAD_DOCUMENT);
|
||||
ASSERT_NE(tree.getEventType(), android::ResXMLTree::END_DOCUMENT);
|
||||
ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::BAD_DOCUMENT));
|
||||
ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::END_DOCUMENT));
|
||||
}
|
||||
|
||||
const StringPiece16 kPackage = u"package";
|
||||
ssize_t idx = tree.indexOfAttribute(nullptr, 0, kPackage.data(), kPackage.size());
|
||||
ASSERT_GE(idx, 0);
|
||||
ASSERT_THAT(idx, Ge(0));
|
||||
|
||||
size_t len;
|
||||
EXPECT_NE(nullptr, tree.getAttributeStringValue(idx, &len));
|
||||
EXPECT_THAT(tree.getAttributeStringValue(idx, &len), NotNull());
|
||||
}
|
||||
|
||||
TEST_F(XmlFlattenerTest, FlattenNonStandardPackageId) {
|
||||
@@ -236,11 +217,11 @@ TEST_F(XmlFlattenerTest, FlattenNonStandardPackageId) {
|
||||
context_->SetPackageId(0x80);
|
||||
context_->SetNameManglerPolicy({"com.app.test.feature"});
|
||||
|
||||
std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDomForPackageName(context_.get(), R"EOF(
|
||||
std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDomForPackageName(context_.get(), R"(
|
||||
<View xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@id/foo"
|
||||
app:foo="@id/foo" />)EOF");
|
||||
app:foo="@id/foo" />)");
|
||||
|
||||
XmlReferenceLinker linker;
|
||||
ASSERT_TRUE(linker.Consume(context_.get(), doc.get()));
|
||||
@@ -253,59 +234,57 @@ TEST_F(XmlFlattenerTest, FlattenNonStandardPackageId) {
|
||||
ASSERT_TRUE(Flatten(doc.get(), &tree));
|
||||
|
||||
while (tree.next() != android::ResXMLTree::START_TAG) {
|
||||
ASSERT_NE(android::ResXMLTree::BAD_DOCUMENT, tree.getEventType());
|
||||
ASSERT_NE(android::ResXMLTree::END_DOCUMENT, tree.getEventType());
|
||||
ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::BAD_DOCUMENT));
|
||||
ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::END_DOCUMENT));
|
||||
}
|
||||
|
||||
ssize_t idx;
|
||||
|
||||
idx = tree.indexOfAttribute(xml::kSchemaAndroid, "id");
|
||||
ASSERT_GE(idx, 0);
|
||||
EXPECT_EQ(idx, tree.indexOfID());
|
||||
EXPECT_EQ(ResourceId(0x010100d0), ResourceId(tree.getAttributeNameResID(idx)));
|
||||
ASSERT_THAT(idx, Ge(0));
|
||||
EXPECT_THAT(tree.indexOfID(), Eq(idx));
|
||||
EXPECT_THAT(tree.getAttributeNameResID(idx), Eq(0x010100d0u));
|
||||
|
||||
idx = tree.indexOfAttribute(xml::kSchemaAuto, "foo");
|
||||
ASSERT_GE(idx, 0);
|
||||
EXPECT_EQ(ResourceId(0x80010000), ResourceId(tree.getAttributeNameResID(idx)));
|
||||
EXPECT_EQ(android::Res_value::TYPE_REFERENCE, tree.getAttributeDataType(idx));
|
||||
EXPECT_EQ(ResourceId(0x80020000), tree.getAttributeData(idx));
|
||||
ASSERT_THAT(idx, Ge(0));
|
||||
EXPECT_THAT(tree.getAttributeNameResID(idx), Eq(0x80010000u));
|
||||
EXPECT_THAT(tree.getAttributeDataType(idx), Eq(android::Res_value::TYPE_REFERENCE));
|
||||
EXPECT_THAT(tree.getAttributeData(idx), Eq(int32_t(0x80020000)));
|
||||
}
|
||||
|
||||
TEST_F(XmlFlattenerTest, ProcessEscapedStrings) {
|
||||
std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(
|
||||
R"EOF(<element value="\?hello" pattern="\\d{5}">\\d{5}</element>)EOF");
|
||||
R"(<element value="\?hello" pattern="\\d{5}" other=""">\\d{5}</element>)");
|
||||
|
||||
android::ResXMLTree tree;
|
||||
ASSERT_TRUE(Flatten(doc.get(), &tree));
|
||||
|
||||
while (tree.next() != android::ResXMLTree::START_TAG) {
|
||||
ASSERT_NE(tree.getEventType(), android::ResXMLTree::BAD_DOCUMENT);
|
||||
ASSERT_NE(tree.getEventType(), android::ResXMLTree::END_DOCUMENT);
|
||||
ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::BAD_DOCUMENT));
|
||||
ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::END_DOCUMENT));
|
||||
}
|
||||
|
||||
const StringPiece16 kValue = u"value";
|
||||
const StringPiece16 kPattern = u"pattern";
|
||||
const StringPiece16 kOther = u"other";
|
||||
|
||||
size_t len;
|
||||
ssize_t idx;
|
||||
const char16_t* str16;
|
||||
|
||||
idx = tree.indexOfAttribute(nullptr, 0, kValue.data(), kValue.size());
|
||||
ASSERT_GE(idx, 0);
|
||||
str16 = tree.getAttributeStringValue(idx, &len);
|
||||
ASSERT_NE(nullptr, str16);
|
||||
EXPECT_EQ(StringPiece16(u"?hello"), StringPiece16(str16, len));
|
||||
ASSERT_THAT(idx, Ge(0));
|
||||
EXPECT_THAT(tree.getAttributeStringValue(idx, &len), StrEq(u"?hello"));
|
||||
|
||||
idx = tree.indexOfAttribute(nullptr, 0, kPattern.data(), kPattern.size());
|
||||
ASSERT_GE(idx, 0);
|
||||
str16 = tree.getAttributeStringValue(idx, &len);
|
||||
ASSERT_NE(nullptr, str16);
|
||||
EXPECT_EQ(StringPiece16(u"\\d{5}"), StringPiece16(str16, len));
|
||||
ASSERT_THAT(idx, Ge(0));
|
||||
EXPECT_THAT(tree.getAttributeStringValue(idx, &len), StrEq(u"\\d{5}"));
|
||||
|
||||
ASSERT_EQ(android::ResXMLTree::TEXT, tree.next());
|
||||
str16 = tree.getText(&len);
|
||||
ASSERT_NE(nullptr, str16);
|
||||
EXPECT_EQ(StringPiece16(u"\\d{5}"), StringPiece16(str16, len));
|
||||
idx = tree.indexOfAttribute(nullptr, 0, kOther.data(), kOther.size());
|
||||
ASSERT_THAT(idx, Ge(0));
|
||||
EXPECT_THAT(tree.getAttributeStringValue(idx, &len), StrEq(u"\""));
|
||||
|
||||
ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::TEXT));
|
||||
EXPECT_THAT(tree.getText(&len), StrEq(u"\\d{5}"));
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
@@ -145,6 +145,12 @@ void PrintTo(const Maybe<T>& value, std::ostream* out) {
|
||||
|
||||
namespace test {
|
||||
|
||||
MATCHER_P(StrEq, a,
|
||||
std::string(negation ? "isn't" : "is") + " equal to " +
|
||||
::testing::PrintToString(android::StringPiece16(a))) {
|
||||
return android::StringPiece16(arg) == a;
|
||||
}
|
||||
|
||||
MATCHER_P(ValueEq, a,
|
||||
std::string(negation ? "isn't" : "is") + " equal to " + ::testing::PrintToString(a)) {
|
||||
return arg.Equals(&a);
|
||||
|
||||
@@ -281,16 +281,12 @@ inline Maybe<T> make_nothing() {
|
||||
return Maybe<T>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the == operator between Maybe<T> and Maybe<U> only if the operator T
|
||||
* == U is defined.
|
||||
* That way the compiler will show an error at the callsite when comparing two
|
||||
* Maybe<> objects
|
||||
* whose inner types can't be compared.
|
||||
*/
|
||||
// Define the == operator between Maybe<T> and Maybe<U> only if the operator T == U is defined.
|
||||
// That way the compiler will show an error at the callsite when comparing two Maybe<> objects
|
||||
// whose inner types can't be compared.
|
||||
template <typename T, typename U>
|
||||
typename std::enable_if<has_eq_op<T, U>::value, bool>::type operator==(
|
||||
const Maybe<T>& a, const Maybe<U>& b) {
|
||||
typename std::enable_if<has_eq_op<T, U>::value, bool>::type operator==(const Maybe<T>& a,
|
||||
const Maybe<U>& b) {
|
||||
if (a && b) {
|
||||
return a.value() == b.value();
|
||||
} else if (!a && !b) {
|
||||
@@ -299,18 +295,22 @@ typename std::enable_if<has_eq_op<T, U>::value, bool>::type operator==(
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as operator== but negated.
|
||||
*/
|
||||
template <typename T, typename U>
|
||||
typename std::enable_if<has_eq_op<T, U>::value, bool>::type operator!=(
|
||||
const Maybe<T>& a, const Maybe<U>& b) {
|
||||
typename std::enable_if<has_eq_op<T, U>::value, bool>::type operator==(const Maybe<T>& a,
|
||||
const U& b) {
|
||||
return a ? a.value() == b : false;
|
||||
}
|
||||
|
||||
// Same as operator== but negated.
|
||||
template <typename T, typename U>
|
||||
typename std::enable_if<has_eq_op<T, U>::value, bool>::type operator!=(const Maybe<T>& a,
|
||||
const Maybe<U>& b) {
|
||||
return !(a == b);
|
||||
}
|
||||
|
||||
template <typename T, typename U>
|
||||
typename std::enable_if<has_lt_op<T, U>::value, bool>::type operator<(
|
||||
const Maybe<T>& a, const Maybe<U>& b) {
|
||||
typename std::enable_if<has_lt_op<T, U>::value, bool>::type operator<(const Maybe<T>& a,
|
||||
const Maybe<U>& b) {
|
||||
if (a && b) {
|
||||
return a.value() < b.value();
|
||||
} else if (!a && !b) {
|
||||
|
||||
@@ -312,6 +312,9 @@ static Maybe<std::string> ParseUnicodeCodepoint(const char** start,
|
||||
return result_utf8;
|
||||
}
|
||||
|
||||
StringBuilder::StringBuilder(bool preserve_spaces) : preserve_spaces_(preserve_spaces) {
|
||||
}
|
||||
|
||||
StringBuilder& StringBuilder::Append(const StringPiece& str) {
|
||||
if (!error_.empty()) {
|
||||
return *this;
|
||||
@@ -368,14 +371,12 @@ StringBuilder& StringBuilder::Append(const StringPiece& str) {
|
||||
}
|
||||
last_char_was_escape_ = false;
|
||||
start = current + 1;
|
||||
} else if (*current == '"') {
|
||||
} else if (!preserve_spaces_ && *current == '"') {
|
||||
if (!quote_ && trailing_space_) {
|
||||
// We found an opening quote, and we have
|
||||
// trailing space, so we should append that
|
||||
// We found an opening quote, and we have trailing space, so we should append that
|
||||
// space now.
|
||||
if (trailing_space_) {
|
||||
// We had trailing whitespace, so
|
||||
// replace with a single space.
|
||||
// We had trailing whitespace, so replace with a single space.
|
||||
if (!str_.empty()) {
|
||||
str_ += ' ';
|
||||
}
|
||||
@@ -385,7 +386,7 @@ StringBuilder& StringBuilder::Append(const StringPiece& str) {
|
||||
quote_ = !quote_;
|
||||
str_.append(start, current - start);
|
||||
start = current + 1;
|
||||
} else if (*current == '\'' && !quote_) {
|
||||
} else if (!preserve_spaces_ && *current == '\'' && !quote_) {
|
||||
// This should be escaped.
|
||||
error_ = "unescaped apostrophe";
|
||||
return *this;
|
||||
@@ -402,7 +403,7 @@ StringBuilder& StringBuilder::Append(const StringPiece& str) {
|
||||
str_.append(start, current - start);
|
||||
start = current + 1;
|
||||
last_char_was_escape_ = true;
|
||||
} else if (!quote_) {
|
||||
} else if (!preserve_spaces_ && !quote_) {
|
||||
// This is not quoted text, so look for whitespace.
|
||||
if (isspace(*current)) {
|
||||
// We found whitespace, see if we have seen some
|
||||
|
||||
@@ -166,6 +166,8 @@ bool VerifyJavaStringFormat(const android::StringPiece& str);
|
||||
|
||||
class StringBuilder {
|
||||
public:
|
||||
explicit StringBuilder(bool preserve_spaces = false);
|
||||
|
||||
StringBuilder& Append(const android::StringPiece& str);
|
||||
const std::string& ToString() const;
|
||||
const std::string& Error() const;
|
||||
@@ -179,6 +181,7 @@ class StringBuilder {
|
||||
explicit operator bool() const;
|
||||
|
||||
private:
|
||||
bool preserve_spaces_;
|
||||
std::string str_;
|
||||
size_t utf16_len_ = 0;
|
||||
bool quote_ = false;
|
||||
|
||||
@@ -20,16 +20,17 @@
|
||||
|
||||
#include "test/Test.h"
|
||||
|
||||
using android::StringPiece;
|
||||
using ::android::StringPiece;
|
||||
using ::testing::Eq;
|
||||
using ::testing::Ne;
|
||||
using ::testing::SizeIs;
|
||||
|
||||
namespace aapt {
|
||||
|
||||
TEST(UtilTest, TrimOnlyWhitespace) {
|
||||
const std::string full = "\n ";
|
||||
|
||||
StringPiece trimmed = util::TrimWhitespace(full);
|
||||
const StringPiece trimmed = util::TrimWhitespace("\n ");
|
||||
EXPECT_TRUE(trimmed.empty());
|
||||
EXPECT_EQ(0u, trimmed.size());
|
||||
EXPECT_THAT(trimmed, SizeIs(0u));
|
||||
}
|
||||
|
||||
TEST(UtilTest, StringEndsWith) {
|
||||
@@ -41,85 +42,74 @@ TEST(UtilTest, StringStartsWith) {
|
||||
}
|
||||
|
||||
TEST(UtilTest, StringBuilderSplitEscapeSequence) {
|
||||
EXPECT_EQ(StringPiece("this is a new\nline."), util::StringBuilder()
|
||||
.Append("this is a new\\")
|
||||
.Append("nline.")
|
||||
.ToString());
|
||||
EXPECT_THAT(util::StringBuilder().Append("this is a new\\").Append("nline.").ToString(),
|
||||
Eq("this is a new\nline."));
|
||||
}
|
||||
|
||||
TEST(UtilTest, StringBuilderWhitespaceRemoval) {
|
||||
EXPECT_EQ(StringPiece("hey guys this is so cool"),
|
||||
util::StringBuilder()
|
||||
.Append(" hey guys ")
|
||||
.Append(" this is so cool ")
|
||||
.ToString());
|
||||
|
||||
EXPECT_EQ(StringPiece(" wow, so many \t spaces. what?"),
|
||||
util::StringBuilder()
|
||||
.Append(" \" wow, so many \t ")
|
||||
.Append("spaces. \"what? ")
|
||||
.ToString());
|
||||
|
||||
EXPECT_EQ(StringPiece("where is the pie?"), util::StringBuilder()
|
||||
.Append(" where \t ")
|
||||
.Append(" \nis the "
|
||||
" pie?")
|
||||
.ToString());
|
||||
EXPECT_THAT(util::StringBuilder().Append(" hey guys ").Append(" this is so cool ").ToString(),
|
||||
Eq("hey guys this is so cool"));
|
||||
EXPECT_THAT(
|
||||
util::StringBuilder().Append(" \" wow, so many \t ").Append("spaces. \"what? ").ToString(),
|
||||
Eq(" wow, so many \t spaces. what?"));
|
||||
EXPECT_THAT(util::StringBuilder().Append(" where \t ").Append(" \nis the pie?").ToString(),
|
||||
Eq("where is the pie?"));
|
||||
}
|
||||
|
||||
TEST(UtilTest, StringBuilderEscaping) {
|
||||
EXPECT_EQ(StringPiece("hey guys\n this \t is so\\ cool"),
|
||||
util::StringBuilder()
|
||||
.Append(" hey guys\\n ")
|
||||
.Append(" this \\t is so\\\\ cool ")
|
||||
.ToString());
|
||||
|
||||
EXPECT_EQ(StringPiece("@?#\\\'"),
|
||||
util::StringBuilder().Append("\\@\\?\\#\\\\\\'").ToString());
|
||||
EXPECT_THAT(util::StringBuilder()
|
||||
.Append(" hey guys\\n ")
|
||||
.Append(" this \\t is so\\\\ cool ")
|
||||
.ToString(),
|
||||
Eq("hey guys\n this \t is so\\ cool"));
|
||||
EXPECT_THAT(util::StringBuilder().Append("\\@\\?\\#\\\\\\'").ToString(), Eq("@?#\\\'"));
|
||||
}
|
||||
|
||||
TEST(UtilTest, StringBuilderMisplacedQuote) {
|
||||
util::StringBuilder builder{};
|
||||
util::StringBuilder builder;
|
||||
EXPECT_FALSE(builder.Append("they're coming!"));
|
||||
}
|
||||
|
||||
TEST(UtilTest, StringBuilderUnicodeCodes) {
|
||||
EXPECT_EQ(std::string("\u00AF\u0AF0 woah"),
|
||||
util::StringBuilder().Append("\\u00AF\\u0AF0 woah").ToString());
|
||||
|
||||
EXPECT_THAT(util::StringBuilder().Append("\\u00AF\\u0AF0 woah").ToString(),
|
||||
Eq("\u00AF\u0AF0 woah"));
|
||||
EXPECT_FALSE(util::StringBuilder().Append("\\u00 yo"));
|
||||
}
|
||||
|
||||
TEST(UtilTest, StringBuilderPreserveSpaces) {
|
||||
EXPECT_THAT(util::StringBuilder(true /*preserve_spaces*/).Append("\"").ToString(), Eq("\""));
|
||||
}
|
||||
|
||||
TEST(UtilTest, TokenizeInput) {
|
||||
auto tokenizer = util::Tokenize(StringPiece("this| is|the|end"), '|');
|
||||
auto iter = tokenizer.begin();
|
||||
ASSERT_EQ(*iter, StringPiece("this"));
|
||||
ASSERT_THAT(*iter, Eq("this"));
|
||||
++iter;
|
||||
ASSERT_EQ(*iter, StringPiece(" is"));
|
||||
ASSERT_THAT(*iter, Eq(" is"));
|
||||
++iter;
|
||||
ASSERT_EQ(*iter, StringPiece("the"));
|
||||
ASSERT_THAT(*iter, Eq("the"));
|
||||
++iter;
|
||||
ASSERT_EQ(*iter, StringPiece("end"));
|
||||
ASSERT_THAT(*iter, Eq("end"));
|
||||
++iter;
|
||||
ASSERT_EQ(tokenizer.end(), iter);
|
||||
ASSERT_THAT(iter, Eq(tokenizer.end()));
|
||||
}
|
||||
|
||||
TEST(UtilTest, TokenizeEmptyString) {
|
||||
auto tokenizer = util::Tokenize(StringPiece(""), '|');
|
||||
auto iter = tokenizer.begin();
|
||||
ASSERT_NE(tokenizer.end(), iter);
|
||||
ASSERT_EQ(StringPiece(), *iter);
|
||||
ASSERT_THAT(iter, Ne(tokenizer.end()));
|
||||
ASSERT_THAT(*iter, Eq(StringPiece()));
|
||||
++iter;
|
||||
ASSERT_EQ(tokenizer.end(), iter);
|
||||
ASSERT_THAT(iter, Eq(tokenizer.end()));
|
||||
}
|
||||
|
||||
TEST(UtilTest, TokenizeAtEnd) {
|
||||
auto tokenizer = util::Tokenize(StringPiece("one."), '.');
|
||||
auto iter = tokenizer.begin();
|
||||
ASSERT_EQ(*iter, StringPiece("one"));
|
||||
ASSERT_THAT(*iter, Eq("one"));
|
||||
++iter;
|
||||
ASSERT_NE(iter, tokenizer.end());
|
||||
ASSERT_EQ(*iter, StringPiece());
|
||||
ASSERT_THAT(iter, Ne(tokenizer.end()));
|
||||
ASSERT_THAT(*iter, Eq(StringPiece()));
|
||||
}
|
||||
|
||||
TEST(UtilTest, IsJavaClassName) {
|
||||
|
||||
@@ -28,17 +28,16 @@ constexpr const char* kXmlPreamble =
|
||||
|
||||
TEST(XmlDomTest, Inflate) {
|
||||
std::stringstream in(kXmlPreamble);
|
||||
in << R"EOF(
|
||||
<Layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<TextView android:id="@+id/id"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
</Layout>
|
||||
)EOF";
|
||||
in << R"(
|
||||
<Layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<TextView android:id="@+id/id"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
</Layout>)";
|
||||
|
||||
const Source source = {"test.xml"};
|
||||
const Source source("test.xml");
|
||||
StdErrDiagnostics diag;
|
||||
std::unique_ptr<xml::XmlResource> doc = xml::Inflate(&in, &diag, source);
|
||||
ASSERT_NE(doc, nullptr);
|
||||
@@ -51,8 +50,8 @@ TEST(XmlDomTest, Inflate) {
|
||||
|
||||
// Escaping is handled after parsing of the values for resource-specific values.
|
||||
TEST(XmlDomTest, ForwardEscapes) {
|
||||
std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"EOF(
|
||||
<element value="\?hello" pattern="\\d{5}">\\d{5}</element>)EOF");
|
||||
std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"(
|
||||
<element value="\?hello" pattern="\\d{5}">\\d{5}</element>)");
|
||||
|
||||
xml::Element* el = xml::FindRootElement(doc->root.get());
|
||||
ASSERT_NE(nullptr, el);
|
||||
@@ -65,10 +64,20 @@ TEST(XmlDomTest, ForwardEscapes) {
|
||||
ASSERT_NE(nullptr, attr);
|
||||
EXPECT_EQ("\\?hello", attr->value);
|
||||
|
||||
ASSERT_EQ(1u, el->children.size());
|
||||
xml::Text* text = xml::NodeCast<xml::Text>(el->children[0].get());
|
||||
ASSERT_NE(nullptr, text);
|
||||
EXPECT_EQ("\\\\d{5}", text->text);
|
||||
}
|
||||
|
||||
TEST(XmlDomTest, XmlEscapeSequencesAreParsed) {
|
||||
std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"(<element value=""" />)");
|
||||
|
||||
xml::Element* el = xml::FindRootElement(doc.get());
|
||||
ASSERT_NE(nullptr, el);
|
||||
|
||||
xml::Attribute* attr = el->FindAttribute({}, "value");
|
||||
ASSERT_NE(nullptr, attr);
|
||||
EXPECT_EQ("\"", attr->value);
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
Reference in New Issue
Block a user