Don't lose the HEAD.TIME section of lifelines.
[gedcom-parse.git] / gedcom / compat.c
1 /* Compatibility handling for the GEDCOM parser.
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 "compat.h"
25 #include "interface.h"
26 #include "encoding.h"
27 #include "xref.h"
28 #include "buffer.h"
29 #include "gedcom_internal.h"
30 #include "gedcom.h"
31
32 int compat_enabled = 1;
33 int compatibility  = 0;
34 int compatibility_program = 0;
35 int compatibility_version = 0;
36 const char* default_charset = "";
37
38 #define SUBMITTER_LINK         "@__COMPAT__SUBM__@"
39 #define SLGC_FAMC_LINK         "@__COMPAT__FAM_SLGC__@"
40 #define DEFAULT_SUBMITTER_NAME "Submitter"
41 #define DEFAULT_GEDCOM_VERS    "5.5"
42 #define DEFAULT_GEDCOM_FORM    "LINEAGE-LINKED"
43
44 struct program_data {
45   const char* name;
46   int         default_compat;
47   const char* default_charset;
48 };
49
50 enum _COMPAT_PROGRAM {
51   CP_FTREE = 1,
52   CP_LIFELINES,
53   CP_PAF,
54   CP_FAMORIG,
55   CP_EASYTREE
56 };
57
58 enum _COMPAT {
59   C_FTREE        = 0x0001,
60   C_LIFELINES    = 0x0002,
61   C_PAF5         = 0x0004,
62   C_PAF2         = 0x0008,
63   C_FAMORIG      = 0x0010,
64   C_EASYTREE     = 0x0020,
65   C_PAF4         = 0x0040
66 };
67
68 struct program_data data[] = {
69   /* NULL */         { "", 0, "" },
70   /* CP_FTREE */     { "ftree", C_FTREE, "" },
71   /* CP_LIFELINES */ { "Lifelines", C_LIFELINES, "ANSI" },
72   /* CP_PAF */       { "Personal Ancestral File", C_PAF5, "" },
73   /* CP_FAMORIG */   { "Family Origins", C_FAMORIG, "" },
74   /* CP_EASYTREE */  { "EasyTree", C_EASYTREE, "" }
75 };
76
77 /* Incompatibility list (with GEDCOM 5.5):
78
79     - ftree:
80         - no submitter record, no submitter link in the header
81         - INDI.ADDR instead of INDI.RESI.ADDR
82         - NOTE doesn't have a value
83
84     - Lifelines (3.0.2):
85         - no submitter record, no submitter link in the header
86         - no GEDC field in the header
87         - no CHAR field in the header
88         - HEAD.TIME instead of HEAD.DATE.TIME (will be ignored here)
89         - '@' not written as '@@' in values
90         - lots of missing required values
91
92     - Personal Ancestral File 5:
93         - '@' not written as '@@' in values
94         - some 5.5.1 (draft) tags are used: EMAIL, FONE, ROMN
95         - no FAMC field in SLGC
96         - uses tab character (will be converted to 8 spaces here)
97
98     - Personal Ancestral File 2:
99         - '@' not written as '@@' in values
100         - COMM tag in submitter record
101         - double dates written as e.g. '1815/1816' instead of '1815/16'
102
103     - Family Origins:
104         - '@' not written as '@@' in values
105         - CONC needs an extra space
106
107     - EasyTree:
108         - no GEDC.FORM field
109         - no submitter link in the header
110         - NOTE doesn't have a value
111         - NOTE.NOTE instead of NOTE.COND
112
113     - Personal Ancestral File 4:
114         - '@' not written as '@@' in values
115  */
116
117 int compat_matrix[] =
118 {
119   /* C_NO_SUBMITTER */        C_FTREE | C_LIFELINES | C_PAF2 | C_EASYTREE,
120   /* C_INDI_ADDR */           C_FTREE,
121   /* C_NOTE_NO_VALUE */       C_FTREE | C_EASYTREE,
122   /* C_NO_GEDC */             C_LIFELINES | C_PAF2,
123   /* C_NO_CHAR */             C_LIFELINES,
124   /* C_HEAD_TIME */           C_LIFELINES,
125   /* C_NO_DOUBLE_AT */        C_LIFELINES | C_PAF5 | C_PAF2 | C_FAMORIG
126                                | C_PAF4,
127   /* C_NO_REQUIRED_VALUES */  C_LIFELINES | C_PAF5,
128   /* C_551_TAGS */            C_PAF5,
129   /* C_NO_SLGC_FAMC */        C_PAF5,
130   /* C_SUBM_COMM */           C_PAF2,
131   /* C_DOUBLE_DATES_4 */      C_PAF2 | C_PAF5 | C_PAF4,
132   /* C_CONC_NEEDS_SPACE */    C_FAMORIG,
133   /* C_NO_GEDC_FORM */        C_EASYTREE,
134   /* C_NOTE_NOTE */           C_EASYTREE,
135   /* C_TAB_CHARACTER */       C_PAF5
136 };
137
138 int compat_state[C_NR_OF_RULES];
139
140 /* Compatibility handling */
141
142 void gedcom_set_compat_handling(int enable_compat)
143 {
144   compat_enabled = enable_compat;
145 }
146
147 void enable_compat_msg(const char* program_name, int version)
148 {
149   if (version > 0)
150     gedcom_warning(_("Enabling compatibility with '%s', version %d"),
151                    program_name, version);
152   else
153     gedcom_warning(_("Enabling compatibility with '%s'"),
154                    program_name);
155 }
156
157 int program_equal(const char* program, const char* compare)
158 {
159   return !strncmp(program, compare, strlen(compare)+1);
160 }
161
162 int program_equal_continued(const char* program, const char* compare)
163 {
164   size_t len = strlen(compare);
165   int result = strncmp(program, compare, len);
166   if (result == 0) {
167     if (strlen(program) > len)
168       set_compatibility_version(program + len);
169   }
170   return !result;
171 }
172
173 void set_compatibility_program(const char* program)
174 {
175   compatibility_program = 0;
176   if (compat_enabled) {
177     if (program_equal(program, "ftree")) {
178       compatibility_program = CP_FTREE;
179     }
180     else if (program_equal_continued(program, "LIFELINES")) {
181       compatibility_program = CP_LIFELINES;
182     }
183     else if (program_equal_continued(program, "PAF")) {
184       compatibility_program = CP_PAF;
185     }
186     else if (program_equal(program, "FamilyOrigins")) {
187       compatibility_program = CP_FAMORIG;
188     }
189     else if (program_equal(program, "EasyTree")) {
190       compatibility_program = CP_EASYTREE;
191     }
192   }
193 }
194
195 void compute_compatibility()
196 {
197   /* Reinitialize compatibility */
198   int i;
199   int version = 0;
200   default_charset = "";
201   compatibility = 0;
202   for (i = 0; i < C_NR_OF_RULES; i++)
203     compat_state[i] = 0;
204
205   switch (compatibility_program) {
206     case CP_PAF:
207       if (compatibility_version >= 20000 && compatibility_version < 30000) {
208         compatibility = C_PAF2;
209         version = 2;
210       }
211       if (compatibility_version >= 40000 && compatibility_version < 50000) {
212         compatibility = C_PAF4;
213         version = 4;
214       }
215       else if (compatibility_version >= 50000) {
216         compatibility = C_PAF5;
217         version = 5;
218       }
219       break;
220     default:
221       compatibility = data[compatibility_program].default_compat;
222       break;
223   }
224   if (compatibility) {
225     default_charset = data[compatibility_program].default_charset;
226     enable_compat_msg(data[compatibility_program].name, version);
227   }
228 }
229
230 void set_compatibility_version(const char* version)
231 {
232   if (compat_enabled) {
233     unsigned int major=0, minor=0, patch=0;
234     int result;
235     
236     result = sscanf(version, " %u.%u.%u", &major, &minor, &patch);
237     if (result > 0) {
238       gedcom_debug_print("Setting compat version to %u.%u.%u",
239                          major, minor, patch);
240       compatibility_version = major * 10000 + minor * 100 + patch;
241     }
242   }
243 }
244
245 int compat_mode(Compat_rule rule)
246 {
247   return (compat_matrix[rule] & compatibility);
248 }
249
250 /********************************************************************/
251 /*  C_NO_SUBMITTER                                                  */
252 /********************************************************************/
253
254 void compat_generate_submitter_link(Gedcom_ctxt parent)
255 {
256   struct xref_value *xr = gedcom_parse_xref(SUBMITTER_LINK, XREF_USED,
257                                             XREF_SUBM);
258   struct tag_struct ts;
259   Gedcom_ctxt self;
260   
261   ts.string = "SUBM";
262   ts.value  = TAG_SUBM;
263   gedcom_warning(_("Adding link to submitter record with xref '%s'"),
264                  SUBMITTER_LINK);
265   self = start_element(ELT_HEAD_SUBM,
266                        parent, 1, ts, SUBMITTER_LINK,
267                        GEDCOM_MAKE_XREF_PTR(val1, xr));
268   end_element(ELT_HEAD_SUBM, parent, self, NULL);
269   compat_state[C_NO_SUBMITTER] = 1;
270 }
271
272 void compat_generate_submitter()
273 {
274   if (compat_state[C_NO_SUBMITTER]) {
275     struct xref_value *xr = gedcom_parse_xref(SUBMITTER_LINK, XREF_DEFINED,
276                                               XREF_SUBM);
277     struct tag_struct ts;
278     Gedcom_ctxt self1, self2;
279     
280     /* first generate "0 SUBM" */
281     ts.string = "SUBM";
282     ts.value  = TAG_SUBM;
283     self1 = start_record(REC_SUBM, 0, GEDCOM_MAKE_XREF_PTR(val1, xr), ts,
284                          NULL, GEDCOM_MAKE_NULL(val2));
285     
286     /* then generate "1 NAME ..." */
287     ts.string = "NAME";
288     ts.value  = TAG_NAME;
289     self2 = start_element(ELT_SUBM_NAME, self1, 1, ts, DEFAULT_SUBMITTER_NAME,
290                           GEDCOM_MAKE_STRING(val1, DEFAULT_SUBMITTER_NAME));
291     
292     /* close "1 NAME ..." */
293     end_element(ELT_SUBM_NAME, self1, self2, NULL);
294     
295     /* close "0 SUBM" */
296     end_record(REC_SUBM, self1, NULL);
297     compat_state[C_NO_SUBMITTER] = 0;
298   }
299 }
300
301 /********************************************************************/
302 /*  C_NO_GEDC                                                       */
303 /********************************************************************/
304
305 void compat_generate_gedcom(Gedcom_ctxt parent)
306 {
307   struct tag_struct ts;
308   Gedcom_ctxt self1, self2;
309   
310   /* first generate "1 GEDC" */
311   ts.string = "GEDC";
312   ts.value  = TAG_GEDC;
313   self1 = start_element(ELT_HEAD_GEDC, parent, 1, ts, NULL,
314                         GEDCOM_MAKE_NULL(val1));
315   
316   /* then generate "2 VERS <DEFAULT_GEDC_VERS>" */
317   ts.string = "VERS";
318   ts.value  = TAG_VERS;
319   self2 = start_element(ELT_HEAD_GEDC_VERS, self1, 2, ts,
320                         DEFAULT_GEDCOM_VERS,
321                         GEDCOM_MAKE_STRING(val1, DEFAULT_GEDCOM_VERS));
322   
323   /* close "2 VERS" */
324   end_element(ELT_HEAD_GEDC_VERS, self1, self2, NULL);
325   
326   /* then generate "2 FORM <DEFAULT_GEDCOM_FORM> */
327   compat_generate_gedcom_form(self1);
328   
329   /* close "1 GEDC" */
330   end_element(ELT_HEAD_GEDC, parent, self1, NULL);
331 }
332
333 /********************************************************************/
334 /*  C_NO_GEDC_FORM                                                  */
335 /********************************************************************/
336
337 void compat_generate_gedcom_form(Gedcom_ctxt parent)
338 {
339   struct tag_struct ts;
340   Gedcom_ctxt self;
341   
342   /* generate "2 FORM <DEFAULT_GEDCOM_FORM> */
343   ts.string = "FORM";
344   ts.value  = TAG_FORM;
345   self = start_element(ELT_HEAD_GEDC_FORM, parent, 2, ts,
346                        DEFAULT_GEDCOM_FORM,
347                        GEDCOM_MAKE_STRING(val1, DEFAULT_GEDCOM_FORM));
348   
349   /* close "2 FORM" */
350   end_element(ELT_HEAD_GEDC_FORM, parent, self, NULL);
351   
352 }
353   
354 /********************************************************************/
355 /*  C_NO_CHAR                                                       */
356 /********************************************************************/
357
358 int compat_generate_char(Gedcom_ctxt parent)
359 {
360   struct tag_struct ts;
361   Gedcom_ctxt self1;
362   char* charset;
363   
364   /* first generate "1 CHAR <DEFAULT_CHAR>" */
365   ts.string = "CHAR";
366   ts.value  = TAG_CHAR;
367
368   /* Must strdup, because default_charset is const char */
369   charset   = strdup(default_charset);
370   if (! charset)
371     MEMORY_ERROR;
372   else {
373     self1 = start_element(ELT_HEAD_CHAR, parent, 1, ts, charset,
374                           GEDCOM_MAKE_STRING(val1, charset));
375     free(charset);
376     
377     /* close "1 CHAR" */
378     end_element(ELT_HEAD_CHAR, parent, self1, NULL);
379   }
380   if (open_conv_to_internal(default_charset) == 0)
381     return 1;
382   else
383     return 0;
384 }
385
386 /********************************************************************/
387 /*  C_INDI_ADDR                                                     */
388 /********************************************************************/
389
390 Gedcom_ctxt compat_generate_resi_start(Gedcom_ctxt parent)
391 {
392   Gedcom_ctxt self;
393   struct tag_struct ts;
394
395   ts.string = "RESI";
396   ts.value  = TAG_RESI;
397   self = start_element(ELT_SUB_INDIV_RESI, parent, 1, ts, NULL,
398                        GEDCOM_MAKE_NULL(val1));
399   return self;
400 }
401
402 void compat_generate_resi_end(Gedcom_ctxt parent, Gedcom_ctxt self)
403 {
404   end_element(ELT_SUB_INDIV_RESI, parent, self, NULL);
405 }
406
407 /********************************************************************/
408 /*  C_551_TAGS                                                      */
409 /********************************************************************/
410
411 int is_551_tag(const char* tag)
412 {
413   if (strncmp(tag, "EMAIL", 6))
414     return 1;
415   else if (strncmp(tag, "FONE", 5))
416     return 1;
417   else if (strncmp(tag, "ROMN", 5))
418     return 1;
419   else
420     return 0;
421 }
422
423 int compat_check_551_tag(const char* tag, struct safe_buffer* b)
424 {
425   if (is_551_tag(tag)) {
426     reset_buffer(b);
427     SAFE_BUF_ADDCHAR(b, '_');
428     safe_buf_append(b, tag);
429     gedcom_warning(_("Converting 5.5.1 tag '%s' to standard 5.5 user tag '%s'"),
430                    tag, get_buf_string(b));
431     return 1;
432   }
433   else
434     return 0;
435 }
436
437 /********************************************************************/
438 /*  C_NO_SLGC_FAMC                                                  */
439 /********************************************************************/
440
441 void compat_generate_slgc_famc_link(Gedcom_ctxt parent)
442 {
443   struct xref_value *xr = gedcom_parse_xref(SLGC_FAMC_LINK, XREF_USED,
444                                             XREF_FAM);
445   struct tag_struct ts;
446   Gedcom_ctxt self;
447   
448   ts.string = "FAMC";
449   ts.value  = TAG_FAMC;
450   gedcom_warning(_("Adding link to family record with xref '%s'"),
451                  SLGC_FAMC_LINK);
452   self = start_element(ELT_SUB_LIO_SLGC_FAMC,
453                        parent, 2, ts, SLGC_FAMC_LINK,
454                        GEDCOM_MAKE_XREF_PTR(val1, xr));
455   end_element(ELT_SUB_LIO_SLGC_FAMC, parent, self, NULL);
456   compat_state[C_NO_SLGC_FAMC]++;
457 }
458
459 void compat_generate_slgc_famc_fam()
460 {
461   /* If bigger than 1, then the FAM record has already been generated */
462   if (compat_state[C_NO_SLGC_FAMC] == 1) {
463     struct xref_value *xr = gedcom_parse_xref(SLGC_FAMC_LINK, XREF_DEFINED,
464                                               XREF_FAM);
465     struct tag_struct ts;
466     Gedcom_ctxt self;
467     
468     /* generate "0 FAM" */
469     ts.string = "FAM";
470     ts.value  = TAG_FAM;
471     self = start_record(REC_FAM, 0, GEDCOM_MAKE_XREF_PTR(val1, xr), ts,
472                         NULL, GEDCOM_MAKE_NULL(val2));
473     
474     /* close "0 FAM" */
475     end_record(REC_FAM, self, NULL);
476   }
477 }
478
479 /********************************************************************/
480 /*  C_SUBM_COMM                                                     */
481 /********************************************************************/
482
483 int compat_check_subm_comm(const char* tag, const char* parent_tag,
484                            struct safe_buffer* b)
485 {
486   if (!strcmp(tag, "COMM") && !strcmp(parent_tag, "SUBM")) {
487     reset_buffer(b);
488     SAFE_BUF_ADDCHAR(b, '_');
489     safe_buf_append(b, tag);
490     gedcom_warning(_("Converting non-standard tag '%s' to user tag '%s'"),
491                    tag, get_buf_string(b));
492     compat_state[C_SUBM_COMM] = 1;
493     return 1;
494   }
495   else
496     return 0;
497 }
498
499 void compat_close_subm_comm()
500 {
501   compat_state[C_SUBM_COMM] = 0;
502 }
503
504 int compat_check_subm_comm_cont(const char* tag)
505 {
506   if (compat_state[C_SUBM_COMM] && !strcmp(tag, "CONT")) {
507     compat_state[C_SUBM_COMM] = 2;
508     return 1;
509   }
510   else
511     return 0;
512 }
513
514 Gedcom_ctxt compat_subm_comm_cont_start(Gedcom_ctxt parent, char* str)
515 {
516   Gedcom_ctxt self = NULL;
517   struct tag_struct ts;
518
519   if (compat_state[C_SUBM_COMM] == 2) {
520     ts.string = "_CONT";
521     ts.value  = USERTAG;
522     self = start_element(ELT_USER, parent, 2, ts, str, &val2);
523   }
524
525   return self;
526 }
527
528 void compat_subm_comm_cont_end(Gedcom_ctxt parent, Gedcom_ctxt self)
529 {
530   if (compat_state[C_SUBM_COMM] == 2) {
531     end_element(ELT_USER, parent, self, NULL);
532     compat_state[C_SUBM_COMM] = 1;
533   }
534 }