Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 6b372991 authored by Adam Lesinski's avatar Adam Lesinski
Browse files

AAPT2: Change XmlDom to exclude Namespace as a node

In preparation for exporting an XML proto format for UAM to consume,
this change brings the XML DOM API more in line with other APIs that
do not make the Namespace a separate node.

Treating Namespace declarations as just properties of an Element
node makes the implementation of algorithms much simpler, as
the constraints that Namespace nodes have only one child
are now built in and traversing to find Element nodes
is much simpler.

Also made a bunch of quality of life improvements, like formatting and
comment style.

Test: make aapt2_tests
Change-Id: Ib97ff1c4252b7907e2cc1f13a448dc4ca3b809a4
parent c10cc118
Loading
Loading
Loading
Loading
+31 −33
Original line number Diff line number Diff line
@@ -33,6 +33,8 @@

namespace aapt {

namespace {

class PrintVisitor : public ValueVisitor {
 public:
  using ValueVisitor::Visit;
@@ -88,9 +90,13 @@ class PrintVisitor : public ValueVisitor {
    }
  }

  void Visit(Array* array) override { array->Print(&std::cout); }
  void Visit(Array* array) override {
    array->Print(&std::cout);
  }

  void Visit(Plural* plural) override { plural->Print(&std::cout); }
  void Visit(Plural* plural) override {
    plural->Print(&std::cout);
  }

  void Visit(Styleable* styleable) override {
    std::cout << "(styleable)";
@@ -110,11 +116,14 @@ class PrintVisitor : public ValueVisitor {
    }
  }

