9e4e9199561ca0778914aedf7ed89dff227d7628
[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 void update_gconv_search_path()
106 {
107   char *gconv_path;
108   /* Add gedcom data directory to gconv search path */
109   gconv_path = getenv(GCONV_SEARCH_PATH);
110   if (gconv_path == NULL || strstr(gconv_path, PKGDATADIR) == NULL) {
111     char *new_gconv_path;
112     if (gconv_path == NULL) {
113       new_gconv_path = (char *)malloc(strlen(GCONV_SEARCH_PATH)
114                                       + strlen(PKGDATADIR)
115                                       + 2);
116       sprintf(new_gconv_path, "%s=%s", GCONV_SEARCH_PATH, PKGDATADIR);
117     }
118     else {
119       new_gconv_path = (char *)malloc(strlen(GCONV_SEARCH_PATH)
120                                       + strlen(gconv_path)
121                                       + strlen(PKGDATADIR)
122                                       + 3);
123       sprintf(new_gconv_path, "%s=%s:%s",
124               GCONV_SEARCH_PATH, gconv_path, PKGDATADIR);
125     }
126     /* Ignore failures of putenv (can't do anything about it anyway) */
127     putenv(new_gconv_path);
128   }
129 }
130
131 void init_encodings()
132 {
133   if (encodings == NULL) {
134     FILE *in;
135     char buffer[MAXBUF + 1];
136     char gedcom_n[MAXBUF + 1];
137     char charwidth[MAXBUF + 1];
138     char iconv_n[MAXBUF + 1];
139
140     atexit(cleanup_encodings);
141     
142     encodings = hash_create(HASHCOUNT_T_MAX, NULL, NULL);
143     hash_set_allocator(encodings, node_alloc, node_free, NULL);
144     
145     /* Open gedcom configuration file and read */
146     in = fopen(ENCODING_CONF_FILE, "r");
147     if (in == NULL) {
148       char path[PATH_MAX];
149       sprintf(path, "%s/%s", PKGDATADIR, ENCODING_CONF_FILE);
150       in = fopen(path, "r");
151     }
152     if (in == NULL) {
153       gedcom_warning(_("Could not open encoding configuration file '%s'"),
154                      ENCODING_CONF_FILE);
155     }
156     else {
157       line_no = 1;
158       while (fgets(buffer, sizeof(buffer), in) != NULL) {
159         if (buffer[strlen(buffer) - 1] != '\n') {
160           gedcom_error(_("Line too long in encoding configuration file '%s'"),
161                        ENCODING_CONF_FILE);
162           return;
163         }
164         else if ((buffer[0] != '#') && (strcmp(buffer, "\n") != 0)) {
165           if (sscanf(buffer, "%s %s %s", gedcom_n, charwidth, iconv_n) == 3) {
166             add_encoding(gedcom_n, charwidth, iconv_n);
167           }
168           else {
169             gedcom_error(_("Missing data in encoding configuration file '%s'"),
170                          ENCODING_CONF_FILE);
171             return;
172           }
173         }
174       }
175       fclose(in);
176     }
177   }
178 }
179
180 void set_encoding_width(ENCODING enc)
181 {
182   the_enc = enc;
183 }
184
185 static char conv_buf[MAXGEDCLINELEN * 2];
186 static size_t conv_buf_size;
187
188 int open_conv_to_internal(char* fromcode)
189 {
190   char *encoding = get_encoding(fromcode, the_enc);
191   if (cd_to_internal != (iconv_t) -1)
192     iconv_close(cd_to_internal);
193   if (encoding == NULL) {
194     cd_to_internal = (iconv_t) -1;
195   }
196   else {
197     memset(conv_buf, 0, sizeof(conv_buf));
198     conv_buf_size = 0;
199     cd_to_internal = iconv_open(INTERNAL_ENCODING, encoding);
200     if (cd_to_internal == (iconv_t) -1) {
201       gedcom_error(_("Error opening conversion context for encoding %s: %s"),
202                    encoding, strerror(errno));
203     }
204   }
205   return (cd_to_internal != (iconv_t) -1);  
206 }
207
208 void close_conv_to_internal()
209 {
210   iconv_close(cd_to_internal);
211   cd_to_internal = (iconv_t) -1;
212 }
213
214 char* to_internal(char* str, size_t len,
215                   char* output_buffer, size_t out_len)
216 {
217   size_t outsize = out_len;
218   char *wrptr = output_buffer;
219   char *rdptr = conv_buf;
220   /* set up input buffer (concatenate to what was left previous time) */
221   /* can't use strcpy, because possible null bytes from unicode */
222   memcpy(conv_buf + conv_buf_size, str, len);
223   conv_buf_size += len;
224   /* set up output buffer (empty it) */
225   memset(output_buffer, 0, out_len);
226   /* do the conversion */
227   iconv(cd_to_internal, &rdptr, &conv_buf_size, &wrptr, &outsize);
228   /* then shift what is left over to the head of the input buffer */
229   memmove(conv_buf, rdptr, conv_buf_size);
230   memset(conv_buf + conv_buf_size, 0, sizeof(conv_buf) - conv_buf_size);
231   return output_buffer;
232 }