| /* |
| * xsltlocale.c: locale handling |
| * |
| * Reference: |
| * RFC 3066: Tags for the Identification of Languages |
| * http://www.ietf.org/rfc/rfc3066.txt |
| * ISO 639-1, ISO 3166-1 |
| * |
| * Author: Nick Wellnhofer |
| * winapi port: Roumen Petrov |
| */ |
| |
| #define IN_LIBXSLT |
| #include "libxslt.h" |
| |
| #include <string.h> |
| #include <libxml/xmlmemory.h> |
| |
| #include "xsltlocale.h" |
| #include "xsltutils.h" |
| |
| #if defined(__GLIBC__) && __GLIBC__ == 2 && __GLIBC_MINOR__ <= 2 |
| #define newlocale __newlocale |
| #define freelocale __freelocale |
| #define strxfrm_l __strxfrm_l |
| #define LC_COLLATE_MASK (1 << LC_COLLATE) |
| #endif |
| |
| #define ISALPHA(c) ((c & 0xc0) == 0x40 && (unsigned)((c & 0x1f) - 1) < 26) |
| #define TOUPPER(c) (c & ~0x20) |
| #define TOLOWER(c) (c | 0x20) |
| |
| /*without terminating null character*/ |
| #define XSLTMAX_ISO639LANGLEN 8 |
| #define XSLTMAX_ISO3166CNTRYLEN 8 |
| /* <lang>-<cntry> */ |
| #define XSLTMAX_LANGTAGLEN (XSLTMAX_ISO639LANGLEN+1+XSLTMAX_ISO3166CNTRYLEN) |
| |
| static const xmlChar* xsltDefaultRegion(const xmlChar *localeName); |
| |
| #ifdef XSLT_LOCALE_WINAPI |
| xmlRMutexPtr xsltLocaleMutex = NULL; |
| |
| struct xsltRFC1766Info_s { |
| /*note typedef unsigned char xmlChar !*/ |
| xmlChar tag[XSLTMAX_LANGTAGLEN+1]; |
| /*note typedef LCID xsltLocale !*/ |
| xsltLocale lcid; |
| }; |
| typedef struct xsltRFC1766Info_s xsltRFC1766Info; |
| |
| static int xsltLocaleListSize = 0; |
| static xsltRFC1766Info *xsltLocaleList = NULL; |
| |
| |
| static xsltLocale |
| xslt_locale_WINAPI(const xmlChar *languageTag) { |
| int k; |
| xsltRFC1766Info *p = xsltLocaleList; |
| |
| for (k=0; k<xsltLocaleListSize; k++, p++) |
| if (xmlStrcmp(p->tag, languageTag) == 0) return p->lcid; |
| return((xsltLocale)0); |
| } |
| |
| static void xsltEnumSupportedLocales(void); |
| #endif |
| |
| /** |
| * xsltNewLocale: |
| * @languageTag: RFC 3066 language tag |
| * |
| * Creates a new locale of an opaque system dependent type based on the |
| * language tag. |
| * |
| * Returns the locale or NULL on error or if no matching locale was found |
| */ |
| xsltLocale |
| xsltNewLocale(const xmlChar *languageTag) { |
| #ifdef XSLT_LOCALE_XLOCALE |
| xsltLocale locale; |
| char localeName[XSLTMAX_LANGTAGLEN+6]; /* 6 chars for ".utf8\0" */ |
| const xmlChar *p = languageTag; |
| const char *region = NULL; |
| char *q = localeName; |
| int i, llen; |
| |
| /* Convert something like "pt-br" to "pt_BR.utf8" */ |
| |
| if (languageTag == NULL) |
| return(NULL); |
| |
| for (i=0; i<XSLTMAX_ISO639LANGLEN && ISALPHA(*p); ++i) |
| *q++ = TOLOWER(*p++); |
| |
| if (i == 0) |
| return(NULL); |
| |
| llen = i; |
| *q++ = '_'; |
| |
| if (*p) { |
| if (*p++ != '-') |
| return(NULL); |
| |
| for (i=0; i<XSLTMAX_ISO3166CNTRYLEN && ISALPHA(*p); ++i) |
| *q++ = TOUPPER(*p++); |
| |
| if (i == 0 || *p) |
| return(NULL); |
| |
| memcpy(q, ".utf8", 6); |
| locale = newlocale(LC_COLLATE_MASK, localeName, NULL); |
| if (locale != NULL) |
| return(locale); |
| |
| /* Continue without using country code */ |
| |
| q = localeName + llen + 1; |
| } |
| |
| /* Try locale without territory, e.g. for Esperanto (eo) */ |
| |
| memcpy(q, ".utf8", 6); |
| locale = newlocale(LC_COLLATE_MASK, localeName, NULL); |
| if (locale != NULL) |
| return(locale); |
| |
| /* Try to find most common country for language */ |
| |
| if (llen != 2) |
| return(NULL); |
| |
| region = (char *)xsltDefaultRegion((xmlChar *)localeName); |
| if (region == NULL) |
| return(NULL); |
| |
| q = localeName + llen + 1; |
| *q++ = region[0]; |
| *q++ = region[1]; |
| memcpy(q, ".utf8", 6); |
| locale = newlocale(LC_COLLATE_MASK, localeName, NULL); |
| |
| return(locale); |
| #endif |
| |
| #ifdef XSLT_LOCALE_WINAPI |
| { |
| xsltLocale locale = (xsltLocale)0; |
| xmlChar localeName[XSLTMAX_LANGTAGLEN+1]; |
| xmlChar *q = localeName; |
| const xmlChar *p = languageTag; |
| int i, llen; |
| const xmlChar *region = NULL; |
| |
| if (languageTag == NULL) goto end; |
| |
| xsltEnumSupportedLocales(); |
| |
| for (i=0; i<XSLTMAX_ISO639LANGLEN && ISALPHA(*p); ++i) |
| *q++ = TOLOWER(*p++); |
| if (i == 0) goto end; |
| |
| llen = i; |
| *q++ = '-'; |
| if (*p) { /*if country tag is given*/ |
| if (*p++ != '-') goto end; |
| |
| for (i=0; i<XSLTMAX_ISO3166CNTRYLEN && ISALPHA(*p); ++i) |
| *q++ = TOUPPER(*p++); |
| if (i == 0 || *p) goto end; |
| |
| *q = '\0'; |
| locale = xslt_locale_WINAPI(localeName); |
| if (locale != (xsltLocale)0) goto end; |
| } |
| /* Try to find most common country for language */ |
| region = xsltDefaultRegion(localeName); |
| if (region == NULL) goto end; |
| |
| strcpy(localeName + llen + 1, region); |
| locale = xslt_locale_WINAPI(localeName); |
| end: |
| return(locale); |
| } |
| #endif |
| |
| #ifdef XSLT_LOCALE_NONE |
| return(NULL); |
| #endif |
| } |
| |
| static const xmlChar* |
| xsltDefaultRegion(const xmlChar *localeName) { |
| xmlChar c; |
| /* region should be xmlChar, but gcc warns on all string assignments */ |
| const char *region = NULL; |
| |
| c = localeName[1]; |
| /* This is based on the locales from glibc 2.3.3 */ |
| |
| switch (localeName[0]) { |
| case 'a': |
| if (c == 'a' || c == 'm') region = "ET"; |
| else if (c == 'f') region = "ZA"; |
| else if (c == 'n') region = "ES"; |
| else if (c == 'r') region = "AE"; |
| else if (c == 'z') region = "AZ"; |
| break; |
| case 'b': |
| if (c == 'e') region = "BY"; |
| else if (c == 'g') region = "BG"; |
| else if (c == 'n') region = "BD"; |
| else if (c == 'r') region = "FR"; |
| else if (c == 's') region = "BA"; |
| break; |
| case 'c': |
| if (c == 'a') region = "ES"; |
| else if (c == 's') region = "CZ"; |
| else if (c == 'y') region = "GB"; |
| break; |
| case 'd': |
| if (c == 'a') region = "DK"; |
| else if (c == 'e') region = "DE"; |
| break; |
| case 'e': |
| if (c == 'l') region = "GR"; |
| else if (c == 'n' || c == 'o') region = "US"; |
| else if (c == 's' || c == 'u') region = "ES"; |
| else if (c == 't') region = "EE"; |
| break; |
| case 'f': |
| if (c == 'a') region = "IR"; |
| else if (c == 'i') region = "FI"; |
| else if (c == 'o') region = "FO"; |
| else if (c == 'r') region = "FR"; |
| break; |
| case 'g': |
| if (c == 'a') region = "IE"; |
| else if (c == 'l') region = "ES"; |
| else if (c == 'v') region = "GB"; |
| break; |
| case 'h': |
| if (c == 'e') region = "IL"; |
| else if (c == 'i') region = "IN"; |
| else if (c == 'r') region = "HT"; |
| else if (c == 'u') region = "HU"; |
| break; |
| case 'i': |
| if (c == 'd') region = "ID"; |
| else if (c == 's') region = "IS"; |
| else if (c == 't') region = "IT"; |
| else if (c == 'w') region = "IL"; |
| break; |
| case 'j': |
| if (c == 'a') region = "JP"; |
| break; |
| case 'k': |
| if (c == 'l') region = "GL"; |
| else if (c == 'o') region = "KR"; |
| else if (c == 'w') region = "GB"; |
| break; |
| case 'l': |
| if (c == 't') region = "LT"; |
| else if (c == 'v') region = "LV"; |
| break; |
| case 'm': |
| if (c == 'k') region = "MK"; |
| else if (c == 'l' || c == 'r') region = "IN"; |
| else if (c == 'n') region = "MN"; |
| else if (c == 's') region = "MY"; |
| else if (c == 't') region = "MT"; |
| break; |
| case 'n': |
| if (c == 'b' || c == 'n' || c == 'o') region = "NO"; |
| else if (c == 'e') region = "NP"; |
| else if (c == 'l') region = "NL"; |
| break; |
| case 'o': |
| if (c == 'm') region = "ET"; |
| break; |
| case 'p': |
| if (c == 'a') region = "IN"; |
| else if (c == 'l') region = "PL"; |
| else if (c == 't') region = "PT"; |
| break; |
| case 'r': |
| if (c == 'o') region = "RO"; |
| else if (c == 'u') region = "RU"; |
| break; |
| case 's': |
| switch (c) { |
| case 'e': region = "NO"; break; |
| case 'h': region = "YU"; break; |
| case 'k': region = "SK"; break; |
| case 'l': region = "SI"; break; |
| case 'o': region = "ET"; break; |
| case 'q': region = "AL"; break; |
| case 't': region = "ZA"; break; |
| case 'v': region = "SE"; break; |
| } |
| break; |
| case 't': |
| if (c == 'a' || c == 'e') region = "IN"; |
| else if (c == 'h') region = "TH"; |
| else if (c == 'i') region = "ER"; |
| else if (c == 'r') region = "TR"; |
| else if (c == 't') region = "RU"; |
| break; |
| case 'u': |
| if (c == 'k') region = "UA"; |
| else if (c == 'r') region = "PK"; |
| break; |
| case 'v': |
| if (c == 'i') region = "VN"; |
| break; |
| case 'w': |
| if (c == 'a') region = "BE"; |
| break; |
| case 'x': |
| if (c == 'h') region = "ZA"; |
| break; |
| case 'z': |
| if (c == 'h') region = "CN"; |
| else if (c == 'u') region = "ZA"; |
| break; |
| } |
| return((xmlChar *)region); |
| } |
| |
| /** |
| * xsltFreeLocale: |
| * @locale: the locale to free |
| * |
| * Frees a locale created with xsltNewLocale |
| */ |
| void |
| xsltFreeLocale(xsltLocale locale) { |
| #ifdef XSLT_LOCALE_XLOCALE |
| freelocale(locale); |
| #endif |
| } |
| |
| /** |
| * xsltStrxfrm: |
| * @locale: locale created with xsltNewLocale |
| * @string: UTF-8 string to transform |
| * |
| * Transforms a string according to locale. The transformed string must then be |
| * compared with xsltLocaleStrcmp and freed with xmlFree. |
| * |
| * Returns the transformed string or NULL on error |
| */ |
| xsltLocaleChar * |
| xsltStrxfrm(xsltLocale locale, const xmlChar *string) |
| { |
| #ifdef XSLT_LOCALE_NONE |
| return(NULL); |
| #else |
| size_t xstrlen, r; |
| xsltLocaleChar *xstr; |
| |
| #ifdef XSLT_LOCALE_XLOCALE |
| xstrlen = strxfrm_l(NULL, (const char *)string, 0, locale) + 1; |
| xstr = (xsltLocaleChar *) xmlMalloc(xstrlen); |
| if (xstr == NULL) { |
| xsltTransformError(NULL, NULL, NULL, |
| "xsltStrxfrm : out of memory error\n"); |
| return(NULL); |
| } |
| |
| r = strxfrm_l((char *)xstr, (const char *)string, xstrlen, locale); |
| #endif |
| |
| #ifdef XSLT_LOCALE_WINAPI |
| xstrlen = MultiByteToWideChar(CP_UTF8, 0, string, -1, NULL, 0); |
| if (xstrlen == 0) { |
| xsltTransformError(NULL, NULL, NULL, "xsltStrxfrm : MultiByteToWideChar check failed\n"); |
| return(NULL); |
| } |
| xstr = (xsltLocaleChar*) xmlMalloc(xstrlen * sizeof(xsltLocaleChar)); |
| if (xstr == NULL) { |
| xsltTransformError(NULL, NULL, NULL, "xsltStrxfrm : out of memory\n"); |
| return(NULL); |
| } |
| r = MultiByteToWideChar(CP_UTF8, 0, string, -1, xstr, xstrlen); |
| if (r == 0) { |
| xsltTransformError(NULL, NULL, NULL, "xsltStrxfrm : MultiByteToWideChar failed\n"); |
| xmlFree(xstr); |
| return(NULL); |
| } |
| return(xstr); |
| #endif /* XSLT_LOCALE_WINAPI */ |
| |
| if (r >= xstrlen) { |
| xsltTransformError(NULL, NULL, NULL, "xsltStrxfrm : strxfrm failed\n"); |
| xmlFree(xstr); |
| return(NULL); |
| } |
| |
| return(xstr); |
| #endif /* XSLT_LOCALE_NONE */ |
| } |
| |
| /** |
| * xsltLocaleStrcmp: |
| * @locale: a locale identifier |
| * @str1: a string transformed with xsltStrxfrm |
| * @str2: a string transformed with xsltStrxfrm |
| * |
| * Compares two strings transformed with xsltStrxfrm |
| * |
| * Returns a value < 0 if str1 sorts before str2, |
| * a value > 0 if str1 sorts after str2, |
| * 0 if str1 and str2 are equal wrt sorting |
| */ |
| int |
| xsltLocaleStrcmp(xsltLocale locale, const xsltLocaleChar *str1, const xsltLocaleChar *str2) { |
| (void)locale; |
| #ifdef XSLT_LOCALE_WINAPI |
| { |
| int ret; |
| if (str1 == str2) return(0); |
| if (str1 == NULL) return(-1); |
| if (str2 == NULL) return(1); |
| ret = CompareStringW(locale, 0, str1, -1, str2, -1); |
| if (ret == 0) { |
| xsltTransformError(NULL, NULL, NULL, "xsltLocaleStrcmp : CompareStringW fail\n"); |
| return(0); |
| } |
| return(ret - 2); |
| } |
| #else |
| return(xmlStrcmp(str1, str2)); |
| #endif |
| } |
| |
| #ifdef XSLT_LOCALE_WINAPI |
| /** |
| * xsltCountSupportedLocales: |
| * @lcid: not used |
| * |
| * callback used to count locales |
| * |
| * Returns TRUE |
| */ |
| BOOL CALLBACK |
| xsltCountSupportedLocales(LPSTR lcid) { |
| (void) lcid; |
| ++xsltLocaleListSize; |
| return(TRUE); |
| } |
| |
| /** |
| * xsltIterateSupportedLocales: |
| * @lcid: not used |
| * |
| * callback used to track locales |
| * |
| * Returns TRUE if not at the end of the array |
| */ |
| BOOL CALLBACK |
| xsltIterateSupportedLocales(LPSTR lcid) { |
| static int count = 0; |
| xmlChar iso639lang [XSLTMAX_ISO639LANGLEN +1]; |
| xmlChar iso3136ctry[XSLTMAX_ISO3166CNTRYLEN+1]; |
| int k, l; |
| xsltRFC1766Info *p = xsltLocaleList + count; |
| |
| k = sscanf(lcid, "%lx", (long*)&p->lcid); |
| if (k < 1) goto end; |
| /*don't count terminating null character*/ |
| k = GetLocaleInfoA(p->lcid, LOCALE_SISO639LANGNAME , iso639lang , sizeof(iso639lang )); |
| if (--k < 1) goto end; |
| l = GetLocaleInfoA(p->lcid, LOCALE_SISO3166CTRYNAME, iso3136ctry, sizeof(iso3136ctry)); |
| if (--l < 1) goto end; |
| |
| { /*fill results*/ |
| xmlChar *q = p->tag; |
| memcpy(q, iso639lang, k); |
| q += k; |
| *q++ = '-'; |
| memcpy(q, iso3136ctry, l); |
| q += l; |
| *q = '\0'; |
| } |
| ++count; |
| end: |
| return((count < xsltLocaleListSize) ? TRUE : FALSE); |
| } |
| |
| |
| static void |
| xsltEnumSupportedLocales(void) { |
| xmlRMutexLock(xsltLocaleMutex); |
| if (xsltLocaleListSize <= 0) { |
| size_t len; |
| |
| EnumSystemLocalesA(xsltCountSupportedLocales, LCID_SUPPORTED); |
| |
| len = xsltLocaleListSize * sizeof(xsltRFC1766Info); |
| xsltLocaleList = xmlMalloc(len); |
| memset(xsltLocaleList, 0, len); |
| EnumSystemLocalesA(xsltIterateSupportedLocales, LCID_SUPPORTED); |
| } |
| xmlRMutexUnlock(xsltLocaleMutex); |
| } |
| |
| #endif /*def XSLT_LOCALE_WINAPI*/ |