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