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