From: Fredrik Roubert Date: Fri, 14 Sep 2018 21:25:35 +0000 (-0700) Subject: ICU-13417 Add the Locale::(get|set)(Unicode)?KeywordValue() functions. X-Git-Tag: release-63-rc~68 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=fbc730fba1879535bdc1f78723e17e6fbff18622;p=icu ICU-13417 Add the Locale::(get|set)(Unicode)?KeywordValue() functions. --- diff --git a/icu4c/source/common/locid.cpp b/icu4c/source/common/locid.cpp index 6a69bab7222..19e4ac7f21b 100644 --- a/icu4c/source/common/locid.cpp +++ b/icu4c/source/common/locid.cpp @@ -48,6 +48,7 @@ #include "ucln_cmn.h" #include "ustr_imp.h" #include "charstr.h" +#include "bytesinkutil.h" U_CDECL_BEGIN static UBool U_CALLCONV locale_cleanup(void); @@ -1261,6 +1262,105 @@ Locale::getKeywordValue(const char* keywordName, char *buffer, int32_t bufLen, U return uloc_getKeywordValue(fullName, keywordName, buffer, bufLen, &status); } +void +Locale::getKeywordValue(StringPiece keywordName, ByteSink& sink, UErrorCode& status) const { + if (U_FAILURE(status)) { + return; + } + + if (fIsBogus) { + status = U_ILLEGAL_ARGUMENT_ERROR; + return; + } + + // TODO: Remove the need for a const char* to a NUL terminated buffer. + const CharString keywordName_nul(keywordName, status); + if (U_FAILURE(status)) { + return; + } + + LocalMemory scratch; + int32_t scratch_capacity = 16; // Arbitrarily chosen default size. + + char* buffer; + int32_t result_capacity, reslen; + + for (;;) { + if (scratch.allocateInsteadAndReset(scratch_capacity) == nullptr) { + status = U_MEMORY_ALLOCATION_ERROR; + return; + } + + buffer = sink.GetAppendBuffer( + /*min_capacity=*/scratch_capacity, + /*desired_capacity_hint=*/scratch_capacity, + scratch.getAlias(), + scratch_capacity, + &result_capacity); + + reslen = uloc_getKeywordValue( + fullName, + keywordName_nul.data(), + buffer, + result_capacity, + &status); + + if (status != U_BUFFER_OVERFLOW_ERROR) { + break; + } + + scratch_capacity = reslen; + status = U_ZERO_ERROR; + } + + if (U_FAILURE(status)) { + return; + } + + sink.Append(buffer, reslen); + if (status == U_STRING_NOT_TERMINATED_WARNING) { + status = U_ZERO_ERROR; // Terminators not used. + } +} + +void +Locale::getUnicodeKeywordValue(StringPiece keywordName, + ByteSink& sink, + UErrorCode& status) const { + // TODO: Remove the need for a const char* to a NUL terminated buffer. + const CharString keywordName_nul(keywordName, status); + if (U_FAILURE(status)) { + return; + } + + const char* legacy_key = uloc_toLegacyKey(keywordName_nul.data()); + + if (legacy_key == nullptr) { + status = U_ILLEGAL_ARGUMENT_ERROR; + return; + } + + CharString legacy_value; + { + CharStringByteSink sink(&legacy_value); + getKeywordValue(legacy_key, sink, status); + } + + if (U_FAILURE(status)) { + return; + } + + const char* unicode_value = uloc_toUnicodeLocaleType( + keywordName_nul.data(), legacy_value.data()); + + if (unicode_value == nullptr) { + status = U_ILLEGAL_ARGUMENT_ERROR; + return; + } + + sink.Append(unicode_value, uprv_strlen(unicode_value)); +} + void Locale::setKeywordValue(const char* keywordName, const char* keywordValue, UErrorCode &status) { @@ -1271,6 +1371,46 @@ Locale::setKeywordValue(const char* keywordName, const char* keywordValue, UErro } } +void +Locale::setKeywordValue(StringPiece keywordName, + StringPiece keywordValue, + UErrorCode& status) { + // TODO: Remove the need for a const char* to a NUL terminated buffer. + const CharString keywordName_nul(keywordName, status); + const CharString keywordValue_nul(keywordValue, status); + setKeywordValue(keywordName_nul.data(), keywordValue_nul.data(), status); +} + +void +Locale::setUnicodeKeywordValue(StringPiece keywordName, + StringPiece keywordValue, + UErrorCode& status) { + // TODO: Remove the need for a const char* to a NUL terminated buffer. + const CharString keywordName_nul(keywordName, status); + const CharString keywordValue_nul(keywordValue, status); + + if (U_FAILURE(status)) { + return; + } + + const char* legacy_key = uloc_toLegacyKey(keywordName_nul.data()); + + if (legacy_key == nullptr) { + status = U_ILLEGAL_ARGUMENT_ERROR; + return; + } + + const char* legacy_value = + uloc_toLegacyType(keywordName_nul.data(), keywordValue_nul.data()); + + if (legacy_value == nullptr) { + status = U_ILLEGAL_ARGUMENT_ERROR; + return; + } + + setKeywordValue(legacy_key, legacy_value, status); +} + const char * Locale::getBaseName() const { return baseName; diff --git a/icu4c/source/common/unicode/locid.h b/icu4c/source/common/unicode/locid.h index 962e6ddb269..ea48ed91a17 100644 --- a/icu4c/source/common/unicode/locid.h +++ b/icu4c/source/common/unicode/locid.h @@ -511,6 +511,11 @@ public: /** * Gets the value for a keyword. * + * This uses legacy keyword=value pairs, like "collation=phonebook". + * + * ICU4C doesn't do automatic conversion between legacy and Unicode + * keywords and values in getters and setters (as opposed to ICU4J). + * * @param keywordName name of the keyword for which we want the value. Case insensitive. * @param buffer The buffer to receive the keyword value. * @param bufferCapacity The capacity of receiving buffer @@ -521,12 +526,81 @@ public: */ int32_t getKeywordValue(const char* keywordName, char *buffer, int32_t bufferCapacity, UErrorCode &status) const; +#ifndef U_HIDE_DRAFT_API + /** + * Gets the value for a keyword. + * + * This uses legacy keyword=value pairs, like "collation=phonebook". + * + * ICU4C doesn't do automatic conversion between legacy and Unicode + * keywords and values in getters and setters (as opposed to ICU4J). + * + * @param keywordName name of the keyword for which we want the value. + * @param sink the sink to receive the keyword value. + * @param status error information if getting the value failed. + * @draft ICU 63 + */ + void getKeywordValue(StringPiece keywordName, ByteSink& sink, UErrorCode& status) const; + + /** + * Gets the value for a keyword. + * + * This uses legacy keyword=value pairs, like "collation=phonebook". + * + * ICU4C doesn't do automatic conversion between legacy and Unicode + * keywords and values in getters and setters (as opposed to ICU4J). + * + * @param keywordName name of the keyword for which we want the value. + * @param status error information if getting the value failed. + * @return the keyword value. + * @draft ICU 63 + */ + template + inline StringClass getKeywordValue(StringPiece keywordName, UErrorCode& status) const; + + /** + * Gets the Unicode value for a Unicode keyword. + * + * This uses Unicode key-value pairs, like "co-phonebk". + * + * ICU4C doesn't do automatic conversion between legacy and Unicode + * keywords and values in getters and setters (as opposed to ICU4J). + * + * @param keywordName name of the keyword for which we want the value. + * @param sink the sink to receive the keyword value. + * @param status error information if getting the value failed. + * @draft ICU 63 + */ + void getUnicodeKeywordValue(StringPiece keywordName, ByteSink& sink, UErrorCode& status) const; + + /** + * Gets the Unicode value for a Unicode keyword. + * + * This uses Unicode key-value pairs, like "co-phonebk". + * + * ICU4C doesn't do automatic conversion between legacy and Unicode + * keywords and values in getters and setters (as opposed to ICU4J). + * + * @param keywordName name of the keyword for which we want the value. + * @param status error information if getting the value failed. + * @return the keyword value. + * @draft ICU 63 + */ + template + inline StringClass getUnicodeKeywordValue(StringPiece keywordName, UErrorCode& status) const; +#endif // U_HIDE_DRAFT_API + /** * Sets or removes the value for a keyword. * * For removing all keywords, use getBaseName(), * and construct a new Locale if it differs from getName(). * + * This uses legacy keyword=value pairs, like "collation=phonebook". + * + * ICU4C doesn't do automatic conversion between legacy and Unicode + * keywords and values in getters and setters (as opposed to ICU4J). + * * @param keywordName name of the keyword to be set. Case insensitive. * @param keywordValue value of the keyword to be set. If 0-length or * NULL, will result in the keyword being removed. No error is given if @@ -537,6 +611,48 @@ public: */ void setKeywordValue(const char* keywordName, const char* keywordValue, UErrorCode &status); +#ifndef U_HIDE_DRAFT_API + /** + * Sets or removes the value for a keyword. + * + * For removing all keywords, use getBaseName(), + * and construct a new Locale if it differs from getName(). + * + * This uses legacy keyword=value pairs, like "collation=phonebook". + * + * ICU4C doesn't do automatic conversion between legacy and Unicode + * keywords and values in getters and setters (as opposed to ICU4J). + * + * @param keywordName name of the keyword to be set. + * @param keywordValue value of the keyword to be set. If 0-length or + * NULL, will result in the keyword being removed. No error is given if + * that keyword does not exist. + * @param status Returns any error information while performing this operation. + * @draft ICU 63 + */ + void setKeywordValue(StringPiece keywordName, StringPiece keywordValue, UErrorCode& status); + + /** + * Sets or removes the Unicode value for a Unicode keyword. + * + * For removing all keywords, use getBaseName(), + * and construct a new Locale if it differs from getName(). + * + * This uses Unicode key-value pairs, like "co-phonebk". + * + * ICU4C doesn't do automatic conversion between legacy and Unicode + * keywords and values in getters and setters (as opposed to ICU4J). + * + * @param keywordName name of the keyword to be set. + * @param keywordValue value of the keyword to be set. If 0-length or + * NULL, will result in the keyword being removed. No error is given if + * that keyword does not exist. + * @param status Returns any error information while performing this operation. + * @draft ICU 63 + */ + void setUnicodeKeywordValue(StringPiece keywordName, StringPiece keywordValue, UErrorCode& status); +#endif // U_HIDE_DRAFT_API + /** * returns the locale's three-letter language code, as specified * in ISO draft standard ISO-639-2. @@ -881,6 +997,28 @@ Locale::getName() const return fullName; } +#ifndef U_HIDE_DRAFT_API + +template inline StringClass +Locale::getKeywordValue(StringPiece keywordName, UErrorCode& status) const +{ + StringClass result; + StringByteSink sink(&result); + getKeywordValue(keywordName, sink, status); + return result; +} + +template inline StringClass +Locale::getUnicodeKeywordValue(StringPiece keywordName, UErrorCode& status) const +{ + StringClass result; + StringByteSink sink(&result); + getUnicodeKeywordValue(keywordName, sink, status); + return result; +} + +#endif // U_HIDE_DRAFT_API + inline UBool Locale::isBogus(void) const { return fIsBogus; diff --git a/icu4c/source/test/intltest/loctest.cpp b/icu4c/source/test/intltest/loctest.cpp index 33174cba05c..3f5756e731a 100644 --- a/icu4c/source/test/intltest/loctest.cpp +++ b/icu4c/source/test/intltest/loctest.cpp @@ -224,7 +224,11 @@ void LocaleTest::runIndexedTest( int32_t index, UBool exec, const char* &name, c TESTCASE_AUTO(TestKeywordVariants); TESTCASE_AUTO(TestCreateUnicodeKeywords); TESTCASE_AUTO(TestKeywordVariantParsing); + TESTCASE_AUTO(TestGetKeywordValueStdString); + TESTCASE_AUTO(TestGetUnicodeKeywordValueStdString); TESTCASE_AUTO(TestSetKeywordValue); + TESTCASE_AUTO(TestSetKeywordValueStringPiece); + TESTCASE_AUTO(TestSetUnicodeKeywordValueStringPiece); TESTCASE_AUTO(TestGetBaseName); #if !UCONFIG_NO_FILE_IO TESTCASE_AUTO(TestGetLocale); @@ -1790,6 +1794,36 @@ LocaleTest::TestKeywordVariantParsing(void) { } } +void +LocaleTest::TestGetKeywordValueStdString(void) { + IcuTestErrorCode status(*this, "TestGetKeywordValueStdString()"); + + static const char tag[] = "fa-u-nu-latn"; + static const char keyword[] = "numbers"; + static const char expected[] = "latn"; + + Locale l = Locale::forLanguageTag(tag, status); + status.errIfFailureAndReset("\"%s\"", tag); + + std::string result = l.getKeywordValue(keyword, status); + status.errIfFailureAndReset("\"%s\"", keyword); + assertEquals(keyword, expected, result.c_str()); +} + +void +LocaleTest::TestGetUnicodeKeywordValueStdString(void) { + IcuTestErrorCode status(*this, "TestGetUnicodeKeywordValueStdString()"); + + static const char keyword[] = "co"; + static const char expected[] = "phonebk"; + + static const Locale l("de@calendar=buddhist;collation=phonebook"); + + std::string result = l.getUnicodeKeywordValue(keyword, status); + status.errIfFailureAndReset("\"%s\"", keyword); + assertEquals(keyword, expected, result.c_str()); +} + void LocaleTest::TestSetKeywordValue(void) { static const struct { @@ -1825,6 +1859,33 @@ LocaleTest::TestSetKeywordValue(void) { } } +void +LocaleTest::TestSetKeywordValueStringPiece(void) { + IcuTestErrorCode status(*this, "TestSetKeywordValueStringPiece()"); + Locale l(Locale::getGerman()); + + l.setKeywordValue(StringPiece("collation"), StringPiece("phonebook"), status); + l.setKeywordValue(StringPiece("calendarxxx", 8), StringPiece("buddhistxxx", 8), status); + + static const char expected[] = "de@calendar=buddhist;collation=phonebook"; + assertEquals("", expected, l.getName()); +} + +void +LocaleTest::TestSetUnicodeKeywordValueStringPiece(void) { + IcuTestErrorCode status(*this, "TestSetUnicodeKeywordValueStringPiece()"); + Locale l(Locale::getGerman()); + + l.setUnicodeKeywordValue(StringPiece("co"), StringPiece("phonebk"), status); + status.errIfFailureAndReset(); + + l.setUnicodeKeywordValue(StringPiece("caxxx", 2), StringPiece("buddhistxxx", 8), status); + status.errIfFailureAndReset(); + + static const char expected[] = "de@calendar=buddhist;collation=phonebook"; + assertEquals("", expected, l.getName()); +} + void LocaleTest::TestGetBaseName(void) { static const struct { diff --git a/icu4c/source/test/intltest/loctest.h b/icu4c/source/test/intltest/loctest.h index 7bb52f47a9f..06589161cff 100644 --- a/icu4c/source/test/intltest/loctest.h +++ b/icu4c/source/test/intltest/loctest.h @@ -78,9 +78,13 @@ public: /* Test getting keyword values */ void TestKeywordVariantParsing(void); + void TestGetKeywordValueStdString(void); + void TestGetUnicodeKeywordValueStdString(void); /* Test setting keyword values */ void TestSetKeywordValue(void); + void TestSetKeywordValueStringPiece(void); + void TestSetUnicodeKeywordValueStringPiece(void); /* Test getting the locale base name */ void TestGetBaseName(void);