AAPT2: Change xml file parsing to DOM based

We modify the XML of layouts and AndroidManifest enough
that it warrants we operate on the tree in memory.
These files are never very large so this should be fine.

Change-Id: I5d597abdb3fca2a203cf7c0b40fcd926aecb3137
diff --git a/tools/aapt2/Android.mk b/tools/aapt2/Android.mk
index 23d679d..8c128d3 100644
--- a/tools/aapt2/Android.mk
+++ b/tools/aapt2/Android.mk
@@ -27,7 +27,6 @@
 sources := \
 	BigBuffer.cpp \
 	BinaryResourceParser.cpp \
-	BinaryXmlPullParser.cpp \
 	BindingXmlPullParser.cpp \
 	ConfigDescription.cpp \
 	Debug.cpp \
@@ -53,6 +52,7 @@
 	ScopedXmlPullParser.cpp \
 	SourceXmlPullParser.cpp \
 	XliffXmlPullParser.cpp \
+	XmlDom.cpp \
 	XmlFlattener.cpp \
 	ZipEntry.cpp \
 	ZipFile.cpp
@@ -76,6 +76,7 @@
 	StringPool_test.cpp \
 	Util_test.cpp \
 	XliffXmlPullParser_test.cpp \
+	XmlDom_test.cpp \
 	XmlFlattener_test.cpp
 
 cIncludes := \
diff --git a/tools/aapt2/BinaryXmlPullParser.cpp b/tools/aapt2/BinaryXmlPullParser.cpp
deleted file mode 100644
index 476a215..0000000
--- a/tools/aapt2/BinaryXmlPullParser.cpp
+++ /dev/null
@@ -1,259 +0,0 @@
-/*
- * 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 "BinaryXmlPullParser.h"
-#include "Maybe.h"
-#include "Util.h"
-
-#include <androidfw/ResourceTypes.h>
-#include <memory>
-#include <string>
-#include <vector>
-
-namespace aapt {
-
-static XmlPullParser::Event codeToEvent(android::ResXMLParser::event_code_t code) {
-    switch (code) {
-        case android::ResXMLParser::START_DOCUMENT:
-            return XmlPullParser::Event::kStartDocument;
-        case android::ResXMLParser::END_DOCUMENT:
-            return XmlPullParser::Event::kEndDocument;
-        case android::ResXMLParser::START_NAMESPACE:
-            return XmlPullParser::Event::kStartNamespace;
-        case android::ResXMLParser::END_NAMESPACE:
-            return XmlPullParser::Event::kEndNamespace;
-        case android::ResXMLParser::START_TAG:
-            return XmlPullParser::Event::kStartElement;
-        case android::ResXMLParser::END_TAG:
-            return XmlPullParser::Event::kEndElement;
-        case android::ResXMLParser::TEXT:
-            return XmlPullParser::Event::kText;
-        default:
-            break;
-    }
-    return XmlPullParser::Event::kBadDocument;
-}
-
-BinaryXmlPullParser::BinaryXmlPullParser(const std::shared_ptr<android::ResXMLTree>& parser)
-    : mParser(parser), mEvent(Event::kStartDocument), mHasComment(false), sEmpty(), sEmpty8(),
-      mDepth(0) {
-}
-
-XmlPullParser::Event BinaryXmlPullParser::next() {
-    mStr1.clear();
-    mStr2.clear();
-    mAttributes.clear();
-
-    android::ResXMLParser::event_code_t code;
-    if (mHasComment) {
-        mHasComment = false;
-        code = mParser->getEventType();
-    } else {
-        code = mParser->next();
-        if (code != android::ResXMLParser::BAD_DOCUMENT) {
-            size_t len;
-            const char16_t* comment = mParser->getComment(&len);
-            if (comment) {
-                mHasComment = true;
-                mStr1.assign(comment, len);
-                return XmlPullParser::Event::kComment;
-            }
-        }
-    }
-
-    size_t len;
-    const char16_t* data;
-    mEvent = codeToEvent(code);
-    switch (mEvent) {
-        case Event::kStartNamespace:
-        case Event::kEndNamespace: {
-            data = mParser->getNamespacePrefix(&len);
-            if (data) {
-                mStr1.assign(data, len);
-            } else {
-                mStr1.clear();
-            }
-            data = mParser->getNamespaceUri(&len);
-            if (data) {
-                mStr2.assign(data, len);
-            } else {
-                mStr2.clear();
-            }
-
-            Maybe<std::u16string> result = util::extractPackageFromNamespace(mStr2);
-            if (result) {
-                if (mEvent == Event::kStartNamespace) {
-                    mPackageAliases.emplace_back(mStr1, result.value());
-                } else {
-                    assert(mPackageAliases.back().second == result.value());
-                    mPackageAliases.pop_back();
-                }
-            }
-            break;
-        }
-
-        case Event::kStartElement:
-            copyAttributes();
-            // fallthrough
-
-        case Event::kEndElement:
-            data = mParser->getElementNamespace(&len);
-            if (data) {
-                mStr1.assign(data, len);
-            } else {
-                mStr1.clear();
-            }
-            data = mParser->getElementName(&len);
-            if (data) {
-                mStr2.assign(data, len);
-            } else {
-                mStr2.clear();
-            }
-            break;
-
-        case Event::kText:
-            data = mParser->getText(&len);
-            if (data) {
-                mStr1.assign(data, len);
-            } else {
-                mStr1.clear();
-            }
-            break;
-
-        default:
-            break;
-    }
-    return mEvent;
-}
-
-XmlPullParser::Event BinaryXmlPullParser::getEvent() const {
-    if (mHasComment) {
-        return XmlPullParser::Event::kComment;
-    }
-    return mEvent;
-}
-
-const std::string& BinaryXmlPullParser::getLastError() const {
-    return sEmpty8;
-}
-
-const std::u16string& BinaryXmlPullParser::getComment() const {
-    if (mHasComment) {
-        return mStr1;
-    }
-    return sEmpty;
-}
-
-size_t BinaryXmlPullParser::getLineNumber() const {
-    return mParser->getLineNumber();
-}
-
-size_t BinaryXmlPullParser::getDepth() const {
-    return mDepth;
-}
-
-const std::u16string& BinaryXmlPullParser::getText() const {
-    if (!mHasComment && mEvent == XmlPullParser::Event::kText) {
-        return mStr1;
-    }
-    return sEmpty;
-}
-
-const std::u16string& BinaryXmlPullParser::getNamespacePrefix() const {
-    if (!mHasComment && (mEvent == XmlPullParser::Event::kStartNamespace ||
-            mEvent == XmlPullParser::Event::kEndNamespace)) {
-        return mStr1;
-    }
-    return sEmpty;
-}
-
-const std::u16string& BinaryXmlPullParser::getNamespaceUri() const {
-    if (!mHasComment && (mEvent == XmlPullParser::Event::kStartNamespace ||
-            mEvent == XmlPullParser::Event::kEndNamespace)) {
-        return mStr2;
-    }
-    return sEmpty;
-}
-
-bool BinaryXmlPullParser::applyPackageAlias(std::u16string* package,
-                                            const std::u16string& defaultPackage) const {
-    const auto endIter = mPackageAliases.rend();
-    for (auto iter = mPackageAliases.rbegin(); iter != endIter; ++iter) {
-        if (iter->first == *package) {
-            if (iter->second.empty()) {
-                *package = defaultPackage;
-            } else {
-                *package = iter->second;
-            }
-            return true;
-        }
-    }
-    return false;
-}
-
-const std::u16string& BinaryXmlPullParser::getElementNamespace() const {
-    if (!mHasComment && (mEvent == XmlPullParser::Event::kStartElement ||
-            mEvent == XmlPullParser::Event::kEndElement)) {
-        return mStr1;
-    }
-    return sEmpty;
-}
-
-const std::u16string& BinaryXmlPullParser::getElementName() const {
-    if (!mHasComment && (mEvent == XmlPullParser::Event::kStartElement ||
-            mEvent == XmlPullParser::Event::kEndElement)) {
-        return mStr2;
-    }
-    return sEmpty;
-}
-
-size_t BinaryXmlPullParser::getAttributeCount() const {
-    return mAttributes.size();
-}
-
-XmlPullParser::const_iterator BinaryXmlPullParser::beginAttributes() const {
-    return mAttributes.begin();
-}
-
-XmlPullParser::const_iterator BinaryXmlPullParser::endAttributes() const {
-    return mAttributes.end();
-}
-
-void BinaryXmlPullParser::copyAttributes() {
-    const size_t attrCount = mParser->getAttributeCount();
-    if (attrCount > 0) {
-        mAttributes.reserve(attrCount);
-        for (size_t i = 0; i < attrCount; i++) {
-            XmlPullParser::Attribute attr;
-            size_t len;
-            const char16_t* str = mParser->getAttributeNamespace(i, &len);
-            if (str) {
-                attr.namespaceUri.assign(str, len);
-            }
-            str = mParser->getAttributeName(i, &len);
-            if (str) {
-                attr.name.assign(str, len);
-            }
-            str = mParser->getAttributeStringValue(i, &len);
-            if (str) {
-                attr.value.assign(str, len);
-            }
-            mAttributes.push_back(std::move(attr));
-        }
-    }
-}
-
-} // namespace aapt
diff --git a/tools/aapt2/BinaryXmlPullParser.h b/tools/aapt2/BinaryXmlPullParser.h
deleted file mode 100644
index 16fc8b7..0000000
--- a/tools/aapt2/BinaryXmlPullParser.h
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * 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_BINARY_XML_PULL_PARSER_H
-#define AAPT_BINARY_XML_PULL_PARSER_H
-
-#include "XmlPullParser.h"
-
-#include <androidfw/ResourceTypes.h>
-#include <memory>
-#include <string>
-#include <vector>
-
-namespace aapt {
-
-/**
- * Wraps a ResTable into the canonical XmlPullParser interface.
- */
-class BinaryXmlPullParser : public XmlPullParser {
-public:
-    BinaryXmlPullParser(const std::shared_ptr<android::ResXMLTree>& parser);
-    BinaryXmlPullParser(const BinaryXmlPullParser& rhs) = delete;
-
-    Event getEvent() const override;
-    const std::string& getLastError() const override;
-    Event next() override;
-
-    const std::u16string& getComment() const override;
-    size_t getLineNumber() const override;
-    size_t getDepth() const override;
-
-    const std::u16string& getText() const override;
-
-    const std::u16string& getNamespacePrefix() const override;
-    const std::u16string& getNamespaceUri() const override;
-    bool applyPackageAlias(std::u16string* package, const std::u16string& defaultpackage)
-            const override;
-
-    const std::u16string& getElementNamespace() const override;
-    const std::u16string& getElementName() const override;
-
-    const_iterator beginAttributes() const override;
-    const_iterator endAttributes() const override;
-    size_t getAttributeCount() const override;
-
-private:
-    void copyAttributes();
-
-    std::shared_ptr<android::ResXMLTree> mParser;
-    std::u16string mStr1;
-    std::u16string mStr2;
-    std::vector<Attribute> mAttributes;
-    Event mEvent;
-    bool mHasComment;
-    const std::u16string sEmpty;
-    const std::string sEmpty8;
-    size_t mDepth;
-    std::vector<std::pair<std::u16string, std::u16string>> mPackageAliases;
-};
-
-} // namespace aapt
-
-#endif // AAPT_BINARY_XML_PULL_PARSER_H
diff --git a/tools/aapt2/Main.cpp b/tools/aapt2/Main.cpp
index 91639c5..84957b4 100644
--- a/tools/aapt2/Main.cpp
+++ b/tools/aapt2/Main.cpp
@@ -17,7 +17,6 @@
 #include "AppInfo.h"
 #include "BigBuffer.h"
 #include "BinaryResourceParser.h"
