| // © 2016 and later: Unicode, Inc. and others. |
| // License & terms of use: http://www.unicode.org/copyright.html |
| /* |
| ******************************************************************************* |
| * |
| * Copyright (C) 1997-2016, International Business Machines |
| * Corporation and others. All Rights Reserved. |
| * |
| ******************************************************************************* |
| * file name: loclikely.cpp |
| * encoding: UTF-8 |
| * tab size: 8 (not used) |
| * indentation:4 |
| * |
| * created on: 2010feb25 |
| * created by: Markus W. Scherer |
| * |
| * Code for likely and minimized locale subtags, separated out from other .cpp files |
| * that then do not depend on resource bundle code and likely-subtags data. |
| */ |
| |
| #include <utility> |
| |
| #include "unicode/bytestream.h" |
| #include "unicode/utypes.h" |
| #include "unicode/locid.h" |
| #include "unicode/putil.h" |
| #include "unicode/uchar.h" |
| #include "unicode/uloc.h" |
| #include "unicode/ures.h" |
| #include "unicode/uscript.h" |
| #include "bytesinkutil.h" |
| #include "charstr.h" |
| #include "cmemory.h" |
| #include "cstring.h" |
| #include "loclikelysubtags.h" |
| #include "ulocimp.h" |
| #include "ustr_imp.h" |
| |
| /** |
| * Create a tag string from the supplied parameters. The lang, script and region |
| * parameters may be nullptr pointers. If they are, their corresponding length parameters |
| * must be less than or equal to 0. |
| * |
| * If an illegal argument is provided, the function returns the error |
| * U_ILLEGAL_ARGUMENT_ERROR. |
| * |
| * @param lang The language tag to use. |
| * @param langLength The length of the language tag. |
| * @param script The script tag to use. |
| * @param scriptLength The length of the script tag. |
| * @param region The region tag to use. |
| * @param regionLength The length of the region tag. |
| * @param trailing Any trailing data to append to the new tag. |
| * @param trailingLength The length of the trailing data. |
| * @param sink The output sink receiving the tag string. |
| * @param err A pointer to a UErrorCode for error reporting. |
| **/ |
| static void U_CALLCONV |
| createTagStringWithAlternates( |
| const char* lang, |
| int32_t langLength, |
| const char* script, |
| int32_t scriptLength, |
| const char* region, |
| int32_t regionLength, |
| const char* trailing, |
| int32_t trailingLength, |
| icu::ByteSink& sink, |
| UErrorCode* err) { |
| if (U_FAILURE(*err)) { |
| return; |
| } |
| |
| if (langLength >= ULOC_LANG_CAPACITY || |
| scriptLength >= ULOC_SCRIPT_CAPACITY || |
| regionLength >= ULOC_COUNTRY_CAPACITY) { |
| *err = U_ILLEGAL_ARGUMENT_ERROR; |
| return; |
| } |
| |
| UBool regionAppended = false; |
| |
| if (langLength > 0) { |
| sink.Append(lang, langLength); |
| } |
| |
| if (scriptLength > 0) { |
| sink.Append("_", 1); |
| sink.Append(script, scriptLength); |
| } |
| |
| if (regionLength > 0) { |
| sink.Append("_", 1); |
| sink.Append(region, regionLength); |
| |
| regionAppended = true; |
| } |
| |
| if (trailingLength > 0) { |
| if (*trailing != '@') { |
| sink.Append("_", 1); |
| if (!regionAppended) { |
| /* extra separator is required */ |
| sink.Append("_", 1); |
| } |
| } |
| |
| /* |
| * Copy the trailing data into the supplied buffer. |
| */ |
| sink.Append(trailing, trailingLength); |
| } |
| } |
| |
| /** |
| * Parse the language, script, and region subtags from a tag string, and copy the |
| * results into the corresponding output parameters. |
| * |
| * If an illegal argument is provided, the function returns the error |
| * U_ILLEGAL_ARGUMENT_ERROR. |
| * |
| * @param localeID The locale ID to parse. |
| * @param lang The language tag buffer. |
| * @param script The script tag buffer. |
| * @param region The region tag buffer. |
| * @param err A pointer to a UErrorCode for error reporting. |
| * @return The number of chars of the localeID parameter consumed. |
| **/ |
| static int32_t U_CALLCONV |
| parseTagString( |
| const char* localeID, |
| icu::CharString& lang, |
| icu::CharString& script, |
| icu::CharString& region, |
| UErrorCode* err) |
| { |
| if (U_FAILURE(*err)) { |
| return 0; |
| } |
| |
| if (localeID == nullptr) { |
| *err = U_ILLEGAL_ARGUMENT_ERROR; |
| return 0; |
| } |
| |
| const char* position = localeID; |
| |
| { |
| icu::CharString variant; |
| ulocimp_getSubtags(localeID, &lang, &script, ®ion, &variant, &position, *err); |
| |
| if (U_FAILURE(*err)) { |
| return 0; |
| } |
| |
| if (!variant.isEmpty()) { |
| position -= 1 + variant.length(); |
| } |
| } |
| |
| if (_isIDSeparator(*position)) { |
| ++position; |
| } |
| |
| if (region.isEmpty() && *position != 0 && *position != '@') { |
| /* back up over consumed trailing separator */ |
| --position; |
| } |
| |
| return (int32_t)(position - localeID); |
| } |
| |
| namespace { |
| inline bool CHECK_TRAILING_VARIANT_SIZE(const char* trailing, int32_t trailingLength) { |
| int32_t count = 0; |
| int32_t i; |
| for (i = 0; i < trailingLength; i++) { |
| if (trailing[i] == '-' || trailing[i] == '_') { |
| count = 0; |
| if (count > 8) { |
| return false; |
| } |
| } else if (trailing[i] == '@') { |
| break; |
| } else if (count > 8) { |
| return false; |
| } else { |
| count++; |
| } |
| } |
| return true; |
| } |
| } // namespace |
| |
| static UBool |
| _uloc_addLikelySubtags(const char* localeID, |
| icu::ByteSink& sink, |
| UErrorCode* err) { |
| if (U_FAILURE(*err)) { |
| return false; |
| } |
| |
| if (localeID == nullptr) { |
| *err = U_ILLEGAL_ARGUMENT_ERROR; |
| return false; |
| } |
| |
| icu::CharString lang; |
| icu::CharString script; |
| icu::CharString region; |
| int32_t trailingIndex = parseTagString(localeID, lang, script, region, err); |
| if (U_FAILURE(*err)) { |
| return false; |
| } |
| |
| if (lang.length() > 3) { |
| if (lang.length() == 4 && script.isEmpty()) { |
| script = std::move(lang); |
| lang.clear(); |
| } else { |
| *err = U_ILLEGAL_ARGUMENT_ERROR; |
| return false; |
| } |
| } |
| |
| /* Find the length of the trailing portion. */ |
| while (_isIDSeparator(localeID[trailingIndex])) { |
| trailingIndex++; |
| } |
| const char* trailing = &localeID[trailingIndex]; |
| int32_t trailingLength = (int32_t)uprv_strlen(trailing); |
| |
| if (!CHECK_TRAILING_VARIANT_SIZE(trailing, trailingLength)) { |
| *err = U_ILLEGAL_ARGUMENT_ERROR; |
| return false; |
| } |
| |
| const icu::LikelySubtags* likelySubtags = icu::LikelySubtags::getSingleton(*err); |
| if (U_FAILURE(*err)) { |
| return false; |
| } |
| // We need to keep l on the stack because lsr may point into internal |
| // memory of l. |
| icu::Locale l = icu::Locale::createFromName(localeID); |
| if (l.isBogus()) { |
| *err = U_ILLEGAL_ARGUMENT_ERROR; |
| return false; |
| } |
| icu::LSR lsr = likelySubtags->makeMaximizedLsrFrom(l, true, *err); |
| if (U_FAILURE(*err)) { |
| return false; |
| } |
| const char* language = lsr.language; |
| if (uprv_strcmp(language, "und") == 0) { |
| language = ""; |
| } |
| createTagStringWithAlternates( |
| language, |
| (int32_t)uprv_strlen(language), |
| lsr.script, |
| (int32_t)uprv_strlen(lsr.script), |
| lsr.region, |
| (int32_t)uprv_strlen(lsr.region), |
| trailing, |
| trailingLength, |
| sink, |
| err); |
| |
| return U_SUCCESS(*err); |
| } |
| |
| // Add likely subtags to the sink |
| // return true if the value in the sink is produced by a match during the lookup |
| // return false if the value in the sink is the same as input because there are |
| // no match after the lookup. |
| static UBool _ulocimp_addLikelySubtags(const char*, icu::ByteSink&, UErrorCode*); |
| |
| static void |
| _uloc_minimizeSubtags(const char* localeID, |
| icu::ByteSink& sink, |
| bool favorScript, |
| UErrorCode* err) { |
| if (U_FAILURE(*err)) { |
| return; |
| } |
| |
| if (localeID == nullptr) { |
| *err = U_ILLEGAL_ARGUMENT_ERROR; |
| return; |
| } |
| |
| icu::CharString lang; |
| icu::CharString script; |
| icu::CharString region; |
| int32_t trailingIndex = parseTagString(localeID, lang, script, region, err); |
| if (U_FAILURE(*err)) { |
| return; |
| } |
| |
| /* Find the spot where the variants or the keywords begin, if any. */ |
| while (_isIDSeparator(localeID[trailingIndex])) { |
| trailingIndex++; |
| } |
| const char* trailing = &localeID[trailingIndex]; |
| int32_t trailingLength = (int32_t)uprv_strlen(trailing); |
| |
| if (!CHECK_TRAILING_VARIANT_SIZE(trailing, trailingLength)) { |
| *err = U_ILLEGAL_ARGUMENT_ERROR; |
| return; |
| } |
| |
| const icu::LikelySubtags* likelySubtags = icu::LikelySubtags::getSingleton(*err); |
| if (U_FAILURE(*err)) { |
| return; |
| } |
| icu::LSR lsr = likelySubtags->minimizeSubtags( |
| lang.toStringPiece(), |
| script.toStringPiece(), |
| region.toStringPiece(), |
| favorScript, |
| *err); |
| if (U_FAILURE(*err)) { |
| return; |
| } |
| const char* language = lsr.language; |
| if (uprv_strcmp(language, "und") == 0) { |
| language = ""; |
| } |
| createTagStringWithAlternates( |
| language, |
| (int32_t)uprv_strlen(language), |
| lsr.script, |
| (int32_t)uprv_strlen(lsr.script), |
| lsr.region, |
| (int32_t)uprv_strlen(lsr.region), |
| trailing, |
| trailingLength, |
| sink, |
| err); |
| } |
| |
| U_CAPI int32_t U_EXPORT2 |
| uloc_addLikelySubtags(const char* localeID, |
| char* maximizedLocaleID, |
| int32_t maximizedLocaleIDCapacity, |
| UErrorCode* status) { |
| if (U_FAILURE(*status)) { |
| return 0; |
| } |
| |
| icu::CheckedArrayByteSink sink( |
| maximizedLocaleID, maximizedLocaleIDCapacity); |
| |
| ulocimp_addLikelySubtags(localeID, sink, status); |
| int32_t reslen = sink.NumberOfBytesAppended(); |
| |
| if (U_FAILURE(*status)) { |
| return sink.Overflowed() ? reslen : -1; |
| } |
| |
| if (sink.Overflowed()) { |
| *status = U_BUFFER_OVERFLOW_ERROR; |
| } else { |
| u_terminateChars( |
| maximizedLocaleID, maximizedLocaleIDCapacity, reslen, status); |
| } |
| |
| return reslen; |
| } |
| |
| static UBool |
| _ulocimp_addLikelySubtags(const char* localeID, |
| icu::ByteSink& sink, |
| UErrorCode* status) { |
| icu::CharString localeBuffer; |
| { |
| icu::CharStringByteSink localeSink(&localeBuffer); |
| ulocimp_canonicalize(localeID, localeSink, status); |
| } |
| if (U_SUCCESS(*status)) { |
| return _uloc_addLikelySubtags(localeBuffer.data(), sink, status); |
| } else { |
| return false; |
| } |
| } |
| |
| U_CAPI void U_EXPORT2 |
| ulocimp_addLikelySubtags(const char* localeID, |
| icu::ByteSink& sink, |
| UErrorCode* status) { |
| _ulocimp_addLikelySubtags(localeID, sink, status); |
| } |
| |
| U_CAPI int32_t U_EXPORT2 |
| uloc_minimizeSubtags(const char* localeID, |
| char* minimizedLocaleID, |
| int32_t minimizedLocaleIDCapacity, |
| UErrorCode* status) { |
| if (U_FAILURE(*status)) { |
| return 0; |
| } |
| |
| icu::CheckedArrayByteSink sink( |
| minimizedLocaleID, minimizedLocaleIDCapacity); |
| |
| ulocimp_minimizeSubtags(localeID, sink, false, status); |
| int32_t reslen = sink.NumberOfBytesAppended(); |
| |
| if (U_FAILURE(*status)) { |
| return sink.Overflowed() ? reslen : -1; |
| } |
| |
| if (sink.Overflowed()) { |
| *status = U_BUFFER_OVERFLOW_ERROR; |
| } else { |
| u_terminateChars( |
| minimizedLocaleID, minimizedLocaleIDCapacity, reslen, status); |
| } |
| |
| return reslen; |
| } |
| |
| U_CAPI void U_EXPORT2 |
| ulocimp_minimizeSubtags(const char* localeID, |
| icu::ByteSink& sink, |
| bool favorScript, |
| UErrorCode* status) { |
| icu::CharString localeBuffer; |
| { |
| icu::CharStringByteSink localeSink(&localeBuffer); |
| ulocimp_canonicalize(localeID, localeSink, status); |
| } |
| _uloc_minimizeSubtags(localeBuffer.data(), sink, favorScript, status); |
| } |
| |
| // Pairs of (language subtag, + or -) for finding out fast if common languages |
| // are LTR (minus) or RTL (plus). |
| static const char LANG_DIR_STRING[] = |
| "root-en-es-pt-zh-ja-ko-de-fr-it-ar+he+fa+ru-nl-pl-th-tr-"; |
| |
| // Implemented here because this calls ulocimp_addLikelySubtags(). |
| U_CAPI UBool U_EXPORT2 |
| uloc_isRightToLeft(const char *locale) { |
| UErrorCode errorCode = U_ZERO_ERROR; |
| icu::CharString lang; |
| icu::CharString script; |
| ulocimp_getSubtags(locale, &lang, &script, nullptr, nullptr, nullptr, errorCode); |
| if (U_FAILURE(errorCode) || script.isEmpty()) { |
| // Fastpath: We know the likely scripts and their writing direction |
| // for some common languages. |
| if (!lang.isEmpty()) { |
| const char* langPtr = uprv_strstr(LANG_DIR_STRING, lang.data()); |
| if (langPtr != nullptr) { |
| switch (langPtr[lang.length()]) { |
| case '-': return false; |
| case '+': return true; |
| default: break; // partial match of a longer code |
| } |
| } |
| } |
| // Otherwise, find the likely script. |
| errorCode = U_ZERO_ERROR; |
| icu::CharString likely; |
| { |
| icu::CharStringByteSink sink(&likely); |
| ulocimp_addLikelySubtags(locale, sink, &errorCode); |
| } |
| if (U_FAILURE(errorCode)) { |
| return false; |
| } |
| ulocimp_getSubtags(likely.data(), nullptr, &script, nullptr, nullptr, nullptr, errorCode); |
| if (U_FAILURE(errorCode) || script.isEmpty()) { |
| return false; |
| } |
| } |
| UScriptCode scriptCode = (UScriptCode)u_getPropertyValueEnum(UCHAR_SCRIPT, script.data()); |
| return uscript_isRightToLeft(scriptCode); |
| } |
| |
| U_NAMESPACE_BEGIN |
| |
| UBool |
| Locale::isRightToLeft() const { |
| return uloc_isRightToLeft(getBaseName()); |
| } |
| |
| U_NAMESPACE_END |
| |
| namespace { |
| icu::CharString |
| GetRegionFromKey(const char* localeID, const char* key, UErrorCode& status) { |
| icu::CharString result; |
| |
| // First check for keyword value |
| icu::CharString kw; |
| { |
| icu::CharStringByteSink sink(&kw); |
| ulocimp_getKeywordValue(localeID, key, sink, &status); |
| } |
| int32_t len = kw.length(); |
| if (U_SUCCESS(status) && len >= 3 && len <= 7) { |
| // chop off the subdivision code (which will generally be "zzzz" anyway) |
| const char* const data = kw.data(); |
| if (uprv_isASCIILetter(data[0])) { |
| result.append(uprv_toupper(data[0]), status); |
| result.append(uprv_toupper(data[1]), status); |
| } else { |
| // assume three-digit region code |
| result.append(data, 3, status); |
| } |
| } |
| return result; |
| } |
| } // namespace |
| |
| U_EXPORT icu::CharString U_EXPORT2 |
| ulocimp_getRegionForSupplementalData(const char *localeID, UBool inferRegion, |
| UErrorCode* status) { |
| if (U_FAILURE(*status)) { |
| return {}; |
| } |
| icu::CharString rgBuf = GetRegionFromKey(localeID, "rg", *status); |
| if (U_SUCCESS(*status) && rgBuf.isEmpty()) { |
| // No valid rg keyword value, try for unicode_region_subtag |
| rgBuf = ulocimp_getRegion(localeID, *status); |
| if (U_SUCCESS(*status) && rgBuf.isEmpty() && inferRegion) { |
| // Second check for sd keyword value |
| rgBuf = GetRegionFromKey(localeID, "sd", *status); |
| if (U_SUCCESS(*status) && rgBuf.isEmpty()) { |
| // no unicode_region_subtag but inferRegion true, try likely subtags |
| UErrorCode rgStatus = U_ZERO_ERROR; |
| icu::CharString locBuf; |
| { |
| icu::CharStringByteSink sink(&locBuf); |
| ulocimp_addLikelySubtags(localeID, sink, &rgStatus); |
| } |
| if (U_SUCCESS(rgStatus)) { |
| rgBuf = ulocimp_getRegion(locBuf.data(), *status); |
| } |
| } |
| } |
| } |
| |
| return rgBuf; |
| } |