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