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 
19 #include <functional>
20 #include <limits>
21 #include <sstream>
22 
23 #include "android-base/logging.h"
24 
25 #include "ResourceTable.h"
26 #include "ResourceUtils.h"
27 #include "ResourceValues.h"
28 #include "ValueVisitor.h"
29 #include "text/Utf8Iterator.h"
30 #include "util/ImmutableMap.h"
31 #include "util/Maybe.h"
32 #include "util/Util.h"
33 #include "xml/XmlPullParser.h"
34 
35 #include "idmap2/Policies.h"
36 
37 using ::aapt::ResourceUtils::StringBuilder;
38 using ::aapt::text::Utf8Iterator;
39 using ::android::ConfigDescription;
40 using ::android::StringPiece;
41 
42 using android::idmap2::policy::kPolicyStringToFlag;
43 
44 namespace aapt {
45 namespace {
46 constexpr const char* kPublicGroupTag = "public-group";
47 constexpr const char* kStagingPublicGroupTag = "staging-public-group";
48 constexpr const char* kStagingPublicGroupFinalTag = "staging-public-group-final";
49 }  // namespace
50 
51 constexpr const char* sXliffNamespaceUri = "urn:oasis:names:tc:xliff:document:1.2";
52 
53 // Returns true if the element is <skip> or <eat-comment> and can be safely ignored.
ShouldIgnoreElement(const StringPiece & ns,const StringPiece & name)54 static bool ShouldIgnoreElement(const StringPiece& ns, const StringPiece& name) {
55   return ns.empty() && (name == "skip" || name == "eat-comment");
56 }
57 
ParseFormatTypeNoEnumsOrFlags(const StringPiece & piece)58 static uint32_t ParseFormatTypeNoEnumsOrFlags(const StringPiece& piece) {
59   if (piece == "reference") {
60     return android::ResTable_map::TYPE_REFERENCE;
61   } else if (piece == "string") {
62     return android::ResTable_map::TYPE_STRING;
63   } else if (piece == "integer") {
64     return android::ResTable_map::TYPE_INTEGER;
65   } else if (piece == "boolean") {
66     return android::ResTable_map::TYPE_BOOLEAN;
67   } else if (piece == "color") {
68     return android::ResTable_map::TYPE_COLOR;
69   } else if (piece == "float") {
70     return android::ResTable_map::TYPE_FLOAT;
71   } else if (piece == "dimension") {
72     return android::ResTable_map::TYPE_DIMENSION;
73   } else if (piece == "fraction") {
74     return android::ResTable_map::TYPE_FRACTION;
75   }
76   return 0;
77 }
78 
ParseFormatType(const StringPiece & piece)79 static uint32_t ParseFormatType(const StringPiece& piece) {
80   if (piece == "enum") {
81     return android::ResTable_map::TYPE_ENUM;
82   } else if (piece == "flags") {
83     return android::ResTable_map::TYPE_FLAGS;
84   }
85   return ParseFormatTypeNoEnumsOrFlags(piece);
86 }
87 
ParseFormatAttribute(const StringPiece & str)88 static uint32_t ParseFormatAttribute(const StringPiece& str) {
89   uint32_t mask = 0;
90   for (const StringPiece& part : util::Tokenize(str, '|')) {
91     StringPiece trimmed_part = util::TrimWhitespace(part);
92     uint32_t type = ParseFormatType(trimmed_part);
93     if (type == 0) {
94       return 0;
95     }
96     mask |= type;
97   }
98   return mask;
99 }
100 
101 // A parsed resource ready to be added to the ResourceTable.
102 struct ParsedResource {
103   ResourceName name;
104   ConfigDescription config;
105   std::string product;
106   Source source;
107 
108   ResourceId id;
109   Visibility::Level visibility_level = Visibility::Level::kUndefined;
110   bool staged_api = false;
111   bool allow_new = false;
112   Maybe<OverlayableItem> overlayable_item;
113   Maybe<StagedId> staged_alias;
114 
115   std::string comment;
116   std::unique_ptr<Value> value;
117   std::list<ParsedResource> child_resources;
118 };
119 
120 // Recursively adds resources to the ResourceTable.
AddResourcesToTable(ResourceTable * table,IDiagnostics * diag,ParsedResource * res)121 static bool AddResourcesToTable(ResourceTable* table, IDiagnostics* diag, ParsedResource* res) {
122   StringPiece trimmed_comment = util::TrimWhitespace(res->comment);
123   if (trimmed_comment.size() != res->comment.size()) {
124     // Only if there was a change do we re-assign.
125     res->comment = trimmed_comment.to_string();
126   }
127 
128   NewResourceBuilder res_builder(res->name);
129   if (res->visibility_level != Visibility::Level::kUndefined) {
130     Visibility visibility;
131     visibility.level = res->visibility_level;
132     visibility.staged_api = res->staged_api;
133     visibility.source = res->source;
134     visibility.comment = res->comment;
135     res_builder.SetVisibility(visibility);
136   }
137 
138   if (res->id.is_valid()) {
139     res_builder.SetId(res->id);
140   }
141 
142   if (res->allow_new) {
143     AllowNew allow_new;
144     allow_new.source = res->source;
145     allow_new.comment = res->comment;
146     res_builder.SetAllowNew(allow_new);
147   }
148 
149   if (res->overlayable_item) {
150     res_builder.SetOverlayable(res->overlayable_item.value());
151   }
152 
153   if (res->value != nullptr) {
154     // Attach the comment, source and config to the value.
155     res->value->SetComment(std::move(res->comment));
156     res->value->SetSource(std::move(res->source));
157     res_builder.SetValue(std::move(res->value), res->config, res->product);
158   }
159 
160   if (res->staged_alias) {
161     res_builder.SetStagedId(res->staged_alias.value());
162   }
163 
164   bool error = false;
165   if (!res->name.entry.empty()) {
166     if (!table->AddResource(res_builder.Build(), diag)) {
167       return false;
168     }
169   }
170   for (ParsedResource& child : res->child_resources) {
171     error |= !AddResourcesToTable(table, diag, &child);
172   }
173   return !error;
174 }
175 
176 // Convenient aliases for more readable function calls.
177 enum { kAllowRawString = true, kNoRawString = false };
178 
ResourceParser(IDiagnostics * diag,ResourceTable * table,const Source & source,const ConfigDescription & config,const ResourceParserOptions & options)179 ResourceParser::ResourceParser(IDiagnostics* diag, ResourceTable* table,
180                                const Source& source,
181                                const ConfigDescription& config,
182                                const ResourceParserOptions& options)
183     : diag_(diag),
184       table_(table),
185       source_(source),
186       config_(config),
187       options_(options) {}
188 
189 // Base class Node for representing the various Spans and UntranslatableSections of an XML string.
190 // This will be used to traverse and flatten the XML string into a single std::string, with all
191 // Span and Untranslatable data maintained in parallel, as indices into the string.
192 class Node {
193  public:
194   virtual ~Node() = default;
195 
196   // Adds the given child node to this parent node's set of child nodes, moving ownership to the
197   // parent node as well.
198   // Returns a pointer to the child node that was added as a convenience.
199   template <typename T>
AddChild(std::unique_ptr<T> node)200   T* AddChild(std::unique_ptr<T> node) {
201     T* raw_ptr = node.get();
202     children.push_back(std::move(node));
203     return raw_ptr;
204   }
205 
Build(StringBuilder * builder) const206   virtual void Build(StringBuilder* builder) const {
207     for (const auto& child : children) {
208       child->Build(builder);
209     }
210   }
211 
212   std::vector<std::unique_ptr<Node>> children;
213 };
214 
215 // A chunk of text in the XML string. This lives between other tags, such as XLIFF tags and Spans.
216 class SegmentNode : public Node {
217  public:
218   std::string data;
219 
Build(StringBuilder * builder) const220   void Build(StringBuilder* builder) const override {
221     builder->AppendText(data);
222   }
223 };
224 
225 // A tag that will be encoded into the final flattened string. Tags like <b> or <i>.
226 class SpanNode : public Node {
227  public:
228   std::string name;
229 
Build(StringBuilder * builder) const230   void Build(StringBuilder* builder) const override {
231     StringBuilder::SpanHandle span_handle = builder->StartSpan(name);
232     Node::Build(builder);
233     builder->EndSpan(span_handle);
234   }
235 };
236 
237 // An XLIFF 'g' tag, which marks a section of the string as untranslatable.
238 class UntranslatableNode : public Node {
239  public:
Build(StringBuilder * builder) const240   void Build(StringBuilder* builder) const override {
241     StringBuilder::UntranslatableHandle handle = builder->StartUntranslatable();
242     Node::Build(builder);
243     builder->EndUntranslatable(handle);
244   }
245 };
246 
247 // Build a string from XML that converts nested elements into Span objects.
FlattenXmlSubtree(xml::XmlPullParser * parser,std::string * out_raw_string,StyleString * out_style_string,std::vector<UntranslatableSection> * out_untranslatable_sections)248 bool ResourceParser::FlattenXmlSubtree(
249     xml::XmlPullParser* parser, std::string* out_raw_string, StyleString* out_style_string,
250     std::vector<UntranslatableSection>* out_untranslatable_sections) {
251   std::string raw_string;
252   std::string current_text;
253 
254   // The first occurrence of a <xliff:g> tag. Nested <xliff:g> tags are illegal.
255   Maybe<size_t> untranslatable_start_depth;
256 
257   Node root;
258   std::vector<Node*> node_stack;
259   node_stack.push_back(&root);
260 
261   bool saw_span_node = false;
262   SegmentNode* first_segment = nullptr;
263   SegmentNode* last_segment = nullptr;
264 
265   size_t depth = 1;
266   while (depth > 0 && xml::XmlPullParser::IsGoodEvent(parser->Next())) {
267     const xml::XmlPullParser::Event event = parser->event();
268 
269     // First take care of any SegmentNodes that should be created.
270     if (event == xml::XmlPullParser::Event::kStartElement
271         || event == xml::XmlPullParser::Event::kEndElement) {
272       if (!current_text.empty()) {
273         auto segment_node = util::make_unique<SegmentNode>();
274         segment_node->data = std::move(current_text);
275 
276         last_segment = node_stack.back()->AddChild(std::move(segment_node));
277         if (first_segment == nullptr) {
278           first_segment = last_segment;
279         }
280         current_text = {};
281       }
282     }
283 
284     switch (event) {
285       case xml::XmlPullParser::Event::kText: {
286         current_text += parser->text();
287         raw_string += parser->text();
288       } break;
289 
290       case xml::XmlPullParser::Event::kStartElement: {
291         if (parser->element_namespace().empty()) {
292           // This is an HTML tag which we encode as a span. Add it to the span stack.
293           std::unique_ptr<SpanNode> span_node = util::make_unique<SpanNode>();
294           span_node->name = parser->element_name();
295           const auto end_attr_iter = parser->end_attributes();
296           for (auto attr_iter = parser->begin_attributes(); attr_iter != end_attr_iter;
297                ++attr_iter) {
298             span_node->name += ";";
299             span_node->name += attr_iter->name;
300             span_node->name += "=";
301             span_node->name += attr_iter->value;
302           }
303 
304           node_stack.push_back(node_stack.back()->AddChild(std::move(span_node)));
305           saw_span_node = true;
306         } else if (parser->element_namespace() == sXliffNamespaceUri) {
307           // This is an XLIFF tag, which is not encoded as a span.
308           if (parser->element_name() == "g") {
309             // Check that an 'untranslatable' tag is not already being processed. Nested
310             // <xliff:g> tags are illegal.
311             if (untranslatable_start_depth) {
312               diag_->Error(DiagMessage(source_.WithLine(parser->line_number()))
313                            << "illegal nested XLIFF 'g' tag");
314               return false;
315             } else {
316               // Mark the beginning of an 'untranslatable' section.
317               untranslatable_start_depth = depth;
318               node_stack.push_back(
319                   node_stack.back()->AddChild(util::make_unique<UntranslatableNode>()));
320             }
321           } else {
322             // Ignore unknown XLIFF tags, but don't warn.
323             node_stack.push_back(node_stack.back()->AddChild(util::make_unique<Node>()));
324           }
325         } else {
326           // Besides XLIFF, any other namespaced tag is unsupported and ignored.
327           diag_->Warn(DiagMessage(source_.WithLine(parser->line_number()))
328                       << "ignoring element '" << parser->element_name()
329                       << "' with unknown namespace '" << parser->element_namespace() << "'");
330           node_stack.push_back(node_stack.back()->AddChild(util::make_unique<Node>()));
331         }
332 
333         // Enter one level inside the element.
334         depth++;
335       } break;
336 
337       case xml::XmlPullParser::Event::kEndElement: {
338         // Return one level from within the element.
339         depth--;
340         if (depth == 0) {
341           break;
342         }
343 
344         node_stack.pop_back();
345         if (untranslatable_start_depth == make_value(depth)) {
346           // This is the end of an untranslatable section.
347           untranslatable_start_depth = {};
348         }
349       } break;
350 
351       default:
352         // ignore.
353         break;
354     }
355   }
356 
357   // Validity check to make sure we processed all the nodes.
358   CHECK(node_stack.size() == 1u);
359   CHECK(node_stack.back() == &root);
360 
361   if (!saw_span_node) {
362     // If there were no spans, we must treat this string a little differently (according to AAPT).
363     // Find and strip the leading whitespace from the first segment, and the trailing whitespace
364     // from the last segment.
365     if (first_segment != nullptr) {
366       // Trim leading whitespace.
367       StringPiece trimmed = util::TrimLeadingWhitespace(first_segment->data);
368       if (trimmed.size() != first_segment->data.size()) {
369         first_segment->data = trimmed.to_string();
370       }
371     }
372 
373     if (last_segment != nullptr) {
374       // Trim trailing whitespace.
375       StringPiece trimmed = util::TrimTrailingWhitespace(last_segment->data);
376       if (trimmed.size() != last_segment->data.size()) {
377         last_segment->data = trimmed.to_string();
378       }
379     }
380   }
381 
382   // Have the XML structure flatten itself into the StringBuilder. The StringBuilder will take
383   // care of recording the correctly adjusted Spans and UntranslatableSections.
384   StringBuilder builder;
385   root.Build(&builder);
386   if (!builder) {
387     diag_->Error(DiagMessage(source_.WithLine(parser->line_number())) << builder.GetError());
388     return false;
389   }
390 
391   ResourceUtils::FlattenedXmlString flattened_string = builder.GetFlattenedString();
392   *out_raw_string = std::move(raw_string);
393   *out_untranslatable_sections = std::move(flattened_string.untranslatable_sections);
394   out_style_string->str = std::move(flattened_string.text);
395   out_style_string->spans = std::move(flattened_string.spans);
396   return true;
397 }
398 
Parse(xml::XmlPullParser * parser)399 bool ResourceParser::Parse(xml::XmlPullParser* parser) {
400   bool error = false;
401   const size_t depth = parser->depth();
402   while (xml::XmlPullParser::NextChildNode(parser, depth)) {
403     if (parser->event() != xml::XmlPullParser::Event::kStartElement) {
404       // Skip comments and text.
405       continue;
406     }
407 
408     if (!parser->element_namespace().empty() || parser->element_name() != "resources") {
409       diag_->Error(DiagMessage(source_.WithLine(parser->line_number()))
410                    << "root element must be <resources>");
411       return false;
412     }
413 
414     error |= !ParseResources(parser);
415     break;
416   };
417 
418   if (parser->event() == xml::XmlPullParser::Event::kBadDocument) {
419     diag_->Error(DiagMessage(source_.WithLine(parser->line_number()))
420                  << "xml parser error: " << parser->error());
421     return false;
422   }
423   return !error;
424 }
425 
ParseResources(xml::XmlPullParser * parser)426 bool ResourceParser::ParseResources(xml::XmlPullParser* parser) {
427   std::set<ResourceName> stripped_resources;
428 
429   bool error = false;
430   std::string comment;
431   const size_t depth = parser->depth();
432   while (xml::XmlPullParser::NextChildNode(parser, depth)) {
433     const xml::XmlPullParser::Event event = parser->event();
434     if (event == xml::XmlPullParser::Event::kComment) {
435       comment = parser->comment();
436       continue;
437     }
438 
439     if (event == xml::XmlPullParser::Event::kText) {
440       if (!util::TrimWhitespace(parser->text()).empty()) {
441         diag_->Error(DiagMessage(source_.WithLine(parser->line_number()))
442                      << "plain text not allowed here");
443         error = true;
444       }
445       continue;
446     }
447 
448     CHECK(event == xml::XmlPullParser::Event::kStartElement);
449 
450     if (!parser->element_namespace().empty()) {
451       // Skip unknown namespace.
452       continue;
453     }
454 
455     std::string element_name = parser->element_name();
456     if (element_name == "skip" || element_name == "eat-comment") {
457       comment = "";
458       continue;
459     }
460 
461     ParsedResource parsed_resource;
462     parsed_resource.config = config_;
463     parsed_resource.source = source_.WithLine(parser->line_number());
464     // NOLINTNEXTLINE(bugprone-use-after-move) move+reset comment
465     parsed_resource.comment = std::move(comment);
466     if (options_.visibility) {
467       parsed_resource.visibility_level = options_.visibility.value();
468     }
469 
470     // Extract the product name if it exists.
471     if (Maybe<StringPiece> maybe_product = xml::FindNonEmptyAttribute(parser, "product")) {
472       parsed_resource.product = maybe_product.value().to_string();
473     }
474 
475     // Parse the resource regardless of product.
476     if (!ParseResource(parser, &parsed_resource)) {
477       error = true;
478       continue;
479     }
480 
481     if (!AddResourcesToTable(table_, diag_, &parsed_resource)) {
482       error = true;
483     }
484   }
485 
486   // Check that we included at least one variant of each stripped resource.
487   for (const ResourceName& stripped_resource : stripped_resources) {
488     if (!table_->FindResource(stripped_resource)) {
489       // Failed to find the resource.
490       diag_->Error(DiagMessage(source_) << "resource '" << stripped_resource
491                                         << "' was filtered out but no product variant remains");
492       error = true;
493     }
494   }
495 
496   return !error;
497 }
498 
ParseResource(xml::XmlPullParser * parser,ParsedResource * out_resource)499 bool ResourceParser::ParseResource(xml::XmlPullParser* parser,
500                                    ParsedResource* out_resource) {
501   struct ItemTypeFormat {
502     ResourceType type;
503     uint32_t format;
504   };
505 
506   using BagParseFunc = std::function<bool(ResourceParser*, xml::XmlPullParser*,
507                                           ParsedResource*)>;
508 
509   static const auto elToItemMap = ImmutableMap<std::string, ItemTypeFormat>::CreatePreSorted({
510       {"bool", {ResourceType::kBool, android::ResTable_map::TYPE_BOOLEAN}},
511       {"color", {ResourceType::kColor, android::ResTable_map::TYPE_COLOR}},
512       {"configVarying", {ResourceType::kConfigVarying, android::ResTable_map::TYPE_ANY}},
513       {"dimen",
514        {ResourceType::kDimen,
515         android::ResTable_map::TYPE_FLOAT | android::ResTable_map::TYPE_FRACTION |
516             android::ResTable_map::TYPE_DIMENSION}},
517       {"drawable", {ResourceType::kDrawable, android::ResTable_map::TYPE_COLOR}},
518       {"fraction",
519        {ResourceType::kFraction,
520         android::ResTable_map::TYPE_FLOAT | android::ResTable_map::TYPE_FRACTION |
521             android::ResTable_map::TYPE_DIMENSION}},
522       {"integer", {ResourceType::kInteger, android::ResTable_map::TYPE_INTEGER}},
523       {"string", {ResourceType::kString, android::ResTable_map::TYPE_STRING}},
524   });
525 
526   static const auto elToBagMap = ImmutableMap<std::string, BagParseFunc>::CreatePreSorted({
527       {"add-resource", std::mem_fn(&ResourceParser::ParseAddResource)},
528       {"array", std::mem_fn(&ResourceParser::ParseArray)},
529       {"attr", std::mem_fn(&ResourceParser::ParseAttr)},
530       {"configVarying",
531        std::bind(&ResourceParser::ParseStyle, std::placeholders::_1, ResourceType::kConfigVarying,
532                  std::placeholders::_2, std::placeholders::_3)},
533       {"declare-styleable", std::mem_fn(&ResourceParser::ParseDeclareStyleable)},
534       {"integer-array", std::mem_fn(&ResourceParser::ParseIntegerArray)},
535       {"java-symbol", std::mem_fn(&ResourceParser::ParseSymbol)},
536       {"overlayable", std::mem_fn(&ResourceParser::ParseOverlayable)},
537       {"plurals", std::mem_fn(&ResourceParser::ParsePlural)},
538       {"public", std::mem_fn(&ResourceParser::ParsePublic)},
539       {"public-group", std::mem_fn(&ResourceParser::ParsePublicGroup)},
540       {"staging-public-group", std::mem_fn(&ResourceParser::ParseStagingPublicGroup)},
541       {"staging-public-group-final", std::mem_fn(&ResourceParser::ParseStagingPublicGroupFinal)},
542       {"string-array", std::mem_fn(&ResourceParser::ParseStringArray)},
543       {"style", std::bind(&ResourceParser::ParseStyle, std::placeholders::_1, ResourceType::kStyle,
544                           std::placeholders::_2, std::placeholders::_3)},
545       {"symbol", std::mem_fn(&ResourceParser::ParseSymbol)},
546   });
547 
548   std::string resource_type = parser->element_name();
549 
550   // The value format accepted for this resource.
551   uint32_t resource_format = 0u;
552 
553   bool can_be_item = true;
554   bool can_be_bag = true;
555   if (resource_type == "item") {
556     can_be_bag = false;
557 
558     // The default format for <item> is any. If a format attribute is present, that one will
559     // override the default.
560     resource_format = android::ResTable_map::TYPE_ANY;
561 
562     // Items have their type encoded in the type attribute.
563     if (Maybe<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type")) {
564       resource_type = maybe_type.value().to_string();
565     } else {
566       diag_->Error(DiagMessage(source_.WithLine(parser->line_number()))
567                    << "<item> must have a 'type' attribute");
568       return false;
569     }
570 
571     if (Maybe<StringPiece> maybe_format = xml::FindNonEmptyAttribute(parser, "format")) {
572       // An explicit format for this resource was specified. The resource will
573       // retain its type in its name, but the accepted value for this type is
574       // overridden.
575       resource_format = ParseFormatTypeNoEnumsOrFlags(maybe_format.value());
576       if (!resource_format) {
577         diag_->Error(DiagMessage(out_resource->source)
578                      << "'" << maybe_format.value()
579                      << "' is an invalid format");
580         return false;
581       }
582     }
583   } else if (resource_type == "bag") {
584     can_be_item = false;
585 
586     // Bags have their type encoded in the type attribute.
587     if (Maybe<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type")) {
588       resource_type = maybe_type.value().to_string();
589     } else {
590       diag_->Error(DiagMessage(source_.WithLine(parser->line_number()))
591                    << "<bag> must have a 'type' attribute");
592       return false;
593     }
594   }
595 
596   // Get the name of the resource. This will be checked later, because not all
597   // XML elements require a name.
598   Maybe<StringPiece> maybe_name = xml::FindNonEmptyAttribute(parser, "name");
599 
600   if (resource_type == "id") {
601     if (!maybe_name) {
602       diag_->Error(DiagMessage(out_resource->source)
603                    << "<" << parser->element_name()
604                    << "> missing 'name' attribute");
605       return false;
606     }
607 
608     out_resource->name.type = ResourceType::kId;
609     out_resource->name.entry = maybe_name.value().to_string();
610 
611     // Ids either represent a unique resource id or reference another resource id
612     auto item = ParseItem(parser, out_resource, resource_format);
613     if (!item) {
614       return false;
615     }
616 
617     String* empty = ValueCast<String>(out_resource->value.get());
618     if (empty && *empty->value == "") {
619       // If no inner element exists, represent a unique identifier
620       out_resource->value = util::make_unique<Id>();
621     } else {
622       Reference* ref = ValueCast<Reference>(out_resource->value.get());
623       if (ref && !ref->name && !ref->id) {
624         // A null reference also means there is no inner element when ids are in the form:
625         //    <id name="name"/>
626         out_resource->value = util::make_unique<Id>();
627       } else if (!ref || ref->name.value().type != ResourceType::kId) {
628         // If an inner element exists, the inner element must be a reference to another resource id
629         diag_->Error(DiagMessage(out_resource->source)
630                          << "<" << parser->element_name()
631                          << "> inner element must either be a resource reference or empty");
632         return false;
633       }
634     }
635 
636     return true;
637   } else if (resource_type == "macro") {
638     if (!maybe_name) {
639       diag_->Error(DiagMessage(out_resource->source)
640                    << "<" << parser->element_name() << "> missing 'name' attribute");
641       return false;
642     }
643 
644     out_resource->name.type = ResourceType::kMacro;
645     out_resource->name.entry = maybe_name.value().to_string();
646     return ParseMacro(parser, out_resource);
647   }
648 
649   if (can_be_item) {
650     const auto item_iter = elToItemMap.find(resource_type);
651     if (item_iter != elToItemMap.end()) {
652       // This is an item, record its type and format and start parsing.
653 
654       if (!maybe_name) {
655         diag_->Error(DiagMessage(out_resource->source)
656                      << "<" << parser->element_name() << "> missing 'name' attribute");
657         return false;
658       }
659 
660       out_resource->name.type = item_iter->second.type;
661       out_resource->name.entry = maybe_name.value().to_string();
662 
663       // Only use the implied format of the type when there is no explicit format.
664       if (resource_format == 0u) {
665         resource_format = item_iter->second.format;
666       }
667 
668       if (!ParseItem(parser, out_resource, resource_format)) {
669         return false;
670       }
671       return true;
672     }
673   }
674 
675   // This might be a bag or something.
676   if (can_be_bag) {
677     const auto bag_iter = elToBagMap.find(resource_type);
678     if (bag_iter != elToBagMap.end()) {
679       // Ensure we have a name (unless this is a <public-group> or <overlayable>).
680       if (resource_type != kPublicGroupTag && resource_type != kStagingPublicGroupTag &&
681           resource_type != kStagingPublicGroupFinalTag && resource_type != "overlayable") {
682         if (!maybe_name) {
683           diag_->Error(DiagMessage(out_resource->source)
684                        << "<" << parser->element_name() << "> missing 'name' attribute");
685           return false;
686         }
687 
688         out_resource->name.entry = maybe_name.value().to_string();
689       }
690 
691       // Call the associated parse method. The type will be filled in by the
692       // parse func.
693       if (!bag_iter->second(this, parser, out_resource)) {
694         return false;
695       }
696       return true;
697     }
698   }
699 
700   if (can_be_item) {
701     // Try parsing the elementName (or type) as a resource. These shall only be
702     // resources like 'layout' or 'xml' and they can only be references.
703     const ResourceType* parsed_type = ParseResourceType(resource_type);
704     if (parsed_type) {
705       if (!maybe_name) {
706         diag_->Error(DiagMessage(out_resource->source)
707                      << "<" << parser->element_name()
708                      << "> missing 'name' attribute");
709         return false;
710       }
711 
712       out_resource->name.type = *parsed_type;
713       out_resource->name.entry = maybe_name.value().to_string();
714       out_resource->value = ParseXml(parser, android::ResTable_map::TYPE_REFERENCE, kNoRawString);
715       if (!out_resource->value) {
716         diag_->Error(DiagMessage(out_resource->source)
717                      << "invalid value for type '" << *parsed_type << "'. Expected a reference");
718         return false;
719       }
720       return true;
721     }
722   }
723 
724   // If the resource type was not recognized, write the error and return false.
725   diag_->Error(DiagMessage(out_resource->source)
726               << "unknown resource type '" << resource_type << "'");
727   return false;
728 }
729 
ParseItem(xml::XmlPullParser * parser,ParsedResource * out_resource,const uint32_t format)730 bool ResourceParser::ParseItem(xml::XmlPullParser* parser,
731                                ParsedResource* out_resource,
732                                const uint32_t format) {
733   if (format == android::ResTable_map::TYPE_STRING) {
734     return ParseString(parser, out_resource);
735   }
736 
737   out_resource->value = ParseXml(parser, format, kNoRawString);
738   if (!out_resource->value) {
739     diag_->Error(DiagMessage(out_resource->source) << "invalid "
740                                                    << out_resource->name.type);
741     return false;
742   }
743   return true;
744 }
745 
CreateFlattenSubTree(xml::XmlPullParser * parser)746 std::optional<FlattenedXmlSubTree> ResourceParser::CreateFlattenSubTree(
747     xml::XmlPullParser* parser) {
748   const size_t begin_xml_line = parser->line_number();
749 
750   std::string raw_value;
751   StyleString style_string;
752   std::vector<UntranslatableSection> untranslatable_sections;
753   if (!FlattenXmlSubtree(parser, &raw_value, &style_string, &untranslatable_sections)) {
754     return {};
755   }
756 
757   return FlattenedXmlSubTree{.raw_value = raw_value,
758                              .style_string = style_string,
759                              .untranslatable_sections = untranslatable_sections,
760                              .namespace_resolver = parser,
761                              .source = source_.WithLine(begin_xml_line)};
762 }
763 
764 /**
765  * Reads the entire XML subtree and attempts to parse it as some Item,
766  * with typeMask denoting which items it can be. If allowRawValue is
767  * true, a RawString is returned if the XML couldn't be parsed as
768  * an Item. If allowRawValue is false, nullptr is returned in this
769  * case.
770  */
ParseXml(xml::XmlPullParser * parser,const uint32_t type_mask,const bool allow_raw_value)771 std::unique_ptr<Item> ResourceParser::ParseXml(xml::XmlPullParser* parser, const uint32_t type_mask,
772                                                const bool allow_raw_value) {
773   auto sub_tree = CreateFlattenSubTree(parser);
774   if (!sub_tree.has_value()) {
775     return {};
776   }
777   return ParseXml(sub_tree.value(), type_mask, allow_raw_value, *table_, config_, *diag_);
778 }
779 
ParseXml(const FlattenedXmlSubTree & xmlsub_tree,const uint32_t type_mask,const bool allow_raw_value,ResourceTable & table,const android::ConfigDescription & config,IDiagnostics & diag)780 std::unique_ptr<Item> ResourceParser::ParseXml(const FlattenedXmlSubTree& xmlsub_tree,
781                                                const uint32_t type_mask, const bool allow_raw_value,
782                                                ResourceTable& table,
783                                                const android::ConfigDescription& config,
784                                                IDiagnostics& diag) {
785   if (!xmlsub_tree.style_string.spans.empty()) {
786     // This can only be a StyledString.
787     std::unique_ptr<StyledString> styled_string =
788         util::make_unique<StyledString>(table.string_pool.MakeRef(
789             xmlsub_tree.style_string,
790             StringPool::Context(StringPool::Context::kNormalPriority, config)));
791     styled_string->untranslatable_sections = xmlsub_tree.untranslatable_sections;
792     return std::move(styled_string);
793   }
794 
795   auto on_create_reference = [&](const ResourceName& name) {
796     // name.package can be empty here, as it will assume the package name of the
797     // table.
798     auto id = util::make_unique<Id>();
799     id->SetSource(xmlsub_tree.source);
800     return table.AddResource(NewResourceBuilder(name).SetValue(std::move(id)).Build(), &diag);
801   };
802 
803   // Process the raw value.
804   std::unique_ptr<Item> processed_item = ResourceUtils::TryParseItemForAttribute(
805       xmlsub_tree.raw_value, type_mask, on_create_reference);
806   if (processed_item) {
807     // Fix up the reference.
808     if (auto ref = ValueCast<Reference>(processed_item.get())) {
809       ref->allow_raw = allow_raw_value;
810       ResolvePackage(xmlsub_tree.namespace_resolver, ref);
811     }
812     return processed_item;
813   }
814 
815   // Try making a regular string.
816   if (type_mask & android::ResTable_map::TYPE_STRING) {
817     // Use the trimmed, escaped string.
818     std::unique_ptr<String> string = util::make_unique<String>(
819         table.string_pool.MakeRef(xmlsub_tree.style_string.str, StringPool::Context(config)));
820     string->untranslatable_sections = xmlsub_tree.untranslatable_sections;
821     return std::move(string);
822   }
823 
824   if (allow_raw_value) {
825     // We can't parse this so return a RawString if we are allowed.
826     return util::make_unique<RawString>(table.string_pool.MakeRef(
827         util::TrimWhitespace(xmlsub_tree.raw_value), StringPool::Context(config)));
828   } else if (util::TrimWhitespace(xmlsub_tree.raw_value).empty()) {
829     // If the text is empty, and the value is not allowed to be a string, encode it as a @null.
830     return ResourceUtils::MakeNull();
831   }
832   return {};
833 }
834 
ParseString(xml::XmlPullParser * parser,ParsedResource * out_resource)835 bool ResourceParser::ParseString(xml::XmlPullParser* parser,
836                                  ParsedResource* out_resource) {
837   bool formatted = true;
838   if (Maybe<StringPiece> formatted_attr =
839           xml::FindAttribute(parser, "formatted")) {
840     Maybe<bool> maybe_formatted =
841         ResourceUtils::ParseBool(formatted_attr.value());
842     if (!maybe_formatted) {
843       diag_->Error(DiagMessage(out_resource->source)
844                    << "invalid value for 'formatted'. Must be a boolean");
845       return false;
846     }
847     formatted = maybe_formatted.value();
848   }
849 
850   bool translatable = options_.translatable;
851   if (Maybe<StringPiece> translatable_attr = xml::FindAttribute(parser, "translatable")) {
852     Maybe<bool> maybe_translatable = ResourceUtils::ParseBool(translatable_attr.value());
853     if (!maybe_translatable) {
854       diag_->Error(DiagMessage(out_resource->source)
855                    << "invalid value for 'translatable'. Must be a boolean");
856       return false;
857     }
858     translatable = maybe_translatable.value();
859   }
860 
861   out_resource->value =
862       ParseXml(parser, android::ResTable_map::TYPE_STRING, kNoRawString);
863   if (!out_resource->value) {
864     diag_->Error(DiagMessage(out_resource->source) << "not a valid string");
865     return false;
866   }
867 
868   if (String* string_value = ValueCast<String>(out_resource->value.get())) {
869     string_value->SetTranslatable(translatable);
870 
871     if (formatted && translatable) {
872       if (!util::VerifyJavaStringFormat(*string_value->value)) {
873         DiagMessage msg(out_resource->source);
874         msg << "multiple substitutions specified in non-positional format; "
875                "did you mean to add the formatted=\"false\" attribute?";
876         if (options_.error_on_positional_arguments) {
877           diag_->Error(msg);
878           return false;
879         }
880 
881         diag_->Warn(msg);
882       }
883     }
884 
885   } else if (StyledString* string_value = ValueCast<StyledString>(out_resource->value.get())) {
886     string_value->SetTranslatable(translatable);
887   }
888   return true;
889 }
890 
ParseMacro(xml::XmlPullParser * parser,ParsedResource * out_resource)891 bool ResourceParser::ParseMacro(xml::XmlPullParser* parser, ParsedResource* out_resource) {
892   auto sub_tree = CreateFlattenSubTree(parser);
893   if (!sub_tree) {
894     return false;
895   }
896 
897   if (out_resource->config != ConfigDescription::DefaultConfig()) {
898     diag_->Error(DiagMessage(out_resource->source)
899                  << "<macro> tags cannot be declared in configurations other than the default "
900                     "configuration'");
901     return false;
902   }
903 
904   auto macro = std::make_unique<Macro>();
905   macro->raw_value = std::move(sub_tree->raw_value);
906   macro->style_string = std::move(sub_tree->style_string);
907   macro->untranslatable_sections = std::move(sub_tree->untranslatable_sections);
908 
909   for (const auto& decl : parser->package_decls()) {
910     macro->alias_namespaces.emplace_back(
911         Macro::Namespace{.alias = decl.prefix,
912                          .package_name = decl.package.package,
913                          .is_private = decl.package.private_namespace});
914   }
915 
916   out_resource->value = std::move(macro);
917   return true;
918 }
919 
ParsePublic(xml::XmlPullParser * parser,ParsedResource * out_resource)920 bool ResourceParser::ParsePublic(xml::XmlPullParser* parser, ParsedResource* out_resource) {
921   if (options_.visibility) {
922     diag_->Error(DiagMessage(out_resource->source)
923                  << "<public> tag not allowed with --visibility flag");
924     return false;
925   }
926 
927   if (out_resource->config != ConfigDescription::DefaultConfig()) {
928     diag_->Warn(DiagMessage(out_resource->source)
929                 << "ignoring configuration '" << out_resource->config << "' for <public> tag");
930   }
931 
932   Maybe<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type");
933   if (!maybe_type) {
934     diag_->Error(DiagMessage(out_resource->source)
935                  << "<public> must have a 'type' attribute");
936     return false;
937   }
938 
939   const ResourceType* parsed_type = ParseResourceType(maybe_type.value());
940   if (!parsed_type) {
941     diag_->Error(DiagMessage(out_resource->source) << "invalid resource type '"
942                                                    << maybe_type.value()
943                                                    << "' in <public>");
944     return false;
945   }
946 
947   out_resource->name.type = *parsed_type;
948 
949   if (Maybe<StringPiece> maybe_id_str = xml::FindNonEmptyAttribute(parser, "id")) {
950     Maybe<ResourceId> maybe_id = ResourceUtils::ParseResourceId(maybe_id_str.value());
951     if (!maybe_id) {
952       diag_->Error(DiagMessage(out_resource->source)
953                    << "invalid resource ID '" << maybe_id_str.value() << "' in <public>");
954       return false;
955     }
956     out_resource->id = maybe_id.value();
957   }
958 
959   if (*parsed_type == ResourceType::kId) {
960     // An ID marked as public is also the definition of an ID.
961     out_resource->value = util::make_unique<Id>();
962   }
963 
964   out_resource->visibility_level = Visibility::Level::kPublic;
965   return true;
966 }
967 
968 template <typename Func>
ParseGroupImpl(xml::XmlPullParser * parser,ParsedResource * out_resource,const char * tag_name,IDiagnostics * diag,Func && func)969 bool static ParseGroupImpl(xml::XmlPullParser* parser, ParsedResource* out_resource,
970                            const char* tag_name, IDiagnostics* diag, Func&& func) {
971   if (out_resource->config != ConfigDescription::DefaultConfig()) {
972     diag->Warn(DiagMessage(out_resource->source)
973                << "ignoring configuration '" << out_resource->config << "' for <" << tag_name
974                << "> tag");
975   }
976 
977   Maybe<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type");
978   if (!maybe_type) {
979     diag->Error(DiagMessage(out_resource->source)
980                 << "<" << tag_name << "> must have a 'type' attribute");
981     return false;
982   }
983 
984   const ResourceType* parsed_type = ParseResourceType(maybe_type.value());
985   if (!parsed_type) {
986     diag->Error(DiagMessage(out_resource->source)
987                 << "invalid resource type '" << maybe_type.value() << "' in <" << tag_name << ">");
988     return false;
989   }
990 
991   Maybe<StringPiece> maybe_id_str = xml::FindNonEmptyAttribute(parser, "first-id");
992   if (!maybe_id_str) {
993     diag->Error(DiagMessage(out_resource->source)
994                 << "<" << tag_name << "> must have a 'first-id' attribute");
995     return false;
996   }
997 
998   Maybe<ResourceId> maybe_id = ResourceUtils::ParseResourceId(maybe_id_str.value());
999   if (!maybe_id) {
1000     diag->Error(DiagMessage(out_resource->source)
1001                 << "invalid resource ID '" << maybe_id_str.value() << "' in <" << tag_name << ">");
1002     return false;
1003   }
1004 
1005   std::string comment;
1006   ResourceId next_id = maybe_id.value();
1007   bool error = false;
1008   const size_t depth = parser->depth();
1009   while (xml::XmlPullParser::NextChildNode(parser, depth)) {
1010     if (parser->event() == xml::XmlPullParser::Event::kComment) {
1011       comment = util::TrimWhitespace(parser->comment()).to_string();
1012       continue;
1013     } else if (parser->event() != xml::XmlPullParser::Event::kStartElement) {
1014       // Skip text.
1015       continue;
1016     }
1017 
1018     const Source item_source = out_resource->source.WithLine(parser->line_number());
1019     const std::string& element_namespace = parser->element_namespace();
1020     const std::string& element_name = parser->element_name();
1021     if (element_namespace.empty() && element_name == "public") {
1022       auto maybe_name = xml::FindNonEmptyAttribute(parser, "name");
1023       if (!maybe_name) {
1024         diag->Error(DiagMessage(item_source) << "<public> must have a 'name' attribute");
1025         error = true;
1026         continue;
1027       }
1028 
1029       if (xml::FindNonEmptyAttribute(parser, "id")) {
1030         diag->Error(DiagMessage(item_source) << "'id' is ignored within <" << tag_name << ">");
1031         error = true;
1032         continue;
1033       }
1034 
1035       if (xml::FindNonEmptyAttribute(parser, "type")) {
1036         diag->Error(DiagMessage(item_source) << "'type' is ignored within <" << tag_name << ">");
1037         error = true;
1038         continue;
1039       }
1040 
1041       ParsedResource& entry_res = out_resource->child_resources.emplace_back(ParsedResource{
1042           .name = ResourceName{{}, *parsed_type, maybe_name.value().to_string()},
1043           .source = item_source,
1044           .comment = std::move(comment),
1045       });
1046 
1047       // Execute group specific code.
1048       func(entry_res, next_id);
1049 
1050       next_id.id++;
1051     } else if (!ShouldIgnoreElement(element_namespace, element_name)) {
1052       diag->Error(DiagMessage(item_source) << ":" << element_name << ">");
1053       error = true;
1054     }
1055   }
1056   return !error;
1057 }
1058 
ParseStagingPublicGroup(xml::XmlPullParser * parser,ParsedResource * out_resource)1059 bool ResourceParser::ParseStagingPublicGroup(xml::XmlPullParser* parser,
1060                                              ParsedResource* out_resource) {
1061   return ParseGroupImpl(parser, out_resource, kStagingPublicGroupTag, diag_,
1062                         [](ParsedResource& parsed_entry, ResourceId id) {
1063                           parsed_entry.id = id;
1064                           parsed_entry.staged_api = true;
1065                           parsed_entry.visibility_level = Visibility::Level::kPublic;
1066                         });
1067 }
1068 
ParseStagingPublicGroupFinal(xml::XmlPullParser * parser,ParsedResource * out_resource)1069 bool ResourceParser::ParseStagingPublicGroupFinal(xml::XmlPullParser* parser,
1070                                                   ParsedResource* out_resource) {
1071   return ParseGroupImpl(parser, out_resource, kStagingPublicGroupFinalTag, diag_,
1072                         [](ParsedResource& parsed_entry, ResourceId id) {
1073                           parsed_entry.staged_alias = StagedId{id, parsed_entry.source};
1074                         });
1075 }
1076 
ParsePublicGroup(xml::XmlPullParser * parser,ParsedResource * out_resource)1077 bool ResourceParser::ParsePublicGroup(xml::XmlPullParser* parser, ParsedResource* out_resource) {
1078   if (options_.visibility) {
1079     diag_->Error(DiagMessage(out_resource->source)
1080                  << "<" << kPublicGroupTag << "> tag not allowed with --visibility flag");
1081     return false;
1082   }
1083 
1084   return ParseGroupImpl(parser, out_resource, kPublicGroupTag, diag_,
1085                         [](ParsedResource& parsed_entry, ResourceId id) {
1086                           parsed_entry.id = id;
1087                           parsed_entry.visibility_level = Visibility::Level::kPublic;
1088                         });
1089 }
1090 
ParseSymbolImpl(xml::XmlPullParser * parser,ParsedResource * out_resource)1091 bool ResourceParser::ParseSymbolImpl(xml::XmlPullParser* parser,
1092                                      ParsedResource* out_resource) {
1093   Maybe<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type");
1094   if (!maybe_type) {
1095     diag_->Error(DiagMessage(out_resource->source)
1096                  << "<" << parser->element_name()
1097                  << "> must have a 'type' attribute");
1098     return false;
1099   }
1100 
1101   const ResourceType* parsed_type = ParseResourceType(maybe_type.value());
1102   if (!parsed_type) {
1103     diag_->Error(DiagMessage(out_resource->source)
1104                  << "invalid resource type '" << maybe_type.value() << "' in <"
1105                  << parser->element_name() << ">");
1106     return false;
1107   }
1108 
1109   out_resource->name.type = *parsed_type;
1110   return true;
1111 }
1112 
ParseSymbol(xml::XmlPullParser * parser,ParsedResource * out_resource)1113 bool ResourceParser::ParseSymbol(xml::XmlPullParser* parser, ParsedResource* out_resource) {
1114   if (options_.visibility) {
1115     diag_->Error(DiagMessage(out_resource->source)
1116                  << "<java-symbol> and <symbol> tags not allowed with --visibility flag");
1117     return false;
1118   }
1119   if (out_resource->config != ConfigDescription::DefaultConfig()) {
1120     diag_->Warn(DiagMessage(out_resource->source)
1121                 << "ignoring configuration '" << out_resource->config << "' for <"
1122                 << parser->element_name() << "> tag");
1123   }
1124 
1125   if (!ParseSymbolImpl(parser, out_resource)) {
1126     return false;
1127   }
1128 
1129   out_resource->visibility_level = Visibility::Level::kPrivate;
1130   return true;
1131 }
1132 
ParseOverlayable(xml::XmlPullParser * parser,ParsedResource * out_resource)1133 bool ResourceParser::ParseOverlayable(xml::XmlPullParser* parser, ParsedResource* out_resource) {
1134   if (out_resource->config != ConfigDescription::DefaultConfig()) {
1135     diag_->Warn(DiagMessage(out_resource->source)
1136                 << "ignoring configuration '" << out_resource->config
1137                 << "' for <overlayable> tag");
1138   }
1139 
1140   Maybe<StringPiece> overlayable_name = xml::FindNonEmptyAttribute(parser, "name");
1141   if (!overlayable_name) {
1142     diag_->Error(DiagMessage(out_resource->source)
1143                   << "<overlayable> tag must have a 'name' attribute");
1144     return false;
1145   }
1146 
1147   const std::string kActorUriScheme =
1148       android::base::StringPrintf("%s://", Overlayable::kActorScheme);
1149   Maybe<StringPiece> overlayable_actor = xml::FindNonEmptyAttribute(parser, "actor");
1150   if (overlayable_actor && !util::StartsWith(overlayable_actor.value(), kActorUriScheme)) {
1151     diag_->Error(DiagMessage(out_resource->source)
1152                  << "specified <overlayable> tag 'actor' attribute must use the scheme '"
1153                  << Overlayable::kActorScheme << "'");
1154     return false;
1155   }
1156 
1157   // Create a overlayable entry grouping that represents this <overlayable>
1158   auto overlayable = std::make_shared<Overlayable>(
1159       overlayable_name.value(), (overlayable_actor) ? overlayable_actor.value() : "",
1160       source_);
1161 
1162   bool error = false;
1163   std::string comment;
1164   PolicyFlags current_policies = PolicyFlags::NONE;
1165   const size_t start_depth = parser->depth();
1166   while (xml::XmlPullParser::IsGoodEvent(parser->Next())) {
1167     xml::XmlPullParser::Event event = parser->event();
1168     if (event == xml::XmlPullParser::Event::kEndElement && parser->depth() == start_depth) {
1169       // Break the loop when exiting the <overlayable>
1170       break;
1171     } else if (event == xml::XmlPullParser::Event::kEndElement
1172                && parser->depth() == start_depth + 1) {
1173       // Clear the current policies when exiting the <policy> tags
1174       current_policies = PolicyFlags::NONE;
1175       continue;
1176     } else if (event == xml::XmlPullParser::Event::kComment) {
1177       // Retrieve the comment of individual <item> tags
1178       comment = parser->comment();
1179       continue;
1180     } else if (event != xml::XmlPullParser::Event::kStartElement) {
1181       // Skip to the start of the next element
1182       continue;
1183     }
1184 
1185     const Source element_source = source_.WithLine(parser->line_number());
1186     const std::string& element_name = parser->element_name();
1187     const std::string& element_namespace = parser->element_namespace();
1188     if (element_namespace.empty() && element_name == "item") {
1189       if (current_policies == PolicyFlags::NONE) {
1190         diag_->Error(DiagMessage(element_source)
1191                          << "<item> within an <overlayable> must be inside a <policy> block");
1192         error = true;
1193         continue;
1194       }
1195 
1196       // Items specify the name and type of resource that should be overlayable
1197       Maybe<StringPiece> item_name = xml::FindNonEmptyAttribute(parser, "name");
1198       if (!item_name) {
1199         diag_->Error(DiagMessage(element_source)
1200                      << "<item> within an <overlayable> must have a 'name' attribute");
1201         error = true;
1202         continue;
1203       }
1204 
1205       Maybe<StringPiece> item_type = xml::FindNonEmptyAttribute(parser, "type");
1206       if (!item_type) {
1207         diag_->Error(DiagMessage(element_source)
1208                      << "<item> within an <overlayable> must have a 'type' attribute");
1209         error = true;
1210         continue;
1211       }
1212 
1213       const ResourceType* type = ParseResourceType(item_type.value());
1214       if (type == nullptr) {
1215         diag_->Error(DiagMessage(element_source)
1216                      << "invalid resource type '" << item_type.value()
1217                      << "' in <item> within an <overlayable>");
1218         error = true;
1219         continue;
1220       }
1221 
1222       OverlayableItem overlayable_item(overlayable);
1223       overlayable_item.policies = current_policies;
1224       overlayable_item.comment = comment;
1225       overlayable_item.source = element_source;
1226 
1227       ParsedResource child_resource{};
1228       child_resource.name.type = *type;
1229       child_resource.name.entry = item_name.value().to_string();
1230       child_resource.overlayable_item = overlayable_item;
1231       out_resource->child_resources.push_back(std::move(child_resource));
1232 
1233     } else if (element_namespace.empty() && element_name == "policy") {
1234       if (current_policies != PolicyFlags::NONE) {
1235         // If the policy list is not empty, then we are currently inside a policy element
1236         diag_->Error(DiagMessage(element_source) << "<policy> blocks cannot be recursively nested");
1237         error = true;
1238         break;
1239       } else if (Maybe<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type")) {
1240         // Parse the polices separated by vertical bar characters to allow for specifying multiple
1241         // policies. Items within the policy tag will have the specified policy.
1242         for (const StringPiece& part : util::Tokenize(maybe_type.value(), '|')) {
1243           StringPiece trimmed_part = util::TrimWhitespace(part);
1244           const auto policy = std::find_if(kPolicyStringToFlag.begin(),
1245                                            kPolicyStringToFlag.end(),
1246                                            [trimmed_part](const auto& it) {
1247                                              return trimmed_part == it.first;
1248                                            });
1249           if (policy == kPolicyStringToFlag.end()) {
1250             diag_->Error(DiagMessage(element_source)
1251                          << "<policy> has unsupported type '" << trimmed_part << "'");
1252             error = true;
1253             continue;
1254           }
1255 
1256           current_policies |= policy->second;
1257         }
1258       } else {
1259         diag_->Error(DiagMessage(element_source)
1260                      << "<policy> must have a 'type' attribute");
1261         error = true;
1262         continue;
1263       }
1264     } else if (!ShouldIgnoreElement(element_namespace, element_name)) {
1265       diag_->Error(DiagMessage(element_source) << "invalid element <" << element_name << "> "
1266                                                << " in <overlayable>");
1267       error = true;
1268       break;
1269     }
1270 
1271     comment.clear();
1272   }
1273 
1274   return !error;
1275 }
1276 
ParseAddResource(xml::XmlPullParser * parser,ParsedResource * out_resource)1277 bool ResourceParser::ParseAddResource(xml::XmlPullParser* parser, ParsedResource* out_resource) {
1278   if (ParseSymbolImpl(parser, out_resource)) {
1279     out_resource->visibility_level = Visibility::Level::kUndefined;
1280     out_resource->allow_new = true;
1281     return true;
1282   }
1283   return false;
1284 }
1285 
ParseAttr(xml::XmlPullParser * parser,ParsedResource * out_resource)1286 bool ResourceParser::ParseAttr(xml::XmlPullParser* parser,
1287                                ParsedResource* out_resource) {
1288   return ParseAttrImpl(parser, out_resource, false);
1289 }
1290 
ParseAttrImpl(xml::XmlPullParser * parser,ParsedResource * out_resource,bool weak)1291 bool ResourceParser::ParseAttrImpl(xml::XmlPullParser* parser,
1292                                    ParsedResource* out_resource, bool weak) {
1293   out_resource->name.type = ResourceType::kAttr;
1294 
1295   // Attributes only end up in default configuration.
1296   if (out_resource->config != ConfigDescription::DefaultConfig()) {
1297     diag_->Warn(DiagMessage(out_resource->source)
1298                 << "ignoring configuration '" << out_resource->config
1299                 << "' for attribute " << out_resource->name);
1300     out_resource->config = ConfigDescription::DefaultConfig();
1301   }
1302 
1303   uint32_t type_mask = 0;
1304 
1305   Maybe<StringPiece> maybe_format = xml::FindAttribute(parser, "format");
1306   if (maybe_format) {
1307     type_mask = ParseFormatAttribute(maybe_format.value());
1308     if (type_mask == 0) {
1309       diag_->Error(DiagMessage(source_.WithLine(parser->line_number()))
1310                    << "invalid attribute format '" << maybe_format.value() << "'");
1311       return false;
1312     }
1313   }
1314 
1315   Maybe<int32_t> maybe_min, maybe_max;
1316 
1317   if (Maybe<StringPiece> maybe_min_str = xml::FindAttribute(parser, "min")) {
1318     StringPiece min_str = util::TrimWhitespace(maybe_min_str.value());
1319     if (!min_str.empty()) {
1320       std::u16string min_str16 = util::Utf8ToUtf16(min_str);
1321       android::Res_value value;
1322       if (android::ResTable::stringToInt(min_str16.data(), min_str16.size(), &value)) {
1323         maybe_min = static_cast<int32_t>(value.data);
1324       }
1325     }
1326 
1327     if (!maybe_min) {
1328       diag_->Error(DiagMessage(source_.WithLine(parser->line_number()))
1329                    << "invalid 'min' value '" << min_str << "'");
1330       return false;
1331     }
1332   }
1333 
1334   if (Maybe<StringPiece> maybe_max_str = xml::FindAttribute(parser, "max")) {
1335     StringPiece max_str = util::TrimWhitespace(maybe_max_str.value());
1336     if (!max_str.empty()) {
1337       std::u16string max_str16 = util::Utf8ToUtf16(max_str);
1338       android::Res_value value;
1339       if (android::ResTable::stringToInt(max_str16.data(), max_str16.size(), &value)) {
1340         maybe_max = static_cast<int32_t>(value.data);
1341       }
1342     }
1343 
1344     if (!maybe_max) {
1345       diag_->Error(DiagMessage(source_.WithLine(parser->line_number()))
1346                    << "invalid 'max' value '" << max_str << "'");
1347       return false;
1348     }
1349   }
1350 
1351   if ((maybe_min || maybe_max) &&
1352       (type_mask & android::ResTable_map::TYPE_INTEGER) == 0) {
1353     diag_->Error(DiagMessage(source_.WithLine(parser->line_number()))
1354                  << "'min' and 'max' can only be used when format='integer'");
1355     return false;
1356   }
1357 
1358   struct SymbolComparator {
1359     bool operator()(const Attribute::Symbol& a, const Attribute::Symbol& b) const {
1360       return a.symbol.name.value() < b.symbol.name.value();
1361     }
1362   };
1363 
1364   std::set<Attribute::Symbol, SymbolComparator> items;
1365 
1366   std::string comment;
1367   bool error = false;
1368   const size_t depth = parser->depth();
1369   while (xml::XmlPullParser::NextChildNode(parser, depth)) {
1370     if (parser->event() == xml::XmlPullParser::Event::kComment) {
1371       comment = util::TrimWhitespace(parser->comment()).to_string();
1372       continue;
1373     } else if (parser->event() != xml::XmlPullParser::Event::kStartElement) {
1374       // Skip text.
1375       continue;
1376     }
1377 
1378     const Source item_source = source_.WithLine(parser->line_number());
1379     const std::string& element_namespace = parser->element_namespace();
1380     const std::string& element_name = parser->element_name();
1381     if (element_namespace.empty() && (element_name == "flag" || element_name == "enum")) {
1382       if (element_name == "enum") {
1383         if (type_mask & android::ResTable_map::TYPE_FLAGS) {
1384           diag_->Error(DiagMessage(item_source)
1385                        << "can not define an <enum>; already defined a <flag>");
1386           error = true;
1387           continue;
1388         }
1389         type_mask |= android::ResTable_map::TYPE_ENUM;
1390 
1391       } else if (element_name == "flag") {
1392         if (type_mask & android::ResTable_map::TYPE_ENUM) {
1393           diag_->Error(DiagMessage(item_source)
1394                        << "can not define a <flag>; already defined an <enum>");
1395           error = true;
1396           continue;
1397         }
1398         type_mask |= android::ResTable_map::TYPE_FLAGS;
1399       }
1400 
1401       if (Maybe<Attribute::Symbol> s =
1402               ParseEnumOrFlagItem(parser, element_name)) {
1403         Attribute::Symbol& symbol = s.value();
1404         ParsedResource child_resource;
1405         child_resource.name = symbol.symbol.name.value();
1406         child_resource.source = item_source;
1407         child_resource.value = util::make_unique<Id>();
1408         if (options_.visibility) {
1409           child_resource.visibility_level = options_.visibility.value();
1410         }
1411         out_resource->child_resources.push_back(std::move(child_resource));
1412 
1413         symbol.symbol.SetComment(std::move(comment));
1414         symbol.symbol.SetSource(item_source);
1415 
1416         auto insert_result = items.insert(std::move(symbol));
1417         if (!insert_result.second) {
1418           const Attribute::Symbol& existing_symbol = *insert_result.first;
1419           diag_->Error(DiagMessage(item_source)
1420                        << "duplicate symbol '"
1421                        << existing_symbol.symbol.name.value().entry << "'");
1422 
1423           diag_->Note(DiagMessage(existing_symbol.symbol.GetSource())
1424                       << "first defined here");
1425           error = true;
1426         }
1427       } else {
1428         error = true;
1429       }
1430     } else if (!ShouldIgnoreElement(element_namespace, element_name)) {
1431       diag_->Error(DiagMessage(item_source) << ":" << element_name << ">");
1432       error = true;
1433     }
1434 
1435     comment = {};
1436   }
1437 
1438   if (error) {
1439     return false;
1440   }
1441 
1442   std::unique_ptr<Attribute> attr = util::make_unique<Attribute>(
1443       type_mask ? type_mask : uint32_t{android::ResTable_map::TYPE_ANY});
1444   attr->SetWeak(weak);
1445   attr->symbols = std::vector<Attribute::Symbol>(items.begin(), items.end());
1446   attr->min_int = maybe_min.value_or_default(std::numeric_limits<int32_t>::min());
1447   attr->max_int = maybe_max.value_or_default(std::numeric_limits<int32_t>::max());
1448   out_resource->value = std::move(attr);
1449   return true;
1450 }
1451 
ParseEnumOrFlagItem(xml::XmlPullParser * parser,const StringPiece & tag)1452 Maybe<Attribute::Symbol> ResourceParser::ParseEnumOrFlagItem(
1453     xml::XmlPullParser* parser, const StringPiece& tag) {
1454   const Source source = source_.WithLine(parser->line_number());
1455 
1456   Maybe<StringPiece> maybe_name = xml::FindNonEmptyAttribute(parser, "name");
1457   if (!maybe_name) {
1458     diag_->Error(DiagMessage(source) << "no attribute 'name' found for tag <"
1459                                      << tag << ">");
1460     return {};
1461   }
1462 
1463   Maybe<StringPiece> maybe_value = xml::FindNonEmptyAttribute(parser, "value");
1464   if (!maybe_value) {
1465     diag_->Error(DiagMessage(source) << "no attribute 'value' found for tag <"
1466                                      << tag << ">");
1467     return {};
1468   }
1469 
1470   std::u16string value16 = util::Utf8ToUtf16(maybe_value.value());
1471   android::Res_value val;
1472   if (!android::ResTable::stringToInt(value16.data(), value16.size(), &val)) {
1473     diag_->Error(DiagMessage(source) << "invalid value '" << maybe_value.value()
1474                                      << "' for <" << tag
1475                                      << ">; must be an integer");
1476     return {};
1477   }
1478 
1479   return Attribute::Symbol{
1480       Reference(ResourceNameRef({}, ResourceType::kId, maybe_name.value())),
1481       val.data, val.dataType};
1482 }
1483 
ParseStyleItem(xml::XmlPullParser * parser,Style * style)1484 bool ResourceParser::ParseStyleItem(xml::XmlPullParser* parser, Style* style) {
1485   const Source source = source_.WithLine(parser->line_number());
1486 
1487   Maybe<StringPiece> maybe_name = xml::FindNonEmptyAttribute(parser, "name");
1488   if (!maybe_name) {
1489     diag_->Error(DiagMessage(source) << "<item> must have a 'name' attribute");
1490     return false;
1491   }
1492 
1493   Maybe<Reference> maybe_key = ResourceUtils::ParseXmlAttributeName(maybe_name.value());
1494   if (!maybe_key) {
1495     diag_->Error(DiagMessage(source) << "invalid attribute name '" << maybe_name.value() << "'");
1496     return false;
1497   }
1498 
1499   ResolvePackage(parser, &maybe_key.value());
1500   maybe_key.value().SetSource(source);
1501 
1502   std::unique_ptr<Item> value = ParseXml(parser, 0, kAllowRawString);
1503   if (!value) {
1504     diag_->Error(DiagMessage(source) << "could not parse style item");
1505     return false;
1506   }
1507 
1508   style->entries.push_back(Style::Entry{std::move(maybe_key.value()), std::move(value)});
1509   return true;
1510 }
1511 
ParseStyle(const ResourceType type,xml::XmlPullParser * parser,ParsedResource * out_resource)1512 bool ResourceParser::ParseStyle(const ResourceType type, xml::XmlPullParser* parser,
1513                                 ParsedResource* out_resource) {
1514   out_resource->name.type = type;
1515 
1516   std::unique_ptr<Style> style = util::make_unique<Style>();
1517 
1518   Maybe<StringPiece> maybe_parent = xml::FindAttribute(parser, "parent");
1519   if (maybe_parent) {
1520     // If the parent is empty, we don't have a parent, but we also don't infer either.
1521     if (!maybe_parent.value().empty()) {
1522       std::string err_str;
1523       style->parent = ResourceUtils::ParseStyleParentReference(maybe_parent.value(), &err_str);
1524       if (!style->parent) {
1525         diag_->Error(DiagMessage(out_resource->source) << err_str);
1526         return false;
1527       }
1528 
1529       // Transform the namespace prefix to the actual package name, and mark the reference as
1530       // private if appropriate.
1531       ResolvePackage(parser, &style->parent.value());
1532     }
1533 
1534   } else {
1535     // No parent was specified, so try inferring it from the style name.
1536     std::string style_name = out_resource->name.entry;
1537     size_t pos = style_name.find_last_of(u'.');
1538     if (pos != std::string::npos) {
1539       style->parent_inferred = true;
1540       style->parent = Reference(ResourceName({}, ResourceType::kStyle, style_name.substr(0, pos)));
1541     }
1542   }
1543 
1544   bool error = false;
1545   const size_t depth = parser->depth();
1546   while (xml::XmlPullParser::NextChildNode(parser, depth)) {
1547     if (parser->event() != xml::XmlPullParser::Event::kStartElement) {
1548       // Skip text and comments.
1549       continue;
1550     }
1551 
1552     const std::string& element_namespace = parser->element_namespace();
1553     const std::string& element_name = parser->element_name();
1554     if (element_namespace == "" && element_name == "item") {
1555       error |= !ParseStyleItem(parser, style.get());
1556 
1557     } else if (!ShouldIgnoreElement(element_namespace, element_name)) {
1558       diag_->Error(DiagMessage(source_.WithLine(parser->line_number()))
1559                    << ":" << element_name << ">");
1560       error = true;
1561     }
1562   }
1563 
1564   if (error) {
1565     return false;
1566   }
1567 
1568   out_resource->value = std::move(style);
1569   return true;
1570 }
1571 
ParseArray(xml::XmlPullParser * parser,ParsedResource * out_resource)1572 bool ResourceParser::ParseArray(xml::XmlPullParser* parser, ParsedResource* out_resource) {
1573   uint32_t resource_format = android::ResTable_map::TYPE_ANY;
1574   if (Maybe<StringPiece> format_attr = xml::FindNonEmptyAttribute(parser, "format")) {
1575     resource_format = ParseFormatTypeNoEnumsOrFlags(format_attr.value());
1576     if (resource_format == 0u) {
1577       diag_->Error(DiagMessage(source_.WithLine(parser->line_number()))
1578                    << "'" << format_attr.value() << "' is an invalid format");
1579       return false;
1580     }
1581   }
1582   return ParseArrayImpl(parser, out_resource, resource_format);
1583 }
1584 
ParseIntegerArray(xml::XmlPullParser * parser,ParsedResource * out_resource)1585 bool ResourceParser::ParseIntegerArray(xml::XmlPullParser* parser, ParsedResource* out_resource) {
1586   return ParseArrayImpl(parser, out_resource, android::ResTable_map::TYPE_INTEGER);
1587 }
1588 
ParseStringArray(xml::XmlPullParser * parser,ParsedResource * out_resource)1589 bool ResourceParser::ParseStringArray(xml::XmlPullParser* parser, ParsedResource* out_resource) {
1590   return ParseArrayImpl(parser, out_resource, android::ResTable_map::TYPE_STRING);
1591 }
1592 
ParseArrayImpl(xml::XmlPullParser * parser,ParsedResource * out_resource,const uint32_t typeMask)1593 bool ResourceParser::ParseArrayImpl(xml::XmlPullParser* parser,
1594                                     ParsedResource* out_resource,
1595                                     const uint32_t typeMask) {
1596   out_resource->name.type = ResourceType::kArray;
1597 
1598   std::unique_ptr<Array> array = util::make_unique<Array>();
1599 
1600   bool translatable = options_.translatable;
1601   if (Maybe<StringPiece> translatable_attr = xml::FindAttribute(parser, "translatable")) {
1602     Maybe<bool> maybe_translatable = ResourceUtils::ParseBool(translatable_attr.value());
1603     if (!maybe_translatable) {
1604       diag_->Error(DiagMessage(out_resource->source)
1605                    << "invalid value for 'translatable'. Must be a boolean");
1606       return false;
1607     }
1608     translatable = maybe_translatable.value();
1609   }
1610   array->SetTranslatable(translatable);
1611 
1612   bool error = false;
1613   const size_t depth = parser->depth();
1614   while (xml::XmlPullParser::NextChildNode(parser, depth)) {
1615     if (parser->event() != xml::XmlPullParser::Event::kStartElement) {
1616       // Skip text and comments.
1617       continue;
1618     }
1619 
1620     const Source item_source = source_.WithLine(parser->line_number());
1621     const std::string& element_namespace = parser->element_namespace();
1622     const std::string& element_name = parser->element_name();
1623     if (element_namespace.empty() && element_name == "item") {
1624       std::unique_ptr<Item> item = ParseXml(parser, typeMask, kNoRawString);
1625       if (!item) {
1626         diag_->Error(DiagMessage(item_source) << "could not parse array item");
1627         error = true;
1628         continue;
1629       }
1630       item->SetSource(item_source);
1631       array->elements.emplace_back(std::move(item));
1632 
1633     } else if (!ShouldIgnoreElement(element_namespace, element_name)) {
1634       diag_->Error(DiagMessage(source_.WithLine(parser->line_number()))
1635                    << "unknown tag <" << element_namespace << ":"
1636                    << element_name << ">");
1637       error = true;
1638     }
1639   }
1640 
1641   if (error) {
1642     return false;
1643   }
1644 
1645   out_resource->value = std::move(array);
1646   return true;
1647 }
1648 
ParsePlural(xml::XmlPullParser * parser,ParsedResource * out_resource)1649 bool ResourceParser::ParsePlural(xml::XmlPullParser* parser,
1650                                  ParsedResource* out_resource) {
1651   out_resource->name.type = ResourceType::kPlurals;
1652 
1653   std::unique_ptr<Plural> plural = util::make_unique<Plural>();
1654 
1655   bool error = false;
1656   const size_t depth = parser->depth();
1657   while (xml::XmlPullParser::NextChildNode(parser, depth)) {
1658     if (parser->event() != xml::XmlPullParser::Event::kStartElement) {
1659       // Skip text and comments.
1660       continue;
1661     }
1662 
1663     const Source item_source = source_.WithLine(parser->line_number());
1664     const std::string& element_namespace = parser->element_namespace();
1665     const std::string& element_name = parser->element_name();
1666     if (element_namespace.empty() && element_name == "item") {
1667       Maybe<StringPiece> maybe_quantity =
1668           xml::FindNonEmptyAttribute(parser, "quantity");
1669       if (!maybe_quantity) {
1670         diag_->Error(DiagMessage(item_source)
1671                      << "<item> in <plurals> requires attribute "
1672                      << "'quantity'");
1673         error = true;
1674         continue;
1675       }
1676 
1677       StringPiece trimmed_quantity =
1678           util::TrimWhitespace(maybe_quantity.value());
1679       size_t index = 0;
1680       if (trimmed_quantity == "zero") {
1681         index = Plural::Zero;
1682       } else if (trimmed_quantity == "one") {
1683         index = Plural::One;
1684       } else if (trimmed_quantity == "two") {
1685         index = Plural::Two;
1686       } else if (trimmed_quantity == "few") {
1687         index = Plural::Few;
1688       } else if (trimmed_quantity == "many") {
1689         index = Plural::Many;
1690       } else if (trimmed_quantity == "other") {
1691         index = Plural::Other;
1692       } else {
1693         diag_->Error(DiagMessage(item_source)
1694                      << "<item> in <plural> has invalid value '"
1695                      << trimmed_quantity << "' for attribute 'quantity'");
1696         error = true;
1697         continue;
1698       }
1699 
1700       if (plural->values[index]) {
1701         diag_->Error(DiagMessage(item_source) << "duplicate quantity '"
1702                                               << trimmed_quantity << "'");
1703         error = true;
1704         continue;
1705       }
1706 
1707       if (!(plural->values[index] = ParseXml(
1708                 parser, android::ResTable_map::TYPE_STRING, kNoRawString))) {
1709         error = true;
1710         continue;
1711       }
1712 
1713       plural->values[index]->SetSource(item_source);
1714 
1715     } else if (!ShouldIgnoreElement(element_namespace, element_name)) {
1716       diag_->Error(DiagMessage(item_source) << "unknown tag <"
1717                                             << element_namespace << ":"
1718                                             << element_name << ">");
1719       error = true;
1720     }
1721   }
1722 
1723   if (error) {
1724     return false;
1725   }
1726 
1727   out_resource->value = std::move(plural);
1728   return true;
1729 }
1730 
ParseDeclareStyleable(xml::XmlPullParser * parser,ParsedResource * out_resource)1731 bool ResourceParser::ParseDeclareStyleable(xml::XmlPullParser* parser,
1732                                            ParsedResource* out_resource) {
1733   out_resource->name.type = ResourceType::kStyleable;
1734 
1735   if (!options_.preserve_visibility_of_styleables) {
1736     // This was added in change Idd21b5de4d20be06c6f8c8eb5a22ccd68afc4927 to mimic aapt1, but no one
1737     // knows exactly what for.
1738     //
1739     // FWIW, styleables only appear in generated R classes.  For custom views these should always be
1740     // package-private (to be used only by the view class); themes are a different story.
1741     out_resource->visibility_level = Visibility::Level::kPublic;
1742   }
1743 
1744   // Declare-styleable only ends up in default config;
1745   if (out_resource->config != ConfigDescription::DefaultConfig()) {
1746     diag_->Warn(DiagMessage(out_resource->source)
1747                 << "ignoring configuration '" << out_resource->config
1748                 << "' for styleable " << out_resource->name.entry);
1749     out_resource->config = ConfigDescription::DefaultConfig();
1750   }
1751 
1752   std::unique_ptr<Styleable> styleable = util::make_unique<Styleable>();
1753 
1754   std::string comment;
1755   bool error = false;
1756   const size_t depth = parser->depth();
1757   while (xml::XmlPullParser::NextChildNode(parser, depth)) {
1758     if (parser->event() == xml::XmlPullParser::Event::kComment) {
1759       comment = util::TrimWhitespace(parser->comment()).to_string();
1760       continue;
1761     } else if (parser->event() != xml::XmlPullParser::Event::kStartElement) {
1762       // Ignore text.
1763       continue;
1764     }
1765 
1766     const Source item_source = source_.WithLine(parser->line_number());
1767     const std::string& element_namespace = parser->element_namespace();
1768     const std::string& element_name = parser->element_name();
1769     if (element_namespace.empty() && element_name == "attr") {
1770       Maybe<StringPiece> maybe_name = xml::FindNonEmptyAttribute(parser, "name");
1771       if (!maybe_name) {
1772         diag_->Error(DiagMessage(item_source) << "<attr> tag must have a 'name' attribute");
1773         error = true;
1774         continue;
1775       }
1776 
1777       // If this is a declaration, the package name may be in the name. Separate
1778       // these out.
1779       // Eg. <attr name="android:text" />
1780       Maybe<Reference> maybe_ref = ResourceUtils::ParseXmlAttributeName(maybe_name.value());
1781       if (!maybe_ref) {
1782         diag_->Error(DiagMessage(item_source) << "<attr> tag has invalid name '"
1783                                               << maybe_name.value() << "'");
1784         error = true;
1785         continue;
1786       }
1787 
1788       Reference& child_ref = maybe_ref.value();
1789       xml::ResolvePackage(parser, &child_ref);
1790 
1791       // Create the ParsedResource that will add the attribute to the table.
1792       ParsedResource child_resource;
1793       child_resource.name = child_ref.name.value();
1794       child_resource.source = item_source;
1795       // NOLINTNEXTLINE(bugprone-use-after-move) move+reset comment
1796       child_resource.comment = std::move(comment);
1797       if (options_.visibility) {
1798         child_resource.visibility_level = options_.visibility.value();
1799       }
1800 
1801       if (!ParseAttrImpl(parser, &child_resource, true)) {
1802         error = true;
1803         continue;
1804       }
1805 
1806       // Create the reference to this attribute.
1807       child_ref.SetComment(child_resource.comment);
1808       child_ref.SetSource(item_source);
1809       styleable->entries.push_back(std::move(child_ref));
1810 
1811       // Do not add referenced attributes that do not define a format to the table.
1812       CHECK(child_resource.value != nullptr);
1813       Attribute* attr = ValueCast<Attribute>(child_resource.value.get());
1814 
1815       CHECK(attr != nullptr);
1816       if (attr->type_mask != android::ResTable_map::TYPE_ANY) {
1817         out_resource->child_resources.push_back(std::move(child_resource));
1818       }
1819 
1820     } else if (!ShouldIgnoreElement(element_namespace, element_name)) {
1821       diag_->Error(DiagMessage(item_source) << "unknown tag <"
1822                                             << element_namespace << ":"
1823                                             << element_name << ">");
1824       error = true;
1825     }
1826 
1827     comment = {};
1828   }
1829 
1830   if (error) {
1831     return false;
1832   }
1833 
1834   out_resource->value = std::move(styleable);
1835   return true;
1836 }
1837 
1838 }  // namespace aapt
1839