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