blob: 4c961872572062ac0075a10e78fe1f2ad862e39d [file] [log] [blame]
/*
* 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 "Logger.h"
#include "ResourceParser.h"
#include "ResourceValues.h"
#include "ScopedXmlPullParser.h"
#include "SourceXmlPullParser.h"
#include "Util.h"
#include "XliffXmlPullParser.h"
namespace aapt {
void ResourceParser::extractResourceName(const StringPiece16& str, StringPiece16* outPackage,
StringPiece16* outType, StringPiece16* outEntry) {
const char16_t* start = str.data();
const char16_t* end = start + str.size();
const char16_t* current = start;
while (current != end) {
if (outType->size() == 0 && *current == u'/') {
outType->assign(start, current - start);
start = current + 1;
} else if (outPackage->size() == 0 && *current == u':') {
outPackage->assign(start, current - start);
start = current + 1;
}
current++;
}
outEntry->assign(start, end - start);
}
bool ResourceParser::tryParseReference(const StringPiece16& str, ResourceNameRef* outRef,
bool* outCreate, bool* outPrivate) {
StringPiece16 trimmedStr(util::trimWhitespace(str));
if (trimmedStr.empty()) {
return false;
}
if (trimmedStr.data()[0] == u'@') {
size_t offset = 1;
*outCreate = false;
if (trimmedStr.data()[1] == u'+') {
*outCreate = true;
offset += 1;
} else if (trimmedStr.data()[1] == u'*') {
*outPrivate = true;
offset += 1;
}
StringPiece16 package;
StringPiece16 type;
StringPiece16 entry;
extractResourceName(trimmedStr.substr(offset, trimmedStr.size() - offset),
&package, &type, &entry);
const ResourceType* parsedType = parseResourceType(type);
if (!parsedType) {
return false;
}
if (*outCreate && *parsedType != ResourceType::kId) {
return false;
}
outRef->package = package;
outRef->type = *parsedType;
outRef->entry = entry;
return true;
}
return false;
}
bool ResourceParser::tryParseAttributeReference(const StringPiece16& str,
ResourceNameRef* outRef) {
StringPiece16 trimmedStr(util::trimWhitespace(str));
if (trimmedStr.empty()) {
return false;
}
if (*trimmedStr.data() == u'?') {
StringPiece16 package;
StringPiece16 type;
StringPiece16 entry;
extractResourceName(trimmedStr.substr(1, trimmedStr.size() - 1), &package, &type, &entry);
if (!type.empty() && type != u"attr") {
return false;
}
outRef->package = package;
outRef->type = ResourceType::kAttr;
outRef->entry = entry;
return true;
}
return false;
}
std::unique_ptr<Reference> ResourceParser::tryParseReference(const StringPiece16& str,
const StringPiece16& defaultPackage,
bool* outCreate) {
ResourceNameRef ref;
bool privateRef = false;
if (tryParseReference(str, &ref, outCreate, &privateRef)) {
if (ref.package.empty()) {
ref.package = defaultPackage;
}
std::unique_ptr<Reference> value = util::make_unique<Reference>(ref);
value->privateReference = privateRef;
return value;
}
if (tryParseAttributeReference(str, &ref)) {
if (ref.package.empty()) {
ref.package = defaultPackage;
}
*outCreate = false;
return util::make_unique<Reference>(ref, Reference::Type::kAttribute);
}
return {};
}
std::unique_ptr<BinaryPrimitive> ResourceParser::tryParseNullOrEmpty(const StringPiece16& str) {
StringPiece16 trimmedStr(util::trimWhitespace(str));
uint32_t data = 0;
if (trimmedStr == u"@null") {
data = android::Res_value::DATA_NULL_UNDEFINED;
} else if (trimmedStr == u"@empty") {
data = android::Res_value::DATA_NULL_EMPTY;
} else {
return {};
}
android::Res_value value = {};
value.dataType = android::Res_value::TYPE_NULL;
value.data = data;
return util::make_unique<BinaryPrimitive>(value);
}
std::unique_ptr<BinaryPrimitive> ResourceParser::tryParseEnumSymbol(const Attribute& enumAttr,
const StringPiece16& str) {
StringPiece16 trimmedStr(util::trimWhitespace(str));
for (const auto& entry : enumAttr.symbols) {
// Enum symbols are stored as @package:id/symbol resources,
// so we need to match against the 'entry' part of the identifier.
const ResourceName& enumSymbolResourceName = entry.symbol.name;
if (trimmedStr == enumSymbolResourceName.entry) {
android::Res_value value = {};
value.dataType = android::Res_value::TYPE_INT_DEC;
value.data = entry.value;
return util::make_unique<BinaryPrimitive>(value);
}
}
return {};
}
std::unique_ptr<BinaryPrimitive> ResourceParser::tryParseFlagSymbol(const Attribute& flagAttr,
const StringPiece16& str) {
android::Res_value flags = {};
flags.dataType = android::Res_value::TYPE_INT_DEC;
for (StringPiece16 part : util::tokenize(str, u'|')) {
StringPiece16 trimmedPart = util::trimWhitespace(part);
bool flagSet = false;
for (const auto& entry : flagAttr.symbols) {
// Flag symbols are stored as @package:id/symbol resources,
// so we need to match against the 'entry' part of the identifier.
const ResourceName& flagSymbolResourceName = entry.symbol.name;
if (trimmedPart == flagSymbolResourceName.entry) {
flags.data |= entry.value;
flagSet = true;
break;
}
}
if (!flagSet) {
return {};
}
}
return util::make_unique<BinaryPrimitive>(flags);
}
static uint32_t parseHex(char16_t c, bool* outError) {
if (c >= u'0' && c <= u'9') {
return c - u'0';
} else if (c >= u'a' && c <= u'f') {
return c - u'a' + 0xa;
} else if (c >= u'A' && c <= u'F') {
return c - u'A' + 0xa;
} else {
*outError = true;
return 0xffffffffu;
}
}
std::unique_ptr<BinaryPrimitive> ResourceParser::tryParseColor(const StringPiece16& str) {
StringPiece16 colorStr(util::trimWhitespace(str));
const char16_t* start = colorStr.data();
const size_t len = colorStr.size();
if (len == 0 || start[0] != u'#') {
return {};
}
android::Res_value value = {};
bool error = false;
if (len == 4) {
value.dataType = android::Res_value::TYPE_INT_COLOR_RGB4;
value.data = 0xff000000u;
value.data |= parseHex(start[1], &error) << 20;
value.data |= parseHex(start[1], &error) << 16;
value.data |= parseHex(start[2], &error) << 12;
value.data |= parseHex(start[2], &error) << 8;
value.data |= parseHex(start[3], &error) << 4;
value.data |= parseHex(start[3], &error);
} else if (len == 5) {
value.dataType = android::Res_value::TYPE_INT_COLOR_ARGB4;
value.data |= parseHex(start[1], &error) << 28;
value.data |= parseHex(start[1], &error) << 24;
value.data |= parseHex(start[2], &error) << 20;
value.data |= parseHex(start[2], &error) << 16;
value.data |= parseHex(start[3], &error) << 12;
value.data |= parseHex(start[3], &error) << 8;
value.data |= parseHex(start[4], &error) << 4;
value.data |= parseHex(start[4], &error);
} else if (len == 7) {
value.dataType = android::Res_value::TYPE_INT_COLOR_RGB8;
value.data = 0xff000000u;
value.data |= parseHex(start[1], &error) << 20;
value.data |= parseHex(start[2], &error) << 16;
value.data |= parseHex(start[3], &error) << 12;
value.data |= parseHex(start[4], &error) << 8;
value.data |= parseHex(start[5], &error) << 4;
value.data |= parseHex(start[6], &error);
} else if (len == 9) {
value.dataType = android::Res_value::TYPE_INT_COLOR_ARGB8;
value.data |= parseHex(start[1], &error) << 28;
value.data |= parseHex(start[2], &error) << 24;
value.data |= parseHex(start[3], &error) << 20;
value.data |= parseHex(start[4], &error) << 16;
value.data |= parseHex(start[5], &error) << 12;
value.data |= parseHex(start[6], &error) << 8;
value.data |= parseHex(start[7], &error) << 4;
value.data |= parseHex(start[8], &error);
} else {
return {};
}
return error ? std::unique_ptr<BinaryPrimitive>() : util::make_unique<BinaryPrimitive>(value);
}
std::unique_ptr<BinaryPrimitive> ResourceParser::tryParseBool(const StringPiece16& str) {
StringPiece16 trimmedStr(util::trimWhitespace(str));
uint32_t data = 0;
if (trimmedStr == u"true" || trimmedStr == u"TRUE") {
data = 1;
} else if (trimmedStr != u"false" && trimmedStr != u"FALSE") {
return {};
}
android::Res_value value = {};
value.dataType = android::Res_value::TYPE_INT_BOOLEAN;
value.data = data;
return util::make_unique<BinaryPrimitive>(value);
}
std::unique_ptr<BinaryPrimitive> ResourceParser::tryParseInt(const StringPiece16& str) {
android::Res_value value;
if (!android::ResTable::stringToInt(str.data(), str.size(), &value)) {
return {};
}
return util::make_unique<BinaryPrimitive>(value);
}
std::unique_ptr<BinaryPrimitive> ResourceParser::tryParseFloat(const StringPiece16& str) {
android::Res_value value;
if (!android::ResTable::stringToFloat(str.data(), str.size(), &value)) {
return {};
}
return util::make_unique<BinaryPrimitive>(value);
}
uint32_t ResourceParser::androidTypeToAttributeTypeMask(uint16_t type) {
switch (type) {
case android::Res_value::TYPE_NULL:
case android::Res_value::TYPE_REFERENCE:
case android::Res_value::TYPE_ATTRIBUTE:
case android::Res_value::TYPE_DYNAMIC_REFERENCE:
return android::ResTable_map::TYPE_REFERENCE;
case android::Res_value::TYPE_STRING:
return android::ResTable_map::TYPE_STRING;
case android::Res_value::TYPE_FLOAT:
return android::ResTable_map::TYPE_FLOAT;
case android::Res_value::TYPE_DIMENSION:
return android::ResTable_map::TYPE_DIMENSION;
case android::Res_value::TYPE_FRACTION:
return android::ResTable_map::TYPE_FRACTION;
case android::Res_value::TYPE_INT_DEC:
case android::Res_value::TYPE_INT_HEX:
return android::ResTable_map::TYPE_INTEGER |
android::ResTable_map::TYPE_ENUM |
android::ResTable_map::TYPE_FLAGS;
case android::Res_value::TYPE_INT_BOOLEAN:
return android::ResTable_map::TYPE_BOOLEAN;
case android::Res_value::TYPE_INT_COLOR_ARGB8:
case android::Res_value::TYPE_INT_COLOR_RGB8:
case android::Res_value::TYPE_INT_COLOR_ARGB4:
case android::Res_value::TYPE_INT_COLOR_RGB4:
return android::ResTable_map::TYPE_COLOR;
default:
return 0;
};
}
std::unique_ptr<Item> ResourceParser::parseItemForAttribute(
const StringPiece16& value, uint32_t typeMask, const StringPiece16& defaultPackage,
std::function<void(const ResourceName&)> onCreateReference) {
std::unique_ptr<BinaryPrimitive> nullOrEmpty = tryParseNullOrEmpty(value);
if (nullOrEmpty) {
return std::move(nullOrEmpty);
}
bool create = false;
std::unique_ptr<Reference> reference = tryParseReference(value, defaultPackage, &create);
if (reference) {
if (create && onCreateReference) {
onCreateReference(reference->name);
}
return std::move(reference);
}
if (typeMask & android::ResTable_map::TYPE_COLOR) {
// Try parsing this as a color.
std::unique_ptr<BinaryPrimitive> color = tryParseColor(value);
if (color) {
return std::move(color);
}
}
if (typeMask & android::ResTable_map::TYPE_BOOLEAN) {
// Try parsing this as a boolean.
std::unique_ptr<BinaryPrimitive> boolean = tryParseBool(value);
if (boolean) {
return std::move(boolean);
}
}
if (typeMask & android::ResTable_map::TYPE_INTEGER) {
// Try parsing this as an integer.
std::unique_ptr<BinaryPrimitive> integer = tryParseInt(value);
if (integer) {
return std::move(integer);
}
}
const uint32_t floatMask = android::ResTable_map::TYPE_FLOAT |
android::ResTable_map::TYPE_DIMENSION |
android::ResTable_map::TYPE_FRACTION;
if (typeMask & floatMask) {
// Try parsing this as a float.
std::unique_ptr<BinaryPrimitive> floatingPoint = tryParseFloat(value);
if (floatingPoint) {
if (typeMask & androidTypeToAttributeTypeMask(floatingPoint->value.dataType)) {
return std::move(floatingPoint);
}
}
}
return {};
}
/**
* We successively try to parse the string as a resource type that the Attribute
* allows.
*/
std::unique_ptr<Item> ResourceParser::parseItemForAttribute(
const StringPiece16& str, const Attribute& attr, const StringPiece16& defaultPackage,
std::function<void(const ResourceName&)> onCreateReference) {
const uint32_t typeMask = attr.typeMask;
std::unique_ptr<Item> value = parseItemForAttribute(str, typeMask, defaultPackage,
onCreateReference);
if (value) {
return value;
}
if (typeMask & android::ResTable_map::TYPE_ENUM) {
// Try parsing this as an enum.
std::unique_ptr<BinaryPrimitive> enumValue = tryParseEnumSymbol(attr, str);
if (enumValue) {
return std::move(enumValue);
}
}
if (typeMask & android::ResTable_map::TYPE_FLAGS) {
// Try parsing this as a flag.
std::unique_ptr<BinaryPrimitive> flagValue = tryParseFlagSymbol(attr, str);
if (flagValue) {
return std::move(flagValue);
}
}
return {};
}
ResourceParser::ResourceParser(const std::shared_ptr<ResourceTable>& table, const Source& source,
const ConfigDescription& config,
const std::shared_ptr<XmlPullParser>& parser) :
mTable(table), mSource(source), mConfig(config), mLogger(source),
mParser(std::make_shared<XliffXmlPullParser>(parser)) {
}
/**
* Build a string from XML that converts nested elements into Span objects.
*/
bool ResourceParser::flattenXmlSubtree(XmlPullParser* parser, std::u16string* outRawString,
StyleString* outStyleString) {
std::vector<Span> spanStack;
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) {
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) {
// TODO(adamlesinski): Verify format strings.
outRawString->append(parser->getText());
builder.append(parser->getText());
} else if (event == XmlPullParser::Event::kStartElement) {
if (parser->getElementNamespace().size() > 0) {
mLogger.warn(parser->getLineNumber())
<< "skipping element '"
<< parser->getElementName()
<< "' with unknown namespace '"
<< parser->getElementNamespace()
<< "'."
<< std::endl;
XmlPullParser::skipCurrentElement(parser);
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()) {
mLogger.error(parser->getLineNumber())
<< "style string '"
<< builder.str()
<< "' is too long."
<< std::endl;
return false;
}
spanStack.push_back(Span{ spanName, static_cast<uint32_t>(builder.str().size()) });
} else if (event == XmlPullParser::Event::kComment) {
// Skip
} else {
mLogger.warn(parser->getLineNumber())
<< "unknown event "
<< event
<< "."
<< std::endl;
}
}
assert(spanStack.empty() && "spans haven't been fully processed");
outStyleString->str = builder.str();
return true;
}
bool ResourceParser::parse() {
while (XmlPullParser::isGoodEvent(mParser->next())) {
if (mParser->getEvent() != XmlPullParser::Event::kStartElement) {
continue;
}
ScopedXmlPullParser parser(mParser.get());
if (!parser.getElementNamespace().empty() ||
parser.getElementName() != u"resources") {
mLogger.error(parser.getLineNumber())
<< "root element must be <resources> in the global namespace."
<< std::endl;
return false;
}
if (!parseResources(&parser)) {
return false;
}
}
if (mParser->getEvent() == XmlPullParser::Event::kBadDocument) {
mLogger.error(mParser->getLineNumber())
<< mParser->getLastError()
<< std::endl;
return false;
}
return true;
}
bool ResourceParser::parseResources(XmlPullParser* parser) {
bool success = true;
std::u16string comment;
while (XmlPullParser::isGoodEvent(parser->next())) {
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()) {
comment = u"";
}
continue;
}
if (event != XmlPullParser::Event::kStartElement) {
continue;
}
ScopedXmlPullParser childParser(parser);
if (!childParser.getElementNamespace().empty()) {
// Skip unknown namespace.
continue;
}
StringPiece16 name = childParser.getElementName();
if (name == u"skip" || name == u"eat-comment") {
continue;
}
if (name == u"private-symbols") {
// Handle differently.
mLogger.note(childParser.getLineNumber())
<< "got a <private-symbols> tag."
<< std::endl;
continue;
}
const auto endAttrIter = childParser.endAttributes();
auto attrIter = childParser.findAttribute(u"", u"name");
if (attrIter == endAttrIter || attrIter->value.empty()) {
mLogger.error(childParser.getLineNumber())
<< "<" << name << "> tag must have a 'name' attribute."
<< std::endl;
success = false;
continue;
}
// Copy because our iterator will go out of scope when
// we parse more XML.
std::u16string attributeName = attrIter->value;
if (name == u"item") {
// Items simply have their type encoded in the type attribute.
auto typeIter = childParser.findAttribute(u"", u"type");
if (typeIter == endAttrIter || typeIter->value.empty()) {
mLogger.error(childParser.getLineNumber())
<< "<item> must have a 'type' attribute."
<< std::endl;
success = false;
continue;
}
name = typeIter->value;
}
if (name == u"id") {
success &= mTable->addResource(ResourceNameRef{ {}, ResourceType::kId, attributeName },
{}, mSource.line(childParser.getLineNumber()),
util::make_unique<Id>());
} else if (name == u"string") {
success &= parseString(&childParser,
ResourceNameRef{ {}, ResourceType::kString, attributeName });
} else if (name == u"color") {
success &= parseColor(&childParser,
ResourceNameRef{ {}, ResourceType::kColor, attributeName });
} else if (name == u"drawable") {
success &= parseColor(&childParser,
ResourceNameRef{ {}, ResourceType::kDrawable, attributeName });
} else if (name == u"bool") {
success &= parsePrimitive(&childParser,
ResourceNameRef{ {}, ResourceType::kBool, attributeName });
} else if (name == u"integer") {
success &= parsePrimitive(
&childParser,
ResourceNameRef{ {}, ResourceType::kInteger, attributeName });
} else if (name == u"dimen") {
success &= parsePrimitive(&childParser,
ResourceNameRef{ {}, ResourceType::kDimen, attributeName });
} else if (name == u"fraction") {
// success &= parsePrimitive(
// &childParser,
// ResourceNameRef{ {}, ResourceType::kFraction, attributeName });
} else if (name == u"style") {
success &= parseStyle(&childParser,
ResourceNameRef{ {}, ResourceType::kStyle, attributeName });
} else if (name == u"plurals") {
success &= parsePlural(&childParser,
ResourceNameRef{ {}, ResourceType::kPlurals, attributeName });
} else if (name == u"array") {
success &= parseArray(&childParser,
ResourceNameRef{ {}, ResourceType::kArray, attributeName },
android::ResTable_map::TYPE_ANY);
} else if (name == u"string-array") {
success &= parseArray(&childParser,
ResourceNameRef{ {}, ResourceType::kArray, attributeName },
android::ResTable_map::TYPE_STRING);
} else if (name == u"integer-array") {
success &= parseArray(&childParser,
ResourceNameRef{ {}, ResourceType::kArray, attributeName },
android::ResTable_map::TYPE_INTEGER);
} else if (name == u"public") {
success &= parsePublic(&childParser, attributeName);
} else if (name == u"declare-styleable") {
success &= parseDeclareStyleable(
&childParser,
ResourceNameRef{ {}, ResourceType::kStyleable, attributeName });
} else if (name == u"attr") {
success &= parseAttr(&childParser,
ResourceNameRef{ {}, ResourceType::kAttr, attributeName });
} else if (name == u"bag") {
} else if (name == u"public-padding") {
} else if (name == u"java-symbol") {
} else if (name == u"add-resource") {
}
}
if (parser->getEvent() == XmlPullParser::Event::kBadDocument) {
mLogger.error(parser->getLineNumber())
<< parser->getLastError()
<< std::endl;
return false;
}
return success;
}
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<Item> ResourceParser::parseXml(XmlPullParser* parser, uint32_t typeMask,
bool allowRawValue) {
const size_t beginXmlLine = parser->getLineNumber();
std::u16string rawValue;
StyleString styleString;
if (!flattenXmlSubtree(parser, &rawValue, &styleString)) {
return {};
}
StringPool& pool = mTable->getValueStringPool();
if (!styleString.spans.empty()) {
// This can only be a StyledString.
return util::make_unique<StyledString>(
pool.makeRef(styleString, StringPool::Context{ 1, mConfig }));
}
auto onCreateReference = [&](const ResourceName& name) {
mTable->addResource(name, {}, mSource.line(beginXmlLine), util::make_unique<Id>());
};
// Process the raw value.
std::unique_ptr<Item> processedItem = parseItemForAttribute(rawValue, typeMask,
mTable->getPackage(),
onCreateReference);
if (processedItem) {
return processedItem;
}
// Try making a regular string.
if (typeMask & android::ResTable_map::TYPE_STRING) {
// Use the trimmed, escaped string.
return util::make_unique<String>(
pool.makeRef(styleString.str, StringPool::Context{ 1, mConfig }));
}
// We can't parse this so return a RawString if we are allowed.
if (allowRawValue) {
return util::make_unique<RawString>(
pool.makeRef(rawValue, StringPool::Context{ 1, mConfig }));
}
return {};
}
bool ResourceParser::parseString(XmlPullParser* parser, const ResourceNameRef& resourceName) {
const SourceLine source = mSource.line(parser->getLineNumber());
// Mark the string as untranslateable if needed.
const auto endAttrIter = parser->endAttributes();
auto attrIter = parser->findAttribute(u"", u"untranslateable");
// bool untranslateable = attrIter != endAttrIter;
// TODO(adamlesinski): Do something with this (mark the string).
// Deal with the product.
attrIter = parser->findAttribute(u"", u"product");
if (attrIter != endAttrIter) {
if (attrIter->value != u"default" && attrIter->value != u"phone") {
// TODO(adamlesinski): Match products.
return true;
}
}
std::unique_ptr<Item> processedItem = parseXml(parser, android::ResTable_map::TYPE_STRING,
kNoRawString);
if (!processedItem) {
mLogger.error(source.line)
<< "not a valid string."
<< std::endl;
return false;
}
return mTable->addResource(resourceName, mConfig, source, std::move(processedItem));
}
bool ResourceParser::parseColor(XmlPullParser* parser, const ResourceNameRef& resourceName) {
const SourceLine source = mSource.line(parser->getLineNumber());
std::unique_ptr<Item> item = parseXml(parser, android::ResTable_map::TYPE_COLOR, kNoRawString);
if (!item) {
mLogger.error(source.line) << "invalid color." << std::endl;
return false;
}
return mTable->addResource(resourceName, mConfig, source, std::move(item));
}
bool ResourceParser::parsePrimitive(XmlPullParser* parser, const ResourceNameRef& resourceName) {
const SourceLine source = mSource.line(parser->getLineNumber());
uint32_t typeMask = 0;
switch (resourceName.type) {
case ResourceType::kInteger:
typeMask |= android::ResTable_map::TYPE_INTEGER;
break;
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;
}
std::unique_ptr<Item> item = parseXml(parser, typeMask, kNoRawString);
if (!item) {
mLogger.error(source.line)
<< "invalid "
<< resourceName.type
<< "."
<< std::endl;
return false;
}
return mTable->addResource(resourceName, mConfig, source, std::move(item));
}
bool ResourceParser::parsePublic(XmlPullParser* parser, const StringPiece16& name) {
const SourceLine source = mSource.line(parser->getLineNumber());
const auto endAttrIter = parser->endAttributes();
const auto typeAttrIter = parser->findAttribute(u"", u"type");
if (typeAttrIter == endAttrIter || typeAttrIter->value.empty()) {
mLogger.error(source.line)
<< "<public> must have a 'type' attribute."
<< std::endl;
return false;
}
const ResourceType* parsedType = parseResourceType(typeAttrIter->value);
if (!parsedType) {
mLogger.error(source.line)
<< "invalid resource type '"
<< typeAttrIter->value
<< "' in <public>."
<< std::endl;
return false;
}
ResourceNameRef resourceName { {}, *parsedType, name };
ResourceId resourceId;
const auto idAttrIter = parser->findAttribute(u"", u"id");
if (idAttrIter != endAttrIter && !idAttrIter->value.empty()) {
android::Res_value val;
bool result = android::ResTable::stringToInt(idAttrIter->value.data(),
idAttrIter->value.size(), &val);
resourceId.id = val.data;
if (!result || !resourceId.isValid()) {
mLogger.error(source.line)
<< "invalid resource ID '"
<< idAttrIter->value
<< "' in <public>."
<< std::endl;
return false;
}
}
if (*parsedType == ResourceType::kId) {
// An ID marked as public is also the definition of an ID.
mTable->addResource(resourceName, {}, source, util::make_unique<Id>());
}
return mTable->markPublic(resourceName, resourceId, source);
}
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, const ResourceNameRef& resourceName) {
const SourceLine source = mSource.line(parser->getLineNumber());
std::unique_ptr<Attribute> attr = parseAttrImpl(parser, resourceName, false);
if (!attr) {
return false;
}
return mTable->addResource(resourceName, mConfig, source, std::move(attr));
}
std::unique_ptr<Attribute> ResourceParser::parseAttrImpl(XmlPullParser* parser,
const ResourceNameRef& resourceName,
bool weak) {
uint32_t typeMask = 0;
const auto endAttrIter = parser->endAttributes();
const auto formatAttrIter = parser->findAttribute(u"", u"format");
if (formatAttrIter != endAttrIter) {
typeMask = parseFormatAttribute(formatAttrIter->value);
if (typeMask == 0) {
mLogger.error(parser->getLineNumber())
<< "invalid attribute format '"
<< formatAttrIter->value
<< "'."
<< std::endl;
return {};
}
}
std::vector<Attribute::Symbol> items;
bool error = false;
while (XmlPullParser::isGoodEvent(parser->next())) {
if (parser->getEvent() != XmlPullParser::Event::kStartElement) {
continue;
}
ScopedXmlPullParser childParser(parser);
const std::u16string& name = childParser.getElementName();
if (!childParser.getElementNamespace().empty()
|| (name != u"flag" && name != u"enum")) {
mLogger.error(childParser.getLineNumber())
<< "unexpected tag <"
<< name
<< "> in <attr>."
<< std::endl;
error = true;
continue;
}
if (name == u"enum") {
if (typeMask & android::ResTable_map::TYPE_FLAGS) {
mLogger.error(childParser.getLineNumber())
<< "can not define an <enum>; already defined a <flag>."
<< std::endl;
error = true;
continue;
}
typeMask |= android::ResTable_map::TYPE_ENUM;
} else if (name == u"flag") {
if (typeMask & android::ResTable_map::TYPE_ENUM) {
mLogger.error(childParser.getLineNumber())
<< "can not define a <flag>; already defined an <enum>."
<< std::endl;
error = true;
continue;
}
typeMask |= android::ResTable_map::TYPE_FLAGS;
}
Attribute::Symbol item;
if (parseEnumOrFlagItem(&childParser, name, &item)) {
if (!mTable->addResource(item.symbol.name, mConfig,
mSource.line(childParser.getLineNumber()),
util::make_unique<Id>())) {
error = true;
} else {
items.push_back(std::move(item));
}
} else {
error = true;
}
}
if (error) {
return {};
}
std::unique_ptr<Attribute> attr = util::make_unique<Attribute>(weak);
attr->symbols.swap(items);
attr->typeMask = typeMask ? typeMask : uint32_t(android::ResTable_map::TYPE_ANY);
return attr;
}
bool ResourceParser::parseEnumOrFlagItem(XmlPullParser* parser, const StringPiece16& tag,
Attribute::Symbol* outSymbol) {
const auto attrIterEnd = parser->endAttributes();
const auto nameAttrIter = parser->findAttribute(u"", u"name");
if (nameAttrIter == attrIterEnd || nameAttrIter->value.empty()) {
mLogger.error(parser->getLineNumber())
<< "no attribute 'name' found for tag <" << tag << ">."
<< std::endl;
return false;
}
const auto valueAttrIter = parser->findAttribute(u"", u"value");
if (valueAttrIter == attrIterEnd || valueAttrIter->value.empty()) {
mLogger.error(parser->getLineNumber())
<< "no attribute 'value' found for tag <" << tag << ">."
<< std::endl;
return false;
}
android::Res_value val;
if (!android::ResTable::stringToInt(valueAttrIter->value.data(),
valueAttrIter->value.size(), &val)) {
mLogger.error(parser->getLineNumber())
<< "invalid value '"
<< valueAttrIter->value
<< "' for <" << tag << ">; must be an integer."
<< std::endl;
return false;
}
outSymbol->symbol.name = ResourceName {
mTable->getPackage(), ResourceType::kId, nameAttrIter->value };
outSymbol->value = val.data;
return true;
}
static bool parseXmlAttributeName(StringPiece16 str, ResourceNameRef* outRef) {
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++;
}
outRef->package = package;
outRef->type = ResourceType::kAttr;
if (name.size() == 0) {
outRef->entry = str;
} else {
outRef->entry = name;
}
return true;
}
bool ResourceParser::parseUntypedItem(XmlPullParser* parser, Style& style) {
const auto endAttrIter = parser->endAttributes();
const auto nameAttrIter = parser->findAttribute(u"", u"name");
if (nameAttrIter == endAttrIter || nameAttrIter->value.empty()) {
mLogger.error(parser->getLineNumber())
<< "<item> must have a 'name' attribute."
<< std::endl;
return false;
}
ResourceNameRef keyRef;
if (!parseXmlAttributeName(nameAttrIter->value, &keyRef)) {
mLogger.error(parser->getLineNumber())
<< "invalid attribute name '"
<< nameAttrIter->value
<< "'."
<< std::endl;
return false;
}
if (keyRef.package.empty()) {
keyRef.package = mTable->getPackage();
}
// Create a copy instead of a reference because we
// are about to invalidate keyRef when advancing the parser.
ResourceName key = keyRef.toResourceName();
std::unique_ptr<Item> value = parseXml(parser, 0, kAllowRawString);
if (!value) {
return false;
}
style.entries.push_back(Style::Entry{ Reference(key), std::move(value) });
return true;
}
bool ResourceParser::parseStyle(XmlPullParser* parser, const ResourceNameRef& resourceName) {
const SourceLine source = mSource.line(parser->getLineNumber());
std::unique_ptr<Style> style = util::make_unique<Style>();
const auto endAttrIter = parser->endAttributes();
const auto parentAttrIter = parser->findAttribute(u"", u"parent");
if (parentAttrIter != endAttrIter) {
ResourceNameRef ref;
bool create = false;
bool privateRef = false;
if (tryParseReference(parentAttrIter->value, &ref, &create, &privateRef)) {
if (create) {
mLogger.error(source.line)
<< "parent of style can not be an ID."
<< std::endl;
return false;
}
style->parent.name = ref.toResourceName();
style->parent.privateReference = privateRef;
} else if (tryParseAttributeReference(parentAttrIter->value, &ref)) {
style->parent.name = ref.toResourceName();
} else {
// TODO(adamlesinski): Try parsing without the '@' or '?'.
// Also, make sure to check the entry name for weird symbols.
style->parent.name = ResourceName {
{}, ResourceType::kStyle, parentAttrIter->value
};
}
if (style->parent.name.package.empty()) {
style->parent.name.package = mTable->getPackage();
}
}
bool success = true;
while (XmlPullParser::isGoodEvent(parser->next())) {
if (parser->getEvent() != XmlPullParser::Event::kStartElement) {
continue;
}
ScopedXmlPullParser childParser(parser);
const std::u16string& name = childParser.getElementName();
if (name == u"item") {
success &= parseUntypedItem(&childParser, *style);
} else {
mLogger.error(childParser.getLineNumber())
<< "unexpected tag <"
<< name
<< "> in <style> resource."
<< std::endl;
success = false;
}
}
if (!success) {
return false;
}
return mTable->addResource(resourceName, mConfig, source, std::move(style));
}
bool ResourceParser::parseArray(XmlPullParser* parser, const ResourceNameRef& resourceName,
uint32_t typeMask) {
const SourceLine source = mSource.line(parser->getLineNumber());
std::unique_ptr<Array> array = util::make_unique<Array>();
bool error = false;
while (XmlPullParser::isGoodEvent(parser->next())) {
if (parser->getEvent() != XmlPullParser::Event::kStartElement) {
continue;
}
ScopedXmlPullParser childParser(parser);
if (childParser.getElementName() != u"item") {
mLogger.error(childParser.getLineNumber())
<< "unexpected tag <"
<< childParser.getElementName()
<< "> in <array> resource."
<< std::endl;
error = true;
continue;
}
std::unique_ptr<Item> item = parseXml(&childParser, typeMask, kNoRawString);
if (!item) {
error = true;
continue;
}
array->items.emplace_back(std::move(item));
}
if (error) {
return false;
}
return mTable->addResource(resourceName, mConfig, source, std::move(array));
}
bool ResourceParser::parsePlural(XmlPullParser* parser, const ResourceNameRef& resourceName) {
const SourceLine source = mSource.line(parser->getLineNumber());
std::unique_ptr<Plural> plural = util::make_unique<Plural>();
bool success = true;
while (XmlPullParser::isGoodEvent(parser->next())) {
if (parser->getEvent() != XmlPullParser::Event::kStartElement) {
continue;
}
ScopedXmlPullParser childParser(parser);
if (!childParser.getElementNamespace().empty() ||
childParser.getElementName() != u"item") {
success = false;
continue;
}
const auto endAttrIter = childParser.endAttributes();
auto attrIter = childParser.findAttribute(u"", u"quantity");
if (attrIter == endAttrIter || attrIter->value.empty()) {
mLogger.error(childParser.getLineNumber())
<< "<item> in <plurals> requires attribute 'quantity'."
<< std::endl;
success = false;
continue;
}
StringPiece16 trimmedQuantity = util::trimWhitespace(attrIter->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 {
mLogger.error(childParser.getLineNumber())
<< "<item> in <plural> has invalid value '"
<< trimmedQuantity
<< "' for attribute 'quantity'."
<< std::endl;
success = false;
continue;
}
if (plural->values[index]) {
mLogger.error(childParser.getLineNumber())
<< "duplicate quantity '"
<< trimmedQuantity
<< "'."
<< std::endl;
success = false;
continue;
}
if (!(plural->values[index] = parseXml(&childParser, android::ResTable_map::TYPE_STRING,
kNoRawString))) {
success = false;
}
}
if (!success) {
return false;
}
return mTable->addResource(resourceName, mConfig, source, std::move(plural));
}
bool ResourceParser::parseDeclareStyleable(XmlPullParser* parser,
const ResourceNameRef& resourceName) {
const SourceLine source = mSource.line(parser->getLineNumber());
std::unique_ptr<Styleable> styleable = util::make_unique<Styleable>();
bool success = true;
while (XmlPullParser::isGoodEvent(parser->next())) {
if (parser->getEvent() != XmlPullParser::Event::kStartElement) {
continue;
}
ScopedXmlPullParser childParser(parser);
const std::u16string& elementName = childParser.getElementName();
if (elementName == u"attr") {
const auto endAttrIter = childParser.endAttributes();
auto attrIter = childParser.findAttribute(u"", u"name");
if (attrIter == endAttrIter || attrIter->value.empty()) {
mLogger.error(childParser.getLineNumber())
<< "<attr> tag must have a 'name' attribute."
<< std::endl;
success = false;
continue;
}
// Copy because our iterator will be invalidated.
std::u16string attrName = attrIter->value;
ResourceNameRef attrResourceName = {
mTable->getPackage(),
ResourceType::kAttr,
attrName
};
std::unique_ptr<Attribute> attr = parseAttrImpl(&childParser, attrResourceName, true);
if (!attr) {
success = false;
continue;
}
styleable->entries.emplace_back(attrResourceName);
success &= mTable->addResource(attrResourceName, mConfig,
mSource.line(childParser.getLineNumber()),
std::move(attr));
} else if (elementName != u"eat-comment" && elementName != u"skip") {
mLogger.error(childParser.getLineNumber())
<< "<"
<< elementName
<< "> is not allowed inside <declare-styleable>."
<< std::endl;
success = false;
}
}
if (!success) {
return false;
}
return mTable->addResource(resourceName, mConfig, source, std::move(styleable));
}
} // namespace aapt