1 /*
2 * Copyright (C) 2015 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 #include "ResourceParser.h"
18
19 #include <sstream>
20 #include <string>
21
22 #include "ResourceTable.h"
23 #include "ResourceUtils.h"
24 #include "ResourceValues.h"
25 #include "test/Test.h"
26 #include "xml/XmlPullParser.h"
27
28 using ::aapt::test::ValueEq;
29 using ::android::StringPiece;
30 using ::testing::Eq;
31 using ::testing::NotNull;
32 using ::testing::Pointee;
33
34 namespace aapt {
35
36 constexpr const char* kXmlPreamble = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
37
TEST(ResourceParserSingleTest,FailToParseWithNoRootResourcesElement)38 TEST(ResourceParserSingleTest, FailToParseWithNoRootResourcesElement) {
39 std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
40 std::stringstream input(kXmlPreamble);
41 input << R"(<attr name="foo"/>)" << std::endl;
42 ResourceTable table;
43 ResourceParser parser(context->GetDiagnostics(), &table, Source{"test"}, {});
44 xml::XmlPullParser xml_parser(input);
45 ASSERT_FALSE(parser.Parse(&xml_parser));
46 }
47
48 class ResourceParserTest : public ::testing::Test {
49 public:
SetUp()50 void SetUp() override {
51 context_ = test::ContextBuilder().Build();
52 }
53
TestParse(const StringPiece & str)54 ::testing::AssertionResult TestParse(const StringPiece& str) {
55 return TestParse(str, ConfigDescription{});
56 }
57
TestParse(const StringPiece & str,const ConfigDescription & config)58 ::testing::AssertionResult TestParse(const StringPiece& str, const ConfigDescription& config) {
59 std::stringstream input(kXmlPreamble);
60 input << "<resources>\n" << str << "\n</resources>" << std::endl;
61 ResourceParserOptions parserOptions;
62 ResourceParser parser(context_->GetDiagnostics(), &table_, Source{"test"}, config,
63 parserOptions);
64 xml::XmlPullParser xmlParser(input);
65 if (parser.Parse(&xmlParser)) {
66 return ::testing::AssertionSuccess();
67 }
68 return ::testing::AssertionFailure();
69 }
70
71 protected:
72 ResourceTable table_;
73 std::unique_ptr<IAaptContext> context_;
74 };
75
TEST_F(ResourceParserTest,ParseQuotedString)76 TEST_F(ResourceParserTest, ParseQuotedString) {
77 std::string input = "<string name=\"foo\"> \" hey there \" </string>";
78 ASSERT_TRUE(TestParse(input));
79
80 String* str = test::GetValue<String>(&table_, "string/foo");
81 ASSERT_NE(nullptr, str);
82 EXPECT_EQ(std::string(" hey there "), *str->value);
83 EXPECT_TRUE(str->untranslatable_sections.empty());
84 }
85
TEST_F(ResourceParserTest,ParseEscapedString)86 TEST_F(ResourceParserTest, ParseEscapedString) {
87 std::string input = "<string name=\"foo\">\\?123</string>";
88 ASSERT_TRUE(TestParse(input));
89
90 String* str = test::GetValue<String>(&table_, "string/foo");
91 ASSERT_NE(nullptr, str);
92 EXPECT_EQ(std::string("?123"), *str->value);
93 EXPECT_TRUE(str->untranslatable_sections.empty());
94 }
95
TEST_F(ResourceParserTest,ParseFormattedString)96 TEST_F(ResourceParserTest, ParseFormattedString) {
97 std::string input = "<string name=\"foo\">%d %s</string>";
98 ASSERT_FALSE(TestParse(input));
99
100 input = "<string name=\"foo\">%1$d %2$s</string>";
101 ASSERT_TRUE(TestParse(input));
102 }
103
TEST_F(ResourceParserTest,ParseStyledString)104 TEST_F(ResourceParserTest, ParseStyledString) {
105 // Use a surrogate pair unicode point so that we can verify that the span
106 // indices use UTF-16 length and not UTF-8 length.
107 std::string input =
108 "<string name=\"foo\">This is my aunt\u2019s <b>fickle <small>string</small></b></string>";
109 ASSERT_TRUE(TestParse(input));
110
111 StyledString* str = test::GetValue<StyledString>(&table_, "string/foo");
112 ASSERT_NE(nullptr, str);
113
114 const std::string expected_str = "This is my aunt\u2019s fickle string";
115 EXPECT_EQ(expected_str, *str->value->str);
116 EXPECT_EQ(2u, str->value->spans.size());
117 EXPECT_TRUE(str->untranslatable_sections.empty());
118
119 EXPECT_EQ(std::string("b"), *str->value->spans[0].name);
120 EXPECT_EQ(17u, str->value->spans[0].first_char);
121 EXPECT_EQ(30u, str->value->spans[0].last_char);
122
123 EXPECT_EQ(std::string("small"), *str->value->spans[1].name);
124 EXPECT_EQ(24u, str->value->spans[1].first_char);
125 EXPECT_EQ(30u, str->value->spans[1].last_char);
126 }
127
TEST_F(ResourceParserTest,ParseStringWithWhitespace)128 TEST_F(ResourceParserTest, ParseStringWithWhitespace) {
129 std::string input = "<string name=\"foo\"> This is what I think </string>";
130 ASSERT_TRUE(TestParse(input));
131
132 String* str = test::GetValue<String>(&table_, "string/foo");
133 ASSERT_NE(nullptr, str);
134 EXPECT_EQ(std::string("This is what I think"), *str->value);
135 EXPECT_TRUE(str->untranslatable_sections.empty());
136
137 input = "<string name=\"foo2\">\" This is what I think \"</string>";
138 ASSERT_TRUE(TestParse(input));
139
140 str = test::GetValue<String>(&table_, "string/foo2");
141 ASSERT_NE(nullptr, str);
142 EXPECT_EQ(std::string(" This is what I think "), *str->value);
143 }
144
TEST_F(ResourceParserTest,IgnoreXliffTagsOtherThanG)145 TEST_F(ResourceParserTest, IgnoreXliffTagsOtherThanG) {
146 std::string input = R"EOF(
147 <string name="foo" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
148 There are <xliff:source>no</xliff:source> apples</string>)EOF";
149 ASSERT_TRUE(TestParse(input));
150
151 String* str = test::GetValue<String>(&table_, "string/foo");
152 ASSERT_NE(nullptr, str);
153 EXPECT_EQ(StringPiece("There are no apples"), StringPiece(*str->value));
154 EXPECT_TRUE(str->untranslatable_sections.empty());
155 }
156
TEST_F(ResourceParserTest,NestedXliffGTagsAreIllegal)157 TEST_F(ResourceParserTest, NestedXliffGTagsAreIllegal) {
158 std::string input = R"EOF(
159 <string name="foo" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
160 Do not <xliff:g>translate <xliff:g>this</xliff:g></xliff:g></string>)EOF";
161 EXPECT_FALSE(TestParse(input));
162 }
163
TEST_F(ResourceParserTest,RecordUntranslateableXliffSectionsInString)164 TEST_F(ResourceParserTest, RecordUntranslateableXliffSectionsInString) {
165 std::string input = R"EOF(
166 <string name="foo" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
167 There are <xliff:g id="count">%1$d</xliff:g> apples</string>)EOF";
168 ASSERT_TRUE(TestParse(input));
169
170 String* str = test::GetValue<String>(&table_, "string/foo");
171 ASSERT_NE(nullptr, str);
172 EXPECT_EQ(StringPiece("There are %1$d apples"), StringPiece(*str->value));
173
174 ASSERT_EQ(1u, str->untranslatable_sections.size());
175
176 // We expect indices and lengths that span to include the whitespace
177 // before %1$d. This is due to how the StringBuilder withholds whitespace unless
178 // needed (to deal with line breaks, etc.).
179 EXPECT_EQ(9u, str->untranslatable_sections[0].start);
180 EXPECT_EQ(14u, str->untranslatable_sections[0].end);
181 }
182
TEST_F(ResourceParserTest,RecordUntranslateableXliffSectionsInStyledString)183 TEST_F(ResourceParserTest, RecordUntranslateableXliffSectionsInStyledString) {
184 std::string input = R"EOF(
185 <string name="foo" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
186 There are <b><xliff:g id="count">%1$d</xliff:g></b> apples</string>)EOF";
187 ASSERT_TRUE(TestParse(input));
188
189 StyledString* str = test::GetValue<StyledString>(&table_, "string/foo");
190 ASSERT_NE(nullptr, str);
191 EXPECT_EQ(StringPiece("There are %1$d apples"), StringPiece(*str->value->str));
192
193 ASSERT_EQ(1u, str->untranslatable_sections.size());
194
195 // We expect indices and lengths that span to include the whitespace
196 // before %1$d. This is due to how the StringBuilder withholds whitespace unless
197 // needed (to deal with line breaks, etc.).
198 EXPECT_EQ(9u, str->untranslatable_sections[0].start);
199 EXPECT_EQ(14u, str->untranslatable_sections[0].end);
200 }
201
TEST_F(ResourceParserTest,ParseNull)202 TEST_F(ResourceParserTest, ParseNull) {
203 std::string input = "<integer name=\"foo\">@null</integer>";
204 ASSERT_TRUE(TestParse(input));
205
206 // The Android runtime treats a value of android::Res_value::TYPE_NULL as
207 // a non-existing value, and this causes problems in styles when trying to
208 // resolve an attribute. Null values must be encoded as android::Res_value::TYPE_REFERENCE
209 // with a data value of 0.
210 Reference* null_ref = test::GetValue<Reference>(&table_, "integer/foo");
211 ASSERT_THAT(null_ref, NotNull());
212 EXPECT_FALSE(null_ref->name);
213 EXPECT_FALSE(null_ref->id);
214 EXPECT_EQ(Reference::Type::kResource, null_ref->reference_type);
215 }
216
TEST_F(ResourceParserTest,ParseEmpty)217 TEST_F(ResourceParserTest, ParseEmpty) {
218 std::string input = "<integer name=\"foo\">@empty</integer>";
219 ASSERT_TRUE(TestParse(input));
220
221 BinaryPrimitive* integer = test::GetValue<BinaryPrimitive>(&table_, "integer/foo");
222 ASSERT_NE(nullptr, integer);
223 EXPECT_EQ(uint16_t(android::Res_value::TYPE_NULL), integer->value.dataType);
224 EXPECT_EQ(uint32_t(android::Res_value::DATA_NULL_EMPTY), integer->value.data);
225 }
226
TEST_F(ResourceParserTest,ParseAttr)227 TEST_F(ResourceParserTest, ParseAttr) {
228 std::string input =
229 "<attr name=\"foo\" format=\"string\"/>\n"
230 "<attr name=\"bar\"/>";
231 ASSERT_TRUE(TestParse(input));
232
233 Attribute* attr = test::GetValue<Attribute>(&table_, "attr/foo");
234 ASSERT_NE(nullptr, attr);
235 EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_STRING), attr->type_mask);
236
237 attr = test::GetValue<Attribute>(&table_, "attr/bar");
238 ASSERT_NE(nullptr, attr);
239 EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_ANY), attr->type_mask);
240 }
241
242 // Old AAPT allowed attributes to be defined under different configurations, but
243 // ultimately
244 // stored them with the default configuration. Check that we have the same
245 // behavior.
TEST_F(ResourceParserTest,ParseAttrAndDeclareStyleableUnderConfigButRecordAsNoConfig)246 TEST_F(ResourceParserTest, ParseAttrAndDeclareStyleableUnderConfigButRecordAsNoConfig) {
247 const ConfigDescription watch_config = test::ParseConfigOrDie("watch");
248 std::string input = R"(
249 <attr name="foo" />
250 <declare-styleable name="bar">
251 <attr name="baz" />
252 </declare-styleable>)";
253 ASSERT_TRUE(TestParse(input, watch_config));
254
255 EXPECT_EQ(nullptr, test::GetValueForConfig<Attribute>(&table_, "attr/foo", watch_config));
256 EXPECT_EQ(nullptr, test::GetValueForConfig<Attribute>(&table_, "attr/baz", watch_config));
257 EXPECT_EQ(nullptr, test::GetValueForConfig<Styleable>(&table_, "styleable/bar", watch_config));
258
259 EXPECT_NE(nullptr, test::GetValue<Attribute>(&table_, "attr/foo"));
260 EXPECT_NE(nullptr, test::GetValue<Attribute>(&table_, "attr/baz"));
261 EXPECT_NE(nullptr, test::GetValue<Styleable>(&table_, "styleable/bar"));
262 }
263
TEST_F(ResourceParserTest,ParseAttrWithMinMax)264 TEST_F(ResourceParserTest, ParseAttrWithMinMax) {
265 std::string input =
266 "<attr name=\"foo\" min=\"10\" max=\"23\" format=\"integer\"/>";
267 ASSERT_TRUE(TestParse(input));
268
269 Attribute* attr = test::GetValue<Attribute>(&table_, "attr/foo");
270 ASSERT_NE(nullptr, attr);
271 EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_INTEGER), attr->type_mask);
272 EXPECT_EQ(10, attr->min_int);
273 EXPECT_EQ(23, attr->max_int);
274 }
275
TEST_F(ResourceParserTest,FailParseAttrWithMinMaxButNotInteger)276 TEST_F(ResourceParserTest, FailParseAttrWithMinMaxButNotInteger) {
277 std::string input =
278 "<attr name=\"foo\" min=\"10\" max=\"23\" format=\"string\"/>";
279 ASSERT_FALSE(TestParse(input));
280 }
281
TEST_F(ResourceParserTest,ParseUseAndDeclOfAttr)282 TEST_F(ResourceParserTest, ParseUseAndDeclOfAttr) {
283 std::string input =
284 "<declare-styleable name=\"Styleable\">\n"
285 " <attr name=\"foo\" />\n"
286 "</declare-styleable>\n"
287 "<attr name=\"foo\" format=\"string\"/>";
288 ASSERT_TRUE(TestParse(input));
289
290 Attribute* attr = test::GetValue<Attribute>(&table_, "attr/foo");
291 ASSERT_NE(nullptr, attr);
292 EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_STRING), attr->type_mask);
293 }
294
TEST_F(ResourceParserTest,ParseDoubleUseOfAttr)295 TEST_F(ResourceParserTest, ParseDoubleUseOfAttr) {
296 std::string input =
297 "<declare-styleable name=\"Theme\">"
298 " <attr name=\"foo\" />\n"
299 "</declare-styleable>\n"
300 "<declare-styleable name=\"Window\">\n"
301 " <attr name=\"foo\" format=\"boolean\"/>\n"
302 "</declare-styleable>";
303 ASSERT_TRUE(TestParse(input));
304
305 Attribute* attr = test::GetValue<Attribute>(&table_, "attr/foo");
306 ASSERT_NE(nullptr, attr);
307 EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_BOOLEAN), attr->type_mask);
308 }
309
TEST_F(ResourceParserTest,ParseEnumAttr)310 TEST_F(ResourceParserTest, ParseEnumAttr) {
311 std::string input =
312 "<attr name=\"foo\">\n"
313 " <enum name=\"bar\" value=\"0\"/>\n"
314 " <enum name=\"bat\" value=\"1\"/>\n"
315 " <enum name=\"baz\" value=\"2\"/>\n"
316 "</attr>";
317 ASSERT_TRUE(TestParse(input));
318
319 Attribute* enum_attr = test::GetValue<Attribute>(&table_, "attr/foo");
320 ASSERT_NE(enum_attr, nullptr);
321 EXPECT_EQ(enum_attr->type_mask, android::ResTable_map::TYPE_ENUM);
322 ASSERT_EQ(enum_attr->symbols.size(), 3u);
323
324 AAPT_ASSERT_TRUE(enum_attr->symbols[0].symbol.name);
325 EXPECT_EQ(enum_attr->symbols[0].symbol.name.value().entry, "bar");
326 EXPECT_EQ(enum_attr->symbols[0].value, 0u);
327
328 AAPT_ASSERT_TRUE(enum_attr->symbols[1].symbol.name);
329 EXPECT_EQ(enum_attr->symbols[1].symbol.name.value().entry, "bat");
330 EXPECT_EQ(enum_attr->symbols[1].value, 1u);
331
332 AAPT_ASSERT_TRUE(enum_attr->symbols[2].symbol.name);
333 EXPECT_EQ(enum_attr->symbols[2].symbol.name.value().entry, "baz");
334 EXPECT_EQ(enum_attr->symbols[2].value, 2u);
335 }
336
TEST_F(ResourceParserTest,ParseFlagAttr)337 TEST_F(ResourceParserTest, ParseFlagAttr) {
338 std::string input =
339 "<attr name=\"foo\">\n"
340 " <flag name=\"bar\" value=\"0\"/>\n"
341 " <flag name=\"bat\" value=\"1\"/>\n"
342 " <flag name=\"baz\" value=\"2\"/>\n"
343 "</attr>";
344 ASSERT_TRUE(TestParse(input));
345
346 Attribute* flag_attr = test::GetValue<Attribute>(&table_, "attr/foo");
347 ASSERT_NE(nullptr, flag_attr);
348 EXPECT_EQ(flag_attr->type_mask, android::ResTable_map::TYPE_FLAGS);
349 ASSERT_EQ(flag_attr->symbols.size(), 3u);
350
351 AAPT_ASSERT_TRUE(flag_attr->symbols[0].symbol.name);
352 EXPECT_EQ(flag_attr->symbols[0].symbol.name.value().entry, "bar");
353 EXPECT_EQ(flag_attr->symbols[0].value, 0u);
354
355 AAPT_ASSERT_TRUE(flag_attr->symbols[1].symbol.name);
356 EXPECT_EQ(flag_attr->symbols[1].symbol.name.value().entry, "bat");
357 EXPECT_EQ(flag_attr->symbols[1].value, 1u);
358
359 AAPT_ASSERT_TRUE(flag_attr->symbols[2].symbol.name);
360 EXPECT_EQ(flag_attr->symbols[2].symbol.name.value().entry, "baz");
361 EXPECT_EQ(flag_attr->symbols[2].value, 2u);
362
363 std::unique_ptr<BinaryPrimitive> flag_value =
364 ResourceUtils::TryParseFlagSymbol(flag_attr, "baz|bat");
365 ASSERT_NE(nullptr, flag_value);
366 EXPECT_EQ(flag_value->value.data, 1u | 2u);
367 }
368
TEST_F(ResourceParserTest,FailToParseEnumAttrWithNonUniqueKeys)369 TEST_F(ResourceParserTest, FailToParseEnumAttrWithNonUniqueKeys) {
370 std::string input =
371 "<attr name=\"foo\">\n"
372 " <enum name=\"bar\" value=\"0\"/>\n"
373 " <enum name=\"bat\" value=\"1\"/>\n"
374 " <enum name=\"bat\" value=\"2\"/>\n"
375 "</attr>";
376 ASSERT_FALSE(TestParse(input));
377 }
378
TEST_F(ResourceParserTest,ParseStyle)379 TEST_F(ResourceParserTest, ParseStyle) {
380 std::string input =
381 "<style name=\"foo\" parent=\"@style/fu\">\n"
382 " <item name=\"bar\">#ffffffff</item>\n"
383 " <item name=\"bat\">@string/hey</item>\n"
384 " <item name=\"baz\"><b>hey</b></item>\n"
385 "</style>";
386 ASSERT_TRUE(TestParse(input));
387
388 Style* style = test::GetValue<Style>(&table_, "style/foo");
389 ASSERT_NE(nullptr, style);
390 AAPT_ASSERT_TRUE(style->parent);
391 AAPT_ASSERT_TRUE(style->parent.value().name);
392 EXPECT_EQ(test::ParseNameOrDie("style/fu"),
393 style->parent.value().name.value());
394 ASSERT_EQ(3u, style->entries.size());
395
396 AAPT_ASSERT_TRUE(style->entries[0].key.name);
397 EXPECT_EQ(test::ParseNameOrDie("attr/bar"),
398 style->entries[0].key.name.value());
399
400 AAPT_ASSERT_TRUE(style->entries[1].key.name);
401 EXPECT_EQ(test::ParseNameOrDie("attr/bat"),
402 style->entries[1].key.name.value());
403
404 AAPT_ASSERT_TRUE(style->entries[2].key.name);
405 EXPECT_EQ(test::ParseNameOrDie("attr/baz"),
406 style->entries[2].key.name.value());
407 }
408
TEST_F(ResourceParserTest,ParseStyleWithShorthandParent)409 TEST_F(ResourceParserTest, ParseStyleWithShorthandParent) {
410 std::string input = "<style name=\"foo\" parent=\"com.app:Theme\"/>";
411 ASSERT_TRUE(TestParse(input));
412
413 Style* style = test::GetValue<Style>(&table_, "style/foo");
414 ASSERT_NE(nullptr, style);
415 AAPT_ASSERT_TRUE(style->parent);
416 AAPT_ASSERT_TRUE(style->parent.value().name);
417 EXPECT_EQ(test::ParseNameOrDie("com.app:style/Theme"),
418 style->parent.value().name.value());
419 }
420
TEST_F(ResourceParserTest,ParseStyleWithPackageAliasedParent)421 TEST_F(ResourceParserTest, ParseStyleWithPackageAliasedParent) {
422 std::string input =
423 "<style xmlns:app=\"http://schemas.android.com/apk/res/android\"\n"
424 " name=\"foo\" parent=\"app:Theme\"/>";
425 ASSERT_TRUE(TestParse(input));
426
427 Style* style = test::GetValue<Style>(&table_, "style/foo");
428 ASSERT_NE(nullptr, style);
429 AAPT_ASSERT_TRUE(style->parent);
430 AAPT_ASSERT_TRUE(style->parent.value().name);
431 EXPECT_EQ(test::ParseNameOrDie("android:style/Theme"),
432 style->parent.value().name.value());
433 }
434
TEST_F(ResourceParserTest,ParseStyleWithPackageAliasedItems)435 TEST_F(ResourceParserTest, ParseStyleWithPackageAliasedItems) {
436 std::string input =
437 "<style xmlns:app=\"http://schemas.android.com/apk/res/android\" "
438 "name=\"foo\">\n"
439 " <item name=\"app:bar\">0</item>\n"
440 "</style>";
441 ASSERT_TRUE(TestParse(input));
442
443 Style* style = test::GetValue<Style>(&table_, "style/foo");
444 ASSERT_NE(nullptr, style);
445 ASSERT_EQ(1u, style->entries.size());
446 EXPECT_EQ(test::ParseNameOrDie("android:attr/bar"),
447 style->entries[0].key.name.value());
448 }
449
TEST_F(ResourceParserTest,ParseStyleWithInferredParent)450 TEST_F(ResourceParserTest, ParseStyleWithInferredParent) {
451 std::string input = "<style name=\"foo.bar\"/>";
452 ASSERT_TRUE(TestParse(input));
453
454 Style* style = test::GetValue<Style>(&table_, "style/foo.bar");
455 ASSERT_NE(nullptr, style);
456 AAPT_ASSERT_TRUE(style->parent);
457 AAPT_ASSERT_TRUE(style->parent.value().name);
458 EXPECT_EQ(style->parent.value().name.value(),
459 test::ParseNameOrDie("style/foo"));
460 EXPECT_TRUE(style->parent_inferred);
461 }
462
TEST_F(ResourceParserTest,ParseStyleWithInferredParentOverridenByEmptyParentAttribute)463 TEST_F(ResourceParserTest,
464 ParseStyleWithInferredParentOverridenByEmptyParentAttribute) {
465 std::string input = "<style name=\"foo.bar\" parent=\"\"/>";
466 ASSERT_TRUE(TestParse(input));
467
468 Style* style = test::GetValue<Style>(&table_, "style/foo.bar");
469 ASSERT_NE(nullptr, style);
470 AAPT_EXPECT_FALSE(style->parent);
471 EXPECT_FALSE(style->parent_inferred);
472 }
473
TEST_F(ResourceParserTest,ParseStyleWithPrivateParentReference)474 TEST_F(ResourceParserTest, ParseStyleWithPrivateParentReference) {
475 std::string input =
476 R"EOF(<style name="foo" parent="*android:style/bar" />)EOF";
477 ASSERT_TRUE(TestParse(input));
478
479 Style* style = test::GetValue<Style>(&table_, "style/foo");
480 ASSERT_NE(nullptr, style);
481 AAPT_ASSERT_TRUE(style->parent);
482 EXPECT_TRUE(style->parent.value().private_reference);
483 }
484
TEST_F(ResourceParserTest,ParseAutoGeneratedIdReference)485 TEST_F(ResourceParserTest, ParseAutoGeneratedIdReference) {
486 std::string input = "<string name=\"foo\">@+id/bar</string>";
487 ASSERT_TRUE(TestParse(input));
488
489 Id* id = test::GetValue<Id>(&table_, "id/bar");
490 ASSERT_NE(id, nullptr);
491 }
492
TEST_F(ResourceParserTest,ParseAttributesDeclareStyleable)493 TEST_F(ResourceParserTest, ParseAttributesDeclareStyleable) {
494 std::string input =
495 "<declare-styleable name=\"foo\">\n"
496 " <attr name=\"bar\" />\n"
497 " <attr name=\"bat\" format=\"string|reference\"/>\n"
498 " <attr name=\"baz\">\n"
499 " <enum name=\"foo\" value=\"1\"/>\n"
500 " </attr>\n"
501 "</declare-styleable>";
502 ASSERT_TRUE(TestParse(input));
503
504 Maybe<ResourceTable::SearchResult> result =
505 table_.FindResource(test::ParseNameOrDie("styleable/foo"));
506 AAPT_ASSERT_TRUE(result);
507 EXPECT_EQ(SymbolState::kPublic, result.value().entry->symbol_status.state);
508
509 Attribute* attr = test::GetValue<Attribute>(&table_, "attr/bar");
510 ASSERT_NE(attr, nullptr);
511 EXPECT_TRUE(attr->IsWeak());
512
513 attr = test::GetValue<Attribute>(&table_, "attr/bat");
514 ASSERT_NE(attr, nullptr);
515 EXPECT_TRUE(attr->IsWeak());
516
517 attr = test::GetValue<Attribute>(&table_, "attr/baz");
518 ASSERT_NE(attr, nullptr);
519 EXPECT_TRUE(attr->IsWeak());
520 EXPECT_EQ(1u, attr->symbols.size());
521
522 EXPECT_NE(nullptr, test::GetValue<Id>(&table_, "id/foo"));
523
524 Styleable* styleable = test::GetValue<Styleable>(&table_, "styleable/foo");
525 ASSERT_NE(styleable, nullptr);
526 ASSERT_EQ(3u, styleable->entries.size());
527
528 EXPECT_EQ(test::ParseNameOrDie("attr/bar"),
529 styleable->entries[0].name.value());
530 EXPECT_EQ(test::ParseNameOrDie("attr/bat"),
531 styleable->entries[1].name.value());
532 }
533
TEST_F(ResourceParserTest,ParsePrivateAttributesDeclareStyleable)534 TEST_F(ResourceParserTest, ParsePrivateAttributesDeclareStyleable) {
535 std::string input =
536 "<declare-styleable name=\"foo\" "
537 "xmlns:privAndroid=\"http://schemas.android.com/apk/prv/res/android\">\n"
538 " <attr name=\"*android:bar\" />\n"
539 " <attr name=\"privAndroid:bat\" />\n"
540 "</declare-styleable>";
541 ASSERT_TRUE(TestParse(input));
542 Styleable* styleable = test::GetValue<Styleable>(&table_, "styleable/foo");
543 ASSERT_NE(nullptr, styleable);
544 ASSERT_EQ(2u, styleable->entries.size());
545
546 EXPECT_TRUE(styleable->entries[0].private_reference);
547 AAPT_ASSERT_TRUE(styleable->entries[0].name);
548 EXPECT_EQ(std::string("android"), styleable->entries[0].name.value().package);
549
550 EXPECT_TRUE(styleable->entries[1].private_reference);
551 AAPT_ASSERT_TRUE(styleable->entries[1].name);
552 EXPECT_EQ(std::string("android"), styleable->entries[1].name.value().package);
553 }
554
TEST_F(ResourceParserTest,ParseArray)555 TEST_F(ResourceParserTest, ParseArray) {
556 std::string input =
557 "<array name=\"foo\">\n"
558 " <item>@string/ref</item>\n"
559 " <item>hey</item>\n"
560 " <item>23</item>\n"
561 "</array>";
562 ASSERT_TRUE(TestParse(input));
563
564 Array* array = test::GetValue<Array>(&table_, "array/foo");
565 ASSERT_NE(array, nullptr);
566 ASSERT_EQ(3u, array->items.size());
567
568 EXPECT_NE(nullptr, ValueCast<Reference>(array->items[0].get()));
569 EXPECT_NE(nullptr, ValueCast<String>(array->items[1].get()));
570 EXPECT_NE(nullptr, ValueCast<BinaryPrimitive>(array->items[2].get()));
571 }
572
TEST_F(ResourceParserTest,ParseStringArray)573 TEST_F(ResourceParserTest, ParseStringArray) {
574 std::string input = R"EOF(
575 <string-array name="foo">
576 <item>"Werk"</item>"
577 </string-array>)EOF";
578 ASSERT_TRUE(TestParse(input));
579 EXPECT_NE(nullptr, test::GetValue<Array>(&table_, "array/foo"));
580 }
581
582 TEST_F(ResourceParserTest, ParseArrayWithFormat) {
583 std::string input = R"EOF(
584 <array name="foo" format="string">
585 <item>100</item>
586 </array>)EOF";
587 ASSERT_TRUE(TestParse(input));
588
589 Array* array = test::GetValue<Array>(&table_, "array/foo");
590 ASSERT_NE(nullptr, array);
591
592 ASSERT_EQ(1u, array->items.size());
593
594 String* str = ValueCast<String>(array->items[0].get());
595 ASSERT_NE(nullptr, str);
596 EXPECT_EQ(std::string("100"), *str->value);
597 }
598
599 TEST_F(ResourceParserTest, ParseArrayWithBadFormat) {
600 std::string input = R"EOF(
601 <array name="foo" format="integer">
602 <item>Hi</item>
603 </array>)EOF";
604 ASSERT_FALSE(TestParse(input));
605 }
606
607 TEST_F(ResourceParserTest, ParsePlural) {
608 std::string input =
609 "<plurals name=\"foo\">\n"
610 " <item quantity=\"other\">apples</item>\n"
611 " <item quantity=\"one\">apple</item>\n"
612 "</plurals>";
613 ASSERT_TRUE(TestParse(input));
614
615 Plural* plural = test::GetValue<Plural>(&table_, "plurals/foo");
616 ASSERT_NE(nullptr, plural);
617 EXPECT_EQ(nullptr, plural->values[Plural::Zero]);
618 EXPECT_EQ(nullptr, plural->values[Plural::Two]);
619 EXPECT_EQ(nullptr, plural->values[Plural::Few]);
620 EXPECT_EQ(nullptr, plural->values[Plural::Many]);
621
622 EXPECT_NE(nullptr, plural->values[Plural::One]);
623 EXPECT_NE(nullptr, plural->values[Plural::Other]);
624 }
625
TEST_F(ResourceParserTest,ParseCommentsWithResource)626 TEST_F(ResourceParserTest, ParseCommentsWithResource) {
627 std::string input =
628 "<!--This is a comment-->\n"
629 "<string name=\"foo\">Hi</string>";
630 ASSERT_TRUE(TestParse(input));
631
632 String* value = test::GetValue<String>(&table_, "string/foo");
633 ASSERT_NE(nullptr, value);
634 EXPECT_EQ(value->GetComment(), "This is a comment");
635 }
636
TEST_F(ResourceParserTest,DoNotCombineMultipleComments)637 TEST_F(ResourceParserTest, DoNotCombineMultipleComments) {
638 std::string input =
639 "<!--One-->\n"
640 "<!--Two-->\n"
641 "<string name=\"foo\">Hi</string>";
642
643 ASSERT_TRUE(TestParse(input));
644
645 String* value = test::GetValue<String>(&table_, "string/foo");
646 ASSERT_NE(nullptr, value);
647 EXPECT_EQ(value->GetComment(), "Two");
648 }
649
TEST_F(ResourceParserTest,IgnoreCommentBeforeEndTag)650 TEST_F(ResourceParserTest, IgnoreCommentBeforeEndTag) {
651 std::string input =
652 "<!--One-->\n"
653 "<string name=\"foo\">\n"
654 " Hi\n"
655 "<!--Two-->\n"
656 "</string>";
657
658 ASSERT_TRUE(TestParse(input));
659
660 String* value = test::GetValue<String>(&table_, "string/foo");
661 ASSERT_NE(nullptr, value);
662 EXPECT_EQ(value->GetComment(), "One");
663 }
664
TEST_F(ResourceParserTest,ParseNestedComments)665 TEST_F(ResourceParserTest, ParseNestedComments) {
666 // We only care about declare-styleable and enum/flag attributes because
667 // comments
668 // from those end up in R.java
669 std::string input = R"EOF(
670 <declare-styleable name="foo">
671 <!-- The name of the bar -->
672 <attr name="barName" format="string|reference" />
673 </declare-styleable>
674
675 <attr name="foo">
676 <!-- The very first -->
677 <enum name="one" value="1" />
678 </attr>)EOF";
679 ASSERT_TRUE(TestParse(input));
680
681 Styleable* styleable = test::GetValue<Styleable>(&table_, "styleable/foo");
682 ASSERT_NE(nullptr, styleable);
683 ASSERT_EQ(1u, styleable->entries.size());
684
685 EXPECT_EQ(StringPiece("The name of the bar"),
686 styleable->entries.front().GetComment());
687
688 Attribute* attr = test::GetValue<Attribute>(&table_, "attr/foo");
689 ASSERT_NE(nullptr, attr);
690 ASSERT_EQ(1u, attr->symbols.size());
691
692 EXPECT_EQ(StringPiece("The very first"),
693 attr->symbols.front().symbol.GetComment());
694 }
695
696 /*
697 * Declaring an ID as public should not require a separate definition
698 * (as an ID has no value).
699 */
TEST_F(ResourceParserTest,ParsePublicIdAsDefinition)700 TEST_F(ResourceParserTest, ParsePublicIdAsDefinition) {
701 std::string input = "<public type=\"id\" name=\"foo\"/>";
702 ASSERT_TRUE(TestParse(input));
703
704 Id* id = test::GetValue<Id>(&table_, "id/foo");
705 ASSERT_NE(nullptr, id);
706 }
707
TEST_F(ResourceParserTest,KeepAllProducts)708 TEST_F(ResourceParserTest, KeepAllProducts) {
709 std::string input = R"EOF(
710 <string name="foo" product="phone">hi</string>
711 <string name="foo" product="no-sdcard">ho</string>
712 <string name="bar" product="">wee</string>
713 <string name="baz">woo</string>
714 <string name="bit" product="phablet">hoot</string>
715 <string name="bot" product="default">yes</string>
716 )EOF";
717 ASSERT_TRUE(TestParse(input));
718
719 EXPECT_NE(nullptr, test::GetValueForConfigAndProduct<String>(
720 &table_, "string/foo",
721 ConfigDescription::DefaultConfig(), "phone"));
722 EXPECT_NE(nullptr, test::GetValueForConfigAndProduct<String>(
723 &table_, "string/foo",
724 ConfigDescription::DefaultConfig(), "no-sdcard"));
725 EXPECT_NE(nullptr,
726 test::GetValueForConfigAndProduct<String>(
727 &table_, "string/bar", ConfigDescription::DefaultConfig(), ""));
728 EXPECT_NE(nullptr,
729 test::GetValueForConfigAndProduct<String>(
730 &table_, "string/baz", ConfigDescription::DefaultConfig(), ""));
731 EXPECT_NE(nullptr, test::GetValueForConfigAndProduct<String>(
732 &table_, "string/bit",
733 ConfigDescription::DefaultConfig(), "phablet"));
734 EXPECT_NE(nullptr, test::GetValueForConfigAndProduct<String>(
735 &table_, "string/bot",
736 ConfigDescription::DefaultConfig(), "default"));
737 }
738
TEST_F(ResourceParserTest,AutoIncrementIdsInPublicGroup)739 TEST_F(ResourceParserTest, AutoIncrementIdsInPublicGroup) {
740 std::string input = R"EOF(
741 <public-group type="attr" first-id="0x01010040">
742 <public name="foo" />
743 <public name="bar" />
744 </public-group>)EOF";
745 ASSERT_TRUE(TestParse(input));
746
747 Maybe<ResourceTable::SearchResult> result =
748 table_.FindResource(test::ParseNameOrDie("attr/foo"));
749 AAPT_ASSERT_TRUE(result);
750
751 AAPT_ASSERT_TRUE(result.value().package->id);
752 AAPT_ASSERT_TRUE(result.value().type->id);
753 AAPT_ASSERT_TRUE(result.value().entry->id);
754 ResourceId actual_id(result.value().package->id.value(),
755 result.value().type->id.value(),
756 result.value().entry->id.value());
757 EXPECT_EQ(ResourceId(0x01010040), actual_id);
758
759 result = table_.FindResource(test::ParseNameOrDie("attr/bar"));
760 AAPT_ASSERT_TRUE(result);
761
762 AAPT_ASSERT_TRUE(result.value().package->id);
763 AAPT_ASSERT_TRUE(result.value().type->id);
764 AAPT_ASSERT_TRUE(result.value().entry->id);
765 actual_id = ResourceId(result.value().package->id.value(),
766 result.value().type->id.value(),
767 result.value().entry->id.value());
768 EXPECT_EQ(ResourceId(0x01010041), actual_id);
769 }
770
TEST_F(ResourceParserTest,ExternalTypesShouldOnlyBeReferences)771 TEST_F(ResourceParserTest, ExternalTypesShouldOnlyBeReferences) {
772 std::string input =
773 R"EOF(<item type="layout" name="foo">@layout/bar</item>)EOF";
774 ASSERT_TRUE(TestParse(input));
775
776 input = R"EOF(<item type="layout" name="bar">"this is a string"</item>)EOF";
777 ASSERT_FALSE(TestParse(input));
778 }
779
TEST_F(ResourceParserTest,AddResourcesElementShouldAddEntryWithUndefinedSymbol)780 TEST_F(ResourceParserTest, AddResourcesElementShouldAddEntryWithUndefinedSymbol) {
781 std::string input = R"EOF(<add-resource name="bar" type="string" />)EOF";
782 ASSERT_TRUE(TestParse(input));
783
784 Maybe<ResourceTable::SearchResult> result =
785 table_.FindResource(test::ParseNameOrDie("string/bar"));
786 AAPT_ASSERT_TRUE(result);
787 const ResourceEntry* entry = result.value().entry;
788 ASSERT_NE(nullptr, entry);
789 EXPECT_EQ(SymbolState::kUndefined, entry->symbol_status.state);
790 EXPECT_TRUE(entry->symbol_status.allow_new);
791 }
792
TEST_F(ResourceParserTest,ParseItemElementWithFormat)793 TEST_F(ResourceParserTest, ParseItemElementWithFormat) {
794 std::string input = R"(<item name="foo" type="integer" format="float">0.3</item>)";
795 ASSERT_TRUE(TestParse(input));
796
797 BinaryPrimitive* val = test::GetValue<BinaryPrimitive>(&table_, "integer/foo");
798 ASSERT_THAT(val, NotNull());
799 EXPECT_THAT(val->value.dataType, Eq(android::Res_value::TYPE_FLOAT));
800
801 input = R"(<item name="bar" type="integer" format="fraction">100</item>)";
802 ASSERT_FALSE(TestParse(input));
803 }
804
805 // An <item> without a format specifier accepts all types of values.
TEST_F(ResourceParserTest,ParseItemElementWithoutFormat)806 TEST_F(ResourceParserTest, ParseItemElementWithoutFormat) {
807 std::string input = R"(<item name="foo" type="integer">100%p</item>)";
808 ASSERT_TRUE(TestParse(input));
809
810 BinaryPrimitive* val = test::GetValue<BinaryPrimitive>(&table_, "integer/foo");
811 ASSERT_THAT(val, NotNull());
812 EXPECT_THAT(val->value.dataType, Eq(android::Res_value::TYPE_FRACTION));
813 }
814
TEST_F(ResourceParserTest,ParseConfigVaryingItem)815 TEST_F(ResourceParserTest, ParseConfigVaryingItem) {
816 std::string input = R"EOF(<item name="foo" type="configVarying">Hey</item>)EOF";
817 ASSERT_TRUE(TestParse(input));
818 ASSERT_NE(nullptr, test::GetValue<String>(&table_, "configVarying/foo"));
819 }
820
TEST_F(ResourceParserTest,ParseBagElement)821 TEST_F(ResourceParserTest, ParseBagElement) {
822 std::string input =
823 R"EOF(<bag name="bag" type="configVarying"><item name="test">Hello!</item></bag>)EOF";
824 ASSERT_TRUE(TestParse(input));
825
826 Style* val = test::GetValue<Style>(&table_, "configVarying/bag");
827 ASSERT_NE(nullptr, val);
828
829 ASSERT_EQ(1u, val->entries.size());
830 EXPECT_EQ(Reference(test::ParseNameOrDie("attr/test")), val->entries[0].key);
831 EXPECT_NE(nullptr, ValueCast<RawString>(val->entries[0].value.get()));
832 }
833
TEST_F(ResourceParserTest,ParseElementWithNoValue)834 TEST_F(ResourceParserTest, ParseElementWithNoValue) {
835 std::string input = R"(
836 <item type="drawable" format="reference" name="foo" />
837 <string name="foo" />)";
838 ASSERT_TRUE(TestParse(input));
839 ASSERT_THAT(test::GetValue(&table_, "drawable/foo"), Pointee(ValueEq(Reference())));
840
841 String* str = test::GetValue<String>(&table_, "string/foo");
842 ASSERT_THAT(str, NotNull());
843 EXPECT_THAT(*str->value, Eq(""));
844 }
845
846 } // namespace aapt
847