android_frameworks_base/tools/aapt2/ResourceParser.cpp
Adam Lesinski e4bb9eb5af AAPT2: Introduce notion of 'product' to ResourceTable
This allows us to preserve the various product definitions during the compile
phase, and allows us to select the product in the link phase.

This allows compiled files to remain product-independent, so that they do not need
to be recompiled when switching targets.

Bug:25958912
Change-Id: Iaa7eed25c834b67a39cdc9be43613e8b5ab6cdd7
2016-02-12 22:21:48 -08:00

1255 lines
48 KiB
C++

/*
* 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 <functional>
#include <sstream>
namespace aapt {
constexpr const char16_t* sXliffNamespaceUri = u"urn:oasis:names:tc:xliff:document:1.2";
/**
* Returns true if the element is <skip> or <eat-comment> 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> symbolState;
std::u16string comment;
std::unique_ptr<Value> value;
std::list<ParsedResource> childResources;
};
// Recursively adds resources to the ResourceTable.
static bool addResourcesToTable(ResourceTable* table, IDiagnostics* diag, ParsedResource* res) {
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<Span> 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<uint32_t>::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<uint32_t>(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 <resources>");
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<ResourceName> 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<StringPiece16> 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<bool(ResourceParser*, xml::XmlPullParser*, ParsedResource*)>;
static const auto elToItemMap = ImmutableMap<std::u16string, ItemTypeFormat>::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<std::u16string, BagParseFunc>::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<StringPiece16> maybeType = xml::findNonEmptyAttribute(parser, u"type")) {
resourceType = maybeType.value().toString();
} else {
mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
<< "<item> must have a 'type' attribute");
return false;
}
if (Maybe<StringPiece16> 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<StringPiece16> 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<Id>();
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 <public-group>).
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<Item> 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<StyledString>(
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> id = util::make_unique<Id>();
id->setSource(mSource.withLine(beginXmlLine));
mTable->addResource(name, {}, {}, std::move(id), mDiag);
};
// Process the raw value.
std::unique_ptr<Item> processedItem = ResourceUtils::parseItemForAttribute(rawValue, typeMask,
onCreateReference);
if (processedItem) {
// Fix up the reference.
if (Reference* ref = valueCast<Reference>(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<String>(
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<RawString>(
mTable->stringPool.makeRef(rawValue, StringPool::Context{ 1, mConfig }));
}
return {};
}
bool ResourceParser::parseString(xml::XmlPullParser* parser, ParsedResource* outResource) {
bool formatted = true;
if (Maybe<StringPiece16> 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<StringPiece16> 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<String>(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<StyledString>(outResource->value.get())) {
stringValue->setTranslateable(translateable);
}
return true;
}
bool ResourceParser::parsePublic(xml::XmlPullParser* parser, ParsedResource* outResource) {
Maybe<StringPiece16> maybeType = xml::findNonEmptyAttribute(parser, u"type");
if (!maybeType) {
mDiag->error(DiagMessage(outResource->source) << "<public> 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 <public>");
return false;
}
outResource->name.type = *parsedType;
if (Maybe<StringPiece16> 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 <public>");
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<Id>();
}
outResource->symbolState = SymbolState::kPublic;
return true;
}
bool ResourceParser::parsePublicGroup(xml::XmlPullParser* parser, ParsedResource* outResource) {
Maybe<StringPiece16> maybeType = xml::findNonEmptyAttribute(parser, u"type");
if (!maybeType) {
mDiag->error(DiagMessage(outResource->source)
<< "<public-group> 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 <public-group>");
return false;
}
Maybe<StringPiece16> maybeId = xml::findNonEmptyAttribute(parser, u"first-id");
if (!maybeId) {
mDiag->error(DiagMessage(outResource->source)
<< "<public-group> 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 <public-group>");
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<StringPiece16> maybeName = xml::findNonEmptyAttribute(parser, u"name");
if (!maybeName) {
mDiag->error(DiagMessage(itemSource) << "<public> must have a 'name' attribute");
error = true;
continue;
}
if (xml::findNonEmptyAttribute(parser, u"id")) {
mDiag->error(DiagMessage(itemSource) << "'id' is ignored within <public-group>");
error = true;
continue;
}
if (xml::findNonEmptyAttribute(parser, u"type")) {
mDiag->error(DiagMessage(itemSource) << "'type' is ignored within <public-group>");
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<StringPiece16> 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<StringPiece16> 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<int32_t> maybeMin, maybeMax;
if (Maybe<StringPiece16> 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<int32_t>(value.data);
}
}
if (!maybeMin) {
mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
<< "invalid 'min' value '" << minStr << "'");
return false;
}
}
if (Maybe<StringPiece16> 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<int32_t>(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<Attribute::Symbol, SymbolComparator> 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 <enum>; already defined a <flag>");
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 <flag>; already defined an <enum>");
error = true;
continue;
}
typeMask |= android::ResTable_map::TYPE_FLAGS;
}
if (Maybe<Attribute::Symbol> 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<Id>();
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<Attribute> attr = util::make_unique<Attribute>(weak);
attr->symbols = std::vector<Attribute::Symbol>(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<Attribute::Symbol> ResourceParser::parseEnumOrFlagItem(xml::XmlPullParser* parser,
const StringPiece16& tag) {
const Source source = mSource.withLine(parser->getLineNumber());
Maybe<StringPiece16> maybeName = xml::findNonEmptyAttribute(parser, u"name");
if (!maybeName) {
mDiag->error(DiagMessage(source) << "no attribute 'name' found for tag <" << tag << ">");
return {};
}
Maybe<StringPiece16> 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<Reference> 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<Reference>(std::move(ref));
}
bool ResourceParser::parseStyleItem(xml::XmlPullParser* parser, Style* style) {
const Source source = mSource.withLine(parser->getLineNumber());
Maybe<StringPiece16> maybeName = xml::findNonEmptyAttribute(parser, u"name");
if (!maybeName) {
mDiag->error(DiagMessage(source) << "<item> must have a 'name' attribute");
return false;
}
Maybe<Reference> 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<Item> 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<Style> style = util::make_unique<Style>();
Maybe<StringPiece16> maybeParent = xml::findAttribute(parser, u"parent");
if (maybeParent) {
// If the parent is empty, we don't have a parent, but we also don't infer either.
if (!maybeParent.value().empty()) {
std::string errStr;
style->parent = ResourceUtils::parseStyleParentReference(maybeParent.value(), &errStr);
if (!style->parent) {
mDiag->error(DiagMessage(outResource->source) << errStr);
return false;
}
// Transform the namespace prefix to the actual package name, and mark the reference as
// private if appropriate.
transformReferenceFromNamespace(parser, u"", &style->parent.value());
}
} else {
// No parent was specified, so try inferring it from the style name.
std::u16string styleName = outResource->name.entry;
size_t pos = styleName.find_last_of(u'.');
if (pos != std::string::npos) {
style->parentInferred = true;
style->parent = Reference(ResourceName({}, ResourceType::kStyle,
styleName.substr(0, pos)));
}
}
bool error = false;
const size_t depth = parser->getDepth();
while (xml::XmlPullParser::nextChildNode(parser, depth)) {
if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) {
// Skip text and comments.
continue;
}
const std::u16string& elementNamespace = parser->getElementNamespace();
const std::u16string& elementName = parser->getElementName();
if (elementNamespace == u"" && elementName == u"item") {
error |= !parseStyleItem(parser, style.get());
} else if (!shouldIgnoreElement(elementNamespace, elementName)) {
mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
<< ":" << elementName << ">");
error = true;
}
}
if (error) {
return false;
}
outResource->value = std::move(style);
return true;
}
bool ResourceParser::parseArray(xml::XmlPullParser* parser, ParsedResource* outResource) {
return parseArrayImpl(parser, outResource, android::ResTable_map::TYPE_ANY);
}
bool ResourceParser::parseIntegerArray(xml::XmlPullParser* parser, ParsedResource* outResource) {
return parseArrayImpl(parser, outResource, android::ResTable_map::TYPE_INTEGER);
}
bool ResourceParser::parseStringArray(xml::XmlPullParser* parser, ParsedResource* outResource) {
return parseArrayImpl(parser, outResource, android::ResTable_map::TYPE_STRING);
}
bool ResourceParser::parseArrayImpl(xml::XmlPullParser* parser, ParsedResource* outResource,
const uint32_t typeMask) {
outResource->name.type = ResourceType::kArray;
std::unique_ptr<Array> array = util::make_unique<Array>();
bool error = false;
const size_t depth = parser->getDepth();
while (xml::XmlPullParser::nextChildNode(parser, depth)) {
if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) {
// Skip text and comments.
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"item") {
std::unique_ptr<Item> item = parseXml(parser, typeMask, kNoRawString);
if (!item) {
mDiag->error(DiagMessage(itemSource) << "could not parse array item");
error = true;
continue;
}
item->setSource(itemSource);
array->items.emplace_back(std::move(item));
} else if (!shouldIgnoreElement(elementNamespace, elementName)) {
mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
<< "unknown tag <" << elementNamespace << ":" << elementName << ">");
error = true;
}
}
if (error) {
return false;
}
outResource->value = std::move(array);
return true;
}
bool ResourceParser::parsePlural(xml::XmlPullParser* parser, ParsedResource* outResource) {
outResource->name.type = ResourceType::kPlurals;
std::unique_ptr<Plural> plural = util::make_unique<Plural>();
bool error = false;
const size_t depth = parser->getDepth();
while (xml::XmlPullParser::nextChildNode(parser, depth)) {
if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) {
// Skip text and comments.
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"item") {
Maybe<StringPiece16> maybeQuantity = xml::findNonEmptyAttribute(parser, u"quantity");
if (!maybeQuantity) {
mDiag->error(DiagMessage(itemSource) << "<item> in <plurals> requires attribute "
<< "'quantity'");
error = true;
continue;
}
StringPiece16 trimmedQuantity = util::trimWhitespace(maybeQuantity.value());
size_t index = 0;
if (trimmedQuantity == u"zero") {
index = Plural::Zero;
} else if (trimmedQuantity == u"one") {
index = Plural::One;
} else if (trimmedQuantity == u"two") {
index = Plural::Two;
} else if (trimmedQuantity == u"few") {
index = Plural::Few;
} else if (trimmedQuantity == u"many") {
index = Plural::Many;
} else if (trimmedQuantity == u"other") {
index = Plural::Other;
} else {
mDiag->error(DiagMessage(itemSource)
<< "<item> in <plural> has invalid value '" << trimmedQuantity
<< "' for attribute 'quantity'");
error = true;
continue;
}
if (plural->values[index]) {
mDiag->error(DiagMessage(itemSource)
<< "duplicate quantity '" << trimmedQuantity << "'");
error = true;
continue;
}
if (!(plural->values[index] = parseXml(parser, android::ResTable_map::TYPE_STRING,
kNoRawString))) {
error = true;
}
plural->values[index]->setSource(itemSource);
} else if (!shouldIgnoreElement(elementNamespace, elementName)) {
mDiag->error(DiagMessage(itemSource) << "unknown tag <" << elementNamespace << ":"
<< elementName << ">");
error = true;
}
}
if (error) {
return false;
}
outResource->value = std::move(plural);
return true;
}
bool ResourceParser::parseDeclareStyleable(xml::XmlPullParser* parser,
ParsedResource* outResource) {
outResource->name.type = ResourceType::kStyleable;
// Declare-styleable is kPrivate by default, because it technically only exists in R.java.
outResource->symbolState = SymbolState::kPublic;
// Declare-styleable only ends up in default config;
if (outResource->config != ConfigDescription::defaultConfig()) {
mDiag->warn(DiagMessage(outResource->source) << "ignoring configuration '"
<< outResource->config << "' for styleable "
<< outResource->name.entry);
outResource->config = ConfigDescription::defaultConfig();
}
std::unique_ptr<Styleable> styleable = util::make_unique<Styleable>();
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) {
// Ignore 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"attr") {
Maybe<StringPiece16> maybeName = xml::findNonEmptyAttribute(parser, u"name");
if (!maybeName) {
mDiag->error(DiagMessage(itemSource) << "<attr> tag must have a 'name' attribute");
error = true;
continue;
}
// If this is a declaration, the package name may be in the name. Separate these out.
// Eg. <attr name="android:text" />
Maybe<Reference> maybeRef = parseXmlAttributeName(maybeName.value());
if (!maybeRef) {
mDiag->error(DiagMessage(itemSource) << "<attr> tag has invalid name '"
<< maybeName.value() << "'");
error = true;
continue;
}
Reference& childRef = maybeRef.value();
xml::transformReferenceFromNamespace(parser, u"", &childRef);
// Create the ParsedResource that will add the attribute to the table.
ParsedResource childResource;
childResource.name = childRef.name.value();
childResource.source = itemSource;
childResource.comment = std::move(comment);
if (!parseAttrImpl(parser, &childResource, true)) {
error = true;
continue;
}
// Create the reference to this attribute.
childRef.setComment(childResource.comment);
childRef.setSource(itemSource);
styleable->entries.push_back(std::move(childRef));
outResource->childResources.push_back(std::move(childResource));
} else if (!shouldIgnoreElement(elementNamespace, elementName)) {
mDiag->error(DiagMessage(itemSource) << "unknown tag <" << elementNamespace << ":"
<< elementName << ">");
error = true;
}
comment = {};
}
if (error) {
return false;
}
outResource->value = std::move(styleable);
return true;
}
} // namespace aapt