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 #include "ResourceTable.h" 19 #include "ResourceUtils.h" 20 #include "ResourceValues.h" 21 #include "ValueVisitor.h" 22 #include "util/ImmutableMap.h" 23 #include "util/Util.h" 24 #include "xml/XmlPullParser.h" 25 26 #include <functional> 27 #include <sstream> 28 29 namespace aapt { 30 31 constexpr const char16_t* sXliffNamespaceUri = u"urn:oasis:names:tc:xliff:document:1.2"; 32 33 /** 34 * Returns true if the element is <skip> or <eat-comment> and can be safely ignored. 35 */ 36 static bool shouldIgnoreElement(const StringPiece16& ns, const StringPiece16& name) { 37 return ns.empty() && (name == u"skip" || name == u"eat-comment"); 38 } 39 40 static uint32_t parseFormatType(const StringPiece16& piece) { 41 if (piece == u"reference") return android::ResTable_map::TYPE_REFERENCE; 42 else if (piece == u"string") return android::ResTable_map::TYPE_STRING; 43 else if (piece == u"integer") return android::ResTable_map::TYPE_INTEGER; 44 else if (piece == u"boolean") return android::ResTable_map::TYPE_BOOLEAN; 45 else if (piece == u"color") return android::ResTable_map::TYPE_COLOR; 46 else if (piece == u"float") return android::ResTable_map::TYPE_FLOAT; 47 else if (piece == u"dimension") return android::ResTable_map::TYPE_DIMENSION; 48 else if (piece == u"fraction") return android::ResTable_map::TYPE_FRACTION; 49 else if (piece == u"enum") return android::ResTable_map::TYPE_ENUM; 50 else if (piece == u"flags") return android::ResTable_map::TYPE_FLAGS; 51 return 0; 52 } 53 54 static uint32_t parseFormatAttribute(const StringPiece16& str) { 55 uint32_t mask = 0; 56 for (StringPiece16 part : util::tokenize(str, u'|')) { 57 StringPiece16 trimmedPart = util::trimWhitespace(part); 58 uint32_t type = parseFormatType(trimmedPart); 59 if (type == 0) { 60 return 0; 61 } 62 mask |= type; 63 } 64 return mask; 65 } 66 67 /** 68 * A parsed resource ready to be added to the ResourceTable. 69 */ 70 struct ParsedResource { 71 ResourceName name; 72 ConfigDescription config; 73 std::string product; 74 Source source; 75 ResourceId id; 76 Maybe<SymbolState> symbolState; 77 std::u16string comment; 78 std::unique_ptr<Value> value; 79 std::list<ParsedResource> childResources; 80 }; 81 82 // Recursively adds resources to the ResourceTable. 83 static bool addResourcesToTable(ResourceTable* table, IDiagnostics* diag, ParsedResource* res) { 84 StringPiece16 trimmedComment = util::trimWhitespace(res->comment); 85 if (trimmedComment.size() != res->comment.size()) { 86 // Only if there was a change do we re-assign. 87 res->comment = trimmedComment.toString(); 88 } 89 90 if (res->symbolState) { 91 Symbol symbol; 92 symbol.state = res->symbolState.value(); 93 symbol.source = res->source; 94 symbol.comment = res->comment; 95 if (!table->setSymbolState(res->name, res->id, symbol, diag)) { 96 return false; 97 } 98 } 99 100 if (res->value) { 101 // Attach the comment, source and config to the value. 102 res->value->setComment(std::move(res->comment)); 103 res->value->setSource(std::move(res->source)); 104 105 if (!table->addResource(res->name, res->id, res->config, res->product, 106 std::move(res->value), diag)) { 107 return false; 108 } 109 } 110 111 bool error = false; 112 for (ParsedResource& child : res->childResources) { 113 error |= !addResourcesToTable(table, diag, &child); 114 } 115 return !error; 116 } 117 118 // Convenient aliases for more readable function calls. 119 enum { 120 kAllowRawString = true, 121 kNoRawString = false 122 }; 123 124 ResourceParser::ResourceParser(IDiagnostics* diag, ResourceTable* table, const Source& source, 125 const ConfigDescription& config, 126 const ResourceParserOptions& options) : 127 mDiag(diag), mTable(table), mSource(source), mConfig(config), mOptions(options) { 128 } 129 130 /** 131 * Build a string from XML that converts nested elements into Span objects. 132 */ 133 bool ResourceParser::flattenXmlSubtree(xml::XmlPullParser* parser, std::u16string* outRawString, 134 StyleString* outStyleString) { 135 std::vector<Span> spanStack; 136 137 bool error = false; 138 outRawString->clear(); 139 outStyleString->spans.clear(); 140 util::StringBuilder builder; 141 size_t depth = 1; 142 while (xml::XmlPullParser::isGoodEvent(parser->next())) { 143 const xml::XmlPullParser::Event event = parser->getEvent(); 144 if (event == xml::XmlPullParser::Event::kEndElement) { 145 if (!parser->getElementNamespace().empty()) { 146 // We already warned and skipped the start element, so just skip here too 147 continue; 148 } 149 150 depth--; 151 if (depth == 0) { 152 break; 153 } 154 155 spanStack.back().lastChar = builder.str().size(); 156 outStyleString->spans.push_back(spanStack.back()); 157 spanStack.pop_back(); 158 159 } else if (event == xml::XmlPullParser::Event::kText) { 160 outRawString->append(parser->getText()); 161 builder.append(parser->getText()); 162 163 } else if (event == xml::XmlPullParser::Event::kStartElement) { 164 if (!parser->getElementNamespace().empty()) { 165 if (parser->getElementNamespace() != sXliffNamespaceUri) { 166 // Only warn if this isn't an xliff namespace. 167 mDiag->warn(DiagMessage(mSource.withLine(parser->getLineNumber())) 168 << "skipping element '" 169 << parser->getElementName() 170 << "' with unknown namespace '" 171 << parser->getElementNamespace() 172 << "'"); 173 } 174 continue; 175 } 176 depth++; 177 178 // Build a span object out of the nested element. 179 std::u16string spanName = parser->getElementName(); 180 const auto endAttrIter = parser->endAttributes(); 181 for (auto attrIter = parser->beginAttributes(); attrIter != endAttrIter; ++attrIter) { 182 spanName += u";"; 183 spanName += attrIter->name; 184 spanName += u"="; 185 spanName += attrIter->value; 186 } 187 188 if (builder.str().size() > std::numeric_limits<uint32_t>::max()) { 189 mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber())) 190 << "style string '" << builder.str() << "' is too long"); 191 error = true; 192 } else { 193 spanStack.push_back(Span{ spanName, static_cast<uint32_t>(builder.str().size()) }); 194 } 195 196 } else if (event == xml::XmlPullParser::Event::kComment) { 197 // Skip 198 } else { 199 assert(false); 200 } 201 } 202 assert(spanStack.empty() && "spans haven't been fully processed"); 203 204 outStyleString->str = builder.str(); 205 return !error; 206 } 207 208 bool ResourceParser::parse(xml::XmlPullParser* parser) { 209 bool error = false; 210 const size_t depth = parser->getDepth(); 211 while (xml::XmlPullParser::nextChildNode(parser, depth)) { 212 if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) { 213 // Skip comments and text. 214 continue; 215 } 216 217 if (!parser->getElementNamespace().empty() || parser->getElementName() != u"resources") { 218 mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber())) 219 << "root element must be <resources>"); 220 return false; 221 } 222 223 error |= !parseResources(parser); 224 break; 225 }; 226 227 if (parser->getEvent() == xml::XmlPullParser::Event::kBadDocument) { 228 mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber())) 229 << "xml parser error: " << parser->getLastError()); 230 return false; 231 } 232 return !error; 233 } 234 235 bool ResourceParser::parseResources(xml::XmlPullParser* parser) { 236 std::set<ResourceName> strippedResources; 237 238 bool error = false; 239 std::u16string comment; 240 const size_t depth = parser->getDepth(); 241 while (xml::XmlPullParser::nextChildNode(parser, depth)) { 242 const xml::XmlPullParser::Event event = parser->getEvent(); 243 if (event == xml::XmlPullParser::Event::kComment) { 244 comment = parser->getComment(); 245 continue; 246 } 247 248 if (event == xml::XmlPullParser::Event::kText) { 249 if (!util::trimWhitespace(parser->getText()).empty()) { 250 mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber())) 251 << "plain text not allowed here"); 252 error = true; 253 } 254 continue; 255 } 256 257 assert(event == xml::XmlPullParser::Event::kStartElement); 258 259 if (!parser->getElementNamespace().empty()) { 260 // Skip unknown namespace. 261 continue; 262 } 263 264 std::u16string elementName = parser->getElementName(); 265 if (elementName == u"skip" || elementName == u"eat-comment") { 266 comment = u""; 267 continue; 268 } 269 270 ParsedResource parsedResource; 271 parsedResource.config = mConfig; 272 parsedResource.source = mSource.withLine(parser->getLineNumber()); 273 parsedResource.comment = std::move(comment); 274 275 // Extract the product name if it exists. 276 if (Maybe<StringPiece16> maybeProduct = xml::findNonEmptyAttribute(parser, u"product")) { 277 parsedResource.product = util::utf16ToUtf8(maybeProduct.value()); 278 } 279 280 // Parse the resource regardless of product. 281 if (!parseResource(parser, &parsedResource)) { 282 error = true; 283 continue; 284 } 285 286 if (!addResourcesToTable(mTable, mDiag, &parsedResource)) { 287 error = true; 288 } 289 } 290 291 // Check that we included at least one variant of each stripped resource. 292 for (const ResourceName& strippedResource : strippedResources) { 293 if (!mTable->findResource(strippedResource)) { 294 // Failed to find the resource. 295 mDiag->error(DiagMessage(mSource) << "resource '" << strippedResource << "' " 296 "was filtered out but no product variant remains"); 297 error = true; 298 } 299 } 300 301 return !error; 302 } 303 304 305 bool ResourceParser::parseResource(xml::XmlPullParser* parser, ParsedResource* outResource) { 306 struct ItemTypeFormat { 307 ResourceType type; 308 uint32_t format; 309 }; 310 311 using BagParseFunc = std::function<bool(ResourceParser*, xml::XmlPullParser*, ParsedResource*)>; 312 313 static const auto elToItemMap = ImmutableMap<std::u16string, ItemTypeFormat>::createPreSorted({ 314 { u"bool", { ResourceType::kBool, android::ResTable_map::TYPE_BOOLEAN } }, 315 { u"color", { ResourceType::kColor, android::ResTable_map::TYPE_COLOR } }, 316 { u"dimen", { ResourceType::kDimen, android::ResTable_map::TYPE_FLOAT 317 | android::ResTable_map::TYPE_FRACTION 318 | android::ResTable_map::TYPE_DIMENSION } }, 319 { u"drawable", { ResourceType::kDrawable, android::ResTable_map::TYPE_COLOR } }, 320 { u"fraction", { ResourceType::kFraction, android::ResTable_map::TYPE_FLOAT 321 | android::ResTable_map::TYPE_FRACTION 322 | android::ResTable_map::TYPE_DIMENSION } }, 323 { u"integer", { ResourceType::kInteger, android::ResTable_map::TYPE_INTEGER } }, 324 { u"string", { ResourceType::kString, android::ResTable_map::TYPE_STRING } }, 325 }); 326 327 static const auto elToBagMap = ImmutableMap<std::u16string, BagParseFunc>::createPreSorted({ 328 { u"add-resource", std::mem_fn(&ResourceParser::parseAddResource) }, 329 { u"array", std::mem_fn(&ResourceParser::parseArray) }, 330 { u"attr", std::mem_fn(&ResourceParser::parseAttr) }, 331 { u"declare-styleable", std::mem_fn(&ResourceParser::parseDeclareStyleable) }, 332 { u"integer-array", std::mem_fn(&ResourceParser::parseIntegerArray) }, 333 { u"java-symbol", std::mem_fn(&ResourceParser::parseSymbol) }, 334 { u"plurals", std::mem_fn(&ResourceParser::parsePlural) }, 335 { u"public", std::mem_fn(&ResourceParser::parsePublic) }, 336 { u"public-group", std::mem_fn(&ResourceParser::parsePublicGroup) }, 337 { u"string-array", std::mem_fn(&ResourceParser::parseStringArray) }, 338 { u"style", std::mem_fn(&ResourceParser::parseStyle) }, 339 { u"symbol", std::mem_fn(&ResourceParser::parseSymbol) }, 340 }); 341 342 std::u16string resourceType = parser->getElementName(); 343 344 // The value format accepted for this resource. 345 uint32_t resourceFormat = 0u; 346 347 if (resourceType == u"item") { 348 // Items have their type encoded in the type attribute. 349 if (Maybe<StringPiece16> maybeType = xml::findNonEmptyAttribute(parser, u"type")) { 350 resourceType = maybeType.value().toString(); 351 } else { 352 mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber())) 353 << "<item> must have a 'type' attribute"); 354 return false; 355 } 356 357 if (Maybe<StringPiece16> maybeFormat = xml::findNonEmptyAttribute(parser, u"format")) { 358 // An explicit format for this resource was specified. The resource will retain 359 // its type in its name, but the accepted value for this type is overridden. 360 resourceFormat = parseFormatType(maybeFormat.value()); 361 if (!resourceFormat) { 362 mDiag->error(DiagMessage(outResource->source) 363 << "'" << maybeFormat.value() << "' is an invalid format"); 364 return false; 365 } 366 } 367 } 368 369 // Get the name of the resource. This will be checked later, because not all 370 // XML elements require a name. 371 Maybe<StringPiece16> maybeName = xml::findNonEmptyAttribute(parser, u"name"); 372 373 if (resourceType == u"id") { 374 if (!maybeName) { 375 mDiag->error(DiagMessage(outResource->source) 376 << "<" << parser->getElementName() << "> missing 'name' attribute"); 377 return false; 378 } 379 380 outResource->name.type = ResourceType::kId; 381 outResource->name.entry = maybeName.value().toString(); 382 outResource->value = util::make_unique<Id>(); 383 return true; 384 } 385 386 const auto itemIter = elToItemMap.find(resourceType); 387 if (itemIter != elToItemMap.end()) { 388 // This is an item, record its type and format and start parsing. 389 390 if (!maybeName) { 391 mDiag->error(DiagMessage(outResource->source) 392 << "<" << parser->getElementName() << "> missing 'name' attribute"); 393 return false; 394 } 395 396 outResource->name.type = itemIter->second.type; 397 outResource->name.entry = maybeName.value().toString(); 398 399 // Only use the implicit format for this type if it wasn't overridden. 400 if (!resourceFormat) { 401 resourceFormat = itemIter->second.format; 402 } 403 404 if (!parseItem(parser, outResource, resourceFormat)) { 405 return false; 406 } 407 return true; 408 } 409 410 // This might be a bag or something. 411 const auto bagIter = elToBagMap.find(resourceType); 412 if (bagIter != elToBagMap.end()) { 413 // Ensure we have a name (unless this is a <public-group>). 414 if (resourceType != u"public-group") { 415 if (!maybeName) { 416 mDiag->error(DiagMessage(outResource->source) 417 << "<" << parser->getElementName() << "> missing 'name' attribute"); 418 return false; 419 } 420 421 outResource->name.entry = maybeName.value().toString(); 422 } 423 424 // Call the associated parse method. The type will be filled in by the 425 // parse func. 426 if (!bagIter->second(this, parser, outResource)) { 427 return false; 428 } 429 return true; 430 } 431 432 // Try parsing the elementName (or type) as a resource. These shall only be 433 // resources like 'layout' or 'xml' and they can only be references. 434 const ResourceType* parsedType = parseResourceType(resourceType); 435 if (parsedType) { 436 if (!maybeName) { 437 mDiag->error(DiagMessage(outResource->source) 438 << "<" << parser->getElementName() << "> missing 'name' attribute"); 439 return false; 440 } 441 442 outResource->name.type = *parsedType; 443 outResource->name.entry = maybeName.value().toString(); 444 outResource->value = parseXml(parser, android::ResTable_map::TYPE_REFERENCE, kNoRawString); 445 if (!outResource->value) { 446 mDiag->error(DiagMessage(outResource->source) 447 << "invalid value for type '" << *parsedType << "'. Expected a reference"); 448 return false; 449 } 450 return true; 451 } 452 453 mDiag->warn(DiagMessage(outResource->source) 454 << "unknown resource type '" << parser->getElementName() << "'"); 455 return false; 456 } 457 458 bool ResourceParser::parseItem(xml::XmlPullParser* parser, ParsedResource* outResource, 459 const uint32_t format) { 460 if (format == android::ResTable_map::TYPE_STRING) { 461 return parseString(parser, outResource); 462 } 463 464 outResource->value = parseXml(parser, format, kNoRawString); 465 if (!outResource->value) { 466 mDiag->error(DiagMessage(outResource->source) << "invalid " << outResource->name.type); 467 return false; 468 } 469 return true; 470 } 471 472 /** 473 * Reads the entire XML subtree and attempts to parse it as some Item, 474 * with typeMask denoting which items it can be. If allowRawValue is 475 * true, a RawString is returned if the XML couldn't be parsed as 476 * an Item. If allowRawValue is false, nullptr is returned in this 477 * case. 478 */ 479 std::unique_ptr<Item> ResourceParser::parseXml(xml::XmlPullParser* parser, const uint32_t typeMask, 480 const bool allowRawValue) { 481 const size_t beginXmlLine = parser->getLineNumber(); 482 483 std::u16string rawValue; 484 StyleString styleString; 485 if (!flattenXmlSubtree(parser, &rawValue, &styleString)) { 486 return {}; 487 } 488 489 if (!styleString.spans.empty()) { 490 // This can only be a StyledString. 491 return util::make_unique<StyledString>( 492 mTable->stringPool.makeRef(styleString, StringPool::Context{ 1, mConfig })); 493 } 494 495 auto onCreateReference = [&](const ResourceName& name) { 496 // name.package can be empty here, as it will assume the package name of the table. 497 std::unique_ptr<Id> id = util::make_unique<Id>(); 498 id->setSource(mSource.withLine(beginXmlLine)); 499 mTable->addResource(name, {}, {}, std::move(id), mDiag); 500 }; 501 502 // Process the raw value. 503 std::unique_ptr<Item> processedItem = ResourceUtils::parseItemForAttribute(rawValue, typeMask, 504 onCreateReference); 505 if (processedItem) { 506 // Fix up the reference. 507 if (Reference* ref = valueCast<Reference>(processedItem.get())) { 508 transformReferenceFromNamespace(parser, u"", ref); 509 } 510 return processedItem; 511 } 512 513 // Try making a regular string. 514 if (typeMask & android::ResTable_map::TYPE_STRING) { 515 // Use the trimmed, escaped string. 516 return util::make_unique<String>( 517 mTable->stringPool.makeRef(styleString.str, StringPool::Context{ 1, mConfig })); 518 } 519 520 if (allowRawValue) { 521 // We can't parse this so return a RawString if we are allowed. 522 return util::make_unique<RawString>( 523 mTable->stringPool.makeRef(rawValue, StringPool::Context{ 1, mConfig })); 524 } 525 return {}; 526 } 527 528 bool ResourceParser::parseString(xml::XmlPullParser* parser, ParsedResource* outResource) { 529 bool formatted = true; 530 if (Maybe<StringPiece16> formattedAttr = xml::findAttribute(parser, u"formatted")) { 531 if (!ResourceUtils::tryParseBool(formattedAttr.value(), &formatted)) { 532 mDiag->error(DiagMessage(outResource->source) 533 << "invalid value for 'formatted'. Must be a boolean"); 534 return false; 535 } 536 } 537 538 bool translateable = mOptions.translatable; 539 if (Maybe<StringPiece16> translateableAttr = xml::findAttribute(parser, u"translatable")) { 540 if (!ResourceUtils::tryParseBool(translateableAttr.value(), &translateable)) { 541 mDiag->error(DiagMessage(outResource->source) 542 << "invalid value for 'translatable'. Must be a boolean"); 543 return false; 544 } 545 } 546 547 outResource->value = parseXml(parser, android::ResTable_map::TYPE_STRING, kNoRawString); 548 if (!outResource->value) { 549 mDiag->error(DiagMessage(outResource->source) << "not a valid string"); 550 return false; 551 } 552 553 if (String* stringValue = valueCast<String>(outResource->value.get())) { 554 stringValue->setTranslateable(translateable); 555 556 if (formatted && translateable) { 557 if (!util::verifyJavaStringFormat(*stringValue->value)) { 558 DiagMessage msg(outResource->source); 559 msg << "multiple substitutions specified in non-positional format; " 560 "did you mean to add the formatted=\"false\" attribute?"; 561 if (mOptions.errorOnPositionalArguments) { 562 mDiag->error(msg); 563 return false; 564 } 565 566 mDiag->warn(msg); 567 } 568 } 569 570 } else if (StyledString* stringValue = valueCast<StyledString>(outResource->value.get())) { 571 stringValue->setTranslateable(translateable); 572 } 573 return true; 574 } 575 576 bool ResourceParser::parsePublic(xml::XmlPullParser* parser, ParsedResource* outResource) { 577 Maybe<StringPiece16> maybeType = xml::findNonEmptyAttribute(parser, u"type"); 578 if (!maybeType) { 579 mDiag->error(DiagMessage(outResource->source) << "<public> must have a 'type' attribute"); 580 return false; 581 } 582 583 const ResourceType* parsedType = parseResourceType(maybeType.value()); 584 if (!parsedType) { 585 mDiag->error(DiagMessage(outResource->source) 586 << "invalid resource type '" << maybeType.value() << "' in <public>"); 587 return false; 588 } 589 590 outResource->name.type = *parsedType; 591 592 if (Maybe<StringPiece16> maybeId = xml::findNonEmptyAttribute(parser, u"id")) { 593 android::Res_value val; 594 bool result = android::ResTable::stringToInt(maybeId.value().data(), 595 maybeId.value().size(), &val); 596 ResourceId resourceId(val.data); 597 if (!result || !resourceId.isValid()) { 598 mDiag->error(DiagMessage(outResource->source) 599 << "invalid resource ID '" << maybeId.value() << "' in <public>"); 600 return false; 601 } 602 outResource->id = resourceId; 603 } 604 605 if (*parsedType == ResourceType::kId) { 606 // An ID marked as public is also the definition of an ID. 607 outResource->value = util::make_unique<Id>(); 608 } 609 610 outResource->symbolState = SymbolState::kPublic; 611 return true; 612 } 613 614 bool ResourceParser::parsePublicGroup(xml::XmlPullParser* parser, ParsedResource* outResource) { 615 Maybe<StringPiece16> maybeType = xml::findNonEmptyAttribute(parser, u"type"); 616 if (!maybeType) { 617 mDiag->error(DiagMessage(outResource->source) 618 << "<public-group> must have a 'type' attribute"); 619 return false; 620 } 621 622 const ResourceType* parsedType = parseResourceType(maybeType.value()); 623 if (!parsedType) { 624 mDiag->error(DiagMessage(outResource->source) 625 << "invalid resource type '" << maybeType.value() << "' in <public-group>"); 626 return false; 627 } 628 629 Maybe<StringPiece16> maybeId = xml::findNonEmptyAttribute(parser, u"first-id"); 630 if (!maybeId) { 631 mDiag->error(DiagMessage(outResource->source) 632 << "<public-group> must have a 'first-id' attribute"); 633 return false; 634 } 635 636 android::Res_value val; 637 bool result = android::ResTable::stringToInt(maybeId.value().data(), 638 maybeId.value().size(), &val); 639 ResourceId nextId(val.data); 640 if (!result || !nextId.isValid()) { 641 mDiag->error(DiagMessage(outResource->source) 642 << "invalid resource ID '" << maybeId.value() << "' in <public-group>"); 643 return false; 644 } 645 646 std::u16string comment; 647 bool error = false; 648 const size_t depth = parser->getDepth(); 649 while (xml::XmlPullParser::nextChildNode(parser, depth)) { 650 if (parser->getEvent() == xml::XmlPullParser::Event::kComment) { 651 comment = util::trimWhitespace(parser->getComment()).toString(); 652 continue; 653 } else if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) { 654 // Skip text. 655 continue; 656 } 657 658 const Source itemSource = mSource.withLine(parser->getLineNumber()); 659 const std::u16string& elementNamespace = parser->getElementNamespace(); 660 const std::u16string& elementName = parser->getElementName(); 661 if (elementNamespace.empty() && elementName == u"public") { 662 Maybe<StringPiece16> maybeName = xml::findNonEmptyAttribute(parser, u"name"); 663 if (!maybeName) { 664 mDiag->error(DiagMessage(itemSource) << "<public> must have a 'name' attribute"); 665 error = true; 666 continue; 667 } 668 669 if (xml::findNonEmptyAttribute(parser, u"id")) { 670 mDiag->error(DiagMessage(itemSource) << "'id' is ignored within <public-group>"); 671 error = true; 672 continue; 673 } 674 675 if (xml::findNonEmptyAttribute(parser, u"type")) { 676 mDiag->error(DiagMessage(itemSource) << "'type' is ignored within <public-group>"); 677 error = true; 678 continue; 679 } 680 681 ParsedResource childResource; 682 childResource.name.type = *parsedType; 683 childResource.name.entry = maybeName.value().toString(); 684 childResource.id = nextId; 685 childResource.comment = std::move(comment); 686 childResource.source = itemSource; 687 childResource.symbolState = SymbolState::kPublic; 688 outResource->childResources.push_back(std::move(childResource)); 689 690 nextId.id += 1; 691 692 } else if (!shouldIgnoreElement(elementNamespace, elementName)) { 693 mDiag->error(DiagMessage(itemSource) << ":" << elementName << ">"); 694 error = true; 695 } 696 } 697 return !error; 698 } 699 700 bool ResourceParser::parseSymbolImpl(xml::XmlPullParser* parser, ParsedResource* outResource) { 701 Maybe<StringPiece16> maybeType = xml::findNonEmptyAttribute(parser, u"type"); 702 if (!maybeType) { 703 mDiag->error(DiagMessage(outResource->source) 704 << "<" << parser->getElementName() << "> must have a 'type' attribute"); 705 return false; 706 } 707 708 const ResourceType* parsedType = parseResourceType(maybeType.value()); 709 if (!parsedType) { 710 mDiag->error(DiagMessage(outResource->source) 711 << "invalid resource type '" << maybeType.value() 712 << "' in <" << parser->getElementName() << ">"); 713 return false; 714 } 715 716 outResource->name.type = *parsedType; 717 return true; 718 } 719 720 bool ResourceParser::parseSymbol(xml::XmlPullParser* parser, ParsedResource* outResource) { 721 if (parseSymbolImpl(parser, outResource)) { 722 outResource->symbolState = SymbolState::kPrivate; 723 return true; 724 } 725 return false; 726 } 727 728 bool ResourceParser::parseAddResource(xml::XmlPullParser* parser, ParsedResource* outResource) { 729 if (parseSymbolImpl(parser, outResource)) { 730 outResource->symbolState = SymbolState::kUndefined; 731 return true; 732 } 733 return false; 734 } 735 736 737 bool ResourceParser::parseAttr(xml::XmlPullParser* parser, ParsedResource* outResource) { 738 return parseAttrImpl(parser, outResource, false); 739 } 740 741 bool ResourceParser::parseAttrImpl(xml::XmlPullParser* parser, ParsedResource* outResource, 742 bool weak) { 743 outResource->name.type = ResourceType::kAttr; 744 745 // Attributes only end up in default configuration. 746 if (outResource->config != ConfigDescription::defaultConfig()) { 747 mDiag->warn(DiagMessage(outResource->source) << "ignoring configuration '" 748 << outResource->config << "' for attribute " << outResource->name); 749 outResource->config = ConfigDescription::defaultConfig(); 750 } 751 752 uint32_t typeMask = 0; 753 754 Maybe<StringPiece16> maybeFormat = xml::findAttribute(parser, u"format"); 755 if (maybeFormat) { 756 typeMask = parseFormatAttribute(maybeFormat.value()); 757 if (typeMask == 0) { 758 mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber())) 759 << "invalid attribute format '" << maybeFormat.value() << "'"); 760 return false; 761 } 762 } 763 764 Maybe<int32_t> maybeMin, maybeMax; 765 766 if (Maybe<StringPiece16> maybeMinStr = xml::findAttribute(parser, u"min")) { 767 StringPiece16 minStr = util::trimWhitespace(maybeMinStr.value()); 768 if (!minStr.empty()) { 769 android::Res_value value; 770 if (android::ResTable::stringToInt(minStr.data(), minStr.size(), &value)) { 771 maybeMin = static_cast<int32_t>(value.data); 772 } 773 } 774 775 if (!maybeMin) { 776 mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber())) 777 << "invalid 'min' value '" << minStr << "'"); 778 return false; 779 } 780 } 781 782 if (Maybe<StringPiece16> maybeMaxStr = xml::findAttribute(parser, u"max")) { 783 StringPiece16 maxStr = util::trimWhitespace(maybeMaxStr.value()); 784 if (!maxStr.empty()) { 785 android::Res_value value; 786 if (android::ResTable::stringToInt(maxStr.data(), maxStr.size(), &value)) { 787 maybeMax = static_cast<int32_t>(value.data); 788 } 789 } 790 791 if (!maybeMax) { 792 mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber())) 793 << "invalid 'max' value '" << maxStr << "'"); 794 return false; 795 } 796 } 797 798 if ((maybeMin || maybeMax) && (typeMask & android::ResTable_map::TYPE_INTEGER) == 0) { 799 mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber())) 800 << "'min' and 'max' can only be used when format='integer'"); 801 return false; 802 } 803 804 struct SymbolComparator { 805 bool operator()(const Attribute::Symbol& a, const Attribute::Symbol& b) { 806 return a.symbol.name.value() < b.symbol.name.value(); 807 } 808 }; 809 810 std::set<Attribute::Symbol, SymbolComparator> items; 811 812 std::u16string comment; 813 bool error = false; 814 const size_t depth = parser->getDepth(); 815 while (xml::XmlPullParser::nextChildNode(parser, depth)) { 816 if (parser->getEvent() == xml::XmlPullParser::Event::kComment) { 817 comment = util::trimWhitespace(parser->getComment()).toString(); 818 continue; 819 } else if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) { 820 // Skip text. 821 continue; 822 } 823 824 const Source itemSource = mSource.withLine(parser->getLineNumber()); 825 const std::u16string& elementNamespace = parser->getElementNamespace(); 826 const std::u16string& elementName = parser->getElementName(); 827 if (elementNamespace.empty() && (elementName == u"flag" || elementName == u"enum")) { 828 if (elementName == u"enum") { 829 if (typeMask & android::ResTable_map::TYPE_FLAGS) { 830 mDiag->error(DiagMessage(itemSource) 831 << "can not define an <enum>; already defined a <flag>"); 832 error = true; 833 continue; 834 } 835 typeMask |= android::ResTable_map::TYPE_ENUM; 836 837 } else if (elementName == u"flag") { 838 if (typeMask & android::ResTable_map::TYPE_ENUM) { 839 mDiag->error(DiagMessage(itemSource) 840 << "can not define a <flag>; already defined an <enum>"); 841 error = true; 842 continue; 843 } 844 typeMask |= android::ResTable_map::TYPE_FLAGS; 845 } 846 847 if (Maybe<Attribute::Symbol> s = parseEnumOrFlagItem(parser, elementName)) { 848 Attribute::Symbol& symbol = s.value(); 849 ParsedResource childResource; 850 childResource.name = symbol.symbol.name.value(); 851 childResource.source = itemSource; 852 childResource.value = util::make_unique<Id>(); 853 outResource->childResources.push_back(std::move(childResource)); 854 855 symbol.symbol.setComment(std::move(comment)); 856 symbol.symbol.setSource(itemSource); 857 858 auto insertResult = items.insert(std::move(symbol)); 859 if (!insertResult.second) { 860 const Attribute::Symbol& existingSymbol = *insertResult.first; 861 mDiag->error(DiagMessage(itemSource) 862 << "duplicate symbol '" << existingSymbol.symbol.name.value().entry 863 << "'"); 864 865 mDiag->note(DiagMessage(existingSymbol.symbol.getSource()) 866 << "first defined here"); 867 error = true; 868 } 869 } else { 870 error = true; 871 } 872 } else if (!shouldIgnoreElement(elementNamespace, elementName)) { 873 mDiag->error(DiagMessage(itemSource) << ":" << elementName << ">"); 874 error = true; 875 } 876 877 comment = {}; 878 } 879 880 if (error) { 881 return false; 882 } 883 884 std::unique_ptr<Attribute> attr = util::make_unique<Attribute>(weak); 885 attr->symbols = std::vector<Attribute::Symbol>(items.begin(), items.end()); 886 attr->typeMask = typeMask ? typeMask : uint32_t(android::ResTable_map::TYPE_ANY); 887 if (maybeMin) { 888 attr->minInt = maybeMin.value(); 889 } 890 891 if (maybeMax) { 892 attr->maxInt = maybeMax.value(); 893 } 894 outResource->value = std::move(attr); 895 return true; 896 } 897 898 Maybe<Attribute::Symbol> ResourceParser::parseEnumOrFlagItem(xml::XmlPullParser* parser, 899 const StringPiece16& tag) { 900 const Source source = mSource.withLine(parser->getLineNumber()); 901 902 Maybe<StringPiece16> maybeName = xml::findNonEmptyAttribute(parser, u"name"); 903 if (!maybeName) { 904 mDiag->error(DiagMessage(source) << "no attribute 'name' found for tag <" << tag << ">"); 905 return {}; 906 } 907 908 Maybe<StringPiece16> maybeValue = xml::findNonEmptyAttribute(parser, u"value"); 909 if (!maybeValue) { 910 mDiag->error(DiagMessage(source) << "no attribute 'value' found for tag <" << tag << ">"); 911 return {}; 912 } 913 914 android::Res_value val; 915 if (!android::ResTable::stringToInt(maybeValue.value().data(), 916 maybeValue.value().size(), &val)) { 917 mDiag->error(DiagMessage(source) << "invalid value '" << maybeValue.value() 918 << "' for <" << tag << ">; must be an integer"); 919 return {}; 920 } 921 922 return Attribute::Symbol{ 923 Reference(ResourceNameRef({}, ResourceType::kId, maybeName.value())), val.data }; 924 } 925 926 static Maybe<Reference> parseXmlAttributeName(StringPiece16 str) { 927 str = util::trimWhitespace(str); 928 const char16_t* start = str.data(); 929 const char16_t* const end = start + str.size(); 930 const char16_t* p = start; 931 932 Reference ref; 933 if (p != end && *p == u'*') { 934 ref.privateReference = true; 935 start++; 936 p++; 937 } 938 939 StringPiece16 package; 940 StringPiece16 name; 941 while (p != end) { 942 if (*p == u':') { 943 package = StringPiece16(start, p - start); 944 name = StringPiece16(p + 1, end - (p + 1)); 945 break; 946 } 947 p++; 948 } 949 950 ref.name = ResourceName(package.toString(), ResourceType::kAttr, 951 name.empty() ? str.toString() : name.toString()); 952 return Maybe<Reference>(std::move(ref)); 953 } 954 955 bool ResourceParser::parseStyleItem(xml::XmlPullParser* parser, Style* style) { 956 const Source source = mSource.withLine(parser->getLineNumber()); 957 958 Maybe<StringPiece16> maybeName = xml::findNonEmptyAttribute(parser, u"name"); 959 if (!maybeName) { 960 mDiag->error(DiagMessage(source) << "<item> must have a 'name' attribute"); 961 return false; 962 } 963 964 Maybe<Reference> maybeKey = parseXmlAttributeName(maybeName.value()); 965 if (!maybeKey) { 966 mDiag->error(DiagMessage(source) << "invalid attribute name '" << maybeName.value() << "'"); 967 return false; 968 } 969 970 transformReferenceFromNamespace(parser, u"", &maybeKey.value()); 971 maybeKey.value().setSource(source); 972 973 std::unique_ptr<Item> value = parseXml(parser, 0, kAllowRawString); 974 if (!value) { 975 mDiag->error(DiagMessage(source) << "could not parse style item"); 976 return false; 977 } 978 979 style->entries.push_back(Style::Entry{ std::move(maybeKey.value()), std::move(value) }); 980 return true; 981 } 982 983 bool ResourceParser::parseStyle(xml::XmlPullParser* parser, ParsedResource* outResource) { 984 outResource->name.type = ResourceType::kStyle; 985 986 std::unique_ptr<Style> style = util::make_unique<Style>(); 987 988 Maybe<StringPiece16> maybeParent = xml::findAttribute(parser, u"parent"); 989 if (maybeParent) { 990 // If the parent is empty, we don't have a parent, but we also don't infer either. 991 if (!maybeParent.value().empty()) { 992 std::string errStr; 993 style->parent = ResourceUtils::parseStyleParentReference(maybeParent.value(), &errStr); 994 if (!style->parent) { 995 mDiag->error(DiagMessage(outResource->source) << errStr); 996 return false; 997 } 998 999 // Transform the namespace prefix to the actual package name, and mark the reference as 1000 // private if appropriate. 1001 transformReferenceFromNamespace(parser, u"", &style->parent.value()); 1002 } 1003 1004 } else { 1005 // No parent was specified, so try inferring it from the style name. 1006 std::u16string styleName = outResource->name.entry; 1007 size_t pos = styleName.find_last_of(u'.'); 1008 if (pos != std::string::npos) { 1009 style->parentInferred = true; 1010 style->parent = Reference(ResourceName({}, ResourceType::kStyle, 1011 styleName.substr(0, pos))); 1012 } 1013 } 1014 1015 bool error = false; 1016 const size_t depth = parser->getDepth(); 1017 while (xml::XmlPullParser::nextChildNode(parser, depth)) { 1018 if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) { 1019 // Skip text and comments. 1020 continue; 1021 } 1022 1023 const std::u16string& elementNamespace = parser->getElementNamespace(); 1024 const std::u16string& elementName = parser->getElementName(); 1025 if (elementNamespace == u"" && elementName == u"item") { 1026 error |= !parseStyleItem(parser, style.get()); 1027 1028 } else if (!shouldIgnoreElement(elementNamespace, elementName)) { 1029 mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber())) 1030 << ":" << elementName << ">"); 1031 error = true; 1032 } 1033 } 1034 1035 if (error) { 1036 return false; 1037 } 1038 1039 outResource->value = std::move(style); 1040 return true; 1041 } 1042 1043 bool ResourceParser::parseArray(xml::XmlPullParser* parser, ParsedResource* outResource) { 1044 return parseArrayImpl(parser, outResource, android::ResTable_map::TYPE_ANY); 1045 } 1046 1047 bool ResourceParser::parseIntegerArray(xml::XmlPullParser* parser, ParsedResource* outResource) { 1048 return parseArrayImpl(parser, outResource, android::ResTable_map::TYPE_INTEGER); 1049 } 1050 1051 bool ResourceParser::parseStringArray(xml::XmlPullParser* parser, ParsedResource* outResource) { 1052 return parseArrayImpl(parser, outResource, android::ResTable_map::TYPE_STRING); 1053 } 1054 1055 bool ResourceParser::parseArrayImpl(xml::XmlPullParser* parser, ParsedResource* outResource, 1056 const uint32_t typeMask) { 1057 outResource->name.type = ResourceType::kArray; 1058 1059 std::unique_ptr<Array> array = util::make_unique<Array>(); 1060 1061 bool error = false; 1062 const size_t depth = parser->getDepth(); 1063 while (xml::XmlPullParser::nextChildNode(parser, depth)) { 1064 if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) { 1065 // Skip text and comments. 1066 continue; 1067 } 1068 1069 const Source itemSource = mSource.withLine(parser->getLineNumber()); 1070 const std::u16string& elementNamespace = parser->getElementNamespace(); 1071 const std::u16string& elementName = parser->getElementName(); 1072 if (elementNamespace.empty() && elementName == u"item") { 1073 std::unique_ptr<Item> item = parseXml(parser, typeMask, kNoRawString); 1074 if (!item) { 1075 mDiag->error(DiagMessage(itemSource) << "could not parse array item"); 1076 error = true; 1077 continue; 1078 } 1079 item->setSource(itemSource); 1080 array->items.emplace_back(std::move(item)); 1081 1082 } else if (!shouldIgnoreElement(elementNamespace, elementName)) { 1083 mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber())) 1084 << "unknown tag <" << elementNamespace << ":" << elementName << ">"); 1085 error = true; 1086 } 1087 } 1088 1089 if (error) { 1090 return false; 1091 } 1092 1093 outResource->value = std::move(array); 1094 return true; 1095 } 1096 1097 bool ResourceParser::parsePlural(xml::XmlPullParser* parser, ParsedResource* outResource) { 1098 outResource->name.type = ResourceType::kPlurals; 1099 1100 std::unique_ptr<Plural> plural = util::make_unique<Plural>(); 1101 1102 bool error = false; 1103 const size_t depth = parser->getDepth(); 1104 while (xml::XmlPullParser::nextChildNode(parser, depth)) { 1105 if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) { 1106 // Skip text and comments. 1107 continue; 1108 } 1109 1110 const Source itemSource = mSource.withLine(parser->getLineNumber()); 1111 const std::u16string& elementNamespace = parser->getElementNamespace(); 1112 const std::u16string& elementName = parser->getElementName(); 1113 if (elementNamespace.empty() && elementName == u"item") { 1114 Maybe<StringPiece16> maybeQuantity = xml::findNonEmptyAttribute(parser, u"quantity"); 1115 if (!maybeQuantity) { 1116 mDiag->error(DiagMessage(itemSource) << "<item> in <plurals> requires attribute " 1117 << "'quantity'"); 1118 error = true; 1119 continue; 1120 } 1121 1122 StringPiece16 trimmedQuantity = util::trimWhitespace(maybeQuantity.value()); 1123 size_t index = 0; 1124 if (trimmedQuantity == u"zero") { 1125 index = Plural::Zero; 1126 } else if (trimmedQuantity == u"one") { 1127 index = Plural::One; 1128 } else if (trimmedQuantity == u"two") { 1129 index = Plural::Two; 1130 } else if (trimmedQuantity == u"few") { 1131 index = Plural::Few; 1132 } else if (trimmedQuantity == u"many") { 1133 index = Plural::Many; 1134 } else if (trimmedQuantity == u"other") { 1135 index = Plural::Other; 1136 } else { 1137 mDiag->error(DiagMessage(itemSource) 1138 << "<item> in <plural> has invalid value '" << trimmedQuantity 1139 << "' for attribute 'quantity'"); 1140 error = true; 1141 continue; 1142 } 1143 1144 if (plural->values[index]) { 1145 mDiag->error(DiagMessage(itemSource) 1146 << "duplicate quantity '" << trimmedQuantity << "'"); 1147 error = true; 1148 continue; 1149 } 1150 1151 if (!(plural->values[index] = parseXml(parser, android::ResTable_map::TYPE_STRING, 1152 kNoRawString))) { 1153 error = true; 1154 } 1155 plural->values[index]->setSource(itemSource); 1156 1157 } else if (!shouldIgnoreElement(elementNamespace, elementName)) { 1158 mDiag->error(DiagMessage(itemSource) << "unknown tag <" << elementNamespace << ":" 1159 << elementName << ">"); 1160 error = true; 1161 } 1162 } 1163 1164 if (error) { 1165 return false; 1166 } 1167 1168 outResource->value = std::move(plural); 1169 return true; 1170 } 1171 1172 bool ResourceParser::parseDeclareStyleable(xml::XmlPullParser* parser, 1173 ParsedResource* outResource) { 1174 outResource->name.type = ResourceType::kStyleable; 1175 1176 // Declare-styleable is kPrivate by default, because it technically only exists in R.java. 1177 outResource->symbolState = SymbolState::kPublic; 1178 1179 // Declare-styleable only ends up in default config; 1180 if (outResource->config != ConfigDescription::defaultConfig()) { 1181 mDiag->warn(DiagMessage(outResource->source) << "ignoring configuration '" 1182 << outResource->config << "' for styleable " 1183 << outResource->name.entry); 1184 outResource->config = ConfigDescription::defaultConfig(); 1185 } 1186 1187 std::unique_ptr<Styleable> styleable = util::make_unique<Styleable>(); 1188 1189 std::u16string comment; 1190 bool error = false; 1191 const size_t depth = parser->getDepth(); 1192 while (xml::XmlPullParser::nextChildNode(parser, depth)) { 1193 if (parser->getEvent() == xml::XmlPullParser::Event::kComment) { 1194 comment = util::trimWhitespace(parser->getComment()).toString(); 1195 continue; 1196 } else if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) { 1197 // Ignore text. 1198 continue; 1199 } 1200 1201 const Source itemSource = mSource.withLine(parser->getLineNumber()); 1202 const std::u16string& elementNamespace = parser->getElementNamespace(); 1203 const std::u16string& elementName = parser->getElementName(); 1204 if (elementNamespace.empty() && elementName == u"attr") { 1205 Maybe<StringPiece16> maybeName = xml::findNonEmptyAttribute(parser, u"name"); 1206 if (!maybeName) { 1207 mDiag->error(DiagMessage(itemSource) << "<attr> tag must have a 'name' attribute"); 1208 error = true; 1209 continue; 1210 } 1211 1212 // If this is a declaration, the package name may be in the name. Separate these out. 1213 // Eg. <attr name="android:text" /> 1214 Maybe<Reference> maybeRef = parseXmlAttributeName(maybeName.value()); 1215 if (!maybeRef) { 1216 mDiag->error(DiagMessage(itemSource) << "<attr> tag has invalid name '" 1217 << maybeName.value() << "'"); 1218 error = true; 1219 continue; 1220 } 1221 1222 Reference& childRef = maybeRef.value(); 1223 xml::transformReferenceFromNamespace(parser, u"", &childRef); 1224 1225 // Create the ParsedResource that will add the attribute to the table. 1226 ParsedResource childResource; 1227 childResource.name = childRef.name.value(); 1228 childResource.source = itemSource; 1229 childResource.comment = std::move(comment); 1230 1231 if (!parseAttrImpl(parser, &childResource, true)) { 1232 error = true; 1233 continue; 1234 } 1235 1236 // Create the reference to this attribute. 1237 childRef.setComment(childResource.comment); 1238 childRef.setSource(itemSource); 1239 styleable->entries.push_back(std::move(childRef)); 1240 1241 outResource->childResources.push_back(std::move(childResource)); 1242 1243 } else if (!shouldIgnoreElement(elementNamespace, elementName)) { 1244 mDiag->error(DiagMessage(itemSource) << "unknown tag <" << elementNamespace << ":" 1245 << elementName << ">"); 1246 error = true; 1247 } 1248 1249 comment = {}; 1250 } 1251 1252 if (error) { 1253 return false; 1254 } 1255 1256 outResource->value = std::move(styleable); 1257 return true; 1258 } 1259 1260 } // namespace aapt 1261