]> granicus.if.org Git - icu/commitdiff
ICU-13417 Add the Locale::(get|set)(Unicode)?KeywordValue() functions.
authorFredrik Roubert <fredrik@roubert.name>
Fri, 14 Sep 2018 21:25:35 +0000 (14:25 -0700)
committerShane Carr <shane@unicode.org>
Thu, 27 Sep 2018 21:27:39 +0000 (14:27 -0700)
icu4c/source/common/locid.cpp
icu4c/source/common/unicode/locid.h
icu4c/source/test/intltest/loctest.cpp
icu4c/source/test/intltest/loctest.h

index 6a69bab7222106a2434f03f7bca00abc57c0ff9b..19e4ac7f21b1a50625bb1d06514c9e4b7557e6a6 100644 (file)
@@ -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<char> 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;
index 962e6ddb269992e9ec26f84e40bac3b99387ad79..ea48ed91a17ded7ed032166b55d08825bf210dc1 100644 (file)
@@ -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<typename StringClass>
+    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<typename StringClass>
+    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<typename StringClass> inline StringClass
+Locale::getKeywordValue(StringPiece keywordName, UErrorCode& status) const
+{
+    StringClass result;
+    StringByteSink<StringClass> sink(&result);
+    getKeywordValue(keywordName, sink, status);
+    return result;
+}
+
+template<typename StringClass> inline StringClass
+Locale::getUnicodeKeywordValue(StringPiece keywordName, UErrorCode& status) const
+{
+    StringClass result;
+    StringByteSink<StringClass> sink(&result);
+    getUnicodeKeywordValue(keywordName, sink, status);
+    return result;
+}
+
+#endif  // U_HIDE_DRAFT_API
+
 inline UBool
 Locale::isBogus(void) const {
     return fIsBogus;
index 33174cba05ce003e7a87c399b4fab6361336e031..3f5756e731a1f9357cd5515d39dbdbaef58daba6 100644 (file)
@@ -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<std::string>(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<std::string>(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 {
index 7bb52f47a9f8e5adb9b1b13fa8e5866267badb47..06589161cff4ce08aa5a764f6a5cf4a0d590a468 100644 (file)
@@ -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);