blob: ec03cde0f5d6c13425b27054d4d41f4b904df50b [file] [log] [blame]
/*
* 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*/