HELLO·Android
系统源代码
IT资讯
技术文章
我的收藏
注册
登录
-
我收藏的文章
创建代码块
我的代码块
我的账号
Nougat 7.0
|
7.0.0_r31
下载
查看原文件
收藏
根目录
frameworks
base
tools
aapt2
ResourceParser.cpp
/* * 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 char16_t* sXliffNamespaceUri = u"urn:oasis:names:tc:xliff:document:1.2"; /** * Returns true if the element is
or
and can be safely ignored. */ static bool shouldIgnoreElement(const StringPiece16& ns, const StringPiece16& name) { return ns.empty() && (name == u"skip" || name == u"eat-comment"); } static uint32_t parseFormatType(const StringPiece16& piece) { if (piece == u"reference") return android::ResTable_map::TYPE_REFERENCE; else if (piece == u"string") return android::ResTable_map::TYPE_STRING; else if (piece == u"integer") return android::ResTable_map::TYPE_INTEGER; else if (piece == u"boolean") return android::ResTable_map::TYPE_BOOLEAN; else if (piece == u"color") return android::ResTable_map::TYPE_COLOR; else if (piece == u"float") return android::ResTable_map::TYPE_FLOAT; else if (piece == u"dimension") return android::ResTable_map::TYPE_DIMENSION; else if (piece == u"fraction") return android::ResTable_map::TYPE_FRACTION; else if (piece == u"enum") return android::ResTable_map::TYPE_ENUM; else if (piece == u"flags") return android::ResTable_map::TYPE_FLAGS; return 0; } static uint32_t parseFormatAttribute(const StringPiece16& str) { uint32_t mask = 0; for (StringPiece16 part : util::tokenize(str, u'|')) { StringPiece16 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::u16string comment; std::unique_ptr
value; std::list
childResources; }; // Recursively adds resources to the ResourceTable. static bool addResourcesToTable(ResourceTable* table, IDiagnostics* diag, ParsedResource* res) { StringPiece16 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::u16string* 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.str().size(); 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::u16string spanName = parser->getElementName(); const auto endAttrIter = parser->endAttributes(); for (auto attrIter = parser->beginAttributes(); attrIter != endAttrIter; ++attrIter) { spanName += u";"; spanName += attrIter->name; spanName += u"="; spanName += attrIter->value; } if (builder.str().size() > 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.str().size()) }); } } 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() != u"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::u16string 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::u16string elementName = parser->getElementName(); if (elementName == u"skip" || elementName == u"eat-comment") { comment = u""; 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, u"product")) { parsedResource.product = util::utf16ToUtf8(maybeProduct.value()); } // 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({ { u"bool", { ResourceType::kBool, android::ResTable_map::TYPE_BOOLEAN } }, { u"color", { ResourceType::kColor, android::ResTable_map::TYPE_COLOR } }, { u"dimen", { ResourceType::kDimen, android::ResTable_map::TYPE_FLOAT | android::ResTable_map::TYPE_FRACTION | android::ResTable_map::TYPE_DIMENSION } }, { u"drawable", { ResourceType::kDrawable, android::ResTable_map::TYPE_COLOR } }, { u"fraction", { ResourceType::kFraction, android::ResTable_map::TYPE_FLOAT | android::ResTable_map::TYPE_FRACTION | android::ResTable_map::TYPE_DIMENSION } }, { u"integer", { ResourceType::kInteger, android::ResTable_map::TYPE_INTEGER } }, { u"string", { ResourceType::kString, android::ResTable_map::TYPE_STRING } }, }); static const auto elToBagMap = ImmutableMap
::createPreSorted({ { u"add-resource", std::mem_fn(&ResourceParser::parseAddResource) }, { u"array", std::mem_fn(&ResourceParser::parseArray) }, { u"attr", std::mem_fn(&ResourceParser::parseAttr) }, { u"declare-styleable", std::mem_fn(&ResourceParser::parseDeclareStyleable) }, { u"integer-array", std::mem_fn(&ResourceParser::parseIntegerArray) }, { u"java-symbol", std::mem_fn(&ResourceParser::parseSymbol) }, { u"plurals", std::mem_fn(&ResourceParser::parsePlural) }, { u"public", std::mem_fn(&ResourceParser::parsePublic) }, { u"public-group", std::mem_fn(&ResourceParser::parsePublicGroup) }, { u"string-array", std::mem_fn(&ResourceParser::parseStringArray) }, { u"style", std::mem_fn(&ResourceParser::parseStyle) }, { u"symbol", std::mem_fn(&ResourceParser::parseSymbol) }, }); std::u16string resourceType = parser->getElementName(); // The value format accepted for this resource. uint32_t resourceFormat = 0u; if (resourceType == u"item") { // Items have their type encoded in the type attribute. if (Maybe
maybeType = xml::findNonEmptyAttribute(parser, u"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, u"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, u"name"); if (resourceType == u"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 != u"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::u16string 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{ 1, 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::parseItemForAttribute(rawValue, typeMask, onCreateReference); if (processedItem) { // Fix up the reference. if (Reference* ref = valueCast
(processedItem.get())) { transformReferenceFromNamespace(parser, u"", 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{ 1, 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{ 1, mConfig })); } return {}; } bool ResourceParser::parseString(xml::XmlPullParser* parser, ParsedResource* outResource) { bool formatted = true; if (Maybe
formattedAttr = xml::findAttribute(parser, u"formatted")) { if (!ResourceUtils::tryParseBool(formattedAttr.value(), &formatted)) { mDiag->error(DiagMessage(outResource->source) << "invalid value for 'formatted'. Must be a boolean"); return false; } } bool translateable = mOptions.translatable; if (Maybe
translateableAttr = xml::findAttribute(parser, u"translatable")) { if (!ResourceUtils::tryParseBool(translateableAttr.value(), &translateable)) { mDiag->error(DiagMessage(outResource->source) << "invalid value for 'translatable'. Must be a boolean"); return false; } } 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, u"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
maybeId = xml::findNonEmptyAttribute(parser, u"id")) { android::Res_value val; bool result = android::ResTable::stringToInt(maybeId.value().data(), maybeId.value().size(), &val); ResourceId resourceId(val.data); if (!result || !resourceId.isValid()) { mDiag->error(DiagMessage(outResource->source) << "invalid resource ID '" << maybeId.value() << "' in
"); return false; } outResource->id = resourceId; } 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, u"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
maybeId = xml::findNonEmptyAttribute(parser, u"first-id"); if (!maybeId) { mDiag->error(DiagMessage(outResource->source) << "
must have a 'first-id' attribute"); return false; } android::Res_value val; bool result = android::ResTable::stringToInt(maybeId.value().data(), maybeId.value().size(), &val); ResourceId nextId(val.data); if (!result || !nextId.isValid()) { mDiag->error(DiagMessage(outResource->source) << "invalid resource ID '" << maybeId.value() << "' in
"); return false; } std::u16string 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::u16string& elementNamespace = parser->getElementNamespace(); const std::u16string& elementName = parser->getElementName(); if (elementNamespace.empty() && elementName == u"public") { Maybe
maybeName = xml::findNonEmptyAttribute(parser, u"name"); if (!maybeName) { mDiag->error(DiagMessage(itemSource) << "
must have a 'name' attribute"); error = true; continue; } if (xml::findNonEmptyAttribute(parser, u"id")) { mDiag->error(DiagMessage(itemSource) << "'id' is ignored within
"); error = true; continue; } if (xml::findNonEmptyAttribute(parser, u"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, u"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, u"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, u"min")) { StringPiece16 minStr = util::trimWhitespace(maybeMinStr.value()); if (!minStr.empty()) { android::Res_value value; if (android::ResTable::stringToInt(minStr.data(), minStr.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, u"max")) { StringPiece16 maxStr = util::trimWhitespace(maybeMaxStr.value()); if (!maxStr.empty()) { android::Res_value value; if (android::ResTable::stringToInt(maxStr.data(), maxStr.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::u16string 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::u16string& elementNamespace = parser->getElementNamespace(); const std::u16string& elementName = parser->getElementName(); if (elementNamespace.empty() && (elementName == u"flag" || elementName == u"enum")) { if (elementName == u"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 == u"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 StringPiece16& tag) { const Source source = mSource.withLine(parser->getLineNumber()); Maybe
maybeName = xml::findNonEmptyAttribute(parser, u"name"); if (!maybeName) { mDiag->error(DiagMessage(source) << "no attribute 'name' found for tag <" << tag << ">"); return {}; } Maybe
maybeValue = xml::findNonEmptyAttribute(parser, u"value"); if (!maybeValue) { mDiag->error(DiagMessage(source) << "no attribute 'value' found for tag <" << tag << ">"); return {}; } android::Res_value val; if (!android::ResTable::stringToInt(maybeValue.value().data(), maybeValue.value().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 }; } static Maybe
parseXmlAttributeName(StringPiece16 str) { str = util::trimWhitespace(str); const char16_t* start = str.data(); const char16_t* const end = start + str.size(); const char16_t* p = start; Reference ref; if (p != end && *p == u'*') { ref.privateReference = true; start++; p++; } StringPiece16 package; StringPiece16 name; while (p != end) { if (*p == u':') { package = StringPiece16(start, p - start); name = StringPiece16(p + 1, end - (p + 1)); break; } p++; } ref.name = ResourceName(package.toString(), ResourceType::kAttr, name.empty() ? str.toString() : name.toString()); return Maybe
(std::move(ref)); } bool ResourceParser::parseStyleItem(xml::XmlPullParser* parser, Style* style) { const Source source = mSource.withLine(parser->getLineNumber()); Maybe
maybeName = xml::findNonEmptyAttribute(parser, u"name"); if (!maybeName) { mDiag->error(DiagMessage(source) << "
must have a 'name' attribute"); return false; } Maybe
maybeKey = parseXmlAttributeName(maybeName.value()); if (!maybeKey) { mDiag->error(DiagMessage(source) << "invalid attribute name '" << maybeName.value() << "'"); return false; } transformReferenceFromNamespace(parser, u"", &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