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#include "ResourceTable.h"
19#include "ResourceValues.h"
20#include "SourceXmlPullParser.h"
21
22#include <gtest/gtest.h>
23#include <sstream>
24#include <string>
25
26namespace aapt {
27
28constexpr const char* kXmlPreamble = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
29
30TEST(ResourceParserReferenceTest, ParseReferenceWithNoPackage) {
31    ResourceNameRef expected = { {}, ResourceType::kColor, u"foo" };
32    ResourceNameRef actual;
33    bool create = false;
34    bool privateRef = false;
35    EXPECT_TRUE(ResourceParser::tryParseReference(u"@color/foo", &actual, &create, &privateRef));
36    EXPECT_EQ(expected, actual);
37    EXPECT_FALSE(create);
38    EXPECT_FALSE(privateRef);
39}
40
41TEST(ResourceParserReferenceTest, ParseReferenceWithPackage) {
42    ResourceNameRef expected = { u"android", ResourceType::kColor, u"foo" };
43    ResourceNameRef actual;
44    bool create = false;
45    bool privateRef = false;
46    EXPECT_TRUE(ResourceParser::tryParseReference(u"@android:color/foo", &actual, &create,
47                                                  &privateRef));
48    EXPECT_EQ(expected, actual);
49    EXPECT_FALSE(create);
50    EXPECT_FALSE(privateRef);
51}
52
53TEST(ResourceParserReferenceTest, ParseReferenceWithSurroundingWhitespace) {
54    ResourceNameRef expected = { u"android", ResourceType::kColor, u"foo" };
55    ResourceNameRef actual;
56    bool create = false;
57    bool privateRef = false;
58    EXPECT_TRUE(ResourceParser::tryParseReference(u"\t @android:color/foo\n \n\t", &actual,
59                                                  &create, &privateRef));
60    EXPECT_EQ(expected, actual);
61    EXPECT_FALSE(create);
62    EXPECT_FALSE(privateRef);
63}
64
65TEST(ResourceParserReferenceTest, ParseAutoCreateIdReference) {
66    ResourceNameRef expected = { u"android", ResourceType::kId, u"foo" };
67    ResourceNameRef actual;
68    bool create = false;
69    bool privateRef = false;
70    EXPECT_TRUE(ResourceParser::tryParseReference(u"@+android:id/foo", &actual, &create,
71                                                  &privateRef));
72    EXPECT_EQ(expected, actual);
73    EXPECT_TRUE(create);
74    EXPECT_FALSE(privateRef);
75}
76
77TEST(ResourceParserReferenceTest, ParsePrivateReference) {
78    ResourceNameRef expected = { u"android", ResourceType::kId, u"foo" };
79    ResourceNameRef actual;
80    bool create = false;
81    bool privateRef = false;
82    EXPECT_TRUE(ResourceParser::tryParseReference(u"@*android:id/foo", &actual, &create,
83                                                  &privateRef));
84    EXPECT_EQ(expected, actual);
85    EXPECT_FALSE(create);
86    EXPECT_TRUE(privateRef);
87}
88
89TEST(ResourceParserReferenceTest, FailToParseAutoCreateNonIdReference) {
90    bool create = false;
91    bool privateRef = false;
92    ResourceNameRef actual;
93    EXPECT_FALSE(ResourceParser::tryParseReference(u"@+android:color/foo", &actual, &create,
94                                                   &privateRef));
95}
96
97TEST(ResourceParserReferenceTest, ParseStyleParentReference) {
98    Reference ref;
99    std::string errStr;
100    EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"@android:style/foo", &ref, &errStr));
101    EXPECT_EQ(ref.name, (ResourceName{ u"android", ResourceType::kStyle, u"foo" }));
102
103    EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"@style/foo", &ref, &errStr));
104    EXPECT_EQ(ref.name, (ResourceName{ {}, ResourceType::kStyle, u"foo" }));
105
106    EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"?android:style/foo", &ref, &errStr));
107    EXPECT_EQ(ref.name, (ResourceName{ u"android", ResourceType::kStyle, u"foo" }));
108
109    EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"?style/foo", &ref, &errStr));
110    EXPECT_EQ(ref.name, (ResourceName{ {}, ResourceType::kStyle, u"foo" }));
111
112    EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"android:style/foo", &ref, &errStr));
113    EXPECT_EQ(ref.name, (ResourceName{ u"android", ResourceType::kStyle, u"foo" }));
114
115    EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"android:foo", &ref, &errStr));
116    EXPECT_EQ(ref.name, (ResourceName{ u"android", ResourceType::kStyle, u"foo" }));
117
118    EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"foo", &ref, &errStr));
119    EXPECT_EQ(ref.name, (ResourceName{ {}, ResourceType::kStyle, u"foo" }));
120}
121
122struct ResourceParserTest : public ::testing::Test {
123    virtual void SetUp() override {
124        mTable = std::make_shared<ResourceTable>();
125        mTable->setPackage(u"android");
126    }
127
128    ::testing::AssertionResult testParse(const StringPiece& str) {
129        std::stringstream input(kXmlPreamble);
130        input << "<resources>\n" << str << "\n</resources>" << std::endl;
131        ResourceParser parser(mTable, Source{ "test" }, {},
132                              std::make_shared<SourceXmlPullParser>(input));
133        if (parser.parse()) {
134            return ::testing::AssertionSuccess();
135        }
136        return ::testing::AssertionFailure();
137    }
138
139    template <typename T>
140    const T* findResource(const ResourceNameRef& name, const ConfigDescription& config) {
141        using std::begin;
142        using std::end;
143
144        const ResourceTableType* type;
145        const ResourceEntry* entry;
146        std::tie(type, entry) = mTable->findResource(name);
147        if (!type || !entry) {
148            return nullptr;
149        }
150
151        for (const auto& configValue : entry->values) {
152            if (configValue.config == config) {
153                return dynamic_cast<const T*>(configValue.value.get());
154            }
155        }
156        return nullptr;
157    }
158
159    template <typename T>
160    const T* findResource(const ResourceNameRef& name) {
161        return findResource<T>(name, {});
162    }
163
164    std::shared_ptr<ResourceTable> mTable;
165};
166
167TEST_F(ResourceParserTest, FailToParseWithNoRootResourcesElement) {
168    std::stringstream input(kXmlPreamble);
169    input << "<attr name=\"foo\"/>" << std::endl;
170    ResourceParser parser(mTable, {}, {}, std::make_shared<SourceXmlPullParser>(input));
171    ASSERT_FALSE(parser.parse());
172}
173
174TEST_F(ResourceParserTest, ParseQuotedString) {
175    std::string input = "<string name=\"foo\">   \"  hey there \" </string>";
176    ASSERT_TRUE(testParse(input));
177
178    const String* str = findResource<String>(ResourceName{
179            u"android", ResourceType::kString, u"foo"});
180    ASSERT_NE(nullptr, str);
181    EXPECT_EQ(std::u16string(u"  hey there "), *str->value);
182}
183
184TEST_F(ResourceParserTest, ParseEscapedString) {
185    std::string input = "<string name=\"foo\">\\?123</string>";
186    ASSERT_TRUE(testParse(input));
187
188    const String* str = findResource<String>(ResourceName{
189            u"android", ResourceType::kString, u"foo" });
190    ASSERT_NE(nullptr, str);
191    EXPECT_EQ(std::u16string(u"?123"), *str->value);
192}
193
194TEST_F(ResourceParserTest, ParseNull) {
195    std::string input = "<integer name=\"foo\">@null</integer>";
196    ASSERT_TRUE(testParse(input));
197
198    // The Android runtime treats a value of android::Res_value::TYPE_NULL as
199    // a non-existing value, and this causes problems in styles when trying to resolve
200    // an attribute. Null values must be encoded as android::Res_value::TYPE_REFERENCE
201    // with a data value of 0.
202    const BinaryPrimitive* integer = findResource<BinaryPrimitive>(ResourceName{
203            u"android", ResourceType::kInteger, u"foo" });
204    ASSERT_NE(nullptr, integer);
205    EXPECT_EQ(uint16_t(android::Res_value::TYPE_REFERENCE), integer->value.dataType);
206    EXPECT_EQ(0u, integer->value.data);
207}
208
209TEST_F(ResourceParserTest, ParseEmpty) {
210    std::string input = "<integer name=\"foo\">@empty</integer>";
211    ASSERT_TRUE(testParse(input));
212
213    const BinaryPrimitive* integer = findResource<BinaryPrimitive>(ResourceName{
214            u"android", ResourceType::kInteger, u"foo" });
215    ASSERT_NE(nullptr, integer);
216    EXPECT_EQ(uint16_t(android::Res_value::TYPE_NULL), integer->value.dataType);
217    EXPECT_EQ(uint32_t(android::Res_value::DATA_NULL_EMPTY), integer->value.data);
218}
219
220TEST_F(ResourceParserTest, ParseAttr) {
221    std::string input = "<attr name=\"foo\" format=\"string\"/>\n"
222                        "<attr name=\"bar\"/>";
223    ASSERT_TRUE(testParse(input));
224
225    const Attribute* attr = findResource<Attribute>(ResourceName{
226            u"android", ResourceType::kAttr, u"foo"});
227    EXPECT_NE(nullptr, attr);
228    EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_STRING), attr->typeMask);
229
230    attr = findResource<Attribute>(ResourceName{
231            u"android", ResourceType::kAttr, u"bar"});
232    EXPECT_NE(nullptr, attr);
233    EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_ANY), attr->typeMask);
234}
235
236TEST_F(ResourceParserTest, ParseUseAndDeclOfAttr) {
237    std::string input = "<declare-styleable name=\"Styleable\">\n"
238                        "  <attr name=\"foo\" />\n"
239                        "</declare-styleable>\n"
240                        "<attr name=\"foo\" format=\"string\"/>";
241    ASSERT_TRUE(testParse(input));
242
243    const Attribute* attr = findResource<Attribute>(ResourceName{
244            u"android", ResourceType::kAttr, u"foo"});
245    ASSERT_NE(nullptr, attr);
246    EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_STRING), attr->typeMask);
247}
248
249TEST_F(ResourceParserTest, ParseDoubleUseOfAttr) {
250    std::string input = "<declare-styleable name=\"Theme\">"
251                        "  <attr name=\"foo\" />\n"
252                        "</declare-styleable>\n"
253                        "<declare-styleable name=\"Window\">\n"
254                        "  <attr name=\"foo\" format=\"boolean\"/>\n"
255                        "</declare-styleable>";
256    ASSERT_TRUE(testParse(input));
257
258    const Attribute* attr = findResource<Attribute>(ResourceName{
259            u"android", ResourceType::kAttr, u"foo"});
260    ASSERT_NE(nullptr, attr);
261    EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_BOOLEAN), attr->typeMask);
262}
263
264TEST_F(ResourceParserTest, ParseEnumAttr) {
265    std::string input = "<attr name=\"foo\">\n"
266                        "  <enum name=\"bar\" value=\"0\"/>\n"
267                        "  <enum name=\"bat\" value=\"1\"/>\n"
268                        "  <enum name=\"baz\" value=\"2\"/>\n"
269                        "</attr>";
270    ASSERT_TRUE(testParse(input));
271
272    const Attribute* enumAttr = findResource<Attribute>(ResourceName{
273            u"android", ResourceType::kAttr, u"foo"});
274    ASSERT_NE(enumAttr, nullptr);
275    EXPECT_EQ(enumAttr->typeMask, android::ResTable_map::TYPE_ENUM);
276    ASSERT_EQ(enumAttr->symbols.size(), 3u);
277
278    EXPECT_EQ(enumAttr->symbols[0].symbol.name.entry, u"bar");
279    EXPECT_EQ(enumAttr->symbols[0].value, 0u);
280
281    EXPECT_EQ(enumAttr->symbols[1].symbol.name.entry, u"bat");
282    EXPECT_EQ(enumAttr->symbols[1].value, 1u);
283
284    EXPECT_EQ(enumAttr->symbols[2].symbol.name.entry, u"baz");
285    EXPECT_EQ(enumAttr->symbols[2].value, 2u);
286}
287
288TEST_F(ResourceParserTest, ParseFlagAttr) {
289    std::string input = "<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    const Attribute* flagAttr = findResource<Attribute>(ResourceName{
297            u"android", ResourceType::kAttr, u"foo"});
298    ASSERT_NE(flagAttr, nullptr);
299    EXPECT_EQ(flagAttr->typeMask, android::ResTable_map::TYPE_FLAGS);
300    ASSERT_EQ(flagAttr->symbols.size(), 3u);
301
302    EXPECT_EQ(flagAttr->symbols[0].symbol.name.entry, u"bar");
303    EXPECT_EQ(flagAttr->symbols[0].value, 0u);
304
305    EXPECT_EQ(flagAttr->symbols[1].symbol.name.entry, u"bat");
306    EXPECT_EQ(flagAttr->symbols[1].value, 1u);
307
308    EXPECT_EQ(flagAttr->symbols[2].symbol.name.entry, u"baz");
309    EXPECT_EQ(flagAttr->symbols[2].value, 2u);
310
311    std::unique_ptr<BinaryPrimitive> flagValue =
312            ResourceParser::tryParseFlagSymbol(*flagAttr, u"baz|bat");
313    ASSERT_NE(flagValue, nullptr);
314    EXPECT_EQ(flagValue->value.data, 1u | 2u);
315}
316
317TEST_F(ResourceParserTest, FailToParseEnumAttrWithNonUniqueKeys) {
318    std::string input = "<attr name=\"foo\">\n"
319                        "  <enum name=\"bar\" value=\"0\"/>\n"
320                        "  <enum name=\"bat\" value=\"1\"/>\n"
321                        "  <enum name=\"bat\" value=\"2\"/>\n"
322                        "</attr>";
323    ASSERT_FALSE(testParse(input));
324}
325
326TEST_F(ResourceParserTest, ParseStyle) {
327    std::string input = "<style name=\"foo\" parent=\"@style/fu\">\n"
328                        "  <item name=\"bar\">#ffffffff</item>\n"
329                        "  <item name=\"bat\">@string/hey</item>\n"
330                        "  <item name=\"baz\"><b>hey</b></item>\n"
331                        "</style>";
332    ASSERT_TRUE(testParse(input));
333
334    const Style* style = findResource<Style>(ResourceName{
335            u"android", ResourceType::kStyle, u"foo"});
336    ASSERT_NE(style, nullptr);
337    EXPECT_EQ(ResourceNameRef(u"android", ResourceType::kStyle, u"fu"), style->parent.name);
338    ASSERT_EQ(style->entries.size(), 3u);
339
340    EXPECT_EQ(style->entries[0].key.name,
341              (ResourceName{ u"android", ResourceType::kAttr, u"bar" }));
342    EXPECT_EQ(style->entries[1].key.name,
343              (ResourceName{ u"android", ResourceType::kAttr, u"bat" }));
344    EXPECT_EQ(style->entries[2].key.name,
345              (ResourceName{ u"android", ResourceType::kAttr, u"baz" }));
346}
347
348TEST_F(ResourceParserTest, ParseStyleWithShorthandParent) {
349    std::string input = "<style name=\"foo\" parent=\"com.app:Theme\"/>";
350    ASSERT_TRUE(testParse(input));
351
352    const Style* style = findResource<Style>(
353            ResourceName{ u"android", ResourceType::kStyle, u"foo" });
354    ASSERT_NE(style, nullptr);
355    EXPECT_EQ(ResourceNameRef(u"com.app", ResourceType::kStyle, u"Theme"), style->parent.name);
356}
357
358TEST_F(ResourceParserTest, ParseStyleWithPackageAliasedParent) {
359    std::string input = "<style xmlns:app=\"http://schemas.android.com/apk/res/android\"\n"
360                        "       name=\"foo\" parent=\"app:Theme\"/>";
361    ASSERT_TRUE(testParse(input));
362
363    const Style* style = findResource<Style>(ResourceName{
364            u"android", ResourceType::kStyle, u"foo" });
365    ASSERT_NE(style, nullptr);
366    EXPECT_EQ(ResourceNameRef(u"android", ResourceType::kStyle, u"Theme"), style->parent.name);
367}
368
369TEST_F(ResourceParserTest, ParseStyleWithPackageAliasedItems) {
370    std::string input =
371            "<style xmlns:app=\"http://schemas.android.com/apk/res/android\" name=\"foo\">\n"
372            "  <item name=\"app:bar\">0</item>\n"
373            "</style>";
374    ASSERT_TRUE(testParse(input));
375
376    const Style* style = findResource<Style>(ResourceName{
377            u"android", ResourceType::kStyle, u"foo" });
378    ASSERT_NE(style, nullptr);
379    ASSERT_EQ(1u, style->entries.size());
380    EXPECT_EQ(ResourceNameRef(u"android", ResourceType::kAttr, u"bar"),
381              style->entries[0].key.name);
382}
383
384TEST_F(ResourceParserTest, ParseStyleWithInferredParent) {
385    std::string input = "<style name=\"foo.bar\"/>";
386    ASSERT_TRUE(testParse(input));
387
388    const Style* style = findResource<Style>(ResourceName{
389            u"android", ResourceType::kStyle, u"foo.bar" });
390    ASSERT_NE(style, nullptr);
391    EXPECT_EQ(style->parent.name, (ResourceName{ u"android", ResourceType::kStyle, u"foo" }));
392    EXPECT_TRUE(style->parentInferred);
393}
394
395TEST_F(ResourceParserTest, ParseStyleWithInferredParentOverridenByEmptyParentAttribute) {
396    std::string input = "<style name=\"foo.bar\" parent=\"\"/>";
397    ASSERT_TRUE(testParse(input));
398
399    const Style* style = findResource<Style>(ResourceName{
400            u"android", ResourceType::kStyle, u"foo.bar" });
401    ASSERT_NE(style, nullptr);
402    EXPECT_FALSE(style->parent.name.isValid());
403    EXPECT_FALSE(style->parentInferred);
404}
405
406TEST_F(ResourceParserTest, ParseAutoGeneratedIdReference) {
407    std::string input = "<string name=\"foo\">@+id/bar</string>";
408    ASSERT_TRUE(testParse(input));
409
410    const Id* id = findResource<Id>(ResourceName{ u"android", ResourceType::kId, u"bar"});
411    ASSERT_NE(id, nullptr);
412}
413
414TEST_F(ResourceParserTest, ParseAttributesDeclareStyleable) {
415    std::string input = "<declare-styleable name=\"foo\">\n"
416                        "  <attr name=\"bar\" />\n"
417                        "  <attr name=\"bat\" format=\"string|reference\"/>\n"
418                        "</declare-styleable>";
419    ASSERT_TRUE(testParse(input));
420
421    const Attribute* attr = findResource<Attribute>(ResourceName{
422            u"android", ResourceType::kAttr, u"bar"});
423    ASSERT_NE(attr, nullptr);
424    EXPECT_TRUE(attr->isWeak());
425
426    attr = findResource<Attribute>(ResourceName{ u"android", ResourceType::kAttr, u"bat"});
427    ASSERT_NE(attr, nullptr);
428    EXPECT_TRUE(attr->isWeak());
429
430    const Styleable* styleable = findResource<Styleable>(ResourceName{
431            u"android", ResourceType::kStyleable, u"foo" });
432    ASSERT_NE(styleable, nullptr);
433    ASSERT_EQ(2u, styleable->entries.size());
434
435    EXPECT_EQ((ResourceName{u"android", ResourceType::kAttr, u"bar"}), styleable->entries[0].name);
436    EXPECT_EQ((ResourceName{u"android", ResourceType::kAttr, u"bat"}), styleable->entries[1].name);
437}
438
439TEST_F(ResourceParserTest, ParseArray) {
440    std::string input = "<array name=\"foo\">\n"
441                        "  <item>@string/ref</item>\n"
442                        "  <item>hey</item>\n"
443                        "  <item>23</item>\n"
444                        "</array>";
445    ASSERT_TRUE(testParse(input));
446
447    const Array* array = findResource<Array>(ResourceName{
448            u"android", ResourceType::kArray, u"foo" });
449    ASSERT_NE(array, nullptr);
450    ASSERT_EQ(3u, array->items.size());
451
452    EXPECT_NE(nullptr, dynamic_cast<const Reference*>(array->items[0].get()));
453    EXPECT_NE(nullptr, dynamic_cast<const String*>(array->items[1].get()));
454    EXPECT_NE(nullptr, dynamic_cast<const BinaryPrimitive*>(array->items[2].get()));
455}
456
457TEST_F(ResourceParserTest, ParsePlural) {
458    std::string input = "<plurals name=\"foo\">\n"
459                        "  <item quantity=\"other\">apples</item>\n"
460                        "  <item quantity=\"one\">apple</item>\n"
461                        "</plurals>";
462    ASSERT_TRUE(testParse(input));
463}
464
465TEST_F(ResourceParserTest, ParseCommentsWithResource) {
466    std::string input = "<!-- This is a comment -->\n"
467                        "<string name=\"foo\">Hi</string>";
468    ASSERT_TRUE(testParse(input));
469
470    const ResourceTableType* type;
471    const ResourceEntry* entry;
472    std::tie(type, entry) = mTable->findResource(ResourceName{
473            u"android", ResourceType::kString, u"foo"});
474    ASSERT_NE(type, nullptr);
475    ASSERT_NE(entry, nullptr);
476    ASSERT_FALSE(entry->values.empty());
477    EXPECT_EQ(entry->values.front().comment, u"This is a comment");
478}
479
480/*
481 * Declaring an ID as public should not require a separate definition
482 * (as an ID has no value).
483 */
484TEST_F(ResourceParserTest, ParsePublicIdAsDefinition) {
485    std::string input = "<public type=\"id\" name=\"foo\"/>";
486    ASSERT_TRUE(testParse(input));
487
488    const Id* id = findResource<Id>(ResourceName{ u"android", ResourceType::kId, u"foo" });
489    ASSERT_NE(nullptr, id);
490}
491
492} // namespace aapt
493