diff options
Diffstat (limited to 'tools/aapt2/ResourceParser.cpp')
-rw-r--r-- | tools/aapt2/ResourceParser.cpp | 1317 |
1 files changed, 1317 insertions, 0 deletions
diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp new file mode 100644 index 0000000..d3720c4 --- /dev/null +++ b/tools/aapt2/ResourceParser.cpp @@ -0,0 +1,1317 @@ +/* + * 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 : 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 |