blob: d3720c46931c55bba2487509aa389e0a7475f4e2 [file] [log] [blame]
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001/*
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 "Logger.h"
18#include "ResourceParser.h"
19#include "ResourceValues.h"
20#include "ScopedXmlPullParser.h"
21#include "SourceXmlPullParser.h"
22#include "Util.h"
23#include "XliffXmlPullParser.h"
24
25namespace aapt {
26
27void ResourceParser::extractResourceName(const StringPiece16& str, StringPiece16* outPackage,
28 StringPiece16* outType, StringPiece16* outEntry) {
29 const char16_t* start = str.data();
30 const char16_t* end = start + str.size();
31 const char16_t* current = start;
32 while (current != end) {
33 if (outType->size() == 0 && *current == u'/') {
34 outType->assign(start, current - start);
35 start = current + 1;
36 } else if (outPackage->size() == 0 && *current == u':') {
37 outPackage->assign(start, current - start);
38 start = current + 1;
39 }
40 current++;
41 }
42 outEntry->assign(start, end - start);
43}
44
45bool ResourceParser::tryParseReference(const StringPiece16& str, ResourceNameRef* outRef,
46 bool* outCreate, bool* outPrivate) {
47 StringPiece16 trimmedStr(util::trimWhitespace(str));
48 if (trimmedStr.empty()) {
49 return false;
50 }
51
52 if (trimmedStr.data()[0] == u'@') {
53 size_t offset = 1;
54 *outCreate = false;
55 if (trimmedStr.data()[1] == u'+') {
56 *outCreate = true;
57 offset += 1;
58 } else if (trimmedStr.data()[1] == u'*') {
59 *outPrivate = true;
60 offset += 1;
61 }
62 StringPiece16 package;
63 StringPiece16 type;
64 StringPiece16 entry;
65 extractResourceName(trimmedStr.substr(offset, trimmedStr.size() - offset),
66 &package, &type, &entry);
67
68 const ResourceType* parsedType = parseResourceType(type);
69 if (!parsedType) {
70 return false;
71 }
72
73 if (*outCreate && *parsedType != ResourceType::kId) {
74 return false;
75 }
76
77 outRef->package = package;
78 outRef->type = *parsedType;
79 outRef->entry = entry;
80 return true;
81 }
82 return false;
83}
84
85bool ResourceParser::tryParseAttributeReference(const StringPiece16& str,
86 ResourceNameRef* outRef) {
87 StringPiece16 trimmedStr(util::trimWhitespace(str));
88 if (trimmedStr.empty()) {
89 return false;
90 }
91
92 if (*trimmedStr.data() == u'?') {
93 StringPiece16 package;
94 StringPiece16 type;
95 StringPiece16 entry;
96 extractResourceName(trimmedStr.substr(1, trimmedStr.size() - 1), &package, &type, &entry);
97
98 if (!type.empty() && type != u"attr") {
99 return false;
100 }
101
102 outRef->package = package;
103 outRef->type = ResourceType::kAttr;
104 outRef->entry = entry;
105 return true;
106 }
107 return false;
108}
109
110std::unique_ptr<Reference> ResourceParser::tryParseReference(const StringPiece16& str,
111 const StringPiece16& defaultPackage,
112 bool* outCreate) {
113 ResourceNameRef ref;
114 bool privateRef = false;
115 if (tryParseReference(str, &ref, outCreate, &privateRef)) {
116 if (ref.package.empty()) {
117 ref.package = defaultPackage;
118 }
119 std::unique_ptr<Reference> value = util::make_unique<Reference>(ref);
120 value->privateReference = privateRef;
121 return value;
122 }
123
124 if (tryParseAttributeReference(str, &ref)) {
125 if (ref.package.empty()) {
126 ref.package = defaultPackage;
127 }
128 *outCreate = false;
129 return util::make_unique<Reference>(ref, Reference::Type::kAttribute);
130 }
131 return {};
132}
133
134std::unique_ptr<BinaryPrimitive> ResourceParser::tryParseNullOrEmpty(const StringPiece16& str) {
135 StringPiece16 trimmedStr(util::trimWhitespace(str));
136 uint32_t data = 0;
137 if (trimmedStr == u"@null") {
138 data = android::Res_value::DATA_NULL_UNDEFINED;
139 } else if (trimmedStr == u"@empty") {
140 data = android::Res_value::DATA_NULL_EMPTY;
141 } else {
142 return {};
143 }
144
145 android::Res_value value = {};
146 value.dataType = android::Res_value::TYPE_NULL;
147 value.data = data;
148 return util::make_unique<BinaryPrimitive>(value);
149}
150
151std::unique_ptr<BinaryPrimitive> ResourceParser::tryParseEnumSymbol(const Attribute& enumAttr,
152 const StringPiece16& str) {
153 StringPiece16 trimmedStr(util::trimWhitespace(str));
154 for (const auto& entry : enumAttr.symbols) {
155 // Enum symbols are stored as @package:id/symbol resources,
156 // so we need to match against the 'entry' part of the identifier.
157 const ResourceName& enumSymbolResourceName = entry.symbol.name;
158 if (trimmedStr == enumSymbolResourceName.entry) {
159 android::Res_value value = {};
160 value.dataType = android::Res_value::TYPE_INT_DEC;
161 value.data = entry.value;
162 return util::make_unique<BinaryPrimitive>(value);
163 }
164 }
165 return {};
166}
167
168std::unique_ptr<BinaryPrimitive> ResourceParser::tryParseFlagSymbol(const Attribute& flagAttr,
169 const StringPiece16& str) {
170 android::Res_value flags = {};
171 flags.dataType = android::Res_value::TYPE_INT_DEC;
172
173 for (StringPiece16 part : util::tokenize(str, u'|')) {
174 StringPiece16 trimmedPart = util::trimWhitespace(part);
175
176 bool flagSet = false;
177 for (const auto& entry : flagAttr.symbols) {
178 // Flag symbols are stored as @package:id/symbol resources,
179 // so we need to match against the 'entry' part of the identifier.
180 const ResourceName& flagSymbolResourceName = entry.symbol.name;
181 if (trimmedPart == flagSymbolResourceName.entry) {
182 flags.data |= entry.value;
183 flagSet = true;
184 break;
185 }
186 }
187
188 if (!flagSet) {
189 return {};
190 }
191 }
192 return util::make_unique<BinaryPrimitive>(flags);
193}
194
195static uint32_t parseHex(char16_t c, bool* outError) {
196 if (c >= u'0' && c <= u'9') {
197 return c - u'0';
198 } else if (c >= u'a' && c <= u'f') {
199 return c - u'a' + 0xa;
200 } else if (c >= u'A' && c <= u'F') {
201 return c - u'A' + 0xa;
202 } else {
203 *outError = true;
204 return 0xffffffffu;
205 }
206}
207
208std::unique_ptr<BinaryPrimitive> ResourceParser::tryParseColor(const StringPiece16& str) {
209 StringPiece16 colorStr(util::trimWhitespace(str));
210 const char16_t* start = colorStr.data();
211 const size_t len = colorStr.size();
212 if (len == 0 || start[0] != u'#') {
213 return {};
214 }
215
216 android::Res_value value = {};
217 bool error = false;
218 if (len == 4) {
219 value.dataType = android::Res_value::TYPE_INT_COLOR_RGB4;
220 value.data = 0xff000000u;
221 value.data |= parseHex(start[1], &error) << 20;
222 value.data |= parseHex(start[1], &error) << 16;
223 value.data |= parseHex(start[2], &error) << 12;
224 value.data |= parseHex(start[2], &error) << 8;
225 value.data |= parseHex(start[3], &error) << 4;
226 value.data |= parseHex(start[3], &error);
227 } else if (len == 5) {
228 value.dataType = android::Res_value::TYPE_INT_COLOR_ARGB4;
229 value.data |= parseHex(start[1], &error) << 28;
230 value.data |= parseHex(start[1], &error) << 24;
231 value.data |= parseHex(start[2], &error) << 20;
232 value.data |= parseHex(start[2], &error) << 16;
233 value.data |= parseHex(start[3], &error) << 12;
234 value.data |= parseHex(start[3], &error) << 8;
235 value.data |= parseHex(start[4], &error) << 4;
236 value.data |= parseHex(start[4], &error);
237 } else if (len == 7) {
238 value.dataType = android::Res_value::TYPE_INT_COLOR_RGB8;
239 value.data = 0xff000000u;
240 value.data |= parseHex(start[1], &error) << 20;
241 value.data |= parseHex(start[2], &error) << 16;
242 value.data |= parseHex(start[3], &error) << 12;
243 value.data |= parseHex(start[4], &error) << 8;
244 value.data |= parseHex(start[5], &error) << 4;
245 value.data |= parseHex(start[6], &error);
246 } else if (len == 9) {
247 value.dataType = android::Res_value::TYPE_INT_COLOR_ARGB8;
248 value.data |= parseHex(start[1], &error) << 28;
249 value.data |= parseHex(start[2], &error) << 24;
250 value.data |= parseHex(start[3], &error) << 20;
251 value.data |= parseHex(start[4], &error) << 16;
252 value.data |= parseHex(start[5], &error) << 12;
253 value.data |= parseHex(start[6], &error) << 8;
254 value.data |= parseHex(start[7], &error) << 4;
255 value.data |= parseHex(start[8], &error);
256 } else {
257 return {};
258 }
259 return error ? std::unique_ptr<BinaryPrimitive>() : util::make_unique<BinaryPrimitive>(value);
260}
261
262std::unique_ptr<BinaryPrimitive> ResourceParser::tryParseBool(const StringPiece16& str) {
263 StringPiece16 trimmedStr(util::trimWhitespace(str));
264 uint32_t data = 0;
265 if (trimmedStr == u"true" || trimmedStr == u"TRUE") {
266 data = 1;
267 } else if (trimmedStr != u"false" && trimmedStr != u"FALSE") {
268 return {};
269 }
270 android::Res_value value = {};
271 value.dataType = android::Res_value::TYPE_INT_BOOLEAN;
272 value.data = data;
273 return util::make_unique<BinaryPrimitive>(value);
274}
275
276std::unique_ptr<BinaryPrimitive> ResourceParser::tryParseInt(const StringPiece16& str) {
277 android::Res_value value;
278 if (!android::ResTable::stringToInt(str.data(), str.size(), &value)) {
279 return {};
280 }
281 return util::make_unique<BinaryPrimitive>(value);
282}
283
284std::unique_ptr<BinaryPrimitive> ResourceParser::tryParseFloat(const StringPiece16& str) {
285 android::Res_value value;
286 if (!android::ResTable::stringToFloat(str.data(), str.size(), &value)) {
287 return {};
288 }
289 return util::make_unique<BinaryPrimitive>(value);
290}
291
292uint32_t ResourceParser::androidTypeToAttributeTypeMask(uint16_t type) {
293 switch (type) {
294 case android::Res_value::TYPE_NULL:
295 case android::Res_value::TYPE_REFERENCE:
296 case android::Res_value::TYPE_ATTRIBUTE:
297 case android::Res_value::TYPE_DYNAMIC_REFERENCE:
298 return android::ResTable_map::TYPE_REFERENCE;
299
300 case android::Res_value::TYPE_STRING:
301 return android::ResTable_map::TYPE_STRING;
302
303 case android::Res_value::TYPE_FLOAT:
304 return android::ResTable_map::TYPE_FLOAT;
305
306 case android::Res_value::TYPE_DIMENSION:
307 return android::ResTable_map::TYPE_DIMENSION;
308
309 case android::Res_value::TYPE_FRACTION:
310 return android::ResTable_map::TYPE_FRACTION;
311
312 case android::Res_value::TYPE_INT_DEC:
313 case android::Res_value::TYPE_INT_HEX:
314 return android::ResTable_map::TYPE_INTEGER |
315 android::ResTable_map::TYPE_ENUM |
316 android::ResTable_map::TYPE_FLAGS;
317
318 case android::Res_value::TYPE_INT_BOOLEAN:
319 return android::ResTable_map::TYPE_BOOLEAN;
320
321 case android::Res_value::TYPE_INT_COLOR_ARGB8:
322 case android::Res_value::TYPE_INT_COLOR_RGB8:
323 case android::Res_value::TYPE_INT_COLOR_ARGB4:
324 case android::Res_value::TYPE_INT_COLOR_RGB4:
325 return android::ResTable_map::TYPE_COLOR;
326
327 default:
328 return 0;
329 };
330}
331
332std::unique_ptr<Item> ResourceParser::parseItemForAttribute(
333 const StringPiece16& value, uint32_t typeMask, const StringPiece16& defaultPackage,
334 std::function<void(const ResourceName&)> onCreateReference) {
335 std::unique_ptr<BinaryPrimitive> nullOrEmpty = tryParseNullOrEmpty(value);
336 if (nullOrEmpty) {
337 return std::move(nullOrEmpty);
338 }
339
340 bool create = false;
341 std::unique_ptr<Reference> reference = tryParseReference(value, defaultPackage, &create);
342 if (reference) {
343 if (create && onCreateReference) {
344 onCreateReference(reference->name);
345 }
346 return std::move(reference);
347 }
348
349 if (typeMask & android::ResTable_map::TYPE_COLOR) {
350 // Try parsing this as a color.
351 std::unique_ptr<BinaryPrimitive> color = tryParseColor(value);
352 if (color) {
353 return std::move(color);
354 }
355 }
356
357 if (typeMask & android::ResTable_map::TYPE_BOOLEAN) {
358 // Try parsing this as a boolean.
359 std::unique_ptr<BinaryPrimitive> boolean = tryParseBool(value);
360 if (boolean) {
361 return std::move(boolean);
362 }
363 }
364
365 if (typeMask & android::ResTable_map::TYPE_INTEGER) {
366 // Try parsing this as an integer.
367 std::unique_ptr<BinaryPrimitive> integer = tryParseInt(value);
368 if (integer) {
369 return std::move(integer);
370 }
371 }
372
373 const uint32_t floatMask = android::ResTable_map::TYPE_FLOAT |
374 android::ResTable_map::TYPE_DIMENSION |
375 android::ResTable_map::TYPE_FRACTION;
376 if (typeMask & floatMask) {
377 // Try parsing this as a float.
378 std::unique_ptr<BinaryPrimitive> floatingPoint = tryParseFloat(value);
379 if (floatingPoint) {
380 if (typeMask & androidTypeToAttributeTypeMask(floatingPoint->value.dataType)) {
381 return std::move(floatingPoint);
382 }
383 }
384 }
385 return {};
386}
387
388/**
389 * We successively try to parse the string as a resource type that the Attribute
390 * allows.
391 */
392std::unique_ptr<Item> ResourceParser::parseItemForAttribute(
393 const StringPiece16& str, const Attribute& attr, const StringPiece16& defaultPackage,
394 std::function<void(const ResourceName&)> onCreateReference) {
395 const uint32_t typeMask = attr.typeMask;
396 std::unique_ptr<Item> value = parseItemForAttribute(str, typeMask, defaultPackage,
397 onCreateReference);
398 if (value) {
399 return value;
400 }
401
402 if (typeMask & android::ResTable_map::TYPE_ENUM) {
403 // Try parsing this as an enum.
404 std::unique_ptr<BinaryPrimitive> enumValue = tryParseEnumSymbol(attr, str);
405 if (enumValue) {
406 return std::move(enumValue);
407 }
408 }
409
410 if (typeMask & android::ResTable_map::TYPE_FLAGS) {
411 // Try parsing this as a flag.
412 std::unique_ptr<BinaryPrimitive> flagValue = tryParseFlagSymbol(attr, str);
413 if (flagValue) {
414 return std::move(flagValue);
415 }
416 }
417 return {};
418}
419
420ResourceParser::ResourceParser(const std::shared_ptr<ResourceTable>& table, const Source& source,
421 const ConfigDescription& config,
422 const std::shared_ptr<XmlPullParser>& parser) :
423 mTable(table), mSource(source), mConfig(config), mLogger(source),
424 mParser(std::make_shared<XliffXmlPullParser>(parser)) {
425}
426
427/**
428 * Build a string from XML that converts nested elements into Span objects.
429 */
430bool ResourceParser::flattenXmlSubtree(XmlPullParser* parser, std::u16string* outRawString,
431 StyleString* outStyleString) {
432 std::vector<Span> spanStack;
433
434 outRawString->clear();
435 outStyleString->spans.clear();
436 util::StringBuilder builder;
437 size_t depth = 1;
438 while (XmlPullParser::isGoodEvent(parser->next())) {
439 const XmlPullParser::Event event = parser->getEvent();
440 if (event == XmlPullParser::Event::kEndElement) {
441 depth--;
442 if (depth == 0) {
443 break;
444 }
445
446 spanStack.back().lastChar = builder.str().size();
447 outStyleString->spans.push_back(spanStack.back());
448 spanStack.pop_back();
449
450 } else if (event == XmlPullParser::Event::kText) {
451 // TODO(adamlesinski): Verify format strings.
452 outRawString->append(parser->getText());
453 builder.append(parser->getText());
454
455 } else if (event == XmlPullParser::Event::kStartElement) {
456 if (parser->getElementNamespace().size() > 0) {
457 mLogger.warn(parser->getLineNumber())
458 << "skipping element '"
459 << parser->getElementName()
460 << "' with unknown namespace '"
461 << parser->getElementNamespace()
462 << "'."
463 << std::endl;
464 XmlPullParser::skipCurrentElement(parser);
465 continue;
466 }
467 depth++;
468
469 // Build a span object out of the nested element.
470 std::u16string spanName = parser->getElementName();
471 const auto endAttrIter = parser->endAttributes();
472 for (auto attrIter = parser->beginAttributes(); attrIter != endAttrIter; ++attrIter) {
473 spanName += u";";
474 spanName += attrIter->name;
475 spanName += u"=";
476 spanName += attrIter->value;
477 }
478
479 if (builder.str().size() > std::numeric_limits<uint32_t>::max()) {
480 mLogger.error(parser->getLineNumber())
481 << "style string '"
482 << builder.str()
483 << "' is too long."
484 << std::endl;
485 return false;
486 }
487 spanStack.push_back(Span{ spanName, static_cast<uint32_t>(builder.str().size()) });
488
489 } else if (event == XmlPullParser::Event::kComment) {
490 // Skip
491 } else {
492 mLogger.warn(parser->getLineNumber())
493 << "unknown event "
494 << event
495 << "."
496 << std::endl;
497 }
498 }
499 assert(spanStack.empty() && "spans haven't been fully processed");
500
501 outStyleString->str = builder.str();
502 return true;
503}
504
505bool ResourceParser::parse() {
506 while (XmlPullParser::isGoodEvent(mParser->next())) {
507 if (mParser->getEvent() != XmlPullParser::Event::kStartElement) {
508 continue;
509 }
510
511 ScopedXmlPullParser parser(mParser.get());
512 if (!parser.getElementNamespace().empty() ||
513 parser.getElementName() != u"resources") {
514 mLogger.error(parser.getLineNumber())
515 << "root element must be <resources> in the global namespace."
516 << std::endl;
517 return false;
518 }
519
520 if (!parseResources(&parser)) {
521 return false;
522 }
523 }
524
525 if (mParser->getEvent() == XmlPullParser::Event::kBadDocument) {
526 mLogger.error(mParser->getLineNumber())
527 << mParser->getLastError()
528 << std::endl;
529 return false;
530 }
531 return true;
532}
533
534bool ResourceParser::parseResources(XmlPullParser* parser) {
535 bool success = true;
536
537 std::u16string comment;
538 while (XmlPullParser::isGoodEvent(parser->next())) {
539 const XmlPullParser::Event event = parser->getEvent();
540 if (event == XmlPullParser::Event::kComment) {
541 comment = parser->getComment();
542 continue;
543 }
544
545 if (event == XmlPullParser::Event::kText) {
546 if (!util::trimWhitespace(parser->getText()).empty()) {
547 comment = u"";
548 }
549 continue;
550 }
551
552 if (event != XmlPullParser::Event::kStartElement) {
553 continue;
554 }
555
556 ScopedXmlPullParser childParser(parser);
557
558 if (!childParser.getElementNamespace().empty()) {
559 // Skip unknown namespace.
560 continue;
561 }
562
563 StringPiece16 name = childParser.getElementName();
564 if (name == u"skip" || name == u"eat-comment") {
565 continue;
566 }
567
568 if (name == u"private-symbols") {
569 // Handle differently.
570 mLogger.note(childParser.getLineNumber())
571 << "got a <private-symbols> tag."
572 << std::endl;
573 continue;
574 }
575
576 const auto endAttrIter = childParser.endAttributes();
577 auto attrIter = childParser.findAttribute(u"", u"name");
578 if (attrIter == endAttrIter || attrIter->value.empty()) {
579 mLogger.error(childParser.getLineNumber())
580 << "<" << name << "> tag must have a 'name' attribute."
581 << std::endl;
582 success = false;
583 continue;
584 }
585
586 // Copy because our iterator will go out of scope when
587 // we parse more XML.
588 std::u16string attributeName = attrIter->value;
589
590 if (name == u"item") {
591 // Items simply have their type encoded in the type attribute.
592 auto typeIter = childParser.findAttribute(u"", u"type");
593 if (typeIter == endAttrIter || typeIter->value.empty()) {
594 mLogger.error(childParser.getLineNumber())
595 << "<item> must have a 'type' attribute."
596 << std::endl;
597 success = false;
598 continue;
599 }
600 name = typeIter->value;
601 }
602
603 if (name == u"id") {
604 success &= mTable->addResource(ResourceNameRef{ {}, ResourceType::kId, attributeName },
605 {}, mSource.line(childParser.getLineNumber()),
606 util::make_unique<Id>());
607 } else if (name == u"string") {
608 success &= parseString(&childParser,
609 ResourceNameRef{ {}, ResourceType::kString, attributeName });
610 } else if (name == u"color") {
611 success &= parseColor(&childParser,
612 ResourceNameRef{ {}, ResourceType::kColor, attributeName });
613 } else if (name == u"drawable") {
614 success &= parseColor(&childParser,
615 ResourceNameRef{ {}, ResourceType::kDrawable, attributeName });
616 } else if (name == u"bool") {
617 success &= parsePrimitive(&childParser,
618 ResourceNameRef{ {}, ResourceType::kBool, attributeName });
619 } else if (name == u"integer") {
620 success &= parsePrimitive(
621 &childParser,
622 ResourceNameRef{ {}, ResourceType::kInteger, attributeName });
623 } else if (name == u"dimen") {
624 success &= parsePrimitive(&childParser,
625 ResourceNameRef{ {}, ResourceType::kDimen, attributeName });
626 } else if (name == u"fraction") {
627// success &= parsePrimitive(
628// &childParser,
629// ResourceNameRef{ {}, ResourceType::kFraction, attributeName });
630 } else if (name == u"style") {
631 success &= parseStyle(&childParser,
632 ResourceNameRef{ {}, ResourceType::kStyle, attributeName });
633 } else if (name == u"plurals") {
634 success &= parsePlural(&childParser,
635 ResourceNameRef{ {}, ResourceType::kPlurals, attributeName });
636 } else if (name == u"array") {
637 success &= parseArray(&childParser,
638 ResourceNameRef{ {}, ResourceType::kArray, attributeName },
639 android::ResTable_map::TYPE_ANY);
640 } else if (name == u"string-array") {
641 success &= parseArray(&childParser,
642 ResourceNameRef{ {}, ResourceType::kArray, attributeName },
643 android::ResTable_map::TYPE_STRING);
644 } else if (name == u"integer-array") {
645 success &= parseArray(&childParser,
646 ResourceNameRef{ {}, ResourceType::kArray, attributeName },
647 android::ResTable_map::TYPE_INTEGER);
648 } else if (name == u"public") {
649 success &= parsePublic(&childParser, attributeName);
650 } else if (name == u"declare-styleable") {
651 success &= parseDeclareStyleable(
652 &childParser,
653 ResourceNameRef{ {}, ResourceType::kStyleable, attributeName });
654 } else if (name == u"attr") {
655 success &= parseAttr(&childParser,
656 ResourceNameRef{ {}, ResourceType::kAttr, attributeName });
657 } else if (name == u"bag") {
658 } else if (name == u"public-padding") {
659 } else if (name == u"java-symbol") {
660 } else if (name == u"add-resource") {
661 }
662 }
663
664 if (parser->getEvent() == XmlPullParser::Event::kBadDocument) {
665 mLogger.error(parser->getLineNumber())
666 << parser->getLastError()
667 << std::endl;
668 return false;
669 }
670 return success;
671}
672
673
674
675enum {
676 kAllowRawString = true,
677 kNoRawString = false
678};
679
680/**
681 * Reads the entire XML subtree and attempts to parse it as some Item,
682 * with typeMask denoting which items it can be. If allowRawValue is
683 * true, a RawString is returned if the XML couldn't be parsed as
684 * an Item. If allowRawValue is false, nullptr is returned in this
685 * case.
686 */
687std::unique_ptr<Item> ResourceParser::parseXml(XmlPullParser* parser, uint32_t typeMask,
688 bool allowRawValue) {
689 const size_t beginXmlLine = parser->getLineNumber();
690
691 std::u16string rawValue;
692 StyleString styleString;
693 if (!flattenXmlSubtree(parser, &rawValue, &styleString)) {
694 return {};
695 }
696
697 StringPool& pool = mTable->getValueStringPool();
698
699 if (!styleString.spans.empty()) {
700 // This can only be a StyledString.
701 return util::make_unique<StyledString>(
702 pool.makeRef(styleString, StringPool::Context{ 1, mConfig }));
703 }
704
705 auto onCreateReference = [&](const ResourceName& name) {
706 mTable->addResource(name, {}, mSource.line(beginXmlLine), util::make_unique<Id>());
707 };
708
709 // Process the raw value.
710 std::unique_ptr<Item> processedItem = parseItemForAttribute(rawValue, typeMask,
711 mTable->getPackage(),
712 onCreateReference);
713 if (processedItem) {
714 return processedItem;
715 }
716
717 // Try making a regular string.
718 if (typeMask & android::ResTable_map::TYPE_STRING) {
719 // Use the trimmed, escaped string.
720 return util::make_unique<String>(
721 pool.makeRef(styleString.str, StringPool::Context{ 1, mConfig }));
722 }
723
724 // We can't parse this so return a RawString if we are allowed.
725 if (allowRawValue) {
726 return util::make_unique<RawString>(
727 pool.makeRef(rawValue, StringPool::Context{ 1, mConfig }));
728 }
729 return {};
730}
731
732bool ResourceParser::parseString(XmlPullParser* parser, const ResourceNameRef& resourceName) {
733 const SourceLine source = mSource.line(parser->getLineNumber());
734
735 // Mark the string as untranslateable if needed.
736 const auto endAttrIter = parser->endAttributes();
737 auto attrIter = parser->findAttribute(u"", u"untranslateable");
738 // bool untranslateable = attrIter != endAttrIter;
739 // TODO(adamlesinski): Do something with this (mark the string).
740
741 // Deal with the product.
742 attrIter = parser->findAttribute(u"", u"product");
743 if (attrIter != endAttrIter) {
744 if (attrIter->value != u"default" && attrIter->value != u"phone") {
745 // TODO(adamlesinski): Match products.
746 return true;
747 }
748 }
749
750 std::unique_ptr<Item> processedItem = parseXml(parser, android::ResTable_map::TYPE_STRING,
751 kNoRawString);
752 if (!processedItem) {
753 mLogger.error(source.line)
754 << "not a valid string."
755 << std::endl;
756 return false;
757 }
758
759 return mTable->addResource(resourceName, mConfig, source, std::move(processedItem));
760}
761
762bool ResourceParser::parseColor(XmlPullParser* parser, const ResourceNameRef& resourceName) {
763 const SourceLine source = mSource.line(parser->getLineNumber());
764
765 std::unique_ptr<Item> item = parseXml(parser, android::ResTable_map::TYPE_COLOR, kNoRawString);
766 if (!item) {
767 mLogger.error(source.line) << "invalid color." << std::endl;
768 return false;
769 }
770 return mTable->addResource(resourceName, mConfig, source, std::move(item));
771}
772
773bool ResourceParser::parsePrimitive(XmlPullParser* parser, const ResourceNameRef& resourceName) {
774 const SourceLine source = mSource.line(parser->getLineNumber());
775
776 uint32_t typeMask = 0;
777 switch (resourceName.type) {
778 case ResourceType::kInteger:
779 typeMask |= android::ResTable_map::TYPE_INTEGER;
780 break;
781
782 case ResourceType::kDimen:
783 typeMask |= android::ResTable_map::TYPE_DIMENSION
784 | android::ResTable_map::TYPE_FLOAT
785 | android::ResTable_map::TYPE_FRACTION;
786 break;
787
788 case ResourceType::kBool:
789 typeMask |= android::ResTable_map::TYPE_BOOLEAN;
790 break;
791
792 default:
793 assert(false);
794 break;
795 }
796
797 std::unique_ptr<Item> item = parseXml(parser, typeMask, kNoRawString);
798 if (!item) {
799 mLogger.error(source.line)
800 << "invalid "
801 << resourceName.type
802 << "."
803 << std::endl;
804 return false;
805 }
806
807 return mTable->addResource(resourceName, mConfig, source, std::move(item));
808}
809
810bool ResourceParser::parsePublic(XmlPullParser* parser, const StringPiece16& name) {
811 const SourceLine source = mSource.line(parser->getLineNumber());
812
813 const auto endAttrIter = parser->endAttributes();
814 const auto typeAttrIter = parser->findAttribute(u"", u"type");
815 if (typeAttrIter == endAttrIter || typeAttrIter->value.empty()) {
816 mLogger.error(source.line)
817 << "<public> must have a 'type' attribute."
818 << std::endl;
819 return false;
820 }
821
822 const ResourceType* parsedType = parseResourceType(typeAttrIter->value);
823 if (!parsedType) {
824 mLogger.error(source.line)
825 << "invalid resource type '"
826 << typeAttrIter->value
827 << "' in <public>."
828 << std::endl;
829 return false;
830 }
831
832 ResourceNameRef resourceName { {}, *parsedType, name };
833 ResourceId resourceId;
834
835 const auto idAttrIter = parser->findAttribute(u"", u"id");
836 if (idAttrIter != endAttrIter && !idAttrIter->value.empty()) {
837 android::Res_value val;
838 bool result = android::ResTable::stringToInt(idAttrIter->value.data(),
839 idAttrIter->value.size(), &val);
840 resourceId.id = val.data;
841 if (!result || !resourceId.isValid()) {
842 mLogger.error(source.line)
843 << "invalid resource ID '"
844 << idAttrIter->value
845 << "' in <public>."
846 << std::endl;
847 return false;
848 }
849 }
850
851 if (*parsedType == ResourceType::kId) {
852 // An ID marked as public is also the definition of an ID.
853 mTable->addResource(resourceName, {}, source, util::make_unique<Id>());
854 }
855
856 return mTable->markPublic(resourceName, resourceId, source);
857}
858
859static uint32_t parseFormatType(const StringPiece16& piece) {
860 if (piece == u"reference") return android::ResTable_map::TYPE_REFERENCE;
861 else if (piece == u"string") return android::ResTable_map::TYPE_STRING;
862 else if (piece == u"integer") return android::ResTable_map::TYPE_INTEGER;
863 else if (piece == u"boolean") return android::ResTable_map::TYPE_BOOLEAN;
864 else if (piece == u"color") return android::ResTable_map::TYPE_COLOR;
865 else if (piece == u"float") return android::ResTable_map::TYPE_FLOAT;
866 else if (piece == u"dimension") return android::ResTable_map::TYPE_DIMENSION;
867 else if (piece == u"fraction") return android::ResTable_map::TYPE_FRACTION;
868 else if (piece == u"enum") return android::ResTable_map::TYPE_ENUM;
869 else if (piece == u"flags") return android::ResTable_map::TYPE_FLAGS;
870 return 0;
871}
872
873static uint32_t parseFormatAttribute(const StringPiece16& str) {
874 uint32_t mask = 0;
875 for (StringPiece16 part : util::tokenize(str, u'|')) {
876 StringPiece16 trimmedPart = util::trimWhitespace(part);
877 uint32_t type = parseFormatType(trimmedPart);
878 if (type == 0) {
879 return 0;
880 }
881 mask |= type;
882 }
883 return mask;
884}
885
886bool ResourceParser::parseAttr(XmlPullParser* parser, const ResourceNameRef& resourceName) {
887 const SourceLine source = mSource.line(parser->getLineNumber());
888 std::unique_ptr<Attribute> attr = parseAttrImpl(parser, resourceName, false);
889 if (!attr) {
890 return false;
891 }
892 return mTable->addResource(resourceName, mConfig, source, std::move(attr));
893}
894
895std::unique_ptr<Attribute> ResourceParser::parseAttrImpl(XmlPullParser* parser,
896 const ResourceNameRef& resourceName,
897 bool weak) {
898 uint32_t typeMask = 0;
899
900 const auto endAttrIter = parser->endAttributes();
901 const auto formatAttrIter = parser->findAttribute(u"", u"format");
902 if (formatAttrIter != endAttrIter) {
903 typeMask = parseFormatAttribute(formatAttrIter->value);
904 if (typeMask == 0) {
905 mLogger.error(parser->getLineNumber())
906 << "invalid attribute format '"
907 << formatAttrIter->value
908 << "'."
909 << std::endl;
910 return {};
911 }
912 }
913
914 std::vector<Attribute::Symbol> items;
915
916 bool error = false;
917 while (XmlPullParser::isGoodEvent(parser->next())) {
918 if (parser->getEvent() != XmlPullParser::Event::kStartElement) {
919 continue;
920 }
921
922 ScopedXmlPullParser childParser(parser);
923
924 const std::u16string& name = childParser.getElementName();
925 if (!childParser.getElementNamespace().empty()
926 || (name != u"flag" && name != u"enum")) {
927 mLogger.error(childParser.getLineNumber())
928 << "unexpected tag <"
929 << name
930 << "> in <attr>."
931 << std::endl;
932 error = true;
933 continue;
934 }
935
936 if (name == u"enum") {
937 if (typeMask & android::ResTable_map::TYPE_FLAGS) {
938 mLogger.error(childParser.getLineNumber())
939 << "can not define an <enum>; already defined a <flag>."
940 << std::endl;
941 error = true;
942 continue;
943 }
944 typeMask |= android::ResTable_map::TYPE_ENUM;
945 } else if (name == u"flag") {
946 if (typeMask & android::ResTable_map::TYPE_ENUM) {
947 mLogger.error(childParser.getLineNumber())
948 << "can not define a <flag>; already defined an <enum>."
949 << std::endl;
950 error = true;
951 continue;
952 }
953 typeMask |= android::ResTable_map::TYPE_FLAGS;
954 }
955
956 Attribute::Symbol item;
957 if (parseEnumOrFlagItem(&childParser, name, &item)) {
958 if (!mTable->addResource(item.symbol.name, mConfig,
959 mSource.line(childParser.getLineNumber()),
960 util::make_unique<Id>())) {
961 error = true;
962 } else {
963 items.push_back(std::move(item));
964 }
965 } else {
966 error = true;
967 }
968 }
969
970 if (error) {
971 return {};
972 }
973
974 std::unique_ptr<Attribute> attr = util::make_unique<Attribute>(weak);
975 attr->symbols.swap(items);
976 attr->typeMask = typeMask ? typeMask : android::ResTable_map::TYPE_ANY;
977 return attr;
978}
979
980bool ResourceParser::parseEnumOrFlagItem(XmlPullParser* parser, const StringPiece16& tag,
981 Attribute::Symbol* outSymbol) {
982 const auto attrIterEnd = parser->endAttributes();
983 const auto nameAttrIter = parser->findAttribute(u"", u"name");
984 if (nameAttrIter == attrIterEnd || nameAttrIter->value.empty()) {
985 mLogger.error(parser->getLineNumber())
986 << "no attribute 'name' found for tag <" << tag << ">."
987 << std::endl;
988 return false;
989 }
990
991 const auto valueAttrIter = parser->findAttribute(u"", u"value");
992 if (valueAttrIter == attrIterEnd || valueAttrIter->value.empty()) {
993 mLogger.error(parser->getLineNumber())
994 << "no attribute 'value' found for tag <" << tag << ">."
995 << std::endl;
996 return false;
997 }
998
999 android::Res_value val;
1000 if (!android::ResTable::stringToInt(valueAttrIter->value.data(),
1001 valueAttrIter->value.size(), &val)) {
1002 mLogger.error(parser->getLineNumber())
1003 << "invalid value '"
1004 << valueAttrIter->value
1005 << "' for <" << tag << ">; must be an integer."
1006 << std::endl;
1007 return false;
1008 }
1009
1010 outSymbol->symbol.name = ResourceName {
1011 mTable->getPackage(), ResourceType::kId, nameAttrIter->value };
1012 outSymbol->value = val.data;
1013 return true;
1014}
1015
1016static bool parseXmlAttributeName(StringPiece16 str, ResourceNameRef* outRef) {
1017 str = util::trimWhitespace(str);
1018 const char16_t* const start = str.data();
1019 const char16_t* const end = start + str.size();
1020 const char16_t* p = start;
1021
1022 StringPiece16 package;
1023 StringPiece16 name;
1024 while (p != end) {
1025 if (*p == u':') {
1026 package = StringPiece16(start, p - start);
1027 name = StringPiece16(p + 1, end - (p + 1));
1028 break;
1029 }
1030 p++;
1031 }
1032
1033 outRef->package = package;
1034 outRef->type = ResourceType::kAttr;
1035 if (name.size() == 0) {
1036 outRef->entry = str;
1037 } else {
1038 outRef->entry = name;
1039 }
1040 return true;
1041}
1042
1043bool ResourceParser::parseUntypedItem(XmlPullParser* parser, Style& style) {
1044 const auto endAttrIter = parser->endAttributes();
1045 const auto nameAttrIter = parser->findAttribute(u"", u"name");
1046 if (nameAttrIter == endAttrIter || nameAttrIter->value.empty()) {
1047 mLogger.error(parser->getLineNumber())
1048 << "<item> must have a 'name' attribute."
1049 << std::endl;
1050 return false;
1051 }
1052
1053 ResourceNameRef keyRef;
1054 if (!parseXmlAttributeName(nameAttrIter->value, &keyRef)) {
1055 mLogger.error(parser->getLineNumber())
1056 << "invalid attribute name '"
1057 << nameAttrIter->value
1058 << "'."
1059 << std::endl;
1060 return false;
1061 }
1062
1063 if (keyRef.package.empty()) {
1064 keyRef.package = mTable->getPackage();
1065 }
1066
1067 // Create a copy instead of a reference because we
1068 // are about to invalidate keyRef when advancing the parser.
1069 ResourceName key = keyRef.toResourceName();
1070
1071 std::unique_ptr<Item> value = parseXml(parser, 0, kAllowRawString);
1072 if (!value) {
1073 return false;
1074 }
1075
1076 style.entries.push_back(Style::Entry{ Reference(key), std::move(value) });
1077 return true;
1078}
1079
1080bool ResourceParser::parseStyle(XmlPullParser* parser, const ResourceNameRef& resourceName) {
1081 const SourceLine source = mSource.line(parser->getLineNumber());
1082 std::unique_ptr<Style> style = util::make_unique<Style>();
1083
1084 const auto endAttrIter = parser->endAttributes();
1085 const auto parentAttrIter = parser->findAttribute(u"", u"parent");
1086 if (parentAttrIter != endAttrIter) {
1087 ResourceNameRef ref;
1088 bool create = false;
1089 bool privateRef = false;
1090 if (tryParseReference(parentAttrIter->value, &ref, &create, &privateRef)) {
1091 if (create) {
1092 mLogger.error(source.line)
1093 << "parent of style can not be an ID."
1094 << std::endl;
1095 return false;
1096 }
1097 style->parent.name = ref.toResourceName();
1098 style->parent.privateReference = privateRef;
1099 } else if (tryParseAttributeReference(parentAttrIter->value, &ref)) {
1100 style->parent.name = ref.toResourceName();
1101 } else {
1102 // TODO(adamlesinski): Try parsing without the '@' or '?'.
1103 // Also, make sure to check the entry name for weird symbols.
1104 style->parent.name = ResourceName {
1105 {}, ResourceType::kStyle, parentAttrIter->value
1106 };
1107 }
1108
1109 if (style->parent.name.package.empty()) {
1110 style->parent.name.package = mTable->getPackage();
1111 }
1112 }
1113
1114 bool success = true;
1115 while (XmlPullParser::isGoodEvent(parser->next())) {
1116 if (parser->getEvent() != XmlPullParser::Event::kStartElement) {
1117 continue;
1118 }
1119
1120 ScopedXmlPullParser childParser(parser);
1121 const std::u16string& name = childParser.getElementName();
1122 if (name == u"item") {
1123 success &= parseUntypedItem(&childParser, *style);
1124 } else {
1125 mLogger.error(childParser.getLineNumber())
1126 << "unexpected tag <"
1127 << name
1128 << "> in <style> resource."
1129 << std::endl;
1130 success = false;
1131 }
1132 }
1133
1134 if (!success) {
1135 return false;
1136 }
1137
1138 return mTable->addResource(resourceName, mConfig, source, std::move(style));
1139}
1140
1141bool ResourceParser::parseArray(XmlPullParser* parser, const ResourceNameRef& resourceName,
1142 uint32_t typeMask) {
1143 const SourceLine source = mSource.line(parser->getLineNumber());
1144 std::unique_ptr<Array> array = util::make_unique<Array>();
1145
1146 bool error = false;
1147 while (XmlPullParser::isGoodEvent(parser->next())) {
1148 if (parser->getEvent() != XmlPullParser::Event::kStartElement) {
1149 continue;
1150 }
1151
1152 ScopedXmlPullParser childParser(parser);
1153
1154 if (childParser.getElementName() != u"item") {
1155 mLogger.error(childParser.getLineNumber())
1156 << "unexpected tag <"
1157 << childParser.getElementName()
1158 << "> in <array> resource."
1159 << std::endl;
1160 error = true;
1161 continue;
1162 }
1163
1164 std::unique_ptr<Item> item = parseXml(&childParser, typeMask, kNoRawString);
1165 if (!item) {
1166 error = true;
1167 continue;
1168 }
1169 array->items.emplace_back(std::move(item));
1170 }
1171
1172 if (error) {
1173 return false;
1174 }
1175
1176 return mTable->addResource(resourceName, mConfig, source, std::move(array));
1177}
1178
1179bool ResourceParser::parsePlural(XmlPullParser* parser, const ResourceNameRef& resourceName) {
1180 const SourceLine source = mSource.line(parser->getLineNumber());
1181 std::unique_ptr<Plural> plural = util::make_unique<Plural>();
1182
1183 bool success = true;
1184 while (XmlPullParser::isGoodEvent(parser->next())) {
1185 if (parser->getEvent() != XmlPullParser::Event::kStartElement) {
1186 continue;
1187 }
1188
1189 ScopedXmlPullParser childParser(parser);
1190
1191 if (!childParser.getElementNamespace().empty() ||
1192 childParser.getElementName() != u"item") {
1193 success = false;
1194 continue;
1195 }
1196
1197 const auto endAttrIter = childParser.endAttributes();
1198 auto attrIter = childParser.findAttribute(u"", u"quantity");
1199 if (attrIter == endAttrIter || attrIter->value.empty()) {
1200 mLogger.error(childParser.getLineNumber())
1201 << "<item> in <plurals> requires attribute 'quantity'."
1202 << std::endl;
1203 success = false;
1204 continue;
1205 }
1206
1207 StringPiece16 trimmedQuantity = util::trimWhitespace(attrIter->value);
1208 size_t index = 0;
1209 if (trimmedQuantity == u"zero") {
1210 index = Plural::Zero;
1211 } else if (trimmedQuantity == u"one") {
1212 index = Plural::One;
1213 } else if (trimmedQuantity == u"two") {
1214 index = Plural::Two;
1215 } else if (trimmedQuantity == u"few") {
1216 index = Plural::Few;
1217 } else if (trimmedQuantity == u"many") {
1218 index = Plural::Many;
1219 } else if (trimmedQuantity == u"other") {
1220 index = Plural::Other;
1221 } else {
1222 mLogger.error(childParser.getLineNumber())
1223 << "<item> in <plural> has invalid value '"
1224 << trimmedQuantity
1225 << "' for attribute 'quantity'."
1226 << std::endl;
1227 success = false;
1228 continue;
1229 }
1230
1231 if (plural->values[index]) {
1232 mLogger.error(childParser.getLineNumber())
1233 << "duplicate quantity '"
1234 << trimmedQuantity
1235 << "'."
1236 << std::endl;
1237 success = false;
1238 continue;
1239 }
1240
1241 if (!(plural->values[index] = parseXml(&childParser, android::ResTable_map::TYPE_STRING,
1242 kNoRawString))) {
1243 success = false;
1244 }
1245 }
1246
1247 if (!success) {
1248 return false;
1249 }
1250
1251 return mTable->addResource(resourceName, mConfig, source, std::move(plural));
1252}
1253
1254bool ResourceParser::parseDeclareStyleable(XmlPullParser* parser,
1255 const ResourceNameRef& resourceName) {
1256 const SourceLine source = mSource.line(parser->getLineNumber());
1257 std::unique_ptr<Styleable> styleable = util::make_unique<Styleable>();
1258
1259 bool success = true;
1260 while (XmlPullParser::isGoodEvent(parser->next())) {
1261 if (parser->getEvent() != XmlPullParser::Event::kStartElement) {
1262 continue;
1263 }
1264
1265 ScopedXmlPullParser childParser(parser);
1266
1267 const std::u16string& elementName = childParser.getElementName();
1268 if (elementName == u"attr") {
1269 const auto endAttrIter = childParser.endAttributes();
1270 auto attrIter = childParser.findAttribute(u"", u"name");
1271 if (attrIter == endAttrIter || attrIter->value.empty()) {
1272 mLogger.error(childParser.getLineNumber())
1273 << "<attr> tag must have a 'name' attribute."
1274 << std::endl;
1275 success = false;
1276 continue;
1277 }
1278
1279 // Copy because our iterator will be invalidated.
1280 std::u16string attrName = attrIter->value;
1281
1282 ResourceNameRef attrResourceName = {
1283 mTable->getPackage(),
1284 ResourceType::kAttr,
1285 attrName
1286 };
1287
1288 std::unique_ptr<Attribute> attr = parseAttrImpl(&childParser, attrResourceName, true);
1289 if (!attr) {
1290 success = false;
1291 continue;
1292 }
1293
1294 styleable->entries.emplace_back(attrResourceName);
1295
1296 success &= mTable->addResource(attrResourceName, mConfig,
1297 mSource.line(childParser.getLineNumber()),
1298 std::move(attr));
1299
1300 } else if (elementName != u"eat-comment" && elementName != u"skip") {
1301 mLogger.error(childParser.getLineNumber())
1302 << "<"
1303 << elementName
1304 << "> is not allowed inside <declare-styleable>."
1305 << std::endl;
1306 success = false;
1307 }
1308 }
1309
1310 if (!success) {
1311 return false;
1312 }
1313
1314 return mTable->addResource(resourceName, mConfig, source, std::move(styleable));
1315}
1316
1317} // namespace aapt