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