24aad163bc
Previously, you could only reference namespace prefixes in attribute names: <View xmlns:appcompat="http://schemas.android.com/apk/res/android.support.v7.appcompat" appcompat:name="hey" ... Now you can also reference them in resource names within an attribute value: ... android:text="@appcompat:string/confirm" ... Which will be treated as "@android.support.v7.appcompat:string/confirm". Change-Id: Ib076e867a990c80cf877a704eb77cd1ef0b23b52
299 lines
9.3 KiB
C++
299 lines
9.3 KiB
C++
/*
|
|
* 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 "Maybe.h"
|
|
#include "StringPiece.h"
|
|
#include "Util.h"
|
|
|
|
#include <algorithm>
|
|
#include <ostream>
|
|
#include <string>
|
|
#include <utils/Unicode.h>
|
|
#include <vector>
|
|
|
|
namespace aapt {
|
|
namespace util {
|
|
|
|
constexpr const char16_t* kSchemaAuto = u"http://schemas.android.com/apk/res-auto";
|
|
constexpr const char16_t* kSchemaPrefix = u"http://schemas.android.com/apk/res/";
|
|
|
|
static std::vector<std::string> splitAndTransform(const StringPiece& str, char sep,
|
|
const std::function<char(char)>& f) {
|
|
std::vector<std::string> parts;
|
|
const StringPiece::const_iterator end = std::end(str);
|
|
StringPiece::const_iterator start = std::begin(str);
|
|
StringPiece::const_iterator current;
|
|
do {
|
|
current = std::find(start, end, sep);
|
|
parts.emplace_back(str.substr(start, current).toString());
|
|
if (f) {
|
|
std::string& part = parts.back();
|
|
std::transform(part.begin(), part.end(), part.begin(), f);
|
|
}
|
|
start = current + 1;
|
|
} while (current != end);
|
|
return parts;
|
|
}
|
|
|
|
std::vector<std::string> split(const StringPiece& str, char sep) {
|
|
return splitAndTransform(str, sep, nullptr);
|
|
}
|
|
|
|
std::vector<std::string> splitAndLowercase(const StringPiece& str, char sep) {
|
|
return splitAndTransform(str, sep, ::tolower);
|
|
}
|
|
|
|
StringPiece16 trimWhitespace(const StringPiece16& str) {
|
|
if (str.size() == 0 || str.data() == nullptr) {
|
|
return str;
|
|
}
|
|
|
|
const char16_t* start = str.data();
|
|
const char16_t* end = str.data() + str.length();
|
|
|
|
while (start != end && util::isspace16(*start)) {
|
|
start++;
|
|
}
|
|
|
|
while (end != start && util::isspace16(*(end - 1))) {
|
|
end--;
|
|
}
|
|
|
|
return StringPiece16(start, end - start);
|
|
}
|
|
|
|
StringPiece16::const_iterator findNonAlphaNumericAndNotInSet(const StringPiece16& str,
|
|
const StringPiece16& allowedChars) {
|
|
const auto endIter = str.end();
|
|
for (auto iter = str.begin(); iter != endIter; ++iter) {
|
|
char16_t c = *iter;
|
|
if ((c >= u'a' && c <= u'z') ||
|
|
(c >= u'A' && c <= u'Z') ||
|
|
(c >= u'0' && c <= u'9')) {
|
|
continue;
|
|
}
|
|
|
|
bool match = false;
|
|
for (char16_t i : allowedChars) {
|
|
if (c == i) {
|
|
match = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!match) {
|
|
return iter;
|
|
}
|
|
}
|
|
return endIter;
|
|
}
|
|
|
|
static Maybe<char16_t> parseUnicodeCodepoint(const char16_t** start, const char16_t* end) {
|
|
char16_t code = 0;
|
|
for (size_t i = 0; i < 4 && *start != end; i++, (*start)++) {
|
|
char16_t c = **start;
|
|
int a;
|
|
if (c >= '0' && c <= '9') {
|
|
a = c - '0';
|
|
} else if (c >= 'a' && c <= 'f') {
|
|
a = c - 'a' + 10;
|
|
} else if (c >= 'A' && c <= 'F') {
|
|
a = c - 'A' + 10;
|
|
} else {
|
|
return make_nothing<char16_t>();
|
|
}
|
|
code = (code << 4) | a;
|
|
}
|
|
return make_value(code);
|
|
}
|
|
|
|
StringBuilder& StringBuilder::append(const StringPiece16& str) {
|
|
if (!mError.empty()) {
|
|
return *this;
|
|
}
|
|
|
|
const char16_t* const end = str.end();
|
|
const char16_t* start = str.begin();
|
|
const char16_t* current = start;
|
|
while (current != end) {
|
|
if (*current == u'"') {
|
|
if (!mQuote && mTrailingSpace) {
|
|
// We found an opening quote, and we have
|
|
// trailing space, so we should append that
|
|
// space now.
|
|
if (mTrailingSpace) {
|
|
// We had trailing whitespace, so
|
|
// replace with a single space.
|
|
if (!mStr.empty()) {
|
|
mStr += u' ';
|
|
}
|
|
mTrailingSpace = false;
|
|
}
|
|
}
|
|
mQuote = !mQuote;
|
|
mStr.append(start, current - start);
|
|
start = current + 1;
|
|
} else if (*current == u'\'' && !mQuote) {
|
|
// This should be escaped.
|
|
mError = "unescaped apostrophe";
|
|
return *this;
|
|
} else if (*current == u'\\') {
|
|
// This is an escape sequence, convert to the real value.
|
|
if (!mQuote && mTrailingSpace) {
|
|
// We had trailing whitespace, so
|
|
// replace with a single space.
|
|
if (!mStr.empty()) {
|
|
mStr += u' ';
|
|
}
|
|
mTrailingSpace = false;
|
|
}
|
|
mStr.append(start, current - start);
|
|
start = current + 1;
|
|
|
|
current++;
|
|
if (current != end) {
|
|
switch (*current) {
|
|
case u't':
|
|
mStr += u'\t';
|
|
break;
|
|
case u'n':
|
|
mStr += u'\n';
|
|
break;
|
|
case u'#':
|
|
mStr += u'#';
|
|
break;
|
|
case u'@':
|
|
mStr += u'@';
|
|
break;
|
|
case u'?':
|
|
mStr += u'?';
|
|
break;
|
|
case u'"':
|
|
mStr += u'"';
|
|
break;
|
|
case u'\'':
|
|
mStr += u'\'';
|
|
break;
|
|
case u'\\':
|
|
mStr += u'\\';
|
|
break;
|
|
case u'u': {
|
|
current++;
|
|
Maybe<char16_t> c = parseUnicodeCodepoint(¤t, end);
|
|
if (!c) {
|
|
mError = "invalid unicode escape sequence";
|
|
return *this;
|
|
}
|
|
mStr += c.value();
|
|
current -= 1;
|
|
break;
|
|
}
|
|
|
|
default:
|
|
// Ignore.
|
|
break;
|
|
}
|
|
start = current + 1;
|
|
}
|
|
} else if (!mQuote) {
|
|
// This is not quoted text, so look for whitespace.
|
|
if (isspace16(*current)) {
|
|
// We found whitespace, see if we have seen some
|
|
// before.
|
|
if (!mTrailingSpace) {
|
|
// We didn't see a previous adjacent space,
|
|
// so mark that we did.
|
|
mTrailingSpace = true;
|
|
mStr.append(start, current - start);
|
|
}
|
|
|
|
// Keep skipping whitespace.
|
|
start = current + 1;
|
|
} else if (mTrailingSpace) {
|
|
// We saw trailing space before, so replace all
|
|
// that trailing space with one space.
|
|
if (!mStr.empty()) {
|
|
mStr += u' ';
|
|
}
|
|
mTrailingSpace = false;
|
|
}
|
|
}
|
|
current++;
|
|
}
|
|
mStr.append(start, end - start);
|
|
return *this;
|
|
}
|
|
|
|
std::u16string utf8ToUtf16(const StringPiece& utf8) {
|
|
ssize_t utf16Length = utf8_to_utf16_length(reinterpret_cast<const uint8_t*>(utf8.data()),
|
|
utf8.length());
|
|
if (utf16Length <= 0) {
|
|
return {};
|
|
}
|
|
|
|
std::u16string utf16;
|
|
utf16.resize(utf16Length);
|
|
utf8_to_utf16(reinterpret_cast<const uint8_t*>(utf8.data()), utf8.length(), &*utf16.begin());
|
|
return utf16;
|
|
}
|
|
|
|
std::string utf16ToUtf8(const StringPiece16& utf16) {
|
|
ssize_t utf8Length = utf16_to_utf8_length(utf16.data(), utf16.length());
|
|
if (utf8Length <= 0) {
|
|
return {};
|
|
}
|
|
|
|
std::string utf8;
|
|
utf8.resize(utf8Length);
|
|
utf16_to_utf8(utf16.data(), utf16.length(), &*utf8.begin());
|
|
return utf8;
|
|
}
|
|
|
|
bool writeAll(std::ostream& out, const BigBuffer& buffer) {
|
|
for (const auto& b : buffer) {
|
|
if (!out.write(reinterpret_cast<const char*>(b.buffer.get()), b.size)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
std::unique_ptr<uint8_t[]> copy(const BigBuffer& buffer) {
|
|
std::unique_ptr<uint8_t[]> data = std::unique_ptr<uint8_t[]>(new uint8_t[buffer.size()]);
|
|
uint8_t* p = data.get();
|
|
for (const auto& block : buffer) {
|
|
memcpy(p, block.buffer.get(), block.size);
|
|
p += block.size;
|
|
}
|
|
return data;
|
|
}
|
|
|
|
Maybe<std::u16string> extractPackageFromNamespace(const std::u16string& namespaceUri) {
|
|
if (stringStartsWith<char16_t>(namespaceUri, kSchemaPrefix)) {
|
|
StringPiece16 schemaPrefix = kSchemaPrefix;
|
|
StringPiece16 package = namespaceUri;
|
|
return package.substr(schemaPrefix.size(), package.size() - schemaPrefix.size())
|
|
.toString();
|
|
} else if (namespaceUri == kSchemaAuto) {
|
|
return std::u16string();
|
|
}
|
|
return {};
|
|
}
|
|
|
|
} // namespace util
|
|
} // namespace aapt
|