2014-11-14 14:48:12 -08:00
|
|
|
/*
|
|
|
|
* 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.
|
|
|
|
*/
|
|
|
|
|
2018-10-18 14:50:15 +02:00
|
|
|
#include "android-base/macros.h"
|
2018-06-20 08:46:41 +02:00
|
|
|
#include "androidfw/Locale.h"
|
|
|
|
#include "androidfw/Util.h"
|
2014-11-14 14:48:12 -08:00
|
|
|
|
|
|
|
#include <ctype.h>
|
2016-10-21 17:56:45 -07:00
|
|
|
|
2016-10-19 12:18:14 -07:00
|
|
|
#include <algorithm>
|
2014-11-14 14:48:12 -08:00
|
|
|
#include <string>
|
|
|
|
#include <vector>
|
|
|
|
|
2017-09-12 17:39:52 -07:00
|
|
|
using ::android::ResTable_config;
|
|
|
|
using ::android::StringPiece;
|
2014-11-14 14:48:12 -08:00
|
|
|
|
2018-06-20 08:46:41 +02:00
|
|
|
namespace android {
|
2014-11-14 14:48:12 -08:00
|
|
|
|
2016-10-21 17:56:45 -07:00
|
|
|
void LocaleValue::set_language(const char* language_chars) {
|
2016-10-19 12:18:14 -07:00
|
|
|
size_t i = 0;
|
2016-10-21 17:56:45 -07:00
|
|
|
while ((*language_chars) != '\0') {
|
|
|
|
language[i++] = ::tolower(*language_chars);
|
|
|
|
language_chars++;
|
2016-10-19 12:18:14 -07:00
|
|
|
}
|
2014-11-14 14:48:12 -08:00
|
|
|
}
|
|
|
|
|
2016-10-21 17:56:45 -07:00
|
|
|
void LocaleValue::set_region(const char* region_chars) {
|
2016-10-19 12:18:14 -07:00
|
|
|
size_t i = 0;
|
2016-10-21 17:56:45 -07:00
|
|
|
while ((*region_chars) != '\0') {
|
|
|
|
region[i++] = ::toupper(*region_chars);
|
|
|
|
region_chars++;
|
2016-10-19 12:18:14 -07:00
|
|
|
}
|
2014-11-14 14:48:12 -08:00
|
|
|
}
|
|
|
|
|
2016-10-21 17:56:45 -07:00
|
|
|
void LocaleValue::set_script(const char* script_chars) {
|
2016-10-19 12:18:14 -07:00
|
|
|
size_t i = 0;
|
2016-10-21 17:56:45 -07:00
|
|
|
while ((*script_chars) != '\0') {
|
2016-10-19 12:18:14 -07:00
|
|
|
if (i == 0) {
|
2016-10-21 17:56:45 -07:00
|
|
|
script[i++] = ::toupper(*script_chars);
|
2016-10-19 12:18:14 -07:00
|
|
|
} else {
|
2016-10-21 17:56:45 -07:00
|
|
|
script[i++] = ::tolower(*script_chars);
|
2014-11-14 14:48:12 -08:00
|
|
|
}
|
2016-10-21 17:56:45 -07:00
|
|
|
script_chars++;
|
2016-10-19 12:18:14 -07:00
|
|
|
}
|
2014-11-14 14:48:12 -08:00
|
|
|
}
|
|
|
|
|
2016-10-21 17:56:45 -07:00
|
|
|
void LocaleValue::set_variant(const char* variant_chars) {
|
2016-10-19 12:18:14 -07:00
|
|
|
size_t i = 0;
|
2016-10-21 17:56:45 -07:00
|
|
|
while ((*variant_chars) != '\0') {
|
|
|
|
variant[i++] = *variant_chars;
|
|
|
|
variant_chars++;
|
2016-10-19 12:18:14 -07:00
|
|
|
}
|
2014-11-14 14:48:12 -08:00
|
|
|
}
|
|
|
|
|
2016-10-21 17:56:45 -07:00
|
|
|
static inline bool is_alpha(const std::string& str) {
|
2016-10-19 12:18:14 -07:00
|
|
|
return std::all_of(std::begin(str), std::end(str), ::isalpha);
|
2014-11-14 14:48:12 -08:00
|
|
|
}
|
|
|
|
|
2016-10-21 17:56:45 -07:00
|
|
|
static inline bool is_number(const std::string& str) {
|
2016-10-19 12:18:14 -07:00
|
|
|
return std::all_of(std::begin(str), std::end(str), ::isdigit);
|
2014-11-14 14:48:12 -08:00
|
|
|
}
|
|
|
|
|
2017-09-12 17:39:52 -07:00
|
|
|
bool LocaleValue::InitFromFilterString(const StringPiece& str) {
|
2016-10-19 12:18:14 -07:00
|
|
|
// A locale (as specified in the filter) is an underscore separated name such
|
|
|
|
// as "en_US", "en_Latn_US", or "en_US_POSIX".
|
2016-10-21 17:56:45 -07:00
|
|
|
std::vector<std::string> parts = util::SplitAndLowercase(str, '_');
|
2016-10-19 12:18:14 -07:00
|
|
|
|
2016-10-21 17:56:45 -07:00
|
|
|
const int num_tags = parts.size();
|
2016-10-19 12:18:14 -07:00
|
|
|
bool valid = false;
|
2016-10-21 17:56:45 -07:00
|
|
|
if (num_tags >= 1) {
|
2016-10-19 12:18:14 -07:00
|
|
|
const std::string& lang = parts[0];
|
2016-10-21 17:56:45 -07:00
|
|
|
if (is_alpha(lang) && (lang.length() == 2 || lang.length() == 3)) {
|
|
|
|
set_language(lang.c_str());
|
2016-10-19 12:18:14 -07:00
|
|
|
valid = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-10-21 17:56:45 -07:00
|
|
|
if (!valid || num_tags == 1) {
|
2016-10-19 12:18:14 -07:00
|
|
|
return valid;
|
|
|
|
}
|
|
|
|
|
|
|
|
// At this point, valid == true && numTags > 1.
|
|
|
|
const std::string& part2 = parts[1];
|
2016-10-21 17:56:45 -07:00
|
|
|
if ((part2.length() == 2 && is_alpha(part2)) ||
|
|
|
|
(part2.length() == 3 && is_number(part2))) {
|
|
|
|
set_region(part2.c_str());
|
|
|
|
} else if (part2.length() == 4 && is_alpha(part2)) {
|
|
|
|
set_script(part2.c_str());
|
2016-10-19 12:18:14 -07:00
|
|
|
} else if (part2.length() >= 4 && part2.length() <= 8) {
|
2016-10-21 17:56:45 -07:00
|
|
|
set_variant(part2.c_str());
|
2016-10-19 12:18:14 -07:00
|
|
|
} else {
|
|
|
|
valid = false;
|
|
|
|
}
|
|
|
|
|
2016-10-21 17:56:45 -07:00
|
|
|
if (!valid || num_tags == 2) {
|
2016-10-19 12:18:14 -07:00
|
|
|
return valid;
|
|
|
|
}
|
|
|
|
|
|
|
|
// At this point, valid == true && numTags > 1.
|
|
|
|
const std::string& part3 = parts[2];
|
2016-10-21 17:56:45 -07:00
|
|
|
if (((part3.length() == 2 && is_alpha(part3)) ||
|
|
|
|
(part3.length() == 3 && is_number(part3))) &&
|
2016-10-19 12:18:14 -07:00
|
|
|
script[0]) {
|
2016-10-21 17:56:45 -07:00
|
|
|
set_region(part3.c_str());
|
2016-10-19 12:18:14 -07:00
|
|
|
} else if (part3.length() >= 4 && part3.length() <= 8) {
|
2016-10-21 17:56:45 -07:00
|
|
|
set_variant(part3.c_str());
|
2016-10-19 12:18:14 -07:00
|
|
|
} else {
|
|
|
|
valid = false;
|
|
|
|
}
|
|
|
|
|
2016-10-21 17:56:45 -07:00
|
|
|
if (!valid || num_tags == 3) {
|
2016-10-19 12:18:14 -07:00
|
|
|
return valid;
|
|
|
|
}
|
|
|
|
|
|
|
|
const std::string& part4 = parts[3];
|
|
|
|
if (part4.length() >= 4 && part4.length() <= 8) {
|
2016-10-21 17:56:45 -07:00
|
|
|
set_variant(part4.c_str());
|
2016-10-19 12:18:14 -07:00
|
|
|
} else {
|
|
|
|
valid = false;
|
|
|
|
}
|
|
|
|
|
2016-10-21 17:56:45 -07:00
|
|
|
if (!valid || num_tags > 4) {
|
2016-10-19 12:18:14 -07:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
2014-11-14 14:48:12 -08:00
|
|
|
}
|
|
|
|
|
2017-09-12 17:39:52 -07:00
|
|
|
bool LocaleValue::InitFromBcp47Tag(const StringPiece& bcp47tag) {
|
|
|
|
return InitFromBcp47TagImpl(bcp47tag, '-');
|
|
|
|
}
|
|
|
|
|
|
|
|
bool LocaleValue::InitFromBcp47TagImpl(const StringPiece& bcp47tag, const char separator) {
|
|
|
|
std::vector<std::string> subtags = util::SplitAndLowercase(bcp47tag, separator);
|
|
|
|
if (subtags.size() == 1) {
|
|
|
|
set_language(subtags[0].c_str());
|
|
|
|
} else if (subtags.size() == 2) {
|
|
|
|
set_language(subtags[0].c_str());
|
|
|
|
|
|
|
|
// The second tag can either be a region, a variant or a script.
|
|
|
|
switch (subtags[1].size()) {
|
|
|
|
case 2:
|
|
|
|
case 3:
|
|
|
|
set_region(subtags[1].c_str());
|
|
|
|
break;
|
|
|
|
case 4:
|
|
|
|
if ('0' <= subtags[1][0] && subtags[1][0] <= '9') {
|
|
|
|
// This is a variant: fall through
|
|
|
|
} else {
|
|
|
|
set_script(subtags[1].c_str());
|
|
|
|
break;
|
|
|
|
}
|
2018-10-18 14:50:15 +02:00
|
|
|
FALLTHROUGH_INTENDED;
|
2017-09-12 17:39:52 -07:00
|
|
|
case 5:
|
|
|
|
case 6:
|
|
|
|
case 7:
|
|
|
|
case 8:
|
|
|
|
set_variant(subtags[1].c_str());
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
} else if (subtags.size() == 3) {
|
|
|
|
// The language is always the first subtag.
|
|
|
|
set_language(subtags[0].c_str());
|
|
|
|
|
|
|
|
// The second subtag can either be a script or a region code.
|
|
|
|
// If its size is 4, it's a script code, else it's a region code.
|
|
|
|
if (subtags[1].size() == 4) {
|
|
|
|
set_script(subtags[1].c_str());
|
|
|
|
} else if (subtags[1].size() == 2 || subtags[1].size() == 3) {
|
|
|
|
set_region(subtags[1].c_str());
|
|
|
|
} else {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// The third tag can either be a region code (if the second tag was
|
|
|
|
// a script), else a variant code.
|
|
|
|
if (subtags[2].size() >= 4) {
|
|
|
|
set_variant(subtags[2].c_str());
|
|
|
|
} else {
|
|
|
|
set_region(subtags[2].c_str());
|
|
|
|
}
|
|
|
|
} else if (subtags.size() == 4) {
|
|
|
|
set_language(subtags[0].c_str());
|
|
|
|
set_script(subtags[1].c_str());
|
|
|
|
set_region(subtags[2].c_str());
|
|
|
|
set_variant(subtags[3].c_str());
|
|
|
|
} else {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2016-10-21 17:56:45 -07:00
|
|
|
ssize_t LocaleValue::InitFromParts(std::vector<std::string>::iterator iter,
|
2016-10-19 12:18:14 -07:00
|
|
|
std::vector<std::string>::iterator end) {
|
2016-10-21 17:56:45 -07:00
|
|
|
const std::vector<std::string>::iterator start_iter = iter;
|
2016-10-19 12:18:14 -07:00
|
|
|
|
|
|
|
std::string& part = *iter;
|
|
|
|
if (part[0] == 'b' && part[1] == '+') {
|
|
|
|
// This is a "modified" BCP 47 language tag. Same semantics as BCP 47 tags,
|
2017-09-12 17:39:52 -07:00
|
|
|
// except that the separator is "+" and not "-". Skip the prefix 'b+'.
|
|
|
|
if (!InitFromBcp47TagImpl(StringPiece(part).substr(2), '+')) {
|
2016-10-19 12:18:14 -07:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
++iter;
|
|
|
|
} else {
|
2017-09-12 17:39:52 -07:00
|
|
|
if ((part.length() == 2 || part.length() == 3) && is_alpha(part) && part != "car") {
|
2016-10-21 17:56:45 -07:00
|
|
|
set_language(part.c_str());
|
2016-10-19 12:18:14 -07:00
|
|
|
++iter;
|
|
|
|
|
|
|
|
if (iter != end) {
|
2016-10-21 17:56:45 -07:00
|
|
|
const std::string& region_part = *iter;
|
|
|
|
if (region_part.c_str()[0] == 'r' && region_part.length() == 3) {
|
|
|
|
set_region(region_part.c_str() + 1);
|
2016-10-19 12:18:14 -07:00
|
|
|
++iter;
|
2014-11-14 14:48:12 -08:00
|
|
|
}
|
2016-10-19 12:18:14 -07:00
|
|
|
}
|
2014-11-14 14:48:12 -08:00
|
|
|
}
|
2016-10-19 12:18:14 -07:00
|
|
|
}
|
2016-10-21 17:56:45 -07:00
|
|
|
return static_cast<ssize_t>(iter - start_iter);
|
2014-11-14 14:48:12 -08:00
|
|
|
}
|
|
|
|
|
2016-10-21 17:56:45 -07:00
|
|
|
void LocaleValue::InitFromResTable(const ResTable_config& config) {
|
2016-10-19 12:18:14 -07:00
|
|
|
config.unpackLanguage(language);
|
|
|
|
config.unpackRegion(region);
|
|
|
|
if (config.localeScript[0] && !config.localeScriptWasComputed) {
|
|
|
|
memcpy(script, config.localeScript, sizeof(config.localeScript));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (config.localeVariant[0]) {
|
|
|
|
memcpy(variant, config.localeVariant, sizeof(config.localeVariant));
|
|
|
|
}
|
2014-11-14 14:48:12 -08:00
|
|
|
}
|
|
|
|
|
2016-10-21 17:56:45 -07:00
|
|
|
void LocaleValue::WriteTo(ResTable_config* out) const {
|
2016-10-19 12:18:14 -07:00
|
|
|
out->packLanguage(language);
|
|
|
|
out->packRegion(region);
|
2014-11-14 14:48:12 -08:00
|
|
|
|
2016-10-19 12:18:14 -07:00
|
|
|
if (script[0]) {
|
|
|
|
memcpy(out->localeScript, script, sizeof(out->localeScript));
|
|
|
|
}
|
2014-11-14 14:48:12 -08:00
|
|
|
|
2016-10-19 12:18:14 -07:00
|
|
|
if (variant[0]) {
|
|
|
|
memcpy(out->localeVariant, variant, sizeof(out->localeVariant));
|
|
|
|
}
|
2014-11-14 14:48:12 -08:00
|
|
|
}
|
|
|
|
|
2018-06-20 08:46:41 +02:00
|
|
|
} // namespace android
|