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

Commit 3ec44592 authored by Ryan Mitchell's avatar Ryan Mitchell Committed by Android (Google) Code Review
Browse files

Merge "Add <macro> tag to aapt2" into sc-dev

parents a78718ac 326e35ff
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -78,6 +78,8 @@ StringPiece to_string(ResourceType type) {
      return "interpolator";
    case ResourceType::kLayout:
      return "layout";
    case ResourceType::kMacro:
      return "macro";
    case ResourceType::kMenu:
      return "menu";
    case ResourceType::kMipmap:
@@ -119,6 +121,7 @@ static const std::map<StringPiece, ResourceType> sResourceTypeMap{
    {"integer", ResourceType::kInteger},
    {"interpolator", ResourceType::kInterpolator},
    {"layout", ResourceType::kLayout},
    {"macro", ResourceType::kMacro},
    {"menu", ResourceType::kMenu},
    {"mipmap", ResourceType::kMipmap},
    {"navigation", ResourceType::kNavigation},
+1 −0
Original line number Diff line number Diff line
@@ -57,6 +57,7 @@ enum class ResourceType {
  kInteger,
  kInterpolator,
  kLayout,
  kMacro,
  kMenu,
  kMipmap,
  kNavigation,
+85 −25
Original line number Diff line number Diff line
@@ -627,6 +627,16 @@ bool ResourceParser::ParseResource(xml::XmlPullParser* parser,
    }

    return true;
  } else if (resource_type == "macro") {
    if (!maybe_name) {
      diag_->Error(DiagMessage(out_resource->source)
                   << "<" << parser->element_name() << "> missing 'name' attribute");
      return false;
    }

    out_resource->name.type = ResourceType::kMacro;
    out_resource->name.entry = maybe_name.value().to_string();
    return ParseMacro(parser, out_resource);
  }

  if (can_be_item) {
@@ -726,6 +736,24 @@ bool ResourceParser::ParseItem(xml::XmlPullParser* parser,
  return true;
}

std::optional<FlattenedXmlSubTree> ResourceParser::CreateFlattenSubTree(
    xml::XmlPullParser* parser) {
  const size_t begin_xml_line = parser->line_number();

  std::string raw_value;
  StyleString style_string;
  std::vector<UntranslatableSection> untranslatable_sections;
  if (!FlattenXmlSubtree(parser, &raw_value, &style_string, &untranslatable_sections)) {
    return {};
  }

  return FlattenedXmlSubTree{.raw_value = raw_value,
                             .style_string = style_string,
                             .untranslatable_sections = untranslatable_sections,
                             .namespace_resolver = parser,
                             .source = source_.WithLine(begin_xml_line)};
}

/**
 * Reads the entire XML subtree and attempts to parse it as some Item,
 * with typeMask denoting which items it can be. If allowRawValue is
@@ -733,42 +761,46 @@ bool ResourceParser::ParseItem(xml::XmlPullParser* parser,
 * an Item. If allowRawValue is false, nullptr is returned in this
 * case.
 */
std::unique_ptr<Item> ResourceParser::ParseXml(xml::XmlPullParser* parser,
                                               const uint32_t type_mask,
std::unique_ptr<Item> ResourceParser::ParseXml(xml::XmlPullParser* parser, const uint32_t type_mask,
                                               const bool allow_raw_value) {
  const size_t begin_xml_line = parser->line_number();

  std::string raw_value;
  StyleString style_string;
  std::vector<UntranslatableSection> untranslatable_sections;
  if (!FlattenXmlSubtree(parser, &raw_value, &style_string, &untranslatable_sections)) {
  auto sub_tree = CreateFlattenSubTree(parser);
  if (!sub_tree.has_value()) {
    return {};
  }
  return ParseXml(sub_tree.value(), type_mask, allow_raw_value, *table_, config_, *diag_);
}

  if (!style_string.spans.empty()) {
std::unique_ptr<Item> ResourceParser::ParseXml(const FlattenedXmlSubTree& xmlsub_tree,
                                               const uint32_t type_mask, const bool allow_raw_value,
                                               ResourceTable& table,
                                               const android::ConfigDescription& config,
                                               IDiagnostics& diag) {
  if (!xmlsub_tree.style_string.spans.empty()) {
    // This can only be a StyledString.
    std::unique_ptr<StyledString> styled_string =
        util::make_unique<StyledString>(table_->string_pool.MakeRef(
            style_string, StringPool::Context(StringPool::Context::kNormalPriority, config_)));
    styled_string->untranslatable_sections = std::move(untranslatable_sections);
        util::make_unique<StyledString>(table.string_pool.MakeRef(
            xmlsub_tree.style_string,
            StringPool::Context(StringPool::Context::kNormalPriority, config)));
    styled_string->untranslatable_sections = xmlsub_tree.untranslatable_sections;
    return std::move(styled_string);
  }

  auto on_create_reference = [&](const ResourceName& name) {
    // name.package can be empty here, as it will assume the package name of the
    // table.
    std::unique_ptr<Id> id = util::make_unique<Id>();
    id->SetSource(source_.WithLine(begin_xml_line));
    table_->AddResource(NewResourceBuilder(name).SetValue(std::move(id)).Build(), diag_);
    auto id = util::make_unique<Id>();
    id->SetSource(xmlsub_tree.source);
    return table.AddResource(NewResourceBuilder(name).SetValue(std::move(id)).Build(), &diag);
  };

  // Process the raw value.
  std::unique_ptr<Item> processed_item =
      ResourceUtils::TryParseItemForAttribute(raw_value, type_mask, on_create_reference);
  std::unique_ptr<Item> processed_item = ResourceUtils::TryParseItemForAttribute(
      xmlsub_tree.raw_value, type_mask, on_create_reference);
  if (processed_item) {
    // Fix up the reference.
    if (Reference* ref = ValueCast<Reference>(processed_item.get())) {
      ResolvePackage(parser, ref);
    if (auto ref = ValueCast<Reference>(processed_item.get())) {
      ref->allow_raw = allow_raw_value;
      ResolvePackage(xmlsub_tree.namespace_resolver, ref);
    }
    return processed_item;
  }
@@ -777,17 +809,16 @@ std::unique_ptr<Item> ResourceParser::ParseXml(xml::XmlPullParser* parser,
  if (type_mask & android::ResTable_map::TYPE_STRING) {
    // Use the trimmed, escaped string.
    std::unique_ptr<String> string = util::make_unique<String>(
        table_->string_pool.MakeRef(style_string.str, StringPool::Context(config_)));
    string->untranslatable_sections = std::move(untranslatable_sections);
        table.string_pool.MakeRef(xmlsub_tree.style_string.str, StringPool::Context(config)));
    string->untranslatable_sections = xmlsub_tree.untranslatable_sections;
    return std::move(string);
  }

  if (allow_raw_value) {
    // We can't parse this so return a RawString if we are allowed.
    return util::make_unique<RawString>(
        table_->string_pool.MakeRef(util::TrimWhitespace(raw_value),
                                    StringPool::Context(config_)));
  } else if (util::TrimWhitespace(raw_value).empty()) {
    return util::make_unique<RawString>(table.string_pool.MakeRef(
        util::TrimWhitespace(xmlsub_tree.raw_value), StringPool::Context(config)));
  } else if (util::TrimWhitespace(xmlsub_tree.raw_value).empty()) {
    // If the text is empty, and the value is not allowed to be a string, encode it as a @null.
    return ResourceUtils::MakeNull();
  }
@@ -850,6 +881,35 @@ bool ResourceParser::ParseString(xml::XmlPullParser* parser,
  return true;
}

bool ResourceParser::ParseMacro(xml::XmlPullParser* parser, ParsedResource* out_resource) {
  auto sub_tree = CreateFlattenSubTree(parser);
  if (!sub_tree) {
    return false;
  }

  if (out_resource->config != ConfigDescription::DefaultConfig()) {
    diag_->Error(DiagMessage(out_resource->source)
                 << "<macro> tags cannot be declared in configurations other than the default "
                    "configuration'");
    return false;
  }

  auto macro = std::make_unique<Macro>();
  macro->raw_value = std::move(sub_tree->raw_value);
  macro->style_string = std::move(sub_tree->style_string);
  macro->untranslatable_sections = std::move(sub_tree->untranslatable_sections);

  for (const auto& decl : parser->package_decls()) {
    macro->alias_namespaces.emplace_back(
        Macro::Namespace{.alias = decl.prefix,
                         .package_name = decl.package.package,
                         .is_private = decl.package.private_namespace});
  }

  out_resource->value = std::move(macro);
  return true;
}

bool ResourceParser::ParsePublic(xml::XmlPullParser* parser, ParsedResource* out_resource) {
  if (options_.visibility) {
    diag_->Error(DiagMessage(out_resource->source)
+17 −3
Original line number Diff line number Diff line
@@ -57,6 +57,14 @@ struct ResourceParserOptions {
  Maybe<Visibility::Level> visibility;
};

struct FlattenedXmlSubTree {
  std::string raw_value;
  StyleString style_string;
  std::vector<UntranslatableSection> untranslatable_sections;
  xml::IPackageDeclStack* namespace_resolver;
  Source source;
};

/*
 * Parses an XML file for resources and adds them to a ResourceTable.
 */
@@ -67,9 +75,16 @@ class ResourceParser {
                 const ResourceParserOptions& options = {});
  bool Parse(xml::XmlPullParser* parser);

  static std::unique_ptr<Item> ParseXml(const FlattenedXmlSubTree& xmlsub_tree, uint32_t type_mask,
                                        bool allow_raw_value, ResourceTable& table,
                                        const android::ConfigDescription& config,
                                        IDiagnostics& diag);

 private:
  DISALLOW_COPY_AND_ASSIGN(ResourceParser);

  std::optional<FlattenedXmlSubTree> CreateFlattenSubTree(xml::XmlPullParser* parser);

  // Parses the XML subtree as a StyleString (flattened XML representation for strings with
  // formatting). If parsing fails, false is returned and the out parameters are left in an
  // unspecified state. Otherwise,
@@ -96,7 +111,7 @@ class ResourceParser {

  bool ParseItem(xml::XmlPullParser* parser, ParsedResource* out_resource, uint32_t format);
  bool ParseString(xml::XmlPullParser* parser, ParsedResource* out_resource);

  bool ParseMacro(xml::XmlPullParser* parser, ParsedResource* out_resource);
  bool ParsePublic(xml::XmlPullParser* parser, ParsedResource* out_resource);
  bool ParsePublicGroup(xml::XmlPullParser* parser, ParsedResource* out_resource);
  bool ParseStagingPublicGroup(xml::XmlPullParser* parser, ParsedResource* out_resource);
@@ -108,8 +123,7 @@ class ResourceParser {
  bool ParseAttrImpl(xml::XmlPullParser* parser, ParsedResource* out_resource, bool weak);
  Maybe<Attribute::Symbol> ParseEnumOrFlagItem(xml::XmlPullParser* parser,
                                               const android::StringPiece& tag);
  bool ParseStyle(const ResourceType type, xml::XmlPullParser* parser,
                  ParsedResource* out_resource);
  bool ParseStyle(ResourceType type, xml::XmlPullParser* parser, ParsedResource* out_resource);
  bool ParseStyleItem(xml::XmlPullParser* parser, Style* style);
  bool ParseDeclareStyleable(xml::XmlPullParser* parser, ParsedResource* out_resource);
  bool ParseArray(xml::XmlPullParser* parser, ParsedResource* out_resource);
+84 −0
Original line number Diff line number Diff line
@@ -336,6 +336,90 @@ TEST_F(ResourceParserTest, ParseAttr) {
  EXPECT_THAT(attr->type_mask, Eq(ResTable_map::TYPE_ANY));
}

TEST_F(ResourceParserTest, ParseMacro) {
  std::string input = R"(<macro name="foo">12345</macro>)";
  ASSERT_TRUE(TestParse(input));

  Macro* macro = test::GetValue<Macro>(&table_, "macro/foo");
  ASSERT_THAT(macro, NotNull());
  EXPECT_THAT(macro->raw_value, Eq("12345"));
  EXPECT_THAT(macro->style_string.str, Eq("12345"));
  EXPECT_THAT(macro->style_string.spans, IsEmpty());
  EXPECT_THAT(macro->untranslatable_sections, IsEmpty());
  EXPECT_THAT(macro->alias_namespaces, IsEmpty());
}

TEST_F(ResourceParserTest, ParseMacroUntranslatableSection) {
  std::string input = R"(<macro name="foo" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
This being <b><xliff:g>human</xliff:g></b> is a guest house.</macro>)";
  ASSERT_TRUE(TestParse(input));

  Macro* macro = test::GetValue<Macro>(&table_, "macro/foo");
  ASSERT_THAT(macro, NotNull());
  EXPECT_THAT(macro->raw_value, Eq("\nThis being human is a guest house."));
  EXPECT_THAT(macro->style_string.str, Eq(" This being human is a guest house."));
  EXPECT_THAT(macro->style_string.spans.size(), Eq(1));
  EXPECT_THAT(macro->style_string.spans[0].name, Eq("b"));
  EXPECT_THAT(macro->style_string.spans[0].first_char, Eq(12));
  EXPECT_THAT(macro->style_string.spans[0].last_char, Eq(16));
  ASSERT_THAT(macro->untranslatable_sections.size(), Eq(1));
  EXPECT_THAT(macro->untranslatable_sections[0].start, Eq(12));
  EXPECT_THAT(macro->untranslatable_sections[0].end, Eq(17));
  EXPECT_THAT(macro->alias_namespaces, IsEmpty());
}

TEST_F(ResourceParserTest, ParseMacroNamespaces) {
  std::string input = R"(<macro name="foo" xmlns:app="http://schemas.android.com/apk/res/android">
@app:string/foo</macro>)";
  ASSERT_TRUE(TestParse(input));

  Macro* macro = test::GetValue<Macro>(&table_, "macro/foo");
  ASSERT_THAT(macro, NotNull());
  EXPECT_THAT(macro->raw_value, Eq("\n@app:string/foo"));
  EXPECT_THAT(macro->style_string.str, Eq("@app:string/foo"));
  EXPECT_THAT(macro->style_string.spans, IsEmpty());
  EXPECT_THAT(macro->untranslatable_sections, IsEmpty());
  EXPECT_THAT(macro->alias_namespaces.size(), Eq(1));
  EXPECT_THAT(macro->alias_namespaces[0].alias, Eq("app"));
  EXPECT_THAT(macro->alias_namespaces[0].package_name, Eq("android"));
  EXPECT_THAT(macro->alias_namespaces[0].is_private, Eq(false));
}

TEST_F(ResourceParserTest, ParseMacroReference) {
  std::string input = R"(<string name="res_string">@macro/foo</string>)";
  ASSERT_TRUE(TestParse(input));

  Reference* macro = test::GetValue<Reference>(&table_, "string/res_string");
  ASSERT_THAT(macro, NotNull());
  EXPECT_THAT(macro->type_flags, Eq(ResTable_map::TYPE_STRING));
  EXPECT_THAT(macro->allow_raw, Eq(false));

  input = R"(<style name="foo">
               <item name="bar">@macro/foo</item>
             </style>)";

  ASSERT_TRUE(TestParse(input));
  Style* style = test::GetValue<Style>(&table_, "style/foo");
  ASSERT_THAT(style, NotNull());
  EXPECT_THAT(style->entries.size(), Eq(1));

  macro = ValueCast<Reference>(style->entries[0].value.get());
  ASSERT_THAT(macro, NotNull());
  EXPECT_THAT(macro->type_flags, Eq(0U));
  EXPECT_THAT(macro->allow_raw, Eq(true));
}

TEST_F(ResourceParserTest, ParseMacroNoNameFail) {
  std::string input = R"(<macro>12345</macro>)";
  ASSERT_FALSE(TestParse(input));
}

TEST_F(ResourceParserTest, ParseMacroNonDefaultConfigurationFail) {
  const ConfigDescription watch_config = test::ParseConfigOrDie("watch");
  std::string input = R"(<macro name="foo">12345</macro>)";
  ASSERT_FALSE(TestParse(input, watch_config));
}

// Old AAPT allowed attributes to be defined under different configurations, but ultimately
// stored them with the default configuration. Check that we have the same behavior.
TEST_F(ResourceParserTest, ParseAttrAndDeclareStyleableUnderConfigButRecordAsNoConfig) {
Loading