* src/folder.c
[claws.git] / src / simple-gettext.c
1 /* simple-gettext.c  - a simplified version of gettext.
2  * Copyright (C) 1995, 1996, 1997, 1998, 1999 Free Software Foundation, Inc.
3  *
4  * This file is part of GnuPG.
5  *
6  * GnuPG is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * GnuPG is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
19  */
20
21 /* This is a simplified version of gettext written by Ulrich Drepper.
22  * It is used for the Win32 version of GnuPG becuase all the overhead
23  * of gettext is not needed and we have to do some special Win32 stuff.
24  * I decided that this is far easier than to tweak gettext for the special
25  * cases (I tried it but it is a lot of code).  wk 15.09.99
26  */
27
28 #include <config.h>
29 #ifdef USE_SIMPLE_GETTEXT
30 #ifndef __MINGW32__
31   #error This file can only be used with MinGW32
32 #endif
33
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <string.h>
37 #include <ctype.h>
38 #include <errno.h>
39 #include <sys/types.h>
40 #include <sys/stat.h>
41 #include <windows.h>
42 #include "w32reg.h"
43
44 typedef unsigned int u32; /* That is fine with MingW32 */
45
46 typedef unsigned long ulong;
47
48 /* The magic number of the GNU message catalog format.  */
49 #define MAGIC         0x950412de
50 #define MAGIC_SWAPPED 0xde120495
51
52 /* Revision number of the currently used .mo (binary) file format.  */
53 #define MO_REVISION_NUMBER 0
54
55
56 /* Header for binary .mo file format.  */
57 struct mo_file_header
58 {
59   /* The magic number.  */
60   u32 magic;
61   /* The revision number of the file format.  */
62   u32 revision;
63   /* The number of strings pairs.  */
64   u32 nstrings;
65   /* Offset of table with start offsets of original strings.  */
66   u32 orig_tab_offset;
67   /* Offset of table with start offsets of translation strings.  */
68   u32 trans_tab_offset;
69   /* Size of hashing table.  */
70   u32 hash_tab_size;
71   /* Offset of first hashing entry.  */
72   u32 hash_tab_offset;
73 };
74
75 struct string_desc
76 {
77   /* Length of addressed string.  */
78   u32 length;
79   /* Offset of string in file.  */
80   u32 offset;
81 };
82
83
84
85 struct loaded_domain
86 {
87   char *data;
88   int must_swap;
89   u32 nstrings;
90   char *mapped;
91   struct string_desc *orig_tab;
92   struct string_desc *trans_tab;
93   u32 hash_size;
94   u32 *hash_tab;
95 };
96
97
98 static struct loaded_domain *the_domain;
99
100 static __inline__ u32
101 do_swap_u32( u32 i )
102 {
103   return (i << 24) | ((i & 0xff00) << 8) | ((i >> 8) & 0xff00) | (i >> 24);
104 }
105
106 #define SWAPIT(flag, data) ((flag) ? do_swap_u32(data) : (data) )
107
108
109 /* We assume to have `unsigned long int' value with at least 32 bits.  */
110 #define HASHWORDBITS 32
111
112 /* The so called `hashpjw' function by P.J. Weinberger
113    [see Aho/Sethi/Ullman, COMPILERS: Principles, Techniques and Tools,
114    1986, 1987 Bell Telephone Laboratories, Inc.]  */
115
116 static __inline__ ulong
117 hash_string( const char *str_param )
118 {
119     unsigned long int hval, g;
120     const char *str = str_param;
121
122     hval = 0;
123     while (*str != '\0')
124     {
125         hval <<= 4;
126         hval += (unsigned long int) *str++;
127         g = hval & ((unsigned long int) 0xf << (HASHWORDBITS - 4));
128         if (g != 0)
129         {
130           hval ^= g >> (HASHWORDBITS - 8);
131           hval ^= g;
132         }
133     }
134     return hval;
135 }
136
137
138 static struct loaded_domain *
139 load_domain( const char *filename )
140 {
141     FILE *fp;
142     size_t size;
143     struct stat st;
144     struct mo_file_header *data = NULL;
145     struct loaded_domain *domain = NULL;
146     size_t to_read;
147     char *read_ptr;
148
149     fp = fopen( filename, "rb" );
150     if( !fp )
151        return NULL; /* can't open the file */
152     /* we must know about the size of the file */
153     if( fstat( fileno(fp ), &st )
154         || (size = (size_t)st.st_size) != st.st_size
155         || size < sizeof (struct mo_file_header) ) {
156         fclose( fp );
157         return NULL;
158     }
159
160     data = malloc( size );
161     if( !data ) {
162         fclose( fp );
163         return NULL; /* out of memory */
164     }
165
166     to_read = size;
167     read_ptr = (char *) data;
168     do {
169         long int nb = fread( read_ptr, 1, to_read, fp );
170         if( nb < to_read ) {
171             fclose (fp);
172             free(data);
173             return NULL; /* read error */
174         }
175         read_ptr += nb;
176         to_read -= nb;
177     } while( to_read > 0 );
178     fclose (fp);
179
180     /* Using the magic number we can test whether it really is a message
181      * catalog file.  */
182     if( data->magic != MAGIC && data->magic != MAGIC_SWAPPED ) {
183         /* The magic number is wrong: not a message catalog file.  */
184         free( data );
185         return NULL;
186     }
187
188     domain = calloc( 1, sizeof *domain );
189     if( !domain )  {
190         free( data );
191         return NULL;
192     }
193     domain->data = (char *) data;
194     domain->must_swap = data->magic != MAGIC;
195
196     /* Fill in the information about the available tables.  */
197     switch( SWAPIT(domain->must_swap, data->revision) ) {
198       case 0:
199         domain->nstrings = SWAPIT(domain->must_swap, data->nstrings);
200         domain->orig_tab = (struct string_desc *)
201           ((char *) data + SWAPIT(domain->must_swap, data->orig_tab_offset));
202         domain->trans_tab = (struct string_desc *)
203           ((char *) data + SWAPIT(domain->must_swap, data->trans_tab_offset));
204         domain->hash_size = SWAPIT(domain->must_swap, data->hash_tab_size);
205         domain->hash_tab = (u32 *)
206           ((char *) data + SWAPIT(domain->must_swap, data->hash_tab_offset));
207       break;
208
209       default: /* This is an invalid revision.  */
210         free( data );
211         free( domain );
212         return NULL;
213     }
214
215     /* allocate an array to keep track of code page mappings */
216     domain->mapped = calloc( 1, domain->nstrings );
217     if( !domain->mapped ) {
218         free( data );
219         free( domain );
220         return NULL;
221     }
222
223     return domain;
224 }
225
226
227 /****************
228  * Set the file used for translations.  Pass a NULL to disable
229  * translation.  A new filename may be set at anytime.
230  * WARNING: After changing the filename you shoudl not access any data
231  *          retrieved by gettext().
232  */
233 int
234 set_gettext_file( const char *filename )
235 {
236     struct loaded_domain *domain = NULL;
237
238     if( filename && *filename ) {
239         if( filename[0] == '/'
240            #ifdef HAVE_DRIVE_LETTERS
241             || ( isalpha(filename[0])
242                  && filename[1] == ':'
243                  && (filename[2] == '/' || filename[2] == '\\') )
244            #endif
245            ) {
246             /* absolute path - use it as is */
247             domain = load_domain( filename );
248         }
249         else { /* relative path - append ".mo" and get dir from the environment */
250             char *buf = NULL;
251             char *dir;
252
253             dir = read_w32_registry_string( NULL,
254                                             "Control Panel\\Mingw32\\NLS",
255                                             "MODir" );
256             if( dir && (buf=malloc(strlen(dir)+strlen(filename)+1+3+1)) ) {
257                 strcpy(stpcpy(stpcpy(stpcpy( buf, dir),"\\"), filename),".mo");
258                 domain = load_domain( buf );
259                 free(buf);
260             }
261             free(dir);
262         }
263         if( !domain ) {
264             return -1; }
265     }
266     if( the_domain ) {
267         free( the_domain->data );
268         free( the_domain->mapped );
269         free( the_domain );
270         the_domain = NULL;
271     }
272     the_domain = domain;
273     return NULL;
274 }
275
276
277 static const char*
278 get_string( struct loaded_domain *domain, u32 idx )
279 {
280     char *p = domain->data + SWAPIT(domain->must_swap,
281                                     domain->trans_tab[idx].offset);
282
283         /* status of domain->mapped[idx] is ignored.
284          * not sure about the consequences.
285          * perhaps mapped can entirely be removed?
286          */
287
288         /* we assume, strings are already correctly
289          * encoded.
290          */
291
292     return (const char*)p;
293 }
294
295
296
297 const char *
298 gettext( const char *msgid )
299 {
300     struct loaded_domain *domain;
301     size_t act = 0;
302     size_t top, bottom;
303
304     if( !(domain = the_domain) ) {
305         goto not_found;
306         }
307
308     /* Locate the MSGID and its translation.  */
309     if( domain->hash_size > 2 && domain->hash_tab ) {
310         /* Use the hashing table.  */
311         u32 len = strlen (msgid);
312         u32 hash_val = hash_string (msgid);
313         u32 idx = hash_val % domain->hash_size;
314         u32 incr = 1 + (hash_val % (domain->hash_size - 2));
315         u32 nstr = SWAPIT (domain->must_swap, domain->hash_tab[idx]);
316
317         if ( !nstr ) /* Hash table entry is empty.  */
318             goto not_found;
319
320         if( SWAPIT(domain->must_swap,
321                     domain->orig_tab[nstr - 1].length) == len
322             && !strcmp( msgid,
323                        domain->data + SWAPIT(domain->must_swap,
324                                     domain->orig_tab[nstr - 1].offset)) )
325             return get_string( domain, nstr - 1 );
326
327         for(;;) {
328             if (idx >= domain->hash_size - incr)
329                 idx -= domain->hash_size - incr;
330             else
331                 idx += incr;
332
333             nstr = SWAPIT(domain->must_swap, domain->hash_tab[idx]);
334             if( !nstr )
335                 goto not_found; /* Hash table entry is empty.  */
336
337             if ( SWAPIT(domain->must_swap,
338                                 domain->orig_tab[nstr - 1].length) == len
339                  && !strcmp (msgid,
340                          domain->data + SWAPIT(domain->must_swap,
341                                            domain->orig_tab[nstr - 1].offset)))
342                 return get_string( domain, nstr-1 );
343         }
344         /* NOTREACHED */
345     }
346
347     /* Now we try the default method:  binary search in the sorted
348        array of messages.  */
349     bottom = 0;
350     top = domain->nstrings;
351     while( bottom < top ) {
352         int cmp_val;
353
354         act = (bottom + top) / 2;
355         cmp_val = strcmp(msgid, domain->data
356                                + SWAPIT(domain->must_swap,
357                                         domain->orig_tab[act].offset));
358         if (cmp_val < 0)
359             top = act;
360         else if (cmp_val > 0)
361             bottom = act + 1;
362         else
363             return get_string( domain, act );
364     }
365
366   not_found:
367     return msgid;
368 }
369
370 #if 0
371        unsigned int cp1, cp2;
372
373        cp1 = GetConsoleCP();
374        cp2 = GetConsoleOutputCP();
375
376        log_info("InputCP=%u  OutputCP=%u\n", cp1, cp2 );
377
378        if( !SetConsoleOutputCP( 1252 ) )
379            log_info("SetConsoleOutputCP failed: %d\n", (int)GetLastError() );
380
381        cp1 = GetConsoleCP();
382        cp2 = GetConsoleOutputCP();
383        log_info("InputCP=%u  OutputCP=%u after switch1\n", cp1, cp2 );
384 #endif
385
386 #endif /* USE_SIMPLE_GETTEXT */