-#include "BinaryXmlPullParser.h"
 #include "BindingXmlPullParser.h"
 #include "Debug.h"
 #include "Files.h"
@@ -128,7 +127,7 @@
                     auto iter = style.entries.begin();
                     while (iter != style.entries.end()) {
                         if (iter->key.name.package == u"android") {
-                            size_t sdkLevel = findAttributeSdkLevel(iter->key.name.entry);
+                            size_t sdkLevel = findAttributeSdkLevel(iter->key.name);
                             if (sdkLevel > 1 && sdkLevel > configValue.config.sdkVersion) {
                                 // Record that we are about to strip this.
                                 stripped.emplace_back(std::move(*iter));
@@ -300,6 +299,42 @@
     ResourceName dumpStyleTarget;
 };
 
+struct IdCollector : public xml::Visitor {
+    IdCollector(const Source& source, const std::shared_ptr<ResourceTable>& table) :
+            mSource(source), mTable(table) {
+    }
+
+    virtual void visit(xml::Text* node) override {}
+
+    virtual void visit(xml::Namespace* node) override {
+        for (const auto& child : node->children) {
+            child->accept(this);
+        }
+    }
+
+    virtual void visit(xml::Element* node) override {
+        for (const xml::Attribute& attr : node->attributes) {
+            bool create = false;
+            bool priv = false;
+            ResourceNameRef nameRef;
+            if (ResourceParser::tryParseReference(attr.value, &nameRef, &create, &priv)) {
+                if (create) {
+                    mTable->addResource(nameRef, {}, mSource.line(node->lineNumber),
+                                        util::make_unique<Id>());
+                }
+            }
+        }
+
+        for (const auto& child : node->children) {
+            child->accept(this);
+        }
+    }
+
+private:
+    Source mSource;
+    std::shared_ptr<ResourceTable> mTable;
+};
+
 bool compileXml(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table,
                 const CompileItem& item, ZipFile* outApk) {
     std::ifstream in(item.source.path, std::ifstream::binary);
@@ -308,20 +343,19 @@
         return false;
     }
 
+    SourceLogger logger(item.source);
+    std::unique_ptr<xml::Node> root = xml::inflate(&in, &logger);
+    if (!root) {
+        return false;
+    }
+
+    // Collect any resource ID's declared here.
+    IdCollector idCollector(item.source, table);
+    root->accept(&idCollector);
+
     BigBuffer outBuffer(1024);
-
-    // No resolver, since we are not compiling attributes here.
-    XmlFlattener flattener(table, {});
-
-    XmlFlattener::Options xmlOptions;
-    xmlOptions.defaultPackage = table->getPackage();
-    xmlOptions.keepRawValues = true;
-
-    std::shared_ptr<XmlPullParser> parser = std::make_shared<SourceXmlPullParser>(in);
-
-    Maybe<size_t> minStrippedSdk = flattener.flatten(item.source, parser, &outBuffer,
-                                                     xmlOptions);
-    if (!minStrippedSdk) {
+    if (!xml::flatten(root.get(), options.appInfo.package, &outBuffer)) {
+        logger.error() << "failed to encode XML." << std::endl;
         return false;
     }
 
@@ -369,19 +403,13 @@
 bool linkXml(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table,
              const std::shared_ptr<IResolver>& resolver, const LinkItem& item,
              const void* data, size_t dataLen, ZipFile* outApk, std::queue<LinkItem>* outQueue) {
-    std::shared_ptr<android::ResXMLTree> tree = std::make_shared<android::ResXMLTree>();
-    if (tree->setTo(data, dataLen, false) != android::NO_ERROR) {
+    SourceLogger logger(item.source);
+    std::unique_ptr<xml::Node> root = xml::inflate(data, dataLen, &logger);
+    if (!root) {
         return false;
     }
 
-    std::shared_ptr<XmlPullParser> parser = std::make_shared<BinaryXmlPullParser>(tree);
-
-    BigBuffer outBuffer(1024);
-    XmlFlattener flattener({}, resolver);
-
-    XmlFlattener::Options xmlOptions;
-    xmlOptions.defaultPackage = item.originalPackage;
-
+    xml::FlattenOptions xmlOptions;
     if (options.packageType == AaptOptions::PackageType::StaticLibrary) {
         xmlOptions.keepRawValues = true;
     }
@@ -392,16 +420,12 @@
         xmlOptions.maxSdkAttribute = item.config.sdkVersion ? item.config.sdkVersion : 1;
     }
 
-    std::shared_ptr<BindingXmlPullParser> binding;
-    if (item.name.type == ResourceType::kLayout) {
-        // Layouts may have defined bindings, so we need to make sure they get processed.
-        binding = std::make_shared<BindingXmlPullParser>(parser);
-        parser = binding;
-    }
-
-    Maybe<size_t> minStrippedSdk = flattener.flatten(item.source, parser, &outBuffer,
-                                                     xmlOptions);
+    BigBuffer outBuffer(1024);
+    Maybe<size_t> minStrippedSdk = xml::flattenAndLink(item.source, root.get(),
+                                                       item.originalPackage, resolver,
+                                                       xmlOptions, &outBuffer);
     if (!minStrippedSdk) {
+        logger.error() << "failed to encode XML." << std::endl;
         return false;
     }
 
@@ -431,30 +455,6 @@
                                       << buildFileReference(item) << "' to apk." << std::endl;
         return false;
     }
-
-    if (binding && !options.bindingOutput.path.empty()) {
-        // We generated a binding xml file, write it out.
-        Source bindingOutput = options.bindingOutput;
-        appendPath(&bindingOutput.path, buildFileReference(item));
-
-        if (!mkdirs(bindingOutput.path)) {
-            Logger::error(bindingOutput) << strerror(errno) << std::endl;
-            return false;
-        }
-
-        appendPath(&bindingOutput.path, "bind.xml");
-
-        std::ofstream bout(bindingOutput.path);
-        if (!bout) {
-            Logger::error(bindingOutput) << strerror(errno) << std::endl;
-            return false;
-        }
-
-        if (!binding->writeToFile(bout)) {
-            Logger::error(bindingOutput) << strerror(errno) << std::endl;
-            return false;
-        }
-    }
     return true;
 }
 
@@ -504,13 +504,15 @@
         return false;
     }
 
-    BigBuffer outBuffer(1024);
-    std::shared_ptr<XmlPullParser> xmlParser = std::make_shared<SourceXmlPullParser>(in);
-    XmlFlattener flattener({}, resolver);
+    SourceLogger logger(options.manifest);
+    std::unique_ptr<xml::Node> root = xml::inflate(&in, &logger);
+    if (!root) {
+        return false;
+    }
 
-    XmlFlattener::Options xmlOptions;
-    xmlOptions.defaultPackage = options.appInfo.package;
-    if (!flattener.flatten(options.manifest, xmlParser, &outBuffer, xmlOptions)) {
+    BigBuffer outBuffer(1024);
+    if (!xml::flattenAndLink(options.manifest, root.get(), options.appInfo.package, resolver, {},
+                &outBuffer)) {
         return false;
     }
 
diff --git a/tools/aapt2/SdkConstants.cpp b/tools/aapt2/SdkConstants.cpp
index 3f156a6..9bdae49 100644
--- a/tools/aapt2/SdkConstants.cpp
+++ b/tools/aapt2/SdkConstants.cpp
@@ -14,11 +14,51 @@
  * limitations under the License.
  */
 
+#include "SdkConstants.h"
+
+#include <algorithm>
 #include <string>
 #include <unordered_map>
+#include <vector>
 
 namespace aapt {
 
+static const std::vector<std::pair<uint16_t, size_t>> sAttrIdMap = {
+    { 0x021c, 1 },
+    { 0x021d, 2 },
+    { 0x0269, SDK_CUPCAKE },
+    { 0x028d, SDK_DONUT },
+    { 0x02ad, SDK_ECLAIR },
+    { 0x02b3, SDK_ECLAIR_0_1 },
+    { 0x02b5, SDK_ECLAIR_MR1 },
+    { 0x02bd, SDK_FROYO },
+    { 0x02cb, SDK_GINGERBREAD },
+    { 0x0361, SDK_HONEYCOMB },
+    { 0x0366, SDK_HONEYCOMB_MR1 },
+    { 0x03a6, SDK_HONEYCOMB_MR2 },
+    { 0x03ae, SDK_JELLY_BEAN },
+    { 0x03cc, SDK_JELLY_BEAN_MR1 },
+    { 0x03da, SDK_JELLY_BEAN_MR2 },
+    { 0x03f1, SDK_KITKAT },
+    { 0x03f6, SDK_KITKAT_WATCH },
+    { 0x04ce, SDK_LOLLIPOP },
+};
+
+static bool lessEntryId(const std::pair<uint16_t, size_t>& p, uint16_t entryId) {
+    return p.first < entryId;
+}
+
+size_t findAttributeSdkLevel(ResourceId id) {
+    if (id.packageId() != 0x01 && id.typeId() != 0x01) {
+        return 0;
+    }
+    auto iter = std::lower_bound(sAttrIdMap.begin(), sAttrIdMap.end(), id.entryId(), lessEntryId);
+    if (iter == sAttrIdMap.end()) {
+        return SDK_LOLLIPOP_MR1;
+    }
+    return iter->second;
+}
+
 static const std::unordered_map<std::u16string, size_t> sAttrMap = {
     { u"marqueeRepeatLimit", 2 },
     { u"windowNoDisplay", 3 },
@@ -682,12 +722,16 @@
     { u"colorEdgeEffect", 21 }
 };
 
-size_t findAttributeSdkLevel(const std::u16string& name) {
-    auto iter = sAttrMap.find(name);
+size_t findAttributeSdkLevel(const ResourceName& name) {
+    if (name.package != u"android" && name.type != ResourceType::kAttr) {
+        return 0;
+    }
+
+    auto iter = sAttrMap.find(name.entry);
     if (iter != sAttrMap.end()) {
         return iter->second;
     }
-    return 0;
+    return SDK_LOLLIPOP_MR1;
 }
 
 } // namespace aapt
diff --git a/tools/aapt2/SdkConstants.h b/tools/aapt2/SdkConstants.h
index 469c355..803da03 100644
--- a/tools/aapt2/SdkConstants.h
+++ b/tools/aapt2/SdkConstants.h
@@ -19,8 +19,6 @@
 
 #include "Resource.h"
 
-#include <string>
-
 namespace aapt {
 
 enum {
@@ -46,7 +44,8 @@
     SDK_LOLLIPOP_MR1 = 22,
 };
 
-size_t findAttributeSdkLevel(const std::u16string& name);
+size_t findAttributeSdkLevel(ResourceId id);
+size_t findAttributeSdkLevel(const ResourceName& name);
 
 } // namespace aapt
 
diff --git a/tools/aapt2/XmlDom.cpp b/tools/aapt2/XmlDom.cpp
new file mode 100644
index 0000000..763029f
--- /dev/null
+++ b/tools/aapt2/XmlDom.cpp
@@ -0,0 +1,431 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Logger.h"
+#include "Util.h"
+#include "XmlDom.h"
+#include "XmlPullParser.h"
+
+#include <cassert>
+#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 (thisNode->type != NodeType::kText) {
+        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 (lastChild->type == NodeType::kText) {
+                Text* text = static_cast<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<Node> inflate(std::istream* in, SourceLogger* logger) {
+    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 = {};
+            logger->error() << strerror(errno) << std::endl;
+            break;
+        }
+
+        if (XML_Parse(parser, buffer, in->gcount(), in->eof()) == XML_STATUS_ERROR) {
+            stack.root = {};
+            logger->error(XML_GetCurrentLineNumber(parser))
+                    << XML_ErrorString(XML_GetErrorCode(parser)) << std::endl;
+            break;
+        }
+    }
+
+    XML_ParserFree(parser);
+    return std::move(stack.root);
+}
+
+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<Node> inflate(const void* data, size_t dataLen, SourceLogger* logger) {
+    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 (thisNode->type != NodeType::kText) {
+                nodeStack.push(thisNode);
+            }
+        }
+    }
+    return std::move(root);
+}
+
+Node::Node(NodeType type) : type(type), parent(nullptr), lineNumber(0), columnNumber(0) {
+}
+
+void Node::addChild(std::unique_ptr<Node> child) {
+    child->parent = this;
+    children.push_back(std::move(child));
+}
+
+Namespace::Namespace() : BaseNode(NodeType::kNamespace) {
+}
+
+std::unique_ptr<Node> Namespace::clone() const {
+    Namespace* ns = new Namespace();
+    ns->lineNumber = lineNumber;
+    ns->columnNumber = columnNumber;
+    ns->comment = comment;
+    ns->namespacePrefix = namespacePrefix;
+    ns->namespaceUri = namespaceUri;
+    for (auto& child : children) {
+        ns->addChild(child->clone());
+    }
+    return std::unique_ptr<Node>(ns);
+}
+
+Element::Element() : BaseNode(NodeType::kElement) {
+}
+
+std::unique_ptr<Node> Element::clone() const {
+    Element* el = new Element();
+    el->lineNumber = lineNumber;
+    el->columnNumber = columnNumber;
+    el->comment = comment;
+    el->namespaceUri = namespaceUri;
+    el->name = name;
+    el->attributes = attributes;
+    for (auto& child : children) {
+        el->addChild(child->clone());
+    }
+    return std::unique_ptr<Node>(el);
+}
+
+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, nullptr);
+}
+
+Element* Element::findChildWithAttribute(const StringPiece16& ns, const StringPiece16& name,
+                                         const Attribute* reqAttr) {
+    for (auto& childNode : children) {
+        Node* child = childNode.get();
+        while (child->type == NodeType::kNamespace) {
+            if (child->children.empty()) {
+                break;
+            }
+            child = child->children[0].get();
+        }
+
+        if (child->type == NodeType::kElement) {
+            Element* el = static_cast<Element*>(child);
+            if (ns == el->namespaceUri && name == el->name) {
+                if (!reqAttr) {
+                    return el;
+                }
+
+                Attribute* attrName = el->findAttribute(reqAttr->namespaceUri, reqAttr->name);
+                if (attrName && attrName->value == reqAttr->value) {
+                    return el;
+                }
+            }
+        }
+    }
+    return nullptr;
+}
+
+std::vector<Element*> Element::getChildElements() {
+    std::vector<Element*> elements;
+    for (auto& childNode : children) {
+        Node* child = childNode.get();
+        while (child->type == NodeType::kNamespace) {
+            if (child->children.empty()) {
+                break;
+            }
+            child = child->children[0].get();
+        }
+
+        if (child->type == NodeType::kElement) {
+            elements.push_back(static_cast<Element*>(child));
+        }
+    }
+    return elements;
+}
+
+Text::Text() : BaseNode(NodeType::kText) {
+}
+
+std::unique_ptr<Node> Text::clone() const {
+    Text* el = new Text();
+    el->lineNumber = lineNumber;
+    el->columnNumber = columnNumber;
+    el->comment = comment;
+    el->text = text;
+    return std::unique_ptr<Node>(el);
+}
+
+} // namespace xml
+} // namespace aapt
diff --git a/tools/aapt2/XmlDom.h b/tools/aapt2/XmlDom.h
new file mode 100644
index 0000000..6931884
--- /dev/null
+++ b/tools/aapt2/XmlDom.h
@@ -0,0 +1,154 @@
+/*
+ * 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 "Logger.h"
+#include "StringPiece.h"
+
+#include <istream>
+#include <libexpat/expat.h>
+#include <memory>
+#include <string>
+#include <vector>
+
+namespace aapt {
+namespace xml {
+
+struct Visitor;
+
+/**
+ * The type of node. Can be used to downcast to the concrete XML node
+ * class.
+ */
+enum class NodeType {
+    kNamespace,
+    kElement,
+    kText,
+};
+
+/**
+ * Base class for all XML nodes.
+ */
+struct Node {
+    NodeType type;
+    Node* parent;
+    size_t lineNumber;
+    size_t columnNumber;
+    std::u16string comment;
+    std::vector<std::unique_ptr<Node>> children;
+
+    Node(NodeType type);
+    void addChild(std::unique_ptr<Node> child);
+    virtual std::unique_ptr<Node> clone() const = 0;
+    virtual void accept(Visitor* visitor) = 0;
+    virtual ~Node() {}
+};
+
+/**
+ * Base class that implements the visitor methods for a
+ * subclass of Node.
+ */
+template <typename Derived>
+struct BaseNode : public Node {
+    BaseNode(NodeType t);
+    virtual void accept(Visitor* visitor) override;
+};
+
+/**
+ * A Namespace XML node. Can only have one child.
+ */
+struct Namespace : public BaseNode<Namespace> {
+    std::u16string namespacePrefix;
+    std::u16string namespaceUri;
+
+    Namespace();
+    virtual std::unique_ptr<Node> clone() const override;
+};
+
+/**
+ * An XML attribute.
+ */
+struct Attribute {
+    std::u16string namespaceUri;
+    std::u16string name;
+    std::u16string value;
+};
+
+/**
+ * An Element XML node.
+ */
+struct Element : public BaseNode<Element> {
+    std::u16string namespaceUri;
+    std::u16string name;
+    std::vector<Attribute> attributes;
+
+    Element();
+    virtual std::unique_ptr<Node> clone() const override;
+    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 xml::Attribute* reqAttr);
+    std::vector<xml::Element*> getChildElements();
+};
+
+/**
+ * A Text (CDATA) XML node. Can not have any children.
+ */
+struct Text : public BaseNode<Text> {
+    std::u16string text;
+
+    Text();
+    virtual std::unique_ptr<Node> clone() const override;
+};
+
+/**
+ * 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<Node> inflate(std::istream* in, SourceLogger* logger);
+
+/**
+ * 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<Node> inflate(const void* data, size_t dataLen, SourceLogger* logger);
+
+/**
+ * A visitor interface for the different XML Node subtypes.
+ */
+struct Visitor {
+    virtual void visit(Namespace* node) = 0;
+    virtual void visit(Element* node) = 0;
+    virtual void visit(Text* text) = 0;
+};
+
+// Implementations
+
+template <typename Derived>
+BaseNode<Derived>::BaseNode(NodeType type) : Node(type) {
+}
+
+template <typename Derived>
+void BaseNode<Derived>::accept(Visitor* visitor) {
+    visitor->visit(static_cast<Derived*>(this));
+}
+
+} // namespace xml
+} // namespace aapt
+
+#endif // AAPT_XML_DOM_H
diff --git a/tools/aapt2/XmlDom_test.cpp b/tools/aapt2/XmlDom_test.cpp
new file mode 100644
index 0000000..0217144
--- /dev/null
+++ b/tools/aapt2/XmlDom_test.cpp
@@ -0,0 +1,49 @@
+/*
+ * 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 <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";
+
+    SourceLogger logger(Source{ "/test/path" });
+    std::unique_ptr<xml::Node> root = xml::inflate(&in, &logger);
+    ASSERT_NE(root, nullptr);
+
+    EXPECT_EQ(root->type, xml::NodeType::kNamespace);
+    xml::Namespace* ns = static_cast<xml::Namespace*>(root.get());
+    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/XmlFlattener.cpp b/tools/aapt2/XmlFlattener.cpp
index f78e38d..56b5613d 100644
--- a/tools/aapt2/XmlFlattener.cpp
+++ b/tools/aapt2/XmlFlattener.cpp
@@ -34,425 +34,444 @@
 #include <vector>
 
 namespace aapt {
+namespace xml {
 
-constexpr const char16_t* kSchemaAndroid = u"http://schemas.android.com/apk/res/android";
+constexpr uint32_t kLowPriority = 0xffffffffu;
 
-struct AttributeValueFlattener : ValueVisitor {
-    AttributeValueFlattener(
-            std::shared_ptr<IResolver> resolver, SourceLogger* logger,
-            android::Res_value* outValue, std::shared_ptr<XmlPullParser> parser, bool* outError,
-            StringPool::Ref rawValue, std::u16string* defaultPackage,
-            std::vector<std::pair<StringPool::Ref, android::ResStringPool_ref*>>* outStringRefs) :
-            mResolver(resolver), mLogger(logger), mOutValue(outValue), mParser(parser),
-            mError(outError), mRawValue(rawValue), mDefaultPackage(defaultPackage),
-            mStringRefs(outStringRefs) {
+// A vector that maps String refs to their final destination in the out buffer.
+using FlatStringRefList = std::vector<std::pair<StringPool::Ref, android::ResStringPool_ref*>>;
+
+struct XmlFlattener : public Visitor {
+    XmlFlattener(BigBuffer* outBuffer, StringPool* pool, FlatStringRefList* stringRefs,
+                 const std::u16string& defaultPackage) :
+            mOut(outBuffer), mPool(pool), mStringRefs(stringRefs),
+            mDefaultPackage(defaultPackage) {
     }
 
-    void visit(Reference& reference, ValueVisitorArgs&) override {
-        // First see if we can convert the package name from a prefix to a real
-        // package name.
-        ResourceName aliasedName = reference.name;
+    // No copying.
+    XmlFlattener(const XmlFlattener&) = delete;
+    XmlFlattener& operator=(const XmlFlattener&) = delete;
 
-        if (!reference.name.package.empty()) {
-            // Only if we specified a package do we look for its alias.
-            mParser->applyPackageAlias(&reference.name.package, *mDefaultPackage);
-        } else {
-            reference.name.package = *mDefaultPackage;
+    void writeNamespace(Namespace* node, uint16_t type) {
+        const size_t startIndex = mOut->size();
+        android::ResXMLTree_node* flatNode = mOut->nextBlock<android::ResXMLTree_node>();
+        android::ResXMLTree_namespaceExt* flatNs =
+                mOut->nextBlock<android::ResXMLTree_namespaceExt>();
+        mOut->align4();
+
+        flatNode->header = { type, sizeof(*flatNode), (uint32_t)(mOut->size() - startIndex) };
+        flatNode->lineNumber = node->lineNumber;
+        flatNode->comment.index = -1;
+        addString(node->namespacePrefix, kLowPriority, &flatNs->prefix);
+        addString(node->namespaceUri, kLowPriority, &flatNs->uri);
+    }
+
+    virtual void visit(Namespace* node) override {
+        // Extract the package/prefix from this namespace node.
+        Maybe<std::u16string> package = util::extractPackageFromNamespace(node->namespaceUri);
+        if (package) {
+            mPackageAliases.emplace_back(
+                    node->namespacePrefix,
+                    package.value().empty() ? mDefaultPackage : package.value());
         }
 
-        Maybe<ResourceId> result = mResolver->findId(reference.name);
-        if (!result || !result.value().isValid()) {
-            std::ostream& out = mLogger->error(mParser->getLineNumber())
-                    << "unresolved reference '"
-                    << aliasedName
-                    << "'";
-            if (aliasedName != reference.name) {
-                out << " (aka '" << reference.name << "')";
+        writeNamespace(node, android::RES_XML_START_NAMESPACE_TYPE);
+        for (const auto& child : node->children) {
+            child->accept(this);
+        }
+        writeNamespace(node, android::RES_XML_END_NAMESPACE_TYPE);
+
+        if (package) {
+            mPackageAliases.pop_back();
+        }
+    }
+
+    virtual void visit(Text* node) override {
+        if (util::trimWhitespace(node->text).empty()) {
+            return;
+        }
+
+        const size_t startIndex = mOut->size();
+        android::ResXMLTree_node* flatNode = mOut->nextBlock<android::ResXMLTree_node>();
+        android::ResXMLTree_cdataExt* flatText = mOut->nextBlock<android::ResXMLTree_cdataExt>();
+        mOut->align4();
+
+        const uint16_t type = android::RES_XML_CDATA_TYPE;
+        flatNode->header = { type, sizeof(*flatNode), (uint32_t)(mOut->size() - startIndex) };
+        flatNode->lineNumber = node->lineNumber;
+        flatNode->comment.index = -1;
+        addString(node->text, kLowPriority, &flatText->data);
+    }
+
+    virtual void visit(Element* node) override {
+        const size_t startIndex = mOut->size();
+        android::ResXMLTree_node* flatNode = mOut->nextBlock<android::ResXMLTree_node>();
+        android::ResXMLTree_attrExt* flatElem = mOut->nextBlock<android::ResXMLTree_attrExt>();
+
+        const uint16_t type = android::RES_XML_START_ELEMENT_TYPE;
+        flatNode->header = { type, sizeof(*flatNode), 0 };
+        flatNode->lineNumber = node->lineNumber;
+        flatNode->comment.index = -1;
+
+        addString(node->namespaceUri, kLowPriority, &flatElem->ns);
+        addString(node->name, kLowPriority, &flatElem->name);
+        flatElem->attributeStart = sizeof(*flatElem);
+        flatElem->attributeSize = sizeof(android::ResXMLTree_attribute);
+        flatElem->attributeCount = node->attributes.size();
+
+        if (!writeAttributes(mOut, node, flatElem)) {
+            mError = true;
+        }
+
+        mOut->align4();
+        flatNode->header.size = (uint32_t)(mOut->size() - startIndex);
+
+        for (const auto& child : node->children) {
+            child->accept(this);
+        }
+
+        const size_t startEndIndex = mOut->size();
+        android::ResXMLTree_node* flatEndNode = mOut->nextBlock<android::ResXMLTree_node>();
+        android::ResXMLTree_endElementExt* flatEndElem =
+                mOut->nextBlock<android::ResXMLTree_endElementExt>();
+        mOut->align4();
+
+        const uint16_t endType = android::RES_XML_END_ELEMENT_TYPE;
+        flatEndNode->header = { endType, sizeof(*flatEndNode),
+                (uint32_t)(mOut->size() - startEndIndex) };
+        flatEndNode->lineNumber = node->lineNumber;
+        flatEndNode->comment.index = -1;
+
+        addString(node->namespaceUri, kLowPriority, &flatEndElem->ns);
+        addString(node->name, kLowPriority, &flatEndElem->name);
+    }
+
+    bool success() const {
+        return !mError;
+    }
+
+protected:
+    void addString(const StringPiece16& str, uint32_t priority, android::ResStringPool_ref* dest) {
+        if (!str.empty()) {
+            mStringRefs->emplace_back(mPool->makeRef(str, StringPool::Context{ priority }), dest);
+        } else {
+            // The device doesn't think a string of size 0 is the same as null.
+            dest->index = -1;
+        }
+    }
+
+    void addString(const StringPool::Ref& ref, android::ResStringPool_ref* dest) {
+        mStringRefs->emplace_back(ref, dest);
+    }
+
+    Maybe<std::u16string> getPackageAlias(const std::u16string& prefix) {
+        const auto endIter = mPackageAliases.rend();
+        for (auto iter = mPackageAliases.rbegin(); iter != endIter; ++iter) {
+            if (iter->first == prefix) {
+                return iter->second;
             }
-            out << "'." << std::endl;
-            *mError = true;
-        } else {
-            reference.id = result.value();
-            reference.flatten(*mOutValue);
         }
+        return {};
     }
 
-    void visit(String& string, ValueVisitorArgs&) override {
-        mOutValue->dataType = android::Res_value::TYPE_STRING;
-        mStringRefs->emplace_back(
-                mRawValue,
-                reinterpret_cast<android::ResStringPool_ref*>(mOutValue->data));
+    const std::u16string& getDefaultPackage() const {
+        return mDefaultPackage;
     }
 
-    void visitItem(Item& item, ValueVisitorArgs&) override {
-        item.flatten(*mOutValue);
-    }
+    /**
+     * Subclasses override this to deal with attributes. Attributes can be flattened as
+     * raw values or as resources.
+     */
+    virtual bool writeAttributes(BigBuffer* out, Element* node,
+                                 android::ResXMLTree_attrExt* flatElem) = 0;
 
 private:
