When including 'wine/port.h', include it first.
[wine] / dlls / msvcrt / locale.c
1 /*
2  * msvcrt.dll locale functions
3  *
4  * Copyright 2000 Jon Griffiths
5  */
6 #include "winnt.h"
7 #include "winbase.h"
8 #include "winuser.h"
9
10 #include "msvcrt.h"
11 #include "msvcrt/locale.h"
12
13 DEFAULT_DEBUG_CHANNEL(msvcrt);
14
15 /* FIXME: Need to hold locale for each LC_* type and aggregate
16  * string to produce lc_all.
17  */
18 #define MAX_ELEM_LEN 64 /* Max length of country/language/CP string */
19 #define MAX_LOCALE_LENGTH 256
20 char MSVCRT_current_lc_all[MAX_LOCALE_LENGTH];
21 LCID MSVCRT_current_lc_all_lcid;
22 int MSVCRT_current_lc_all_cp;
23
24 /* MT */
25 extern CRITICAL_SECTION MSVCRT_locale_cs;
26 #define LOCK_LOCALE    EnterCriticalSection(&MSVCRT_locale_cs)
27 #define UNLOCK_LOCALE  LeaveCriticalSection(&MSVCRT_locale_cs)
28
29 /* ctype data modified when the locale changes */
30 extern WORD MSVCRT__ctype [257];
31 extern WORD MSVCRT_current_ctype[257];
32 extern WORD* MSVCRT__pctype;
33
34 /* mbctype data modified when the locale changes */
35 extern int MSVCRT___mb_cur_max;
36 extern unsigned char MSVCRT_mbctype[257];
37
38 #define MSVCRT_LEADBYTE  0x8000
39
40 /* Friendly country strings & iso codes for synonym support.
41  * Based on MS documentation for setlocale().
42  */
43 static const char* _country_synonyms[] =
44 {
45   "Hong Kong","HK",
46   "Hong-Kong","HK",
47   "New Zealand","NZ",
48   "New-Zealand","NZ",
49   "PR China","CN",
50   "PR-China","CN",
51   "United Kingdom","GB",
52   "United-Kingdom","GB",
53   "Britain","GB",
54   "England","GB",
55   "Great Britain","GB",
56   "United States","US",
57   "United-States","US",
58   "America","US"
59 };
60
61 /* INTERNAL: Map a synonym to an ISO code */
62 static void remap_synonym(char *name)
63 {
64   size_t i;
65   for (i = 0; i < sizeof(_country_synonyms)/sizeof(char*); i += 2 )
66   {
67     if (!strcasecmp(_country_synonyms[i],name))
68     {
69       TRACE(":Mapping synonym %s to %s\n",name,_country_synonyms[i+1]);
70       name[0] = _country_synonyms[i+1][0];
71       name[1] = _country_synonyms[i+1][1];
72       name[2] = '\0';
73       return;
74     }
75   }
76 }
77
78 /* Note: Flags are weighted in order of matching importance */
79 #define FOUND_LANGUAGE         0x4
80 #define FOUND_COUNTRY          0x2
81 #define FOUND_CODEPAGE         0x1
82
83 typedef struct {
84   char search_language[MAX_ELEM_LEN];
85   char search_country[MAX_ELEM_LEN];
86   char search_codepage[MAX_ELEM_LEN];
87   char found_language[MAX_ELEM_LEN];
88   char found_country[MAX_ELEM_LEN];
89   char found_codepage[MAX_ELEM_LEN];
90   unsigned int match_flags;
91   LANGID found_lang_id;
92 } locale_search_t;
93
94 #define CONTINUE_LOOKING TRUE
95 #define STOP_LOOKING     FALSE
96
97 /* INTERNAL: Get and compare locale info with a given string */
98 static int compare_info(LCID lcid, DWORD flags, char* buff, const char* cmp)
99 {
100   buff[0] = 0;
101   GetLocaleInfoA(lcid, flags|LOCALE_NOUSEROVERRIDE,buff, MAX_ELEM_LEN);
102   if (!buff[0] || !cmp[0])
103     return 0;
104   /* Partial matches are allowed, e.g. "Germ" matches "Germany" */
105   return !strncasecmp(cmp, buff, strlen(cmp));
106 }
107
108 static BOOL CALLBACK
109 find_best_locale_proc(HMODULE hModule WINE_UNUSED, LPCSTR type WINE_UNUSED,
110                       LPCSTR name WINE_UNUSED, WORD LangID, LONG lParam)
111 {
112   locale_search_t *res = (locale_search_t *)lParam;
113   const LCID lcid = MAKELCID(LangID, SORT_DEFAULT);
114   char buff[MAX_ELEM_LEN];
115   unsigned int flags = 0;
116
117   if(PRIMARYLANGID(LangID) == LANG_NEUTRAL)
118     return CONTINUE_LOOKING;
119
120   /* Check Language */
121   if (compare_info(lcid,LOCALE_SISO639LANGNAME,buff,res->search_language) ||
122       compare_info(lcid,LOCALE_SABBREVLANGNAME,buff,res->search_language) ||
123       compare_info(lcid,LOCALE_SENGLANGUAGE,buff,res->search_language))
124   {
125     TRACE(":Found language: %s->%s\n", res->search_language, buff);
126     flags |= FOUND_LANGUAGE;
127     memcpy(res->found_language,res->search_language,MAX_ELEM_LEN);
128   }
129   else if (res->match_flags & FOUND_LANGUAGE)
130   {
131     return CONTINUE_LOOKING;
132   }
133
134   /* Check Country */
135   if (compare_info(lcid,LOCALE_SISO3166CTRYNAME,buff,res->search_country) ||
136       compare_info(lcid,LOCALE_SABBREVCTRYNAME,buff,res->search_country) ||
137       compare_info(lcid,LOCALE_SENGCOUNTRY,buff,res->search_country))
138   {
139     TRACE("Found country:%s->%s\n", res->search_country, buff);
140     flags |= FOUND_COUNTRY;
141     memcpy(res->found_country,res->search_country,MAX_ELEM_LEN);
142   }
143   else if (res->match_flags & FOUND_COUNTRY)
144   {
145     return CONTINUE_LOOKING;
146   }
147
148   /* Check codepage */
149   if (compare_info(lcid,LOCALE_IDEFAULTCODEPAGE,buff,res->search_codepage) ||
150       (compare_info(lcid,LOCALE_IDEFAULTANSICODEPAGE,buff,res->search_codepage)))
151   {
152     TRACE("Found codepage:%s->%s\n", res->search_codepage, buff);
153     flags |= FOUND_CODEPAGE;
154     memcpy(res->found_codepage,res->search_codepage,MAX_ELEM_LEN);
155   }
156   else if (res->match_flags & FOUND_CODEPAGE)
157   {
158     return CONTINUE_LOOKING;
159   }
160
161   if (flags > res->match_flags)
162   {
163     /* Found a better match than previously */
164     res->match_flags = flags;
165     res->found_lang_id = LangID;
166   }
167   if (flags & (FOUND_LANGUAGE & FOUND_COUNTRY & FOUND_CODEPAGE))
168   {
169     TRACE(":found exact locale match\n");
170     return STOP_LOOKING;
171   }
172   return CONTINUE_LOOKING;
173 }
174
175 extern int atoi(const char *);
176
177 /* Internal: Find the LCID for a locale specification */
178 static LCID MSVCRT_locale_to_LCID(locale_search_t* locale)
179 {
180   LCID lcid;
181   EnumResourceLanguagesA(GetModuleHandleA("KERNEL32"), RT_STRINGA,
182                          (LPCSTR)LOCALE_ILANGUAGE,find_best_locale_proc,
183                          (LONG)locale);
184
185   if (!locale->match_flags)
186     return 0;
187
188   /* If we were given something that didn't match, fail */
189   if (locale->search_country[0] && !(locale->match_flags & FOUND_COUNTRY))
190     return 0;
191
192   lcid =  MAKELCID(locale->found_lang_id, SORT_DEFAULT);
193
194   /* Populate partial locale, translating LCID to locale string elements */
195   if (!locale->found_codepage[0])
196   {
197     /* Even if a codepage is not enumerated for a locale
198      * it can be set if valid */
199     if (locale->search_codepage[0])
200     {
201       if (IsValidCodePage(atoi(locale->search_codepage)))
202         memcpy(locale->found_codepage,locale->search_codepage,MAX_ELEM_LEN);
203       else
204       {
205         /* Special codepage values: OEM & ANSI */
206         if (strcasecmp(locale->search_codepage,"OCP"))
207         {
208           GetLocaleInfoA(lcid, LOCALE_IDEFAULTCODEPAGE,
209                          locale->found_codepage, MAX_ELEM_LEN);
210         }
211         if (strcasecmp(locale->search_codepage,"ACP"))
212         {
213           GetLocaleInfoA(lcid, LOCALE_IDEFAULTANSICODEPAGE,
214                          locale->found_codepage, MAX_ELEM_LEN);
215         }
216         else
217           return 0;
218
219         if (!atoi(locale->found_codepage))
220            return 0;
221       }
222     }
223     else
224     {
225       /* Prefer ANSI codepages if present */
226       GetLocaleInfoA(lcid, LOCALE_IDEFAULTANSICODEPAGE,
227                      locale->found_codepage, MAX_ELEM_LEN);
228       if (!locale->found_codepage[0] || !atoi(locale->found_codepage))
229           GetLocaleInfoA(lcid, LOCALE_IDEFAULTCODEPAGE,
230                          locale->found_codepage, MAX_ELEM_LEN);
231     }
232   }
233   GetLocaleInfoA(lcid, LOCALE_SENGLANGUAGE|LOCALE_NOUSEROVERRIDE,
234                  locale->found_language, MAX_ELEM_LEN);
235   GetLocaleInfoA(lcid, LOCALE_SENGCOUNTRY|LOCALE_NOUSEROVERRIDE,
236                  locale->found_country, MAX_ELEM_LEN);
237   return lcid;
238 }
239
240 extern int snprintf(char *, int, const char *, ...);
241
242 /* INTERNAL: Set ctype behaviour for a codepage */
243 static void msvcrt_set_ctype(unsigned int codepage, LCID lcid)
244 {
245   CPINFO cp;
246
247   memset(&cp, 0, sizeof(CPINFO));
248
249   if (GetCPInfo(codepage, &cp))
250   {
251     int i;
252     char str[3];
253     unsigned char *traverse = (unsigned char *)cp.LeadByte;
254
255     memset(MSVCRT_current_ctype, 0, sizeof(MSVCRT__ctype));
256     MSVCRT_current_lc_all_cp = codepage;
257
258     /* Switch ctype macros to MBCS if needed */
259     MSVCRT___mb_cur_max = cp.MaxCharSize;
260
261     /* Set remaining ctype flags: FIXME: faster way to do this? */
262     str[1] = str[2] = 0;
263     for (i = 0; i < 256; i++)
264     {
265       if (!(MSVCRT__pctype[i] & MSVCRT_LEADBYTE))
266       {
267         str[0] = i;
268         GetStringTypeA(lcid, CT_CTYPE1, str, 1, MSVCRT__pctype + i);
269       }
270     }
271
272     /* Set leadbyte flags */
273     while (traverse[0] || traverse[1])
274     {
275       for( i = traverse[0]; i <= traverse[1]; i++ )
276         MSVCRT_current_ctype[i+1] |= MSVCRT_LEADBYTE;
277       traverse += 2;
278     };
279   }
280 }
281
282
283 /*********************************************************************
284  *              setlocale (MSVCRT.@)
285  */
286 char* MSVCRT_setlocale(int category, const char* locale)
287 {
288   LCID lcid = 0;
289   locale_search_t lc;
290   int haveLang, haveCountry, haveCP;
291   char* next;
292   int lc_all = 0;
293
294   TRACE("(%d %s)\n",category,locale);
295
296   if (category < MSVCRT_LC_MIN || category > MSVCRT_LC_MAX)
297     return NULL;
298
299   if (locale == NULL)
300   {
301     /* Report the current Locale */
302     return MSVCRT_current_lc_all;
303   }
304
305   LOCK_LOCALE;
306
307   if (locale[0] == 'L' && locale[1] == 'C' && locale[2] == '_')
308   {
309     FIXME(":restore previous locale not implemented!\n");
310     /* FIXME: Easiest way to do this is parse the string and
311      * call this function recursively with its elements,
312      * Where they differ for each lc_ type.
313      */
314     UNLOCK_LOCALE;
315     return MSVCRT_current_lc_all;
316   }
317
318   /* Default Locale: Special case handling */
319   if (!strlen(locale) || ((toupper(locale[0]) == 'C') && !locale[1]))
320   {
321     MSVCRT_current_lc_all[0] = 'C';
322     MSVCRT_current_lc_all[1] = '\0';
323     MSVCRT_current_lc_all_cp = GetACP();
324
325     switch (category) {
326     case MSVCRT_LC_ALL:
327       lc_all = 1; /* Fall through all cases ... */
328     case MSVCRT_LC_COLLATE:
329       if (!lc_all) break;
330     case MSVCRT_LC_CTYPE:
331       /* Restore C locale ctype info */
332       MSVCRT___mb_cur_max = 1;
333       memcpy(MSVCRT_current_ctype, MSVCRT__ctype, sizeof(MSVCRT__ctype));
334       memset(MSVCRT_mbctype, 0, sizeof(MSVCRT_mbctype));
335       if (!lc_all) break;
336     case MSVCRT_LC_MONETARY:
337       if (!lc_all) break;
338     case MSVCRT_LC_NUMERIC:
339       if (!lc_all) break;
340     case MSVCRT_LC_TIME:
341       break;
342     }
343     UNLOCK_LOCALE;
344     return MSVCRT_current_lc_all;
345   }
346
347   /* Get locale elements */
348   haveLang = haveCountry = haveCP = 0;
349   memset(&lc,0,sizeof(lc));
350
351   next = strchr(locale,'_');
352   if (next && next != locale)
353   {
354     haveLang = 1;
355     strncpy(lc.search_language,locale,next-locale);
356     locale += next-locale+1;
357   }
358
359   next = strchr(locale,'.');
360   if (next)
361   {
362     haveCP = 1;
363     if (next == locale)
364     {
365       locale++;
366       strncpy(lc.search_codepage, locale, MAX_ELEM_LEN);
367     }
368     else
369     {
370       if (haveLang)
371       {
372         haveCountry = 1;
373         strncpy(lc.search_country,locale,next-locale);
374         locale += next-locale+1;
375       }
376       else
377       {
378         haveLang = 1;
379         strncpy(lc.search_language,locale,next-locale);
380         locale += next-locale+1;
381       }
382       strncpy(lc.search_codepage, locale, MAX_ELEM_LEN);
383     }
384   }
385   else
386   {
387     if (haveLang)
388     {
389       haveCountry = 1;
390       strncpy(lc.search_country, locale, MAX_ELEM_LEN);
391     }
392     else
393     {
394       haveLang = 1;
395       strncpy(lc.search_language, locale, MAX_ELEM_LEN);
396     }
397   }
398
399   if (haveCountry)
400     remap_synonym(lc.search_country);
401
402   if (haveCP && !haveCountry && !haveLang)
403   {
404     FIXME(":Codepage only locale not implemented\n");
405     /* FIXME: Use default lang/country and skip locale_to_LCID()
406      * call below...
407      */
408     UNLOCK_LOCALE;
409     return NULL;
410   }
411
412   lcid = MSVCRT_locale_to_LCID(&lc);
413
414   TRACE(":found LCID %ld\n",lcid);
415
416   if (lcid == 0)
417   {
418     UNLOCK_LOCALE;
419     return NULL;
420   }
421
422   MSVCRT_current_lc_all_lcid = lcid;
423
424   snprintf(MSVCRT_current_lc_all,MAX_LOCALE_LENGTH,"%s_%s.%s",
425            lc.found_language,lc.found_country,lc.found_codepage);
426
427   switch (category) {
428   case MSVCRT_LC_ALL:
429     lc_all = 1; /* Fall through all cases ... */
430   case MSVCRT_LC_COLLATE:
431     if (!lc_all) break;
432   case MSVCRT_LC_CTYPE:
433     msvcrt_set_ctype(atoi(lc.found_codepage),lcid);
434     if (!lc_all) break;
435   case MSVCRT_LC_MONETARY:
436     if (!lc_all) break;
437   case MSVCRT_LC_NUMERIC:
438     if (!lc_all) break;
439   case MSVCRT_LC_TIME:
440     break;
441   }
442   UNLOCK_LOCALE;
443   return MSVCRT_current_lc_all;
444 }
445
446
447 /*********************************************************************
448  *              _Getdays (MSVCRT.@)
449  */
450 const char* _Getdays(void)
451 {
452   static const char *MSVCRT_days = ":Sun:Sunday:Mon:Monday:Tue:Tuesday:Wed:"
453                             "Wednesday:Thu:Thursday:Fri:Friday:Sat:Saturday";
454   /* FIXME: Use locale */
455   TRACE("(void) semi-stub\n");
456   return MSVCRT_days;
457 }
458
459 /*********************************************************************
460  *              _Getmonths (MSVCRT.@)
461  */
462 const char* _Getmonths(void)
463 {
464   static const char *MSVCRT_months = ":Jan:January:Feb:February:Mar:March:Apr:"
465                 "April:May:May:Jun:June:Jul:July:Aug:August:Sep:September:Oct:"
466                 "October:Nov:November:Dec:December";
467   /* FIXME: Use locale */
468   TRACE("(void) semi-stub\n");
469   return MSVCRT_months;
470 }
471
472 /*********************************************************************
473  *              _Getnames (MSVCRT.@)
474  */
475 const char* _Getnames(void)
476 {
477   /* FIXME: */
478   TRACE("(void) stub\n");
479   return "";
480 }
481
482 /*********************************************************************
483  *              _Strftime (MSVCRT.@)
484  */
485 const char* _Strftime(char *out, unsigned int len, const char *fmt,
486                                      const void *tm, void *foo)
487 {
488   /* FIXME: */
489   TRACE("(%p %d %s %p %p) stub\n", out, len, fmt, tm, foo);
490   return "";
491 }
492
493 /* FIXME: MBCP probably belongs in mbcs.c */
494
495 /*********************************************************************
496  *              _setmbcp (MSVCRT.@)
497  */
498 void _setmbcp(int cp)
499 {
500   LOCK_LOCALE;
501   if (MSVCRT_current_lc_all_cp != cp)
502   {
503     /* FIXME: set ctype behaviour for this cp */
504     MSVCRT_current_lc_all_cp = cp;
505   }
506   UNLOCK_LOCALE;
507 }
508
509 /*********************************************************************
510  *              _getmbcp (MSVCRT.@)
511  */
512 int _getmbcp(void)
513 {
514   return MSVCRT_current_lc_all_cp;
515 }