| // © 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. |
| */ |
| |
| #if defined(STARBOARD) |
| #include "starboard/client_porting/poem/string_poem.h" |
| #endif // defined(STARBOARD) |
| #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 "ulocimp.h" |
| #include "ustr_imp.h" |
| |
| /** |
| * These are the canonical strings for unknown languages, scripts and regions. |
| **/ |
| static const char* const unknownLanguage = "und"; |
| static const char* const unknownScript = "Zzzz"; |
| static const char* const unknownRegion = "ZZ"; |
| |
| /** |
| * This function looks for the localeID in the likelySubtags resource. |
| * |
| * @param localeID The tag to find. |
| * @param buffer A buffer to hold the matching entry |
| * @param bufferLength The length of the output buffer |
| * @return A pointer to "buffer" if found, or a null pointer if not. |
| */ |
| static const char* U_CALLCONV |
| findLikelySubtags(const char* localeID, |
| char* buffer, |
| int32_t bufferLength, |
| UErrorCode* err) { |
| const char* result = NULL; |
| |
| if (!U_FAILURE(*err)) { |
| int32_t resLen = 0; |
| const UChar* s = NULL; |
| UErrorCode tmpErr = U_ZERO_ERROR; |
| icu::LocalUResourceBundlePointer subtags(ures_openDirect(NULL, "likelySubtags", &tmpErr)); |
| if (U_SUCCESS(tmpErr)) { |
| icu::CharString und; |
| if (localeID != NULL) { |
| if (*localeID == '\0') { |
| localeID = unknownLanguage; |
| } else if (*localeID == '_') { |
| und.append(unknownLanguage, *err); |
| und.append(localeID, *err); |
| if (U_FAILURE(*err)) { |
| return NULL; |
| } |
| localeID = und.data(); |
| } |
| } |
| s = ures_getStringByKey(subtags.getAlias(), localeID, &resLen, &tmpErr); |
| |
| if (U_FAILURE(tmpErr)) { |
| /* |
| * If a resource is missing, it's not really an error, it's |
| * just that we don't have any data for that particular locale ID. |
| */ |
| if (tmpErr != U_MISSING_RESOURCE_ERROR) { |
| *err = tmpErr; |
| } |
| } |
| else if (resLen >= bufferLength) { |
| /* The buffer should never overflow. */ |
| *err = U_INTERNAL_PROGRAM_ERROR; |
| } |
| else { |
| u_UCharsToChars(s, buffer, resLen + 1); |
| if (resLen >= 3 && |
| uprv_strnicmp(buffer, unknownLanguage, 3) == 0 && |
| (resLen == 3 || buffer[3] == '_')) { |
| uprv_memmove(buffer, buffer + 3, resLen - 3 + 1); |
| } |
| result = buffer; |
| } |
| } else { |
| *err = tmpErr; |
| } |
| } |
| |
| return result; |
| } |
| |
| /** |
| * Append a tag to a buffer, adding the separator if necessary. The buffer |
| * must be large enough to contain the resulting tag plus any separator |
| * necessary. The tag must not be a zero-length string. |
| * |
| * @param tag The tag to add. |
| * @param tagLength The length of the tag. |
| * @param buffer The output buffer. |
| * @param bufferLength The length of the output buffer. This is an input/ouput parameter. |
| **/ |
| static void U_CALLCONV |
| appendTag( |
| const char* tag, |
| int32_t tagLength, |
| char* buffer, |
| int32_t* bufferLength, |
| UBool withSeparator) { |
| |
| if (withSeparator) { |
| buffer[*bufferLength] = '_'; |
| ++(*bufferLength); |
| } |
| |
| uprv_memmove( |
| &buffer[*bufferLength], |
| tag, |
| tagLength); |
| |
| *bufferLength += tagLength; |
| } |
| |
| /** |
| * Create a tag string from the supplied parameters. The lang, script and region |
| * parameters may be NULL pointers. If they are, their corresponding length parameters |
| * must be less than or equal to 0. |
| * |
| * If any of the language, script or region parameters are empty, and the alternateTags |
| * parameter is not NULL, it will be parsed for potential language, script and region tags |
| * to be used when constructing the new tag. If the alternateTags parameter is NULL, or |
| * it contains no language tag, the default tag for the unknown language is used. |
| * |
| * If the length of the new string exceeds the capacity of the output buffer, |
| * the function copies as many bytes to the output buffer as it can, and returns |
| * the error U_BUFFER_OVERFLOW_ERROR. |
| * |
| * If an illegal argument is provided, the function returns the error |
| * U_ILLEGAL_ARGUMENT_ERROR. |
| * |
| * Note that this function can return the warning U_STRING_NOT_TERMINATED_WARNING if |
| * the tag string fits in the output buffer, but the null terminator doesn't. |
| * |
| * @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 alternateTags A string containing any alternate tags. |
| * @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, |
| const char* alternateTags, |
| icu::ByteSink& sink, |
| UErrorCode* err) { |
| |
| if (U_FAILURE(*err)) { |
| goto error; |
| } |
| else if (langLength >= ULOC_LANG_CAPACITY || |
| scriptLength >= ULOC_SCRIPT_CAPACITY || |
| regionLength >= ULOC_COUNTRY_CAPACITY) { |
| goto error; |
| } |
| else { |
| /** |
| * ULOC_FULLNAME_CAPACITY will provide enough capacity |
| * that we can build a string that contains the language, |
| * script and region code without worrying about overrunning |
| * the user-supplied buffer. |
| **/ |
| char tagBuffer[ULOC_FULLNAME_CAPACITY]; |
| int32_t tagLength = 0; |
| UBool regionAppended = FALSE; |
| |
| if (langLength > 0) { |
| appendTag( |
| lang, |
| langLength, |
| tagBuffer, |
| &tagLength, |
| /*withSeparator=*/FALSE); |
| } |
| else if (alternateTags == NULL) { |
| /* |
| * Use the empty string for an unknown language, if |
| * we found no language. |
| */ |
| } |
| else { |
| /* |
| * Parse the alternateTags string for the language. |
| */ |
| char alternateLang[ULOC_LANG_CAPACITY]; |
| int32_t alternateLangLength = sizeof(alternateLang); |
| |
| alternateLangLength = |
| uloc_getLanguage( |
| alternateTags, |
| alternateLang, |
| alternateLangLength, |
| err); |
| if(U_FAILURE(*err) || |
| alternateLangLength >= ULOC_LANG_CAPACITY) { |
| goto error; |
| } |
| else if (alternateLangLength == 0) { |
| /* |
| * Use the empty string for an unknown language, if |
| * we found no language. |
| */ |
| } |
| else { |
| appendTag( |
| alternateLang, |
| alternateLangLength, |
| tagBuffer, |
| &tagLength, |
| /*withSeparator=*/FALSE); |
| } |
| } |
| |
| if (scriptLength > 0) { |
| appendTag( |
| script, |
| scriptLength, |
| tagBuffer, |
| &tagLength, |
| /*withSeparator=*/TRUE); |
| } |
| else if (alternateTags != NULL) { |
| /* |
| * Parse the alternateTags string for the script. |
| */ |
| char alternateScript[ULOC_SCRIPT_CAPACITY]; |
| |
| const int32_t alternateScriptLength = |
| uloc_getScript( |
| alternateTags, |
| alternateScript, |
| sizeof(alternateScript), |
| err); |
| |
| if (U_FAILURE(*err) || |
| alternateScriptLength >= ULOC_SCRIPT_CAPACITY) { |
| goto error; |
| } |
| else if (alternateScriptLength > 0) { |
| appendTag( |
| alternateScript, |
| alternateScriptLength, |
| tagBuffer, |
| &tagLength, |
| /*withSeparator=*/TRUE); |
| } |
| } |
| |
| if (regionLength > 0) { |
| appendTag( |
| region, |
| regionLength, |
| tagBuffer, |
| &tagLength, |
| /*withSeparator=*/TRUE); |
| |
| regionAppended = TRUE; |
| } |
| else if (alternateTags != NULL) { |
| /* |
| * Parse the alternateTags string for the region. |
| */ |
| char alternateRegion[ULOC_COUNTRY_CAPACITY]; |
| |
| const int32_t alternateRegionLength = |
| uloc_getCountry( |
| alternateTags, |
| alternateRegion, |
| sizeof(alternateRegion), |
| err); |
| if (U_FAILURE(*err) || |
| alternateRegionLength >= ULOC_COUNTRY_CAPACITY) { |
| goto error; |
| } |
| else if (alternateRegionLength > 0) { |
| appendTag( |
| alternateRegion, |
| alternateRegionLength, |
| tagBuffer, |
| &tagLength, |
| /*withSeparator=*/TRUE); |
| |
| regionAppended = TRUE; |
| } |
| } |
| |
| /** |
| * Copy the partial tag from our internal buffer to the supplied |
| * target. |
| **/ |
| sink.Append(tagBuffer, tagLength); |
| |
| 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); |
| } |
| |
| return; |
| } |
| |
| error: |
| |
| /** |
| * An overflow indicates the locale ID passed in |
| * is ill-formed. If we got here, and there was |
| * no previous error, it's an implicit overflow. |
| **/ |
| if (*err == U_BUFFER_OVERFLOW_ERROR || |
| U_SUCCESS(*err)) { |
| *err = U_ILLEGAL_ARGUMENT_ERROR; |
| } |
| } |
| |
| /** |
| * Create a tag string from the supplied parameters. The lang, script and region |
| * parameters may be NULL pointers. If they are, their corresponding length parameters |
| * must be less than or equal to 0. If the lang parameter is an empty string, the |
| * default value for an unknown language is written to the output buffer. |
| * |
| * If the length of the new string exceeds the capacity of the output buffer, |
| * the function copies as many bytes to the output buffer as it can, and returns |
| * the error U_BUFFER_OVERFLOW_ERROR. |
| * |
| * 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 |
| createTagString( |
| 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) |
| { |
| createTagStringWithAlternates( |
| lang, |
| langLength, |
| script, |
| scriptLength, |
| region, |
| regionLength, |
| trailing, |
| trailingLength, |
| NULL, |
| sink, |
| err); |
| } |
| |
| /** |
| * Parse the language, script, and region subtags from a tag string, and copy the |
| * results into the corresponding output parameters. The buffers are null-terminated, |
| * unless overflow occurs. |
| * |
| * The langLength, scriptLength, and regionLength parameters are input/output |
| * parameters, and must contain the capacity of their corresponding buffers on |
| * input. On output, they will contain the actual length of the buffers, not |
| * including the null terminator. |
| * |
| * If the length of any of the output subtags exceeds the capacity of the corresponding |
| * buffer, the function copies as many bytes to the output buffer as it can, and returns |
| * the error U_BUFFER_OVERFLOW_ERROR. It will not parse any more subtags once overflow |
| * occurs. |
| * |
| * 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 langLength The length of the language tag. |
| * @param script The script tag buffer. |
| * @param scriptLength The length of the script tag. |
| * @param region The region tag buffer. |
| * @param regionLength The length of the region tag. |
| * @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, |
| char* lang, |
| int32_t* langLength, |
| char* script, |
| int32_t* scriptLength, |
| char* region, |
| int32_t* regionLength, |
| UErrorCode* err) |
| { |
| const char* position = localeID; |
| int32_t subtagLength = 0; |
| |
| if(U_FAILURE(*err) || |
| localeID == NULL || |
| lang == NULL || |
| langLength == NULL || |
| script == NULL || |
| scriptLength == NULL || |
| region == NULL || |
| regionLength == NULL) { |
| goto error; |
| } |
| |
| subtagLength = ulocimp_getLanguage(position, &position, *err).extract(lang, *langLength, *err); |
| |
| /* |
| * Note that we explicit consider U_STRING_NOT_TERMINATED_WARNING |
| * to be an error, because it indicates the user-supplied tag is |
| * not well-formed. |
| */ |
| if(U_FAILURE(*err)) { |
| goto error; |
| } |
| |
| *langLength = subtagLength; |
| |
| /* |
| * If no language was present, use the empty string instead. |
| * Otherwise, move past any separator. |
| */ |
| if (_isIDSeparator(*position)) { |
| ++position; |
| } |
| |
| subtagLength = ulocimp_getScript(position, &position, *err).extract(script, *scriptLength, *err); |
| |
| if(U_FAILURE(*err)) { |
| goto error; |
| } |
| |
| *scriptLength = subtagLength; |
| |
| if (*scriptLength > 0) { |
| if (uprv_strnicmp(script, unknownScript, *scriptLength) == 0) { |
| /** |
| * If the script part is the "unknown" script, then don't return it. |
| **/ |
| *scriptLength = 0; |
| } |
| |
| /* |
| * Move past any separator. |
| */ |
| if (_isIDSeparator(*position)) { |
| ++position; |
| } |
| } |
| |
| subtagLength = ulocimp_getCountry(position, &position, *err).extract(region, *regionLength, *err); |
| |
| if(U_FAILURE(*err)) { |
| goto error; |
| } |
| |
| *regionLength = subtagLength; |
| |
| if (*regionLength > 0) { |
| if (uprv_strnicmp(region, unknownRegion, *regionLength) == 0) { |
| /** |
| * If the region part is the "unknown" region, then don't return it. |
| **/ |
| *regionLength = 0; |
| } |
| } else if (*position != 0 && *position != '@') { |
| /* back up over consumed trailing separator */ |
| --position; |
| } |
| |
| exit: |
| |
| return (int32_t)(position - localeID); |
| |
| error: |
| |
| /** |
| * If we get here, we have no explicit error, it's the result of an |
| * illegal argument. |
| **/ |
| if (!U_FAILURE(*err)) { |
| *err = U_ILLEGAL_ARGUMENT_ERROR; |
| } |
| |
| goto exit; |
| } |
| |
| static UBool U_CALLCONV |
| createLikelySubtagsString( |
| const char* lang, |
| int32_t langLength, |
| const char* script, |
| int32_t scriptLength, |
| const char* region, |
| int32_t regionLength, |
| const char* variants, |
| int32_t variantsLength, |
| icu::ByteSink& sink, |
| UErrorCode* err) { |
| /** |
| * ULOC_FULLNAME_CAPACITY will provide enough capacity |
| * that we can build a string that contains the language, |
| * script and region code without worrying about overrunning |
| * the user-supplied buffer. |
| **/ |
| char likelySubtagsBuffer[ULOC_FULLNAME_CAPACITY]; |
| |
| if(U_FAILURE(*err)) { |
| goto error; |
| } |
| |
| /** |
| * Try the language with the script and region first. |
| **/ |
| if (scriptLength > 0 && regionLength > 0) { |
| |
| const char* likelySubtags = NULL; |
| |
| icu::CharString tagBuffer; |
| { |
| icu::CharStringByteSink sink(&tagBuffer); |
| createTagString( |
| lang, |
| langLength, |
| script, |
| scriptLength, |
| region, |
| regionLength, |
| NULL, |
| 0, |
| sink, |
| err); |
| } |
| if(U_FAILURE(*err)) { |
| goto error; |
| } |
| |
| likelySubtags = |
| findLikelySubtags( |
| tagBuffer.data(), |
| likelySubtagsBuffer, |
| sizeof(likelySubtagsBuffer), |
| err); |
| if(U_FAILURE(*err)) { |
| goto error; |
| } |
| |
| if (likelySubtags != NULL) { |
| /* Always use the language tag from the |
| maximal string, since it may be more |
| specific than the one provided. */ |
| createTagStringWithAlternates( |
| NULL, |
| 0, |
| NULL, |
| 0, |
| NULL, |
| 0, |
| variants, |
| variantsLength, |
| likelySubtags, |
| sink, |
| err); |
| return TRUE; |
| } |
| } |
| |
| /** |
| * Try the language with just the script. |
| **/ |
| if (scriptLength > 0) { |
| |
| const char* likelySubtags = NULL; |
| |
| icu::CharString tagBuffer; |
| { |
| icu::CharStringByteSink sink(&tagBuffer); |
| createTagString( |
| lang, |
| langLength, |
| script, |
| scriptLength, |
| NULL, |
| 0, |
| NULL, |
| 0, |
| sink, |
| err); |
| } |
| if(U_FAILURE(*err)) { |
| goto error; |
| } |
| |
| likelySubtags = |
| findLikelySubtags( |
| tagBuffer.data(), |
| likelySubtagsBuffer, |
| sizeof(likelySubtagsBuffer), |
| err); |
| if(U_FAILURE(*err)) { |
| goto error; |
| } |
| |
| if (likelySubtags != NULL) { |
| /* Always use the language tag from the |
| maximal string, since it may be more |
| specific than the one provided. */ |
| createTagStringWithAlternates( |
| NULL, |
| 0, |
| NULL, |
| 0, |
| region, |
| regionLength, |
| variants, |
| variantsLength, |
| likelySubtags, |
| sink, |
| err); |
| return TRUE; |
| } |
| } |
| |
| /** |
| * Try the language with just the region. |
| **/ |
| if (regionLength > 0) { |
| |
| const char* likelySubtags = NULL; |
| |
| icu::CharString tagBuffer; |
| { |
| icu::CharStringByteSink sink(&tagBuffer); |
| createTagString( |
| lang, |
| langLength, |
| NULL, |
| 0, |
| region, |
| regionLength, |
| NULL, |
| 0, |
| sink, |
| err); |
| } |
| if(U_FAILURE(*err)) { |
| goto error; |
| } |
| |
| likelySubtags = |
| findLikelySubtags( |
| tagBuffer.data(), |
| likelySubtagsBuffer, |
| sizeof(likelySubtagsBuffer), |
| err); |
| if(U_FAILURE(*err)) { |
| goto error; |
| } |
| |
| if (likelySubtags != NULL) { |
| /* Always use the language tag from the |
| maximal string, since it may be more |
| specific than the one provided. */ |
| createTagStringWithAlternates( |
| NULL, |
| 0, |
| script, |
| scriptLength, |
| NULL, |
| 0, |
| variants, |
| variantsLength, |
| likelySubtags, |
| sink, |
| err); |
| return TRUE; |
| } |
| } |
| |
| /** |
| * Finally, try just the language. |
| **/ |
| { |
| const char* likelySubtags = NULL; |
| |
| icu::CharString tagBuffer; |
| { |
| icu::CharStringByteSink sink(&tagBuffer); |
| createTagString( |
| lang, |
| langLength, |
| NULL, |
| 0, |
| NULL, |
| 0, |
| NULL, |
| 0, |
| sink, |
| err); |
| } |
| if(U_FAILURE(*err)) { |
| goto error; |
| } |
| |
| likelySubtags = |
| findLikelySubtags( |
| tagBuffer.data(), |
| likelySubtagsBuffer, |
| sizeof(likelySubtagsBuffer), |
| err); |
| if(U_FAILURE(*err)) { |
| goto error; |
| } |
| |
| if (likelySubtags != NULL) { |
| /* Always use the language tag from the |
| maximal string, since it may be more |
| specific than the one provided. */ |
| createTagStringWithAlternates( |
| NULL, |
| 0, |
| script, |
| scriptLength, |
| region, |
| regionLength, |
| variants, |
| variantsLength, |
| likelySubtags, |
| sink, |
| err); |
| return TRUE; |
| } |
| } |
| |
| return FALSE; |
| |
| error: |
| |
| if (!U_FAILURE(*err)) { |
| *err = U_ILLEGAL_ARGUMENT_ERROR; |
| } |
| |
| return FALSE; |
| } |
| |
| #define CHECK_TRAILING_VARIANT_SIZE(trailing, trailingLength) UPRV_BLOCK_MACRO_BEGIN { \ |
| int32_t count = 0; \ |
| int32_t i; \ |
| for (i = 0; i < trailingLength; i++) { \ |
| if (trailing[i] == '-' || trailing[i] == '_') { \ |
| count = 0; \ |
| if (count > 8) { \ |
| goto error; \ |
| } \ |
| } else if (trailing[i] == '@') { \ |
| break; \ |
| } else if (count > 8) { \ |
| goto error; \ |
| } else { \ |
| count++; \ |
| } \ |
| } \ |
| } UPRV_BLOCK_MACRO_END |
| |
| static UBool |
| _uloc_addLikelySubtags(const char* localeID, |
| icu::ByteSink& sink, |
| UErrorCode* err) { |
| char lang[ULOC_LANG_CAPACITY]; |
| int32_t langLength = sizeof(lang); |
| char script[ULOC_SCRIPT_CAPACITY]; |
| int32_t scriptLength = sizeof(script); |
| char region[ULOC_COUNTRY_CAPACITY]; |
| int32_t regionLength = sizeof(region); |
| const char* trailing = ""; |
| int32_t trailingLength = 0; |
| int32_t trailingIndex = 0; |
| UBool success = FALSE; |
| |
| if(U_FAILURE(*err)) { |
| goto error; |
| } |
| if (localeID == NULL) { |
| goto error; |
| } |
| |
| trailingIndex = parseTagString( |
| localeID, |
| lang, |
| &langLength, |
| script, |
| &scriptLength, |
| region, |
| ®ionLength, |
| err); |
| if(U_FAILURE(*err)) { |
| /* Overflow indicates an illegal argument error */ |
| if (*err == U_BUFFER_OVERFLOW_ERROR) { |
| *err = U_ILLEGAL_ARGUMENT_ERROR; |
| } |
| |
| goto error; |
| } |
| |
| /* Find the length of the trailing portion. */ |
| while (_isIDSeparator(localeID[trailingIndex])) { |
| trailingIndex++; |
| } |
| trailing = &localeID[trailingIndex]; |
| trailingLength = (int32_t)uprv_strlen(trailing); |
| |
| CHECK_TRAILING_VARIANT_SIZE(trailing, trailingLength); |
| |
| success = |
| createLikelySubtagsString( |
| lang, |
| langLength, |
| script, |
| scriptLength, |
| region, |
| regionLength, |
| trailing, |
| trailingLength, |
| sink, |
| err); |
| |
| if (!success) { |
| const int32_t localIDLength = (int32_t)uprv_strlen(localeID); |
| |
| /* |
| * If we get here, we need to return localeID. |
| */ |
| sink.Append(localeID, localIDLength); |
| } |
| |
| return success; |
| |
| error: |
| |
| if (!U_FAILURE(*err)) { |
| *err = U_ILLEGAL_ARGUMENT_ERROR; |
| } |
| return FALSE; |
| } |
| |
| // 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, |
| UErrorCode* err) { |
| icu::CharString maximizedTagBuffer; |
| |
| char lang[ULOC_LANG_CAPACITY]; |
| int32_t langLength = sizeof(lang); |
| char script[ULOC_SCRIPT_CAPACITY]; |
| int32_t scriptLength = sizeof(script); |
| char region[ULOC_COUNTRY_CAPACITY]; |
| int32_t regionLength = sizeof(region); |
| const char* trailing = ""; |
| int32_t trailingLength = 0; |
| int32_t trailingIndex = 0; |
| UBool successGetMax = FALSE; |
| |
| if(U_FAILURE(*err)) { |
| goto error; |
| } |
| else if (localeID == NULL) { |
| goto error; |
| } |
| |
| trailingIndex = |
| parseTagString( |
| localeID, |
| lang, |
| &langLength, |
| script, |
| &scriptLength, |
| region, |
| ®ionLength, |
| err); |
| if(U_FAILURE(*err)) { |
| |
| /* Overflow indicates an illegal argument error */ |
| if (*err == U_BUFFER_OVERFLOW_ERROR) { |
| *err = U_ILLEGAL_ARGUMENT_ERROR; |
| } |
| |
| goto error; |
| } |
| |
| /* Find the spot where the variants or the keywords begin, if any. */ |
| while (_isIDSeparator(localeID[trailingIndex])) { |
| trailingIndex++; |
| } |
| trailing = &localeID[trailingIndex]; |
| trailingLength = (int32_t)uprv_strlen(trailing); |
| |
| CHECK_TRAILING_VARIANT_SIZE(trailing, trailingLength); |
| |
| { |
| icu::CharString base; |
| { |
| icu::CharStringByteSink baseSink(&base); |
| createTagString( |
| lang, |
| langLength, |
| script, |
| scriptLength, |
| region, |
| regionLength, |
| NULL, |
| 0, |
| baseSink, |
| err); |
| } |
| |
| /** |
| * First, we need to first get the maximization |
| * from AddLikelySubtags. |
| **/ |
| { |
| icu::CharStringByteSink maxSink(&maximizedTagBuffer); |
| successGetMax = _ulocimp_addLikelySubtags(base.data(), maxSink, err); |
| } |
| } |
| |
| if(U_FAILURE(*err)) { |
| goto error; |
| } |
| |
| if (!successGetMax) { |
| /** |
| * If we got here, return the locale ID parameter unchanged. |
| **/ |
| const int32_t localeIDLength = (int32_t)uprv_strlen(localeID); |
| sink.Append(localeID, localeIDLength); |
| return; |
| } |
| |
| // In the following, the lang, script, region are referring to those in |
| // the maximizedTagBuffer, not the one in the localeID. |
| langLength = sizeof(lang); |
| scriptLength = sizeof(script); |
| regionLength = sizeof(region); |
| parseTagString( |
| maximizedTagBuffer.data(), |
| lang, |
| &langLength, |
| script, |
| &scriptLength, |
| region, |
| ®ionLength, |
| err); |
| if(U_FAILURE(*err)) { |
| goto error; |
| } |
| |
| /** |
| * Start first with just the language. |
| **/ |
| { |
| icu::CharString tagBuffer; |
| { |
| icu::CharStringByteSink tagSink(&tagBuffer); |
| createLikelySubtagsString( |
| lang, |
| langLength, |
| NULL, |
| 0, |
| NULL, |
| 0, |
| NULL, |
| 0, |
| tagSink, |
| err); |
| } |
| |
| if(U_FAILURE(*err)) { |
| goto error; |
| } |
| else if (!tagBuffer.isEmpty() && |
| uprv_strnicmp( |
| maximizedTagBuffer.data(), |
| tagBuffer.data(), |
| tagBuffer.length()) == 0) { |
| |
| createTagString( |
| lang, |
| langLength, |
| NULL, |
| 0, |
| NULL, |
| 0, |
| trailing, |
| trailingLength, |
| sink, |
| err); |
| return; |
| } |
| } |
| |
| /** |
| * Next, try the language and region. |
| **/ |
| if (regionLength > 0) { |
| |
| icu::CharString tagBuffer; |
| { |
| icu::CharStringByteSink tagSink(&tagBuffer); |
| createLikelySubtagsString( |
| lang, |
| langLength, |
| NULL, |
| 0, |
| region, |
| regionLength, |
| NULL, |
| 0, |
| tagSink, |
| err); |
| } |
| |
| if(U_FAILURE(*err)) { |
| goto error; |
| } |
| else if (!tagBuffer.isEmpty() && |
| uprv_strnicmp( |
| maximizedTagBuffer.data(), |
| tagBuffer.data(), |
| tagBuffer.length()) == 0) { |
| |
| createTagString( |
| lang, |
| langLength, |
| NULL, |
| 0, |
| region, |
| regionLength, |
| trailing, |
| trailingLength, |
| sink, |
| err); |
| return; |
| } |
| } |
| |
| /** |
| * Finally, try the language and script. This is our last chance, |
| * since trying with all three subtags would only yield the |
| * maximal version that we already have. |
| **/ |
| if (scriptLength > 0) { |
| icu::CharString tagBuffer; |
| { |
| icu::CharStringByteSink tagSink(&tagBuffer); |
| createLikelySubtagsString( |
| lang, |
| langLength, |
| script, |
| scriptLength, |
| NULL, |
| 0, |
| NULL, |
| 0, |
| tagSink, |
| err); |
| } |
| |
| if(U_FAILURE(*err)) { |
| goto error; |
| } |
| else if (!tagBuffer.isEmpty() && |
| uprv_strnicmp( |
| maximizedTagBuffer.data(), |
| tagBuffer.data(), |
| tagBuffer.length()) == 0) { |
| |
| createTagString( |
| lang, |
| langLength, |
| script, |
| scriptLength, |
| NULL, |
| 0, |
| trailing, |
| trailingLength, |
| sink, |
| err); |
| return; |
| } |
| } |
| |
| { |
| /** |
| * If we got here, return the max + trail. |
| **/ |
| createTagString( |
| lang, |
| langLength, |
| script, |
| scriptLength, |
| region, |
| regionLength, |
| trailing, |
| trailingLength, |
| sink, |
| err); |
| return; |
| } |
| |
| error: |
| |
| if (!U_FAILURE(*err)) { |
| *err = U_ILLEGAL_ARGUMENT_ERROR; |
| } |
| } |
| |
| static UBool |
| do_canonicalize(const char* localeID, |
| char* buffer, |
| int32_t bufferCapacity, |
| UErrorCode* err) |
| { |
| uloc_canonicalize( |
| localeID, |
| buffer, |
| bufferCapacity, |
| err); |
| |
| if (*err == U_STRING_NOT_TERMINATED_WARNING || |
| *err == U_BUFFER_OVERFLOW_ERROR) { |
| *err = U_ILLEGAL_ARGUMENT_ERROR; |
| |
| return FALSE; |
| } |
| else if (U_FAILURE(*err)) { |
| |
| return FALSE; |
| } |
| else { |
| return TRUE; |
| } |
| } |
| |
| 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) { |
| char localeBuffer[ULOC_FULLNAME_CAPACITY]; |
| |
| if (do_canonicalize(localeID, localeBuffer, sizeof localeBuffer, status)) { |
| return _uloc_addLikelySubtags(localeBuffer, sink, status); |
| } |
| 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, 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, |
| UErrorCode* status) { |
| char localeBuffer[ULOC_FULLNAME_CAPACITY]; |
| |
| if (do_canonicalize(localeID, localeBuffer, sizeof localeBuffer, status)) { |
| _uloc_minimizeSubtags(localeBuffer, sink, 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; |
| char script[8]; |
| int32_t scriptLength = uloc_getScript(locale, script, UPRV_LENGTHOF(script), &errorCode); |
| if (U_FAILURE(errorCode) || errorCode == U_STRING_NOT_TERMINATED_WARNING || |
| scriptLength == 0) { |
| // Fastpath: We know the likely scripts and their writing direction |
| // for some common languages. |
| errorCode = U_ZERO_ERROR; |
| char lang[8]; |
| int32_t langLength = uloc_getLanguage(locale, lang, UPRV_LENGTHOF(lang), &errorCode); |
| if (U_FAILURE(errorCode) || errorCode == U_STRING_NOT_TERMINATED_WARNING) { |
| return FALSE; |
| } |
| if (langLength > 0) { |
| const char* langPtr = uprv_strstr(LANG_DIR_STRING, lang); |
| if (langPtr != NULL) { |
| switch (langPtr[langLength]) { |
| 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) || errorCode == U_STRING_NOT_TERMINATED_WARNING) { |
| return FALSE; |
| } |
| scriptLength = uloc_getScript(likely.data(), script, UPRV_LENGTHOF(script), &errorCode); |
| if (U_FAILURE(errorCode) || errorCode == U_STRING_NOT_TERMINATED_WARNING || |
| scriptLength == 0) { |
| return FALSE; |
| } |
| } |
| UScriptCode scriptCode = (UScriptCode)u_getPropertyValueEnum(UCHAR_SCRIPT, script); |
| return uscript_isRightToLeft(scriptCode); |
| } |
| |
| U_NAMESPACE_BEGIN |
| |
| UBool |
| Locale::isRightToLeft() const { |
| return uloc_isRightToLeft(getBaseName()); |
| } |
| |
| U_NAMESPACE_END |
| |
| // The following must at least allow for rg key value (6) plus terminator (1). |
| #define ULOC_RG_BUFLEN 8 |
| |
| U_CAPI int32_t U_EXPORT2 |
| ulocimp_getRegionForSupplementalData(const char *localeID, UBool inferRegion, |
| char *region, int32_t regionCapacity, UErrorCode* status) { |
| if (U_FAILURE(*status)) { |
| return 0; |
| } |
| char rgBuf[ULOC_RG_BUFLEN]; |
| UErrorCode rgStatus = U_ZERO_ERROR; |
| |
| // First check for rg keyword value |
| int32_t rgLen = uloc_getKeywordValue(localeID, "rg", rgBuf, ULOC_RG_BUFLEN, &rgStatus); |
| if (U_FAILURE(rgStatus) || rgLen != 6) { |
| rgLen = 0; |
| } else { |
| // rgBuf guaranteed to be zero terminated here, with text len 6 |
| char *rgPtr = rgBuf; |
| for (; *rgPtr!= 0; rgPtr++) { |
| *rgPtr = uprv_toupper(*rgPtr); |
| } |
| rgLen = (uprv_strcmp(rgBuf+2, "ZZZZ") == 0)? 2: 0; |
| } |
| |
| if (rgLen == 0) { |
| // No valid rg keyword value, try for unicode_region_subtag |
| rgLen = uloc_getCountry(localeID, rgBuf, ULOC_RG_BUFLEN, status); |
| if (U_FAILURE(*status)) { |
| rgLen = 0; |
| } else if (rgLen == 0 && inferRegion) { |
| // no unicode_region_subtag but inferRegion TRUE, try likely subtags |
| rgStatus = U_ZERO_ERROR; |
| icu::CharString locBuf; |
| { |
| icu::CharStringByteSink sink(&locBuf); |
| ulocimp_addLikelySubtags(localeID, sink, &rgStatus); |
| } |
| if (U_SUCCESS(rgStatus)) { |
| rgLen = uloc_getCountry(locBuf.data(), rgBuf, ULOC_RG_BUFLEN, status); |
| if (U_FAILURE(*status)) { |
| rgLen = 0; |
| } |
| } |
| } |
| } |
| |
| rgBuf[rgLen] = 0; |
| uprv_strncpy(region, rgBuf, regionCapacity); |
| return u_terminateChars(region, regionCapacity, rgLen, status); |
| } |
| |