/src/skia/third_party/externals/icu/source/common/localebuilder.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | // © 2019 and later: Unicode, Inc. and others. |
2 | | // License & terms of use: http://www.unicode.org/copyright.html |
3 | | |
4 | | #include <utility> |
5 | | |
6 | | #include "bytesinkutil.h" // CharStringByteSink |
7 | | #include "charstr.h" |
8 | | #include "cstring.h" |
9 | | #include "ulocimp.h" |
10 | | #include "unicode/localebuilder.h" |
11 | | #include "unicode/locid.h" |
12 | | |
13 | | U_NAMESPACE_BEGIN |
14 | | |
15 | 0 | #define UPRV_ISDIGIT(c) (((c) >= '0') && ((c) <= '9')) |
16 | 0 | #define UPRV_ISALPHANUM(c) (uprv_isASCIILetter(c) || UPRV_ISDIGIT(c) ) |
17 | | |
18 | | const char* kAttributeKey = "attribute"; |
19 | | |
20 | 0 | static bool _isExtensionSubtags(char key, const char* s, int32_t len) { |
21 | 0 | switch (uprv_tolower(key)) { |
22 | 0 | case 'u': |
23 | 0 | return ultag_isUnicodeExtensionSubtags(s, len); |
24 | 0 | case 't': |
25 | 0 | return ultag_isTransformedExtensionSubtags(s, len); |
26 | 0 | case 'x': |
27 | 0 | return ultag_isPrivateuseValueSubtags(s, len); |
28 | 0 | default: |
29 | 0 | return ultag_isExtensionSubtags(s, len); |
30 | 0 | } |
31 | 0 | } |
32 | | |
33 | | LocaleBuilder::LocaleBuilder() : UObject(), status_(U_ZERO_ERROR), language_(), |
34 | | script_(), region_(), variant_(nullptr), extensions_(nullptr) |
35 | 0 | { |
36 | 0 | language_[0] = 0; |
37 | 0 | script_[0] = 0; |
38 | 0 | region_[0] = 0; |
39 | 0 | } |
40 | | |
41 | | LocaleBuilder::~LocaleBuilder() |
42 | 0 | { |
43 | 0 | delete variant_; |
44 | 0 | delete extensions_; |
45 | 0 | } |
46 | | |
47 | | LocaleBuilder& LocaleBuilder::setLocale(const Locale& locale) |
48 | 0 | { |
49 | 0 | clear(); |
50 | 0 | setLanguage(locale.getLanguage()); |
51 | 0 | setScript(locale.getScript()); |
52 | 0 | setRegion(locale.getCountry()); |
53 | 0 | setVariant(locale.getVariant()); |
54 | 0 | extensions_ = locale.clone(); |
55 | 0 | if (extensions_ == nullptr) { |
56 | 0 | status_ = U_MEMORY_ALLOCATION_ERROR; |
57 | 0 | } |
58 | 0 | return *this; |
59 | 0 | } |
60 | | |
61 | | LocaleBuilder& LocaleBuilder::setLanguageTag(StringPiece tag) |
62 | 0 | { |
63 | 0 | Locale l = Locale::forLanguageTag(tag, status_); |
64 | 0 | if (U_FAILURE(status_)) { return *this; } |
65 | | // Because setLocale will reset status_ we need to return |
66 | | // first if we have error in forLanguageTag. |
67 | 0 | setLocale(l); |
68 | 0 | return *this; |
69 | 0 | } |
70 | | |
71 | | static void setField(StringPiece input, char* dest, UErrorCode& errorCode, |
72 | 0 | UBool (*test)(const char*, int32_t)) { |
73 | 0 | if (U_FAILURE(errorCode)) { return; } |
74 | 0 | if (input.empty()) { |
75 | 0 | dest[0] = '\0'; |
76 | 0 | } else if (test(input.data(), input.length())) { |
77 | 0 | uprv_memcpy(dest, input.data(), input.length()); |
78 | 0 | dest[input.length()] = '\0'; |
79 | 0 | } else { |
80 | 0 | errorCode = U_ILLEGAL_ARGUMENT_ERROR; |
81 | 0 | } |
82 | 0 | } |
83 | | |
84 | | LocaleBuilder& LocaleBuilder::setLanguage(StringPiece language) |
85 | 0 | { |
86 | 0 | setField(language, language_, status_, &ultag_isLanguageSubtag); |
87 | 0 | return *this; |
88 | 0 | } |
89 | | |
90 | | LocaleBuilder& LocaleBuilder::setScript(StringPiece script) |
91 | 0 | { |
92 | 0 | setField(script, script_, status_, &ultag_isScriptSubtag); |
93 | 0 | return *this; |
94 | 0 | } |
95 | | |
96 | | LocaleBuilder& LocaleBuilder::setRegion(StringPiece region) |
97 | 0 | { |
98 | 0 | setField(region, region_, status_, &ultag_isRegionSubtag); |
99 | 0 | return *this; |
100 | 0 | } |
101 | | |
102 | 0 | static void transform(char* data, int32_t len) { |
103 | 0 | for (int32_t i = 0; i < len; i++, data++) { |
104 | 0 | if (*data == '_') { |
105 | 0 | *data = '-'; |
106 | 0 | } else { |
107 | 0 | *data = uprv_tolower(*data); |
108 | 0 | } |
109 | 0 | } |
110 | 0 | } |
111 | | |
112 | | LocaleBuilder& LocaleBuilder::setVariant(StringPiece variant) |
113 | 0 | { |
114 | 0 | if (U_FAILURE(status_)) { return *this; } |
115 | 0 | if (variant.empty()) { |
116 | 0 | delete variant_; |
117 | 0 | variant_ = nullptr; |
118 | 0 | return *this; |
119 | 0 | } |
120 | 0 | CharString* new_variant = new CharString(variant, status_); |
121 | 0 | if (U_FAILURE(status_)) { return *this; } |
122 | 0 | if (new_variant == nullptr) { |
123 | 0 | status_ = U_MEMORY_ALLOCATION_ERROR; |
124 | 0 | return *this; |
125 | 0 | } |
126 | 0 | transform(new_variant->data(), new_variant->length()); |
127 | 0 | if (!ultag_isVariantSubtags(new_variant->data(), new_variant->length())) { |
128 | 0 | delete new_variant; |
129 | 0 | status_ = U_ILLEGAL_ARGUMENT_ERROR; |
130 | 0 | return *this; |
131 | 0 | } |
132 | 0 | delete variant_; |
133 | 0 | variant_ = new_variant; |
134 | 0 | return *this; |
135 | 0 | } |
136 | | |
137 | | static bool |
138 | | _isKeywordValue(const char* key, const char* value, int32_t value_len) |
139 | 0 | { |
140 | 0 | if (key[1] == '\0') { |
141 | | // one char key |
142 | 0 | return (UPRV_ISALPHANUM(uprv_tolower(key[0])) && |
143 | 0 | _isExtensionSubtags(key[0], value, value_len)); |
144 | 0 | } else if (uprv_strcmp(key, kAttributeKey) == 0) { |
145 | | // unicode attributes |
146 | 0 | return ultag_isUnicodeLocaleAttributes(value, value_len); |
147 | 0 | } |
148 | | // otherwise: unicode extension value |
149 | | // We need to convert from legacy key/value to unicode |
150 | | // key/value |
151 | 0 | const char* unicode_locale_key = uloc_toUnicodeLocaleKey(key); |
152 | 0 | const char* unicode_locale_type = uloc_toUnicodeLocaleType(key, value); |
153 | |
|
154 | 0 | return unicode_locale_key && unicode_locale_type && |
155 | 0 | ultag_isUnicodeLocaleKey(unicode_locale_key, -1) && |
156 | 0 | ultag_isUnicodeLocaleType(unicode_locale_type, -1); |
157 | 0 | } |
158 | | |
159 | | static void |
160 | | _copyExtensions(const Locale& from, icu::StringEnumeration *keywords, |
161 | | Locale& to, bool validate, UErrorCode& errorCode) |
162 | 0 | { |
163 | 0 | if (U_FAILURE(errorCode)) { return; } |
164 | 0 | LocalPointer<icu::StringEnumeration> ownedKeywords; |
165 | 0 | if (keywords == nullptr) { |
166 | 0 | ownedKeywords.adoptInstead(from.createKeywords(errorCode)); |
167 | 0 | if (U_FAILURE(errorCode) || ownedKeywords.isNull()) { return; } |
168 | 0 | keywords = ownedKeywords.getAlias(); |
169 | 0 | } |
170 | 0 | const char* key; |
171 | 0 | while ((key = keywords->next(nullptr, errorCode)) != nullptr) { |
172 | 0 | CharString value; |
173 | 0 | CharStringByteSink sink(&value); |
174 | 0 | from.getKeywordValue(key, sink, errorCode); |
175 | 0 | if (U_FAILURE(errorCode)) { return; } |
176 | 0 | if (uprv_strcmp(key, kAttributeKey) == 0) { |
177 | 0 | transform(value.data(), value.length()); |
178 | 0 | } |
179 | 0 | if (validate && |
180 | 0 | !_isKeywordValue(key, value.data(), value.length())) { |
181 | 0 | errorCode = U_ILLEGAL_ARGUMENT_ERROR; |
182 | 0 | return; |
183 | 0 | } |
184 | 0 | to.setKeywordValue(key, value.data(), errorCode); |
185 | 0 | if (U_FAILURE(errorCode)) { return; } |
186 | 0 | } |
187 | 0 | } |
188 | | |
189 | | void static |
190 | | _clearUAttributesAndKeyType(Locale& locale, UErrorCode& errorCode) |
191 | 0 | { |
192 | | // Clear Unicode attributes |
193 | 0 | locale.setKeywordValue(kAttributeKey, "", errorCode); |
194 | | |
195 | | // Clear all Unicode keyword values |
196 | 0 | LocalPointer<icu::StringEnumeration> iter(locale.createUnicodeKeywords(errorCode)); |
197 | 0 | if (U_FAILURE(errorCode) || iter.isNull()) { return; } |
198 | 0 | const char* key; |
199 | 0 | while ((key = iter->next(nullptr, errorCode)) != nullptr) { |
200 | 0 | locale.setUnicodeKeywordValue(key, nullptr, errorCode); |
201 | 0 | } |
202 | 0 | } |
203 | | |
204 | | static void |
205 | | _setUnicodeExtensions(Locale& locale, const CharString& value, UErrorCode& errorCode) |
206 | 0 | { |
207 | | // Add the unicode extensions to extensions_ |
208 | 0 | CharString locale_str("und-u-", errorCode); |
209 | 0 | locale_str.append(value, errorCode); |
210 | 0 | _copyExtensions( |
211 | 0 | Locale::forLanguageTag(locale_str.data(), errorCode), nullptr, |
212 | 0 | locale, false, errorCode); |
213 | 0 | } |
214 | | |
215 | | LocaleBuilder& LocaleBuilder::setExtension(char key, StringPiece value) |
216 | 0 | { |
217 | 0 | if (U_FAILURE(status_)) { return *this; } |
218 | 0 | if (!UPRV_ISALPHANUM(key)) { |
219 | 0 | status_ = U_ILLEGAL_ARGUMENT_ERROR; |
220 | 0 | return *this; |
221 | 0 | } |
222 | 0 | CharString value_str(value, status_); |
223 | 0 | if (U_FAILURE(status_)) { return *this; } |
224 | 0 | transform(value_str.data(), value_str.length()); |
225 | 0 | if (!value_str.isEmpty() && |
226 | 0 | !_isExtensionSubtags(key, value_str.data(), value_str.length())) { |
227 | 0 | status_ = U_ILLEGAL_ARGUMENT_ERROR; |
228 | 0 | return *this; |
229 | 0 | } |
230 | 0 | if (extensions_ == nullptr) { |
231 | 0 | extensions_ = new Locale(); |
232 | 0 | if (extensions_ == nullptr) { |
233 | 0 | status_ = U_MEMORY_ALLOCATION_ERROR; |
234 | 0 | return *this; |
235 | 0 | } |
236 | 0 | } |
237 | 0 | if (uprv_tolower(key) != 'u') { |
238 | | // for t, x and others extension. |
239 | 0 | extensions_->setKeywordValue(StringPiece(&key, 1), value_str.data(), |
240 | 0 | status_); |
241 | 0 | return *this; |
242 | 0 | } |
243 | 0 | _clearUAttributesAndKeyType(*extensions_, status_); |
244 | 0 | if (U_FAILURE(status_)) { return *this; } |
245 | 0 | if (!value.empty()) { |
246 | 0 | _setUnicodeExtensions(*extensions_, value_str, status_); |
247 | 0 | } |
248 | 0 | return *this; |
249 | 0 | } |
250 | | |
251 | | LocaleBuilder& LocaleBuilder::setUnicodeLocaleKeyword( |
252 | | StringPiece key, StringPiece type) |
253 | 0 | { |
254 | 0 | if (U_FAILURE(status_)) { return *this; } |
255 | 0 | if (!ultag_isUnicodeLocaleKey(key.data(), key.length()) || |
256 | 0 | (!type.empty() && |
257 | 0 | !ultag_isUnicodeLocaleType(type.data(), type.length()))) { |
258 | 0 | status_ = U_ILLEGAL_ARGUMENT_ERROR; |
259 | 0 | return *this; |
260 | 0 | } |
261 | 0 | if (extensions_ == nullptr) { |
262 | 0 | extensions_ = new Locale(); |
263 | 0 | } |
264 | 0 | if (extensions_ == nullptr) { |
265 | 0 | status_ = U_MEMORY_ALLOCATION_ERROR; |
266 | 0 | return *this; |
267 | 0 | } |
268 | 0 | extensions_->setUnicodeKeywordValue(key, type, status_); |
269 | 0 | return *this; |
270 | 0 | } |
271 | | |
272 | | LocaleBuilder& LocaleBuilder::addUnicodeLocaleAttribute( |
273 | | StringPiece value) |
274 | 0 | { |
275 | 0 | CharString value_str(value, status_); |
276 | 0 | if (U_FAILURE(status_)) { return *this; } |
277 | 0 | transform(value_str.data(), value_str.length()); |
278 | 0 | if (!ultag_isUnicodeLocaleAttribute(value_str.data(), value_str.length())) { |
279 | 0 | status_ = U_ILLEGAL_ARGUMENT_ERROR; |
280 | 0 | return *this; |
281 | 0 | } |
282 | 0 | if (extensions_ == nullptr) { |
283 | 0 | extensions_ = new Locale(); |
284 | 0 | if (extensions_ == nullptr) { |
285 | 0 | status_ = U_MEMORY_ALLOCATION_ERROR; |
286 | 0 | return *this; |
287 | 0 | } |
288 | 0 | extensions_->setKeywordValue(kAttributeKey, value_str.data(), status_); |
289 | 0 | return *this; |
290 | 0 | } |
291 | | |
292 | 0 | CharString attributes; |
293 | 0 | CharStringByteSink sink(&attributes); |
294 | 0 | UErrorCode localErrorCode = U_ZERO_ERROR; |
295 | 0 | extensions_->getKeywordValue(kAttributeKey, sink, localErrorCode); |
296 | 0 | if (U_FAILURE(localErrorCode)) { |
297 | 0 | CharString new_attributes(value_str.data(), status_); |
298 | | // No attributes, set the attribute. |
299 | 0 | extensions_->setKeywordValue(kAttributeKey, new_attributes.data(), status_); |
300 | 0 | return *this; |
301 | 0 | } |
302 | | |
303 | 0 | transform(attributes.data(),attributes.length()); |
304 | 0 | const char* start = attributes.data(); |
305 | 0 | const char* limit = attributes.data() + attributes.length(); |
306 | 0 | CharString new_attributes; |
307 | 0 | bool inserted = false; |
308 | 0 | while (start < limit) { |
309 | 0 | if (!inserted) { |
310 | 0 | int cmp = uprv_strcmp(start, value_str.data()); |
311 | 0 | if (cmp == 0) { return *this; } // Found it in attributes: Just return |
312 | 0 | if (cmp > 0) { |
313 | 0 | if (!new_attributes.isEmpty()) new_attributes.append('_', status_); |
314 | 0 | new_attributes.append(value_str.data(), status_); |
315 | 0 | inserted = true; |
316 | 0 | } |
317 | 0 | } |
318 | 0 | if (!new_attributes.isEmpty()) { |
319 | 0 | new_attributes.append('_', status_); |
320 | 0 | } |
321 | 0 | new_attributes.append(start, status_); |
322 | 0 | start += uprv_strlen(start) + 1; |
323 | 0 | } |
324 | 0 | if (!inserted) { |
325 | 0 | if (!new_attributes.isEmpty()) { |
326 | 0 | new_attributes.append('_', status_); |
327 | 0 | } |
328 | 0 | new_attributes.append(value_str.data(), status_); |
329 | 0 | } |
330 | | // Not yet in the attributes, set the attribute. |
331 | 0 | extensions_->setKeywordValue(kAttributeKey, new_attributes.data(), status_); |
332 | 0 | return *this; |
333 | 0 | } |
334 | | |
335 | | LocaleBuilder& LocaleBuilder::removeUnicodeLocaleAttribute( |
336 | | StringPiece value) |
337 | 0 | { |
338 | 0 | CharString value_str(value, status_); |
339 | 0 | if (U_FAILURE(status_)) { return *this; } |
340 | 0 | transform(value_str.data(), value_str.length()); |
341 | 0 | if (!ultag_isUnicodeLocaleAttribute(value_str.data(), value_str.length())) { |
342 | 0 | status_ = U_ILLEGAL_ARGUMENT_ERROR; |
343 | 0 | return *this; |
344 | 0 | } |
345 | 0 | if (extensions_ == nullptr) { return *this; } |
346 | 0 | UErrorCode localErrorCode = U_ZERO_ERROR; |
347 | 0 | CharString attributes; |
348 | 0 | CharStringByteSink sink(&attributes); |
349 | 0 | extensions_->getKeywordValue(kAttributeKey, sink, localErrorCode); |
350 | | // get failure, just return |
351 | 0 | if (U_FAILURE(localErrorCode)) { return *this; } |
352 | | // Do not have any attributes, just return. |
353 | 0 | if (attributes.isEmpty()) { return *this; } |
354 | | |
355 | 0 | char* p = attributes.data(); |
356 | | // Replace null terminiator in place for _ and - so later |
357 | | // we can use uprv_strcmp to compare. |
358 | 0 | for (int32_t i = 0; i < attributes.length(); i++, p++) { |
359 | 0 | *p = (*p == '_' || *p == '-') ? '\0' : uprv_tolower(*p); |
360 | 0 | } |
361 | |
|
362 | 0 | const char* start = attributes.data(); |
363 | 0 | const char* limit = attributes.data() + attributes.length(); |
364 | 0 | CharString new_attributes; |
365 | 0 | bool found = false; |
366 | 0 | while (start < limit) { |
367 | 0 | if (uprv_strcmp(start, value_str.data()) == 0) { |
368 | 0 | found = true; |
369 | 0 | } else { |
370 | 0 | if (!new_attributes.isEmpty()) { |
371 | 0 | new_attributes.append('_', status_); |
372 | 0 | } |
373 | 0 | new_attributes.append(start, status_); |
374 | 0 | } |
375 | 0 | start += uprv_strlen(start) + 1; |
376 | 0 | } |
377 | | // Found the value in attributes, set the attribute. |
378 | 0 | if (found) { |
379 | 0 | extensions_->setKeywordValue(kAttributeKey, new_attributes.data(), status_); |
380 | 0 | } |
381 | 0 | return *this; |
382 | 0 | } |
383 | | |
384 | | LocaleBuilder& LocaleBuilder::clear() |
385 | 0 | { |
386 | 0 | status_ = U_ZERO_ERROR; |
387 | 0 | language_[0] = 0; |
388 | 0 | script_[0] = 0; |
389 | 0 | region_[0] = 0; |
390 | 0 | delete variant_; |
391 | 0 | variant_ = nullptr; |
392 | 0 | clearExtensions(); |
393 | 0 | return *this; |
394 | 0 | } |
395 | | |
396 | | LocaleBuilder& LocaleBuilder::clearExtensions() |
397 | 0 | { |
398 | 0 | delete extensions_; |
399 | 0 | extensions_ = nullptr; |
400 | 0 | return *this; |
401 | 0 | } |
402 | | |
403 | 0 | Locale makeBogusLocale() { |
404 | 0 | Locale bogus; |
405 | 0 | bogus.setToBogus(); |
406 | 0 | return bogus; |
407 | 0 | } |
408 | | |
409 | | void LocaleBuilder::copyExtensionsFrom(const Locale& src, UErrorCode& errorCode) |
410 | 0 | { |
411 | 0 | if (U_FAILURE(errorCode)) { return; } |
412 | 0 | LocalPointer<icu::StringEnumeration> keywords(src.createKeywords(errorCode)); |
413 | 0 | if (U_FAILURE(errorCode) || keywords.isNull() || keywords->count(errorCode) == 0) { |
414 | | // Error, or no extensions to copy. |
415 | 0 | return; |
416 | 0 | } |
417 | 0 | if (extensions_ == nullptr) { |
418 | 0 | extensions_ = new Locale(); |
419 | 0 | if (extensions_ == nullptr) { |
420 | 0 | status_ = U_MEMORY_ALLOCATION_ERROR; |
421 | 0 | return; |
422 | 0 | } |
423 | 0 | } |
424 | 0 | _copyExtensions(src, keywords.getAlias(), *extensions_, false, errorCode); |
425 | 0 | } |
426 | | |
427 | | Locale LocaleBuilder::build(UErrorCode& errorCode) |
428 | 0 | { |
429 | 0 | if (U_FAILURE(errorCode)) { |
430 | 0 | return makeBogusLocale(); |
431 | 0 | } |
432 | 0 | if (U_FAILURE(status_)) { |
433 | 0 | errorCode = status_; |
434 | 0 | return makeBogusLocale(); |
435 | 0 | } |
436 | 0 | CharString locale_str(language_, errorCode); |
437 | 0 | if (uprv_strlen(script_) > 0) { |
438 | 0 | locale_str.append('-', errorCode).append(StringPiece(script_), errorCode); |
439 | 0 | } |
440 | 0 | if (uprv_strlen(region_) > 0) { |
441 | 0 | locale_str.append('-', errorCode).append(StringPiece(region_), errorCode); |
442 | 0 | } |
443 | 0 | if (variant_ != nullptr) { |
444 | 0 | locale_str.append('-', errorCode).append(StringPiece(variant_->data()), errorCode); |
445 | 0 | } |
446 | 0 | if (U_FAILURE(errorCode)) { |
447 | 0 | return makeBogusLocale(); |
448 | 0 | } |
449 | 0 | Locale product(locale_str.data()); |
450 | 0 | if (extensions_ != nullptr) { |
451 | 0 | _copyExtensions(*extensions_, nullptr, product, true, errorCode); |
452 | 0 | } |
453 | 0 | if (U_FAILURE(errorCode)) { |
454 | 0 | return makeBogusLocale(); |
455 | 0 | } |
456 | 0 | return product; |
457 | 0 | } |
458 | | |
459 | 0 | UBool LocaleBuilder::copyErrorTo(UErrorCode &outErrorCode) const { |
460 | 0 | if (U_FAILURE(outErrorCode)) { |
461 | | // Do not overwrite the older error code |
462 | 0 | return TRUE; |
463 | 0 | } |
464 | 0 | outErrorCode = status_; |
465 | 0 | return U_FAILURE(outErrorCode); |
466 | 0 | } |
467 | | |
468 | | U_NAMESPACE_END |