  void VisitItem(Item* item) override { item->Print(&std::cout); }
  void VisitItem(Item* item) override {
    item->Print(&std::cout);
  }
};

void Debug::PrintTable(ResourceTable* table,
                       const DebugPrintTableOptions& options) {
}  // namespace

void Debug::PrintTable(ResourceTable* table, const DebugPrintTableOptions& options) {
  PrintVisitor visitor;

  for (auto& package : table->packages) {
@@ -148,10 +157,9 @@ void Debug::PrintTable(ResourceTable* table,
      }

      for (const ResourceEntry* entry : sorted_entries) {
        ResourceId id(package->id ? package->id.value() : uint8_t(0),
                      type->id ? type->id.value() : uint8_t(0),
                      entry->id ? entry->id.value() : uint16_t(0));
        ResourceName name(package->name, type->type, entry->name);
        const ResourceId id(package->id.value_or_default(0), type->id.value_or_default(0),
                            entry->id.value_or_default(0));
        const ResourceName name(package->name, type->type, entry->name);

        std::cout << "    spec resource " << id << " " << name;
        switch (entry->symbol_status.state) {
@@ -180,16 +188,14 @@ void Debug::PrintTable(ResourceTable* table,
  }
}

static size_t GetNodeIndex(const std::vector<ResourceName>& names,
                           const ResourceName& name) {
static size_t GetNodeIndex(const std::vector<ResourceName>& names, const ResourceName& name) {
  auto iter = std::lower_bound(names.begin(), names.end(), name);
  CHECK(iter != names.end());
  CHECK(*iter == name);
  return std::distance(names.begin(), iter);
}

void Debug::PrintStyleGraph(ResourceTable* table,
                            const ResourceName& target_style) {
void Debug::PrintStyleGraph(ResourceTable* table, const ResourceName& target_style) {
  std::map<ResourceName, std::set<ResourceName>> graph;

  std::queue<ResourceName> styles_to_visit;
@@ -223,8 +229,7 @@ void Debug::PrintStyleGraph(ResourceTable* table,

  std::cout << "digraph styles {\n";
  for (const auto& name : names) {
    std::cout << "  node_" << GetNodeIndex(names, name) << " [label=\"" << name
              << "\"];\n";
    std::cout << "  node_" << GetNodeIndex(names, name) << " [label=\"" << name << "\"];\n";
  }

  for (const auto& entry : graph) {
@@ -243,8 +248,7 @@ void Debug::PrintStyleGraph(ResourceTable* table,
void Debug::DumpHex(const void* data, size_t len) {
  const uint8_t* d = (const uint8_t*)data;
  for (size_t i = 0; i < len; i++) {
    std::cerr << std::hex << std::setfill('0') << std::setw(2) << (uint32_t)d[i]
              << " ";
    std::cerr << std::hex << std::setfill('0') << std::setw(2) << (uint32_t)d[i] << " ";
    if (i % 8 == 7) {
      std::cerr << "\n";
    }
@@ -262,8 +266,15 @@ class XmlPrinter : public xml::Visitor {
  using xml::Visitor::Visit;

  void Visit(xml::Element* el) override {
    std::cerr << prefix_;
    std::cerr << "E: ";
    const size_t previous_size = prefix_.size();

    for (const xml::NamespaceDecl& decl : el->namespace_decls) {
      std::cerr << prefix_ << "N: " << decl.prefix << "=" << decl.uri
                << " (line=" << decl.line_number << ")\n";
      prefix_ += "  ";
    }

    std::cerr << prefix_ << "E: ";
    if (!el->namespace_uri.empty()) {
      std::cerr << el->namespace_uri << ":";
    }
@@ -283,26 +294,13 @@ class XmlPrinter : public xml::Visitor {
      std::cerr << "=" << attr.value << "\n";
    }

    const size_t previous_size = prefix_.size();
    prefix_ += "  ";
    xml::Visitor::Visit(el);
    prefix_.resize(previous_size);
  }

  void Visit(xml::Namespace* ns) override {
    std::cerr << prefix_;
    std::cerr << "N: " << ns->namespace_prefix << "=" << ns->namespace_uri
              << " (line=" << ns->line_number << ")\n";

    const size_t previous_size = prefix_.size();
    prefix_ += "  ";
    xml::Visitor::Visit(ns);
    prefix_.resize(previous_size);
  }

  void Visit(xml::Text* text) override {
    std::cerr << prefix_;
    std::cerr << "T: '" << text->text << "'\n";
    std::cerr << prefix_ << "T: '" << text->text << "'\n";
  }

 private:
+1 −1
Original line number Diff line number Diff line
@@ -482,7 +482,7 @@ std::vector<std::unique_ptr<xml::XmlResource>> ResourceFileFlattener::LinkAndVer

  if (options_.no_version_vectors || options_.no_version_transitions) {
    // Skip this if it is a vector or animated-vector.
    xml::Element* el = xml::FindRootElement(doc);
    xml::Element* el = doc->root.get();
    if (el && el->namespace_uri.empty()) {
      if ((options_.no_version_vectors && IsVectorElement(el->name)) ||
          (options_.no_version_transitions && IsTransitionElement(el->name))) {
+15 −12
Original line number Diff line number Diff line
@@ -28,7 +28,7 @@
#include "util/Maybe.h"
#include "util/Util.h"

using android::StringPiece;
using ::android::StringPiece;

namespace aapt {

@@ -134,19 +134,21 @@ static xml::AaptAttribute CreateAttributeWithId(const ResourceId& id) {
  return xml::AaptAttribute(Attribute(), id);
}

static xml::NamespaceDecl CreateAndroidNamespaceDecl() {
  xml::NamespaceDecl decl;
  decl.prefix = "android";
  decl.uri = xml::kSchemaAndroid;
  return decl;
}

std::unique_ptr<xml::XmlResource> GenerateSplitManifest(const AppInfo& app_info,
                                                        const SplitConstraints& constraints) {
  const ResourceId kVersionCode(0x0101021b);
  const ResourceId kRevisionCode(0x010104d5);
  const ResourceId kHasCode(0x0101000c);

  std::unique_ptr<xml::XmlResource> doc = util::make_unique<xml::XmlResource>();

  std::unique_ptr<xml::Namespace> namespace_android = util::make_unique<xml::Namespace>();
  namespace_android->namespace_uri = xml::kSchemaAndroid;
  namespace_android->namespace_prefix = "android";

  std::unique_ptr<xml::Element> manifest_el = util::make_unique<xml::Element>();
  manifest_el->namespace_decls.push_back(CreateAndroidNamespaceDecl());
  manifest_el->name = "manifest";
  manifest_el->attributes.push_back(xml::Attribute{"", "package", app_info.package});

@@ -179,8 +181,8 @@ std::unique_ptr<xml::XmlResource> GenerateSplitManifest(const AppInfo& app_info,
        xml::Attribute{"", "configForSplit", app_info.split_name.value()});
  }

  // Splits may contain more configurations than originally desired (fallback densities, etc.).
  // This makes programmatic discovery of split targetting difficult. Encode the original
  // Splits may contain more configurations than originally desired (fall-back densities, etc.).
  // This makes programmatic discovery of split targeting difficult. Encode the original
  // split constraints intended for this split.
  std::stringstream target_config_str;
  target_config_str << util::Joiner(constraints.configs, ",");
@@ -193,8 +195,9 @@ std::unique_ptr<xml::XmlResource> GenerateSplitManifest(const AppInfo& app_info,
                     util::make_unique<BinaryPrimitive>(android::Res_value::TYPE_INT_BOOLEAN, 0u)});

  manifest_el->AppendChild(std::move(application_el));
  namespace_android->AppendChild(std::move(manifest_el));
  doc->root = std::move(namespace_android);

  std::unique_ptr<xml::XmlResource> doc = util::make_unique<xml::XmlResource>();
  doc->root = std::move(manifest_el);
  return doc;
}

@@ -284,7 +287,7 @@ static Maybe<int> ExtractSdkVersion(xml::Attribute* attr, std::string* out_error

Maybe<AppInfo> ExtractAppInfoFromBinaryManifest(xml::XmlResource* xml_res, IDiagnostics* diag) {
  // Make sure the first element is <manifest> with package attribute.
  xml::Element* manifest_el = xml::FindRootElement(xml_res->root.get());
  xml::Element* manifest_el = xml_res->root.get();
  if (manifest_el == nullptr) {
    return {};
  }
+40 −68
Original line number Diff line number Diff line
@@ -16,12 +16,8 @@

#include "compile/InlineXmlFormatParser.h"

#include <sstream>
#include <string>

#include "android-base/macros.h"

#include "Debug.h"
#include "ResourceUtils.h"
#include "util/Util.h"
#include "xml/XmlDom.h"
@@ -31,19 +27,17 @@ namespace aapt {

namespace {

/**
 * XML Visitor that will find all <aapt:attr> elements for extraction.
 */
class Visitor : public xml::PackageAwareVisitor {
 public:
  using xml::PackageAwareVisitor::Visit;

struct InlineDeclaration {
  xml::Element* el;
  std::string attr_namespace_uri;
  std::string attr_name;
};

// XML Visitor that will find all <aapt:attr> elements for extraction.
class Visitor : public xml::PackageAwareVisitor {
 public:
  using xml::PackageAwareVisitor::Visit;

  explicit Visitor(IAaptContext* context, xml::XmlResource* xml_resource)
      : context_(context), xml_resource_(xml_resource) {}

@@ -53,51 +47,44 @@ class Visitor : public xml::PackageAwareVisitor {
      return;
    }

    const Source& src = xml_resource_->file.source.WithLine(el->line_number);
    const Source src = xml_resource_->file.source.WithLine(el->line_number);

    xml::Attribute* attr = el->FindAttribute({}, "name");
    if (!attr) {
      context_->GetDiagnostics()->Error(DiagMessage(src)
                                        << "missing 'name' attribute");
      context_->GetDiagnostics()->Error(DiagMessage(src) << "missing 'name' attribute");
      error_ = true;
      return;
    }

    Maybe<Reference> ref = ResourceUtils::ParseXmlAttributeName(attr->value);
    if (!ref) {
      context_->GetDiagnostics()->Error(
          DiagMessage(src) << "invalid XML attribute '" << attr->value << "'");
      context_->GetDiagnostics()->Error(DiagMessage(src) << "invalid XML attribute '" << attr->value
                                                         << "'");
      error_ = true;
      return;
    }

    const ResourceName& name = ref.value().name.value();

    // Use an empty string for the compilation package because we don't want to
    // default to
    // the local package if the user specified name="style" or something. This
    // should just
    // Use an empty string for the compilation package because we don't want to default to
    // the local package if the user specified name="style" or something. This should just
    // be the default namespace.
    Maybe<xml::ExtractedPackage> maybe_pkg =
        TransformPackageAlias(name.package, {});
    Maybe<xml::ExtractedPackage> maybe_pkg = TransformPackageAlias(name.package, {});
    if (!maybe_pkg) {
      context_->GetDiagnostics()->Error(DiagMessage(src)
                                        << "invalid namespace prefix '"
      context_->GetDiagnostics()->Error(DiagMessage(src) << "invalid namespace prefix '"
                                                         << name.package << "'");
      error_ = true;
      return;
    }

    const xml::ExtractedPackage& pkg = maybe_pkg.value();
    const bool private_namespace =
        pkg.private_namespace || ref.value().private_reference;
    const bool private_namespace = pkg.private_namespace || ref.value().private_reference;

    InlineDeclaration decl;
    decl.el = el;
    decl.attr_name = name.entry;
    if (!pkg.package.empty()) {
      decl.attr_namespace_uri =
          xml::BuildPackageNamespace(pkg.package, private_namespace);
      decl.attr_namespace_uri = xml::BuildPackageNamespace(pkg.package, private_namespace);
    }

    inline_declarations_.push_back(std::move(decl));
@@ -107,7 +94,9 @@ class Visitor : public xml::PackageAwareVisitor {
    return inline_declarations_;
  }

  bool HasError() const { return error_; }
  bool HasError() const {
    return error_;
  }

 private:
  DISALLOW_COPY_AND_ASSIGN(Visitor);
@@ -120,8 +109,7 @@ class Visitor : public xml::PackageAwareVisitor {

}  // namespace

bool InlineXmlFormatParser::Consume(IAaptContext* context,
                                    xml::XmlResource* doc) {
bool InlineXmlFormatParser::Consume(IAaptContext* context, xml::XmlResource* doc) {
  Visitor visitor(context, doc);
  doc->root->Accept(&visitor);
  if (visitor.HasError()) {
@@ -129,69 +117,53 @@ bool InlineXmlFormatParser::Consume(IAaptContext* context,
  }

  size_t name_suffix_counter = 0;
  for (const Visitor::InlineDeclaration& decl :
       visitor.GetInlineDeclarations()) {
  for (const InlineDeclaration& decl : visitor.GetInlineDeclarations()) {
    auto new_doc = util::make_unique<xml::XmlResource>();
    new_doc->file.config = doc->file.config;
    new_doc->file.source = doc->file.source.WithLine(decl.el->line_number);
    new_doc->file.name = doc->file.name;

    // Modify the new entry name. We need to suffix the entry with a number to
    // avoid
    // local collisions, then mangle it with the empty package, such that it
    // won't show up
    // avoid local collisions, then mangle it with the empty package, such that it won't show up
    // in R.java.

    new_doc->file.name.entry =
        NameMangler::MangleEntry({}, new_doc->file.name.entry + "__" +
                                         std::to_string(name_suffix_counter));
    new_doc->file.name.entry = NameMangler::MangleEntry(
        {}, new_doc->file.name.entry + "__" + std::to_string(name_suffix_counter));

    // Extracted elements must be the only child of <aapt:attr>.
    // Make sure there is one root node in the children (ignore empty text).
    for (auto& child : decl.el->children) {
    for (std::unique_ptr<xml::Node>& child : decl.el->children) {
      const Source child_source = doc->file.source.WithLine(child->line_number);
      if (xml::Text* t = xml::NodeCast<xml::Text>(child.get())) {
        if (!util::TrimWhitespace(t->text).empty()) {
          context->GetDiagnostics()->Error(
              DiagMessage(child_source)
          context->GetDiagnostics()->Error(DiagMessage(child_source)
                                           << "can't extract text into its own resource");
          return false;
        }
      } else if (new_doc->root) {
        context->GetDiagnostics()->Error(
            DiagMessage(child_source)
        context->GetDiagnostics()->Error(DiagMessage(child_source)
                                         << "inline XML resources must have a single root");
        return false;
      } else {
        new_doc->root = std::move(child);
        new_doc->root.reset(static_cast<xml::Element*>(child.release()));
        new_doc->root->parent = nullptr;
      }
    }

    // Walk up and find the parent element.
    xml::Node* node = decl.el;
    xml::Element* parent_el = nullptr;
    while (node->parent &&
           (parent_el = xml::NodeCast<xml::Element>(node->parent)) == nullptr) {
      node = node->parent;
    }

    // Get the parent element of <aapt:attr>
    xml::Element* parent_el = decl.el->parent;
    if (!parent_el) {
      context->GetDiagnostics()->Error(
          DiagMessage(new_doc->file.source)
      context->GetDiagnostics()->Error(DiagMessage(new_doc->file.source)
                                       << "no suitable parent for inheriting attribute");
      return false;
    }

    // Add the inline attribute to the parent.
    parent_el->attributes.push_back(
        xml::Attribute{decl.attr_namespace_uri, decl.attr_name,
    parent_el->attributes.push_back(xml::Attribute{decl.attr_namespace_uri, decl.attr_name,
                                                   "@" + new_doc->file.name.ToString()});

    // Delete the subtree.
    for (auto iter = parent_el->children.begin();
         iter != parent_el->children.end(); ++iter) {
      if (iter->get() == node) {
    for (auto iter = parent_el->children.begin(); iter != parent_el->children.end(); ++iter) {
      if (iter->get() == decl.el) {
        parent_el->children.erase(iter);
        break;
      }
+18 −23
Original line number Diff line number Diff line
@@ -26,35 +26,30 @@

namespace aapt {

/**
 * Extracts Inline XML definitions into their own xml::XmlResource objects.
 *
 * Inline XML looks like:
 *
 * <animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
 *                  xmlns:aapt="http://schemas.android.com/aapt" >
 *   <aapt:attr name="android:drawable" >
 *     <vector
 *       android:height="64dp"
 *       android:width="64dp"
 *       android:viewportHeight="600"
 *       android:viewportWidth="600"/>
 *   </aapt:attr>
 * </animated-vector>
 *
 * The <vector> will be extracted into its own XML file and <animated-vector>
 * will
 * gain an attribute 'android:drawable' set to a reference to the extracted
 * <vector> resource.
 */
// Extracts Inline XML definitions into their own xml::XmlResource objects.
//
// Inline XML looks like:
//
// <animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
//                  xmlns:aapt="http://schemas.android.com/aapt" >
//   <aapt:attr name="android:drawable" >
//     <vector
//       android:height="64dp"
//       android:width="64dp"
//       android:viewportHeight="600"
//       android:viewportWidth="600"/>
//   </aapt:attr>
// </animated-vector>
//
// The <vector> will be extracted into its own XML file and <animated-vector> will
// gain an attribute 'android:drawable' set to a reference to the extracted <vector> resource.
class InlineXmlFormatParser : public IXmlResourceConsumer {
 public:
  explicit InlineXmlFormatParser() = default;

  bool Consume(IAaptContext* context, xml::XmlResource* doc) override;

  std::vector<std::unique_ptr<xml::XmlResource>>&
  GetExtractedInlineXmlDocuments() {
  std::vector<std::unique_ptr<xml::XmlResource>>& GetExtractedInlineXmlDocuments() {
    return queue_;
  }

Loading