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