From 27181e36a6bed372482e5a11c25009fa4dd971f1 Mon Sep 17 00:00:00 2001 From: Frank Tang Date: Thu, 17 Aug 2023 01:54:33 +0000 Subject: [PATCH] ICU-22435 Add C API for Locale See #2531 --- icu4c/source/common/BUILD.bazel | 1 + icu4c/source/common/common.vcxproj | 1 + icu4c/source/common/common.vcxproj.filters | 6 + icu4c/source/common/common_uwp.vcxproj | 1 + icu4c/source/common/sources.txt | 1 + icu4c/source/common/ulocale.cpp | 96 ++++++ icu4c/source/common/ulocbuilder.cpp | 7 +- icu4c/source/common/unicode/ulocale.h | 229 ++++++++++++++ icu4c/source/test/cintltst/Makefile.in | 3 +- icu4c/source/test/cintltst/cintltst.vcxproj | 1 + .../test/cintltst/cintltst.vcxproj.filters | 3 + icu4c/source/test/cintltst/cutiltst.c | 2 + icu4c/source/test/cintltst/ulocaletst.c | 285 ++++++++++++++++++ icu4c/source/test/depstest/dependencies.txt | 1 + 14 files changed, 633 insertions(+), 4 deletions(-) create mode 100644 icu4c/source/common/ulocale.cpp create mode 100644 icu4c/source/common/unicode/ulocale.h create mode 100644 icu4c/source/test/cintltst/ulocaletst.c diff --git a/icu4c/source/common/BUILD.bazel b/icu4c/source/common/BUILD.bazel index 00f80046dfb1..30f49425c742 100644 --- a/icu4c/source/common/BUILD.bazel +++ b/icu4c/source/common/BUILD.bazel @@ -609,6 +609,7 @@ cc_library( "uloc.cpp", "uloc_tag.cpp", "uloc_keytype.cpp", + "ulocale.cpp", "ulocbuilder.cpp", "uresbund.cpp", "uresdata.cpp", diff --git a/icu4c/source/common/common.vcxproj b/icu4c/source/common/common.vcxproj index caa62100ea92..be3093bd9392 100644 --- a/icu4c/source/common/common.vcxproj +++ b/icu4c/source/common/common.vcxproj @@ -186,6 +186,7 @@ + diff --git a/icu4c/source/common/common.vcxproj.filters b/icu4c/source/common/common.vcxproj.filters index abb276f217b3..1faff8765d33 100644 --- a/icu4c/source/common/common.vcxproj.filters +++ b/icu4c/source/common/common.vcxproj.filters @@ -376,6 +376,9 @@ locales & resources + + locales & resources + locales & resources @@ -1153,6 +1156,9 @@ locales & resources + + locales & resources + locales & resources diff --git a/icu4c/source/common/common_uwp.vcxproj b/icu4c/source/common/common_uwp.vcxproj index 247da53f7359..b40f65c52726 100644 --- a/icu4c/source/common/common_uwp.vcxproj +++ b/icu4c/source/common/common_uwp.vcxproj @@ -320,6 +320,7 @@ + diff --git a/icu4c/source/common/sources.txt b/icu4c/source/common/sources.txt index 6e7f9b020df7..c1d2080bcc66 100644 --- a/icu4c/source/common/sources.txt +++ b/icu4c/source/common/sources.txt @@ -139,6 +139,7 @@ ulist.cpp uloc.cpp uloc_keytype.cpp uloc_tag.cpp +ulocale.cpp ulocbuilder.cpp umapfile.cpp umath.cpp diff --git a/icu4c/source/common/ulocale.cpp b/icu4c/source/common/ulocale.cpp new file mode 100644 index 000000000000..283331439631 --- /dev/null +++ b/icu4c/source/common/ulocale.cpp @@ -0,0 +1,96 @@ +// © 2023 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html +// +#include "unicode/errorcode.h" +#include "unicode/stringpiece.h" +#include "unicode/utypes.h" +#include "unicode/ustring.h" +#include "unicode/ulocale.h" +#include "unicode/locid.h" + +#include "charstr.h" +#include "cmemory.h" + +U_NAMESPACE_USE +#define EXTERNAL(i) (reinterpret_cast(i)) +#define CONST_INTERNAL(e) (reinterpret_cast(e)) +#define INTERNAL(e) (reinterpret_cast(e)) + +ULocale* +ulocale_openForLocaleID(const char* localeID, int32_t length, UErrorCode* err) { + CharString str(length < 0 ? StringPiece(localeID) : StringPiece(localeID, length), *err); + if (U_FAILURE(*err)) return nullptr; + return EXTERNAL(icu::Locale::createFromName(str.data()).clone()); +} + +ULocale* +ulocale_openForLanguageTag(const char* tag, int32_t length, UErrorCode* err) { + Locale l = icu::Locale::forLanguageTag(length < 0 ? StringPiece(tag) : StringPiece(tag, length), *err); + if (U_FAILURE(*err)) return nullptr; + return EXTERNAL(l.clone()); +} + +void +ulocale_close(ULocale* locale) { + delete INTERNAL(locale); +} + +#define IMPL_ULOCALE_STRING_GETTER(N1, N2) \ +const char* ulocale_get ## N1(const ULocale* locale) { \ + if (locale == nullptr) return nullptr; \ + return CONST_INTERNAL(locale)->get ## N2(); \ +} + +#define IMPL_ULOCALE_STRING_IDENTICAL_GETTER(N) IMPL_ULOCALE_STRING_GETTER(N, N) + +#define IMPL_ULOCALE_GET_KEYWORD_VALUE(N) \ +int32_t ulocale_get ##N ( \ + const ULocale* locale, const char* keyword, int32_t keywordLength, \ + char* valueBuffer, int32_t bufferCapacity, UErrorCode *err) { \ + if (U_FAILURE(*err)) return 0; \ + if (locale == nullptr) { \ + *err = U_ILLEGAL_ARGUMENT_ERROR; \ + return 0; \ + } \ + CheckedArrayByteSink sink(valueBuffer, bufferCapacity); \ + CONST_INTERNAL(locale)->get ## N( \ + keywordLength < 0 ? StringPiece(keyword) : StringPiece(keyword, keywordLength), \ + sink, *err); \ + if (U_FAILURE(*err)) return 0; \ + if (sink.Overflowed()) { \ + *err = U_BUFFER_OVERFLOW_ERROR; \ + return sink.NumberOfBytesAppended()+1; \ + } \ + int32_t len = sink.NumberOfBytesWritten(); \ + if (len < bufferCapacity) valueBuffer[len] = '\0'; \ + return len; \ +} + +#define IMPL_ULOCALE_GET_KEYWORDS(N) \ +UEnumeration* ulocale_get ## N(const ULocale* locale, UErrorCode *err) { \ + if (U_FAILURE(*err)) return nullptr; \ + if (locale == nullptr) { \ + *err = U_ILLEGAL_ARGUMENT_ERROR; \ + return nullptr; \ + } \ + return uenum_openFromStringEnumeration( \ + CONST_INTERNAL(locale)->create ## N(*err), err); \ +} + +IMPL_ULOCALE_STRING_IDENTICAL_GETTER(Language) +IMPL_ULOCALE_STRING_IDENTICAL_GETTER(Script) +IMPL_ULOCALE_STRING_GETTER(Region, Country) +IMPL_ULOCALE_STRING_IDENTICAL_GETTER(Variant) +IMPL_ULOCALE_STRING_GETTER(LocaleID, Name) +IMPL_ULOCALE_STRING_IDENTICAL_GETTER(BaseName) +IMPL_ULOCALE_GET_KEYWORD_VALUE(KeywordValue) +IMPL_ULOCALE_GET_KEYWORD_VALUE(UnicodeKeywordValue) +IMPL_ULOCALE_GET_KEYWORDS(Keywords) +IMPL_ULOCALE_GET_KEYWORDS(UnicodeKeywords) + +bool ulocale_isBogus(const ULocale* locale) { + if (locale == nullptr) return false; + return CONST_INTERNAL(locale)->isBogus(); +} + +/*eof*/ diff --git a/icu4c/source/common/ulocbuilder.cpp b/icu4c/source/common/ulocbuilder.cpp index be344c9dfbd6..3adaabf9be32 100644 --- a/icu4c/source/common/ulocbuilder.cpp +++ b/icu4c/source/common/ulocbuilder.cpp @@ -14,8 +14,9 @@ using icu::CheckedArrayByteSink; using icu::StringPiece; -#define EXTERNAL(i) ((ULocaleBuilder*)(i)) -#define INTERNAL(e) ((icu::LocaleBuilder*)(e)) +#define EXTERNAL(i) (reinterpret_cast(i)) +#define INTERNAL(e) (reinterpret_cast(e)) +#define CONST_INTERNAL(e) (reinterpret_cast(e)) ULocaleBuilder* ulocbld_open() { return EXTERNAL(new icu::LocaleBuilder()); @@ -141,5 +142,5 @@ UBool ulocbld_copyErrorTo(const ULocaleBuilder* builder, UErrorCode *outErrorCod *outErrorCode = U_ILLEGAL_ARGUMENT_ERROR; return true; } - return INTERNAL(builder)->copyErrorTo(*outErrorCode); + return CONST_INTERNAL(builder)->copyErrorTo(*outErrorCode); } diff --git a/icu4c/source/common/unicode/ulocale.h b/icu4c/source/common/unicode/ulocale.h new file mode 100644 index 000000000000..33e92844bc1e --- /dev/null +++ b/icu4c/source/common/unicode/ulocale.h @@ -0,0 +1,229 @@ +// © 2023 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html + +#ifndef ULOCALE_H +#define ULOCALE_H + +#include "unicode/localpointer.h" +#include "unicode/uenum.h" +#include "unicode/utypes.h" + +/** + * \file + * \brief C API: Locale ID functionality similar to C++ class Locale + */ + +#ifndef U_HIDE_DRAFT_API +/** + * Opaque C service object type for the locale API + * @draft ICU 74 + */ +struct ULocale; + +/** + * C typedef for struct ULocale. + * @draft ICU 74 + */ +typedef struct ULocale ULocale; + +/** + * Constructs an ULocale from the locale ID. + * The created ULocale should be destroyed by calling + * ulocale_close(); + * @param localeID the locale, a const char * pointer (need not be terminated when + * the length is non-negative) + * @param length the length of the locale; if negative, then the locale need to be + * null terminated. + * @param err the error code + * @return the locale. + * + * @draft ICU 74 + */ +U_CAPI ULocale* U_EXPORT2 +ulocale_openForLocaleID(const char* localeID, int32_t length, UErrorCode* err); + +/** + * Constructs an ULocale from the provided IETF BCP 47 language tag. + * The created ULocale should be destroyed by calling + * ulocale_close(); + * @param tag the language tag, defined as IETF BCP 47 language tag, const + * char* pointer (need not be terminated when the length is non-negative) + * @param length the length of the tag; if negative, then the tag need to be + * null terminated. + * @param err the error code + * @return the locale. + * + * @draft ICU 74 + */ +U_CAPI ULocale* U_EXPORT2 +ulocale_openForLanguageTag(const char* tag, int32_t length, UErrorCode* err); + +/** + * Close the locale and destroy it's internal states. + * + * @param locale the locale + * @draft ICU 74 + */ +U_CAPI void U_EXPORT2 +ulocale_close(ULocale* locale); + +/** + * Returns the locale's ISO-639 language code. + * + * @param locale the locale + * @return the language code of the locale. + * @draft ICU 74 + */ +U_CAPI const char* U_EXPORT2 +ulocale_getLanguage(const ULocale* locale); + +/** + * Returns the locale's ISO-15924 abbreviation script code. + * + * @param locale the locale + * @return A pointer to the script. + * @draft ICU 74 + */ +U_CAPI const char* U_EXPORT2 +ulocale_getScript(const ULocale* locale); + +/** + * Returns the locale's ISO-3166 region code. + * + * @param locale the locale + * @return A pointer to the region. + * @draft ICU 74 + */ +U_CAPI const char* U_EXPORT2 +ulocale_getRegion(const ULocale* locale); + +/** + * Returns the locale's variant code. + * + * @param locale the locale + * @return A pointer to the variant. + * @draft ICU 74 + */ +U_CAPI const char* U_EXPORT2 +ulocale_getVariant(const ULocale* locale); + +/** + * Returns the programmatic name of the entire locale, with the language, + * country and variant separated by underbars. If a field is missing, up + * to two leading underbars will occur. Example: "en", "de_DE", "en_US_WIN", + * "de__POSIX", "fr__MAC", "__MAC", "_MT", "_FR_EURO" + * + * @param locale the locale + * @return A pointer to "name". + * @draft ICU 74 + */ +U_CAPI const char* U_EXPORT2 +ulocale_getLocaleID(const ULocale* locale); + +/** + * Returns the programmatic name of the entire locale as ulocale_getLocaleID() + * would return, but without keywords. + * + * @param locale the locale + * @return A pointer to "base name". + * @draft ICU 74 + */ +U_CAPI const char* U_EXPORT2 +ulocale_getBaseName(const ULocale* locale); + +/** + * Gets the bogus state. Locale object can be bogus if it doesn't exist + * + * @param locale the locale + * @return false if it is a real locale, true if it is a bogus locale + * @draft ICU 74 + */ +U_CAPI bool U_EXPORT2 +ulocale_isBogus(const ULocale* locale); + +/** + * Gets the list of keywords for the specified locale. + * + * @param locale the locale + * @param err the error code + * @return pointer to UEnumeration, or nullptr if there are no keywords. + * Client must call uenum_close() to dispose the returned value. + * @draft ICU 74 + */ +U_CAPI UEnumeration* U_EXPORT2 +ulocale_getKeywords(const ULocale* locale, UErrorCode *err); + +/** + * Gets the list of unicode keywords for the specified locale. + * + * @param locale the locale + * @param err the error code + * @return pointer to UEnumeration, or nullptr if there are no keywords. + * Client must call uenum_close() to dispose the returned value. + * @draft ICU 74 + */ +U_CAPI UEnumeration* U_EXPORT2 +ulocale_getUnicodeKeywords(const ULocale* locale, UErrorCode *err); + +/** + * Gets the value for a keyword. + * + * This uses legacy keyword=value pairs, like "collation=phonebook". + * + * @param locale the locale + * @param keyword the keyword, a const char * pointer (need not be + * terminated when the length is non-negative) + * @param keywordLength the length of the keyword; if negative, then the + * keyword need to be null terminated. + * @param valueBuffer The buffer to receive the value. + * @param valueBufferCapacity The capacity of receiving valueBuffer. + * @param err the error code + * @draft ICU 74 + */ +U_CAPI int32_t U_EXPORT2 +ulocale_getKeywordValue( + const ULocale* locale, const char* keyword, int32_t keywordLength, + char* valueBuffer, int32_t valueBufferCapacity, UErrorCode *err); + +/** + * Gets the Unicode value for a Unicode keyword. + * + * This uses Unicode key-value pairs, like "co-phonebk". + * + * @param locale the locale + * @param keyword the Unicode keyword, a const char * pointer (need not be + * terminated when the length is non-negative) + * @param keywordLength the length of the Unicode keyword; if negative, + * then the keyword need to be null terminated. + * @param valueBuffer The buffer to receive the Unicode value. + * @param valueBufferCapacity The capacity of receiving valueBuffer. + * @param err the error code + * @draft ICU 74 + */ +U_CAPI int32_t U_EXPORT2 +ulocale_getUnicodeKeywordValue( + const ULocale* locale, const char* keyword, int32_t keywordLength, + char* valueBuffer, int32_t valueBufferCapacity, UErrorCode *err); + +#if U_SHOW_CPLUSPLUS_API + +U_NAMESPACE_BEGIN + +/** + * \class LocalULocalePointer + * "Smart pointer" class, closes a ULocale via ulocale_close(). + * For most methods see the LocalPointerBase base class. + * + * @see LocalPointerBase + * @see LocalPointer + * @draft ICU 74 + */ +U_DEFINE_LOCAL_OPEN_POINTER(LocalULocalePointer, ULocale, ulocale_close); + +U_NAMESPACE_END + +#endif /* U_SHOW_CPLUSPLUS_API */ + +#endif /* U_HIDE_DRAFT_API */ + +#endif /*_ULOCALE */ diff --git a/icu4c/source/test/cintltst/Makefile.in b/icu4c/source/test/cintltst/Makefile.in index 5c0fb8fd04b8..552a105bd90d 100644 --- a/icu4c/source/test/cintltst/Makefile.in +++ b/icu4c/source/test/cintltst/Makefile.in @@ -46,7 +46,8 @@ LIBS = $(LIBCTESTFW) $(LIBICUI18N) $(LIBICUTOOLUTIL) $(LIBICUUC) $(DEFAULT_LIBS) OBJECTS = callcoll.o calltest.o capitst.o cbiapts.o cbkittst.o \ ccaltst.o ucnvseltst.o cctest.o ccapitst.o ccolltst.o encoll.o cconvtst.o ccurrtst.o \ cdateintervalformattest.o cdattst.o cdetst.o cdtdptst.o cdtrgtst.o cestst.o cfintst.o \ -cformtst.o cfrtst.o cg7coll.o chashtst.o cintltst.o citertst.o cjaptst.o cloctst.o ulocbuildertst.o \ +cformtst.o cfrtst.o cg7coll.o chashtst.o cintltst.o citertst.o cjaptst.o cloctst.o \ +ulocaletst.o ulocbuildertst.o \ cmsccoll.o cmsgtst.o cpluralrulestest.o cposxtst.o cldrtest.o \ cnmdptst.o cnormtst.o cnumtst.o crelativedateformattest.o crestst.o creststn.o cturtst.o \ cucdapi.o cucdtst.o custrtst.o cstrcase.o cutiltst.o nucnvtst.o nccbtst.o bocu1tst.o \ diff --git a/icu4c/source/test/cintltst/cintltst.vcxproj b/icu4c/source/test/cintltst/cintltst.vcxproj index 0f5a5c7ae196..0183f3bf032c 100644 --- a/icu4c/source/test/cintltst/cintltst.vcxproj +++ b/icu4c/source/test/cintltst/cintltst.vcxproj @@ -127,6 +127,7 @@ + diff --git a/icu4c/source/test/cintltst/cintltst.vcxproj.filters b/icu4c/source/test/cintltst/cintltst.vcxproj.filters index 03d13c2ec799..3628b0956430 100644 --- a/icu4c/source/test/cintltst/cintltst.vcxproj.filters +++ b/icu4c/source/test/cintltst/cintltst.vcxproj.filters @@ -234,6 +234,9 @@ locales & resources + + locales & resources + locales & resources diff --git a/icu4c/source/test/cintltst/cutiltst.c b/icu4c/source/test/cintltst/cutiltst.c index bbac813103d6..20b65c8f7672 100644 --- a/icu4c/source/test/cintltst/cutiltst.c +++ b/icu4c/source/test/cintltst/cutiltst.c @@ -17,6 +17,7 @@ #include "cintltst.h" void addLocaleTest(TestNode**); +void addULocaleTest(TestNode**); void addLocaleBuilderTest(TestNode**); void addCLDRTest(TestNode**); void addUnicodeTest(TestNode**); @@ -43,6 +44,7 @@ void addUtility(TestNode** root) addUCPTrieTest(root); addLocaleTest(root); addLocaleBuilderTest(root); + addULocaleTest(root); addCLDRTest(root); addUnicodeTest(root); addUStringTest(root); diff --git a/icu4c/source/test/cintltst/ulocaletst.c b/icu4c/source/test/cintltst/ulocaletst.c new file mode 100644 index 000000000000..6d3fae84c72d --- /dev/null +++ b/icu4c/source/test/cintltst/ulocaletst.c @@ -0,0 +1,285 @@ +// © 2023 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html + +#include "unicode/ctest.h" +#include "unicode/uloc.h" +#include "unicode/ulocale.h" +#include "cintltst.h" +#include "cmemory.h" +#include "cstring.h" + +#define WHERE __FILE__ ":" XLINE(__LINE__) " " +#define XLINE(s) LINE(s) +#define LINE(s) #s + +#define TESTCASE(name) addTest(root, &name, "tsutil/ulocaletst/" #name) + +enum { + LANGUAGE = 0, + SCRIPT, + REGION, + VAR, + NAME, + BASENAME, +}; +static const char* const rawData[][6] = { + { "en", "", "US", "", "en_US", "en_US"}, + { "fr", "", "FR", "", "fr_FR", "fr_FR"}, + { "ca", "", "ES", "", "ca_ES", "ca_ES"}, + { "el", "", "GR", "", "el_GR", "el_GR"}, + { "no", "", "NO", "NY", "no_NO_NY", "no_NO_NY"}, + { "it", "", "", "", "it", "it"}, + { "xx", "", "YY", "", "xx_YY", "xx_YY"}, + { "zh", "Hans", "CN", "", "zh_Hans_CN", "zh_Hans_CN"}, + { "zh", "Hans", "CN", "", "zh_Hans_CN", "zh_Hans_CN"}, + { "x-klingon", "Latn", "ZX", "", "x-klingon_Latn_ZX.utf32be@special", "x-klingon_Latn_ZX.utf32be@special"}, +}; + +static void TestBasicGetters() { + for (int32_t i = 0; i < UPRV_LENGTHOF(rawData); i++) { + UErrorCode status = U_ZERO_ERROR; + ULocale* l = ulocale_openForLocaleID(rawData[i][NAME], -1, &status); + if (assertSuccess(WHERE "ulocale_openForLocaleID()", &status)) { + assertEquals(WHERE "ulocale_getLanguage()", rawData[i][LANGUAGE], ulocale_getLanguage(l)); + assertEquals(WHERE "ulocale_getScript()", rawData[i][SCRIPT], ulocale_getScript(l)); + assertEquals(WHERE "ulocale_getRegion()", rawData[i][REGION], ulocale_getRegion(l)); + assertEquals(WHERE "ulocale_getVariant()", rawData[i][VAR], ulocale_getVariant(l)); + assertEquals(WHERE "ulocale_getLocaleID()", rawData[i][NAME], ulocale_getLocaleID(l)); + assertEquals(WHERE "ulocale_getBaseName()", rawData[i][BASENAME], ulocale_getBaseName(l)); + ulocale_close(l); + } + } +} + +static void VerifyMatch(const char* localeID, const char* tag) { + UErrorCode status = U_ZERO_ERROR; + ULocale* fromID = ulocale_openForLocaleID(localeID, -1, &status); + if (assertSuccess(WHERE "ulocale_openForLocaleID()", &status)) { + ULocale* fromTag = ulocale_openForLanguageTag(tag, -1, &status); + if (assertSuccess(WHERE "ulocale_openForLanguageTag()", &status)) { + assertEquals(tag, ulocale_getLocaleID(fromID), ulocale_getLocaleID(fromTag)); + ulocale_close(fromTag); + } + ulocale_close(fromID); + } +} + +static void TestForLanguageTag() { + VerifyMatch("en_US", "en-US"); + VerifyMatch("en_GB_OXENDICT", "en-GB-oed"); + VerifyMatch("af@calendar=coptic;t=ar-i0-handwrit;x=foo", "af-t-ar-i0-handwrit-u-ca-coptic-x-foo"); + VerifyMatch("en_GB", "en-GB"); + VerifyMatch("en_GB@1=abc-efg;a=xyz", "en-GB-1-abc-efg-a-xyz"); // ext + VerifyMatch("sl__1994_BISKE_ROZAJ", "sl-rozaj-biske-1994"); // var + + UErrorCode status = U_ZERO_ERROR; + ULocale* result_ill = ulocale_openForLanguageTag("!", -1, &status); + assertIntEquals("!", U_ILLEGAL_ARGUMENT_ERROR, status); + assertPtrEquals("!", NULL, result_ill); + ulocale_close(result_ill); + + VerifyMatch("", NULL); + + // ICU-21433 + VerifyMatch("__1994_BISKE_ROZAJ", "und-1994-biske-rozaj"); + VerifyMatch("de__1994_BISKE_ROZAJ", "de-1994-biske-rozaj"); + VerifyMatch("@x=private", "und-x-private"); + VerifyMatch("de__1994_BISKE_ROZAJ@x=private", "de-1994-biske-rozaj-x-private"); + VerifyMatch("__1994_BISKE_ROZAJ@x=private", "und-1994-biske-rozaj-x-private"); +} + +static void TestGetKeywords() { + UErrorCode status = U_ZERO_ERROR; + ULocale* l = ulocale_openForLocaleID("de@calendar=buddhist;collation=phonebook", -1, &status); + if (assertSuccess(WHERE "ulocale_openForLocaleID()", &status)) { + UEnumeration* en = ulocale_getKeywords(l, &status); + if (assertSuccess(WHERE "ulocale_getKeywords()", &status)) { + assertIntEquals("uenum_count()", 2, uenum_count(en, &status)); + bool hasCalendar = false; + bool hasCollation = false; + const char* key = NULL; + while ((key = uenum_next(en, NULL, &status)) != NULL) { + if (uprv_strcmp(key, "calendar") == 0) { + hasCalendar = true; + } else if (uprv_strcmp(key, "collation") == 0) { + hasCollation = true; + } + } + assertTrue( + WHERE "ulocale_getKeywords() should return UEnumeration that has \"calendar\"", + hasCalendar); + assertTrue( + WHERE "ulocale_getKeywords() should return UEnumeration that has \"collation\"", + hasCollation); + uenum_close(en); + } + ulocale_close(l); + } +} + +static void TestGetKeywordsEmpty() { + UErrorCode status = U_ZERO_ERROR; + ULocale* l = ulocale_openForLocaleID("de", -1, &status); + if (assertSuccess(WHERE "ulocale_openForLocaleID()", &status)) { + UEnumeration* en = ulocale_getKeywords(l, &status); + if (assertSuccess(WHERE "ulocale_getKeywords()", &status)) { + assertPtrEquals(WHERE "ulocale_getKeyword()", NULL, en); + uenum_close(en); + } + ulocale_close(l); + } +} + +static void TestGetKeywordsWithPrivateUse() { + UErrorCode status = U_ZERO_ERROR; + ULocale* l = ulocale_openForLanguageTag("en-US-u-ca-gregory-x-foo", -1, &status); + if (assertSuccess(WHERE "ulocale_openForLanguageTag()", &status)) { + UEnumeration* en = ulocale_getKeywords(l, &status); + if (assertSuccess(WHERE "ulocale_getKeywords()", &status)) { + assertIntEquals("uenum_count()", 2, uenum_count(en, &status)); + bool hasCalendar = false; + bool hasX = false; + const char* key = NULL; + while ((key = uenum_next(en, NULL, &status)) != NULL) { + if (uprv_strcmp(key, "calendar") == 0) { + hasCalendar = true; + } else if (uprv_strcmp(key, "x") == 0) { + hasX = true; + } + } + assertTrue(WHERE "ulocale_getKeywords() should return UEnumeration that has \"calendar\"", + hasCalendar); + assertTrue(WHERE "ulocale_getKeywords() should return UEnumeration that has \"x\"", + hasX); + uenum_close(en); + } + ulocale_close(l); + } +} + +static void TestGetUnicodeKeywords() { + UErrorCode status = U_ZERO_ERROR; + ULocale* l = ulocale_openForLocaleID("de@calendar=buddhist;collation=phonebook", -1, &status); + if (assertSuccess(WHERE "ulocale_openForLocaleID()", &status)) { + UEnumeration* en = ulocale_getUnicodeKeywords(l, &status); + if (assertSuccess(WHERE "ulocale_getUnicodeKeywords()", &status)) { + assertIntEquals("uenum_count()", 2, uenum_count(en, &status)); + bool hasCa = false; + bool hasCo = false; + const char* key = NULL; + while ((key = uenum_next(en, NULL, &status)) != NULL) { + if (uprv_strcmp(key, "ca") == 0) { + hasCa = true; + } else if (uprv_strcmp(key, "co") == 0) { + hasCo = true; + } + } + assertTrue( + WHERE "ulocale_getUnicodeKeywords() should return UEnumeration that has \"ca\"", + hasCa); + assertTrue( + WHERE "ulocale_getUnicodeKeywords() should return UEnumeration that has \"co\"", + hasCo); + uenum_close(en); + } + ulocale_close(l); + } +} + +static void TestGetUnicodeKeywordsEmpty() { + UErrorCode status = U_ZERO_ERROR; + ULocale* l = ulocale_openForLocaleID("de", -1, &status); + if (assertSuccess(WHERE "ulocale_openForLocaleID()", &status)) { + UEnumeration* en = ulocale_getUnicodeKeywords(l, &status); + if (assertSuccess(WHERE "ulocale_getUnicodeKeywords()", &status)) { + assertPtrEquals("ulocale_getUnicodeKeyword()", NULL, en); + uenum_close(en); + } + ulocale_close(l); + } +} + +static void TestGetUnicodeKeywordsWithPrivateUse() { + UErrorCode status = U_ZERO_ERROR; + ULocale* l = ulocale_openForLanguageTag("en-US-u-ca-gregory-x-foo", -1, &status); + if (assertSuccess(WHERE "ulocale_openForLanguageTag()", &status)) { + UEnumeration* en = ulocale_getUnicodeKeywords(l, &status); + if (assertSuccess(WHERE "ulocale_getUnicodeKeywords()", &status)) { + int32_t count = uenum_count(en, &status); + if (count != 1) { + log_knownIssue("ICU-22457", "uenum_count() should be 1 but get %d\n", count); + } + const char* key = uenum_next(en, NULL, &status); + if (assertSuccess(WHERE "uenum_next()", &status)) { + assertEquals(WHERE "ulocale_getUnicodeKeywords() should return UEnumeration that has \"ca\"", + "ca", key); + } + uenum_close(en); + } + ulocale_close(l); + } +} + +static void TestGetKeywordValue() { + UErrorCode status = U_ZERO_ERROR; + ULocale* l = ulocale_openForLanguageTag("fa-u-nu-thai", -1, &status); + if (assertSuccess(WHERE "ulocale_openForLanguageTag()", &status)) { + char buffer[ULOC_FULLNAME_CAPACITY]; + ulocale_getKeywordValue(l, "numbers", -1, buffer, ULOC_FULLNAME_CAPACITY, &status); + if (assertSuccess(WHERE "ulocale_getKeywordValue()", &status)) { + assertEquals(WHERE "ulocale_getKeywordValue(\"numbers\")", "thai", buffer); + } + + ulocale_getKeywordValue(l, "calendar", -1, buffer, ULOC_FULLNAME_CAPACITY, &status); + if (assertSuccess(WHERE "ulocale_getKeywordValue()", &status)) { + assertEquals(WHERE "ulocale_getKeywordValue(\"calendar\")", "", buffer); + } + ulocale_getKeywordValue(l, "collation", -1, buffer, ULOC_FULLNAME_CAPACITY, &status); + if (assertSuccess(WHERE "ulocale_getKeywordValue()", &status)) { + assertEquals(WHERE "ulocale_getKeywordValue(\"collation\")", "", buffer); + } + ulocale_close(l); + } +} + +static void TestGetUnicodeKeywordValue() { + UErrorCode status = U_ZERO_ERROR; + ULocale* l = ulocale_openForLanguageTag("fa-u-nu-thai", -1, &status); + if (assertSuccess(WHERE "ulocale_openForLanguageTag()", &status)) { + char buffer[ULOC_FULLNAME_CAPACITY]; + ulocale_getUnicodeKeywordValue(l, "nu", -1, buffer, ULOC_FULLNAME_CAPACITY, &status); + if (assertSuccess(WHERE "ulocale_getUnicodeKeywordValue()", &status)) { + assertEquals(WHERE "ulocale_getUnicodeKeywordValue(\"nu\")", "thai", buffer); + } + + ulocale_getUnicodeKeywordValue(l, "ca", -1, buffer, ULOC_FULLNAME_CAPACITY, &status); + if (U_FAILURE(status) || buffer[0] != '\0') { + log_knownIssue( + "ICU-22459", + WHERE "ulocale_getUnicodeKeywordValue(\"fa-u-nu-thai\", \"ca\") should not return error and should return empty string."); + } + ulocale_getUnicodeKeywordValue(l, "co", -1, buffer, ULOC_FULLNAME_CAPACITY, &status); + if (U_FAILURE(status) || buffer[0] != '\0') { + log_knownIssue( + "ICU-22459", + WHERE "ulocale_getUnicodeKeywordValue(\"fa-u-nu-thai\", \"co\") should not return error and should return empty string."); + } + ulocale_close(l); + } +} + +void addULocaleTest(TestNode** root); +void addULocaleTest(TestNode** root) +{ + TESTCASE(TestBasicGetters); + TESTCASE(TestForLanguageTag); + TESTCASE(TestGetKeywords); + TESTCASE(TestGetKeywordsEmpty); + TESTCASE(TestGetKeywordsWithPrivateUse); + TESTCASE(TestGetUnicodeKeywords); + TESTCASE(TestGetUnicodeKeywordsEmpty); + TESTCASE(TestGetUnicodeKeywordsWithPrivateUse); + TESTCASE(TestGetKeywordValue); + TESTCASE(TestGetUnicodeKeywordValue); +} + diff --git a/icu4c/source/test/depstest/dependencies.txt b/icu4c/source/test/depstest/dependencies.txt index 29819db31af5..ca5027ed5cb6 100644 --- a/icu4c/source/test/depstest/dependencies.txt +++ b/icu4c/source/test/depstest/dependencies.txt @@ -653,6 +653,7 @@ group: resourcebundle locbased.o loclikely.o localebuilder.o + ulocale.o ulocbuilder.o deps udata ucol_swp