2008-02-09 Marcus Brinkmann <marcus@g10code.de>
[gnupg.git] / jnlib / w32-gettext.c
blob2af655be5df08f6e724db7962e7d433f240ca2d3
1 /* w32-gettext.c - A simplified version of gettext for use under W32.
2 * Copyright (C) 1995, 1996, 1997, 1999,
3 * 2005, 2007 Free Software Foundation, Inc.
5 * This file is part of JNLIB.
7 * JNLIB is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU Lesser General Public License as
9 * published by the Free Software Foundation; either version 3 of
10 * the License, or (at your option) any later version.
12 * JNLIB is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this program; if not, see <http://www.gnu.org/licenses/>.
21 /*
22 This is a simplified version of gettext written by Ulrich Drepper.
23 It is used for the Win32 version of GnuPG becaise all the overhead
24 of gettext is not needed and we have to do some special Win32
25 stuff. I decided that this is far easier than to tweak gettext for
26 the special cases (I tried it but it is a lot of code). wk 15.09.99
29 #include <config.h>
30 #ifdef USE_SIMPLE_GETTEXT
31 #if !defined (_WIN32) && !defined (__CYGWIN32__)
32 #error This module may only be build for Windows or Cygwin32
33 #endif
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <string.h>
38 #include <ctype.h>
39 #include <errno.h>
40 #include <sys/types.h>
41 #include <sys/stat.h>
43 #include "libjnlib-config.h"
44 #include "types.h"
45 #include "stringhelp.h"
46 #include "utf8conv.h"
47 #include "w32help.h"
49 #include "windows.h" /* For GetModuleFileName. */
51 /* The magic number of the GNU message catalog format. */
52 #define MAGIC 0x950412de
53 #define MAGIC_SWAPPED 0xde120495
55 /* Revision number of the currently used .mo (binary) file format. */
56 #define MO_REVISION_NUMBER 0
59 /* Header for binary .mo file format. */
60 struct mo_file_header
62 /* The magic number. */
63 u32 magic;
64 /* The revision number of the file format. */
65 u32 revision;
66 /* The number of strings pairs. */
67 u32 nstrings;
68 /* Offset of table with start offsets of original strings. */
69 u32 orig_tab_offset;
70 /* Offset of table with start offsets of translation strings. */
71 u32 trans_tab_offset;
72 /* Size of hashing table. */
73 u32 hash_tab_size;
74 /* Offset of first hashing entry. */
75 u32 hash_tab_offset;
78 struct string_desc
80 /* Length of addressed string. */
81 u32 length;
82 /* Offset of string in file. */
83 u32 offset;
87 struct overflow_space_s
89 struct overflow_space_s *next;
90 u32 idx;
91 char d[1];
94 struct loaded_domain
96 char *data;
97 int must_swap;
98 u32 nstrings;
99 char *mapped; /* 0 = not yet mapped, 1 = mapped,
100 2 = mapped to
101 overflow space */
102 struct overflow_space_s *overflow_space;
103 struct string_desc *orig_tab;
104 struct string_desc *trans_tab;
105 u32 hash_size;
106 u32 *hash_tab;
110 static struct loaded_domain *the_domain;
112 static __inline__ u32
113 do_swap_u32( u32 i )
115 return (i << 24) | ((i & 0xff00) << 8) | ((i >> 8) & 0xff00) | (i >> 24);
118 #define SWAPIT(flag, data) ((flag) ? do_swap_u32(data) : (data) )
121 /* We assume to have `unsigned long int' value with at least 32 bits. */
122 #define HASHWORDBITS 32
124 /* The so called `hashpjw' function by P.J. Weinberger
125 [see Aho/Sethi/Ullman, COMPILERS: Principles, Techniques and Tools,
126 1986, 1987 Bell Telephone Laboratories, Inc.] */
128 static __inline__ ulong
129 hash_string (const char *str_param)
131 unsigned long int hval, g;
132 const char *str = str_param;
134 hval = 0;
135 while (*str != '\0')
137 hval <<= 4;
138 hval += (unsigned long int) *str++;
139 g = hval & ((unsigned long int) 0xf << (HASHWORDBITS - 4));
140 if (g != 0)
142 hval ^= g >> (HASHWORDBITS - 8);
143 hval ^= g;
146 return hval;
150 static struct loaded_domain *
151 load_domain (const char *filename)
153 FILE *fp;
154 size_t size;
155 struct stat st;
156 struct mo_file_header *data = NULL;
157 struct loaded_domain *domain = NULL;
158 size_t to_read;
159 char *read_ptr;
161 fp = fopen( filename, "rb" );
162 if (!fp)
163 return NULL; /* Can't open the file. */
164 /* We need to know the size of the file. */
165 if (fstat( fileno(fp ), &st )
166 || (size = (size_t)st.st_size) != st.st_size
167 || size < sizeof (struct mo_file_header) )
169 fclose (fp);
170 return NULL;
173 data = jnlib_malloc (size);
174 if (!data)
176 fclose (fp);
177 return NULL; /* Out of memory. */
180 to_read = size;
181 read_ptr = (char *) data;
184 long int nb;
186 nb = fread (read_ptr, 1, to_read, fp);
187 if (nb < to_read )
189 fclose (fp);
190 jnlib_free (data);
191 return NULL; /* Read error. */
193 read_ptr += nb;
194 to_read -= nb;
196 while (to_read > 0);
197 fclose (fp);
199 /* Using the magic number we test whether it is really a message
200 catalog file. */
201 if (data->magic != MAGIC && data->magic != MAGIC_SWAPPED)
203 /* The magic number is wrong: not a message catalog file. */
204 jnlib_free (data);
205 return NULL;
208 domain = jnlib_calloc (1, sizeof *domain);
209 if (!domain)
211 jnlib_free (data);
212 return NULL;
214 domain->data = (char *) data;
215 domain->must_swap = data->magic != MAGIC;
217 /* Fill in the information about the available tables. */
218 switch (SWAPIT(domain->must_swap, data->revision))
220 case 0:
221 domain->nstrings = SWAPIT(domain->must_swap, data->nstrings);
222 domain->orig_tab = (struct string_desc *)
223 ((char *) data + SWAPIT(domain->must_swap, data->orig_tab_offset));
224 domain->trans_tab = (struct string_desc *)
225 ((char *) data + SWAPIT(domain->must_swap, data->trans_tab_offset));
226 domain->hash_size = SWAPIT(domain->must_swap, data->hash_tab_size);
227 domain->hash_tab = (u32 *)
228 ((char *) data + SWAPIT(domain->must_swap, data->hash_tab_offset));
229 break;
231 default: /* This is an invalid revision. */
232 jnlib_free( data );
233 jnlib_free( domain );
234 return NULL;
237 /* Allocate an array to keep track of code page mappings. */
238 domain->mapped = jnlib_calloc (1, domain->nstrings);
239 if (!domain->mapped)
241 jnlib_free (data);
242 jnlib_free (domain);
243 return NULL;
246 return domain;
250 /* Set the file used for translations. Pass a NULL to disable
251 translation. A new filename may be set at anytime. WARNING: After
252 changing the filename you should not access any data retrieved by
253 gettext().
255 If REGKEY is not NULL, the function tries to selected the language
256 the registry key "Lang" below that key. If in addition the
257 environment variable LANGUAGE has been set, that value will
258 override a value set by the registry key.
261 set_gettext_file ( const char *filename, const char *regkey )
263 struct loaded_domain *domain = NULL;
265 if ( filename && *filename )
267 if ( filename[0] == '/'
268 #ifdef HAVE_DRIVE_LETTERS
269 || ( isalpha(filename[0])
270 && filename[1] == ':'
271 && (filename[2] == '/' || filename[2] == '\\') )
272 #endif
275 /* absolute path - use it as is */
276 domain = load_domain( filename );
278 else if (regkey) /* Standard. */
280 char *instdir, *langid, *fname;
281 char *p;
282 int envvar_mode = 0;
284 again:
285 if (!envvar_mode && (p = getenv ("LANGUAGE")) && *p)
287 envvar_mode = 1;
288 langid = jnlib_malloc (strlen (p)+1);
289 if (!langid)
290 return -1;
291 strcpy (langid, p);
292 /* We only make use of the first language given. Strip
293 the rest. */
294 p = strchr (langid, ':');
295 if (p)
296 *p = 0;
298 /* In the $LANGUAGE case we do not use the registered
299 installation directory but the one where the gpg
300 binary has been found. */
301 instdir = jnlib_malloc (MAX_PATH+5);
302 if ( !instdir || !GetModuleFileName (NULL, instdir, MAX_PATH) )
304 jnlib_free (langid);
305 jnlib_free (instdir);
306 return -1; /* Error getting the process' file name. */
308 p = strrchr (instdir, DIRSEP_C);
309 if (!p)
311 jnlib_free (langid);
312 jnlib_free (instdir);
313 return -1; /* Invalid file name returned. */
315 *p = 0;
317 else
319 instdir = read_w32_registry_string ("HKEY_LOCAL_MACHINE",
320 regkey,
321 "Install Directory");
322 if (!instdir)
323 return -1;
324 langid = read_w32_registry_string (NULL, /* HKCU then HKLM */
325 regkey,
326 "Lang");
327 if (!langid)
329 jnlib_free (instdir);
330 return -1;
334 /* Strip stuff after a dot in case the user tried to enter
335 the entire locale syntacs as usual for POSIX. */
336 p = strchr (langid, '.');
337 if (p)
338 *p = 0;
340 /* Build the key: "<instdir>/<domain>.nls/<langid>.mo" We
341 use a directory below the installation directory with the
342 domain included in case the software has been insalled
343 with other software altogether at the same place. */
344 fname = jnlib_malloc (strlen (instdir) + 1 + strlen (filename) + 5
345 + strlen (langid) + 3 + 1);
346 if (!fname)
348 jnlib_free (instdir);
349 jnlib_free (langid);
350 return -1;
352 strcpy (stpcpy (stpcpy (stpcpy (stpcpy ( stpcpy (fname,
353 instdir),"\\"), filename), ".nls\\"), langid), ".mo");
354 jnlib_free (instdir);
355 jnlib_free (langid);
357 /* Better make sure that we don't mix forward and backward
358 slashes. It seems that some Windoze versions don't
359 accept this. */
360 for (p=fname; *p; p++)
362 if (*p == '/')
363 *p = '\\';
365 domain = load_domain (fname);
366 jnlib_free(fname);
368 if (!domain && envvar_mode == 1)
370 /* In case it failed, we try again using the registry
371 method. */
372 envvar_mode++;
373 goto again;
378 if (!domain)
379 return -1;
382 if ( the_domain )
384 struct overflow_space_s *os, *os2;
386 jnlib_free ( the_domain->data );
387 jnlib_free ( the_domain->mapped );
388 for (os=the_domain->overflow_space; os; os = os2)
390 os2 = os->next;
391 jnlib_free (os);
393 jnlib_free ( the_domain );
394 the_domain = NULL;
396 the_domain = domain;
397 return 0;
401 static const char*
402 get_string( struct loaded_domain *domain, u32 idx )
404 struct overflow_space_s *os;
405 char *p;
407 p = domain->data + SWAPIT(domain->must_swap, domain->trans_tab[idx].offset);
408 if (!domain->mapped[idx])
410 size_t plen, buflen;
411 char *buf;
413 domain->mapped[idx] = 1;
415 plen = strlen (p);
416 buf = utf8_to_native (p, plen, -1);
417 buflen = strlen (buf);
418 if (buflen <= plen)
419 strcpy (p, buf);
420 else
422 /* There is not enough space for the translation - store it
423 in the overflow_space else and mark that in the mapped
424 array. Because we expect that this won't happen too
425 often, we use a simple linked list. */
426 os = jnlib_malloc (sizeof *os + buflen);
427 if (os)
429 os->idx = idx;
430 strcpy (os->d, buf);
431 os->next = domain->overflow_space;
432 domain->overflow_space = os;
433 p = os->d;
435 else
436 p = "ERROR in GETTEXT MALLOC";
438 jnlib_free (buf);
440 else if (domain->mapped[idx] == 2)
441 { /* We need to get the string from the overflow_space. */
442 for (os=domain->overflow_space; os; os = os->next)
443 if (os->idx == idx)
444 return (const char*)os->d;
445 p = "ERROR in GETTEXT\n";
447 return (const char*)p;
452 const char *
453 gettext( const char *msgid )
455 struct loaded_domain *domain;
456 size_t act = 0;
457 size_t top, bottom;
459 if (!(domain = the_domain))
460 goto not_found;
462 /* Locate the MSGID and its translation. */
463 if (domain->hash_size > 2 && domain->hash_tab)
465 /* Use the hashing table. */
466 u32 len = strlen (msgid);
467 u32 hash_val = hash_string (msgid);
468 u32 idx = hash_val % domain->hash_size;
469 u32 incr = 1 + (hash_val % (domain->hash_size - 2));
470 u32 nstr = SWAPIT (domain->must_swap, domain->hash_tab[idx]);
472 if ( !nstr ) /* Hash table entry is empty. */
473 goto not_found;
475 if (SWAPIT(domain->must_swap,
476 domain->orig_tab[nstr - 1].length) == len
477 && !strcmp (msgid,
478 domain->data + SWAPIT(domain->must_swap,
479 domain->orig_tab[nstr-1].offset)))
480 return get_string( domain, nstr - 1 );
482 for (;;)
484 if (idx >= domain->hash_size - incr)
485 idx -= domain->hash_size - incr;
486 else
487 idx += incr;
489 nstr = SWAPIT (domain->must_swap, domain->hash_tab[idx]);
490 if (!nstr)
491 goto not_found; /* Hash table entry is empty. */
493 if ( SWAPIT(domain->must_swap,
494 domain->orig_tab[nstr - 1].length) == len
495 && !strcmp (msgid,
496 domain->data
497 + SWAPIT(domain->must_swap,
498 domain->orig_tab[nstr-1].offset)))
499 return get_string( domain, nstr-1 );
501 /*NOTREACHED*/
504 /* Now we try the default method: binary search in the sorted array
505 of messages. */
506 bottom = 0;
507 top = domain->nstrings;
508 while (bottom < top)
510 int cmp_val;
512 act = (bottom + top) / 2;
513 cmp_val = strcmp(msgid, domain->data
514 + SWAPIT(domain->must_swap,
515 domain->orig_tab[act].offset));
516 if (cmp_val < 0)
517 top = act;
518 else if (cmp_val > 0)
519 bottom = act + 1;
520 else
521 return get_string (domain, act);
524 not_found:
525 return msgid;
529 const char *
530 ngettext (const char *msgid1, const char *msgid2, unsigned long int n)
532 /* We use the simple Germanic plural rule. */
533 return gettext (n==1? msgid1 : msgid2);
538 #if 0
539 unsigned int cp1, cp2;
541 cp1 = GetConsoleCP();
542 cp2 = GetConsoleOutputCP();
544 log_info("InputCP=%u OutputCP=%u\n", cp1, cp2 );
546 if( !SetConsoleOutputCP( 1252 ) )
547 log_info("SetConsoleOutputCP failed: %s\n", w32_strerror (0));
549 cp1 = GetConsoleCP();
550 cp2 = GetConsoleOutputCP();
551 log_info("InputCP=%u OutputCP=%u after switch1\n", cp1, cp2 );
552 #endif
554 #endif /* USE_SIMPLE_GETTEXT */