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