/* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "ResourceParser.h" #include "ResourceTable.h" #include "ResourceUtils.h" #include "ResourceValues.h" #include "ValueVisitor.h" #include "util/ImmutableMap.h" #include "util/Util.h" #include "xml/XmlPullParser.h" #include #include namespace aapt { constexpr const char* sXliffNamespaceUri = "urn:oasis:names:tc:xliff:document:1.2"; /** * Returns true if the element is or and can be safely * ignored. */ static bool shouldIgnoreElement(const StringPiece& ns, const StringPiece& name) { return ns.empty() && (name == "skip" || name == "eat-comment"); } static uint32_t parseFormatType(const StringPiece& piece) { if (piece == "reference") return android::ResTable_map::TYPE_REFERENCE; else if (piece == "string") return android::ResTable_map::TYPE_STRING; else if (piece == "integer") return android::ResTable_map::TYPE_INTEGER; else if (piece == "boolean") return android::ResTable_map::TYPE_BOOLEAN; else if (piece == "color") return android::ResTable_map::TYPE_COLOR; else if (piece == "float") return android::ResTable_map::TYPE_FLOAT; else if (piece == "dimension") return android::ResTable_map::TYPE_DIMENSION; else if (piece == "fraction") return android::ResTable_map::TYPE_FRACTION; else if (piece == "enum") return android::ResTable_map::TYPE_ENUM; else if (piece == "flags") return android::ResTable_map::TYPE_FLAGS; return 0; } static uint32_t parseFormatAttribute(const StringPiece& str) { uint32_t mask = 0; for (StringPiece part : util::tokenize(str, '|')) { StringPiece trimmedPart = util::trimWhitespace(part); uint32_t type = parseFormatType(trimmedPart); if (type == 0) { return 0; } mask |= type; } return mask; } /** * A parsed resource ready to be added to the ResourceTable. */ struct ParsedResource { ResourceName name; ConfigDescription config; std::string product; Source source; ResourceId id; Maybe symbolState; std::string comment; std::unique_ptr value; std::list childResources; }; // Recursively adds resources to the ResourceTable. static bool addResourcesToTable(ResourceTable* table, IDiagnostics* diag, ParsedResource* res) { StringPiece trimmedComment = util::trimWhitespace(res->comment); if (trimmedComment.size() != res->comment.size()) { // Only if there was a change do we re-assign. res->comment = trimmedComment.toString(); } if (res->symbolState) { Symbol symbol; symbol.state = res->symbolState.value(); symbol.source = res->source; symbol.comment = res->comment; if (!table->setSymbolState(res->name, res->id, symbol, diag)) { return false; } } if (res->value) { // Attach the comment, source and config to the value. res->value->setComment(std::move(res->comment)); res->value->setSource(std::move(res->source)); if (!table->addResource(res->name, res->id, res->config, res->product, std::move(res->value), diag)) { return false; } } bool error = false; for (ParsedResource& child : res->childResources) { error |= !addResourcesToTable(table, diag, &child); } return !error; } // Convenient aliases for more readable function calls. enum { kAllowRawString = true, kNoRawString = false }; ResourceParser::ResourceParser(IDiagnostics* diag, ResourceTable* table, const Source& source, const ConfigDescription& config, const ResourceParserOptions& options) : mDiag(diag), mTable(table), mSource(source), mConfig(config), mOptions(options) {} /** * Build a string from XML that converts nested elements into Span objects. */ bool ResourceParser::flattenXmlSubtree(xml::XmlPullParser* parser, std::string* outRawString, StyleString* outStyleString) { std::vector spanStack; bool error = false; outRawString->clear(); outStyleString->spans.clear(); util::StringBuilder builder; size_t depth = 1; while (xml::XmlPullParser::isGoodEvent(parser->next())) { const xml::XmlPullParser::Event event = parser->getEvent(); if (event == xml::XmlPullParser::Event::kEndElement) { if (!parser->getElementNamespace().empty()) { // We already warned and skipped the start element, so just skip here // too continue; } depth--; if (depth == 0) { break; } spanStack.back().lastChar = builder.utf16Len() - 1; outStyleString->spans.push_back(spanStack.back()); spanStack.pop_back(); } else if (event == xml::XmlPullParser::Event::kText) { outRawString->append(parser->getText()); builder.append(parser->getText()); } else if (event == xml::XmlPullParser::Event::kStartElement) { if (!parser->getElementNamespace().empty()) { if (parser->getElementNamespace() != sXliffNamespaceUri) { // Only warn if this isn't an xliff namespace. mDiag->warn(DiagMessage(mSource.withLine(parser->getLineNumber())) << "skipping element '" << parser->getElementName() << "' with unknown namespace '" << parser->getElementNamespace() << "'"); } continue; } depth++; // Build a span object out of the nested element. std::string spanName = parser->getElementName(); const auto endAttrIter = parser->endAttributes(); for (auto attrIter = parser->beginAttributes(); attrIter != endAttrIter; ++attrIter) { spanName += ";"; spanName += attrIter->name; spanName += "="; spanName += attrIter->value; } if (builder.utf16Len() > std::numeric_limits::max()) { mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber())) << "style string '" << builder.str() << "' is too long"); error = true; } else { spanStack.push_back( Span{spanName, static_cast(builder.utf16Len())}); } } else if (event == xml::XmlPullParser::Event::kComment) { // Skip } else { assert(false); } } assert(spanStack.empty() && "spans haven't been fully processed"); outStyleString->str = builder.str(); return !error; } bool ResourceParser::parse(xml::XmlPullParser* parser) { bool error = false; const size_t depth = parser->getDepth(); while (xml::XmlPullParser::nextChildNode(parser, depth)) { if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) { // Skip comments and text. continue; } if (!parser->getElementNamespace().empty() || parser->getElementName() != "resources") { mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber())) << "root element must be "); return false; } error |= !parseResources(parser); break; }; if (parser->getEvent() == xml::XmlPullParser::Event::kBadDocument) { mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber())) << "xml parser error: " << parser->getLastError()); return false; } return !error; } bool ResourceParser::parseResources(xml::XmlPullParser* parser) { std::set strippedResources; bool error = false; std::string comment; const size_t depth = parser->getDepth(); while (xml::XmlPullParser::nextChildNode(parser, depth)) { const xml::XmlPullParser::Event event = parser->getEvent(); if (event == xml::XmlPullParser::Event::kComment) { comment = parser->getComment(); continue; } if (event == xml::XmlPullParser::Event::kText) { if (!util::trimWhitespace(parser->getText()).empty()) { mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber())) << "plain text not allowed here"); error = true; } continue; } assert(event == xml::XmlPullParser::Event::kStartElement); if (!parser->getElementNamespace().empty()) { // Skip unknown namespace. continue; } std::string elementName = parser->getElementName(); if (elementName == "skip" || elementName == "eat-comment") { comment = ""; continue; } ParsedResource parsedResource; parsedResource.config = mConfig; parsedResource.source = mSource.withLine(parser->getLineNumber()); parsedResource.comment = std::move(comment); // Extract the product name if it exists. if (Maybe maybeProduct = xml::findNonEmptyAttribute(parser, "product")) { parsedResource.product = maybeProduct.value().toString(); } // Parse the resource regardless of product. if (!parseResource(parser, &parsedResource)) { error = true; continue; } if (!addResourcesToTable(mTable, mDiag, &parsedResource)) { error = true; } } // Check that we included at least one variant of each stripped resource. for (const ResourceName& strippedResource : strippedResources) { if (!mTable->findResource(strippedResource)) { // Failed to find the resource. mDiag->error(DiagMessage(mSource) << "resource '" << strippedResource << "' " "was filtered out but no product variant remains"); error = true; } } return !error; } bool ResourceParser::parseResource(xml::XmlPullParser* parser, ParsedResource* outResource) { struct ItemTypeFormat { ResourceType type; uint32_t format; }; using BagParseFunc = std::function; static const auto elToItemMap = ImmutableMap::createPreSorted({ {"bool", {ResourceType::kBool, android::ResTable_map::TYPE_BOOLEAN}}, {"color", {ResourceType::kColor, android::ResTable_map::TYPE_COLOR}}, {"dimen", {ResourceType::kDimen, android::ResTable_map::TYPE_FLOAT | android::ResTable_map::TYPE_FRACTION | android::ResTable_map::TYPE_DIMENSION}}, {"drawable", {ResourceType::kDrawable, android::ResTable_map::TYPE_COLOR}}, {"fraction", {ResourceType::kFraction, android::ResTable_map::TYPE_FLOAT | android::ResTable_map::TYPE_FRACTION | android::ResTable_map::TYPE_DIMENSION}}, {"integer", {ResourceType::kInteger, android::ResTable_map::TYPE_INTEGER}}, {"string", {ResourceType::kString, android::ResTable_map::TYPE_STRING}}, }); static const auto elToBagMap = ImmutableMap::createPreSorted({ {"add-resource", std::mem_fn(&ResourceParser::parseAddResource)}, {"array", std::mem_fn(&ResourceParser::parseArray)}, {"attr", std::mem_fn(&ResourceParser::parseAttr)}, {"declare-styleable", std::mem_fn(&ResourceParser::parseDeclareStyleable)}, {"integer-array", std::mem_fn(&ResourceParser::parseIntegerArray)}, {"java-symbol", std::mem_fn(&ResourceParser::parseSymbol)}, {"plurals", std::mem_fn(&ResourceParser::parsePlural)}, {"public", std::mem_fn(&ResourceParser::parsePublic)}, {"public-group", std::mem_fn(&ResourceParser::parsePublicGroup)}, {"string-array", std::mem_fn(&ResourceParser::parseStringArray)}, {"style", std::mem_fn(&ResourceParser::parseStyle)}, {"symbol", std::mem_fn(&ResourceParser::parseSymbol)}, }); std::string resourceType = parser->getElementName(); // The value format accepted for this resource. uint32_t resourceFormat = 0u; if (resourceType == "item") { // Items have their type encoded in the type attribute. if (Maybe maybeType = xml::findNonEmptyAttribute(parser, "type")) { resourceType = maybeType.value().toString(); } else { mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber())) << " must have a 'type' attribute"); return false; } if (Maybe maybeFormat = xml::findNonEmptyAttribute(parser, "format")) { // An explicit format for this resource was specified. The resource will // retain // its type in its name, but the accepted value for this type is // overridden. resourceFormat = parseFormatType(maybeFormat.value()); if (!resourceFormat) { mDiag->error(DiagMessage(outResource->source) << "'" << maybeFormat.value() << "' is an invalid format"); return false; } } } // Get the name of the resource. This will be checked later, because not all // XML elements require a name. Maybe maybeName = xml::findNonEmptyAttribute(parser, "name"); if (resourceType == "id") { if (!maybeName) { mDiag->error(DiagMessage(outResource->source) << "<" << parser->getElementName() << "> missing 'name' attribute"); return false; } outResource->name.type = ResourceType::kId; outResource->name.entry = maybeName.value().toString(); outResource->value = util::make_unique(); return true; } const auto itemIter = elToItemMap.find(resourceType); if (itemIter != elToItemMap.end()) { // This is an item, record its type and format and start parsing. if (!maybeName) { mDiag->error(DiagMessage(outResource->source) << "<" << parser->getElementName() << "> missing 'name' attribute"); return false; } outResource->name.type = itemIter->second.type; outResource->name.entry = maybeName.value().toString(); // Only use the implicit format for this type if it wasn't overridden. if (!resourceFormat) { resourceFormat = itemIter->second.format; } if (!parseItem(parser, outResource, resourceFormat)) { return false; } return true; } // This might be a bag or something. const auto bagIter = elToBagMap.find(resourceType); if (bagIter != elToBagMap.end()) { // Ensure we have a name (unless this is a ). if (resourceType != "public-group") { if (!maybeName) { mDiag->error(DiagMessage(outResource->source) << "<" << parser->getElementName() << "> missing 'name' attribute"); return false; } outResource->name.entry = maybeName.value().toString(); } // Call the associated parse method. The type will be filled in by the // parse func. if (!bagIter->second(this, parser, outResource)) { return false; } return true; } // Try parsing the elementName (or type) as a resource. These shall only be // resources like 'layout' or 'xml' and they can only be references. const ResourceType* parsedType = parseResourceType(resourceType); if (parsedType) { if (!maybeName) { mDiag->error(DiagMessage(outResource->source) << "<" << parser->getElementName() << "> missing 'name' attribute"); return false; } outResource->name.type = *parsedType; outResource->name.entry = maybeName.value().toString(); outResource->value = parseXml(parser, android::ResTable_map::TYPE_REFERENCE, kNoRawString); if (!outResource->value) { mDiag->error(DiagMessage(outResource->source) << "invalid value for type '" << *parsedType << "'. Expected a reference"); return false; } return true; } mDiag->warn(DiagMessage(outResource->source) << "unknown resource type '" << parser->getElementName() << "'"); return false; } bool ResourceParser::parseItem(xml::XmlPullParser* parser, ParsedResource* outResource, const uint32_t format) { if (format == android::ResTable_map::TYPE_STRING) { return parseString(parser, outResource); } outResource->value = parseXml(parser, format, kNoRawString); if (!outResource->value) { mDiag->error(DiagMessage(outResource->source) << "invalid " << outResource->name.type); return false; } return true; } /** * Reads the entire XML subtree and attempts to parse it as some Item, * with typeMask denoting which items it can be. If allowRawValue is * true, a RawString is returned if the XML couldn't be parsed as * an Item. If allowRawValue is false, nullptr is returned in this * case. */ std::unique_ptr ResourceParser::parseXml(xml::XmlPullParser* parser, const uint32_t typeMask, const bool allowRawValue) { const size_t beginXmlLine = parser->getLineNumber(); std::string rawValue; StyleString styleString; if (!flattenXmlSubtree(parser, &rawValue, &styleString)) { return {}; } if (!styleString.spans.empty()) { // This can only be a StyledString. return util::make_unique(mTable->stringPool.makeRef( styleString, StringPool::Context(StringPool::Context::kStylePriority, mConfig))); } auto onCreateReference = [&](const ResourceName& name) { // name.package can be empty here, as it will assume the package name of the // table. std::unique_ptr id = util::make_unique(); id->setSource(mSource.withLine(beginXmlLine)); mTable->addResource(name, {}, {}, std::move(id), mDiag); }; // Process the raw value. std::unique_ptr processedItem = ResourceUtils::tryParseItemForAttribute( rawValue, typeMask, onCreateReference); if (processedItem) { // Fix up the reference. if (Reference* ref = valueCast(processedItem.get())) { transformReferenceFromNamespace(parser, "", ref); } return processedItem; } // Try making a regular string. if (typeMask & android::ResTable_map::TYPE_STRING) { // Use the trimmed, escaped string. return util::make_unique(mTable->stringPool.makeRef( styleString.str, StringPool::Context(mConfig))); } if (allowRawValue) { // We can't parse this so return a RawString if we are allowed. return util::make_unique( mTable->stringPool.makeRef(rawValue, StringPool::Context(mConfig))); } return {}; } bool ResourceParser::parseString(xml::XmlPullParser* parser, ParsedResource* outResource) { bool formatted = true; if (Maybe formattedAttr = xml::findAttribute(parser, "formatted")) { Maybe maybeFormatted = ResourceUtils::parseBool(formattedAttr.value()); if (!maybeFormatted) { mDiag->error(DiagMessage(outResource->source) << "invalid value for 'formatted'. Must be a boolean"); return false; } formatted = maybeFormatted.value(); } bool translateable = mOptions.translatable; if (Maybe translateableAttr = xml::findAttribute(parser, "translatable")) { Maybe maybeTranslateable = ResourceUtils::parseBool(translateableAttr.value()); if (!maybeTranslateable) { mDiag->error(DiagMessage(outResource->source) << "invalid value for 'translatable'. Must be a boolean"); return false; } translateable = maybeTranslateable.value(); } outResource->value = parseXml(parser, android::ResTable_map::TYPE_STRING, kNoRawString); if (!outResource->value) { mDiag->error(DiagMessage(outResource->source) << "not a valid string"); return false; } if (String* stringValue = valueCast(outResource->value.get())) { stringValue->setTranslateable(translateable); if (formatted && translateable) { if (!util::verifyJavaStringFormat(*stringValue->value)) { DiagMessage msg(outResource->source); msg << "multiple substitutions specified in non-positional format; " "did you mean to add the formatted=\"false\" attribute?"; if (mOptions.errorOnPositionalArguments) { mDiag->error(msg); return false; } mDiag->warn(msg); } } } else if (StyledString* stringValue = valueCast(outResource->value.get())) { stringValue->setTranslateable(translateable); } return true; } bool ResourceParser::parsePublic(xml::XmlPullParser* parser, ParsedResource* outResource) { Maybe maybeType = xml::findNonEmptyAttribute(parser, "type"); if (!maybeType) { mDiag->error(DiagMessage(outResource->source) << " must have a 'type' attribute"); return false; } const ResourceType* parsedType = parseResourceType(maybeType.value()); if (!parsedType) { mDiag->error(DiagMessage(outResource->source) << "invalid resource type '" << maybeType.value() << "' in "); return false; } outResource->name.type = *parsedType; if (Maybe maybeIdStr = xml::findNonEmptyAttribute(parser, "id")) { Maybe maybeId = ResourceUtils::parseResourceId(maybeIdStr.value()); if (!maybeId) { mDiag->error(DiagMessage(outResource->source) << "invalid resource ID '" << maybeId.value() << "' in "); return false; } outResource->id = maybeId.value(); } if (*parsedType == ResourceType::kId) { // An ID marked as public is also the definition of an ID. outResource->value = util::make_unique(); } outResource->symbolState = SymbolState::kPublic; return true; } bool ResourceParser::parsePublicGroup(xml::XmlPullParser* parser, ParsedResource* outResource) { Maybe maybeType = xml::findNonEmptyAttribute(parser, "type"); if (!maybeType) { mDiag->error(DiagMessage(outResource->source) << " must have a 'type' attribute"); return false; } const ResourceType* parsedType = parseResourceType(maybeType.value()); if (!parsedType) { mDiag->error(DiagMessage(outResource->source) << "invalid resource type '" << maybeType.value() << "' in "); return false; } Maybe maybeIdStr = xml::findNonEmptyAttribute(parser, "first-id"); if (!maybeIdStr) { mDiag->error(DiagMessage(outResource->source) << " must have a 'first-id' attribute"); return false; } Maybe maybeId = ResourceUtils::parseResourceId(maybeIdStr.value()); if (!maybeId) { mDiag->error(DiagMessage(outResource->source) << "invalid resource ID '" << maybeIdStr.value() << "' in "); return false; } ResourceId nextId = maybeId.value(); std::string comment; bool error = false; const size_t depth = parser->getDepth(); while (xml::XmlPullParser::nextChildNode(parser, depth)) { if (parser->getEvent() == xml::XmlPullParser::Event::kComment) { comment = util::trimWhitespace(parser->getComment()).toString(); continue; } else if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) { // Skip text. continue; } const Source itemSource = mSource.withLine(parser->getLineNumber()); const std::string& elementNamespace = parser->getElementNamespace(); const std::string& elementName = parser->getElementName(); if (elementNamespace.empty() && elementName == "public") { Maybe maybeName = xml::findNonEmptyAttribute(parser, "name"); if (!maybeName) { mDiag->error(DiagMessage(itemSource) << " must have a 'name' attribute"); error = true; continue; } if (xml::findNonEmptyAttribute(parser, "id")) { mDiag->error(DiagMessage(itemSource) << "'id' is ignored within "); error = true; continue; } if (xml::findNonEmptyAttribute(parser, "type")) { mDiag->error(DiagMessage(itemSource) << "'type' is ignored within "); error = true; continue; } ParsedResource childResource; childResource.name.type = *parsedType; childResource.name.entry = maybeName.value().toString(); childResource.id = nextId; childResource.comment = std::move(comment); childResource.source = itemSource; childResource.symbolState = SymbolState::kPublic; outResource->childResources.push_back(std::move(childResource)); nextId.id += 1; } else if (!shouldIgnoreElement(elementNamespace, elementName)) { mDiag->error(DiagMessage(itemSource) << ":" << elementName << ">"); error = true; } } return !error; } bool ResourceParser::parseSymbolImpl(xml::XmlPullParser* parser, ParsedResource* outResource) { Maybe maybeType = xml::findNonEmptyAttribute(parser, "type"); if (!maybeType) { mDiag->error(DiagMessage(outResource->source) << "<" << parser->getElementName() << "> must have a 'type' attribute"); return false; } const ResourceType* parsedType = parseResourceType(maybeType.value()); if (!parsedType) { mDiag->error(DiagMessage(outResource->source) << "invalid resource type '" << maybeType.value() << "' in <" << parser->getElementName() << ">"); return false; } outResource->name.type = *parsedType; return true; } bool ResourceParser::parseSymbol(xml::XmlPullParser* parser, ParsedResource* outResource) { if (parseSymbolImpl(parser, outResource)) { outResource->symbolState = SymbolState::kPrivate; return true; } return false; } bool ResourceParser::parseAddResource(xml::XmlPullParser* parser, ParsedResource* outResource) { if (parseSymbolImpl(parser, outResource)) { outResource->symbolState = SymbolState::kUndefined; return true; } return false; } bool ResourceParser::parseAttr(xml::XmlPullParser* parser, ParsedResource* outResource) { return parseAttrImpl(parser, outResource, false); } bool ResourceParser::parseAttrImpl(xml::XmlPullParser* parser, ParsedResource* outResource, bool weak) { outResource->name.type = ResourceType::kAttr; // Attributes only end up in default configuration. if (outResource->config != ConfigDescription::defaultConfig()) { mDiag->warn(DiagMessage(outResource->source) << "ignoring configuration '" << outResource->config << "' for attribute " << outResource->name); outResource->config = ConfigDescription::defaultConfig(); } uint32_t typeMask = 0; Maybe maybeFormat = xml::findAttribute(parser, "format"); if (maybeFormat) { typeMask = parseFormatAttribute(maybeFormat.value()); if (typeMask == 0) { mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber())) << "invalid attribute format '" << maybeFormat.value() << "'"); return false; } } Maybe maybeMin, maybeMax; if (Maybe maybeMinStr = xml::findAttribute(parser, "min")) { StringPiece minStr = util::trimWhitespace(maybeMinStr.value()); if (!minStr.empty()) { std::u16string minStr16 = util::utf8ToUtf16(minStr); android::Res_value value; if (android::ResTable::stringToInt(minStr16.data(), minStr16.size(), &value)) { maybeMin = static_cast(value.data); } } if (!maybeMin) { mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber())) << "invalid 'min' value '" << minStr << "'"); return false; } } if (Maybe maybeMaxStr = xml::findAttribute(parser, "max")) { StringPiece maxStr = util::trimWhitespace(maybeMaxStr.value()); if (!maxStr.empty()) { std::u16string maxStr16 = util::utf8ToUtf16(maxStr); android::Res_value value; if (android::ResTable::stringToInt(maxStr16.data(), maxStr16.size(), &value)) { maybeMax = static_cast(value.data); } } if (!maybeMax) { mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber())) << "invalid 'max' value '" << maxStr << "'"); return false; } } if ((maybeMin || maybeMax) && (typeMask & android::ResTable_map::TYPE_INTEGER) == 0) { mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber())) << "'min' and 'max' can only be used when format='integer'"); return false; } struct SymbolComparator { bool operator()(const Attribute::Symbol& a, const Attribute::Symbol& b) { return a.symbol.name.value() < b.symbol.name.value(); } }; std::set items; std::string comment; bool error = false; const size_t depth = parser->getDepth(); while (xml::XmlPullParser::nextChildNode(parser, depth)) { if (parser->getEvent() == xml::XmlPullParser::Event::kComment) { comment = util::trimWhitespace(parser->getComment()).toString(); continue; } else if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) { // Skip text. continue; } const Source itemSource = mSource.withLine(parser->getLineNumber()); const std::string& elementNamespace = parser->getElementNamespace(); const std::string& elementName = parser->getElementName(); if (elementNamespace.empty() && (elementName == "flag" || elementName == "enum")) { if (elementName == "enum") { if (typeMask & android::ResTable_map::TYPE_FLAGS) { mDiag->error(DiagMessage(itemSource) << "can not define an ; already defined a "); error = true; continue; } typeMask |= android::ResTable_map::TYPE_ENUM; } else if (elementName == "flag") { if (typeMask & android::ResTable_map::TYPE_ENUM) { mDiag->error(DiagMessage(itemSource) << "can not define a ; already defined an "); error = true; continue; } typeMask |= android::ResTable_map::TYPE_FLAGS; } if (Maybe s = parseEnumOrFlagItem(parser, elementName)) { Attribute::Symbol& symbol = s.value(); ParsedResource childResource; childResource.name = symbol.symbol.name.value(); childResource.source = itemSource; childResource.value = util::make_unique(); outResource->childResources.push_back(std::move(childResource)); symbol.symbol.setComment(std::move(comment)); symbol.symbol.setSource(itemSource); auto insertResult = items.insert(std::move(symbol)); if (!insertResult.second) { const Attribute::Symbol& existingSymbol = *insertResult.first; mDiag->error(DiagMessage(itemSource) << "duplicate symbol '" << existingSymbol.symbol.name.value().entry << "'"); mDiag->note(DiagMessage(existingSymbol.symbol.getSource()) << "first defined here"); error = true; } } else { error = true; } } else if (!shouldIgnoreElement(elementNamespace, elementName)) { mDiag->error(DiagMessage(itemSource) << ":" << elementName << ">"); error = true; } comment = {}; } if (error) { return false; } std::unique_ptr attr = util::make_unique(weak); attr->symbols = std::vector(items.begin(), items.end()); attr->typeMask = typeMask ? typeMask : uint32_t(android::ResTable_map::TYPE_ANY); if (maybeMin) { attr->minInt = maybeMin.value(); } if (maybeMax) { attr->maxInt = maybeMax.value(); } outResource->value = std::move(attr); return true; } Maybe ResourceParser::parseEnumOrFlagItem( xml::XmlPullParser* parser, const StringPiece& tag) { const Source source = mSource.withLine(parser->getLineNumber()); Maybe maybeName = xml::findNonEmptyAttribute(parser, "name"); if (!maybeName) { mDiag->error(DiagMessage(source) << "no attribute 'name' found for tag <" << tag << ">"); return {}; } Maybe maybeValue = xml::findNonEmptyAttribute(parser, "value"); if (!maybeValue) { mDiag->error(DiagMessage(source) << "no attribute 'value' found for tag <" << tag << ">"); return {}; } std::u16string value16 = util::utf8ToUtf16(maybeValue.value()); android::Res_value val; if (!android::ResTable::stringToInt(value16.data(), value16.size(), &val)) { mDiag->error(DiagMessage(source) << "invalid value '" << maybeValue.value() << "' for <" << tag << ">; must be an integer"); return {}; } return Attribute::Symbol{ Reference(ResourceNameRef({}, ResourceType::kId, maybeName.value())), val.data}; } bool ResourceParser::parseStyleItem(xml::XmlPullParser* parser, Style* style) { const Source source = mSource.withLine(parser->getLineNumber()); Maybe maybeName = xml::findNonEmptyAttribute(parser, "name"); if (!maybeName) { mDiag->error(DiagMessage(source) << " must have a 'name' attribute"); return false; } Maybe maybeKey = ResourceUtils::parseXmlAttributeName(maybeName.value()); if (!maybeKey) { mDiag->error(DiagMessage(source) << "invalid attribute name '" << maybeName.value() << "'"); return false; } transformReferenceFromNamespace(parser, "", &maybeKey.value()); maybeKey.value().setSource(source); std::unique_ptr value = parseXml(parser, 0, kAllowRawString); if (!value) { mDiag->error(DiagMessage(source) << "could not parse style item"); return false; } style->entries.push_back( Style::Entry{std::move(maybeKey.value()), std::move(value)}); return true; } bool ResourceParser::parseStyle(xml::XmlPullParser* parser, ParsedResource* outResource) { outResource->name.type = ResourceType::kStyle; std::unique_ptr