blob: ffe6595cf0b62738e0065a4ffa53e756925fd055 [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
17#include "ResourceUtils.h"
Adam Lesinski467f1712015-11-16 17:35:44 -080018#include "flatten/ResourceTypeExtensions.h"
Adam Lesinski1ab598f2015-08-14 14:26:04 -070019#include "util/Util.h"
20
21#include <androidfw/ResourceTypes.h>
22#include <sstream>
23
24namespace aapt {
25namespace ResourceUtils {
26
Adam Lesinski7298bc9c2015-11-16 12:31:52 -080027bool extractResourceName(const StringPiece16& str, StringPiece16* outPackage,
Adam Lesinski1ab598f2015-08-14 14:26:04 -070028 StringPiece16* outType, StringPiece16* outEntry) {
Adam Lesinski7298bc9c2015-11-16 12:31:52 -080029 bool hasPackageSeparator = false;
30 bool hasTypeSeparator = false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -070031 const char16_t* start = str.data();
32 const char16_t* end = start + str.size();
33 const char16_t* current = start;
34 while (current != end) {
35 if (outType->size() == 0 && *current == u'/') {
Adam Lesinski7298bc9c2015-11-16 12:31:52 -080036 hasTypeSeparator = true;
Adam Lesinski1ab598f2015-08-14 14:26:04 -070037 outType->assign(start, current - start);
38 start = current + 1;
39 } else if (outPackage->size() == 0 && *current == u':') {
Adam Lesinski7298bc9c2015-11-16 12:31:52 -080040 hasPackageSeparator = true;
Adam Lesinski1ab598f2015-08-14 14:26:04 -070041 outPackage->assign(start, current - start);
42 start = current + 1;
43 }
44 current++;
45 }
46 outEntry->assign(start, end - start);
Adam Lesinski7298bc9c2015-11-16 12:31:52 -080047
48 return !(hasPackageSeparator && outPackage->empty()) && !(hasTypeSeparator && outType->empty());
Adam Lesinski1ab598f2015-08-14 14:26:04 -070049}
50
Adam Lesinski467f1712015-11-16 17:35:44 -080051bool parseResourceName(const StringPiece16& str, ResourceNameRef* outRef, bool* outPrivate) {
52 size_t offset = 0;
53 bool priv = false;
54 if (str.data()[0] == u'*') {
55 priv = true;
56 offset = 1;
57 }
58
59 StringPiece16 package;
60 StringPiece16 type;
61 StringPiece16 entry;
62 if (!extractResourceName(str.substr(offset, str.size() - offset), &package, &type, &entry)) {
63 return false;
64 }
65
66 const ResourceType* parsedType = parseResourceType(type);
67 if (!parsedType) {
68 return false;
69 }
70
71 if (entry.empty()) {
72 return false;
73 }
74
75 if (outRef) {
76 outRef->package = package;
77 outRef->type = *parsedType;
78 outRef->entry = entry;
79 }
80
81 if (outPrivate) {
82 *outPrivate = priv;
83 }
84 return true;
85}
86
Adam Lesinski1ab598f2015-08-14 14:26:04 -070087bool tryParseReference(const StringPiece16& str, ResourceNameRef* outRef, bool* outCreate,
88 bool* outPrivate) {
89 StringPiece16 trimmedStr(util::trimWhitespace(str));
90 if (trimmedStr.empty()) {
91 return false;
92 }
93
94 bool create = false;
95 bool priv = false;
96 if (trimmedStr.data()[0] == u'@') {
97 size_t offset = 1;
98 if (trimmedStr.data()[1] == u'+') {
99 create = true;
100 offset += 1;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700101 }
Adam Lesinski467f1712015-11-16 17:35:44 -0800102
103 ResourceNameRef name;
104 if (!parseResourceName(trimmedStr.substr(offset, trimmedStr.size() - offset),
105 &name, &priv)) {
Adam Lesinski7298bc9c2015-11-16 12:31:52 -0800106 return false;
107 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700108
Adam Lesinski467f1712015-11-16 17:35:44 -0800109 if (create && priv) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700110 return false;
111 }
112
Adam Lesinski467f1712015-11-16 17:35:44 -0800113 if (create && name.type != ResourceType::kId) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700114 return false;
115 }
116
Adam Lesinski7298bc9c2015-11-16 12:31:52 -0800117 if (outRef) {
Adam Lesinski467f1712015-11-16 17:35:44 -0800118 *outRef = name;
Adam Lesinski2ae4a872015-11-02 16:10:55 -0800119 }
Adam Lesinski7298bc9c2015-11-16 12:31:52 -0800120
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700121 if (outCreate) {
122 *outCreate = create;
123 }
Adam Lesinski7298bc9c2015-11-16 12:31:52 -0800124
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700125 if (outPrivate) {
126 *outPrivate = priv;
127 }
128 return true;
129 }
130 return false;
131}
132
Adam Lesinski2ae4a872015-11-02 16:10:55 -0800133bool isReference(const StringPiece16& str) {
134 return tryParseReference(str, nullptr, nullptr, nullptr);
135}
136
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700137bool tryParseAttributeReference(const StringPiece16& str, ResourceNameRef* outRef) {
138 StringPiece16 trimmedStr(util::trimWhitespace(str));
139 if (trimmedStr.empty()) {
140 return false;
141 }
142
143 if (*trimmedStr.data() == u'?') {
144 StringPiece16 package;
145 StringPiece16 type;
146 StringPiece16 entry;
Adam Lesinski7298bc9c2015-11-16 12:31:52 -0800147 if (!extractResourceName(trimmedStr.substr(1, trimmedStr.size() - 1),
148 &package, &type, &entry)) {
149 return false;
150 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700151
152 if (!type.empty() && type != u"attr") {
153 return false;
154 }
155
Adam Lesinski7298bc9c2015-11-16 12:31:52 -0800156 if (entry.empty()) {
157 return false;
158 }
159
160 if (outRef) {
161 outRef->package = package;
162 outRef->type = ResourceType::kAttr;
163 outRef->entry = entry;
164 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700165 return true;
166 }
167 return false;
168}
169
Adam Lesinski7298bc9c2015-11-16 12:31:52 -0800170bool isAttributeReference(const StringPiece16& str) {
171 return tryParseAttributeReference(str, nullptr);
172}
173
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700174/*
175 * Style parent's are a bit different. We accept the following formats:
176 *
177 * @[package:]style/<entry>
178 * ?[package:]style/<entry>
179 * <package>:[style/]<entry>
180 * [package:style/]<entry>
181 */
182Maybe<Reference> parseStyleParentReference(const StringPiece16& str, std::string* outError) {
183 if (str.empty()) {
184 return {};
185 }
186
187 StringPiece16 name = str;
188
189 bool hasLeadingIdentifiers = false;
190 bool privateRef = false;
191
192 // Skip over these identifiers. A style's parent is a normal reference.
193 if (name.data()[0] == u'@' || name.data()[0] == u'?') {
194 hasLeadingIdentifiers = true;
195 name = name.substr(1, name.size() - 1);
196 if (name.data()[0] == u'*') {
197 privateRef = true;
198 name = name.substr(1, name.size() - 1);
199 }
200 }
201
202 ResourceNameRef ref;
203 ref.type = ResourceType::kStyle;
204
205 StringPiece16 typeStr;
206 extractResourceName(name, &ref.package, &typeStr, &ref.entry);
207 if (!typeStr.empty()) {
208 // If we have a type, make sure it is a Style.
209 const ResourceType* parsedType = parseResourceType(typeStr);
210 if (!parsedType || *parsedType != ResourceType::kStyle) {
211 std::stringstream err;
212 err << "invalid resource type '" << typeStr << "' for parent of style";
213 *outError = err.str();
214 return {};
215 }
216 } else {
217 // No type was defined, this should not have a leading identifier.
218 if (hasLeadingIdentifiers) {
219 std::stringstream err;
220 err << "invalid parent reference '" << str << "'";
221 *outError = err.str();
222 return {};
223 }
224 }
225
226 if (!hasLeadingIdentifiers && ref.package.empty() && !typeStr.empty()) {
227 std::stringstream err;
228 err << "invalid parent reference '" << str << "'";
229 *outError = err.str();
230 return {};
231 }
232
233 Reference result(ref);
234 result.privateReference = privateRef;
235 return result;
236}
237
238std::unique_ptr<Reference> tryParseReference(const StringPiece16& str, bool* outCreate) {
239 ResourceNameRef ref;
240 bool privateRef = false;
241 if (tryParseReference(str, &ref, outCreate, &privateRef)) {
242 std::unique_ptr<Reference> value = util::make_unique<Reference>(ref);
243 value->privateReference = privateRef;
244 return value;
245 }
246
247 if (tryParseAttributeReference(str, &ref)) {
248 if (outCreate) {
249 *outCreate = false;
250 }
251 return util::make_unique<Reference>(ref, Reference::Type::kAttribute);
252 }
253 return {};
254}
255
256std::unique_ptr<BinaryPrimitive> tryParseNullOrEmpty(const StringPiece16& str) {
257 StringPiece16 trimmedStr(util::trimWhitespace(str));
258 android::Res_value value = { };
259 if (trimmedStr == u"@null") {
260 // TYPE_NULL with data set to 0 is interpreted by the runtime as an error.
261 // Instead we set the data type to TYPE_REFERENCE with a value of 0.
262 value.dataType = android::Res_value::TYPE_REFERENCE;
263 } else if (trimmedStr == u"@empty") {
264 // TYPE_NULL with value of DATA_NULL_EMPTY is handled fine by the runtime.
265 value.dataType = android::Res_value::TYPE_NULL;
266 value.data = android::Res_value::DATA_NULL_EMPTY;
267 } else {
268 return {};
269 }
270 return util::make_unique<BinaryPrimitive>(value);
271}
272
273std::unique_ptr<BinaryPrimitive> tryParseEnumSymbol(const Attribute* enumAttr,
274 const StringPiece16& str) {
275 StringPiece16 trimmedStr(util::trimWhitespace(str));
276 for (const Attribute::Symbol& symbol : enumAttr->symbols) {
277 // Enum symbols are stored as @package:id/symbol resources,
278 // so we need to match against the 'entry' part of the identifier.
279 const ResourceName& enumSymbolResourceName = symbol.symbol.name.value();
280 if (trimmedStr == enumSymbolResourceName.entry) {
281 android::Res_value value = { };
282 value.dataType = android::Res_value::TYPE_INT_DEC;
283 value.data = symbol.value;
284 return util::make_unique<BinaryPrimitive>(value);
285 }
286 }
287 return {};
288}
289
290std::unique_ptr<BinaryPrimitive> tryParseFlagSymbol(const Attribute* flagAttr,
291 const StringPiece16& str) {
292 android::Res_value flags = { };
293 flags.dataType = android::Res_value::TYPE_INT_DEC;
294
295 for (StringPiece16 part : util::tokenize(str, u'|')) {
296 StringPiece16 trimmedPart = util::trimWhitespace(part);
297
298 bool flagSet = false;
299 for (const Attribute::Symbol& symbol : flagAttr->symbols) {
300 // Flag symbols are stored as @package:id/symbol resources,
301 // so we need to match against the 'entry' part of the identifier.
302 const ResourceName& flagSymbolResourceName = symbol.symbol.name.value();
303 if (trimmedPart == flagSymbolResourceName.entry) {
304 flags.data |= symbol.value;
305 flagSet = true;
306 break;
307 }
308 }
309
310 if (!flagSet) {
311 return {};
312 }
313 }
314 return util::make_unique<BinaryPrimitive>(flags);
315}
316
317static uint32_t parseHex(char16_t c, bool* outError) {
318 if (c >= u'0' && c <= u'9') {
319 return c - u'0';
320 } else if (c >= u'a' && c <= u'f') {
321 return c - u'a' + 0xa;
322 } else if (c >= u'A' && c <= u'F') {
323 return c - u'A' + 0xa;
324 } else {
325 *outError = true;
326 return 0xffffffffu;
327 }
328}
329
330std::unique_ptr<BinaryPrimitive> tryParseColor(const StringPiece16& str) {
331 StringPiece16 colorStr(util::trimWhitespace(str));
332 const char16_t* start = colorStr.data();
333 const size_t len = colorStr.size();
334 if (len == 0 || start[0] != u'#') {
335 return {};
336 }
337
338 android::Res_value value = { };
339 bool error = false;
340 if (len == 4) {
341 value.dataType = android::Res_value::TYPE_INT_COLOR_RGB4;
342 value.data = 0xff000000u;
343 value.data |= parseHex(start[1], &error) << 20;
344 value.data |= parseHex(start[1], &error) << 16;
345 value.data |= parseHex(start[2], &error) << 12;
346 value.data |= parseHex(start[2], &error) << 8;
347 value.data |= parseHex(start[3], &error) << 4;
348 value.data |= parseHex(start[3], &error);
349 } else if (len == 5) {
350 value.dataType = android::Res_value::TYPE_INT_COLOR_ARGB4;
351 value.data |= parseHex(start[1], &error) << 28;
352 value.data |= parseHex(start[1], &error) << 24;
353 value.data |= parseHex(start[2], &error) << 20;
354 value.data |= parseHex(start[2], &error) << 16;
355 value.data |= parseHex(start[3], &error) << 12;
356 value.data |= parseHex(start[3], &error) << 8;
357 value.data |= parseHex(start[4], &error) << 4;
358 value.data |= parseHex(start[4], &error);
359 } else if (len == 7) {
360 value.dataType = android::Res_value::TYPE_INT_COLOR_RGB8;
361 value.data = 0xff000000u;
362 value.data |= parseHex(start[1], &error) << 20;
363 value.data |= parseHex(start[2], &error) << 16;
364 value.data |= parseHex(start[3], &error) << 12;
365 value.data |= parseHex(start[4], &error) << 8;
366 value.data |= parseHex(start[5], &error) << 4;
367 value.data |= parseHex(start[6], &error);
368 } else if (len == 9) {
369 value.dataType = android::Res_value::TYPE_INT_COLOR_ARGB8;
370 value.data |= parseHex(start[1], &error) << 28;
371 value.data |= parseHex(start[2], &error) << 24;
372 value.data |= parseHex(start[3], &error) << 20;
373 value.data |= parseHex(start[4], &error) << 16;
374 value.data |= parseHex(start[5], &error) << 12;
375 value.data |= parseHex(start[6], &error) << 8;
376 value.data |= parseHex(start[7], &error) << 4;
377 value.data |= parseHex(start[8], &error);
378 } else {
379 return {};
380 }
381 return error ? std::unique_ptr<BinaryPrimitive>() : util::make_unique<BinaryPrimitive>(value);
382}
383
Adam Lesinskib23f1e02015-11-03 12:24:17 -0800384bool tryParseBool(const StringPiece16& str, bool* outValue) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700385 StringPiece16 trimmedStr(util::trimWhitespace(str));
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700386 if (trimmedStr == u"true" || trimmedStr == u"TRUE") {
Adam Lesinskib23f1e02015-11-03 12:24:17 -0800387 if (outValue) {
388 *outValue = true;
389 }
390 return true;
391 } else if (trimmedStr == u"false" || trimmedStr == u"FALSE") {
392 if (outValue) {
393 *outValue = false;
394 }
395 return true;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700396 }
Adam Lesinskib23f1e02015-11-03 12:24:17 -0800397 return false;
398}
399
400std::unique_ptr<BinaryPrimitive> tryParseBool(const StringPiece16& str) {
401 bool result = false;
402 if (tryParseBool(str, &result)) {
403 android::Res_value value = {};
404 value.dataType = android::Res_value::TYPE_INT_BOOLEAN;
405
406 if (result) {
407 value.data = 0xffffffffu;
408 } else {
409 value.data = 0;
410 }
411 return util::make_unique<BinaryPrimitive>(value);
412 }
413 return {};
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700414}
415
416std::unique_ptr<BinaryPrimitive> tryParseInt(const StringPiece16& str) {
417 android::Res_value value;
418 if (!android::ResTable::stringToInt(str.data(), str.size(), &value)) {
419 return {};
420 }
421 return util::make_unique<BinaryPrimitive>(value);
422}
423
424std::unique_ptr<BinaryPrimitive> tryParseFloat(const StringPiece16& str) {
425 android::Res_value value;
426 if (!android::ResTable::stringToFloat(str.data(), str.size(), &value)) {
427 return {};
428 }
429 return util::make_unique<BinaryPrimitive>(value);
430}
431
432uint32_t androidTypeToAttributeTypeMask(uint16_t type) {
433 switch (type) {
434 case android::Res_value::TYPE_NULL:
435 case android::Res_value::TYPE_REFERENCE:
436 case android::Res_value::TYPE_ATTRIBUTE:
437 case android::Res_value::TYPE_DYNAMIC_REFERENCE:
438 return android::ResTable_map::TYPE_REFERENCE;
439
440 case android::Res_value::TYPE_STRING:
441 return android::ResTable_map::TYPE_STRING;
442
443 case android::Res_value::TYPE_FLOAT:
444 return android::ResTable_map::TYPE_FLOAT;
445
446 case android::Res_value::TYPE_DIMENSION:
447 return android::ResTable_map::TYPE_DIMENSION;
448
449 case android::Res_value::TYPE_FRACTION:
450 return android::ResTable_map::TYPE_FRACTION;
451
452 case android::Res_value::TYPE_INT_DEC:
453 case android::Res_value::TYPE_INT_HEX:
454 return android::ResTable_map::TYPE_INTEGER | android::ResTable_map::TYPE_ENUM
455 | android::ResTable_map::TYPE_FLAGS;
456
457 case android::Res_value::TYPE_INT_BOOLEAN:
458 return android::ResTable_map::TYPE_BOOLEAN;
459
460 case android::Res_value::TYPE_INT_COLOR_ARGB8:
461 case android::Res_value::TYPE_INT_COLOR_RGB8:
462 case android::Res_value::TYPE_INT_COLOR_ARGB4:
463 case android::Res_value::TYPE_INT_COLOR_RGB4:
464 return android::ResTable_map::TYPE_COLOR;
465
466 default:
467 return 0;
468 };
469}
470
471std::unique_ptr<Item> parseItemForAttribute(
472 const StringPiece16& value, uint32_t typeMask,
473 std::function<void(const ResourceName&)> onCreateReference) {
474 std::unique_ptr<BinaryPrimitive> nullOrEmpty = tryParseNullOrEmpty(value);
475 if (nullOrEmpty) {
476 return std::move(nullOrEmpty);
477 }
478
479 bool create = false;
480 std::unique_ptr<Reference> reference = tryParseReference(value, &create);
481 if (reference) {
482 if (create && onCreateReference) {
483 onCreateReference(reference->name.value());
484 }
485 return std::move(reference);
486 }
487
488 if (typeMask & android::ResTable_map::TYPE_COLOR) {
489 // Try parsing this as a color.
490 std::unique_ptr<BinaryPrimitive> color = tryParseColor(value);
491 if (color) {
492 return std::move(color);
493 }
494 }
495
496 if (typeMask & android::ResTable_map::TYPE_BOOLEAN) {
497 // Try parsing this as a boolean.
498 std::unique_ptr<BinaryPrimitive> boolean = tryParseBool(value);
499 if (boolean) {
500 return std::move(boolean);
501 }
502 }
503
504 if (typeMask & android::ResTable_map::TYPE_INTEGER) {
505 // Try parsing this as an integer.
506 std::unique_ptr<BinaryPrimitive> integer = tryParseInt(value);
507 if (integer) {
508 return std::move(integer);
509 }
510 }
511
512 const uint32_t floatMask = android::ResTable_map::TYPE_FLOAT
513 | android::ResTable_map::TYPE_DIMENSION | android::ResTable_map::TYPE_FRACTION;
514 if (typeMask & floatMask) {
515 // Try parsing this as a float.
516 std::unique_ptr<BinaryPrimitive> floatingPoint = tryParseFloat(value);
517 if (floatingPoint) {
518 if (typeMask & androidTypeToAttributeTypeMask(floatingPoint->value.dataType)) {
519 return std::move(floatingPoint);
520 }
521 }
522 }
523 return {};
524}
525
526/**
527 * We successively try to parse the string as a resource type that the Attribute
528 * allows.
529 */
530std::unique_ptr<Item> parseItemForAttribute(
531 const StringPiece16& str, const Attribute* attr,
532 std::function<void(const ResourceName&)> onCreateReference) {
533 const uint32_t typeMask = attr->typeMask;
534 std::unique_ptr<Item> value = parseItemForAttribute(str, typeMask, onCreateReference);
535 if (value) {
536 return value;
537 }
538
539 if (typeMask & android::ResTable_map::TYPE_ENUM) {
540 // Try parsing this as an enum.
541 std::unique_ptr<BinaryPrimitive> enumValue = tryParseEnumSymbol(attr, str);
542 if (enumValue) {
543 return std::move(enumValue);
544 }
545 }
546
547 if (typeMask & android::ResTable_map::TYPE_FLAGS) {
548 // Try parsing this as a flag.
549 std::unique_ptr<BinaryPrimitive> flagValue = tryParseFlagSymbol(attr, str);
550 if (flagValue) {
551 return std::move(flagValue);
552 }
553 }
554 return {};
555}
556
557} // namespace ResourceUtils
558} // namespace aapt