Author: zooey Date: 2010-03-31 23:23:07 +0200 (Wed, 31 Mar 2010) New Revision: 36017 Changeset: http://dev.haiku-os.org/changeset/36017/haiku Added: haiku/branches/developer/zooey/posix-locale/src/tests/system/libroot/posix/locale_test.cpp Modified: haiku/branches/developer/zooey/posix-locale/headers/posix/locale.h haiku/branches/developer/zooey/posix-locale/src/system/libroot/posix/locale/ICULocaleBackend.cpp haiku/branches/developer/zooey/posix-locale/src/system/libroot/posix/locale/ICULocaleBackend.h haiku/branches/developer/zooey/posix-locale/src/system/libroot/posix/locale/Jamfile haiku/branches/developer/zooey/posix-locale/src/system/libroot/posix/locale/setlocale.cpp haiku/branches/developer/zooey/posix-locale/src/tests/system/libroot/posix/Jamfile Log: * adjusted setlocale() to support setting a different locale for each category * fleshed out backend some more, now there are (more or less stubbed) classes for each category, which will hold the respective data once we get to it * added LC_LAST and commentary note to locale.h * added trivial test locale_test.cpp basic setting and getting of different locales seems to work now (still no effect, though) Modified: haiku/branches/developer/zooey/posix-locale/headers/posix/locale.h =================================================================== --- haiku/branches/developer/zooey/posix-locale/headers/posix/locale.h 2010-03-31 18:34:03 UTC (rev 36016) +++ haiku/branches/developer/zooey/posix-locale/headers/posix/locale.h 2010-03-31 21:23:07 UTC (rev 36017) @@ -1,6 +1,6 @@ #ifndef _LOCALE_H_ #define _LOCALE_H_ -/* +/* ** Distributed under the terms of the OpenBeOS License. */ @@ -34,6 +34,11 @@ #define LC_NUMERIC 4 #define LC_TIME 5 #define LC_MESSAGES 6 +/* + * the values above must be kept in loopable order (i.e. strictly increasing + * with no holes) and in sync with the value below + */ +#define LC_LAST LC_MESSAGES #ifdef __cplusplus extern "C" { Modified: haiku/branches/developer/zooey/posix-locale/src/system/libroot/posix/locale/ICULocaleBackend.cpp =================================================================== --- haiku/branches/developer/zooey/posix-locale/src/system/libroot/posix/locale/ICULocaleBackend.cpp 2010-03-31 18:34:03 UTC (rev 36016) +++ haiku/branches/developer/zooey/posix-locale/src/system/libroot/posix/locale/ICULocaleBackend.cpp 2010-03-31 21:23:07 UTC (rev 36017) @@ -10,6 +10,7 @@ #include <limits.h> #include <locale.h> +#include <string.h> namespace BPrivate { @@ -43,6 +44,129 @@ }; +CategoryData::CategoryData() +{ + *fPosixLocaleName = '\0'; + *fGivenCharset = '\0'; +} + + +CategoryData::~CategoryData() +{ +} + + +status_t +CategoryData::SetTo(const Locale& locale, const char* posixLocaleName) +{ + if (!posixLocaleName) + return B_BAD_VALUE; + + fLocale = locale; + strlcpy(fPosixLocaleName, posixLocaleName, skMaxPosixLocaleNameLen); + *fGivenCharset = '\0'; + + // POSIX locales often contain an embedded charset, but ICU does not + // handle these within locales (that part of the name is simply + // ignored). + // We need to fetch the charset specification and lookup an appropriate + // ICU charset converter. This converter will later be used to get info + // about ctype properties. + const char* charsetStart = strchr(fPosixLocaleName, '.'); + if (charsetStart) { + ++charsetStart; + int l = 0; + while (charsetStart[l] != '\0' && charsetStart[l] != '@') + ++l; + snprintf(fGivenCharset, UCNV_MAX_CONVERTER_NAME_LENGTH, "%.*s", l, + charsetStart); + } + + return B_OK; +} + + +status_t +CollateData::SetTo(const Locale& locale, const char* posixLocaleName) +{ + status_t result = CategoryData::SetTo(locale, posixLocaleName); + + return result; +} + + +CtypeData::CtypeData() + : + fConverter(NULL) +{ +} + + +CtypeData::~CtypeData() +{ + if (fConverter) + ucnv_close(fConverter); +} + + +status_t +CtypeData::SetTo(const Locale& locale, const char* posixLocaleName) +{ + status_t result = CategoryData::SetTo(locale, posixLocaleName); + if (result == B_OK && *fGivenCharset != '\0') { + UErrorCode icuStatus = U_ZERO_ERROR; + fConverter = ucnv_open(fGivenCharset, &icuStatus); + if (fConverter == NULL) + result = B_BAD_VALUE; + } + + return result; +} + + +status_t +MessagesData::SetTo(const Locale& locale, const char* posixLocaleName) +{ + status_t result = CategoryData::SetTo(locale, posixLocaleName); + + return result; +} + + +status_t +MonetaryData::SetTo(const Locale& locale, const char* posixLocaleName) +{ + status_t result = CategoryData::SetTo(locale, posixLocaleName); + + return result; +} + + +status_t +NumericData::SetTo(const Locale& locale, const char* posixLocaleName) +{ + status_t result = CategoryData::SetTo(locale, posixLocaleName); + + return result; +} + + +status_t +TimeData::SetTo(const Locale& locale, const char* posixLocaleName) +{ + status_t result = CategoryData::SetTo(locale, posixLocaleName); + + return result; +} + + +extern "C" LocaleBackend* +CreateInstance() +{ + return new(std::nothrow) ICULocaleBackend(); +} + + ICULocaleBackend::ICULocaleBackend() { } @@ -54,9 +178,52 @@ const char* -ICULocaleBackend::SetLocale(int category, const char* locale) +ICULocaleBackend::SetLocale(int category, const char* posixLocaleName) { - return locale; + if (posixLocaleName == NULL) + return _QueryLocale(category); + + Locale locale = Locale::createCanonical(posixLocaleName); + switch (category) { + case LC_ALL: + if (fCollateData.SetTo(locale, posixLocaleName) != B_OK + || fCtypeData.SetTo(locale, posixLocaleName) != B_OK + || fMessagesData.SetTo(locale, posixLocaleName) != B_OK + || fMonetaryData.SetTo(locale, posixLocaleName) != B_OK + || fNumericData.SetTo(locale, posixLocaleName) != B_OK + || fTimeData.SetTo(locale, posixLocaleName) != B_OK) + return NULL; + break; + case LC_COLLATE: + if (fCollateData.SetTo(locale, posixLocaleName) != B_OK) + return NULL; + break; + case LC_CTYPE: + if (fCtypeData.SetTo(locale, posixLocaleName) != B_OK) + return NULL; + break; + case LC_MESSAGES: + if (fMessagesData.SetTo(locale, posixLocaleName) != B_OK) + return NULL; + break; + case LC_MONETARY: + if (fMonetaryData.SetTo(locale, posixLocaleName) != B_OK) + return NULL; + break; + case LC_NUMERIC: + if (fNumericData.SetTo(locale, posixLocaleName) != B_OK) + return NULL; + break; + case LC_TIME: + if (fTimeData.SetTo(locale, posixLocaleName) != B_OK) + return NULL; + break; + default: + // unsupported category + return NULL; + } + + return posixLocaleName; } @@ -67,10 +234,48 @@ } -extern "C" LocaleBackend* -CreateInstance() +const char* +ICULocaleBackend::_QueryLocale(int category) { - return new(std::nothrow) ICULocaleBackend(); + switch (category) { + case LC_ALL: + { + // if all categories have the same locale, return that + const char* locale = fCollateData.PosixLocaleName(); + if (strcmp(locale, fCtypeData.PosixLocaleName()) == 0 + && strcmp(locale, fMessagesData.PosixLocaleName()) == 0 + && strcmp(locale, fMonetaryData.PosixLocaleName()) == 0 + && strcmp(locale, fNumericData.PosixLocaleName()) == 0 + && strcmp(locale, fTimeData.PosixLocaleName()) == 0) + return locale; + + // build a string with all info (at least glibc does it, too) + static char localeDescr[512]; + snprintf(localeDescr, sizeof(localeDescr), + "LC_COLLATE=%s;LC_CTYPE=%s;LC_MESSAGES=%s;LC_MONETARY=%s;" + "LC_NUMERIC=%s;LC_TIME=%s", + fCollateData.PosixLocaleName(), fCtypeData.PosixLocaleName(), + fMessagesData.PosixLocaleName(), + fMonetaryData.PosixLocaleName(), fNumericData.PosixLocaleName(), + fTimeData.PosixLocaleName()); + return localeDescr; + } + case LC_COLLATE: + return fCollateData.PosixLocaleName(); + case LC_CTYPE: + return fCtypeData.PosixLocaleName(); + case LC_MESSAGES: + return fMessagesData.PosixLocaleName(); + case LC_MONETARY: + return fMonetaryData.PosixLocaleName(); + case LC_NUMERIC: + return fNumericData.PosixLocaleName(); + case LC_TIME: + return fTimeData.PosixLocaleName(); + default: + // unsupported category + return NULL; + } } Modified: haiku/branches/developer/zooey/posix-locale/src/system/libroot/posix/locale/ICULocaleBackend.h =================================================================== --- haiku/branches/developer/zooey/posix-locale/src/system/libroot/posix/locale/ICULocaleBackend.h 2010-03-31 18:34:03 UTC (rev 36016) +++ haiku/branches/developer/zooey/posix-locale/src/system/libroot/posix/locale/ICULocaleBackend.h 2010-03-31 21:23:07 UTC (rev 36017) @@ -6,19 +6,100 @@ #define _ICU_LOCALE_BACKEND_H +#include <unicode/locid.h> +#include <unicode/ucnv.h> + #include "LocaleBackend.h" namespace BPrivate { +class CategoryData { +public: + CategoryData(); +virtual ~CategoryData(); + +virtual status_t SetTo(const Locale& locale, + const char* posixLocaleName); + + const char * PosixLocaleName() + { return fPosixLocaleName; } + +protected: +static const uint16 skMaxPosixLocaleNameLen = 128; + Locale fLocale; + char fPosixLocaleName[skMaxPosixLocaleNameLen]; + char fGivenCharset[UCNV_MAX_CONVERTER_NAME_LENGTH]; +}; + + +class CollateData : public CategoryData { +public: +virtual status_t SetTo(const Locale& locale, + const char* posixLocaleName); +}; + + +class CtypeData : public CategoryData { +public: + CtypeData(); +virtual ~CtypeData(); + +virtual status_t SetTo(const Locale& locale, + const char* posixLocaleName); + +private: + UConverter* fConverter; +}; + + +class MessagesData : public CategoryData { +public: +virtual status_t SetTo(const Locale& locale, + const char* posixLocaleName); +}; + + +class MonetaryData : public CategoryData { +public: +virtual status_t SetTo(const Locale& locale, + const char* posixLocaleName); +}; + + +class NumericData : public CategoryData { +public: +virtual status_t SetTo(const Locale& locale, + const char* posixLocaleName); +}; + + +class TimeData : public CategoryData { +public: +virtual status_t SetTo(const Locale& locale, + const char* posixLocaleName); +}; + + class ICULocaleBackend : public LocaleBackend { public: ICULocaleBackend(); virtual ~ICULocaleBackend(); -virtual const char* SetLocale(int category, const char* locale); +virtual const char* SetLocale(int category, + const char* posixLocaleName); virtual struct lconv* LocaleConv(); + +private: + const char* _QueryLocale(int category); + + CollateData fCollateData; + CtypeData fCtypeData; + MessagesData fMessagesData; + MonetaryData fMonetaryData; + NumericData fNumericData; + TimeData fTimeData; }; Modified: haiku/branches/developer/zooey/posix-locale/src/system/libroot/posix/locale/Jamfile =================================================================== --- haiku/branches/developer/zooey/posix-locale/src/system/libroot/posix/locale/Jamfile 2010-03-31 18:34:03 UTC (rev 36016) +++ haiku/branches/developer/zooey/posix-locale/src/system/libroot/posix/locale/Jamfile 2010-03-31 21:23:07 UTC (rev 36017) @@ -12,7 +12,11 @@ #wcrtomb.c ; +UseLibraryHeaders icu ; + SharedLibrary libroot-addon-locale.so : ICULocaleBackend.cpp + : + $(TARGET_LIBSTDC++) libicu-common.so libicu-i18n.so ; Modified: haiku/branches/developer/zooey/posix-locale/src/system/libroot/posix/locale/setlocale.cpp =================================================================== --- haiku/branches/developer/zooey/posix-locale/src/system/libroot/posix/locale/setlocale.cpp 2010-03-31 18:34:03 UTC (rev 36016) +++ haiku/branches/developer/zooey/posix-locale/src/system/libroot/posix/locale/setlocale.cpp 2010-03-31 21:23:07 UTC (rev 36017) @@ -15,60 +15,105 @@ using BPrivate::LocaleBackend; -static const char* -LocaleAccordingToEnvironment(int category) +static status_t +GetLocalesFromEnvironment(int category, const char** locales) { const char* locale = getenv("LC_ALL"); - if (!locale || *locale == '\0') { - switch (category) { - case LC_COLLATE: - locale = getenv("LC_COLLATE"); - break; - case LC_CTYPE: - locale = getenv("LC_CTYPE"); - break; - case LC_MESSAGES: - locale = getenv("LC_MESSAGES"); - break; - case LC_MONETARY: - locale = getenv("LC_MONETARY"); - break; - case LC_NUMERIC: - locale = getenv("LC_NUMERIC"); - break; - case LC_TIME: - locale = getenv("LC_TIME"); - break; + if (locale && *locale) + locales[category] = locale; + else { + // the order of the names must match the one specified in locale.h + const char* categoryNames[] = { + "LC_ALL", + "LC_COLLATE", + "LC_CTYPE", + "LC_MONETARY", + "LC_NUMERIC", + "LC_TIME", + "LC_MESSAGES" + }; + int from, to; + if (category == LC_ALL) { + // we need to check each real category if all of them should be set + from = 1; + to = LC_LAST; + } else + from = to = category; + bool haveDifferentLocales = false; + locale = NULL; + for (int lc = from; lc <= to; lc++) { + const char* lastLocale = locale; + locale = getenv(categoryNames[lc]); + if (!locale || *locale == '\0') + locale = getenv("LANG"); + if (!locale || *locale == '\0') + locale = "POSIX"; + locales[lc] = locale; + if (lastLocale && strcmp(locale, lastLocale) != 0) + haveDifferentLocales = true; } - if (!locale || *locale == '\0') - locale = getenv("LANG"); - if (!locale || *locale == '\0') - locale = "POSIX"; + if (!haveDifferentLocales) { + // we can set all locales at once + locales[LC_ALL] = locale; + } } - return locale; + + return B_OK; } extern "C" char* setlocale(int category, const char* locale) { - // update locale according to environment vars, if requested - if (locale && *locale == '\0') - locale = LocaleAccordingToEnvironment(category); + if (category < 0 || category > LC_LAST) + return NULL; - // for any locale other than POSIX/C, we try to activate the ICU backend, - // unless the backend has already been loaded, or a query has been requested - if (!gLocaleBackend && locale != NULL && strcmp(locale, "POSIX") != 0 - && strcmp(locale, "C") != 0) { - if (LocaleBackend::LoadBackend() != B_OK) - return NULL; + if (locale == NULL) { + // query the locale of the given category + if (gLocaleBackend != NULL) + return const_cast<char*>(gLocaleBackend->SetLocale(category, NULL)); + else + return "POSIX"; } - if (gLocaleBackend) - return const_cast<char*>(gLocaleBackend->SetLocale(category, locale)); + // we may have to invoke SetLocale once for each category, so we use an + // array to collect the locale per category + const char* locales[LC_LAST + 1]; + for (int lc = 0; lc <= LC_LAST; lc++) + locales[lc] = NULL; - if (locale && strcmp(locale, "C") == 0) - return "C"; + if (*locale == '\0') + GetLocalesFromEnvironment(category, locales); + else + locales[category] = locale; + if (!gLocaleBackend) { + // for any locale other than POSIX/C, we try to activate the ICU + // backend + bool needBackend = false; + for (int lc = 0; lc <= LC_LAST; lc++) { + if (locales[lc] != NULL && strcmp(locales[lc], "POSIX") != 0 + && strcmp(locales[lc], "C") != 0) { + needBackend = true; + break; + } + } + if (needBackend && LocaleBackend::LoadBackend() != B_OK) + return NULL; + } + + if (gLocaleBackend != NULL) { + for (int lc = 0; lc <= LC_LAST; lc++) { + if (locales[lc] != NULL) { + locale = gLocaleBackend->SetLocale(lc, locales[lc]); + if (lc == LC_ALL) { + // skip the rest, LC_ALL overrides + return const_cast<char*>(locale); + } + } + } + return const_cast<char*>(gLocaleBackend->SetLocale(category, NULL)); + } + return "POSIX"; } Modified: haiku/branches/developer/zooey/posix-locale/src/tests/system/libroot/posix/Jamfile =================================================================== --- haiku/branches/developer/zooey/posix-locale/src/tests/system/libroot/posix/Jamfile 2010-03-31 18:34:03 UTC (rev 36016) +++ haiku/branches/developer/zooey/posix-locale/src/tests/system/libroot/posix/Jamfile 2010-03-31 21:23:07 UTC (rev 36017) @@ -76,6 +76,10 @@ : xsi_sem_test1.cpp ; +SimpleTest locale_test + : locale_test.cpp +; + SimpleTest mbtest : mbtest.c ; Added: haiku/branches/developer/zooey/posix-locale/src/tests/system/libroot/posix/locale_test.cpp =================================================================== --- haiku/branches/developer/zooey/posix-locale/src/tests/system/libroot/posix/locale_test.cpp (rev 0) +++ haiku/branches/developer/zooey/posix-locale/src/tests/system/libroot/posix/locale_test.cpp 2010-03-31 21:23:07 UTC (rev 36017) @@ -0,0 +1,46 @@ +/* + * Copyright 2010, Oliver Tappe, zooey@xxxxxxxxxxxxxxx + * Distributed under the terms of the MIT License. + */ + + +#include <locale.h> +#include <stdio.h> + + +/* + * Test several different aspects of the POSIX locale and the functions + * influenced by it. + */ +int +main(void) +{ + const char* locales[] = { + "POSIX", + "C", + "de_DE", + "de_DE.iso8859-1", + "De_dE.IsO8859-15", + "de_DE.utf8", + "de_DE.UTF-8", + "de_DE@euro", + "de_DE@EURO", + "de_DE.utf-8@Euro", + "en_US.ANSI_X3.4-1968", + "hr_HR.ISO-8859-2", + "POSIX", + "C", + NULL + }; + for(int i = 0; locales[i] != NULL; ++i) { + printf("locale '%s': ", locales[i]); + char* result = setlocale(LC_ALL, locales[i]); + if (!result) { + printf("not ok\n"); + continue; + } + printf("ok (%s)\n", result); + } + + return 0; +}