blob: 3eedda88fdced00327bbb8a710f626039c1ddb2d [file] [log] [blame]
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
Mårten Kongstade0930d32018-10-18 14:50:15 +020017#include "android-base/macros.h"
Mårten Kongstad24c9aa62018-06-20 08:46:41 +020018#include "androidfw/Locale.h"
19#include "androidfw/Util.h"
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080020
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080021#include <ctype.h>
Adam Lesinskice5e56e2016-10-21 17:56:45 -070022
Adam Lesinskicacb28f2016-10-19 12:18:14 -070023#include <algorithm>
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080024#include <string>
25#include <vector>
26
Adam Lesinskib58c3ef2017-09-12 17:39:52 -070027using ::android::ResTable_config;
28using ::android::StringPiece;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080029
Mårten Kongstad24c9aa62018-06-20 08:46:41 +020030namespace android {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080031
Adam Lesinskice5e56e2016-10-21 17:56:45 -070032void LocaleValue::set_language(const char* language_chars) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -070033 size_t i = 0;
Adam Lesinskice5e56e2016-10-21 17:56:45 -070034 while ((*language_chars) != '\0') {
35 language[i++] = ::tolower(*language_chars);
36 language_chars++;
Adam Lesinskicacb28f2016-10-19 12:18:14 -070037 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080038}
39
Adam Lesinskice5e56e2016-10-21 17:56:45 -070040void LocaleValue::set_region(const char* region_chars) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -070041 size_t i = 0;
Adam Lesinskice5e56e2016-10-21 17:56:45 -070042 while ((*region_chars) != '\0') {
43 region[i++] = ::toupper(*region_chars);
44 region_chars++;
Adam Lesinskicacb28f2016-10-19 12:18:14 -070045 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080046}
47
Adam Lesinskice5e56e2016-10-21 17:56:45 -070048void LocaleValue::set_script(const char* script_chars) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -070049 size_t i = 0;
Adam Lesinskice5e56e2016-10-21 17:56:45 -070050 while ((*script_chars) != '\0') {
Adam Lesinskicacb28f2016-10-19 12:18:14 -070051 if (i == 0) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -070052 script[i++] = ::toupper(*script_chars);
Adam Lesinskicacb28f2016-10-19 12:18:14 -070053 } else {
Adam Lesinskice5e56e2016-10-21 17:56:45 -070054 script[i++] = ::tolower(*script_chars);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080055 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -070056 script_chars++;
Adam Lesinskicacb28f2016-10-19 12:18:14 -070057 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080058}
59
Adam Lesinskice5e56e2016-10-21 17:56:45 -070060void LocaleValue::set_variant(const char* variant_chars) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -070061 size_t i = 0;
Adam Lesinskice5e56e2016-10-21 17:56:45 -070062 while ((*variant_chars) != '\0') {
63 variant[i++] = *variant_chars;
64 variant_chars++;
Adam Lesinskicacb28f2016-10-19 12:18:14 -070065 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080066}
67
Adam Lesinskice5e56e2016-10-21 17:56:45 -070068static inline bool is_alpha(const std::string& str) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -070069 return std::all_of(std::begin(str), std::end(str), ::isalpha);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080070}
71
Adam Lesinskice5e56e2016-10-21 17:56:45 -070072static inline bool is_number(const std::string& str) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -070073 return std::all_of(std::begin(str), std::end(str), ::isdigit);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080074}
75
Adam Lesinskib58c3ef2017-09-12 17:39:52 -070076bool LocaleValue::InitFromFilterString(const StringPiece& str) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -070077 // A locale (as specified in the filter) is an underscore separated name such
78 // as "en_US", "en_Latn_US", or "en_US_POSIX".
Adam Lesinskice5e56e2016-10-21 17:56:45 -070079 std::vector<std::string> parts = util::SplitAndLowercase(str, '_');
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080080
Adam Lesinskice5e56e2016-10-21 17:56:45 -070081 const int num_tags = parts.size();
Adam Lesinskicacb28f2016-10-19 12:18:14 -070082 bool valid = false;
Adam Lesinskice5e56e2016-10-21 17:56:45 -070083 if (num_tags >= 1) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -070084 const std::string& lang = parts[0];
Adam Lesinskice5e56e2016-10-21 17:56:45 -070085 if (is_alpha(lang) && (lang.length() == 2 || lang.length() == 3)) {
86 set_language(lang.c_str());
Adam Lesinskicacb28f2016-10-19 12:18:14 -070087 valid = true;
88 }
89 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080090
Adam Lesinskice5e56e2016-10-21 17:56:45 -070091 if (!valid || num_tags == 1) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -070092 return valid;
93 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080094
Adam Lesinskicacb28f2016-10-19 12:18:14 -070095 // At this point, valid == true && numTags > 1.
96 const std::string& part2 = parts[1];
Adam Lesinskice5e56e2016-10-21 17:56:45 -070097 if ((part2.length() == 2 && is_alpha(part2)) ||
98 (part2.length() == 3 && is_number(part2))) {
99 set_region(part2.c_str());
100 } else if (part2.length() == 4 && is_alpha(part2)) {
101 set_script(part2.c_str());
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700102 } else if (part2.length() >= 4 && part2.length() <= 8) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700103 set_variant(part2.c_str());
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700104 } else {
105 valid = false;
106 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800107
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700108 if (!valid || num_tags == 2) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700109 return valid;
110 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800111
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700112 // At this point, valid == true && numTags > 1.
113 const std::string& part3 = parts[2];
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700114 if (((part3.length() == 2 && is_alpha(part3)) ||
115 (part3.length() == 3 && is_number(part3))) &&
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700116 script[0]) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700117 set_region(part3.c_str());
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700118 } else if (part3.length() >= 4 && part3.length() <= 8) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700119 set_variant(part3.c_str());
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700120 } else {
121 valid = false;
122 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800123
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700124 if (!valid || num_tags == 3) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700125 return valid;
126 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800127
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700128 const std::string& part4 = parts[3];
129 if (part4.length() >= 4 && part4.length() <= 8) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700130 set_variant(part4.c_str());
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700131 } else {
132 valid = false;
133 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800134
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700135 if (!valid || num_tags > 4) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700136 return false;
137 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800138
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700139 return true;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800140}
141
Adam Lesinskib58c3ef2017-09-12 17:39:52 -0700142bool LocaleValue::InitFromBcp47Tag(const StringPiece& bcp47tag) {
143 return InitFromBcp47TagImpl(bcp47tag, '-');
144}
145
146bool LocaleValue::InitFromBcp47TagImpl(const StringPiece& bcp47tag, const char separator) {
147 std::vector<std::string> subtags = util::SplitAndLowercase(bcp47tag, separator);
148 if (subtags.size() == 1) {
149 set_language(subtags[0].c_str());
150 } else if (subtags.size() == 2) {
151 set_language(subtags[0].c_str());
152
153 // The second tag can either be a region, a variant or a script.
154 switch (subtags[1].size()) {
155 case 2:
156 case 3:
157 set_region(subtags[1].c_str());
158 break;
159 case 4:
160 if ('0' <= subtags[1][0] && subtags[1][0] <= '9') {
161 // This is a variant: fall through
162 } else {
163 set_script(subtags[1].c_str());
164 break;
165 }
Mårten Kongstade0930d32018-10-18 14:50:15 +0200166 FALLTHROUGH_INTENDED;
Adam Lesinskib58c3ef2017-09-12 17:39:52 -0700167 case 5:
168 case 6:
169 case 7:
170 case 8:
171 set_variant(subtags[1].c_str());
172 break;
173 default:
174 return false;
175 }
176 } else if (subtags.size() == 3) {
177 // The language is always the first subtag.
178 set_language(subtags[0].c_str());
179
180 // The second subtag can either be a script or a region code.
181 // If its size is 4, it's a script code, else it's a region code.
182 if (subtags[1].size() == 4) {
183 set_script(subtags[1].c_str());
184 } else if (subtags[1].size() == 2 || subtags[1].size() == 3) {
185 set_region(subtags[1].c_str());
186 } else {
187 return false;
188 }
189
190 // The third tag can either be a region code (if the second tag was
191 // a script), else a variant code.
192 if (subtags[2].size() >= 4) {
193 set_variant(subtags[2].c_str());
194 } else {
195 set_region(subtags[2].c_str());
196 }
197 } else if (subtags.size() == 4) {
198 set_language(subtags[0].c_str());
199 set_script(subtags[1].c_str());
200 set_region(subtags[2].c_str());
201 set_variant(subtags[3].c_str());
202 } else {
203 return false;
204 }
205 return true;
206}
207
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700208ssize_t LocaleValue::InitFromParts(std::vector<std::string>::iterator iter,
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700209 std::vector<std::string>::iterator end) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700210 const std::vector<std::string>::iterator start_iter = iter;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800211
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700212 std::string& part = *iter;
213 if (part[0] == 'b' && part[1] == '+') {
214 // This is a "modified" BCP 47 language tag. Same semantics as BCP 47 tags,
Adam Lesinskib58c3ef2017-09-12 17:39:52 -0700215 // except that the separator is "+" and not "-". Skip the prefix 'b+'.
216 if (!InitFromBcp47TagImpl(StringPiece(part).substr(2), '+')) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700217 return -1;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800218 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700219 ++iter;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700220 } else {
Adam Lesinskib58c3ef2017-09-12 17:39:52 -0700221 if ((part.length() == 2 || part.length() == 3) && is_alpha(part) && part != "car") {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700222 set_language(part.c_str());
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700223 ++iter;
224
225 if (iter != end) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700226 const std::string& region_part = *iter;
227 if (region_part.c_str()[0] == 'r' && region_part.length() == 3) {
228 set_region(region_part.c_str() + 1);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700229 ++iter;
230 }
231 }
232 }
233 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700234 return static_cast<ssize_t>(iter - start_iter);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800235}
236
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700237void LocaleValue::InitFromResTable(const ResTable_config& config) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700238 config.unpackLanguage(language);
239 config.unpackRegion(region);
240 if (config.localeScript[0] && !config.localeScriptWasComputed) {
241 memcpy(script, config.localeScript, sizeof(config.localeScript));
242 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800243
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700244 if (config.localeVariant[0]) {
245 memcpy(variant, config.localeVariant, sizeof(config.localeVariant));
246 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800247}
248
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700249void LocaleValue::WriteTo(ResTable_config* out) const {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700250 out->packLanguage(language);
251 out->packRegion(region);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800252
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700253 if (script[0]) {
254 memcpy(out->localeScript, script, sizeof(out->localeScript));
255 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800256
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700257 if (variant[0]) {
258 memcpy(out->localeVariant, variant, sizeof(out->localeVariant));
259 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800260}
261
Mårten Kongstad24c9aa62018-06-20 08:46:41 +0200262} // namespace android