Beginnings of write support.
[gedcom-parse.git] / gedcom / write.c
1 /* Write functions for Gedcom.
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 "gedcom_internal.h"
25 #include "gedcom.h"
26 #include "encoding.h"
27 #include "tag_data.h"
28 #include "buffer.h"
29 #include "utf8.h"
30 #include <unistd.h>
31 #include <sys/types.h>
32 #include <sys/stat.h>
33 #include <fcntl.h>
34
35 const char* encoding = "ASCII";
36 int write_encoding_details = ONE_BYTE;
37 /* SYS_NEWLINE is defined in config.h */
38 const char* write_terminator = SYS_NEWLINE;
39
40 struct Gedcom_write_struct {
41   int       filedesc;
42   convert_t conv;
43   int       total_conv_fails;
44   const char* term;
45   int       ctxt_stack[MAXGEDCLEVEL+1];
46   int       ctxt_level;
47 };
48
49 const char* default_encoding[] = {
50   /* ONE_BYTE */      "ASCII",
51   /* TWO_BYTE_HILO */ "UCS-2BE",
52   /* TWO_BYTE_LOHI */ "UCS-2LE"
53 };
54
55 const char* terminator[] = {
56   /* END_CR */     "\x0D",
57   /* END_LF */     "\x0A",
58   /* END_CR_LF */  "\x0D\x0A",
59   /* END_LF_CR */  "\x0A\x0D"
60 };
61
62 void cleanup_write_buffer();
63
64 struct safe_buffer write_buffer = { NULL, 0, cleanup_write_buffer };
65
66 void cleanup_write_buffer()
67 {
68   cleanup_buffer(&write_buffer);
69 }
70
71 int write_simple(Gedcom_write_hndl hndl,
72                  int level, char* xref, char* tag, char* value)
73 {
74   int res;
75   
76   if (hndl) {
77     char* converted;
78     int conv_fails;
79     size_t outlen;
80     
81     reset_buffer(&write_buffer);
82     res = safe_buf_append(&write_buffer, "%d", level);
83     if (xref)
84       res += safe_buf_append(&write_buffer, " %s", xref);
85     res += safe_buf_append(&write_buffer, " %s", tag);
86     if (value)
87       res += safe_buf_append(&write_buffer, " %s", value);
88     res += safe_buf_append(&write_buffer, hndl->term);
89
90     converted = convert_from_utf8(hndl->conv, get_buf_string(&write_buffer),
91                                   &conv_fails, &outlen);
92     
93     if (converted && (conv_fails == 0))
94       write(hndl->filedesc, converted, outlen);
95     else {
96       hndl->total_conv_fails += conv_fails;
97       gedcom_error
98         (_("Error converting output string: %s (%d conversion failures)"),
99          strerror(errno), conv_fails);
100     }
101   }
102   return 0;
103 }
104
105 int gedcom_write_set_encoding(const char* charset,
106                               Encoding width, Enc_bom bom)
107 {
108   char* new_encoding = NULL;
109   if (!strcmp(charset, "UNICODE")) {
110     if (width == ONE_BYTE) {
111       gedcom_error(_("Unicode cannot be encoded into one byte"));
112       return 1;
113     }
114     else {
115       new_encoding = get_encoding(charset, width);
116       if (new_encoding) {
117         encoding = new_encoding;
118         write_encoding_details = width | bom;
119       }
120     }
121   }
122   else {
123     new_encoding = get_encoding(charset, ONE_BYTE);
124     if (new_encoding) {
125       encoding = new_encoding;
126       write_encoding_details = ONE_BYTE;
127     }
128   }
129   return 0;
130 }
131
132 int gedcom_write_set_line_terminator(Enc_line_end end)
133 {
134   write_terminator = terminator[end];
135   return 0;
136 }
137
138 Gedcom_write_hndl gedcom_write_open(const char *filename)
139 {
140   Gedcom_write_hndl hndl;
141
142   hndl = (Gedcom_write_hndl)malloc(sizeof(struct Gedcom_write_struct));
143
144   if (!hndl)
145     MEMORY_ERROR;
146   else {
147     hndl->total_conv_fails = 0;
148     hndl->conv = initialize_utf8_conversion(encoding, 0);
149     if (!hndl->conv) {
150       gedcom_error(_("Could not open encoding '%s' for writing: %s"),
151                    encoding, strerror(errno));
152       free(hndl);
153       hndl = NULL;
154     }
155     else {
156       hndl->filedesc = open(filename, O_WRONLY|O_CREAT|O_TRUNC, 0666);
157       if (!hndl->filedesc) {
158         gedcom_error(_("Could not open file '%s' for writing: %s"),
159                      filename, strerror(errno));
160         cleanup_utf8_conversion(hndl->conv);
161         free(hndl);
162         hndl = NULL;
163       }
164       else {
165         hndl->term = write_terminator;
166         hndl->ctxt_level = -1;
167         if (write_encoding_details & WITH_BOM) {
168           if (write_encoding_details & TWO_BYTE_HILO)
169             write(hndl->filedesc, "\xFE\xFF", 2);
170           else if (write_encoding_details & TWO_BYTE_LOHI)
171             write(hndl->filedesc, "\xFF\xFE", 2);
172           else
173             gedcom_warning(_("Byte order mark configured, but no Unicode"));
174         }
175       }
176     }
177   }
178
179   return hndl;
180 }
181
182 int gedcom_write_close(Gedcom_write_hndl hndl, int* total_conv_fails)
183 {
184   int result = 0;
185   if (hndl) {
186     write_simple(hndl, 0, NULL, "TRLR", NULL);
187     if (total_conv_fails)  *total_conv_fails = hndl->total_conv_fails;
188     result = close(hndl->filedesc);
189     cleanup_utf8_conversion(hndl->conv);
190     free(hndl);
191   }
192   return result;
193 }
194
195 char* get_tag_string(int elt_or_rec, char* tag)
196 {
197   char* result = tag_data[elt_or_rec].tag_name;
198
199   if (result)
200     return result;
201   else if (tag)
202     return tag;
203   else {
204     gedcom_error(_("The element or record type '%s' requires a specific tag"
205                    "for writing"),
206                  tag_data[elt_or_rec].elt_name);
207     return NULL;
208   }
209 }
210
211 int check_type(int elt_or_rec, Gedcom_val_type type)
212 {
213   int allowed = tag_data[elt_or_rec].allowed_types;
214   if (allowed & type)
215     return 1;
216   else {
217     gedcom_error(_("Wrong data type for writing element or record type '%s'"),
218                  tag_data[elt_or_rec].elt_name);
219     return 0;
220   }
221 }
222
223 int get_level(Gedcom_write_hndl hndl, int elt_or_rec, int parent)
224 {
225   if (parent == -1) {
226     hndl->ctxt_level = 0;
227   }
228   else {
229     while (hndl->ctxt_level && hndl->ctxt_stack[hndl->ctxt_level] != parent)
230       hndl->ctxt_level--;
231     if (hndl->ctxt_stack[hndl->ctxt_level] == parent) {
232       hndl->ctxt_level++;
233     }
234     else {
235       gedcom_error(_("Parent %d not found during write of %d"),
236                    parent, elt_or_rec);
237       return -1;
238     }
239   }
240   hndl->ctxt_stack[hndl->ctxt_level] = elt_or_rec;
241   return hndl->ctxt_level;
242 }
243
244 int gedcom_write_record_str(Gedcom_write_hndl hndl,
245                             Gedcom_rec rec, char* tag,
246                             struct xref_value* xref, char* val)
247 {
248   int result = 1;
249   int level = 0;
250   char* tag_str = NULL;
251   char* xref_str = NULL;
252
253   tag_str = get_tag_string(rec, tag);
254   level   = get_level(hndl, rec, -1);
255   if (tag_str && check_type(rec, (val ? GV_CHAR_PTR : GV_NULL))) {
256     if (xref)
257       xref_str = xref->string;
258     result = write_simple(hndl, level, xref_str, tag_str, val);
259   }
260
261   return result;
262 }
263
264 int gedcom_write_element_str(Gedcom_write_hndl hndl,
265                              Gedcom_elt elt, char* tag, int parent_rec_or_elt,
266                              char* val)
267 {
268   int result = 1;
269   int level  = -1;
270   char* tag_str = NULL;
271
272   tag_str = get_tag_string(elt, tag);
273   level   = get_level(hndl, elt, parent_rec_or_elt);
274   if (tag_str && (level != -1)
275       && check_type(elt, (val ? GV_CHAR_PTR : GV_NULL))) {
276     result = write_simple(hndl, level, NULL, tag_str, val);
277   }
278
279   return result;
280 }