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