From 07dfd2d8642f8a3630ca6429f740865a0c0bfdf7 Mon Sep 17 00:00:00 2001 From: Adam Lesinski Date: Wed, 28 Oct 2015 15:44:27 -0700 Subject: Implement AAPT Bundle format AAPT will scan XML files looking for the XML tag. The SINGLE child element of the tag is extracted into its own top level resource. It is given a generated name. The parent element of is then given the resource attribute that was assigned to the `name' attribute. The value is set to a reference to the generated resource. Bug:22627686 Change-Id: I8575fc4f739011402662fbf6b3db96df0012f598 --- tools/aapt/ResourceTable.cpp | 246 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 238 insertions(+), 8 deletions(-) (limited to 'tools/aapt/ResourceTable.cpp') diff --git a/tools/aapt/ResourceTable.cpp b/tools/aapt/ResourceTable.cpp index d5a09d817b1e..0e470d924f5a 100644 --- a/tools/aapt/ResourceTable.cpp +++ b/tools/aapt/ResourceTable.cpp @@ -88,8 +88,11 @@ status_t compileXmlFile(const Bundle* bundle, root->setUTF8(true); } - bool hasErrors = false; + if (table->processBundleFormat(bundle, resourceName, target, root) != NO_ERROR) { + return UNKNOWN_ERROR; + } + bool hasErrors = false; if ((options&XML_COMPILE_ASSIGN_ATTRIBUTE_IDS) != 0) { status_t err = root->assignResourceIds(assets, table); if (err != NO_ERROR) { @@ -97,9 +100,11 @@ status_t compileXmlFile(const Bundle* bundle, } } - status_t err = root->parseValues(assets, table); - if (err != NO_ERROR) { - hasErrors = true; + if ((options&XML_COMPILE_PARSE_VALUES) != 0) { + status_t err = root->parseValues(assets, table); + if (err != NO_ERROR) { + hasErrors = true; + } } if (hasErrors) { @@ -114,7 +119,7 @@ status_t compileXmlFile(const Bundle* bundle, printf("Input XML Resource:\n"); root->print(); } - err = root->flatten(target, + status_t err = root->flatten(target, (options&XML_COMPILE_STRIP_COMMENTS) != 0, (options&XML_COMPILE_STRIP_RAW_VALUES) != 0); if (err != NO_ERROR) { @@ -4755,9 +4760,9 @@ status_t ResourceTable::modifyForCompat(const Bundle* bundle, newConfig.sdkVersion = sdkVersionToGenerate; sp newFile = new AaptFile(target->getSourceFile(), AaptGroupEntry(newConfig), target->getResourceType()); - String8 resPath = String8::format("res/%s/%s", + String8 resPath = String8::format("res/%s/%s.xml", newFile->getGroupEntry().toDirName(target->getResourceType()).string(), - target->getSourceFile().getPathLeaf().string()); + String8(resourceName).string()); resPath.convertToResPath(); // Add a resource table entry. @@ -4784,9 +4789,11 @@ status_t ResourceTable::modifyForCompat(const Bundle* bundle, item.resourceName = resourceName; item.resPath = resPath; item.file = newFile; + item.xmlRoot = newRoot; + item.needsCompiling = false; // This step occurs after we parse/assign, so we don't need + // to do it again. mWorkQueue.push(item); } - return NO_ERROR; } @@ -4825,3 +4832,226 @@ void ResourceTable::getDensityVaryingResources( } } } + +static String16 buildNamespace(const String16& package) { + return String16("http://schemas.android.com/apk/res/") + package; +} + +static sp findOnlyChildElement(const sp& parent) { + const Vector >& children = parent->getChildren(); + sp onlyChild; + for (size_t i = 0; i < children.size(); i++) { + if (children[i]->getType() != XMLNode::TYPE_CDATA) { + if (onlyChild != NULL) { + return NULL; + } + onlyChild = children[i]; + } + } + return onlyChild; +} + +/** + * Detects use of the `bundle' format and extracts nested resources into their own top level + * resources. The bundle format looks like this: + * + * + * + * + * + * + * + * + * + * + * When AAPT sees the tag, it will extract its single element and its children + * into a new high-level resource, assigning it a name and ID. Then value of the `name` + * attribute must be a resource attribute. That resource attribute is inserted into the parent + * with the reference to the extracted resource as the value. + * + * + * + * + * + * + * + * + * + */ +status_t ResourceTable::processBundleFormat(const Bundle* bundle, + const String16& resourceName, + const sp& target, + const sp& root) { + Vector > namespaces; + if (root->getType() == XMLNode::TYPE_NAMESPACE) { + namespaces.push(root); + } + return processBundleFormatImpl(bundle, resourceName, target, root, &namespaces); +} + +status_t ResourceTable::processBundleFormatImpl(const Bundle* bundle, + const String16& resourceName, + const sp& target, + const sp& parent, + Vector >* namespaces) { + const String16 kAaptNamespaceUri16("http://schemas.android.com/aapt"); + const String16 kName16("name"); + const String16 kAttr16("attr"); + const String16 kAssetPackage16(mAssets->getPackage()); + + Vector >& children = parent->getChildren(); + for (size_t i = 0; i < children.size(); i++) { + const sp& child = children[i]; + + if (child->getType() == XMLNode::TYPE_CDATA) { + continue; + } else if (child->getType() == XMLNode::TYPE_NAMESPACE) { + namespaces->push(child); + } + + if (child->getElementNamespace() != kAaptNamespaceUri16 || + child->getElementName() != kAttr16) { + status_t result = processBundleFormatImpl(bundle, resourceName, target, child, + namespaces); + if (result != NO_ERROR) { + return result; + } + + if (child->getType() == XMLNode::TYPE_NAMESPACE) { + namespaces->pop(); + } + continue; + } + + // This is the tag. Look for the 'name' attribute. + SourcePos source(child->getFilename(), child->getStartLineNumber()); + + sp nestedRoot = findOnlyChildElement(child); + if (nestedRoot == NULL) { + source.error("<%s:%s> must have exactly one child element", + String8(child->getElementNamespace()).string(), + String8(child->getElementName()).string()); + return UNKNOWN_ERROR; + } + + // Find the special attribute 'parent-attr'. This attribute's value contains + // the resource attribute for which this element should be assigned in the parent. + const XMLNode::attribute_entry* attr = child->getAttribute(String16(), kName16); + if (attr == NULL) { + source.error("inline resource definition must specify an attribute via 'name'"); + return UNKNOWN_ERROR; + } + + // Parse the attribute name. + const char* errorMsg = NULL; + String16 attrPackage, attrType, attrName; + bool result = ResTable::expandResourceRef(attr->string.string(), + attr->string.size(), + &attrPackage, &attrType, &attrName, + &kAttr16, &kAssetPackage16, + &errorMsg, NULL); + if (!result) { + source.error("invalid attribute name for 'name': %s", errorMsg); + return UNKNOWN_ERROR; + } + + if (attrType != kAttr16) { + // The value of the 'name' attribute must be an attribute reference. + source.error("value of 'name' must be an attribute reference."); + return UNKNOWN_ERROR; + } + + // Generate a name for this nested resource and try to add it to the table. + // We do this in a loop because the name may be taken, in which case we will + // increment a suffix until we succeed. + String8 nestedResourceName; + String8 nestedResourcePath; + int suffix = 1; + while (true) { + // This child element will be extracted into its own resource file. + // Generate a name and path for it from its parent. + nestedResourceName = String8::format("%s_%d", + String8(resourceName).string(), suffix++); + nestedResourcePath = String8::format("res/%s/%s.xml", + target->getGroupEntry().toDirName(target->getResourceType()) + .string(), + nestedResourceName.string()); + + // Lookup or create the entry for this name. + sp entry = getEntry(kAssetPackage16, + String16(target->getResourceType()), + String16(nestedResourceName), + source, + false, + &target->getGroupEntry().toParams(), + true); + if (entry == NULL) { + return UNKNOWN_ERROR; + } + + if (entry->getType() == Entry::TYPE_UNKNOWN) { + // The value for this resource has never been set, + // meaning we're good! + entry->setItem(source, String16(nestedResourcePath)); + break; + } + + // We failed (name already exists), so try with a different name + // (increment the suffix). + } + + if (bundle->getVerbose()) { + source.printf("generating nested resource %s:%s/%s", + mAssets->getPackage().string(), target->getResourceType().string(), + nestedResourceName.string()); + } + + // Build the attribute reference and assign it to the parent. + String16 nestedResourceRef = String16(String8::format("@%s:%s/%s", + mAssets->getPackage().string(), target->getResourceType().string(), + nestedResourceName.string())); + + String16 attrNs = buildNamespace(attrPackage); + if (parent->getAttribute(attrNs, attrName) != NULL) { + SourcePos(parent->getFilename(), parent->getStartLineNumber()) + .error("parent of nested resource already defines attribute '%s:%s'", + String8(attrPackage).string(), String8(attrName).string()); + return UNKNOWN_ERROR; + } + + // Add the reference to the inline resource. + parent->addAttribute(attrNs, attrName, nestedResourceRef); + + // Remove the child element from here. + children.removeAt(i); + i--; + + // Append all namespace declarations that we've seen on this branch in the XML tree + // to this resource. + // We do this because the order of namespace declarations and prefix usage is determined + // by the developer and we do not want to override any decisions. Be conservative. + for (size_t nsIndex = namespaces->size(); nsIndex > 0; nsIndex--) { + const sp& ns = namespaces->itemAt(nsIndex - 1); + sp newNs = XMLNode::newNamespace(ns->getFilename(), ns->getNamespacePrefix(), + ns->getNamespaceUri()); + newNs->addChild(nestedRoot); + nestedRoot = newNs; + } + + // Schedule compilation of the nested resource. + CompileResourceWorkItem workItem; + workItem.resPath = nestedResourcePath; + workItem.resourceName = String16(nestedResourceName); + workItem.xmlRoot = nestedRoot; + workItem.file = new AaptFile(target->getSourceFile(), target->getGroupEntry(), + target->getResourceType()); + mWorkQueue.push(workItem); + } + return NO_ERROR; +} -- cgit v1.2.3-59-g8ed1b