diff --git a/tools/aapt2/format/binary/XmlFlattener.cpp b/tools/aapt2/format/binary/XmlFlattener.cpp index d897941da6c46..2fe24245f3d98 100644 --- a/tools/aapt2/format/binary/XmlFlattener.cpp +++ b/tools/aapt2/format/binary/XmlFlattener.cpp @@ -79,23 +79,31 @@ class XmlFlattenerVisitor : public xml::ConstVisitor { } void Visit(const xml::Text* node) override { - if (util::TrimWhitespace(node->text).empty()) { - // Skip whitespace only text nodes. + std::string text = util::TrimWhitespace(node->text).to_string(); + + // Skip whitespace only text nodes. + if (text.empty()) { return; } + // Compact leading and trailing whitespace into a single space + if (isspace(node->text[0])) { + text = ' ' + text; + } + if (isspace(node->text[node->text.length() - 1])) { + text = text + ' '; + } + ChunkWriter writer(buffer_); ResXMLTree_node* flat_node = writer.StartChunk(RES_XML_CDATA_TYPE); flat_node->lineNumber = util::HostToDevice32(node->line_number); flat_node->comment.index = util::HostToDevice32(-1); - ResXMLTree_cdataExt* flat_text = writer.NextBlock(); - // Process plain strings to make sure they get properly escaped. - StringBuilder builder; - builder.AppendText(node->text); - AddString(builder.to_string(), kLowPriority, &flat_text->data); + text = StringBuilder(true /*preserve_spaces*/).AppendText(text).to_string(); + ResXMLTree_cdataExt* flat_text = writer.NextBlock(); + AddString(text, kLowPriority, &flat_text->data); writer.Finish(); } diff --git a/tools/aapt2/format/binary/XmlFlattener_test.cpp b/tools/aapt2/format/binary/XmlFlattener_test.cpp index 08243feb3769f..25786b1659e7d 100644 --- a/tools/aapt2/format/binary/XmlFlattener_test.cpp +++ b/tools/aapt2/format/binary/XmlFlattener_test.cpp @@ -286,6 +286,165 @@ TEST_F(XmlFlattenerTest, ProcessEscapedStrings) { EXPECT_THAT(tree.getText(&len), StrEq(u"\\d{5}")); } +TEST_F(XmlFlattenerTest, ProcessQuotes) { + std::unique_ptr doc = test::BuildXmlDom( + R"( + Regular text + "Text in double quotes" + 'Text in single quotes' + Text containing "double quotes" + Text containing 'single quotes' + )"); + + size_t len; + android::ResXMLTree tree; + + XmlFlattenerOptions options; + ASSERT_TRUE(Flatten(doc.get(), &tree, options)); + + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::START_TAG)); + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::START_TAG)); + EXPECT_THAT(tree.getElementName(&len), StrEq(u"item")); + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::TEXT)); + EXPECT_THAT(tree.getText(&len), StrEq(u"Regular text")); + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::END_TAG)); + + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::START_TAG)); + EXPECT_THAT(tree.getElementName(&len), StrEq(u"item")); + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::TEXT)); + EXPECT_THAT(tree.getText(&len), StrEq(u"\"Text in double quotes\"")); + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::END_TAG)); + + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::START_TAG)); + EXPECT_THAT(tree.getElementNamespace(&len), IsNull()); + EXPECT_THAT(tree.getElementName(&len), StrEq(u"item")); + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::TEXT)); + EXPECT_THAT(tree.getText(&len), StrEq(u"'Text in single quotes'")); + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::END_TAG)); + + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::START_TAG)); + EXPECT_THAT(tree.getElementName(&len), StrEq(u"item")); + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::TEXT)); + EXPECT_THAT(tree.getText(&len), StrEq(u"Text containing \"double quotes\"")); + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::END_TAG)); + + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::START_TAG)); + EXPECT_THAT(tree.getElementName(&len), StrEq(u"item")); + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::TEXT)); + EXPECT_THAT(tree.getText(&len), StrEq(u"Text containing 'single quotes'")); + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::END_TAG)); + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::END_TAG)); + + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::END_DOCUMENT)); +} + +TEST_F(XmlFlattenerTest, ProcessWhitepspace) { + std::unique_ptr doc = test::BuildXmlDom( + R"( + Compact Spaces + + A + + B + C + D + E + F + G + H + +I + + + + J + + + + + )"); + + size_t len; + android::ResXMLTree tree; + + XmlFlattenerOptions options; + ASSERT_TRUE(Flatten(doc.get(), &tree, options)); + + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::START_TAG)); + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::START_TAG)); + EXPECT_THAT(tree.getElementName(&len), StrEq(u"item")); + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::TEXT)); + EXPECT_THAT(tree.getText(&len), StrEq(u" Compact Spaces ")); + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::END_TAG)); + + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::START_TAG)); + EXPECT_THAT(tree.getElementName(&len), StrEq(u"item")); + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::TEXT)); + EXPECT_THAT(tree.getText(&len), StrEq(u" A ")); + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::END_TAG)); + + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::START_TAG)); + EXPECT_THAT(tree.getElementName(&len), StrEq(u"item")); + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::TEXT)); + EXPECT_THAT(tree.getText(&len), StrEq(u"B ")); + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::END_TAG)); + + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::START_TAG)); + EXPECT_THAT(tree.getElementName(&len), StrEq(u"item")); + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::TEXT)); + EXPECT_THAT(tree.getText(&len), StrEq(u"C ")); + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::END_TAG)); + + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::START_TAG)); + EXPECT_THAT(tree.getElementName(&len), StrEq(u"item")); + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::TEXT)); + EXPECT_THAT(tree.getText(&len), StrEq(u" D ")); + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::END_TAG)); + + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::START_TAG)); + EXPECT_THAT(tree.getElementName(&len), StrEq(u"item")); + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::TEXT)); + EXPECT_THAT(tree.getText(&len), StrEq(u" E")); + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::END_TAG)); + + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::START_TAG)); + EXPECT_THAT(tree.getElementName(&len), StrEq(u"item")); + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::TEXT)); + EXPECT_THAT(tree.getText(&len), StrEq(u" F")); + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::END_TAG)); + + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::START_TAG)); + EXPECT_THAT(tree.getElementName(&len), StrEq(u"item")); + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::TEXT)); + EXPECT_THAT(tree.getText(&len), StrEq(u" G ")); + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::END_TAG)); + + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::START_TAG)); + EXPECT_THAT(tree.getElementName(&len), StrEq(u"item")); + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::TEXT)); + EXPECT_THAT(tree.getText(&len), StrEq(u" H ")); + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::END_TAG)); + + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::START_TAG)); + EXPECT_THAT(tree.getElementName(&len), StrEq(u"item")); + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::TEXT)); + EXPECT_THAT(tree.getText(&len), StrEq(u" I ")); + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::END_TAG)); + + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::START_TAG)); + EXPECT_THAT(tree.getElementName(&len), StrEq(u"item")); + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::TEXT)); + EXPECT_THAT(tree.getText(&len), StrEq(u" J ")); + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::END_TAG)); + + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::START_TAG)); + EXPECT_THAT(tree.getElementName(&len), StrEq(u"item")); + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::END_TAG)); + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::END_TAG)); + + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::END_DOCUMENT)); +} + TEST_F(XmlFlattenerTest, FlattenRawValueOnlyMakesCompiledValueToo) { std::unique_ptr doc = test::BuildXmlDom(R"()");