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
35using ::aapt::ResourceUtils::StringBuilder;
36using ::aapt::text::Utf8Iterator;
37using ::android::StringPiece;
38
39namespace aapt {
40
41constexpr 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.
44static bool ShouldIgnoreElement(const StringPiece& ns, const StringPiece& name) {
45  return ns.empty() && (name == "skip" || name == "eat-comment");
46}
47
48static 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
69static 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
78static 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.
92struct 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.
109static 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.
163enum { kAllowRawString = true, kNoRawString = false };
164
165ResourceParser::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.
178class 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>
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
192  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.
202class SegmentNode : public Node {
203 public:
204  std::string data;
205
206  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>.
212class SpanNode : public Node {
213 public:
214  std::string name;
215
216  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.
224class UntranslatableNode : public Node {
225 public:
226  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.
234bool 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
384bool 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
411bool 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
480bool 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
697bool 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 */
720std::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
782bool 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
838bool 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
880bool 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
979bool 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
1001bool 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
1016bool 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
1084bool 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
1093bool ResourceParser::ParseAttr(xml::XmlPullParser* parser,
1094                               ParsedResource* out_resource) {
1095  return ParseAttrImpl(parser, out_resource, false);
1096}
1097
1098bool 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
1256Maybe<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
1288bool 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
1316bool 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
1376bool 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
1389bool ResourceParser::ParseIntegerArray(xml::XmlPullParser* parser, ParsedResource* out_resource) {
1390  return ParseArrayImpl(parser, out_resource, android::ResTable_map::TYPE_INTEGER);
1391}
1392
1393bool ResourceParser::ParseStringArray(xml::XmlPullParser* parser, ParsedResource* out_resource) {
1394  return ParseArrayImpl(parser, out_resource, android::ResTable_map::TYPE_STRING);
1395}
1396
1397bool 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
1453bool 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
1533bool 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