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