Copied from old documentation. Removed all Gedcom_val details.
[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             while (value_ptr[line_len] == ' '
160                    || value_ptr[line_len-1] == ' ') {
161               line_len--;
162             }
163           }
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           if (*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 Gedcom_write_hndl gedcom_write_open(const char *filename)
193 {
194   Gedcom_write_hndl hndl;
195
196   hndl = (Gedcom_write_hndl)malloc(sizeof(struct Gedcom_write_struct));
197
198   if (!hndl)
199     MEMORY_ERROR;
200   else {
201     init_write_encoding();
202     init_write_terminator();
203     hndl->total_conv_fails = 0;
204     hndl->conv = initialize_utf8_conversion(write_encoding.encoding, 0);
205     if (!hndl->conv) {
206       gedcom_error(_("Could not open encoding '%s' for writing: %s"),
207                    write_encoding.encoding, strerror(errno));
208       free(hndl);
209       hndl = NULL;
210     }
211     else {
212       hndl->filedesc = open(filename, O_WRONLY|O_CREAT|O_TRUNC, 0666);
213       if (!hndl->filedesc) {
214         gedcom_error(_("Could not open file '%s' for writing: %s"),
215                      filename, strerror(errno));
216         cleanup_utf8_conversion(hndl->conv);
217         free(hndl);
218         hndl = NULL;
219       }
220       else {
221         hndl->term = write_encoding.terminator;
222         hndl->ctxt_level = -1;
223         if (write_encoding.bom == WITH_BOM) {
224           if (write_encoding.width == TWO_BYTE_HILO)
225             write(hndl->filedesc, "\xFE\xFF", 2);
226           else if (write_encoding.width == TWO_BYTE_LOHI)
227             write(hndl->filedesc, "\xFF\xFE", 2);
228           else if (!strcmp(write_encoding.encoding, "UTF-8"))
229             write(hndl->filedesc, "\xEF\xBB\xBF", 3);
230           else
231             gedcom_warning(_("Byte order mark configured, but not relevant"));
232         }
233       }
234     }
235   }
236
237   return hndl;
238 }
239
240 int gedcom_write_close(Gedcom_write_hndl hndl, int* total_conv_fails)
241 {
242   int result = 0;
243   if (hndl) {
244     write_simple(hndl, 0, NULL, "TRLR", NULL);
245     if (total_conv_fails)  *total_conv_fails = hndl->total_conv_fails;
246     result = close(hndl->filedesc);
247     cleanup_utf8_conversion(hndl->conv);
248     free(hndl);
249   }
250   return result;
251 }
252
253 char* get_tag_string(int elt_or_rec, int tag)
254 {
255   int tagnum = tag_data[elt_or_rec].tag;
256   if (!tagnum) tagnum = tag;
257
258   if (tagnum) {
259     if (tagnum >= TAG_NUM_START && tagnum <= TAG_NUM_END)
260       return tag_name[tagnum - TAG_NUM_START];
261     else {
262       gedcom_error(_("Not a valid tag: %d"), tagnum);
263       return NULL;
264     }
265   }
266   else {
267     gedcom_error(_("The element or record type '%s' requires a specific tag "
268                    "for writing"),
269                  tag_data[elt_or_rec].elt_name);
270     return NULL;
271   }
272 }
273
274 int check_type(int elt_or_rec, Gedcom_val_type type)
275 {
276   int allowed = tag_data[elt_or_rec].allowed_types;
277   if (allowed & type)
278     return 1;
279   else {
280     gedcom_error(_("Wrong data type for writing element or record type '%s'"),
281                  tag_data[elt_or_rec].elt_name);
282     return 0;
283   }
284 }
285
286 int get_level(Gedcom_write_hndl hndl, int elt_or_rec, int parent)
287 {
288   if (parent == -1) {
289     hndl->ctxt_level = 0;
290   }
291   else {
292     while (hndl->ctxt_level && hndl->ctxt_stack[hndl->ctxt_level] != parent)
293       hndl->ctxt_level--;
294     if (hndl->ctxt_stack[hndl->ctxt_level] == parent) {
295       hndl->ctxt_level++;
296     }
297     else {
298       gedcom_error(_("Parent %d not found during write of %d"),
299                    parent, elt_or_rec);
300       return -1;
301     }
302   }
303   hndl->ctxt_stack[hndl->ctxt_level] = elt_or_rec;
304   return hndl->ctxt_level;
305 }
306
307 char* convert_at(const char* input)
308 {
309   if (input) {
310     const char* ptr = input;
311     reset_buffer(&convert_at_buffer);
312     while (*ptr) {
313       if (*ptr == '@') {
314         SAFE_BUF_ADDCHAR(&convert_at_buffer, '@');
315         SAFE_BUF_ADDCHAR(&convert_at_buffer, '@');
316       }
317       else {
318         SAFE_BUF_ADDCHAR(&convert_at_buffer, *ptr);
319       }
320       ptr++;
321     }
322     return get_buf_string(&convert_at_buffer);
323   }
324   else
325     return NULL;
326 }
327
328 int _gedcom_write_val(Gedcom_write_hndl hndl,
329                       int rec_or_elt, int tag, int parent_rec_or_elt,
330                       const char* xrefstr, const char* val)
331 {
332   int result = 1;
333   int level = 0;
334   char* tag_str = NULL;
335
336   tag_str = get_tag_string(rec_or_elt, tag);
337   level   = get_level(hndl, rec_or_elt, parent_rec_or_elt);
338   if (tag_str && (level != -1)) {
339     if (rec_or_elt == ELT_HEAD_CHAR)
340       result = write_encoding_value(hndl, level, xrefstr, tag_str, val);
341     else if (supports_continuation(rec_or_elt, OPT_CONT|OPT_CONC))
342       result = write_long(hndl, rec_or_elt, level, xrefstr, tag_str, val);
343     else
344       result = write_simple(hndl, level, xrefstr, tag_str, val);
345   }
346
347   return result;
348 }
349
350 int gedcom_write_record_str(Gedcom_write_hndl hndl,
351                             Gedcom_rec rec, const char* xrefstr,
352                             const char* val)
353 {
354   int result = 1;
355   if (check_type(rec, (val ? GV_CHAR_PTR : GV_NULL)))
356     result = _gedcom_write_val(hndl, rec, 0, -1, xrefstr, convert_at(val));
357   return result;
358 }
359
360 int gedcom_write_element_str(Gedcom_write_hndl hndl,
361                              Gedcom_elt elt, int tag, int parent_rec_or_elt,
362                              const char* val)
363 {
364   int result = 1;
365   if (check_type(elt, (val ? GV_CHAR_PTR : GV_NULL)))
366     result = _gedcom_write_val(hndl, elt, tag, parent_rec_or_elt, NULL,
367                                convert_at(val));
368   return result;
369 }
370
371 int gedcom_write_element_xref(Gedcom_write_hndl hndl,
372                               Gedcom_elt elt, int tag, int parent_rec_or_elt,
373                               const struct xref_value* val)
374 {
375   int result = 1;
376   if (check_type(elt, (val ? GV_XREF_PTR : GV_NULL)))
377     result = _gedcom_write_val(hndl, elt, tag, parent_rec_or_elt, NULL,
378                                val->string);
379   return result;
380 }
381
382 int gedcom_write_element_date(Gedcom_write_hndl hndl,
383                               Gedcom_elt elt, int tag, int parent_rec_or_elt,
384                               const struct date_value* val)
385 {
386   int result = 1;
387   if (check_type(elt, (val ? GV_DATE_VALUE : GV_NULL)))
388     result = _gedcom_write_val(hndl, elt, tag, parent_rec_or_elt, NULL,
389                                gedcom_date_to_string(val));
390   return result;
391 }
392
393 int gedcom_write_element_age(Gedcom_write_hndl hndl,
394                              Gedcom_elt elt, int tag, int parent_rec_or_elt,
395                              const struct age_value* val)
396 {
397   int result = 1;
398   if (check_type(elt, (val ? GV_AGE_VALUE : GV_NULL)))
399     result = _gedcom_write_val(hndl, elt, tag, parent_rec_or_elt, NULL,
400                                gedcom_age_to_string(val));
401   return result;
402 }
403
404 int gedcom_write_user_str(Gedcom_write_hndl hndl, int level, const char* tag,
405                           const char* xrefstr, const char* value)
406 {
407   int result = 1;
408   if (tag && tag[0] == '_')
409     result = write_simple(hndl, level, xrefstr, tag, convert_at(value));
410   return result;
411 }
412
413 int gedcom_write_user_xref(Gedcom_write_hndl hndl, int level, const char* tag,
414                            const char* xrefstr, const struct xref_value* val)
415 {
416   int result = 1;
417   if (tag && tag[0] == '_')
418     result = write_simple(hndl, level, xrefstr, tag, val->string);
419   return result;
420 }