f880bbbe90ef0279d4ab3b1cbfcccab7c608b38b
[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 "encoding_state.h"
28 #include "tag_data.h"
29 #include "buffer.h"
30 #include "utf8tools.h"
31 #include <unistd.h>
32 #include <sys/types.h>
33 #include <sys/stat.h>
34 #include <fcntl.h>
35
36 #define MAXWRITELEN MAXGEDCLINELEN
37
38 struct Gedcom_write_struct {
39   int       filedesc;
40   convert_t conv;
41   int       total_conv_fails;
42   const char* term;
43   int       ctxt_stack[MAXGEDCLEVEL+1];
44   int       ctxt_level;
45 };
46
47 void cleanup_write_buffer();
48 struct safe_buffer write_buffer = { NULL, 0, NULL, 0, cleanup_write_buffer };
49
50 void cleanup_convert_at_buffer();
51 struct safe_buffer convert_at_buffer = { NULL, 0, NULL, 0,
52                                          cleanup_convert_at_buffer };
53
54 void cleanup_write_buffer()
55 {
56   cleanup_buffer(&write_buffer);
57 }
58
59 void cleanup_convert_at_buffer()
60 {
61   cleanup_buffer(&convert_at_buffer);
62 }
63
64 int write_simple(Gedcom_write_hndl hndl,
65                  int level, const char* xref, const char* tag,
66                  const char* value)
67 {
68   int res;
69   
70   if (hndl) {
71     char* converted;
72     int conv_fails;
73     size_t outlen;
74     
75     reset_buffer(&write_buffer);
76     res = safe_buf_append(&write_buffer, "%d", level);
77     if (xref)
78       res += safe_buf_append(&write_buffer, " %s", xref);
79     res += safe_buf_append(&write_buffer, " %s", tag);
80     if (value)
81       res += safe_buf_append(&write_buffer, " %s", value);
82     res += safe_buf_append(&write_buffer, hndl->term);
83
84     if (utf8_strlen(get_buf_string(&write_buffer)) > MAXGEDCLINELEN) {
85       gedcom_error(_("Line too long"));
86     }
87     else {
88       converted = convert_from_utf8(hndl->conv, get_buf_string(&write_buffer),
89                                     &conv_fails, &outlen);
90       
91       if (converted && (conv_fails == 0)) {
92         line_no++;
93         write(hndl->filedesc, converted, outlen);
94       }
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   }
103   return 0;
104 }
105
106 int write_encoding_value(Gedcom_write_hndl hndl,
107                          int level, char* xref, char* tag, char* value)
108 {
109   if (strcmp(value, write_encoding.charset))
110     gedcom_warning(_("Forcing HEAD.CHAR value to '%s'"),
111                    write_encoding.charset);
112   return write_simple(hndl, level, xref, tag, write_encoding.charset);
113 }
114
115 int supports_continuation(int elt_or_rec, int which_continuation)
116 {
117   return tag_data[elt_or_rec].options & which_continuation;
118 }
119
120 int write_long(Gedcom_write_hndl hndl, int elt_or_rec,
121                int level, const char* xref, const char* tag, const char* value)
122 {
123   int prefix_len, value_len = 0, term_len;
124   char* nl_pos = NULL;
125   if (value) nl_pos = strchr(value, '\n');
126
127   prefix_len = utf8_strlen(tag) + 3;  /* for e.g. "0 INDI " */
128   if (level > 9) prefix_len++;
129   if (xref)      prefix_len += utf8_strlen(xref) + 1;
130   if (value)     value_len  = utf8_strlen(value);
131   term_len   = strlen(hndl->term);
132
133   if (!nl_pos && prefix_len + value_len + term_len <= MAXWRITELEN)
134     write_simple(hndl, level, xref, tag, value);
135   else {
136     const char* value_ptr = value;
137     int cont_supported = supports_continuation(elt_or_rec, OPT_CONT);
138     int cont_as_conc   = supports_continuation(elt_or_rec, OPT_CONT_AS_CONC);
139     if (nl_pos && !cont_supported) {
140       gedcom_error (_("The tag %s doesn't support newlines"), tag);
141       return 1;
142     }
143     else {
144       char value_part[MAXWRITELEN];
145       int cont_prefix_len, write_level = level;
146       cont_prefix_len = utf8_strlen("CONT") + 3;
147       if (level + 1 > 9) cont_prefix_len++;
148
149       while (value_ptr) {
150         char* cont_tag = "CONT";
151         int line_len = (nl_pos && cont_supported
152                         ? nl_pos - value_ptr : value_len);
153
154         if (prefix_len + line_len + term_len > MAXWRITELEN) {
155           line_len = MAXWRITELEN - prefix_len - term_len;
156           if (!cont_as_conc)
157             cont_tag = "CONC";
158         }
159         
160         memset(value_part, 0, sizeof(value_part));
161         strncpy(value_part, value_ptr, line_len);
162         write_simple(hndl, write_level, xref, tag, value_part);
163         
164         if (line_len < value_len) {
165           value_ptr   = value_ptr + line_len;
166           value_len   = value_len - line_len;
167           while (*value_ptr == '\n') {
168             value_ptr++;
169             value_len--;
170           }
171           prefix_len  = cont_prefix_len;
172           write_level = level + 1;
173           xref        = NULL;
174           tag         = cont_tag;
175           nl_pos      = strchr(value_ptr, '\n');
176         }
177         else
178           value_ptr = NULL;
179       }
180     }
181   }
182   
183   return 0;
184 }
185
186 Gedcom_write_hndl gedcom_write_open(const char *filename)
187 {
188   Gedcom_write_hndl hndl;
189
190   hndl = (Gedcom_write_hndl)malloc(sizeof(struct Gedcom_write_struct));
191
192   if (!hndl)
193     MEMORY_ERROR;
194   else {
195     init_write_encoding();
196     init_write_terminator();
197     hndl->total_conv_fails = 0;
198     hndl->conv = initialize_utf8_conversion(write_encoding.encoding, 0);
199     if (!hndl->conv) {
200       gedcom_error(_("Could not open encoding '%s' for writing: %s"),
201                    write_encoding.encoding, strerror(errno));
202       free(hndl);
203       hndl = NULL;
204     }
205     else {
206       hndl->filedesc = open(filename, O_WRONLY|O_CREAT|O_TRUNC, 0666);
207       if (!hndl->filedesc) {
208         gedcom_error(_("Could not open file '%s' for writing: %s"),
209                      filename, strerror(errno));
210         cleanup_utf8_conversion(hndl->conv);
211         free(hndl);
212         hndl = NULL;
213       }
214       else {
215         hndl->term = write_encoding.terminator;
216         hndl->ctxt_level = -1;
217         if (write_encoding.bom == WITH_BOM) {
218           if (write_encoding.width == TWO_BYTE_HILO)
219             write(hndl->filedesc, "\xFE\xFF", 2);
220           else if (write_encoding.width == TWO_BYTE_LOHI)
221             write(hndl->filedesc, "\xFF\xFE", 2);
222           else if (!strcmp(write_encoding.encoding, "UTF-8"))
223             write(hndl->filedesc, "\xEF\xBB\xBF", 3);
224           else
225             gedcom_warning(_("Byte order mark configured, but not relevant"));
226         }
227       }
228     }
229   }
230
231   return hndl;
232 }
233
234 int gedcom_write_close(Gedcom_write_hndl hndl, int* total_conv_fails)
235 {
236   int result = 0;
237   if (hndl) {
238     write_simple(hndl, 0, NULL, "TRLR", NULL);
239     if (total_conv_fails)  *total_conv_fails = hndl->total_conv_fails;
240     result = close(hndl->filedesc);
241     cleanup_utf8_conversion(hndl->conv);
242     free(hndl);
243   }
244   return result;
245 }
246
247 char* get_tag_string(int elt_or_rec, int tag)
248 {
249   int tagnum = tag_data[elt_or_rec].tag;
250   if (!tagnum) tagnum = tag;
251
252   if (tagnum) {
253     if (tagnum >= TAG_NUM_START && tagnum <= TAG_NUM_END)
254       return tag_name[tagnum - TAG_NUM_START];
255     else {
256       gedcom_error(_("Not a valid tag: %d"), tagnum);
257       return NULL;
258     }
259   }
260   else {
261     gedcom_error(_("The element or record type '%s' requires a specific tag "
262                    "for writing"),
263                  tag_data[elt_or_rec].elt_name);
264     return NULL;
265   }
266 }
267
268 int check_type(int elt_or_rec, Gedcom_val_type type)
269 {
270   int allowed = tag_data[elt_or_rec].allowed_types;
271   if (allowed & type)
272     return 1;
273   else {
274     gedcom_error(_("Wrong data type for writing element or record type '%s'"),
275                  tag_data[elt_or_rec].elt_name);
276     return 0;
277   }
278 }
279
280 int get_level(Gedcom_write_hndl hndl, int elt_or_rec, int parent)
281 {
282   if (parent == -1) {
283     hndl->ctxt_level = 0;
284   }
285   else {
286     while (hndl->ctxt_level && hndl->ctxt_stack[hndl->ctxt_level] != parent)
287       hndl->ctxt_level--;
288     if (hndl->ctxt_stack[hndl->ctxt_level] == parent) {
289       hndl->ctxt_level++;
290     }
291     else {
292       gedcom_error(_("Parent %d not found during write of %d"),
293                    parent, elt_or_rec);
294       return -1;
295     }
296   }
297   hndl->ctxt_stack[hndl->ctxt_level] = elt_or_rec;
298   return hndl->ctxt_level;
299 }
300
301 char* convert_at(const char* input)
302 {
303   if (input) {
304     const char* ptr = input;
305     reset_buffer(&convert_at_buffer);
306     while (*ptr) {
307       if (*ptr == '@') {
308         SAFE_BUF_ADDCHAR(&convert_at_buffer, '@');
309         SAFE_BUF_ADDCHAR(&convert_at_buffer, '@');
310       }
311       else {
312         SAFE_BUF_ADDCHAR(&convert_at_buffer, *ptr);
313       }
314       ptr++;
315     }
316     return get_buf_string(&convert_at_buffer);
317   }
318   else
319     return NULL;
320 }
321
322 int _gedcom_write_val(Gedcom_write_hndl hndl,
323                       int rec_or_elt, int tag, int parent_rec_or_elt,
324                       char* xrefstr, char* val)
325 {
326   int result = 1;
327   int level = 0;
328   char* tag_str = NULL;
329
330   tag_str = get_tag_string(rec_or_elt, tag);
331   level   = get_level(hndl, rec_or_elt, parent_rec_or_elt);
332   if (tag_str && (level != -1)) {
333     if (rec_or_elt == ELT_HEAD_CHAR)
334       result = write_encoding_value(hndl, level, xrefstr, tag_str, val);
335     else if (supports_continuation(rec_or_elt, OPT_CONT|OPT_CONC))
336       result = write_long(hndl, rec_or_elt, level, xrefstr, tag_str, val);
337     else
338       result = write_simple(hndl, level, xrefstr, tag_str, val);
339   }
340
341   return result;
342 }
343
344 int gedcom_write_record_str(Gedcom_write_hndl hndl,
345                             Gedcom_rec rec, char* xrefstr, char* val)
346 {
347   int result = 1;
348   if (check_type(rec, (val ? GV_CHAR_PTR : GV_NULL)))
349     result = _gedcom_write_val(hndl, rec, 0, -1, xrefstr, convert_at(val));
350   return result;
351 }
352
353 int gedcom_write_element_str(Gedcom_write_hndl hndl,
354                              Gedcom_elt elt, int tag, int parent_rec_or_elt,
355                              char* val)
356 {
357   int result = 1;
358   if (check_type(elt, (val ? GV_CHAR_PTR : GV_NULL)))
359     result = _gedcom_write_val(hndl, elt, tag, parent_rec_or_elt, NULL,
360                                convert_at(val));
361   return result;
362 }
363
364 int gedcom_write_element_xref(Gedcom_write_hndl hndl,
365                               Gedcom_elt elt, int tag, int parent_rec_or_elt,
366                               struct xref_value* val)
367 {
368   int result = 1;
369   if (check_type(elt, (val ? GV_XREF_PTR : GV_NULL)))
370     result = _gedcom_write_val(hndl, elt, tag, parent_rec_or_elt, NULL,
371                                val->string);
372   return result;
373 }
374
375 int gedcom_write_element_date(Gedcom_write_hndl hndl,
376                               Gedcom_elt elt, int tag, int parent_rec_or_elt,
377                               struct date_value* val)
378 {
379   int result = 1;
380   if (check_type(elt, (val ? GV_DATE_VALUE : GV_NULL)))
381     result = _gedcom_write_val(hndl, elt, tag, parent_rec_or_elt, NULL,
382                                gedcom_date_to_string(val));
383   return result;
384 }
385
386 int gedcom_write_element_age(Gedcom_write_hndl hndl,
387                              Gedcom_elt elt, int tag, int parent_rec_or_elt,
388                              struct age_value* val)
389 {
390   int result = 1;
391   if (check_type(elt, (val ? GV_AGE_VALUE : GV_NULL)))
392     result = _gedcom_write_val(hndl, elt, tag, parent_rec_or_elt, NULL,
393                                gedcom_age_to_string(val));
394   return result;
395 }
396
397 int gedcom_write_user_str(Gedcom_write_hndl hndl, int level, char* tag,
398                           char* xrefstr, char* value)
399 {
400   int result = 1;
401   if (tag && tag[0] == '_')
402     result = write_simple(hndl, level, xrefstr, tag, convert_at(value));
403   return result;
404 }
405
406 int gedcom_write_user_xref(Gedcom_write_hndl hndl, int level, char* tag,
407                            char* xrefstr, struct xref_value* val)
408 {
409   int result = 1;
410   if (tag && tag[0] == '_')
411     result = write_simple(hndl, level, xrefstr, tag, val->string);
412   return result;
413 }