blob: 07f62afe05b981e9a67d0da084d99903f858f948 [file] [log] [blame]
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001/*
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
Adam Lesinskia6fe3452015-12-09 15:20:52 -080017#include "NameMangler.h"
Adam Lesinski1ab598f2015-08-14 14:26:04 -070018#include "ResourceUtils.h"
Adam Lesinski467f1712015-11-16 17:35:44 -080019#include "flatten/ResourceTypeExtensions.h"
Adam Lesinskia6fe3452015-12-09 15:20:52 -080020#include "util/Files.h"
Adam Lesinski1ab598f2015-08-14 14:26:04 -070021#include "util/Util.h"
22
23#include <androidfw/ResourceTypes.h>
24#include <sstream>
25
26namespace aapt {
27namespace ResourceUtils {
28
Adam Lesinski7298bc9c2015-11-16 12:31:52 -080029bool extractResourceName(const StringPiece16& str, StringPiece16* outPackage,
Adam Lesinski1ab598f2015-08-14 14:26:04 -070030 StringPiece16* outType, StringPiece16* outEntry) {
Adam Lesinski7298bc9c2015-11-16 12:31:52 -080031 bool hasPackageSeparator = false;
32 bool hasTypeSeparator = false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -070033 const char16_t* start = str.data();
34 const char16_t* end = start + str.size();
35 const char16_t* current = start;
36 while (current != end) {
37 if (outType->size() == 0 && *current == u'/') {
Adam Lesinski7298bc9c2015-11-16 12:31:52 -080038 hasTypeSeparator = true;
Adam Lesinski1ab598f2015-08-14 14:26:04 -070039 outType->assign(start, current - start);
40 start = current + 1;
41 } else if (outPackage->size() == 0 && *current == u':') {
Adam Lesinski7298bc9c2015-11-16 12:31:52 -080042 hasPackageSeparator = true;
Adam Lesinski1ab598f2015-08-14 14:26:04 -070043 outPackage->assign(start, current - start);
44 start = current + 1;
45 }
46 current++;
47 }
48 outEntry->assign(start, end - start);
Adam Lesinski7298bc9c2015-11-16 12:31:52 -080049
50 return !(hasPackageSeparator && outPackage->empty()) && !(hasTypeSeparator && outType->empty());
Adam Lesinski1ab598f2015-08-14 14:26:04 -070051}
52
Adam Lesinski467f1712015-11-16 17:35:44 -080053bool parseResourceName(const StringPiece16& str, ResourceNameRef* outRef, bool* outPrivate) {
54 size_t offset = 0;
55 bool priv = false;
56 if (str.data()[0] == u'*') {
57 priv = true;
58 offset = 1;
59 }
60
61 StringPiece16 package;
62 StringPiece16 type;
63 StringPiece16 entry;
64 if (!extractResourceName(str.substr(offset, str.size() - offset), &package, &type, &entry)) {
65 return false;
66 }
67
68 const ResourceType* parsedType = parseResourceType(type);
69 if (!parsedType) {
70 return false;
71 }
72
73 if (entry.empty()) {
74 return false;
75 }
76
77 if (outRef) {
78 outRef->package = package;
79 outRef->type = *parsedType;
80 outRef->entry = entry;
81 }
82
83 if (outPrivate) {
84 *outPrivate = priv;
85 }
86 return true;
87}
88
Adam Lesinski1ab598f2015-08-14 14:26:04 -070089bool tryParseReference(const StringPiece16& str, ResourceNameRef* outRef, bool* outCreate,
90 bool* outPrivate) {
91 StringPiece16 trimmedStr(util::trimWhitespace(str));
92 if (trimmedStr.empty()) {
93 return false;
94 }
95
96 bool create = false;
97 bool priv = false;
98 if (trimmedStr.data()[0] == u'@') {
99 size_t offset = 1;
100 if (trimmedStr.data()[1] == u'+') {
101 create = true;
102 offset += 1;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700103 }
Adam Lesinski467f1712015-11-16 17:35:44 -0800104
105 ResourceNameRef name;
106 if (!parseResourceName(trimmedStr.substr(offset, trimmedStr.size() - offset),
107 &name, &priv)) {
Adam Lesinski7298bc9c2015-11-16 12:31:52 -0800108 return false;
109 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700110
Adam Lesinski467f1712015-11-16 17:35:44 -0800111 if (create && priv) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700112 return false;
113 }
114
Adam Lesinski467f1712015-11-16 17:35:44 -0800115 if (create && name.type != ResourceType::kId) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700116 return false;
117 }
118
Adam Lesinski7298bc9c2015-11-16 12:31:52 -0800119 if (outRef) {
Adam Lesinski467f1712015-11-16 17:35:44 -0800120 *outRef = name;
Adam Lesinski2ae4a872015-11-02 16:10:55 -0800121 }
Adam Lesinski7298bc9c2015-11-16 12:31:52 -0800122
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700123 if (outCreate) {
124 *outCreate = create;
125 }
Adam Lesinski7298bc9c2015-11-16 12:31:52 -0800126
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700127 if (outPrivate) {
128 *outPrivate = priv;
129 }
130 return true;
131 }
132 return false;
133}
134
Adam Lesinski2ae4a872015-11-02 16:10:55 -0800135bool isReference(const StringPiece16& str) {
136 return tryParseReference(str, nullptr, nullptr, nullptr);
137}
138
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700139bool tryParseAttributeReference(const StringPiece16& str, ResourceNameRef* outRef) {
140 StringPiece16 trimmedStr(util::trimWhitespace(str));
141 if (trimmedStr.empty()) {
142 return false;
143 }
144
145 if (*trimmedStr.data() == u'?') {
146 StringPiece16 package;
147 StringPiece16 type;
148 StringPiece16 entry;
Adam Lesinski7298bc9c2015-11-16 12:31:52 -0800149 if (!extractResourceName(trimmedStr.substr(1, trimmedStr.size() - 1),
150 &package, &type, &entry)) {
151 return false;
152 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700153
154 if (!type.empty() && type != u"attr") {
155 return false;
156 }
157
Adam Lesinski7298bc9c2015-11-16 12:31:52 -0800158 if (entry.empty()) {
159 return false;
160 }
161
162 if (outRef) {
163 outRef->package = package;
164 outRef->type = ResourceType::kAttr;
165 outRef->entry = entry;
166 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700167 return true;
168 }
169 return false;
170}
171
Adam Lesinski7298bc9c2015-11-16 12:31:52 -0800172bool isAttributeReference(const StringPiece16& str) {
173 return tryParseAttributeReference(str, nullptr);
174}
175
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700176/*
177 * Style parent's are a bit different. We accept the following formats:
178 *
Adam Lesinski52364f72016-01-11 13:10:24 -0800179 * @[[*]package:][style/]<entry>
Adam Lesinski24b8ff02015-12-16 14:01:57 -0800180 * ?[[*]package:]style/<entry>
181 * <[*]package>:[style/]<entry>
182 * [[*]package:style/]<entry>
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700183 */
184Maybe<Reference> parseStyleParentReference(const StringPiece16& str, std::string* outError) {
185 if (str.empty()) {
186 return {};
187 }
188
189 StringPiece16 name = str;
190
191 bool hasLeadingIdentifiers = false;
192 bool privateRef = false;
193
194 // Skip over these identifiers. A style's parent is a normal reference.
195 if (name.data()[0] == u'@' || name.data()[0] == u'?') {
196 hasLeadingIdentifiers = true;
197 name = name.substr(1, name.size() - 1);
Adam Lesinski24b8ff02015-12-16 14:01:57 -0800198 }
199
200 if (name.data()[0] == u'*') {
201 privateRef = true;
202 name = name.substr(1, name.size() - 1);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700203 }
204
205 ResourceNameRef ref;
206 ref.type = ResourceType::kStyle;
207
208 StringPiece16 typeStr;
209 extractResourceName(name, &ref.package, &typeStr, &ref.entry);
210 if (!typeStr.empty()) {
211 // If we have a type, make sure it is a Style.
212 const ResourceType* parsedType = parseResourceType(typeStr);
213 if (!parsedType || *parsedType != ResourceType::kStyle) {
214 std::stringstream err;
215 err << "invalid resource type '" << typeStr << "' for parent of style";
216 *outError = err.str();
217 return {};
218 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700219 }
220
221 if (!hasLeadingIdentifiers && ref.package.empty() && !typeStr.empty()) {
222 std::stringstream err;
223 err << "invalid parent reference '" << str << "'";
224 *outError = err.str();
225 return {};
226 }
227
228 Reference result(ref);
229 result.privateReference = privateRef;
230 return result;
231}
232
233std::unique_ptr<Reference> tryParseReference(const StringPiece16& str, bool* outCreate) {
234 ResourceNameRef ref;
235 bool privateRef = false;
236 if (tryParseReference(str, &ref, outCreate, &privateRef)) {
237 std::unique_ptr<Reference> value = util::make_unique<Reference>(ref);
238 value->privateReference = privateRef;
239 return value;
240 }
241
242 if (tryParseAttributeReference(str, &ref)) {
243 if (outCreate) {
244 *outCreate = false;
245 }
246 return util::make_unique<Reference>(ref, Reference::Type::kAttribute);
247 }
248 return {};
249}
250
251std::unique_ptr<BinaryPrimitive> tryParseNullOrEmpty(const StringPiece16& str) {
252 StringPiece16 trimmedStr(util::trimWhitespace(str));
253 android::Res_value value = { };
254 if (trimmedStr == u"@null") {
255 // TYPE_NULL with data set to 0 is interpreted by the runtime as an error.
256 // Instead we set the data type to TYPE_REFERENCE with a value of 0.
257 value.dataType = android::Res_value::TYPE_REFERENCE;
258 } else if (trimmedStr == u"@empty") {
259 // TYPE_NULL with value of DATA_NULL_EMPTY is handled fine by the runtime.
260 value.dataType = android::Res_value::TYPE_NULL;
261 value.data = android::Res_value::DATA_NULL_EMPTY;
262 } else {
263 return {};
264 }
265 return util::make_unique<BinaryPrimitive>(value);
266}
267
268std::unique_ptr<BinaryPrimitive> tryParseEnumSymbol(const Attribute* enumAttr,
269 const StringPiece16& str) {
270 StringPiece16 trimmedStr(util::trimWhitespace(str));
271 for (const Attribute::Symbol& symbol : enumAttr->symbols) {
272 // Enum symbols are stored as @package:id/symbol resources,
273 // so we need to match against the 'entry' part of the identifier.
274 const ResourceName& enumSymbolResourceName = symbol.symbol.name.value();
275 if (trimmedStr == enumSymbolResourceName.entry) {
276 android::Res_value value = { };
277 value.dataType = android::Res_value::TYPE_INT_DEC;
278 value.data = symbol.value;
279 return util::make_unique<BinaryPrimitive>(value);
280 }
281 }
282 return {};
283}
284
285std::unique_ptr<BinaryPrimitive> tryParseFlagSymbol(const Attribute* flagAttr,
286 const StringPiece16& str) {
287 android::Res_value flags = { };
288 flags.dataType = android::Res_value::TYPE_INT_DEC;
Adam Lesinski52364f72016-01-11 13:10:24 -0800289 flags.data = 0u;
290
291 if (util::trimWhitespace(str).empty()) {
292 // Empty string is a valid flag (0).
293 return util::make_unique<BinaryPrimitive>(flags);
294 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700295
296 for (StringPiece16 part : util::tokenize(str, u'|')) {
297 StringPiece16 trimmedPart = util::trimWhitespace(part);
298
299 bool flagSet = false;
300 for (const Attribute::Symbol& symbol : flagAttr->symbols) {
301 // Flag symbols are stored as @package:id/symbol resources,
302 // so we need to match against the 'entry' part of the identifier.
303 const ResourceName& flagSymbolResourceName = symbol.symbol.name.value();
304 if (trimmedPart == flagSymbolResourceName.entry) {
305 flags.data |= symbol.value;
306 flagSet = true;
307 break;
308 }
309 }
310
311 if (!flagSet) {
312 return {};
313 }
314 }
315 return util::make_unique<BinaryPrimitive>(flags);
316}
317
318static uint32_t parseHex(char16_t c, bool* outError) {
319 if (c >= u'0' && c <= u'9') {
320 return c - u'0';
321 } else if (c >= u'a' && c <= u'f') {
322 return c - u'a' + 0xa;
323 } else if (c >= u'A' && c <= u'F') {
324 return c - u'A' + 0xa;
325 } else {
326 *outError = true;
327 return 0xffffffffu;
328 }
329}
330
331std::unique_ptr<BinaryPrimitive> tryParseColor(const StringPiece16& str) {
332 StringPiece16 colorStr(util::trimWhitespace(str));
333 const char16_t* start = colorStr.data();
334 const size_t len = colorStr.size();
335 if (len == 0 || start[0] != u'#') {
336 return {};
337 }
338
339 android::Res_value value = { };
340 bool error = false;
341 if (len == 4) {
342 value.dataType = android::Res_value::TYPE_INT_COLOR_RGB4;
343 value.data = 0xff000000u;
344 value.data |= parseHex(start[1], &error) << 20;
345 value.data |= parseHex(start[1], &error) << 16;
346 value.data |= parseHex(start[2], &error) << 12;
347 value.data |= parseHex(start[2], &error) << 8;
348 value.data |= parseHex(start[3], &error) << 4;
349 value.data |= parseHex(start[3], &error);
350 } else if (len == 5) {
351 value.dataType = android::Res_value::TYPE_INT_COLOR_ARGB4;
352 value.data |= parseHex(start[1], &error) << 28;
353 value.data |= parseHex(start[1], &error) << 24;
354 value.data |= parseHex(start[2], &error) << 20;
355 value.data |= parseHex(start[2], &error) << 16;
356 value.data |= parseHex(start[3], &error) << 12;
357 value.data |= parseHex(start[3], &error) << 8;
358 value.data |= parseHex(start[4], &error) << 4;
359 value.data |= parseHex(start[4], &error);
360 } else if (len == 7) {
361 value.dataType = android::Res_value::TYPE_INT_COLOR_RGB8;
362 value.data = 0xff000000u;
363 value.data |= parseHex(start[1], &error) << 20;
364 value.data |= parseHex(start[2], &error) << 16;
365 value.data |= parseHex(start[3], &error) << 12;
366 value.data |= parseHex(start[4], &error) << 8;
367 value.data |= parseHex(start[5], &error) << 4;
368 value.data |= parseHex(start[6], &error);
369 } else if (len == 9) {
370 value.dataType = android::Res_value::TYPE_INT_COLOR_ARGB8;
371 value.data |= parseHex(start[1], &error) << 28;
372 value.data |= parseHex(start[2], &error) << 24;
373 value.data |= parseHex(start[3], &error) << 20;
374 value.data |= parseHex(start[4], &error) << 16;
375 value.data |= parseHex(start[5], &error) << 12;
376 value.data |= parseHex(start[6], &error) << 8;
377 value.data |= parseHex(start[7], &error) << 4;
378 value.data |= parseHex(start[8], &error);
379 } else {
380 return {};
381 }
382 return error ? std::unique_ptr<BinaryPrimitive>() : util::make_unique<BinaryPrimitive>(value);
383}
384
Adam Lesinskib23f1e02015-11-03 12:24:17 -0800385bool tryParseBool(const StringPiece16& str, bool* outValue) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700386 StringPiece16 trimmedStr(util::trimWhitespace(str));
Adam Lesinski52364f72016-01-11 13:10:24 -0800387 if (trimmedStr == u"true" || trimmedStr == u"TRUE" || trimmedStr == u"True") {
Adam Lesinskib23f1e02015-11-03 12:24:17 -0800388 if (outValue) {
389 *outValue = true;
390 }
391 return true;
Adam Lesinski52364f72016-01-11 13:10:24 -0800392 } else if (trimmedStr == u"false" || trimmedStr == u"FALSE" || trimmedStr == u"False") {
Adam Lesinskib23f1e02015-11-03 12:24:17 -0800393 if (outValue) {
394 *outValue = false;
395 }
396 return true;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700397 }
Adam Lesinskib23f1e02015-11-03 12:24:17 -0800398 return false;
399}
400
401std::unique_ptr<BinaryPrimitive> tryParseBool(const StringPiece16& str) {
402 bool result = false;
403 if (tryParseBool(str, &result)) {
404 android::Res_value value = {};
405 value.dataType = android::Res_value::TYPE_INT_BOOLEAN;
406
407 if (result) {
408 value.data = 0xffffffffu;
409 } else {
410 value.data = 0;
411 }
412 return util::make_unique<BinaryPrimitive>(value);
413 }
414 return {};
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700415}
416
417std::unique_ptr<BinaryPrimitive> tryParseInt(const StringPiece16& str) {
418 android::Res_value value;
419 if (!android::ResTable::stringToInt(str.data(), str.size(), &value)) {
420 return {};
421 }
422 return util::make_unique<BinaryPrimitive>(value);
423}
424
425std::unique_ptr<BinaryPrimitive> tryParseFloat(const StringPiece16& str) {
426 android::Res_value value;
427 if (!android::ResTable::stringToFloat(str.data(), str.size(), &value)) {
428 return {};
429 }
430 return util::make_unique<BinaryPrimitive>(value);
431}
432
433uint32_t androidTypeToAttributeTypeMask(uint16_t type) {
434 switch (type) {
435 case android::Res_value::TYPE_NULL:
436 case android::Res_value::TYPE_REFERENCE:
437 case android::Res_value::TYPE_ATTRIBUTE:
438 case android::Res_value::TYPE_DYNAMIC_REFERENCE:
439 return android::ResTable_map::TYPE_REFERENCE;
440
441 case android::Res_value::TYPE_STRING:
442 return android::ResTable_map::TYPE_STRING;
443
444 case android::Res_value::TYPE_FLOAT:
445 return android::ResTable_map::TYPE_FLOAT;
446
447 case android::Res_value::TYPE_DIMENSION:
448 return android::ResTable_map::TYPE_DIMENSION;
449
450 case android::Res_value::TYPE_FRACTION:
451 return android::ResTable_map::TYPE_FRACTION;
452
453 case android::Res_value::TYPE_INT_DEC:
454 case android::Res_value::TYPE_INT_HEX:
455 return android::ResTable_map::TYPE_INTEGER | android::ResTable_map::TYPE_ENUM
456 | android::ResTable_map::TYPE_FLAGS;
457
458 case android::Res_value::TYPE_INT_BOOLEAN:
459 return android::ResTable_map::TYPE_BOOLEAN;
460
461 case android::Res_value::TYPE_INT_COLOR_ARGB8:
462 case android::Res_value::TYPE_INT_COLOR_RGB8:
463 case android::Res_value::TYPE_INT_COLOR_ARGB4:
464 case android::Res_value::TYPE_INT_COLOR_RGB4:
465 return android::ResTable_map::TYPE_COLOR;
466
467 default:
468 return 0;
469 };
470}
471
472std::unique_ptr<Item> parseItemForAttribute(
473 const StringPiece16& value, uint32_t typeMask,
474 std::function<void(const ResourceName&)> onCreateReference) {
475 std::unique_ptr<BinaryPrimitive> nullOrEmpty = tryParseNullOrEmpty(value);
476 if (nullOrEmpty) {
477 return std::move(nullOrEmpty);
478 }
479
480 bool create = false;
481 std::unique_ptr<Reference> reference = tryParseReference(value, &create);
482 if (reference) {
483 if (create && onCreateReference) {
484 onCreateReference(reference->name.value());
485 }
486 return std::move(reference);
487 }
488
489 if (typeMask & android::ResTable_map::TYPE_COLOR) {
490 // Try parsing this as a color.
491 std::unique_ptr<BinaryPrimitive> color = tryParseColor(value);
492 if (color) {
493 return std::move(color);
494 }
495 }
496
497 if (typeMask & android::ResTable_map::TYPE_BOOLEAN) {
498 // Try parsing this as a boolean.
499 std::unique_ptr<BinaryPrimitive> boolean = tryParseBool(value);
500 if (boolean) {
501 return std::move(boolean);
502 }
503 }
504
505 if (typeMask & android::ResTable_map::TYPE_INTEGER) {
506 // Try parsing this as an integer.
507 std::unique_ptr<BinaryPrimitive> integer = tryParseInt(value);
508 if (integer) {
509 return std::move(integer);
510 }
511 }
512
513 const uint32_t floatMask = android::ResTable_map::TYPE_FLOAT
514 | android::ResTable_map::TYPE_DIMENSION | android::ResTable_map::TYPE_FRACTION;
515 if (typeMask & floatMask) {
516 // Try parsing this as a float.
517 std::unique_ptr<BinaryPrimitive> floatingPoint = tryParseFloat(value);
518 if (floatingPoint) {
519 if (typeMask & androidTypeToAttributeTypeMask(floatingPoint->value.dataType)) {
520 return std::move(floatingPoint);
521 }
522 }
523 }
524 return {};
525}
526
527/**
528 * We successively try to parse the string as a resource type that the Attribute
529 * allows.
530 */
531std::unique_ptr<Item> parseItemForAttribute(
532 const StringPiece16& str, const Attribute* attr,
533 std::function<void(const ResourceName&)> onCreateReference) {
534 const uint32_t typeMask = attr->typeMask;
535 std::unique_ptr<Item> value = parseItemForAttribute(str, typeMask, onCreateReference);
536 if (value) {
537 return value;
538 }
539
540 if (typeMask & android::ResTable_map::TYPE_ENUM) {
541 // Try parsing this as an enum.
542 std::unique_ptr<BinaryPrimitive> enumValue = tryParseEnumSymbol(attr, str);
543 if (enumValue) {
544 return std::move(enumValue);
545 }
546 }
547
548 if (typeMask & android::ResTable_map::TYPE_FLAGS) {
549 // Try parsing this as a flag.
550 std::unique_ptr<BinaryPrimitive> flagValue = tryParseFlagSymbol(attr, str);
551 if (flagValue) {
552 return std::move(flagValue);
553 }
554 }
555 return {};
556}
557
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800558std::string buildResourceFileName(const ResourceFile& resFile, const NameMangler* mangler) {
559 std::stringstream out;
560 out << "res/" << resFile.name.type;
561 if (resFile.config != ConfigDescription{}) {
562 out << "-" << resFile.config;
563 }
564 out << "/";
565
566 if (mangler && mangler->shouldMangle(resFile.name.package)) {
567 out << NameMangler::mangleEntry(resFile.name.package, resFile.name.entry);
568 } else {
569 out << resFile.name.entry;
570 }
571 out << file::getExtension(resFile.source.path);
572 return out.str();
573}
574
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700575} // namespace ResourceUtils
576} // namespace aapt