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