AAPT2: Separate out the various steps
An early refactor. Some ideas became clearer as development continued. Now the various phases are much clearer and more easily reusable. Also added a ton of tests! Change-Id: Ic8f0a70c8222370352e63533b329c40457c0903e
This commit is contained in:
@@ -25,62 +25,71 @@ LOCAL_PATH:= $(call my-dir)
|
||||
|
||||
main := Main.cpp
|
||||
sources := \
|
||||
BigBuffer.cpp \
|
||||
BinaryResourceParser.cpp \
|
||||
BindingXmlPullParser.cpp \
|
||||
compile/IdAssigner.cpp \
|
||||
compile/Png.cpp \
|
||||
compile/XmlIdCollector.cpp \
|
||||
flatten/Archive.cpp \
|
||||
flatten/TableFlattener.cpp \
|
||||
flatten/XmlFlattener.cpp \
|
||||
link/AutoVersioner.cpp \
|
||||
link/PrivateAttributeMover.cpp \
|
||||
link/ReferenceLinker.cpp \
|
||||
link/TableMerger.cpp \
|
||||
link/XmlReferenceLinker.cpp \
|
||||
process/SymbolTable.cpp \
|
||||
unflatten/BinaryResourceParser.cpp \
|
||||
unflatten/ResChunkPullParser.cpp \
|
||||
util/BigBuffer.cpp \
|
||||
util/Files.cpp \
|
||||
util/Util.cpp \
|
||||
ConfigDescription.cpp \
|
||||
Debug.cpp \
|
||||
Files.cpp \
|
||||
Flag.cpp \
|
||||
Flags.cpp \
|
||||
JavaClassGenerator.cpp \
|
||||
Linker.cpp \
|
||||
Locale.cpp \
|
||||
Logger.cpp \
|
||||
ManifestMerger.cpp \
|
||||
ManifestParser.cpp \
|
||||
ManifestValidator.cpp \
|
||||
Png.cpp \
|
||||
ProguardRules.cpp \
|
||||
ResChunkPullParser.cpp \
|
||||
Resource.cpp \
|
||||
ResourceParser.cpp \
|
||||
ResourceTable.cpp \
|
||||
ResourceTableResolver.cpp \
|
||||
ResourceUtils.cpp \
|
||||
ResourceValues.cpp \
|
||||
SdkConstants.cpp \
|
||||
StringPool.cpp \
|
||||
TableFlattener.cpp \
|
||||
Util.cpp \
|
||||
ScopedXmlPullParser.cpp \
|
||||
SourceXmlPullParser.cpp \
|
||||
XliffXmlPullParser.cpp \
|
||||
XmlDom.cpp \
|
||||
XmlFlattener.cpp \
|
||||
ZipEntry.cpp \
|
||||
ZipFile.cpp
|
||||
XmlPullParser.cpp
|
||||
|
||||
testSources := \
|
||||
BigBuffer_test.cpp \
|
||||
BindingXmlPullParser_test.cpp \
|
||||
Compat_test.cpp \
|
||||
compile/IdAssigner_test.cpp \
|
||||
compile/XmlIdCollector_test.cpp \
|
||||
flatten/FileExportWriter_test.cpp \
|
||||
flatten/TableFlattener_test.cpp \
|
||||
flatten/XmlFlattener_test.cpp \
|
||||
link/AutoVersioner_test.cpp \
|
||||
link/PrivateAttributeMover_test.cpp \
|
||||
link/ReferenceLinker_test.cpp \
|
||||
link/TableMerger_test.cpp \
|
||||
link/XmlReferenceLinker_test.cpp \
|
||||
process/SymbolTable_test.cpp \
|
||||
unflatten/FileExportHeaderReader_test.cpp \
|
||||
util/BigBuffer_test.cpp \
|
||||
util/Maybe_test.cpp \
|
||||
util/StringPiece_test.cpp \
|
||||
util/Util_test.cpp \
|
||||
ConfigDescription_test.cpp \
|
||||
JavaClassGenerator_test.cpp \
|
||||
Linker_test.cpp \
|
||||
Locale_test.cpp \
|
||||
ManifestMerger_test.cpp \
|
||||
ManifestParser_test.cpp \
|
||||
Maybe_test.cpp \
|
||||
NameMangler_test.cpp \
|
||||
ResourceParser_test.cpp \
|
||||
Resource_test.cpp \
|
||||
ResourceParser_test.cpp \
|
||||
ResourceTable_test.cpp \
|
||||
ScopedXmlPullParser_test.cpp \
|
||||
StringPiece_test.cpp \
|
||||
ResourceUtils_test.cpp \
|
||||
StringPool_test.cpp \
|
||||
Util_test.cpp \
|
||||
XliffXmlPullParser_test.cpp \
|
||||
ValueVisitor_test.cpp \
|
||||
XmlDom_test.cpp \
|
||||
XmlFlattener_test.cpp
|
||||
XmlPullParser_test.cpp
|
||||
|
||||
toolSources := \
|
||||
compile/Compile.cpp \
|
||||
link/Link.cpp
|
||||
|
||||
hostLdLibs :=
|
||||
|
||||
@@ -101,7 +110,7 @@ else
|
||||
endif
|
||||
|
||||
cFlags := -Wall -Werror -Wno-unused-parameter -UNDEBUG
|
||||
cppFlags := -std=c++11 -Wno-missing-field-initializers -Wno-unused-private-field
|
||||
cppFlags := -std=c++11 -Wno-missing-field-initializers -fno-exceptions
|
||||
|
||||
# ==========================================================
|
||||
# Build the host static library: libaapt2
|
||||
@@ -139,7 +148,7 @@ include $(BUILD_HOST_NATIVE_TEST)
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := aapt2
|
||||
|
||||
LOCAL_SRC_FILES := $(main)
|
||||
LOCAL_SRC_FILES := $(main) $(toolSources)
|
||||
|
||||
LOCAL_STATIC_LIBRARIES += libaapt2 $(hostStaticLibs)
|
||||
LOCAL_LDLIBS += $(hostLdLibs)
|
||||
|
||||
@@ -1,268 +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 "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();
|
||||
}
|
||||
|
||||
bool BindingXmlPullParser::applyPackageAlias(std::u16string* package,
|
||||
const std::u16string& defaultPackage) const {
|
||||
return mParser->applyPackageAlias(package, defaultPackage);
|
||||
}
|
||||
|
||||
const std::u16string& BindingXmlPullParser::getElementNamespace() const {
|
||||
return mParser->getElementNamespace();
|
||||
}
|
||||
|
||||
const std::u16string& BindingXmlPullParser::getElementName() const {
|
||||
return mParser->getElementName();
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
@@ -1,90 +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_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;
|
||||
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;
|
||||
|
||||
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
|
||||
@@ -1,110 +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 "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
|
||||
@@ -17,8 +17,8 @@
|
||||
#include "ConfigDescription.h"
|
||||
#include "Locale.h"
|
||||
#include "SdkConstants.h"
|
||||
#include "StringPiece.h"
|
||||
#include "Util.h"
|
||||
#include "util/StringPiece.h"
|
||||
#include "util/Util.h"
|
||||
|
||||
#include <androidfw/ResourceTypes.h>
|
||||
#include <string>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
#ifndef AAPT_CONFIG_DESCRIPTION_H
|
||||
#define AAPT_CONFIG_DESCRIPTION_H
|
||||
|
||||
#include "StringPiece.h"
|
||||
#include "util/StringPiece.h"
|
||||
|
||||
#include <androidfw/ResourceTypes.h>
|
||||
#include <ostream>
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
#include "ConfigDescription.h"
|
||||
#include "StringPiece.h"
|
||||
#include "util/StringPiece.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <string>
|
||||
|
||||
@@ -17,7 +17,8 @@
|
||||
#include "Debug.h"
|
||||
#include "ResourceTable.h"
|
||||
#include "ResourceValues.h"
|
||||
#include "Util.h"
|
||||
#include "util/Util.h"
|
||||
#include "ValueVisitor.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
@@ -29,102 +30,119 @@
|
||||
|
||||
namespace aapt {
|
||||
|
||||
struct PrintVisitor : ConstValueVisitor {
|
||||
void visit(const Attribute& attr, ValueVisitorArgs&) override {
|
||||
struct PrintVisitor : public ValueVisitor {
|
||||
using ValueVisitor::visit;
|
||||
|
||||
void visit(Attribute* attr) override {
|
||||
std::cout << "(attr) type=";
|
||||
attr.printMask(std::cout);
|
||||
attr->printMask(&std::cout);
|
||||
static constexpr uint32_t kMask = android::ResTable_map::TYPE_ENUM |
|
||||
android::ResTable_map::TYPE_FLAGS;
|
||||
if (attr.typeMask & kMask) {
|
||||
for (const auto& symbol : attr.symbols) {
|
||||
std::cout << "\n "
|
||||
<< symbol.symbol.name.entry << " (" << symbol.symbol.id << ") = "
|
||||
<< symbol.value;
|
||||
if (attr->typeMask & kMask) {
|
||||
for (const auto& symbol : attr->symbols) {
|
||||
std::cout << "\n " << symbol.symbol.name.value().entry;
|
||||
if (symbol.symbol.id) {
|
||||
std::cout << " (" << symbol.symbol.id.value() << ")";
|
||||
}
|
||||
std::cout << " = " << symbol.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void visit(const Style& style, ValueVisitorArgs&) override {
|
||||
void visit(Style* style) override {
|
||||
std::cout << "(style)";
|
||||
if (style.parent.name.isValid() || style.parent.id.isValid()) {
|
||||
if (style->parent) {
|
||||
std::cout << " parent=";
|
||||
if (style.parent.name.isValid()) {
|
||||
std::cout << style.parent.name << " ";
|
||||
if (style->parent.value().name) {
|
||||
std::cout << style->parent.value().name.value() << " ";
|
||||
}
|
||||
|
||||
if (style.parent.id.isValid()) {
|
||||
std::cout << style.parent.id;
|
||||
if (style->parent.value().id) {
|
||||
std::cout << style->parent.value().id.value();
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& entry : style.entries) {
|
||||
for (const auto& entry : style->entries) {
|
||||
std::cout << "\n ";
|
||||
if (entry.key.name.isValid()) {
|
||||
std::cout << entry.key.name.package << ":" << entry.key.name.entry;
|
||||
if (entry.key.name) {
|
||||
std::cout << entry.key.name.value().package << ":" << entry.key.name.value().entry;
|
||||
}
|
||||
|
||||
if (entry.key.id.isValid()) {
|
||||
std::cout << "(" << entry.key.id << ")";
|
||||
if (entry.key.id) {
|
||||
std::cout << "(" << entry.key.id.value() << ")";
|
||||
}
|
||||
|
||||
std::cout << "=" << *entry.value;
|
||||
}
|
||||
}
|
||||
|
||||
void visit(const Array& array, ValueVisitorArgs&) override {
|
||||
array.print(std::cout);
|
||||
void visit(Array* array) override {
|
||||
array->print(&std::cout);
|
||||
}
|
||||
|
||||
void visit(const Plural& plural, ValueVisitorArgs&) override {
|
||||
plural.print(std::cout);
|
||||
void visit(Plural* plural) override {
|
||||
plural->print(&std::cout);
|
||||
}
|
||||
|
||||
void visit(const Styleable& styleable, ValueVisitorArgs&) override {
|
||||
styleable.print(std::cout);
|
||||
void visit(Styleable* styleable) override {
|
||||
styleable->print(&std::cout);
|
||||
}
|
||||
|
||||
void visitItem(const Item& item, ValueVisitorArgs& args) override {
|
||||
item.print(std::cout);
|
||||
void visitItem(Item* item) override {
|
||||
item->print(&std::cout);
|
||||
}
|
||||
};
|
||||
|
||||
void Debug::printTable(const std::shared_ptr<ResourceTable>& table) {
|
||||
std::cout << "Package name=" << table->getPackage();
|
||||
if (table->getPackageId() != ResourceTable::kUnsetPackageId) {
|
||||
std::cout << " id=" << std::hex << table->getPackageId() << std::dec;
|
||||
}
|
||||
std::cout << std::endl;
|
||||
|
||||
for (const auto& type : *table) {
|
||||
std::cout << " type " << type->type;
|
||||
if (type->typeId != ResourceTableType::kUnsetTypeId) {
|
||||
std::cout << " id=" << std::hex << type->typeId << std::dec;
|
||||
void Debug::printTable(ResourceTable* table) {
|
||||
for (auto& package : table->packages) {
|
||||
std::cout << "Package name=" << package->name;
|
||||
if (package->id) {
|
||||
std::cout << " id=" << std::hex << (int) package->id.value() << std::dec;
|
||||
}
|
||||
std::cout << " entryCount=" << type->entries.size() << std::endl;
|
||||
std::cout << std::endl;
|
||||
|
||||
std::vector<const ResourceEntry*> sortedEntries;
|
||||
for (const auto& entry : type->entries) {
|
||||
auto iter = std::lower_bound(sortedEntries.begin(), sortedEntries.end(), entry.get(),
|
||||
[](const ResourceEntry* a, const ResourceEntry* b) -> bool {
|
||||
return a->entryId < b->entryId;
|
||||
});
|
||||
sortedEntries.insert(iter, entry.get());
|
||||
}
|
||||
|
||||
for (const ResourceEntry* entry : sortedEntries) {
|
||||
ResourceId id = { table->getPackageId(), type->typeId, entry->entryId };
|
||||
ResourceName name = { table->getPackage(), type->type, entry->name };
|
||||
std::cout << " spec resource " << id << " " << name;
|
||||
if (entry->publicStatus.isPublic) {
|
||||
std::cout << " PUBLIC";
|
||||
for (const auto& type : package->types) {
|
||||
std::cout << " type " << type->type;
|
||||
if (type->id) {
|
||||
std::cout << " id=" << std::hex << (int) type->id.value() << std::dec;
|
||||
}
|
||||
std::cout << std::endl;
|
||||
std::cout << " entryCount=" << type->entries.size() << std::endl;
|
||||
|
||||
PrintVisitor visitor;
|
||||
for (const auto& value : entry->values) {
|
||||
std::cout << " (" << value.config << ") ";
|
||||
value.value->accept(visitor, {});
|
||||
std::vector<const ResourceEntry*> sortedEntries;
|
||||
for (const auto& entry : type->entries) {
|
||||
auto iter = std::lower_bound(sortedEntries.begin(), sortedEntries.end(), entry.get(),
|
||||
[](const ResourceEntry* a, const ResourceEntry* b) -> bool {
|
||||
if (a->id && b->id) {
|
||||
return a->id.value() < b->id.value();
|
||||
} else if (a->id) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
sortedEntries.insert(iter, entry.get());
|
||||
}
|
||||
|
||||
for (const ResourceEntry* entry : sortedEntries) {
|
||||
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 };
|
||||
std::cout << " spec resource " << id << " " << name;
|
||||
if (entry->publicStatus.isPublic) {
|
||||
std::cout << " PUBLIC";
|
||||
}
|
||||
std::cout << std::endl;
|
||||
|
||||
PrintVisitor visitor;
|
||||
for (const auto& value : entry->values) {
|
||||
std::cout << " (" << value.config << ") ";
|
||||
value.value->accept(&visitor);
|
||||
std::cout << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -136,8 +154,7 @@ static size_t getNodeIndex(const std::vector<ResourceName>& names, const Resourc
|
||||
return std::distance(names.begin(), iter);
|
||||
}
|
||||
|
||||
void Debug::printStyleGraph(const std::shared_ptr<ResourceTable>& table,
|
||||
const ResourceName& targetStyle) {
|
||||
void Debug::printStyleGraph(ResourceTable* table, const ResourceName& targetStyle) {
|
||||
std::map<ResourceName, std::set<ResourceName>> graph;
|
||||
|
||||
std::queue<ResourceName> stylesToVisit;
|
||||
@@ -150,17 +167,16 @@ void Debug::printStyleGraph(const std::shared_ptr<ResourceTable>& table,
|
||||
continue;
|
||||
}
|
||||
|
||||
const ResourceTableType* type;
|
||||
const ResourceEntry* entry;
|
||||
std::tie(type, entry) = table->findResource(styleName);
|
||||
if (entry) {
|
||||
Maybe<ResourceTable::SearchResult> result = table->findResource(styleName);
|
||||
if (result) {
|
||||
ResourceEntry* entry = result.value().entry;
|
||||
for (const auto& value : entry->values) {
|
||||
visitFunc<Style>(*value.value, [&](const Style& style) {
|
||||
if (style.parent.name.isValid()) {
|
||||
parents.insert(style.parent.name);
|
||||
stylesToVisit.push(style.parent.name);
|
||||
if (Style* style = valueCast<Style>(value.value.get())) {
|
||||
if (style->parent && style->parent.value().name) {
|
||||
parents.insert(style->parent.value().name.value());
|
||||
stylesToVisit.push(style->parent.value().name.value());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,13 +20,11 @@
|
||||
#include "Resource.h"
|
||||
#include "ResourceTable.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
struct Debug {
|
||||
static void printTable(const std::shared_ptr<ResourceTable>& table);
|
||||
static void printStyleGraph(const std::shared_ptr<ResourceTable>& table,
|
||||
static void printTable(ResourceTable* table);
|
||||
static void printStyleGraph(ResourceTable* table,
|
||||
const ResourceName& targetStyle);
|
||||
};
|
||||
|
||||
|
||||
98
tools/aapt2/Diagnostics.h
Normal file
98
tools/aapt2/Diagnostics.h
Normal file
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* 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_DIAGNOSTICS_H
|
||||
#define AAPT_DIAGNOSTICS_H
|
||||
|
||||
#include "Source.h"
|
||||
|
||||
#include "util/StringPiece.h"
|
||||
#include "util/Util.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
struct DiagMessageActual {
|
||||
Source source;
|
||||
std::string message;
|
||||
};
|
||||
|
||||
struct DiagMessage {
|
||||
private:
|
||||
Source mSource;
|
||||
std::stringstream mMessage;
|
||||
|
||||
public:
|
||||
DiagMessage() = default;
|
||||
|
||||
DiagMessage(const StringPiece& src) : mSource(src) {
|
||||
}
|
||||
|
||||
DiagMessage(const Source& src) : mSource(src) {
|
||||
}
|
||||
|
||||
template <typename T> DiagMessage& operator<<(const T& value) {
|
||||
mMessage << value;
|
||||
return *this;
|
||||
}
|
||||
/*
|
||||
template <typename T> DiagMessage& operator<<(
|
||||
const ::std::function<::std::ostream&(::std::ostream&)>& f) {
|
||||
f(mMessage);
|
||||
return *this;
|
||||
}*/
|
||||
|
||||
DiagMessageActual build() const {
|
||||
return DiagMessageActual{ mSource, mMessage.str() };
|
||||
}
|
||||
};
|
||||
|
||||
struct IDiagnostics {
|
||||
virtual ~IDiagnostics() = default;
|
||||
|
||||
virtual void error(const DiagMessage& message) = 0;
|
||||
virtual void warn(const DiagMessage& message) = 0;
|
||||
virtual void note(const DiagMessage& message) = 0;
|
||||
};
|
||||
|
||||
struct StdErrDiagnostics : public IDiagnostics {
|
||||
void emit(const DiagMessage& msg, const char* tag) {
|
||||
DiagMessageActual actual = msg.build();
|
||||
if (!actual.source.path.empty()) {
|
||||
std::cerr << actual.source << ": ";
|
||||
}
|
||||
std::cerr << tag << actual.message << "." << std::endl;
|
||||
}
|
||||
|
||||
void error(const DiagMessage& msg) override {
|
||||
emit(msg, "error: ");
|
||||
}
|
||||
|
||||
void warn(const DiagMessage& msg) override {
|
||||
emit(msg, "warn: ");
|
||||
}
|
||||
|
||||
void note(const DiagMessage& msg) override {
|
||||
emit(msg, "note: ");
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
#endif /* AAPT_DIAGNOSTICS_H */
|
||||
@@ -1,132 +0,0 @@
|
||||
#include "Flag.h"
|
||||
#include "StringPiece.h"
|
||||
|
||||
#include <functional>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace aapt {
|
||||
namespace flag {
|
||||
|
||||
struct Flag {
|
||||
std::string name;
|
||||
std::string description;
|
||||
std::function<bool(const StringPiece&, std::string*)> action;
|
||||
bool required;
|
||||
bool* flagResult;
|
||||
bool flagValueWhenSet;
|
||||
bool parsed;
|
||||
};
|
||||
|
||||
static std::vector<Flag> sFlags;
|
||||
static std::vector<std::string> sArgs;
|
||||
|
||||
static std::function<bool(const StringPiece&, std::string*)> wrap(
|
||||
const std::function<void(const StringPiece&)>& action) {
|
||||
return [action](const StringPiece& arg, std::string*) -> bool {
|
||||
action(arg);
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
void optionalFlag(const StringPiece& name, const StringPiece& description,
|
||||
std::function<void(const StringPiece&)> action) {
|
||||
sFlags.push_back(Flag{
|
||||
name.toString(), description.toString(), wrap(action),
|
||||
false, nullptr, false, false });
|
||||
}
|
||||
|
||||
void requiredFlag(const StringPiece& name, const StringPiece& description,
|
||||
std::function<void(const StringPiece&)> action) {
|
||||
sFlags.push_back(Flag{ name.toString(), description.toString(), wrap(action),
|
||||
true, nullptr, false, false });
|
||||
}
|
||||
|
||||
void requiredFlag(const StringPiece& name, const StringPiece& description,
|
||||
std::function<bool(const StringPiece&, std::string*)> action) {
|
||||
sFlags.push_back(Flag{ name.toString(), description.toString(), action,
|
||||
true, nullptr, false, false });
|
||||
}
|
||||
|
||||
void optionalSwitch(const StringPiece& name, const StringPiece& description, bool resultWhenSet,
|
||||
bool* result) {
|
||||
sFlags.push_back(Flag{
|
||||
name.toString(), description.toString(), {},
|
||||
false, result, resultWhenSet, false });
|
||||
}
|
||||
|
||||
void usageAndDie(const StringPiece& command) {
|
||||
std::cerr << command << " [options]";
|
||||
for (const Flag& flag : sFlags) {
|
||||
if (flag.required) {
|
||||
std::cerr << " " << flag.name << " arg";
|
||||
}
|
||||
}
|
||||
std::cerr << " files..." << std::endl << std::endl << "Options:" << std::endl;
|
||||
|
||||
for (const Flag& flag : sFlags) {
|
||||
std::string command = flag.name;
|
||||
if (!flag.flagResult) {
|
||||
command += " arg ";
|
||||
}
|
||||
std::cerr << " " << std::setw(30) << std::left << command
|
||||
<< flag.description << std::endl;
|
||||
}
|
||||
exit(1);
|
||||
}
|
||||
|
||||
void parse(int argc, char** argv, const StringPiece& command) {
|
||||
std::string errorStr;
|
||||
for (int i = 0; i < argc; i++) {
|
||||
const StringPiece arg(argv[i]);
|
||||
if (*arg.data() != '-') {
|
||||
sArgs.push_back(arg.toString());
|
||||
continue;
|
||||
}
|
||||
|
||||
bool match = false;
|
||||
for (Flag& flag : sFlags) {
|
||||
if (arg == flag.name) {
|
||||
match = true;
|
||||
flag.parsed = true;
|
||||
if (flag.flagResult) {
|
||||
*flag.flagResult = flag.flagValueWhenSet;
|
||||
} else {
|
||||
i++;
|
||||
if (i >= argc) {
|
||||
std::cerr << flag.name << " missing argument." << std::endl
|
||||
<< std::endl;
|
||||
usageAndDie(command);
|
||||
}
|
||||
|
||||
if (!flag.action(argv[i], &errorStr)) {
|
||||
std::cerr << errorStr << "." << std::endl << std::endl;
|
||||
usageAndDie(command);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!match) {
|
||||
std::cerr << "unknown option '" << arg << "'." << std::endl << std::endl;
|
||||
usageAndDie(command);
|
||||
}
|
||||
}
|
||||
|
||||
for (const Flag& flag : sFlags) {
|
||||
if (flag.required && !flag.parsed) {
|
||||
std::cerr << "missing required flag " << flag.name << std::endl << std::endl;
|
||||
usageAndDie(command);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<std::string>& getArgs() {
|
||||
return sArgs;
|
||||
}
|
||||
|
||||
} // namespace flag
|
||||
} // namespace aapt
|
||||
@@ -1,34 +0,0 @@
|
||||
#ifndef AAPT_FLAG_H
|
||||
#define AAPT_FLAG_H
|
||||
|
||||
#include "StringPiece.h"
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace aapt {
|
||||
namespace flag {
|
||||
|
||||
void requiredFlag(const StringPiece& name, const StringPiece& description,
|
||||
std::function<void(const StringPiece&)> action);
|
||||
|
||||
void requiredFlag(const StringPiece& name, const StringPiece& description,
|
||||
std::function<bool(const StringPiece&, std::string*)> action);
|
||||
|
||||
void optionalFlag(const StringPiece& name, const StringPiece& description,
|
||||
std::function<void(const StringPiece&)> action);
|
||||
|
||||
void optionalSwitch(const StringPiece& name, const StringPiece& description, bool resultWhenSet,
|
||||
bool* result);
|
||||
|
||||
void usageAndDie(const StringPiece& command);
|
||||
|
||||
void parse(int argc, char** argv, const StringPiece& command);
|
||||
|
||||
const std::vector<std::string>& getArgs();
|
||||
|
||||
} // namespace flag
|
||||
} // namespace aapt
|
||||
|
||||
#endif // AAPT_FLAG_H
|
||||
158
tools/aapt2/Flags.cpp
Normal file
158
tools/aapt2/Flags.cpp
Normal file
@@ -0,0 +1,158 @@
|
||||
/*
|
||||
* 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 "Flags.h"
|
||||
#include "util/StringPiece.h"
|
||||
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
Flags& Flags::requiredFlag(const StringPiece& name, const StringPiece& description,
|
||||
std::string* value) {
|
||||
auto func = [value](const StringPiece& arg) -> bool {
|
||||
*value = arg.toString();
|
||||
return true;
|
||||
};
|
||||
|
||||
mFlags.push_back(Flag{ name.toString(), description.toString(), func, true, 1, false});
|
||||
return *this;
|
||||
}
|
||||
|
||||
Flags& Flags::requiredFlagList(const StringPiece& name, const StringPiece& description,
|
||||
std::vector<std::string>* value) {
|
||||
auto func = [value](const StringPiece& arg) -> bool {
|
||||
value->push_back(arg.toString());
|
||||
return true;
|
||||
};
|
||||
|
||||
mFlags.push_back(Flag{ name.toString(), description.toString(), func, true, 1, false });
|
||||
return *this;
|
||||
}
|
||||
|
||||
Flags& Flags::optionalFlag(const StringPiece& name, const StringPiece& description,
|
||||
Maybe<std::string>* value) {
|
||||
auto func = [value](const StringPiece& arg) -> bool {
|
||||
*value = arg.toString();
|
||||
return true;
|
||||
};
|
||||
|
||||
mFlags.push_back(Flag{ name.toString(), description.toString(), func, false, 1, false });
|
||||
return *this;
|
||||
}
|
||||
|
||||
Flags& Flags::optionalFlagList(const StringPiece& name, const StringPiece& description,
|
||||
std::vector<std::string>* value) {
|
||||
auto func = [value](const StringPiece& arg) -> bool {
|
||||
value->push_back(arg.toString());
|
||||
return true;
|
||||
};
|
||||
|
||||
mFlags.push_back(Flag{ name.toString(), description.toString(), func, false, 1, false });
|
||||
return *this;
|
||||
}
|
||||
|
||||
Flags& Flags::optionalSwitch(const StringPiece& name, const StringPiece& description,
|
||||
bool* value) {
|
||||
auto func = [value](const StringPiece& arg) -> bool {
|
||||
*value = true;
|
||||
return true;
|
||||
};
|
||||
|
||||
mFlags.push_back(Flag{ name.toString(), description.toString(), func, false, 0, false });
|
||||
return *this;
|
||||
}
|
||||
|
||||
void Flags::usage(const StringPiece& command, std::ostream* out) {
|
||||
*out << command << " [options]";
|
||||
for (const Flag& flag : mFlags) {
|
||||
if (flag.required) {
|
||||
*out << " " << flag.name << " arg";
|
||||
}
|
||||
}
|
||||
|
||||
*out << " files...\n\nOptions:\n";
|
||||
|
||||
for (const Flag& flag : mFlags) {
|
||||
std::string argLine = flag.name;
|
||||
if (flag.numArgs > 0) {
|
||||
argLine += " arg";
|
||||
}
|
||||
*out << " " << std::setw(30) << std::left << argLine << flag.description << "\n";
|
||||
}
|
||||
*out << " " << std::setw(30) << std::left << "-h" << "Displays this help menu\n";
|
||||
out->flush();
|
||||
}
|
||||
|
||||
bool Flags::parse(const StringPiece& command, const std::vector<StringPiece>& args,
|
||||
std::ostream* outError) {
|
||||
for (size_t i = 0; i < args.size(); i++) {
|
||||
StringPiece arg = args[i];
|
||||
if (*(arg.data()) != '-') {
|
||||
mArgs.push_back(arg.toString());
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg == "-h" || arg == "--help") {
|
||||
usage(command, outError);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool match = false;
|
||||
for (Flag& flag : mFlags) {
|
||||
if (arg == flag.name) {
|
||||
if (flag.numArgs > 0) {
|
||||
i++;
|
||||
if (i >= args.size()) {
|
||||
*outError << flag.name << " missing argument.\n\n";
|
||||
usage(command, outError);
|
||||
return false;
|
||||
}
|
||||
flag.action(args[i]);
|
||||
} else {
|
||||
flag.action({});
|
||||
}
|
||||
flag.parsed = true;
|
||||
match = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!match) {
|
||||
*outError << "unknown option '" << arg << "'.\n\n";
|
||||
usage(command, outError);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (const Flag& flag : mFlags) {
|
||||
if (flag.required && !flag.parsed) {
|
||||
*outError << "missing required flag " << flag.name << "\n\n";
|
||||
usage(command, outError);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const std::vector<std::string>& Flags::getArgs() {
|
||||
return mArgs;
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
67
tools/aapt2/Flags.h
Normal file
67
tools/aapt2/Flags.h
Normal file
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* 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_FLAGS_H
|
||||
#define AAPT_FLAGS_H
|
||||
|
||||
#include "util/Maybe.h"
|
||||
#include "util/StringPiece.h"
|
||||
|
||||
#include <functional>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
class Flags {
|
||||
public:
|
||||
Flags& requiredFlag(const StringPiece& name, const StringPiece& description,
|
||||
std::string* value);
|
||||
Flags& requiredFlagList(const StringPiece& name, const StringPiece& description,
|
||||
std::vector<std::string>* value);
|
||||
Flags& optionalFlag(const StringPiece& name, const StringPiece& description,
|
||||
Maybe<std::string>* value);
|
||||
Flags& optionalFlagList(const StringPiece& name, const StringPiece& description,
|
||||
std::vector<std::string>* value);
|
||||
Flags& optionalSwitch(const StringPiece& name, const StringPiece& description,
|
||||
bool* value);
|
||||
|
||||
void usage(const StringPiece& command, std::ostream* out);
|
||||
|
||||
bool parse(const StringPiece& command, const std::vector<StringPiece>& args,
|
||||
std::ostream* outError);
|
||||
|
||||
const std::vector<std::string>& getArgs();
|
||||
|
||||
private:
|
||||
struct Flag {
|
||||
std::string name;
|
||||
std::string description;
|
||||
std::function<bool(const StringPiece& value)> action;
|
||||
bool required;
|
||||
size_t numArgs;
|
||||
|
||||
bool parsed;
|
||||
};
|
||||
|
||||
std::vector<Flag> mFlags;
|
||||
std::vector<std::string> mArgs;
|
||||
};
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
#endif // AAPT_FLAGS_H
|
||||
@@ -19,7 +19,7 @@
|
||||
#include "Resource.h"
|
||||
#include "ResourceTable.h"
|
||||
#include "ResourceValues.h"
|
||||
#include "StringPiece.h"
|
||||
#include "util/StringPiece.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <ostream>
|
||||
@@ -32,21 +32,18 @@ namespace aapt {
|
||||
// The number of attributes to emit per line in a Styleable array.
|
||||
constexpr size_t kAttribsPerLine = 4;
|
||||
|
||||
JavaClassGenerator::JavaClassGenerator(const std::shared_ptr<const ResourceTable>& table,
|
||||
Options options) :
|
||||
JavaClassGenerator::JavaClassGenerator(ResourceTable* table, JavaClassGeneratorOptions options) :
|
||||
mTable(table), mOptions(options) {
|
||||
}
|
||||
|
||||
static void generateHeader(std::ostream& out, const StringPiece16& package) {
|
||||
out << "/* AUTO-GENERATED FILE. DO NOT MODIFY.\n"
|
||||
" *\n"
|
||||
" * This class was automatically generated by the\n"
|
||||
" * aapt tool from the resource data it found. It\n"
|
||||
" * should not be modified by hand.\n"
|
||||
" */\n\n";
|
||||
out << "package " << package << ";"
|
||||
<< std::endl
|
||||
<< std::endl;
|
||||
static void generateHeader(const StringPiece16& packageNameToGenerate, std::ostream* out) {
|
||||
*out << "/* AUTO-GENERATED FILE. DO NOT MODIFY.\n"
|
||||
" *\n"
|
||||
" * This class was automatically generated by the\n"
|
||||
" * aapt tool from the resource data it found. It\n"
|
||||
" * should not be modified by hand.\n"
|
||||
" */\n\n"
|
||||
"package " << packageNameToGenerate << ";\n\n";
|
||||
}
|
||||
|
||||
static const std::set<StringPiece16> sJavaIdentifiers = {
|
||||
@@ -80,42 +77,32 @@ static std::u16string transform(const StringPiece16& symbol) {
|
||||
return output;
|
||||
}
|
||||
|
||||
struct GenArgs : ValueVisitorArgs {
|
||||
GenArgs(std::ostream* o, const std::u16string* p, std::u16string* e) :
|
||||
out(o), package(p), entryName(e) {
|
||||
}
|
||||
|
||||
std::ostream* out;
|
||||
const std::u16string* package;
|
||||
std::u16string* entryName;
|
||||
};
|
||||
|
||||
void JavaClassGenerator::visit(const Styleable& styleable, ValueVisitorArgs& a) {
|
||||
void JavaClassGenerator::generateStyleable(const StringPiece16& packageNameToGenerate,
|
||||
const std::u16string& entryName,
|
||||
const Styleable* styleable,
|
||||
std::ostream* out) {
|
||||
const StringPiece finalModifier = mOptions.useFinal ? " final" : "";
|
||||
std::ostream* out = static_cast<GenArgs&>(a).out;
|
||||
const std::u16string* package = static_cast<GenArgs&>(a).package;
|
||||
std::u16string* entryName = static_cast<GenArgs&>(a).entryName;
|
||||
|
||||
// This must be sorted by resource ID.
|
||||
std::vector<std::pair<ResourceId, ResourceNameRef>> sortedAttributes;
|
||||
sortedAttributes.reserve(styleable.entries.size());
|
||||
for (const auto& attr : styleable.entries) {
|
||||
sortedAttributes.reserve(styleable->entries.size());
|
||||
for (const auto& attr : styleable->entries) {
|
||||
// If we are not encoding final attributes, the styleable entry may have no ID
|
||||
// if we are building a static library.
|
||||
assert((!mOptions.useFinal || attr.id.isValid()) && "no ID set for Styleable entry");
|
||||
assert(attr.name.isValid() && "no name set for Styleable entry");
|
||||
sortedAttributes.emplace_back(attr.id, attr.name);
|
||||
assert((!mOptions.useFinal || attr.id) && "no ID set for Styleable entry");
|
||||
assert(attr.name && "no name set for Styleable entry");
|
||||
sortedAttributes.emplace_back(attr.id ? attr.id.value() : ResourceId(0), attr.name.value());
|
||||
}
|
||||
std::sort(sortedAttributes.begin(), sortedAttributes.end());
|
||||
|
||||
// First we emit the array containing the IDs of each attribute.
|
||||
*out << " "
|
||||
<< "public static final int[] " << transform(*entryName) << " = {";
|
||||
<< "public static final int[] " << transform(entryName) << " = {";
|
||||
|
||||
const size_t attrCount = sortedAttributes.size();
|
||||
for (size_t i = 0; i < attrCount; i++) {
|
||||
if (i % kAttribsPerLine == 0) {
|
||||
*out << std::endl << " ";
|
||||
*out << "\n ";
|
||||
}
|
||||
|
||||
*out << sortedAttributes[i].first;
|
||||
@@ -123,44 +110,46 @@ void JavaClassGenerator::visit(const Styleable& styleable, ValueVisitorArgs& a)
|
||||
*out << ", ";
|
||||
}
|
||||
}
|
||||
*out << std::endl << " };" << std::endl;
|
||||
*out << "\n };\n";
|
||||
|
||||
// Now we emit the indices into the array.
|
||||
for (size_t i = 0; i < attrCount; i++) {
|
||||
*out << " "
|
||||
<< "public static" << finalModifier
|
||||
<< " int " << transform(*entryName);
|
||||
<< " int " << transform(entryName);
|
||||
|
||||
// We may reference IDs from other packages, so prefix the entry name with
|
||||
// the package.
|
||||
const ResourceNameRef& itemName = sortedAttributes[i].second;
|
||||
if (itemName.package != *package) {
|
||||
if (packageNameToGenerate != itemName.package) {
|
||||
*out << "_" << transform(itemName.package);
|
||||
}
|
||||
*out << "_" << transform(itemName.entry) << " = " << i << ";" << std::endl;
|
||||
*out << "_" << transform(itemName.entry) << " = " << i << ";\n";
|
||||
}
|
||||
}
|
||||
|
||||
bool JavaClassGenerator::generateType(const std::u16string& package, size_t packageId,
|
||||
const ResourceTableType& type, std::ostream& out) {
|
||||
bool JavaClassGenerator::generateType(const StringPiece16& packageNameToGenerate,
|
||||
const ResourceTablePackage* package,
|
||||
const ResourceTableType* type,
|
||||
std::ostream* out) {
|
||||
const StringPiece finalModifier = mOptions.useFinal ? " final" : "";
|
||||
|
||||
std::u16string unmangledPackage;
|
||||
std::u16string unmangledName;
|
||||
for (const auto& entry : type.entries) {
|
||||
ResourceId id = { packageId, type.typeId, entry->entryId };
|
||||
for (const auto& entry : type->entries) {
|
||||
ResourceId id = { package->id.value(), type->id.value(), entry->id.value() };
|
||||
assert(id.isValid());
|
||||
|
||||
unmangledName = entry->name;
|
||||
if (NameMangler::unmangle(&unmangledName, &unmangledPackage)) {
|
||||
// The entry name was mangled, and we successfully unmangled it.
|
||||
// Check that we want to emit this symbol.
|
||||
if (package != unmangledPackage) {
|
||||
if (package->name != unmangledPackage) {
|
||||
// Skip the entry if it doesn't belong to the package we're writing.
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
if (package != mTable->getPackage()) {
|
||||
if (packageNameToGenerate != package->name) {
|
||||
// We are processing a mangled package name,
|
||||
// but this is a non-mangled resource.
|
||||
continue;
|
||||
@@ -168,40 +157,42 @@ bool JavaClassGenerator::generateType(const std::u16string& package, size_t pack
|
||||
}
|
||||
|
||||
if (!isValidSymbol(unmangledName)) {
|
||||
ResourceNameRef resourceName = { package, type.type, unmangledName };
|
||||
ResourceNameRef resourceName = { packageNameToGenerate, type->type, unmangledName };
|
||||
std::stringstream err;
|
||||
err << "invalid symbol name '" << resourceName << "'";
|
||||
mError = err.str();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (type.type == ResourceType::kStyleable) {
|
||||
if (type->type == ResourceType::kStyleable) {
|
||||
assert(!entry->values.empty());
|
||||
entry->values.front().value->accept(*this, GenArgs{ &out, &package, &unmangledName });
|
||||
generateStyleable(packageNameToGenerate, unmangledName, static_cast<const Styleable*>(
|
||||
entry->values.front().value.get()), out);
|
||||
} else {
|
||||
out << " " << "public static" << finalModifier
|
||||
<< " int " << transform(unmangledName) << " = " << id << ";" << std::endl;
|
||||
*out << " " << "public static" << finalModifier
|
||||
<< " int " << transform(unmangledName) << " = " << id << ";\n";
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool JavaClassGenerator::generate(const std::u16string& package, std::ostream& out) {
|
||||
const size_t packageId = mTable->getPackageId();
|
||||
bool JavaClassGenerator::generate(const StringPiece16& packageNameToGenerate, std::ostream* out) {
|
||||
generateHeader(packageNameToGenerate, out);
|
||||
|
||||
generateHeader(out, package);
|
||||
*out << "public final class R {\n";
|
||||
|
||||
out << "public final class R {" << std::endl;
|
||||
|
||||
for (const auto& type : *mTable) {
|
||||
out << " public static final class " << type->type << " {" << std::endl;
|
||||
if (!generateType(package, packageId, *type, out)) {
|
||||
return false;
|
||||
for (const auto& package : mTable->packages) {
|
||||
for (const auto& type : package->types) {
|
||||
*out << " public static final class " << type->type << " {\n";
|
||||
if (!generateType(packageNameToGenerate, package.get(), type.get(), out)) {
|
||||
return false;
|
||||
}
|
||||
*out << " }\n";
|
||||
}
|
||||
out << " }" << std::endl;
|
||||
}
|
||||
|
||||
out << "}" << std::endl;
|
||||
*out << "}\n";
|
||||
out->flush();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -20,28 +20,27 @@
|
||||
#include "ResourceTable.h"
|
||||
#include "ResourceValues.h"
|
||||
|
||||
#include "util/StringPiece.h"
|
||||
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
struct JavaClassGeneratorOptions {
|
||||
/*
|
||||
* Specifies whether to use the 'final' modifier
|
||||
* on resource entries. Default is true.
|
||||
*/
|
||||
bool useFinal = true;
|
||||
};
|
||||
|
||||
/*
|
||||
* Generates the R.java file for a resource table.
|
||||
*/
|
||||
class JavaClassGenerator : ConstValueVisitor {
|
||||
class JavaClassGenerator {
|
||||
public:
|
||||
/*
|
||||
* A set of options for this JavaClassGenerator.
|
||||
*/
|
||||
struct Options {
|
||||
/*
|
||||
* Specifies whether to use the 'final' modifier
|
||||
* on resource entries. Default is true.
|
||||
*/
|
||||
bool useFinal = true;
|
||||
};
|
||||
|
||||
JavaClassGenerator(const std::shared_ptr<const ResourceTable>& table, Options options);
|
||||
JavaClassGenerator(ResourceTable* table, JavaClassGeneratorOptions options);
|
||||
|
||||
/*
|
||||
* Writes the R.java file to `out`. Only symbols belonging to `package` are written.
|
||||
@@ -50,21 +49,23 @@ public:
|
||||
* We need to generate these symbols in a separate file.
|
||||
* Returns true on success.
|
||||
*/
|
||||
bool generate(const std::u16string& package, std::ostream& out);
|
||||
|
||||
/*
|
||||
* ConstValueVisitor implementation.
|
||||
*/
|
||||
void visit(const Styleable& styleable, ValueVisitorArgs& args);
|
||||
bool generate(const StringPiece16& package, std::ostream* out);
|
||||
|
||||
const std::string& getError() const;
|
||||
|
||||
private:
|
||||
bool generateType(const std::u16string& package, size_t packageId,
|
||||
const ResourceTableType& type, std::ostream& out);
|
||||
bool generateType(const StringPiece16& packageNameToGenerate,
|
||||
const ResourceTablePackage* package,
|
||||
const ResourceTableType* type,
|
||||
std::ostream* out);
|
||||
|
||||
std::shared_ptr<const ResourceTable> mTable;
|
||||
Options mOptions;
|
||||
void generateStyleable(const StringPiece16& packageNameToGenerate,
|
||||
const std::u16string& entryName,
|
||||
const Styleable* styleable,
|
||||
std::ostream* out);
|
||||
|
||||
ResourceTable* mTable;
|
||||
JavaClassGeneratorOptions mOptions;
|
||||
std::string mError;
|
||||
};
|
||||
|
||||
|
||||
@@ -15,12 +15,9 @@
|
||||
*/
|
||||
|
||||
#include "JavaClassGenerator.h"
|
||||
#include "Linker.h"
|
||||
#include "MockResolver.h"
|
||||
#include "ResourceTable.h"
|
||||
#include "ResourceTableResolver.h"
|
||||
#include "ResourceValues.h"
|
||||
#include "Util.h"
|
||||
#include "util/Util.h"
|
||||
|
||||
#include "test/Builders.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <sstream>
|
||||
@@ -28,51 +25,34 @@
|
||||
|
||||
namespace aapt {
|
||||
|
||||
struct JavaClassGeneratorTest : public ::testing::Test {
|
||||
virtual void SetUp() override {
|
||||
mTable = std::make_shared<ResourceTable>();
|
||||
mTable->setPackage(u"android");
|
||||
mTable->setPackageId(0x01);
|
||||
}
|
||||
TEST(JavaClassGeneratorTest, FailWhenEntryIsJavaKeyword) {
|
||||
std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
|
||||
.setPackageId(u"android", 0x01)
|
||||
.addSimple(u"@android:id/class", ResourceId(0x01020000))
|
||||
.build();
|
||||
|
||||
bool addResource(const ResourceNameRef& name, ResourceId id) {
|
||||
return mTable->addResource(name, id, {}, SourceLine{ "test.xml", 21 },
|
||||
util::make_unique<Id>());
|
||||
}
|
||||
|
||||
std::shared_ptr<ResourceTable> mTable;
|
||||
};
|
||||
|
||||
TEST_F(JavaClassGeneratorTest, FailWhenEntryIsJavaKeyword) {
|
||||
ASSERT_TRUE(addResource(ResourceName{ {}, ResourceType::kId, u"class" },
|
||||
ResourceId{ 0x01, 0x02, 0x0000 }));
|
||||
|
||||
JavaClassGenerator generator(mTable, {});
|
||||
JavaClassGenerator generator(table.get(), {});
|
||||
|
||||
std::stringstream out;
|
||||
EXPECT_FALSE(generator.generate(mTable->getPackage(), out));
|
||||
EXPECT_FALSE(generator.generate(u"android", &out));
|
||||
}
|
||||
|
||||
TEST_F(JavaClassGeneratorTest, TransformInvalidJavaIdentifierCharacter) {
|
||||
ASSERT_TRUE(addResource(ResourceName{ {}, ResourceType::kId, u"hey-man" },
|
||||
ResourceId{ 0x01, 0x02, 0x0000 }));
|
||||
TEST(JavaClassGeneratorTest, TransformInvalidJavaIdentifierCharacter) {
|
||||
std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
|
||||
.setPackageId(u"android", 0x01)
|
||||
.addSimple(u"@android:id/hey-man", ResourceId(0x01020000))
|
||||
.addSimple(u"@android:attr/cool.attr", ResourceId(0x01010000))
|
||||
.addValue(u"@android:styleable/hey.dude", ResourceId(0x01030000),
|
||||
test::StyleableBuilder()
|
||||
.addItem(u"@android:attr/cool.attr", ResourceId(0x01010000))
|
||||
.build())
|
||||
.build();
|
||||
|
||||
ASSERT_TRUE(addResource(ResourceName{ {}, ResourceType::kAttr, u"cool.attr" },
|
||||
ResourceId{ 0x01, 0x01, 0x0000 }));
|
||||
|
||||
std::unique_ptr<Styleable> styleable = util::make_unique<Styleable>();
|
||||
Reference ref(ResourceName{ u"android", ResourceType::kAttr, u"cool.attr"});
|
||||
ref.id = ResourceId{ 0x01, 0x01, 0x0000 };
|
||||
styleable->entries.emplace_back(ref);
|
||||
|
||||
ASSERT_TRUE(mTable->addResource(ResourceName{ {}, ResourceType::kStyleable, u"hey.dude" },
|
||||
ResourceId{ 0x01, 0x03, 0x0000 }, {},
|
||||
SourceLine{ "test.xml", 21 }, std::move(styleable)));
|
||||
|
||||
JavaClassGenerator generator(mTable, {});
|
||||
JavaClassGenerator generator(table.get(), {});
|
||||
|
||||
std::stringstream out;
|
||||
EXPECT_TRUE(generator.generate(mTable->getPackage(), out));
|
||||
EXPECT_TRUE(generator.generate(u"android", &out));
|
||||
|
||||
std::string output = out.str();
|
||||
|
||||
EXPECT_NE(std::string::npos,
|
||||
@@ -85,14 +65,15 @@ TEST_F(JavaClassGeneratorTest, TransformInvalidJavaIdentifierCharacter) {
|
||||
output.find("public static final int hey_dude_cool_attr = 0;"));
|
||||
}
|
||||
|
||||
|
||||
TEST_F(JavaClassGeneratorTest, EmitPackageMangledSymbols) {
|
||||
/*
|
||||
* TODO(adamlesinski): Re-enable this once we get merging working again.
|
||||
* TEST(JavaClassGeneratorTest, EmitPackageMangledSymbols) {
|
||||
ASSERT_TRUE(addResource(ResourceName{ {}, ResourceType::kId, u"foo" },
|
||||
ResourceId{ 0x01, 0x02, 0x0000 }));
|
||||
ResourceTable table;
|
||||
table.setPackage(u"com.lib");
|
||||
ASSERT_TRUE(table.addResource(ResourceName{ {}, ResourceType::kId, u"test" }, {},
|
||||
SourceLine{ "lib.xml", 33 }, util::make_unique<Id>()));
|
||||
Source{ "lib.xml", 33 }, util::make_unique<Id>()));
|
||||
ASSERT_TRUE(mTable->merge(std::move(table)));
|
||||
|
||||
Linker linker(mTable,
|
||||
@@ -113,34 +94,29 @@ TEST_F(JavaClassGeneratorTest, EmitPackageMangledSymbols) {
|
||||
output = out.str();
|
||||
EXPECT_NE(std::string::npos, output.find("int test ="));
|
||||
EXPECT_EQ(std::string::npos, output.find("int foo ="));
|
||||
}
|
||||
}*/
|
||||
|
||||
TEST_F(JavaClassGeneratorTest, EmitOtherPackagesAttributesInStyleable) {
|
||||
std::unique_ptr<Styleable> styleable = util::make_unique<Styleable>();
|
||||
styleable->entries.emplace_back(ResourceNameRef{ mTable->getPackage(),
|
||||
ResourceType::kAttr,
|
||||
u"bar" });
|
||||
styleable->entries.emplace_back(ResourceNameRef{ u"com.lib", ResourceType::kAttr, u"bar" });
|
||||
ASSERT_TRUE(mTable->addResource(ResourceName{ {}, ResourceType::kStyleable, u"Foo" }, {}, {},
|
||||
std::move(styleable)));
|
||||
TEST(JavaClassGeneratorTest, EmitOtherPackagesAttributesInStyleable) {
|
||||
std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
|
||||
.setPackageId(u"android", 0x01)
|
||||
.setPackageId(u"com.lib", 0x02)
|
||||
.addSimple(u"@android:attr/bar", ResourceId(0x01010000))
|
||||
.addSimple(u"@com.lib:attr/bar", ResourceId(0x02010000))
|
||||
.addValue(u"@android:styleable/foo", ResourceId(0x01030000),
|
||||
test::StyleableBuilder()
|
||||
.addItem(u"@android:attr/bar", ResourceId(0x01010000))
|
||||
.addItem(u"@com.lib:attr/bar", ResourceId(0x02010000))
|
||||
.build())
|
||||
.build();
|
||||
|
||||
std::shared_ptr<IResolver> resolver = std::make_shared<MockResolver>(mTable,
|
||||
std::map<ResourceName, ResourceId>({
|
||||
{ ResourceName{ u"android", ResourceType::kAttr, u"bar" },
|
||||
ResourceId{ 0x01, 0x01, 0x0000 } },
|
||||
{ ResourceName{ u"com.lib", ResourceType::kAttr, u"bar" },
|
||||
ResourceId{ 0x02, 0x01, 0x0000 } }}));
|
||||
|
||||
Linker linker(mTable, resolver, {});
|
||||
ASSERT_TRUE(linker.linkAndValidate());
|
||||
|
||||
JavaClassGenerator generator(mTable, {});
|
||||
JavaClassGenerator generator(table.get(), {});
|
||||
|
||||
std::stringstream out;
|
||||
EXPECT_TRUE(generator.generate(mTable->getPackage(), out));
|
||||
EXPECT_TRUE(generator.generate(u"android", &out));
|
||||
|
||||
std::string output = out.str();
|
||||
EXPECT_NE(std::string::npos, output.find("int Foo_bar ="));
|
||||
EXPECT_NE(std::string::npos, output.find("int Foo_com_lib_bar ="));
|
||||
EXPECT_NE(std::string::npos, output.find("int foo_bar ="));
|
||||
EXPECT_NE(std::string::npos, output.find("int foo_com_lib_bar ="));
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
@@ -1,290 +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 "Linker.h"
|
||||
#include "Logger.h"
|
||||
#include "NameMangler.h"
|
||||
#include "Resolver.h"
|
||||
#include "ResourceParser.h"
|
||||
#include "ResourceTable.h"
|
||||
#include "ResourceValues.h"
|
||||
#include "StringPiece.h"
|
||||
#include "Util.h"
|
||||
|
||||
#include <androidfw/AssetManager.h>
|
||||
#include <array>
|
||||
#include <bitset>
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <ostream>
|
||||
#include <set>
|
||||
#include <sstream>
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
Linker::Args::Args(const ResourceNameRef& r, const SourceLine& s) : referrer(r), source(s) {
|
||||
}
|
||||
|
||||
Linker::Linker(const std::shared_ptr<ResourceTable>& table,
|
||||
const std::shared_ptr<IResolver>& resolver, const Options& options) :
|
||||
mResolver(resolver), mTable(table), mOptions(options), mError(false) {
|
||||
}
|
||||
|
||||
bool Linker::linkAndValidate() {
|
||||
std::bitset<256> usedTypeIds;
|
||||
std::array<std::set<uint16_t>, 256> usedIds;
|
||||
usedTypeIds.set(0);
|
||||
|
||||
// Collect which resource IDs are already taken.
|
||||
for (auto& type : *mTable) {
|
||||
if (type->typeId != ResourceTableType::kUnsetTypeId) {
|
||||
// The ID for this type has already been set. We
|
||||
// mark this ID as taken so we don't re-assign it
|
||||
// later.
|
||||
usedTypeIds.set(type->typeId);
|
||||
}
|
||||
|
||||
for (auto& entry : type->entries) {
|
||||
if (type->typeId != ResourceTableType::kUnsetTypeId &&
|
||||
entry->entryId != ResourceEntry::kUnsetEntryId) {
|
||||
// The ID for this entry has already been set. We
|
||||
// mark this ID as taken so we don't re-assign it
|
||||
// later.
|
||||
usedIds[type->typeId].insert(entry->entryId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Assign resource IDs that are available.
|
||||
size_t nextTypeIndex = 0;
|
||||
for (auto& type : *mTable) {
|
||||
if (type->typeId == ResourceTableType::kUnsetTypeId) {
|
||||
while (nextTypeIndex < usedTypeIds.size() && usedTypeIds[nextTypeIndex]) {
|
||||
nextTypeIndex++;
|
||||
}
|
||||
type->typeId = nextTypeIndex++;
|
||||
}
|
||||
|
||||
const auto endEntryIter = std::end(usedIds[type->typeId]);
|
||||
auto nextEntryIter = std::begin(usedIds[type->typeId]);
|
||||
size_t nextIndex = 0;
|
||||
for (auto& entry : type->entries) {
|
||||
if (entry->entryId == ResourceTableType::kUnsetTypeId) {
|
||||
while (nextEntryIter != endEntryIter &&
|
||||
nextIndex == *nextEntryIter) {
|
||||
nextIndex++;
|
||||
++nextEntryIter;
|
||||
}
|
||||
entry->entryId = nextIndex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now do reference linking.
|
||||
for (auto& type : *mTable) {
|
||||
for (auto& entry : type->entries) {
|
||||
if (entry->publicStatus.isPublic && entry->values.empty()) {
|
||||
// A public resource has no values. It will not be encoded
|
||||
// properly without a symbol table. This is a unresolved symbol.
|
||||
addUnresolvedSymbol(ResourceNameRef{
|
||||
mTable->getPackage(), type->type, entry->name },
|
||||
entry->publicStatus.source);
|
||||
continue;
|
||||
}
|
||||
|
||||
for (auto& valueConfig : entry->values) {
|
||||
// Dispatch to the right method of this linker
|
||||
// based on the value's type.
|
||||
valueConfig.value->accept(*this, Args{
|
||||
ResourceNameRef{ mTable->getPackage(), type->type, entry->name },
|
||||
valueConfig.source
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return !mError;
|
||||
}
|
||||
|
||||
const Linker::ResourceNameToSourceMap& Linker::getUnresolvedReferences() const {
|
||||
return mUnresolvedSymbols;
|
||||
}
|
||||
|
||||
void Linker::doResolveReference(Reference& reference, const SourceLine& source) {
|
||||
Maybe<ResourceId> result = mResolver->findId(reference.name);
|
||||
if (!result) {
|
||||
addUnresolvedSymbol(reference.name, source);
|
||||
return;
|
||||
}
|
||||
assert(result.value().isValid());
|
||||
|
||||
if (mOptions.linkResourceIds) {
|
||||
reference.id = result.value();
|
||||
} else {
|
||||
reference.id = 0;
|
||||
}
|
||||
}
|
||||
|
||||
const Attribute* Linker::doResolveAttribute(Reference& attribute, const SourceLine& source) {
|
||||
Maybe<IResolver::Entry> result = mResolver->findAttribute(attribute.name);
|
||||
if (!result || !result.value().attr) {
|
||||
addUnresolvedSymbol(attribute.name, source);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const IResolver::Entry& entry = result.value();
|
||||
assert(entry.id.isValid());
|
||||
|
||||
if (mOptions.linkResourceIds) {
|
||||
attribute.id = entry.id;
|
||||
} else {
|
||||
attribute.id = 0;
|
||||
}
|
||||
return entry.attr;
|
||||
}
|
||||
|
||||
void Linker::visit(Reference& reference, ValueVisitorArgs& a) {
|
||||
Args& args = static_cast<Args&>(a);
|
||||
|
||||
if (reference.name.entry.empty()) {
|
||||
// We can't have a completely bad reference.
|
||||
if (!reference.id.isValid()) {
|
||||
Logger::error() << "srsly? " << args.referrer << std::endl;
|
||||
assert(reference.id.isValid());
|
||||
}
|
||||
|
||||
// This reference has no name but has an ID.
|
||||
// It is a really bad error to have no name and have the same
|
||||
// package ID.
|
||||
assert(reference.id.packageId() != mTable->getPackageId());
|
||||
|
||||
// The reference goes outside this package, let it stay as a
|
||||
// resource ID because it will not change.
|
||||
return;
|
||||
}
|
||||
|
||||
doResolveReference(reference, args.source);
|
||||
|
||||
// TODO(adamlesinski): Verify the referencedType is another reference
|
||||
// or a compatible primitive.
|
||||
}
|
||||
|
||||
void Linker::processAttributeValue(const ResourceNameRef& name, const SourceLine& source,
|
||||
const Attribute& attr, std::unique_ptr<Item>& value) {
|
||||
std::unique_ptr<Item> convertedValue;
|
||||
visitFunc<RawString>(*value, [&](RawString& str) {
|
||||
// This is a raw string, so check if it can be converted to anything.
|
||||
// We can NOT swap value with the converted value in here, since
|
||||
// we called through the original value.
|
||||
|
||||
auto onCreateReference = [&](const ResourceName& name) {
|
||||
// We should never get here. All references would have been
|
||||
// parsed in the parser phase.
|
||||
assert(false);
|
||||
};
|
||||
|
||||
convertedValue = ResourceParser::parseItemForAttribute(*str.value, attr,
|
||||
onCreateReference);
|
||||
if (!convertedValue && attr.typeMask & android::ResTable_map::TYPE_STRING) {
|
||||
// Last effort is to parse as a string.
|
||||
util::StringBuilder builder;
|
||||
builder.append(*str.value);
|
||||
if (builder) {
|
||||
convertedValue = util::make_unique<String>(
|
||||
mTable->getValueStringPool().makeRef(builder.str()));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (convertedValue) {
|
||||
value = std::move(convertedValue);
|
||||
}
|
||||
|
||||
// Process this new or old value (it can be a reference!).
|
||||
value->accept(*this, Args{ name, source });
|
||||
|
||||
// Flatten the value to see what resource type it is.
|
||||
android::Res_value resValue;
|
||||
value->flatten(resValue);
|
||||
|
||||
// Always allow references.
|
||||
const uint32_t typeMask = attr.typeMask | android::ResTable_map::TYPE_REFERENCE;
|
||||
if (!(typeMask & ResourceParser::androidTypeToAttributeTypeMask(resValue.dataType))) {
|
||||
Logger::error(source)
|
||||
<< *value
|
||||
<< " is not compatible with attribute "
|
||||
<< attr
|
||||
<< "."
|
||||
<< std::endl;
|
||||
mError = true;
|
||||
}
|
||||
}
|
||||
|
||||
void Linker::visit(Style& style, ValueVisitorArgs& a) {
|
||||
Args& args = static_cast<Args&>(a);
|
||||
|
||||
if (style.parent.name.isValid() || style.parent.id.isValid()) {
|
||||
visit(style.parent, a);
|
||||
}
|
||||
|
||||
for (Style::Entry& styleEntry : style.entries) {
|
||||
const Attribute* attr = doResolveAttribute(styleEntry.key, args.source);
|
||||
if (attr) {
|
||||
processAttributeValue(args.referrer, args.source, *attr, styleEntry.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Linker::visit(Attribute& attr, ValueVisitorArgs& a) {
|
||||
static constexpr uint32_t kMask = android::ResTable_map::TYPE_ENUM |
|
||||
android::ResTable_map::TYPE_FLAGS;
|
||||
if (attr.typeMask & kMask) {
|
||||
for (auto& symbol : attr.symbols) {
|
||||
visit(symbol.symbol, a);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Linker::visit(Styleable& styleable, ValueVisitorArgs& a) {
|
||||
for (auto& attrRef : styleable.entries) {
|
||||
visit(attrRef, a);
|
||||
}
|
||||
}
|
||||
|
||||
void Linker::visit(Array& array, ValueVisitorArgs& a) {
|
||||
Args& args = static_cast<Args&>(a);
|
||||
|
||||
for (auto& item : array.items) {
|
||||
item->accept(*this, Args{ args.referrer, args.source });
|
||||
}
|
||||
}
|
||||
|
||||
void Linker::visit(Plural& plural, ValueVisitorArgs& a) {
|
||||
Args& args = static_cast<Args&>(a);
|
||||
|
||||
for (auto& item : plural.values) {
|
||||
if (item) {
|
||||
item->accept(*this, Args{ args.referrer, args.source });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Linker::addUnresolvedSymbol(const ResourceNameRef& name, const SourceLine& source) {
|
||||
mUnresolvedSymbols[name.toResourceName()].push_back(source);
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
@@ -1,124 +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_LINKER_H
|
||||
#define AAPT_LINKER_H
|
||||
|
||||
#include "Resolver.h"
|
||||
#include "ResourceTable.h"
|
||||
#include "ResourceValues.h"
|
||||
#include "Source.h"
|
||||
#include "StringPiece.h"
|
||||
|
||||
#include <androidfw/AssetManager.h>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <ostream>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
/**
|
||||
* The Linker has two jobs. It follows resource references
|
||||
* and verifies that their targert exists and that their
|
||||
* types are compatible. The Linker will also assign resource
|
||||
* IDs and fill in all the dependent references with the newly
|
||||
* assigned resource IDs.
|
||||
*
|
||||
* To do this, the Linker builds a graph of references. This
|
||||
* can be useful to do other analysis, like building a
|
||||
* dependency graph of source files. The hope is to be able to
|
||||
* add functionality that operates on the graph without
|
||||
* overcomplicating the Linker.
|
||||
*
|
||||
* TODO(adamlesinski): Build the graph first then run the separate
|
||||
* steps over the graph.
|
||||
*/
|
||||
class Linker : ValueVisitor {
|
||||
public:
|
||||
struct Options {
|
||||
/**
|
||||
* Assign resource Ids to references when linking.
|
||||
* When building a static library, set this to false.
|
||||
*/
|
||||
bool linkResourceIds = true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a Linker for the given resource table with the sources available in
|
||||
* IResolver. IResolver should contain the ResourceTable as a source too.
|
||||
*/
|
||||
Linker(const std::shared_ptr<ResourceTable>& table,
|
||||
const std::shared_ptr<IResolver>& resolver, const Options& options);
|
||||
|
||||
Linker(const Linker&) = delete;
|
||||
|
||||
virtual ~Linker() = default;
|
||||
|
||||
/**
|
||||
* Entry point to the linker. Assigns resource IDs, follows references,
|
||||
* and validates types. Returns true if all references to defined values
|
||||
* are type-compatible. Missing resource references are recorded but do
|
||||
* not cause this method to fail.
|
||||
*/
|
||||
bool linkAndValidate();
|
||||
|
||||
/**
|
||||
* Returns any references to resources that were not defined in any of the
|
||||
* sources.
|
||||
*/
|
||||
using ResourceNameToSourceMap = std::map<ResourceName, std::vector<SourceLine>>;
|
||||
const ResourceNameToSourceMap& getUnresolvedReferences() const;
|
||||
|
||||
protected:
|
||||
virtual void doResolveReference(Reference& reference, const SourceLine& source);
|
||||
virtual const Attribute* doResolveAttribute(Reference& attribute, const SourceLine& source);
|
||||
|
||||
std::shared_ptr<IResolver> mResolver;
|
||||
|
||||
private:
|
||||
struct Args : public ValueVisitorArgs {
|
||||
Args(const ResourceNameRef& r, const SourceLine& s);
|
||||
|
||||
const ResourceNameRef& referrer;
|
||||
const SourceLine& source;
|
||||
};
|
||||
|
||||
//
|
||||
// Overrides of ValueVisitor
|
||||
//
|
||||
void visit(Reference& reference, ValueVisitorArgs& args) override;
|
||||
void visit(Attribute& attribute, ValueVisitorArgs& args) override;
|
||||
void visit(Styleable& styleable, ValueVisitorArgs& args) override;
|
||||
void visit(Style& style, ValueVisitorArgs& args) override;
|
||||
void visit(Array& array, ValueVisitorArgs& args) override;
|
||||
void visit(Plural& plural, ValueVisitorArgs& args) override;
|
||||
|
||||
void processAttributeValue(const ResourceNameRef& name, const SourceLine& source,
|
||||
const Attribute& attr, std::unique_ptr<Item>& value);
|
||||
|
||||
void addUnresolvedSymbol(const ResourceNameRef& name, const SourceLine& source);
|
||||
|
||||
std::shared_ptr<ResourceTable> mTable;
|
||||
std::map<ResourceName, std::vector<SourceLine>> mUnresolvedSymbols;
|
||||
Options mOptions;
|
||||
bool mError;
|
||||
};
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
#endif // AAPT_LINKER_H
|
||||
@@ -1,153 +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 "Linker.h"
|
||||
#include "ResourceTable.h"
|
||||
#include "ResourceTableResolver.h"
|
||||
#include "ResourceValues.h"
|
||||
#include "Util.h"
|
||||
|
||||
#include <androidfw/AssetManager.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <string>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
struct LinkerTest : public ::testing::Test {
|
||||
virtual void SetUp() override {
|
||||
mTable = std::make_shared<ResourceTable>();
|
||||
mTable->setPackage(u"android");
|
||||
mTable->setPackageId(0x01);
|
||||
mLinker = std::make_shared<Linker>(mTable, std::make_shared<ResourceTableResolver>(
|
||||
mTable, std::vector<std::shared_ptr<const android::AssetManager>>()),
|
||||
Linker::Options{});
|
||||
|
||||
// Create a few attributes for use in the tests.
|
||||
|
||||
addResource(ResourceName{ {}, ResourceType::kAttr, u"integer" },
|
||||
util::make_unique<Attribute>(false, android::ResTable_map::TYPE_INTEGER));
|
||||
|
||||
addResource(ResourceName{ {}, ResourceType::kAttr, u"string" },
|
||||
util::make_unique<Attribute>(false, android::ResTable_map::TYPE_STRING));
|
||||
|
||||
addResource(ResourceName{ {}, ResourceType::kId, u"apple" }, util::make_unique<Id>());
|
||||
|
||||
addResource(ResourceName{ {}, ResourceType::kId, u"banana" }, util::make_unique<Id>());
|
||||
|
||||
std::unique_ptr<Attribute> flagAttr = util::make_unique<Attribute>(
|
||||
false, android::ResTable_map::TYPE_FLAGS);
|
||||
flagAttr->symbols.push_back(Attribute::Symbol{
|
||||
ResourceNameRef{ u"android", ResourceType::kId, u"apple" }, 1 });
|
||||
flagAttr->symbols.push_back(Attribute::Symbol{
|
||||
ResourceNameRef{ u"android", ResourceType::kId, u"banana" }, 2 });
|
||||
addResource(ResourceName{ {}, ResourceType::kAttr, u"flags" }, std::move(flagAttr));
|
||||
}
|
||||
|
||||
/*
|
||||
* Convenience method for adding resources with the default configuration and some
|
||||
* bogus source line.
|
||||
*/
|
||||
bool addResource(const ResourceNameRef& name, std::unique_ptr<Value> value) {
|
||||
return mTable->addResource(name, {}, SourceLine{ "test.xml", 21 }, std::move(value));
|
||||
}
|
||||
|
||||
std::shared_ptr<ResourceTable> mTable;
|
||||
std::shared_ptr<Linker> mLinker;
|
||||
};
|
||||
|
||||
TEST_F(LinkerTest, DoNotInterpretEscapedStringAsReference) {
|
||||
ASSERT_TRUE(addResource(ResourceName{ u"android", ResourceType::kString, u"foo" },
|
||||
util::make_unique<String>(mTable->getValueStringPool().makeRef(u"?123"))));
|
||||
|
||||
ASSERT_TRUE(mLinker->linkAndValidate());
|
||||
EXPECT_TRUE(mLinker->getUnresolvedReferences().empty());
|
||||
}
|
||||
|
||||
TEST_F(LinkerTest, EscapeAndConvertRawString) {
|
||||
std::unique_ptr<Style> style = util::make_unique<Style>();
|
||||
style->entries.push_back(Style::Entry{
|
||||
ResourceNameRef{ u"android", ResourceType::kAttr, u"integer" },
|
||||
util::make_unique<RawString>(mTable->getValueStringPool().makeRef(u" 123"))
|
||||
});
|
||||
const Style* result = style.get();
|
||||
ASSERT_TRUE(addResource(ResourceName{ u"android", ResourceType::kStyle, u"foo" },
|
||||
std::move(style)));
|
||||
|
||||
ASSERT_TRUE(mLinker->linkAndValidate());
|
||||
EXPECT_TRUE(mLinker->getUnresolvedReferences().empty());
|
||||
|
||||
EXPECT_NE(nullptr, dynamic_cast<BinaryPrimitive*>(result->entries.front().value.get()));
|
||||
}
|
||||
|
||||
TEST_F(LinkerTest, FailToConvertRawString) {
|
||||
std::unique_ptr<Style> style = util::make_unique<Style>();
|
||||
style->entries.push_back(Style::Entry{
|
||||
ResourceNameRef{ u"android", ResourceType::kAttr, u"integer" },
|
||||
util::make_unique<RawString>(mTable->getValueStringPool().makeRef(u"yo what is up?"))
|
||||
});
|
||||
ASSERT_TRUE(addResource(ResourceName{ u"android", ResourceType::kStyle, u"foo" },
|
||||
std::move(style)));
|
||||
|
||||
ASSERT_FALSE(mLinker->linkAndValidate());
|
||||
}
|
||||
|
||||
TEST_F(LinkerTest, ConvertRawStringToString) {
|
||||
std::unique_ptr<Style> style = util::make_unique<Style>();
|
||||
style->entries.push_back(Style::Entry{
|
||||
ResourceNameRef{ u"android", ResourceType::kAttr, u"string" },
|
||||
util::make_unique<RawString>(
|
||||
mTable->getValueStringPool().makeRef(u" \"this is \\u00fa\"."))
|
||||
});
|
||||
const Style* result = style.get();
|
||||
ASSERT_TRUE(addResource(ResourceName{ u"android", ResourceType::kStyle, u"foo" },
|
||||
std::move(style)));
|
||||
|
||||
ASSERT_TRUE(mLinker->linkAndValidate());
|
||||
EXPECT_TRUE(mLinker->getUnresolvedReferences().empty());
|
||||
|
||||
const String* str = dynamic_cast<const String*>(result->entries.front().value.get());
|
||||
ASSERT_NE(nullptr, str);
|
||||
EXPECT_EQ(*str->value, u"this is \u00fa.");
|
||||
}
|
||||
|
||||
TEST_F(LinkerTest, ConvertRawStringToFlags) {
|
||||
std::unique_ptr<Style> style = util::make_unique<Style>();
|
||||
style->entries.push_back(Style::Entry{
|
||||
ResourceNameRef{ u"android", ResourceType::kAttr, u"flags" },
|
||||
util::make_unique<RawString>(mTable->getValueStringPool().makeRef(u"banana | apple"))
|
||||
});
|
||||
const Style* result = style.get();
|
||||
ASSERT_TRUE(addResource(ResourceName{ u"android", ResourceType::kStyle, u"foo" },
|
||||
std::move(style)));
|
||||
|
||||
ASSERT_TRUE(mLinker->linkAndValidate());
|
||||
EXPECT_TRUE(mLinker->getUnresolvedReferences().empty());
|
||||
|
||||
const BinaryPrimitive* bin = dynamic_cast<const BinaryPrimitive*>(
|
||||
result->entries.front().value.get());
|
||||
ASSERT_NE(nullptr, bin);
|
||||
EXPECT_EQ(bin->value.data, 1u | 2u);
|
||||
}
|
||||
|
||||
TEST_F(LinkerTest, AllowReferenceWithOnlyResourceIdPointingToDifferentPackage) {
|
||||
ASSERT_TRUE(addResource(ResourceName{ u"android", ResourceType::kInteger, u"foo" },
|
||||
util::make_unique<Reference>(ResourceId{ 0x02, 0x01, 0x01 })));
|
||||
|
||||
ASSERT_TRUE(mLinker->linkAndValidate());
|
||||
EXPECT_TRUE(mLinker->getUnresolvedReferences().empty());
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
#include "Locale.h"
|
||||
#include "Util.h"
|
||||
#include "util/Util.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <ctype.h>
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
#include "Locale.h"
|
||||
#include "Util.h"
|
||||
#include "util/Util.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <string>
|
||||
|
||||
@@ -1,97 +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 "Logger.h"
|
||||
#include "Source.h"
|
||||
|
||||
#include <memory>
|
||||
#include <iostream>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
Log::Log(std::ostream& _out, std::ostream& _err) : out(_out), err(_err) {
|
||||
}
|
||||
|
||||
std::shared_ptr<Log> Logger::sLog(std::make_shared<Log>(std::cerr, std::cerr));
|
||||
|
||||
void Logger::setLog(const std::shared_ptr<Log>& log) {
|
||||
sLog = log;
|
||||
}
|
||||
|
||||
std::ostream& Logger::error() {
|
||||
return sLog->err << "error: ";
|
||||
}
|
||||
|
||||
std::ostream& Logger::error(const Source& source) {
|
||||
return sLog->err << source << ": error: ";
|
||||
}
|
||||
|
||||
std::ostream& Logger::error(const SourceLine& source) {
|
||||
return sLog->err << source << ": error: ";
|
||||
}
|
||||
|
||||
std::ostream& Logger::warn() {
|
||||
return sLog->err << "warning: ";
|
||||
}
|
||||
|
||||
std::ostream& Logger::warn(const Source& source) {
|
||||
return sLog->err << source << ": warning: ";
|
||||
}
|
||||
|
||||
std::ostream& Logger::warn(const SourceLine& source) {
|
||||
return sLog->err << source << ": warning: ";
|
||||
}
|
||||
|
||||
std::ostream& Logger::note() {
|
||||
return sLog->out << "note: ";
|
||||
}
|
||||
|
||||
std::ostream& Logger::note(const Source& source) {
|
||||
return sLog->err << source << ": note: ";
|
||||
}
|
||||
|
||||
std::ostream& Logger::note(const SourceLine& source) {
|
||||
return sLog->err << source << ": note: ";
|
||||
}
|
||||
|
||||
SourceLogger::SourceLogger(const Source& source)
|
||||
: mSource(source) {
|
||||
}
|
||||
|
||||
std::ostream& SourceLogger::error() {
|
||||
return Logger::error(mSource);
|
||||
}
|
||||
|
||||
std::ostream& SourceLogger::error(size_t line) {
|
||||
return Logger::error(SourceLine{ mSource.path, line });
|
||||
}
|
||||
|
||||
std::ostream& SourceLogger::warn() {
|
||||
return Logger::warn(mSource);
|
||||
}
|
||||
|
||||
std::ostream& SourceLogger::warn(size_t line) {
|
||||
return Logger::warn(SourceLine{ mSource.path, line });
|
||||
}
|
||||
|
||||
std::ostream& SourceLogger::note() {
|
||||
return Logger::note(mSource);
|
||||
}
|
||||
|
||||
std::ostream& SourceLogger::note(size_t line) {
|
||||
return Logger::note(SourceLine{ mSource.path, line });
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
@@ -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_LOGGER_H
|
||||
#define AAPT_LOGGER_H
|
||||
|
||||
#include "Source.h"
|
||||
#include "StringPiece.h"
|
||||
|
||||
#include <memory>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
struct Log {
|
||||
Log(std::ostream& out, std::ostream& err);
|
||||
Log(const Log& rhs) = delete;
|
||||
|
||||
std::ostream& out;
|
||||
std::ostream& err;
|
||||
};
|
||||
|
||||
class Logger {
|
||||
public:
|
||||
static void setLog(const std::shared_ptr<Log>& log);
|
||||
|
||||
static std::ostream& error();
|
||||
static std::ostream& error(const Source& source);
|
||||
static std::ostream& error(const SourceLine& sourceLine);
|
||||
|
||||
static std::ostream& warn();
|
||||
static std::ostream& warn(const Source& source);
|
||||
static std::ostream& warn(const SourceLine& sourceLine);
|
||||
|
||||
static std::ostream& note();
|
||||
static std::ostream& note(const Source& source);
|
||||
static std::ostream& note(const SourceLine& sourceLine);
|
||||
|
||||
private:
|
||||
static std::shared_ptr<Log> sLog;
|
||||
};
|
||||
|
||||
class SourceLogger {
|
||||
public:
|
||||
SourceLogger(const Source& source);
|
||||
|
||||
std::ostream& error();
|
||||
std::ostream& error(size_t line);
|
||||
|
||||
std::ostream& warn();
|
||||
std::ostream& warn(size_t line);
|
||||
|
||||
std::ostream& note();
|
||||
std::ostream& note(size_t line);
|
||||
|
||||
private:
|
||||
Source mSource;
|
||||
};
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
#endif // AAPT_LOGGER_H
|
||||
1265
tools/aapt2/Main.cpp
1265
tools/aapt2/Main.cpp
File diff suppressed because it is too large
Load Diff
@@ -1,376 +0,0 @@
|
||||
#include "ManifestMerger.h"
|
||||
#include "Maybe.h"
|
||||
#include "ResourceParser.h"
|
||||
#include "Source.h"
|
||||
#include "Util.h"
|
||||
#include "XmlPullParser.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <string>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
constexpr const char16_t* kSchemaAndroid = u"http://schemas.android.com/apk/res/android";
|
||||
|
||||
static xml::Element* findManifest(xml::Node* root) {
|
||||
if (!root) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
while (root->type == xml::NodeType::kNamespace) {
|
||||
if (root->children.empty()) {
|
||||
break;
|
||||
}
|
||||
root = root->children[0].get();
|
||||
}
|
||||
|
||||
if (root && root->type == xml::NodeType::kElement) {
|
||||
xml::Element* el = static_cast<xml::Element*>(root);
|
||||
if (el->namespaceUri.empty() && el->name == u"manifest") {
|
||||
return el;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static xml::Element* findChildWithSameName(xml::Element* parent, xml::Element* src) {
|
||||
xml::Attribute* attrKey = src->findAttribute(kSchemaAndroid, u"name");
|
||||
if (!attrKey) {
|
||||
return nullptr;
|
||||
}
|
||||
return parent->findChildWithAttribute(src->namespaceUri, src->name, attrKey);
|
||||
}
|
||||
|
||||
static bool attrLess(const xml::Attribute& lhs, const xml::Attribute& rhs) {
|
||||
return std::tie(lhs.namespaceUri, lhs.name, lhs.value)
|
||||
< std::tie(rhs.namespaceUri, rhs.name, rhs.value);
|
||||
}
|
||||
|
||||
static int compare(xml::Element* lhs, xml::Element* rhs) {
|
||||
int diff = lhs->attributes.size() - rhs->attributes.size();
|
||||
if (diff != 0) {
|
||||
return diff;
|
||||
}
|
||||
|
||||
std::set<xml::Attribute, decltype(&attrLess)> lhsAttrs(&attrLess);
|
||||
lhsAttrs.insert(lhs->attributes.begin(), lhs->attributes.end());
|
||||
for (auto& attr : rhs->attributes) {
|
||||
if (lhsAttrs.erase(attr) == 0) {
|
||||
// The rhs attribute is not in the left.
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (!lhsAttrs.empty()) {
|
||||
// The lhs has attributes not in the rhs.
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
ManifestMerger::ManifestMerger(const Options& options) :
|
||||
mOptions(options), mAppLogger({}), mLogger({}) {
|
||||
}
|
||||
|
||||
bool ManifestMerger::setAppManifest(const Source& source, const std::u16string& package,
|
||||
std::unique_ptr<xml::Node> root) {
|
||||
|
||||
mAppLogger = SourceLogger{ source };
|
||||
mRoot = std::move(root);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ManifestMerger::checkEqual(xml::Element* elA, xml::Element* elB) {
|
||||
if (compare(elA, elB) != 0) {
|
||||
mLogger.error(elB->lineNumber)
|
||||
<< "library tag '" << elB->name << "' conflicts with app tag."
|
||||
<< std::endl;
|
||||
mAppLogger.note(elA->lineNumber)
|
||||
<< "app tag '" << elA->name << "' defined here."
|
||||
<< std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<xml::Element*> childrenA = elA->getChildElements();
|
||||
std::vector<xml::Element*> childrenB = elB->getChildElements();
|
||||
|
||||
if (childrenA.size() != childrenB.size()) {
|
||||
mLogger.error(elB->lineNumber)
|
||||
<< "library tag '" << elB->name << "' children conflict with app tag."
|
||||
<< std::endl;
|
||||
mAppLogger.note(elA->lineNumber)
|
||||
<< "app tag '" << elA->name << "' defined here."
|
||||
<< std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto cmp = [](xml::Element* lhs, xml::Element* rhs) -> bool {
|
||||
return compare(lhs, rhs) < 0;
|
||||
};
|
||||
|
||||
std::sort(childrenA.begin(), childrenA.end(), cmp);
|
||||
std::sort(childrenB.begin(), childrenB.end(), cmp);
|
||||
|
||||
for (size_t i = 0; i < childrenA.size(); i++) {
|
||||
if (!checkEqual(childrenA[i], childrenB[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ManifestMerger::mergeNewOrEqual(xml::Element* parentA, xml::Element* elA, xml::Element* elB) {
|
||||
if (!elA) {
|
||||
parentA->addChild(elB->clone());
|
||||
return true;
|
||||
}
|
||||
return checkEqual(elA, elB);
|
||||
}
|
||||
|
||||
bool ManifestMerger::mergePreferRequired(xml::Element* parentA, xml::Element* elA,
|
||||
xml::Element* elB) {
|
||||
if (!elA) {
|
||||
parentA->addChild(elB->clone());
|
||||
return true;
|
||||
}
|
||||
|
||||
xml::Attribute* reqA = elA->findAttribute(kSchemaAndroid, u"required");
|
||||
xml::Attribute* reqB = elB->findAttribute(kSchemaAndroid, u"required");
|
||||
bool requiredA = !reqA || (reqA->value != u"false" && reqA->value != u"FALSE");
|
||||
bool requiredB = !reqB || (reqB->value != u"false" && reqB->value != u"FALSE");
|
||||
if (!requiredA && requiredB) {
|
||||
if (reqA) {
|
||||
*reqA = xml::Attribute{ kSchemaAndroid, u"required", u"true" };
|
||||
} else {
|
||||
elA->attributes.push_back(xml::Attribute{ kSchemaAndroid, u"required", u"true" });
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static int findIntegerValue(xml::Attribute* attr, int defaultValue) {
|
||||
if (attr) {
|
||||
std::unique_ptr<BinaryPrimitive> integer = ResourceParser::tryParseInt(attr->value);
|
||||
if (integer) {
|
||||
return integer->value.data;
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
bool ManifestMerger::mergeUsesSdk(xml::Element* elA, xml::Element* elB) {
|
||||
bool error = false;
|
||||
xml::Attribute* minAttrA = nullptr;
|
||||
xml::Attribute* minAttrB = nullptr;
|
||||
if (elA) {
|
||||
minAttrA = elA->findAttribute(kSchemaAndroid, u"minSdkVersion");
|
||||
}
|
||||
|
||||
if (elB) {
|
||||
minAttrB = elB->findAttribute(kSchemaAndroid, u"minSdkVersion");
|
||||
}
|
||||
|
||||
int minSdkA = findIntegerValue(minAttrA, 1);
|
||||
int minSdkB = findIntegerValue(minAttrB, 1);
|
||||
|
||||
if (minSdkA < minSdkB) {
|
||||
std::ostream* out;
|
||||
if (minAttrA) {
|
||||
out = &(mAppLogger.error(elA->lineNumber) << "app declares ");
|
||||
} else if (elA) {
|
||||
out = &(mAppLogger.error(elA->lineNumber) << "app has implied ");
|
||||
} else {
|
||||
out = &(mAppLogger.error() << "app has implied ");
|
||||
}
|
||||
|
||||
*out << "minSdkVersion=" << minSdkA << " but library expects a higher SDK version."
|
||||
<< std::endl;
|
||||
|
||||
// elB is valid because minSdkB wouldn't be greater than minSdkA if it wasn't.
|
||||
mLogger.note(elB->lineNumber)
|
||||
<< "library declares minSdkVersion=" << minSdkB << "."
|
||||
<< std::endl;
|
||||
error = true;
|
||||
}
|
||||
|
||||
xml::Attribute* targetAttrA = nullptr;
|
||||
xml::Attribute* targetAttrB = nullptr;
|
||||
|
||||
if (elA) {
|
||||
targetAttrA = elA->findAttribute(kSchemaAndroid, u"targetSdkVersion");
|
||||
}
|
||||
|
||||
if (elB) {
|
||||
targetAttrB = elB->findAttribute(kSchemaAndroid, u"targetSdkVersion");
|
||||
}
|
||||
|
||||
int targetSdkA = findIntegerValue(targetAttrA, minSdkA);
|
||||
int targetSdkB = findIntegerValue(targetAttrB, minSdkB);
|
||||
|
||||
if (targetSdkA < targetSdkB) {
|
||||
std::ostream* out;
|
||||
if (targetAttrA) {
|
||||
out = &(mAppLogger.warn(elA->lineNumber) << "app declares ");
|
||||
} else if (elA) {
|
||||
out = &(mAppLogger.warn(elA->lineNumber) << "app has implied ");
|
||||
} else {
|
||||
out = &(mAppLogger.warn() << "app has implied ");
|
||||
}
|
||||
|
||||
*out << "targetSdkVerion=" << targetSdkA << " but library expects target SDK "
|
||||
<< targetSdkB << "." << std::endl;
|
||||
|
||||
mLogger.note(elB->lineNumber)
|
||||
<< "library declares targetSdkVersion=" << targetSdkB << "."
|
||||
<< std::endl;
|
||||
error = true;
|
||||
}
|
||||
return !error;
|
||||
}
|
||||
|
||||
bool ManifestMerger::mergeApplication(xml::Element* applicationA, xml::Element* applicationB) {
|
||||
if (!applicationA || !applicationB) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool error = false;
|
||||
|
||||
// First make sure that the names are identical.
|
||||
xml::Attribute* nameA = applicationA->findAttribute(kSchemaAndroid, u"name");
|
||||
xml::Attribute* nameB = applicationB->findAttribute(kSchemaAndroid, u"name");
|
||||
if (nameB) {
|
||||
if (!nameA) {
|
||||
applicationA->attributes.push_back(*nameB);
|
||||
} else if (nameA->value != nameB->value) {
|
||||
mLogger.error(applicationB->lineNumber)
|
||||
<< "conflicting application name '"
|
||||
<< nameB->value
|
||||
<< "'." << std::endl;
|
||||
mAppLogger.note(applicationA->lineNumber)
|
||||
<< "application defines application name '"
|
||||
<< nameA->value
|
||||
<< "'." << std::endl;
|
||||
error = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Now we descend into the activity/receiver/service/provider tags
|
||||
for (xml::Element* elB : applicationB->getChildElements()) {
|
||||
if (!elB->namespaceUri.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (elB->name == u"activity" || elB->name == u"activity-alias"
|
||||
|| elB->name == u"service" || elB->name == u"receiver"
|
||||
|| elB->name == u"provider" || elB->name == u"meta-data") {
|
||||
xml::Element* elA = findChildWithSameName(applicationA, elB);
|
||||
error |= !mergeNewOrEqual(applicationA, elA, elB);
|
||||
} else if (elB->name == u"uses-library") {
|
||||
xml::Element* elA = findChildWithSameName(applicationA, elB);
|
||||
error |= !mergePreferRequired(applicationA, elA, elB);
|
||||
}
|
||||
}
|
||||
return !error;
|
||||
}
|
||||
|
||||
bool ManifestMerger::mergeLibraryManifest(const Source& source, const std::u16string& package,
|
||||
std::unique_ptr<xml::Node> libRoot) {
|
||||
mLogger = SourceLogger{ source };
|
||||
xml::Element* manifestA = findManifest(mRoot.get());
|
||||
xml::Element* manifestB = findManifest(libRoot.get());
|
||||
if (!manifestA) {
|
||||
mAppLogger.error() << "missing manifest tag." << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!manifestB) {
|
||||
mLogger.error() << "library missing manifest tag." << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool error = false;
|
||||
|
||||
// Do <application> first.
|
||||
xml::Element* applicationA = manifestA->findChild({}, u"application");
|
||||
xml::Element* applicationB = manifestB->findChild({}, u"application");
|
||||
error |= !mergeApplication(applicationA, applicationB);
|
||||
|
||||
// Do <uses-sdk> next.
|
||||
xml::Element* usesSdkA = manifestA->findChild({}, u"uses-sdk");
|
||||
xml::Element* usesSdkB = manifestB->findChild({}, u"uses-sdk");
|
||||
error |= !mergeUsesSdk(usesSdkA, usesSdkB);
|
||||
|
||||
for (xml::Element* elB : manifestB->getChildElements()) {
|
||||
if (!elB->namespaceUri.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (elB->name == u"uses-permission" || elB->name == u"permission"
|
||||
|| elB->name == u"permission-group" || elB->name == u"permission-tree") {
|
||||
xml::Element* elA = findChildWithSameName(manifestA, elB);
|
||||
error |= !mergeNewOrEqual(manifestA, elA, elB);
|
||||
} else if (elB->name == u"uses-feature") {
|
||||
xml::Element* elA = findChildWithSameName(manifestA, elB);
|
||||
error |= !mergePreferRequired(manifestA, elA, elB);
|
||||
} else if (elB->name == u"uses-configuration" || elB->name == u"supports-screen"
|
||||
|| elB->name == u"compatible-screens" || elB->name == u"supports-gl-texture") {
|
||||
xml::Element* elA = findChildWithSameName(manifestA, elB);
|
||||
error |= !checkEqual(elA, elB);
|
||||
}
|
||||
}
|
||||
return !error;
|
||||
}
|
||||
|
||||
static void printMerged(xml::Node* node, int depth) {
|
||||
std::string indent;
|
||||
for (int i = 0; i < depth; i++) {
|
||||
indent += " ";
|
||||
}
|
||||
|
||||
switch (node->type) {
|
||||
case xml::NodeType::kNamespace:
|
||||
std::cerr << indent << "N: "
|
||||
<< "xmlns:" << static_cast<xml::Namespace*>(node)->namespacePrefix
|
||||
<< "=\"" << static_cast<xml::Namespace*>(node)->namespaceUri
|
||||
<< "\"\n";
|
||||
break;
|
||||
|
||||
case xml::NodeType::kElement:
|
||||
std::cerr << indent << "E: "
|
||||
<< static_cast<xml::Element*>(node)->namespaceUri
|
||||
<< ":" << static_cast<xml::Element*>(node)->name
|
||||
<< "\n";
|
||||
for (const auto& attr : static_cast<xml::Element*>(node)->attributes) {
|
||||
std::cerr << indent << " A: "
|
||||
<< attr.namespaceUri
|
||||
<< ":" << attr.name
|
||||
<< "=\"" << attr.value << "\"\n";
|
||||
}
|
||||
break;
|
||||
|
||||
case xml::NodeType::kText:
|
||||
std::cerr << indent << "T: \"" << static_cast<xml::Text*>(node)->text << "\"\n";
|
||||
break;
|
||||
}
|
||||
|
||||
for (auto& child : node->children) {
|
||||
printMerged(child.get(), depth + 1);
|
||||
}
|
||||
}
|
||||
|
||||
xml::Node* ManifestMerger::getMergedXml() {
|
||||
return mRoot.get();
|
||||
}
|
||||
|
||||
bool ManifestMerger::printMerged() {
|
||||
if (!mRoot) {
|
||||
return false;
|
||||
}
|
||||
|
||||
::aapt::printMerged(mRoot.get(), 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
@@ -1,45 +0,0 @@
|
||||
#ifndef AAPT_MANIFEST_MERGER_H
|
||||
#define AAPT_MANIFEST_MERGER_H
|
||||
|
||||
#include "Logger.h"
|
||||
#include "Source.h"
|
||||
#include "XmlDom.h"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
class ManifestMerger {
|
||||
public:
|
||||
struct Options {
|
||||
};
|
||||
|
||||
ManifestMerger(const Options& options);
|
||||
|
||||
bool setAppManifest(const Source& source, const std::u16string& package,
|
||||
std::unique_ptr<xml::Node> root);
|
||||
|
||||
bool mergeLibraryManifest(const Source& source, const std::u16string& package,
|
||||
std::unique_ptr<xml::Node> libRoot);
|
||||
|
||||
xml::Node* getMergedXml();
|
||||
|
||||
bool printMerged();
|
||||
|
||||
private:
|
||||
bool mergeNewOrEqual(xml::Element* parentA, xml::Element* elA, xml::Element* elB);
|
||||
bool mergePreferRequired(xml::Element* parentA, xml::Element* elA, xml::Element* elB);
|
||||
bool checkEqual(xml::Element* elA, xml::Element* elB);
|
||||
bool mergeApplication(xml::Element* applicationA, xml::Element* applicationB);
|
||||
bool mergeUsesSdk(xml::Element* elA, xml::Element* elB);
|
||||
|
||||
Options mOptions;
|
||||
std::unique_ptr<xml::Node> mRoot;
|
||||
SourceLogger mAppLogger;
|
||||
SourceLogger mLogger;
|
||||
};
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
#endif // AAPT_MANIFEST_MERGER_H
|
||||
@@ -1,121 +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 "ManifestMerger.h"
|
||||
#include "SourceXmlPullParser.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
constexpr const char* kAppManifest = R"EOF(<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<uses-sdk android:minSdkVersion="7" android:targetSdkVersion="21" />
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-feature android:name="android.hardware.GPS" android:required="false" />
|
||||
<application android:name="com.android.library.Application">
|
||||
<activity android:name="com.android.example.MainActivity">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<service android:name="com.android.library.Service">
|
||||
<intent-filter>
|
||||
<action android:name="com.android.library.intent.action.SYNC" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
</application>
|
||||
</manifest>
|
||||
)EOF";
|
||||
|
||||
constexpr const char* kLibManifest = R"EOF(<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<uses-sdk android:minSdkVersion="4" android:targetSdkVersion="21" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-feature android:name="android.hardware.GPS" />
|
||||
<uses-permission android:name="android.permission.GPS" />
|
||||
<application android:name="com.android.library.Application">
|
||||
<service android:name="com.android.library.Service">
|
||||
<intent-filter>
|
||||
<action android:name="com.android.library.intent.action.SYNC" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
<provider android:name="com.android.library.DocumentProvider"
|
||||
android:authorities="com.android.library.documents"
|
||||
android:grantUriPermission="true"
|
||||
android:exported="true"
|
||||
android:permission="android.permission.MANAGE_DOCUMENTS"
|
||||
android:enabled="@bool/atLeastKitKat">
|
||||
<intent-filter>
|
||||
<action android:name="android.content.action.DOCUMENTS_PROVIDER" />
|
||||
</intent-filter>
|
||||
</provider>
|
||||
</application>
|
||||
</manifest>
|
||||
)EOF";
|
||||
|
||||
constexpr const char* kBadLibManifest = R"EOF(<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<uses-sdk android:minSdkVersion="17" android:targetSdkVersion="22" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-feature android:name="android.hardware.GPS" />
|
||||
<uses-permission android:name="android.permission.GPS" />
|
||||
<application android:name="com.android.library.Application2">
|
||||
<service android:name="com.android.library.Service">
|
||||
<intent-filter>
|
||||
<action android:name="com.android.library.intent.action.SYNC_ACTION" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
</application>
|
||||
</manifest>
|
||||
)EOF";
|
||||
|
||||
TEST(ManifestMergerTest, MergeManifestsSuccess) {
|
||||
std::stringstream inA(kAppManifest);
|
||||
std::stringstream inB(kLibManifest);
|
||||
|
||||
const Source sourceA = { "AndroidManifest.xml" };
|
||||
const Source sourceB = { "lib.apk/AndroidManifest.xml" };
|
||||
SourceLogger loggerA(sourceA);
|
||||
SourceLogger loggerB(sourceB);
|
||||
|
||||
ManifestMerger merger({});
|
||||
EXPECT_TRUE(merger.setAppManifest(sourceA, u"com.android.example",
|
||||
xml::inflate(&inA, &loggerA)));
|
||||
EXPECT_TRUE(merger.mergeLibraryManifest(sourceB, u"com.android.library",
|
||||
xml::inflate(&inB, &loggerB)));
|
||||
}
|
||||
|
||||
TEST(ManifestMergerTest, MergeManifestFail) {
|
||||
std::stringstream inA(kAppManifest);
|
||||
std::stringstream inB(kBadLibManifest);
|
||||
|
||||
const Source sourceA = { "AndroidManifest.xml" };
|
||||
const Source sourceB = { "lib.apk/AndroidManifest.xml" };
|
||||
SourceLogger loggerA(sourceA);
|
||||
SourceLogger loggerB(sourceB);
|
||||
|
||||
ManifestMerger merger({});
|
||||
EXPECT_TRUE(merger.setAppManifest(sourceA, u"com.android.example",
|
||||
xml::inflate(&inA, &loggerA)));
|
||||
EXPECT_FALSE(merger.mergeLibraryManifest(sourceB, u"com.android.library",
|
||||
xml::inflate(&inB, &loggerB)));
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
@@ -1,84 +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 "AppInfo.h"
|
||||
#include "Logger.h"
|
||||
#include "ManifestParser.h"
|
||||
#include "Source.h"
|
||||
#include "XmlPullParser.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
bool ManifestParser::parse(const Source& source, std::shared_ptr<XmlPullParser> parser,
|
||||
AppInfo* outInfo) {
|
||||
SourceLogger logger = { source };
|
||||
|
||||
int depth = 0;
|
||||
while (XmlPullParser::isGoodEvent(parser->next())) {
|
||||
XmlPullParser::Event event = parser->getEvent();
|
||||
if (event == XmlPullParser::Event::kEndElement) {
|
||||
depth--;
|
||||
continue;
|
||||
} else if (event != XmlPullParser::Event::kStartElement) {
|
||||
continue;
|
||||
}
|
||||
|
||||
depth++;
|
||||
|
||||
const std::u16string& element = parser->getElementName();
|
||||
if (depth == 1) {
|
||||
if (element == u"manifest") {
|
||||
if (!parseManifest(logger, parser, outInfo)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
logger.error()
|
||||
<< "unexpected top-level element '"
|
||||
<< element
|
||||
<< "'."
|
||||
<< std::endl;
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
XmlPullParser::skipCurrentElement(parser.get());
|
||||
}
|
||||
}
|
||||
|
||||
if (parser->getEvent() == XmlPullParser::Event::kBadDocument) {
|
||||
logger.error(parser->getLineNumber())
|
||||
<< "failed to parse manifest: "
|
||||
<< parser->getLastError()
|
||||
<< "."
|
||||
<< std::endl;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ManifestParser::parseManifest(SourceLogger& logger, std::shared_ptr<XmlPullParser> parser,
|
||||
AppInfo* outInfo) {
|
||||
auto attrIter = parser->findAttribute(u"", u"package");
|
||||
if (attrIter == parser->endAttributes() || attrIter->value.empty()) {
|
||||
logger.error() << "no 'package' attribute found for element <manifest>." << std::endl;
|
||||
return false;
|
||||
}
|
||||
outInfo->package = attrIter->value;
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
@@ -1,42 +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 "AppInfo.h"
|
||||
#include "ManifestParser.h"
|
||||
#include "SourceXmlPullParser.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
TEST(ManifestParserTest, FindPackage) {
|
||||
std::stringstream input;
|
||||
input << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
|
||||
"<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
|
||||
"package=\"android\">\n"
|
||||
"</manifest>\n";
|
||||
|
||||
ManifestParser parser;
|
||||
AppInfo info;
|
||||
std::shared_ptr<XmlPullParser> xmlParser = std::make_shared<SourceXmlPullParser>(input);
|
||||
ASSERT_TRUE(parser.parse(Source{ "AndroidManifest.xml" }, xmlParser, &info));
|
||||
|
||||
EXPECT_EQ(std::u16string(u"android"), info.package);
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
@@ -16,9 +16,9 @@
|
||||
|
||||
#include "Logger.h"
|
||||
#include "ManifestValidator.h"
|
||||
#include "Maybe.h"
|
||||
#include "util/Maybe.h"
|
||||
#include "Source.h"
|
||||
#include "Util.h"
|
||||
#include "util/Util.h"
|
||||
|
||||
#include <androidfw/ResourceTypes.h>
|
||||
|
||||
|
||||
@@ -18,9 +18,9 @@
|
||||
#define AAPT_MANIFEST_VALIDATOR_H
|
||||
|
||||
#include "Logger.h"
|
||||
#include "Maybe.h"
|
||||
#include "util/Maybe.h"
|
||||
#include "Source.h"
|
||||
#include "StringPiece.h"
|
||||
#include "util/StringPiece.h"
|
||||
|
||||
#include <androidfw/ResourceTypes.h>
|
||||
|
||||
|
||||
@@ -1,93 +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_MOCK_RESOLVER_H
|
||||
#define AAPT_MOCK_RESOLVER_H
|
||||
|
||||
#include "Maybe.h"
|
||||
#include "Resolver.h"
|
||||
#include "Resource.h"
|
||||
#include "ResourceTable.h"
|
||||
#include "ResourceTableResolver.h"
|
||||
#include "ResourceValues.h"
|
||||
#include "StringPiece.h"
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
struct MockResolver : public IResolver {
|
||||
MockResolver(const std::shared_ptr<ResourceTable>& table,
|
||||
const std::map<ResourceName, ResourceId>& items) :
|
||||
mResolver(std::make_shared<ResourceTableResolver>(
|
||||
table, std::vector<std::shared_ptr<const android::AssetManager>>())),
|
||||
mAttr(false, android::ResTable_map::TYPE_ANY), mItems(items) {
|
||||
}
|
||||
|
||||
virtual Maybe<ResourceId> findId(const ResourceName& name) override {
|
||||
Maybe<ResourceId> result = mResolver->findId(name);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const auto iter = mItems.find(name);
|
||||
if (iter != mItems.end()) {
|
||||
return iter->second;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
virtual Maybe<Entry> findAttribute(const ResourceName& name) override {
|
||||
Maybe<Entry> tableResult = mResolver->findAttribute(name);
|
||||
if (tableResult) {
|
||||
return tableResult;
|
||||
}
|
||||
|
||||
Maybe<ResourceId> result = findId(name);
|
||||
if (result) {
|
||||
if (name.type == ResourceType::kAttr) {
|
||||
return Entry{ result.value(), &mAttr };
|
||||
} else {
|
||||
return Entry{ result.value() };
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
virtual Maybe<ResourceName> findName(ResourceId resId) override {
|
||||
Maybe<ResourceName> result = mResolver->findName(resId);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
|
||||
for (auto& p : mItems) {
|
||||
if (p.second == resId) {
|
||||
return p.first;
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<ResourceTableResolver> mResolver;
|
||||
Attribute mAttr;
|
||||
std::map<ResourceName, ResourceId> mItems;
|
||||
};
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
#endif // AAPT_MOCK_RESOLVER_H
|
||||
@@ -17,19 +17,63 @@
|
||||
#ifndef AAPT_NAME_MANGLER_H
|
||||
#define AAPT_NAME_MANGLER_H
|
||||
|
||||
#include "Resource.h"
|
||||
|
||||
#include "util/Maybe.h"
|
||||
|
||||
#include <set>
|
||||
#include <string>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
struct NameMangler {
|
||||
struct NameManglerPolicy {
|
||||
/**
|
||||
* Mangles the name in `outName` with the `package` and stores the mangled
|
||||
* result in `outName`. The mangled name should contain symbols that are
|
||||
* illegal to define in XML, so that there will never be name mangling
|
||||
* collisions.
|
||||
* Represents the package we are trying to build. References pointing
|
||||
* to this package are not mangled, and mangled references inherit this package name.
|
||||
*/
|
||||
static void mangle(const std::u16string& package, std::u16string* outName) {
|
||||
*outName = package + u"$" + *outName;
|
||||
std::u16string targetPackageName;
|
||||
|
||||
/**
|
||||
* We must know which references to mangle, and which to keep (android vs. com.android.support).
|
||||
*/
|
||||
std::set<std::u16string> packagesToMangle;
|
||||
};
|
||||
|
||||
class NameMangler {
|
||||
private:
|
||||
NameManglerPolicy mPolicy;
|
||||
|
||||
public:
|
||||
NameMangler(NameManglerPolicy policy) : mPolicy(policy) {
|
||||
}
|
||||
|
||||
Maybe<ResourceName> mangleName(const ResourceName& name) {
|
||||
if (mPolicy.targetPackageName == name.package ||
|
||||
mPolicy.packagesToMangle.count(name.package) == 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return ResourceName{
|
||||
mPolicy.targetPackageName,
|
||||
name.type,
|
||||
mangleEntry(name.package, name.entry)
|
||||
};
|
||||
}
|
||||
|
||||
bool shouldMangle(const std::u16string& package) {
|
||||
if (package.empty() || mPolicy.targetPackageName == package) {
|
||||
return false;
|
||||
}
|
||||
return mPolicy.packagesToMangle.count(package) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a mangled name that is a combination of `name` and `package`.
|
||||
* The mangled name should contain symbols that are illegal to define in XML,
|
||||
* so that there will never be name mangling collisions.
|
||||
*/
|
||||
static std::u16string mangleEntry(const std::u16string& package, const std::u16string& name) {
|
||||
return package + u"$" + name;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -15,9 +15,10 @@
|
||||
*/
|
||||
|
||||
#include "ProguardRules.h"
|
||||
#include "Util.h"
|
||||
#include "XmlDom.h"
|
||||
|
||||
#include "util/Util.h"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
@@ -61,11 +62,11 @@ public:
|
||||
|
||||
protected:
|
||||
void addClass(size_t lineNumber, const std::u16string& className) {
|
||||
mKeepSet->addClass(mSource.line(lineNumber), className);
|
||||
mKeepSet->addClass(Source(mSource.path, lineNumber), className);
|
||||
}
|
||||
|
||||
void addMethod(size_t lineNumber, const std::u16string& methodName) {
|
||||
mKeepSet->addMethod(mSource.line(lineNumber), methodName);
|
||||
mKeepSet->addMethod(Source(mSource.path, lineNumber), methodName);
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -186,30 +187,36 @@ struct ManifestVisitor : public BaseVisitor {
|
||||
std::u16string mPackage;
|
||||
};
|
||||
|
||||
bool collectProguardRulesForManifest(const Source& source, xml::Node* node, KeepSet* keepSet) {
|
||||
bool collectProguardRulesForManifest(const Source& source, XmlResource* res, KeepSet* keepSet) {
|
||||
ManifestVisitor visitor(source, keepSet);
|
||||
node->accept(&visitor);
|
||||
return true;
|
||||
if (res->root) {
|
||||
res->root->accept(&visitor);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool collectProguardRules(ResourceType type, const Source& source, xml::Node* node,
|
||||
KeepSet* keepSet) {
|
||||
switch (type) {
|
||||
bool collectProguardRules(const Source& source, XmlResource* res, KeepSet* keepSet) {
|
||||
if (!res->root) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (res->file.name.type) {
|
||||
case ResourceType::kLayout: {
|
||||
LayoutVisitor visitor(source, keepSet);
|
||||
node->accept(&visitor);
|
||||
res->root->accept(&visitor);
|
||||
break;
|
||||
}
|
||||
|
||||
case ResourceType::kXml: {
|
||||
XmlResourceVisitor visitor(source, keepSet);
|
||||
node->accept(&visitor);
|
||||
res->root->accept(&visitor);
|
||||
break;
|
||||
}
|
||||
|
||||
case ResourceType::kTransition: {
|
||||
TransitionVisitor visitor(source, keepSet);
|
||||
node->accept(&visitor);
|
||||
res->root->accept(&visitor);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -221,14 +228,14 @@ bool collectProguardRules(ResourceType type, const Source& source, xml::Node* no
|
||||
|
||||
bool writeKeepSet(std::ostream* out, const KeepSet& keepSet) {
|
||||
for (const auto& entry : keepSet.mKeepSet) {
|
||||
for (const SourceLine& source : entry.second) {
|
||||
for (const Source& source : entry.second) {
|
||||
*out << "// Referenced at " << source << "\n";
|
||||
}
|
||||
*out << "-keep class " << entry.first << " { <init>(...); }\n" << std::endl;
|
||||
}
|
||||
|
||||
for (const auto& entry : keepSet.mKeepMethodSet) {
|
||||
for (const SourceLine& source : entry.second) {
|
||||
for (const Source& source : entry.second) {
|
||||
*out << "// Referenced at " << source << "\n";
|
||||
}
|
||||
*out << "-keepclassmembers class * { *** " << entry.first << "(...); }\n" << std::endl;
|
||||
|
||||
@@ -19,7 +19,8 @@
|
||||
|
||||
#include "Resource.h"
|
||||
#include "Source.h"
|
||||
#include "XmlDom.h"
|
||||
|
||||
#include "process/IResourceTableConsumer.h"
|
||||
|
||||
#include <map>
|
||||
#include <ostream>
|
||||
@@ -31,24 +32,23 @@ namespace proguard {
|
||||
|
||||
class KeepSet {
|
||||
public:
|
||||
inline void addClass(const SourceLine& source, const std::u16string& className) {
|
||||
inline void addClass(const Source& source, const std::u16string& className) {
|
||||
mKeepSet[className].insert(source);
|
||||
}
|
||||
|
||||
inline void addMethod(const SourceLine& source, const std::u16string& methodName) {
|
||||
inline void addMethod(const Source& source, const std::u16string& methodName) {
|
||||
mKeepMethodSet[methodName].insert(source);
|
||||
}
|
||||
|
||||
private:
|
||||
friend bool writeKeepSet(std::ostream* out, const KeepSet& keepSet);
|
||||
|
||||
std::map<std::u16string, std::set<SourceLine>> mKeepSet;
|
||||
std::map<std::u16string, std::set<SourceLine>> mKeepMethodSet;
|
||||
std::map<std::u16string, std::set<Source>> mKeepSet;
|
||||
std::map<std::u16string, std::set<Source>> mKeepMethodSet;
|
||||
};
|
||||
|
||||
bool collectProguardRulesForManifest(const Source& source, xml::Node* node, KeepSet* keepSet);
|
||||
bool collectProguardRules(ResourceType type, const Source& source, xml::Node* node,
|
||||
KeepSet* keepSet);
|
||||
bool collectProguardRulesForManifest(const Source& source, XmlResource* res, KeepSet* keepSet);
|
||||
bool collectProguardRules(const Source& source, XmlResource* res, KeepSet* keepSet);
|
||||
|
||||
bool writeKeepSet(std::ostream* out, const KeepSet& keepSet);
|
||||
|
||||
|
||||
@@ -1,75 +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_RESOLVER_H
|
||||
#define AAPT_RESOLVER_H
|
||||
|
||||
#include "Maybe.h"
|
||||
#include "Resource.h"
|
||||
#include "ResourceValues.h"
|
||||
|
||||
#include <androidfw/ResourceTypes.h>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
/**
|
||||
* Resolves symbolic references (package:type/entry) into resource IDs/objects.
|
||||
*/
|
||||
class IResolver {
|
||||
public:
|
||||
virtual ~IResolver() {};
|
||||
|
||||
/**
|
||||
* Holds the result of a resource name lookup.
|
||||
*/
|
||||
struct Entry {
|
||||
/**
|
||||
* The ID of the resource. ResourceId::isValid() may
|
||||
* return false if the resource has not been assigned
|
||||
* an ID.
|
||||
*/
|
||||
ResourceId id;
|
||||
|
||||
/**
|
||||
* If the resource is an attribute, this will point
|
||||
* to a valid Attribute object, or else it will be
|
||||
* nullptr.
|
||||
*/
|
||||
const Attribute* attr;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a ResourceID if the name is found. The ResourceID
|
||||
* may not be valid if the resource was not assigned an ID.
|
||||
*/
|
||||
virtual Maybe<ResourceId> findId(const ResourceName& name) = 0;
|
||||
|
||||
/**
|
||||
* Returns an Entry if the name is found. Entry::attr
|
||||
* may be nullptr if the resource is not an attribute.
|
||||
*/
|
||||
virtual Maybe<Entry> findAttribute(const ResourceName& name) = 0;
|
||||
|
||||
/**
|
||||
* Find a resource by ID. Resolvers may contain resources without
|
||||
* resource IDs assigned to them.
|
||||
*/
|
||||
virtual Maybe<ResourceName> findName(ResourceId resId) = 0;
|
||||
};
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
#endif // AAPT_RESOLVER_H
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
#include "Resource.h"
|
||||
#include "StringPiece.h"
|
||||
#include "util/StringPiece.h"
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
@@ -28,7 +28,7 @@ StringPiece16 toString(ResourceType type) {
|
||||
case ResourceType::kAnimator: return u"animator";
|
||||
case ResourceType::kArray: return u"array";
|
||||
case ResourceType::kAttr: return u"attr";
|
||||
case ResourceType::kAttrPrivate: return u"attr";
|
||||
case ResourceType::kAttrPrivate: return u"^attr-private";
|
||||
case ResourceType::kBool: return u"bool";
|
||||
case ResourceType::kColor: return u"color";
|
||||
case ResourceType::kDimen: return u"dimen";
|
||||
|
||||
@@ -17,12 +17,16 @@
|
||||
#ifndef AAPT_RESOURCE_H
|
||||
#define AAPT_RESOURCE_H
|
||||
|
||||
#include "StringPiece.h"
|
||||
#include "ConfigDescription.h"
|
||||
#include "Source.h"
|
||||
|
||||
#include "util/StringPiece.h"
|
||||
|
||||
#include <iomanip>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
@@ -78,6 +82,7 @@ struct ResourceName {
|
||||
bool operator<(const ResourceName& rhs) const;
|
||||
bool operator==(const ResourceName& rhs) const;
|
||||
bool operator!=(const ResourceName& rhs) const;
|
||||
std::u16string toString() const;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -125,7 +130,7 @@ struct ResourceId {
|
||||
ResourceId();
|
||||
ResourceId(const ResourceId& rhs);
|
||||
ResourceId(uint32_t resId);
|
||||
ResourceId(size_t p, size_t t, size_t e);
|
||||
ResourceId(uint8_t p, uint8_t t, uint16_t e);
|
||||
|
||||
bool isValid() const;
|
||||
uint8_t packageId() const;
|
||||
@@ -135,6 +140,29 @@ struct ResourceId {
|
||||
bool operator==(const ResourceId& rhs) const;
|
||||
};
|
||||
|
||||
struct SourcedResourceName {
|
||||
ResourceName name;
|
||||
size_t line;
|
||||
|
||||
inline bool operator==(const SourcedResourceName& rhs) const {
|
||||
return name == rhs.name && line == rhs.line;
|
||||
}
|
||||
};
|
||||
|
||||
struct ResourceFile {
|
||||
// Name
|
||||
ResourceName name;
|
||||
|
||||
// Configuration
|
||||
ConfigDescription config;
|
||||
|
||||
// Source
|
||||
Source source;
|
||||
|
||||
// Exported symbols
|
||||
std::vector<SourcedResourceName> exportedSymbols;
|
||||
};
|
||||
|
||||
//
|
||||
// ResourceId implementation.
|
||||
//
|
||||
@@ -148,17 +176,7 @@ inline ResourceId::ResourceId(const ResourceId& rhs) : id(rhs.id) {
|
||||
inline ResourceId::ResourceId(uint32_t resId) : id(resId) {
|
||||
}
|
||||
|
||||
inline ResourceId::ResourceId(size_t p, size_t t, size_t e) : id(0) {
|
||||
if (p > std::numeric_limits<uint8_t>::max() ||
|
||||
t > std::numeric_limits<uint8_t>::max() ||
|
||||
e > std::numeric_limits<uint16_t>::max()) {
|
||||
// This will leave the ResourceId in an invalid state.
|
||||
return;
|
||||
}
|
||||
|
||||
id = (static_cast<uint8_t>(p) << 24) |
|
||||
(static_cast<uint8_t>(t) << 16) |
|
||||
static_cast<uint16_t>(e);
|
||||
inline ResourceId::ResourceId(uint8_t p, uint8_t t, uint16_t e) : id((p << 24) | (t << 16) | e) {
|
||||
}
|
||||
|
||||
inline bool ResourceId::isValid() const {
|
||||
@@ -217,6 +235,10 @@ inline bool ResourceName::operator<(const ResourceName& rhs) const {
|
||||
< std::tie(rhs.package, rhs.type, rhs.entry);
|
||||
}
|
||||
|
||||
inline bool operator<(const ResourceName& lhs, const ResourceNameRef& b) {
|
||||
return ResourceNameRef(lhs) < b;
|
||||
}
|
||||
|
||||
inline bool ResourceName::operator==(const ResourceName& rhs) const {
|
||||
return std::tie(package, type, entry)
|
||||
== std::tie(rhs.package, rhs.type, rhs.entry);
|
||||
@@ -227,6 +249,18 @@ inline bool ResourceName::operator!=(const ResourceName& rhs) const {
|
||||
!= std::tie(rhs.package, rhs.type, rhs.entry);
|
||||
}
|
||||
|
||||
inline bool operator!=(const ResourceName& lhs, const ResourceNameRef& rhs) {
|
||||
return ResourceNameRef(lhs) != rhs;
|
||||
}
|
||||
|
||||
inline std::u16string ResourceName::toString() const {
|
||||
std::u16string result;
|
||||
if (!package.empty()) {
|
||||
result = package + u":";
|
||||
}
|
||||
return result + aapt::toString(type).toString() + u"/" + entry;
|
||||
}
|
||||
|
||||
inline ::std::ostream& operator<<(::std::ostream& out, const ResourceName& name) {
|
||||
if (!name.package.empty()) {
|
||||
out << name.package << ":";
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -18,14 +18,15 @@
|
||||
#define AAPT_RESOURCE_PARSER_H
|
||||
|
||||
#include "ConfigDescription.h"
|
||||
#include "Logger.h"
|
||||
#include "Diagnostics.h"
|
||||
#include "ResourceTable.h"
|
||||
#include "ResourceValues.h"
|
||||
#include "StringPiece.h"
|
||||
#include "StringPool.h"
|
||||
#include "XmlPullParser.h"
|
||||
|
||||
#include <istream>
|
||||
#include "util/Maybe.h"
|
||||
#include "util/StringPiece.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace aapt {
|
||||
@@ -35,118 +36,12 @@ namespace aapt {
|
||||
*/
|
||||
class ResourceParser {
|
||||
public:
|
||||
/*
|
||||
* Extracts the package, type, and name from a string of the format:
|
||||
*
|
||||
* [package:]type/name
|
||||
*
|
||||
* where the package can be empty. Validation must be performed on each
|
||||
* individual extracted piece to verify that the pieces are valid.
|
||||
*/
|
||||
static void extractResourceName(const StringPiece16& str, StringPiece16* outPackage,
|
||||
StringPiece16* outType, StringPiece16* outEntry);
|
||||
|
||||
/*
|
||||
* Returns true if the string was parsed as a reference (@[+][package:]type/name), with
|
||||
* `outReference` set to the parsed reference.
|
||||
*
|
||||
* If '+' was present in the reference, `outCreate` is set to true.
|
||||
* If '*' was present in the reference, `outPrivate` is set to true.
|
||||
*/
|
||||
static bool tryParseReference(const StringPiece16& str, ResourceNameRef* outReference,
|
||||
bool* outCreate, bool* outPrivate);
|
||||
|
||||
/*
|
||||
* Returns true if the string was parsed as an attribute reference (?[package:]type/name),
|
||||
* with `outReference` set to the parsed reference.
|
||||
*/
|
||||
static bool tryParseAttributeReference(const StringPiece16& str,
|
||||
ResourceNameRef* outReference);
|
||||
|
||||
/*
|
||||
* Returns true if the string `str` was parsed as a valid reference to a style.
|
||||
* The format for a style parent is slightly more flexible than a normal reference:
|
||||
*
|
||||
* @[package:]style/<entry> or
|
||||
* ?[package:]style/<entry> or
|
||||
* <package>:[style/]<entry>
|
||||
*/
|
||||
static bool parseStyleParentReference(const StringPiece16& str, Reference* outReference,
|
||||
std::string* outError);
|
||||
|
||||
/*
|
||||
* Returns a Reference object if the string was parsed as a resource or attribute reference,
|
||||
* ( @[+][package:]type/name | ?[package:]type/name ) setting outCreate to true if
|
||||
* the '+' was present in the string.
|
||||
*/
|
||||
static std::unique_ptr<Reference> tryParseReference(const StringPiece16& str,
|
||||
bool* outCreate);
|
||||
|
||||
/*
|
||||
* Returns a BinaryPrimitve object representing @null or @empty if the string was parsed
|
||||
* as one.
|
||||
*/
|
||||
static std::unique_ptr<BinaryPrimitive> tryParseNullOrEmpty(const StringPiece16& str);
|
||||
|
||||
/*
|
||||
* Returns a BinaryPrimitve object representing a color if the string was parsed
|
||||
* as one.
|
||||
*/
|
||||
static std::unique_ptr<BinaryPrimitive> tryParseColor(const StringPiece16& str);
|
||||
|
||||
/*
|
||||
* Returns a BinaryPrimitve object representing a boolean if the string was parsed
|
||||
* as one.
|
||||
*/
|
||||
static std::unique_ptr<BinaryPrimitive> tryParseBool(const StringPiece16& str);
|
||||
|
||||
/*
|
||||
* Returns a BinaryPrimitve object representing an integer if the string was parsed
|
||||
* as one.
|
||||
*/
|
||||
static std::unique_ptr<BinaryPrimitive> tryParseInt(const StringPiece16& str);
|
||||
|
||||
/*
|
||||
* Returns a BinaryPrimitve object representing a floating point number
|
||||
* (float, dimension, etc) if the string was parsed as one.
|
||||
*/
|
||||
static std::unique_ptr<BinaryPrimitive> tryParseFloat(const StringPiece16& str);
|
||||
|
||||
/*
|
||||
* Returns a BinaryPrimitve object representing an enum symbol if the string was parsed
|
||||
* as one.
|
||||
*/
|
||||
static std::unique_ptr<BinaryPrimitive> tryParseEnumSymbol(const Attribute& enumAttr,
|
||||
const StringPiece16& str);
|
||||
|
||||
/*
|
||||
* Returns a BinaryPrimitve object representing a flag symbol if the string was parsed
|
||||
* as one.
|
||||
*/
|
||||
static std::unique_ptr<BinaryPrimitive> tryParseFlagSymbol(const Attribute& enumAttr,
|
||||
const StringPiece16& str);
|
||||
/*
|
||||
* Try to convert a string to an Item for the given attribute. The attribute will
|
||||
* restrict what values the string can be converted to.
|
||||
* The callback function onCreateReference is called when the parsed item is a
|
||||
* reference to an ID that must be created (@+id/foo).
|
||||
*/
|
||||
static std::unique_ptr<Item> parseItemForAttribute(
|
||||
const StringPiece16& value, const Attribute& attr,
|
||||
std::function<void(const ResourceName&)> onCreateReference = {});
|
||||
|
||||
static std::unique_ptr<Item> parseItemForAttribute(
|
||||
const StringPiece16& value, uint32_t typeMask,
|
||||
std::function<void(const ResourceName&)> onCreateReference = {});
|
||||
|
||||
static uint32_t androidTypeToAttributeTypeMask(uint16_t type);
|
||||
|
||||
ResourceParser(const std::shared_ptr<ResourceTable>& table, const Source& source,
|
||||
const ConfigDescription& config, const std::shared_ptr<XmlPullParser>& parser);
|
||||
ResourceParser(IDiagnostics* diag, ResourceTable* table, const Source& source,
|
||||
const ConfigDescription& config);
|
||||
|
||||
ResourceParser(const ResourceParser&) = delete; // No copy.
|
||||
|
||||
bool parse();
|
||||
bool parse(XmlPullParser* parser);
|
||||
|
||||
private:
|
||||
/*
|
||||
@@ -155,7 +50,7 @@ private:
|
||||
* contains the escaped and whitespace trimmed text, while `outRawString`
|
||||
* contains the unescaped text. Returns true on success.
|
||||
*/
|
||||
bool flattenXmlSubtree(XmlPullParser* parser, std::u16string* outRawString,\
|
||||
bool flattenXmlSubtree(XmlPullParser* parser, std::u16string* outRawString,
|
||||
StyleString* outStyleString);
|
||||
|
||||
/*
|
||||
@@ -175,19 +70,17 @@ private:
|
||||
std::unique_ptr<Attribute> parseAttrImpl(XmlPullParser* parser,
|
||||
ResourceName* resourceName,
|
||||
bool weak);
|
||||
bool parseEnumOrFlagItem(XmlPullParser* parser, const StringPiece16& tag,
|
||||
Attribute::Symbol* outSymbol);
|
||||
Maybe<Attribute::Symbol> parseEnumOrFlagItem(XmlPullParser* parser, const StringPiece16& tag);
|
||||
bool parseStyle(XmlPullParser* parser, const ResourceNameRef& resourceName);
|
||||
bool parseUntypedItem(XmlPullParser* parser, Style& style);
|
||||
bool parseStyleItem(XmlPullParser* parser, Style* style);
|
||||
bool parseDeclareStyleable(XmlPullParser* parser, const ResourceNameRef& resourceName);
|
||||
bool parseArray(XmlPullParser* parser, const ResourceNameRef& resourceName, uint32_t typeMask);
|
||||
bool parsePlural(XmlPullParser* parser, const ResourceNameRef& resourceName);
|
||||
|
||||
std::shared_ptr<ResourceTable> mTable;
|
||||
IDiagnostics* mDiag;
|
||||
ResourceTable* mTable;
|
||||
Source mSource;
|
||||
ConfigDescription mConfig;
|
||||
SourceLogger mLogger;
|
||||
std::shared_ptr<XmlPullParser> mParser;
|
||||
};
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
@@ -16,8 +16,11 @@
|
||||
|
||||
#include "ResourceParser.h"
|
||||
#include "ResourceTable.h"
|
||||
#include "ResourceUtils.h"
|
||||
#include "ResourceValues.h"
|
||||
#include "SourceXmlPullParser.h"
|
||||
#include "XmlPullParser.h"
|
||||
|
||||
#include "test/Context.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <sstream>
|
||||
@@ -27,156 +30,41 @@ namespace aapt {
|
||||
|
||||
constexpr const char* kXmlPreamble = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
|
||||
|
||||
TEST(ResourceParserReferenceTest, ParseReferenceWithNoPackage) {
|
||||
ResourceNameRef expected = { {}, ResourceType::kColor, u"foo" };
|
||||
ResourceNameRef actual;
|
||||
bool create = false;
|
||||
bool privateRef = false;
|
||||
EXPECT_TRUE(ResourceParser::tryParseReference(u"@color/foo", &actual, &create, &privateRef));
|
||||
EXPECT_EQ(expected, actual);
|
||||
EXPECT_FALSE(create);
|
||||
EXPECT_FALSE(privateRef);
|
||||
}
|
||||
|
||||
TEST(ResourceParserReferenceTest, ParseReferenceWithPackage) {
|
||||
ResourceNameRef expected = { u"android", ResourceType::kColor, u"foo" };
|
||||
ResourceNameRef actual;
|
||||
bool create = false;
|
||||
bool privateRef = false;
|
||||
EXPECT_TRUE(ResourceParser::tryParseReference(u"@android:color/foo", &actual, &create,
|
||||
&privateRef));
|
||||
EXPECT_EQ(expected, actual);
|
||||
EXPECT_FALSE(create);
|
||||
EXPECT_FALSE(privateRef);
|
||||
}
|
||||
|
||||
TEST(ResourceParserReferenceTest, ParseReferenceWithSurroundingWhitespace) {
|
||||
ResourceNameRef expected = { u"android", ResourceType::kColor, u"foo" };
|
||||
ResourceNameRef actual;
|
||||
bool create = false;
|
||||
bool privateRef = false;
|
||||
EXPECT_TRUE(ResourceParser::tryParseReference(u"\t @android:color/foo\n \n\t", &actual,
|
||||
&create, &privateRef));
|
||||
EXPECT_EQ(expected, actual);
|
||||
EXPECT_FALSE(create);
|
||||
EXPECT_FALSE(privateRef);
|
||||
}
|
||||
|
||||
TEST(ResourceParserReferenceTest, ParseAutoCreateIdReference) {
|
||||
ResourceNameRef expected = { u"android", ResourceType::kId, u"foo" };
|
||||
ResourceNameRef actual;
|
||||
bool create = false;
|
||||
bool privateRef = false;
|
||||
EXPECT_TRUE(ResourceParser::tryParseReference(u"@+android:id/foo", &actual, &create,
|
||||
&privateRef));
|
||||
EXPECT_EQ(expected, actual);
|
||||
EXPECT_TRUE(create);
|
||||
EXPECT_FALSE(privateRef);
|
||||
}
|
||||
|
||||
TEST(ResourceParserReferenceTest, ParsePrivateReference) {
|
||||
ResourceNameRef expected = { u"android", ResourceType::kId, u"foo" };
|
||||
ResourceNameRef actual;
|
||||
bool create = false;
|
||||
bool privateRef = false;
|
||||
EXPECT_TRUE(ResourceParser::tryParseReference(u"@*android:id/foo", &actual, &create,
|
||||
&privateRef));
|
||||
EXPECT_EQ(expected, actual);
|
||||
EXPECT_FALSE(create);
|
||||
EXPECT_TRUE(privateRef);
|
||||
}
|
||||
|
||||
TEST(ResourceParserReferenceTest, FailToParseAutoCreateNonIdReference) {
|
||||
bool create = false;
|
||||
bool privateRef = false;
|
||||
ResourceNameRef actual;
|
||||
EXPECT_FALSE(ResourceParser::tryParseReference(u"@+android:color/foo", &actual, &create,
|
||||
&privateRef));
|
||||
}
|
||||
|
||||
TEST(ResourceParserReferenceTest, ParseStyleParentReference) {
|
||||
Reference ref;
|
||||
std::string errStr;
|
||||
EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"@android:style/foo", &ref, &errStr));
|
||||
EXPECT_EQ(ref.name, (ResourceName{ u"android", ResourceType::kStyle, u"foo" }));
|
||||
|
||||
EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"@style/foo", &ref, &errStr));
|
||||
EXPECT_EQ(ref.name, (ResourceName{ {}, ResourceType::kStyle, u"foo" }));
|
||||
|
||||
EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"?android:style/foo", &ref, &errStr));
|
||||
EXPECT_EQ(ref.name, (ResourceName{ u"android", ResourceType::kStyle, u"foo" }));
|
||||
|
||||
EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"?style/foo", &ref, &errStr));
|
||||
EXPECT_EQ(ref.name, (ResourceName{ {}, ResourceType::kStyle, u"foo" }));
|
||||
|
||||
EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"android:style/foo", &ref, &errStr));
|
||||
EXPECT_EQ(ref.name, (ResourceName{ u"android", ResourceType::kStyle, u"foo" }));
|
||||
|
||||
EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"android:foo", &ref, &errStr));
|
||||
EXPECT_EQ(ref.name, (ResourceName{ u"android", ResourceType::kStyle, u"foo" }));
|
||||
|
||||
EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"foo", &ref, &errStr));
|
||||
EXPECT_EQ(ref.name, (ResourceName{ {}, ResourceType::kStyle, u"foo" }));
|
||||
TEST(ResourceParserSingleTest, FailToParseWithNoRootResourcesElement) {
|
||||
std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
|
||||
std::stringstream input(kXmlPreamble);
|
||||
input << "<attr name=\"foo\"/>" << std::endl;
|
||||
ResourceTable table;
|
||||
ResourceParser parser(context->getDiagnostics(), &table, Source{ "test" }, {});
|
||||
XmlPullParser xmlParser(input);
|
||||
ASSERT_FALSE(parser.parse(&xmlParser));
|
||||
}
|
||||
|
||||
struct ResourceParserTest : public ::testing::Test {
|
||||
virtual void SetUp() override {
|
||||
mTable = std::make_shared<ResourceTable>();
|
||||
mTable->setPackage(u"android");
|
||||
ResourceTable mTable;
|
||||
std::unique_ptr<IAaptContext> mContext;
|
||||
|
||||
void SetUp() override {
|
||||
mContext = test::ContextBuilder().build();
|
||||
}
|
||||
|
||||
::testing::AssertionResult testParse(const StringPiece& str) {
|
||||
std::stringstream input(kXmlPreamble);
|
||||
input << "<resources>\n" << str << "\n</resources>" << std::endl;
|
||||
ResourceParser parser(mTable, Source{ "test" }, {},
|
||||
std::make_shared<SourceXmlPullParser>(input));
|
||||
if (parser.parse()) {
|
||||
ResourceParser parser(mContext->getDiagnostics(), &mTable, Source{ "test" }, {});
|
||||
XmlPullParser xmlParser(input);
|
||||
if (parser.parse(&xmlParser)) {
|
||||
return ::testing::AssertionSuccess();
|
||||
}
|
||||
return ::testing::AssertionFailure();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
const T* findResource(const ResourceNameRef& name, const ConfigDescription& config) {
|
||||
using std::begin;
|
||||
using std::end;
|
||||
|
||||
const ResourceTableType* type;
|
||||
const ResourceEntry* entry;
|
||||
std::tie(type, entry) = mTable->findResource(name);
|
||||
if (!type || !entry) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
for (const auto& configValue : entry->values) {
|
||||
if (configValue.config == config) {
|
||||
return dynamic_cast<const T*>(configValue.value.get());
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
const T* findResource(const ResourceNameRef& name) {
|
||||
return findResource<T>(name, {});
|
||||
}
|
||||
|
||||
std::shared_ptr<ResourceTable> mTable;
|
||||
};
|
||||
|
||||
TEST_F(ResourceParserTest, FailToParseWithNoRootResourcesElement) {
|
||||
std::stringstream input(kXmlPreamble);
|
||||
input << "<attr name=\"foo\"/>" << std::endl;
|
||||
ResourceParser parser(mTable, {}, {}, std::make_shared<SourceXmlPullParser>(input));
|
||||
ASSERT_FALSE(parser.parse());
|
||||
}
|
||||
|
||||
TEST_F(ResourceParserTest, ParseQuotedString) {
|
||||
std::string input = "<string name=\"foo\"> \" hey there \" </string>";
|
||||
ASSERT_TRUE(testParse(input));
|
||||
|
||||
const String* str = findResource<String>(ResourceName{
|
||||
u"android", ResourceType::kString, u"foo"});
|
||||
String* str = test::getValue<String>(&mTable, u"@string/foo");
|
||||
ASSERT_NE(nullptr, str);
|
||||
EXPECT_EQ(std::u16string(u" hey there "), *str->value);
|
||||
}
|
||||
@@ -185,12 +73,22 @@ TEST_F(ResourceParserTest, ParseEscapedString) {
|
||||
std::string input = "<string name=\"foo\">\\?123</string>";
|
||||
ASSERT_TRUE(testParse(input));
|
||||
|
||||
const String* str = findResource<String>(ResourceName{
|
||||
u"android", ResourceType::kString, u"foo" });
|
||||
String* str = test::getValue<String>(&mTable, u"@string/foo");
|
||||
ASSERT_NE(nullptr, str);
|
||||
EXPECT_EQ(std::u16string(u"?123"), *str->value);
|
||||
}
|
||||
|
||||
TEST_F(ResourceParserTest, IgnoreXliffTags) {
|
||||
std::string input = "<string name=\"foo\" \n"
|
||||
" xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\">\n"
|
||||
" There are <xliff:g id=\"count\">%1$d</xliff:g> apples</string>";
|
||||
ASSERT_TRUE(testParse(input));
|
||||
|
||||
String* str = test::getValue<String>(&mTable, u"@string/foo");
|
||||
ASSERT_NE(nullptr, str);
|
||||
EXPECT_EQ(StringPiece16(u"There are %1$d apples"), StringPiece16(*str->value));
|
||||
}
|
||||
|
||||
TEST_F(ResourceParserTest, ParseNull) {
|
||||
std::string input = "<integer name=\"foo\">@null</integer>";
|
||||
ASSERT_TRUE(testParse(input));
|
||||
@@ -199,8 +97,7 @@ TEST_F(ResourceParserTest, ParseNull) {
|
||||
// a non-existing value, and this causes problems in styles when trying to resolve
|
||||
// an attribute. Null values must be encoded as android::Res_value::TYPE_REFERENCE
|
||||
// with a data value of 0.
|
||||
const BinaryPrimitive* integer = findResource<BinaryPrimitive>(ResourceName{
|
||||
u"android", ResourceType::kInteger, u"foo" });
|
||||
BinaryPrimitive* integer = test::getValue<BinaryPrimitive>(&mTable, u"@integer/foo");
|
||||
ASSERT_NE(nullptr, integer);
|
||||
EXPECT_EQ(uint16_t(android::Res_value::TYPE_REFERENCE), integer->value.dataType);
|
||||
EXPECT_EQ(0u, integer->value.data);
|
||||
@@ -210,8 +107,7 @@ TEST_F(ResourceParserTest, ParseEmpty) {
|
||||
std::string input = "<integer name=\"foo\">@empty</integer>";
|
||||
ASSERT_TRUE(testParse(input));
|
||||
|
||||
const BinaryPrimitive* integer = findResource<BinaryPrimitive>(ResourceName{
|
||||
u"android", ResourceType::kInteger, u"foo" });
|
||||
BinaryPrimitive* integer = test::getValue<BinaryPrimitive>(&mTable, u"@integer/foo");
|
||||
ASSERT_NE(nullptr, integer);
|
||||
EXPECT_EQ(uint16_t(android::Res_value::TYPE_NULL), integer->value.dataType);
|
||||
EXPECT_EQ(uint32_t(android::Res_value::DATA_NULL_EMPTY), integer->value.data);
|
||||
@@ -222,14 +118,12 @@ TEST_F(ResourceParserTest, ParseAttr) {
|
||||
"<attr name=\"bar\"/>";
|
||||
ASSERT_TRUE(testParse(input));
|
||||
|
||||
const Attribute* attr = findResource<Attribute>(ResourceName{
|
||||
u"android", ResourceType::kAttr, u"foo"});
|
||||
EXPECT_NE(nullptr, attr);
|
||||
Attribute* attr = test::getValue<Attribute>(&mTable, u"@attr/foo");
|
||||
ASSERT_NE(nullptr, attr);
|
||||
EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_STRING), attr->typeMask);
|
||||
|
||||
attr = findResource<Attribute>(ResourceName{
|
||||
u"android", ResourceType::kAttr, u"bar"});
|
||||
EXPECT_NE(nullptr, attr);
|
||||
attr = test::getValue<Attribute>(&mTable, u"@attr/bar");
|
||||
ASSERT_NE(nullptr, attr);
|
||||
EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_ANY), attr->typeMask);
|
||||
}
|
||||
|
||||
@@ -240,8 +134,7 @@ TEST_F(ResourceParserTest, ParseUseAndDeclOfAttr) {
|
||||
"<attr name=\"foo\" format=\"string\"/>";
|
||||
ASSERT_TRUE(testParse(input));
|
||||
|
||||
const Attribute* attr = findResource<Attribute>(ResourceName{
|
||||
u"android", ResourceType::kAttr, u"foo"});
|
||||
Attribute* attr = test::getValue<Attribute>(&mTable, u"@attr/foo");
|
||||
ASSERT_NE(nullptr, attr);
|
||||
EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_STRING), attr->typeMask);
|
||||
}
|
||||
@@ -255,8 +148,7 @@ TEST_F(ResourceParserTest, ParseDoubleUseOfAttr) {
|
||||
"</declare-styleable>";
|
||||
ASSERT_TRUE(testParse(input));
|
||||
|
||||
const Attribute* attr = findResource<Attribute>(ResourceName{
|
||||
u"android", ResourceType::kAttr, u"foo"});
|
||||
Attribute* attr = test::getValue<Attribute>(&mTable, u"@attr/foo");
|
||||
ASSERT_NE(nullptr, attr);
|
||||
EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_BOOLEAN), attr->typeMask);
|
||||
}
|
||||
@@ -269,19 +161,21 @@ TEST_F(ResourceParserTest, ParseEnumAttr) {
|
||||
"</attr>";
|
||||
ASSERT_TRUE(testParse(input));
|
||||
|
||||
const Attribute* enumAttr = findResource<Attribute>(ResourceName{
|
||||
u"android", ResourceType::kAttr, u"foo"});
|
||||
Attribute* enumAttr = test::getValue<Attribute>(&mTable, u"@attr/foo");
|
||||
ASSERT_NE(enumAttr, nullptr);
|
||||
EXPECT_EQ(enumAttr->typeMask, android::ResTable_map::TYPE_ENUM);
|
||||
ASSERT_EQ(enumAttr->symbols.size(), 3u);
|
||||
|
||||
EXPECT_EQ(enumAttr->symbols[0].symbol.name.entry, u"bar");
|
||||
AAPT_ASSERT_TRUE(enumAttr->symbols[0].symbol.name);
|
||||
EXPECT_EQ(enumAttr->symbols[0].symbol.name.value().entry, u"bar");
|
||||
EXPECT_EQ(enumAttr->symbols[0].value, 0u);
|
||||
|
||||
EXPECT_EQ(enumAttr->symbols[1].symbol.name.entry, u"bat");
|
||||
AAPT_ASSERT_TRUE(enumAttr->symbols[1].symbol.name);
|
||||
EXPECT_EQ(enumAttr->symbols[1].symbol.name.value().entry, u"bat");
|
||||
EXPECT_EQ(enumAttr->symbols[1].value, 1u);
|
||||
|
||||
EXPECT_EQ(enumAttr->symbols[2].symbol.name.entry, u"baz");
|
||||
AAPT_ASSERT_TRUE(enumAttr->symbols[2].symbol.name);
|
||||
EXPECT_EQ(enumAttr->symbols[2].symbol.name.value().entry, u"baz");
|
||||
EXPECT_EQ(enumAttr->symbols[2].value, 2u);
|
||||
}
|
||||
|
||||
@@ -293,23 +187,25 @@ TEST_F(ResourceParserTest, ParseFlagAttr) {
|
||||
"</attr>";
|
||||
ASSERT_TRUE(testParse(input));
|
||||
|
||||
const Attribute* flagAttr = findResource<Attribute>(ResourceName{
|
||||
u"android", ResourceType::kAttr, u"foo"});
|
||||
Attribute* flagAttr = test::getValue<Attribute>(&mTable, u"@attr/foo");
|
||||
ASSERT_NE(flagAttr, nullptr);
|
||||
EXPECT_EQ(flagAttr->typeMask, android::ResTable_map::TYPE_FLAGS);
|
||||
ASSERT_EQ(flagAttr->symbols.size(), 3u);
|
||||
|
||||
EXPECT_EQ(flagAttr->symbols[0].symbol.name.entry, u"bar");
|
||||
AAPT_ASSERT_TRUE(flagAttr->symbols[0].symbol.name);
|
||||
EXPECT_EQ(flagAttr->symbols[0].symbol.name.value().entry, u"bar");
|
||||
EXPECT_EQ(flagAttr->symbols[0].value, 0u);
|
||||
|
||||
EXPECT_EQ(flagAttr->symbols[1].symbol.name.entry, u"bat");
|
||||
AAPT_ASSERT_TRUE(flagAttr->symbols[1].symbol.name);
|
||||
EXPECT_EQ(flagAttr->symbols[1].symbol.name.value().entry, u"bat");
|
||||
EXPECT_EQ(flagAttr->symbols[1].value, 1u);
|
||||
|
||||
EXPECT_EQ(flagAttr->symbols[2].symbol.name.entry, u"baz");
|
||||
AAPT_ASSERT_TRUE(flagAttr->symbols[2].symbol.name);
|
||||
EXPECT_EQ(flagAttr->symbols[2].symbol.name.value().entry, u"baz");
|
||||
EXPECT_EQ(flagAttr->symbols[2].value, 2u);
|
||||
|
||||
std::unique_ptr<BinaryPrimitive> flagValue =
|
||||
ResourceParser::tryParseFlagSymbol(*flagAttr, u"baz|bat");
|
||||
std::unique_ptr<BinaryPrimitive> flagValue = ResourceUtils::tryParseFlagSymbol(flagAttr,
|
||||
u"baz|bat");
|
||||
ASSERT_NE(flagValue, nullptr);
|
||||
EXPECT_EQ(flagValue->value.data, 1u | 2u);
|
||||
}
|
||||
@@ -331,28 +227,32 @@ TEST_F(ResourceParserTest, ParseStyle) {
|
||||
"</style>";
|
||||
ASSERT_TRUE(testParse(input));
|
||||
|
||||
const Style* style = findResource<Style>(ResourceName{
|
||||
u"android", ResourceType::kStyle, u"foo"});
|
||||
Style* style = test::getValue<Style>(&mTable, u"@style/foo");
|
||||
ASSERT_NE(style, nullptr);
|
||||
EXPECT_EQ(ResourceNameRef(u"android", ResourceType::kStyle, u"fu"), style->parent.name);
|
||||
ASSERT_EQ(style->entries.size(), 3u);
|
||||
AAPT_ASSERT_TRUE(style->parent);
|
||||
AAPT_ASSERT_TRUE(style->parent.value().name);
|
||||
EXPECT_EQ(test::parseNameOrDie(u"@style/fu"), style->parent.value().name.value());
|
||||
ASSERT_EQ(3u, style->entries.size());
|
||||
|
||||
EXPECT_EQ(style->entries[0].key.name,
|
||||
(ResourceName{ u"android", ResourceType::kAttr, u"bar" }));
|
||||
EXPECT_EQ(style->entries[1].key.name,
|
||||
(ResourceName{ u"android", ResourceType::kAttr, u"bat" }));
|
||||
EXPECT_EQ(style->entries[2].key.name,
|
||||
(ResourceName{ u"android", ResourceType::kAttr, u"baz" }));
|
||||
AAPT_ASSERT_TRUE(style->entries[0].key.name);
|
||||
EXPECT_EQ(test::parseNameOrDie(u"@attr/bar"), style->entries[0].key.name.value());
|
||||
|
||||
AAPT_ASSERT_TRUE(style->entries[1].key.name);
|
||||
EXPECT_EQ(test::parseNameOrDie(u"@attr/bat"), style->entries[1].key.name.value());
|
||||
|
||||
AAPT_ASSERT_TRUE(style->entries[2].key.name);
|
||||
EXPECT_EQ(test::parseNameOrDie(u"@attr/baz"), style->entries[2].key.name.value());
|
||||
}
|
||||
|
||||
TEST_F(ResourceParserTest, ParseStyleWithShorthandParent) {
|
||||
std::string input = "<style name=\"foo\" parent=\"com.app:Theme\"/>";
|
||||
ASSERT_TRUE(testParse(input));
|
||||
|
||||
const Style* style = findResource<Style>(
|
||||
ResourceName{ u"android", ResourceType::kStyle, u"foo" });
|
||||
Style* style = test::getValue<Style>(&mTable, u"@style/foo");
|
||||
ASSERT_NE(style, nullptr);
|
||||
EXPECT_EQ(ResourceNameRef(u"com.app", ResourceType::kStyle, u"Theme"), style->parent.name);
|
||||
AAPT_ASSERT_TRUE(style->parent);
|
||||
AAPT_ASSERT_TRUE(style->parent.value().name);
|
||||
EXPECT_EQ(test::parseNameOrDie(u"@com.app:style/Theme"), style->parent.value().name.value());
|
||||
}
|
||||
|
||||
TEST_F(ResourceParserTest, ParseStyleWithPackageAliasedParent) {
|
||||
@@ -360,10 +260,11 @@ TEST_F(ResourceParserTest, ParseStyleWithPackageAliasedParent) {
|
||||
" name=\"foo\" parent=\"app:Theme\"/>";
|
||||
ASSERT_TRUE(testParse(input));
|
||||
|
||||
const Style* style = findResource<Style>(ResourceName{
|
||||
u"android", ResourceType::kStyle, u"foo" });
|
||||
Style* style = test::getValue<Style>(&mTable, u"@style/foo");
|
||||
ASSERT_NE(style, nullptr);
|
||||
EXPECT_EQ(ResourceNameRef(u"android", ResourceType::kStyle, u"Theme"), style->parent.name);
|
||||
AAPT_ASSERT_TRUE(style->parent);
|
||||
AAPT_ASSERT_TRUE(style->parent.value().name);
|
||||
EXPECT_EQ(test::parseNameOrDie(u"@android:style/Theme"), style->parent.value().name.value());
|
||||
}
|
||||
|
||||
TEST_F(ResourceParserTest, ParseStyleWithPackageAliasedItems) {
|
||||
@@ -373,22 +274,21 @@ TEST_F(ResourceParserTest, ParseStyleWithPackageAliasedItems) {
|
||||
"</style>";
|
||||
ASSERT_TRUE(testParse(input));
|
||||
|
||||
const Style* style = findResource<Style>(ResourceName{
|
||||
u"android", ResourceType::kStyle, u"foo" });
|
||||
Style* style = test::getValue<Style>(&mTable, u"@style/foo");
|
||||
ASSERT_NE(style, nullptr);
|
||||
ASSERT_EQ(1u, style->entries.size());
|
||||
EXPECT_EQ(ResourceNameRef(u"android", ResourceType::kAttr, u"bar"),
|
||||
style->entries[0].key.name);
|
||||
EXPECT_EQ(test::parseNameOrDie(u"@android:attr/bar"), style->entries[0].key.name.value());
|
||||
}
|
||||
|
||||
TEST_F(ResourceParserTest, ParseStyleWithInferredParent) {
|
||||
std::string input = "<style name=\"foo.bar\"/>";
|
||||
ASSERT_TRUE(testParse(input));
|
||||
|
||||
const Style* style = findResource<Style>(ResourceName{
|
||||
u"android", ResourceType::kStyle, u"foo.bar" });
|
||||
Style* style = test::getValue<Style>(&mTable, u"@style/foo.bar");
|
||||
ASSERT_NE(style, nullptr);
|
||||
EXPECT_EQ(style->parent.name, (ResourceName{ u"android", ResourceType::kStyle, u"foo" }));
|
||||
AAPT_ASSERT_TRUE(style->parent);
|
||||
AAPT_ASSERT_TRUE(style->parent.value().name);
|
||||
EXPECT_EQ(style->parent.value().name.value(), test::parseNameOrDie(u"@style/foo"));
|
||||
EXPECT_TRUE(style->parentInferred);
|
||||
}
|
||||
|
||||
@@ -396,10 +296,9 @@ TEST_F(ResourceParserTest, ParseStyleWithInferredParentOverridenByEmptyParentAtt
|
||||
std::string input = "<style name=\"foo.bar\" parent=\"\"/>";
|
||||
ASSERT_TRUE(testParse(input));
|
||||
|
||||
const Style* style = findResource<Style>(ResourceName{
|
||||
u"android", ResourceType::kStyle, u"foo.bar" });
|
||||
Style* style = test::getValue<Style>(&mTable, u"@style/foo.bar");
|
||||
ASSERT_NE(style, nullptr);
|
||||
EXPECT_FALSE(style->parent.name.isValid());
|
||||
AAPT_EXPECT_FALSE(style->parent);
|
||||
EXPECT_FALSE(style->parentInferred);
|
||||
}
|
||||
|
||||
@@ -407,7 +306,7 @@ TEST_F(ResourceParserTest, ParseAutoGeneratedIdReference) {
|
||||
std::string input = "<string name=\"foo\">@+id/bar</string>";
|
||||
ASSERT_TRUE(testParse(input));
|
||||
|
||||
const Id* id = findResource<Id>(ResourceName{ u"android", ResourceType::kId, u"bar"});
|
||||
Id* id = test::getValue<Id>(&mTable, u"@id/bar");
|
||||
ASSERT_NE(id, nullptr);
|
||||
}
|
||||
|
||||
@@ -418,22 +317,20 @@ TEST_F(ResourceParserTest, ParseAttributesDeclareStyleable) {
|
||||
"</declare-styleable>";
|
||||
ASSERT_TRUE(testParse(input));
|
||||
|
||||
const Attribute* attr = findResource<Attribute>(ResourceName{
|
||||
u"android", ResourceType::kAttr, u"bar"});
|
||||
Attribute* attr = test::getValue<Attribute>(&mTable, u"@attr/bar");
|
||||
ASSERT_NE(attr, nullptr);
|
||||
EXPECT_TRUE(attr->isWeak());
|
||||
|
||||
attr = findResource<Attribute>(ResourceName{ u"android", ResourceType::kAttr, u"bat"});
|
||||
attr = test::getValue<Attribute>(&mTable, u"@attr/bat");
|
||||
ASSERT_NE(attr, nullptr);
|
||||
EXPECT_TRUE(attr->isWeak());
|
||||
|
||||
const Styleable* styleable = findResource<Styleable>(ResourceName{
|
||||
u"android", ResourceType::kStyleable, u"foo" });
|
||||
Styleable* styleable = test::getValue<Styleable>(&mTable, u"@styleable/foo");
|
||||
ASSERT_NE(styleable, nullptr);
|
||||
ASSERT_EQ(2u, styleable->entries.size());
|
||||
|
||||
EXPECT_EQ((ResourceName{u"android", ResourceType::kAttr, u"bar"}), styleable->entries[0].name);
|
||||
EXPECT_EQ((ResourceName{u"android", ResourceType::kAttr, u"bat"}), styleable->entries[1].name);
|
||||
EXPECT_EQ(test::parseNameOrDie(u"@attr/bar"), styleable->entries[0].name.value());
|
||||
EXPECT_EQ(test::parseNameOrDie(u"@attr/bat"), styleable->entries[1].name.value());
|
||||
}
|
||||
|
||||
TEST_F(ResourceParserTest, ParseArray) {
|
||||
@@ -444,14 +341,13 @@ TEST_F(ResourceParserTest, ParseArray) {
|
||||
"</array>";
|
||||
ASSERT_TRUE(testParse(input));
|
||||
|
||||
const Array* array = findResource<Array>(ResourceName{
|
||||
u"android", ResourceType::kArray, u"foo" });
|
||||
Array* array = test::getValue<Array>(&mTable, u"@array/foo");
|
||||
ASSERT_NE(array, nullptr);
|
||||
ASSERT_EQ(3u, array->items.size());
|
||||
|
||||
EXPECT_NE(nullptr, dynamic_cast<const Reference*>(array->items[0].get()));
|
||||
EXPECT_NE(nullptr, dynamic_cast<const String*>(array->items[1].get()));
|
||||
EXPECT_NE(nullptr, dynamic_cast<const BinaryPrimitive*>(array->items[2].get()));
|
||||
EXPECT_NE(nullptr, valueCast<Reference>(array->items[0].get()));
|
||||
EXPECT_NE(nullptr, valueCast<String>(array->items[1].get()));
|
||||
EXPECT_NE(nullptr, valueCast<BinaryPrimitive>(array->items[2].get()));
|
||||
}
|
||||
|
||||
TEST_F(ResourceParserTest, ParsePlural) {
|
||||
@@ -467,11 +363,11 @@ TEST_F(ResourceParserTest, ParseCommentsWithResource) {
|
||||
"<string name=\"foo\">Hi</string>";
|
||||
ASSERT_TRUE(testParse(input));
|
||||
|
||||
const ResourceTableType* type;
|
||||
const ResourceEntry* entry;
|
||||
std::tie(type, entry) = mTable->findResource(ResourceName{
|
||||
u"android", ResourceType::kString, u"foo"});
|
||||
ASSERT_NE(type, nullptr);
|
||||
Maybe<ResourceTable::SearchResult> result = mTable.findResource(
|
||||
test::parseNameOrDie(u"@string/foo"));
|
||||
AAPT_ASSERT_TRUE(result);
|
||||
|
||||
ResourceEntry* entry = result.value().entry;
|
||||
ASSERT_NE(entry, nullptr);
|
||||
ASSERT_FALSE(entry->values.empty());
|
||||
EXPECT_EQ(entry->values.front().comment, u"This is a comment");
|
||||
@@ -485,7 +381,7 @@ TEST_F(ResourceParserTest, ParsePublicIdAsDefinition) {
|
||||
std::string input = "<public type=\"id\" name=\"foo\"/>";
|
||||
ASSERT_TRUE(testParse(input));
|
||||
|
||||
const Id* id = findResource<Id>(ResourceName{ u"android", ResourceType::kId, u"foo" });
|
||||
Id* id = test::getValue<Id>(&mTable, u"@id/foo");
|
||||
ASSERT_NE(nullptr, id);
|
||||
}
|
||||
|
||||
|
||||
@@ -15,11 +15,11 @@
|
||||
*/
|
||||
|
||||
#include "ConfigDescription.h"
|
||||
#include "Logger.h"
|
||||
#include "NameMangler.h"
|
||||
#include "ResourceTable.h"
|
||||
#include "ResourceValues.h"
|
||||
#include "Util.h"
|
||||
#include "ValueVisitor.h"
|
||||
#include "util/Util.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <androidfw/ResourceTypes.h>
|
||||
@@ -37,65 +37,109 @@ static bool lessThanType(const std::unique_ptr<ResourceTableType>& lhs, Resource
|
||||
return lhs->type < rhs;
|
||||
}
|
||||
|
||||
static bool lessThanEntry(const std::unique_ptr<ResourceEntry>& lhs, const StringPiece16& rhs) {
|
||||
template <typename T>
|
||||
static bool lessThanStructWithName(const std::unique_ptr<T>& lhs,
|
||||
const StringPiece16& rhs) {
|
||||
return lhs->name.compare(0, lhs->name.size(), rhs.data(), rhs.size()) < 0;
|
||||
}
|
||||
|
||||
ResourceTable::ResourceTable() : mPackageId(kUnsetPackageId) {
|
||||
// Make sure attrs always have type ID 1.
|
||||
findOrCreateType(ResourceType::kAttr)->typeId = 1;
|
||||
ResourceTablePackage* ResourceTable::findPackage(const StringPiece16& name) {
|
||||
const auto last = packages.end();
|
||||
auto iter = std::lower_bound(packages.begin(), last, name,
|
||||
lessThanStructWithName<ResourceTablePackage>);
|
||||
if (iter != last && name == (*iter)->name) {
|
||||
return iter->get();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::unique_ptr<ResourceTableType>& ResourceTable::findOrCreateType(ResourceType type) {
|
||||
auto last = mTypes.end();
|
||||
auto iter = std::lower_bound(mTypes.begin(), last, type, lessThanType);
|
||||
if (iter != last) {
|
||||
if ((*iter)->type == type) {
|
||||
return *iter;
|
||||
ResourceTablePackage* ResourceTable::findPackageById(uint8_t id) {
|
||||
for (auto& package : packages) {
|
||||
if (package->id && package->id.value() == id) {
|
||||
return package.get();
|
||||
}
|
||||
}
|
||||
return *mTypes.emplace(iter, new ResourceTableType{ type });
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::unique_ptr<ResourceEntry>& ResourceTable::findOrCreateEntry(
|
||||
std::unique_ptr<ResourceTableType>& type, const StringPiece16& name) {
|
||||
auto last = type->entries.end();
|
||||
auto iter = std::lower_bound(type->entries.begin(), last, name, lessThanEntry);
|
||||
if (iter != last) {
|
||||
if (name == (*iter)->name) {
|
||||
return *iter;
|
||||
}
|
||||
ResourceTablePackage* ResourceTable::createPackage(const StringPiece16& name, uint8_t id) {
|
||||
ResourceTablePackage* package = findOrCreatePackage(name);
|
||||
if (!package->id) {
|
||||
package->id = id;
|
||||
return package;
|
||||
}
|
||||
return *type->entries.emplace(iter, new ResourceEntry{ name });
|
||||
|
||||
if (package->id.value() == id) {
|
||||
return package;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
struct IsAttributeVisitor : ConstValueVisitor {
|
||||
bool isAttribute = false;
|
||||
|
||||
void visit(const Attribute&, ValueVisitorArgs&) override {
|
||||
isAttribute = true;
|
||||
ResourceTablePackage* ResourceTable::findOrCreatePackage(const StringPiece16& name) {
|
||||
const auto last = packages.end();
|
||||
auto iter = std::lower_bound(packages.begin(), last, name,
|
||||
lessThanStructWithName<ResourceTablePackage>);
|
||||
if (iter != last && name == (*iter)->name) {
|
||||
return iter->get();
|
||||
}
|
||||
|
||||
operator bool() {
|
||||
return isAttribute;
|
||||
std::unique_ptr<ResourceTablePackage> newPackage = util::make_unique<ResourceTablePackage>();
|
||||
newPackage->name = name.toString();
|
||||
return packages.emplace(iter, std::move(newPackage))->get();
|
||||
}
|
||||
|
||||
ResourceTableType* ResourceTablePackage::findType(ResourceType type) {
|
||||
const auto last = types.end();
|
||||
auto iter = std::lower_bound(types.begin(), last, type, lessThanType);
|
||||
if (iter != last && (*iter)->type == type) {
|
||||
return iter->get();
|
||||
}
|
||||
};
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ResourceTableType* ResourceTablePackage::findOrCreateType(ResourceType type) {
|
||||
const auto last = types.end();
|
||||
auto iter = std::lower_bound(types.begin(), last, type, lessThanType);
|
||||
if (iter != last && (*iter)->type == type) {
|
||||
return iter->get();
|
||||
}
|
||||
return types.emplace(iter, new ResourceTableType{ type })->get();
|
||||
}
|
||||
|
||||
ResourceEntry* ResourceTableType::findEntry(const StringPiece16& name) {
|
||||
const auto last = entries.end();
|
||||
auto iter = std::lower_bound(entries.begin(), last, name,
|
||||
lessThanStructWithName<ResourceEntry>);
|
||||
if (iter != last && name == (*iter)->name) {
|
||||
return iter->get();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ResourceEntry* ResourceTableType::findOrCreateEntry(const StringPiece16& name) {
|
||||
auto last = entries.end();
|
||||
auto iter = std::lower_bound(entries.begin(), last, name,
|
||||
lessThanStructWithName<ResourceEntry>);
|
||||
if (iter != last && name == (*iter)->name) {
|
||||
return iter->get();
|
||||
}
|
||||
return entries.emplace(iter, new ResourceEntry{ name })->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* The default handler for collisions. A return value of -1 means keep the
|
||||
* existing value, 0 means fail, and +1 means take the incoming value.
|
||||
*/
|
||||
static int defaultCollisionHandler(const Value& existing, const Value& incoming) {
|
||||
IsAttributeVisitor existingIsAttr, incomingIsAttr;
|
||||
existing.accept(existingIsAttr, {});
|
||||
incoming.accept(incomingIsAttr, {});
|
||||
int ResourceTable::resolveValueCollision(Value* existing, Value* incoming) {
|
||||
Attribute* existingAttr = valueCast<Attribute>(existing);
|
||||
Attribute* incomingAttr = valueCast<Attribute>(incoming);
|
||||
|
||||
if (!incomingIsAttr) {
|
||||
if (incoming.isWeak()) {
|
||||
if (!incomingAttr) {
|
||||
if (incoming->isWeak()) {
|
||||
// We're trying to add a weak resource but a resource
|
||||
// already exists. Keep the existing.
|
||||
return -1;
|
||||
} else if (existing.isWeak()) {
|
||||
} else if (existing->isWeak()) {
|
||||
// Override the weak resource with the new strong resource.
|
||||
return 1;
|
||||
}
|
||||
@@ -104,8 +148,8 @@ static int defaultCollisionHandler(const Value& existing, const Value& incoming)
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!existingIsAttr) {
|
||||
if (existing.isWeak()) {
|
||||
if (!existingAttr) {
|
||||
if (existing->isWeak()) {
|
||||
// The existing value is not an attribute and it is weak,
|
||||
// so take the incoming attribute value.
|
||||
return 1;
|
||||
@@ -115,27 +159,27 @@ static int defaultCollisionHandler(const Value& existing, const Value& incoming)
|
||||
return 0;
|
||||
}
|
||||
|
||||
assert(incomingAttr && existingAttr);
|
||||
|
||||
//
|
||||
// Attribute specific handling. At this point we know both
|
||||
// values are attributes. Since we can declare and define
|
||||
// attributes all-over, we do special handling to see
|
||||
// which definition sticks.
|
||||
//
|
||||
const Attribute& existingAttr = static_cast<const Attribute&>(existing);
|
||||
const Attribute& incomingAttr = static_cast<const Attribute&>(incoming);
|
||||
if (existingAttr.typeMask == incomingAttr.typeMask) {
|
||||
if (existingAttr->typeMask == incomingAttr->typeMask) {
|
||||
// The two attributes are both DECLs, but they are plain attributes
|
||||
// with the same formats.
|
||||
// Keep the strongest one.
|
||||
return existingAttr.isWeak() ? 1 : -1;
|
||||
return existingAttr->isWeak() ? 1 : -1;
|
||||
}
|
||||
|
||||
if (existingAttr.isWeak() && existingAttr.typeMask == android::ResTable_map::TYPE_ANY) {
|
||||
if (existingAttr->isWeak() && existingAttr->typeMask == android::ResTable_map::TYPE_ANY) {
|
||||
// Any incoming attribute is better than this.
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (incomingAttr.isWeak() && incomingAttr.typeMask == android::ResTable_map::TYPE_ANY) {
|
||||
if (incomingAttr->isWeak() && incomingAttr->typeMask == android::ResTable_map::TYPE_ANY) {
|
||||
// The incoming attribute may be a USE instead of a DECL.
|
||||
// Keep the existing attribute.
|
||||
return -1;
|
||||
@@ -147,180 +191,183 @@ static constexpr const char16_t* kValidNameChars = u"._-";
|
||||
static constexpr const char16_t* kValidNameMangledChars = u"._-$";
|
||||
|
||||
bool ResourceTable::addResource(const ResourceNameRef& name, const ConfigDescription& config,
|
||||
const SourceLine& source, std::unique_ptr<Value> value) {
|
||||
return addResourceImpl(name, ResourceId{}, config, source, std::move(value), kValidNameChars);
|
||||
const Source& source, std::unique_ptr<Value> value,
|
||||
IDiagnostics* diag) {
|
||||
return addResourceImpl(name, ResourceId{}, config, source, std::move(value), kValidNameChars,
|
||||
diag);
|
||||
}
|
||||
|
||||
bool ResourceTable::addResource(const ResourceNameRef& name, const ResourceId resId,
|
||||
const ConfigDescription& config, const SourceLine& source,
|
||||
std::unique_ptr<Value> value) {
|
||||
return addResourceImpl(name, resId, config, source, std::move(value), kValidNameChars);
|
||||
const ConfigDescription& config, const Source& source,
|
||||
std::unique_ptr<Value> value, IDiagnostics* diag) {
|
||||
return addResourceImpl(name, resId, config, source, std::move(value), kValidNameChars, diag);
|
||||
}
|
||||
|
||||
bool ResourceTable::addFileReference(const ResourceNameRef& name, const ConfigDescription& config,
|
||||
const Source& source, const StringPiece16& path,
|
||||
IDiagnostics* diag) {
|
||||
return addResourceImpl(name, ResourceId{}, config, source,
|
||||
util::make_unique<FileReference>(stringPool.makeRef(path)),
|
||||
kValidNameChars, diag);
|
||||
}
|
||||
|
||||
bool ResourceTable::addResourceAllowMangled(const ResourceNameRef& name,
|
||||
const ConfigDescription& config,
|
||||
const SourceLine& source,
|
||||
std::unique_ptr<Value> value) {
|
||||
const Source& source,
|
||||
std::unique_ptr<Value> value,
|
||||
IDiagnostics* diag) {
|
||||
return addResourceImpl(name, ResourceId{}, config, source, std::move(value),
|
||||
kValidNameMangledChars);
|
||||
kValidNameMangledChars, diag);
|
||||
}
|
||||
|
||||
bool ResourceTable::addResourceImpl(const ResourceNameRef& name, const ResourceId resId,
|
||||
const ConfigDescription& config, const SourceLine& source,
|
||||
std::unique_ptr<Value> value, const char16_t* validChars) {
|
||||
if (!name.package.empty() && name.package != mPackage) {
|
||||
Logger::error(source)
|
||||
<< "resource '"
|
||||
<< name
|
||||
<< "' has incompatible package. Must be '"
|
||||
<< mPackage
|
||||
<< "'."
|
||||
<< std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
const ConfigDescription& config, const Source& source,
|
||||
std::unique_ptr<Value> value, const char16_t* validChars,
|
||||
IDiagnostics* diag) {
|
||||
auto badCharIter = util::findNonAlphaNumericAndNotInSet(name.entry, validChars);
|
||||
if (badCharIter != name.entry.end()) {
|
||||
Logger::error(source)
|
||||
<< "resource '"
|
||||
<< name
|
||||
<< "' has invalid entry name '"
|
||||
<< name.entry
|
||||
<< "'. Invalid character '"
|
||||
<< StringPiece16(badCharIter, 1)
|
||||
<< "'."
|
||||
<< std::endl;
|
||||
diag->error(DiagMessage(source)
|
||||
<< "resource '"
|
||||
<< name
|
||||
<< "' has invalid entry name '"
|
||||
<< name.entry
|
||||
<< "'. Invalid character '"
|
||||
<< StringPiece16(badCharIter, 1)
|
||||
<< "'");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::unique_ptr<ResourceTableType>& type = findOrCreateType(name.type);
|
||||
if (resId.isValid() && type->typeId != ResourceTableType::kUnsetTypeId &&
|
||||
type->typeId != resId.typeId()) {
|
||||
Logger::error(source)
|
||||
<< "trying to add resource '"
|
||||
<< name
|
||||
<< "' with ID "
|
||||
<< resId
|
||||
<< " but type '"
|
||||
<< type->type
|
||||
<< "' already has ID "
|
||||
<< std::hex << type->typeId << std::dec
|
||||
<< "."
|
||||
<< std::endl;
|
||||
ResourceTablePackage* package = findOrCreatePackage(name.package);
|
||||
if (resId.isValid() && package->id && package->id.value() != resId.packageId()) {
|
||||
diag->error(DiagMessage(source)
|
||||
<< "trying to add resource '"
|
||||
<< name
|
||||
<< "' with ID "
|
||||
<< resId
|
||||
<< " but package '"
|
||||
<< package->name
|
||||
<< "' already has ID "
|
||||
<< std::hex << (int) package->id.value() << std::dec);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::unique_ptr<ResourceEntry>& entry = findOrCreateEntry(type, name.entry);
|
||||
if (resId.isValid() && entry->entryId != ResourceEntry::kUnsetEntryId &&
|
||||
entry->entryId != resId.entryId()) {
|
||||
Logger::error(source)
|
||||
<< "trying to add resource '"
|
||||
<< name
|
||||
<< "' with ID "
|
||||
<< resId
|
||||
<< " but resource already has ID "
|
||||
<< ResourceId(mPackageId, type->typeId, entry->entryId)
|
||||
<< "."
|
||||
<< std::endl;
|
||||
ResourceTableType* type = package->findOrCreateType(name.type);
|
||||
if (resId.isValid() && type->id && type->id.value() != resId.typeId()) {
|
||||
diag->error(DiagMessage(source)
|
||||
<< "trying to add resource '"
|
||||
<< name
|
||||
<< "' with ID "
|
||||
<< resId
|
||||
<< " but type '"
|
||||
<< type->type
|
||||
<< "' already has ID "
|
||||
<< std::hex << (int) type->id.value() << std::dec);
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto endIter = std::end(entry->values);
|
||||
auto iter = std::lower_bound(std::begin(entry->values), endIter, config, compareConfigs);
|
||||
ResourceEntry* entry = type->findOrCreateEntry(name.entry);
|
||||
if (resId.isValid() && entry->id && entry->id.value() != resId.entryId()) {
|
||||
diag->error(DiagMessage(source)
|
||||
<< "trying to add resource '"
|
||||
<< name
|
||||
<< "' with ID "
|
||||
<< resId
|
||||
<< " but resource already has ID "
|
||||
<< ResourceId(package->id.value(), type->id.value(), entry->id.value()));
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto endIter = entry->values.end();
|
||||
auto iter = std::lower_bound(entry->values.begin(), endIter, config, compareConfigs);
|
||||
if (iter == endIter || iter->config != config) {
|
||||
// This resource did not exist before, add it.
|
||||
entry->values.insert(iter, ResourceConfigValue{ config, source, {}, std::move(value) });
|
||||
} else {
|
||||
int collisionResult = defaultCollisionHandler(*iter->value, *value);
|
||||
int collisionResult = resolveValueCollision(iter->value.get(), value.get());
|
||||
if (collisionResult > 0) {
|
||||
// Take the incoming value.
|
||||
*iter = ResourceConfigValue{ config, source, {}, std::move(value) };
|
||||
} else if (collisionResult == 0) {
|
||||
Logger::error(source)
|
||||
<< "duplicate value for resource '" << name << "' "
|
||||
<< "with config '" << iter->config << "'."
|
||||
<< std::endl;
|
||||
|
||||
Logger::error(iter->source)
|
||||
<< "resource previously defined here."
|
||||
<< std::endl;
|
||||
diag->error(DiagMessage(source)
|
||||
<< "duplicate value for resource '" << name << "' "
|
||||
<< "with config '" << iter->config << "'");
|
||||
diag->error(DiagMessage(iter->source)
|
||||
<< "resource previously defined here");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (resId.isValid()) {
|
||||
type->typeId = resId.typeId();
|
||||
entry->entryId = resId.entryId();
|
||||
package->id = resId.packageId();
|
||||
type->id = resId.typeId();
|
||||
entry->id = resId.entryId();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ResourceTable::markPublic(const ResourceNameRef& name, const ResourceId resId,
|
||||
const SourceLine& source) {
|
||||
return markPublicImpl(name, resId, source, kValidNameChars);
|
||||
const Source& source, IDiagnostics* diag) {
|
||||
return markPublicImpl(name, resId, source, kValidNameChars, diag);
|
||||
}
|
||||
|
||||
bool ResourceTable::markPublicAllowMangled(const ResourceNameRef& name, const ResourceId resId,
|
||||
const SourceLine& source) {
|
||||
return markPublicImpl(name, resId, source, kValidNameMangledChars);
|
||||
const Source& source, IDiagnostics* diag) {
|
||||
return markPublicImpl(name, resId, source, kValidNameMangledChars, diag);
|
||||
}
|
||||
|
||||
bool ResourceTable::markPublicImpl(const ResourceNameRef& name, const ResourceId resId,
|
||||
const SourceLine& source, const char16_t* validChars) {
|
||||
if (!name.package.empty() && name.package != mPackage) {
|
||||
Logger::error(source)
|
||||
<< "resource '"
|
||||
<< name
|
||||
<< "' has incompatible package. Must be '"
|
||||
<< mPackage
|
||||
<< "'."
|
||||
<< std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
const Source& source, const char16_t* validChars,
|
||||
IDiagnostics* diag) {
|
||||
auto badCharIter = util::findNonAlphaNumericAndNotInSet(name.entry, validChars);
|
||||
if (badCharIter != name.entry.end()) {
|
||||
Logger::error(source)
|
||||
<< "resource '"
|
||||
<< name
|
||||
<< "' has invalid entry name '"
|
||||
<< name.entry
|
||||
<< "'. Invalid character '"
|
||||
<< StringPiece16(badCharIter, 1)
|
||||
<< "'."
|
||||
<< std::endl;
|
||||
diag->error(DiagMessage(source)
|
||||
<< "resource '"
|
||||
<< name
|
||||
<< "' has invalid entry name '"
|
||||
<< name.entry
|
||||
<< "'. Invalid character '"
|
||||
<< StringPiece16(badCharIter, 1)
|
||||
<< "'");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::unique_ptr<ResourceTableType>& type = findOrCreateType(name.type);
|
||||
if (resId.isValid() && type->typeId != ResourceTableType::kUnsetTypeId &&
|
||||
type->typeId != resId.typeId()) {
|
||||
Logger::error(source)
|
||||
<< "trying to make resource '"
|
||||
<< name
|
||||
<< "' public with ID "
|
||||
<< resId
|
||||
<< " but type '"
|
||||
<< type->type
|
||||
<< "' already has ID "
|
||||
<< std::hex << type->typeId << std::dec
|
||||
<< "."
|
||||
<< std::endl;
|
||||
ResourceTablePackage* package = findOrCreatePackage(name.package);
|
||||
if (resId.isValid() && package->id && package->id.value() != resId.packageId()) {
|
||||
diag->error(DiagMessage(source)
|
||||
<< "trying to add resource '"
|
||||
<< name
|
||||
<< "' with ID "
|
||||
<< resId
|
||||
<< " but package '"
|
||||
<< package->name
|
||||
<< "' already has ID "
|
||||
<< std::hex << (int) package->id.value() << std::dec);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::unique_ptr<ResourceEntry>& entry = findOrCreateEntry(type, name.entry);
|
||||
if (resId.isValid() && entry->entryId != ResourceEntry::kUnsetEntryId &&
|
||||
entry->entryId != resId.entryId()) {
|
||||
Logger::error(source)
|
||||
<< "trying to make resource '"
|
||||
<< name
|
||||
<< "' public with ID "
|
||||
<< resId
|
||||
<< " but resource already has ID "
|
||||
<< ResourceId(mPackageId, type->typeId, entry->entryId)
|
||||
<< "."
|
||||
<< std::endl;
|
||||
ResourceTableType* type = package->findOrCreateType(name.type);
|
||||
if (resId.isValid() && type->id && type->id.value() != resId.typeId()) {
|
||||
diag->error(DiagMessage(source)
|
||||
<< "trying to add resource '"
|
||||
<< name
|
||||
<< "' with ID "
|
||||
<< resId
|
||||
<< " but type '"
|
||||
<< type->type
|
||||
<< "' already has ID "
|
||||
<< std::hex << (int) type->id.value() << std::dec);
|
||||
return false;
|
||||
}
|
||||
|
||||
ResourceEntry* entry = type->findOrCreateEntry(name.entry);
|
||||
if (resId.isValid() && entry->id && entry->id.value() != resId.entryId()) {
|
||||
diag->error(DiagMessage(source)
|
||||
<< "trying to add resource '"
|
||||
<< name
|
||||
<< "' with ID "
|
||||
<< resId
|
||||
<< " but resource already has ID "
|
||||
<< ResourceId(package->id.value(), type->id.value(), entry->id.value()));
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -329,102 +376,30 @@ bool ResourceTable::markPublicImpl(const ResourceNameRef& name, const ResourceId
|
||||
entry->publicStatus.source = source;
|
||||
|
||||
if (resId.isValid()) {
|
||||
type->typeId = resId.typeId();
|
||||
entry->entryId = resId.entryId();
|
||||
package->id = resId.packageId();
|
||||
type->id = resId.typeId();
|
||||
entry->id = resId.entryId();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ResourceTable::merge(ResourceTable&& other) {
|
||||
const bool mangleNames = mPackage != other.getPackage();
|
||||
std::u16string mangledName;
|
||||
|
||||
for (auto& otherType : other) {
|
||||
std::unique_ptr<ResourceTableType>& type = findOrCreateType(otherType->type);
|
||||
if (otherType->publicStatus.isPublic) {
|
||||
if (type->publicStatus.isPublic && type->typeId != otherType->typeId) {
|
||||
Logger::error() << "can not merge type '" << type->type
|
||||
<< "': conflicting public IDs "
|
||||
<< "(" << type->typeId << " vs " << otherType->typeId << ")."
|
||||
<< std::endl;
|
||||
return false;
|
||||
}
|
||||
type->publicStatus = std::move(otherType->publicStatus);
|
||||
type->typeId = otherType->typeId;
|
||||
}
|
||||
|
||||
for (auto& otherEntry : otherType->entries) {
|
||||
const std::u16string* nameToAdd = &otherEntry->name;
|
||||
if (mangleNames) {
|
||||
mangledName = otherEntry->name;
|
||||
NameMangler::mangle(other.getPackage(), &mangledName);
|
||||
nameToAdd = &mangledName;
|
||||
}
|
||||
|
||||
std::unique_ptr<ResourceEntry>& entry = findOrCreateEntry(type, *nameToAdd);
|
||||
if (otherEntry->publicStatus.isPublic) {
|
||||
if (entry->publicStatus.isPublic && entry->entryId != otherEntry->entryId) {
|
||||
Logger::error() << "can not merge entry '" << type->type << "/" << entry->name
|
||||
<< "': conflicting public IDs "
|
||||
<< "(" << entry->entryId << " vs " << entry->entryId << ")."
|
||||
<< std::endl;
|
||||
return false;
|
||||
}
|
||||
entry->publicStatus = std::move(otherEntry->publicStatus);
|
||||
entry->entryId = otherEntry->entryId;
|
||||
}
|
||||
|
||||
for (ResourceConfigValue& otherValue : otherEntry->values) {
|
||||
auto iter = std::lower_bound(entry->values.begin(), entry->values.end(),
|
||||
otherValue.config, compareConfigs);
|
||||
if (iter != entry->values.end() && iter->config == otherValue.config) {
|
||||
int collisionResult = defaultCollisionHandler(*iter->value, *otherValue.value);
|
||||
if (collisionResult > 0) {
|
||||
// Take the incoming value.
|
||||
iter->source = std::move(otherValue.source);
|
||||
iter->comment = std::move(otherValue.comment);
|
||||
iter->value = std::unique_ptr<Value>(otherValue.value->clone(&mValuePool));
|
||||
} else if (collisionResult == 0) {
|
||||
ResourceNameRef resourceName = { mPackage, type->type, entry->name };
|
||||
Logger::error(otherValue.source)
|
||||
<< "resource '" << resourceName << "' has a conflicting value for "
|
||||
<< "configuration (" << otherValue.config << ")."
|
||||
<< std::endl;
|
||||
Logger::note(iter->source) << "originally defined here." << std::endl;
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
entry->values.insert(iter, ResourceConfigValue{
|
||||
otherValue.config,
|
||||
std::move(otherValue.source),
|
||||
std::move(otherValue.comment),
|
||||
std::unique_ptr<Value>(otherValue.value->clone(&mValuePool)),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::tuple<const ResourceTableType*, const ResourceEntry*>
|
||||
ResourceTable::findResource(const ResourceNameRef& name) const {
|
||||
if (name.package != mPackage) {
|
||||
Maybe<ResourceTable::SearchResult>
|
||||
ResourceTable::findResource(const ResourceNameRef& name) {
|
||||
ResourceTablePackage* package = findPackage(name.package);
|
||||
if (!package) {
|
||||
return {};
|
||||
}
|
||||
|
||||
auto iter = std::lower_bound(mTypes.begin(), mTypes.end(), name.type, lessThanType);
|
||||
if (iter == mTypes.end() || (*iter)->type != name.type) {
|
||||
ResourceTableType* type = package->findType(name.type);
|
||||
if (!type) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const std::unique_ptr<ResourceTableType>& type = *iter;
|
||||
auto iter2 = std::lower_bound(type->entries.begin(), type->entries.end(), name.entry,
|
||||
lessThanEntry);
|
||||
if (iter2 == type->entries.end() || name.entry != (*iter2)->name) {
|
||||
ResourceEntry* entry = type->findEntry(name.entry);
|
||||
if (!entry) {
|
||||
return {};
|
||||
}
|
||||
return std::make_tuple(iter->get(), iter2->get());
|
||||
return SearchResult{ package, type, entry };
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#define AAPT_RESOURCE_TABLE_H
|
||||
|
||||
#include "ConfigDescription.h"
|
||||
#include "Diagnostics.h"
|
||||
#include "Resource.h"
|
||||
#include "ResourceValues.h"
|
||||
#include "Source.h"
|
||||
@@ -35,7 +36,7 @@ namespace aapt {
|
||||
*/
|
||||
struct Public {
|
||||
bool isPublic = false;
|
||||
SourceLine source;
|
||||
Source source;
|
||||
std::u16string comment;
|
||||
};
|
||||
|
||||
@@ -44,7 +45,7 @@ struct Public {
|
||||
*/
|
||||
struct ResourceConfigValue {
|
||||
ConfigDescription config;
|
||||
SourceLine source;
|
||||
Source source;
|
||||
std::u16string comment;
|
||||
std::unique_ptr<Value> value;
|
||||
};
|
||||
@@ -54,10 +55,6 @@ struct ResourceConfigValue {
|
||||
* varying values for each defined configuration.
|
||||
*/
|
||||
struct ResourceEntry {
|
||||
enum {
|
||||
kUnsetEntryId = 0xffffffffu
|
||||
};
|
||||
|
||||
/**
|
||||
* The name of the resource. Immutable, as
|
||||
* this determines the order of this resource
|
||||
@@ -68,7 +65,7 @@ struct ResourceEntry {
|
||||
/**
|
||||
* The entry ID for this resource.
|
||||
*/
|
||||
size_t entryId;
|
||||
Maybe<uint16_t> id;
|
||||
|
||||
/**
|
||||
* Whether this resource is public (and must maintain the same
|
||||
@@ -81,8 +78,7 @@ struct ResourceEntry {
|
||||
*/
|
||||
std::vector<ResourceConfigValue> values;
|
||||
|
||||
inline ResourceEntry(const StringPiece16& _name);
|
||||
inline ResourceEntry(const ResourceEntry* rhs);
|
||||
ResourceEntry(const StringPiece16& name) : name(name.toString()) { }
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -90,10 +86,6 @@ struct ResourceEntry {
|
||||
* for this type.
|
||||
*/
|
||||
struct ResourceTableType {
|
||||
enum {
|
||||
kUnsetTypeId = 0xffffffffu
|
||||
};
|
||||
|
||||
/**
|
||||
* The logical type of resource (string, drawable, layout, etc.).
|
||||
*/
|
||||
@@ -102,7 +94,7 @@ struct ResourceTableType {
|
||||
/**
|
||||
* The type ID for this resource.
|
||||
*/
|
||||
size_t typeId;
|
||||
Maybe<uint8_t> id;
|
||||
|
||||
/**
|
||||
* Whether this type is public (and must maintain the same
|
||||
@@ -115,8 +107,30 @@ struct ResourceTableType {
|
||||
*/
|
||||
std::vector<std::unique_ptr<ResourceEntry>> entries;
|
||||
|
||||
ResourceTableType(const ResourceType _type);
|
||||
ResourceTableType(const ResourceTableType* rhs);
|
||||
explicit ResourceTableType(const ResourceType type) : type(type) { }
|
||||
|
||||
ResourceEntry* findEntry(const StringPiece16& name);
|
||||
|
||||
ResourceEntry* findOrCreateEntry(const StringPiece16& name);
|
||||
};
|
||||
|
||||
enum class PackageType {
|
||||
System,
|
||||
Vendor,
|
||||
App,
|
||||
Dynamic
|
||||
};
|
||||
|
||||
struct ResourceTablePackage {
|
||||
PackageType type = PackageType::App;
|
||||
Maybe<uint8_t> id;
|
||||
std::u16string name;
|
||||
|
||||
std::vector<std::unique_ptr<ResourceTableType>> types;
|
||||
|
||||
ResourceTableType* findType(ResourceType type);
|
||||
|
||||
ResourceTableType* findOrCreateType(const ResourceType type);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -125,23 +139,28 @@ struct ResourceTableType {
|
||||
*/
|
||||
class ResourceTable {
|
||||
public:
|
||||
using iterator = std::vector<std::unique_ptr<ResourceTableType>>::iterator;
|
||||
using const_iterator = std::vector<std::unique_ptr<ResourceTableType>>::const_iterator;
|
||||
ResourceTable() = default;
|
||||
ResourceTable(const ResourceTable&) = delete;
|
||||
ResourceTable& operator=(const ResourceTable&) = delete;
|
||||
|
||||
enum {
|
||||
kUnsetPackageId = 0xffffffff
|
||||
};
|
||||
|
||||
ResourceTable();
|
||||
|
||||
size_t getPackageId() const;
|
||||
void setPackageId(size_t packageId);
|
||||
|
||||
const std::u16string& getPackage() const;
|
||||
void setPackage(const StringPiece16& package);
|
||||
/**
|
||||
* When a collision of resources occurs, this method decides which value to keep.
|
||||
* Returns -1 if the existing value should be chosen.
|
||||
* Returns 0 if the collision can not be resolved (error).
|
||||
* Returns 1 if the incoming value should be chosen.
|
||||
*/
|
||||
static int resolveValueCollision(Value* existing, Value* incoming);
|
||||
|
||||
bool addResource(const ResourceNameRef& name, const ConfigDescription& config,
|
||||
const SourceLine& source, std::unique_ptr<Value> value);
|
||||
const Source& source, std::unique_ptr<Value> value,
|
||||
IDiagnostics* diag);
|
||||
|
||||
bool addResource(const ResourceNameRef& name, const ResourceId resId,
|
||||
const ConfigDescription& config, const Source& source,
|
||||
std::unique_ptr<Value> value, IDiagnostics* diag);
|
||||
|
||||
bool addFileReference(const ResourceNameRef& name, const ConfigDescription& config,
|
||||
const Source& source, const StringPiece16& path, IDiagnostics* diag);
|
||||
|
||||
/**
|
||||
* Same as addResource, but doesn't verify the validity of the name. This is used
|
||||
@@ -149,129 +168,59 @@ public:
|
||||
* names.
|
||||
*/
|
||||
bool addResourceAllowMangled(const ResourceNameRef& name, const ConfigDescription& config,
|
||||
const SourceLine& source, std::unique_ptr<Value> value);
|
||||
const Source& source, std::unique_ptr<Value> value,
|
||||
IDiagnostics* diag);
|
||||
|
||||
bool addResource(const ResourceNameRef& name, const ResourceId resId,
|
||||
const ConfigDescription& config, const SourceLine& source,
|
||||
std::unique_ptr<Value> value);
|
||||
|
||||
bool markPublic(const ResourceNameRef& name, const ResourceId resId, const SourceLine& source);
|
||||
bool markPublic(const ResourceNameRef& name, const ResourceId resId, const Source& source,
|
||||
IDiagnostics* diag);
|
||||
bool markPublicAllowMangled(const ResourceNameRef& name, const ResourceId resId,
|
||||
const SourceLine& source);
|
||||
const Source& source, IDiagnostics* diag);
|
||||
struct SearchResult {
|
||||
ResourceTablePackage* package;
|
||||
ResourceTableType* type;
|
||||
ResourceEntry* entry;
|
||||
};
|
||||
|
||||
/*
|
||||
* Merges the resources from `other` into this table, mangling the names of the resources
|
||||
* if `other` has a different package name.
|
||||
*/
|
||||
bool merge(ResourceTable&& other);
|
||||
Maybe<SearchResult> findResource(const ResourceNameRef& name);
|
||||
|
||||
/**
|
||||
* Returns the string pool used by this ResourceTable.
|
||||
* Values that reference strings should use this pool to create
|
||||
* their strings.
|
||||
* The string pool used by this resource table. Values that reference strings must use
|
||||
* this pool to create their strings.
|
||||
*
|
||||
* NOTE: `stringPool` must come before `packages` so that it is destroyed after.
|
||||
* When `string pool` references are destroyed (as they will be when `packages`
|
||||
* is destroyed), they decrement a refCount, which would cause invalid
|
||||
* memory access if the pool was already destroyed.
|
||||
*/
|
||||
StringPool& getValueStringPool();
|
||||
const StringPool& getValueStringPool() const;
|
||||
StringPool stringPool;
|
||||
|
||||
std::tuple<const ResourceTableType*, const ResourceEntry*>
|
||||
findResource(const ResourceNameRef& name) const;
|
||||
/**
|
||||
* The list of packages in this table, sorted alphabetically by package name.
|
||||
*/
|
||||
std::vector<std::unique_ptr<ResourceTablePackage>> packages;
|
||||
|
||||
iterator begin();
|
||||
iterator end();
|
||||
const_iterator begin() const;
|
||||
const_iterator end() const;
|
||||
/**
|
||||
* Returns the package struct with the given name, or nullptr if such a package does not
|
||||
* exist. The empty string is a valid package and typically is used to represent the
|
||||
* 'current' package before it is known to the ResourceTable.
|
||||
*/
|
||||
ResourceTablePackage* findPackage(const StringPiece16& name);
|
||||
|
||||
ResourceTablePackage* findPackageById(uint8_t id);
|
||||
|
||||
ResourceTablePackage* createPackage(const StringPiece16& name, uint8_t id);
|
||||
|
||||
private:
|
||||
std::unique_ptr<ResourceTableType>& findOrCreateType(ResourceType type);
|
||||
std::unique_ptr<ResourceEntry>& findOrCreateEntry(std::unique_ptr<ResourceTableType>& type,
|
||||
const StringPiece16& name);
|
||||
ResourceTablePackage* findOrCreatePackage(const StringPiece16& name);
|
||||
|
||||
bool addResourceImpl(const ResourceNameRef& name, const ResourceId resId,
|
||||
const ConfigDescription& config, const SourceLine& source,
|
||||
std::unique_ptr<Value> value, const char16_t* validChars);
|
||||
const ConfigDescription& config, const Source& source,
|
||||
std::unique_ptr<Value> value, const char16_t* validChars,
|
||||
IDiagnostics* diag);
|
||||
bool markPublicImpl(const ResourceNameRef& name, const ResourceId resId,
|
||||
const SourceLine& source, const char16_t* validChars);
|
||||
|
||||
std::u16string mPackage;
|
||||
size_t mPackageId;
|
||||
|
||||
// StringPool must come before mTypes so that it is destroyed after.
|
||||
// When StringPool references are destroyed (as they will be when mTypes
|
||||
// is destroyed), they decrement a refCount, which would cause invalid
|
||||
// memory access if the pool was already destroyed.
|
||||
StringPool mValuePool;
|
||||
|
||||
std::vector<std::unique_ptr<ResourceTableType>> mTypes;
|
||||
const Source& source, const char16_t* validChars, IDiagnostics* diag);
|
||||
};
|
||||
|
||||
//
|
||||
// ResourceEntry implementation.
|
||||
//
|
||||
|
||||
inline ResourceEntry::ResourceEntry(const StringPiece16& _name) :
|
||||
name(_name.toString()), entryId(kUnsetEntryId) {
|
||||
}
|
||||
|
||||
inline ResourceEntry::ResourceEntry(const ResourceEntry* rhs) :
|
||||
name(rhs->name), entryId(rhs->entryId), publicStatus(rhs->publicStatus) {
|
||||
}
|
||||
|
||||
//
|
||||
// ResourceTableType implementation.
|
||||
//
|
||||
|
||||
inline ResourceTableType::ResourceTableType(const ResourceType _type) :
|
||||
type(_type), typeId(kUnsetTypeId) {
|
||||
}
|
||||
|
||||
inline ResourceTableType::ResourceTableType(const ResourceTableType* rhs) :
|
||||
type(rhs->type), typeId(rhs->typeId), publicStatus(rhs->publicStatus) {
|
||||
}
|
||||
|
||||
//
|
||||
// ResourceTable implementation.
|
||||
//
|
||||
|
||||
inline StringPool& ResourceTable::getValueStringPool() {
|
||||
return mValuePool;
|
||||
}
|
||||
|
||||
inline const StringPool& ResourceTable::getValueStringPool() const {
|
||||
return mValuePool;
|
||||
}
|
||||
|
||||
inline ResourceTable::iterator ResourceTable::begin() {
|
||||
return mTypes.begin();
|
||||
}
|
||||
|
||||
inline ResourceTable::iterator ResourceTable::end() {
|
||||
return mTypes.end();
|
||||
}
|
||||
|
||||
inline ResourceTable::const_iterator ResourceTable::begin() const {
|
||||
return mTypes.begin();
|
||||
}
|
||||
|
||||
inline ResourceTable::const_iterator ResourceTable::end() const {
|
||||
return mTypes.end();
|
||||
}
|
||||
|
||||
inline const std::u16string& ResourceTable::getPackage() const {
|
||||
return mPackage;
|
||||
}
|
||||
|
||||
inline size_t ResourceTable::getPackageId() const {
|
||||
return mPackageId;
|
||||
}
|
||||
|
||||
inline void ResourceTable::setPackage(const StringPiece16& package) {
|
||||
mPackage = package.toString();
|
||||
}
|
||||
|
||||
inline void ResourceTable::setPackageId(size_t packageId) {
|
||||
mPackageId = packageId;
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
#endif // AAPT_RESOURCE_TABLE_H
|
||||
|
||||
@@ -1,202 +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 "Maybe.h"
|
||||
#include "NameMangler.h"
|
||||
#include "Resource.h"
|
||||
#include "ResourceTable.h"
|
||||
#include "ResourceTableResolver.h"
|
||||
#include "ResourceValues.h"
|
||||
#include "Util.h"
|
||||
|
||||
#include <androidfw/AssetManager.h>
|
||||
#include <androidfw/ResourceTypes.h>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
ResourceTableResolver::ResourceTableResolver(
|
||||
std::shared_ptr<const ResourceTable> table,
|
||||
const std::vector<std::shared_ptr<const android::AssetManager>>& sources) :
|
||||
mTable(table), mSources(sources) {
|
||||
for (const auto& assetManager : mSources) {
|
||||
const android::ResTable& resTable = assetManager->getResources(false);
|
||||
const size_t packageCount = resTable.getBasePackageCount();
|
||||
for (size_t i = 0; i < packageCount; i++) {
|
||||
std::u16string packageName = resTable.getBasePackageName(i).string();
|
||||
mIncludedPackages.insert(std::move(packageName));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Maybe<ResourceId> ResourceTableResolver::findId(const ResourceName& name) {
|
||||
Maybe<Entry> result = findAttribute(name);
|
||||
if (result) {
|
||||
return result.value().id;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
Maybe<IResolver::Entry> ResourceTableResolver::findAttribute(const ResourceName& name) {
|
||||
auto cacheIter = mCache.find(name);
|
||||
if (cacheIter != std::end(mCache)) {
|
||||
return Entry{ cacheIter->second.id, cacheIter->second.attr.get() };
|
||||
}
|
||||
|
||||
ResourceName mangledName;
|
||||
const ResourceName* nameToSearch = &name;
|
||||
if (name.package != mTable->getPackage()) {
|
||||
// This may be a reference to an included resource or
|
||||
// to a mangled resource.
|
||||
if (mIncludedPackages.find(name.package) == mIncludedPackages.end()) {
|
||||
// This is not in our included set, so mangle the name and
|
||||
// check for that.
|
||||
mangledName.entry = name.entry;
|
||||
NameMangler::mangle(name.package, &mangledName.entry);
|
||||
mangledName.package = mTable->getPackage();
|
||||
mangledName.type = name.type;
|
||||
nameToSearch = &mangledName;
|
||||
} else {
|
||||
const CacheEntry* cacheEntry = buildCacheEntry(name);
|
||||
if (cacheEntry) {
|
||||
return Entry{ cacheEntry->id, cacheEntry->attr.get() };
|
||||
}
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
const ResourceTableType* type;
|
||||
const ResourceEntry* entry;
|
||||
std::tie(type, entry) = mTable->findResource(*nameToSearch);
|
||||
if (type && entry) {
|
||||
Entry result = {};
|
||||
if (mTable->getPackageId() != ResourceTable::kUnsetPackageId &&
|
||||
type->typeId != ResourceTableType::kUnsetTypeId &&
|
||||
entry->entryId != ResourceEntry::kUnsetEntryId) {
|
||||
result.id = ResourceId(mTable->getPackageId(), type->typeId, entry->entryId);
|
||||
}
|
||||
|
||||
if (!entry->values.empty()) {
|
||||
visitFunc<Attribute>(*entry->values.front().value, [&result](Attribute& attr) {
|
||||
result.attr = &attr;
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
Maybe<ResourceName> ResourceTableResolver::findName(ResourceId resId) {
|
||||
for (const auto& assetManager : mSources) {
|
||||
const android::ResTable& table = assetManager->getResources(false);
|
||||
|
||||
android::ResTable::resource_name resourceName;
|
||||
if (!table.getResourceName(resId.id, false, &resourceName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const ResourceType* type = parseResourceType(StringPiece16(resourceName.type,
|
||||
resourceName.typeLen));
|
||||
assert(type);
|
||||
return ResourceName{
|
||||
{ resourceName.package, resourceName.packageLen },
|
||||
*type,
|
||||
{ resourceName.name, resourceName.nameLen } };
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* This is called when we need to lookup a resource name in the AssetManager.
|
||||
* Since the values in the AssetManager are not parsed like in a ResourceTable,
|
||||
* we must create Attribute objects here if we find them.
|
||||
*/
|
||||
const ResourceTableResolver::CacheEntry* ResourceTableResolver::buildCacheEntry(
|
||||
const ResourceName& name) {
|
||||
for (const auto& assetManager : mSources) {
|
||||
const android::ResTable& table = assetManager->getResources(false);
|
||||
|
||||
const StringPiece16 type16 = toString(name.type);
|
||||
ResourceId resId {
|
||||
table.identifierForName(
|
||||
name.entry.data(), name.entry.size(),
|
||||
type16.data(), type16.size(),
|
||||
name.package.data(), name.package.size())
|
||||
};
|
||||
|
||||
if (!resId.isValid()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
CacheEntry& entry = mCache[name];
|
||||
entry.id = resId;
|
||||
|
||||
//
|
||||
// Now check to see if this resource is an Attribute.
|
||||
//
|
||||
|
||||
const android::ResTable::bag_entry* bagBegin;
|
||||
ssize_t bags = table.lockBag(resId.id, &bagBegin);
|
||||
if (bags < 1) {
|
||||
table.unlockBag(bagBegin);
|
||||
return &entry;
|
||||
}
|
||||
|
||||
// Look for the ATTR_TYPE key in the bag and check the types it supports.
|
||||
uint32_t attrTypeMask = 0;
|
||||
for (ssize_t i = 0; i < bags; i++) {
|
||||
if (bagBegin[i].map.name.ident == android::ResTable_map::ATTR_TYPE) {
|
||||
attrTypeMask = bagBegin[i].map.value.data;
|
||||
}
|
||||
}
|
||||
|
||||
entry.attr = util::make_unique<Attribute>(false);
|
||||
|
||||
if (attrTypeMask & android::ResTable_map::TYPE_ENUM ||
|
||||
attrTypeMask & android::ResTable_map::TYPE_FLAGS) {
|
||||
for (ssize_t i = 0; i < bags; i++) {
|
||||
if (Res_INTERNALID(bagBegin[i].map.name.ident)) {
|
||||
// Internal IDs are special keys, which are not enum/flag symbols, so skip.
|
||||
continue;
|
||||
}
|
||||
|
||||
android::ResTable::resource_name symbolName;
|
||||
bool result = table.getResourceName(bagBegin[i].map.name.ident, false,
|
||||
&symbolName);
|
||||
assert(result);
|
||||
const ResourceType* type = parseResourceType(
|
||||
StringPiece16(symbolName.type, symbolName.typeLen));
|
||||
assert(type);
|
||||
|
||||
entry.attr->symbols.push_back(Attribute::Symbol{
|
||||
Reference(ResourceNameRef(
|
||||
StringPiece16(symbolName.package, symbolName.packageLen),
|
||||
*type,
|
||||
StringPiece16(symbolName.name, symbolName.nameLen))),
|
||||
bagBegin[i].map.value.data
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
entry.attr->typeMask |= attrTypeMask;
|
||||
table.unlockBag(bagBegin);
|
||||
return &entry;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
@@ -1,70 +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_RESOURCE_TABLE_RESOLVER_H
|
||||
#define AAPT_RESOURCE_TABLE_RESOLVER_H
|
||||
|
||||
#include "Maybe.h"
|
||||
#include "Resolver.h"
|
||||
#include "Resource.h"
|
||||
#include "ResourceTable.h"
|
||||
#include "ResourceValues.h"
|
||||
|
||||
#include <androidfw/AssetManager.h>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
/**
|
||||
* Encapsulates the search of library sources as well as the local ResourceTable.
|
||||
*/
|
||||
class ResourceTableResolver : public IResolver {
|
||||
public:
|
||||
/**
|
||||
* Creates a resolver with a local ResourceTable and an AssetManager
|
||||
* loaded with library packages.
|
||||
*/
|
||||
ResourceTableResolver(
|
||||
std::shared_ptr<const ResourceTable> table,
|
||||
const std::vector<std::shared_ptr<const android::AssetManager>>& sources);
|
||||
|
||||
ResourceTableResolver(const ResourceTableResolver&) = delete; // Not copyable.
|
||||
|
||||
virtual Maybe<ResourceId> findId(const ResourceName& name) override;
|
||||
|
||||
virtual Maybe<Entry> findAttribute(const ResourceName& name) override;
|
||||
|
||||
virtual Maybe<ResourceName> findName(ResourceId resId) override;
|
||||
|
||||
private:
|
||||
struct CacheEntry {
|
||||
ResourceId id;
|
||||
std::unique_ptr<Attribute> attr;
|
||||
};
|
||||
|
||||
const CacheEntry* buildCacheEntry(const ResourceName& name);
|
||||
|
||||
std::shared_ptr<const ResourceTable> mTable;
|
||||
std::vector<std::shared_ptr<const android::AssetManager>> mSources;
|
||||
std::map<ResourceName, CacheEntry> mCache;
|
||||
std::unordered_set<std::u16string> mIncludedPackages;
|
||||
};
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
#endif // AAPT_RESOURCE_TABLE_RESOLVER_H
|
||||
@@ -14,9 +14,12 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "Diagnostics.h"
|
||||
#include "ResourceTable.h"
|
||||
#include "ResourceValues.h"
|
||||
#include "Util.h"
|
||||
#include "util/Util.h"
|
||||
|
||||
#include "test/Common.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <gtest/gtest.h>
|
||||
@@ -25,204 +28,90 @@
|
||||
|
||||
namespace aapt {
|
||||
|
||||
struct TestValue : public Value {
|
||||
std::u16string value;
|
||||
struct ResourceTableTest : public ::testing::Test {
|
||||
struct EmptyDiagnostics : public IDiagnostics {
|
||||
void error(const DiagMessage& msg) override {}
|
||||
void warn(const DiagMessage& msg) override {}
|
||||
void note(const DiagMessage& msg) override {}
|
||||
};
|
||||
|
||||
TestValue(StringPiece16 str) : value(str.toString()) {
|
||||
}
|
||||
|
||||
TestValue* clone(StringPool* /*newPool*/) const override {
|
||||
return new TestValue(value);
|
||||
}
|
||||
|
||||
void print(std::ostream& out) const override {
|
||||
out << "(test) " << value;
|
||||
}
|
||||
|
||||
virtual void accept(ValueVisitor&, ValueVisitorArgs&&) override {}
|
||||
virtual void accept(ConstValueVisitor&, ValueVisitorArgs&&) const override {}
|
||||
EmptyDiagnostics mDiagnostics;
|
||||
};
|
||||
|
||||
struct TestWeakValue : public Value {
|
||||
bool isWeak() const override {
|
||||
return true;
|
||||
}
|
||||
|
||||
TestWeakValue* clone(StringPool* /*newPool*/) const override {
|
||||
return new TestWeakValue();
|
||||
}
|
||||
|
||||
void print(std::ostream& out) const override {
|
||||
out << "(test) [weak]";
|
||||
}
|
||||
|
||||
virtual void accept(ValueVisitor&, ValueVisitorArgs&&) override {}
|
||||
virtual void accept(ConstValueVisitor&, ValueVisitorArgs&&) const override {}
|
||||
};
|
||||
|
||||
TEST(ResourceTableTest, FailToAddResourceWithBadName) {
|
||||
TEST_F(ResourceTableTest, FailToAddResourceWithBadName) {
|
||||
ResourceTable table;
|
||||
table.setPackage(u"android");
|
||||
|
||||
EXPECT_FALSE(table.addResource(
|
||||
ResourceNameRef{ u"android", ResourceType::kId, u"hey,there" },
|
||||
{}, SourceLine{ "test.xml", 21 },
|
||||
util::make_unique<TestValue>(u"rawValue")));
|
||||
{}, Source{ "test.xml", 21 },
|
||||
util::make_unique<Id>(), &mDiagnostics));
|
||||
|
||||
EXPECT_FALSE(table.addResource(
|
||||
ResourceNameRef{ u"android", ResourceType::kId, u"hey:there" },
|
||||
{}, SourceLine{ "test.xml", 21 },
|
||||
util::make_unique<TestValue>(u"rawValue")));
|
||||
{}, Source{ "test.xml", 21 },
|
||||
util::make_unique<Id>(), &mDiagnostics));
|
||||
}
|
||||
|
||||
TEST(ResourceTableTest, AddOneResource) {
|
||||
const std::u16string kAndroidPackage = u"android";
|
||||
|
||||
TEST_F(ResourceTableTest, AddOneResource) {
|
||||
ResourceTable table;
|
||||
table.setPackage(kAndroidPackage);
|
||||
|
||||
const ResourceName name = { kAndroidPackage, ResourceType::kAttr, u"id" };
|
||||
EXPECT_TRUE(table.addResource(test::parseNameOrDie(u"@android:attr/id"), {},
|
||||
Source{ "test/path/file.xml", 23 },
|
||||
util::make_unique<Id>(), &mDiagnostics));
|
||||
|
||||
EXPECT_TRUE(table.addResource(name, {}, SourceLine{ "test/path/file.xml", 23 },
|
||||
util::make_unique<TestValue>(u"rawValue")));
|
||||
|
||||
const ResourceTableType* type;
|
||||
const ResourceEntry* entry;
|
||||
std::tie(type, entry) = table.findResource(name);
|
||||
ASSERT_NE(nullptr, type);
|
||||
ASSERT_NE(nullptr, entry);
|
||||
EXPECT_EQ(name.entry, entry->name);
|
||||
|
||||
ASSERT_NE(std::end(entry->values),
|
||||
std::find_if(std::begin(entry->values), std::end(entry->values),
|
||||
[](const ResourceConfigValue& val) -> bool {
|
||||
return val.config == ConfigDescription{};
|
||||
}));
|
||||
ASSERT_NE(nullptr, test::getValue<Id>(&table, u"@android:attr/id"));
|
||||
}
|
||||
|
||||
TEST(ResourceTableTest, AddMultipleResources) {
|
||||
const std::u16string kAndroidPackage = u"android";
|
||||
TEST_F(ResourceTableTest, AddMultipleResources) {
|
||||
ResourceTable table;
|
||||
table.setPackage(kAndroidPackage);
|
||||
|
||||
ConfigDescription config;
|
||||
ConfigDescription languageConfig;
|
||||
memcpy(languageConfig.language, "pl", sizeof(languageConfig.language));
|
||||
|
||||
EXPECT_TRUE(table.addResource(
|
||||
ResourceName{ kAndroidPackage, ResourceType::kAttr, u"layout_width" },
|
||||
config, SourceLine{ "test/path/file.xml", 10 },
|
||||
util::make_unique<TestValue>(u"rawValue")));
|
||||
test::parseNameOrDie(u"@android:attr/layout_width"),
|
||||
config, Source{ "test/path/file.xml", 10 },
|
||||
util::make_unique<Id>(), &mDiagnostics));
|
||||
|
||||
EXPECT_TRUE(table.addResource(
|
||||
ResourceName{ kAndroidPackage, ResourceType::kAttr, u"id" },
|
||||
config, SourceLine{ "test/path/file.xml", 12 },
|
||||
util::make_unique<TestValue>(u"rawValue")));
|
||||
test::parseNameOrDie(u"@android:attr/id"),
|
||||
config, Source{ "test/path/file.xml", 12 },
|
||||
util::make_unique<Id>(), &mDiagnostics));
|
||||
|
||||
EXPECT_TRUE(table.addResource(
|
||||
ResourceName{ kAndroidPackage, ResourceType::kString, u"ok" },
|
||||
config, SourceLine{ "test/path/file.xml", 14 },
|
||||
util::make_unique<TestValue>(u"Ok")));
|
||||
test::parseNameOrDie(u"@android:string/ok"),
|
||||
config, Source{ "test/path/file.xml", 14 },
|
||||
util::make_unique<Id>(), &mDiagnostics));
|
||||
|
||||
EXPECT_TRUE(table.addResource(
|
||||
ResourceName{ kAndroidPackage, ResourceType::kString, u"ok" },
|
||||
languageConfig, SourceLine{ "test/path/file.xml", 20 },
|
||||
util::make_unique<TestValue>(u"Tak")));
|
||||
test::parseNameOrDie(u"@android:string/ok"),
|
||||
languageConfig, Source{ "test/path/file.xml", 20 },
|
||||
util::make_unique<BinaryPrimitive>(android::Res_value{}), &mDiagnostics));
|
||||
|
||||
const auto endTypeIter = std::end(table);
|
||||
auto typeIter = std::begin(table);
|
||||
|
||||
ASSERT_NE(endTypeIter, typeIter);
|
||||
EXPECT_EQ(ResourceType::kAttr, (*typeIter)->type);
|
||||
|
||||
{
|
||||
const std::unique_ptr<ResourceTableType>& type = *typeIter;
|
||||
const auto endEntryIter = std::end(type->entries);
|
||||
auto entryIter = std::begin(type->entries);
|
||||
ASSERT_NE(endEntryIter, entryIter);
|
||||
EXPECT_EQ(std::u16string(u"id"), (*entryIter)->name);
|
||||
|
||||
++entryIter;
|
||||
ASSERT_NE(endEntryIter, entryIter);
|
||||
EXPECT_EQ(std::u16string(u"layout_width"), (*entryIter)->name);
|
||||
|
||||
++entryIter;
|
||||
ASSERT_EQ(endEntryIter, entryIter);
|
||||
}
|
||||
|
||||
++typeIter;
|
||||
ASSERT_NE(endTypeIter, typeIter);
|
||||
EXPECT_EQ(ResourceType::kString, (*typeIter)->type);
|
||||
|
||||
{
|
||||
const std::unique_ptr<ResourceTableType>& type = *typeIter;
|
||||
const auto endEntryIter = std::end(type->entries);
|
||||
auto entryIter = std::begin(type->entries);
|
||||
ASSERT_NE(endEntryIter, entryIter);
|
||||
EXPECT_EQ(std::u16string(u"ok"), (*entryIter)->name);
|
||||
|
||||
{
|
||||
const std::unique_ptr<ResourceEntry>& entry = *entryIter;
|
||||
const auto endConfigIter = std::end(entry->values);
|
||||
auto configIter = std::begin(entry->values);
|
||||
|
||||
ASSERT_NE(endConfigIter, configIter);
|
||||
EXPECT_EQ(config, configIter->config);
|
||||
const TestValue* value =
|
||||
dynamic_cast<const TestValue*>(configIter->value.get());
|
||||
ASSERT_NE(nullptr, value);
|
||||
EXPECT_EQ(std::u16string(u"Ok"), value->value);
|
||||
|
||||
++configIter;
|
||||
ASSERT_NE(endConfigIter, configIter);
|
||||
EXPECT_EQ(languageConfig, configIter->config);
|
||||
EXPECT_NE(nullptr, configIter->value);
|
||||
|
||||
value = dynamic_cast<const TestValue*>(configIter->value.get());
|
||||
ASSERT_NE(nullptr, value);
|
||||
EXPECT_EQ(std::u16string(u"Tak"), value->value);
|
||||
|
||||
++configIter;
|
||||
EXPECT_EQ(endConfigIter, configIter);
|
||||
}
|
||||
|
||||
++entryIter;
|
||||
ASSERT_EQ(endEntryIter, entryIter);
|
||||
}
|
||||
|
||||
++typeIter;
|
||||
EXPECT_EQ(endTypeIter, typeIter);
|
||||
ASSERT_NE(nullptr, test::getValue<Id>(&table, u"@android:attr/layout_width"));
|
||||
ASSERT_NE(nullptr, test::getValue<Id>(&table, u"@android:attr/id"));
|
||||
ASSERT_NE(nullptr, test::getValue<Id>(&table, u"@android:string/ok"));
|
||||
ASSERT_NE(nullptr, test::getValueForConfig<BinaryPrimitive>(&table, u"@android:string/ok",
|
||||
languageConfig));
|
||||
}
|
||||
|
||||
TEST(ResourceTableTest, OverrideWeakResourceValue) {
|
||||
const std::u16string kAndroid = u"android";
|
||||
|
||||
TEST_F(ResourceTableTest, OverrideWeakResourceValue) {
|
||||
ResourceTable table;
|
||||
table.setPackage(kAndroid);
|
||||
table.setPackageId(0x01);
|
||||
|
||||
ASSERT_TRUE(table.addResource(
|
||||
ResourceName{ kAndroid, ResourceType::kAttr, u"foo" },
|
||||
{}, {}, util::make_unique<TestWeakValue>()));
|
||||
ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:attr/foo"), {}, {},
|
||||
util::make_unique<Attribute>(true), &mDiagnostics));
|
||||
|
||||
const ResourceTableType* type;
|
||||
const ResourceEntry* entry;
|
||||
std::tie(type, entry) = table.findResource(
|
||||
ResourceNameRef{ kAndroid, ResourceType::kAttr, u"foo" });
|
||||
ASSERT_NE(nullptr, type);
|
||||
ASSERT_NE(nullptr, entry);
|
||||
ASSERT_EQ(entry->values.size(), 1u);
|
||||
EXPECT_TRUE(entry->values.front().value->isWeak());
|
||||
Attribute* attr = test::getValue<Attribute>(&table, u"@android:attr/foo");
|
||||
ASSERT_NE(nullptr, attr);
|
||||
EXPECT_TRUE(attr->isWeak());
|
||||
|
||||
ASSERT_TRUE(table.addResource(ResourceName{ kAndroid, ResourceType::kAttr, u"foo" }, {}, {},
|
||||
util::make_unique<TestValue>(u"bar")));
|
||||
ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:attr/foo"), {}, {},
|
||||
util::make_unique<Attribute>(false), &mDiagnostics));
|
||||
|
||||
std::tie(type, entry) = table.findResource(
|
||||
ResourceNameRef{ kAndroid, ResourceType::kAttr, u"foo" });
|
||||
ASSERT_NE(nullptr, type);
|
||||
ASSERT_NE(nullptr, entry);
|
||||
ASSERT_EQ(entry->values.size(), 1u);
|
||||
EXPECT_FALSE(entry->values.front().value->isWeak());
|
||||
attr = test::getValue<Attribute>(&table, u"@android:attr/foo");
|
||||
ASSERT_NE(nullptr, attr);
|
||||
EXPECT_FALSE(attr->isWeak());
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
481
tools/aapt2/ResourceUtils.cpp
Normal file
481
tools/aapt2/ResourceUtils.cpp
Normal file
@@ -0,0 +1,481 @@
|
||||
/*
|
||||
* 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 "ResourceUtils.h"
|
||||
#include "util/Util.h"
|
||||
|
||||
#include <androidfw/ResourceTypes.h>
|
||||
#include <sstream>
|
||||
|
||||
namespace aapt {
|
||||
namespace ResourceUtils {
|
||||
|
||||
void extractResourceName(const StringPiece16& str, StringPiece16* outPackage,
|
||||
StringPiece16* outType, StringPiece16* outEntry) {
|
||||
const char16_t* start = str.data();
|
||||
const char16_t* end = start + str.size();
|
||||
const char16_t* current = start;
|
||||
while (current != end) {
|
||||
if (outType->size() == 0 && *current == u'/') {
|
||||
outType->assign(start, current - start);
|
||||
start = current + 1;
|
||||
} else if (outPackage->size() == 0 && *current == u':') {
|
||||
outPackage->assign(start, current - start);
|
||||
start = current + 1;
|
||||
}
|
||||
current++;
|
||||
}
|
||||
outEntry->assign(start, end - start);
|
||||
}
|
||||
|
||||
bool tryParseReference(const StringPiece16& str, ResourceNameRef* outRef, bool* outCreate,
|
||||
bool* outPrivate) {
|
||||
StringPiece16 trimmedStr(util::trimWhitespace(str));
|
||||
if (trimmedStr.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool create = false;
|
||||
bool priv = false;
|
||||
if (trimmedStr.data()[0] == u'@') {
|
||||
size_t offset = 1;
|
||||
if (trimmedStr.data()[1] == u'+') {
|
||||
create = true;
|
||||
offset += 1;
|
||||
} else if (trimmedStr.data()[1] == u'*') {
|
||||
priv = true;
|
||||
offset += 1;
|
||||
}
|
||||
StringPiece16 package;
|
||||
StringPiece16 type;
|
||||
StringPiece16 entry;
|
||||
extractResourceName(trimmedStr.substr(offset, trimmedStr.size() - offset), &package, &type,
|
||||
&entry);
|
||||
|
||||
const ResourceType* parsedType = parseResourceType(type);
|
||||
if (!parsedType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (create && *parsedType != ResourceType::kId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
outRef->package = package;
|
||||
outRef->type = *parsedType;
|
||||
outRef->entry = entry;
|
||||
if (outCreate) {
|
||||
*outCreate = create;
|
||||
}
|
||||
if (outPrivate) {
|
||||
*outPrivate = priv;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool tryParseAttributeReference(const StringPiece16& str, ResourceNameRef* outRef) {
|
||||
StringPiece16 trimmedStr(util::trimWhitespace(str));
|
||||
if (trimmedStr.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (*trimmedStr.data() == u'?') {
|
||||
StringPiece16 package;
|
||||
StringPiece16 type;
|
||||
StringPiece16 entry;
|
||||
extractResourceName(trimmedStr.substr(1, trimmedStr.size() - 1), &package, &type, &entry);
|
||||
|
||||
if (!type.empty() && type != u"attr") {
|
||||
return false;
|
||||
}
|
||||
|
||||
outRef->package = package;
|
||||
outRef->type = ResourceType::kAttr;
|
||||
outRef->entry = entry;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Style parent's are a bit different. We accept the following formats:
|
||||
*
|
||||
* @[package:]style/<entry>
|
||||
* ?[package:]style/<entry>
|
||||
* <package>:[style/]<entry>
|
||||
* [package:style/]<entry>
|
||||
*/
|
||||
Maybe<Reference> parseStyleParentReference(const StringPiece16& str, std::string* outError) {
|
||||
if (str.empty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
StringPiece16 name = str;
|
||||
|
||||
bool hasLeadingIdentifiers = false;
|
||||
bool privateRef = false;
|
||||
|
||||
// Skip over these identifiers. A style's parent is a normal reference.
|
||||
if (name.data()[0] == u'@' || name.data()[0] == u'?') {
|
||||
hasLeadingIdentifiers = true;
|
||||
name = name.substr(1, name.size() - 1);
|
||||
if (name.data()[0] == u'*') {
|
||||
privateRef = true;
|
||||
name = name.substr(1, name.size() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
ResourceNameRef ref;
|
||||
ref.type = ResourceType::kStyle;
|
||||
|
||||
StringPiece16 typeStr;
|
||||
extractResourceName(name, &ref.package, &typeStr, &ref.entry);
|
||||
if (!typeStr.empty()) {
|
||||
// If we have a type, make sure it is a Style.
|
||||
const ResourceType* parsedType = parseResourceType(typeStr);
|
||||
if (!parsedType || *parsedType != ResourceType::kStyle) {
|
||||
std::stringstream err;
|
||||
err << "invalid resource type '" << typeStr << "' for parent of style";
|
||||
*outError = err.str();
|
||||
return {};
|
||||
}
|
||||
} else {
|
||||
// No type was defined, this should not have a leading identifier.
|
||||
if (hasLeadingIdentifiers) {
|
||||
std::stringstream err;
|
||||
err << "invalid parent reference '" << str << "'";
|
||||
*outError = err.str();
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasLeadingIdentifiers && ref.package.empty() && !typeStr.empty()) {
|
||||
std::stringstream err;
|
||||
err << "invalid parent reference '" << str << "'";
|
||||
*outError = err.str();
|
||||
return {};
|
||||
}
|
||||
|
||||
Reference result(ref);
|
||||
result.privateReference = privateRef;
|
||||
return result;
|
||||
}
|
||||
|
||||
std::unique_ptr<Reference> tryParseReference(const StringPiece16& str, bool* outCreate) {
|
||||
ResourceNameRef ref;
|
||||
bool privateRef = false;
|
||||
if (tryParseReference(str, &ref, outCreate, &privateRef)) {
|
||||
std::unique_ptr<Reference> value = util::make_unique<Reference>(ref);
|
||||
value->privateReference = privateRef;
|
||||
return value;
|
||||
}
|
||||
|
||||
if (tryParseAttributeReference(str, &ref)) {
|
||||
if (outCreate) {
|
||||
*outCreate = false;
|
||||
}
|
||||
return util::make_unique<Reference>(ref, Reference::Type::kAttribute);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
std::unique_ptr<BinaryPrimitive> tryParseNullOrEmpty(const StringPiece16& str) {
|
||||
StringPiece16 trimmedStr(util::trimWhitespace(str));
|
||||
android::Res_value value = { };
|
||||
if (trimmedStr == u"@null") {
|
||||
// TYPE_NULL with data set to 0 is interpreted by the runtime as an error.
|
||||
// Instead we set the data type to TYPE_REFERENCE with a value of 0.
|
||||
value.dataType = android::Res_value::TYPE_REFERENCE;
|
||||
} else if (trimmedStr == u"@empty") {
|
||||
// TYPE_NULL with value of DATA_NULL_EMPTY is handled fine by the runtime.
|
||||
value.dataType = android::Res_value::TYPE_NULL;
|
||||
value.data = android::Res_value::DATA_NULL_EMPTY;
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
return util::make_unique<BinaryPrimitive>(value);
|
||||
}
|
||||
|
||||
std::unique_ptr<BinaryPrimitive> tryParseEnumSymbol(const Attribute* enumAttr,
|
||||
const StringPiece16& str) {
|
||||
StringPiece16 trimmedStr(util::trimWhitespace(str));
|
||||
for (const Attribute::Symbol& symbol : enumAttr->symbols) {
|
||||
// Enum symbols are stored as @package:id/symbol resources,
|
||||
// so we need to match against the 'entry' part of the identifier.
|
||||
const ResourceName& enumSymbolResourceName = symbol.symbol.name.value();
|
||||
if (trimmedStr == enumSymbolResourceName.entry) {
|
||||
android::Res_value value = { };
|
||||
value.dataType = android::Res_value::TYPE_INT_DEC;
|
||||
value.data = symbol.value;
|
||||
return util::make_unique<BinaryPrimitive>(value);
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
std::unique_ptr<BinaryPrimitive> tryParseFlagSymbol(const Attribute* flagAttr,
|
||||
const StringPiece16& str) {
|
||||
android::Res_value flags = { };
|
||||
flags.dataType = android::Res_value::TYPE_INT_DEC;
|
||||
|
||||
for (StringPiece16 part : util::tokenize(str, u'|')) {
|
||||
StringPiece16 trimmedPart = util::trimWhitespace(part);
|
||||
|
||||
bool flagSet = false;
|
||||
for (const Attribute::Symbol& symbol : flagAttr->symbols) {
|
||||
// Flag symbols are stored as @package:id/symbol resources,
|
||||
// so we need to match against the 'entry' part of the identifier.
|
||||
const ResourceName& flagSymbolResourceName = symbol.symbol.name.value();
|
||||
if (trimmedPart == flagSymbolResourceName.entry) {
|
||||
flags.data |= symbol.value;
|
||||
flagSet = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!flagSet) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
return util::make_unique<BinaryPrimitive>(flags);
|
||||
}
|
||||
|
||||
static uint32_t parseHex(char16_t c, bool* outError) {
|
||||
if (c >= u'0' && c <= u'9') {
|
||||
return c - u'0';
|
||||
} else if (c >= u'a' && c <= u'f') {
|
||||
return c - u'a' + 0xa;
|
||||
} else if (c >= u'A' && c <= u'F') {
|
||||
return c - u'A' + 0xa;
|
||||
} else {
|
||||
*outError = true;
|
||||
return 0xffffffffu;
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<BinaryPrimitive> tryParseColor(const StringPiece16& str) {
|
||||
StringPiece16 colorStr(util::trimWhitespace(str));
|
||||
const char16_t* start = colorStr.data();
|
||||
const size_t len = colorStr.size();
|
||||
if (len == 0 || start[0] != u'#') {
|
||||
return {};
|
||||
}
|
||||
|
||||
android::Res_value value = { };
|
||||
bool error = false;
|
||||
if (len == 4) {
|
||||
value.dataType = android::Res_value::TYPE_INT_COLOR_RGB4;
|
||||
value.data = 0xff000000u;
|
||||
value.data |= parseHex(start[1], &error) << 20;
|
||||
value.data |= parseHex(start[1], &error) << 16;
|
||||
value.data |= parseHex(start[2], &error) << 12;
|
||||
value.data |= parseHex(start[2], &error) << 8;
|
||||
value.data |= parseHex(start[3], &error) << 4;
|
||||
value.data |= parseHex(start[3], &error);
|
||||
} else if (len == 5) {
|
||||
value.dataType = android::Res_value::TYPE_INT_COLOR_ARGB4;
|
||||
value.data |= parseHex(start[1], &error) << 28;
|
||||
value.data |= parseHex(start[1], &error) << 24;
|
||||
value.data |= parseHex(start[2], &error) << 20;
|
||||
value.data |= parseHex(start[2], &error) << 16;
|
||||
value.data |= parseHex(start[3], &error) << 12;
|
||||
value.data |= parseHex(start[3], &error) << 8;
|
||||
value.data |= parseHex(start[4], &error) << 4;
|
||||
value.data |= parseHex(start[4], &error);
|
||||
} else if (len == 7) {
|
||||
value.dataType = android::Res_value::TYPE_INT_COLOR_RGB8;
|
||||
value.data = 0xff000000u;
|
||||
value.data |= parseHex(start[1], &error) << 20;
|
||||
value.data |= parseHex(start[2], &error) << 16;
|
||||
value.data |= parseHex(start[3], &error) << 12;
|
||||
value.data |= parseHex(start[4], &error) << 8;
|
||||
value.data |= parseHex(start[5], &error) << 4;
|
||||
value.data |= parseHex(start[6], &error);
|
||||
} else if (len == 9) {
|
||||
value.dataType = android::Res_value::TYPE_INT_COLOR_ARGB8;
|
||||
value.data |= parseHex(start[1], &error) << 28;
|
||||
value.data |= parseHex(start[2], &error) << 24;
|
||||
value.data |= parseHex(start[3], &error) << 20;
|
||||
value.data |= parseHex(start[4], &error) << 16;
|
||||
value.data |= parseHex(start[5], &error) << 12;
|
||||
value.data |= parseHex(start[6], &error) << 8;
|
||||
value.data |= parseHex(start[7], &error) << 4;
|
||||
value.data |= parseHex(start[8], &error);
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
return error ? std::unique_ptr<BinaryPrimitive>() : util::make_unique<BinaryPrimitive>(value);
|
||||
}
|
||||
|
||||
std::unique_ptr<BinaryPrimitive> tryParseBool(const StringPiece16& str) {
|
||||
StringPiece16 trimmedStr(util::trimWhitespace(str));
|
||||
uint32_t data = 0;
|
||||
if (trimmedStr == u"true" || trimmedStr == u"TRUE") {
|
||||
data = 0xffffffffu;
|
||||
} else if (trimmedStr != u"false" && trimmedStr != u"FALSE") {
|
||||
return {};
|
||||
}
|
||||
android::Res_value value = { };
|
||||
value.dataType = android::Res_value::TYPE_INT_BOOLEAN;
|
||||
value.data = data;
|
||||
return util::make_unique<BinaryPrimitive>(value);
|
||||
}
|
||||
|
||||
std::unique_ptr<BinaryPrimitive> tryParseInt(const StringPiece16& str) {
|
||||
android::Res_value value;
|
||||
if (!android::ResTable::stringToInt(str.data(), str.size(), &value)) {
|
||||
return {};
|
||||
}
|
||||
return util::make_unique<BinaryPrimitive>(value);
|
||||
}
|
||||
|
||||
std::unique_ptr<BinaryPrimitive> tryParseFloat(const StringPiece16& str) {
|
||||
android::Res_value value;
|
||||
if (!android::ResTable::stringToFloat(str.data(), str.size(), &value)) {
|
||||
return {};
|
||||
}
|
||||
return util::make_unique<BinaryPrimitive>(value);
|
||||
}
|
||||
|
||||
uint32_t androidTypeToAttributeTypeMask(uint16_t type) {
|
||||
switch (type) {
|
||||
case android::Res_value::TYPE_NULL:
|
||||
case android::Res_value::TYPE_REFERENCE:
|
||||
case android::Res_value::TYPE_ATTRIBUTE:
|
||||
case android::Res_value::TYPE_DYNAMIC_REFERENCE:
|
||||
return android::ResTable_map::TYPE_REFERENCE;
|
||||
|
||||
case android::Res_value::TYPE_STRING:
|
||||
return android::ResTable_map::TYPE_STRING;
|
||||
|
||||
case android::Res_value::TYPE_FLOAT:
|
||||
return android::ResTable_map::TYPE_FLOAT;
|
||||
|
||||
case android::Res_value::TYPE_DIMENSION:
|
||||
return android::ResTable_map::TYPE_DIMENSION;
|
||||
|
||||
case android::Res_value::TYPE_FRACTION:
|
||||
return android::ResTable_map::TYPE_FRACTION;
|
||||
|
||||
case android::Res_value::TYPE_INT_DEC:
|
||||
case android::Res_value::TYPE_INT_HEX:
|
||||
return android::ResTable_map::TYPE_INTEGER | android::ResTable_map::TYPE_ENUM
|
||||
| android::ResTable_map::TYPE_FLAGS;
|
||||
|
||||
case android::Res_value::TYPE_INT_BOOLEAN:
|
||||
return android::ResTable_map::TYPE_BOOLEAN;
|
||||
|
||||
case android::Res_value::TYPE_INT_COLOR_ARGB8:
|
||||
case android::Res_value::TYPE_INT_COLOR_RGB8:
|
||||
case android::Res_value::TYPE_INT_COLOR_ARGB4:
|
||||
case android::Res_value::TYPE_INT_COLOR_RGB4:
|
||||
return android::ResTable_map::TYPE_COLOR;
|
||||
|
||||
default:
|
||||
return 0;
|
||||
};
|
||||
}
|
||||
|
||||
std::unique_ptr<Item> parseItemForAttribute(
|
||||
const StringPiece16& value, uint32_t typeMask,
|
||||
std::function<void(const ResourceName&)> onCreateReference) {
|
||||
std::unique_ptr<BinaryPrimitive> nullOrEmpty = tryParseNullOrEmpty(value);
|
||||
if (nullOrEmpty) {
|
||||
return std::move(nullOrEmpty);
|
||||
}
|
||||
|
||||
bool create = false;
|
||||
std::unique_ptr<Reference> reference = tryParseReference(value, &create);
|
||||
if (reference) {
|
||||
if (create && onCreateReference) {
|
||||
onCreateReference(reference->name.value());
|
||||
}
|
||||
return std::move(reference);
|
||||
}
|
||||
|
||||
if (typeMask & android::ResTable_map::TYPE_COLOR) {
|
||||
// Try parsing this as a color.
|
||||
std::unique_ptr<BinaryPrimitive> color = tryParseColor(value);
|
||||
if (color) {
|
||||
return std::move(color);
|
||||
}
|
||||
}
|
||||
|
||||
if (typeMask & android::ResTable_map::TYPE_BOOLEAN) {
|
||||
// Try parsing this as a boolean.
|
||||
std::unique_ptr<BinaryPrimitive> boolean = tryParseBool(value);
|
||||
if (boolean) {
|
||||
return std::move(boolean);
|
||||
}
|
||||
}
|
||||
|
||||
if (typeMask & android::ResTable_map::TYPE_INTEGER) {
|
||||
// Try parsing this as an integer.
|
||||
std::unique_ptr<BinaryPrimitive> integer = tryParseInt(value);
|
||||
if (integer) {
|
||||
return std::move(integer);
|
||||
}
|
||||
}
|
||||
|
||||
const uint32_t floatMask = android::ResTable_map::TYPE_FLOAT
|
||||
| android::ResTable_map::TYPE_DIMENSION | android::ResTable_map::TYPE_FRACTION;
|
||||
if (typeMask & floatMask) {
|
||||
// Try parsing this as a float.
|
||||
std::unique_ptr<BinaryPrimitive> floatingPoint = tryParseFloat(value);
|
||||
if (floatingPoint) {
|
||||
if (typeMask & androidTypeToAttributeTypeMask(floatingPoint->value.dataType)) {
|
||||
return std::move(floatingPoint);
|
||||
}
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* We successively try to parse the string as a resource type that the Attribute
|
||||
* allows.
|
||||
*/
|
||||
std::unique_ptr<Item> parseItemForAttribute(
|
||||
const StringPiece16& str, const Attribute* attr,
|
||||
std::function<void(const ResourceName&)> onCreateReference) {
|
||||
const uint32_t typeMask = attr->typeMask;
|
||||
std::unique_ptr<Item> value = parseItemForAttribute(str, typeMask, onCreateReference);
|
||||
if (value) {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (typeMask & android::ResTable_map::TYPE_ENUM) {
|
||||
// Try parsing this as an enum.
|
||||
std::unique_ptr<BinaryPrimitive> enumValue = tryParseEnumSymbol(attr, str);
|
||||
if (enumValue) {
|
||||
return std::move(enumValue);
|
||||
}
|
||||
}
|
||||
|
||||
if (typeMask & android::ResTable_map::TYPE_FLAGS) {
|
||||
// Try parsing this as a flag.
|
||||
std::unique_ptr<BinaryPrimitive> flagValue = tryParseFlagSymbol(attr, str);
|
||||
if (flagValue) {
|
||||
return std::move(flagValue);
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace ResourceUtils
|
||||
} // namespace aapt
|
||||
137
tools/aapt2/ResourceUtils.h
Normal file
137
tools/aapt2/ResourceUtils.h
Normal file
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
* 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_RESOURCEUTILS_H
|
||||
#define AAPT_RESOURCEUTILS_H
|
||||
|
||||
#include "Resource.h"
|
||||
#include "ResourceValues.h"
|
||||
#include "util/StringPiece.h"
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
namespace aapt {
|
||||
namespace ResourceUtils {
|
||||
|
||||
/*
|
||||
* Extracts the package, type, and name from a string of the format:
|
||||
*
|
||||
* [package:]type/name
|
||||
*
|
||||
* where the package can be empty. Validation must be performed on each
|
||||
* individual extracted piece to verify that the pieces are valid.
|
||||
*/
|
||||
void extractResourceName(const StringPiece16& str, StringPiece16* outPackage,
|
||||
StringPiece16* outType, StringPiece16* outEntry);
|
||||
|
||||
/*
|
||||
* Returns true if the string was parsed as a reference (@[+][package:]type/name), with
|
||||
* `outReference` set to the parsed reference.
|
||||
*
|
||||
* If '+' was present in the reference, `outCreate` is set to true.
|
||||
* If '*' was present in the reference, `outPrivate` is set to true.
|
||||
*/
|
||||
bool tryParseReference(const StringPiece16& str, ResourceNameRef* outReference,
|
||||
bool* outCreate = nullptr, bool* outPrivate = nullptr);
|
||||
|
||||
/*
|
||||
* Returns true if the string was parsed as an attribute reference (?[package:]type/name),
|
||||
* with `outReference` set to the parsed reference.
|
||||
*/
|
||||
bool tryParseAttributeReference(const StringPiece16& str, ResourceNameRef* outReference);
|
||||
|
||||
/*
|
||||
* Returns a Reference, or None Maybe instance if the string `str` was parsed as a
|
||||
* valid reference to a style.
|
||||
* The format for a style parent is slightly more flexible than a normal reference:
|
||||
*
|
||||
* @[package:]style/<entry> or
|
||||
* ?[package:]style/<entry> or
|
||||
* <package>:[style/]<entry>
|
||||
*/
|
||||
Maybe<Reference> parseStyleParentReference(const StringPiece16& str, std::string* outError);
|
||||
|
||||
/*
|
||||
* Returns a Reference object if the string was parsed as a resource or attribute reference,
|
||||
* ( @[+][package:]type/name | ?[package:]type/name ) setting outCreate to true if
|
||||
* the '+' was present in the string.
|
||||
*/
|
||||
std::unique_ptr<Reference> tryParseReference(const StringPiece16& str, bool* outCreate = nullptr);
|
||||
|
||||
/*
|
||||
* Returns a BinaryPrimitve object representing @null or @empty if the string was parsed
|
||||
* as one.
|
||||
*/
|
||||
std::unique_ptr<BinaryPrimitive> tryParseNullOrEmpty(const StringPiece16& str);
|
||||
|
||||
/*
|
||||
* Returns a BinaryPrimitve object representing a color if the string was parsed
|
||||
* as one.
|
||||
*/
|
||||
std::unique_ptr<BinaryPrimitive> tryParseColor(const StringPiece16& str);
|
||||
|
||||
/*
|
||||
* Returns a BinaryPrimitve object representing a boolean if the string was parsed
|
||||
* as one.
|
||||
*/
|
||||
std::unique_ptr<BinaryPrimitive> tryParseBool(const StringPiece16& str);
|
||||
|
||||
/*
|
||||
* Returns a BinaryPrimitve object representing an integer if the string was parsed
|
||||
* as one.
|
||||
*/
|
||||
std::unique_ptr<BinaryPrimitive> tryParseInt(const StringPiece16& str);
|
||||
|
||||
/*
|
||||
* Returns a BinaryPrimitve object representing a floating point number
|
||||
* (float, dimension, etc) if the string was parsed as one.
|
||||
*/
|
||||
std::unique_ptr<BinaryPrimitive> tryParseFloat(const StringPiece16& str);
|
||||
|
||||
/*
|
||||
* Returns a BinaryPrimitve object representing an enum symbol if the string was parsed
|
||||
* as one.
|
||||
*/
|
||||
std::unique_ptr<BinaryPrimitive> tryParseEnumSymbol(const Attribute* enumAttr,
|
||||
const StringPiece16& str);
|
||||
|
||||
/*
|
||||
* Returns a BinaryPrimitve object representing a flag symbol if the string was parsed
|
||||
* as one.
|
||||
*/
|
||||
std::unique_ptr<BinaryPrimitive> tryParseFlagSymbol(const Attribute* enumAttr,
|
||||
const StringPiece16& str);
|
||||
/*
|
||||
* Try to convert a string to an Item for the given attribute. The attribute will
|
||||
* restrict what values the string can be converted to.
|
||||
* The callback function onCreateReference is called when the parsed item is a
|
||||
* reference to an ID that must be created (@+id/foo).
|
||||
*/
|
||||
std::unique_ptr<Item> parseItemForAttribute(
|
||||
const StringPiece16& value, const Attribute* attr,
|
||||
std::function<void(const ResourceName&)> onCreateReference = {});
|
||||
|
||||
std::unique_ptr<Item> parseItemForAttribute(
|
||||
const StringPiece16& value, uint32_t typeMask,
|
||||
std::function<void(const ResourceName&)> onCreateReference = {});
|
||||
|
||||
uint32_t androidTypeToAttributeTypeMask(uint16_t type);
|
||||
|
||||
} // namespace ResourceUtils
|
||||
} // namespace aapt
|
||||
|
||||
#endif /* AAPT_RESOURCEUTILS_H */
|
||||
127
tools/aapt2/ResourceUtils_test.cpp
Normal file
127
tools/aapt2/ResourceUtils_test.cpp
Normal file
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* 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 "Resource.h"
|
||||
#include "ResourceUtils.h"
|
||||
|
||||
#include "test/Common.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
TEST(ResourceUtilsTest, ParseReferenceWithNoPackage) {
|
||||
ResourceNameRef expected = { {}, ResourceType::kColor, u"foo" };
|
||||
ResourceNameRef actual;
|
||||
bool create = false;
|
||||
bool privateRef = false;
|
||||
EXPECT_TRUE(ResourceUtils::tryParseReference(u"@color/foo", &actual, &create, &privateRef));
|
||||
EXPECT_EQ(expected, actual);
|
||||
EXPECT_FALSE(create);
|
||||
EXPECT_FALSE(privateRef);
|
||||
}
|
||||
|
||||
TEST(ResourceUtilsTest, ParseReferenceWithPackage) {
|
||||
ResourceNameRef expected = { u"android", ResourceType::kColor, u"foo" };
|
||||
ResourceNameRef actual;
|
||||
bool create = false;
|
||||
bool privateRef = false;
|
||||
EXPECT_TRUE(ResourceUtils::tryParseReference(u"@android:color/foo", &actual, &create,
|
||||
&privateRef));
|
||||
EXPECT_EQ(expected, actual);
|
||||
EXPECT_FALSE(create);
|
||||
EXPECT_FALSE(privateRef);
|
||||
}
|
||||
|
||||
TEST(ResourceUtilsTest, ParseReferenceWithSurroundingWhitespace) {
|
||||
ResourceNameRef expected = { u"android", ResourceType::kColor, u"foo" };
|
||||
ResourceNameRef actual;
|
||||
bool create = false;
|
||||
bool privateRef = false;
|
||||
EXPECT_TRUE(ResourceUtils::tryParseReference(u"\t @android:color/foo\n \n\t", &actual,
|
||||
&create, &privateRef));
|
||||
EXPECT_EQ(expected, actual);
|
||||
EXPECT_FALSE(create);
|
||||
EXPECT_FALSE(privateRef);
|
||||
}
|
||||
|
||||
TEST(ResourceUtilsTest, ParseAutoCreateIdReference) {
|
||||
ResourceNameRef expected = { u"android", ResourceType::kId, u"foo" };
|
||||
ResourceNameRef actual;
|
||||
bool create = false;
|
||||
bool privateRef = false;
|
||||
EXPECT_TRUE(ResourceUtils::tryParseReference(u"@+android:id/foo", &actual, &create,
|
||||
&privateRef));
|
||||
EXPECT_EQ(expected, actual);
|
||||
EXPECT_TRUE(create);
|
||||
EXPECT_FALSE(privateRef);
|
||||
}
|
||||
|
||||
TEST(ResourceUtilsTest, ParsePrivateReference) {
|
||||
ResourceNameRef expected = { u"android", ResourceType::kId, u"foo" };
|
||||
ResourceNameRef actual;
|
||||
bool create = false;
|
||||
bool privateRef = false;
|
||||
EXPECT_TRUE(ResourceUtils::tryParseReference(u"@*android:id/foo", &actual, &create,
|
||||
&privateRef));
|
||||
EXPECT_EQ(expected, actual);
|
||||
EXPECT_FALSE(create);
|
||||
EXPECT_TRUE(privateRef);
|
||||
}
|
||||
|
||||
TEST(ResourceUtilsTest, FailToParseAutoCreateNonIdReference) {
|
||||
bool create = false;
|
||||
bool privateRef = false;
|
||||
ResourceNameRef actual;
|
||||
EXPECT_FALSE(ResourceUtils::tryParseReference(u"@+android:color/foo", &actual, &create,
|
||||
&privateRef));
|
||||
}
|
||||
|
||||
TEST(ResourceUtilsTest, ParseStyleParentReference) {
|
||||
const ResourceName kAndroidStyleFooName = { u"android", ResourceType::kStyle, u"foo" };
|
||||
const ResourceName kStyleFooName = { {}, ResourceType::kStyle, u"foo" };
|
||||
|
||||
std::string errStr;
|
||||
Maybe<Reference> ref = ResourceUtils::parseStyleParentReference(u"@android:style/foo", &errStr);
|
||||
AAPT_ASSERT_TRUE(ref);
|
||||
EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName);
|
||||
|
||||
ref = ResourceUtils::parseStyleParentReference(u"@style/foo", &errStr);
|
||||
AAPT_ASSERT_TRUE(ref);
|
||||
EXPECT_EQ(ref.value().name.value(), kStyleFooName);
|
||||
|
||||
ref = ResourceUtils::parseStyleParentReference(u"?android:style/foo", &errStr);
|
||||
AAPT_ASSERT_TRUE(ref);
|
||||
EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName);
|
||||
|
||||
ref = ResourceUtils::parseStyleParentReference(u"?style/foo", &errStr);
|
||||
AAPT_ASSERT_TRUE(ref);
|
||||
EXPECT_EQ(ref.value().name.value(), kStyleFooName);
|
||||
|
||||
ref = ResourceUtils::parseStyleParentReference(u"android:style/foo", &errStr);
|
||||
AAPT_ASSERT_TRUE(ref);
|
||||
EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName);
|
||||
|
||||
ref = ResourceUtils::parseStyleParentReference(u"android:foo", &errStr);
|
||||
AAPT_ASSERT_TRUE(ref);
|
||||
EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName);
|
||||
|
||||
ref = ResourceUtils::parseStyleParentReference(u"foo", &errStr);
|
||||
AAPT_ASSERT_TRUE(ref);
|
||||
EXPECT_EQ(ref.value().name.value(), kStyleFooName);
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
@@ -15,15 +15,26 @@
|
||||
*/
|
||||
|
||||
#include "Resource.h"
|
||||
#include "ResourceTypeExtensions.h"
|
||||
#include "flatten/ResourceTypeExtensions.h"
|
||||
#include "ResourceValues.h"
|
||||
#include "Util.h"
|
||||
#include "util/Util.h"
|
||||
#include "ValueVisitor.h"
|
||||
|
||||
#include <androidfw/ResourceTypes.h>
|
||||
#include <limits>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
template <typename Derived>
|
||||
void BaseValue<Derived>::accept(RawValueVisitor* visitor) {
|
||||
visitor->visit(static_cast<Derived*>(this));
|
||||
}
|
||||
|
||||
template <typename Derived>
|
||||
void BaseItem<Derived>::accept(RawValueVisitor* visitor) {
|
||||
visitor->visit(static_cast<Derived*>(this));
|
||||
}
|
||||
|
||||
bool Value::isItem() const {
|
||||
return false;
|
||||
}
|
||||
@@ -43,14 +54,14 @@ RawString* RawString::clone(StringPool* newPool) const {
|
||||
return new RawString(newPool->makeRef(*value));
|
||||
}
|
||||
|
||||
bool RawString::flatten(android::Res_value& outValue) const {
|
||||
outValue.dataType = ExtendedTypes::TYPE_RAW_STRING;
|
||||
outValue.data = static_cast<uint32_t>(value.getIndex());
|
||||
bool RawString::flatten(android::Res_value* outValue) const {
|
||||
outValue->dataType = ExtendedTypes::TYPE_RAW_STRING;
|
||||
outValue->data = util::hostToDevice32(static_cast<uint32_t>(value.getIndex()));
|
||||
return true;
|
||||
}
|
||||
|
||||
void RawString::print(std::ostream& out) const {
|
||||
out << "(raw string) " << *value;
|
||||
void RawString::print(std::ostream* out) const {
|
||||
*out << "(raw string) " << *value;
|
||||
}
|
||||
|
||||
Reference::Reference() : referenceType(Reference::Type::kResource) {
|
||||
@@ -63,11 +74,11 @@ Reference::Reference(const ResourceNameRef& n, Type t) :
|
||||
Reference::Reference(const ResourceId& i, Type type) : id(i), referenceType(type) {
|
||||
}
|
||||
|
||||
bool Reference::flatten(android::Res_value& outValue) const {
|
||||
outValue.dataType = (referenceType == Reference::Type::kResource)
|
||||
bool Reference::flatten(android::Res_value* outValue) const {
|
||||
outValue->dataType = (referenceType == Reference::Type::kResource)
|
||||
? android::Res_value::TYPE_REFERENCE
|
||||
: android::Res_value::TYPE_ATTRIBUTE;
|
||||
outValue.data = id.id;
|
||||
outValue->data = util::hostToDevice32(id ? id.value().id : 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -79,20 +90,20 @@ Reference* Reference::clone(StringPool* /*newPool*/) const {
|
||||
return ref;
|
||||
}
|
||||
|
||||
void Reference::print(std::ostream& out) const {
|
||||
out << "(reference) ";
|
||||
void Reference::print(std::ostream* out) const {
|
||||
*out << "(reference) ";
|
||||
if (referenceType == Reference::Type::kResource) {
|
||||
out << "@";
|
||||
*out << "@";
|
||||
} else {
|
||||
out << "?";
|
||||
*out << "?";
|
||||
}
|
||||
|
||||
if (name.isValid()) {
|
||||
out << name;
|
||||
if (name) {
|
||||
*out << name.value();
|
||||
}
|
||||
|
||||
if (id.isValid() || Res_INTERNALID(id.id)) {
|
||||
out << " " << id;
|
||||
if (id && !Res_INTERNALID(id.value().id)) {
|
||||
*out << " " << id.value();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,9 +111,9 @@ bool Id::isWeak() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Id::flatten(android::Res_value& out) const {
|
||||
out.dataType = android::Res_value::TYPE_INT_BOOLEAN;
|
||||
out.data = 0;
|
||||
bool Id::flatten(android::Res_value* out) const {
|
||||
out->dataType = android::Res_value::TYPE_INT_BOOLEAN;
|
||||
out->data = util::hostToDevice32(0);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -110,21 +121,21 @@ Id* Id::clone(StringPool* /*newPool*/) const {
|
||||
return new Id();
|
||||
}
|
||||
|
||||
void Id::print(std::ostream& out) const {
|
||||
out << "(id)";
|
||||
void Id::print(std::ostream* out) const {
|
||||
*out << "(id)";
|
||||
}
|
||||
|
||||
String::String(const StringPool::Ref& ref) : value(ref) {
|
||||
}
|
||||
|
||||
bool String::flatten(android::Res_value& outValue) const {
|
||||
// Verify that our StringPool index is within encodeable limits.
|
||||
bool String::flatten(android::Res_value* outValue) const {
|
||||
// Verify that our StringPool index is within encode-able limits.
|
||||
if (value.getIndex() > std::numeric_limits<uint32_t>::max()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
outValue.dataType = android::Res_value::TYPE_STRING;
|
||||
outValue.data = static_cast<uint32_t>(value.getIndex());
|
||||
outValue->dataType = android::Res_value::TYPE_STRING;
|
||||
outValue->data = util::hostToDevice32(static_cast<uint32_t>(value.getIndex()));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -132,20 +143,20 @@ String* String::clone(StringPool* newPool) const {
|
||||
return new String(newPool->makeRef(*value));
|
||||
}
|
||||
|
||||
void String::print(std::ostream& out) const {
|
||||
out << "(string) \"" << *value << "\"";
|
||||
void String::print(std::ostream* out) const {
|
||||
*out << "(string) \"" << *value << "\"";
|
||||
}
|
||||
|
||||
StyledString::StyledString(const StringPool::StyleRef& ref) : value(ref) {
|
||||
}
|
||||
|
||||
bool StyledString::flatten(android::Res_value& outValue) const {
|
||||
bool StyledString::flatten(android::Res_value* outValue) const {
|
||||
if (value.getIndex() > std::numeric_limits<uint32_t>::max()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
outValue.dataType = android::Res_value::TYPE_STRING;
|
||||
outValue.data = static_cast<uint32_t>(value.getIndex());
|
||||
outValue->dataType = android::Res_value::TYPE_STRING;
|
||||
outValue->data = util::hostToDevice32(static_cast<uint32_t>(value.getIndex()));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -153,20 +164,20 @@ StyledString* StyledString::clone(StringPool* newPool) const {
|
||||
return new StyledString(newPool->makeRef(value));
|
||||
}
|
||||
|
||||
void StyledString::print(std::ostream& out) const {
|
||||
out << "(styled string) \"" << *value->str << "\"";
|
||||
void StyledString::print(std::ostream* out) const {
|
||||
*out << "(styled string) \"" << *value->str << "\"";
|
||||
}
|
||||
|
||||
FileReference::FileReference(const StringPool::Ref& _path) : path(_path) {
|
||||
}
|
||||
|
||||
bool FileReference::flatten(android::Res_value& outValue) const {
|
||||
bool FileReference::flatten(android::Res_value* outValue) const {
|
||||
if (path.getIndex() > std::numeric_limits<uint32_t>::max()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
outValue.dataType = android::Res_value::TYPE_STRING;
|
||||
outValue.data = static_cast<uint32_t>(path.getIndex());
|
||||
outValue->dataType = android::Res_value::TYPE_STRING;
|
||||
outValue->data = util::hostToDevice32(static_cast<uint32_t>(path.getIndex()));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -174,15 +185,21 @@ FileReference* FileReference::clone(StringPool* newPool) const {
|
||||
return new FileReference(newPool->makeRef(*path));
|
||||
}
|
||||
|
||||
void FileReference::print(std::ostream& out) const {
|
||||
out << "(file) " << *path;
|
||||
void FileReference::print(std::ostream* out) const {
|
||||
*out << "(file) " << *path;
|
||||
}
|
||||
|
||||
BinaryPrimitive::BinaryPrimitive(const android::Res_value& val) : value(val) {
|
||||
}
|
||||
|
||||
bool BinaryPrimitive::flatten(android::Res_value& outValue) const {
|
||||
outValue = value;
|
||||
BinaryPrimitive::BinaryPrimitive(uint8_t dataType, uint32_t data) {
|
||||
value.dataType = dataType;
|
||||
value.data = data;
|
||||
}
|
||||
|
||||
bool BinaryPrimitive::flatten(android::Res_value* outValue) const {
|
||||
outValue->dataType = value.dataType;
|
||||
outValue->data = util::hostToDevice32(value.data);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -190,29 +207,29 @@ BinaryPrimitive* BinaryPrimitive::clone(StringPool* /*newPool*/) const {
|
||||
return new BinaryPrimitive(value);
|
||||
}
|
||||
|
||||
void BinaryPrimitive::print(std::ostream& out) const {
|
||||
void BinaryPrimitive::print(std::ostream* out) const {
|
||||
switch (value.dataType) {
|
||||
case android::Res_value::TYPE_NULL:
|
||||
out << "(null)";
|
||||
*out << "(null)";
|
||||
break;
|
||||
case android::Res_value::TYPE_INT_DEC:
|
||||
out << "(integer) " << value.data;
|
||||
*out << "(integer) " << value.data;
|
||||
break;
|
||||
case android::Res_value::TYPE_INT_HEX:
|
||||
out << "(integer) " << std::hex << value.data << std::dec;
|
||||
*out << "(integer) " << std::hex << value.data << std::dec;
|
||||
break;
|
||||
case android::Res_value::TYPE_INT_BOOLEAN:
|
||||
out << "(boolean) " << (value.data != 0 ? "true" : "false");
|
||||
*out << "(boolean) " << (value.data != 0 ? "true" : "false");
|
||||
break;
|
||||
case android::Res_value::TYPE_INT_COLOR_ARGB8:
|
||||
case android::Res_value::TYPE_INT_COLOR_RGB8:
|
||||
case android::Res_value::TYPE_INT_COLOR_ARGB4:
|
||||
case android::Res_value::TYPE_INT_COLOR_RGB4:
|
||||
out << "(color) #" << std::hex << value.data << std::dec;
|
||||
*out << "(color) #" << std::hex << value.data << std::dec;
|
||||
break;
|
||||
default:
|
||||
out << "(unknown 0x" << std::hex << (int) value.dataType << ") 0x"
|
||||
<< std::hex << value.data << std::dec;
|
||||
*out << "(unknown 0x" << std::hex << (int) value.dataType << ") 0x"
|
||||
<< std::hex << value.data << std::dec;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -231,9 +248,9 @@ Attribute* Attribute::clone(StringPool* /*newPool*/) const {
|
||||
return attr;
|
||||
}
|
||||
|
||||
void Attribute::printMask(std::ostream& out) const {
|
||||
void Attribute::printMask(std::ostream* out) const {
|
||||
if (typeMask == android::ResTable_map::TYPE_ANY) {
|
||||
out << "any";
|
||||
*out << "any";
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -242,103 +259,105 @@ void Attribute::printMask(std::ostream& out) const {
|
||||
if (!set) {
|
||||
set = true;
|
||||
} else {
|
||||
out << "|";
|
||||
*out << "|";
|
||||
}
|
||||
out << "reference";
|
||||
*out << "reference";
|
||||
}
|
||||
|
||||
if ((typeMask & android::ResTable_map::TYPE_STRING) != 0) {
|
||||
if (!set) {
|
||||
set = true;
|
||||
} else {
|
||||
out << "|";
|
||||
*out << "|";
|
||||
}
|
||||
out << "string";
|
||||
*out << "string";
|
||||
}
|
||||
|
||||
if ((typeMask & android::ResTable_map::TYPE_INTEGER) != 0) {
|
||||
if (!set) {
|
||||
set = true;
|
||||
} else {
|
||||
out << "|";
|
||||
*out << "|";
|
||||
}
|
||||
out << "integer";
|
||||
*out << "integer";
|
||||
}
|
||||
|
||||
if ((typeMask & android::ResTable_map::TYPE_BOOLEAN) != 0) {
|
||||
if (!set) {
|
||||
set = true;
|
||||
} else {
|
||||
out << "|";
|
||||
*out << "|";
|
||||
}
|
||||
out << "boolean";
|
||||
*out << "boolean";
|
||||
}
|
||||
|
||||
if ((typeMask & android::ResTable_map::TYPE_COLOR) != 0) {
|
||||
if (!set) {
|
||||
set = true;
|
||||
} else {
|
||||
out << "|";
|
||||
*out << "|";
|
||||
}
|
||||
out << "color";
|
||||
*out << "color";
|
||||
}
|
||||
|
||||
if ((typeMask & android::ResTable_map::TYPE_FLOAT) != 0) {
|
||||
if (!set) {
|
||||
set = true;
|
||||
} else {
|
||||
out << "|";
|
||||
*out << "|";
|
||||
}
|
||||
out << "float";
|
||||
*out << "float";
|
||||
}
|
||||
|
||||
if ((typeMask & android::ResTable_map::TYPE_DIMENSION) != 0) {
|
||||
if (!set) {
|
||||
set = true;
|
||||
} else {
|
||||
out << "|";
|
||||
*out << "|";
|
||||
}
|
||||
out << "dimension";
|
||||
*out << "dimension";
|
||||
}
|
||||
|
||||
if ((typeMask & android::ResTable_map::TYPE_FRACTION) != 0) {
|
||||
if (!set) {
|
||||
set = true;
|
||||
} else {
|
||||
out << "|";
|
||||
*out << "|";
|
||||
}
|
||||
out << "fraction";
|
||||
*out << "fraction";
|
||||
}
|
||||
|
||||
if ((typeMask & android::ResTable_map::TYPE_ENUM) != 0) {
|
||||
if (!set) {
|
||||
set = true;
|
||||
} else {
|
||||
out << "|";
|
||||
*out << "|";
|
||||
}
|
||||
out << "enum";
|
||||
*out << "enum";
|
||||
}
|
||||
|
||||
if ((typeMask & android::ResTable_map::TYPE_FLAGS) != 0) {
|
||||
if (!set) {
|
||||
set = true;
|
||||
} else {
|
||||
out << "|";
|
||||
*out << "|";
|
||||
}
|
||||
out << "flags";
|
||||
*out << "flags";
|
||||
}
|
||||
}
|
||||
|
||||
void Attribute::print(std::ostream& out) const {
|
||||
out << "(attr) ";
|
||||
void Attribute::print(std::ostream* out) const {
|
||||
*out << "(attr) ";
|
||||
printMask(out);
|
||||
|
||||
out << " ["
|
||||
<< util::joiner(symbols.begin(), symbols.end(), ", ")
|
||||
<< "]";
|
||||
if (!symbols.empty()) {
|
||||
*out << " ["
|
||||
<< util::joiner(symbols.begin(), symbols.end(), ", ")
|
||||
<< "]";
|
||||
}
|
||||
|
||||
if (weak) {
|
||||
out << " [weak]";
|
||||
*out << " [weak]";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -355,19 +374,24 @@ Style* Style::clone(StringPool* newPool) const {
|
||||
return style;
|
||||
}
|
||||
|
||||
void Style::print(std::ostream& out) const {
|
||||
out << "(style) ";
|
||||
if (!parent.name.entry.empty()) {
|
||||
out << parent.name;
|
||||
void Style::print(std::ostream* out) const {
|
||||
*out << "(style) ";
|
||||
if (parent && parent.value().name) {
|
||||
*out << parent.value().name.value();
|
||||
}
|
||||
out << " ["
|
||||
*out << " ["
|
||||
<< util::joiner(entries.begin(), entries.end(), ", ")
|
||||
<< "]";
|
||||
}
|
||||
|
||||
static ::std::ostream& operator<<(::std::ostream& out, const Style::Entry& value) {
|
||||
out << value.key.name << " = ";
|
||||
value.value->print(out);
|
||||
if (value.key.name) {
|
||||
out << value.key.name.value();
|
||||
} else {
|
||||
out << "???";
|
||||
}
|
||||
out << " = ";
|
||||
value.value->print(&out);
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -379,8 +403,8 @@ Array* Array::clone(StringPool* newPool) const {
|
||||
return array;
|
||||
}
|
||||
|
||||
void Array::print(std::ostream& out) const {
|
||||
out << "(array) ["
|
||||
void Array::print(std::ostream* out) const {
|
||||
*out << "(array) ["
|
||||
<< util::joiner(items.begin(), items.end(), ", ")
|
||||
<< "]";
|
||||
}
|
||||
@@ -396,8 +420,8 @@ Plural* Plural::clone(StringPool* newPool) const {
|
||||
return p;
|
||||
}
|
||||
|
||||
void Plural::print(std::ostream& out) const {
|
||||
out << "(plural)";
|
||||
void Plural::print(std::ostream* out) const {
|
||||
*out << "(plural)";
|
||||
}
|
||||
|
||||
static ::std::ostream& operator<<(::std::ostream& out, const std::unique_ptr<Item>& item) {
|
||||
@@ -410,8 +434,8 @@ Styleable* Styleable::clone(StringPool* /*newPool*/) const {
|
||||
return styleable;
|
||||
}
|
||||
|
||||
void Styleable::print(std::ostream& out) const {
|
||||
out << "(styleable) " << " ["
|
||||
void Styleable::print(std::ostream* out) const {
|
||||
*out << "(styleable) " << " ["
|
||||
<< util::joiner(entries.begin(), entries.end(), ", ")
|
||||
<< "]";
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#ifndef AAPT_RESOURCE_VALUES_H
|
||||
#define AAPT_RESOURCE_VALUES_H
|
||||
|
||||
#include "util/Maybe.h"
|
||||
#include "Resource.h"
|
||||
#include "StringPool.h"
|
||||
|
||||
@@ -27,9 +28,7 @@
|
||||
|
||||
namespace aapt {
|
||||
|
||||
struct ValueVisitor;
|
||||
struct ConstValueVisitor;
|
||||
struct ValueVisitorArgs;
|
||||
struct RawValueVisitor;
|
||||
|
||||
/**
|
||||
* A resource value. This is an all-encompassing representation
|
||||
@@ -39,13 +38,15 @@ struct ValueVisitorArgs;
|
||||
* but it is the simplest strategy.
|
||||
*/
|
||||
struct Value {
|
||||
virtual ~Value() = default;
|
||||
|
||||
/**
|
||||
* Whether or not this is an Item.
|
||||
*/
|
||||
virtual bool isItem() const;
|
||||
|
||||
/**
|
||||
* Whether this value is weak and can be overriden without
|
||||
* Whether this value is weak and can be overridden without
|
||||
* warning or error. Default for base class is false.
|
||||
*/
|
||||
virtual bool isWeak() const;
|
||||
@@ -53,12 +54,7 @@ struct Value {
|
||||
/**
|
||||
* Calls the appropriate overload of ValueVisitor.
|
||||
*/
|
||||
virtual void accept(ValueVisitor& visitor, ValueVisitorArgs&& args) = 0;
|
||||
|
||||
/**
|
||||
* Const version of accept().
|
||||
*/
|
||||
virtual void accept(ConstValueVisitor& visitor, ValueVisitorArgs&& args) const = 0;
|
||||
virtual void accept(RawValueVisitor* visitor) = 0;
|
||||
|
||||
/**
|
||||
* Clone the value.
|
||||
@@ -68,7 +64,7 @@ struct Value {
|
||||
/**
|
||||
* Human readable printout of this value.
|
||||
*/
|
||||
virtual void print(std::ostream& out) const = 0;
|
||||
virtual void print(std::ostream* out) const = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -76,8 +72,7 @@ struct Value {
|
||||
*/
|
||||
template <typename Derived>
|
||||
struct BaseValue : public Value {
|
||||
virtual void accept(ValueVisitor& visitor, ValueVisitorArgs&& args) override;
|
||||
virtual void accept(ConstValueVisitor& visitor, ValueVisitorArgs&& args) const override;
|
||||
void accept(RawValueVisitor* visitor) override;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -96,9 +91,9 @@ struct Item : public Value {
|
||||
|
||||
/**
|
||||
* Fills in an android::Res_value structure with this Item's binary representation.
|
||||
* Returns false if an error ocurred.
|
||||
* Returns false if an error occurred.
|
||||
*/
|
||||
virtual bool flatten(android::Res_value& outValue) const = 0;
|
||||
virtual bool flatten(android::Res_value* outValue) const = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -106,8 +101,7 @@ struct Item : public Value {
|
||||
*/
|
||||
template <typename Derived>
|
||||
struct BaseItem : public Item {
|
||||
virtual void accept(ValueVisitor& visitor, ValueVisitorArgs&& args) override;
|
||||
virtual void accept(ConstValueVisitor& visitor, ValueVisitorArgs&& args) const override;
|
||||
void accept(RawValueVisitor* visitor) override;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -122,8 +116,8 @@ struct Reference : public BaseItem<Reference> {
|
||||
kAttribute,
|
||||
};
|
||||
|
||||
ResourceName name;
|
||||
ResourceId id;
|
||||
Maybe<ResourceName> name;
|
||||
Maybe<ResourceId> id;
|
||||
Reference::Type referenceType;
|
||||
bool privateReference = false;
|
||||
|
||||
@@ -131,9 +125,9 @@ struct Reference : public BaseItem<Reference> {
|
||||
Reference(const ResourceNameRef& n, Type type = Type::kResource);
|
||||
Reference(const ResourceId& i, Type type = Type::kResource);
|
||||
|
||||
bool flatten(android::Res_value& outValue) const override;
|
||||
bool flatten(android::Res_value* outValue) const override;
|
||||
Reference* clone(StringPool* newPool) const override;
|
||||
void print(std::ostream& out) const override;
|
||||
void print(std::ostream* out) const override;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -141,9 +135,9 @@ struct Reference : public BaseItem<Reference> {
|
||||
*/
|
||||
struct Id : public BaseItem<Id> {
|
||||
bool isWeak() const override;
|
||||
bool flatten(android::Res_value& out) const override;
|
||||
bool flatten(android::Res_value* out) const override;
|
||||
Id* clone(StringPool* newPool) const override;
|
||||
void print(std::ostream& out) const override;
|
||||
void print(std::ostream* out) const override;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -156,9 +150,9 @@ struct RawString : public BaseItem<RawString> {
|
||||
|
||||
RawString(const StringPool::Ref& ref);
|
||||
|
||||
bool flatten(android::Res_value& outValue) const override;
|
||||
bool flatten(android::Res_value* outValue) const override;
|
||||
RawString* clone(StringPool* newPool) const override;
|
||||
void print(std::ostream& out) const override;
|
||||
void print(std::ostream* out) const override;
|
||||
};
|
||||
|
||||
struct String : public BaseItem<String> {
|
||||
@@ -166,9 +160,9 @@ struct String : public BaseItem<String> {
|
||||
|
||||
String(const StringPool::Ref& ref);
|
||||
|
||||
bool flatten(android::Res_value& outValue) const override;
|
||||
bool flatten(android::Res_value* outValue) const override;
|
||||
String* clone(StringPool* newPool) const override;
|
||||
void print(std::ostream& out) const override;
|
||||
void print(std::ostream* out) const override;
|
||||
};
|
||||
|
||||
struct StyledString : public BaseItem<StyledString> {
|
||||
@@ -176,9 +170,9 @@ struct StyledString : public BaseItem<StyledString> {
|
||||
|
||||
StyledString(const StringPool::StyleRef& ref);
|
||||
|
||||
bool flatten(android::Res_value& outValue) const override;
|
||||
bool flatten(android::Res_value* outValue) const override;
|
||||
StyledString* clone(StringPool* newPool) const override;
|
||||
void print(std::ostream& out) const override;
|
||||
void print(std::ostream* out) const override;
|
||||
};
|
||||
|
||||
struct FileReference : public BaseItem<FileReference> {
|
||||
@@ -187,9 +181,9 @@ struct FileReference : public BaseItem<FileReference> {
|
||||
FileReference() = default;
|
||||
FileReference(const StringPool::Ref& path);
|
||||
|
||||
bool flatten(android::Res_value& outValue) const override;
|
||||
bool flatten(android::Res_value* outValue) const override;
|
||||
FileReference* clone(StringPool* newPool) const override;
|
||||
void print(std::ostream& out) const override;
|
||||
void print(std::ostream* out) const override;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -200,10 +194,11 @@ struct BinaryPrimitive : public BaseItem<BinaryPrimitive> {
|
||||
|
||||
BinaryPrimitive() = default;
|
||||
BinaryPrimitive(const android::Res_value& val);
|
||||
BinaryPrimitive(uint8_t dataType, uint32_t data);
|
||||
|
||||
bool flatten(android::Res_value& outValue) const override;
|
||||
bool flatten(android::Res_value* outValue) const override;
|
||||
BinaryPrimitive* clone(StringPool* newPool) const override;
|
||||
void print(std::ostream& out) const override;
|
||||
void print(std::ostream* out) const override;
|
||||
};
|
||||
|
||||
struct Attribute : public BaseValue<Attribute> {
|
||||
@@ -212,7 +207,7 @@ struct Attribute : public BaseValue<Attribute> {
|
||||
uint32_t value;
|
||||
};
|
||||
|
||||
bool weak;
|
||||
bool weak;
|
||||
uint32_t typeMask;
|
||||
uint32_t minInt;
|
||||
uint32_t maxInt;
|
||||
@@ -221,9 +216,9 @@ struct Attribute : public BaseValue<Attribute> {
|
||||
Attribute(bool w, uint32_t t = 0u);
|
||||
|
||||
bool isWeak() const override;
|
||||
virtual Attribute* clone(StringPool* newPool) const override;
|
||||
void printMask(std::ostream& out) const;
|
||||
virtual void print(std::ostream& out) const override;
|
||||
Attribute* clone(StringPool* newPool) const override;
|
||||
void printMask(std::ostream* out) const;
|
||||
void print(std::ostream* out) const override;
|
||||
};
|
||||
|
||||
struct Style : public BaseValue<Style> {
|
||||
@@ -232,7 +227,7 @@ struct Style : public BaseValue<Style> {
|
||||
std::unique_ptr<Item> value;
|
||||
};
|
||||
|
||||
Reference parent;
|
||||
Maybe<Reference> parent;
|
||||
|
||||
/**
|
||||
* If set to true, the parent was auto inferred from the
|
||||
@@ -243,14 +238,14 @@ struct Style : public BaseValue<Style> {
|
||||
std::vector<Entry> entries;
|
||||
|
||||
Style* clone(StringPool* newPool) const override;
|
||||
void print(std::ostream& out) const override;
|
||||
void print(std::ostream* out) const override;
|
||||
};
|
||||
|
||||
struct Array : public BaseValue<Array> {
|
||||
std::vector<std::unique_ptr<Item>> items;
|
||||
|
||||
Array* clone(StringPool* newPool) const override;
|
||||
void print(std::ostream& out) const override;
|
||||
void print(std::ostream* out) const override;
|
||||
};
|
||||
|
||||
struct Plural : public BaseValue<Plural> {
|
||||
@@ -267,180 +262,31 @@ struct Plural : public BaseValue<Plural> {
|
||||
std::array<std::unique_ptr<Item>, Count> values;
|
||||
|
||||
Plural* clone(StringPool* newPool) const override;
|
||||
void print(std::ostream& out) const override;
|
||||
void print(std::ostream* out) const override;
|
||||
};
|
||||
|
||||
struct Styleable : public BaseValue<Styleable> {
|
||||
std::vector<Reference> entries;
|
||||
|
||||
Styleable* clone(StringPool* newPool) const override;
|
||||
void print(std::ostream& out) const override;
|
||||
void print(std::ostream* out) const override;
|
||||
};
|
||||
|
||||
/**
|
||||
* Stream operator for printing Value objects.
|
||||
*/
|
||||
inline ::std::ostream& operator<<(::std::ostream& out, const Value& value) {
|
||||
value.print(out);
|
||||
value.print(&out);
|
||||
return out;
|
||||
}
|
||||
|
||||
inline ::std::ostream& operator<<(::std::ostream& out, const Attribute::Symbol& s) {
|
||||
return out << s.symbol.name.entry << "=" << s.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* The argument object that gets passed through the value
|
||||
* back to the ValueVisitor. Subclasses of ValueVisitor should
|
||||
* subclass ValueVisitorArgs to contain the data they need
|
||||
* to operate.
|
||||
*/
|
||||
struct ValueVisitorArgs {};
|
||||
|
||||
/**
|
||||
* Visits a value and runs the appropriate method based on its type.
|
||||
*/
|
||||
struct ValueVisitor {
|
||||
virtual void visit(Reference& reference, ValueVisitorArgs& args) {
|
||||
visitItem(reference, args);
|
||||
if (s.symbol.name) {
|
||||
out << s.symbol.name.value().entry;
|
||||
} else {
|
||||
out << "???";
|
||||
}
|
||||
|
||||
virtual void visit(RawString& string, ValueVisitorArgs& args) {
|
||||
visitItem(string, args);
|
||||
}
|
||||
|
||||
virtual void visit(String& string, ValueVisitorArgs& args) {
|
||||
visitItem(string, args);
|
||||
}
|
||||
|
||||
virtual void visit(StyledString& string, ValueVisitorArgs& args) {
|
||||
visitItem(string, args);
|
||||
}
|
||||
|
||||
virtual void visit(FileReference& file, ValueVisitorArgs& args) {
|
||||
visitItem(file, args);
|
||||
}
|
||||
|
||||
virtual void visit(Id& id, ValueVisitorArgs& args) {
|
||||
visitItem(id, args);
|
||||
}
|
||||
|
||||
virtual void visit(BinaryPrimitive& primitive, ValueVisitorArgs& args) {
|
||||
visitItem(primitive, args);
|
||||
}
|
||||
|
||||
virtual void visit(Attribute& attr, ValueVisitorArgs& args) {}
|
||||
virtual void visit(Style& style, ValueVisitorArgs& args) {}
|
||||
virtual void visit(Array& array, ValueVisitorArgs& args) {}
|
||||
virtual void visit(Plural& array, ValueVisitorArgs& args) {}
|
||||
virtual void visit(Styleable& styleable, ValueVisitorArgs& args) {}
|
||||
|
||||
virtual void visitItem(Item& item, ValueVisitorArgs& args) {}
|
||||
};
|
||||
|
||||
/**
|
||||
* Const version of ValueVisitor.
|
||||
*/
|
||||
struct ConstValueVisitor {
|
||||
virtual void visit(const Reference& reference, ValueVisitorArgs& args) {
|
||||
visitItem(reference, args);
|
||||
}
|
||||
|
||||
virtual void visit(const RawString& string, ValueVisitorArgs& args) {
|
||||
visitItem(string, args);
|
||||
}
|
||||
|
||||
virtual void visit(const String& string, ValueVisitorArgs& args) {
|
||||
visitItem(string, args);
|
||||
}
|
||||
|
||||
virtual void visit(const StyledString& string, ValueVisitorArgs& args) {
|
||||
visitItem(string, args);
|
||||
}
|
||||
|
||||
virtual void visit(const FileReference& file, ValueVisitorArgs& args) {
|
||||
visitItem(file, args);
|
||||
}
|
||||
|
||||
virtual void visit(const Id& id, ValueVisitorArgs& args) {
|
||||
visitItem(id, args);
|
||||
}
|
||||
|
||||
virtual void visit(const BinaryPrimitive& primitive, ValueVisitorArgs& args) {
|
||||
visitItem(primitive, args);
|
||||
}
|
||||
|
||||
virtual void visit(const Attribute& attr, ValueVisitorArgs& args) {}
|
||||
virtual void visit(const Style& style, ValueVisitorArgs& args) {}
|
||||
virtual void visit(const Array& array, ValueVisitorArgs& args) {}
|
||||
virtual void visit(const Plural& array, ValueVisitorArgs& args) {}
|
||||
virtual void visit(const Styleable& styleable, ValueVisitorArgs& args) {}
|
||||
|
||||
virtual void visitItem(const Item& item, ValueVisitorArgs& args) {}
|
||||
};
|
||||
|
||||
/**
|
||||
* Convenience Visitor that forwards a specific type to a function.
|
||||
* Args are not used as the function can bind variables. Do not use
|
||||
* directly, use the wrapper visitFunc() method.
|
||||
*/
|
||||
template <typename T, typename TFunc>
|
||||
struct ValueVisitorFunc : ValueVisitor {
|
||||
TFunc func;
|
||||
|
||||
ValueVisitorFunc(TFunc f) : func(f) {
|
||||
}
|
||||
|
||||
void visit(T& value, ValueVisitorArgs&) override {
|
||||
func(value);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Const version of ValueVisitorFunc.
|
||||
*/
|
||||
template <typename T, typename TFunc>
|
||||
struct ConstValueVisitorFunc : ConstValueVisitor {
|
||||
TFunc func;
|
||||
|
||||
ConstValueVisitorFunc(TFunc f) : func(f) {
|
||||
}
|
||||
|
||||
void visit(const T& value, ValueVisitorArgs&) override {
|
||||
func(value);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, typename TFunc>
|
||||
void visitFunc(Value& value, TFunc f) {
|
||||
ValueVisitorFunc<T, TFunc> visitor(f);
|
||||
value.accept(visitor, ValueVisitorArgs{});
|
||||
}
|
||||
|
||||
template <typename T, typename TFunc>
|
||||
void visitFunc(const Value& value, TFunc f) {
|
||||
ConstValueVisitorFunc<T, TFunc> visitor(f);
|
||||
value.accept(visitor, ValueVisitorArgs{});
|
||||
}
|
||||
|
||||
template <typename Derived>
|
||||
void BaseValue<Derived>::accept(ValueVisitor& visitor, ValueVisitorArgs&& args) {
|
||||
visitor.visit(static_cast<Derived&>(*this), args);
|
||||
}
|
||||
|
||||
template <typename Derived>
|
||||
void BaseValue<Derived>::accept(ConstValueVisitor& visitor, ValueVisitorArgs&& args) const {
|
||||
visitor.visit(static_cast<const Derived&>(*this), args);
|
||||
}
|
||||
|
||||
template <typename Derived>
|
||||
void BaseItem<Derived>::accept(ValueVisitor& visitor, ValueVisitorArgs&& args) {
|
||||
visitor.visit(static_cast<Derived&>(*this), args);
|
||||
}
|
||||
|
||||
template <typename Derived>
|
||||
void BaseItem<Derived>::accept(ConstValueVisitor& visitor, ValueVisitorArgs&& args) const {
|
||||
visitor.visit(static_cast<const Derived&>(*this), args);
|
||||
return out << "=" << s.value;
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
@@ -1,104 +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 "ScopedXmlPullParser.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
ScopedXmlPullParser::ScopedXmlPullParser(XmlPullParser* parser) :
|
||||
mParser(parser), mDepth(parser->getDepth()), mDone(false) {
|
||||
}
|
||||
|
||||
ScopedXmlPullParser::~ScopedXmlPullParser() {
|
||||
while (isGoodEvent(next()));
|
||||
}
|
||||
|
||||
XmlPullParser::Event ScopedXmlPullParser::next() {
|
||||
if (mDone) {
|
||||
return Event::kEndDocument;
|
||||
}
|
||||
|
||||
const Event event = mParser->next();
|
||||
if (mParser->getDepth() <= mDepth) {
|
||||
mDone = true;
|
||||
}
|
||||
return event;
|
||||
}
|
||||
|
||||
XmlPullParser::Event ScopedXmlPullParser::getEvent() const {
|
||||
return mParser->getEvent();
|
||||
}
|
||||
|
||||
const std::string& ScopedXmlPullParser::getLastError() const {
|
||||
return mParser->getLastError();
|
||||
}
|
||||
|
||||
const std::u16string& ScopedXmlPullParser::getComment() const {
|
||||
return mParser->getComment();
|
||||
}
|
||||
|
||||
size_t ScopedXmlPullParser::getLineNumber() const {
|
||||
return mParser->getLineNumber();
|
||||
}
|
||||
|
||||
size_t ScopedXmlPullParser::getDepth() const {
|
||||
const size_t depth = mParser->getDepth();
|
||||
if (depth < mDepth) {
|
||||
return 0;
|
||||
}
|
||||
return depth - mDepth;
|
||||
}
|
||||
|
||||
const std::u16string& ScopedXmlPullParser::getText() const {
|
||||
return mParser->getText();
|
||||
}
|
||||
|
||||
const std::u16string& ScopedXmlPullParser::getNamespacePrefix() const {
|
||||
return mParser->getNamespacePrefix();
|
||||
}
|
||||
|
||||
const std::u16string& ScopedXmlPullParser::getNamespaceUri() const {
|
||||
return mParser->getNamespaceUri();
|
||||
}
|
||||
|
||||
bool ScopedXmlPullParser::applyPackageAlias(std::u16string* package,
|
||||
const std::u16string& defaultPackage) const {
|
||||
return mParser->applyPackageAlias(package, defaultPackage);
|
||||
}
|
||||
|
||||
const std::u16string& ScopedXmlPullParser::getElementNamespace() const {
|
||||
return mParser->getElementNamespace();
|
||||
}
|
||||
|
||||
const std::u16string& ScopedXmlPullParser::getElementName() const {
|
||||
return mParser->getElementName();
|
||||
}
|
||||
|
||||
size_t ScopedXmlPullParser::getAttributeCount() const {
|
||||
return mParser->getAttributeCount();
|
||||
}
|
||||
|
||||
XmlPullParser::const_iterator ScopedXmlPullParser::beginAttributes() const {
|
||||
return mParser->beginAttributes();
|
||||
}
|
||||
|
||||
XmlPullParser::const_iterator ScopedXmlPullParser::endAttributes() const {
|
||||
return mParser->endAttributes();
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
@@ -1,85 +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_SCOPED_XML_PULL_PARSER_H
|
||||
#define AAPT_SCOPED_XML_PULL_PARSER_H
|
||||
|
||||
#include "XmlPullParser.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
/**
|
||||
* An XmlPullParser that will not read past the depth
|
||||
* of the underlying parser. When this parser is destroyed,
|
||||
* it moves the underlying parser to the same depth it
|
||||
* started with.
|
||||
*
|
||||
* You can write code like this:
|
||||
*
|
||||
* while (XmlPullParser::isGoodEvent(parser.next())) {
|
||||
* if (parser.getEvent() != XmlPullParser::Event::StartElement) {
|
||||
* continue;
|
||||
* }
|
||||
*
|
||||
* ScopedXmlPullParser scoped(parser);
|
||||
* if (parser.getElementName() == u"id") {
|
||||
* // do work.
|
||||
* } else {
|
||||
* // do nothing, as all the sub elements will be skipped
|
||||
* // when scoped goes out of scope.
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
class ScopedXmlPullParser : public XmlPullParser {
|
||||
public:
|
||||
ScopedXmlPullParser(XmlPullParser* parser);
|
||||
ScopedXmlPullParser(const ScopedXmlPullParser&) = delete;
|
||||
ScopedXmlPullParser& operator=(const ScopedXmlPullParser&) = delete;
|
||||
~ScopedXmlPullParser();
|
||||
|
||||
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:
|
||||
XmlPullParser* mParser;
|
||||
size_t mDepth;
|
||||
bool mDone;
|
||||
};
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
#endif // AAPT_SCOPED_XML_PULL_PARSER_H
|
||||
@@ -1,106 +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 "ScopedXmlPullParser.h"
|
||||
#include "SourceXmlPullParser.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
TEST(ScopedXmlPullParserTest, StopIteratingAtNoNZeroDepth) {
|
||||
std::stringstream input;
|
||||
input << "<?xml version=\"1.0\" encoding=\"utf-8\"?>" << std::endl
|
||||
<< "<resources><string></string></resources>" << std::endl;
|
||||
|
||||
SourceXmlPullParser sourceParser(input);
|
||||
EXPECT_EQ(XmlPullParser::Event::kStartElement, sourceParser.next());
|
||||
EXPECT_EQ(std::u16string(u"resources"), sourceParser.getElementName());
|
||||
|
||||
EXPECT_EQ(XmlPullParser::Event::kStartElement, sourceParser.next());
|
||||
EXPECT_EQ(std::u16string(u"string"), sourceParser.getElementName());
|
||||
|
||||
{
|
||||
ScopedXmlPullParser scopedParser(&sourceParser);
|
||||
EXPECT_EQ(XmlPullParser::Event::kEndElement, scopedParser.next());
|
||||
EXPECT_EQ(std::u16string(u"string"), sourceParser.getElementName());
|
||||
|
||||
EXPECT_EQ(XmlPullParser::Event::kEndDocument, scopedParser.next());
|
||||
}
|
||||
|
||||
EXPECT_EQ(XmlPullParser::Event::kEndElement, sourceParser.next());
|
||||
EXPECT_EQ(std::u16string(u"resources"), sourceParser.getElementName());
|
||||
|
||||
EXPECT_EQ(XmlPullParser::Event::kEndDocument, sourceParser.next());
|
||||
}
|
||||
|
||||
TEST(ScopedXmlPullParserTest, FinishCurrentElementOnDestruction) {
|
||||
std::stringstream input;
|
||||
input << "<?xml version=\"1.0\" encoding=\"utf-8\"?>" << std::endl
|
||||
<< "<resources><string></string></resources>" << std::endl;
|
||||
|
||||
SourceXmlPullParser sourceParser(input);
|
||||
EXPECT_EQ(XmlPullParser::Event::kStartElement, sourceParser.next());
|
||||
EXPECT_EQ(std::u16string(u"resources"), sourceParser.getElementName());
|
||||
|
||||
EXPECT_EQ(XmlPullParser::Event::kStartElement, sourceParser.next());
|
||||
EXPECT_EQ(std::u16string(u"string"), sourceParser.getElementName());
|
||||
|
||||
{
|
||||
ScopedXmlPullParser scopedParser(&sourceParser);
|
||||
EXPECT_EQ(std::u16string(u"string"), sourceParser.getElementName());
|
||||
}
|
||||
|
||||
EXPECT_EQ(XmlPullParser::Event::kEndElement, sourceParser.next());
|
||||
EXPECT_EQ(std::u16string(u"resources"), sourceParser.getElementName());
|
||||
|
||||
EXPECT_EQ(XmlPullParser::Event::kEndDocument, sourceParser.next());
|
||||
}
|
||||
|
||||
TEST(ScopedXmlPullParserTest, NestedParsersOperateCorrectly) {
|
||||
std::stringstream input;
|
||||
input << "<?xml version=\"1.0\" encoding=\"utf-8\"?>" << std::endl
|
||||
<< "<resources><string><foo></foo></string></resources>" << std::endl;
|
||||
|
||||
SourceXmlPullParser sourceParser(input);
|
||||
EXPECT_EQ(XmlPullParser::Event::kStartElement, sourceParser.next());
|
||||
EXPECT_EQ(std::u16string(u"resources"), sourceParser.getElementName());
|
||||
|
||||
EXPECT_EQ(XmlPullParser::Event::kStartElement, sourceParser.next());
|
||||
EXPECT_EQ(std::u16string(u"string"), sourceParser.getElementName());
|
||||
|
||||
{
|
||||
ScopedXmlPullParser scopedParser(&sourceParser);
|
||||
EXPECT_EQ(std::u16string(u"string"), scopedParser.getElementName());
|
||||
while (XmlPullParser::isGoodEvent(scopedParser.next())) {
|
||||
if (scopedParser.getEvent() != XmlPullParser::Event::kStartElement) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ScopedXmlPullParser subScopedParser(&scopedParser);
|
||||
EXPECT_EQ(std::u16string(u"foo"), subScopedParser.getElementName());
|
||||
}
|
||||
}
|
||||
|
||||
EXPECT_EQ(XmlPullParser::Event::kEndElement, sourceParser.next());
|
||||
EXPECT_EQ(std::u16string(u"resources"), sourceParser.getElementName());
|
||||
|
||||
EXPECT_EQ(XmlPullParser::Event::kEndDocument, sourceParser.next());
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
@@ -34,8 +34,9 @@ static const std::vector<std::pair<uint16_t, size_t>> sAttrIdMap = {
|
||||
{ 0x02bd, SDK_FROYO },
|
||||
{ 0x02cb, SDK_GINGERBREAD },
|
||||
{ 0x0361, SDK_HONEYCOMB },
|
||||
{ 0x0366, SDK_HONEYCOMB_MR1 },
|
||||
{ 0x03a6, SDK_HONEYCOMB_MR2 },
|
||||
{ 0x0363, SDK_HONEYCOMB_MR1 },
|
||||
{ 0x0366, SDK_HONEYCOMB_MR2 },
|
||||
{ 0x03a6, SDK_ICE_CREAM_SANDWICH },
|
||||
{ 0x03ae, SDK_JELLY_BEAN },
|
||||
{ 0x03cc, SDK_JELLY_BEAN_MR1 },
|
||||
{ 0x03da, SDK_JELLY_BEAN_MR2 },
|
||||
|
||||
@@ -17,72 +17,58 @@
|
||||
#ifndef AAPT_SOURCE_H
|
||||
#define AAPT_SOURCE_H
|
||||
|
||||
#include "util/Maybe.h"
|
||||
#include "util/StringPiece.h"
|
||||
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
struct SourceLineColumn;
|
||||
struct SourceLine;
|
||||
|
||||
/**
|
||||
* Represents a file on disk. Used for logging and
|
||||
* showing errors.
|
||||
*/
|
||||
struct Source {
|
||||
std::string path;
|
||||
Maybe<size_t> line;
|
||||
|
||||
inline SourceLine line(size_t line) const;
|
||||
};
|
||||
Source() = default;
|
||||
|
||||
/**
|
||||
* Represents a file on disk and a line number in that file.
|
||||
* Used for logging and showing errors.
|
||||
*/
|
||||
struct SourceLine {
|
||||
std::string path;
|
||||
size_t line;
|
||||
inline Source(const StringPiece& path) : path(path.toString()) {
|
||||
}
|
||||
|
||||
inline SourceLineColumn column(size_t column) const;
|
||||
};
|
||||
inline Source(const StringPiece& path, size_t line) : path(path.toString()), line(line) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a file on disk and a line:column number in that file.
|
||||
* Used for logging and showing errors.
|
||||
*/
|
||||
struct SourceLineColumn {
|
||||
std::string path;
|
||||
size_t line;
|
||||
size_t column;
|
||||
inline Source withLine(size_t line) const {
|
||||
return Source(path, line);
|
||||
}
|
||||
};
|
||||
|
||||
//
|
||||
// Implementations
|
||||
//
|
||||
|
||||
SourceLine Source::line(size_t line) const {
|
||||
return SourceLine{ path, line };
|
||||
}
|
||||
|
||||
SourceLineColumn SourceLine::column(size_t column) const {
|
||||
return SourceLineColumn{ path, line, column };
|
||||
}
|
||||
|
||||
inline ::std::ostream& operator<<(::std::ostream& out, const Source& source) {
|
||||
return out << source.path;
|
||||
out << source.path;
|
||||
if (source.line) {
|
||||
out << ":" << source.line.value();
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
inline ::std::ostream& operator<<(::std::ostream& out, const SourceLine& source) {
|
||||
return out << source.path << ":" << source.line;
|
||||
}
|
||||
|
||||
inline ::std::ostream& operator<<(::std::ostream& out, const SourceLineColumn& source) {
|
||||
return out << source.path << ":" << source.line << ":" << source.column;
|
||||
}
|
||||
|
||||
inline bool operator<(const SourceLine& lhs, const SourceLine& rhs) {
|
||||
return std::tie(lhs.path, lhs.line) < std::tie(rhs.path, rhs.line);
|
||||
inline bool operator<(const Source& lhs, const Source& rhs) {
|
||||
int cmp = lhs.path.compare(rhs.path);
|
||||
if (cmp < 0) return true;
|
||||
if (cmp > 0) return false;
|
||||
if (lhs.line) {
|
||||
if (rhs.line) {
|
||||
return lhs.line.value() < rhs.line.value();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return bool(rhs.line);
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
@@ -1,91 +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_SOURCE_XML_PULL_PARSER_H
|
||||
#define AAPT_SOURCE_XML_PULL_PARSER_H
|
||||
|
||||
#include "XmlPullParser.h"
|
||||
|
||||
#include <istream>
|
||||
#include <expat.h>
|
||||
#include <queue>
|
||||
#include <stack>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
class SourceXmlPullParser : public XmlPullParser {
|
||||
public:
|
||||
SourceXmlPullParser(std::istream& in);
|
||||
SourceXmlPullParser(const SourceXmlPullParser& rhs) = delete;
|
||||
~SourceXmlPullParser();
|
||||
|
||||
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:
|
||||
static void XMLCALL startNamespaceHandler(void* userData, const char* prefix, const char* uri);
|
||||
static void XMLCALL startElementHandler(void* userData, const char* name, const char** attrs);
|
||||
static void XMLCALL characterDataHandler(void* userData, const char* s, int len);
|
||||
static void XMLCALL endElementHandler(void* userData, const char* name);
|
||||
static void XMLCALL endNamespaceHandler(void* userData, const char* prefix);
|
||||
static void XMLCALL commentDataHandler(void* userData, const char* comment);
|
||||
|
||||
struct EventData {
|
||||
Event event;
|
||||
size_t lineNumber;
|
||||
size_t depth;
|
||||
std::u16string data1;
|
||||
std::u16string data2;
|
||||
std::u16string comment;
|
||||
std::vector<Attribute> attributes;
|
||||
};
|
||||
|
||||
std::istream& mIn;
|
||||
XML_Parser mParser;
|
||||
char mBuffer[16384];
|
||||
std::queue<EventData> mEventQueue;
|
||||
std::string mLastError;
|
||||
const std::u16string mEmpty;
|
||||
size_t mDepth;
|
||||
std::stack<std::u16string> mNamespaceUris;
|
||||
std::vector<std::pair<std::u16string, std::u16string>> mPackageAliases;
|
||||
};
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
#endif // AAPT_SOURCE_XML_PULL_PARSER_H
|
||||
@@ -14,10 +14,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "BigBuffer.h"
|
||||
#include "StringPiece.h"
|
||||
#include "util/BigBuffer.h"
|
||||
#include "util/StringPiece.h"
|
||||
#include "StringPool.h"
|
||||
#include "Util.h"
|
||||
#include "util/Util.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <androidfw/ResourceTypes.h>
|
||||
@@ -219,7 +219,7 @@ void StringPool::prune() {
|
||||
auto indexIter = std::begin(mIndexedStrings);
|
||||
while (indexIter != iterEnd) {
|
||||
if (indexIter->second->ref <= 0) {
|
||||
mIndexedStrings.erase(indexIter++);
|
||||
indexIter = mIndexedStrings.erase(indexIter);
|
||||
} else {
|
||||
++indexIter;
|
||||
}
|
||||
@@ -241,6 +241,12 @@ void StringPool::prune() {
|
||||
// a deleted string from the StyleEntry.
|
||||
mStrings.erase(endIter2, std::end(mStrings));
|
||||
mStyles.erase(endIter3, std::end(mStyles));
|
||||
|
||||
// Reassign the indices.
|
||||
const size_t len = mStrings.size();
|
||||
for (size_t index = 0; index < len; index++) {
|
||||
mStrings[index]->index = index;
|
||||
}
|
||||
}
|
||||
|
||||
void StringPool::sort(const std::function<bool(const Entry&, const Entry&)>& cmp) {
|
||||
|
||||
@@ -17,9 +17,9 @@
|
||||
#ifndef AAPT_STRING_POOL_H
|
||||
#define AAPT_STRING_POOL_H
|
||||
|
||||
#include "BigBuffer.h"
|
||||
#include "util/BigBuffer.h"
|
||||
#include "ConfigDescription.h"
|
||||
#include "StringPiece.h"
|
||||
#include "util/StringPiece.h"
|
||||
|
||||
#include <functional>
|
||||
#include <map>
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
#include "StringPool.h"
|
||||
#include "Util.h"
|
||||
#include "util/Util.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <string>
|
||||
@@ -67,15 +67,23 @@ TEST(StringPoolTest, MaintainInsertionOrderIndex) {
|
||||
TEST(StringPoolTest, PruneStringsWithNoReferences) {
|
||||
StringPool pool;
|
||||
|
||||
StringPool::Ref refA = pool.makeRef(u"foo");
|
||||
{
|
||||
StringPool::Ref ref = pool.makeRef(u"wut");
|
||||
EXPECT_EQ(*ref, u"wut");
|
||||
EXPECT_EQ(1u, pool.size());
|
||||
EXPECT_EQ(2u, pool.size());
|
||||
}
|
||||
StringPool::Ref refB = pool.makeRef(u"bar");
|
||||
|
||||
EXPECT_EQ(1u, pool.size());
|
||||
EXPECT_EQ(3u, pool.size());
|
||||
pool.prune();
|
||||
EXPECT_EQ(0u, pool.size());
|
||||
EXPECT_EQ(2u, pool.size());
|
||||
StringPool::const_iterator iter = begin(pool);
|
||||
EXPECT_EQ((*iter)->value, u"foo");
|
||||
EXPECT_LT((*iter)->index, 2u);
|
||||
++iter;
|
||||
EXPECT_EQ((*iter)->value, u"bar");
|
||||
EXPECT_LT((*iter)->index, 2u);
|
||||
}
|
||||
|
||||
TEST(StringPoolTest, SortAndMaintainIndexesInReferences) {
|
||||
|
||||
@@ -1,570 +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 "BigBuffer.h"
|
||||
#include "ConfigDescription.h"
|
||||
#include "Logger.h"
|
||||
#include "ResourceTable.h"
|
||||
#include "ResourceTypeExtensions.h"
|
||||
#include "ResourceValues.h"
|
||||
#include "StringPool.h"
|
||||
#include "TableFlattener.h"
|
||||
#include "Util.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <androidfw/ResourceTypes.h>
|
||||
#include <sstream>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
struct FlatEntry {
|
||||
const ResourceEntry* entry;
|
||||
const Value* value;
|
||||
uint32_t entryKey;
|
||||
uint32_t sourcePathKey;
|
||||
uint32_t sourceLine;
|
||||
};
|
||||
|
||||
/**
|
||||
* Visitor that knows how to encode Map values.
|
||||
*/
|
||||
class MapFlattener : public ConstValueVisitor {
|
||||
public:
|
||||
MapFlattener(BigBuffer* out, const FlatEntry& flatEntry, SymbolEntryVector* symbols) :
|
||||
mOut(out), mSymbols(symbols) {
|
||||
mMap = mOut->nextBlock<android::ResTable_map_entry>();
|
||||
mMap->key.index = flatEntry.entryKey;
|
||||
mMap->flags = android::ResTable_entry::FLAG_COMPLEX;
|
||||
if (flatEntry.entry->publicStatus.isPublic) {
|
||||
mMap->flags |= android::ResTable_entry::FLAG_PUBLIC;
|
||||
}
|
||||
if (flatEntry.value->isWeak()) {
|
||||
mMap->flags |= android::ResTable_entry::FLAG_WEAK;
|
||||
}
|
||||
|
||||
ResTable_entry_source* sourceBlock = mOut->nextBlock<ResTable_entry_source>();
|
||||
sourceBlock->pathIndex = flatEntry.sourcePathKey;
|
||||
sourceBlock->line = flatEntry.sourceLine;
|
||||
|
||||
mMap->size = sizeof(*mMap) + sizeof(*sourceBlock);
|
||||
}
|
||||
|
||||
void flattenParent(const Reference& ref) {
|
||||
if (!ref.id.isValid()) {
|
||||
mSymbols->push_back({
|
||||
ResourceNameRef(ref.name),
|
||||
(mOut->size() - mMap->size) + sizeof(*mMap) - sizeof(android::ResTable_entry)
|
||||
});
|
||||
}
|
||||
mMap->parent.ident = ref.id.id;
|
||||
}
|
||||
|
||||
void flattenEntry(const Reference& key, const Item& value) {
|
||||
mMap->count++;
|
||||
|
||||
android::ResTable_map* outMapEntry = mOut->nextBlock<android::ResTable_map>();
|
||||
|
||||
// Write the key.
|
||||
if (!Res_INTERNALID(key.id.id) && !key.id.isValid()) {
|
||||
assert(!key.name.entry.empty());
|
||||
mSymbols->push_back(std::make_pair(ResourceNameRef(key.name),
|
||||
mOut->size() - sizeof(*outMapEntry)));
|
||||
}
|
||||
outMapEntry->name.ident = key.id.id;
|
||||
|
||||
// Write the value.
|
||||
value.flatten(outMapEntry->value);
|
||||
|
||||
if (outMapEntry->value.data == 0x0) {
|
||||
visitFunc<Reference>(value, [&](const Reference& reference) {
|
||||
mSymbols->push_back(std::make_pair(ResourceNameRef(reference.name),
|
||||
mOut->size() - sizeof(outMapEntry->value.data)));
|
||||
});
|
||||
}
|
||||
outMapEntry->value.size = sizeof(outMapEntry->value);
|
||||
}
|
||||
|
||||
void flattenValueOnly(const Item& value) {
|
||||
mMap->count++;
|
||||
|
||||
android::ResTable_map* outMapEntry = mOut->nextBlock<android::ResTable_map>();
|
||||
|
||||
// Write the value.
|
||||
value.flatten(outMapEntry->value);
|
||||
|
||||
if (outMapEntry->value.data == 0x0) {
|
||||
visitFunc<Reference>(value, [&](const Reference& reference) {
|
||||
mSymbols->push_back(std::make_pair(ResourceNameRef(reference.name),
|
||||
mOut->size() - sizeof(outMapEntry->value.data)));
|
||||
});
|
||||
}
|
||||
outMapEntry->value.size = sizeof(outMapEntry->value);
|
||||
}
|
||||
|
||||
static bool compareStyleEntries(const Style::Entry* lhs, const Style::Entry* rhs) {
|
||||
return lhs->key.id < rhs->key.id;
|
||||
}
|
||||
|
||||
void visit(const Style& style, ValueVisitorArgs&) override {
|
||||
if (style.parent.name.isValid()) {
|
||||
flattenParent(style.parent);
|
||||
}
|
||||
|
||||
// First sort the entries by ID.
|
||||
std::vector<const Style::Entry*> sortedEntries;
|
||||
for (const auto& styleEntry : style.entries) {
|
||||
auto iter = std::lower_bound(sortedEntries.begin(), sortedEntries.end(),
|
||||
&styleEntry, compareStyleEntries);
|
||||
sortedEntries.insert(iter, &styleEntry);
|
||||
}
|
||||
|
||||
for (const Style::Entry* styleEntry : sortedEntries) {
|
||||
flattenEntry(styleEntry->key, *styleEntry->value);
|
||||
}
|
||||
}
|
||||
|
||||
void visit(const Attribute& attr, ValueVisitorArgs&) override {
|
||||
android::Res_value tempVal;
|
||||
tempVal.dataType = android::Res_value::TYPE_INT_DEC;
|
||||
tempVal.data = attr.typeMask;
|
||||
flattenEntry(Reference(ResourceId{android::ResTable_map::ATTR_TYPE}),
|
||||
BinaryPrimitive(tempVal));
|
||||
|
||||
for (const auto& symbol : attr.symbols) {
|
||||
tempVal.data = symbol.value;
|
||||
flattenEntry(symbol.symbol, BinaryPrimitive(tempVal));
|
||||
}
|
||||
}
|
||||
|
||||
void visit(const Styleable& styleable, ValueVisitorArgs&) override {
|
||||
for (const auto& attr : styleable.entries) {
|
||||
flattenEntry(attr, BinaryPrimitive(android::Res_value{}));
|
||||
}
|
||||
}
|
||||
|
||||
void visit(const Array& array, ValueVisitorArgs&) override {
|
||||
for (const auto& item : array.items) {
|
||||
flattenValueOnly(*item);
|
||||
}
|
||||
}
|
||||
|
||||
void visit(const Plural& plural, ValueVisitorArgs&) override {
|
||||
const size_t count = plural.values.size();
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
if (!plural.values[i]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ResourceId q;
|
||||
switch (i) {
|
||||
case Plural::Zero:
|
||||
q.id = android::ResTable_map::ATTR_ZERO;
|
||||
break;
|
||||
|
||||
case Plural::One:
|
||||
q.id = android::ResTable_map::ATTR_ONE;
|
||||
break;
|
||||
|
||||
case Plural::Two:
|
||||
q.id = android::ResTable_map::ATTR_TWO;
|
||||
break;
|
||||
|
||||
case Plural::Few:
|
||||
q.id = android::ResTable_map::ATTR_FEW;
|
||||
break;
|
||||
|
||||
case Plural::Many:
|
||||
q.id = android::ResTable_map::ATTR_MANY;
|
||||
break;
|
||||
|
||||
case Plural::Other:
|
||||
q.id = android::ResTable_map::ATTR_OTHER;
|
||||
break;
|
||||
|
||||
default:
|
||||
assert(false);
|
||||
break;
|
||||
}
|
||||
|
||||
flattenEntry(Reference(q), *plural.values[i]);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
BigBuffer* mOut;
|
||||
SymbolEntryVector* mSymbols;
|
||||
android::ResTable_map_entry* mMap;
|
||||
};
|
||||
|
||||
/**
|
||||
* Flattens a value, with special handling for References.
|
||||
*/
|
||||
struct ValueFlattener : ConstValueVisitor {
|
||||
ValueFlattener(BigBuffer* out, SymbolEntryVector* symbols) :
|
||||
result(false), mOut(out), mOutValue(nullptr), mSymbols(symbols) {
|
||||
mOutValue = mOut->nextBlock<android::Res_value>();
|
||||
}
|
||||
|
||||
virtual void visit(const Reference& ref, ValueVisitorArgs& a) override {
|
||||
visitItem(ref, a);
|
||||
if (mOutValue->data == 0x0) {
|
||||
mSymbols->push_back({
|
||||
ResourceNameRef(ref.name),
|
||||
mOut->size() - sizeof(mOutValue->data)});
|
||||
}
|
||||
}
|
||||
|
||||
virtual void visitItem(const Item& item, ValueVisitorArgs&) override {
|
||||
result = item.flatten(*mOutValue);
|
||||
mOutValue->res0 = 0;
|
||||
mOutValue->size = sizeof(*mOutValue);
|
||||
}
|
||||
|
||||
bool result;
|
||||
|
||||
private:
|
||||
BigBuffer* mOut;
|
||||
android::Res_value* mOutValue;
|
||||
SymbolEntryVector* mSymbols;
|
||||
};
|
||||
|
||||
TableFlattener::TableFlattener(Options options)
|
||||
: mOptions(options) {
|
||||
}
|
||||
|
||||
bool TableFlattener::flattenValue(BigBuffer* out, const FlatEntry& flatEntry,
|
||||
SymbolEntryVector* symbols) {
|
||||
if (flatEntry.value->isItem()) {
|
||||
android::ResTable_entry* entry = out->nextBlock<android::ResTable_entry>();
|
||||
|
||||
if (flatEntry.entry->publicStatus.isPublic) {
|
||||
entry->flags |= android::ResTable_entry::FLAG_PUBLIC;
|
||||
}
|
||||
|
||||
if (flatEntry.value->isWeak()) {
|
||||
entry->flags |= android::ResTable_entry::FLAG_WEAK;
|
||||
}
|
||||
|
||||
entry->key.index = flatEntry.entryKey;
|
||||
entry->size = sizeof(*entry);
|
||||
|
||||
if (mOptions.useExtendedChunks) {
|
||||
// Write the extra source block. This will be ignored by
|
||||
// the Android runtime.
|
||||
ResTable_entry_source* sourceBlock = out->nextBlock<ResTable_entry_source>();
|
||||
sourceBlock->pathIndex = flatEntry.sourcePathKey;
|
||||
sourceBlock->line = flatEntry.sourceLine;
|
||||
entry->size += sizeof(*sourceBlock);
|
||||
}
|
||||
|
||||
const Item* item = static_cast<const Item*>(flatEntry.value);
|
||||
ValueFlattener flattener(out, symbols);
|
||||
item->accept(flattener, {});
|
||||
return flattener.result;
|
||||
}
|
||||
|
||||
MapFlattener flattener(out, flatEntry, symbols);
|
||||
flatEntry.value->accept(flattener, {});
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TableFlattener::flatten(BigBuffer* out, const ResourceTable& table) {
|
||||
const size_t beginning = out->size();
|
||||
|
||||
if (table.getPackageId() == ResourceTable::kUnsetPackageId) {
|
||||
Logger::error()
|
||||
<< "ResourceTable has no package ID set."
|
||||
<< std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
SymbolEntryVector symbolEntries;
|
||||
|
||||
StringPool typePool;
|
||||
StringPool keyPool;
|
||||
StringPool sourcePool;
|
||||
|
||||
// Sort the types by their IDs. They will be inserted into the StringPool
|
||||
// in this order.
|
||||
std::vector<ResourceTableType*> sortedTypes;
|
||||
for (const auto& type : table) {
|
||||
if (type->type == ResourceType::kStyleable && !mOptions.useExtendedChunks) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto iter = std::lower_bound(std::begin(sortedTypes), std::end(sortedTypes), type.get(),
|
||||
[](const ResourceTableType* lhs, const ResourceTableType* rhs) -> bool {
|
||||
return lhs->typeId < rhs->typeId;
|
||||
});
|
||||
sortedTypes.insert(iter, type.get());
|
||||
}
|
||||
|
||||
BigBuffer typeBlock(1024);
|
||||
size_t expectedTypeId = 1;
|
||||
for (const ResourceTableType* type : sortedTypes) {
|
||||
if (type->typeId == ResourceTableType::kUnsetTypeId
|
||||
|| type->typeId == 0) {
|
||||
Logger::error()
|
||||
<< "resource type '"
|
||||
<< type->type
|
||||
<< "' from package '"
|
||||
<< table.getPackage()
|
||||
<< "' has no ID."
|
||||
<< std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
// If there is a gap in the type IDs, fill in the StringPool
|
||||
// with empty values until we reach the ID we expect.
|
||||
while (type->typeId > expectedTypeId) {
|
||||
std::u16string typeName(u"?");
|
||||
typeName += expectedTypeId;
|
||||
typePool.makeRef(typeName);
|
||||
expectedTypeId++;
|
||||
}
|
||||
expectedTypeId++;
|
||||
typePool.makeRef(toString(type->type));
|
||||
|
||||
android::ResTable_typeSpec* spec = typeBlock.nextBlock<android::ResTable_typeSpec>();
|
||||
spec->header.type = android::RES_TABLE_TYPE_SPEC_TYPE;
|
||||
spec->header.headerSize = sizeof(*spec);
|
||||
spec->header.size = spec->header.headerSize + (type->entries.size() * sizeof(uint32_t));
|
||||
spec->id = type->typeId;
|
||||
spec->entryCount = type->entries.size();
|
||||
|
||||
if (type->entries.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Reserve space for the masks of each resource in this type. These
|
||||
// show for which configuration axis the resource changes.
|
||||
uint32_t* configMasks = typeBlock.nextBlock<uint32_t>(type->entries.size());
|
||||
|
||||
// Sort the entries by entry ID and write their configuration masks.
|
||||
std::vector<ResourceEntry*> entries;
|
||||
const size_t entryCount = type->entries.size();
|
||||
for (size_t entryIndex = 0; entryIndex < entryCount; entryIndex++) {
|
||||
const auto& entry = type->entries[entryIndex];
|
||||
|
||||
if (entry->entryId == ResourceEntry::kUnsetEntryId) {
|
||||
Logger::error()
|
||||
<< "resource '"
|
||||
<< ResourceName{ table.getPackage(), type->type, entry->name }
|
||||
<< "' has no ID."
|
||||
<< std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto iter = std::lower_bound(std::begin(entries), std::end(entries), entry.get(),
|
||||
[](const ResourceEntry* lhs, const ResourceEntry* rhs) -> bool {
|
||||
return lhs->entryId < rhs->entryId;
|
||||
});
|
||||
entries.insert(iter, entry.get());
|
||||
|
||||
// Populate the config masks for this entry.
|
||||
if (entry->publicStatus.isPublic) {
|
||||
configMasks[entry->entryId] |= android::ResTable_typeSpec::SPEC_PUBLIC;
|
||||
}
|
||||
|
||||
const size_t configCount = entry->values.size();
|
||||
for (size_t i = 0; i < configCount; i++) {
|
||||
const ConfigDescription& config = entry->values[i].config;
|
||||
for (size_t j = i + 1; j < configCount; j++) {
|
||||
configMasks[entry->entryId] |= config.diff(entry->values[j].config);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const size_t beforePublicHeader = typeBlock.size();
|
||||
Public_header* publicHeader = nullptr;
|
||||
if (mOptions.useExtendedChunks) {
|
||||
publicHeader = typeBlock.nextBlock<Public_header>();
|
||||
publicHeader->header.type = RES_TABLE_PUBLIC_TYPE;
|
||||
publicHeader->header.headerSize = sizeof(*publicHeader);
|
||||
publicHeader->typeId = type->typeId;
|
||||
}
|
||||
|
||||
// The binary resource table lists resource entries for each configuration.
|
||||
// We store them inverted, where a resource entry lists the values for each
|
||||
// configuration available. Here we reverse this to match the binary table.
|
||||
std::map<ConfigDescription, std::vector<FlatEntry>> data;
|
||||
for (const ResourceEntry* entry : entries) {
|
||||
size_t keyIndex = keyPool.makeRef(entry->name).getIndex();
|
||||
|
||||
if (keyIndex > std::numeric_limits<uint32_t>::max()) {
|
||||
Logger::error()
|
||||
<< "resource key string pool exceeded max size."
|
||||
<< std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (publicHeader && entry->publicStatus.isPublic) {
|
||||
// Write the public status of this entry.
|
||||
Public_entry* publicEntry = typeBlock.nextBlock<Public_entry>();
|
||||
publicEntry->entryId = static_cast<uint32_t>(entry->entryId);
|
||||
publicEntry->key.index = static_cast<uint32_t>(keyIndex);
|
||||
publicEntry->source.index = static_cast<uint32_t>(sourcePool.makeRef(
|
||||
util::utf8ToUtf16(entry->publicStatus.source.path)).getIndex());
|
||||
publicEntry->sourceLine = static_cast<uint32_t>(entry->publicStatus.source.line);
|
||||
publicHeader->count += 1;
|
||||
}
|
||||
|
||||
for (const auto& configValue : entry->values) {
|
||||
data[configValue.config].push_back(FlatEntry{
|
||||
entry,
|
||||
configValue.value.get(),
|
||||
static_cast<uint32_t>(keyIndex),
|
||||
static_cast<uint32_t>(sourcePool.makeRef(util::utf8ToUtf16(
|
||||
configValue.source.path)).getIndex()),
|
||||
static_cast<uint32_t>(configValue.source.line)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (publicHeader) {
|
||||
typeBlock.align4();
|
||||
publicHeader->header.size =
|
||||
static_cast<uint32_t>(typeBlock.size() - beforePublicHeader);
|
||||
}
|
||||
|
||||
// Begin flattening a configuration for the current type.
|
||||
for (const auto& entry : data) {
|
||||
const size_t typeHeaderStart = typeBlock.size();
|
||||
android::ResTable_type* typeHeader = typeBlock.nextBlock<android::ResTable_type>();
|
||||
typeHeader->header.type = android::RES_TABLE_TYPE_TYPE;
|
||||
typeHeader->header.headerSize = sizeof(*typeHeader);
|
||||
typeHeader->id = type->typeId;
|
||||
typeHeader->entryCount = type->entries.size();
|
||||
typeHeader->entriesStart = typeHeader->header.headerSize
|
||||
+ (sizeof(uint32_t) * type->entries.size());
|
||||
typeHeader->config = entry.first;
|
||||
|
||||
uint32_t* indices = typeBlock.nextBlock<uint32_t>(type->entries.size());
|
||||
memset(indices, 0xff, type->entries.size() * sizeof(uint32_t));
|
||||
|
||||
const size_t entryStart = typeBlock.size();
|
||||
for (const FlatEntry& flatEntry : entry.second) {
|
||||
assert(flatEntry.entry->entryId < type->entries.size());
|
||||
indices[flatEntry.entry->entryId] = typeBlock.size() - entryStart;
|
||||
if (!flattenValue(&typeBlock, flatEntry, &symbolEntries)) {
|
||||
Logger::error()
|
||||
<< "failed to flatten resource '"
|
||||
<< ResourceNameRef {
|
||||
table.getPackage(), type->type, flatEntry.entry->name }
|
||||
<< "' for configuration '"
|
||||
<< entry.first
|
||||
<< "'."
|
||||
<< std::endl;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
typeBlock.align4();
|
||||
typeHeader->header.size = typeBlock.size() - typeHeaderStart;
|
||||
}
|
||||
}
|
||||
|
||||
const size_t beforeTable = out->size();
|
||||
android::ResTable_header* header = out->nextBlock<android::ResTable_header>();
|
||||
header->header.type = android::RES_TABLE_TYPE;
|
||||
header->header.headerSize = sizeof(*header);
|
||||
header->packageCount = 1;
|
||||
|
||||
SymbolTable_entry* symbolEntryData = nullptr;
|
||||
if (!symbolEntries.empty() && mOptions.useExtendedChunks) {
|
||||
const size_t beforeSymbolTable = out->size();
|
||||
StringPool symbolPool;
|
||||
SymbolTable_header* symbolHeader = out->nextBlock<SymbolTable_header>();
|
||||
symbolHeader->header.type = RES_TABLE_SYMBOL_TABLE_TYPE;
|
||||
symbolHeader->header.headerSize = sizeof(*symbolHeader);
|
||||
symbolHeader->count = symbolEntries.size();
|
||||
|
||||
symbolEntryData = out->nextBlock<SymbolTable_entry>(symbolHeader->count);
|
||||
|
||||
size_t i = 0;
|
||||
for (const auto& entry : symbolEntries) {
|
||||
symbolEntryData[i].offset = entry.second;
|
||||
StringPool::Ref ref = symbolPool.makeRef(
|
||||
entry.first.package.toString() + u":" +
|
||||
toString(entry.first.type).toString() + u"/" +
|
||||
entry.first.entry.toString());
|
||||
symbolEntryData[i].stringIndex = ref.getIndex();
|
||||
i++;
|
||||
}
|
||||
|
||||
StringPool::flattenUtf8(out, symbolPool);
|
||||
out->align4();
|
||||
symbolHeader->header.size = out->size() - beforeSymbolTable;
|
||||
}
|
||||
|
||||
if (sourcePool.size() > 0 && mOptions.useExtendedChunks) {
|
||||
const size_t beforeSourcePool = out->size();
|
||||
android::ResChunk_header* sourceHeader = out->nextBlock<android::ResChunk_header>();
|
||||
sourceHeader->type = RES_TABLE_SOURCE_POOL_TYPE;
|
||||
sourceHeader->headerSize = sizeof(*sourceHeader);
|
||||
StringPool::flattenUtf8(out, sourcePool);
|
||||
out->align4();
|
||||
sourceHeader->size = out->size() - beforeSourcePool;
|
||||
}
|
||||
|
||||
StringPool::flattenUtf8(out, table.getValueStringPool());
|
||||
|
||||
const size_t beforePackageIndex = out->size();
|
||||
android::ResTable_package* package = out->nextBlock<android::ResTable_package>();
|
||||
package->header.type = android::RES_TABLE_PACKAGE_TYPE;
|
||||
package->header.headerSize = sizeof(*package);
|
||||
|
||||
if (table.getPackageId() > std::numeric_limits<uint8_t>::max()) {
|
||||
Logger::error()
|
||||
<< "package ID 0x'"
|
||||
<< std::hex << table.getPackageId() << std::dec
|
||||
<< "' is invalid."
|
||||
<< std::endl;
|
||||
return false;
|
||||
}
|
||||
package->id = table.getPackageId();
|
||||
|
||||
if (table.getPackage().size() >= sizeof(package->name) / sizeof(package->name[0])) {
|
||||
Logger::error()
|
||||
<< "package name '"
|
||||
<< table.getPackage()
|
||||
<< "' is too long."
|
||||
<< std::endl;
|
||||
return false;
|
||||
}
|
||||
memcpy(package->name, reinterpret_cast<const uint16_t*>(table.getPackage().data()),
|
||||
table.getPackage().length() * sizeof(char16_t));
|
||||
package->name[table.getPackage().length()] = 0;
|
||||
|
||||
package->typeStrings = package->header.headerSize;
|
||||
StringPool::flattenUtf16(out, typePool);
|
||||
package->keyStrings = out->size() - beforePackageIndex;
|
||||
StringPool::flattenUtf16(out, keyPool);
|
||||
|
||||
if (symbolEntryData != nullptr) {
|
||||
for (size_t i = 0; i < symbolEntries.size(); i++) {
|
||||
symbolEntryData[i].offset += out->size() - beginning;
|
||||
}
|
||||
}
|
||||
|
||||
out->appendBuffer(std::move(typeBlock));
|
||||
|
||||
package->header.size = out->size() - beforePackageIndex;
|
||||
header->header.size = out->size() - beforeTable;
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
@@ -1,61 +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_TABLE_FLATTENER_H
|
||||
#define AAPT_TABLE_FLATTENER_H
|
||||
|
||||
#include "BigBuffer.h"
|
||||
#include "ResourceTable.h"
|
||||
|
||||
namespace aapt {
|
||||
|
||||
using SymbolEntryVector = std::vector<std::pair<ResourceNameRef, uint32_t>>;
|
||||
|
||||
struct FlatEntry;
|
||||
|
||||
/**
|
||||
* Flattens a ResourceTable into a binary format suitable
|
||||
* for loading into a ResTable on the host or device.
|
||||
*/
|
||||
struct TableFlattener {
|
||||
/**
|
||||
* A set of options for this TableFlattener.
|
||||
*/
|
||||
struct Options {
|
||||
/**
|
||||
* Specifies whether to output extended chunks, like
|
||||
* source information and mising symbol entries. Default
|
||||
* is true.
|
||||
*
|
||||
* Set this to false when emitting the final table to be used
|
||||
* on device.
|
||||
*/
|
||||
bool useExtendedChunks = true;
|
||||
};
|
||||
|
||||
TableFlattener(Options options);
|
||||
|
||||
bool flatten(BigBuffer* out, const ResourceTable& table);
|
||||
|
||||
private:
|
||||
bool flattenValue(BigBuffer* out, const FlatEntry& flatEntry, SymbolEntryVector* symbols);
|
||||
|
||||
Options mOptions;
|
||||
};
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
#endif // AAPT_TABLE_FLATTENER_H
|
||||
133
tools/aapt2/ValueVisitor.h
Normal file
133
tools/aapt2/ValueVisitor.h
Normal file
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
* 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_VALUE_VISITOR_H
|
||||
#define AAPT_VALUE_VISITOR_H
|
||||
|
||||
#include "ResourceValues.h"
|
||||
|
||||
namespace aapt {
|
||||
|
||||
/**
|
||||
* Visits a value and invokes the appropriate method based on its type. Does not traverse
|
||||
* into compound types. Use ValueVisitor for that.
|
||||
*/
|
||||
struct RawValueVisitor {
|
||||
virtual ~RawValueVisitor() = default;
|
||||
|
||||
virtual void visitItem(Item* value) {}
|
||||
virtual void visit(Reference* value) { visitItem(value); }
|
||||
virtual void visit(RawString* value) { visitItem(value); }
|
||||
virtual void visit(String* value) { visitItem(value); }
|
||||
virtual void visit(StyledString* value) { visitItem(value); }
|
||||
virtual void visit(FileReference* value) { visitItem(value); }
|
||||
virtual void visit(Id* value) { visitItem(value); }
|
||||
virtual void visit(BinaryPrimitive* value) { visitItem(value); }
|
||||
|
||||
virtual void visit(Attribute* value) {}
|
||||
virtual void visit(Style* value) {}
|
||||
virtual void visit(Array* value) {}
|
||||
virtual void visit(Plural* value) {}
|
||||
virtual void visit(Styleable* value) {}
|
||||
};
|
||||
|
||||
#define DECL_VISIT_COMPOUND_VALUE(T) \
|
||||
virtual void visit(T* value) { \
|
||||
visitSubValues(value); \
|
||||
}
|
||||
|
||||
/**
|
||||
* Visits values, and if they are compound values, visits the components as well.
|
||||
*/
|
||||
struct ValueVisitor : public RawValueVisitor {
|
||||
// The compiler will think we're hiding an overload, when we actually intend
|
||||
// to call into RawValueVisitor. This will expose the visit methods in the super
|
||||
// class so the compiler knows we are trying to call them.
|
||||
using RawValueVisitor::visit;
|
||||
|
||||
void visitSubValues(Attribute* attribute) {
|
||||
for (Attribute::Symbol& symbol : attribute->symbols) {
|
||||
visit(&symbol.symbol);
|
||||
}
|
||||
}
|
||||
|
||||
void visitSubValues(Style* style) {
|
||||
if (style->parent) {
|
||||
visit(&style->parent.value());
|
||||
}
|
||||
|
||||
for (Style::Entry& entry : style->entries) {
|
||||
visit(&entry.key);
|
||||
entry.value->accept(this);
|
||||
}
|
||||
}
|
||||
|
||||
void visitSubValues(Array* array) {
|
||||
for (std::unique_ptr<Item>& item : array->items) {
|
||||
item->accept(this);
|
||||
}
|
||||
}
|
||||
|
||||
void visitSubValues(Plural* plural) {
|
||||
for (std::unique_ptr<Item>& item : plural->values) {
|
||||
if (item) {
|
||||
item->accept(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void visitSubValues(Styleable* styleable) {
|
||||
for (Reference& reference : styleable->entries) {
|
||||
visit(&reference);
|
||||
}
|
||||
}
|
||||
|
||||
DECL_VISIT_COMPOUND_VALUE(Attribute);
|
||||
DECL_VISIT_COMPOUND_VALUE(Style);
|
||||
DECL_VISIT_COMPOUND_VALUE(Array);
|
||||
DECL_VISIT_COMPOUND_VALUE(Plural);
|
||||
DECL_VISIT_COMPOUND_VALUE(Styleable);
|
||||
};
|
||||
|
||||
/**
|
||||
* Do not use directly. Helper struct for dyn_cast.
|
||||
*/
|
||||
template <typename T>
|
||||
struct DynCastVisitor : public RawValueVisitor {
|
||||
T* value = nullptr;
|
||||
|
||||
void visit(T* v) override {
|
||||
value = v;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a valid pointer to T if the Value is of subtype T.
|
||||
* Otherwise, returns nullptr.
|
||||
*/
|
||||
template <typename T>
|
||||
T* valueCast(Value* value) {
|
||||
if (!value) {
|
||||
return nullptr;
|
||||
}
|
||||
DynCastVisitor<T> visitor;
|
||||
value->accept(&visitor);
|
||||
return visitor.value;
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
#endif // AAPT_VALUE_VISITOR_H
|
||||
87
tools/aapt2/ValueVisitor_test.cpp
Normal file
87
tools/aapt2/ValueVisitor_test.cpp
Normal file
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* 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 <gtest/gtest.h>
|
||||
#include <string>
|
||||
|
||||
#include "ResourceValues.h"
|
||||
#include "util/Util.h"
|
||||
#include "ValueVisitor.h"
|
||||
#include "test/Builders.h"
|
||||
|
||||
namespace aapt {
|
||||
|
||||
struct SingleReferenceVisitor : public ValueVisitor {
|
||||
using ValueVisitor::visit;
|
||||
|
||||
Reference* visited = nullptr;
|
||||
|
||||
void visit(Reference* ref) override {
|
||||
visited = ref;
|
||||
}
|
||||
};
|
||||
|
||||
struct StyleVisitor : public ValueVisitor {
|
||||
using ValueVisitor::visit;
|
||||
|
||||
std::list<Reference*> visitedRefs;
|
||||
Style* visitedStyle = nullptr;
|
||||
|
||||
void visit(Reference* ref) override {
|
||||
visitedRefs.push_back(ref);
|
||||
}
|
||||
|
||||
void visit(Style* style) override {
|
||||
visitedStyle = style;
|
||||
ValueVisitor::visit(style);
|
||||
}
|
||||
};
|
||||
|
||||
TEST(ValueVisitorTest, VisitsReference) {
|
||||
Reference ref(ResourceName{u"android", ResourceType::kAttr, u"foo"});
|
||||
SingleReferenceVisitor visitor;
|
||||
ref.accept(&visitor);
|
||||
|
||||
EXPECT_EQ(visitor.visited, &ref);
|
||||
}
|
||||
|
||||
TEST(ValueVisitorTest, VisitsReferencesInStyle) {
|
||||
std::unique_ptr<Style> style = test::StyleBuilder()
|
||||
.setParent(u"@android:style/foo")
|
||||
.addItem(u"@android:attr/one", test::buildReference(u"@android:id/foo"))
|
||||
.build();
|
||||
|
||||
StyleVisitor visitor;
|
||||
style->accept(&visitor);
|
||||
|
||||
ASSERT_EQ(style.get(), visitor.visitedStyle);
|
||||
|
||||
// Entry attribute references, plus the parent reference, plus one value reference.
|
||||
ASSERT_EQ(style->entries.size() + 2, visitor.visitedRefs.size());
|
||||
}
|
||||
|
||||
TEST(ValueVisitorTest, ValueCast) {
|
||||
std::unique_ptr<Reference> ref = test::buildReference(u"@android:color/white");
|
||||
EXPECT_NE(valueCast<Reference>(ref.get()), nullptr);
|
||||
|
||||
std::unique_ptr<Style> style = test::StyleBuilder()
|
||||
.addItem(u"@android:attr/foo", test::buildReference(u"@android:color/black"))
|
||||
.build();
|
||||
EXPECT_NE(valueCast<Style>(style.get()), nullptr);
|
||||
EXPECT_EQ(valueCast<Reference>(style.get()), nullptr);
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
@@ -1,113 +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 "XliffXmlPullParser.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
XliffXmlPullParser::XliffXmlPullParser(const std::shared_ptr<XmlPullParser>& parser) :
|
||||
mParser(parser) {
|
||||
}
|
||||
|
||||
XmlPullParser::Event XliffXmlPullParser::next() {
|
||||
while (XmlPullParser::isGoodEvent(mParser->next())) {
|
||||
Event event = mParser->getEvent();
|
||||
if (event != Event::kStartElement && event != Event::kEndElement) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (mParser->getElementNamespace() !=
|
||||
u"urn:oasis:names:tc:xliff:document:1.2") {
|
||||
break;
|
||||
}
|
||||
|
||||
const std::u16string& name = mParser->getElementName();
|
||||
if (name != u"bpt"
|
||||
&& name != u"ept"
|
||||
&& name != u"it"
|
||||
&& name != u"ph"
|
||||
&& name != u"g"
|
||||
&& name != u"bx"
|
||||
&& name != u"ex"
|
||||
&& name != u"x") {
|
||||
break;
|
||||
}
|
||||
|
||||
// We hit a tag that was ignored, so get the next event.
|
||||
}
|
||||
return mParser->getEvent();
|
||||
}
|
||||
|
||||
XmlPullParser::Event XliffXmlPullParser::getEvent() const {
|
||||
return mParser->getEvent();
|
||||
}
|
||||
|
||||
const std::string& XliffXmlPullParser::getLastError() const {
|
||||
return mParser->getLastError();
|
||||
}
|
||||
|
||||
const std::u16string& XliffXmlPullParser::getComment() const {
|
||||
return mParser->getComment();
|
||||
}
|
||||
|
||||
size_t XliffXmlPullParser::getLineNumber() const {
|
||||
return mParser->getLineNumber();
|
||||
}
|
||||
|
||||
size_t XliffXmlPullParser::getDepth() const {
|
||||
return mParser->getDepth();
|
||||
}
|
||||
|
||||
const std::u16string& XliffXmlPullParser::getText() const {
|
||||
return mParser->getText();
|
||||
}
|
||||
|
||||
const std::u16string& XliffXmlPullParser::getNamespacePrefix() const {
|
||||
return mParser->getNamespacePrefix();
|
||||
}
|
||||
|
||||
const std::u16string& XliffXmlPullParser::getNamespaceUri() const {
|
||||
return mParser->getNamespaceUri();
|
||||
}
|
||||
|
||||
bool XliffXmlPullParser::applyPackageAlias(std::u16string* package,
|
||||
const std::u16string& defaultPackage) const {
|
||||
return mParser->applyPackageAlias(package, defaultPackage);
|
||||
}
|
||||
|
||||
const std::u16string& XliffXmlPullParser::getElementNamespace() const {
|
||||
return mParser->getElementNamespace();
|
||||
}
|
||||
|
||||
const std::u16string& XliffXmlPullParser::getElementName() const {
|
||||
return mParser->getElementName();
|
||||
}
|
||||
|
||||
size_t XliffXmlPullParser::getAttributeCount() const {
|
||||
return mParser->getAttributeCount();
|
||||
}
|
||||
|
||||
XmlPullParser::const_iterator XliffXmlPullParser::beginAttributes() const {
|
||||
return mParser->beginAttributes();
|
||||
}
|
||||
|
||||
XmlPullParser::const_iterator XliffXmlPullParser::endAttributes() const {
|
||||
return mParser->endAttributes();
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
@@ -1,64 +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_XLIFF_XML_PULL_PARSER_H
|
||||
#define AAPT_XLIFF_XML_PULL_PARSER_H
|
||||
|
||||
#include "XmlPullParser.h"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
/**
|
||||
* Strips xliff elements and provides the caller with a view of the
|
||||
* underlying XML without xliff.
|
||||
*/
|
||||
class XliffXmlPullParser : public XmlPullParser {
|
||||
public:
|
||||
XliffXmlPullParser(const std::shared_ptr<XmlPullParser>& parser);
|
||||
XliffXmlPullParser(const XliffXmlPullParser& 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:
|
||||
std::shared_ptr<XmlPullParser> mParser;
|
||||
};
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
#endif // AAPT_XLIFF_XML_PULL_PARSER_H
|
||||
@@ -1,75 +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 "SourceXmlPullParser.h"
|
||||
#include "XliffXmlPullParser.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
TEST(XliffXmlPullParserTest, IgnoreXliffTags) {
|
||||
std::stringstream input;
|
||||
input << "<?xml version=\"1.0\" encoding=\"utf-8\"?>" << std::endl
|
||||
<< "<resources xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\">" << std::endl
|
||||
<< "<string name=\"foo\">"
|
||||
<< "Hey <xliff:g><xliff:it>there</xliff:it></xliff:g> world</string>" << std::endl
|
||||
<< "</resources>" << std::endl;
|
||||
std::shared_ptr<XmlPullParser> sourceParser = std::make_shared<SourceXmlPullParser>(input);
|
||||
XliffXmlPullParser parser(sourceParser);
|
||||
EXPECT_EQ(XmlPullParser::Event::kStartDocument, parser.getEvent());
|
||||
|
||||
EXPECT_EQ(XmlPullParser::Event::kStartNamespace, parser.next());
|
||||
EXPECT_EQ(parser.getNamespaceUri(), u"urn:oasis:names:tc:xliff:document:1.2");
|
||||
EXPECT_EQ(parser.getNamespacePrefix(), u"xliff");
|
||||
|
||||
EXPECT_EQ(XmlPullParser::Event::kStartElement, parser.next());
|
||||
EXPECT_EQ(parser.getElementNamespace(), u"");
|
||||
EXPECT_EQ(parser.getElementName(), u"resources");
|
||||
EXPECT_EQ(XmlPullParser::Event::kText, parser.next()); // Account for newline/whitespace.
|
||||
|
||||
EXPECT_EQ(XmlPullParser::Event::kStartElement, parser.next());
|
||||
EXPECT_EQ(parser.getElementNamespace(), u"");
|
||||
EXPECT_EQ(parser.getElementName(), u"string");
|
||||
|
||||
EXPECT_EQ(XmlPullParser::Event::kText, parser.next());
|
||||
EXPECT_EQ(parser.getText(), u"Hey ");
|
||||
|
||||
EXPECT_EQ(XmlPullParser::Event::kText, parser.next());
|
||||
EXPECT_EQ(parser.getText(), u"there");
|
||||
|
||||
EXPECT_EQ(XmlPullParser::Event::kText, parser.next());
|
||||
EXPECT_EQ(parser.getText(), u" world");
|
||||
|
||||
EXPECT_EQ(XmlPullParser::Event::kEndElement, parser.next());
|
||||
EXPECT_EQ(parser.getElementNamespace(), u"");
|
||||
EXPECT_EQ(parser.getElementName(), u"string");
|
||||
EXPECT_EQ(XmlPullParser::Event::kText, parser.next()); // Account for newline/whitespace.
|
||||
|
||||
EXPECT_EQ(XmlPullParser::Event::kEndElement, parser.next());
|
||||
EXPECT_EQ(parser.getElementNamespace(), u"");
|
||||
EXPECT_EQ(parser.getElementName(), u"resources");
|
||||
|
||||
EXPECT_EQ(XmlPullParser::Event::kEndNamespace, parser.next());
|
||||
EXPECT_EQ(parser.getNamespacePrefix(), u"xliff");
|
||||
EXPECT_EQ(parser.getNamespaceUri(), u"urn:oasis:names:tc:xliff:document:1.2");
|
||||
|
||||
EXPECT_EQ(XmlPullParser::Event::kEndDocument, parser.next());
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
@@ -14,8 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "Logger.h"
|
||||
#include "Util.h"
|
||||
#include "util/Util.h"
|
||||
#include "XmlDom.h"
|
||||
#include "XmlPullParser.h"
|
||||
|
||||
@@ -65,7 +64,7 @@ static void addToStack(Stack* stack, XML_Parser parser, std::unique_ptr<Node> no
|
||||
stack->root = std::move(node);
|
||||
}
|
||||
|
||||
if (thisNode->type != NodeType::kText) {
|
||||
if (!nodeCast<Text>(thisNode)) {
|
||||
stack->nodeStack.push(thisNode);
|
||||
}
|
||||
}
|
||||
@@ -143,8 +142,7 @@ static void XMLCALL characterDataHandler(void* userData, const char* s, int len)
|
||||
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);
|
||||
if (Text* text = nodeCast<Text>(lastChild)) {
|
||||
text->text += util::utf8ToUtf16(StringPiece(s, len));
|
||||
return;
|
||||
}
|
||||
@@ -166,7 +164,7 @@ static void XMLCALL commentDataHandler(void* userData, const char* comment) {
|
||||
stack->pendingComment += util::utf8ToUtf16(comment);
|
||||
}
|
||||
|
||||
std::unique_ptr<Node> inflate(std::istream* in, SourceLogger* logger) {
|
||||
std::unique_ptr<XmlResource> inflate(std::istream* in, IDiagnostics* diag, const Source& source) {
|
||||
Stack stack;
|
||||
|
||||
XML_Parser parser = XML_ParserCreateNS(nullptr, kXmlNamespaceSep);
|
||||
@@ -182,20 +180,23 @@ std::unique_ptr<Node> inflate(std::istream* in, SourceLogger* logger) {
|
||||
in->read(buffer, sizeof(buffer) / sizeof(buffer[0]));
|
||||
if (in->bad() && !in->eof()) {
|
||||
stack.root = {};
|
||||
logger->error() << strerror(errno) << std::endl;
|
||||
diag->error(DiagMessage(source) << strerror(errno));
|
||||
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;
|
||||
diag->error(DiagMessage(source.withLine(XML_GetCurrentLineNumber(parser)))
|
||||
<< XML_ErrorString(XML_GetErrorCode(parser)));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
XML_ParserFree(parser);
|
||||
return std::move(stack.root);
|
||||
if (stack.root) {
|
||||
return util::make_unique<XmlResource>(ResourceFile{}, std::move(stack.root));
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
static void copyAttributes(Element* el, android::ResXMLParser* parser) {
|
||||
@@ -224,7 +225,8 @@ static void copyAttributes(Element* el, android::ResXMLParser* parser) {
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<Node> inflate(const void* data, size_t dataLen, SourceLogger* logger) {
|
||||
std::unique_ptr<XmlResource> inflate(const void* data, size_t dataLen, IDiagnostics* diag,
|
||||
const Source& source) {
|
||||
std::unique_ptr<Node> root;
|
||||
std::stack<Node*> nodeStack;
|
||||
|
||||
@@ -307,15 +309,12 @@ std::unique_ptr<Node> inflate(const void* data, size_t dataLen, SourceLogger* lo
|
||||
nodeStack.top()->addChild(std::move(newNode));
|
||||
}
|
||||
|
||||
if (thisNode->type != NodeType::kText) {
|
||||
if (!nodeCast<Text>(thisNode)) {
|
||||
nodeStack.push(thisNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
return root;
|
||||
}
|
||||
|
||||
Node::Node(NodeType type) : type(type), parent(nullptr), lineNumber(0), columnNumber(0) {
|
||||
return util::make_unique<XmlResource>(ResourceFile{}, std::move(root));
|
||||
}
|
||||
|
||||
void Node::addChild(std::unique_ptr<Node> child) {
|
||||
@@ -323,39 +322,6 @@ void Node::addChild(std::unique_ptr<Node> child) {
|
||||
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) {
|
||||
@@ -366,29 +332,29 @@ Attribute* Element::findAttribute(const StringPiece16& ns, const StringPiece16&
|
||||
}
|
||||
|
||||
Element* Element::findChild(const StringPiece16& ns, const StringPiece16& name) {
|
||||
return findChildWithAttribute(ns, name, nullptr);
|
||||
return findChildWithAttribute(ns, name, {}, {}, {});
|
||||
}
|
||||
|
||||
Element* Element::findChildWithAttribute(const StringPiece16& ns, const StringPiece16& name,
|
||||
const Attribute* reqAttr) {
|
||||
const StringPiece16& attrNs, const StringPiece16& attrName,
|
||||
const StringPiece16& attrValue) {
|
||||
for (auto& childNode : children) {
|
||||
Node* child = childNode.get();
|
||||
while (child->type == NodeType::kNamespace) {
|
||||
while (nodeCast<Namespace>(child)) {
|
||||
if (child->children.empty()) {
|
||||
break;
|
||||
}
|
||||
child = child->children[0].get();
|
||||
}
|
||||
|
||||
if (child->type == NodeType::kElement) {
|
||||
Element* el = static_cast<Element*>(child);
|
||||
if (Element* el = nodeCast<Element>(child)) {
|
||||
if (ns == el->namespaceUri && name == el->name) {
|
||||
if (!reqAttr) {
|
||||
if (attrNs.empty() && attrName.empty()) {
|
||||
return el;
|
||||
}
|
||||
|
||||
Attribute* attrName = el->findAttribute(reqAttr->namespaceUri, reqAttr->name);
|
||||
if (attrName && attrName->value == reqAttr->value) {
|
||||
Attribute* attr = el->findAttribute(attrNs, attrName);
|
||||
if (attr && attrValue == attr->value) {
|
||||
return el;
|
||||
}
|
||||
}
|
||||
@@ -401,31 +367,19 @@ std::vector<Element*> Element::getChildElements() {
|
||||
std::vector<Element*> elements;
|
||||
for (auto& childNode : children) {
|
||||
Node* child = childNode.get();
|
||||
while (child->type == NodeType::kNamespace) {
|
||||
while (nodeCast<Namespace>(child)) {
|
||||
if (child->children.empty()) {
|
||||
break;
|
||||
}
|
||||
child = child->children[0].get();
|
||||
}
|
||||
|
||||
if (child->type == NodeType::kElement) {
|
||||
elements.push_back(static_cast<Element*>(child));
|
||||
if (Element* el = nodeCast<Element>(child)) {
|
||||
elements.push_back(el);
|
||||
}
|
||||
}
|
||||
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
|
||||
|
||||
@@ -17,8 +17,13 @@
|
||||
#ifndef AAPT_XML_DOM_H
|
||||
#define AAPT_XML_DOM_H
|
||||
|
||||
#include "Logger.h"
|
||||
#include "StringPiece.h"
|
||||
#include "Diagnostics.h"
|
||||
#include "Resource.h"
|
||||
#include "ResourceValues.h"
|
||||
#include "util/StringPiece.h"
|
||||
#include "util/Util.h"
|
||||
|
||||
#include "process/IResourceTableConsumer.h"
|
||||
|
||||
#include <istream>
|
||||
#include <expat.h>
|
||||
@@ -29,7 +34,7 @@
|
||||
namespace aapt {
|
||||
namespace xml {
|
||||
|
||||
struct Visitor;
|
||||
struct RawVisitor;
|
||||
|
||||
/**
|
||||
* The type of node. Can be used to downcast to the concrete XML node
|
||||
@@ -45,17 +50,14 @@ enum class NodeType {
|
||||
* Base class for all XML nodes.
|
||||
*/
|
||||
struct Node {
|
||||
NodeType type;
|
||||
Node* parent;
|
||||
size_t lineNumber;
|
||||
size_t columnNumber;
|
||||
Node* parent = nullptr;
|
||||
size_t lineNumber = 0;
|
||||
size_t columnNumber = 0;
|
||||
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 void accept(RawVisitor* visitor) = 0;
|
||||
virtual ~Node() {}
|
||||
};
|
||||
|
||||
@@ -65,8 +67,7 @@ struct Node {
|
||||
*/
|
||||
template <typename Derived>
|
||||
struct BaseNode : public Node {
|
||||
BaseNode(NodeType t);
|
||||
virtual void accept(Visitor* visitor) override;
|
||||
virtual void accept(RawVisitor* visitor) override;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -75,9 +76,11 @@ struct BaseNode : public Node {
|
||||
struct Namespace : public BaseNode<Namespace> {
|
||||
std::u16string namespacePrefix;
|
||||
std::u16string namespaceUri;
|
||||
};
|
||||
|
||||
Namespace();
|
||||
virtual std::unique_ptr<Node> clone() const override;
|
||||
struct AaptAttribute {
|
||||
ResourceId id;
|
||||
aapt::Attribute attribute;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -87,6 +90,9 @@ struct Attribute {
|
||||
std::u16string namespaceUri;
|
||||
std::u16string name;
|
||||
std::u16string value;
|
||||
|
||||
Maybe<AaptAttribute> compiledAttribute;
|
||||
std::unique_ptr<Item> compiledValue;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -97,12 +103,12 @@ struct Element : public BaseNode<Element> {
|
||||
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);
|
||||
const StringPiece16& attrNs,
|
||||
const StringPiece16& attrName,
|
||||
const StringPiece16& attrValue);
|
||||
std::vector<xml::Element*> getChildElements();
|
||||
};
|
||||
|
||||
@@ -111,41 +117,133 @@ struct Element : public BaseNode<Element> {
|
||||
*/
|
||||
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);
|
||||
std::unique_ptr<XmlResource> inflate(std::istream* in, IDiagnostics* diag, const Source& source);
|
||||
|
||||
/**
|
||||
* 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);
|
||||
std::unique_ptr<XmlResource> inflate(const void* data, size_t dataLen, IDiagnostics* diag,
|
||||
const Source& source);
|
||||
|
||||
/**
|
||||
* A visitor interface for the different XML Node subtypes.
|
||||
* A visitor interface for the different XML Node subtypes. This will not traverse into
|
||||
* children. Use Visitor for that.
|
||||
*/
|
||||
struct Visitor {
|
||||
virtual void visit(Namespace* node) = 0;
|
||||
virtual void visit(Element* node) = 0;
|
||||
virtual void visit(Text* text) = 0;
|
||||
struct RawVisitor {
|
||||
virtual ~RawVisitor() = default;
|
||||
|
||||
virtual void visit(Namespace* node) {}
|
||||
virtual void visit(Element* node) {}
|
||||
virtual void visit(Text* text) {}
|
||||
};
|
||||
|
||||
/**
|
||||
* Visitor whose default implementation visits the children nodes of any node.
|
||||
*/
|
||||
struct Visitor : public RawVisitor {
|
||||
using RawVisitor::visit;
|
||||
|
||||
void visit(Namespace* node) override {
|
||||
visitChildren(node);
|
||||
}
|
||||
|
||||
void visit(Element* node) override {
|
||||
visitChildren(node);
|
||||
}
|
||||
|
||||
void visit(Text* text) override {
|
||||
visitChildren(text);
|
||||
}
|
||||
|
||||
void visitChildren(Node* node) {
|
||||
for (auto& child : node->children) {
|
||||
child->accept(this);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* An XML DOM visitor that will record the package name for a namespace prefix.
|
||||
*/
|
||||
class PackageAwareVisitor : public Visitor, public IPackageDeclStack {
|
||||
private:
|
||||
struct PackageDecl {
|
||||
std::u16string prefix;
|
||||
std::u16string package;
|
||||
};
|
||||
|
||||
std::vector<PackageDecl> mPackageDecls;
|
||||
|
||||
public:
|
||||
using Visitor::visit;
|
||||
|
||||
void visit(Namespace* ns) override {
|
||||
bool added = false;
|
||||
{
|
||||
Maybe<std::u16string> package = util::extractPackageFromNamespace(ns->namespaceUri);
|
||||
if (package) {
|
||||
mPackageDecls.push_back(PackageDecl{ ns->namespacePrefix, package.value() });
|
||||
added = true;
|
||||
}
|
||||
}
|
||||
|
||||
Visitor::visit(ns);
|
||||
|
||||
if (added) {
|
||||
mPackageDecls.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
Maybe<ResourceName> transformPackage(const ResourceName& name,
|
||||
const StringPiece16& localPackage) const override {
|
||||
if (name.package.empty()) {
|
||||
return ResourceName{ localPackage.toString(), name.type, name.entry };
|
||||
}
|
||||
|
||||
const auto rend = mPackageDecls.rend();
|
||||
for (auto iter = mPackageDecls.rbegin(); iter != rend; ++iter) {
|
||||
if (name.package == iter->prefix) {
|
||||
if (iter->package.empty()) {
|
||||
return ResourceName{ localPackage.toString(), name.type, name.entry };
|
||||
} else {
|
||||
return ResourceName{ iter->package, name.type, name.entry };
|
||||
}
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
// Implementations
|
||||
|
||||
template <typename Derived>
|
||||
BaseNode<Derived>::BaseNode(NodeType type) : Node(type) {
|
||||
void BaseNode<Derived>::accept(RawVisitor* visitor) {
|
||||
visitor->visit(static_cast<Derived*>(this));
|
||||
}
|
||||
|
||||
template <typename Derived>
|
||||
void BaseNode<Derived>::accept(Visitor* visitor) {
|
||||
visitor->visit(static_cast<Derived*>(this));
|
||||
template <typename T>
|
||||
struct NodeCastImpl : public RawVisitor {
|
||||
using RawVisitor::visit;
|
||||
|
||||
T* value = nullptr;
|
||||
|
||||
void visit(T* v) override {
|
||||
value = v;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
T* nodeCast(Node* node) {
|
||||
NodeCastImpl<T> visitor;
|
||||
node->accept(&visitor);
|
||||
return visitor.value;
|
||||
}
|
||||
|
||||
} // namespace xml
|
||||
|
||||
@@ -36,12 +36,13 @@ TEST(XmlDomTest, Inflate) {
|
||||
</Layout>
|
||||
)EOF";
|
||||
|
||||
SourceLogger logger(Source{ "/test/path" });
|
||||
std::unique_ptr<xml::Node> root = xml::inflate(&in, &logger);
|
||||
ASSERT_NE(root, nullptr);
|
||||
const Source source = { "test.xml" };
|
||||
StdErrDiagnostics diag;
|
||||
std::unique_ptr<XmlResource> doc = xml::inflate(&in, &diag, source);
|
||||
ASSERT_NE(doc, nullptr);
|
||||
|
||||
EXPECT_EQ(root->type, xml::NodeType::kNamespace);
|
||||
xml::Namespace* ns = static_cast<xml::Namespace*>(root.get());
|
||||
xml::Namespace* ns = xml::nodeCast<xml::Namespace>(doc->root.get());
|
||||
ASSERT_NE(ns, nullptr);
|
||||
EXPECT_EQ(ns->namespaceUri, u"http://schemas.android.com/apk/res/android");
|
||||
EXPECT_EQ(ns->namespacePrefix, u"android");
|
||||
}
|
||||
|
||||
@@ -1,574 +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 "BigBuffer.h"
|
||||
#include "Logger.h"
|
||||
#include "Maybe.h"
|
||||
#include "Resolver.h"
|
||||
#include "Resource.h"
|
||||
#include "ResourceParser.h"
|
||||
#include "ResourceValues.h"
|
||||
#include "SdkConstants.h"
|
||||
#include "Source.h"
|
||||
#include "StringPool.h"
|
||||
#include "Util.h"
|
||||
#include "XmlFlattener.h"
|
||||
|
||||
#include <androidfw/ResourceTypes.h>
|
||||
#include <limits>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace aapt {
|
||||
namespace xml {
|
||||
|
||||
constexpr uint32_t kLowPriority = 0xffffffffu;
|
||||
|
||||
// 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) {
|
||||
}
|
||||
|
||||
// No copying.
|
||||
XmlFlattener(const XmlFlattener&) = delete;
|
||||
XmlFlattener& operator=(const XmlFlattener&) = delete;
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
const std::u16string& getDefaultPackage() const {
|
||||
return mDefaultPackage;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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:
|
||||
BigBuffer* mOut;
|
||||
StringPool* mPool;
|
||||
FlatStringRefList* mStringRefs;
|
||||
std::u16string mDefaultPackage;
|
||||
bool mError = false;
|
||||
std::vector<std::pair<std::u16string, std::u16string>> mPackageAliases;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
};
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
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) {
|
||||
refEntry.second->index = refEntry.first.getIndex();
|
||||
}
|
||||
|
||||
// Write the XML header.
|
||||
const size_t beforeXmlTreeIndex = outBuffer->size();
|
||||
android::ResXMLTree_header* header = outBuffer->nextBlock<android::ResXMLTree_header>();
|
||||
header->header.type = android::RES_XML_TYPE;
|
||||
header->header.headerSize = sizeof(*header);
|
||||
|
||||
// Flatten the StringPool.
|
||||
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) {
|
||||
ResourceId id { str->context.priority };
|
||||
if (id.id == kLowPriority || !id.isValid()) {
|
||||
// When we see the first non-resource ID,
|
||||
// we're done.
|
||||
break;
|
||||
}
|
||||
|
||||
*outBuffer->nextBlock<uint32_t>() = id.id;
|
||||
}
|
||||
resIdMapChunk->size = outBuffer->size() - beforeResIdMapIndex;
|
||||
|
||||
// Move the temporary BigBuffer into outBuffer.
|
||||
outBuffer->appendBuffer(std::move(xmlTreeBuffer));
|
||||
header->header.size = outBuffer->size() - beforeXmlTreeIndex;
|
||||
}
|
||||
|
||||
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
|
||||
@@ -1,69 +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_XML_FLATTENER_H
|
||||
#define AAPT_XML_FLATTENER_H
|
||||
|
||||
#include "BigBuffer.h"
|
||||
#include "Maybe.h"
|
||||
#include "Resolver.h"
|
||||
#include "Source.h"
|
||||
#include "XmlDom.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace aapt {
|
||||
namespace xml {
|
||||
|
||||
/**
|
||||
* Flattens an XML file into a binary representation parseable by
|
||||
* the Android resource system.
|
||||
*/
|
||||
bool flatten(Node* root, const std::u16string& defaultPackage, BigBuffer* outBuffer);
|
||||
|
||||
/**
|
||||
* Options for flattenAndLink.
|
||||
*/
|
||||
struct FlattenOptions {
|
||||
/**
|
||||
* Keep attribute raw string values along with typed values.
|
||||
*/
|
||||
bool keepRawValues = false;
|
||||
|
||||
/**
|
||||
* If set, any attribute introduced in a later SDK will not be encoded.
|
||||
*/
|
||||
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
|
||||
@@ -1,232 +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 "MockResolver.h"
|
||||
#include "ResourceTable.h"
|
||||
#include "ResourceValues.h"
|
||||
#include "Util.h"
|
||||
#include "XmlFlattener.h"
|
||||
|
||||
#include <androidfw/AssetManager.h>
|
||||
#include <androidfw/ResourceTypes.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
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 {
|
||||
mResolver = std::make_shared<MockResolver>(
|
||||
std::make_shared<ResourceTable>(),
|
||||
std::map<ResourceName, ResourceId>({
|
||||
{ ResourceName{ u"android", ResourceType::kAttr, u"attr" },
|
||||
ResourceId{ 0x01010000u } },
|
||||
{ ResourceName{ u"android", ResourceType::kId, u"id" },
|
||||
ResourceId{ 0x01020000u } },
|
||||
{ ResourceName{ u"com.lib", ResourceType::kAttr, u"attr" },
|
||||
ResourceId{ 0x01010001u } },
|
||||
{ ResourceName{ u"com.lib", ResourceType::kId, u"id" },
|
||||
ResourceId{ 0x01020001u } }}));
|
||||
}
|
||||
|
||||
::testing::AssertionResult testFlatten(const std::string& in, ResXMLTree* outTree) {
|
||||
std::stringstream input(kXmlPreamble);
|
||||
input << in << std::endl;
|
||||
|
||||
SourceLogger logger(Source{ "test.xml" });
|
||||
std::unique_ptr<Node> root = inflate(&input, &logger);
|
||||
if (!root) {
|
||||
return ::testing::AssertionFailure();
|
||||
}
|
||||
|
||||
BigBuffer outBuffer(1024);
|
||||
if (!flattenAndLink(Source{ "test.xml" }, root.get(), std::u16string(u"android"),
|
||||
mResolver, {}, &outBuffer)) {
|
||||
return ::testing::AssertionFailure();
|
||||
}
|
||||
|
||||
std::unique_ptr<uint8_t[]> data = util::copy(outBuffer);
|
||||
if (outTree->setTo(data.get(), outBuffer.size(), true) != NO_ERROR) {
|
||||
return ::testing::AssertionFailure();
|
||||
}
|
||||
return ::testing::AssertionSuccess();
|
||||
}
|
||||
|
||||
std::shared_ptr<IResolver> mResolver;
|
||||
};
|
||||
|
||||
TEST_F(XmlFlattenerTest, ParseSimpleView) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(XmlFlattenerTest, ParseViewWithPackageAlias) {
|
||||
std::string input = "<View xmlns:ns1=\"http://schemas.android.com/apk/res/android\"\n"
|
||||
" xmlns:ns2=\"http://schemas.android.com/apk/res/android\"\n"
|
||||
" ns1:attr=\"@ns2:id/id\">\n"
|
||||
"</View>";
|
||||
ResXMLTree tree;
|
||||
ASSERT_TRUE(testFlatten(input, &tree));
|
||||
|
||||
while (tree.next() != ResXMLTree::END_DOCUMENT) {
|
||||
ASSERT_NE(tree.getEventType(), ResXMLTree::BAD_DOCUMENT);
|
||||
}
|
||||
}
|
||||
|
||||
::testing::AssertionResult attributeNameAndValueEquals(ResXMLTree* tree, size_t index,
|
||||
ResourceId nameId, ResourceId valueId) {
|
||||
if (index >= tree->getAttributeCount()) {
|
||||
return ::testing::AssertionFailure() << "index " << index << " is out of bounds ("
|
||||
<< tree->getAttributeCount() << ")";
|
||||
}
|
||||
|
||||
if (tree->getAttributeNameResID(index) != nameId.id) {
|
||||
return ::testing::AssertionFailure()
|
||||
<< "attribute at index " << index << " has ID "
|
||||
<< ResourceId{ (uint32_t) tree->getAttributeNameResID(index) }
|
||||
<< ". Expected ID " << nameId;
|
||||
}
|
||||
|
||||
if (tree->getAttributeDataType(index) != Res_value::TYPE_REFERENCE) {
|
||||
return ::testing::AssertionFailure() << "attribute at index " << index << " has value of "
|
||||
<< "type " << std::hex
|
||||
<< tree->getAttributeDataType(index) << std::dec
|
||||
<< ". Expected reference (" << std::hex
|
||||
<< Res_value::TYPE_REFERENCE << std::dec << ")";
|
||||
}
|
||||
|
||||
if ((uint32_t) tree->getAttributeData(index) != valueId.id) {
|
||||
return ::testing::AssertionFailure()
|
||||
<< "attribute at index " << index << " has value " << "with ID "
|
||||
<< ResourceId{ (uint32_t) tree->getAttributeData(index) }
|
||||
<< ". Expected ID " << valueId;
|
||||
}
|
||||
return ::testing::AssertionSuccess();
|
||||
}
|
||||
|
||||
TEST_F(XmlFlattenerTest, ParseViewWithShadowedPackageAlias) {
|
||||
std::string input = "<View xmlns:app=\"http://schemas.android.com/apk/res/android\"\n"
|
||||
" app:attr=\"@app:id/id\">\n"
|
||||
" <View xmlns:app=\"http://schemas.android.com/apk/res/com.lib\"\n"
|
||||
" app:attr=\"@app:id/id\"/>\n"
|
||||
"</View>";
|
||||
ResXMLTree tree;
|
||||
ASSERT_TRUE(testFlatten(input, &tree));
|
||||
|
||||
while (tree.next() != ResXMLTree::START_TAG) {
|
||||
ASSERT_NE(tree.getEventType(), ResXMLTree::BAD_DOCUMENT);
|
||||
ASSERT_NE(tree.getEventType(), ResXMLTree::END_DOCUMENT);
|
||||
}
|
||||
|
||||
ASSERT_TRUE(attributeNameAndValueEquals(&tree, 0u, ResourceId{ 0x01010000u },
|
||||
ResourceId{ 0x01020000u }));
|
||||
|
||||
while (tree.next() != ResXMLTree::START_TAG) {
|
||||
ASSERT_NE(tree.getEventType(), ResXMLTree::BAD_DOCUMENT);
|
||||
ASSERT_NE(tree.getEventType(), ResXMLTree::END_DOCUMENT);
|
||||
}
|
||||
|
||||
ASSERT_TRUE(attributeNameAndValueEquals(&tree, 0u, ResourceId{ 0x01010001u },
|
||||
ResourceId{ 0x01020001u }));
|
||||
}
|
||||
|
||||
TEST_F(XmlFlattenerTest, ParseViewWithLocalPackageAndAliasOfTheSameName) {
|
||||
std::string input = "<View xmlns:android=\"http://schemas.android.com/apk/res/com.lib\"\n"
|
||||
" android:attr=\"@id/id\"/>";
|
||||
ResXMLTree tree;
|
||||
ASSERT_TRUE(testFlatten(input, &tree));
|
||||
|
||||
while (tree.next() != ResXMLTree::START_TAG) {
|
||||
ASSERT_NE(tree.getEventType(), ResXMLTree::BAD_DOCUMENT);
|
||||
ASSERT_NE(tree.getEventType(), ResXMLTree::END_DOCUMENT);
|
||||
}
|
||||
|
||||
// We expect the 'android:attr' to be converted to 'com.lib:attr' due to the namespace
|
||||
// assignment.
|
||||
// However, we didn't give '@id/id' a package, so it should use the default package
|
||||
// 'android', and not be converted from 'android' to 'com.lib'.
|
||||
ASSERT_TRUE(attributeNameAndValueEquals(&tree, 0u, ResourceId{ 0x01010001u },
|
||||
ResourceId{ 0x01020000u }));
|
||||
}
|
||||
|
||||
/*
|
||||
* The device ResXMLParser in libandroidfw differentiates between empty namespace and null
|
||||
* namespace.
|
||||
*/
|
||||
TEST_F(XmlFlattenerTest, NoNamespaceIsNotTheSameAsEmptyNamespace) {
|
||||
std::string input = "<View xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
|
||||
" package=\"android\"/>";
|
||||
|
||||
ResXMLTree tree;
|
||||
ASSERT_TRUE(testFlatten(input, &tree));
|
||||
|
||||
while (tree.next() != ResXMLTree::START_TAG) {
|
||||
ASSERT_NE(tree.getEventType(), ResXMLTree::BAD_DOCUMENT);
|
||||
ASSERT_NE(tree.getEventType(), ResXMLTree::END_DOCUMENT);
|
||||
}
|
||||
|
||||
const StringPiece16 kPackage = u"package";
|
||||
EXPECT_GE(tree.indexOfAttribute(nullptr, 0, kPackage.data(), kPackage.size()), 0);
|
||||
}
|
||||
|
||||
} // namespace xml
|
||||
} // namespace aapt
|
||||
@@ -14,18 +14,18 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "util/Maybe.h"
|
||||
#include "util/Util.h"
|
||||
#include "XmlPullParser.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
#include "Maybe.h"
|
||||
#include "SourceXmlPullParser.h"
|
||||
#include "Util.h"
|
||||
|
||||
namespace aapt {
|
||||
|
||||
constexpr char kXmlNamespaceSep = 1;
|
||||
|
||||
SourceXmlPullParser::SourceXmlPullParser(std::istream& in) : mIn(in), mEmpty(), mDepth(0) {
|
||||
XmlPullParser::XmlPullParser(std::istream& in) : mIn(in), mEmpty(), mDepth(0) {
|
||||
mParser = XML_ParserCreateNS(nullptr, kXmlNamespaceSep);
|
||||
XML_SetUserData(mParser, this);
|
||||
XML_SetElementHandler(mParser, startElementHandler, endElementHandler);
|
||||
@@ -35,11 +35,11 @@ SourceXmlPullParser::SourceXmlPullParser(std::istream& in) : mIn(in), mEmpty(),
|
||||
mEventQueue.push(EventData{ Event::kStartDocument, 0, mDepth++ });
|
||||
}
|
||||
|
||||
SourceXmlPullParser::~SourceXmlPullParser() {
|
||||
XmlPullParser::~XmlPullParser() {
|
||||
XML_ParserFree(mParser);
|
||||
}
|
||||
|
||||
SourceXmlPullParser::Event SourceXmlPullParser::next() {
|
||||
XmlPullParser::Event XmlPullParser::next() {
|
||||
const Event currentEvent = getEvent();
|
||||
if (currentEvent == Event::kBadDocument || currentEvent == Event::kEndDocument) {
|
||||
return currentEvent;
|
||||
@@ -88,34 +88,34 @@ SourceXmlPullParser::Event SourceXmlPullParser::next() {
|
||||
return event;
|
||||
}
|
||||
|
||||
SourceXmlPullParser::Event SourceXmlPullParser::getEvent() const {
|
||||
XmlPullParser::Event XmlPullParser::getEvent() const {
|
||||
return mEventQueue.front().event;
|
||||
}
|
||||
|
||||
const std::string& SourceXmlPullParser::getLastError() const {
|
||||
const std::string& XmlPullParser::getLastError() const {
|
||||
return mLastError;
|
||||
}
|
||||
|
||||
const std::u16string& SourceXmlPullParser::getComment() const {
|
||||
const std::u16string& XmlPullParser::getComment() const {
|
||||
return mEventQueue.front().comment;
|
||||
}
|
||||
|
||||
size_t SourceXmlPullParser::getLineNumber() const {
|
||||
size_t XmlPullParser::getLineNumber() const {
|
||||
return mEventQueue.front().lineNumber;
|
||||
}
|
||||
|
||||
size_t SourceXmlPullParser::getDepth() const {
|
||||
size_t XmlPullParser::getDepth() const {
|
||||
return mEventQueue.front().depth;
|
||||
}
|
||||
|
||||
const std::u16string& SourceXmlPullParser::getText() const {
|
||||
const std::u16string& XmlPullParser::getText() const {
|
||||
if (getEvent() != Event::kText) {
|
||||
return mEmpty;
|
||||
}
|
||||
return mEventQueue.front().data1;
|
||||
}
|
||||
|
||||
const std::u16string& SourceXmlPullParser::getNamespacePrefix() const {
|
||||
const std::u16string& XmlPullParser::getNamespacePrefix() const {
|
||||
const Event currentEvent = getEvent();
|
||||
if (currentEvent != Event::kStartNamespace && currentEvent != Event::kEndNamespace) {
|
||||
return mEmpty;
|
||||
@@ -123,7 +123,7 @@ const std::u16string& SourceXmlPullParser::getNamespacePrefix() const {
|
||||
return mEventQueue.front().data1;
|
||||
}
|
||||
|
||||
const std::u16string& SourceXmlPullParser::getNamespaceUri() const {
|
||||
const std::u16string& XmlPullParser::getNamespaceUri() const {
|
||||
const Event currentEvent = getEvent();
|
||||
if (currentEvent != Event::kStartNamespace && currentEvent != Event::kEndNamespace) {
|
||||
return mEmpty;
|
||||
@@ -131,23 +131,26 @@ const std::u16string& SourceXmlPullParser::getNamespaceUri() const {
|
||||
return mEventQueue.front().data2;
|
||||
}
|
||||
|
||||
bool SourceXmlPullParser::applyPackageAlias(std::u16string* package,
|
||||
const std::u16string& defaultPackage) const {
|
||||
Maybe<ResourceName> XmlPullParser::transformPackage(
|
||||
const ResourceName& name, const StringPiece16& localPackage) const {
|
||||
if (name.package.empty()) {
|
||||
return ResourceName{ localPackage.toString(), name.type, name.entry };
|
||||
}
|
||||
|
||||
const auto endIter = mPackageAliases.rend();
|
||||
for (auto iter = mPackageAliases.rbegin(); iter != endIter; ++iter) {
|
||||
if (iter->first == *package) {
|
||||
if (name.package == iter->first) {
|
||||
if (iter->second.empty()) {
|
||||
*package = defaultPackage;
|
||||
return ResourceName{ localPackage.toString(), name.type, name.entry };
|
||||
} else {
|
||||
*package = iter->second;
|
||||
return ResourceName{ iter->second, name.type, name.entry };
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return {};
|
||||
}
|
||||
|
||||
const std::u16string& SourceXmlPullParser::getElementNamespace() const {
|
||||
const std::u16string& XmlPullParser::getElementNamespace() const {
|
||||
const Event currentEvent = getEvent();
|
||||
if (currentEvent != Event::kStartElement && currentEvent != Event::kEndElement) {
|
||||
return mEmpty;
|
||||
@@ -155,7 +158,7 @@ const std::u16string& SourceXmlPullParser::getElementNamespace() const {
|
||||
return mEventQueue.front().data1;
|
||||
}
|
||||
|
||||
const std::u16string& SourceXmlPullParser::getElementName() const {
|
||||
const std::u16string& XmlPullParser::getElementName() const {
|
||||
const Event currentEvent = getEvent();
|
||||
if (currentEvent != Event::kStartElement && currentEvent != Event::kEndElement) {
|
||||
return mEmpty;
|
||||
@@ -163,15 +166,15 @@ const std::u16string& SourceXmlPullParser::getElementName() const {
|
||||
return mEventQueue.front().data2;
|
||||
}
|
||||
|
||||
XmlPullParser::const_iterator SourceXmlPullParser::beginAttributes() const {
|
||||
XmlPullParser::const_iterator XmlPullParser::beginAttributes() const {
|
||||
return mEventQueue.front().attributes.begin();
|
||||
}
|
||||
|
||||
XmlPullParser::const_iterator SourceXmlPullParser::endAttributes() const {
|
||||
XmlPullParser::const_iterator XmlPullParser::endAttributes() const {
|
||||
return mEventQueue.front().attributes.end();
|
||||
}
|
||||
|
||||
size_t SourceXmlPullParser::getAttributeCount() const {
|
||||
size_t XmlPullParser::getAttributeCount() const {
|
||||
if (getEvent() != Event::kStartElement) {
|
||||
return 0;
|
||||
}
|
||||
@@ -196,9 +199,9 @@ static void splitName(const char* name, std::u16string& outNs, std::u16string& o
|
||||
}
|
||||
}
|
||||
|
||||
void XMLCALL SourceXmlPullParser::startNamespaceHandler(void* userData, const char* prefix,
|
||||
void XMLCALL XmlPullParser::startNamespaceHandler(void* userData, const char* prefix,
|
||||
const char* uri) {
|
||||
SourceXmlPullParser* parser = reinterpret_cast<SourceXmlPullParser*>(userData);
|
||||
XmlPullParser* parser = reinterpret_cast<XmlPullParser*>(userData);
|
||||
std::u16string namespaceUri = uri != nullptr ? util::utf8ToUtf16(uri) : std::u16string();
|
||||
parser->mNamespaceUris.push(namespaceUri);
|
||||
parser->mEventQueue.push(EventData{
|
||||
@@ -210,9 +213,9 @@ void XMLCALL SourceXmlPullParser::startNamespaceHandler(void* userData, const ch
|
||||
});
|
||||
}
|
||||
|
||||
void XMLCALL SourceXmlPullParser::startElementHandler(void* userData, const char* name,
|
||||
void XMLCALL XmlPullParser::startElementHandler(void* userData, const char* name,
|
||||
const char** attrs) {
|
||||
SourceXmlPullParser* parser = reinterpret_cast<SourceXmlPullParser*>(userData);
|
||||
XmlPullParser* parser = reinterpret_cast<XmlPullParser*>(userData);
|
||||
|
||||
EventData data = {
|
||||
Event::kStartElement, XML_GetCurrentLineNumber(parser->mParser), parser->mDepth++
|
||||
@@ -233,8 +236,8 @@ void XMLCALL SourceXmlPullParser::startElementHandler(void* userData, const char
|
||||
parser->mEventQueue.push(std::move(data));
|
||||
}
|
||||
|
||||
void XMLCALL SourceXmlPullParser::characterDataHandler(void* userData, const char* s, int len) {
|
||||
SourceXmlPullParser* parser = reinterpret_cast<SourceXmlPullParser*>(userData);
|
||||
void XMLCALL XmlPullParser::characterDataHandler(void* userData, const char* s, int len) {
|
||||
XmlPullParser* parser = reinterpret_cast<XmlPullParser*>(userData);
|
||||
|
||||
parser->mEventQueue.push(EventData{
|
||||
Event::kText,
|
||||
@@ -244,8 +247,8 @@ void XMLCALL SourceXmlPullParser::characterDataHandler(void* userData, const cha
|
||||
});
|
||||
}
|
||||
|
||||
void XMLCALL SourceXmlPullParser::endElementHandler(void* userData, const char* name) {
|
||||
SourceXmlPullParser* parser = reinterpret_cast<SourceXmlPullParser*>(userData);
|
||||
void XMLCALL XmlPullParser::endElementHandler(void* userData, const char* name) {
|
||||
XmlPullParser* parser = reinterpret_cast<XmlPullParser*>(userData);
|
||||
|
||||
EventData data = {
|
||||
Event::kEndElement, XML_GetCurrentLineNumber(parser->mParser), --(parser->mDepth)
|
||||
@@ -256,8 +259,8 @@ void XMLCALL SourceXmlPullParser::endElementHandler(void* userData, const char*
|
||||
parser->mEventQueue.push(std::move(data));
|
||||
}
|
||||
|
||||
void XMLCALL SourceXmlPullParser::endNamespaceHandler(void* userData, const char* prefix) {
|
||||
SourceXmlPullParser* parser = reinterpret_cast<SourceXmlPullParser*>(userData);
|
||||
void XMLCALL XmlPullParser::endNamespaceHandler(void* userData, const char* prefix) {
|
||||
XmlPullParser* parser = reinterpret_cast<XmlPullParser*>(userData);
|
||||
|
||||
parser->mEventQueue.push(EventData{
|
||||
Event::kEndNamespace,
|
||||
@@ -269,8 +272,8 @@ void XMLCALL SourceXmlPullParser::endNamespaceHandler(void* userData, const char
|
||||
parser->mNamespaceUris.pop();
|
||||
}
|
||||
|
||||
void XMLCALL SourceXmlPullParser::commentDataHandler(void* userData, const char* comment) {
|
||||
SourceXmlPullParser* parser = reinterpret_cast<SourceXmlPullParser*>(userData);
|
||||
void XMLCALL XmlPullParser::commentDataHandler(void* userData, const char* comment) {
|
||||
XmlPullParser* parser = reinterpret_cast<XmlPullParser*>(userData);
|
||||
|
||||
parser->mEventQueue.push(EventData{
|
||||
Event::kComment,
|
||||
@@ -17,16 +17,24 @@
|
||||
#ifndef AAPT_XML_PULL_PARSER_H
|
||||
#define AAPT_XML_PULL_PARSER_H
|
||||
|
||||
#include "util/Maybe.h"
|
||||
#include "Resource.h"
|
||||
#include "util/StringPiece.h"
|
||||
|
||||
#include "process/IResourceTableConsumer.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <expat.h>
|
||||
#include <istream>
|
||||
#include <ostream>
|
||||
#include <queue>
|
||||
#include <stack>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "StringPiece.h"
|
||||
|
||||
namespace aapt {
|
||||
|
||||
class XmlPullParser {
|
||||
class XmlPullParser : public IPackageDeclStack {
|
||||
public:
|
||||
enum class Event {
|
||||
kBadDocument,
|
||||
@@ -41,43 +49,51 @@ public:
|
||||
kComment,
|
||||
};
|
||||
|
||||
static void skipCurrentElement(XmlPullParser* parser);
|
||||
/**
|
||||
* Skips to the next direct descendant node of the given startDepth,
|
||||
* skipping namespace nodes.
|
||||
*
|
||||
* When nextChildNode returns true, you can expect Comments, Text, and StartElement events.
|
||||
*/
|
||||
static bool nextChildNode(XmlPullParser* parser, size_t startDepth);
|
||||
static bool skipCurrentElement(XmlPullParser* parser);
|
||||
static bool isGoodEvent(Event event);
|
||||
|
||||
virtual ~XmlPullParser() {}
|
||||
XmlPullParser(std::istream& in);
|
||||
virtual ~XmlPullParser();
|
||||
|
||||
/**
|
||||
* Returns the current event that is being processed.
|
||||
*/
|
||||
virtual Event getEvent() const = 0;
|
||||
Event getEvent() const;
|
||||
|
||||
virtual const std::string& getLastError() const = 0;
|
||||
const std::string& getLastError() const;
|
||||
|
||||
/**
|
||||
* Note, unlike XmlPullParser, the first call to next() will return
|
||||
* StartElement of the first element.
|
||||
*/
|
||||
virtual Event next() = 0;
|
||||
Event next();
|
||||
|
||||
//
|
||||
// These are available for all nodes.
|
||||
//
|
||||
|
||||
virtual const std::u16string& getComment() const = 0;
|
||||
virtual size_t getLineNumber() const = 0;
|
||||
virtual size_t getDepth() const = 0;
|
||||
const std::u16string& getComment() const;
|
||||
size_t getLineNumber() const;
|
||||
size_t getDepth() const;
|
||||
|
||||
/**
|
||||
* Returns the character data for a Text event.
|
||||
*/
|
||||
virtual const std::u16string& getText() const = 0;
|
||||
const std::u16string& getText() const;
|
||||
|
||||
//
|
||||
// Namespace prefix and URI are available for StartNamespace and EndNamespace.
|
||||
//
|
||||
|
||||
virtual const std::u16string& getNamespacePrefix() const = 0;
|
||||
virtual const std::u16string& getNamespaceUri() const = 0;
|
||||
const std::u16string& getNamespacePrefix() const;
|
||||
const std::u16string& getNamespaceUri() const;
|
||||
|
||||
/*
|
||||
* Uses the current stack of namespaces to resolve the package. Eg:
|
||||
@@ -90,15 +106,17 @@ public:
|
||||
* If xmlns:app="http://schemas.android.com/apk/res-auto", then
|
||||
* 'package' will be set to 'defaultPackage'.
|
||||
*/
|
||||
virtual bool applyPackageAlias(std::u16string* package,
|
||||
const std::u16string& defaultPackage) const = 0;
|
||||
//
|
||||
|
||||
//
|
||||
// These are available for StartElement and EndElement.
|
||||
//
|
||||
|
||||
virtual const std::u16string& getElementNamespace() const = 0;
|
||||
virtual const std::u16string& getElementName() const = 0;
|
||||
const std::u16string& getElementNamespace() const;
|
||||
const std::u16string& getElementName() const;
|
||||
|
||||
Maybe<ResourceName> transformPackage(const ResourceName& name,
|
||||
const StringPiece16& localPackage) const override;
|
||||
|
||||
//
|
||||
// Remaining methods are for retrieving information about attributes
|
||||
@@ -121,10 +139,38 @@ public:
|
||||
|
||||
using const_iterator = std::vector<Attribute>::const_iterator;
|
||||
|
||||
virtual const_iterator beginAttributes() const = 0;
|
||||
virtual const_iterator endAttributes() const = 0;
|
||||
virtual size_t getAttributeCount() const = 0;
|
||||
const_iterator beginAttributes() const;
|
||||
const_iterator endAttributes() const;
|
||||
size_t getAttributeCount() const;
|
||||
const_iterator findAttribute(StringPiece16 namespaceUri, StringPiece16 name) const;
|
||||
|
||||
private:
|
||||
static void XMLCALL startNamespaceHandler(void* userData, const char* prefix, const char* uri);
|
||||
static void XMLCALL startElementHandler(void* userData, const char* name, const char** attrs);
|
||||
static void XMLCALL characterDataHandler(void* userData, const char* s, int len);
|
||||
static void XMLCALL endElementHandler(void* userData, const char* name);
|
||||
static void XMLCALL endNamespaceHandler(void* userData, const char* prefix);
|
||||
static void XMLCALL commentDataHandler(void* userData, const char* comment);
|
||||
|
||||
struct EventData {
|
||||
Event event;
|
||||
size_t lineNumber;
|
||||
size_t depth;
|
||||
std::u16string data1;
|
||||
std::u16string data2;
|
||||
std::u16string comment;
|
||||
std::vector<Attribute> attributes;
|
||||
};
|
||||
|
||||
std::istream& mIn;
|
||||
XML_Parser mParser;
|
||||
char mBuffer[16384];
|
||||
std::queue<EventData> mEventQueue;
|
||||
std::string mLastError;
|
||||
const std::u16string mEmpty;
|
||||
size_t mDepth;
|
||||
std::stack<std::u16string> mNamespaceUris;
|
||||
std::vector<std::pair<std::u16string, std::u16string>> mPackageAliases;
|
||||
};
|
||||
|
||||
//
|
||||
@@ -146,13 +192,35 @@ inline ::std::ostream& operator<<(::std::ostream& out, XmlPullParser::Event even
|
||||
return out;
|
||||
}
|
||||
|
||||
inline void XmlPullParser::skipCurrentElement(XmlPullParser* parser) {
|
||||
inline bool XmlPullParser::nextChildNode(XmlPullParser* parser, size_t startDepth) {
|
||||
Event event;
|
||||
|
||||
// First get back to the start depth.
|
||||
while (isGoodEvent(event = parser->next()) && parser->getDepth() > startDepth + 1) {}
|
||||
|
||||
// Now look for the first good node.
|
||||
while ((event != Event::kEndElement || parser->getDepth() > startDepth) && isGoodEvent(event)) {
|
||||
switch (event) {
|
||||
case Event::kText:
|
||||
case Event::kComment:
|
||||
case Event::kStartElement:
|
||||
return true;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
event = parser->next();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
inline bool XmlPullParser::skipCurrentElement(XmlPullParser* parser) {
|
||||
int depth = 1;
|
||||
while (depth > 0) {
|
||||
switch (parser->next()) {
|
||||
case Event::kEndDocument:
|
||||
return true;
|
||||
case Event::kBadDocument:
|
||||
return;
|
||||
return false;
|
||||
case Event::kStartElement:
|
||||
depth++;
|
||||
break;
|
||||
@@ -163,6 +231,7 @@ inline void XmlPullParser::skipCurrentElement(XmlPullParser* parser) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool XmlPullParser::isGoodEvent(XmlPullParser::Event event) {
|
||||
|
||||
55
tools/aapt2/XmlPullParser_test.cpp
Normal file
55
tools/aapt2/XmlPullParser_test.cpp
Normal file
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* 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 "util/StringPiece.h"
|
||||
#include "XmlPullParser.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <sstream>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
TEST(XmlPullParserTest, NextChildNodeTraversesCorrectly) {
|
||||
std::stringstream str;
|
||||
str << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
|
||||
"<a><b><c xmlns:a=\"http://schema.org\"><d/></c><e/></b></a>";
|
||||
XmlPullParser parser(str);
|
||||
|
||||
const size_t depthOuter = parser.getDepth();
|
||||
ASSERT_TRUE(XmlPullParser::nextChildNode(&parser, depthOuter));
|
||||
|
||||
EXPECT_EQ(XmlPullParser::Event::kStartElement, parser.getEvent());
|
||||
EXPECT_EQ(StringPiece16(u"a"), StringPiece16(parser.getElementName()));
|
||||
|
||||
const size_t depthA = parser.getDepth();
|
||||
ASSERT_TRUE(XmlPullParser::nextChildNode(&parser, depthA));
|
||||
EXPECT_EQ(XmlPullParser::Event::kStartElement, parser.getEvent());
|
||||
EXPECT_EQ(StringPiece16(u"b"), StringPiece16(parser.getElementName()));
|
||||
|
||||
const size_t depthB = parser.getDepth();
|
||||
ASSERT_TRUE(XmlPullParser::nextChildNode(&parser, depthB));
|
||||
EXPECT_EQ(XmlPullParser::Event::kStartElement, parser.getEvent());
|
||||
EXPECT_EQ(StringPiece16(u"c"), StringPiece16(parser.getElementName()));
|
||||
|
||||
ASSERT_TRUE(XmlPullParser::nextChildNode(&parser, depthB));
|
||||
EXPECT_EQ(XmlPullParser::Event::kStartElement, parser.getEvent());
|
||||
EXPECT_EQ(StringPiece16(u"e"), StringPiece16(parser.getElementName()));
|
||||
|
||||
ASSERT_FALSE(XmlPullParser::nextChildNode(&parser, depthOuter));
|
||||
EXPECT_EQ(XmlPullParser::Event::kEndDocument, parser.getEvent());
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
@@ -1,745 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2006 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.
|
||||
*/
|
||||
|
||||
//
|
||||
// Access to entries in a Zip archive.
|
||||
//
|
||||
|
||||
#define LOG_TAG "zip"
|
||||
|
||||
#include "ZipEntry.h"
|
||||
#include <utils/Log.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
using namespace android;
|
||||
|
||||
/*
|
||||
* Initialize a new ZipEntry structure from a FILE* positioned at a
|
||||
* CentralDirectoryEntry.
|
||||
*
|
||||
* On exit, the file pointer will be at the start of the next CDE or
|
||||
* at the EOCD.
|
||||
*/
|
||||
status_t ZipEntry::initFromCDE(FILE* fp)
|
||||
{
|
||||
status_t result;
|
||||
long posn;
|
||||
bool hasDD;
|
||||
|
||||
//ALOGV("initFromCDE ---\n");
|
||||
|
||||
/* read the CDE */
|
||||
result = mCDE.read(fp);
|
||||
if (result != NO_ERROR) {
|
||||
ALOGD("mCDE.read failed\n");
|
||||
return result;
|
||||
}
|
||||
|
||||
//mCDE.dump();
|
||||
|
||||
/* using the info in the CDE, go load up the LFH */
|
||||
posn = ftell(fp);
|
||||
if (fseek(fp, mCDE.mLocalHeaderRelOffset, SEEK_SET) != 0) {
|
||||
ALOGD("local header seek failed (%ld)\n",
|
||||
mCDE.mLocalHeaderRelOffset);
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
result = mLFH.read(fp);
|
||||
if (result != NO_ERROR) {
|
||||
ALOGD("mLFH.read failed\n");
|
||||
return result;
|
||||
}
|
||||
|
||||
if (fseek(fp, posn, SEEK_SET) != 0)
|
||||
return UNKNOWN_ERROR;
|
||||
|
||||
//mLFH.dump();
|
||||
|
||||
/*
|
||||
* We *might* need to read the Data Descriptor at this point and
|
||||
* integrate it into the LFH. If this bit is set, the CRC-32,
|
||||
* compressed size, and uncompressed size will be zero. In practice
|
||||
* these seem to be rare.
|
||||
*/
|
||||
hasDD = (mLFH.mGPBitFlag & kUsesDataDescr) != 0;
|
||||
if (hasDD) {
|
||||
// do something clever
|
||||
//ALOGD("+++ has data descriptor\n");
|
||||
}
|
||||
|
||||
/*
|
||||
* Sanity-check the LFH. Note that this will fail if the "kUsesDataDescr"
|
||||
* flag is set, because the LFH is incomplete. (Not a problem, since we
|
||||
* prefer the CDE values.)
|
||||
*/
|
||||
if (!hasDD && !compareHeaders()) {
|
||||
ALOGW("warning: header mismatch\n");
|
||||
// keep going?
|
||||
}
|
||||
|
||||
/*
|
||||
* If the mVersionToExtract is greater than 20, we may have an
|
||||
* issue unpacking the record -- could be encrypted, compressed
|
||||
* with something we don't support, or use Zip64 extensions. We
|
||||
* can defer worrying about that to when we're extracting data.
|
||||
*/
|
||||
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize a new entry. Pass in the file name and an optional comment.
|
||||
*
|
||||
* Initializes the CDE and the LFH.
|
||||
*/
|
||||
void ZipEntry::initNew(const char* fileName, const char* comment)
|
||||
{
|
||||
assert(fileName != NULL && *fileName != '\0'); // name required
|
||||
|
||||
/* most fields are properly initialized by constructor */
|
||||
mCDE.mVersionMadeBy = kDefaultMadeBy;
|
||||
mCDE.mVersionToExtract = kDefaultVersion;
|
||||
mCDE.mCompressionMethod = kCompressStored;
|
||||
mCDE.mFileNameLength = strlen(fileName);
|
||||
if (comment != NULL)
|
||||
mCDE.mFileCommentLength = strlen(comment);
|
||||
mCDE.mExternalAttrs = 0x81b60020; // matches what WinZip does
|
||||
|
||||
if (mCDE.mFileNameLength > 0) {
|
||||
mCDE.mFileName = new unsigned char[mCDE.mFileNameLength+1];
|
||||
strcpy((char*) mCDE.mFileName, fileName);
|
||||
}
|
||||
if (mCDE.mFileCommentLength > 0) {
|
||||
/* TODO: stop assuming null-terminated ASCII here? */
|
||||
mCDE.mFileComment = new unsigned char[mCDE.mFileCommentLength+1];
|
||||
strcpy((char*) mCDE.mFileComment, comment);
|
||||
}
|
||||
|
||||
copyCDEtoLFH();
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize a new entry, starting with the ZipEntry from a different
|
||||
* archive.
|
||||
*
|
||||
* Initializes the CDE and the LFH.
|
||||
*/
|
||||
status_t ZipEntry::initFromExternal(const ZipFile* /* pZipFile */,
|
||||
const ZipEntry* pEntry, const char* storageName)
|
||||
{
|
||||
mCDE = pEntry->mCDE;
|
||||
if (storageName && *storageName != 0) {
|
||||
mCDE.mFileNameLength = strlen(storageName);
|
||||
mCDE.mFileName = new unsigned char[mCDE.mFileNameLength + 1];
|
||||
strcpy((char*) mCDE.mFileName, storageName);
|
||||
}
|
||||
|
||||
// Check whether we got all the memory needed.
|
||||
if ((mCDE.mFileNameLength > 0 && mCDE.mFileName == NULL) ||
|
||||
(mCDE.mFileCommentLength > 0 && mCDE.mFileComment == NULL) ||
|
||||
(mCDE.mExtraFieldLength > 0 && mCDE.mExtraField == NULL)) {
|
||||
return NO_MEMORY;
|
||||
}
|
||||
|
||||
/* construct the LFH from the CDE */
|
||||
copyCDEtoLFH();
|
||||
|
||||
/*
|
||||
* The LFH "extra" field is independent of the CDE "extra", so we
|
||||
* handle it here.
|
||||
*/
|
||||
assert(mLFH.mExtraField == NULL);
|
||||
mLFH.mExtraFieldLength = pEntry->mLFH.mExtraFieldLength;
|
||||
if (mLFH.mExtraFieldLength > 0) {
|
||||
mLFH.mExtraField = new unsigned char[mLFH.mExtraFieldLength+1];
|
||||
if (mLFH.mExtraField == NULL)
|
||||
return NO_MEMORY;
|
||||
memcpy(mLFH.mExtraField, pEntry->mLFH.mExtraField,
|
||||
mLFH.mExtraFieldLength+1);
|
||||
}
|
||||
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
/*
|
||||
* Insert pad bytes in the LFH by tweaking the "extra" field. This will
|
||||
* potentially confuse something that put "extra" data in here earlier,
|
||||
* but I can't find an actual problem.
|
||||
*/
|
||||
status_t ZipEntry::addPadding(int padding)
|
||||
{
|
||||
if (padding <= 0)
|
||||
return INVALID_OPERATION;
|
||||
|
||||
//ALOGI("HEY: adding %d pad bytes to existing %d in %s\n",
|
||||
// padding, mLFH.mExtraFieldLength, mCDE.mFileName);
|
||||
|
||||
if (mLFH.mExtraFieldLength > 0) {
|
||||
/* extend existing field */
|
||||
unsigned char* newExtra;
|
||||
|
||||
newExtra = new unsigned char[mLFH.mExtraFieldLength + padding];
|
||||
if (newExtra == NULL)
|
||||
return NO_MEMORY;
|
||||
memset(newExtra + mLFH.mExtraFieldLength, 0, padding);
|
||||
memcpy(newExtra, mLFH.mExtraField, mLFH.mExtraFieldLength);
|
||||
|
||||
delete[] mLFH.mExtraField;
|
||||
mLFH.mExtraField = newExtra;
|
||||
mLFH.mExtraFieldLength += padding;
|
||||
} else {
|
||||
/* create new field */
|
||||
mLFH.mExtraField = new unsigned char[padding];
|
||||
memset(mLFH.mExtraField, 0, padding);
|
||||
mLFH.mExtraFieldLength = padding;
|
||||
}
|
||||
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
/*
|
||||
* Set the fields in the LFH equal to the corresponding fields in the CDE.
|
||||
*
|
||||
* This does not touch the LFH "extra" field.
|
||||
*/
|
||||
void ZipEntry::copyCDEtoLFH(void)
|
||||
{
|
||||
mLFH.mVersionToExtract = mCDE.mVersionToExtract;
|
||||
mLFH.mGPBitFlag = mCDE.mGPBitFlag;
|
||||
mLFH.mCompressionMethod = mCDE.mCompressionMethod;
|
||||
mLFH.mLastModFileTime = mCDE.mLastModFileTime;
|
||||
mLFH.mLastModFileDate = mCDE.mLastModFileDate;
|
||||
mLFH.mCRC32 = mCDE.mCRC32;
|
||||
mLFH.mCompressedSize = mCDE.mCompressedSize;
|
||||
mLFH.mUncompressedSize = mCDE.mUncompressedSize;
|
||||
mLFH.mFileNameLength = mCDE.mFileNameLength;
|
||||
// the "extra field" is independent
|
||||
|
||||
delete[] mLFH.mFileName;
|
||||
if (mLFH.mFileNameLength > 0) {
|
||||
mLFH.mFileName = new unsigned char[mLFH.mFileNameLength+1];
|
||||
strcpy((char*) mLFH.mFileName, (const char*) mCDE.mFileName);
|
||||
} else {
|
||||
mLFH.mFileName = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Set some information about a file after we add it.
|
||||
*/
|
||||
void ZipEntry::setDataInfo(long uncompLen, long compLen, unsigned long crc32,
|
||||
int compressionMethod)
|
||||
{
|
||||
mCDE.mCompressionMethod = compressionMethod;
|
||||
mCDE.mCRC32 = crc32;
|
||||
mCDE.mCompressedSize = compLen;
|
||||
mCDE.mUncompressedSize = uncompLen;
|
||||
mCDE.mCompressionMethod = compressionMethod;
|
||||
if (compressionMethod == kCompressDeflated) {
|
||||
mCDE.mGPBitFlag |= 0x0002; // indicates maximum compression used
|
||||
}
|
||||
copyCDEtoLFH();
|
||||
}
|
||||
|
||||
/*
|
||||
* See if the data in mCDE and mLFH match up. This is mostly useful for
|
||||
* debugging these classes, but it can be used to identify damaged
|
||||
* archives.
|
||||
*
|
||||
* Returns "false" if they differ.
|
||||
*/
|
||||
bool ZipEntry::compareHeaders(void) const
|
||||
{
|
||||
if (mCDE.mVersionToExtract != mLFH.mVersionToExtract) {
|
||||
ALOGV("cmp: VersionToExtract\n");
|
||||
return false;
|
||||
}
|
||||
if (mCDE.mGPBitFlag != mLFH.mGPBitFlag) {
|
||||
ALOGV("cmp: GPBitFlag\n");
|
||||
return false;
|
||||
}
|
||||
if (mCDE.mCompressionMethod != mLFH.mCompressionMethod) {
|
||||
ALOGV("cmp: CompressionMethod\n");
|
||||
return false;
|
||||
}
|
||||
if (mCDE.mLastModFileTime != mLFH.mLastModFileTime) {
|
||||
ALOGV("cmp: LastModFileTime\n");
|
||||
return false;
|
||||
}
|
||||
if (mCDE.mLastModFileDate != mLFH.mLastModFileDate) {
|
||||
ALOGV("cmp: LastModFileDate\n");
|
||||
return false;
|
||||
}
|
||||
if (mCDE.mCRC32 != mLFH.mCRC32) {
|
||||
ALOGV("cmp: CRC32\n");
|
||||
return false;
|
||||
}
|
||||
if (mCDE.mCompressedSize != mLFH.mCompressedSize) {
|
||||
ALOGV("cmp: CompressedSize\n");
|
||||
return false;
|
||||
}
|
||||
if (mCDE.mUncompressedSize != mLFH.mUncompressedSize) {
|
||||
ALOGV("cmp: UncompressedSize\n");
|
||||
return false;
|
||||
}
|
||||
if (mCDE.mFileNameLength != mLFH.mFileNameLength) {
|
||||
ALOGV("cmp: FileNameLength\n");
|
||||
return false;
|
||||
}
|
||||
#if 0 // this seems to be used for padding, not real data
|
||||
if (mCDE.mExtraFieldLength != mLFH.mExtraFieldLength) {
|
||||
ALOGV("cmp: ExtraFieldLength\n");
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
if (mCDE.mFileName != NULL) {
|
||||
if (strcmp((char*) mCDE.mFileName, (char*) mLFH.mFileName) != 0) {
|
||||
ALOGV("cmp: FileName\n");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Convert the DOS date/time stamp into a UNIX time stamp.
|
||||
*/
|
||||
time_t ZipEntry::getModWhen(void) const
|
||||
{
|
||||
struct tm parts;
|
||||
|
||||
parts.tm_sec = (mCDE.mLastModFileTime & 0x001f) << 1;
|
||||
parts.tm_min = (mCDE.mLastModFileTime & 0x07e0) >> 5;
|
||||
parts.tm_hour = (mCDE.mLastModFileTime & 0xf800) >> 11;
|
||||
parts.tm_mday = (mCDE.mLastModFileDate & 0x001f);
|
||||
parts.tm_mon = ((mCDE.mLastModFileDate & 0x01e0) >> 5) -1;
|
||||
parts.tm_year = ((mCDE.mLastModFileDate & 0xfe00) >> 9) + 80;
|
||||
parts.tm_wday = parts.tm_yday = 0;
|
||||
parts.tm_isdst = -1; // DST info "not available"
|
||||
|
||||
return mktime(&parts);
|
||||
}
|
||||
|
||||
/*
|
||||
* Set the CDE/LFH timestamp from UNIX time.
|
||||
*/
|
||||
void ZipEntry::setModWhen(time_t when)
|
||||
{
|
||||
#if !defined(_WIN32)
|
||||
struct tm tmResult;
|
||||
#endif
|
||||
time_t even;
|
||||
unsigned short zdate, ztime;
|
||||
|
||||
struct tm* ptm;
|
||||
|
||||
/* round up to an even number of seconds */
|
||||
even = (time_t)(((unsigned long)(when) + 1) & (~1));
|
||||
|
||||
/* expand */
|
||||
#if !defined(_WIN32)
|
||||
ptm = localtime_r(&even, &tmResult);
|
||||
#else
|
||||
ptm = localtime(&even);
|
||||
#endif
|
||||
|
||||
int year;
|
||||
year = ptm->tm_year;
|
||||
if (year < 80)
|
||||
year = 80;
|
||||
|
||||
zdate = (year - 80) << 9 | (ptm->tm_mon+1) << 5 | ptm->tm_mday;
|
||||
ztime = ptm->tm_hour << 11 | ptm->tm_min << 5 | ptm->tm_sec >> 1;
|
||||
|
||||
mCDE.mLastModFileTime = mLFH.mLastModFileTime = ztime;
|
||||
mCDE.mLastModFileDate = mLFH.mLastModFileDate = zdate;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* ===========================================================================
|
||||
* ZipEntry::LocalFileHeader
|
||||
* ===========================================================================
|
||||
*/
|
||||
|
||||
/*
|
||||
* Read a local file header.
|
||||
*
|
||||
* On entry, "fp" points to the signature at the start of the header.
|
||||
* On exit, "fp" points to the start of data.
|
||||
*/
|
||||
status_t ZipEntry::LocalFileHeader::read(FILE* fp)
|
||||
{
|
||||
status_t result = NO_ERROR;
|
||||
unsigned char buf[kLFHLen];
|
||||
|
||||
assert(mFileName == NULL);
|
||||
assert(mExtraField == NULL);
|
||||
|
||||
if (fread(buf, 1, kLFHLen, fp) != kLFHLen) {
|
||||
result = UNKNOWN_ERROR;
|
||||
goto bail;
|
||||
}
|
||||
|
||||
if (ZipEntry::getLongLE(&buf[0x00]) != kSignature) {
|
||||
ALOGD("whoops: didn't find expected signature\n");
|
||||
result = UNKNOWN_ERROR;
|
||||
goto bail;
|
||||
}
|
||||
|
||||
mVersionToExtract = ZipEntry::getShortLE(&buf[0x04]);
|
||||
mGPBitFlag = ZipEntry::getShortLE(&buf[0x06]);
|
||||
mCompressionMethod = ZipEntry::getShortLE(&buf[0x08]);
|
||||
mLastModFileTime = ZipEntry::getShortLE(&buf[0x0a]);
|
||||
mLastModFileDate = ZipEntry::getShortLE(&buf[0x0c]);
|
||||
mCRC32 = ZipEntry::getLongLE(&buf[0x0e]);
|
||||
mCompressedSize = ZipEntry::getLongLE(&buf[0x12]);
|
||||
mUncompressedSize = ZipEntry::getLongLE(&buf[0x16]);
|
||||
mFileNameLength = ZipEntry::getShortLE(&buf[0x1a]);
|
||||
mExtraFieldLength = ZipEntry::getShortLE(&buf[0x1c]);
|
||||
|
||||
// TODO: validate sizes
|
||||
|
||||
/* grab filename */
|
||||
if (mFileNameLength != 0) {
|
||||
mFileName = new unsigned char[mFileNameLength+1];
|
||||
if (mFileName == NULL) {
|
||||
result = NO_MEMORY;
|
||||
goto bail;
|
||||
}
|
||||
if (fread(mFileName, 1, mFileNameLength, fp) != mFileNameLength) {
|
||||
result = UNKNOWN_ERROR;
|
||||
goto bail;
|
||||
}
|
||||
mFileName[mFileNameLength] = '\0';
|
||||
}
|
||||
|
||||
/* grab extra field */
|
||||
if (mExtraFieldLength != 0) {
|
||||
mExtraField = new unsigned char[mExtraFieldLength+1];
|
||||
if (mExtraField == NULL) {
|
||||
result = NO_MEMORY;
|
||||
goto bail;
|
||||
}
|
||||
if (fread(mExtraField, 1, mExtraFieldLength, fp) != mExtraFieldLength) {
|
||||
result = UNKNOWN_ERROR;
|
||||
goto bail;
|
||||
}
|
||||
mExtraField[mExtraFieldLength] = '\0';
|
||||
}
|
||||
|
||||
bail:
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Write a local file header.
|
||||
*/
|
||||
status_t ZipEntry::LocalFileHeader::write(FILE* fp)
|
||||
{
|
||||
unsigned char buf[kLFHLen];
|
||||
|
||||
ZipEntry::putLongLE(&buf[0x00], kSignature);
|
||||
ZipEntry::putShortLE(&buf[0x04], mVersionToExtract);
|
||||
ZipEntry::putShortLE(&buf[0x06], mGPBitFlag);
|
||||
ZipEntry::putShortLE(&buf[0x08], mCompressionMethod);
|
||||
ZipEntry::putShortLE(&buf[0x0a], mLastModFileTime);
|
||||
ZipEntry::putShortLE(&buf[0x0c], mLastModFileDate);
|
||||
ZipEntry::putLongLE(&buf[0x0e], mCRC32);
|
||||
ZipEntry::putLongLE(&buf[0x12], mCompressedSize);
|
||||
ZipEntry::putLongLE(&buf[0x16], mUncompressedSize);
|
||||
ZipEntry::putShortLE(&buf[0x1a], mFileNameLength);
|
||||
ZipEntry::putShortLE(&buf[0x1c], mExtraFieldLength);
|
||||
|
||||
if (fwrite(buf, 1, kLFHLen, fp) != kLFHLen)
|
||||
return UNKNOWN_ERROR;
|
||||
|
||||
/* write filename */
|
||||
if (mFileNameLength != 0) {
|
||||
if (fwrite(mFileName, 1, mFileNameLength, fp) != mFileNameLength)
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
/* write "extra field" */
|
||||
if (mExtraFieldLength != 0) {
|
||||
if (fwrite(mExtraField, 1, mExtraFieldLength, fp) != mExtraFieldLength)
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Dump the contents of a LocalFileHeader object.
|
||||
*/
|
||||
void ZipEntry::LocalFileHeader::dump(void) const
|
||||
{
|
||||
ALOGD(" LocalFileHeader contents:\n");
|
||||
ALOGD(" versToExt=%u gpBits=0x%04x compression=%u\n",
|
||||
mVersionToExtract, mGPBitFlag, mCompressionMethod);
|
||||
ALOGD(" modTime=0x%04x modDate=0x%04x crc32=0x%08lx\n",
|
||||
mLastModFileTime, mLastModFileDate, mCRC32);
|
||||
ALOGD(" compressedSize=%lu uncompressedSize=%lu\n",
|
||||
mCompressedSize, mUncompressedSize);
|
||||
ALOGD(" filenameLen=%u extraLen=%u\n",
|
||||
mFileNameLength, mExtraFieldLength);
|
||||
if (mFileName != NULL)
|
||||
ALOGD(" filename: '%s'\n", mFileName);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* ===========================================================================
|
||||
* ZipEntry::CentralDirEntry
|
||||
* ===========================================================================
|
||||
*/
|
||||
|
||||
/*
|
||||
* Read the central dir entry that appears next in the file.
|
||||
*
|
||||
* On entry, "fp" should be positioned on the signature bytes for the
|
||||
* entry. On exit, "fp" will point at the signature word for the next
|
||||
* entry or for the EOCD.
|
||||
*/
|
||||
status_t ZipEntry::CentralDirEntry::read(FILE* fp)
|
||||
{
|
||||
status_t result = NO_ERROR;
|
||||
unsigned char buf[kCDELen];
|
||||
|
||||
/* no re-use */
|
||||
assert(mFileName == NULL);
|
||||
assert(mExtraField == NULL);
|
||||
assert(mFileComment == NULL);
|
||||
|
||||
if (fread(buf, 1, kCDELen, fp) != kCDELen) {
|
||||
result = UNKNOWN_ERROR;
|
||||
goto bail;
|
||||
}
|
||||
|
||||
if (ZipEntry::getLongLE(&buf[0x00]) != kSignature) {
|
||||
ALOGD("Whoops: didn't find expected signature\n");
|
||||
result = UNKNOWN_ERROR;
|
||||
goto bail;
|
||||
}
|
||||
|
||||
mVersionMadeBy = ZipEntry::getShortLE(&buf[0x04]);
|
||||
mVersionToExtract = ZipEntry::getShortLE(&buf[0x06]);
|
||||
mGPBitFlag = ZipEntry::getShortLE(&buf[0x08]);
|
||||
mCompressionMethod = ZipEntry::getShortLE(&buf[0x0a]);
|
||||
mLastModFileTime = ZipEntry::getShortLE(&buf[0x0c]);
|
||||
mLastModFileDate = ZipEntry::getShortLE(&buf[0x0e]);
|
||||
mCRC32 = ZipEntry::getLongLE(&buf[0x10]);
|
||||
mCompressedSize = ZipEntry::getLongLE(&buf[0x14]);
|
||||
mUncompressedSize = ZipEntry::getLongLE(&buf[0x18]);
|
||||
mFileNameLength = ZipEntry::getShortLE(&buf[0x1c]);
|
||||
mExtraFieldLength = ZipEntry::getShortLE(&buf[0x1e]);
|
||||
mFileCommentLength = ZipEntry::getShortLE(&buf[0x20]);
|
||||
mDiskNumberStart = ZipEntry::getShortLE(&buf[0x22]);
|
||||
mInternalAttrs = ZipEntry::getShortLE(&buf[0x24]);
|
||||
mExternalAttrs = ZipEntry::getLongLE(&buf[0x26]);
|
||||
mLocalHeaderRelOffset = ZipEntry::getLongLE(&buf[0x2a]);
|
||||
|
||||
// TODO: validate sizes and offsets
|
||||
|
||||
/* grab filename */
|
||||
if (mFileNameLength != 0) {
|
||||
mFileName = new unsigned char[mFileNameLength+1];
|
||||
if (mFileName == NULL) {
|
||||
result = NO_MEMORY;
|
||||
goto bail;
|
||||
}
|
||||
if (fread(mFileName, 1, mFileNameLength, fp) != mFileNameLength) {
|
||||
result = UNKNOWN_ERROR;
|
||||
goto bail;
|
||||
}
|
||||
mFileName[mFileNameLength] = '\0';
|
||||
}
|
||||
|
||||
/* read "extra field" */
|
||||
if (mExtraFieldLength != 0) {
|
||||
mExtraField = new unsigned char[mExtraFieldLength+1];
|
||||
if (mExtraField == NULL) {
|
||||
result = NO_MEMORY;
|
||||
goto bail;
|
||||
}
|
||||
if (fread(mExtraField, 1, mExtraFieldLength, fp) != mExtraFieldLength) {
|
||||
result = UNKNOWN_ERROR;
|
||||
goto bail;
|
||||
}
|
||||
mExtraField[mExtraFieldLength] = '\0';
|
||||
}
|
||||
|
||||
|
||||
/* grab comment, if any */
|
||||
if (mFileCommentLength != 0) {
|
||||
mFileComment = new unsigned char[mFileCommentLength+1];
|
||||
if (mFileComment == NULL) {
|
||||
result = NO_MEMORY;
|
||||
goto bail;
|
||||
}
|
||||
if (fread(mFileComment, 1, mFileCommentLength, fp) != mFileCommentLength)
|
||||
{
|
||||
result = UNKNOWN_ERROR;
|
||||
goto bail;
|
||||
}
|
||||
mFileComment[mFileCommentLength] = '\0';
|
||||
}
|
||||
|
||||
bail:
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Write a central dir entry.
|
||||
*/
|
||||
status_t ZipEntry::CentralDirEntry::write(FILE* fp)
|
||||
{
|
||||
unsigned char buf[kCDELen];
|
||||
|
||||
ZipEntry::putLongLE(&buf[0x00], kSignature);
|
||||
ZipEntry::putShortLE(&buf[0x04], mVersionMadeBy);
|
||||
ZipEntry::putShortLE(&buf[0x06], mVersionToExtract);
|
||||
ZipEntry::putShortLE(&buf[0x08], mGPBitFlag);
|
||||
ZipEntry::putShortLE(&buf[0x0a], mCompressionMethod);
|
||||
ZipEntry::putShortLE(&buf[0x0c], mLastModFileTime);
|
||||
ZipEntry::putShortLE(&buf[0x0e], mLastModFileDate);
|
||||
ZipEntry::putLongLE(&buf[0x10], mCRC32);
|
||||
ZipEntry::putLongLE(&buf[0x14], mCompressedSize);
|
||||
ZipEntry::putLongLE(&buf[0x18], mUncompressedSize);
|
||||
ZipEntry::putShortLE(&buf[0x1c], mFileNameLength);
|
||||
ZipEntry::putShortLE(&buf[0x1e], mExtraFieldLength);
|
||||
ZipEntry::putShortLE(&buf[0x20], mFileCommentLength);
|
||||
ZipEntry::putShortLE(&buf[0x22], mDiskNumberStart);
|
||||
ZipEntry::putShortLE(&buf[0x24], mInternalAttrs);
|
||||
ZipEntry::putLongLE(&buf[0x26], mExternalAttrs);
|
||||
ZipEntry::putLongLE(&buf[0x2a], mLocalHeaderRelOffset);
|
||||
|
||||
if (fwrite(buf, 1, kCDELen, fp) != kCDELen)
|
||||
return UNKNOWN_ERROR;
|
||||
|
||||
/* write filename */
|
||||
if (mFileNameLength != 0) {
|
||||
if (fwrite(mFileName, 1, mFileNameLength, fp) != mFileNameLength)
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
/* write "extra field" */
|
||||
if (mExtraFieldLength != 0) {
|
||||
if (fwrite(mExtraField, 1, mExtraFieldLength, fp) != mExtraFieldLength)
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
/* write comment */
|
||||
if (mFileCommentLength != 0) {
|
||||
if (fwrite(mFileComment, 1, mFileCommentLength, fp) != mFileCommentLength)
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
/*
|
||||
* Dump the contents of a CentralDirEntry object.
|
||||
*/
|
||||
void ZipEntry::CentralDirEntry::dump(void) const
|
||||
{
|
||||
ALOGD(" CentralDirEntry contents:\n");
|
||||
ALOGD(" versMadeBy=%u versToExt=%u gpBits=0x%04x compression=%u\n",
|
||||
mVersionMadeBy, mVersionToExtract, mGPBitFlag, mCompressionMethod);
|
||||
ALOGD(" modTime=0x%04x modDate=0x%04x crc32=0x%08lx\n",
|
||||
mLastModFileTime, mLastModFileDate, mCRC32);
|
||||
ALOGD(" compressedSize=%lu uncompressedSize=%lu\n",
|
||||
mCompressedSize, mUncompressedSize);
|
||||
ALOGD(" filenameLen=%u extraLen=%u commentLen=%u\n",
|
||||
mFileNameLength, mExtraFieldLength, mFileCommentLength);
|
||||
ALOGD(" diskNumStart=%u intAttr=0x%04x extAttr=0x%08lx relOffset=%lu\n",
|
||||
mDiskNumberStart, mInternalAttrs, mExternalAttrs,
|
||||
mLocalHeaderRelOffset);
|
||||
|
||||
if (mFileName != NULL)
|
||||
ALOGD(" filename: '%s'\n", mFileName);
|
||||
if (mFileComment != NULL)
|
||||
ALOGD(" comment: '%s'\n", mFileComment);
|
||||
}
|
||||
|
||||
/*
|
||||
* Copy-assignment operator for CentralDirEntry.
|
||||
*/
|
||||
ZipEntry::CentralDirEntry& ZipEntry::CentralDirEntry::operator=(const ZipEntry::CentralDirEntry& src) {
|
||||
if (this == &src) {
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Free up old data.
|
||||
delete[] mFileName;
|
||||
delete[] mExtraField;
|
||||
delete[] mFileComment;
|
||||
|
||||
// Copy scalars.
|
||||
mVersionMadeBy = src.mVersionMadeBy;
|
||||
mVersionToExtract = src.mVersionToExtract;
|
||||
mGPBitFlag = src.mGPBitFlag;
|
||||
mCompressionMethod = src.mCompressionMethod;
|
||||
mLastModFileTime = src.mLastModFileTime;
|
||||
mLastModFileDate = src.mLastModFileDate;
|
||||
mCRC32 = src.mCRC32;
|
||||
mCompressedSize = src.mCompressedSize;
|
||||
mUncompressedSize = src.mUncompressedSize;
|
||||
mFileNameLength = src.mFileNameLength;
|
||||
mExtraFieldLength = src.mExtraFieldLength;
|
||||
mFileCommentLength = src.mFileCommentLength;
|
||||
mDiskNumberStart = src.mDiskNumberStart;
|
||||
mInternalAttrs = src.mInternalAttrs;
|
||||
mExternalAttrs = src.mExternalAttrs;
|
||||
mLocalHeaderRelOffset = src.mLocalHeaderRelOffset;
|
||||
|
||||
// Copy strings, if necessary.
|
||||
if (mFileNameLength > 0) {
|
||||
mFileName = new unsigned char[mFileNameLength + 1];
|
||||
if (mFileName != NULL)
|
||||
strcpy((char*)mFileName, (char*)src.mFileName);
|
||||
} else {
|
||||
mFileName = NULL;
|
||||
}
|
||||
if (mFileCommentLength > 0) {
|
||||
mFileComment = new unsigned char[mFileCommentLength + 1];
|
||||
if (mFileComment != NULL)
|
||||
strcpy((char*)mFileComment, (char*)src.mFileComment);
|
||||
} else {
|
||||
mFileComment = NULL;
|
||||
}
|
||||
if (mExtraFieldLength > 0) {
|
||||
/* we null-terminate this, though it may not be a string */
|
||||
mExtraField = new unsigned char[mExtraFieldLength + 1];
|
||||
if (mExtraField != NULL)
|
||||
memcpy(mExtraField, src.mExtraField, mExtraFieldLength + 1);
|
||||
} else {
|
||||
mExtraField = NULL;
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
@@ -1,350 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2006 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.
|
||||
*/
|
||||
|
||||
//
|
||||
// Zip archive entries.
|
||||
//
|
||||
// The ZipEntry class is tightly meshed with the ZipFile class.
|
||||
//
|
||||
#ifndef __LIBS_ZIPENTRY_H
|
||||
#define __LIBS_ZIPENTRY_H
|
||||
|
||||
#include <utils/Errors.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
using android::status_t;
|
||||
|
||||
class ZipFile;
|
||||
|
||||
/*
|
||||
* ZipEntry objects represent a single entry in a Zip archive.
|
||||
*
|
||||
* You can use one of these to get or set information about an entry, but
|
||||
* there are no functions here for accessing the data itself. (We could
|
||||
* tuck a pointer to the ZipFile in here for convenience, but that raises
|
||||
* the likelihood of using ZipEntry objects after discarding the ZipFile.)
|
||||
*
|
||||
* File information is stored in two places: next to the file data (the Local
|
||||
* File Header, and possibly a Data Descriptor), and at the end of the file
|
||||
* (the Central Directory Entry). The two must be kept in sync.
|
||||
*/
|
||||
class ZipEntry {
|
||||
public:
|
||||
friend class ZipFile;
|
||||
|
||||
ZipEntry(void)
|
||||
: mDeleted(false), mMarked(false)
|
||||
{}
|
||||
~ZipEntry(void) {}
|
||||
|
||||
/*
|
||||
* Returns "true" if the data is compressed.
|
||||
*/
|
||||
bool isCompressed(void) const {
|
||||
return mCDE.mCompressionMethod != kCompressStored;
|
||||
}
|
||||
int getCompressionMethod(void) const { return mCDE.mCompressionMethod; }
|
||||
|
||||
/*
|
||||
* Return the uncompressed length.
|
||||
*/
|
||||
off_t getUncompressedLen(void) const { return mCDE.mUncompressedSize; }
|
||||
|
||||
/*
|
||||
* Return the compressed length. For uncompressed data, this returns
|
||||
* the same thing as getUncompresesdLen().
|
||||
*/
|
||||
off_t getCompressedLen(void) const { return mCDE.mCompressedSize; }
|
||||
|
||||
/*
|
||||
* Return the offset of the local file header.
|
||||
*/
|
||||
off_t getLFHOffset(void) const { return mCDE.mLocalHeaderRelOffset; }
|
||||
|
||||
/*
|
||||
* Return the absolute file offset of the start of the compressed or
|
||||
* uncompressed data.
|
||||
*/
|
||||
off_t getFileOffset(void) const {
|
||||
return mCDE.mLocalHeaderRelOffset +
|
||||
LocalFileHeader::kLFHLen +
|
||||
mLFH.mFileNameLength +
|
||||
mLFH.mExtraFieldLength;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return the data CRC.
|
||||
*/
|
||||
unsigned long getCRC32(void) const { return mCDE.mCRC32; }
|
||||
|
||||
/*
|
||||
* Return file modification time in UNIX seconds-since-epoch.
|
||||
*/
|
||||
time_t getModWhen(void) const;
|
||||
|
||||
/*
|
||||
* Return the archived file name.
|
||||
*/
|
||||
const char* getFileName(void) const { return (const char*) mCDE.mFileName; }
|
||||
|
||||
/*
|
||||
* Application-defined "mark". Can be useful when synchronizing the
|
||||
* contents of an archive with contents on disk.
|
||||
*/
|
||||
bool getMarked(void) const { return mMarked; }
|
||||
void setMarked(bool val) { mMarked = val; }
|
||||
|
||||
/*
|
||||
* Some basic functions for raw data manipulation. "LE" means
|
||||
* Little Endian.
|
||||
*/
|
||||
static inline unsigned short getShortLE(const unsigned char* buf) {
|
||||
return buf[0] | (buf[1] << 8);
|
||||
}
|
||||
static inline unsigned long getLongLE(const unsigned char* buf) {
|
||||
return buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24);
|
||||
}
|
||||
static inline void putShortLE(unsigned char* buf, short val) {
|
||||
buf[0] = (unsigned char) val;
|
||||
buf[1] = (unsigned char) (val >> 8);
|
||||
}
|
||||
static inline void putLongLE(unsigned char* buf, long val) {
|
||||
buf[0] = (unsigned char) val;
|
||||
buf[1] = (unsigned char) (val >> 8);
|
||||
buf[2] = (unsigned char) (val >> 16);
|
||||
buf[3] = (unsigned char) (val >> 24);
|
||||
}
|
||||
|
||||
/* defined for Zip archives */
|
||||
enum {
|
||||
kCompressStored = 0, // no compression
|
||||
// shrunk = 1,
|
||||
// reduced 1 = 2,
|
||||
// reduced 2 = 3,
|
||||
// reduced 3 = 4,
|
||||
// reduced 4 = 5,
|
||||
// imploded = 6,
|
||||
// tokenized = 7,
|
||||
kCompressDeflated = 8, // standard deflate
|
||||
// Deflate64 = 9,
|
||||
// lib imploded = 10,
|
||||
// reserved = 11,
|
||||
// bzip2 = 12,
|
||||
};
|
||||
|
||||
/*
|
||||
* Deletion flag. If set, the entry will be removed on the next
|
||||
* call to "flush".
|
||||
*/
|
||||
bool getDeleted(void) const { return mDeleted; }
|
||||
|
||||
protected:
|
||||
/*
|
||||
* Initialize the structure from the file, which is pointing at
|
||||
* our Central Directory entry.
|
||||
*/
|
||||
status_t initFromCDE(FILE* fp);
|
||||
|
||||
/*
|
||||
* Initialize the structure for a new file. We need the filename
|
||||
* and comment so that we can properly size the LFH area. The
|
||||
* filename is mandatory, the comment is optional.
|
||||
*/
|
||||
void initNew(const char* fileName, const char* comment);
|
||||
|
||||
/*
|
||||
* Initialize the structure with the contents of a ZipEntry from
|
||||
* another file. If fileName is non-NULL, override the name with fileName.
|
||||
*/
|
||||
status_t initFromExternal(const ZipFile* pZipFile, const ZipEntry* pEntry,
|
||||
const char* fileName);
|
||||
|
||||
/*
|
||||
* Add some pad bytes to the LFH. We do this by adding or resizing
|
||||
* the "extra" field.
|
||||
*/
|
||||
status_t addPadding(int padding);
|
||||
|
||||
/*
|
||||
* Set information about the data for this entry.
|
||||
*/
|
||||
void setDataInfo(long uncompLen, long compLen, unsigned long crc32,
|
||||
int compressionMethod);
|
||||
|
||||
/*
|
||||
* Set the modification date.
|
||||
*/
|
||||
void setModWhen(time_t when);
|
||||
|
||||
/*
|
||||
* Set the offset of the local file header, relative to the start of
|
||||
* the current file.
|
||||
*/
|
||||
void setLFHOffset(off_t offset) {
|
||||
mCDE.mLocalHeaderRelOffset = (long) offset;
|
||||
}
|
||||
|
||||
/* mark for deletion; used by ZipFile::remove() */
|
||||
void setDeleted(void) { mDeleted = true; }
|
||||
|
||||
private:
|
||||
/* these are private and not defined */
|
||||
ZipEntry(const ZipEntry& src);
|
||||
ZipEntry& operator=(const ZipEntry& src);
|
||||
|
||||
/* returns "true" if the CDE and the LFH agree */
|
||||
bool compareHeaders(void) const;
|
||||
void copyCDEtoLFH(void);
|
||||
|
||||
bool mDeleted; // set if entry is pending deletion
|
||||
bool mMarked; // app-defined marker
|
||||
|
||||
/*
|
||||
* Every entry in the Zip archive starts off with one of these.
|
||||
*/
|
||||
class LocalFileHeader {
|
||||
public:
|
||||
LocalFileHeader(void) :
|
||||
mVersionToExtract(0),
|
||||
mGPBitFlag(0),
|
||||
mCompressionMethod(0),
|
||||
mLastModFileTime(0),
|
||||
mLastModFileDate(0),
|
||||
mCRC32(0),
|
||||
mCompressedSize(0),
|
||||
mUncompressedSize(0),
|
||||
mFileNameLength(0),
|
||||
mExtraFieldLength(0),
|
||||
mFileName(NULL),
|
||||
mExtraField(NULL)
|
||||
{}
|
||||
virtual ~LocalFileHeader(void) {
|
||||
delete[] mFileName;
|
||||
delete[] mExtraField;
|
||||
}
|
||||
|
||||
status_t read(FILE* fp);
|
||||
status_t write(FILE* fp);
|
||||
|
||||
// unsigned long mSignature;
|
||||
unsigned short mVersionToExtract;
|
||||
unsigned short mGPBitFlag;
|
||||
unsigned short mCompressionMethod;
|
||||
unsigned short mLastModFileTime;
|
||||
unsigned short mLastModFileDate;
|
||||
unsigned long mCRC32;
|
||||
unsigned long mCompressedSize;
|
||||
unsigned long mUncompressedSize;
|
||||
unsigned short mFileNameLength;
|
||||
unsigned short mExtraFieldLength;
|
||||
unsigned char* mFileName;
|
||||
unsigned char* mExtraField;
|
||||
|
||||
enum {
|
||||
kSignature = 0x04034b50,
|
||||
kLFHLen = 30, // LocalFileHdr len, excl. var fields
|
||||
};
|
||||
|
||||
void dump(void) const;
|
||||
};
|
||||
|
||||
/*
|
||||
* Every entry in the Zip archive has one of these in the "central
|
||||
* directory" at the end of the file.
|
||||
*/
|
||||
class CentralDirEntry {
|
||||
public:
|
||||
CentralDirEntry(void) :
|
||||
mVersionMadeBy(0),
|
||||
mVersionToExtract(0),
|
||||
mGPBitFlag(0),
|
||||
mCompressionMethod(0),
|
||||
mLastModFileTime(0),
|
||||
mLastModFileDate(0),
|
||||
mCRC32(0),
|
||||
mCompressedSize(0),
|
||||
mUncompressedSize(0),
|
||||
mFileNameLength(0),
|
||||
mExtraFieldLength(0),
|
||||
mFileCommentLength(0),
|
||||
mDiskNumberStart(0),
|
||||
mInternalAttrs(0),
|
||||
mExternalAttrs(0),
|
||||
mLocalHeaderRelOffset(0),
|
||||
mFileName(NULL),
|
||||
mExtraField(NULL),
|
||||
mFileComment(NULL)
|
||||
{}
|
||||
virtual ~CentralDirEntry(void) {
|
||||
delete[] mFileName;
|
||||
delete[] mExtraField;
|
||||
delete[] mFileComment;
|
||||
}
|
||||
|
||||
status_t read(FILE* fp);
|
||||
status_t write(FILE* fp);
|
||||
|
||||
CentralDirEntry& operator=(const CentralDirEntry& src);
|
||||
|
||||
// unsigned long mSignature;
|
||||
unsigned short mVersionMadeBy;
|
||||
unsigned short mVersionToExtract;
|
||||
unsigned short mGPBitFlag;
|
||||
unsigned short mCompressionMethod;
|
||||
unsigned short mLastModFileTime;
|
||||
unsigned short mLastModFileDate;
|
||||
unsigned long mCRC32;
|
||||
unsigned long mCompressedSize;
|
||||
unsigned long mUncompressedSize;
|
||||
unsigned short mFileNameLength;
|
||||
unsigned short mExtraFieldLength;
|
||||
unsigned short mFileCommentLength;
|
||||
unsigned short mDiskNumberStart;
|
||||
unsigned short mInternalAttrs;
|
||||
unsigned long mExternalAttrs;
|
||||
unsigned long mLocalHeaderRelOffset;
|
||||
unsigned char* mFileName;
|
||||
unsigned char* mExtraField;
|
||||
unsigned char* mFileComment;
|
||||
|
||||
void dump(void) const;
|
||||
|
||||
enum {
|
||||
kSignature = 0x02014b50,
|
||||
kCDELen = 46, // CentralDirEnt len, excl. var fields
|
||||
};
|
||||
};
|
||||
|
||||
enum {
|
||||
//kDataDescriptorSignature = 0x08074b50, // currently unused
|
||||
kDataDescriptorLen = 16, // four 32-bit fields
|
||||
|
||||
kDefaultVersion = 20, // need deflate, nothing much else
|
||||
kDefaultMadeBy = 0x0317, // 03=UNIX, 17=spec v2.3
|
||||
kUsesDataDescr = 0x0008, // GPBitFlag bit 3
|
||||
};
|
||||
|
||||
LocalFileHeader mLFH;
|
||||
CentralDirEntry mCDE;
|
||||
};
|
||||
|
||||
}; // namespace aapt
|
||||
|
||||
#endif // __LIBS_ZIPENTRY_H
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,278 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2006 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.
|
||||
*/
|
||||
|
||||
//
|
||||
// General-purpose Zip archive access. This class allows both reading and
|
||||
// writing to Zip archives, including deletion of existing entries.
|
||||
//
|
||||
#ifndef __LIBS_ZIPFILE_H
|
||||
#define __LIBS_ZIPFILE_H
|
||||
|
||||
#include "BigBuffer.h"
|
||||
#include "ZipEntry.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <utils/Errors.h>
|
||||
#include <vector>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
using android::status_t;
|
||||
|
||||
/*
|
||||
* Manipulate a Zip archive.
|
||||
*
|
||||
* Some changes will not be visible in the until until "flush" is called.
|
||||
*
|
||||
* The correct way to update a file archive is to make all changes to a
|
||||
* copy of the archive in a temporary file, and then unlink/rename over
|
||||
* the original after everything completes. Because we're only interested
|
||||
* in using this for packaging, we don't worry about such things. Crashing
|
||||
* after making changes and before flush() completes could leave us with
|
||||
* an unusable Zip archive.
|
||||
*/
|
||||
class ZipFile {
|
||||
public:
|
||||
ZipFile(void)
|
||||
: mZipFp(NULL), mReadOnly(false), mNeedCDRewrite(false)
|
||||
{}
|
||||
~ZipFile(void) {
|
||||
if (!mReadOnly)
|
||||
flush();
|
||||
if (mZipFp != NULL)
|
||||
fclose(mZipFp);
|
||||
discardEntries();
|
||||
}
|
||||
|
||||
/*
|
||||
* Open a new or existing archive.
|
||||
*/
|
||||
enum {
|
||||
kOpenReadOnly = 0x01,
|
||||
kOpenReadWrite = 0x02,
|
||||
kOpenCreate = 0x04, // create if it doesn't exist
|
||||
kOpenTruncate = 0x08, // if it exists, empty it
|
||||
};
|
||||
status_t open(const char* zipFileName, int flags);
|
||||
|
||||
/*
|
||||
* Add a file to the end of the archive. Specify whether you want the
|
||||
* library to try to store it compressed.
|
||||
*
|
||||
* If "storageName" is specified, the archive will use that instead
|
||||
* of "fileName".
|
||||
*
|
||||
* If there is already an entry with the same name, the call fails.
|
||||
* Existing entries with the same name must be removed first.
|
||||
*
|
||||
* If "ppEntry" is non-NULL, a pointer to the new entry will be returned.
|
||||
*/
|
||||
status_t add(const char* fileName, int compressionMethod,
|
||||
ZipEntry** ppEntry)
|
||||
{
|
||||
return add(fileName, fileName, compressionMethod, ppEntry);
|
||||
}
|
||||
status_t add(const char* fileName, const char* storageName,
|
||||
int compressionMethod, ZipEntry** ppEntry)
|
||||
{
|
||||
return addCommon(fileName, NULL, 0, storageName,
|
||||
ZipEntry::kCompressStored,
|
||||
compressionMethod, ppEntry);
|
||||
}
|
||||
|
||||
/*
|
||||
* Add a file that is already compressed with gzip.
|
||||
*
|
||||
* If "ppEntry" is non-NULL, a pointer to the new entry will be returned.
|
||||
*/
|
||||
status_t addGzip(const char* fileName, const char* storageName,
|
||||
ZipEntry** ppEntry)
|
||||
{
|
||||
return addCommon(fileName, NULL, 0, storageName,
|
||||
ZipEntry::kCompressDeflated,
|
||||
ZipEntry::kCompressDeflated, ppEntry);
|
||||
}
|
||||
|
||||
/*
|
||||
* Add a file from an in-memory data buffer.
|
||||
*
|
||||
* If "ppEntry" is non-NULL, a pointer to the new entry will be returned.
|
||||
*/
|
||||
status_t add(const void* data, size_t size, const char* storageName,
|
||||
int compressionMethod, ZipEntry** ppEntry)
|
||||
{
|
||||
return addCommon(NULL, data, size, storageName,
|
||||
ZipEntry::kCompressStored,
|
||||
compressionMethod, ppEntry);
|
||||
}
|
||||
|
||||
status_t add(const BigBuffer& data, const char* storageName,
|
||||
int compressionMethod, ZipEntry** ppEntry);
|
||||
|
||||
/*
|
||||
* Add an entry by copying it from another zip file. If storageName is
|
||||
* non-NULL, the entry will be inserted with the name storageName, otherwise
|
||||
* it will have the same name as the source entry. If "padding" is
|
||||
* nonzero, the specified number of bytes will be added to the "extra"
|
||||
* field in the header.
|
||||
*
|
||||
* If "ppEntry" is non-NULL, a pointer to the new entry will be returned.
|
||||
*/
|
||||
status_t add(const ZipFile* pSourceZip, const ZipEntry* pSourceEntry,
|
||||
const char* storageName, int padding, ZipEntry** ppEntry);
|
||||
|
||||
/*
|
||||
* Mark an entry as having been removed. It is not actually deleted
|
||||
* from the archive or our internal data structures until flush() is
|
||||
* called.
|
||||
*/
|
||||
status_t remove(ZipEntry* pEntry);
|
||||
|
||||
/*
|
||||
* Flush changes. If mNeedCDRewrite is set, this writes the central dir.
|
||||
*/
|
||||
status_t flush(void);
|
||||
|
||||
/*
|
||||
* Expand the data into the buffer provided. The buffer must hold
|
||||
* at least <uncompressed len> bytes. Variation expands directly
|
||||
* to a file.
|
||||
*
|
||||
* Returns "false" if an error was encountered in the compressed data.
|
||||
*/
|
||||
//bool uncompress(const ZipEntry* pEntry, void* buf) const;
|
||||
//bool uncompress(const ZipEntry* pEntry, FILE* fp) const;
|
||||
void* uncompress(const ZipEntry* pEntry);
|
||||
|
||||
/*
|
||||
* Get an entry, by name. Returns NULL if not found.
|
||||
*
|
||||
* Does not return entries pending deletion.
|
||||
*/
|
||||
ZipEntry* getEntryByName(const char* fileName) const;
|
||||
|
||||
/*
|
||||
* Get the Nth entry in the archive.
|
||||
*
|
||||
* This will return an entry that is pending deletion.
|
||||
*/
|
||||
int getNumEntries(void) const { return mEntries.size(); }
|
||||
ZipEntry* getEntryByIndex(int idx) const;
|
||||
|
||||
private:
|
||||
/* these are private and not defined */
|
||||
ZipFile(const ZipFile& src);
|
||||
ZipFile& operator=(const ZipFile& src);
|
||||
|
||||
class EndOfCentralDir {
|
||||
public:
|
||||
EndOfCentralDir(void) :
|
||||
mDiskNumber(0),
|
||||
mDiskWithCentralDir(0),
|
||||
mNumEntries(0),
|
||||
mTotalNumEntries(0),
|
||||
mCentralDirSize(0),
|
||||
mCentralDirOffset(0),
|
||||
mCommentLen(0),
|
||||
mComment(NULL)
|
||||
{}
|
||||
virtual ~EndOfCentralDir(void) {
|
||||
delete[] mComment;
|
||||
}
|
||||
|
||||
status_t readBuf(const unsigned char* buf, int len);
|
||||
status_t write(FILE* fp);
|
||||
|
||||
//unsigned long mSignature;
|
||||
unsigned short mDiskNumber;
|
||||
unsigned short mDiskWithCentralDir;
|
||||
unsigned short mNumEntries;
|
||||
unsigned short mTotalNumEntries;
|
||||
unsigned long mCentralDirSize;
|
||||
unsigned long mCentralDirOffset; // offset from first disk
|
||||
unsigned short mCommentLen;
|
||||
unsigned char* mComment;
|
||||
|
||||
enum {
|
||||
kSignature = 0x06054b50,
|
||||
kEOCDLen = 22, // EndOfCentralDir len, excl. comment
|
||||
|
||||
kMaxCommentLen = 65535, // longest possible in ushort
|
||||
kMaxEOCDSearch = kMaxCommentLen + EndOfCentralDir::kEOCDLen,
|
||||
|
||||
};
|
||||
|
||||
void dump(void) const;
|
||||
};
|
||||
|
||||
|
||||
/* read all entries in the central dir */
|
||||
status_t readCentralDir(void);
|
||||
|
||||
/* crunch deleted entries out */
|
||||
status_t crunchArchive(void);
|
||||
|
||||
/* clean up mEntries */
|
||||
void discardEntries(void);
|
||||
|
||||
/* common handler for all "add" functions */
|
||||
status_t addCommon(const char* fileName, const void* data, size_t size,
|
||||
const char* storageName, int sourceType, int compressionMethod,
|
||||
ZipEntry** ppEntry);
|
||||
|
||||
/* copy all of "srcFp" into "dstFp" */
|
||||
status_t copyFpToFp(FILE* dstFp, FILE* srcFp, unsigned long* pCRC32);
|
||||
/* copy all of "data" into "dstFp" */
|
||||
status_t copyDataToFp(FILE* dstFp,
|
||||
const void* data, size_t size, unsigned long* pCRC32);
|
||||
/* copy some of "srcFp" into "dstFp" */
|
||||
status_t copyPartialFpToFp(FILE* dstFp, FILE* srcFp, long length,
|
||||
unsigned long* pCRC32);
|
||||
/* like memmove(), but on parts of a single file */
|
||||
status_t filemove(FILE* fp, off_t dest, off_t src, size_t n);
|
||||
/* compress all of "srcFp" into "dstFp", using Deflate */
|
||||
status_t compressFpToFp(FILE* dstFp, FILE* srcFp,
|
||||
const void* data, size_t size, unsigned long* pCRC32);
|
||||
|
||||
/* get modification date from a file descriptor */
|
||||
time_t getModTime(int fd);
|
||||
|
||||
/*
|
||||
* We use stdio FILE*, which gives us buffering but makes dealing
|
||||
* with files >2GB awkward. Until we support Zip64, we're fine.
|
||||
*/
|
||||
FILE* mZipFp; // Zip file pointer
|
||||
|
||||
/* one of these per file */
|
||||
EndOfCentralDir mEOCD;
|
||||
|
||||
/* did we open this read-only? */
|
||||
bool mReadOnly;
|
||||
|
||||
/* set this when we trash the central dir */
|
||||
bool mNeedCDRewrite;
|
||||
|
||||
/*
|
||||
* One ZipEntry per entry in the zip file. I'm using pointers instead
|
||||
* of objects because it's easier than making operator= work for the
|
||||
* classes and sub-classes.
|
||||
*/
|
||||
std::vector<ZipEntry*> mEntries;
|
||||
};
|
||||
|
||||
}; // namespace aapt
|
||||
|
||||
#endif // __LIBS_ZIPFILE_H
|
||||
419
tools/aapt2/compile/Compile.cpp
Normal file
419
tools/aapt2/compile/Compile.cpp
Normal file
@@ -0,0 +1,419 @@
|
||||
/*
|
||||
* 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 "ConfigDescription.h"
|
||||
#include "Diagnostics.h"
|
||||
#include "Flags.h"
|
||||
#include "ResourceParser.h"
|
||||
#include "ResourceTable.h"
|
||||
#include "XmlDom.h"
|
||||
#include "XmlPullParser.h"
|
||||
|
||||
#include "compile/IdAssigner.h"
|
||||
#include "compile/Png.h"
|
||||
#include "compile/XmlIdCollector.h"
|
||||
#include "flatten/FileExportWriter.h"
|
||||
#include "flatten/TableFlattener.h"
|
||||
#include "flatten/XmlFlattener.h"
|
||||
#include "util/Files.h"
|
||||
#include "util/Maybe.h"
|
||||
#include "util/Util.h"
|
||||
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
struct ResourcePathData {
|
||||
Source source;
|
||||
std::u16string resourceDir;
|
||||
std::u16string name;
|
||||
std::string extension;
|
||||
|
||||
// Original config str. We keep this because when we parse the config, we may add on
|
||||
// version qualifiers. We want to preserve the original input so the output is easily
|
||||
// computed before hand.
|
||||
std::string configStr;
|
||||
ConfigDescription config;
|
||||
};
|
||||
|
||||
/**
|
||||
* Resource file paths are expected to look like:
|
||||
* [--/res/]type[-config]/name
|
||||
*/
|
||||
static Maybe<ResourcePathData> extractResourcePathData(const std::string& path,
|
||||
std::string* outError) {
|
||||
std::vector<std::string> parts = util::split(path, file::sDirSep);
|
||||
if (parts.size() < 2) {
|
||||
if (outError) *outError = "bad resource path";
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string& dir = parts[parts.size() - 2];
|
||||
StringPiece dirStr = dir;
|
||||
|
||||
StringPiece configStr;
|
||||
ConfigDescription config;
|
||||
size_t dashPos = dir.find('-');
|
||||
if (dashPos != std::string::npos) {
|
||||
configStr = dirStr.substr(dashPos + 1, dir.size() - (dashPos + 1));
|
||||
if (!ConfigDescription::parse(configStr, &config)) {
|
||||
if (outError) {
|
||||
std::stringstream errStr;
|
||||
errStr << "invalid configuration '" << configStr << "'";
|
||||
*outError = errStr.str();
|
||||
}
|
||||
return {};
|
||||
}
|
||||
dirStr = dirStr.substr(0, dashPos);
|
||||
}
|
||||
|
||||
std::string& filename = parts[parts.size() - 1];
|
||||
StringPiece name = filename;
|
||||
StringPiece extension;
|
||||
size_t dotPos = filename.find('.');
|
||||
if (dotPos != std::string::npos) {
|
||||
extension = name.substr(dotPos + 1, filename.size() - (dotPos + 1));
|
||||
name = name.substr(0, dotPos);
|
||||
}
|
||||
|
||||
return ResourcePathData{
|
||||
Source{ path },
|
||||
util::utf8ToUtf16(dirStr),
|
||||
util::utf8ToUtf16(name),
|
||||
extension.toString(),
|
||||
configStr.toString(),
|
||||
config
|
||||
};
|
||||
}
|
||||
|
||||
struct CompileOptions {
|
||||
std::string outputPath;
|
||||
bool verbose = false;
|
||||
};
|
||||
|
||||
static std::string buildIntermediateFilename(const std::string outDir,
|
||||
const ResourcePathData& data) {
|
||||
std::stringstream name;
|
||||
name << data.resourceDir;
|
||||
if (!data.configStr.empty()) {
|
||||
name << "-" << data.configStr;
|
||||
}
|
||||
name << "_" << data.name << "." << data.extension << ".flat";
|
||||
std::string outPath = outDir;
|
||||
file::appendPath(&outPath, name.str());
|
||||
return outPath;
|
||||
}
|
||||
|
||||
static bool compileTable(IAaptContext* context, const CompileOptions& options,
|
||||
const ResourcePathData& pathData, const std::string& outputPath) {
|
||||
ResourceTable table;
|
||||
table.createPackage(u"", 0x7f);
|
||||
|
||||
{
|
||||
std::ifstream fin(pathData.source.path, std::ifstream::binary);
|
||||
if (!fin) {
|
||||
context->getDiagnostics()->error(DiagMessage(pathData.source) << strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// Parse the values file from XML.
|
||||
XmlPullParser xmlParser(fin);
|
||||
ResourceParser resParser(context->getDiagnostics(), &table, pathData.source,
|
||||
pathData.config);
|
||||
if (!resParser.parse(&xmlParser)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
fin.close();
|
||||
}
|
||||
|
||||
// Assign IDs to prepare the table for flattening.
|
||||
IdAssigner idAssigner;
|
||||
if (!idAssigner.consume(context, &table)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Flatten the table.
|
||||
BigBuffer buffer(1024);
|
||||
TableFlattenerOptions tableFlattenerOptions;
|
||||
tableFlattenerOptions.useExtendedChunks = true;
|
||||
TableFlattener flattener(&buffer, tableFlattenerOptions);
|
||||
if (!flattener.consume(context, &table)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Build the output filename.
|
||||
std::ofstream fout(outputPath, std::ofstream::binary);
|
||||
if (!fout) {
|
||||
context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Write it to disk.
|
||||
if (!util::writeAll(fout, buffer)) {
|
||||
context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool compileXml(IAaptContext* context, const CompileOptions& options,
|
||||
const ResourcePathData& pathData, const std::string& outputPath) {
|
||||
|
||||
std::unique_ptr<XmlResource> xmlRes;
|
||||
|
||||
{
|
||||
std::ifstream fin(pathData.source.path, std::ifstream::binary);
|
||||
if (!fin) {
|
||||
context->getDiagnostics()->error(DiagMessage(pathData.source) << strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
xmlRes = xml::inflate(&fin, context->getDiagnostics(), pathData.source);
|
||||
|
||||
fin.close();
|
||||
}
|
||||
|
||||
if (!xmlRes) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Collect IDs that are defined here.
|
||||
XmlIdCollector collector;
|
||||
if (!collector.consume(context, xmlRes.get())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
xmlRes->file.name = ResourceName{ {}, *parseResourceType(pathData.resourceDir), pathData.name };
|
||||
xmlRes->file.config = pathData.config;
|
||||
xmlRes->file.source = pathData.source;
|
||||
|
||||
BigBuffer buffer(1024);
|
||||
ChunkWriter fileExportWriter = wrapBufferWithFileExportHeader(&buffer, &xmlRes->file);
|
||||
|
||||
XmlFlattenerOptions xmlFlattenerOptions;
|
||||
xmlFlattenerOptions.keepRawValues = true;
|
||||
XmlFlattener flattener(fileExportWriter.getBuffer(), xmlFlattenerOptions);
|
||||
if (!flattener.consume(context, xmlRes.get())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
fileExportWriter.finish();
|
||||
|
||||
std::ofstream fout(outputPath, std::ofstream::binary);
|
||||
if (!fout) {
|
||||
context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Write it to disk.
|
||||
if (!util::writeAll(fout, buffer)) {
|
||||
context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool compilePng(IAaptContext* context, const CompileOptions& options,
|
||||
const ResourcePathData& pathData, const std::string& outputPath) {
|
||||
BigBuffer buffer(4096);
|
||||
ResourceFile resFile;
|
||||
resFile.name = ResourceName{ {}, *parseResourceType(pathData.resourceDir), pathData.name };
|
||||
resFile.config = pathData.config;
|
||||
resFile.source = pathData.source;
|
||||
|
||||
ChunkWriter fileExportWriter = wrapBufferWithFileExportHeader(&buffer, &resFile);
|
||||
|
||||
{
|
||||
std::ifstream fin(pathData.source.path, std::ifstream::binary);
|
||||
if (!fin) {
|
||||
context->getDiagnostics()->error(DiagMessage(pathData.source) << strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
Png png(context->getDiagnostics());
|
||||
if (!png.process(pathData.source, &fin, fileExportWriter.getBuffer(), {})) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
fileExportWriter.finish();
|
||||
|
||||
std::ofstream fout(outputPath, std::ofstream::binary);
|
||||
if (!fout) {
|
||||
context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!util::writeAll(fout, buffer)) {
|
||||
context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool compileFile(IAaptContext* context, const CompileOptions& options,
|
||||
const ResourcePathData& pathData, const std::string& outputPath) {
|
||||
BigBuffer buffer(256);
|
||||
ResourceFile resFile;
|
||||
resFile.name = ResourceName{ {}, *parseResourceType(pathData.resourceDir), pathData.name };
|
||||
resFile.config = pathData.config;
|
||||
resFile.source = pathData.source;
|
||||
|
||||
ChunkWriter fileExportWriter = wrapBufferWithFileExportHeader(&buffer, &resFile);
|
||||
|
||||
std::string errorStr;
|
||||
Maybe<android::FileMap> f = file::mmapPath(pathData.source.path, &errorStr);
|
||||
if (!f) {
|
||||
context->getDiagnostics()->error(DiagMessage(pathData.source) << errorStr);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::ofstream fout(outputPath, std::ofstream::binary);
|
||||
if (!fout) {
|
||||
context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Manually set the size and don't call finish(). This is because we are not copying from
|
||||
// the buffer the entire file.
|
||||
fileExportWriter.getChunkHeader()->size =
|
||||
util::hostToDevice32(buffer.size() + f.value().getDataLength());
|
||||
if (!util::writeAll(fout, buffer)) {
|
||||
context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!fout.write((const char*) f.value().getDataPtr(), f.value().getDataLength())) {
|
||||
context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
class CompileContext : public IAaptContext {
|
||||
private:
|
||||
StdErrDiagnostics mDiagnostics;
|
||||
|
||||
public:
|
||||
IDiagnostics* getDiagnostics() override {
|
||||
return &mDiagnostics;
|
||||
}
|
||||
|
||||
NameMangler* getNameMangler() override {
|
||||
abort();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
StringPiece16 getCompilationPackage() override {
|
||||
return {};
|
||||
}
|
||||
|
||||
uint8_t getPackageId() override {
|
||||
return 0x7f;
|
||||
}
|
||||
|
||||
ISymbolTable* getExternalSymbols() override {
|
||||
abort();
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Entry point for compilation phase. Parses arguments and dispatches to the correct steps.
|
||||
*/
|
||||
int compile(const std::vector<StringPiece>& args) {
|
||||
CompileOptions options;
|
||||
|
||||
Flags flags = Flags()
|
||||
.requiredFlag("-o", "Output path", &options.outputPath)
|
||||
.optionalSwitch("-v", "Enables verbose logging", &options.verbose);
|
||||
if (!flags.parse("aapt2 compile", args, &std::cerr)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
CompileContext context;
|
||||
|
||||
std::vector<ResourcePathData> inputData;
|
||||
inputData.reserve(flags.getArgs().size());
|
||||
|
||||
// Collect data from the path for each input file.
|
||||
for (const std::string& arg : flags.getArgs()) {
|
||||
std::string errorStr;
|
||||
if (Maybe<ResourcePathData> pathData = extractResourcePathData(arg, &errorStr)) {
|
||||
inputData.push_back(std::move(pathData.value()));
|
||||
} else {
|
||||
context.getDiagnostics()->error(DiagMessage() << errorStr << " (" << arg << ")");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
bool error = false;
|
||||
for (ResourcePathData& pathData : inputData) {
|
||||
if (options.verbose) {
|
||||
context.getDiagnostics()->note(DiagMessage(pathData.source) << "processing");
|
||||
}
|
||||
|
||||
if (pathData.resourceDir == u"values") {
|
||||
// Overwrite the extension.
|
||||
pathData.extension = "arsc";
|
||||
|
||||
const std::string outputFilename = buildIntermediateFilename(
|
||||
options.outputPath, pathData);
|
||||
if (!compileTable(&context, options, pathData, outputFilename)) {
|
||||
error = true;
|
||||
}
|
||||
|
||||
} else {
|
||||
const std::string outputFilename = buildIntermediateFilename(options.outputPath,
|
||||
pathData);
|
||||
if (const ResourceType* type = parseResourceType(pathData.resourceDir)) {
|
||||
if (*type != ResourceType::kRaw) {
|
||||
if (pathData.extension == "xml") {
|
||||
if (!compileXml(&context, options, pathData, outputFilename)) {
|
||||
error = true;
|
||||
}
|
||||
} else if (pathData.extension == "png" || pathData.extension == "9.png") {
|
||||
if (!compilePng(&context, options, pathData, outputFilename)) {
|
||||
error = true;
|
||||
}
|
||||
} else {
|
||||
if (!compileFile(&context, options, pathData, outputFilename)) {
|
||||
error = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!compileFile(&context, options, pathData, outputFilename)) {
|
||||
error = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
context.getDiagnostics()->error(
|
||||
DiagMessage() << "invalid file path '" << pathData.source << "'");
|
||||
error = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
112
tools/aapt2/compile/IdAssigner.cpp
Normal file
112
tools/aapt2/compile/IdAssigner.cpp
Normal file
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* 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 "ResourceTable.h"
|
||||
|
||||
#include "compile/IdAssigner.h"
|
||||
#include "process/IResourceTableConsumer.h"
|
||||
#include "util/Util.h"
|
||||
|
||||
#include <bitset>
|
||||
#include <cassert>
|
||||
#include <set>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
bool IdAssigner::consume(IAaptContext* context, ResourceTable* table) {
|
||||
std::bitset<256> usedTypeIds;
|
||||
std::set<uint16_t> usedEntryIds;
|
||||
|
||||
for (auto& package : table->packages) {
|
||||
assert(package->id && "packages must have manually assigned IDs");
|
||||
|
||||
usedTypeIds.reset();
|
||||
|
||||
// Type ID 0 is invalid, reserve it.
|
||||
usedTypeIds.set(0);
|
||||
|
||||
// Collect used type IDs.
|
||||
for (auto& type : package->types) {
|
||||
if (type->id) {
|
||||
usedEntryIds.clear();
|
||||
|
||||
if (usedTypeIds[type->id.value()]) {
|
||||
// This ID is already taken!
|
||||
context->getDiagnostics()->error(DiagMessage()
|
||||
<< "type '" << type->type << "' in "
|
||||
<< "package '" << package->name << "' has "
|
||||
<< "duplicate ID "
|
||||
<< std::hex << (int) type->id.value()
|
||||
<< std::dec);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Mark the type ID as taken.
|
||||
usedTypeIds.set(type->id.value());
|
||||
}
|
||||
|
||||
// Collect used entry IDs.
|
||||
for (auto& entry : type->entries) {
|
||||
if (entry->id) {
|
||||
// Mark entry ID as taken.
|
||||
if (!usedEntryIds.insert(entry->id.value()).second) {
|
||||
// This ID existed before!
|
||||
ResourceNameRef nameRef =
|
||||
{ package->name, type->type, entry->name };
|
||||
ResourceId takenId(package->id.value(), type->id.value(),
|
||||
entry->id.value());
|
||||
context->getDiagnostics()->error(DiagMessage()
|
||||
<< "resource '" << nameRef << "' "
|
||||
<< "has duplicate ID '"
|
||||
<< takenId << "'");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Assign unused entry IDs.
|
||||
const auto endUsedEntryIter = usedEntryIds.end();
|
||||
auto nextUsedEntryIter = usedEntryIds.begin();
|
||||
uint16_t nextId = 0;
|
||||
for (auto& entry : type->entries) {
|
||||
if (!entry->id) {
|
||||
// Assign the next available entryID.
|
||||
while (nextUsedEntryIter != endUsedEntryIter &&
|
||||
nextId == *nextUsedEntryIter) {
|
||||
nextId++;
|
||||
++nextUsedEntryIter;
|
||||
}
|
||||
entry->id = nextId++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Assign unused type IDs.
|
||||
size_t nextTypeId = 0;
|
||||
for (auto& type : package->types) {
|
||||
if (!type->id) {
|
||||
while (nextTypeId < usedTypeIds.size() && usedTypeIds[nextTypeId]) {
|
||||
nextTypeId++;
|
||||
}
|
||||
type->id = static_cast<uint8_t>(nextTypeId);
|
||||
nextTypeId++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
@@ -14,32 +14,21 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef AAPT_MANIFEST_PARSER_H
|
||||
#define AAPT_MANIFEST_PARSER_H
|
||||
#ifndef AAPT_COMPILE_IDASSIGNER_H
|
||||
#define AAPT_COMPILE_IDASSIGNER_H
|
||||
|
||||
#include "AppInfo.h"
|
||||
#include "Logger.h"
|
||||
#include "Source.h"
|
||||
#include "XmlPullParser.h"
|
||||
#include "process/IResourceTableConsumer.h"
|
||||
|
||||
namespace aapt {
|
||||
|
||||
/*
|
||||
* Parses an AndroidManifest.xml file and fills in an AppInfo structure with
|
||||
* app data.
|
||||
/**
|
||||
* Assigns IDs to each resource in the table, respecting existing IDs and filling in gaps
|
||||
* in between fixed ID assignments.
|
||||
*/
|
||||
class ManifestParser {
|
||||
public:
|
||||
ManifestParser() = default;
|
||||
ManifestParser(const ManifestParser&) = delete;
|
||||
|
||||
bool parse(const Source& source, std::shared_ptr<XmlPullParser> parser, AppInfo* outInfo);
|
||||
|
||||
private:
|
||||
bool parseManifest(SourceLogger& logger, std::shared_ptr<XmlPullParser> parser,
|
||||
AppInfo* outInfo);
|
||||
struct IdAssigner : public IResourceTableConsumer {
|
||||
bool consume(IAaptContext* context, ResourceTable* table) override;
|
||||
};
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
#endif // AAPT_MANIFEST_PARSER_H
|
||||
#endif /* AAPT_COMPILE_IDASSIGNER_H */
|
||||
123
tools/aapt2/compile/IdAssigner_test.cpp
Normal file
123
tools/aapt2/compile/IdAssigner_test.cpp
Normal file
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* 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 "compile/IdAssigner.h"
|
||||
|
||||
#include "test/Context.h"
|
||||
#include "test/Builders.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
::testing::AssertionResult verifyIds(ResourceTable* table);
|
||||
|
||||
TEST(IdAssignerTest, AssignIds) {
|
||||
std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
|
||||
.addSimple(u"@android:attr/foo")
|
||||
.addSimple(u"@android:attr/bar")
|
||||
.addSimple(u"@android:id/foo")
|
||||
.setPackageId(u"android", 0x01)
|
||||
.build();
|
||||
|
||||
std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
|
||||
IdAssigner assigner;
|
||||
|
||||
ASSERT_TRUE(assigner.consume(context.get(), table.get()));
|
||||
ASSERT_TRUE(verifyIds(table.get()));
|
||||
}
|
||||
|
||||
TEST(IdAssignerTest, AssignIdsWithReservedIds) {
|
||||
std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
|
||||
.addSimple(u"@android:attr/foo", ResourceId(0x01040006))
|
||||
.addSimple(u"@android:attr/bar")
|
||||
.addSimple(u"@android:id/foo")
|
||||
.addSimple(u"@app:id/biz")
|
||||
.setPackageId(u"android", 0x01)
|
||||
.setPackageId(u"app", 0x7f)
|
||||
.build();
|
||||
|
||||
std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
|
||||
IdAssigner assigner;
|
||||
|
||||
ASSERT_TRUE(assigner.consume(context.get(), table.get()));
|
||||
ASSERT_TRUE(verifyIds(table.get()));
|
||||
}
|
||||
|
||||
TEST(IdAssignerTest, FailWhenNonUniqueIdsAssigned) {
|
||||
std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
|
||||
.addSimple(u"@android:attr/foo", ResourceId(0x01040006))
|
||||
.addSimple(u"@android:attr/bar", ResourceId(0x01040006))
|
||||
.setPackageId(u"android", 0x01)
|
||||
.setPackageId(u"app", 0x7f)
|
||||
.build();
|
||||
|
||||
std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
|
||||
IdAssigner assigner;
|
||||
|
||||
ASSERT_FALSE(assigner.consume(context.get(), table.get()));
|
||||
}
|
||||
|
||||
::testing::AssertionResult verifyIds(ResourceTable* table) {
|
||||
std::set<uint8_t> packageIds;
|
||||
for (auto& package : table->packages) {
|
||||
if (!package->id) {
|
||||
return ::testing::AssertionFailure() << "package " << package->name << " has no ID";
|
||||
}
|
||||
|
||||
if (!packageIds.insert(package->id.value()).second) {
|
||||
return ::testing::AssertionFailure() << "package " << package->name
|
||||
<< " has non-unique ID " << std::hex << (int) package->id.value() << std::dec;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& package : table->packages) {
|
||||
std::set<uint8_t> typeIds;
|
||||
for (auto& type : package->types) {
|
||||
if (!type->id) {
|
||||
return ::testing::AssertionFailure() << "type " << type->type << " of package "
|
||||
<< package->name << " has no ID";
|
||||
}
|
||||
|
||||
if (!typeIds.insert(type->id.value()).second) {
|
||||
return ::testing::AssertionFailure() << "type " << type->type
|
||||
<< " of package " << package->name << " has non-unique ID "
|
||||
<< std::hex << (int) type->id.value() << std::dec;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
for (auto& type : package->types) {
|
||||
std::set<uint16_t> entryIds;
|
||||
for (auto& entry : type->entries) {
|
||||
if (!entry->id) {
|
||||
return ::testing::AssertionFailure() << "entry " << entry->name << " of type "
|
||||
<< type->type << " of package " << package->name << " has no ID";
|
||||
}
|
||||
|
||||
if (!entryIds.insert(entry->id.value()).second) {
|
||||
return ::testing::AssertionFailure() << "entry " << entry->name
|
||||
<< " of type " << type->type << " of package " << package->name
|
||||
<< " has non-unique ID "
|
||||
<< std::hex << (int) entry->id.value() << std::dec;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return ::testing::AssertionSuccess() << "all IDs are unique and assigned";
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
@@ -14,11 +14,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "BigBuffer.h"
|
||||
#include "Logger.h"
|
||||
#include "util/BigBuffer.h"
|
||||
#include "Png.h"
|
||||
#include "Source.h"
|
||||
#include "Util.h"
|
||||
#include "util/Util.h"
|
||||
|
||||
#include <androidfw/ResourceTypes.h>
|
||||
#include <iostream>
|
||||
@@ -95,15 +94,14 @@ static void flushDataToStream(png_structp /*writePtr*/) {
|
||||
}
|
||||
|
||||
static void logWarning(png_structp readPtr, png_const_charp warningMessage) {
|
||||
SourceLogger* logger = reinterpret_cast<SourceLogger*>(png_get_error_ptr(readPtr));
|
||||
logger->warn() << warningMessage << "." << std::endl;
|
||||
IDiagnostics* diag = reinterpret_cast<IDiagnostics*>(png_get_error_ptr(readPtr));
|
||||
diag->warn(DiagMessage() << warningMessage);
|
||||
}
|
||||
|
||||
|
||||
static bool readPng(png_structp readPtr, png_infop infoPtr, PngInfo* outInfo,
|
||||
std::string* outError) {
|
||||
static bool readPng(IDiagnostics* diag, png_structp readPtr, png_infop infoPtr, PngInfo* outInfo) {
|
||||
if (setjmp(png_jmpbuf(readPtr))) {
|
||||
*outError = "failed reading png";
|
||||
diag->error(DiagMessage() << "failed reading png");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -229,7 +227,7 @@ static void checkNinePatchSerialization(android::Res_png_9patch* inPatch, void*
|
||||
#define MAX(a,b) ((a)>(b)?(a):(b))
|
||||
#define ABS(a) ((a)<0?-(a):(a))
|
||||
|
||||
static void analyze_image(SourceLogger* logger, const PngInfo& imageInfo, int grayscaleTolerance,
|
||||
static void analyze_image(IDiagnostics* diag, const PngInfo& imageInfo, int grayscaleTolerance,
|
||||
png_colorp rgbPalette, png_bytep alphaPalette,
|
||||
int *paletteEntries, bool *hasTransparency, int *colorType,
|
||||
png_bytepp outRows) {
|
||||
@@ -363,9 +361,9 @@ static void analyze_image(SourceLogger* logger, const PngInfo& imageInfo, int gr
|
||||
*colorType = PNG_COLOR_TYPE_PALETTE;
|
||||
} else {
|
||||
if (maxGrayDeviation <= grayscaleTolerance) {
|
||||
logger->note() << "forcing image to gray (max deviation = " << maxGrayDeviation
|
||||
<< ")."
|
||||
<< std::endl;
|
||||
diag->note(DiagMessage()
|
||||
<< "forcing image to gray (max deviation = "
|
||||
<< maxGrayDeviation << ")");
|
||||
*colorType = isOpaque ? PNG_COLOR_TYPE_GRAY : PNG_COLOR_TYPE_GRAY_ALPHA;
|
||||
} else {
|
||||
*colorType = isOpaque ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGB_ALPHA;
|
||||
@@ -411,10 +409,10 @@ static void analyze_image(SourceLogger* logger, const PngInfo& imageInfo, int gr
|
||||
}
|
||||
}
|
||||
|
||||
static bool writePng(png_structp writePtr, png_infop infoPtr, PngInfo* info,
|
||||
int grayScaleTolerance, SourceLogger* logger, std::string* outError) {
|
||||
static bool writePng(IDiagnostics* diag, png_structp writePtr, png_infop infoPtr, PngInfo* info,
|
||||
int grayScaleTolerance) {
|
||||
if (setjmp(png_jmpbuf(writePtr))) {
|
||||
*outError = "failed to write png";
|
||||
diag->error(DiagMessage() << "failed to write png");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -442,9 +440,9 @@ static bool writePng(png_structp writePtr, png_infop infoPtr, PngInfo* info,
|
||||
png_set_compression_level(writePtr, Z_BEST_COMPRESSION);
|
||||
|
||||
if (kDebug) {
|
||||
logger->note() << "writing image: w = " << info->width
|
||||
<< ", h = " << info->height
|
||||
<< std::endl;
|
||||
diag->note(DiagMessage()
|
||||
<< "writing image: w = " << info->width
|
||||
<< ", h = " << info->height);
|
||||
}
|
||||
|
||||
png_color rgbPalette[256];
|
||||
@@ -452,7 +450,7 @@ static bool writePng(png_structp writePtr, png_infop infoPtr, PngInfo* info,
|
||||
bool hasTransparency;
|
||||
int paletteEntries;
|
||||
|
||||
analyze_image(logger, *info, grayScaleTolerance, rgbPalette, alphaPalette,
|
||||
analyze_image(diag, *info, grayScaleTolerance, rgbPalette, alphaPalette,
|
||||
&paletteEntries, &hasTransparency, &colorType, outRows);
|
||||
|
||||
// If the image is a 9-patch, we need to preserve it as a ARGB file to make
|
||||
@@ -465,22 +463,22 @@ static bool writePng(png_structp writePtr, png_infop infoPtr, PngInfo* info,
|
||||
if (kDebug) {
|
||||
switch (colorType) {
|
||||
case PNG_COLOR_TYPE_PALETTE:
|
||||
logger->note() << "has " << paletteEntries
|
||||
<< " colors" << (hasTransparency ? " (with alpha)" : "")
|
||||
<< ", using PNG_COLOR_TYPE_PALLETTE."
|
||||
<< std::endl;
|
||||
diag->note(DiagMessage()
|
||||
<< "has " << paletteEntries
|
||||
<< " colors" << (hasTransparency ? " (with alpha)" : "")
|
||||
<< ", using PNG_COLOR_TYPE_PALLETTE");
|
||||
break;
|
||||
case PNG_COLOR_TYPE_GRAY:
|
||||
logger->note() << "is opaque gray, using PNG_COLOR_TYPE_GRAY." << std::endl;
|
||||
diag->note(DiagMessage() << "is opaque gray, using PNG_COLOR_TYPE_GRAY");
|
||||
break;
|
||||
case PNG_COLOR_TYPE_GRAY_ALPHA:
|
||||
logger->note() << "is gray + alpha, using PNG_COLOR_TYPE_GRAY_ALPHA." << std::endl;
|
||||
diag->note(DiagMessage() << "is gray + alpha, using PNG_COLOR_TYPE_GRAY_ALPHA");
|
||||
break;
|
||||
case PNG_COLOR_TYPE_RGB:
|
||||
logger->note() << "is opaque RGB, using PNG_COLOR_TYPE_RGB." << std::endl;
|
||||
diag->note(DiagMessage() << "is opaque RGB, using PNG_COLOR_TYPE_RGB");
|
||||
break;
|
||||
case PNG_COLOR_TYPE_RGB_ALPHA:
|
||||
logger->note() << "is RGB + alpha, using PNG_COLOR_TYPE_RGB_ALPHA." << std::endl;
|
||||
diag->note(DiagMessage() << "is RGB + alpha, using PNG_COLOR_TYPE_RGB_ALPHA");
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -511,7 +509,7 @@ static bool writePng(png_structp writePtr, png_infop infoPtr, PngInfo* info,
|
||||
|
||||
// base 9 patch data
|
||||
if (kDebug) {
|
||||
logger->note() << "adding 9-patch info..." << std::endl;
|
||||
diag->note(DiagMessage() << "adding 9-patch info..");
|
||||
}
|
||||
strcpy((char*)unknowns[pIndex].name, "npTc");
|
||||
unknowns[pIndex].data = (png_byte*) info->serialize9Patch();
|
||||
@@ -587,10 +585,10 @@ static bool writePng(png_structp writePtr, png_infop infoPtr, PngInfo* info,
|
||||
&compressionType, nullptr);
|
||||
|
||||
if (kDebug) {
|
||||
logger->note() << "image written: w = " << width << ", h = " << height
|
||||
<< ", d = " << bitDepth << ", colors = " << colorType
|
||||
<< ", inter = " << interlaceType << ", comp = " << compressionType
|
||||
<< std::endl;
|
||||
diag->note(DiagMessage()
|
||||
<< "image written: w = " << width << ", h = " << height
|
||||
<< ", d = " << bitDepth << ", colors = " << colorType
|
||||
<< ", inter = " << interlaceType << ", comp = " << compressionType);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -1192,23 +1190,22 @@ getout:
|
||||
}
|
||||
|
||||
|
||||
bool Png::process(const Source& source, std::istream& input, BigBuffer* outBuffer,
|
||||
const Options& options, std::string* outError) {
|
||||
bool Png::process(const Source& source, std::istream* input, BigBuffer* outBuffer,
|
||||
const PngOptions& options) {
|
||||
png_byte signature[kPngSignatureSize];
|
||||
|
||||
// Read the PNG signature first.
|
||||
if (!input.read(reinterpret_cast<char*>(signature), kPngSignatureSize)) {
|
||||
*outError = strerror(errno);
|
||||
if (!input->read(reinterpret_cast<char*>(signature), kPngSignatureSize)) {
|
||||
mDiag->error(DiagMessage() << strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the PNG signature doesn't match, bail early.
|
||||
if (png_sig_cmp(signature, 0, kPngSignatureSize) != 0) {
|
||||
*outError = "not a valid png file";
|
||||
mDiag->error(DiagMessage() << "not a valid png file");
|
||||
return false;
|
||||
}
|
||||
|
||||
SourceLogger logger(source);
|
||||
bool result = false;
|
||||
png_structp readPtr = nullptr;
|
||||
png_infop infoPtr = nullptr;
|
||||
@@ -1218,40 +1215,42 @@ bool Png::process(const Source& source, std::istream& input, BigBuffer* outBuffe
|
||||
|
||||
readPtr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, nullptr, nullptr);
|
||||
if (!readPtr) {
|
||||
*outError = "failed to allocate read ptr";
|
||||
mDiag->error(DiagMessage() << "failed to allocate read ptr");
|
||||
goto bail;
|
||||
}
|
||||
|
||||
infoPtr = png_create_info_struct(readPtr);
|
||||
if (!infoPtr) {
|
||||
*outError = "failed to allocate info ptr";
|
||||
mDiag->error(DiagMessage() << "failed to allocate info ptr");
|
||||
goto bail;
|
||||
}
|
||||
|
||||
png_set_error_fn(readPtr, reinterpret_cast<png_voidp>(&logger), nullptr, logWarning);
|
||||
png_set_error_fn(readPtr, reinterpret_cast<png_voidp>(mDiag), nullptr, logWarning);
|
||||
|
||||
// Set the read function to read from std::istream.
|
||||
png_set_read_fn(readPtr, (png_voidp)&input, readDataFromStream);
|
||||
png_set_read_fn(readPtr, (png_voidp) input, readDataFromStream);
|
||||
|
||||
if (!readPng(readPtr, infoPtr, &pngInfo, outError)) {
|
||||
if (!readPng(mDiag, readPtr, infoPtr, &pngInfo)) {
|
||||
goto bail;
|
||||
}
|
||||
|
||||
if (util::stringEndsWith<char>(source.path, ".9.png")) {
|
||||
if (!do9Patch(&pngInfo, outError)) {
|
||||
std::string errorMsg;
|
||||
if (!do9Patch(&pngInfo, &errorMsg)) {
|
||||
mDiag->error(DiagMessage() << errorMsg);
|
||||
goto bail;
|
||||
}
|
||||
}
|
||||
|
||||
writePtr = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, nullptr, nullptr);
|
||||
if (!writePtr) {
|
||||
*outError = "failed to allocate write ptr";
|
||||
mDiag->error(DiagMessage() << "failed to allocate write ptr");
|
||||
goto bail;
|
||||
}
|
||||
|
||||
writeInfoPtr = png_create_info_struct(writePtr);
|
||||
if (!writeInfoPtr) {
|
||||
*outError = "failed to allocate write info ptr";
|
||||
mDiag->error(DiagMessage() << "failed to allocate write info ptr");
|
||||
goto bail;
|
||||
}
|
||||
|
||||
@@ -1260,8 +1259,7 @@ bool Png::process(const Source& source, std::istream& input, BigBuffer* outBuffe
|
||||
// Set the write function to write to std::ostream.
|
||||
png_set_write_fn(writePtr, (png_voidp)outBuffer, writeDataToStream, flushDataToStream);
|
||||
|
||||
if (!writePng(writePtr, writeInfoPtr, &pngInfo, options.grayScaleTolerance, &logger,
|
||||
outError)) {
|
||||
if (!writePng(mDiag, writePtr, writeInfoPtr, &pngInfo, options.grayScaleTolerance)) {
|
||||
goto bail;
|
||||
}
|
||||
|
||||
@@ -17,7 +17,8 @@
|
||||
#ifndef AAPT_PNG_H
|
||||
#define AAPT_PNG_H
|
||||
|
||||
#include "BigBuffer.h"
|
||||
#include "util/BigBuffer.h"
|
||||
#include "Diagnostics.h"
|
||||
#include "Source.h"
|
||||
|
||||
#include <iostream>
|
||||
@@ -25,13 +26,20 @@
|
||||
|
||||
namespace aapt {
|
||||
|
||||
struct Png {
|
||||
struct Options {
|
||||
int grayScaleTolerance = 0;
|
||||
};
|
||||
struct PngOptions {
|
||||
int grayScaleTolerance = 0;
|
||||
};
|
||||
|
||||
bool process(const Source& source, std::istream& input, BigBuffer* outBuffer,
|
||||
const Options& options, std::string* outError);
|
||||
class Png {
|
||||
public:
|
||||
Png(IDiagnostics* diag) : mDiag(diag) {
|
||||
}
|
||||
|
||||
bool process(const Source& source, std::istream* input, BigBuffer* outBuffer,
|
||||
const PngOptions& options);
|
||||
|
||||
private:
|
||||
IDiagnostics* mDiag;
|
||||
};
|
||||
|
||||
} // namespace aapt
|
||||
71
tools/aapt2/compile/XmlIdCollector.cpp
Normal file
71
tools/aapt2/compile/XmlIdCollector.cpp
Normal file
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* 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 "ResourceUtils.h"
|
||||
#include "ResourceValues.h"
|
||||
#include "XmlDom.h"
|
||||
|
||||
#include "compile/XmlIdCollector.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
namespace {
|
||||
|
||||
static bool cmpName(const SourcedResourceName& a, const ResourceNameRef& b) {
|
||||
return a.name < b;
|
||||
}
|
||||
|
||||
struct IdCollector : public xml::Visitor {
|
||||
using xml::Visitor::visit;
|
||||
|
||||
std::vector<SourcedResourceName>* mOutSymbols;
|
||||
|
||||
IdCollector(std::vector<SourcedResourceName>* outSymbols) : mOutSymbols(outSymbols) {
|
||||
}
|
||||
|
||||
void visit(xml::Element* element) override {
|
||||
for (xml::Attribute& attr : element->attributes) {
|
||||
ResourceNameRef name;
|
||||
bool create = false;
|
||||
if (ResourceUtils::tryParseReference(attr.value, &name, &create, nullptr)) {
|
||||
if (create && name.type == ResourceType::kId) {
|
||||
auto iter = std::lower_bound(mOutSymbols->begin(), mOutSymbols->end(),
|
||||
name, cmpName);
|
||||
if (iter == mOutSymbols->end() || iter->name != name) {
|
||||
mOutSymbols->insert(iter, SourcedResourceName{ name.toResourceName(),
|
||||
element->lineNumber });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
xml::Visitor::visit(element);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
bool XmlIdCollector::consume(IAaptContext* context, XmlResource* xmlRes) {
|
||||
xmlRes->file.exportedSymbols.clear();
|
||||
IdCollector collector(&xmlRes->file.exportedSymbols);
|
||||
xmlRes->root->accept(&collector);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
@@ -14,20 +14,17 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#ifndef AAPT_XMLIDCOLLECTOR_H
|
||||
#define AAPT_XMLIDCOLLECTOR_H
|
||||
|
||||
#include "process/IResourceTableConsumer.h"
|
||||
|
||||
namespace aapt {
|
||||
|
||||
TEST(CompatTest, VersionAttributesInStyle) {
|
||||
}
|
||||
|
||||
TEST(CompatTest, VersionAttributesInXML) {
|
||||
}
|
||||
|
||||
TEST(CompatTest, DoNotOverrideExistingVersionedFiles) {
|
||||
}
|
||||
|
||||
TEST(CompatTest, VersionAttributesInStyleWithCorrectPrecedence) {
|
||||
}
|
||||
struct XmlIdCollector : public IXmlResourceConsumer {
|
||||
bool consume(IAaptContext* context, XmlResource* xmlRes) override;
|
||||
};
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
#endif /* AAPT_XMLIDCOLLECTOR_H */
|
||||
61
tools/aapt2/compile/XmlIdCollector_test.cpp
Normal file
61
tools/aapt2/compile/XmlIdCollector_test.cpp
Normal file
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* 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 "compile/XmlIdCollector.h"
|
||||
#include "test/Builders.h"
|
||||
#include "test/Context.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
TEST(XmlIdCollectorTest, CollectsIds) {
|
||||
std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
|
||||
|
||||
std::unique_ptr<XmlResource> doc = test::buildXmlDom(R"EOF(
|
||||
<View xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/foo"
|
||||
text="@+id/bar">
|
||||
<SubView android:id="@+id/car"
|
||||
class="@+id/bar"/>
|
||||
</View>)EOF");
|
||||
|
||||
XmlIdCollector collector;
|
||||
ASSERT_TRUE(collector.consume(context.get(), doc.get()));
|
||||
|
||||
EXPECT_EQ(1u, std::count(doc->file.exportedSymbols.begin(), doc->file.exportedSymbols.end(),
|
||||
SourcedResourceName{ test::parseNameOrDie(u"@id/foo"), 3u }));
|
||||
|
||||
EXPECT_EQ(1u, std::count(doc->file.exportedSymbols.begin(), doc->file.exportedSymbols.end(),
|
||||
SourcedResourceName{ test::parseNameOrDie(u"@id/bar"), 3u }));
|
||||
|
||||
EXPECT_EQ(1u, std::count(doc->file.exportedSymbols.begin(), doc->file.exportedSymbols.end(),
|
||||
SourcedResourceName{ test::parseNameOrDie(u"@id/car"), 6u }));
|
||||
}
|
||||
|
||||
TEST(XmlIdCollectorTest, DontCollectNonIds) {
|
||||
std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
|
||||
|
||||
std::unique_ptr<XmlResource> doc = test::buildXmlDom("<View foo=\"@+string/foo\"/>");
|
||||
|
||||
XmlIdCollector collector;
|
||||
ASSERT_TRUE(collector.consume(context.get(), doc.get()));
|
||||
|
||||
EXPECT_TRUE(doc->file.exportedSymbols.empty());
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
@@ -2,6 +2,6 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.android.app">
|
||||
<application
|
||||
android:name=".Activity">
|
||||
android:name=".ActivityMain">
|
||||
</application>
|
||||
</manifest>
|
||||
|
||||
@@ -21,63 +21,41 @@ LOCAL_PROGUARD := out/proguard.rule
|
||||
# AAPT2 custom rules.
|
||||
##
|
||||
|
||||
PRIVATE_APK_UNALIGNED := $(LOCAL_OUT)/package-unaligned.apk
|
||||
PRIVATE_APK_ALIGNED := $(LOCAL_OUT)/package.apk
|
||||
PRIVATE_R_FILE := $(LOCAL_GEN)/$(subst .,/,$(LOCAL_PACKAGE))/R.java
|
||||
$(info PRIVATE_R_FILE = $(PRIVATE_R_FILE))
|
||||
|
||||
# Eg: framework.apk, etc.
|
||||
PRIVATE_INCLUDES := $(FRAMEWORK)
|
||||
$(info PRIVATE_INCLUDES = $(PRIVATE_INCLUDES))
|
||||
|
||||
# Eg: gen/com/android/app/R.java
|
||||
PRIVATE_R_JAVA := $(LOCAL_GEN)/$(subst .,/,$(LOCAL_PACKAGE))/R.java
|
||||
$(info PRIVATE_R_JAVA = $(PRIVATE_R_JAVA))
|
||||
|
||||
# Eg: res/drawable/icon.png, res/values/styles.xml
|
||||
PRIVATE_RESOURCES := $(shell find $(LOCAL_RESOURCE_DIR) -mindepth 1 -maxdepth 2 -type f)
|
||||
$(info PRIVATE_RESOURCES = $(PRIVATE_RESOURCES))
|
||||
|
||||
# Eg: drawable, values, layouts
|
||||
PRIVATE_RESOURCE_TYPES := \
|
||||
$(patsubst $(LOCAL_RESOURCE_DIR)/%/,%,$(sort $(dir $(PRIVATE_RESOURCES))))
|
||||
$(info PRIVATE_RESOURCE_TYPES = $(PRIVATE_RESOURCE_TYPES))
|
||||
PRIVATE_RESOURCE_OBJECTS := $(subst /,_,$(patsubst $(LOCAL_RESOURCE_DIR)/%,%,$(filter $(LOCAL_RESOURCE_DIR)/values%,$(PRIVATE_RESOURCES))))
|
||||
PRIVATE_RESOURCE_OBJECTS := $(addprefix $(LOCAL_OUT)/,$(PRIVATE_RESOURCE_OBJECTS:.xml=.arsc.flat))
|
||||
$(info PRIVATE_RESOURCE_OBJECTS = $(PRIVATE_RESOURCE_OBJECTS))
|
||||
|
||||
# Eg: out/values-v4.apk, out/drawable-xhdpi.apk
|
||||
PRIVATE_INTERMEDIATE_TABLES := $(patsubst %,$(LOCAL_OUT)/%.apk,$(PRIVATE_RESOURCE_TYPES))
|
||||
$(info PRIVATE_INTERMEDIATE_TABLES = $(PRIVATE_INTERMEDIATE_TABLES))
|
||||
PRIVATE_FILE_OBJECTS := $(subst /,_,$(patsubst $(LOCAL_RESOURCE_DIR)/%,%,$(filter-out $(LOCAL_RESOURCE_DIR)/values%,$(PRIVATE_RESOURCES))))
|
||||
PRIVATE_FILE_OBJECTS := $(addprefix $(LOCAL_OUT)/,$(addsuffix .flat,$(PRIVATE_FILE_OBJECTS)))
|
||||
$(info PRIVATE_FILE_OBJECTS = $(PRIVATE_FILE_OBJECTS))
|
||||
|
||||
# Generates rules for collect phase.
|
||||
# $1: Resource type (values-v4)
|
||||
# returns: out/values-v4.apk: res/values-v4/styles.xml res/values-v4/colors.xml
|
||||
define make-collect-rule
|
||||
$(LOCAL_OUT)/$1.apk: $(filter $(LOCAL_RESOURCE_DIR)/$1/%,$(PRIVATE_RESOURCES))
|
||||
$(AAPT) compile -o $$@ $$^
|
||||
endef
|
||||
.SECONDEXPANSION:
|
||||
|
||||
# Collect: out/values-v4.apk <- res/values-v4/styles.xml res/values-v4/colors.xml
|
||||
$(foreach d,$(PRIVATE_RESOURCE_TYPES),$(eval $(call make-collect-rule,$d)))
|
||||
$(LOCAL_OUT)/%.arsc.flat: $(LOCAL_RESOURCE_DIR)/$$(subst _,/,%).xml
|
||||
$(AAPT) compile -o $(LOCAL_OUT) $<
|
||||
|
||||
# Link: out/package-unaligned.apk <- out/values-v4.apk out/drawable-v4.apk
|
||||
$(PRIVATE_APK_UNALIGNED): $(PRIVATE_INTERMEDIATE_TABLES) $(PRIVATE_INCLUDES) $(LOCAL_LIBS) AndroidManifest.xml
|
||||
$(AAPT) link --manifest AndroidManifest.xml $(addprefix -I ,$(PRIVATE_INCLUDES)) --java $(LOCAL_GEN) -o $@ $(PRIVATE_INTERMEDIATE_TABLES) $(LOCAL_LIBS) --proguard $(LOCAL_PROGUARD) -v
|
||||
$(LOCAL_OUT)/%.flat: $(LOCAL_RESOURCE_DIR)/$$(subst _,/,%)
|
||||
$(AAPT) compile -o $(LOCAL_OUT) $<
|
||||
|
||||
# R.java: gen/com/android/app/R.java <- out/resources.arsc
|
||||
# No action since R.java is generated when out/resources.arsc is.
|
||||
$(PRIVATE_R_JAVA): $(PRIVATE_APK_UNALIGNED)
|
||||
|
||||
# Assemble: zip out/resources.arsc AndroidManifest.xml and res/**/*
|
||||
$(PRIVATE_APK_ALIGNED): $(PRIVATE_APK_UNALIGNED)
|
||||
$(ZIPALIGN) $< $@
|
||||
$(LOCAL_PROGUARD) $(LOCAL_OUT)/package.apk: AndroidManifest.xml
|
||||
$(PRIVATE_R_FILE) $(LOCAL_PROGUARD) $(LOCAL_OUT)/package.apk: $(PRIVATE_FILE_OBJECTS) $(PRIVATE_RESOURCE_OBJECTS)
|
||||
$(AAPT) link -o $(LOCAL_OUT)/package.apk --manifest AndroidManifest.xml --java $(LOCAL_GEN) --proguard $(LOCAL_PROGUARD) -I $(PRIVATE_INCLUDES) $(filter-out AndroidManifest.xml,$^) -v
|
||||
|
||||
# Create the out directory if needed.
|
||||
dummy := $(shell test -d $(LOCAL_OUT) || mkdir -p $(LOCAL_OUT))
|
||||
|
||||
.PHONY: java
|
||||
java: $(PRIVATE_R_JAVA)
|
||||
|
||||
.PHONY: assemble
|
||||
assemble: $(PRIVATE_APK_ALIGNED)
|
||||
|
||||
.PHONY: all
|
||||
all: assemble java
|
||||
all: $(LOCAL_OUT)/package.apk $(LOCAL_PROGUARD) $(PRIVATE_R_FILE)
|
||||
|
||||
.DEFAULT_GOAL := all
|
||||
|
||||
7
tools/aapt2/data/res/layout-v21/main.xml
Normal file
7
tools/aapt2/data/res/layout-v21/main.xml
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:support="http://schemas.android.com/apk/res/android.appcompat"
|
||||
android:id="@+id/view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
</LinearLayout>
|
||||
@@ -14,8 +14,8 @@
|
||||
android:layout_width="1dp"
|
||||
android:onClick="doClick"
|
||||
android:text="@{user.name}"
|
||||
android:background="#ffffff"
|
||||
android:layout_height="match_parent"
|
||||
app:layout_width="@support:bool/allow"
|
||||
app:flags="complex|weak"
|
||||
android:colorAccent="#ffffff"/>
|
||||
</LinearLayout>
|
||||
|
||||
1
tools/aapt2/data/res/raw/test.txt
Normal file
1
tools/aapt2/data/res/raw/test.txt
Normal file
@@ -0,0 +1 @@
|
||||
Hi
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:lib="http://schemas.android.com/apk/res/android.appcompat">
|
||||
<style name="App" parent="android.appcompat:Platform.AppCompat">
|
||||
<style name="App">
|
||||
<item name="android:background">@color/primary</item>
|
||||
<item name="android:colorPrimary">@color/primary</item>
|
||||
<item name="android:colorPrimaryDark">@color/primary_dark</item>
|
||||
@@ -8,8 +8,8 @@
|
||||
</style>
|
||||
<attr name="custom" format="reference" />
|
||||
<style name="Pop">
|
||||
<item name="custom">@drawable/image</item>
|
||||
<item name="android:focusable">@lib:bool/allow</item>
|
||||
<item name="custom">@android:drawable/btn_default</item>
|
||||
<item name="android:focusable">true</item>
|
||||
</style>
|
||||
<string name="yo">@string/wow</string>
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<string name="hooha"><font bgcolor="#ffffff">Hey guys!</font> <xliff:g>My</xliff:g> name is <b>Adam</b>. How <b><i>are</i></b> you?</string>
|
||||
<public name="hooha" type="string" id="0x7f020001"/>
|
||||
<string name="wow">@android:string/ok</string>
|
||||
<public name="image" type="drawable" id="0x7f060000" />
|
||||
<public name="layout_width" type="attr" />
|
||||
<attr name="layout_width" format="boolean" />
|
||||
<attr name="flags">
|
||||
<flag name="complex" value="1" />
|
||||
|
||||
181
tools/aapt2/flatten/Archive.cpp
Normal file
181
tools/aapt2/flatten/Archive.cpp
Normal file
@@ -0,0 +1,181 @@
|
||||
/*
|
||||
* 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 "flatten/Archive.h"
|
||||
#include "util/Files.h"
|
||||
#include "util/StringPiece.h"
|
||||
|
||||
#include <fstream>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <ziparchive/zip_writer.h>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
namespace {
|
||||
|
||||
struct DirectoryWriter : public IArchiveWriter {
|
||||
std::string mOutDir;
|
||||
std::vector<std::unique_ptr<ArchiveEntry>> mEntries;
|
||||
|
||||
explicit DirectoryWriter(const StringPiece& outDir) : mOutDir(outDir.toString()) {
|
||||
}
|
||||
|
||||
ArchiveEntry* writeEntry(const StringPiece& path, uint32_t flags,
|
||||
const BigBuffer& buffer) override {
|
||||
std::string fullPath = mOutDir;
|
||||
file::appendPath(&fullPath, path);
|
||||
file::mkdirs(file::getStem(fullPath));
|
||||
|
||||
std::ofstream fout(fullPath, std::ofstream::binary);
|
||||
if (!fout) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!util::writeAll(fout, buffer)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
mEntries.push_back(util::make_unique<ArchiveEntry>(fullPath, flags, buffer.size()));
|
||||
return mEntries.back().get();
|
||||
}
|
||||
|
||||
ArchiveEntry* writeEntry(const StringPiece& path, uint32_t flags, android::FileMap* fileMap,
|
||||
size_t offset, size_t len) override {
|
||||
std::string fullPath = mOutDir;
|
||||
file::appendPath(&fullPath, path);
|
||||
file::mkdirs(file::getStem(fullPath));
|
||||
|
||||
std::ofstream fout(fullPath, std::ofstream::binary);
|
||||
if (!fout) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!fout.write((const char*) fileMap->getDataPtr() + offset, len)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
mEntries.push_back(util::make_unique<ArchiveEntry>(fullPath, flags, len));
|
||||
return mEntries.back().get();
|
||||
}
|
||||
|
||||
virtual ~DirectoryWriter() {
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
struct ZipFileWriter : public IArchiveWriter {
|
||||
FILE* mFile;
|
||||
std::unique_ptr<ZipWriter> mWriter;
|
||||
std::vector<std::unique_ptr<ArchiveEntry>> mEntries;
|
||||
|
||||
explicit ZipFileWriter(const StringPiece& path) {
|
||||
mFile = fopen(path.data(), "w+b");
|
||||
if (mFile) {
|
||||
mWriter = util::make_unique<ZipWriter>(mFile);
|
||||
}
|
||||
}
|
||||
|
||||
ArchiveEntry* writeEntry(const StringPiece& path, uint32_t flags,
|
||||
const BigBuffer& buffer) override {
|
||||
if (!mWriter) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
size_t zipFlags = 0;
|
||||
if (flags & ArchiveEntry::kCompress) {
|
||||
zipFlags |= ZipWriter::kCompress;
|
||||
}
|
||||
|
||||
if (flags & ArchiveEntry::kAlign) {
|
||||
zipFlags |= ZipWriter::kAlign32;
|
||||
}
|
||||
|
||||
int32_t result = mWriter->StartEntry(path.data(), zipFlags);
|
||||
if (result != 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
for (const BigBuffer::Block& b : buffer) {
|
||||
result = mWriter->WriteBytes(reinterpret_cast<const uint8_t*>(b.buffer.get()), b.size);
|
||||
if (result != 0) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
result = mWriter->FinishEntry();
|
||||
if (result != 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
mEntries.push_back(util::make_unique<ArchiveEntry>(path.toString(), flags, buffer.size()));
|
||||
return mEntries.back().get();
|
||||
}
|
||||
|
||||
ArchiveEntry* writeEntry(const StringPiece& path, uint32_t flags, android::FileMap* fileMap,
|
||||
size_t offset, size_t len) override {
|
||||
if (!mWriter) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
size_t zipFlags = 0;
|
||||
if (flags & ArchiveEntry::kCompress) {
|
||||
zipFlags |= ZipWriter::kCompress;
|
||||
}
|
||||
|
||||
if (flags & ArchiveEntry::kAlign) {
|
||||
zipFlags |= ZipWriter::kAlign32;
|
||||
}
|
||||
|
||||
int32_t result = mWriter->StartEntry(path.data(), zipFlags);
|
||||
if (result != 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
result = mWriter->WriteBytes((const char*) fileMap->getDataPtr() + offset, len);
|
||||
if (result != 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
result = mWriter->FinishEntry();
|
||||
if (result != 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
mEntries.push_back(util::make_unique<ArchiveEntry>(path.toString(), flags, len));
|
||||
return mEntries.back().get();
|
||||
}
|
||||
|
||||
virtual ~ZipFileWriter() {
|
||||
if (mWriter) {
|
||||
mWriter->Finish();
|
||||
fclose(mFile);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
std::unique_ptr<IArchiveWriter> createDirectoryArchiveWriter(const StringPiece& path) {
|
||||
return util::make_unique<DirectoryWriter>(path);
|
||||
}
|
||||
|
||||
std::unique_ptr<IArchiveWriter> createZipFileArchiveWriter(const StringPiece& path) {
|
||||
return util::make_unique<ZipFileWriter>(path);
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
57
tools/aapt2/flatten/Archive.h
Normal file
57
tools/aapt2/flatten/Archive.h
Normal file
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* 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_FLATTEN_ARCHIVE_H
|
||||
#define AAPT_FLATTEN_ARCHIVE_H
|
||||
|
||||
#include "util/BigBuffer.h"
|
||||
#include "util/Files.h"
|
||||
#include "util/StringPiece.h"
|
||||
|
||||
#include <fstream>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
struct ArchiveEntry {
|
||||
enum : uint32_t {
|
||||
kCompress = 0x01,
|
||||
kAlign = 0x02,
|
||||
};
|
||||
|
||||
std::string path;
|
||||
uint32_t flags;
|
||||
size_t uncompressedSize;
|
||||
};
|
||||
|
||||
struct IArchiveWriter {
|
||||
virtual ~IArchiveWriter() = default;
|
||||
|
||||
virtual ArchiveEntry* writeEntry(const StringPiece& path, uint32_t flags,
|
||||
const BigBuffer& buffer) = 0;
|
||||
virtual ArchiveEntry* writeEntry(const StringPiece& path, uint32_t flags,
|
||||
android::FileMap* fileMap, size_t offset, size_t len) = 0;
|
||||
};
|
||||
|
||||
std::unique_ptr<IArchiveWriter> createDirectoryArchiveWriter(const StringPiece& path);
|
||||
|
||||
std::unique_ptr<IArchiveWriter> createZipFileArchiveWriter(const StringPiece& path);
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
#endif /* AAPT_FLATTEN_ARCHIVE_H */
|
||||
87
tools/aapt2/flatten/ChunkWriter.h
Normal file
87
tools/aapt2/flatten/ChunkWriter.h
Normal file
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* 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_FLATTEN_CHUNKWRITER_H
|
||||
#define AAPT_FLATTEN_CHUNKWRITER_H
|
||||
|
||||
#include "util/BigBuffer.h"
|
||||
#include "util/Util.h"
|
||||
|
||||
#include <androidfw/ResourceTypes.h>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
class ChunkWriter {
|
||||
private:
|
||||
BigBuffer* mBuffer;
|
||||
size_t mStartSize = 0;
|
||||
android::ResChunk_header* mHeader = nullptr;
|
||||
|
||||
public:
|
||||
explicit inline ChunkWriter(BigBuffer* buffer) : mBuffer(buffer) {
|
||||
}
|
||||
|
||||
ChunkWriter(const ChunkWriter&) = delete;
|
||||
ChunkWriter& operator=(const ChunkWriter&) = delete;
|
||||
ChunkWriter(ChunkWriter&&) = default;
|
||||
ChunkWriter& operator=(ChunkWriter&&) = default;
|
||||
|
||||
template <typename T>
|
||||
inline T* startChunk(uint16_t type) {
|
||||
mStartSize = mBuffer->size();
|
||||
T* chunk = mBuffer->nextBlock<T>();
|
||||
mHeader = &chunk->header;
|
||||
mHeader->type = util::hostToDevice16(type);
|
||||
mHeader->headerSize = util::hostToDevice16(sizeof(T));
|
||||
return chunk;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline T* nextBlock(size_t count = 1) {
|
||||
return mBuffer->nextBlock<T>(count);
|
||||
}
|
||||
|
||||
inline BigBuffer* getBuffer() {
|
||||
return mBuffer;
|
||||
}
|
||||
|
||||
inline android::ResChunk_header* getChunkHeader() {
|
||||
return mHeader;
|
||||
}
|
||||
|
||||
inline size_t size() {
|
||||
return mBuffer->size() - mStartSize;
|
||||
}
|
||||
|
||||
inline android::ResChunk_header* finish() {
|
||||
mBuffer->align4();
|
||||
mHeader->size = util::hostToDevice32(mBuffer->size() - mStartSize);
|
||||
return mHeader;
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
inline android::ResChunk_header* ChunkWriter::startChunk(uint16_t type) {
|
||||
mStartSize = mBuffer->size();
|
||||
mHeader = mBuffer->nextBlock<android::ResChunk_header>();
|
||||
mHeader->type = util::hostToDevice16(type);
|
||||
mHeader->headerSize = util::hostToDevice16(sizeof(android::ResChunk_header));
|
||||
return mHeader;
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
#endif /* AAPT_FLATTEN_CHUNKWRITER_H */
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user