xref: /aosp_15_r20/frameworks/base/libs/androidfw/Locale.cpp (revision d57664e9bc4670b3ecf6748a746a57c557b6bc9e)
1*d57664e9SAndroid Build Coastguard Worker /*
2*d57664e9SAndroid Build Coastguard Worker  * Copyright (C) 2015 The Android Open Source Project
3*d57664e9SAndroid Build Coastguard Worker  *
4*d57664e9SAndroid Build Coastguard Worker  * Licensed under the Apache License, Version 2.0 (the "License");
5*d57664e9SAndroid Build Coastguard Worker  * you may not use this file except in compliance with the License.
6*d57664e9SAndroid Build Coastguard Worker  * You may obtain a copy of the License at
7*d57664e9SAndroid Build Coastguard Worker  *
8*d57664e9SAndroid Build Coastguard Worker  *      http://www.apache.org/licenses/LICENSE-2.0
9*d57664e9SAndroid Build Coastguard Worker  *
10*d57664e9SAndroid Build Coastguard Worker  * Unless required by applicable law or agreed to in writing, software
11*d57664e9SAndroid Build Coastguard Worker  * distributed under the License is distributed on an "AS IS" BASIS,
12*d57664e9SAndroid Build Coastguard Worker  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13*d57664e9SAndroid Build Coastguard Worker  * See the License for the specific language governing permissions and
14*d57664e9SAndroid Build Coastguard Worker  * limitations under the License.
15*d57664e9SAndroid Build Coastguard Worker  */
16*d57664e9SAndroid Build Coastguard Worker 
17*d57664e9SAndroid Build Coastguard Worker #include "android-base/macros.h"
18*d57664e9SAndroid Build Coastguard Worker #include "androidfw/Locale.h"
19*d57664e9SAndroid Build Coastguard Worker #include "androidfw/Util.h"
20*d57664e9SAndroid Build Coastguard Worker 
21*d57664e9SAndroid Build Coastguard Worker #include <ctype.h>
22*d57664e9SAndroid Build Coastguard Worker 
23*d57664e9SAndroid Build Coastguard Worker #include <algorithm>
24*d57664e9SAndroid Build Coastguard Worker #include <string>
25*d57664e9SAndroid Build Coastguard Worker #include <vector>
26*d57664e9SAndroid Build Coastguard Worker 
27*d57664e9SAndroid Build Coastguard Worker using ::android::ResTable_config;
28*d57664e9SAndroid Build Coastguard Worker using ::android::StringPiece;
29*d57664e9SAndroid Build Coastguard Worker 
30*d57664e9SAndroid Build Coastguard Worker namespace android {
31*d57664e9SAndroid Build Coastguard Worker 
32*d57664e9SAndroid Build Coastguard Worker template <size_t N, class Transformer>
safe_transform_copy(const char * source,char (& dest)[N],Transformer t)33*d57664e9SAndroid Build Coastguard Worker static void safe_transform_copy(const char* source, char (&dest)[N], Transformer t) {
34*d57664e9SAndroid Build Coastguard Worker   size_t i = 0;
35*d57664e9SAndroid Build Coastguard Worker   while (i < N && (*source) != '\0') {
36*d57664e9SAndroid Build Coastguard Worker     dest[i++] = t(i, *source);
37*d57664e9SAndroid Build Coastguard Worker     source++;
38*d57664e9SAndroid Build Coastguard Worker   }
39*d57664e9SAndroid Build Coastguard Worker   while (i < N) {
40*d57664e9SAndroid Build Coastguard Worker     dest[i++] = '\0';
41*d57664e9SAndroid Build Coastguard Worker   }
42*d57664e9SAndroid Build Coastguard Worker }
43*d57664e9SAndroid Build Coastguard Worker 
set_language(const char * language_chars)44*d57664e9SAndroid Build Coastguard Worker void LocaleValue::set_language(const char* language_chars) {
45*d57664e9SAndroid Build Coastguard Worker   safe_transform_copy(language_chars, language, [](size_t, char c) { return ::tolower(c); });
46*d57664e9SAndroid Build Coastguard Worker }
47*d57664e9SAndroid Build Coastguard Worker 
set_region(const char * region_chars)48*d57664e9SAndroid Build Coastguard Worker void LocaleValue::set_region(const char* region_chars) {
49*d57664e9SAndroid Build Coastguard Worker   safe_transform_copy(region_chars, region, [](size_t, char c) { return ::toupper(c); });
50*d57664e9SAndroid Build Coastguard Worker }
51*d57664e9SAndroid Build Coastguard Worker 
set_script(const char * script_chars)52*d57664e9SAndroid Build Coastguard Worker void LocaleValue::set_script(const char* script_chars) {
53*d57664e9SAndroid Build Coastguard Worker   safe_transform_copy(script_chars, script,
54*d57664e9SAndroid Build Coastguard Worker                       [](size_t i, char c) { return i ? ::tolower(c) : ::toupper(c); });
55*d57664e9SAndroid Build Coastguard Worker }
56*d57664e9SAndroid Build Coastguard Worker 
set_variant(const char * variant_chars)57*d57664e9SAndroid Build Coastguard Worker void LocaleValue::set_variant(const char* variant_chars) {
58*d57664e9SAndroid Build Coastguard Worker   safe_transform_copy(variant_chars, variant, [](size_t, char c) { return c; });
59*d57664e9SAndroid Build Coastguard Worker }
60*d57664e9SAndroid Build Coastguard Worker 
is_alpha(const std::string & str)61*d57664e9SAndroid Build Coastguard Worker static inline bool is_alpha(const std::string& str) {
62*d57664e9SAndroid Build Coastguard Worker   return std::all_of(std::begin(str), std::end(str), ::isalpha);
63*d57664e9SAndroid Build Coastguard Worker }
64*d57664e9SAndroid Build Coastguard Worker 
is_number(const std::string & str)65*d57664e9SAndroid Build Coastguard Worker static inline bool is_number(const std::string& str) {
66*d57664e9SAndroid Build Coastguard Worker   return std::all_of(std::begin(str), std::end(str), ::isdigit);
67*d57664e9SAndroid Build Coastguard Worker }
68*d57664e9SAndroid Build Coastguard Worker 
InitFromFilterString(StringPiece str)69*d57664e9SAndroid Build Coastguard Worker bool LocaleValue::InitFromFilterString(StringPiece str) {
70*d57664e9SAndroid Build Coastguard Worker   // A locale (as specified in the filter) is an underscore separated name such
71*d57664e9SAndroid Build Coastguard Worker   // as "en_US", "en_Latn_US", or "en_US_POSIX".
72*d57664e9SAndroid Build Coastguard Worker   std::vector<std::string> parts = util::SplitAndLowercase(str, '_');
73*d57664e9SAndroid Build Coastguard Worker 
74*d57664e9SAndroid Build Coastguard Worker   const int num_tags = parts.size();
75*d57664e9SAndroid Build Coastguard Worker   bool valid = false;
76*d57664e9SAndroid Build Coastguard Worker   if (num_tags >= 1) {
77*d57664e9SAndroid Build Coastguard Worker     const std::string& lang = parts[0];
78*d57664e9SAndroid Build Coastguard Worker     if (is_alpha(lang) && (lang.length() == 2 || lang.length() == 3)) {
79*d57664e9SAndroid Build Coastguard Worker       set_language(lang.c_str());
80*d57664e9SAndroid Build Coastguard Worker       valid = true;
81*d57664e9SAndroid Build Coastguard Worker     }
82*d57664e9SAndroid Build Coastguard Worker   }
83*d57664e9SAndroid Build Coastguard Worker 
84*d57664e9SAndroid Build Coastguard Worker   if (!valid || num_tags == 1) {
85*d57664e9SAndroid Build Coastguard Worker     return valid;
86*d57664e9SAndroid Build Coastguard Worker   }
87*d57664e9SAndroid Build Coastguard Worker 
88*d57664e9SAndroid Build Coastguard Worker   // At this point, valid == true && numTags > 1.
89*d57664e9SAndroid Build Coastguard Worker   const std::string& part2 = parts[1];
90*d57664e9SAndroid Build Coastguard Worker   if ((part2.length() == 2 && is_alpha(part2)) ||
91*d57664e9SAndroid Build Coastguard Worker       (part2.length() == 3 && is_number(part2))) {
92*d57664e9SAndroid Build Coastguard Worker     set_region(part2.c_str());
93*d57664e9SAndroid Build Coastguard Worker   } else if (part2.length() == 4 && is_alpha(part2)) {
94*d57664e9SAndroid Build Coastguard Worker     set_script(part2.c_str());
95*d57664e9SAndroid Build Coastguard Worker   } else if (part2.length() >= 4 && part2.length() <= 8) {
96*d57664e9SAndroid Build Coastguard Worker     set_variant(part2.c_str());
97*d57664e9SAndroid Build Coastguard Worker   } else {
98*d57664e9SAndroid Build Coastguard Worker     valid = false;
99*d57664e9SAndroid Build Coastguard Worker   }
100*d57664e9SAndroid Build Coastguard Worker 
101*d57664e9SAndroid Build Coastguard Worker   if (!valid || num_tags == 2) {
102*d57664e9SAndroid Build Coastguard Worker     return valid;
103*d57664e9SAndroid Build Coastguard Worker   }
104*d57664e9SAndroid Build Coastguard Worker 
105*d57664e9SAndroid Build Coastguard Worker   // At this point, valid == true && numTags > 1.
106*d57664e9SAndroid Build Coastguard Worker   const std::string& part3 = parts[2];
107*d57664e9SAndroid Build Coastguard Worker   if (((part3.length() == 2 && is_alpha(part3)) ||
108*d57664e9SAndroid Build Coastguard Worker        (part3.length() == 3 && is_number(part3))) &&
109*d57664e9SAndroid Build Coastguard Worker       script[0]) {
110*d57664e9SAndroid Build Coastguard Worker     set_region(part3.c_str());
111*d57664e9SAndroid Build Coastguard Worker   } else if (part3.length() >= 4 && part3.length() <= 8) {
112*d57664e9SAndroid Build Coastguard Worker     set_variant(part3.c_str());
113*d57664e9SAndroid Build Coastguard Worker   } else {
114*d57664e9SAndroid Build Coastguard Worker     valid = false;
115*d57664e9SAndroid Build Coastguard Worker   }
116*d57664e9SAndroid Build Coastguard Worker 
117*d57664e9SAndroid Build Coastguard Worker   if (!valid || num_tags == 3) {
118*d57664e9SAndroid Build Coastguard Worker     return valid;
119*d57664e9SAndroid Build Coastguard Worker   }
120*d57664e9SAndroid Build Coastguard Worker 
121*d57664e9SAndroid Build Coastguard Worker   const std::string& part4 = parts[3];
122*d57664e9SAndroid Build Coastguard Worker   if (part4.length() >= 4 && part4.length() <= 8) {
123*d57664e9SAndroid Build Coastguard Worker     set_variant(part4.c_str());
124*d57664e9SAndroid Build Coastguard Worker   } else {
125*d57664e9SAndroid Build Coastguard Worker     valid = false;
126*d57664e9SAndroid Build Coastguard Worker   }
127*d57664e9SAndroid Build Coastguard Worker 
128*d57664e9SAndroid Build Coastguard Worker   if (!valid || num_tags > 4) {
129*d57664e9SAndroid Build Coastguard Worker     return false;
130*d57664e9SAndroid Build Coastguard Worker   }
131*d57664e9SAndroid Build Coastguard Worker 
132*d57664e9SAndroid Build Coastguard Worker   return true;
133*d57664e9SAndroid Build Coastguard Worker }
134*d57664e9SAndroid Build Coastguard Worker 
InitFromBcp47Tag(StringPiece bcp47tag)135*d57664e9SAndroid Build Coastguard Worker bool LocaleValue::InitFromBcp47Tag(StringPiece bcp47tag) {
136*d57664e9SAndroid Build Coastguard Worker   return InitFromBcp47TagImpl(bcp47tag, '-');
137*d57664e9SAndroid Build Coastguard Worker }
138*d57664e9SAndroid Build Coastguard Worker 
InitFromBcp47TagImpl(StringPiece bcp47tag,const char separator)139*d57664e9SAndroid Build Coastguard Worker bool LocaleValue::InitFromBcp47TagImpl(StringPiece bcp47tag, const char separator) {
140*d57664e9SAndroid Build Coastguard Worker   std::vector<std::string> subtags = util::SplitAndLowercase(bcp47tag, separator);
141*d57664e9SAndroid Build Coastguard Worker   if (subtags.size() == 1) {
142*d57664e9SAndroid Build Coastguard Worker     set_language(subtags[0].c_str());
143*d57664e9SAndroid Build Coastguard Worker   } else if (subtags.size() == 2) {
144*d57664e9SAndroid Build Coastguard Worker     set_language(subtags[0].c_str());
145*d57664e9SAndroid Build Coastguard Worker 
146*d57664e9SAndroid Build Coastguard Worker     // The second tag can either be a region, a variant or a script.
147*d57664e9SAndroid Build Coastguard Worker     switch (subtags[1].size()) {
148*d57664e9SAndroid Build Coastguard Worker       case 2:
149*d57664e9SAndroid Build Coastguard Worker       case 3:
150*d57664e9SAndroid Build Coastguard Worker         set_region(subtags[1].c_str());
151*d57664e9SAndroid Build Coastguard Worker         break;
152*d57664e9SAndroid Build Coastguard Worker       case 4:
153*d57664e9SAndroid Build Coastguard Worker         if ('0' <= subtags[1][0] && subtags[1][0] <= '9') {
154*d57664e9SAndroid Build Coastguard Worker           // This is a variant: fall through
155*d57664e9SAndroid Build Coastguard Worker         } else {
156*d57664e9SAndroid Build Coastguard Worker           set_script(subtags[1].c_str());
157*d57664e9SAndroid Build Coastguard Worker           break;
158*d57664e9SAndroid Build Coastguard Worker         }
159*d57664e9SAndroid Build Coastguard Worker         FALLTHROUGH_INTENDED;
160*d57664e9SAndroid Build Coastguard Worker       case 5:
161*d57664e9SAndroid Build Coastguard Worker       case 6:
162*d57664e9SAndroid Build Coastguard Worker       case 7:
163*d57664e9SAndroid Build Coastguard Worker       case 8:
164*d57664e9SAndroid Build Coastguard Worker         set_variant(subtags[1].c_str());
165*d57664e9SAndroid Build Coastguard Worker         break;
166*d57664e9SAndroid Build Coastguard Worker       default:
167*d57664e9SAndroid Build Coastguard Worker         return false;
168*d57664e9SAndroid Build Coastguard Worker     }
169*d57664e9SAndroid Build Coastguard Worker   } else if (subtags.size() == 3) {
170*d57664e9SAndroid Build Coastguard Worker     // The language is always the first subtag.
171*d57664e9SAndroid Build Coastguard Worker     set_language(subtags[0].c_str());
172*d57664e9SAndroid Build Coastguard Worker 
173*d57664e9SAndroid Build Coastguard Worker     // The second subtag can either be a script or a region code.
174*d57664e9SAndroid Build Coastguard Worker     // If its size is 4, it's a script code, else it's a region code.
175*d57664e9SAndroid Build Coastguard Worker     if (subtags[1].size() == 4) {
176*d57664e9SAndroid Build Coastguard Worker       set_script(subtags[1].c_str());
177*d57664e9SAndroid Build Coastguard Worker     } else if (subtags[1].size() == 2 || subtags[1].size() == 3) {
178*d57664e9SAndroid Build Coastguard Worker       set_region(subtags[1].c_str());
179*d57664e9SAndroid Build Coastguard Worker     } else {
180*d57664e9SAndroid Build Coastguard Worker       return false;
181*d57664e9SAndroid Build Coastguard Worker     }
182*d57664e9SAndroid Build Coastguard Worker 
183*d57664e9SAndroid Build Coastguard Worker     // The third tag can either be a region code (if the second tag was
184*d57664e9SAndroid Build Coastguard Worker     // a script), else a variant code.
185*d57664e9SAndroid Build Coastguard Worker     if (subtags[2].size() >= 4) {
186*d57664e9SAndroid Build Coastguard Worker       set_variant(subtags[2].c_str());
187*d57664e9SAndroid Build Coastguard Worker     } else {
188*d57664e9SAndroid Build Coastguard Worker       set_region(subtags[2].c_str());
189*d57664e9SAndroid Build Coastguard Worker     }
190*d57664e9SAndroid Build Coastguard Worker   } else if (subtags.size() == 4) {
191*d57664e9SAndroid Build Coastguard Worker     set_language(subtags[0].c_str());
192*d57664e9SAndroid Build Coastguard Worker     set_script(subtags[1].c_str());
193*d57664e9SAndroid Build Coastguard Worker     set_region(subtags[2].c_str());
194*d57664e9SAndroid Build Coastguard Worker     set_variant(subtags[3].c_str());
195*d57664e9SAndroid Build Coastguard Worker   } else {
196*d57664e9SAndroid Build Coastguard Worker     return false;
197*d57664e9SAndroid Build Coastguard Worker   }
198*d57664e9SAndroid Build Coastguard Worker   return true;
199*d57664e9SAndroid Build Coastguard Worker }
200*d57664e9SAndroid Build Coastguard Worker 
InitFromParts(std::vector<std::string>::iterator iter,std::vector<std::string>::iterator end)201*d57664e9SAndroid Build Coastguard Worker ssize_t LocaleValue::InitFromParts(std::vector<std::string>::iterator iter,
202*d57664e9SAndroid Build Coastguard Worker                                    std::vector<std::string>::iterator end) {
203*d57664e9SAndroid Build Coastguard Worker   const std::vector<std::string>::iterator start_iter = iter;
204*d57664e9SAndroid Build Coastguard Worker 
205*d57664e9SAndroid Build Coastguard Worker   std::string& part = *iter;
206*d57664e9SAndroid Build Coastguard Worker   if (part[0] == 'b' && part[1] == '+') {
207*d57664e9SAndroid Build Coastguard Worker     // This is a "modified" BCP 47 language tag. Same semantics as BCP 47 tags,
208*d57664e9SAndroid Build Coastguard Worker     // except that the separator is "+" and not "-". Skip the prefix 'b+'.
209*d57664e9SAndroid Build Coastguard Worker     if (!InitFromBcp47TagImpl(StringPiece(part).substr(2), '+')) {
210*d57664e9SAndroid Build Coastguard Worker       return -1;
211*d57664e9SAndroid Build Coastguard Worker     }
212*d57664e9SAndroid Build Coastguard Worker     ++iter;
213*d57664e9SAndroid Build Coastguard Worker   } else {
214*d57664e9SAndroid Build Coastguard Worker     if ((part.length() == 2 || part.length() == 3) && is_alpha(part) && part != "car") {
215*d57664e9SAndroid Build Coastguard Worker       set_language(part.c_str());
216*d57664e9SAndroid Build Coastguard Worker       ++iter;
217*d57664e9SAndroid Build Coastguard Worker 
218*d57664e9SAndroid Build Coastguard Worker       if (iter != end) {
219*d57664e9SAndroid Build Coastguard Worker         const std::string& region_part = *iter;
220*d57664e9SAndroid Build Coastguard Worker         if (region_part.c_str()[0] == 'r' && region_part.length() == 3) {
221*d57664e9SAndroid Build Coastguard Worker           set_region(region_part.c_str() + 1);
222*d57664e9SAndroid Build Coastguard Worker           ++iter;
223*d57664e9SAndroid Build Coastguard Worker         }
224*d57664e9SAndroid Build Coastguard Worker       }
225*d57664e9SAndroid Build Coastguard Worker     }
226*d57664e9SAndroid Build Coastguard Worker   }
227*d57664e9SAndroid Build Coastguard Worker   return static_cast<ssize_t>(iter - start_iter);
228*d57664e9SAndroid Build Coastguard Worker }
229*d57664e9SAndroid Build Coastguard Worker 
230*d57664e9SAndroid Build Coastguard Worker // Make sure the following memcpy's are properly sized.
231*d57664e9SAndroid Build Coastguard Worker static_assert(sizeof(ResTable_config::localeScript) == sizeof(LocaleValue::script));
232*d57664e9SAndroid Build Coastguard Worker static_assert(sizeof(ResTable_config::localeVariant) == sizeof(LocaleValue::variant));
233*d57664e9SAndroid Build Coastguard Worker 
InitFromResTable(const ResTable_config & config)234*d57664e9SAndroid Build Coastguard Worker void LocaleValue::InitFromResTable(const ResTable_config& config) {
235*d57664e9SAndroid Build Coastguard Worker   config.unpackLanguage(language);
236*d57664e9SAndroid Build Coastguard Worker   config.unpackRegion(region);
237*d57664e9SAndroid Build Coastguard Worker   if (config.localeScript[0] && !config.localeScriptWasComputed) {
238*d57664e9SAndroid Build Coastguard Worker     memcpy(script, config.localeScript, sizeof(config.localeScript));
239*d57664e9SAndroid Build Coastguard Worker   }
240*d57664e9SAndroid Build Coastguard Worker 
241*d57664e9SAndroid Build Coastguard Worker   if (config.localeVariant[0]) {
242*d57664e9SAndroid Build Coastguard Worker     memcpy(variant, config.localeVariant, sizeof(config.localeVariant));
243*d57664e9SAndroid Build Coastguard Worker   }
244*d57664e9SAndroid Build Coastguard Worker }
245*d57664e9SAndroid Build Coastguard Worker 
WriteTo(ResTable_config * out) const246*d57664e9SAndroid Build Coastguard Worker void LocaleValue::WriteTo(ResTable_config* out) const {
247*d57664e9SAndroid Build Coastguard Worker   out->packLanguage(language);
248*d57664e9SAndroid Build Coastguard Worker   out->packRegion(region);
249*d57664e9SAndroid Build Coastguard Worker 
250*d57664e9SAndroid Build Coastguard Worker   if (script[0]) {
251*d57664e9SAndroid Build Coastguard Worker     memcpy(out->localeScript, script, sizeof(out->localeScript));
252*d57664e9SAndroid Build Coastguard Worker   }
253*d57664e9SAndroid Build Coastguard Worker 
254*d57664e9SAndroid Build Coastguard Worker   if (variant[0]) {
255*d57664e9SAndroid Build Coastguard Worker     memcpy(out->localeVariant, variant, sizeof(out->localeVariant));
256*d57664e9SAndroid Build Coastguard Worker   }
257*d57664e9SAndroid Build Coastguard Worker }
258*d57664e9SAndroid Build Coastguard Worker 
259*d57664e9SAndroid Build Coastguard Worker }  // namespace android
260