Completed writing of strings and xrefs.
[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 #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, term_len;
131   char* nl_pos = strchr(value, '\n');
132
133   prefix_len = utf8_strlen(tag) + 3;  /* for e.g. "0 INDI " */
134   if (level > 9) prefix_len++;
135   if (xref)      prefix_len += utf8_strlen(xref) + 1;
136   value_len  = utf8_strlen(value);
137   term_len   = strlen(hndl->term);
138
139   if (!nl_pos && prefix_len + value_len + term_len <= MAXWRITELEN)
140     write_simple(hndl, level, xref, tag, value);
141   else {
142     char* value_ptr = value;
143     int cont_supported = supports_continuation(elt_or_rec, OPT_CONT);
144     int cont_as_conc   = supports_continuation(elt_or_rec, OPT_CONT_AS_CONC);
145     if (nl_pos && !cont_supported) {
146       gedcom_error (_("The tag %s doesn't support newlines\n"), tag);
147       return 1;
148     }
149     else {
150       char value_part[MAXWRITELEN];
151       int cont_prefix_len, write_level = level;
152       cont_prefix_len = utf8_strlen("CONT") + 3;
153       if (level + 1 > 9) cont_prefix_len++;
154
155       while (value_ptr) {
156         char* cont_tag = "CONT";
157         int line_len = (nl_pos && cont_supported
158                         ? nl_pos - value_ptr : value_len);
159
160         if (prefix_len + line_len + term_len > MAXWRITELEN) {
161           line_len = MAXWRITELEN - prefix_len - term_len;
162           if (!cont_as_conc)
163             cont_tag = "CONC";
164         }
165         
166         memset(value_part, 0, sizeof(value_part));
167         strncpy(value_part, value_ptr, line_len);
168         write_simple(hndl, write_level, xref, tag, value_part);
169         
170         if (line_len < value_len) {
171           value_ptr   = value_ptr + line_len;
172           value_len   = value_len - line_len;
173           while (*value_ptr == '\n') {
174             value_ptr++;
175             value_len--;
176           }
177           prefix_len  = cont_prefix_len;
178           write_level = level + 1;
179           xref        = NULL;
180           tag         = cont_tag;
181           nl_pos      = strchr(value_ptr, '\n');
182         }
183         else
184           value_ptr = NULL;
185       }
186     }
187   }
188   
189   return 0;
190 }
191
192 int gedcom_write_set_encoding(const char* charset,
193                               Encoding width, Enc_bom bom)
194 {
195   char* new_encoding = NULL;
196   if (!strcmp(charset, "UNICODE")) {
197     if (width == ONE_BYTE) {
198       gedcom_error(_("Unicode cannot be encoded into one byte"));
199       return 1;
200     }
201     else {
202       new_encoding = get_encoding(charset, width);
203       if (new_encoding) {
204         encoding = new_encoding;
205         write_encoding_details = width | bom;
206       }
207       else
208         return 1;
209     }
210   }
211   else {
212     new_encoding = get_encoding(charset, ONE_BYTE);
213     if (new_encoding) {
214       encoding = new_encoding;
215       write_encoding_details = ONE_BYTE;
216     }
217     else
218       return 1;
219   }
220   return 0;
221 }
222
223 int gedcom_write_set_line_terminator(Enc_line_end end)
224 {
225   write_terminator = terminator[end];
226   return 0;
227 }
228
229 Gedcom_write_hndl gedcom_write_open(const char *filename)
230 {
231   Gedcom_write_hndl hndl;
232
233   hndl = (Gedcom_write_hndl)malloc(sizeof(struct Gedcom_write_struct));
234
235   if (!hndl)
236     MEMORY_ERROR;
237   else {
238     hndl->total_conv_fails = 0;
239     hndl->conv = initialize_utf8_conversion(encoding, 0);
240     if (!hndl->conv) {
241       gedcom_error(_("Could not open encoding '%s' for writing: %s"),
242                    encoding, strerror(errno));
243       free(hndl);
244       hndl = NULL;
245     }
246     else {
247       hndl->filedesc = open(filename, O_WRONLY|O_CREAT|O_TRUNC, 0666);
248       if (!hndl->filedesc) {
249         gedcom_error(_("Could not open file '%s' for writing: %s"),
250                      filename, strerror(errno));
251         cleanup_utf8_conversion(hndl->conv);
252         free(hndl);
253         hndl = NULL;
254       }
255       else {
256         hndl->term = write_terminator;
257         hndl->ctxt_level = -1;
258         if (write_encoding_details & WITH_BOM) {
259           if (write_encoding_details & TWO_BYTE_HILO)
260             write(hndl->filedesc, "\xFE\xFF", 2);
261           else if (write_encoding_details & TWO_BYTE_LOHI)
262             write(hndl->filedesc, "\xFF\xFE", 2);
263           else
264             gedcom_warning(_("Byte order mark configured, but no Unicode"));
265         }
266       }
267     }
268   }
269
270   return hndl;
271 }
272
273 int gedcom_write_close(Gedcom_write_hndl hndl, int* total_conv_fails)
274 {
275   int result = 0;
276   if (hndl) {
277     write_simple(hndl, 0, NULL, "TRLR", NULL);
278     if (total_conv_fails)  *total_conv_fails = hndl->total_conv_fails;
279     result = close(hndl->filedesc);
280     cleanup_utf8_conversion(hndl->conv);
281     free(hndl);
282   }
283   return result;
284 }
285
286 char* get_tag_string(int elt_or_rec, int tag)
287 {
288   int tagnum = tag_data[elt_or_rec].tag;
289   if (!tagnum) tagnum = tag;
290
291   if (tagnum) {
292     if (tagnum >= TAG_NUM_START && tagnum <= TAG_NUM_END)
293       return tag_name[tagnum - TAG_NUM_START];
294     else {
295       gedcom_error(_("Not a valid tag: %d"), tagnum);
296       return NULL;
297     }
298   }
299   else {
300     gedcom_error(_("The element or record type '%s' requires a specific tag"
301                    "for writing"),
302                  tag_data[elt_or_rec].elt_name);
303     return NULL;
304   }
305 }
306
307 int check_type(int elt_or_rec, Gedcom_val_type type)
308 {
309   int allowed = tag_data[elt_or_rec].allowed_types;
310   if (allowed & type)
311     return 1;
312   else {
313     gedcom_error(_("Wrong data type for writing element or record type '%s'"),
314                  tag_data[elt_or_rec].elt_name);
315     return 0;
316   }
317 }
318
319 int get_level(Gedcom_write_hndl hndl, int elt_or_rec, int parent)
320 {
321   if (parent == -1) {
322     hndl->ctxt_level = 0;
323   }
324   else {
325     while (hndl->ctxt_level && hndl->ctxt_stack[hndl->ctxt_level] != parent)
326       hndl->ctxt_level--;
327     if (hndl->ctxt_stack[hndl->ctxt_level] == parent) {
328       hndl->ctxt_level++;
329     }
330     else {
331       gedcom_error(_("Parent %d not found during write of %d"),
332                    parent, elt_or_rec);
333       return -1;
334     }
335   }
336   hndl->ctxt_stack[hndl->ctxt_level] = elt_or_rec;
337   return hndl->ctxt_level;
338 }
339
340 char* convert_at(const char* input)
341 {
342   if (input) {
343     const char* ptr = input;
344     reset_buffer(&convert_at_buffer);
345     while (*ptr) {
346       if (*ptr == '@') {
347         SAFE_BUF_ADDCHAR(&convert_at_buffer, '@');
348         SAFE_BUF_ADDCHAR(&convert_at_buffer, '@');
349       }
350       else {
351         SAFE_BUF_ADDCHAR(&convert_at_buffer, *ptr);
352       }
353       ptr++;
354     }
355     return get_buf_string(&convert_at_buffer);
356   }
357   else
358     return NULL;
359 }
360
361 int _gedcom_write_val(Gedcom_write_hndl hndl,
362                       int rec_or_elt, int tag, int parent_rec_or_elt,
363                       char* xrefstr, char* val)
364 {
365   int result = 1;
366   int level = 0;
367   char* tag_str = NULL;
368
369   tag_str = get_tag_string(rec_or_elt, tag);
370   level   = get_level(hndl, rec_or_elt, parent_rec_or_elt);
371   if (tag_str && (level != -1)) {
372     if (supports_continuation(rec_or_elt, OPT_CONT|OPT_CONC|OPT_CONT_AS_CONC))
373       result = write_long(hndl, rec_or_elt, level, xrefstr, tag_str, val);
374     else
375       result = write_simple(hndl, level, xrefstr, tag_str, val);
376   }
377
378   return result;
379 }
380
381 int gedcom_write_record_str(Gedcom_write_hndl hndl,
382                             Gedcom_rec rec, int tag,
383                             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, tag, -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_record_xref(Gedcom_write_hndl hndl,
403                              Gedcom_rec rec, int tag,
404                              char* xrefstr, struct xref_value* val)
405 {
406   int result = 1;
407   if (check_type(rec, (val ? GV_XREF_PTR : GV_NULL)))
408     result = _gedcom_write_val(hndl, rec, tag, -1, xrefstr, val->string);
409   return result;
410 }
411
412 int gedcom_write_element_xref(Gedcom_write_hndl hndl,
413                               Gedcom_elt elt, int tag, int parent_rec_or_elt,
414                               struct xref_value* val)
415 {
416   int result = 1;
417   if (check_type(elt, (val ? GV_XREF_PTR : GV_NULL)))
418     result = _gedcom_write_val(hndl, elt, tag, parent_rec_or_elt, NULL,
419                                val->string);
420   return result;
421 }
422
423 int gedcom_write_user_str(Gedcom_write_hndl hndl, int level, char* tag,
424                           char* xrefstr, char* value)
425 {
426   int result = 1;
427   if (tag && tag[0] == '_')
428     result = write_simple(hndl, level, xrefstr, tag, convert_at(value));
429   return result;
430 }
431
432 int gedcom_write_user_xref(Gedcom_write_hndl hndl, int level, char* tag,
433                            char* xrefstr, struct xref_value* val)
434 {
435   int result = 1;
436   if (tag && tag[0] == '_')
437     result = write_simple(hndl, level, xrefstr, tag, val->string);
438   return result;
439 }