diff options
author | Adam Lesinski <adamlesinski@google.com> | 2015-11-16 17:35:44 -0800 |
---|---|---|
committer | Adam Lesinski <adamlesinski@google.com> | 2015-11-19 14:46:53 -0800 |
commit | 467f171315f9c2037fcd3eb5edcfabc40671bf7b (patch) | |
tree | 3b14762e3d9e42a660479d0d5722883b391f835b /tools/aapt2/xml | |
parent | abf83cbe4f63cd76043aab89cd0e08525560fea2 (diff) |
AAPT2: Fail compiling when private symbols are referenced
Also moved some XML specific stuff into its own directory,
and refactored ReferenceLinker a bit.
Change-Id: I912247a82023c1bbf72dc191fbdaf62858cbec0c
Diffstat (limited to 'tools/aapt2/xml')
-rw-r--r-- | tools/aapt2/xml/XmlDom.cpp | 440 | ||||
-rw-r--r-- | tools/aapt2/xml/XmlDom.h | 220 | ||||
-rw-r--r-- | tools/aapt2/xml/XmlDom_test.cpp | 50 | ||||
-rw-r--r-- | tools/aapt2/xml/XmlPullParser.cpp | 308 | ||||
-rw-r--r-- | tools/aapt2/xml/XmlPullParser.h | 298 | ||||
-rw-r--r-- | tools/aapt2/xml/XmlPullParser_test.cpp | 55 | ||||
-rw-r--r-- | tools/aapt2/xml/XmlUtil.cpp | 67 | ||||
-rw-r--r-- | tools/aapt2/xml/XmlUtil.h | 85 | ||||
-rw-r--r-- | tools/aapt2/xml/XmlUtil_test.cpp | 54 |
9 files changed, 1577 insertions, 0 deletions
diff --git a/tools/aapt2/xml/XmlDom.cpp b/tools/aapt2/xml/XmlDom.cpp new file mode 100644 index 000000000000..d27b62fd99fb --- /dev/null +++ b/tools/aapt2/xml/XmlDom.cpp @@ -0,0 +1,440 @@ +/* + * 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 "XmlDom.h" +#include "XmlPullParser.h" +#include "util/Util.h" + +#include <cassert> +#include <expat.h> +#include <memory> +#include <stack> +#include <string> +#include <tuple> + +namespace aapt { +namespace xml { + +constexpr char kXmlNamespaceSep = 1; + +struct Stack { + std::unique_ptr<xml::Node> root; + std::stack<xml::Node*> nodeStack; + std::u16string pendingComment; +}; + +/** + * Extracts the namespace and name of an expanded element or attribute name. + */ +static void splitName(const char* name, std::u16string* outNs, std::u16string* outName) { + const char* p = name; + while (*p != 0 && *p != kXmlNamespaceSep) { + p++; + } + + if (*p == 0) { + outNs->clear(); + *outName = util::utf8ToUtf16(name); + } else { + *outNs = util::utf8ToUtf16(StringPiece(name, (p - name))); + *outName = util::utf8ToUtf16(p + 1); + } +} + +static void addToStack(Stack* stack, XML_Parser parser, std::unique_ptr<Node> node) { + node->lineNumber = XML_GetCurrentLineNumber(parser); + node->columnNumber = XML_GetCurrentColumnNumber(parser); + + Node* thisNode = node.get(); + if (!stack->nodeStack.empty()) { + stack->nodeStack.top()->addChild(std::move(node)); + } else { + stack->root = std::move(node); + } + + if (!nodeCast<Text>(thisNode)) { + stack->nodeStack.push(thisNode); + } +} + +static void XMLCALL startNamespaceHandler(void* userData, const char* prefix, const char* uri) { + XML_Parser parser = reinterpret_cast<XML_Parser>(userData); + Stack* stack = reinterpret_cast<Stack*>(XML_GetUserData(parser)); + + std::unique_ptr<Namespace> ns = util::make_unique<Namespace>(); + if (prefix) { + ns->namespacePrefix = util::utf8ToUtf16(prefix); + } + + if (uri) { + ns->namespaceUri = util::utf8ToUtf16(uri); + } + + addToStack(stack, parser, std::move(ns)); +} + +static void XMLCALL endNamespaceHandler(void* userData, const char* prefix) { + XML_Parser parser = reinterpret_cast<XML_Parser>(userData); + Stack* stack = reinterpret_cast<Stack*>(XML_GetUserData(parser)); + + assert(!stack->nodeStack.empty()); + stack->nodeStack.pop(); +} + +static bool lessAttribute(const Attribute& lhs, const Attribute& rhs) { + return std::tie(lhs.namespaceUri, lhs.name, lhs.value) < + std::tie(rhs.namespaceUri, rhs.name, rhs.value); +} + +static void XMLCALL startElementHandler(void* userData, const char* name, const char** attrs) { + XML_Parser parser = reinterpret_cast<XML_Parser>(userData); + Stack* stack = reinterpret_cast<Stack*>(XML_GetUserData(parser)); + + std::unique_ptr<Element> el = util::make_unique<Element>(); + splitName(name, &el->namespaceUri, &el->name); + + while (*attrs) { + Attribute attribute; + splitName(*attrs++, &attribute.namespaceUri, &attribute.name); + attribute.value = util::utf8ToUtf16(*attrs++); + + // Insert in sorted order. + auto iter = std::lower_bound(el->attributes.begin(), el->attributes.end(), attribute, + lessAttribute); + el->attributes.insert(iter, std::move(attribute)); + } + + el->comment = std::move(stack->pendingComment); + addToStack(stack, parser, std::move(el)); +} + +static void XMLCALL endElementHandler(void* userData, const char* name) { + XML_Parser parser = reinterpret_cast<XML_Parser>(userData); + Stack* stack = reinterpret_cast<Stack*>(XML_GetUserData(parser)); + + assert(!stack->nodeStack.empty()); + //stack->nodeStack.top()->comment = std::move(stack->pendingComment); + stack->nodeStack.pop(); +} + +static void XMLCALL characterDataHandler(void* userData, const char* s, int len) { + XML_Parser parser = reinterpret_cast<XML_Parser>(userData); + Stack* stack = reinterpret_cast<Stack*>(XML_GetUserData(parser)); + + if (!s || len <= 0) { + return; + } + + // See if we can just append the text to a previous text node. + if (!stack->nodeStack.empty()) { + Node* currentParent = stack->nodeStack.top(); + if (!currentParent->children.empty()) { + Node* lastChild = currentParent->children.back().get(); + if (Text* text = nodeCast<Text>(lastChild)) { + text->text += util::utf8ToUtf16(StringPiece(s, len)); + return; + } + } + } + + std::unique_ptr<Text> text = util::make_unique<Text>(); + text->text = util::utf8ToUtf16(StringPiece(s, len)); + addToStack(stack, parser, std::move(text)); +} + +static void XMLCALL commentDataHandler(void* userData, const char* comment) { + XML_Parser parser = reinterpret_cast<XML_Parser>(userData); + Stack* stack = reinterpret_cast<Stack*>(XML_GetUserData(parser)); + + if (!stack->pendingComment.empty()) { + stack->pendingComment += '\n'; + } + stack->pendingComment += util::utf8ToUtf16(comment); +} + +std::unique_ptr<XmlResource> inflate(std::istream* in, IDiagnostics* diag, const Source& source) { + Stack stack; + + XML_Parser parser = XML_ParserCreateNS(nullptr, kXmlNamespaceSep); + XML_SetUserData(parser, &stack); + XML_UseParserAsHandlerArg(parser); + XML_SetElementHandler(parser, startElementHandler, endElementHandler); + XML_SetNamespaceDeclHandler(parser, startNamespaceHandler, endNamespaceHandler); + XML_SetCharacterDataHandler(parser, characterDataHandler); + XML_SetCommentHandler(parser, commentDataHandler); + + char buffer[1024]; + while (!in->eof()) { + in->read(buffer, sizeof(buffer) / sizeof(buffer[0])); + if (in->bad() && !in->eof()) { + stack.root = {}; + diag->error(DiagMessage(source) << strerror(errno)); + break; + } + + if (XML_Parse(parser, buffer, in->gcount(), in->eof()) == XML_STATUS_ERROR) { + stack.root = {}; + diag->error(DiagMessage(source.withLine(XML_GetCurrentLineNumber(parser))) + << XML_ErrorString(XML_GetErrorCode(parser))); + break; + } + } + + XML_ParserFree(parser); + if (stack.root) { + return util::make_unique<XmlResource>(ResourceFile{ {}, {}, source }, std::move(stack.root)); + } + return {}; +} + +static void copyAttributes(Element* el, android::ResXMLParser* parser) { + const size_t attrCount = parser->getAttributeCount(); + if (attrCount > 0) { + el->attributes.reserve(attrCount); + for (size_t i = 0; i < attrCount; i++) { + Attribute attr; + size_t len; + const char16_t* str16 = parser->getAttributeNamespace(i, &len); + if (str16) { + attr.namespaceUri.assign(str16, len); + } + + str16 = parser->getAttributeName(i, &len); + if (str16) { + attr.name.assign(str16, len); + } + + str16 = parser->getAttributeStringValue(i, &len); + if (str16) { + attr.value.assign(str16, len); + } + el->attributes.push_back(std::move(attr)); + } + } +} + +std::unique_ptr<XmlResource> inflate(const void* data, size_t dataLen, IDiagnostics* diag, + const Source& source) { + std::unique_ptr<Node> root; + std::stack<Node*> nodeStack; + + android::ResXMLTree tree; + if (tree.setTo(data, dataLen) != android::NO_ERROR) { + return {}; + } + + android::ResXMLParser::event_code_t code; + while ((code = tree.next()) != android::ResXMLParser::BAD_DOCUMENT && + code != android::ResXMLParser::END_DOCUMENT) { + std::unique_ptr<Node> newNode; + switch (code) { + case android::ResXMLParser::START_NAMESPACE: { + std::unique_ptr<Namespace> node = util::make_unique<Namespace>(); + size_t len; + const char16_t* str16 = tree.getNamespacePrefix(&len); + if (str16) { + node->namespacePrefix.assign(str16, len); + } + + str16 = tree.getNamespaceUri(&len); + if (str16) { + node->namespaceUri.assign(str16, len); + } + newNode = std::move(node); + break; + } + + case android::ResXMLParser::START_TAG: { + std::unique_ptr<Element> node = util::make_unique<Element>(); + size_t len; + const char16_t* str16 = tree.getElementNamespace(&len); + if (str16) { + node->namespaceUri.assign(str16, len); + } + + str16 = tree.getElementName(&len); + if (str16) { + node->name.assign(str16, len); + } + + copyAttributes(node.get(), &tree); + + newNode = std::move(node); + break; + } + + case android::ResXMLParser::TEXT: { + std::unique_ptr<Text> node = util::make_unique<Text>(); + size_t len; + const char16_t* str16 = tree.getText(&len); + if (str16) { + node->text.assign(str16, len); + } + newNode = std::move(node); + break; + } + + case android::ResXMLParser::END_NAMESPACE: + case android::ResXMLParser::END_TAG: + assert(!nodeStack.empty()); + nodeStack.pop(); + break; + + default: + assert(false); + break; + } + + if (newNode) { + newNode->lineNumber = tree.getLineNumber(); + + Node* thisNode = newNode.get(); + if (!root) { + assert(nodeStack.empty()); + root = std::move(newNode); + } else { + assert(!nodeStack.empty()); + nodeStack.top()->addChild(std::move(newNode)); + } + + if (!nodeCast<Text>(thisNode)) { + nodeStack.push(thisNode); + } + } + } + return util::make_unique<XmlResource>(ResourceFile{}, std::move(root)); +} + +Element* findRootElement(XmlResource* doc) { + return findRootElement(doc->root.get()); +} + +Element* findRootElement(Node* node) { + if (!node) { + return nullptr; + } + + Element* el = nullptr; + while ((el = nodeCast<Element>(node)) == nullptr) { + if (node->children.empty()) { + return nullptr; + } + // We are looking for the first element, and namespaces can only have one child. + node = node->children.front().get(); + } + return el; +} + +void Node::addChild(std::unique_ptr<Node> child) { + child->parent = this; + children.push_back(std::move(child)); +} + +Attribute* Element::findAttribute(const StringPiece16& ns, const StringPiece16& name) { + for (auto& attr : attributes) { + if (ns == attr.namespaceUri && name == attr.name) { + return &attr; + } + } + return nullptr; +} + +Element* Element::findChild(const StringPiece16& ns, const StringPiece16& name) { + return findChildWithAttribute(ns, name, {}, {}, {}); +} + +Element* Element::findChildWithAttribute(const StringPiece16& ns, const StringPiece16& name, + const StringPiece16& attrNs, const StringPiece16& attrName, + const StringPiece16& attrValue) { + for (auto& childNode : children) { + Node* child = childNode.get(); + while (nodeCast<Namespace>(child)) { + if (child->children.empty()) { + break; + } + child = child->children[0].get(); + } + + if (Element* el = nodeCast<Element>(child)) { + if (ns == el->namespaceUri && name == el->name) { + if (attrNs.empty() && attrName.empty()) { + return el; + } + + Attribute* attr = el->findAttribute(attrNs, attrName); + if (attr && attrValue == attr->value) { + return el; + } + } + } + } + return nullptr; +} + +std::vector<Element*> Element::getChildElements() { + std::vector<Element*> elements; + for (auto& childNode : children) { + Node* child = childNode.get(); + while (nodeCast<Namespace>(child)) { + if (child->children.empty()) { + break; + } + child = child->children[0].get(); + } + + if (Element* el = nodeCast<Element>(child)) { + elements.push_back(el); + } + } + return elements; +} + +void PackageAwareVisitor::visit(Namespace* ns) { + bool added = false; + if (Maybe<ExtractedPackage> maybePackage = extractPackageFromNamespace(ns->namespaceUri)) { + ExtractedPackage& package = maybePackage.value(); + mPackageDecls.push_back(PackageDecl{ ns->namespacePrefix, std::move(package) }); + added = true; + } + + Visitor::visit(ns); + + if (added) { + mPackageDecls.pop_back(); + } +} + +Maybe<ExtractedPackage> PackageAwareVisitor::transformPackageAlias( + const StringPiece16& alias, const StringPiece16& localPackage) const { + if (alias.empty()) { + return ExtractedPackage{ localPackage.toString(), false /* private */ }; + } + + const auto rend = mPackageDecls.rend(); + for (auto iter = mPackageDecls.rbegin(); iter != rend; ++iter) { + if (alias == iter->prefix) { + if (iter->package.package.empty()) { + return ExtractedPackage{ localPackage.toString(), + iter->package.privateNamespace }; + } + return iter->package; + } + } + return {}; +} + +} // namespace xml +} // namespace aapt diff --git a/tools/aapt2/xml/XmlDom.h b/tools/aapt2/xml/XmlDom.h new file mode 100644 index 000000000000..033b0a4d031c --- /dev/null +++ b/tools/aapt2/xml/XmlDom.h @@ -0,0 +1,220 @@ +/* + * 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. + */ + +#ifndef AAPT_XML_DOM_H +#define AAPT_XML_DOM_H + +#include "Diagnostics.h" +#include "Resource.h" +#include "ResourceValues.h" +#include "util/StringPiece.h" +#include "util/Util.h" +#include "xml/XmlUtil.h" + +#include <istream> +#include <memory> +#include <string> +#include <vector> + +namespace aapt { +namespace xml { + +struct RawVisitor; + +/** + * Base class for all XML nodes. + */ +struct Node { + Node* parent = nullptr; + size_t lineNumber = 0; + size_t columnNumber = 0; + std::u16string comment; + std::vector<std::unique_ptr<Node>> children; + + virtual ~Node() = default; + + void addChild(std::unique_ptr<Node> child); + virtual void accept(RawVisitor* visitor) = 0; +}; + +/** + * Base class that implements the visitor methods for a + * subclass of Node. + */ +template <typename Derived> +struct BaseNode : public Node { + virtual void accept(RawVisitor* visitor) override; +}; + +/** + * A Namespace XML node. Can only have one child. + */ +struct Namespace : public BaseNode<Namespace> { + std::u16string namespacePrefix; + std::u16string namespaceUri; +}; + +struct AaptAttribute { + ResourceId id; + aapt::Attribute attribute; +}; + +/** + * An XML attribute. + */ +struct Attribute { + std::u16string namespaceUri; + std::u16string name; + std::u16string value; + + Maybe<AaptAttribute> compiledAttribute; + std::unique_ptr<Item> compiledValue; +}; + +/** + * An Element XML node. + */ +struct Element : public BaseNode<Element> { + std::u16string namespaceUri; + std::u16string name; + std::vector<Attribute> attributes; + + Attribute* findAttribute(const StringPiece16& ns, const StringPiece16& name); + xml::Element* findChild(const StringPiece16& ns, const StringPiece16& name); + xml::Element* findChildWithAttribute(const StringPiece16& ns, const StringPiece16& name, + const StringPiece16& attrNs, + const StringPiece16& attrName, + const StringPiece16& attrValue); + std::vector<xml::Element*> getChildElements(); +}; + +/** + * A Text (CDATA) XML node. Can not have any children. + */ +struct Text : public BaseNode<Text> { + std::u16string text; +}; + +/** + * An XML resource with a source, name, and XML tree. + */ +struct XmlResource { + ResourceFile file; + std::unique_ptr<xml::Node> root; +}; + +/** + * Inflates an XML DOM from a text stream, logging errors to the logger. + * Returns the root node on success, or nullptr on failure. + */ +std::unique_ptr<XmlResource> inflate(std::istream* in, IDiagnostics* diag, const Source& source); + +/** + * Inflates an XML DOM from a binary ResXMLTree, logging errors to the logger. + * Returns the root node on success, or nullptr on failure. + */ +std::unique_ptr<XmlResource> inflate(const void* data, size_t dataLen, IDiagnostics* diag, + const Source& source); + +Element* findRootElement(XmlResource* doc); +Element* findRootElement(Node* node); + +/** + * A visitor interface for the different XML Node subtypes. This will not traverse into + * children. Use Visitor for that. + */ +struct RawVisitor { + virtual ~RawVisitor() = default; + + virtual void visit(Namespace* node) {} + virtual void visit(Element* node) {} + virtual void visit(Text* text) {} +}; + +/** + * Visitor whose default implementation visits the children nodes of any node. + */ +struct Visitor : public RawVisitor { + using RawVisitor::visit; + + void visit(Namespace* node) override { + visitChildren(node); + } + + void visit(Element* node) override { + visitChildren(node); + } + + void visit(Text* text) override { + visitChildren(text); + } + + void visitChildren(Node* node) { + for (auto& child : node->children) { + child->accept(this); + } + } +}; + +/** + * An XML DOM visitor that will record the package name for a namespace prefix. + */ +class PackageAwareVisitor : public Visitor, public IPackageDeclStack { +private: + struct PackageDecl { + std::u16string prefix; + ExtractedPackage package; + }; + + std::vector<PackageDecl> mPackageDecls; + +public: + using Visitor::visit; + + void visit(Namespace* ns) override; + Maybe<ExtractedPackage> transformPackageAlias( + const StringPiece16& alias, const StringPiece16& localPackage) const override; +}; + +// Implementations + +template <typename Derived> +void BaseNode<Derived>::accept(RawVisitor* visitor) { + visitor->visit(static_cast<Derived*>(this)); +} + +template <typename T> +struct NodeCastImpl : public RawVisitor { + using RawVisitor::visit; + + T* value = nullptr; + + void visit(T* v) override { + value = v; + } +}; + +template <typename T> +T* nodeCast(Node* node) { + NodeCastImpl<T> visitor; + node->accept(&visitor); + return visitor.value; +} + +} // namespace xml +} // namespace aapt + +#endif // AAPT_XML_DOM_H diff --git a/tools/aapt2/xml/XmlDom_test.cpp b/tools/aapt2/xml/XmlDom_test.cpp new file mode 100644 index 000000000000..431ee2c8fb46 --- /dev/null +++ b/tools/aapt2/xml/XmlDom_test.cpp @@ -0,0 +1,50 @@ +/* + * 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 "xml/XmlDom.h" + +#include <gtest/gtest.h> +#include <sstream> +#include <string> + +namespace aapt { + +constexpr const char* kXmlPreamble = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"; + +TEST(XmlDomTest, Inflate) { + std::stringstream in(kXmlPreamble); + in << R"EOF( + <Layout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content"> + <TextView android:id="@+id/id" + android:layout_width="wrap_content" + android:layout_height="wrap_content" /> + </Layout> + )EOF"; + + const Source source = { "test.xml" }; + StdErrDiagnostics diag; + std::unique_ptr<xml::XmlResource> doc = xml::inflate(&in, &diag, source); + ASSERT_NE(doc, nullptr); + + xml::Namespace* ns = xml::nodeCast<xml::Namespace>(doc->root.get()); + ASSERT_NE(ns, nullptr); + EXPECT_EQ(ns->namespaceUri, u"http://schemas.android.com/apk/res/android"); + EXPECT_EQ(ns->namespacePrefix, u"android"); +} + +} // namespace aapt diff --git a/tools/aapt2/xml/XmlPullParser.cpp b/tools/aapt2/xml/XmlPullParser.cpp new file mode 100644 index 000000000000..323ec05b5f2c --- /dev/null +++ b/tools/aapt2/xml/XmlPullParser.cpp @@ -0,0 +1,308 @@ +/* + * 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 "util/Maybe.h" +#include "util/Util.h" +#include "xml/XmlPullParser.h" +#include "xml/XmlUtil.h" + +#include <iostream> +#include <string> + +namespace aapt { +namespace xml { + +constexpr char kXmlNamespaceSep = 1; + +XmlPullParser::XmlPullParser(std::istream& in) : mIn(in), mEmpty(), mDepth(0) { + mParser = XML_ParserCreateNS(nullptr, kXmlNamespaceSep); + XML_SetUserData(mParser, this); + XML_SetElementHandler(mParser, startElementHandler, endElementHandler); + XML_SetNamespaceDeclHandler(mParser, startNamespaceHandler, endNamespaceHandler); + XML_SetCharacterDataHandler(mParser, characterDataHandler); + XML_SetCommentHandler(mParser, commentDataHandler); + mEventQueue.push(EventData{ Event::kStartDocument, 0, mDepth++ }); +} + +XmlPullParser::~XmlPullParser() { + XML_ParserFree(mParser); +} + +XmlPullParser::Event XmlPullParser::next() { + const Event currentEvent = getEvent(); + if (currentEvent == Event::kBadDocument || currentEvent == Event::kEndDocument) { + return currentEvent; + } + + mEventQueue.pop(); + while (mEventQueue.empty()) { + mIn.read(mBuffer, sizeof(mBuffer) / sizeof(*mBuffer)); + + const bool done = mIn.eof(); + if (mIn.bad() && !done) { + mLastError = strerror(errno); + mEventQueue.push(EventData{ Event::kBadDocument }); + continue; + } + + if (XML_Parse(mParser, mBuffer, mIn.gcount(), done) == XML_STATUS_ERROR) { + mLastError = XML_ErrorString(XML_GetErrorCode(mParser)); + mEventQueue.push(EventData{ Event::kBadDocument }); + continue; + } + + if (done) { + mEventQueue.push(EventData{ Event::kEndDocument, 0, 0 }); + } + } + + Event event = getEvent(); + + // Record namespace prefixes and package names so that we can do our own + // handling of references that use namespace aliases. + if (event == Event::kStartNamespace || event == Event::kEndNamespace) { + Maybe<ExtractedPackage> result = extractPackageFromNamespace(getNamespaceUri()); + if (event == Event::kStartNamespace) { + if (result) { + mPackageAliases.emplace_back( + PackageDecl{ getNamespacePrefix(), std::move(result.value()) }); + } + } else { + if (result) { + mPackageAliases.pop_back(); + } + } + } + + return event; +} + +XmlPullParser::Event XmlPullParser::getEvent() const { + return mEventQueue.front().event; +} + +const std::string& XmlPullParser::getLastError() const { + return mLastError; +} + +const std::u16string& XmlPullParser::getComment() const { + return mEventQueue.front().data1; +} + +size_t XmlPullParser::getLineNumber() const { + return mEventQueue.front().lineNumber; +} + +size_t XmlPullParser::getDepth() const { + return mEventQueue.front().depth; +} + +const std::u16string& XmlPullParser::getText() const { + if (getEvent() != Event::kText) { + return mEmpty; + } + return mEventQueue.front().data1; +} + +const std::u16string& XmlPullParser::getNamespacePrefix() const { + const Event currentEvent = getEvent(); + if (currentEvent != Event::kStartNamespace && currentEvent != Event::kEndNamespace) { + return mEmpty; + } + return mEventQueue.front().data1; +} + +const std::u16string& XmlPullParser::getNamespaceUri() const { + const Event currentEvent = getEvent(); + if (currentEvent != Event::kStartNamespace && currentEvent != Event::kEndNamespace) { + return mEmpty; + } + return mEventQueue.front().data2; +} + +Maybe<ExtractedPackage> XmlPullParser::transformPackageAlias( + const StringPiece16& alias, const StringPiece16& localPackage) const { + if (alias.empty()) { + return ExtractedPackage{ localPackage.toString(), false /* private */ }; + } + + const auto endIter = mPackageAliases.rend(); + for (auto iter = mPackageAliases.rbegin(); iter != endIter; ++iter) { + if (alias == iter->prefix) { + if (iter->package.package.empty()) { + return ExtractedPackage{ localPackage.toString(), + iter->package.privateNamespace }; + } + return iter->package; + } + } + return {}; +} + +const std::u16string& XmlPullParser::getElementNamespace() const { + const Event currentEvent = getEvent(); + if (currentEvent != Event::kStartElement && currentEvent != Event::kEndElement) { + return mEmpty; + } + return mEventQueue.front().data1; +} + +const std::u16string& XmlPullParser::getElementName() const { + const Event currentEvent = getEvent(); + if (currentEvent != Event::kStartElement && currentEvent != Event::kEndElement) { + return mEmpty; + } + return mEventQueue.front().data2; +} + +XmlPullParser::const_iterator XmlPullParser::beginAttributes() const { + return mEventQueue.front().attributes.begin(); +} + +XmlPullParser::const_iterator XmlPullParser::endAttributes() const { + return mEventQueue.front().attributes.end(); +} + +size_t XmlPullParser::getAttributeCount() const { + if (getEvent() != Event::kStartElement) { + return 0; + } + return mEventQueue.front().attributes.size(); +} + +/** + * Extracts the namespace and name of an expanded element or attribute name. + */ +static void splitName(const char* name, std::u16string& outNs, std::u16string& outName) { + const char* p = name; + while (*p != 0 && *p != kXmlNamespaceSep) { + p++; + } + + if (*p == 0) { + outNs = std::u16string(); + outName = util::utf8ToUtf16(name); + } else { + outNs = util::utf8ToUtf16(StringPiece(name, (p - name))); + outName = util::utf8ToUtf16(p + 1); + } +} + +void XMLCALL XmlPullParser::startNamespaceHandler(void* userData, const char* prefix, + const char* uri) { + XmlPullParser* parser = reinterpret_cast<XmlPullParser*>(userData); + std::u16string namespaceUri = uri != nullptr ? util::utf8ToUtf16(uri) : std::u16string(); + parser->mNamespaceUris.push(namespaceUri); + parser->mEventQueue.push(EventData{ + Event::kStartNamespace, + XML_GetCurrentLineNumber(parser->mParser), + parser->mDepth++, + prefix != nullptr ? util::utf8ToUtf16(prefix) : std::u16string(), + namespaceUri + }); +} + +void XMLCALL XmlPullParser::startElementHandler(void* userData, const char* name, + const char** attrs) { + XmlPullParser* parser = reinterpret_cast<XmlPullParser*>(userData); + + EventData data = { + Event::kStartElement, XML_GetCurrentLineNumber(parser->mParser), parser->mDepth++ + }; + splitName(name, data.data1, data.data2); + + while (*attrs) { + Attribute attribute; + splitName(*attrs++, attribute.namespaceUri, attribute.name); + attribute.value = util::utf8ToUtf16(*attrs++); + + // Insert in sorted order. + auto iter = std::lower_bound(data.attributes.begin(), data.attributes.end(), attribute); + data.attributes.insert(iter, std::move(attribute)); + } + + // Move the structure into the queue (no copy). + parser->mEventQueue.push(std::move(data)); +} + +void XMLCALL XmlPullParser::characterDataHandler(void* userData, const char* s, int len) { + XmlPullParser* parser = reinterpret_cast<XmlPullParser*>(userData); + + parser->mEventQueue.push(EventData{ + Event::kText, + XML_GetCurrentLineNumber(parser->mParser), + parser->mDepth, + util::utf8ToUtf16(StringPiece(s, len)) + }); +} + +void XMLCALL XmlPullParser::endElementHandler(void* userData, const char* name) { + XmlPullParser* parser = reinterpret_cast<XmlPullParser*>(userData); + + EventData data = { + Event::kEndElement, XML_GetCurrentLineNumber(parser->mParser), --(parser->mDepth) + }; + splitName(name, data.data1, data.data2); + + // Move the data into the queue (no copy). + parser->mEventQueue.push(std::move(data)); +} + +void XMLCALL XmlPullParser::endNamespaceHandler(void* userData, const char* prefix) { + XmlPullParser* parser = reinterpret_cast<XmlPullParser*>(userData); + + parser->mEventQueue.push(EventData{ + Event::kEndNamespace, + XML_GetCurrentLineNumber(parser->mParser), + --(parser->mDepth), + prefix != nullptr ? util::utf8ToUtf16(prefix) : std::u16string(), + parser->mNamespaceUris.top() + }); + parser->mNamespaceUris.pop(); +} + +void XMLCALL XmlPullParser::commentDataHandler(void* userData, const char* comment) { + XmlPullParser* parser = reinterpret_cast<XmlPullParser*>(userData); + + parser->mEventQueue.push(EventData{ + Event::kComment, + XML_GetCurrentLineNumber(parser->mParser), + parser->mDepth, + util::utf8ToUtf16(comment) + }); +} + +Maybe<StringPiece16> findAttribute(const XmlPullParser* parser, const StringPiece16& name) { + auto iter = parser->findAttribute(u"", name); + if (iter != parser->endAttributes()) { + return StringPiece16(util::trimWhitespace(iter->value)); + } + return {}; +} + +Maybe<StringPiece16> findNonEmptyAttribute(const XmlPullParser* parser, const StringPiece16& name) { + auto iter = parser->findAttribute(u"", name); + if (iter != parser->endAttributes()) { + StringPiece16 trimmed = util::trimWhitespace(iter->value); + if (!trimmed.empty()) { + return trimmed; + } + } + return {}; +} + +} // namespace xml +} // namespace aapt diff --git a/tools/aapt2/xml/XmlPullParser.h b/tools/aapt2/xml/XmlPullParser.h new file mode 100644 index 000000000000..7e7070e5e5ea --- /dev/null +++ b/tools/aapt2/xml/XmlPullParser.h @@ -0,0 +1,298 @@ +/* + * 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. + */ + +#ifndef AAPT_XML_PULL_PARSER_H +#define AAPT_XML_PULL_PARSER_H + +#include "Resource.h" +#include "process/IResourceTableConsumer.h" +#include "util/Maybe.h" +#include "util/StringPiece.h" +#include "xml/XmlUtil.h" + +#include <algorithm> +#include <expat.h> +#include <istream> +#include <ostream> +#include <queue> +#include <stack> +#include <string> +#include <vector> + +namespace aapt { +namespace xml { + +class XmlPullParser : public IPackageDeclStack { +public: + enum class Event { + kBadDocument, + kStartDocument, + kEndDocument, + + kStartNamespace, + kEndNamespace, + kStartElement, + kEndElement, + kText, + kComment, + }; + + /** + * Skips to the next direct descendant node of the given startDepth, + * skipping namespace nodes. + * + * When nextChildNode returns true, you can expect Comments, Text, and StartElement events. + */ + static bool nextChildNode(XmlPullParser* parser, size_t startDepth); + static bool skipCurrentElement(XmlPullParser* parser); + static bool isGoodEvent(Event event); + + XmlPullParser(std::istream& in); + ~XmlPullParser(); + + /** + * Returns the current event that is being processed. + */ + Event getEvent() const; + + const std::string& getLastError() const; + + /** + * Note, unlike XmlPullParser, the first call to next() will return + * StartElement of the first element. + */ + Event next(); + + // + // These are available for all nodes. + // + + const std::u16string& getComment() const; + size_t getLineNumber() const; + size_t getDepth() const; + + /** + * Returns the character data for a Text event. + */ + const std::u16string& getText() const; + + // + // Namespace prefix and URI are available for StartNamespace and EndNamespace. + // + + const std::u16string& getNamespacePrefix() const; + const std::u16string& getNamespaceUri() const; + + // + // These are available for StartElement and EndElement. + // + + const std::u16string& getElementNamespace() const; + const std::u16string& getElementName() const; + + /* + * Uses the current stack of namespaces to resolve the package. Eg: + * xmlns:app = "http://schemas.android.com/apk/res/com.android.app" + * ... + * android:text="@app:string/message" + * + * In this case, 'app' will be converted to 'com.android.app'. + * + * If xmlns:app="http://schemas.android.com/apk/res-auto", then + * 'package' will be set to 'defaultPackage'. + */ + Maybe<ExtractedPackage> transformPackageAlias( + const StringPiece16& alias, const StringPiece16& localPackage) const override; + + // + // Remaining methods are for retrieving information about attributes + // associated with a StartElement. + // + // Attributes must be in sorted order (according to the less than operator + // of struct Attribute). + // + + struct Attribute { + std::u16string namespaceUri; + std::u16string name; + std::u16string value; + + int compare(const Attribute& rhs) const; + bool operator<(const Attribute& rhs) const; + bool operator==(const Attribute& rhs) const; + bool operator!=(const Attribute& rhs) const; + }; + + using const_iterator = std::vector<Attribute>::const_iterator; + + const_iterator beginAttributes() const; + const_iterator endAttributes() const; + size_t getAttributeCount() const; + const_iterator findAttribute(StringPiece16 namespaceUri, StringPiece16 name) const; + +private: + static void XMLCALL startNamespaceHandler(void* userData, const char* prefix, const char* uri); + static void XMLCALL startElementHandler(void* userData, const char* name, const char** attrs); + static void XMLCALL characterDataHandler(void* userData, const char* s, int len); + static void XMLCALL endElementHandler(void* userData, const char* name); + static void XMLCALL endNamespaceHandler(void* userData, const char* prefix); + static void XMLCALL commentDataHandler(void* userData, const char* comment); + + struct EventData { + Event event; + size_t lineNumber; + size_t depth; + std::u16string data1; + std::u16string data2; + std::vector<Attribute> attributes; + }; + + std::istream& mIn; + XML_Parser mParser; + char mBuffer[16384]; + std::queue<EventData> mEventQueue; + std::string mLastError; + const std::u16string mEmpty; + size_t mDepth; + std::stack<std::u16string> mNamespaceUris; + + struct PackageDecl { + std::u16string prefix; + ExtractedPackage package; + }; + std::vector<PackageDecl> mPackageAliases; +}; + +/** + * Finds the attribute in the current element within the global namespace. + */ +Maybe<StringPiece16> findAttribute(const XmlPullParser* parser, const StringPiece16& name); + +/** + * Finds the attribute in the current element within the global namespace. The attribute's value + * must not be the empty string. + */ +Maybe<StringPiece16> findNonEmptyAttribute(const XmlPullParser* parser, const StringPiece16& name); + +// +// Implementation +// + +inline ::std::ostream& operator<<(::std::ostream& out, XmlPullParser::Event event) { + switch (event) { + case XmlPullParser::Event::kBadDocument: return out << "BadDocument"; + case XmlPullParser::Event::kStartDocument: return out << "StartDocument"; + case XmlPullParser::Event::kEndDocument: return out << "EndDocument"; + case XmlPullParser::Event::kStartNamespace: return out << "StartNamespace"; + case XmlPullParser::Event::kEndNamespace: return out << "EndNamespace"; + case XmlPullParser::Event::kStartElement: return out << "StartElement"; + case XmlPullParser::Event::kEndElement: return out << "EndElement"; + case XmlPullParser::Event::kText: return out << "Text"; + case XmlPullParser::Event::kComment: return out << "Comment"; + } + return out; +} + +inline bool XmlPullParser::nextChildNode(XmlPullParser* parser, size_t startDepth) { + Event event; + + // First get back to the start depth. + while (isGoodEvent(event = parser->next()) && parser->getDepth() > startDepth + 1) {} + + // Now look for the first good node. + while ((event != Event::kEndElement || parser->getDepth() > startDepth) && isGoodEvent(event)) { + switch (event) { + case Event::kText: + case Event::kComment: + case Event::kStartElement: + return true; + default: + break; + } + event = parser->next(); + } + return false; +} + +inline bool XmlPullParser::skipCurrentElement(XmlPullParser* parser) { + int depth = 1; + while (depth > 0) { + switch (parser->next()) { + case Event::kEndDocument: + return true; + case Event::kBadDocument: + return false; + case Event::kStartElement: + depth++; + break; + case Event::kEndElement: + depth--; + break; + default: + break; + } + } + return true; +} + +inline bool XmlPullParser::isGoodEvent(XmlPullParser::Event event) { + return event != Event::kBadDocument && event != Event::kEndDocument; +} + +inline int XmlPullParser::Attribute::compare(const Attribute& rhs) const { + int cmp = namespaceUri.compare(rhs.namespaceUri); + if (cmp != 0) return cmp; + return name.compare(rhs.name); +} + +inline bool XmlPullParser::Attribute::operator<(const Attribute& rhs) const { + return compare(rhs) < 0; +} + +inline bool XmlPullParser::Attribute::operator==(const Attribute& rhs) const { + return compare(rhs) == 0; +} + +inline bool XmlPullParser::Attribute::operator!=(const Attribute& rhs) const { + return compare(rhs) != 0; +} + +inline XmlPullParser::const_iterator XmlPullParser::findAttribute(StringPiece16 namespaceUri, + StringPiece16 name) const { + const auto endIter = endAttributes(); + const auto iter = std::lower_bound(beginAttributes(), endIter, + std::pair<StringPiece16, StringPiece16>(namespaceUri, name), + [](const Attribute& attr, const std::pair<StringPiece16, StringPiece16>& rhs) -> bool { + int cmp = attr.namespaceUri.compare(0, attr.namespaceUri.size(), + rhs.first.data(), rhs.first.size()); + if (cmp < 0) return true; + if (cmp > 0) return false; + cmp = attr.name.compare(0, attr.name.size(), rhs.second.data(), rhs.second.size()); + if (cmp < 0) return true; + return false; + } + ); + + if (iter != endIter && namespaceUri == iter->namespaceUri && name == iter->name) { + return iter; + } + return endIter; +} + +} // namespace xml +} // namespace aapt + +#endif // AAPT_XML_PULL_PARSER_H diff --git a/tools/aapt2/xml/XmlPullParser_test.cpp b/tools/aapt2/xml/XmlPullParser_test.cpp new file mode 100644 index 000000000000..8fa2c6d274c8 --- /dev/null +++ b/tools/aapt2/xml/XmlPullParser_test.cpp @@ -0,0 +1,55 @@ +/* + * 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 "util/StringPiece.h" +#include "xml/XmlPullParser.h" + +#include <gtest/gtest.h> +#include <sstream> + +namespace aapt { + +TEST(XmlPullParserTest, NextChildNodeTraversesCorrectly) { + std::stringstream str; + str << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" + "<a><b><c xmlns:a=\"http://schema.org\"><d/></c><e/></b></a>"; + xml::XmlPullParser parser(str); + + const size_t depthOuter = parser.getDepth(); + ASSERT_TRUE(xml::XmlPullParser::nextChildNode(&parser, depthOuter)); + + EXPECT_EQ(xml::XmlPullParser::Event::kStartElement, parser.getEvent()); + EXPECT_EQ(StringPiece16(u"a"), StringPiece16(parser.getElementName())); + + const size_t depthA = parser.getDepth(); + ASSERT_TRUE(xml::XmlPullParser::nextChildNode(&parser, depthA)); + EXPECT_EQ(xml::XmlPullParser::Event::kStartElement, parser.getEvent()); + EXPECT_EQ(StringPiece16(u"b"), StringPiece16(parser.getElementName())); + + const size_t depthB = parser.getDepth(); + ASSERT_TRUE(xml::XmlPullParser::nextChildNode(&parser, depthB)); + EXPECT_EQ(xml::XmlPullParser::Event::kStartElement, parser.getEvent()); + EXPECT_EQ(StringPiece16(u"c"), StringPiece16(parser.getElementName())); + + ASSERT_TRUE(xml::XmlPullParser::nextChildNode(&parser, depthB)); + EXPECT_EQ(xml::XmlPullParser::Event::kStartElement, parser.getEvent()); + EXPECT_EQ(StringPiece16(u"e"), StringPiece16(parser.getElementName())); + + ASSERT_FALSE(xml::XmlPullParser::nextChildNode(&parser, depthOuter)); + EXPECT_EQ(xml::XmlPullParser::Event::kEndDocument, parser.getEvent()); +} + +} // namespace aapt diff --git a/tools/aapt2/xml/XmlUtil.cpp b/tools/aapt2/xml/XmlUtil.cpp new file mode 100644 index 000000000000..ab9f544d67ea --- /dev/null +++ b/tools/aapt2/xml/XmlUtil.cpp @@ -0,0 +1,67 @@ +/* + * 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 "util/Maybe.h" +#include "util/Util.h" +#include "xml/XmlUtil.h" + +#include <string> + +namespace aapt { +namespace xml { + +Maybe<ExtractedPackage> extractPackageFromNamespace(const std::u16string& namespaceUri) { + if (util::stringStartsWith<char16_t>(namespaceUri, kSchemaPublicPrefix)) { + StringPiece16 schemaPrefix = kSchemaPublicPrefix; + StringPiece16 package = namespaceUri; + package = package.substr(schemaPrefix.size(), package.size() - schemaPrefix.size()); + if (package.empty()) { + return {}; + } + return ExtractedPackage{ package.toString(), false /* isPrivate */ }; + + } else if (util::stringStartsWith<char16_t>(namespaceUri, kSchemaPrivatePrefix)) { + StringPiece16 schemaPrefix = kSchemaPrivatePrefix; + StringPiece16 package = namespaceUri; + package = package.substr(schemaPrefix.size(), package.size() - schemaPrefix.size()); + if (package.empty()) { + return {}; + } + return ExtractedPackage{ package.toString(), true /* isPrivate */ }; + + } else if (namespaceUri == kSchemaAuto) { + return ExtractedPackage{ std::u16string(), true /* isPrivate */ }; + } + return {}; +} + +void transformReferenceFromNamespace(IPackageDeclStack* declStack, + const StringPiece16& localPackage, Reference* inRef) { + if (inRef->name) { + if (Maybe<ExtractedPackage> transformedPackage = + declStack->transformPackageAlias(inRef->name.value().package, localPackage)) { + ExtractedPackage& extractedPackage = transformedPackage.value(); + inRef->name.value().package = std::move(extractedPackage.package); + + // If the reference was already private (with a * prefix) and the namespace is public, + // we keep the reference private. + inRef->privateReference |= extractedPackage.privateNamespace; + } + } +} + +} // namespace xml +} // namespace aapt diff --git a/tools/aapt2/xml/XmlUtil.h b/tools/aapt2/xml/XmlUtil.h new file mode 100644 index 000000000000..98e5520a6ea2 --- /dev/null +++ b/tools/aapt2/xml/XmlUtil.h @@ -0,0 +1,85 @@ +/* + * 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. + */ + +#ifndef AAPT_XML_XMLUTIL_H +#define AAPT_XML_XMLUTIL_H + +#include "ResourceValues.h" +#include "util/Maybe.h" + +#include <string> + +namespace aapt { +namespace xml { + +constexpr const char16_t* kSchemaAuto = u"http://schemas.android.com/apk/res-auto"; +constexpr const char16_t* kSchemaPublicPrefix = u"http://schemas.android.com/apk/res/"; +constexpr const char16_t* kSchemaPrivatePrefix = u"http://schemas.android.com/apk/prv/res/"; +constexpr const char16_t* kSchemaAndroid = u"http://schemas.android.com/apk/res/android"; + +/** + * Result of extracting a package name from a namespace URI declaration. + */ +struct ExtractedPackage { + /** + * The name of the package. This can be the empty string, which means that the package + * should be assumed to be the package being compiled. + */ + std::u16string package; + + /** + * True if the package's private namespace was declared. This means that private resources + * are made visible. + */ + bool privateNamespace; +}; + +/** + * Returns an ExtractedPackage struct if the namespace URI is of the form: + * http://schemas.android.com/apk/res/<package> or + * http://schemas.android.com/apk/prv/res/<package> + * + * Special case: if namespaceUri is http://schemas.android.com/apk/res-auto, + * returns an empty package name. + */ +Maybe<ExtractedPackage> extractPackageFromNamespace(const std::u16string& namespaceUri); + +/** + * Interface representing a stack of XML namespace declarations. When looking up the package + * for a namespace prefix, the stack is checked from top to bottom. + */ +struct IPackageDeclStack { + virtual ~IPackageDeclStack() = default; + + /** + * Returns an ExtractedPackage struct if the alias given corresponds with a package declaration. + */ + virtual Maybe<ExtractedPackage> transformPackageAlias( + const StringPiece16& alias, const StringPiece16& localPackage) const = 0; +}; + +/** + * Helper function for transforming the original Reference inRef to a fully qualified reference + * via the IPackageDeclStack. This will also mark the Reference as private if the namespace of + * the package declaration was private. + */ +void transformReferenceFromNamespace(IPackageDeclStack* declStack, + const StringPiece16& localPackage, Reference* inRef); + +} // namespace xml +} // namespace aapt + +#endif /* AAPT_XML_XMLUTIL_H */ diff --git a/tools/aapt2/xml/XmlUtil_test.cpp b/tools/aapt2/xml/XmlUtil_test.cpp new file mode 100644 index 000000000000..7796b3ea7691 --- /dev/null +++ b/tools/aapt2/xml/XmlUtil_test.cpp @@ -0,0 +1,54 @@ +/* + * 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 "test/Common.h" +#include "xml/XmlUtil.h" + +#include <gtest/gtest.h> + +namespace aapt { + +TEST(XmlUtilTest, ExtractPackageFromNamespace) { + AAPT_ASSERT_FALSE(xml::extractPackageFromNamespace(u"com.android")); + AAPT_ASSERT_FALSE(xml::extractPackageFromNamespace(u"http://schemas.android.com/apk")); + AAPT_ASSERT_FALSE(xml::extractPackageFromNamespace(u"http://schemas.android.com/apk/res")); + AAPT_ASSERT_FALSE(xml::extractPackageFromNamespace(u"http://schemas.android.com/apk/res/")); + AAPT_ASSERT_FALSE(xml::extractPackageFromNamespace( + u"http://schemas.android.com/apk/prv/res/")); + + Maybe<xml::ExtractedPackage> p = + xml::extractPackageFromNamespace(u"http://schemas.android.com/apk/res/a"); + AAPT_ASSERT_TRUE(p); + EXPECT_EQ(std::u16string(u"a"), p.value().package); + EXPECT_EQ(false, p.value().privateNamespace); + + p = xml::extractPackageFromNamespace(u"http://schemas.android.com/apk/prv/res/android"); + AAPT_ASSERT_TRUE(p); + EXPECT_EQ(std::u16string(u"android"), p.value().package); + EXPECT_EQ(true, p.value().privateNamespace); + + p = xml::extractPackageFromNamespace(u"http://schemas.android.com/apk/prv/res/com.test"); + AAPT_ASSERT_TRUE(p); + EXPECT_EQ(std::u16string(u"com.test"), p.value().package); + EXPECT_EQ(true, p.value().privateNamespace); + + p = xml::extractPackageFromNamespace(u"http://schemas.android.com/apk/res-auto"); + AAPT_ASSERT_TRUE(p); + EXPECT_EQ(std::u16string(), p.value().package); + EXPECT_EQ(true, p.value().privateNamespace); +} + +} // namespace aapt |