de7770c8d045aa2a13bf797b3b6f40a3e6768cb1
[gedcom-parse.git] / gedcom / encoding.c
1 /*  This program is free software; you can redistribute it and/or modify  *
2  *  it under the terms of the GNU General Public License as published by  *
3  *  the Free Software Foundation; either version 2 of the License, or     *
4  *  (at your option) any later version.                                   *
5
6  (C) 2001 by The Genes Development Team
7  Original author: Peter Verthez (Peter.Verthez@advalvas.be)
8 */
9
10 /* $Id$ */
11 /* $Name$ */
12
13 #include <string.h>
14 #include <iconv.h>
15 #include <search.h>
16 #include <stdio.h>
17 #include <limits.h>
18 #include <stdlib.h>
19 #include "gedcom_internal.h"
20 #include "encoding.h"
21
22 #define INTERNAL_ENCODING "UTF8"
23 #define ENCODING_CONF_FILE "gedcom.enc"
24 #define GCONV_SEARCH_PATH "GCONV_PATH"
25 #define MAXBUF 255
26
27 static iconv_t cd_to_internal = (iconv_t) -1;
28 static void *encoding_mapping = NULL;
29 static ENCODING the_enc = ONE_BYTE;
30
31 struct node {
32   char *gedcom_name;
33   char *iconv_name;
34 };
35
36 char* charwidth_string[] = { "1", "2_HILO", "2_LOHI" };
37
38 int node_compare(const void *node1, const void *node2)
39 {
40   return strcmp(((const struct node *) node1)->gedcom_name,
41                 ((const struct node *) node2)->gedcom_name);
42 }
43
44 void add_encoding(char *gedcom_n, char* charwidth, char *iconv_n)
45 {
46   void **datum;
47   struct node *nodeptr = (struct node *) malloc(sizeof *nodeptr);
48   nodeptr->gedcom_name = (char *) malloc(strlen(gedcom_n)
49                                          + strlen(charwidth) + 3);
50   nodeptr->iconv_name  = (char *) malloc(strlen(iconv_n) + 1);
51   /* sprintf is safe here (malloc'ed before) */
52   sprintf(nodeptr->gedcom_name, "%s(%s)", gedcom_n, charwidth);
53   strcpy(nodeptr->iconv_name, iconv_n);
54   datum = tsearch(nodeptr, &encoding_mapping, node_compare);
55   if ((datum == NULL) || (*datum != nodeptr)) {
56     gedcom_warning("Duplicate entry found for encoding '%s', ignoring",
57                    gedcom_n);
58   }
59 }
60
61 char* get_encoding(char* gedcom_n, ENCODING enc)
62 {
63   void **datum;
64   struct node search_node;
65   char *buffer;
66   buffer = (char*)malloc(strlen(gedcom_n) + strlen(charwidth_string[enc]) + 3);
67   /* sprintf is safe here (malloc'ed before) */
68   sprintf(buffer, "%s(%s)", gedcom_n, charwidth_string[enc]);
69   search_node.gedcom_name = buffer;
70   datum = tfind(&search_node, &encoding_mapping, node_compare);
71   free(buffer);
72   if (datum == NULL) {
73     gedcom_error("No encoding found for '%s'", gedcom_n);
74     return NULL;
75   }
76   else {
77     return ((const struct node *) *datum)->iconv_name;
78   }
79 }
80
81 void init_encodings()
82 {
83   if (encoding_mapping == NULL) {
84     FILE *in;
85     char buffer[MAXBUF + 1];
86     char gedcom_n[MAXBUF + 1];
87     char charwidth[MAXBUF + 1];
88     char iconv_n[MAXBUF + 1];
89     char *gconv_path;
90
91     /* Add gedcom data directory to gconv search path */
92     gconv_path = getenv(GCONV_SEARCH_PATH);
93     if (gconv_path == NULL || strstr(gconv_path, PKGDATADIR) == NULL) {
94       char *new_gconv_path;
95       if (gconv_path == NULL) {
96         new_gconv_path = (char *)malloc(strlen(GCONV_SEARCH_PATH)
97                                         + strlen(PKGDATADIR)
98                                         + 2);
99         sprintf(new_gconv_path, "%s=%s", GCONV_SEARCH_PATH, PKGDATADIR);
100       }
101       else {
102         new_gconv_path = (char *)malloc(strlen(GCONV_SEARCH_PATH)
103                                         + strlen(gconv_path)
104                                         + strlen(PKGDATADIR)
105                                         + 3);
106         sprintf(new_gconv_path, "%s=%s:%s",
107                 GCONV_SEARCH_PATH, gconv_path, PKGDATADIR);
108       }
109       if (putenv(new_gconv_path) != 0) {
110         gedcom_warning("Failed updating environment variable %s",
111                        GCONV_SEARCH_PATH);
112       }
113       printf("%s\n", new_gconv_path);
114     }
115     
116     /* Open gedcom configuration file and read */
117     in = fopen(ENCODING_CONF_FILE, "r");
118     if (in == NULL) {
119       char path[PATH_MAX];
120       sprintf(path, "%s/%s", PKGDATADIR, ENCODING_CONF_FILE);
121       in = fopen(path, "r");
122     }
123     if (in == NULL) {
124       gedcom_warning("Could not open encoding configuration file '%s'",
125                      ENCODING_CONF_FILE);
126     }
127     else {
128       while (fgets(buffer, sizeof(buffer), in) != NULL) {
129         if (buffer[strlen(buffer) - 1] != '\n') {
130           gedcom_error("Line too long in encoding configuration file '%s'",
131                        ENCODING_CONF_FILE);
132           return;
133         }
134         else if ((buffer[0] != '#') && (strcmp(buffer, "\n") != 0)) {
135           if (sscanf(buffer, "%s %s %s", gedcom_n, charwidth, iconv_n) == 3) {
136             add_encoding(gedcom_n, charwidth, iconv_n);
137           }
138           else {
139             gedcom_error("Missing data in encoding configuration file '%s'",
140                          ENCODING_CONF_FILE);
141             return;
142           }
143         }
144       }
145       fclose(in);
146     }
147   }
148 }
149
150 void set_encoding_width(ENCODING enc)
151 {
152   the_enc = enc;
153 }
154
155 static char conv_buf[MAXGEDCLINELEN * 2];
156 static size_t conv_buf_size;
157
158 int open_conv_to_internal(char* fromcode)
159 {
160   char *encoding = get_encoding(fromcode, the_enc);
161   if (cd_to_internal != (iconv_t) -1)
162     iconv_close(cd_to_internal);
163   if (encoding == NULL) {
164     cd_to_internal = (iconv_t) -1;
165   }
166   else {
167     memset(conv_buf, 0, sizeof(conv_buf));
168     conv_buf_size = 0;
169     cd_to_internal = iconv_open(INTERNAL_ENCODING, encoding);
170     if (cd_to_internal == (iconv_t) -1) {
171       gedcom_error("Error opening conversion context for encoding %s: %s",
172                    encoding, strerror(errno));
173     }
174   }
175   return (cd_to_internal != (iconv_t) -1);  
176 }
177
178 void close_conv_to_internal()
179 {
180   iconv_close(cd_to_internal);
181   cd_to_internal = (iconv_t) -1;
182 }
183
184 char* to_internal(char* str, size_t len,
185                   char* output_buffer, size_t out_len)
186 {
187   size_t outsize = out_len;
188   char *wrptr = output_buffer;
189   char *rdptr = conv_buf;
190   /* set up input buffer (concatenate to what was left previous time) */
191   /* can't use strcpy, because possible null bytes from unicode */
192   memcpy(conv_buf + conv_buf_size, str, len);
193   conv_buf_size += len;
194   /* set up output buffer (empty it) */
195   memset(output_buffer, 0, out_len);
196   /* do the conversion */
197   iconv(cd_to_internal, &rdptr, &conv_buf_size, &wrptr, &outsize);
198   /* then shift what is left over to the head of the input buffer */
199   memmove(conv_buf, rdptr, conv_buf_size);
200   memset(conv_buf + conv_buf_size, 0, sizeof(conv_buf) - conv_buf_size);
201   return output_buffer;
202 }