1/*
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
17#include "Locale.h"
18#include "util/Util.h"
19
20#include <algorithm>
21#include <ctype.h>
22#include <string>
23#include <vector>
24
25namespace aapt {
26
27using android::ResTable_config;
28
29void LocaleValue::setLanguage(const char* languageChars) {
30     size_t i = 0;
31     while ((*languageChars) != '\0') {
32          language[i++] = ::tolower(*languageChars);
33          languageChars++;
34     }
35}
36
37void LocaleValue::setRegion(const char* regionChars) {
38    size_t i = 0;
39    while ((*regionChars) != '\0') {
40         region[i++] = ::toupper(*regionChars);
41         regionChars++;
42    }
43}
44
45void LocaleValue::setScript(const char* scriptChars) {
46    size_t i = 0;
47    while ((*scriptChars) != '\0') {
48         if (i == 0) {
49             script[i++] = ::toupper(*scriptChars);
50         } else {
51             script[i++] = ::tolower(*scriptChars);
52         }
53         scriptChars++;
54    }
55}
56
57void LocaleValue::setVariant(const char* variantChars) {
58     size_t i = 0;
59     while ((*variantChars) != '\0') {
60          variant[i++] = *variantChars;
61          variantChars++;
62     }
63}
64
65static inline bool isAlpha(const std::string& str) {
66    return std::all_of(std::begin(str), std::end(str), ::isalpha);
67}
68
69static inline bool isNumber(const std::string& str) {
70    return std::all_of(std::begin(str), std::end(str), ::isdigit);
71}
72
73bool LocaleValue::initFromFilterString(const StringPiece& str) {
74     // A locale (as specified in the filter) is an underscore separated name such
75     // as "en_US", "en_Latn_US", or "en_US_POSIX".
76     std::vector<std::string> parts = util::splitAndLowercase(str, '_');
77
78     const int numTags = parts.size();
79     bool valid = false;
80     if (numTags >= 1) {
81         const std::string& lang = parts[0];
82         if (isAlpha(lang) && (lang.length() == 2 || lang.length() == 3)) {
83             setLanguage(lang.c_str());
84             valid = true;
85         }
86     }
87
88     if (!valid || numTags == 1) {
89         return valid;
90     }
91
92     // At this point, valid == true && numTags > 1.
93     const std::string& part2 = parts[1];
94     if ((part2.length() == 2 && isAlpha(part2)) ||
95         (part2.length() == 3 && isNumber(part2))) {
96         setRegion(part2.c_str());
97     } else if (part2.length() == 4 && isAlpha(part2)) {
98         setScript(part2.c_str());
99     } else if (part2.length() >= 4 && part2.length() <= 8) {
100         setVariant(part2.c_str());
101     } else {
102         valid = false;
103     }
104
105     if (!valid || numTags == 2) {
106         return valid;
107     }
108
109     // At this point, valid == true && numTags > 1.
110     const std::string& part3 = parts[2];
111     if (((part3.length() == 2 && isAlpha(part3)) ||
112         (part3.length() == 3 && isNumber(part3))) && script[0]) {
113         setRegion(part3.c_str());
114     } else if (part3.length() >= 4 && part3.length() <= 8) {
115         setVariant(part3.c_str());
116     } else {
117         valid = false;
118     }
119
120     if (!valid || numTags == 3) {
121         return valid;
122     }
123
124     const std::string& part4 = parts[3];
125     if (part4.length() >= 4 && part4.length() <= 8) {
126         setVariant(part4.c_str());
127     } else {
128         valid = false;
129     }
130
131     if (!valid || numTags > 4) {
132         return false;
133     }
134
135     return true;
136}
137
138ssize_t LocaleValue::initFromParts(std::vector<std::string>::iterator iter,
139        std::vector<std::string>::iterator end) {
140    const std::vector<std::string>::iterator startIter = iter;
141
142    std::string& part = *iter;
143    if (part[0] == 'b' && part[1] == '+') {
144        // This is a "modified" BCP 47 language tag. Same semantics as BCP 47 tags,
145        // except that the separator is "+" and not "-".
146        std::vector<std::string> subtags = util::splitAndLowercase(part, '+');
147        subtags.erase(subtags.begin());
148        if (subtags.size() == 1) {
149            setLanguage(subtags[0].c_str());
150        } else if (subtags.size() == 2) {
151            setLanguage(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                    setRegion(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                        setScript(subtags[1].c_str());
164                        break;
165                    }
166                case 5:
167                case 6:
168                case 7:
169                case 8:
170                    setVariant(subtags[1].c_str());
171                    break;
172                default:
173                    return -1;
174            }
175        } else if (subtags.size() == 3) {
176            // The language is always the first subtag.
177            setLanguage(subtags[0].c_str());
178
179            // The second subtag can either be a script or a region code.
180            // If its size is 4, it's a script code, else it's a region code.
181            if (subtags[1].size() == 4) {
182                setScript(subtags[1].c_str());
183            } else if (subtags[1].size() == 2 || subtags[1].size() == 3) {
184                setRegion(subtags[1].c_str());
185            } else {
186                return -1;
187            }
188
189            // The third tag can either be a region code (if the second tag was
190            // a script), else a variant code.
191            if (subtags[2].size() >= 4) {
192                setVariant(subtags[2].c_str());
193            } else {
194                setRegion(subtags[2].c_str());
195            }
196        } else if (subtags.size() == 4) {
197            setLanguage(subtags[0].c_str());
198            setScript(subtags[1].c_str());
199            setRegion(subtags[2].c_str());
200            setVariant(subtags[3].c_str());
201        } else {
202            return -1;
203        }
204
205        ++iter;
206
207    } else {
208        if ((part.length() == 2 || part.length() == 3)
209                && isAlpha(part) && part != "car") {
210            setLanguage(part.c_str());
211            ++iter;
212
213            if (iter != end) {
214                const std::string& regionPart = *iter;
215                if (regionPart.c_str()[0] == 'r' && regionPart.length() == 3) {
216                    setRegion(regionPart.c_str() + 1);
217                    ++iter;
218                }
219            }
220        }
221    }
222
223    return static_cast<ssize_t>(iter - startIter);
224}
225
226
227std::string LocaleValue::toDirName() const {
228    std::string dirName;
229    if (language[0]) {
230        dirName += language;
231    } else {
232        return dirName;
233    }
234
235    if (script[0]) {
236        dirName += "-s";
237        dirName += script;
238    }
239
240    if (region[0]) {
241        dirName += "-r";
242        dirName += region;
243    }
244
245    if (variant[0]) {
246        dirName += "-v";
247        dirName += variant;
248    }
249
250    return dirName;
251}
252
253void LocaleValue::initFromResTable(const ResTable_config& config) {
254    config.unpackLanguage(language);
255    config.unpackRegion(region);
256    if (config.localeScript[0] && !config.localeScriptWasComputed) {
257        memcpy(script, config.localeScript, sizeof(config.localeScript));
258    }
259
260    if (config.localeVariant[0]) {
261        memcpy(variant, config.localeVariant, sizeof(config.localeVariant));
262    }
263}
264
265void LocaleValue::writeTo(ResTable_config* out) const {
266    out->packLanguage(language);
267    out->packRegion(region);
268
269    if (script[0]) {
270        memcpy(out->localeScript, script, sizeof(out->localeScript));
271    }
272
273    if (variant[0]) {
274        memcpy(out->localeVariant, variant, sizeof(out->localeVariant));
275    }
276}
277
278} // namespace aapt
279