diff options
author | Adam Lesinski <adamlesinski@google.com> | 2015-04-09 19:53:22 -0700 |
---|---|---|
committer | Adam Lesinski <adamlesinski@google.com> | 2015-04-10 15:25:39 -0700 |
commit | 4d3a987694f6f6b95d8a0f1542618223ce253e6d (patch) | |
tree | 2cd3a420ba07991db07d0b31d7a93030754d3cb9 /tools/aapt2 | |
parent | a826c4b7a4c7ffd471973d58e46ac6f1f2cad83a (diff) | |
download | frameworks_base-4d3a987694f6f6b95d8a0f1542618223ce253e6d.zip frameworks_base-4d3a987694f6f6b95d8a0f1542618223ce253e6d.tar.gz frameworks_base-4d3a987694f6f6b95d8a0f1542618223ce253e6d.tar.bz2 |
AAPT2: Adding basic binding support
This is incomplete. Still requires:
- filling in layout information in the resulting .bind.xml
- processing elements with <view class=""/>
- processing imports
Change-Id: Ie5d4c5e6435591bbed3248129a548736244894eb
Diffstat (limited to 'tools/aapt2')
-rw-r--r-- | tools/aapt2/Android.mk | 2 | ||||
-rw-r--r-- | tools/aapt2/BindingXmlPullParser.cpp | 263 | ||||
-rw-r--r-- | tools/aapt2/BindingXmlPullParser.h | 88 | ||||
-rw-r--r-- | tools/aapt2/BindingXmlPullParser_test.cpp | 110 | ||||
-rw-r--r-- | tools/aapt2/Files.cpp | 11 | ||||
-rw-r--r-- | tools/aapt2/Files.h | 5 | ||||
-rw-r--r-- | tools/aapt2/Main.cpp | 44 | ||||
-rw-r--r-- | tools/aapt2/Png.cpp | 2 | ||||
-rw-r--r-- | tools/aapt2/Util.cpp | 7 | ||||
-rw-r--r-- | tools/aapt2/Util.h | 19 | ||||
-rw-r--r-- | tools/aapt2/Util_test.cpp | 6 | ||||
-rw-r--r-- | tools/aapt2/data/res/layout/main.xml | 4 |
12 files changed, 540 insertions, 21 deletions
diff --git a/tools/aapt2/Android.mk b/tools/aapt2/Android.mk index 14f558e..0622dc6 100644 --- a/tools/aapt2/Android.mk +++ b/tools/aapt2/Android.mk @@ -27,6 +27,7 @@ main := Main.cpp sources := \ BigBuffer.cpp \ BinaryResourceParser.cpp \ + BindingXmlPullParser.cpp \ ConfigDescription.cpp \ Files.cpp \ Flag.cpp \ @@ -54,6 +55,7 @@ sources := \ testSources := \ BigBuffer_test.cpp \ + BindingXmlPullParser_test.cpp \ Compat_test.cpp \ ConfigDescription_test.cpp \ JavaClassGenerator_test.cpp \ diff --git a/tools/aapt2/BindingXmlPullParser.cpp b/tools/aapt2/BindingXmlPullParser.cpp new file mode 100644 index 0000000..58b96e8 --- /dev/null +++ b/tools/aapt2/BindingXmlPullParser.cpp @@ -0,0 +1,263 @@ +/* + * 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 "BindingXmlPullParser.h" +#include "Util.h" + +#include <iostream> +#include <sstream> +#include <string> +#include <vector> + +namespace aapt { + +constexpr const char16_t* kBindingNamespaceUri = u"http://schemas.android.com/apk/binding"; +constexpr const char16_t* kAndroidNamespaceUri = u"http://schemas.android.com/apk/res/android"; +constexpr const char16_t* kVariableTagName = u"variable"; +constexpr const char* kBindingTagPrefix = "android:binding_"; + +BindingXmlPullParser::BindingXmlPullParser(const std::shared_ptr<XmlPullParser>& parser) : + mParser(parser), mOverride(false), mNextTagId(0) { +} + +bool BindingXmlPullParser::readVariableDeclaration() { + VarDecl var; + + const auto endAttrIter = mParser->endAttributes(); + for (auto attrIter = mParser->beginAttributes(); attrIter != endAttrIter; ++attrIter) { + if (!attrIter->namespaceUri.empty()) { + continue; + } + + if (attrIter->name == u"name") { + var.name = util::utf16ToUtf8(attrIter->value); + } else if (attrIter->name == u"type") { + var.type = util::utf16ToUtf8(attrIter->value); + } + } + + XmlPullParser::skipCurrentElement(mParser.get()); + + if (var.name.empty()) { + mLastError = "variable declaration missing name"; + return false; + } + + if (var.type.empty()) { + mLastError = "variable declaration missing type"; + return false; + } + + mVarDecls.push_back(std::move(var)); + return true; +} + +bool BindingXmlPullParser::readExpressions() { + mOverride = true; + std::vector<XmlPullParser::Attribute> expressions; + std::string idValue; + + const auto endAttrIter = mParser->endAttributes(); + for (auto attr = mParser->beginAttributes(); attr != endAttrIter; ++attr) { + if (attr->namespaceUri == kAndroidNamespaceUri && attr->name == u"id") { + idValue = util::utf16ToUtf8(attr->value); + } else { + StringPiece16 value = util::trimWhitespace(attr->value); + if (util::stringStartsWith<char16_t>(value, u"@{") && + util::stringEndsWith<char16_t>(value, u"}")) { + // This is attribute's value is an expression of the form + // @{expression}. We need to capture the expression inside. + expressions.push_back(XmlPullParser::Attribute{ + attr->namespaceUri, + attr->name, + value.substr(2, value.size() - 3).toString() + }); + } else { + // This is a normal attribute, use as is. + mAttributes.emplace_back(*attr); + } + } + } + + // Check if we have any expressions. + if (!expressions.empty()) { + // We have expressions, so let's assign the target a tag number + // and add it to our targets list. + int32_t targetId = mNextTagId++; + mTargets.push_back(Target{ + util::utf16ToUtf8(mParser->getElementName()), + idValue, + targetId, + std::move(expressions) + }); + + std::stringstream numGen; + numGen << kBindingTagPrefix << targetId; + mAttributes.push_back(XmlPullParser::Attribute{ + std::u16string(kAndroidNamespaceUri), + std::u16string(u"tag"), + util::utf8ToUtf16(numGen.str()) + }); + } + return true; +} + +XmlPullParser::Event BindingXmlPullParser::next() { + // Clear old state in preparation for the next event. + mOverride = false; + mAttributes.clear(); + + while (true) { + Event event = mParser->next(); + if (event == Event::kStartElement) { + if (mParser->getElementNamespace().empty() && + mParser->getElementName() == kVariableTagName) { + // This is a variable tag. Record data from it, and + // then discard the entire element. + if (!readVariableDeclaration()) { + // mLastError is set, so getEvent will return kBadDocument. + return getEvent(); + } + continue; + } else { + // Check for expressions of the form @{} in attribute text. + const auto endAttrIter = mParser->endAttributes(); + for (auto attr = mParser->beginAttributes(); attr != endAttrIter; ++attr) { + StringPiece16 value = util::trimWhitespace(attr->value); + if (util::stringStartsWith<char16_t>(value, u"@{") && + util::stringEndsWith<char16_t>(value, u"}")) { + if (!readExpressions()) { + return getEvent(); + } + break; + } + } + } + } else if (event == Event::kStartNamespace || event == Event::kEndNamespace) { + if (mParser->getNamespaceUri() == kBindingNamespaceUri) { + // Skip binding namespace tags. + continue; + } + } + return event; + } + return Event::kBadDocument; +} + +bool BindingXmlPullParser::writeToFile(std::ostream& out) const { + out << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"; + out << "<Layout directory=\"\" layout=\"\" layoutId=\"\">\n"; + + // Write the variables. + out << " <Variables>\n"; + for (const VarDecl& v : mVarDecls) { + out << " <entries name=\"" << v.name << "\" type=\"" << v.type << "\"/>\n"; + } + out << " </Variables>\n"; + + // Write the imports. + + std::stringstream tagGen; + + // Write the targets. + out << " <Targets>\n"; + for (const Target& t : mTargets) { + tagGen.str({}); + tagGen << kBindingTagPrefix << t.tagId; + out << " <Target boundClass=\"" << t.className << "\" id=\"" << t.id + << "\" tag=\"" << tagGen.str() << "\">\n"; + out << " <Expressions>\n"; + for (const XmlPullParser::Attribute& a : t.expressions) { + out << " <Expression attribute=\"" << a.namespaceUri << ":" << a.name + << "\" text=\"" << a.value << "\"/>\n"; + } + out << " </Expressions>\n"; + out << " </Target>\n"; + } + out << " </Targets>\n"; + + out << "</Layout>\n"; + return bool(out); +} + +XmlPullParser::const_iterator BindingXmlPullParser::beginAttributes() const { + if (mOverride) { + return mAttributes.begin(); + } + return mParser->beginAttributes(); +} + +XmlPullParser::const_iterator BindingXmlPullParser::endAttributes() const { + if (mOverride) { + return mAttributes.end(); + } + return mParser->endAttributes(); +} + +size_t BindingXmlPullParser::getAttributeCount() const { + if (mOverride) { + return mAttributes.size(); + } + return mParser->getAttributeCount(); +} + +XmlPullParser::Event BindingXmlPullParser::getEvent() const { + if (!mLastError.empty()) { + return Event::kBadDocument; + } + return mParser->getEvent(); +} + +const std::string& BindingXmlPullParser::getLastError() const { + if (!mLastError.empty()) { + return mLastError; + } + return mParser->getLastError(); +} + +const std::u16string& BindingXmlPullParser::getComment() const { + return mParser->getComment(); +} + +size_t BindingXmlPullParser::getLineNumber() const { + return mParser->getLineNumber(); +} + +size_t BindingXmlPullParser::getDepth() const { + return mParser->getDepth(); +} + +const std::u16string& BindingXmlPullParser::getText() const { + return mParser->getText(); +} + +const std::u16string& BindingXmlPullParser::getNamespacePrefix() const { + return mParser->getNamespacePrefix(); +} + +const std::u16string& BindingXmlPullParser::getNamespaceUri() const { + return mParser->getNamespaceUri(); +} + +const std::u16string& BindingXmlPullParser::getElementNamespace() const { + return mParser->getElementNamespace(); +} + +const std::u16string& BindingXmlPullParser::getElementName() const { + return mParser->getElementName(); +} + +} // namespace aapt diff --git a/tools/aapt2/BindingXmlPullParser.h b/tools/aapt2/BindingXmlPullParser.h new file mode 100644 index 0000000..c892b09 --- /dev/null +++ b/tools/aapt2/BindingXmlPullParser.h @@ -0,0 +1,88 @@ +/* + * 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_BINDING_XML_PULL_PARSER_H +#define AAPT_BINDING_XML_PULL_PARSER_H + +#include "XmlPullParser.h" + +#include <iostream> +#include <memory> +#include <string> + +namespace aapt { + +class BindingXmlPullParser : public XmlPullParser { +public: + BindingXmlPullParser(const std::shared_ptr<XmlPullParser>& parser); + BindingXmlPullParser(const BindingXmlPullParser& 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; + + 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; + + bool writeToFile(std::ostream& out) const; + +private: + struct VarDecl { + std::string name; + std::string type; + }; + + struct Import { + std::string name; + std::string type; + }; + + struct Target { + std::string className; + std::string id; + int32_t tagId; + + std::vector<XmlPullParser::Attribute> expressions; + }; + + bool readVariableDeclaration(); + bool readExpressions(); + + std::shared_ptr<XmlPullParser> mParser; + std::string mLastError; + bool mOverride; + std::vector<XmlPullParser::Attribute> mAttributes; + std::vector<VarDecl> mVarDecls; + std::vector<Target> mTargets; + int32_t mNextTagId; +}; + +} // namespace aapt + +#endif // AAPT_BINDING_XML_PULL_PARSER_H diff --git a/tools/aapt2/BindingXmlPullParser_test.cpp b/tools/aapt2/BindingXmlPullParser_test.cpp new file mode 100644 index 0000000..28edcb6 --- /dev/null +++ b/tools/aapt2/BindingXmlPullParser_test.cpp @@ -0,0 +1,110 @@ +/* + * 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 "SourceXmlPullParser.h" +#include "BindingXmlPullParser.h" + +#include <gtest/gtest.h> +#include <sstream> +#include <string> + +namespace aapt { + +constexpr const char16_t* kAndroidNamespaceUri = u"http://schemas.android.com/apk/res/android"; + +TEST(BindingXmlPullParserTest, SubstituteBindingExpressionsWithTag) { + std::stringstream input; + input << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" + << "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" + << " xmlns:bind=\"http://schemas.android.com/apk/binding\"\n" + << " android:id=\"@+id/content\">\n" + << " <variable name=\"user\" type=\"com.android.test.User\"/>\n" + << " <TextView android:text=\"@{user.name}\" android:layout_width=\"wrap_content\"\n" + << " android:layout_height=\"wrap_content\"/>\n" + << "</LinearLayout>\n"; + std::shared_ptr<XmlPullParser> sourceParser = std::make_shared<SourceXmlPullParser>(input); + BindingXmlPullParser parser(sourceParser); + + ASSERT_EQ(XmlPullParser::Event::kStartNamespace, parser.next()); + EXPECT_EQ(std::u16string(u"http://schemas.android.com/apk/res/android"), + parser.getNamespaceUri()); + + ASSERT_EQ(XmlPullParser::Event::kStartElement, parser.next()); + EXPECT_EQ(std::u16string(u"LinearLayout"), parser.getElementName()); + + while (parser.next() == XmlPullParser::Event::kText) {} + + ASSERT_EQ(XmlPullParser::Event::kStartElement, parser.getEvent()); + EXPECT_EQ(std::u16string(u"TextView"), parser.getElementName()); + + ASSERT_EQ(3u, parser.getAttributeCount()); + const auto endAttr = parser.endAttributes(); + EXPECT_NE(endAttr, parser.findAttribute(kAndroidNamespaceUri, u"layout_width")); + EXPECT_NE(endAttr, parser.findAttribute(kAndroidNamespaceUri, u"layout_height")); + EXPECT_NE(endAttr, parser.findAttribute(kAndroidNamespaceUri, u"tag")); + + while (parser.next() == XmlPullParser::Event::kText) {} + + ASSERT_EQ(XmlPullParser::Event::kEndElement, parser.getEvent()); + + while (parser.next() == XmlPullParser::Event::kText) {} + + ASSERT_EQ(XmlPullParser::Event::kEndElement, parser.getEvent()); + ASSERT_EQ(XmlPullParser::Event::kEndNamespace, parser.next()); +} + +TEST(BindingXmlPullParserTest, GenerateVariableDeclarations) { + std::stringstream input; + input << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" + << "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" + << " xmlns:bind=\"http://schemas.android.com/apk/binding\"\n" + << " android:id=\"@+id/content\">\n" + << " <variable name=\"user\" type=\"com.android.test.User\"/>\n" + << "</LinearLayout>\n"; + std::shared_ptr<XmlPullParser> sourceParser = std::make_shared<SourceXmlPullParser>(input); + BindingXmlPullParser parser(sourceParser); + + while (XmlPullParser::isGoodEvent(parser.next())) { + ASSERT_NE(XmlPullParser::Event::kBadDocument, parser.getEvent()); + } + + std::stringstream output; + ASSERT_TRUE(parser.writeToFile(output)); + + std::string result = output.str(); + EXPECT_NE(std::string::npos, + result.find("<entries name=\"user\" type=\"com.android.test.User\"/>")); +} + +TEST(BindingXmlPullParserTest, FailOnMissingNameOrTypeInVariableDeclaration) { + std::stringstream input; + input << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" + << "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" + << " xmlns:bind=\"http://schemas.android.com/apk/binding\"\n" + << " android:id=\"@+id/content\">\n" + << " <variable name=\"user\"/>\n" + << "</LinearLayout>\n"; + std::shared_ptr<XmlPullParser> sourceParser = std::make_shared<SourceXmlPullParser>(input); + BindingXmlPullParser parser(sourceParser); + + while (XmlPullParser::isGoodEvent(parser.next())) {} + + EXPECT_EQ(XmlPullParser::Event::kBadDocument, parser.getEvent()); + EXPECT_FALSE(parser.getLastError().empty()); +} + + +} // namespace aapt diff --git a/tools/aapt2/Files.cpp b/tools/aapt2/Files.cpp index 349abbd..8484148 100644 --- a/tools/aapt2/Files.cpp +++ b/tools/aapt2/Files.cpp @@ -105,6 +105,17 @@ bool mkdirs(const StringPiece& path) { return mkdirImpl(path) == 0 || errno == EEXIST; } +std::string getStem(const StringPiece& path) { + const char* start = path.begin(); + const char* end = path.end(); + for (const char* current = end - 1; current != start - 1; --current) { + if (*current == sDirSep) { + return std::string(start, current - start); + } + } + return {}; +} + bool FileFilter::setPattern(const StringPiece& pattern) { mPatternTokens = util::splitAndLowercase(pattern, ':'); return true; diff --git a/tools/aapt2/Files.h b/tools/aapt2/Files.h index 37e6f8c..844fd2b 100644 --- a/tools/aapt2/Files.h +++ b/tools/aapt2/Files.h @@ -71,6 +71,11 @@ void appendPath(std::string* base, const StringPiece& part, const Ts&... parts); */ bool mkdirs(const StringPiece& path); +/** + * Returns all but the last part of the path. + */ +std::string getStem(const StringPiece& path); + /* * Filter that determines which resource files/directories are * processed by AAPT. Takes a pattern string supplied by the user. diff --git a/tools/aapt2/Main.cpp b/tools/aapt2/Main.cpp index 3a4b444..0215a2b 100644 --- a/tools/aapt2/Main.cpp +++ b/tools/aapt2/Main.cpp @@ -17,6 +17,7 @@ #include "AppInfo.h" #include "BigBuffer.h" #include "BinaryResourceParser.h" +#include "BindingXmlPullParser.h" #include "Files.h" #include "Flag.h" #include "JavaClassGenerator.h" @@ -285,8 +286,7 @@ void versionStylesForCompat(std::shared_ptr<ResourceTable> table) { } bool collectXml(std::shared_ptr<ResourceTable> table, const Source& source, - const ResourceName& name, - const ConfigDescription& config) { + const ResourceName& name, const ConfigDescription& config) { std::ifstream in(source.path, std::ifstream::binary); if (!in) { Logger::error(source) << strerror(errno) << std::endl; @@ -295,14 +295,14 @@ bool collectXml(std::shared_ptr<ResourceTable> table, const Source& source, std::set<size_t> sdkLevels; - SourceXmlPullParser pullParser(in); - while (XmlPullParser::isGoodEvent(pullParser.next())) { - if (pullParser.getEvent() != XmlPullParser::Event::kStartElement) { + SourceXmlPullParser parser(in); + while (XmlPullParser::isGoodEvent(parser.next())) { + if (parser.getEvent() != XmlPullParser::Event::kStartElement) { continue; } - const auto endIter = pullParser.endAttributes(); - for (auto iter = pullParser.beginAttributes(); iter != endIter; ++iter) { + const auto endIter = parser.endAttributes(); + for (auto iter = parser.beginAttributes(); iter != endIter; ++iter) { if (iter->namespaceUri == u"http://schemas.android.com/apk/res/android") { size_t sdkLevel = findAttributeSdkLevel(iter->name); if (sdkLevel > 1) { @@ -315,7 +315,7 @@ bool collectXml(std::shared_ptr<ResourceTable> table, const Source& source, bool privateRef = false; if (ResourceParser::tryParseReference(iter->value, &refName, &create, &privateRef) && create) { - table->addResource(refName, {}, source.line(pullParser.getLineNumber()), + table->addResource(refName, {}, source.line(parser.getLineNumber()), util::make_unique<Id>()); } } @@ -353,8 +353,14 @@ bool compileXml(std::shared_ptr<Resolver> resolver, const CompileItem& item, return false; } - BigBuffer outBuffer(1024); + std::shared_ptr<BindingXmlPullParser> binding; std::shared_ptr<XmlPullParser> xmlParser = std::make_shared<SourceXmlPullParser>(in); + if (item.name.type == ResourceType::kLayout) { + binding = std::make_shared<BindingXmlPullParser>(xmlParser); + xmlParser = binding; + } + + BigBuffer outBuffer(1024); XmlFlattener flattener(resolver); // We strip attributes that do not belong in this version of the resource. @@ -383,6 +389,22 @@ bool compileXml(std::shared_ptr<Resolver> resolver, const CompileItem& item, Logger::error(outputSource) << strerror(errno) << std::endl; return false; } + + if (binding) { + // We generated a binding xml file, write it out beside the output file. + Source bindingOutput = outputSource; + 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; } @@ -996,7 +1018,7 @@ int main(int argc, char** argv) { // Load the included libraries. std::shared_ptr<android::AssetManager> libraries = std::make_shared<android::AssetManager>(); for (const Source& source : options.libraries) { - if (util::stringEndsWith(source.path, ".arsc")) { + if (util::stringEndsWith<char>(source.path, ".arsc")) { // We'll process these last so as to avoid a cookie issue. continue; } @@ -1009,7 +1031,7 @@ int main(int argc, char** argv) { } for (const Source& source : options.libraries) { - if (!util::stringEndsWith(source.path, ".arsc")) { + if (!util::stringEndsWith<char>(source.path, ".arsc")) { // We've already processed this. continue; } diff --git a/tools/aapt2/Png.cpp b/tools/aapt2/Png.cpp index dd753f1..76120ac 100644 --- a/tools/aapt2/Png.cpp +++ b/tools/aapt2/Png.cpp @@ -1241,7 +1241,7 @@ bool Png::process(const Source& source, std::istream& input, std::ostream& outpu goto bail; } - if (util::stringEndsWith(source.path, ".9.png")) { + if (util::stringEndsWith<char>(source.path, ".9.png")) { if (!do9Patch(&pngInfo, outError)) { goto bail; } diff --git a/tools/aapt2/Util.cpp b/tools/aapt2/Util.cpp index 8a4c88f..c2418eb 100644 --- a/tools/aapt2/Util.cpp +++ b/tools/aapt2/Util.cpp @@ -54,13 +54,6 @@ std::vector<std::string> splitAndLowercase(const StringPiece& str, char sep) { return splitAndTransform(str, sep, ::tolower); } -bool stringEndsWith(const StringPiece& str, const StringPiece& suffix) { - if (str.size() < suffix.size()) { - return false; - } - return str.substr(str.size() - suffix.size(), suffix.size()) == suffix; -} - StringPiece16 trimWhitespace(const StringPiece16& str) { if (str.size() == 0 || str.data() == nullptr) { return str; diff --git a/tools/aapt2/Util.h b/tools/aapt2/Util.h index 510ed76..9f9707c 100644 --- a/tools/aapt2/Util.h +++ b/tools/aapt2/Util.h @@ -34,9 +34,26 @@ std::vector<std::string> split(const StringPiece& str, char sep); std::vector<std::string> splitAndLowercase(const StringPiece& str, char sep); /** + * Returns true if the string starts with prefix. + */ +template <typename T> +bool stringStartsWith(const BasicStringPiece<T>& str, const BasicStringPiece<T>& prefix) { + if (str.size() < prefix.size()) { + return false; + } + return str.substr(0, prefix.size()) == prefix; +} + +/** * Returns true if the string ends with suffix. */ -bool stringEndsWith(const StringPiece& str, const StringPiece& suffix); +template <typename T> +bool stringEndsWith(const BasicStringPiece<T>& str, const BasicStringPiece<T>& suffix) { + if (str.size() < suffix.size()) { + return false; + } + return str.substr(str.size() - suffix.size(), suffix.size()) == suffix; +} /** * Creates a new StringPiece16 that points to a substring diff --git a/tools/aapt2/Util_test.cpp b/tools/aapt2/Util_test.cpp index 7dbe7e0..c16f6bb 100644 --- a/tools/aapt2/Util_test.cpp +++ b/tools/aapt2/Util_test.cpp @@ -31,7 +31,11 @@ TEST(UtilTest, TrimOnlyWhitespace) { } TEST(UtilTest, StringEndsWith) { - EXPECT_TRUE(util::stringEndsWith("hello.xml", ".xml")); + EXPECT_TRUE(util::stringEndsWith<char>("hello.xml", ".xml")); +} + +TEST(UtilTest, StringStartsWith) { + EXPECT_TRUE(util::stringStartsWith<char>("hello.xml", "he")); } TEST(UtilTest, StringBuilderWhitespaceRemoval) { diff --git a/tools/aapt2/data/res/layout/main.xml b/tools/aapt2/data/res/layout/main.xml index e0b55c0..5160570 100644 --- a/tools/aapt2/data/res/layout/main.xml +++ b/tools/aapt2/data/res/layout/main.xml @@ -3,9 +3,13 @@ android:id="@+id/view" android:layout_width="match_parent" android:layout_height="wrap_content"> + + <variable name="user" type="com.android.User" /> + <View xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/me" android:layout_width="1dp" + android:text="@{user.name}" android:layout_height="match_parent" app:layout_width="false" app:flags="complex|weak" |