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