blob: 272a988ec55a39320f8bc38a64b7c41da3396d15 [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
Yurii Zubrytskyid3752602021-12-01 09:11:46 -080032template <size_t N, class Transformer>
33static void safe_transform_copy(const char* source, char (&dest)[N], Transformer t) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -070034 size_t i = 0;
Yurii Zubrytskyid3752602021-12-01 09:11:46 -080035 while (i < N && (*source) != '\0') {
36 dest[i++] = t(i, *source);
37 source++;
Adam Lesinskicacb28f2016-10-19 12:18:14 -070038 }
Yurii Zubrytskyid3752602021-12-01 09:11:46 -080039 while (i < N) {
40 dest[i++] = '\0';
41 }
42}
43
44void LocaleValue::set_language(const char* language_chars) {
45 safe_transform_copy(language_chars, language, [](size_t, char c) { return ::tolower(c); });
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080046}
47
Adam Lesinskice5e56e2016-10-21 17:56:45 -070048void LocaleValue::set_region(const char* region_chars) {
Yurii Zubrytskyid3752602021-12-01 09:11:46 -080049 safe_transform_copy(region_chars, region, [](size_t, char c) { return ::toupper(c); });
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080050}
51
Adam Lesinskice5e56e2016-10-21 17:56:45 -070052void LocaleValue::set_script(const char* script_chars) {
Yurii Zubrytskyid3752602021-12-01 09:11:46 -080053 safe_transform_copy(script_chars, script,
54 [](size_t i, char c) { return i ? ::tolower(c) : ::toupper(c); });
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080055}
56
Adam Lesinskice5e56e2016-10-21 17:56:45 -070057void LocaleValue::set_variant(const char* variant_chars) {
Yurii Zubrytskyid3752602021-12-01 09:11:46 -080058 safe_transform_copy(variant_chars, variant, [](size_t, char c) { return c; });
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080059}
60
Adam Lesinskice5e56e2016-10-21 17:56:45 -070061static inline bool is_alpha(const std::string& str) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -070062 return std::all_of(std::begin(str), std::end(str), ::isalpha);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080063}
64
Adam Lesinskice5e56e2016-10-21 17:56:45 -070065static inline bool is_number(const std::string& str) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -070066 return std::all_of(std::begin(str), std::end(str), ::isdigit);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080067}
68
Yurii Zubrytskyia5775142022-11-02 17:49:49 -070069bool LocaleValue::InitFromFilterString(StringPiece str) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -070070 // A locale (as specified in the filter) is an underscore separated name such
71 // as "en_US", "en_Latn_US", or "en_US_POSIX".
Adam Lesinskice5e56e2016-10-21 17:56:45 -070072 std::vector<std::string> parts = util::SplitAndLowercase(str, '_');
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080073
Adam Lesinskice5e56e2016-10-21 17:56:45 -070074 const int num_tags = parts.size();
Adam Lesinskicacb28f2016-10-19 12:18:14 -070075 bool valid = false;
Adam Lesinskice5e56e2016-10-21 17:56:45 -070076 if (num_tags >= 1) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -070077 const std::string& lang = parts[0];
Adam Lesinskice5e56e2016-10-21 17:56:45 -070078 if (is_alpha(lang) && (lang.length() == 2 || lang.length() == 3)) {
79 set_language(lang.c_str());
Adam Lesinskicacb28f2016-10-19 12:18:14 -070080 valid = true;
81 }
82 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080083
Adam Lesinskice5e56e2016-10-21 17:56:45 -070084 if (!valid || num_tags == 1) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -070085 return valid;
86 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080087
Adam Lesinskicacb28f2016-10-19 12:18:14 -070088 // At this point, valid == true && numTags > 1.
89 const std::string& part2 = parts[1];
Adam Lesinskice5e56e2016-10-21 17:56:45 -070090 if ((part2.length() == 2 && is_alpha(part2)) ||
91 (part2.length() == 3 && is_number(part2))) {
92 set_region(part2.c_str());
93 } else if (part2.length() == 4 && is_alpha(part2)) {
94 set_script(part2.c_str());
Adam Lesinskicacb28f2016-10-19 12:18:14 -070095 } else if (part2.length() >= 4 && part2.length() <= 8) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -070096 set_variant(part2.c_str());
Adam Lesinskicacb28f2016-10-19 12:18:14 -070097 } else {
98 valid = false;
99 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800100
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700101 if (!valid || num_tags == 2) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700102 return valid;
103 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800104
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700105 // At this point, valid == true && numTags > 1.
106 const std::string& part3 = parts[2];
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700107 if (((part3.length() == 2 && is_alpha(part3)) ||
108 (part3.length() == 3 && is_number(part3))) &&
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700109 script[0]) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700110 set_region(part3.c_str());
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700111 } else if (part3.length() >= 4 && part3.length() <= 8) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700112 set_variant(part3.c_str());
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700113 } else {
114 valid = false;
115 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800116
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700117 if (!valid || num_tags == 3) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700118 return valid;
119 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800120
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700121 const std::string& part4 = parts[3];
122 if (part4.length() >= 4 && part4.length() <= 8) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700123 set_variant(part4.c_str());
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700124 } else {
125 valid = false;
126 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800127
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700128 if (!valid || num_tags > 4) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700129 return false;
130 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800131
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700132 return true;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800133}
134
Yurii Zubrytskyia5775142022-11-02 17:49:49 -0700135bool LocaleValue::InitFromBcp47Tag(StringPiece bcp47tag) {
Adam Lesinskib58c3ef2017-09-12 17:39:52 -0700136 return InitFromBcp47TagImpl(bcp47tag, '-');
137}
138
Yurii Zubrytskyia5775142022-11-02 17:49:49 -0700139bool LocaleValue::InitFromBcp47TagImpl(StringPiece bcp47tag, const char separator) {
Adam Lesinskib58c3ef2017-09-12 17:39:52 -0700140 std::vector<std::string> subtags = util::SplitAndLowercase(bcp47tag, separator);
141 if (subtags.size() == 1) {
142 set_language(subtags[0].c_str());
143 } else if (subtags.size() == 2) {
144 set_language(subtags[0].c_str());
145
146 // The second tag can either be a region, a variant or a script.
147 switch (subtags[1].size()) {
148 case 2:
149 case 3:
150 set_region(subtags[1].c_str());
151 break;
152 case 4:
153 if ('0' <= subtags[1][0] && subtags[1][0] <= '9') {
154 // This is a variant: fall through
155 } else {
156 set_script(subtags[1].c_str());
157 break;
158 }
Mårten Kongstade0930d32018-10-18 14:50:15 +0200159 FALLTHROUGH_INTENDED;
Adam Lesinskib58c3ef2017-09-12 17:39:52 -0700160 case 5:
161 case 6:
162 case 7:
163 case 8:
164 set_variant(subtags[1].c_str());
165 break;
166 default:
167 return false;
168 }
169 } else if (subtags.size() == 3) {
170 // The language is always the first subtag.
171 set_language(subtags[0].c_str());
172
173 // The second subtag can either be a script or a region code.
174 // If its size is 4, it's a script code, else it's a region code.
175 if (subtags[1].size() == 4) {
176 set_script(subtags[1].c_str());
177 } else if (subtags[1].size() == 2 || subtags[1].size() == 3) {
178 set_region(subtags[1].c_str());
179 } else {
180 return false;
181 }
182
183 // The third tag can either be a region code (if the second tag was
184 // a script), else a variant code.
185 if (subtags[2].size() >= 4) {
186 set_variant(subtags[2].c_str());
187 } else {
188 set_region(subtags[2].c_str());
189 }
190 } else if (subtags.size() == 4) {
191 set_language(subtags[0].c_str());
192 set_script(subtags[1].c_str());
193 set_region(subtags[2].c_str());
194 set_variant(subtags[3].c_str());
195 } else {
196 return false;
197 }
198 return true;
199}
200
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700201ssize_t LocaleValue::InitFromParts(std::vector<std::string>::iterator iter,
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700202 std::vector<std::string>::iterator end) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700203 const std::vector<std::string>::iterator start_iter = iter;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800204
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700205 std::string& part = *iter;
206 if (part[0] == 'b' && part[1] == '+') {
207 // This is a "modified" BCP 47 language tag. Same semantics as BCP 47 tags,
Adam Lesinskib58c3ef2017-09-12 17:39:52 -0700208 // except that the separator is "+" and not "-". Skip the prefix 'b+'.
209 if (!InitFromBcp47TagImpl(StringPiece(part).substr(2), '+')) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700210 return -1;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800211 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700212 ++iter;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700213 } else {
Adam Lesinskib58c3ef2017-09-12 17:39:52 -0700214 if ((part.length() == 2 || part.length() == 3) && is_alpha(part) && part != "car") {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700215 set_language(part.c_str());
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700216 ++iter;
217
218 if (iter != end) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700219 const std::string& region_part = *iter;
220 if (region_part.c_str()[0] == 'r' && region_part.length() == 3) {
221 set_region(region_part.c_str() + 1);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700222 ++iter;
223 }
224 }
225 }
226 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700227 return static_cast<ssize_t>(iter - start_iter);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800228}
229
Yurii Zubrytskyid3752602021-12-01 09:11:46 -0800230// Make sure the following memcpy's are properly sized.
231static_assert(sizeof(ResTable_config::localeScript) == sizeof(LocaleValue::script));
232static_assert(sizeof(ResTable_config::localeVariant) == sizeof(LocaleValue::variant));
233
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700234void LocaleValue::InitFromResTable(const ResTable_config& config) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700235 config.unpackLanguage(language);
236 config.unpackRegion(region);
237 if (config.localeScript[0] && !config.localeScriptWasComputed) {
238 memcpy(script, config.localeScript, sizeof(config.localeScript));
239 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800240
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700241 if (config.localeVariant[0]) {
242 memcpy(variant, config.localeVariant, sizeof(config.localeVariant));
243 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800244}
245
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700246void LocaleValue::WriteTo(ResTable_config* out) const {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700247 out->packLanguage(language);
248 out->packRegion(region);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800249
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700250 if (script[0]) {
251 memcpy(out->localeScript, script, sizeof(out->localeScript));
252 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800253
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700254 if (variant[0]) {
255 memcpy(out->localeVariant, variant, sizeof(out->localeVariant));
256 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800257}
258
Mårten Kongstad24c9aa62018-06-20 08:46:41 +0200259} // namespace android