Copied from old documentation. Removed all Gedcom_val details.
[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 "encoding_state.h"
32 #include "hash.h"
33 #include "utf8tools.h"
34
35 #define ENCODING_CONF_FILE "gedcom.enc"
36 #define GCONV_SEARCH_PATH "GCONV_PATH"
37 #define MAXBUF 255
38
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     char* sp_pos = NULL;
93     while ((sp_pos = strchr(gedcom_n, ' ')) != NULL) {
94       *sp_pos = '_';
95     }
96     /* sprintf is safe here (malloc'ed before) */
97     sprintf(key, "%s(%s)", gedcom_n, charwidth_string[enc]);
98     
99     node = hash_lookup(encodings, key);
100     free(key);
101     if (node) {
102       return hnode_get(node);
103     }
104     else {
105       gedcom_error(_("No encoding defined for '%s'"), gedcom_n);
106       return NULL;
107     }
108   }
109   else {
110     MEMORY_ERROR;
111     return NULL;
112   }
113 }
114
115 void cleanup_encodings()
116 {
117   hash_free(encodings);
118 }
119
120 #ifdef USE_GLIBC_ICONV
121
122 static char *new_gconv_path;
123
124 void cleanup_gconv_path()
125 {
126   /* Clean up environment */
127   putenv(GCONV_SEARCH_PATH);
128   if (new_gconv_path)
129     free(new_gconv_path);  
130 }
131
132 /* Let function be called before main() */
133 void update_gconv_search_path() __attribute__ ((constructor));
134
135 #endif /* USE_GLIBC_ICONV */
136
137 /* Note:
138
139    The environment variable GCONV_PATH has to be adjusted before the very
140    first call of iconv_open.  For the most general case, it means that we
141    have to make our own constructor here (in case some of the other library
142    constructors would use iconv_open).
143
144    However, it looks like a change of an environment variable in a constructor
145    doesn't always survive until the main() function.  This is the case if
146    the environment variable is a new one, for which there was no room yet
147    in the initial environment.  The initial environment is located on the
148    stack, but when variables are added, it is moved to the heap (to be able
149    to grow).  Now, the main function takes again the one from the stack, not
150    from the heap, so changes are lost.
151
152    For this, the function below will also be called in gedcom_init(), which
153    needs to be called as early as possible in the program.
154  */
155
156 void update_gconv_search_path()
157 {
158 #ifdef USE_GLIBC_ICONV
159   char *gconv_path;
160   /* Add gedcom data directory to gconv search path */
161   gconv_path = getenv(GCONV_SEARCH_PATH);
162   if (gconv_path == NULL || strstr(gconv_path, PKGDATADIR) == NULL) {
163     if (gconv_path == NULL) {
164       new_gconv_path = (char *)malloc(strlen(GCONV_SEARCH_PATH)
165                                       + strlen(PKGDATADIR)
166                                       + 2);
167       if (new_gconv_path)
168         sprintf(new_gconv_path, "%s=%s", GCONV_SEARCH_PATH, PKGDATADIR);
169     }
170     else {
171       new_gconv_path = (char *)malloc(strlen(GCONV_SEARCH_PATH)
172                                       + strlen(gconv_path)
173                                       + strlen(PKGDATADIR)
174                                       + 3);
175       if (new_gconv_path)
176         sprintf(new_gconv_path, "%s=%s:%s",
177                 GCONV_SEARCH_PATH, gconv_path, PKGDATADIR);
178     }
179     if (new_gconv_path) 
180       /* Ignore failures of putenv (can't do anything about it anyway) */
181       putenv(new_gconv_path);
182     else {
183       fprintf(stderr, "Could not allocate memory at %s, %d\n",
184               __FILE__, __LINE__);
185       abort();
186     }
187   }
188   if (init_called && atexit(cleanup_gconv_path) != 0) {
189     gedcom_warning(_("Could not register path cleanup function"));
190   }    
191 #endif /* USE_GLIBC_ICONV */
192 }
193
194 void init_encodings()
195 {
196   if (encodings == NULL) {
197     FILE *in;
198     char buffer[MAXBUF + 1];
199     char gedcom_n[MAXBUF + 1];
200     char charwidth[MAXBUF + 1];
201     char iconv_n[MAXBUF + 1];
202
203     if (atexit(cleanup_encodings) != 0) {
204       gedcom_warning(_("Could not register encoding cleanup function"));
205     }
206     
207     encodings = hash_create(HASHCOUNT_T_MAX, NULL, NULL);
208     hash_set_allocator(encodings, node_alloc, node_free, NULL);
209     
210     /* Open gedcom configuration file and read */
211     in = fopen(ENCODING_CONF_FILE, "r");
212     if (in == NULL) {
213       char path[PATH_MAX];
214       sprintf(path, "%s/%s", PKGDATADIR, ENCODING_CONF_FILE);
215       in = fopen(path, "r");
216     }
217     if (in == NULL) {
218       gedcom_warning(_("Could not open encoding configuration file '%s': %s"),
219                      ENCODING_CONF_FILE, strerror(errno));
220     }
221     else {
222       line_no = 1;
223       while (fgets(buffer, sizeof(buffer), in) != NULL) {
224         if (buffer[strlen(buffer) - 1] != '\n') {
225           gedcom_error(_("Line too long in encoding configuration file '%s'"),
226                        ENCODING_CONF_FILE);
227           line_no = 0;
228           return;
229         }
230         else if ((buffer[0] != '#') && (strcmp(buffer, "\n") != 0)) {
231           if (sscanf(buffer, "%s %s %s", gedcom_n, charwidth, iconv_n) == 3) {
232             add_encoding(gedcom_n, charwidth, iconv_n);
233           }
234           else {
235             gedcom_error(_("Missing data in encoding configuration file '%s'"),
236                          ENCODING_CONF_FILE);
237             line_no = 0;
238             return;
239           }
240         }
241       }
242       line_no = 0;
243       if (fclose(in) != 0) {
244         gedcom_warning(_("Error closing file '%s': %s"),
245                        ENCODING_CONF_FILE, strerror(errno));
246       }
247     }
248   }
249 }
250
251 static convert_t to_int = NULL;
252 static char* error_value = "<error>";
253
254 int open_conv_to_internal(const char* fromcode)
255 {
256   convert_t new_to_int = NULL;
257   const char *encoding = get_encoding(fromcode, read_encoding.width);
258   
259   if (encoding != NULL) {
260     new_to_int = initialize_utf8_conversion(encoding, 1);
261     if (new_to_int == NULL) {
262       gedcom_error(_("Error opening conversion context for encoding %s: %s"),
263                    encoding, strerror(errno));
264     }
265   }
266
267   if (new_to_int != NULL) {
268     if (to_int != NULL)
269       cleanup_utf8_conversion(to_int);
270     to_int = new_to_int;
271     set_read_encoding(fromcode, encoding);
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 }