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 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: 50 void SetUp() override { 51 context_ = test::ContextBuilder().Build(); 52 } 53 54 ::testing::AssertionResult TestParse(const StringPiece& str) { 55 return TestParse(str, ConfigDescription{}); 56 } 57 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 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 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 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 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 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 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 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 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 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 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 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 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. 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 */ 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 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 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 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 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 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. 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 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 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 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