Add interface function gedcom_init(), to avoid problem with GCONV_PATH
[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 <iconv.h>
26 #include <stdio.h>
27 #include <limits.h>
28 #include <stdlib.h>
29 #include "gedcom_internal.h"
30 #include "encoding.h"
31 #include "hash.h"
32
33 #define ENCODING_CONF_FILE "gedcom.enc"
34 #define GCONV_SEARCH_PATH "GCONV_PATH"
35 #define MAXBUF 255
36
37 static iconv_t cd_to_internal = (iconv_t) -1;
38 static ENCODING the_enc = ONE_BYTE;
39 static hash_t *encodings = NULL;
40
41 char* charwidth_string[] = { "1", "2_HILO", "2_LOHI" };
42
43 hnode_t *node_alloc(void *c __attribute__((unused)))
44 {
45   return malloc(sizeof *node_alloc(NULL));
46 }
47
48 void node_free(hnode_t *n, void *c __attribute__((unused)))
49 {
50   free((void*)hnode_getkey(n));
51   free(hnode_get(n));
52   free(n);
53 }
54
55 void add_encoding(char *gedcom_n, char* charwidth, char *iconv_n)
56 {
57   char *key, *val;
58
59   key = (char *) malloc(strlen(gedcom_n) + strlen(charwidth) + 3);
60   val = (char *) malloc(strlen(iconv_n) + 1);
61
62   /* sprintf is safe here (malloc'ed before) */
63   sprintf(key, "%s(%s)", gedcom_n, charwidth);
64   strcpy(val, iconv_n);
65
66   if (hash_lookup(encodings, key)) {
67     gedcom_warning(_("Duplicate entry found for encoding '%s', ignoring"),
68                    gedcom_n);
69     free(key);
70     free(val);
71   }
72   else {
73     hash_alloc_insert(encodings, key, val);
74   }
75 }
76
77 char* get_encoding(char* gedcom_n, ENCODING enc)
78 {
79   char *key;
80   hnode_t *node;
81   
82   key = (char*)malloc(strlen(gedcom_n) + strlen(charwidth_string[enc]) + 3);
83   /* sprintf is safe here (malloc'ed before) */
84   sprintf(key, "%s(%s)", gedcom_n, charwidth_string[enc]);
85
86   node = hash_lookup(encodings, key);
87   free(key);
88   if (node) {
89     return hnode_get(node);
90   }
91   else {
92     gedcom_error(_("No encoding defined for '%s'"), gedcom_n);
93     return NULL;
94   }
95 }
96
97 void cleanup_encodings()
98 {
99   hash_free(encodings);
100 }
101
102 /* Let function be called before main() */
103 void update_gconv_search_path() __attribute__ ((constructor));
104
105 /* Note:
106
107    The environment variable GCONV_PATH has to be adjusted before the very
108    first call of iconv_open.  For the most general case, it means that we
109    have to make our own constructor here (in case some of the other library
110    constructors would use iconv_open).
111
112    However, it looks like a change of an environment variable in a constructor
113    doesn't always survive until the main() function.  This is the case if
114    the environment variable is a new one, for which there was no room yet
115    in the initial environment.  The initial environment is located on the
116    stack, but when variables are added, it is moved to the heap (to be able
117    to grow).  Now, the main function takes again the one from the stack, not
118    from the heap, so changes are lost.
119
120    For this, the function below will also be called in gedcom_init(), which
121    needs to be called as early as possible in the program.
122  */
123
124 void update_gconv_search_path()
125 {
126   char *gconv_path;
127   /* Add gedcom data directory to gconv search path */
128   gconv_path = getenv(GCONV_SEARCH_PATH);
129   if (gconv_path == NULL || strstr(gconv_path, PKGDATADIR) == NULL) {
130     char *new_gconv_path;
131     if (gconv_path == NULL) {
132       new_gconv_path = (char *)malloc(strlen(GCONV_SEARCH_PATH)
133                                       + strlen(PKGDATADIR)
134                                       + 2);
135       sprintf(new_gconv_path, "%s=%s", GCONV_SEARCH_PATH, PKGDATADIR);
136     }
137     else {
138       new_gconv_path = (char *)malloc(strlen(GCONV_SEARCH_PATH)
139                                       + strlen(gconv_path)
140                                       + strlen(PKGDATADIR)
141                                       + 3);
142       sprintf(new_gconv_path, "%s=%s:%s",
143               GCONV_SEARCH_PATH, gconv_path, PKGDATADIR);
144     }
145     /* Ignore failures of putenv (can't do anything about it anyway) */
146     putenv(new_gconv_path);
147   }
148 }
149
150 void init_encodings()
151 {
152   if (encodings == NULL) {
153     FILE *in;
154     char buffer[MAXBUF + 1];
155     char gedcom_n[MAXBUF + 1];
156     char charwidth[MAXBUF + 1];
157     char iconv_n[MAXBUF + 1];
158
159     atexit(cleanup_encodings);
160     
161     encodings = hash_create(HASHCOUNT_T_MAX, NULL, NULL);
162     hash_set_allocator(encodings, node_alloc, node_free, NULL);
163     
164     /* Open gedcom configuration file and read */
165     in = fopen(ENCODING_CONF_FILE, "r");
166     if (in == NULL) {
167       char path[PATH_MAX];
168       sprintf(path, "%s/%s", PKGDATADIR, ENCODING_CONF_FILE);
169       in = fopen(path, "r");
170     }
171     if (in == NULL) {
172       gedcom_warning(_("Could not open encoding configuration file '%s'"),
173                      ENCODING_CONF_FILE);
174     }
175     else {
176       line_no = 1;
177       while (fgets(buffer, sizeof(buffer), in) != NULL) {
178         if (buffer[strlen(buffer) - 1] != '\n') {
179           gedcom_error(_("Line too long in encoding configuration file '%s'"),
180                        ENCODING_CONF_FILE);
181           return;
182         }
183         else if ((buffer[0] != '#') && (strcmp(buffer, "\n") != 0)) {
184           if (sscanf(buffer, "%s %s %s", gedcom_n, charwidth, iconv_n) == 3) {
185             add_encoding(gedcom_n, charwidth, iconv_n);
186           }
187           else {
188             gedcom_error(_("Missing data in encoding configuration file '%s'"),
189                          ENCODING_CONF_FILE);
190             return;
191           }
192         }
193       }
194       fclose(in);
195     }
196   }
197 }
198
199 void set_encoding_width(ENCODING enc)
200 {
201   the_enc = enc;
202 }
203
204 static char conv_buf[MAXGEDCLINELEN * 2];
205 static size_t conv_buf_size;
206
207 int open_conv_to_internal(char* fromcode)
208 {
209   char *encoding = get_encoding(fromcode, the_enc);
210   if (cd_to_internal != (iconv_t) -1)
211     iconv_close(cd_to_internal);
212   if (encoding == NULL) {
213     cd_to_internal = (iconv_t) -1;
214   }
215   else {
216     memset(conv_buf, 0, sizeof(conv_buf));
217     conv_buf_size = 0;
218     cd_to_internal = iconv_open(INTERNAL_ENCODING, encoding);
219     if (cd_to_internal == (iconv_t) -1) {
220       gedcom_error(_("Error opening conversion context for encoding %s: %s"),
221                    encoding, strerror(errno));
222     }
223   }
224   return (cd_to_internal != (iconv_t) -1);  
225 }
226
227 void close_conv_to_internal()
228 {
229   iconv_close(cd_to_internal);
230   cd_to_internal = (iconv_t) -1;
231 }
232
233 char* to_internal(char* str, size_t len,
234                   char* output_buffer, size_t out_len)
235 {
236   size_t outsize = out_len;
237   char *wrptr = output_buffer;
238   char *rdptr = conv_buf;
239   /* set up input buffer (concatenate to what was left previous time) */
240   /* can't use strcpy, because possible null bytes from unicode */
241   memcpy(conv_buf + conv_buf_size, str, len);
242   conv_buf_size += len;
243   /* set up output buffer (empty it) */
244   memset(output_buffer, 0, out_len);
245   /* do the conversion */
246   iconv(cd_to_internal, &rdptr, &conv_buf_size, &wrptr, &outsize);
247   /* then shift what is left over to the head of the input buffer */
248   memmove(conv_buf, rdptr, conv_buf_size);
249   memset(conv_buf + conv_buf_size, 0, sizeof(conv_buf) - conv_buf_size);
250   return output_buffer;
251 }