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