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