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