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