| // © 2019 and later: Unicode, Inc. and others. |
| // License & terms of use: http://www.unicode.org/copyright.html |
| |
| #include <utility> |
| |
| #include "bytesinkutil.h" // CharStringByteSink |
| #include "charstr.h" |
| #include "cstring.h" |
| #include "ulocimp.h" |
| #include "unicode/localebuilder.h" |
| #include "unicode/locid.h" |
| |
| U_NAMESPACE_BEGIN |
| |
| #define UPRV_ISDIGIT(c) (((c) >= '0') && ((c) <= '9')) |
| #define UPRV_ISALPHANUM(c) (uprv_isASCIILetter(c) || UPRV_ISDIGIT(c) ) |
| |
| const char* kAttributeKey = "attribute"; |
| |
| static bool _isExtensionSubtags(char key, const char* s, int32_t len) { |
| switch (uprv_tolower(key)) { |
| case 'u': |
| return ultag_isUnicodeExtensionSubtags(s, len); |
| case 't': |
| return ultag_isTransformedExtensionSubtags(s, len); |
| case 'x': |
| return ultag_isPrivateuseValueSubtags(s, len); |
| default: |
| return ultag_isExtensionSubtags(s, len); |
| } |
| } |
| |
| LocaleBuilder::LocaleBuilder() : UObject(), status_(U_ZERO_ERROR), language_(), |
| script_(), region_(), variant_(nullptr), extensions_(nullptr) |
| { |
| language_[0] = 0; |
| script_[0] = 0; |
| region_[0] = 0; |
| } |
| |
| LocaleBuilder::~LocaleBuilder() |
| { |
| delete variant_; |
| delete extensions_; |
| } |
| |
| LocaleBuilder& LocaleBuilder::setLocale(const Locale& locale) |
| { |
| clear(); |
| setLanguage(locale.getLanguage()); |
| setScript(locale.getScript()); |
| setRegion(locale.getCountry()); |
| setVariant(locale.getVariant()); |
| extensions_ = locale.clone(); |
| if (extensions_ == nullptr) { |
| status_ = U_MEMORY_ALLOCATION_ERROR; |
| } |
| return *this; |
| } |
| |
| LocaleBuilder& LocaleBuilder::setLanguageTag(StringPiece tag) |
| { |
| Locale l = Locale::forLanguageTag(tag, status_); |
| if (U_FAILURE(status_)) { return *this; } |
| // Because setLocale will reset status_ we need to return |
| // first if we have error in forLanguageTag. |
| setLocale(l); |
| return *this; |
| } |
| |
| static void setField(StringPiece input, char* dest, UErrorCode& errorCode, |
| UBool (*test)(const char*, int32_t)) { |
| if (U_FAILURE(errorCode)) { return; } |
| if (input.empty()) { |
| dest[0] = '\0'; |
| } else if (test(input.data(), input.length())) { |
| uprv_memcpy(dest, input.data(), input.length()); |
| dest[input.length()] = '\0'; |
| } else { |
| errorCode = U_ILLEGAL_ARGUMENT_ERROR; |
| } |
| } |
| |
| LocaleBuilder& LocaleBuilder::setLanguage(StringPiece language) |
| { |
| setField(language, language_, status_, &ultag_isLanguageSubtag); |
| return *this; |
| } |
| |
| LocaleBuilder& LocaleBuilder::setScript(StringPiece script) |
| { |
| setField(script, script_, status_, &ultag_isScriptSubtag); |
| return *this; |
| } |
| |
| LocaleBuilder& LocaleBuilder::setRegion(StringPiece region) |
| { |
| setField(region, region_, status_, &ultag_isRegionSubtag); |
| return *this; |
| } |
| |
| static void transform(char* data, int32_t len) { |
| for (int32_t i = 0; i < len; i++, data++) { |
| if (*data == '_') { |
| *data = '-'; |
| } else { |
| *data = uprv_tolower(*data); |
| } |
| } |
| } |
| |
| LocaleBuilder& LocaleBuilder::setVariant(StringPiece variant) |
| { |
| if (U_FAILURE(status_)) { return *this; } |
| if (variant.empty()) { |
| delete variant_; |
| variant_ = nullptr; |
| return *this; |
| } |
| CharString* new_variant = new CharString(variant, status_); |
| if (U_FAILURE(status_)) { return *this; } |
| if (new_variant == nullptr) { |
| status_ = U_MEMORY_ALLOCATION_ERROR; |
| return *this; |
| } |
| transform(new_variant->data(), new_variant->length()); |
| if (!ultag_isVariantSubtags(new_variant->data(), new_variant->length())) { |
| delete new_variant; |
| status_ = U_ILLEGAL_ARGUMENT_ERROR; |
| return *this; |
| } |
| delete variant_; |
| variant_ = new_variant; |
| return *this; |
| } |
| |
| static bool |
| _isKeywordValue(const char* key, const char* value, int32_t value_len) |
| { |
| if (key[1] == '\0') { |
| // one char key |
| return (UPRV_ISALPHANUM(uprv_tolower(key[0])) && |
| _isExtensionSubtags(key[0], value, value_len)); |
| } else if (uprv_strcmp(key, kAttributeKey) == 0) { |
| // unicode attributes |
| return ultag_isUnicodeLocaleAttributes(value, value_len); |
| } |
| // otherwise: unicode extension value |
| // We need to convert from legacy key/value to unicode |
| // key/value |
| const char* unicode_locale_key = uloc_toUnicodeLocaleKey(key); |
| const char* unicode_locale_type = uloc_toUnicodeLocaleType(key, value); |
| |
| return unicode_locale_key && unicode_locale_type && |
| ultag_isUnicodeLocaleKey(unicode_locale_key, -1) && |
| ultag_isUnicodeLocaleType(unicode_locale_type, -1); |
| } |
| |
| static void |
| _copyExtensions(const Locale& from, icu::StringEnumeration *keywords, |
| Locale& to, bool validate, UErrorCode& errorCode) |
| { |
| if (U_FAILURE(errorCode)) { return; } |
| LocalPointer<icu::StringEnumeration> ownedKeywords; |
| if (keywords == nullptr) { |
| ownedKeywords.adoptInstead(from.createKeywords(errorCode)); |
| if (U_FAILURE(errorCode) || ownedKeywords.isNull()) { return; } |
| keywords = ownedKeywords.getAlias(); |
| } |
| const char* key; |
| while ((key = keywords->next(nullptr, errorCode)) != nullptr) { |
| CharString value; |
| CharStringByteSink sink(&value); |
| from.getKeywordValue(key, sink, errorCode); |
| if (U_FAILURE(errorCode)) { return; } |
| if (uprv_strcmp(key, kAttributeKey) == 0) { |
| transform(value.data(), value.length()); |
| } |
| if (validate && |
| !_isKeywordValue(key, value.data(), value.length())) { |
| errorCode = U_ILLEGAL_ARGUMENT_ERROR; |
| return; |
| } |
| to.setKeywordValue(key, value.data(), errorCode); |
| if (U_FAILURE(errorCode)) { return; } |
| } |
| } |
| |
| void static |
| _clearUAttributesAndKeyType(Locale& locale, UErrorCode& errorCode) |
| { |
| // Clear Unicode attributes |
| locale.setKeywordValue(kAttributeKey, "", errorCode); |
| |
| // Clear all Unicode keyword values |
| LocalPointer<icu::StringEnumeration> iter(locale.createUnicodeKeywords(errorCode)); |
| if (U_FAILURE(errorCode) || iter.isNull()) { return; } |
| const char* key; |
| while ((key = iter->next(nullptr, errorCode)) != nullptr) { |
| locale.setUnicodeKeywordValue(key, nullptr, errorCode); |
| } |
| } |
| |
| static void |
| _setUnicodeExtensions(Locale& locale, const CharString& value, UErrorCode& errorCode) |
| { |
| // Add the unicode extensions to extensions_ |
| CharString locale_str("und-u-", errorCode); |
| locale_str.append(value, errorCode); |
| _copyExtensions( |
| Locale::forLanguageTag(locale_str.data(), errorCode), nullptr, |
| locale, false, errorCode); |
| } |
| |
| LocaleBuilder& LocaleBuilder::setExtension(char key, StringPiece value) |
| { |
| if (U_FAILURE(status_)) { return *this; } |
| if (!UPRV_ISALPHANUM(key)) { |
| status_ = U_ILLEGAL_ARGUMENT_ERROR; |
| return *this; |
| } |
| CharString value_str(value, status_); |
| if (U_FAILURE(status_)) { return *this; } |
| transform(value_str.data(), value_str.length()); |
| if (!value_str.isEmpty() && |
| !_isExtensionSubtags(key, value_str.data(), value_str.length())) { |
| status_ = U_ILLEGAL_ARGUMENT_ERROR; |
| return *this; |
| } |
| if (extensions_ == nullptr) { |
| extensions_ = new Locale(); |
| if (extensions_ == nullptr) { |
| status_ = U_MEMORY_ALLOCATION_ERROR; |
| return *this; |
| } |
| } |
| if (uprv_tolower(key) != 'u') { |
| // for t, x and others extension. |
| extensions_->setKeywordValue(StringPiece(&key, 1), value_str.data(), |
| status_); |
| return *this; |
| } |
| _clearUAttributesAndKeyType(*extensions_, status_); |
| if (U_FAILURE(status_)) { return *this; } |
| if (!value.empty()) { |
| _setUnicodeExtensions(*extensions_, value_str, status_); |
| } |
| return *this; |
| } |
| |
| LocaleBuilder& LocaleBuilder::setUnicodeLocaleKeyword( |
| StringPiece key, StringPiece type) |
| { |
| if (U_FAILURE(status_)) { return *this; } |
| if (!ultag_isUnicodeLocaleKey(key.data(), key.length()) || |
| (!type.empty() && |
| !ultag_isUnicodeLocaleType(type.data(), type.length()))) { |
| status_ = U_ILLEGAL_ARGUMENT_ERROR; |
| return *this; |
| } |
| if (extensions_ == nullptr) { |
| extensions_ = new Locale(); |
| } |
| if (extensions_ == nullptr) { |
| status_ = U_MEMORY_ALLOCATION_ERROR; |
| return *this; |
| } |
| extensions_->setUnicodeKeywordValue(key, type, status_); |
| return *this; |
| } |
| |
| LocaleBuilder& LocaleBuilder::addUnicodeLocaleAttribute( |
| StringPiece value) |
| { |
| CharString value_str(value, status_); |
| if (U_FAILURE(status_)) { return *this; } |
| transform(value_str.data(), value_str.length()); |
| if (!ultag_isUnicodeLocaleAttribute(value_str.data(), value_str.length())) { |
| status_ = U_ILLEGAL_ARGUMENT_ERROR; |
| return *this; |
| } |
| if (extensions_ == nullptr) { |
| extensions_ = new Locale(); |
| if (extensions_ == nullptr) { |
| status_ = U_MEMORY_ALLOCATION_ERROR; |
| return *this; |
| } |
| extensions_->setKeywordValue(kAttributeKey, value_str.data(), status_); |
| return *this; |
| } |
| |
| CharString attributes; |
| CharStringByteSink sink(&attributes); |
| UErrorCode localErrorCode = U_ZERO_ERROR; |
| extensions_->getKeywordValue(kAttributeKey, sink, localErrorCode); |
| if (U_FAILURE(localErrorCode)) { |
| CharString new_attributes(value_str.data(), status_); |
| // No attributes, set the attribute. |
| extensions_->setKeywordValue(kAttributeKey, new_attributes.data(), status_); |
| return *this; |
| } |
| |
| transform(attributes.data(),attributes.length()); |
| const char* start = attributes.data(); |
| const char* limit = attributes.data() + attributes.length(); |
| CharString new_attributes; |
| bool inserted = false; |
| while (start < limit) { |
| if (!inserted) { |
| int cmp = uprv_strcmp(start, value_str.data()); |
| if (cmp == 0) { return *this; } // Found it in attributes: Just return |
| if (cmp > 0) { |
| if (!new_attributes.isEmpty()) new_attributes.append('_', status_); |
| new_attributes.append(value_str.data(), status_); |
| inserted = true; |
| } |
| } |
| if (!new_attributes.isEmpty()) { |
| new_attributes.append('_', status_); |
| } |
| new_attributes.append(start, status_); |
| start += uprv_strlen(start) + 1; |
| } |
| if (!inserted) { |
| if (!new_attributes.isEmpty()) { |
| new_attributes.append('_', status_); |
| } |
| new_attributes.append(value_str.data(), status_); |
| } |
| // Not yet in the attributes, set the attribute. |
| extensions_->setKeywordValue(kAttributeKey, new_attributes.data(), status_); |
| return *this; |
| } |
| |
| LocaleBuilder& LocaleBuilder::removeUnicodeLocaleAttribute( |
| StringPiece value) |
| { |
| CharString value_str(value, status_); |
| if (U_FAILURE(status_)) { return *this; } |
| transform(value_str.data(), value_str.length()); |
| if (!ultag_isUnicodeLocaleAttribute(value_str.data(), value_str.length())) { |
| status_ = U_ILLEGAL_ARGUMENT_ERROR; |
| return *this; |
| } |
| if (extensions_ == nullptr) { return *this; } |
| UErrorCode localErrorCode = U_ZERO_ERROR; |
| CharString attributes; |
| CharStringByteSink sink(&attributes); |
| extensions_->getKeywordValue(kAttributeKey, sink, localErrorCode); |
| // get failure, just return |
| if (U_FAILURE(localErrorCode)) { return *this; } |
| // Do not have any attributes, just return. |
| if (attributes.isEmpty()) { return *this; } |
| |
| char* p = attributes.data(); |
| // Replace null terminiator in place for _ and - so later |
| // we can use uprv_strcmp to compare. |
| for (int32_t i = 0; i < attributes.length(); i++, p++) { |
| *p = (*p == '_' || *p == '-') ? '\0' : uprv_tolower(*p); |
| } |
| |
| const char* start = attributes.data(); |
| const char* limit = attributes.data() + attributes.length(); |
| CharString new_attributes; |
| bool found = false; |
| while (start < limit) { |
| if (uprv_strcmp(start, value_str.data()) == 0) { |
| found = true; |
| } else { |
| if (!new_attributes.isEmpty()) { |
| new_attributes.append('_', status_); |
| } |
| new_attributes.append(start, status_); |
| } |
| start += uprv_strlen(start) + 1; |
| } |
| // Found the value in attributes, set the attribute. |
| if (found) { |
| extensions_->setKeywordValue(kAttributeKey, new_attributes.data(), status_); |
| } |
| return *this; |
| } |
| |
| LocaleBuilder& LocaleBuilder::clear() |
| { |
| status_ = U_ZERO_ERROR; |
| language_[0] = 0; |
| script_[0] = 0; |
| region_[0] = 0; |
| delete variant_; |
| variant_ = nullptr; |
| clearExtensions(); |
| return *this; |
| } |
| |
| LocaleBuilder& LocaleBuilder::clearExtensions() |
| { |
| delete extensions_; |
| extensions_ = nullptr; |
| return *this; |
| } |
| |
| Locale makeBogusLocale() { |
| Locale bogus; |
| bogus.setToBogus(); |
| return bogus; |
| } |
| |
| void LocaleBuilder::copyExtensionsFrom(const Locale& src, UErrorCode& errorCode) |
| { |
| if (U_FAILURE(errorCode)) { return; } |
| LocalPointer<icu::StringEnumeration> keywords(src.createKeywords(errorCode)); |
| if (U_FAILURE(errorCode) || keywords.isNull() || keywords->count(errorCode) == 0) { |
| // Error, or no extensions to copy. |
| return; |
| } |
| if (extensions_ == nullptr) { |
| extensions_ = new Locale(); |
| if (extensions_ == nullptr) { |
| status_ = U_MEMORY_ALLOCATION_ERROR; |
| return; |
| } |
| } |
| _copyExtensions(src, keywords.getAlias(), *extensions_, false, errorCode); |
| } |
| |
| Locale LocaleBuilder::build(UErrorCode& errorCode) |
| { |
| if (U_FAILURE(errorCode)) { |
| return makeBogusLocale(); |
| } |
| if (U_FAILURE(status_)) { |
| errorCode = status_; |
| return makeBogusLocale(); |
| } |
| CharString locale_str(language_, errorCode); |
| if (uprv_strlen(script_) > 0) { |
| locale_str.append('-', errorCode).append(StringPiece(script_), errorCode); |
| } |
| if (uprv_strlen(region_) > 0) { |
| locale_str.append('-', errorCode).append(StringPiece(region_), errorCode); |
| } |
| if (variant_ != nullptr) { |
| locale_str.append('-', errorCode).append(StringPiece(variant_->data()), errorCode); |
| } |
| if (U_FAILURE(errorCode)) { |
| return makeBogusLocale(); |
| } |
| Locale product(locale_str.data()); |
| if (extensions_ != nullptr) { |
| _copyExtensions(*extensions_, nullptr, product, true, errorCode); |
| } |
| if (U_FAILURE(errorCode)) { |
| return makeBogusLocale(); |
| } |
| return product; |
| } |
| |
| UBool LocaleBuilder::copyErrorTo(UErrorCode &outErrorCode) const { |
| if (U_FAILURE(outErrorCode)) { |
| // Do not overwrite the older error code |
| return TRUE; |
| } |
| outErrorCode = status_; |
| return U_FAILURE(outErrorCode); |
| } |
| |
| U_NAMESPACE_END |