From 9d0f7d44d5cc5322415f52f7ce03cc37a478b350 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: I31bc96aae30d38bfd0b16508d0f585de5fd88a07 --- tools/aapt/Resource.cpp | 9 +- tools/aapt/ResourceTable.cpp | 233 ++++++++++++++++++++++++++++++++++++++++++- tools/aapt/ResourceTable.h | 12 +++ tools/aapt/XMLNode.cpp | 18 ++++ tools/aapt/XMLNode.h | 4 +- 5 files changed, 271 insertions(+), 5 deletions(-) diff --git a/tools/aapt/Resource.cpp b/tools/aapt/Resource.cpp index e22e76de66c2..f1a8f1c5f170 100644 --- a/tools/aapt/Resource.cpp +++ b/tools/aapt/Resource.cpp @@ -1537,7 +1537,14 @@ status_t buildResources(Bundle* bundle, const sp& assets, sp& workQueue = table.getWorkQueue(); while (!workQueue.empty()) { CompileResourceWorkItem& workItem = workQueue.front(); - err = compileXmlFile(bundle, assets, workItem.resourceName, workItem.file, &table, xmlFlags); + if (workItem.xmlRoot != NULL) { + err = compileXmlFile(bundle, assets, workItem.resourceName, workItem.xmlRoot, + workItem.file, &table, xmlFlags); + } else { + err = compileXmlFile(bundle, assets, workItem.resourceName, workItem.file, + &table, xmlFlags); + } + if (err == NO_ERROR) { assets->addResource(workItem.resPath.getPathLeaf(), workItem.resPath, diff --git a/tools/aapt/ResourceTable.cpp b/tools/aapt/ResourceTable.cpp index d5a09d817b1e..7d198282fc46 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) { @@ -4755,9 +4758,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,6 +4787,7 @@ status_t ResourceTable::modifyForCompat(const Bundle* bundle, item.resourceName = resourceName; item.resPath = resPath; item.file = newFile; + item.xmlRoot = newRoot; mWorkQueue.push(item); } @@ -4825,3 +4829,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; +} diff --git a/tools/aapt/ResourceTable.h b/tools/aapt/ResourceTable.h index c4bdf09d8b19..8ef84f773f18 100644 --- a/tools/aapt/ResourceTable.h +++ b/tools/aapt/ResourceTable.h @@ -83,6 +83,7 @@ struct CompileResourceWorkItem { String16 resourceName; String8 resPath; sp file; + sp xmlRoot; }; class ResourceTable : public ResTable::Accessor @@ -206,6 +207,12 @@ public: const sp& file, const sp& root); + status_t processBundleFormat(const Bundle* bundle, + const String16& resourceName, + const sp& file, + const sp& parent); + + sp flatten(Bundle* bundle, const sp& filter, const bool isBase); @@ -586,6 +593,11 @@ private: Res_value* outValue); int getPublicAttributeSdkLevel(uint32_t attrId) const; + status_t processBundleFormatImpl(const Bundle* bundle, + const String16& resourceName, + const sp& file, + const sp& parent, + Vector >* namespaces); String16 mAssetsPackage; PackageType mPackageType; diff --git a/tools/aapt/XMLNode.cpp b/tools/aapt/XMLNode.cpp index dc08eb806356..5b215daeb494 100644 --- a/tools/aapt/XMLNode.cpp +++ b/tools/aapt/XMLNode.cpp @@ -693,6 +693,12 @@ const Vector >& XMLNode::getChildren() const return mChildren; } + +Vector >& XMLNode::getChildren() +{ + return mChildren; +} + const String8& XMLNode::getFilename() const { return mFilename; @@ -717,6 +723,18 @@ const XMLNode::attribute_entry* XMLNode::getAttribute(const String16& ns, return NULL; } +bool XMLNode::removeAttribute(const String16& ns, const String16& name) +{ + for (size_t i = 0; i < mAttributes.size(); i++) { + const attribute_entry& ae(mAttributes.itemAt(i)); + if (ae.ns == ns && ae.name == name) { + removeAttribute(i); + return true; + } + } + return false; +} + XMLNode::attribute_entry* XMLNode::editAttribute(const String16& ns, const String16& name) { diff --git a/tools/aapt/XMLNode.h b/tools/aapt/XMLNode.h index b9e5cd574cdc..749bf9f59bf7 100644 --- a/tools/aapt/XMLNode.h +++ b/tools/aapt/XMLNode.h @@ -55,7 +55,7 @@ public: sp newCData(const String8& filename) { return new XMLNode(filename); } - + enum type { TYPE_NAMESPACE, TYPE_ELEMENT, @@ -70,6 +70,7 @@ public: const String16& getElementNamespace() const; const String16& getElementName() const; const Vector >& getChildren() const; + Vector >& getChildren(); const String8& getFilename() const; @@ -97,6 +98,7 @@ public: const Vector& getAttributes() const; const attribute_entry* getAttribute(const String16& ns, const String16& name) const; + bool removeAttribute(const String16& ns, const String16& name); attribute_entry* editAttribute(const String16& ns, const String16& name); -- cgit v1.2.3-59-g8ed1b