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