utf8.h is renamed to utf8tools.h
[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 static Encoding the_enc = ONE_BYTE;
39 static hash_t *encodings = NULL;
40
41 const char* charwidth_string[] = { "1", "2_HILO", "2_LOHI" };
42
43 hnode_t *node_alloc(void *c UNUSED)
44 {
45   return (hnode_t *)malloc(sizeof *node_alloc(NULL));
46 }
47
48 void node_free(hnode_t *n, void *c UNUSED)
49 {
50   free((void*)hnode_getkey(n));
51   free(hnode_get(n));
52   free(n);
53 }
54
55 void add_encoding(const char *gedcom_n, const char* charwidth,
56                   const char *iconv_n)
57 {
58   char *key, *val;
59
60   key = (char *) malloc(strlen(gedcom_n) + strlen(charwidth) + 3);
61   val = (char *) malloc(strlen(iconv_n) + 1);
62
63   if (key && val) {
64     /* sprintf is safe here (malloc'ed before) */
65     sprintf(key, "%s(%s)", gedcom_n, charwidth);
66     strcpy(val, iconv_n);
67     
68     if (hash_lookup(encodings, key)) {
69       gedcom_warning(_("Duplicate entry found for encoding '%s', ignoring"),
70                      gedcom_n);
71       free(key);
72       free(val);
73     }
74     else {
75       hash_alloc_insert(encodings, key, val);
76     }
77   }
78   else
79     MEMORY_ERROR;
80 }
81
82 char* get_encoding(const char* gedcom_n, Encoding enc)
83 {
84   char *key;
85   hnode_t *node;
86
87   if (encodings == NULL) return NULL;
88   
89   key = (char*)malloc(strlen(gedcom_n) + strlen(charwidth_string[enc]) + 3);
90
91   if (key) {
92     /* sprintf is safe here (malloc'ed before) */
93     sprintf(key, "%s(%s)", gedcom_n, charwidth_string[enc]);
94     
95     node = hash_lookup(encodings, key);
96     free(key);
97     if (node) {
98       return hnode_get(node);
99     }
100     else {
101       gedcom_error(_("No encoding defined for '%s'"), gedcom_n);
102       return NULL;
103     }
104   }
105   else {
106     MEMORY_ERROR;
107     return NULL;
108   }
109 }
110
111 void cleanup_encodings()
112 {
113   hash_free(encodings);
114 }
115
116 #ifdef USE_GLIBC_ICONV
117
118 static char *new_gconv_path;
119
120 void cleanup_gconv_path()
121 {
122   /* Clean up environment */
123   putenv(GCONV_SEARCH_PATH);
124   if (new_gconv_path)
125     free(new_gconv_path);  
126 }
127
128 /* Let function be called before main() */
129 void update_gconv_search_path() __attribute__ ((constructor));
130
131 #endif /* USE_GLIBC_ICONV */
132
133 /* Note:
134
135    The environment variable GCONV_PATH has to be adjusted before the very
136    first call of iconv_open.  For the most general case, it means that we
137    have to make our own constructor here (in case some of the other library
138    constructors would use iconv_open).
139
140    However, it looks like a change of an environment variable in a constructor
141    doesn't always survive until the main() function.  This is the case if
142    the environment variable is a new one, for which there was no room yet
143    in the initial environment.  The initial environment is located on the
144    stack, but when variables are added, it is moved to the heap (to be able
145    to grow).  Now, the main function takes again the one from the stack, not
146    from the heap, so changes are lost.
147
148    For this, the function below will also be called in gedcom_init(), which
149    needs to be called as early as possible in the program.
150  */
151
152 void update_gconv_search_path()
153 {
154 #ifdef USE_GLIBC_ICONV
155   char *gconv_path;
156   /* Add gedcom data directory to gconv search path */
157   gconv_path = getenv(GCONV_SEARCH_PATH);
158   if (gconv_path == NULL || strstr(gconv_path, PKGDATADIR) == NULL) {
159     if (gconv_path == NULL) {
160       new_gconv_path = (char *)malloc(strlen(GCONV_SEARCH_PATH)
161                                       + strlen(PKGDATADIR)
162                                       + 2);
163       if (new_gconv_path)
164         sprintf(new_gconv_path, "%s=%s", GCONV_SEARCH_PATH, PKGDATADIR);
165     }
166     else {
167       new_gconv_path = (char *)malloc(strlen(GCONV_SEARCH_PATH)
168                                       + strlen(gconv_path)
169                                       + strlen(PKGDATADIR)
170                                       + 3);
171       if (new_gconv_path)
172         sprintf(new_gconv_path, "%s=%s:%s",
173                 GCONV_SEARCH_PATH, gconv_path, PKGDATADIR);
174     }
175     if (new_gconv_path) 
176       /* Ignore failures of putenv (can't do anything about it anyway) */
177       putenv(new_gconv_path);
178     else {
179       fprintf(stderr, "Could not allocate memory at %s, %d\n",
180               __FILE__, __LINE__);
181       abort();
182     }
183   }
184   if (init_called && atexit(cleanup_gconv_path) != 0) {
185     gedcom_warning(_("Could not register path cleanup function"));
186   }    
187 #endif /* USE_GLIBC_ICONV */
188 }
189
190 void init_encodings()
191 {
192   if (encodings == NULL) {
193     FILE *in;
194     char buffer[MAXBUF + 1];
195     char gedcom_n[MAXBUF + 1];
196     char charwidth[MAXBUF + 1];
197     char iconv_n[MAXBUF + 1];
198
199     if (atexit(cleanup_encodings) != 0) {
200       gedcom_warning(_("Could not register encoding cleanup function"));
201     }
202     
203     encodings = hash_create(HASHCOUNT_T_MAX, NULL, NULL);
204     hash_set_allocator(encodings, node_alloc, node_free, NULL);
205     
206     /* Open gedcom configuration file and read */
207     in = fopen(ENCODING_CONF_FILE, "r");
208     if (in == NULL) {
209       char path[PATH_MAX];
210       sprintf(path, "%s/%s", PKGDATADIR, ENCODING_CONF_FILE);
211       in = fopen(path, "r");
212     }
213     if (in == NULL) {
214       gedcom_warning(_("Could not open encoding configuration file '%s': %s"),
215                      ENCODING_CONF_FILE, strerror(errno));
216     }
217     else {
218       line_no = 1;
219       while (fgets(buffer, sizeof(buffer), in) != NULL) {
220         if (buffer[strlen(buffer) - 1] != '\n') {
221           gedcom_error(_("Line too long in encoding configuration file '%s'"),
222                        ENCODING_CONF_FILE);
223           line_no = 0;
224           return;
225         }
226         else if ((buffer[0] != '#') && (strcmp(buffer, "\n") != 0)) {
227           if (sscanf(buffer, "%s %s %s", gedcom_n, charwidth, iconv_n) == 3) {
228             add_encoding(gedcom_n, charwidth, iconv_n);
229           }
230           else {
231             gedcom_error(_("Missing data in encoding configuration file '%s'"),
232                          ENCODING_CONF_FILE);
233             line_no = 0;
234             return;
235           }
236         }
237       }
238       line_no = 0;
239       if (fclose(in) != 0) {
240         gedcom_warning(_("Error closing file '%s': %s"),
241                        ENCODING_CONF_FILE, strerror(errno));
242       }
243     }
244   }
245 }
246
247 void set_encoding_width(Encoding enc)
248 {
249   the_enc = enc;
250 }
251
252 static convert_t to_int = NULL;
253 static char* error_value = "<error>";
254
255 int open_conv_to_internal(const char* fromcode)
256 {
257   convert_t new_to_int = NULL;
258   const char *encoding = get_encoding(fromcode, the_enc);
259   
260   if (encoding != NULL) {
261     new_to_int = initialize_utf8_conversion(encoding, 1);
262     if (new_to_int == NULL) {
263       gedcom_error(_("Error opening conversion context for encoding %s: %s"),
264                    encoding, strerror(errno));
265     }
266   }
267
268   if (new_to_int != NULL) {
269     if (to_int != NULL)
270       cleanup_utf8_conversion(to_int);
271     to_int = new_to_int;
272   }
273
274   return (new_to_int != NULL);
275 }
276
277 void close_conv_to_internal()
278 {
279   if (to_int != NULL) {
280     cleanup_utf8_conversion(to_int);
281     to_int = NULL;
282   }
283 }
284
285
286 char* to_internal(const char* str, size_t len, struct conv_buffer* output_buf)
287 {
288   if (conversion_set_output_buffer(to_int, output_buf))
289     return convert_to_utf8_incremental(to_int, str, len);
290   else
291     return error_value;
292 }