-    std::shared_ptr<IResolver> mResolver;
-    SourceLogger* mLogger;
-    android::Res_value* mOutValue;
-    std::shared_ptr<XmlPullParser> mParser;
-    bool* mError;
-    StringPool::Ref mRawValue;
-    std::u16string* mDefaultPackage;
-    std::vector<std::pair<StringPool::Ref, android::ResStringPool_ref*>>* mStringRefs;
+    BigBuffer* mOut;
+    StringPool* mPool;
+    FlatStringRefList* mStringRefs;
+    std::u16string mDefaultPackage;
+    bool mError = false;
+    std::vector<std::pair<std::u16string, std::u16string>> mPackageAliases;
 };
 
-struct XmlAttribute {
-    uint32_t resourceId;
-    const XmlPullParser::Attribute* xmlAttr;
-    const Attribute* attr;
-    StringPool::Ref nameRef;
+/**
+ * Flattens XML, encoding the attributes as raw strings. This is used in the compile phase.
+ */
+struct CompileXmlFlattener : public XmlFlattener {
+    CompileXmlFlattener(BigBuffer* outBuffer, StringPool* pool, FlatStringRefList* stringRefs,
+                        const std::u16string& defaultPackage) :
+            XmlFlattener(outBuffer, pool, stringRefs, defaultPackage) {
+    }
+
+    virtual bool writeAttributes(BigBuffer* out, Element* node,
+                                 android::ResXMLTree_attrExt* flatElem) override {
+        flatElem->attributeCount = node->attributes.size();
+        if (node->attributes.empty()) {
+            return true;
+        }
+
+        android::ResXMLTree_attribute* flatAttrs = out->nextBlock<android::ResXMLTree_attribute>(
+                node->attributes.size());
+        for (const Attribute& attr : node->attributes) {
+            addString(attr.namespaceUri, kLowPriority, &flatAttrs->ns);
+            addString(attr.name, kLowPriority, &flatAttrs->name);
+            addString(attr.value, kLowPriority, &flatAttrs->rawValue);
+            flatAttrs++;
+        }
+        return true;
+    }
 };
 
-static bool lessAttributeId(const XmlAttribute& a, uint32_t id) {
+struct AttributeToFlatten {
+    uint32_t resourceId = 0;
+    const Attribute* xmlAttr = nullptr;
+    const ::aapt::Attribute* resourceAttr = nullptr;
+};
+
+static bool lessAttributeId(const AttributeToFlatten& a, uint32_t id) {
     return a.resourceId < id;
 }
 
-XmlFlattener::XmlFlattener(const std::shared_ptr<ResourceTable>& table,
-                           const std::shared_ptr<IResolver>& resolver) :
-        mTable(table), mResolver(resolver) {
-}
+/**
+ * Flattens XML, encoding the attributes as resources.
+ */
+struct LinkedXmlFlattener : public XmlFlattener {
+    LinkedXmlFlattener(BigBuffer* outBuffer, StringPool* pool,
+                       std::map<std::u16string, StringPool>* packagePools,
+                       FlatStringRefList* stringRefs,
+                       const std::u16string& defaultPackage,
+                       const std::shared_ptr<IResolver>& resolver,
+                       SourceLogger* logger,
+                       const FlattenOptions& options) :
+            XmlFlattener(outBuffer, pool, stringRefs, defaultPackage), mResolver(resolver),
+            mLogger(logger), mPackagePools(packagePools), mOptions(options) {
+    }
+
+    virtual bool writeAttributes(BigBuffer* out, Element* node,
+                                 android::ResXMLTree_attrExt* flatElem) override {
+        bool error = false;
+        std::vector<AttributeToFlatten> sortedAttributes;
+        uint32_t nextAttributeId = 0x80000000u;
+
+        // Sort and filter attributes by their resource ID.
+        for (const Attribute& attr : node->attributes) {
+            AttributeToFlatten attrToFlatten;
+            attrToFlatten.xmlAttr = &attr;
+
+            Maybe<std::u16string> package = util::extractPackageFromNamespace(attr.namespaceUri);
+            if (package) {
+                // Find the Attribute object via our Resolver.
+                ResourceName attrName = { package.value(), ResourceType::kAttr, attr.name };
+                if (attrName.package.empty()) {
+                    attrName.package = getDefaultPackage();
+                }
+
+                Maybe<IResolver::Entry> result = mResolver->findAttribute(attrName);
+                if (!result || !result.value().id.isValid() || !result.value().attr) {
+                    error = true;
+                    mLogger->error(node->lineNumber)
+                            << "unresolved attribute '" << attrName << "'."
+                            << std::endl;
+                } else {
+                    attrToFlatten.resourceId = result.value().id.id;
+                    attrToFlatten.resourceAttr = result.value().attr;
+
+                    size_t sdk = findAttributeSdkLevel(attrToFlatten.resourceId);
+                    if (mOptions.maxSdkAttribute && sdk > mOptions.maxSdkAttribute.value()) {
+                        // We need to filter this attribute out.
+                        mSmallestFilteredSdk = std::min(mSmallestFilteredSdk, sdk);
+                        continue;
+                    }
+                }
+            }
+
+            if (attrToFlatten.resourceId == 0) {
+                // Attributes that have no resource ID (because they don't belong to a
+                // package) should appear after those that do have resource IDs. Assign
+                // them some integer value that will appear after.
+                attrToFlatten.resourceId = nextAttributeId++;
+            }
+
+            // Insert the attribute into the sorted vector.
+            auto iter = std::lower_bound(sortedAttributes.begin(), sortedAttributes.end(),
+                                         attrToFlatten.resourceId, lessAttributeId);
+            sortedAttributes.insert(iter, std::move(attrToFlatten));
+        }
+
+        flatElem->attributeCount = sortedAttributes.size();
+        if (sortedAttributes.empty()) {
+            return true;
+        }
+
+        android::ResXMLTree_attribute* flatAttr = out->nextBlock<android::ResXMLTree_attribute>(
+                sortedAttributes.size());
+
+        // Now that we have sorted the attributes into their final encoded order, it's time
+        // to actually write them out.
+        uint16_t attributeIndex = 1;
+        for (const AttributeToFlatten& attrToFlatten : sortedAttributes) {
+            Maybe<std::u16string> package = util::extractPackageFromNamespace(
+                    attrToFlatten.xmlAttr->namespaceUri);
+
+            // Assign the indices for specific attributes.
+            if (package && package.value() == u"android" && attrToFlatten.xmlAttr->name == u"id") {
+                flatElem->idIndex = attributeIndex;
+            } else if (attrToFlatten.xmlAttr->namespaceUri.empty()) {
+                if (attrToFlatten.xmlAttr->name == u"class") {
+                    flatElem->classIndex = attributeIndex;
+                } else if (attrToFlatten.xmlAttr->name == u"style") {
+                    flatElem->styleIndex = attributeIndex;
+                }
+            }
+            attributeIndex++;
+
+            // Add the namespaceUri and name to the list of StringRefs to encode.
+            addString(attrToFlatten.xmlAttr->namespaceUri, kLowPriority, &flatAttr->ns);
+            flatAttr->rawValue.index = -1;
+
+            if (!attrToFlatten.resourceAttr) {
+                addString(attrToFlatten.xmlAttr->name, kLowPriority, &flatAttr->name);
+            } else {
+                // We've already extracted the package successfully before.
+                assert(package);
+
+                // Attribute names are stored without packages, but we use
+                // their StringPool index to lookup their resource IDs.
+                // This will cause collisions, so we can't dedupe
+                // attribute names from different packages. We use separate
+                // pools that we later combine.
+                //
+                // Lookup the StringPool for this package and make the reference there.
+                StringPool::Ref nameRef = (*mPackagePools)[package.value()].makeRef(
+                        attrToFlatten.xmlAttr->name,
+                        StringPool::Context{ attrToFlatten.resourceId });
+
+                // Add it to the list of strings to flatten.
+                addString(nameRef, &flatAttr->name);
+
+                if (mOptions.keepRawValues) {
+                    // Keep raw values (this is for static libraries).
+                    // TODO(with a smarter inflater for binary XML, we can do without this).
+                    addString(attrToFlatten.xmlAttr->value, kLowPriority, &flatAttr->rawValue);
+                }
+            }
+
+            error |= !flattenItem(node, attrToFlatten.xmlAttr->value, attrToFlatten.resourceAttr,
+                                  flatAttr);
+            flatAttr->typedValue.size = sizeof(flatAttr->typedValue);
+            flatAttr++;
+        }
+        return !error;
+    }
+
+    Maybe<size_t> getSmallestFilteredSdk() const {
+        if (mSmallestFilteredSdk == std::numeric_limits<size_t>::max()) {
+            return {};
+        }
+        return mSmallestFilteredSdk;
+    }
+
+private:
+    bool flattenItem(const Node* el, const std::u16string& value, const ::aapt::Attribute* attr,
+                     android::ResXMLTree_attribute* flatAttr) {
+        std::unique_ptr<Item> item;
+        if (!attr) {
+            bool create = false;
+            item = ResourceParser::tryParseReference(value, &create);
+            if (!item) {
+                flatAttr->typedValue.dataType = android::Res_value::TYPE_STRING;
+                addString(value, kLowPriority, &flatAttr->rawValue);
+                addString(value, kLowPriority, reinterpret_cast<android::ResStringPool_ref*>(
+                        &flatAttr->typedValue.data));
+                return true;
+            }
+        } else {
+            item = ResourceParser::parseItemForAttribute(value, *attr);
+            if (!item) {
+                if (!(attr->typeMask & android::ResTable_map::TYPE_STRING)) {
+                    mLogger->error(el->lineNumber)
+                            << "'"
+                            << value
+                            << "' is not compatible with attribute '"
+                            << *attr
+                            << "'."
+                            << std::endl;
+                    return false;
+                }
+
+                flatAttr->typedValue.dataType = android::Res_value::TYPE_STRING;
+                addString(value, kLowPriority, &flatAttr->rawValue);
+                addString(value, kLowPriority, reinterpret_cast<android::ResStringPool_ref*>(
+                        &flatAttr->typedValue.data));
+                return true;
+            }
+        }
+
+        assert(item);
+
+        bool error = false;
+
+        // If this is a reference, resolve the name into an ID.
+        visitFunc<Reference>(*item, [&](Reference& reference) {
+            // First see if we can convert the package name from a prefix to a real
+            // package name.
+            ResourceName realName = reference.name;
+            if (!realName.package.empty()) {
+                Maybe<std::u16string> package = getPackageAlias(realName.package);
+                if (package) {
+                    realName.package = package.value();
+                }
+            } else {
+                realName.package = getDefaultPackage();
+            }
+
+            Maybe<ResourceId> result = mResolver->findId(realName);
+            if (!result || !result.value().isValid()) {
+                std::ostream& out = mLogger->error(el->lineNumber)
+                        << "unresolved reference '"
+                        << reference.name
+                        << "'";
+                if (realName != reference.name) {
+                    out << " (aka '" << realName << "')";
+                }
+                out << "'." << std::endl;
+                error = true;
+            } else {
+                reference.id = result.value();
+            }
+        });
+
+        if (error) {
+            return false;
+        }
+
+        item->flatten(flatAttr->typedValue);
+        return true;
+    }
+
+    std::shared_ptr<IResolver> mResolver;
+    SourceLogger* mLogger;
+    std::map<std::u16string, StringPool>* mPackagePools;
+    FlattenOptions mOptions;
+    size_t mSmallestFilteredSdk = std::numeric_limits<size_t>::max();
+};
 
 /**
- * Reads events from the parser and writes to a BigBuffer. The binary XML file
- * expects the StringPool to appear first, but we haven't collected the strings yet. We
- * write to a temporary BigBuffer while parsing the input, adding strings we encounter
- * to the StringPool. At the end, we write the StringPool to the given BigBuffer and
+ * The binary XML file expects the StringPool to appear first, but we haven't collected the
+ * strings yet. We write to a temporary BigBuffer while parsing the input, adding strings
+ * we encounter to the StringPool. At the end, we write the StringPool to the given BigBuffer and
  * then move the data from the temporary BigBuffer into the given one. This incurs no
  * copies as the given BigBuffer simply takes ownership of the data.
  */
-Maybe<size_t> XmlFlattener::flatten(const Source& source,
-                                    const std::shared_ptr<XmlPullParser>& parser,
-                                    BigBuffer* outBuffer, Options options) {
-    SourceLogger logger(source);
-    StringPool pool;
-    bool error = false;
-
-    size_t smallestStrippedAttributeSdk = std::numeric_limits<size_t>::max();
-
-    // Attribute names are stored without packages, but we use
-    // their StringPool index to lookup their resource IDs.
-    // This will cause collisions, so we can't dedupe
-    // attribute names from different packages. We use separate
-    // pools that we later combine.
-    std::map<std::u16string, StringPool> packagePools;
-
-    // Attribute resource IDs are stored in the same order
-    // as the attribute names appear in the StringPool.
-    // Since the StringPool contains more than just attribute
-    // names, to maintain a tight packing of resource IDs,
-    // we must ensure that attribute names appear first
-    // in our StringPool. For this, we assign a low priority
-    // (0xffffffff) to non-attribute strings. Attribute
-    // names will be stored along with a priority equal
-    // to their resource ID so that they are ordered.
-    StringPool::Context lowPriority { 0xffffffffu };
-
-    // Once we sort the StringPool, we can assign the updated indices
-    // to the correct data locations.
-    std::vector<std::pair<StringPool::Ref, android::ResStringPool_ref*>> stringRefs;
-
-    // Since we don't know the size of the final StringPool, we write to this
-    // temporary BigBuffer, which we will append to outBuffer later.
-    BigBuffer out(1024);
-    while (XmlPullParser::isGoodEvent(parser->next())) {
-        XmlPullParser::Event event = parser->getEvent();
-        switch (event) {
-            case XmlPullParser::Event::kStartNamespace:
-            case XmlPullParser::Event::kEndNamespace: {
-                const size_t startIndex = out.size();
-                android::ResXMLTree_node* node = out.nextBlock<android::ResXMLTree_node>();
-                if (event == XmlPullParser::Event::kStartNamespace) {
-                    node->header.type = android::RES_XML_START_NAMESPACE_TYPE;
-                } else {
-                    node->header.type = android::RES_XML_END_NAMESPACE_TYPE;
-                }
-
-                node->header.headerSize = sizeof(*node);
-                node->lineNumber = parser->getLineNumber();
-                node->comment.index = -1;
-
-                android::ResXMLTree_namespaceExt* ns =
-                        out.nextBlock<android::ResXMLTree_namespaceExt>();
-                stringRefs.emplace_back(
-                        pool.makeRef(parser->getNamespacePrefix(), lowPriority), &ns->prefix);
-                stringRefs.emplace_back(
-                        pool.makeRef(parser->getNamespaceUri(), lowPriority), &ns->uri);
-
-                out.align4();
-                node->header.size = out.size() - startIndex;
-                break;
-            }
-
-            case XmlPullParser::Event::kStartElement: {
-                const size_t startIndex = out.size();
-                android::ResXMLTree_node* node = out.nextBlock<android::ResXMLTree_node>();
-                node->header.type = android::RES_XML_START_ELEMENT_TYPE;
-                node->header.headerSize = sizeof(*node);
-                node->lineNumber = parser->getLineNumber();
-                node->comment.index = -1;
-
-                android::ResXMLTree_attrExt* elem = out.nextBlock<android::ResXMLTree_attrExt>();
-                if (!parser->getElementNamespace().empty()) {
-                    stringRefs.emplace_back(
-                            pool.makeRef(parser->getElementNamespace(), lowPriority), &elem->ns);
-                } else {
-                    // The device doesn't think a string of size 0 is the same as null.
-                    elem->ns.index = -1;
-                }
-                stringRefs.emplace_back(
-                        pool.makeRef(parser->getElementName(), lowPriority), &elem->name);
-                elem->attributeStart = sizeof(*elem);
-                elem->attributeSize = sizeof(android::ResXMLTree_attribute);
-
-                // The resource system expects attributes to be sorted by resource ID.
-                std::vector<XmlAttribute> sortedAttributes;
-                uint32_t nextAttributeId = 0;
-                const auto endAttrIter = parser->endAttributes();
-                for (auto attrIter = parser->beginAttributes();
-                        attrIter != endAttrIter;
-                        ++attrIter) {
-                    uint32_t id;
-                    StringPool::Ref nameRef;
-                    const Attribute* attr = nullptr;
-
-                    if (options.maxSdkAttribute && attrIter->namespaceUri == kSchemaAndroid) {
-                        size_t sdkVersion = findAttributeSdkLevel(attrIter->name);
-                        if (sdkVersion > options.maxSdkAttribute.value()) {
-                            // We will silently omit this attribute
-                            smallestStrippedAttributeSdk =
-                                    std::min(smallestStrippedAttributeSdk, sdkVersion);
-                            continue;
-                        }
-                    }
-
-                    ResourceNameRef genIdName;
-                    bool create = false;
-                    bool privateRef = false;
-                    if (mTable && ResourceParser::tryParseReference(attrIter->value, &genIdName,
-                            &create, &privateRef) && create) {
-                        mTable->addResource(genIdName, {}, source.line(parser->getLineNumber()),
-                                            util::make_unique<Id>());
-                    }
-
-
-                    Maybe<std::u16string> package = util::extractPackageFromNamespace(
-                            attrIter->namespaceUri);
-                    if (!package || !mResolver) {
-                        // Attributes that have no resource ID (because they don't belong to a
-                        // package) should appear after those that do have resource IDs. Assign
-                        // them some integer value that will appear after.
-                        id = 0x80000000u | nextAttributeId++;
-                        nameRef = pool.makeRef(attrIter->name, StringPool::Context{ id });
-
-                    } else {
-                        // Find the Attribute object via our Resolver.
-                        ResourceName attrName = {
-                                package.value(), ResourceType::kAttr, attrIter->name };
-
-                        if (attrName.package.empty()) {
-                            attrName.package = options.defaultPackage;
-                        }
-
-                        Maybe<IResolver::Entry> result = mResolver->findAttribute(attrName);
-                        if (!result || !result.value().id.isValid()) {
-                            logger.error(parser->getLineNumber())
-                                    << "unresolved attribute '"
-                                    << attrName
-                                    << "'."
-                                    << std::endl;
-                            error = true;
-                            continue;
-                        }
-
-                        if (!result.value().attr) {
-                            logger.error(parser->getLineNumber())
-                                    << "not a valid attribute '"
-                                    << attrName
-                                    << "'."
-                                    << std::endl;
-                            error = true;
-                            continue;
-                        }
-
-                        id = result.value().id.id;
-                        attr = result.value().attr;
-
-                        // Put the attribute name into a package specific pool, since we don't
-                        // want to collapse names from different packages.
-                        nameRef = packagePools[package.value()].makeRef(
-                                attrIter->name, StringPool::Context{ id });
-                    }
-
-                    // Insert the attribute into the sorted vector.
-                    auto iter = std::lower_bound(sortedAttributes.begin(), sortedAttributes.end(),
-                                                 id, lessAttributeId);
-                    sortedAttributes.insert(iter, XmlAttribute{ id, &*attrIter, attr, nameRef });
-                }
-
-                if (error) {
-                    break;
-                }
-
-                // Now that we have filtered out some attributes, get the final count.
-                elem->attributeCount = sortedAttributes.size();
-
-                // Flatten the sorted attributes.
-                uint16_t attributeIndex = 1;
-                for (auto entry : sortedAttributes) {
-                    android::ResXMLTree_attribute* attr =
-                            out.nextBlock<android::ResXMLTree_attribute>();
-                    if (!entry.xmlAttr->namespaceUri.empty()) {
-                        stringRefs.emplace_back(
-                                pool.makeRef(entry.xmlAttr->namespaceUri, lowPriority), &attr->ns);
-                    } else {
-                        attr->ns.index = -1;
-                    }
-
-                    StringPool::Ref rawValueRef = pool.makeRef(entry.xmlAttr->value, lowPriority);
-
-                    stringRefs.emplace_back(entry.nameRef, &attr->name);
-
-                    if (options.keepRawValues) {
-                        stringRefs.emplace_back(rawValueRef, &attr->rawValue);
-                    } else {
-                        attr->rawValue.index = -1;
-                    }
-
-                    // Assign the indices for specific attributes.
-                    if (entry.xmlAttr->namespaceUri == kSchemaAndroid &&
-                            entry.xmlAttr->name == u"id") {
-                        elem->idIndex = attributeIndex;
-                    } else if (entry.xmlAttr->namespaceUri.empty()) {
-                        if (entry.xmlAttr->name == u"class") {
-                            elem->classIndex = attributeIndex;
-                        } else if (entry.xmlAttr->name == u"style") {
-                            elem->styleIndex = attributeIndex;
-                        }
-                    }
-                    attributeIndex++;
-
-                    std::unique_ptr<Item> value;
-                    if (entry.attr) {
-                        value = ResourceParser::parseItemForAttribute(entry.xmlAttr->value,
-                                                                      *entry.attr);
-                    } else {
-                        bool create = false;
-                        value = ResourceParser::tryParseReference(entry.xmlAttr->value, &create);
-                    }
-
-                    if (mResolver && value) {
-                        AttributeValueFlattener flattener(
-                                mResolver,
-                                &logger,
-                                &attr->typedValue,
-                                parser,
-                                &error,
-                                rawValueRef,
-                                &options.defaultPackage,
-                                &stringRefs);
-                        value->accept(flattener, {});
-                    } else if (!value && entry.attr &&
-                            !(entry.attr->typeMask & android::ResTable_map::TYPE_STRING)) {
-                        logger.error(parser->getLineNumber())
-                                << "'"
-                                << *rawValueRef
-                                << "' is not compatible with attribute "
-                                << *entry.attr
-                                << "."
-                                << std::endl;
-                        error = true;
-                    } else {
-                        attr->typedValue.dataType = android::Res_value::TYPE_STRING;
-                        if (!options.keepRawValues) {
-                            // Don't set the string twice.
-                            stringRefs.emplace_back(rawValueRef, &attr->rawValue);
-                        }
-                        stringRefs.emplace_back(rawValueRef,
-                                reinterpret_cast<android::ResStringPool_ref*>(
-                                        &attr->typedValue.data));
-                    }
-                    attr->typedValue.size = sizeof(attr->typedValue);
-                }
-
-                out.align4();
-                node->header.size = out.size() - startIndex;
-                break;
-            }
-
-            case XmlPullParser::Event::kEndElement: {
-                const size_t startIndex = out.size();
-                android::ResXMLTree_node* node = out.nextBlock<android::ResXMLTree_node>();
-                node->header.type = android::RES_XML_END_ELEMENT_TYPE;
-                node->header.headerSize = sizeof(*node);
-                node->lineNumber = parser->getLineNumber();
-                node->comment.index = -1;
-
-                android::ResXMLTree_endElementExt* elem =
-                        out.nextBlock<android::ResXMLTree_endElementExt>();
-                stringRefs.emplace_back(
-                        pool.makeRef(parser->getElementNamespace(), lowPriority), &elem->ns);
-                stringRefs.emplace_back(
-                        pool.makeRef(parser->getElementName(), lowPriority), &elem->name);
-
-                out.align4();
-                node->header.size = out.size() - startIndex;
-                break;
-            }
-
-            case XmlPullParser::Event::kText: {
-                StringPiece16 text = util::trimWhitespace(parser->getText());
-                if (text.empty()) {
-                    break;
-                }
-
-                const size_t startIndex = out.size();
-                android::ResXMLTree_node* node = out.nextBlock<android::ResXMLTree_node>();
-                node->header.type = android::RES_XML_CDATA_TYPE;
-                node->header.headerSize = sizeof(*node);
-                node->lineNumber = parser->getLineNumber();
-                node->comment.index = -1;
-
-                android::ResXMLTree_cdataExt* elem = out.nextBlock<android::ResXMLTree_cdataExt>();
-                stringRefs.emplace_back(pool.makeRef(text, lowPriority), &elem->data);
-
-                out.align4();
-                node->header.size = out.size() - startIndex;
-                break;
-            }
-
-            default:
-                break;
-        }
-
-    }
-    out.align4();
-
-    if (error) {
-        return {};
-    }
-
-    if (parser->getEvent() == XmlPullParser::Event::kBadDocument) {
-        logger.error(parser->getLineNumber())
-                << parser->getLastError()
-                << std::endl;
-        return {};
-    }
-
-    // Merge the package pools into the main pool.
-    for (auto& packagePoolEntry : packagePools) {
-        pool.merge(std::move(packagePoolEntry.second));
-    }
-
-    // Sort so that attribute resource IDs show up first.
-    pool.sort([](const StringPool::Entry& a, const StringPool::Entry& b) -> bool {
+static void flattenXml(StringPool* pool, FlatStringRefList* stringRefs, BigBuffer* outBuffer,
+                       BigBuffer&& xmlTreeBuffer) {
+    // Sort the string pool so that attribute resource IDs show up first.
+    pool->sort([](const StringPool::Entry& a, const StringPool::Entry& b) -> bool {
         return a.context.priority < b.context.priority;
     });
 
     // Now we flatten the string pool references into the correct places.
-    for (const auto& refEntry : stringRefs) {
+    for (const auto& refEntry : *stringRefs) {
         refEntry.second->index = refEntry.first.getIndex();
     }
 
@@ -463,36 +482,93 @@
     header->header.headerSize = sizeof(*header);
 
     // Flatten the StringPool.
-    StringPool::flattenUtf16(outBuffer, pool);
+    StringPool::flattenUtf16(outBuffer, *pool);
 
     // Write the array of resource IDs, indexed by StringPool order.
     const size_t beforeResIdMapIndex = outBuffer->size();
     android::ResChunk_header* resIdMapChunk = outBuffer->nextBlock<android::ResChunk_header>();
     resIdMapChunk->type = android::RES_XML_RESOURCE_MAP_TYPE;
     resIdMapChunk->headerSize = sizeof(*resIdMapChunk);
-    for (const auto& str : pool) {
+    for (const auto& str : *pool) {
         ResourceId id { str->context.priority };
-        if (!id.isValid()) {
+        if (id.id == kLowPriority || !id.isValid()) {
             // When we see the first non-resource ID,
             // we're done.
             break;
         }
 
-        uint32_t* flatId = outBuffer->nextBlock<uint32_t>();
-        *flatId = id.id;
+        *outBuffer->nextBlock<uint32_t>() = id.id;
     }
     resIdMapChunk->size = outBuffer->size() - beforeResIdMapIndex;
 
     // Move the temporary BigBuffer into outBuffer.
-    outBuffer->appendBuffer(std::move(out));
-
+    outBuffer->appendBuffer(std::move(xmlTreeBuffer));
     header->header.size = outBuffer->size() - beforeXmlTreeIndex;
-
-    if (smallestStrippedAttributeSdk == std::numeric_limits<size_t>::max()) {
-        // Nothing was stripped
-        return 0u;
-    }
-    return smallestStrippedAttributeSdk;
 }
 
+bool flatten(Node* root, const std::u16string& defaultPackage, BigBuffer* outBuffer) {
+    StringPool pool;
+
+    // This will hold the StringRefs and the location in which to write the index.
+    // Once we sort the StringPool, we can assign the updated indices
+    // to the correct data locations.
+    FlatStringRefList stringRefs;
+
+    // Since we don't know the size of the final StringPool, we write to this
+    // temporary BigBuffer, which we will append to outBuffer later.
+    BigBuffer out(1024);
+
+    CompileXmlFlattener flattener(&out, &pool, &stringRefs, defaultPackage);
+    root->accept(&flattener);
+
+    if (!flattener.success()) {
+        return false;
+    }
+
+    flattenXml(&pool, &stringRefs, outBuffer, std::move(out));
+    return true;
+};
+
+Maybe<size_t> flattenAndLink(const Source& source, Node* root,
+                             const std::u16string& defaultPackage,
+                             const std::shared_ptr<IResolver>& resolver,
+                             const FlattenOptions& options, BigBuffer* outBuffer) {
+    SourceLogger logger(source);
+    StringPool pool;
+
+    // Attribute names are stored without packages, but we use
+    // their StringPool index to lookup their resource IDs.
+    // This will cause collisions, so we can't dedupe
+    // attribute names from different packages. We use separate
+    // pools that we later combine.
+    std::map<std::u16string, StringPool> packagePools;
+
+    FlatStringRefList stringRefs;
+
+    // Since we don't know the size of the final StringPool, we write to this
+    // temporary BigBuffer, which we will append to outBuffer later.
+    BigBuffer out(1024);
+
+    LinkedXmlFlattener flattener(&out, &pool, &packagePools, &stringRefs, defaultPackage, resolver,
+                                 &logger, options);
+    root->accept(&flattener);
+
+    if (!flattener.success()) {
+        return {};
+    }
+
+    // Merge the package pools into the main pool.
+    for (auto& packagePoolEntry : packagePools) {
+        pool.merge(std::move(packagePoolEntry.second));
+    }
+
+    flattenXml(&pool, &stringRefs, outBuffer, std::move(out));
+
+    if (flattener.getSmallestFilteredSdk()) {
+        return flattener.getSmallestFilteredSdk();
+    }
+    return 0;
+}
+
+} // namespace xml
 } // namespace aapt
diff --git a/tools/aapt2/XmlFlattener.h b/tools/aapt2/XmlFlattener.h
index 2cfcc16..4ece0a3 100644
--- a/tools/aapt2/XmlFlattener.h
+++ b/tools/aapt2/XmlFlattener.h
@@ -21,64 +21,49 @@
 #include "Maybe.h"
 #include "Resolver.h"
 #include "Source.h"
-#include "XmlPullParser.h"
+#include "XmlDom.h"
 
 #include <string>
 
 namespace aapt {
+namespace xml {
 
 /**
  * Flattens an XML file into a binary representation parseable by
- * the Android resource system. References to resources are checked
- * and string values are transformed to typed data where possible.
+ * the Android resource system.
  */
-class XmlFlattener {
-public:
-    struct Options {
-        /**
-         * The package to use when a reference has no package specified
-         * (or a namespace URI equals "http://schemas.android.com/apk/res-auto").
-         */
-        std::u16string defaultPackage;
+bool flatten(Node* root, const std::u16string& defaultPackage, BigBuffer* outBuffer);
 
-        /**
-         * If set, tells the XmlFlattener to strip out
-         * attributes that have been introduced after
-         * max SDK.
-         */
-        Maybe<size_t> maxSdkAttribute;
-
-        /**
-         * Setting this to true will keep the raw string value of
-         * an attribute's value when it has been resolved.
-         */
-        bool keepRawValues = false;
-    };
+/**
+ * Options for flattenAndLink.
+ */
+struct FlattenOptions {
+    /**
+     * Keep attribute raw string values along with typed values.
+     */
+    bool keepRawValues = false;
 
     /**
-     * Creates a flattener with a Resolver to resolve references
-     * and attributes.
+     * If set, any attribute introduced in a later SDK will not be encoded.
      */
-    XmlFlattener(const std::shared_ptr<ResourceTable>& table,
-                 const std::shared_ptr<IResolver>& resolver);
-
-    XmlFlattener(const XmlFlattener&) = delete; // Not copyable.
-
-    /**
-     * Flatten an XML file, reading from the XML parser and writing to the
-     * BigBuffer. The source object is mainly for logging errors. If the
-     * function succeeds, returns the smallest SDK version of an attribute that
-     * was stripped out. If no attributes were stripped out, the return value
-     * is 0.
-     */
-    Maybe<size_t> flatten(const Source& source, const std::shared_ptr<XmlPullParser>& parser,
-                          BigBuffer* outBuffer, Options options);
-
-private:
-    std::shared_ptr<ResourceTable> mTable;
-    std::shared_ptr<IResolver> mResolver;
+    Maybe<size_t> maxSdkAttribute;
 };
 
+/**
+ * Like flatten(Node*,BigBuffer*), but references to resources are checked
+ * and string values are transformed to typed data where possible.
+ *
+ * `defaultPackage` is used when a reference has no package or the namespace URI
+ * "http://schemas.android.com/apk/res-auto" is used.
+ *
+ * `resolver` is used to resolve references to resources.
+ */
+Maybe<size_t> flattenAndLink(const Source& source, Node* root,
+                             const std::u16string& defaultPackage,
+                             const std::shared_ptr<IResolver>& resolver,
+                             const FlattenOptions& options, BigBuffer* outBuffer);
+
+} // namespace xml
 } // namespace aapt
 
 #endif // AAPT_XML_FLATTENER_H
diff --git a/tools/aapt2/XmlFlattener_test.cpp b/tools/aapt2/XmlFlattener_test.cpp
index b45cd9b..8915d24 100644
--- a/tools/aapt2/XmlFlattener_test.cpp
+++ b/tools/aapt2/XmlFlattener_test.cpp
@@ -17,7 +17,6 @@
 #include "MockResolver.h"
 #include "ResourceTable.h"
 #include "ResourceValues.h"
-#include "SourceXmlPullParser.h"
 #include "Util.h"
 #include "XmlFlattener.h"
 
@@ -30,13 +29,14 @@
 using namespace android;
 
 namespace aapt {
+namespace xml {
 
 constexpr const char* kXmlPreamble = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
 
 class XmlFlattenerTest : public ::testing::Test {
 public:
     virtual void SetUp() override {
-        std::shared_ptr<IResolver> resolver = std::make_shared<MockResolver>(
+        mResolver = std::make_shared<MockResolver>(
                 std::make_shared<ResourceTable>(),
                 std::map<ResourceName, ResourceId>({
                         { ResourceName{ u"android", ResourceType::kAttr, u"attr" },
@@ -47,18 +47,21 @@
                           ResourceId{ 0x01010001u } },
                         { ResourceName{ u"com.lib", ResourceType::kId, u"id" },
                           ResourceId{ 0x01020001u } }}));
-
-        mFlattener = std::make_shared<XmlFlattener>(nullptr, resolver);
     }
 
     ::testing::AssertionResult testFlatten(const std::string& in, ResXMLTree* outTree) {
         std::stringstream input(kXmlPreamble);
         input << in << std::endl;
-        std::shared_ptr<XmlPullParser> xmlParser = std::make_shared<SourceXmlPullParser>(input);
+
+        SourceLogger logger(Source{ "test.xml" });
+        std::unique_ptr<Node> root = inflate(&input, &logger);
+        if (!root) {
+            return ::testing::AssertionFailure();
+        }
+
         BigBuffer outBuffer(1024);
-        XmlFlattener::Options xmlOptions;
-        xmlOptions.defaultPackage = u"android";
-        if (!mFlattener->flatten(Source{ "test" }, xmlParser, &outBuffer, xmlOptions)) {
+        if (!flattenAndLink(Source{ "test.xml" }, root.get(), std::u16string(u"android"),
+                    mResolver, {}, &outBuffer)) {
             return ::testing::AssertionFailure();
         }
 
@@ -69,16 +72,48 @@
         return ::testing::AssertionSuccess();
     }
 
-    std::shared_ptr<XmlFlattener> mFlattener;
+    std::shared_ptr<IResolver> mResolver;
 };
 
 TEST_F(XmlFlattenerTest, ParseSimpleView) {
-    std::string input = "<View xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
-                        "      android:attr=\"@id/id\">\n"
-                        "</View>";
+    std::string input = R"EOF(
+        <View xmlns:android="http://schemas.android.com/apk/res/android"
+              android:attr="@id/id"
+              class="str"
+              style="@id/id">
+        </View>
+    )EOF";
     ResXMLTree tree;
     ASSERT_TRUE(testFlatten(input, &tree));
 
+    while (tree.next() != ResXMLTree::START_TAG) {
+        ASSERT_NE(tree.getEventType(), ResXMLTree::END_DOCUMENT);
+        ASSERT_NE(tree.getEventType(), ResXMLTree::BAD_DOCUMENT);
+    }
+
+    const StringPiece16 androidNs = u"http://schemas.android.com/apk/res/android";
+    const StringPiece16 attrName = u"attr";
+    ssize_t idx = tree.indexOfAttribute(androidNs.data(), androidNs.size(), attrName.data(),
+                                        attrName.size());
+    ASSERT_GE(idx, 0);
+    EXPECT_EQ(tree.getAttributeNameResID(idx), 0x01010000u);
+    EXPECT_EQ(tree.getAttributeDataType(idx), android::Res_value::TYPE_REFERENCE);
+
+    const StringPiece16 class16 = u"class";
+    idx = tree.indexOfAttribute(nullptr, 0, class16.data(), class16.size());
+    ASSERT_GE(idx, 0);
+    EXPECT_EQ(tree.getAttributeNameResID(idx), 0u);
+    EXPECT_EQ(tree.getAttributeDataType(idx), android::Res_value::TYPE_STRING);
+    EXPECT_EQ(tree.getAttributeData(idx), tree.getAttributeValueStringID(idx));
+
+    const StringPiece16 style16 = u"style";
+    idx = tree.indexOfAttribute(nullptr, 0, style16.data(), style16.size());
+    ASSERT_GE(idx, 0);
+    EXPECT_EQ(tree.getAttributeNameResID(idx), 0u);
+    EXPECT_EQ(tree.getAttributeDataType(idx), android::Res_value::TYPE_REFERENCE);
+    EXPECT_EQ((uint32_t) tree.getAttributeData(idx), 0x01020000u);
+    EXPECT_EQ(tree.getAttributeValueStringID(idx), -1);
+
     while (tree.next() != ResXMLTree::END_DOCUMENT) {
         ASSERT_NE(tree.getEventType(), ResXMLTree::BAD_DOCUMENT);
     }
@@ -193,4 +228,5 @@
     EXPECT_GE(tree.indexOfAttribute(nullptr, 0, kPackage.data(), kPackage.size()), 0);
 }
 
+} // namespace xml
 } // namespace aapt