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