/* * 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 "XmlPullParser.h" #include "util/Util.h" #include namespace aapt { constexpr const char16_t* sXliffNamespaceUri = u"urn:oasis:names:tc:xliff:document:1.2"; static Maybe findAttribute(XmlPullParser* parser, const StringPiece16& name) { auto iter = parser->findAttribute(u"", name); if (iter != parser->endAttributes()) { return StringPiece16(util::trimWhitespace(iter->value)); } return {}; } static Maybe findNonEmptyAttribute(XmlPullParser* parser, const StringPiece16& name) { auto iter = parser->findAttribute(u"", name); if (iter != parser->endAttributes()) { StringPiece16 trimmed = util::trimWhitespace(iter->value); if (!trimmed.empty()) { return trimmed; } } return {}; } /** * 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"); } 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(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 (XmlPullParser::isGoodEvent(parser->next())) { const XmlPullParser::Event event = parser->getEvent(); if (event == 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 == XmlPullParser::Event::kText) { outRawString->append(parser->getText()); builder.append(parser->getText()); } else if (event == 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 == 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(XmlPullParser* parser) { bool error = false; const size_t depth = parser->getDepth(); while (XmlPullParser::nextChildNode(parser, depth)) { if (parser->getEvent() != 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() == XmlPullParser::Event::kBadDocument) { mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber())) << "xml parser error: " << parser->getLastError()); return false; } return !error; } static bool shouldStripResource(XmlPullParser* parser, const Maybe productToMatch) { assert(parser->getEvent() == XmlPullParser::Event::kStartElement); if (Maybe maybeProduct = findNonEmptyAttribute(parser, u"product")) { if (!productToMatch) { if (maybeProduct.value() != u"default" && maybeProduct.value() != u"phone") { // We didn't specify a product and this is not a default product, so skip. return true; } } else { if (productToMatch && maybeProduct.value() != productToMatch.value()) { // We specified a product, but they don't match. return true; } } } return false; } /** * A parsed resource ready to be added to the ResourceTable. */ struct ParsedResource { ResourceName name; Source source; ResourceId id; SymbolState symbolState = SymbolState::kUndefined; std::u16string comment; std::unique_ptr value; std::list childResources; }; // Recursively adds resources to the ResourceTable. static bool addResourcesToTable(ResourceTable* table, const ConfigDescription& config, IDiagnostics* diag, ParsedResource* res) { if (res->symbolState != SymbolState::kUndefined) { Symbol symbol; symbol.state = res->symbolState; 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, config, std::move(res->value), diag)) { return false; } } bool error = false; for (ParsedResource& child : res->childResources) { error |= !addResourcesToTable(table, config, diag, &child); } return !error; } bool ResourceParser::parseResources(XmlPullParser* parser) { std::set strippedResources; bool error = false; std::u16string comment; const size_t depth = parser->getDepth(); while (XmlPullParser::nextChildNode(parser, depth)) { const XmlPullParser::Event event = parser->getEvent(); if (event == XmlPullParser::Event::kComment) { comment = parser->getComment(); continue; } if (event == 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 == 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; } if (elementName == u"item") { // Items simply have their type encoded in the type attribute. if (Maybe maybeType = findNonEmptyAttribute(parser, u"type")) { elementName = maybeType.value().toString(); } else { mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber())) << " must have a 'type' attribute"); error = true; continue; } } ParsedResource parsedResource; parsedResource.source = mSource.withLine(parser->getLineNumber()); parsedResource.comment = std::move(comment); if (Maybe maybeName = findNonEmptyAttribute(parser, u"name")) { parsedResource.name.entry = maybeName.value().toString(); } else if (elementName != u"public-group") { mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber())) << "<" << elementName << "> tag must have a 'name' attribute"); error = true; continue; } // Check if we should skip this product. const bool stripResource = shouldStripResource(parser, mOptions.product); bool result = true; if (elementName == u"id") { parsedResource.name.type = ResourceType::kId; parsedResource.value = util::make_unique(); } else if (elementName == u"string") { parsedResource.name.type = ResourceType::kString; result = parseString(parser, &parsedResource); } else if (elementName == u"color") { parsedResource.name.type = ResourceType::kColor; result = parseColor(parser, &parsedResource); } else if (elementName == u"drawable") { parsedResource.name.type = ResourceType::kDrawable; result = parseColor(parser, &parsedResource); } else if (elementName == u"bool") { parsedResource.name.type = ResourceType::kBool; result = parsePrimitive(parser, &parsedResource); } else if (elementName == u"integer") { parsedResource.name.type = ResourceType::kInteger; result = parsePrimitive(parser, &parsedResource); } else if (elementName == u"dimen") { parsedResource.name.type = ResourceType::kDimen; result = parsePrimitive(parser, &parsedResource); } else if (elementName == u"fraction") { parsedResource.name.type = ResourceType::kFraction; result = parsePrimitive(parser, &parsedResource); } else if (elementName == u"style") { parsedResource.name.type = ResourceType::kStyle; result = parseStyle(parser, &parsedResource); } else if (elementName == u"plurals") { parsedResource.name.type = ResourceType::kPlurals; result = parsePlural(parser, &parsedResource); } else if (elementName == u"array") { parsedResource.name.type = ResourceType::kArray; result = parseArray(parser, &parsedResource, android::ResTable_map::TYPE_ANY); } else if (elementName == u"string-array") { parsedResource.name.type = ResourceType::kArray; result = parseArray(parser, &parsedResource, android::ResTable_map::TYPE_STRING); } else if (elementName == u"integer-array") { parsedResource.name.type = ResourceType::kArray; result = parseArray(parser, &parsedResource, android::ResTable_map::TYPE_INTEGER); } else if (elementName == u"declare-styleable") { parsedResource.name.type = ResourceType::kStyleable; result = parseDeclareStyleable(parser, &parsedResource); } else if (elementName == u"attr") { parsedResource.name.type = ResourceType::kAttr; result = parseAttr(parser, &parsedResource); } else if (elementName == u"public") { result = parsePublic(parser, &parsedResource); } else if (elementName == u"java-symbol" || elementName == u"symbol") { result = parseSymbol(parser, &parsedResource); } else if (elementName == u"public-group") { result = parsePublicGroup(parser, &parsedResource); } else { // Try parsing the elementName (or type) as a resource. These shall only be // resources like 'layout' or 'xml' and they can only be references. if (const ResourceType* type = parseResourceType(elementName)) { parsedResource.name.type = *type; parsedResource.value = parseXml(parser, android::ResTable_map::TYPE_REFERENCE, false); if (!parsedResource.value) { mDiag->error(DiagMessage(parsedResource.source) << "invalid value for type '" << *type << "'. Expected a reference"); result = false; } } else { mDiag->warn(DiagMessage(mSource.withLine(parser->getLineNumber())) << "unknown resource type '" << elementName << "'"); } } if (result) { // We successfully parsed the resource. if (stripResource) { // Record that we stripped out this resource name. // We will check that at least one variant of this resource was included. strippedResources.insert(parsedResource.name); } else { error |= !addResourcesToTable(mTable, mConfig, mDiag, &parsedResource); } } else { 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; } enum { kAllowRawString = true, kNoRawString = false }; /** * 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(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())) { if (Maybe transformedName = parser->transformPackage(ref->name.value(), u"")) { ref->name = std::move(transformedName); } } 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(XmlPullParser* parser, ParsedResource* outResource) { const Source source = mSource.withLine(parser->getLineNumber()); bool formatted = true; if (Maybe formattedAttr = findAttribute(parser, u"formatted")) { if (!ResourceUtils::tryParseBool(formattedAttr.value(), &formatted)) { mDiag->error(DiagMessage(source) << "invalid value for 'formatted'. Must be a boolean"); return false; } } bool translateable = mOptions.translatable; if (Maybe translateableAttr = findAttribute(parser, u"translatable")) { if (!ResourceUtils::tryParseBool(translateableAttr.value(), &translateable)) { mDiag->error(DiagMessage(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(source) << "not a valid string"); return false; } if (formatted && translateable) { if (String* stringValue = valueCast(outResource->value.get())) { if (!util::verifyJavaStringFormat(*stringValue->value)) { mDiag->error(DiagMessage(source) << "multiple substitutions specified in non-positional format; " "did you mean to add the formatted=\"false\" attribute?"); return false; } } } return true; } bool ResourceParser::parseColor(XmlPullParser* parser, ParsedResource* outResource) { const Source source = mSource.withLine(parser->getLineNumber()); outResource->value = parseXml(parser, android::ResTable_map::TYPE_COLOR, kNoRawString); if (!outResource->value) { mDiag->error(DiagMessage(source) << "invalid color"); return false; } return true; } bool ResourceParser::parsePrimitive(XmlPullParser* parser, ParsedResource* outResource) { const Source source = mSource.withLine(parser->getLineNumber()); uint32_t typeMask = 0; switch (outResource->name.type) { case ResourceType::kInteger: typeMask |= android::ResTable_map::TYPE_INTEGER; break; case ResourceType::kFraction: // fallthrough case ResourceType::kDimen: typeMask |= android::ResTable_map::TYPE_DIMENSION | android::ResTable_map::TYPE_FLOAT | android::ResTable_map::TYPE_FRACTION; break; case ResourceType::kBool: typeMask |= android::ResTable_map::TYPE_BOOLEAN; break; default: assert(false); break; } outResource->value = parseXml(parser, typeMask, kNoRawString); if (!outResource->value) { mDiag->error(DiagMessage(source) << "invalid " << outResource->name.type); return false; } return true; } bool ResourceParser::parsePublic(XmlPullParser* parser, ParsedResource* outResource) { const Source source = mSource.withLine(parser->getLineNumber()); Maybe maybeType = findNonEmptyAttribute(parser, u"type"); if (!maybeType) { mDiag->error(DiagMessage(source) << " must have a 'type' attribute"); return false; } const ResourceType* parsedType = parseResourceType(maybeType.value()); if (!parsedType) { mDiag->error(DiagMessage(source) << "invalid resource type '" << maybeType.value() << "' in "); return false; } outResource->name.type = *parsedType; if (Maybe maybeId = 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(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(XmlPullParser* parser, ParsedResource* outResource) { const Source source = mSource.withLine(parser->getLineNumber()); Maybe maybeType = findNonEmptyAttribute(parser, u"type"); if (!maybeType) { mDiag->error(DiagMessage(source) << " must have a 'type' attribute"); return false; } const ResourceType* parsedType = parseResourceType(maybeType.value()); if (!parsedType) { mDiag->error(DiagMessage(source) << "invalid resource type '" << maybeType.value() << "' in "); return false; } Maybe maybeId = findNonEmptyAttribute(parser, u"first-id"); if (!maybeId) { mDiag->error(DiagMessage(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(source) << "invalid resource ID '" << maybeId.value() << "' in "); return false; } std::u16string comment; bool error = false; const size_t depth = parser->getDepth(); while (XmlPullParser::nextChildNode(parser, depth)) { if (parser->getEvent() == XmlPullParser::Event::kComment) { comment = util::trimWhitespace(parser->getComment()).toString(); continue; } else if (parser->getEvent() != 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 = findNonEmptyAttribute(parser, u"name"); if (!maybeName) { mDiag->error(DiagMessage(itemSource) << " must have a 'name' attribute"); error = true; continue; } if (findNonEmptyAttribute(parser, u"id")) { mDiag->error(DiagMessage(itemSource) << "'id' is ignored within "); error = true; continue; } if (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::parseSymbol(XmlPullParser* parser, ParsedResource* outResource) { const Source source = mSource.withLine(parser->getLineNumber()); Maybe maybeType = findNonEmptyAttribute(parser, u"type"); if (!maybeType) { mDiag->error(DiagMessage(source) << "<" << parser->getElementName() << "> must have a " "'type' attribute"); return false; } const ResourceType* parsedType = parseResourceType(maybeType.value()); if (!parsedType) { mDiag->error(DiagMessage(source) << "invalid resource type '" << maybeType.value() << "' in <" << parser->getElementName() << ">"); return false; } outResource->name.type = *parsedType; outResource->symbolState = SymbolState::kPrivate; return true; } 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; } bool ResourceParser::parseAttr(XmlPullParser* parser, ParsedResource* outResource) { outResource->source = mSource.withLine(parser->getLineNumber()); return parseAttrImpl(parser, outResource, false); } bool ResourceParser::parseAttrImpl(XmlPullParser* parser, ParsedResource* outResource, bool weak) { uint32_t typeMask = 0; Maybe maybeFormat = 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; } } // If this is a declaration, the package name may be in the name. Separate these out. // Eg. // No format attribute is allowed. if (weak && !maybeFormat) { StringPiece16 package, type, name; ResourceUtils::extractResourceName(outResource->name.entry, &package, &type, &name); if (type.empty() && !package.empty()) { outResource->name.package = package.toString(); outResource->name.entry = name.toString(); } } 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 (XmlPullParser::nextChildNode(parser, depth)) { if (parser->getEvent() == XmlPullParser::Event::kComment) { comment = util::trimWhitespace(parser->getComment()).toString(); continue; } else if (parser->getEvent() != 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); outResource->value = std::move(attr); return true; } Maybe ResourceParser::parseEnumOrFlagItem(XmlPullParser* parser, const StringPiece16& tag) { const Source source = mSource.withLine(parser->getLineNumber()); Maybe maybeName = findNonEmptyAttribute(parser, u"name"); if (!maybeName) { mDiag->error(DiagMessage(source) << "no attribute 'name' found for tag <" << tag << ">"); return {}; } Maybe maybeValue = 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(ResourceName({}, ResourceType::kId, maybeName.value().toString())), val.data }; } static Maybe parseXmlAttributeName(StringPiece16 str) { str = util::trimWhitespace(str); const char16_t* const start = str.data(); const char16_t* const end = start + str.size(); const char16_t* p = start; StringPiece16 package; StringPiece16 name; while (p != end) { if (*p == u':') { package = StringPiece16(start, p - start); name = StringPiece16(p + 1, end - (p + 1)); break; } p++; } return ResourceName(package.toString(), ResourceType::kAttr, name.empty() ? str.toString() : name.toString()); } bool ResourceParser::parseStyleItem(XmlPullParser* parser, Style* style) { const Source source = mSource.withLine(parser->getLineNumber()); Maybe maybeName = 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; } if (Maybe transformedName = parser->transformPackage(maybeKey.value(), u"")) { maybeKey = std::move(transformedName); } 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{ Reference(maybeKey.value()), std::move(value) }); return true; } bool ResourceParser::parseStyle(XmlPullParser* parser, ParsedResource* outResource) { const Source source = mSource.withLine(parser->getLineNumber()); std::unique_ptr