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
diff --git a/tools/aapt2/xml/XmlDom.cpp b/tools/aapt2/xml/XmlDom.cpp
new file mode 100644
index 0000000..d27b62fd
--- /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 0000000..033b0a4d
--- /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 0000000..431ee2c
--- /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 0000000..323ec05
--- /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 0000000..7e7070e
--- /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 0000000..8fa2c6d
--- /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 0000000..ab9f544
--- /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 0000000..98e5520
--- /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 0000000..7796b3e
--- /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