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