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() ||
    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 
    290 bool 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 
    362 bool 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 
    553 bool 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  */
    576 std::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 
    639 bool 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 
    695 bool 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 
    733 bool 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 
    827 bool 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 
    849 bool 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 
    858 bool 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 
    868 bool ResourceParser::ParseAttr(xml::XmlPullParser* parser,
    869                                ParsedResource* out_resource) {
    870   return ParseAttrImpl(parser, out_resource, false);
    871 }
    872 
    873 bool 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 
   1040 Maybe<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 
   1072 bool 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 
   1103 bool 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 
   1167 bool 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 
   1180 bool ResourceParser::ParseIntegerArray(xml::XmlPullParser* parser, ParsedResource* out_resource) {
   1181   return ParseArrayImpl(parser, out_resource, android::ResTable_map::TYPE_INTEGER);
   1182 }
   1183 
   1184 bool ResourceParser::ParseStringArray(xml::XmlPullParser* parser, ParsedResource* out_resource) {
   1185   return ParseArrayImpl(parser, out_resource, android::ResTable_map::TYPE_STRING);
   1186 }
   1187 
   1188 bool 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 
   1244 bool 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 
   1324 bool 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