blob: ae3b4ff1e363f1c7409dc49207492c35f862e716 [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
331std::unique_ptr<BinaryPrimitive> tryParseBool(const StringPiece16& str) {
332 StringPiece16 trimmedStr(util::trimWhitespace(str));
333 uint32_t data = 0;
334 if (trimmedStr == u"true" || trimmedStr == u"TRUE") {
335 data = 0xffffffffu;
336 } else if (trimmedStr != u"false" && trimmedStr != u"FALSE") {
337 return {};
338 }
339 android::Res_value value = { };
340 value.dataType = android::Res_value::TYPE_INT_BOOLEAN;
341 value.data = data;
342 return util::make_unique<BinaryPrimitive>(value);
343}
344
345std::unique_ptr<BinaryPrimitive> tryParseInt(const StringPiece16& str) {
346 android::Res_value value;
347 if (!android::ResTable::stringToInt(str.data(), str.size(), &value)) {
348 return {};
349 }
350 return util::make_unique<BinaryPrimitive>(value);
351}
352
353std::unique_ptr<BinaryPrimitive> tryParseFloat(const StringPiece16& str) {
354 android::Res_value value;
355 if (!android::ResTable::stringToFloat(str.data(), str.size(), &value)) {
356 return {};
357 }
358 return util::make_unique<BinaryPrimitive>(value);
359}
360
361uint32_t androidTypeToAttributeTypeMask(uint16_t type) {
362 switch (type) {
363 case android::Res_value::TYPE_NULL:
364 case android::Res_value::TYPE_REFERENCE:
365 case android::Res_value::TYPE_ATTRIBUTE:
366 case android::Res_value::TYPE_DYNAMIC_REFERENCE:
367 return android::ResTable_map::TYPE_REFERENCE;
368
369 case android::Res_value::TYPE_STRING:
370 return android::ResTable_map::TYPE_STRING;
371
372 case android::Res_value::TYPE_FLOAT:
373 return android::ResTable_map::TYPE_FLOAT;
374
375 case android::Res_value::TYPE_DIMENSION:
376 return android::ResTable_map::TYPE_DIMENSION;
377
378 case android::Res_value::TYPE_FRACTION:
379 return android::ResTable_map::TYPE_FRACTION;
380
381 case android::Res_value::TYPE_INT_DEC:
382 case android::Res_value::TYPE_INT_HEX:
383 return android::ResTable_map::TYPE_INTEGER | android::ResTable_map::TYPE_ENUM
384 | android::ResTable_map::TYPE_FLAGS;
385
386 case android::Res_value::TYPE_INT_BOOLEAN:
387 return android::ResTable_map::TYPE_BOOLEAN;
388
389 case android::Res_value::TYPE_INT_COLOR_ARGB8:
390 case android::Res_value::TYPE_INT_COLOR_RGB8:
391 case android::Res_value::TYPE_INT_COLOR_ARGB4:
392 case android::Res_value::TYPE_INT_COLOR_RGB4:
393 return android::ResTable_map::TYPE_COLOR;
394
395 default:
396 return 0;
397 };
398}
399
400std::unique_ptr<Item> parseItemForAttribute(
401 const StringPiece16& value, uint32_t typeMask,
402 std::function<void(const ResourceName&)> onCreateReference) {
403 std::unique_ptr<BinaryPrimitive> nullOrEmpty = tryParseNullOrEmpty(value);
404 if (nullOrEmpty) {
405 return std::move(nullOrEmpty);
406 }
407
408 bool create = false;
409 std::unique_ptr<Reference> reference = tryParseReference(value, &create);
410 if (reference) {
411 if (create && onCreateReference) {
412 onCreateReference(reference->name.value());
413 }
414 return std::move(reference);
415 }
416
417 if (typeMask & android::ResTable_map::TYPE_COLOR) {
418 // Try parsing this as a color.
419 std::unique_ptr<BinaryPrimitive> color = tryParseColor(value);
420 if (color) {
421 return std::move(color);
422 }
423 }
424
425 if (typeMask & android::ResTable_map::TYPE_BOOLEAN) {
426 // Try parsing this as a boolean.
427 std::unique_ptr<BinaryPrimitive> boolean = tryParseBool(value);
428 if (boolean) {
429 return std::move(boolean);
430 }
431 }
432
433 if (typeMask & android::ResTable_map::TYPE_INTEGER) {
434 // Try parsing this as an integer.
435 std::unique_ptr<BinaryPrimitive> integer = tryParseInt(value);
436 if (integer) {
437 return std::move(integer);
438 }
439 }
440
441 const uint32_t floatMask = android::ResTable_map::TYPE_FLOAT
442 | android::ResTable_map::TYPE_DIMENSION | android::ResTable_map::TYPE_FRACTION;
443 if (typeMask & floatMask) {
444 // Try parsing this as a float.
445 std::unique_ptr<BinaryPrimitive> floatingPoint = tryParseFloat(value);
446 if (floatingPoint) {
447 if (typeMask & androidTypeToAttributeTypeMask(floatingPoint->value.dataType)) {
448 return std::move(floatingPoint);
449 }
450 }
451 }
452 return {};
453}
454
455/**
456 * We successively try to parse the string as a resource type that the Attribute
457 * allows.
458 */
459std::unique_ptr<Item> parseItemForAttribute(
460 const StringPiece16& str, const Attribute* attr,
461 std::function<void(const ResourceName&)> onCreateReference) {
462 const uint32_t typeMask = attr->typeMask;
463 std::unique_ptr<Item> value = parseItemForAttribute(str, typeMask, onCreateReference);
464 if (value) {
465 return value;
466 }
467
468 if (typeMask & android::ResTable_map::TYPE_ENUM) {
469 // Try parsing this as an enum.
470 std::unique_ptr<BinaryPrimitive> enumValue = tryParseEnumSymbol(attr, str);
471 if (enumValue) {
472 return std::move(enumValue);
473 }
474 }
475
476 if (typeMask & android::ResTable_map::TYPE_FLAGS) {
477 // Try parsing this as a flag.
478 std::unique_ptr<BinaryPrimitive> flagValue = tryParseFlagSymbol(attr, str);
479 if (flagValue) {
480 return std::move(flagValue);
481 }
482 }
483 return {};
484}
485
486} // namespace ResourceUtils
487} // namespace aapt