• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 "ResourceParser.h"
18 #include "ResourceTable.h"
19 #include "ResourceUtils.h"
20 #include "ResourceValues.h"
21 #include "ValueVisitor.h"
22 #include "util/ImmutableMap.h"
23 #include "util/Util.h"
24 #include "xml/XmlPullParser.h"
25 
26 #include <functional>
27 #include <sstream>
28 
29 namespace aapt {
30 
31 constexpr const char16_t* sXliffNamespaceUri = u"urn:oasis:names:tc:xliff:document:1.2";
32 
33 /**
34  * Returns true if the element is <skip> or <eat-comment> and can be safely ignored.
35  */
shouldIgnoreElement(const StringPiece16 & ns,const StringPiece16 & name)36 static bool shouldIgnoreElement(const StringPiece16& ns, const StringPiece16& name) {
37     return ns.empty() && (name == u"skip" || name == u"eat-comment");
38 }
39 
parseFormatType(const StringPiece16 & piece)40 static uint32_t parseFormatType(const StringPiece16& piece) {
41     if (piece == u"reference")      return android::ResTable_map::TYPE_REFERENCE;
42     else if (piece == u"string")    return android::ResTable_map::TYPE_STRING;
43     else if (piece == u"integer")   return android::ResTable_map::TYPE_INTEGER;
44     else if (piece == u"boolean")   return android::ResTable_map::TYPE_BOOLEAN;
45     else if (piece == u"color")     return android::ResTable_map::TYPE_COLOR;
46     else if (piece == u"float")     return android::ResTable_map::TYPE_FLOAT;
47     else if (piece == u"dimension") return android::ResTable_map::TYPE_DIMENSION;
48     else if (piece == u"fraction")  return android::ResTable_map::TYPE_FRACTION;
49     else if (piece == u"enum")      return android::ResTable_map::TYPE_ENUM;
50     else if (piece == u"flags")     return android::ResTable_map::TYPE_FLAGS;
51     return 0;
52 }
53 
parseFormatAttribute(const StringPiece16 & str)54 static uint32_t parseFormatAttribute(const StringPiece16& str) {
55     uint32_t mask = 0;
56     for (StringPiece16 part : util::tokenize(str, u'|')) {
57         StringPiece16 trimmedPart = util::trimWhitespace(part);
58         uint32_t type = parseFormatType(trimmedPart);
59         if (type == 0) {
60             return 0;
61         }
62         mask |= type;
63     }
64     return mask;
65 }
66 
67 /**
68  * A parsed resource ready to be added to the ResourceTable.
69  */
70 struct ParsedResource {
71     ResourceName name;
72     ConfigDescription config;
73     std::string product;
74     Source source;
75     ResourceId id;
76     Maybe<SymbolState> symbolState;
77     std::u16string comment;
78     std::unique_ptr<Value> value;
79     std::list<ParsedResource> childResources;
80 };
81 
82 // Recursively adds resources to the ResourceTable.
addResourcesToTable(ResourceTable * table,IDiagnostics * diag,ParsedResource * res)83 static bool addResourcesToTable(ResourceTable* table, IDiagnostics* diag, ParsedResource* res) {
84     StringPiece16 trimmedComment = util::trimWhitespace(res->comment);
85     if (trimmedComment.size() != res->comment.size()) {
86         // Only if there was a change do we re-assign.
87         res->comment = trimmedComment.toString();
88     }
89 
90     if (res->symbolState) {
91         Symbol symbol;
92         symbol.state = res->symbolState.value();
93         symbol.source = res->source;
94         symbol.comment = res->comment;
95         if (!table->setSymbolState(res->name, res->id, symbol, diag)) {
96             return false;
97         }
98     }
99 
100     if (res->value) {
101         // Attach the comment, source and config to the value.
102         res->value->setComment(std::move(res->comment));
103         res->value->setSource(std::move(res->source));
104 
105         if (!table->addResource(res->name, res->id, res->config, res->product,
106                                 std::move(res->value), diag)) {
107             return false;
108         }
109     }
110 
111     bool error = false;
112     for (ParsedResource& child : res->childResources) {
113         error |= !addResourcesToTable(table, diag, &child);
114     }
115     return !error;
116 }
117 
118 // Convenient aliases for more readable function calls.
119 enum {
120     kAllowRawString = true,
121     kNoRawString = false
122 };
123 
ResourceParser(IDiagnostics * diag,ResourceTable * table,const Source & source,const ConfigDescription & config,const ResourceParserOptions & options)124 ResourceParser::ResourceParser(IDiagnostics* diag, ResourceTable* table, const Source& source,
125                                const ConfigDescription& config,
126                                const ResourceParserOptions& options) :
127         mDiag(diag), mTable(table), mSource(source), mConfig(config), mOptions(options) {
128 }
129 
130 /**
131  * Build a string from XML that converts nested elements into Span objects.
132  */
flattenXmlSubtree(xml::XmlPullParser * parser,std::u16string * outRawString,StyleString * outStyleString)133 bool ResourceParser::flattenXmlSubtree(xml::XmlPullParser* parser, std::u16string* outRawString,
134                                        StyleString* outStyleString) {
135     std::vector<Span> spanStack;
136 
137     bool error = false;
138     outRawString->clear();
139     outStyleString->spans.clear();
140     util::StringBuilder builder;
141     size_t depth = 1;
142     while (xml::XmlPullParser::isGoodEvent(parser->next())) {
143         const xml::XmlPullParser::Event event = parser->getEvent();
144         if (event == xml::XmlPullParser::Event::kEndElement) {
145             if (!parser->getElementNamespace().empty()) {
146                 // We already warned and skipped the start element, so just skip here too
147                 continue;
148             }
149 
150             depth--;
151             if (depth == 0) {
152                 break;
153             }
154 
155             spanStack.back().lastChar = builder.str().size() - 1;
156             outStyleString->spans.push_back(spanStack.back());
157             spanStack.pop_back();
158 
159         } else if (event == xml::XmlPullParser::Event::kText) {
160             outRawString->append(parser->getText());
161             builder.append(parser->getText());
162 
163         } else if (event == xml::XmlPullParser::Event::kStartElement) {
164             if (!parser->getElementNamespace().empty()) {
165                 if (parser->getElementNamespace() != sXliffNamespaceUri) {
166                     // Only warn if this isn't an xliff namespace.
167                     mDiag->warn(DiagMessage(mSource.withLine(parser->getLineNumber()))
168                                 << "skipping element '"
169                                 << parser->getElementName()
170                                 << "' with unknown namespace '"
171                                 << parser->getElementNamespace()
172                                 << "'");
173                 }
174                 continue;
175             }
176             depth++;
177 
178             // Build a span object out of the nested element.
179             std::u16string spanName = parser->getElementName();
180             const auto endAttrIter = parser->endAttributes();
181             for (auto attrIter = parser->beginAttributes(); attrIter != endAttrIter; ++attrIter) {
182                 spanName += u";";
183                 spanName += attrIter->name;
184                 spanName += u"=";
185                 spanName += attrIter->value;
186             }
187 
188             if (builder.str().size() > std::numeric_limits<uint32_t>::max()) {
189                 mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
190                              << "style string '" << builder.str() << "' is too long");
191                 error = true;
192             } else {
193                 spanStack.push_back(Span{ spanName, static_cast<uint32_t>(builder.str().size()) });
194             }
195 
196         } else if (event == xml::XmlPullParser::Event::kComment) {
197             // Skip
198         } else {
199             assert(false);
200         }
201     }
202     assert(spanStack.empty() && "spans haven't been fully processed");
203 
204     outStyleString->str = builder.str();
205     return !error;
206 }
207 
parse(xml::XmlPullParser * parser)208 bool ResourceParser::parse(xml::XmlPullParser* parser) {
209     bool error = false;
210     const size_t depth = parser->getDepth();
211     while (xml::XmlPullParser::nextChildNode(parser, depth)) {
212         if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) {
213             // Skip comments and text.
214             continue;
215         }
216 
217         if (!parser->getElementNamespace().empty() || parser->getElementName() != u"resources") {
218             mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
219                          << "root element must be <resources>");
220             return false;
221         }
222 
223         error |= !parseResources(parser);
224         break;
225     };
226 
227     if (parser->getEvent() == xml::XmlPullParser::Event::kBadDocument) {
228         mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
229                      << "xml parser error: " << parser->getLastError());
230         return false;
231     }
232     return !error;
233 }
234 
parseResources(xml::XmlPullParser * parser)235 bool ResourceParser::parseResources(xml::XmlPullParser* parser) {
236     std::set<ResourceName> strippedResources;
237 
238     bool error = false;
239     std::u16string comment;
240     const size_t depth = parser->getDepth();
241     while (xml::XmlPullParser::nextChildNode(parser, depth)) {
242         const xml::XmlPullParser::Event event = parser->getEvent();
243         if (event == xml::XmlPullParser::Event::kComment) {
244             comment = parser->getComment();
245             continue;
246         }
247 
248         if (event == xml::XmlPullParser::Event::kText) {
249             if (!util::trimWhitespace(parser->getText()).empty()) {
250                 mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
251                              << "plain text not allowed here");
252                 error = true;
253             }
254             continue;
255         }
256 
257         assert(event == xml::XmlPullParser::Event::kStartElement);
258 
259         if (!parser->getElementNamespace().empty()) {
260             // Skip unknown namespace.
261             continue;
262         }
263 
264         std::u16string elementName = parser->getElementName();
265         if (elementName == u"skip" || elementName == u"eat-comment") {
266             comment = u"";
267             continue;
268         }
269 
270         ParsedResource parsedResource;
271         parsedResource.config = mConfig;
272         parsedResource.source = mSource.withLine(parser->getLineNumber());
273         parsedResource.comment = std::move(comment);
274 
275         // Extract the product name if it exists.
276         if (Maybe<StringPiece16> maybeProduct = xml::findNonEmptyAttribute(parser, u"product")) {
277             parsedResource.product = util::utf16ToUtf8(maybeProduct.value());
278         }
279 
280         // Parse the resource regardless of product.
281         if (!parseResource(parser, &parsedResource)) {
282             error = true;
283             continue;
284         }
285 
286         if (!addResourcesToTable(mTable, mDiag, &parsedResource)) {
287             error = true;
288         }
289     }
290 
291     // Check that we included at least one variant of each stripped resource.
292     for (const ResourceName& strippedResource : strippedResources) {
293         if (!mTable->findResource(strippedResource)) {
294             // Failed to find the resource.
295             mDiag->error(DiagMessage(mSource) << "resource '" << strippedResource << "' "
296                          "was filtered out but no product variant remains");
297             error = true;
298         }
299     }
300 
301     return !error;
302 }
303 
304 
parseResource(xml::XmlPullParser * parser,ParsedResource * outResource)305 bool ResourceParser::parseResource(xml::XmlPullParser* parser, ParsedResource* outResource) {
306     struct ItemTypeFormat {
307         ResourceType type;
308         uint32_t format;
309     };
310 
311     using BagParseFunc = std::function<bool(ResourceParser*, xml::XmlPullParser*, ParsedResource*)>;
312 
313     static const auto elToItemMap = ImmutableMap<std::u16string, ItemTypeFormat>::createPreSorted({
314             { u"bool",      { ResourceType::kBool, android::ResTable_map::TYPE_BOOLEAN } },
315             { u"color",     { ResourceType::kColor, android::ResTable_map::TYPE_COLOR } },
316             { u"dimen",     { ResourceType::kDimen, android::ResTable_map::TYPE_FLOAT
317                                                     | android::ResTable_map::TYPE_FRACTION
318                                                     | android::ResTable_map::TYPE_DIMENSION } },
319             { u"drawable",  { ResourceType::kDrawable, android::ResTable_map::TYPE_COLOR } },
320             { u"fraction",  { ResourceType::kFraction, android::ResTable_map::TYPE_FLOAT
321                                                        | android::ResTable_map::TYPE_FRACTION
322                                                        | android::ResTable_map::TYPE_DIMENSION } },
323             { u"integer",   { ResourceType::kInteger, android::ResTable_map::TYPE_INTEGER } },
324             { u"string",    { ResourceType::kString, android::ResTable_map::TYPE_STRING } },
325     });
326 
327     static const auto elToBagMap = ImmutableMap<std::u16string, BagParseFunc>::createPreSorted({
328             { u"add-resource",      std::mem_fn(&ResourceParser::parseAddResource) },
329             { u"array",             std::mem_fn(&ResourceParser::parseArray) },
330             { u"attr",              std::mem_fn(&ResourceParser::parseAttr) },
331             { u"declare-styleable", std::mem_fn(&ResourceParser::parseDeclareStyleable) },
332             { u"integer-array",     std::mem_fn(&ResourceParser::parseIntegerArray) },
333             { u"java-symbol",       std::mem_fn(&ResourceParser::parseSymbol) },
334             { u"plurals",           std::mem_fn(&ResourceParser::parsePlural) },
335             { u"public",            std::mem_fn(&ResourceParser::parsePublic) },
336             { u"public-group",      std::mem_fn(&ResourceParser::parsePublicGroup) },
337             { u"string-array",      std::mem_fn(&ResourceParser::parseStringArray) },
338             { u"style",             std::mem_fn(&ResourceParser::parseStyle) },
339             { u"symbol",            std::mem_fn(&ResourceParser::parseSymbol) },
340     });
341 
342     std::u16string resourceType = parser->getElementName();
343 
344     // The value format accepted for this resource.
345     uint32_t resourceFormat = 0u;
346 
347     if (resourceType == u"item") {
348         // Items have their type encoded in the type attribute.
349         if (Maybe<StringPiece16> maybeType = xml::findNonEmptyAttribute(parser, u"type")) {
350             resourceType = maybeType.value().toString();
351         } else {
352             mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
353                          << "<item> must have a 'type' attribute");
354             return false;
355         }
356 
357         if (Maybe<StringPiece16> maybeFormat = xml::findNonEmptyAttribute(parser, u"format")) {
358             // An explicit format for this resource was specified. The resource will retain
359             // its type in its name, but the accepted value for this type is overridden.
360             resourceFormat = parseFormatType(maybeFormat.value());
361             if (!resourceFormat) {
362                 mDiag->error(DiagMessage(outResource->source)
363                              << "'" << maybeFormat.value() << "' is an invalid format");
364                 return false;
365             }
366         }
367     }
368 
369     // Get the name of the resource. This will be checked later, because not all
370     // XML elements require a name.
371     Maybe<StringPiece16> maybeName = xml::findNonEmptyAttribute(parser, u"name");
372 
373     if (resourceType == u"id") {
374         if (!maybeName) {
375             mDiag->error(DiagMessage(outResource->source)
376                          << "<" << parser->getElementName() << "> missing 'name' attribute");
377             return false;
378         }
379 
380         outResource->name.type = ResourceType::kId;
381         outResource->name.entry = maybeName.value().toString();
382         outResource->value = util::make_unique<Id>();
383         return true;
384     }
385 
386     const auto itemIter = elToItemMap.find(resourceType);
387     if (itemIter != elToItemMap.end()) {
388         // This is an item, record its type and format and start parsing.
389 
390         if (!maybeName) {
391             mDiag->error(DiagMessage(outResource->source)
392                          << "<" << parser->getElementName() << "> missing 'name' attribute");
393             return false;
394         }
395 
396         outResource->name.type = itemIter->second.type;
397         outResource->name.entry = maybeName.value().toString();
398 
399         // Only use the implicit format for this type if it wasn't overridden.
400         if (!resourceFormat) {
401             resourceFormat = itemIter->second.format;
402         }
403 
404         if (!parseItem(parser, outResource, resourceFormat)) {
405             return false;
406         }
407         return true;
408     }
409 
410     // This might be a bag or something.
411     const auto bagIter = elToBagMap.find(resourceType);
412     if (bagIter != elToBagMap.end()) {
413         // Ensure we have a name (unless this is a <public-group>).
414         if (resourceType != u"public-group") {
415             if (!maybeName) {
416                 mDiag->error(DiagMessage(outResource->source)
417                              << "<" << parser->getElementName() << "> missing 'name' attribute");
418                 return false;
419             }
420 
421             outResource->name.entry = maybeName.value().toString();
422         }
423 
424         // Call the associated parse method. The type will be filled in by the
425         // parse func.
426         if (!bagIter->second(this, parser, outResource)) {
427             return false;
428         }
429         return true;
430     }
431 
432     // Try parsing the elementName (or type) as a resource. These shall only be
433     // resources like 'layout' or 'xml' and they can only be references.
434     const ResourceType* parsedType = parseResourceType(resourceType);
435     if (parsedType) {
436         if (!maybeName) {
437             mDiag->error(DiagMessage(outResource->source)
438                          << "<" << parser->getElementName() << "> missing 'name' attribute");
439             return false;
440         }
441 
442         outResource->name.type = *parsedType;
443         outResource->name.entry = maybeName.value().toString();
444         outResource->value = parseXml(parser, android::ResTable_map::TYPE_REFERENCE, kNoRawString);
445         if (!outResource->value) {
446             mDiag->error(DiagMessage(outResource->source)
447                          << "invalid value for type '" << *parsedType << "'. Expected a reference");
448             return false;
449         }
450         return true;
451     }
452 
453     mDiag->warn(DiagMessage(outResource->source)
454                 << "unknown resource type '" << parser->getElementName() << "'");
455     return false;
456 }
457 
parseItem(xml::XmlPullParser * parser,ParsedResource * outResource,const uint32_t format)458 bool ResourceParser::parseItem(xml::XmlPullParser* parser, ParsedResource* outResource,
459                                const uint32_t format) {
460     if (format == android::ResTable_map::TYPE_STRING) {
461         return parseString(parser, outResource);
462     }
463 
464     outResource->value = parseXml(parser, format, kNoRawString);
465     if (!outResource->value) {
466         mDiag->error(DiagMessage(outResource->source) << "invalid " << outResource->name.type);
467         return false;
468     }
469     return true;
470 }
471 
472 /**
473  * Reads the entire XML subtree and attempts to parse it as some Item,
474  * with typeMask denoting which items it can be. If allowRawValue is
475  * true, a RawString is returned if the XML couldn't be parsed as
476  * an Item. If allowRawValue is false, nullptr is returned in this
477  * case.
478  */
parseXml(xml::XmlPullParser * parser,const uint32_t typeMask,const bool allowRawValue)479 std::unique_ptr<Item> ResourceParser::parseXml(xml::XmlPullParser* parser, const uint32_t typeMask,
480                                                const bool allowRawValue) {
481     const size_t beginXmlLine = parser->getLineNumber();
482 
483     std::u16string rawValue;
484     StyleString styleString;
485     if (!flattenXmlSubtree(parser, &rawValue, &styleString)) {
486         return {};
487     }
488 
489     if (!styleString.spans.empty()) {
490         // This can only be a StyledString.
491         return util::make_unique<StyledString>(
492                 mTable->stringPool.makeRef(styleString, StringPool::Context{ 1, mConfig }));
493     }
494 
495     auto onCreateReference = [&](const ResourceName& name) {
496         // name.package can be empty here, as it will assume the package name of the table.
497         std::unique_ptr<Id> id = util::make_unique<Id>();
498         id->setSource(mSource.withLine(beginXmlLine));
499         mTable->addResource(name, {}, {}, std::move(id), mDiag);
500     };
501 
502     // Process the raw value.
503     std::unique_ptr<Item> processedItem = ResourceUtils::parseItemForAttribute(rawValue, typeMask,
504                                                                                onCreateReference);
505     if (processedItem) {
506         // Fix up the reference.
507         if (Reference* ref = valueCast<Reference>(processedItem.get())) {
508             transformReferenceFromNamespace(parser, u"", ref);
509         }
510         return processedItem;
511     }
512 
513     // Try making a regular string.
514     if (typeMask & android::ResTable_map::TYPE_STRING) {
515         // Use the trimmed, escaped string.
516         return util::make_unique<String>(
517                 mTable->stringPool.makeRef(styleString.str, StringPool::Context{ 1, mConfig }));
518     }
519 
520     if (allowRawValue) {
521         // We can't parse this so return a RawString if we are allowed.
522         return util::make_unique<RawString>(
523                 mTable->stringPool.makeRef(rawValue, StringPool::Context{ 1, mConfig }));
524     }
525     return {};
526 }
527 
parseString(xml::XmlPullParser * parser,ParsedResource * outResource)528 bool ResourceParser::parseString(xml::XmlPullParser* parser, ParsedResource* outResource) {
529     bool formatted = true;
530     if (Maybe<StringPiece16> formattedAttr = xml::findAttribute(parser, u"formatted")) {
531         if (!ResourceUtils::tryParseBool(formattedAttr.value(), &formatted)) {
532             mDiag->error(DiagMessage(outResource->source)
533                          << "invalid value for 'formatted'. Must be a boolean");
534             return false;
535         }
536     }
537 
538     bool translateable = mOptions.translatable;
539     if (Maybe<StringPiece16> translateableAttr = xml::findAttribute(parser, u"translatable")) {
540         if (!ResourceUtils::tryParseBool(translateableAttr.value(), &translateable)) {
541             mDiag->error(DiagMessage(outResource->source)
542                          << "invalid value for 'translatable'. Must be a boolean");
543             return false;
544         }
545     }
546 
547     outResource->value = parseXml(parser, android::ResTable_map::TYPE_STRING, kNoRawString);
548     if (!outResource->value) {
549         mDiag->error(DiagMessage(outResource->source) << "not a valid string");
550         return false;
551     }
552 
553     if (String* stringValue = valueCast<String>(outResource->value.get())) {
554         stringValue->setTranslateable(translateable);
555 
556         if (formatted && translateable) {
557             if (!util::verifyJavaStringFormat(*stringValue->value)) {
558                 DiagMessage msg(outResource->source);
559                 msg << "multiple substitutions specified in non-positional format; "
560                        "did you mean to add the formatted=\"false\" attribute?";
561                 if (mOptions.errorOnPositionalArguments) {
562                     mDiag->error(msg);
563                     return false;
564                 }
565 
566                 mDiag->warn(msg);
567             }
568         }
569 
570     } else if (StyledString* stringValue = valueCast<StyledString>(outResource->value.get())) {
571         stringValue->setTranslateable(translateable);
572     }
573     return true;
574 }
575 
parsePublic(xml::XmlPullParser * parser,ParsedResource * outResource)576 bool ResourceParser::parsePublic(xml::XmlPullParser* parser, ParsedResource* outResource) {
577     Maybe<StringPiece16> maybeType = xml::findNonEmptyAttribute(parser, u"type");
578     if (!maybeType) {
579         mDiag->error(DiagMessage(outResource->source) << "<public> must have a 'type' attribute");
580         return false;
581     }
582 
583     const ResourceType* parsedType = parseResourceType(maybeType.value());
584     if (!parsedType) {
585         mDiag->error(DiagMessage(outResource->source)
586                      << "invalid resource type '" << maybeType.value() << "' in <public>");
587         return false;
588     }
589 
590     outResource->name.type = *parsedType;
591 
592     if (Maybe<StringPiece16> maybeId = xml::findNonEmptyAttribute(parser, u"id")) {
593         android::Res_value val;
594         bool result = android::ResTable::stringToInt(maybeId.value().data(),
595                                                      maybeId.value().size(), &val);
596         ResourceId resourceId(val.data);
597         if (!result || !resourceId.isValid()) {
598             mDiag->error(DiagMessage(outResource->source)
599                          << "invalid resource ID '" << maybeId.value() << "' in <public>");
600             return false;
601         }
602         outResource->id = resourceId;
603     }
604 
605     if (*parsedType == ResourceType::kId) {
606         // An ID marked as public is also the definition of an ID.
607         outResource->value = util::make_unique<Id>();
608     }
609 
610     outResource->symbolState = SymbolState::kPublic;
611     return true;
612 }
613 
parsePublicGroup(xml::XmlPullParser * parser,ParsedResource * outResource)614 bool ResourceParser::parsePublicGroup(xml::XmlPullParser* parser, ParsedResource* outResource) {
615     Maybe<StringPiece16> maybeType = xml::findNonEmptyAttribute(parser, u"type");
616     if (!maybeType) {
617         mDiag->error(DiagMessage(outResource->source)
618                      << "<public-group> must have a 'type' attribute");
619         return false;
620     }
621 
622     const ResourceType* parsedType = parseResourceType(maybeType.value());
623     if (!parsedType) {
624         mDiag->error(DiagMessage(outResource->source)
625                      << "invalid resource type '" << maybeType.value() << "' in <public-group>");
626         return false;
627     }
628 
629     Maybe<StringPiece16> maybeId = xml::findNonEmptyAttribute(parser, u"first-id");
630     if (!maybeId) {
631         mDiag->error(DiagMessage(outResource->source)
632                      << "<public-group> must have a 'first-id' attribute");
633         return false;
634     }
635 
636     android::Res_value val;
637     bool result = android::ResTable::stringToInt(maybeId.value().data(),
638                                                  maybeId.value().size(), &val);
639     ResourceId nextId(val.data);
640     if (!result || !nextId.isValid()) {
641         mDiag->error(DiagMessage(outResource->source)
642                      << "invalid resource ID '" << maybeId.value() << "' in <public-group>");
643         return false;
644     }
645 
646     std::u16string comment;
647     bool error = false;
648     const size_t depth = parser->getDepth();
649     while (xml::XmlPullParser::nextChildNode(parser, depth)) {
650         if (parser->getEvent() == xml::XmlPullParser::Event::kComment) {
651             comment = util::trimWhitespace(parser->getComment()).toString();
652             continue;
653         } else if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) {
654             // Skip text.
655             continue;
656         }
657 
658         const Source itemSource = mSource.withLine(parser->getLineNumber());
659         const std::u16string& elementNamespace = parser->getElementNamespace();
660         const std::u16string& elementName = parser->getElementName();
661         if (elementNamespace.empty() && elementName == u"public") {
662             Maybe<StringPiece16> maybeName = xml::findNonEmptyAttribute(parser, u"name");
663             if (!maybeName) {
664                 mDiag->error(DiagMessage(itemSource) << "<public> must have a 'name' attribute");
665                 error = true;
666                 continue;
667             }
668 
669             if (xml::findNonEmptyAttribute(parser, u"id")) {
670                 mDiag->error(DiagMessage(itemSource) << "'id' is ignored within <public-group>");
671                 error = true;
672                 continue;
673             }
674 
675             if (xml::findNonEmptyAttribute(parser, u"type")) {
676                 mDiag->error(DiagMessage(itemSource) << "'type' is ignored within <public-group>");
677                 error = true;
678                 continue;
679             }
680 
681             ParsedResource childResource;
682             childResource.name.type = *parsedType;
683             childResource.name.entry = maybeName.value().toString();
684             childResource.id = nextId;
685             childResource.comment = std::move(comment);
686             childResource.source = itemSource;
687             childResource.symbolState = SymbolState::kPublic;
688             outResource->childResources.push_back(std::move(childResource));
689 
690             nextId.id += 1;
691 
692         } else if (!shouldIgnoreElement(elementNamespace, elementName)) {
693             mDiag->error(DiagMessage(itemSource) << ":" << elementName << ">");
694             error = true;
695         }
696     }
697     return !error;
698 }
699 
parseSymbolImpl(xml::XmlPullParser * parser,ParsedResource * outResource)700 bool ResourceParser::parseSymbolImpl(xml::XmlPullParser* parser, ParsedResource* outResource) {
701     Maybe<StringPiece16> maybeType = xml::findNonEmptyAttribute(parser, u"type");
702     if (!maybeType) {
703         mDiag->error(DiagMessage(outResource->source)
704                      << "<" << parser->getElementName() << "> must have a 'type' attribute");
705         return false;
706     }
707 
708     const ResourceType* parsedType = parseResourceType(maybeType.value());
709     if (!parsedType) {
710         mDiag->error(DiagMessage(outResource->source)
711                      << "invalid resource type '" << maybeType.value()
712                      << "' in <" << parser->getElementName() << ">");
713         return false;
714     }
715 
716     outResource->name.type = *parsedType;
717     return true;
718 }
719 
parseSymbol(xml::XmlPullParser * parser,ParsedResource * outResource)720 bool ResourceParser::parseSymbol(xml::XmlPullParser* parser, ParsedResource* outResource) {
721     if (parseSymbolImpl(parser, outResource)) {
722         outResource->symbolState = SymbolState::kPrivate;
723         return true;
724     }
725     return false;
726 }
727 
parseAddResource(xml::XmlPullParser * parser,ParsedResource * outResource)728 bool ResourceParser::parseAddResource(xml::XmlPullParser* parser, ParsedResource* outResource) {
729     if (parseSymbolImpl(parser, outResource)) {
730         outResource->symbolState = SymbolState::kUndefined;
731         return true;
732     }
733     return false;
734 }
735 
736 
parseAttr(xml::XmlPullParser * parser,ParsedResource * outResource)737 bool ResourceParser::parseAttr(xml::XmlPullParser* parser, ParsedResource* outResource) {
738     return parseAttrImpl(parser, outResource, false);
739 }
740 
parseAttrImpl(xml::XmlPullParser * parser,ParsedResource * outResource,bool weak)741 bool ResourceParser::parseAttrImpl(xml::XmlPullParser* parser, ParsedResource* outResource,
742                                    bool weak) {
743     outResource->name.type = ResourceType::kAttr;
744 
745     // Attributes only end up in default configuration.
746     if (outResource->config != ConfigDescription::defaultConfig()) {
747         mDiag->warn(DiagMessage(outResource->source) << "ignoring configuration '"
748                     << outResource->config << "' for attribute " << outResource->name);
749         outResource->config = ConfigDescription::defaultConfig();
750     }
751 
752     uint32_t typeMask = 0;
753 
754     Maybe<StringPiece16> maybeFormat = xml::findAttribute(parser, u"format");
755     if (maybeFormat) {
756         typeMask = parseFormatAttribute(maybeFormat.value());
757         if (typeMask == 0) {
758             mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
759                          << "invalid attribute format '" << maybeFormat.value() << "'");
760             return false;
761         }
762     }
763 
764     Maybe<int32_t> maybeMin, maybeMax;
765 
766     if (Maybe<StringPiece16> maybeMinStr = xml::findAttribute(parser, u"min")) {
767         StringPiece16 minStr = util::trimWhitespace(maybeMinStr.value());
768         if (!minStr.empty()) {
769             android::Res_value value;
770             if (android::ResTable::stringToInt(minStr.data(), minStr.size(), &value)) {
771                 maybeMin = static_cast<int32_t>(value.data);
772             }
773         }
774 
775         if (!maybeMin) {
776             mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
777                          << "invalid 'min' value '" << minStr << "'");
778             return false;
779         }
780     }
781 
782     if (Maybe<StringPiece16> maybeMaxStr = xml::findAttribute(parser, u"max")) {
783         StringPiece16 maxStr = util::trimWhitespace(maybeMaxStr.value());
784         if (!maxStr.empty()) {
785             android::Res_value value;
786             if (android::ResTable::stringToInt(maxStr.data(), maxStr.size(), &value)) {
787                 maybeMax = static_cast<int32_t>(value.data);
788             }
789         }
790 
791         if (!maybeMax) {
792             mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
793                          << "invalid 'max' value '" << maxStr << "'");
794             return false;
795         }
796     }
797 
798     if ((maybeMin || maybeMax) && (typeMask & android::ResTable_map::TYPE_INTEGER) == 0) {
799         mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
800                      << "'min' and 'max' can only be used when format='integer'");
801         return false;
802     }
803 
804     struct SymbolComparator {
805         bool operator()(const Attribute::Symbol& a, const Attribute::Symbol& b) {
806             return a.symbol.name.value() < b.symbol.name.value();
807         }
808     };
809 
810     std::set<Attribute::Symbol, SymbolComparator> items;
811 
812     std::u16string comment;
813     bool error = false;
814     const size_t depth = parser->getDepth();
815     while (xml::XmlPullParser::nextChildNode(parser, depth)) {
816         if (parser->getEvent() == xml::XmlPullParser::Event::kComment) {
817             comment = util::trimWhitespace(parser->getComment()).toString();
818             continue;
819         } else if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) {
820             // Skip text.
821             continue;
822         }
823 
824         const Source itemSource = mSource.withLine(parser->getLineNumber());
825         const std::u16string& elementNamespace = parser->getElementNamespace();
826         const std::u16string& elementName = parser->getElementName();
827         if (elementNamespace.empty() && (elementName == u"flag" || elementName == u"enum")) {
828             if (elementName == u"enum") {
829                 if (typeMask & android::ResTable_map::TYPE_FLAGS) {
830                     mDiag->error(DiagMessage(itemSource)
831                                  << "can not define an <enum>; already defined a <flag>");
832                     error = true;
833                     continue;
834                 }
835                 typeMask |= android::ResTable_map::TYPE_ENUM;
836 
837             } else if (elementName == u"flag") {
838                 if (typeMask & android::ResTable_map::TYPE_ENUM) {
839                     mDiag->error(DiagMessage(itemSource)
840                                  << "can not define a <flag>; already defined an <enum>");
841                     error = true;
842                     continue;
843                 }
844                 typeMask |= android::ResTable_map::TYPE_FLAGS;
845             }
846 
847             if (Maybe<Attribute::Symbol> s = parseEnumOrFlagItem(parser, elementName)) {
848                 Attribute::Symbol& symbol = s.value();
849                 ParsedResource childResource;
850                 childResource.name = symbol.symbol.name.value();
851                 childResource.source = itemSource;
852                 childResource.value = util::make_unique<Id>();
853                 outResource->childResources.push_back(std::move(childResource));
854 
855                 symbol.symbol.setComment(std::move(comment));
856                 symbol.symbol.setSource(itemSource);
857 
858                 auto insertResult = items.insert(std::move(symbol));
859                 if (!insertResult.second) {
860                     const Attribute::Symbol& existingSymbol = *insertResult.first;
861                     mDiag->error(DiagMessage(itemSource)
862                                  << "duplicate symbol '" << existingSymbol.symbol.name.value().entry
863                                  << "'");
864 
865                     mDiag->note(DiagMessage(existingSymbol.symbol.getSource())
866                                 << "first defined here");
867                     error = true;
868                 }
869             } else {
870                 error = true;
871             }
872         } else if (!shouldIgnoreElement(elementNamespace, elementName)) {
873             mDiag->error(DiagMessage(itemSource) << ":" << elementName << ">");
874             error = true;
875         }
876 
877         comment = {};
878     }
879 
880     if (error) {
881         return false;
882     }
883 
884     std::unique_ptr<Attribute> attr = util::make_unique<Attribute>(weak);
885     attr->symbols = std::vector<Attribute::Symbol>(items.begin(), items.end());
886     attr->typeMask = typeMask ? typeMask : uint32_t(android::ResTable_map::TYPE_ANY);
887     if (maybeMin) {
888         attr->minInt = maybeMin.value();
889     }
890 
891     if (maybeMax) {
892         attr->maxInt = maybeMax.value();
893     }
894     outResource->value = std::move(attr);
895     return true;
896 }
897 
parseEnumOrFlagItem(xml::XmlPullParser * parser,const StringPiece16 & tag)898 Maybe<Attribute::Symbol> ResourceParser::parseEnumOrFlagItem(xml::XmlPullParser* parser,
899                                                              const StringPiece16& tag) {
900     const Source source = mSource.withLine(parser->getLineNumber());
901 
902     Maybe<StringPiece16> maybeName = xml::findNonEmptyAttribute(parser, u"name");
903     if (!maybeName) {
904         mDiag->error(DiagMessage(source) << "no attribute 'name' found for tag <" << tag << ">");
905         return {};
906     }
907 
908     Maybe<StringPiece16> maybeValue = xml::findNonEmptyAttribute(parser, u"value");
909     if (!maybeValue) {
910         mDiag->error(DiagMessage(source) << "no attribute 'value' found for tag <" << tag << ">");
911         return {};
912     }
913 
914     android::Res_value val;
915     if (!android::ResTable::stringToInt(maybeValue.value().data(),
916                                         maybeValue.value().size(), &val)) {
917         mDiag->error(DiagMessage(source) << "invalid value '" << maybeValue.value()
918                      << "' for <" << tag << ">; must be an integer");
919         return {};
920     }
921 
922     return Attribute::Symbol{
923             Reference(ResourceNameRef({}, ResourceType::kId, maybeName.value())), val.data };
924 }
925 
parseXmlAttributeName(StringPiece16 str)926 static Maybe<Reference> parseXmlAttributeName(StringPiece16 str) {
927     str = util::trimWhitespace(str);
928     const char16_t* start = str.data();
929     const char16_t* const end = start + str.size();
930     const char16_t* p = start;
931 
932     Reference ref;
933     if (p != end && *p == u'*') {
934         ref.privateReference = true;
935         start++;
936         p++;
937     }
938 
939     StringPiece16 package;
940     StringPiece16 name;
941     while (p != end) {
942         if (*p == u':') {
943             package = StringPiece16(start, p - start);
944             name = StringPiece16(p + 1, end - (p + 1));
945             break;
946         }
947         p++;
948     }
949 
950     ref.name = ResourceName(package.toString(), ResourceType::kAttr,
951                         name.empty() ? str.toString() : name.toString());
952     return Maybe<Reference>(std::move(ref));
953 }
954 
parseStyleItem(xml::XmlPullParser * parser,Style * style)955 bool ResourceParser::parseStyleItem(xml::XmlPullParser* parser, Style* style) {
956     const Source source = mSource.withLine(parser->getLineNumber());
957 
958     Maybe<StringPiece16> maybeName = xml::findNonEmptyAttribute(parser, u"name");
959     if (!maybeName) {
960         mDiag->error(DiagMessage(source) << "<item> must have a 'name' attribute");
961         return false;
962     }
963 
964     Maybe<Reference> maybeKey = parseXmlAttributeName(maybeName.value());
965     if (!maybeKey) {
966         mDiag->error(DiagMessage(source) << "invalid attribute name '" << maybeName.value() << "'");
967         return false;
968     }
969 
970     transformReferenceFromNamespace(parser, u"", &maybeKey.value());
971     maybeKey.value().setSource(source);
972 
973     std::unique_ptr<Item> value = parseXml(parser, 0, kAllowRawString);
974     if (!value) {
975         mDiag->error(DiagMessage(source) << "could not parse style item");
976         return false;
977     }
978 
979     style->entries.push_back(Style::Entry{ std::move(maybeKey.value()), std::move(value) });
980     return true;
981 }
982 
parseStyle(xml::XmlPullParser * parser,ParsedResource * outResource)983 bool ResourceParser::parseStyle(xml::XmlPullParser* parser, ParsedResource* outResource) {
984     outResource->name.type = ResourceType::kStyle;
985 
986     std::unique_ptr<Style> style = util::make_unique<Style>();
987 
988     Maybe<StringPiece16> maybeParent = xml::findAttribute(parser, u"parent");
989     if (maybeParent) {
990         // If the parent is empty, we don't have a parent, but we also don't infer either.
991         if (!maybeParent.value().empty()) {
992             std::string errStr;
993             style->parent = ResourceUtils::parseStyleParentReference(maybeParent.value(), &errStr);
994             if (!style->parent) {
995                 mDiag->error(DiagMessage(outResource->source) << errStr);
996                 return false;
997             }
998 
999             // Transform the namespace prefix to the actual package name, and mark the reference as
1000             // private if appropriate.
1001             transformReferenceFromNamespace(parser, u"", &style->parent.value());
1002         }
1003 
1004     } else {
1005         // No parent was specified, so try inferring it from the style name.
1006         std::u16string styleName = outResource->name.entry;
1007         size_t pos = styleName.find_last_of(u'.');
1008         if (pos != std::string::npos) {
1009             style->parentInferred = true;
1010             style->parent = Reference(ResourceName({}, ResourceType::kStyle,
1011                                                    styleName.substr(0, pos)));
1012         }
1013     }
1014 
1015     bool error = false;
1016     const size_t depth = parser->getDepth();
1017     while (xml::XmlPullParser::nextChildNode(parser, depth)) {
1018         if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) {
1019             // Skip text and comments.
1020             continue;
1021         }
1022 
1023         const std::u16string& elementNamespace = parser->getElementNamespace();
1024         const std::u16string& elementName = parser->getElementName();
1025         if (elementNamespace == u"" && elementName == u"item") {
1026             error |= !parseStyleItem(parser, style.get());
1027 
1028         } else if (!shouldIgnoreElement(elementNamespace, elementName)) {
1029             mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
1030                          << ":" << elementName << ">");
1031             error = true;
1032         }
1033     }
1034 
1035     if (error) {
1036         return false;
1037     }
1038 
1039     outResource->value = std::move(style);
1040     return true;
1041 }
1042 
parseArray(xml::XmlPullParser * parser,ParsedResource * outResource)1043 bool ResourceParser::parseArray(xml::XmlPullParser* parser, ParsedResource* outResource) {
1044     return parseArrayImpl(parser, outResource, android::ResTable_map::TYPE_ANY);
1045 }
1046 
parseIntegerArray(xml::XmlPullParser * parser,ParsedResource * outResource)1047 bool ResourceParser::parseIntegerArray(xml::XmlPullParser* parser, ParsedResource* outResource) {
1048     return parseArrayImpl(parser, outResource, android::ResTable_map::TYPE_INTEGER);
1049 }
1050 
parseStringArray(xml::XmlPullParser * parser,ParsedResource * outResource)1051 bool ResourceParser::parseStringArray(xml::XmlPullParser* parser, ParsedResource* outResource) {
1052     return parseArrayImpl(parser, outResource, android::ResTable_map::TYPE_STRING);
1053 }
1054 
parseArrayImpl(xml::XmlPullParser * parser,ParsedResource * outResource,const uint32_t typeMask)1055 bool ResourceParser::parseArrayImpl(xml::XmlPullParser* parser, ParsedResource* outResource,
1056                                     const uint32_t typeMask) {
1057     outResource->name.type = ResourceType::kArray;
1058 
1059     std::unique_ptr<Array> array = util::make_unique<Array>();
1060 
1061     bool translateable = mOptions.translatable;
1062     if (Maybe<StringPiece16> translateableAttr = xml::findAttribute(parser, u"translatable")) {
1063         if (!ResourceUtils::tryParseBool(translateableAttr.value(), &translateable)) {
1064             mDiag->error(DiagMessage(outResource->source)
1065                          << "invalid value for 'translatable'. Must be a boolean");
1066             return false;
1067         }
1068     }
1069     array->setTranslateable(translateable);
1070 
1071     bool error = false;
1072     const size_t depth = parser->getDepth();
1073     while (xml::XmlPullParser::nextChildNode(parser, depth)) {
1074         if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) {
1075             // Skip text and comments.
1076             continue;
1077         }
1078 
1079         const Source itemSource = mSource.withLine(parser->getLineNumber());
1080         const std::u16string& elementNamespace = parser->getElementNamespace();
1081         const std::u16string& elementName = parser->getElementName();
1082         if (elementNamespace.empty() && elementName == u"item") {
1083             std::unique_ptr<Item> item = parseXml(parser, typeMask, kNoRawString);
1084             if (!item) {
1085                 mDiag->error(DiagMessage(itemSource) << "could not parse array item");
1086                 error = true;
1087                 continue;
1088             }
1089             item->setSource(itemSource);
1090             array->items.emplace_back(std::move(item));
1091 
1092         } else if (!shouldIgnoreElement(elementNamespace, elementName)) {
1093             mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
1094                          << "unknown tag <" << elementNamespace << ":" << elementName << ">");
1095             error = true;
1096         }
1097     }
1098 
1099     if (error) {
1100         return false;
1101     }
1102 
1103     outResource->value = std::move(array);
1104     return true;
1105 }
1106 
parsePlural(xml::XmlPullParser * parser,ParsedResource * outResource)1107 bool ResourceParser::parsePlural(xml::XmlPullParser* parser, ParsedResource* outResource) {
1108     outResource->name.type = ResourceType::kPlurals;
1109 
1110     std::unique_ptr<Plural> plural = util::make_unique<Plural>();
1111 
1112     bool error = false;
1113     const size_t depth = parser->getDepth();
1114     while (xml::XmlPullParser::nextChildNode(parser, depth)) {
1115         if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) {
1116             // Skip text and comments.
1117             continue;
1118         }
1119 
1120         const Source itemSource = mSource.withLine(parser->getLineNumber());
1121         const std::u16string& elementNamespace = parser->getElementNamespace();
1122         const std::u16string& elementName = parser->getElementName();
1123         if (elementNamespace.empty() && elementName == u"item") {
1124             Maybe<StringPiece16> maybeQuantity = xml::findNonEmptyAttribute(parser, u"quantity");
1125             if (!maybeQuantity) {
1126                 mDiag->error(DiagMessage(itemSource) << "<item> in <plurals> requires attribute "
1127                              << "'quantity'");
1128                 error = true;
1129                 continue;
1130             }
1131 
1132             StringPiece16 trimmedQuantity = util::trimWhitespace(maybeQuantity.value());
1133             size_t index = 0;
1134             if (trimmedQuantity == u"zero") {
1135                 index = Plural::Zero;
1136             } else if (trimmedQuantity == u"one") {
1137                 index = Plural::One;
1138             } else if (trimmedQuantity == u"two") {
1139                 index = Plural::Two;
1140             } else if (trimmedQuantity == u"few") {
1141                 index = Plural::Few;
1142             } else if (trimmedQuantity == u"many") {
1143                 index = Plural::Many;
1144             } else if (trimmedQuantity == u"other") {
1145                 index = Plural::Other;
1146             } else {
1147                 mDiag->error(DiagMessage(itemSource)
1148                              << "<item> in <plural> has invalid value '" << trimmedQuantity
1149                              << "' for attribute 'quantity'");
1150                 error = true;
1151                 continue;
1152             }
1153 
1154             if (plural->values[index]) {
1155                 mDiag->error(DiagMessage(itemSource)
1156                              << "duplicate quantity '" << trimmedQuantity << "'");
1157                 error = true;
1158                 continue;
1159             }
1160 
1161             if (!(plural->values[index] = parseXml(parser, android::ResTable_map::TYPE_STRING,
1162                                                    kNoRawString))) {
1163                 error = true;
1164             }
1165             plural->values[index]->setSource(itemSource);
1166 
1167         } else if (!shouldIgnoreElement(elementNamespace, elementName)) {
1168             mDiag->error(DiagMessage(itemSource) << "unknown tag <" << elementNamespace << ":"
1169                          << elementName << ">");
1170             error = true;
1171         }
1172     }
1173 
1174     if (error) {
1175         return false;
1176     }
1177 
1178     outResource->value = std::move(plural);
1179     return true;
1180 }
1181 
parseDeclareStyleable(xml::XmlPullParser * parser,ParsedResource * outResource)1182 bool ResourceParser::parseDeclareStyleable(xml::XmlPullParser* parser,
1183                                            ParsedResource* outResource) {
1184     outResource->name.type = ResourceType::kStyleable;
1185 
1186     // Declare-styleable is kPrivate by default, because it technically only exists in R.java.
1187     outResource->symbolState = SymbolState::kPublic;
1188 
1189     // Declare-styleable only ends up in default config;
1190     if (outResource->config != ConfigDescription::defaultConfig()) {
1191         mDiag->warn(DiagMessage(outResource->source) << "ignoring configuration '"
1192                             << outResource->config << "' for styleable "
1193                             << outResource->name.entry);
1194         outResource->config = ConfigDescription::defaultConfig();
1195     }
1196 
1197     std::unique_ptr<Styleable> styleable = util::make_unique<Styleable>();
1198 
1199     std::u16string comment;
1200     bool error = false;
1201     const size_t depth = parser->getDepth();
1202     while (xml::XmlPullParser::nextChildNode(parser, depth)) {
1203         if (parser->getEvent() == xml::XmlPullParser::Event::kComment) {
1204             comment = util::trimWhitespace(parser->getComment()).toString();
1205             continue;
1206         } else if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) {
1207             // Ignore text.
1208             continue;
1209         }
1210 
1211         const Source itemSource = mSource.withLine(parser->getLineNumber());
1212         const std::u16string& elementNamespace = parser->getElementNamespace();
1213         const std::u16string& elementName = parser->getElementName();
1214         if (elementNamespace.empty() && elementName == u"attr") {
1215             Maybe<StringPiece16> maybeName = xml::findNonEmptyAttribute(parser, u"name");
1216             if (!maybeName) {
1217                 mDiag->error(DiagMessage(itemSource) << "<attr> tag must have a 'name' attribute");
1218                 error = true;
1219                 continue;
1220             }
1221 
1222             // If this is a declaration, the package name may be in the name. Separate these out.
1223             // Eg. <attr name="android:text" />
1224             Maybe<Reference> maybeRef = parseXmlAttributeName(maybeName.value());
1225             if (!maybeRef) {
1226                 mDiag->error(DiagMessage(itemSource) << "<attr> tag has invalid name '"
1227                              << maybeName.value() << "'");
1228                 error = true;
1229                 continue;
1230             }
1231 
1232             Reference& childRef = maybeRef.value();
1233             xml::transformReferenceFromNamespace(parser, u"", &childRef);
1234 
1235             // Create the ParsedResource that will add the attribute to the table.
1236             ParsedResource childResource;
1237             childResource.name = childRef.name.value();
1238             childResource.source = itemSource;
1239             childResource.comment = std::move(comment);
1240 
1241             if (!parseAttrImpl(parser, &childResource, true)) {
1242                 error = true;
1243                 continue;
1244             }
1245 
1246             // Create the reference to this attribute.
1247             childRef.setComment(childResource.comment);
1248             childRef.setSource(itemSource);
1249             styleable->entries.push_back(std::move(childRef));
1250 
1251             outResource->childResources.push_back(std::move(childResource));
1252 
1253         } else if (!shouldIgnoreElement(elementNamespace, elementName)) {
1254             mDiag->error(DiagMessage(itemSource) << "unknown tag <" << elementNamespace << ":"
1255                          << elementName << ">");
1256             error = true;
1257         }
1258 
1259         comment = {};
1260     }
1261 
1262     if (error) {
1263         return false;
1264     }
1265 
1266     outResource->value = std::move(styleable);
1267     return true;
1268 }
1269 
1270 } // namespace aapt
1271