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