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