Implemented an encoding state.
[gedcom-parse.git] / gedcom / encoding.c
1 /* Conversion between encodings.
2    Copyright (C) 2001,2002 The Genes Development Team
3    This file is part of the Gedcom parser library.
4    Contributed by Peter Verthez <Peter.Verthez@advalvas.be>, 2001.
5
6    The Gedcom parser library is free software; you can redistribute it
7    and/or modify it under the terms of the GNU Lesser General Public
8    License as published by the Free Software Foundation; either
9    version 2.1 of the License, or (at your option) any later version.
10
11    The Gedcom parser library is distributed in the hope that it will be
12    useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14    Lesser General Public License for more details.
15
16    You should have received a copy of the GNU Lesser General Public
17    License along with the Gedcom parser library; if not, write to the
18    Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
19    02111-1307 USA.  */
20
21 /* $Id$ */
22 /* $Name$ */
23
24 #include <string.h>
25 #include <stdio.h>
26 #include <limits.h>
27 #include <stdlib.h>
28 #include "gedcom_internal.h"
29 #include "gedcom.h"
30 #include "encoding.h"
31 #include "hash.h"
32 #include "utf8tools.h"
33
34 #define ENCODING_CONF_FILE "gedcom.enc"
35 #define GCONV_SEARCH_PATH "GCONV_PATH"
36 #define MAXBUF 255
37
38 struct encoding_state read_encoding;
39
40 static hash_t *encodings = NULL;
41
42 const char* charwidth_string[] = { "1", "2_HILO", "2_LOHI" };
43
44 hnode_t *node_alloc(void *c UNUSED)
45 {
46   return (hnode_t *)malloc(sizeof *node_alloc(NULL));
47 }
48
49 void node_free(hnode_t *n, void *c UNUSED)
50 {
51   free((void*)hnode_getkey(n));
52   free(hnode_get(n));
53   free(n);
54 }
55
56 void add_encoding(const char *gedcom_n, const char* charwidth,
57                   const char *iconv_n)
58 {
59   char *key, *val;
60
61   key = (char *) malloc(strlen(gedcom_n) + strlen(charwidth) + 3);
62   val = (char *) malloc(strlen(iconv_n) + 1);
63
64   if (key && val) {
65     /* sprintf is safe here (malloc'ed before) */
66     sprintf(key, "%s(%s)", gedcom_n, charwidth);
67     strcpy(val, iconv_n);
68     
69     if (hash_lookup(encodings, key)) {
70       gedcom_warning(_("Duplicate entry found for encoding '%s', ignoring"),
71                      gedcom_n);
72       free(key);
73       free(val);
74     }
75     else {
76       hash_alloc_insert(encodings, key, val);
77     }
78   }
79   else
80     MEMORY_ERROR;
81 }
82
83 char* get_encoding(const char* gedcom_n, Encoding enc)
84 {
85   char *key;
86   hnode_t *node;
87
88   if (encodings == NULL) return NULL;
89   
90   key = (char*)malloc(strlen(gedcom_n) + strlen(charwidth_string[enc]) + 3);
91
92   if (key) {
93     /* sprintf is safe here (malloc'ed before) */
94     sprintf(key, "%s(%s)", gedcom_n, charwidth_string[enc]);
95     
96     node = hash_lookup(encodings, key);
97     free(key);
98     if (node) {
99       return hnode_get(node);
100     }
101     else {
102       gedcom_error(_("No encoding defined for '%s'"), gedcom_n);
103       return NULL;
104     }
105   }
106   else {
107     MEMORY_ERROR;
108     return NULL;
109   }
110 }
111
112 void cleanup_encodings()
113 {
114   hash_free(encodings);
115 }
116
117 #ifdef USE_GLIBC_ICONV
118
119 static char *new_gconv_path;
120
121 void cleanup_gconv_path()
122 {
123   /* Clean up environment */
124   putenv(GCONV_SEARCH_PATH);
125   if (new_gconv_path)
126     free(new_gconv_path);  
127 }
128
129 /* Let function be called before main() */
130 void update_gconv_search_path() __attribute__ ((constructor));
131
132 #endif /* USE_GLIBC_ICONV */
133
134 /* Note:
135
136    The environment variable GCONV_PATH has to be adjusted before the very
137    first call of iconv_open.  For the most general case, it means that we
138    have to make our own constructor here (in case some of the other library
139    constructors would use iconv_open).
140
141    However, it looks like a change of an environment variable in a constructor
142    doesn't always survive until the main() function.  This is the case if
143    the environment variable is a new one, for which there was no room yet
144    in the initial environment.  The initial environment is located on the
145    stack, but when variables are added, it is moved to the heap (to be able
146    to grow).  Now, the main function takes again the one from the stack, not
147    from the heap, so changes are lost.
148
149    For this, the function below will also be called in gedcom_init(), which
150    needs to be called as early as possible in the program.
151  */
152
153 void update_gconv_search_path()
154 {
155 #ifdef USE_GLIBC_ICONV
156   char *gconv_path;
157   /* Add gedcom data directory to gconv search path */
158   gconv_path = getenv(GCONV_SEARCH_PATH);
159   if (gconv_path == NULL || strstr(gconv_path, PKGDATADIR) == NULL) {
160     if (gconv_path == NULL) {
161       new_gconv_path = (char *)malloc(strlen(GCONV_SEARCH_PATH)
162                                       + strlen(PKGDATADIR)
163                                       + 2);
164       if (new_gconv_path)
165         sprintf(new_gconv_path, "%s=%s", GCONV_SEARCH_PATH, PKGDATADIR);
166     }
167     else {
168       new_gconv_path = (char *)malloc(strlen(GCONV_SEARCH_PATH)
169                                       + strlen(gconv_path)
170                                       + strlen(PKGDATADIR)
171                                       + 3);
172       if (new_gconv_path)
173         sprintf(new_gconv_path, "%s=%s:%s",
174                 GCONV_SEARCH_PATH, gconv_path, PKGDATADIR);
175     }
176     if (new_gconv_path) 
177       /* Ignore failures of putenv (can't do anything about it anyway) */
178       putenv(new_gconv_path);
179     else {
180       fprintf(stderr, "Could not allocate memory at %s, %d\n",
181               __FILE__, __LINE__);
182       abort();
183     }
184   }
185   if (init_called && atexit(cleanup_gconv_path) != 0) {
186     gedcom_warning(_("Could not register path cleanup function"));
187   }    
188 #endif /* USE_GLIBC_ICONV */
189 }
190
191 void init_encodings()
192 {
193   if (encodings == NULL) {
194     FILE *in;
195     char buffer[MAXBUF + 1];
196     char gedcom_n[MAXBUF + 1];
197     char charwidth[MAXBUF + 1];
198     char iconv_n[MAXBUF + 1];
199
200     if (atexit(cleanup_encodings) != 0) {
201       gedcom_warning(_("Could not register encoding cleanup function"));
202     }
203     
204     encodings = hash_create(HASHCOUNT_T_MAX, NULL, NULL);
205     hash_set_allocator(encodings, node_alloc, node_free, NULL);
206     
207     /* Open gedcom configuration file and read */
208     in = fopen(ENCODING_CONF_FILE, "r");
209     if (in == NULL) {
210       char path[PATH_MAX];
211       sprintf(path, "%s/%s", PKGDATADIR, ENCODING_CONF_FILE);
212       in = fopen(path, "r");
213     }
214     if (in == NULL) {
215       gedcom_warning(_("Could not open encoding configuration file '%s': %s"),
216                      ENCODING_CONF_FILE, strerror(errno));
217     }
218     else {
219       line_no = 1;
220       while (fgets(buffer, sizeof(buffer), in) != NULL) {
221         if (buffer[strlen(buffer) - 1] != '\n') {
222           gedcom_error(_("Line too long in encoding configuration file '%s'"),
223                        ENCODING_CONF_FILE);
224           line_no = 0;
225           return;
226         }
227         else if ((buffer[0] != '#') && (strcmp(buffer, "\n") != 0)) {
228           if (sscanf(buffer, "%s %s %s", gedcom_n, charwidth, iconv_n) == 3) {
229             add_encoding(gedcom_n, charwidth, iconv_n);
230           }
231           else {
232             gedcom_error(_("Missing data in encoding configuration file '%s'"),
233                          ENCODING_CONF_FILE);
234             line_no = 0;
235             return;
236           }
237         }
238       }
239       line_no = 0;
240       if (fclose(in) != 0) {
241         gedcom_warning(_("Error closing file '%s': %s"),
242                        ENCODING_CONF_FILE, strerror(errno));
243       }
244     }
245   }
246 }
247
248 void set_encoding_width(Encoding enc)
249 {
250   read_encoding.width = enc;
251 }
252
253 void set_encoding_bom(Enc_bom bom)
254 {
255   read_encoding.bom = bom;
256 }
257
258 void set_encoding_terminator(char* term)
259 {
260   strncpy(read_encoding.terminator, term, MAX_TERMINATOR_LEN);
261 }
262
263 static convert_t to_int = NULL;
264 static char* error_value = "<error>";
265
266 int open_conv_to_internal(const char* fromcode)
267 {
268   convert_t new_to_int = NULL;
269   const char *encoding = get_encoding(fromcode, read_encoding.width);
270   
271   if (encoding != NULL) {
272     new_to_int = initialize_utf8_conversion(encoding, 1);
273     if (new_to_int == NULL) {
274       gedcom_error(_("Error opening conversion context for encoding %s: %s"),
275                    encoding, strerror(errno));
276     }
277   }
278
279   if (new_to_int != NULL) {
280     if (to_int != NULL)
281       cleanup_utf8_conversion(to_int);
282     to_int = new_to_int;
283     strncpy(read_encoding.charset, fromcode, MAX_CHARSET_LEN);
284     read_encoding.encoding = encoding;
285     gedcom_debug_print("Encoding state is now: ");
286     gedcom_debug_print("  charset   : %s", read_encoding.charset);
287     gedcom_debug_print("  encoding  : %s", read_encoding.encoding);
288     gedcom_debug_print("  width     : %d", read_encoding.width);
289     gedcom_debug_print("  BOM       : %d", read_encoding.bom);
290     gedcom_debug_print("  terminator: 0x%02x 0x%02x",
291                        read_encoding.terminator[0],
292                        read_encoding.terminator[1]);
293   }
294
295   return (new_to_int != NULL);
296 }
297
298 void close_conv_to_internal()
299 {
300   if (to_int != NULL) {
301     cleanup_utf8_conversion(to_int);
302     to_int = NULL;
303   }
304 }
305
306
307 char* to_internal(const char* str, size_t len, struct conv_buffer* output_buf)
308 {
309   if (conversion_set_output_buffer(to_int, output_buf))
310     return convert_to_utf8_incremental(to_int, str, len);
311   else
312     return error_value;
313 }