15f6b06d1190a3f445650c0cac5e49e8c2e6332d
[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 union _COMPAT_STATE {
139   int i;
140   void* vp;
141 } compat_state[C_NR_OF_RULES];
142
143 /* Compatibility handling */
144
145 void gedcom_set_compat_handling(int enable_compat)
146 {
147   compat_enabled = enable_compat;
148 }
149
150 void enable_compat_msg(const char* program_name, int version)
151 {
152   if (version > 0)
153     gedcom_warning(_("Enabling compatibility with '%s', version %d"),
154                    program_name, version);
155   else
156     gedcom_warning(_("Enabling compatibility with '%s'"),
157                    program_name);
158 }
159
160 int program_equal(const char* program, const char* compare)
161 {
162   return !strncmp(program, compare, strlen(compare)+1);
163 }
164
165 int program_equal_continued(const char* program, const char* compare)
166 {
167   size_t len = strlen(compare);
168   int result = strncmp(program, compare, len);
169   if (result == 0) {
170     if (strlen(program) > len)
171       set_compatibility_version(program + len);
172   }
173   return !result;
174 }
175
176 void set_compatibility_program(const char* program)
177 {
178   compatibility_program = 0;
179   if (compat_enabled) {
180     if (program_equal(program, "ftree")) {
181       compatibility_program = CP_FTREE;
182     }
183     else if (program_equal_continued(program, "LIFELINES")) {
184       compatibility_program = CP_LIFELINES;
185     }
186     else if (program_equal_continued(program, "PAF")) {
187       compatibility_program = CP_PAF;
188     }
189     else if (program_equal(program, "FamilyOrigins")) {
190       compatibility_program = CP_FAMORIG;
191     }
192     else if (program_equal(program, "EasyTree")) {
193       compatibility_program = CP_EASYTREE;
194     }
195   }
196 }
197
198 void compute_compatibility()
199 {
200   /* Reinitialize compatibility */
201   int i;
202   int version = 0;
203   default_charset = "";
204   compatibility = 0;
205   for (i = 0; i < C_NR_OF_RULES; i++)
206     compat_state[i].i = 0;
207
208   switch (compatibility_program) {
209     case CP_PAF:
210       if (compatibility_version >= 20000 && compatibility_version < 30000) {
211         compatibility = C_PAF2;
212         version = 2;
213       }
214       if (compatibility_version >= 40000 && compatibility_version < 50000) {
215         compatibility = C_PAF4;
216         version = 4;
217       }
218       else if (compatibility_version >= 50000) {
219         compatibility = C_PAF5;
220         version = 5;
221       }
222       break;
223     default:
224       compatibility = data[compatibility_program].default_compat;
225       break;
226   }
227   if (compatibility) {
228     default_charset = data[compatibility_program].default_charset;
229     enable_compat_msg(data[compatibility_program].name, version);
230   }
231 }
232
233 void set_compatibility_version(const char* version)
234 {
235   if (compat_enabled) {
236     unsigned int major=0, minor=0, patch=0;
237     int result;
238     
239     result = sscanf(version, " %u.%u.%u", &major, &minor, &patch);
240     if (result > 0) {
241       gedcom_debug_print("Setting compat version to %u.%u.%u",
242                          major, minor, patch);
243       compatibility_version = major * 10000 + minor * 100 + patch;
244     }
245   }
246 }
247
248 int compat_mode(Compat_rule rule)
249 {
250   return (compat_matrix[rule] & compatibility);
251 }
252
253 /********************************************************************/
254 /*  C_NO_SUBMITTER                                                  */
255 /********************************************************************/
256
257 void compat_generate_submitter_link(Gedcom_ctxt parent)
258 {
259   struct xref_value *xr = gedcom_parse_xref(SUBMITTER_LINK, XREF_USED,
260                                             XREF_SUBM);
261   struct tag_struct ts;
262   Gedcom_ctxt self;
263   
264   ts.string = "SUBM";
265   ts.value  = TAG_SUBM;
266   gedcom_warning(_("Adding link to submitter record with xref '%s'"),
267                  SUBMITTER_LINK);
268   self = start_element(ELT_HEAD_SUBM,
269                        parent, 1, ts, SUBMITTER_LINK,
270                        GEDCOM_MAKE_XREF_PTR(val1, xr));
271   end_element(ELT_HEAD_SUBM, parent, self, NULL);
272   compat_state[C_NO_SUBMITTER].i = 1;
273 }
274
275 void compat_generate_submitter()
276 {
277   if (compat_state[C_NO_SUBMITTER].i) {
278     struct xref_value *xr = gedcom_parse_xref(SUBMITTER_LINK, XREF_DEFINED,
279                                               XREF_SUBM);
280     struct tag_struct ts;
281     Gedcom_ctxt self1, self2;
282     
283     /* first generate "0 SUBM" */
284     ts.string = "SUBM";
285     ts.value  = TAG_SUBM;
286     self1 = start_record(REC_SUBM, 0, GEDCOM_MAKE_XREF_PTR(val1, xr), ts,
287                          NULL, GEDCOM_MAKE_NULL(val2));
288     
289     /* then generate "1 NAME ..." */
290     ts.string = "NAME";
291     ts.value  = TAG_NAME;
292     self2 = start_element(ELT_SUBM_NAME, self1, 1, ts, DEFAULT_SUBMITTER_NAME,
293                           GEDCOM_MAKE_STRING(val1, DEFAULT_SUBMITTER_NAME));
294     
295     /* close "1 NAME ..." */
296     end_element(ELT_SUBM_NAME, self1, self2, NULL);
297     
298     /* close "0 SUBM" */
299     end_record(REC_SUBM, self1, NULL);
300     compat_state[C_NO_SUBMITTER].i = 0;
301   }
302 }
303
304 /********************************************************************/
305 /*  C_NO_GEDC                                                       */
306 /********************************************************************/
307
308 void compat_generate_gedcom(Gedcom_ctxt parent)
309 {
310   struct tag_struct ts;
311   Gedcom_ctxt self1, self2;
312   
313   /* first generate "1 GEDC" */
314   ts.string = "GEDC";
315   ts.value  = TAG_GEDC;
316   self1 = start_element(ELT_HEAD_GEDC, parent, 1, ts, NULL,
317                         GEDCOM_MAKE_NULL(val1));
318   
319   /* then generate "2 VERS <DEFAULT_GEDC_VERS>" */
320   ts.string = "VERS";
321   ts.value  = TAG_VERS;
322   self2 = start_element(ELT_HEAD_GEDC_VERS, self1, 2, ts,
323                         DEFAULT_GEDCOM_VERS,
324                         GEDCOM_MAKE_STRING(val1, DEFAULT_GEDCOM_VERS));
325   
326   /* close "2 VERS" */
327   end_element(ELT_HEAD_GEDC_VERS, self1, self2, NULL);
328   
329   /* then generate "2 FORM <DEFAULT_GEDCOM_FORM> */
330   compat_generate_gedcom_form(self1);
331   
332   /* close "1 GEDC" */
333   end_element(ELT_HEAD_GEDC, parent, self1, NULL);
334 }
335
336 /********************************************************************/
337 /*  C_NO_GEDC_FORM                                                  */
338 /********************************************************************/
339
340 void compat_generate_gedcom_form(Gedcom_ctxt parent)
341 {
342   struct tag_struct ts;
343   Gedcom_ctxt self;
344   
345   /* generate "2 FORM <DEFAULT_GEDCOM_FORM> */
346   ts.string = "FORM";
347   ts.value  = TAG_FORM;
348   self = start_element(ELT_HEAD_GEDC_FORM, parent, 2, ts,
349                        DEFAULT_GEDCOM_FORM,
350                        GEDCOM_MAKE_STRING(val1, DEFAULT_GEDCOM_FORM));
351   
352   /* close "2 FORM" */
353   end_element(ELT_HEAD_GEDC_FORM, parent, self, NULL);
354   
355 }
356   
357 /********************************************************************/
358 /*  C_NO_CHAR                                                       */
359 /********************************************************************/
360
361 int compat_generate_char(Gedcom_ctxt parent)
362 {
363   struct tag_struct ts;
364   Gedcom_ctxt self1;
365   char* charset;
366   
367   /* first generate "1 CHAR <DEFAULT_CHAR>" */
368   ts.string = "CHAR";
369   ts.value  = TAG_CHAR;
370
371   /* Must strdup, because default_charset is const char */
372   charset   = strdup(default_charset);
373   if (! charset)
374     MEMORY_ERROR;
375   else {
376     self1 = start_element(ELT_HEAD_CHAR, parent, 1, ts, charset,
377                           GEDCOM_MAKE_STRING(val1, charset));
378     free(charset);
379     
380     /* close "1 CHAR" */
381     end_element(ELT_HEAD_CHAR, parent, self1, NULL);
382   }
383   if (open_conv_to_internal(default_charset) == 0)
384     return 1;
385   else
386     return 0;
387 }
388
389 /********************************************************************/
390 /*  C_HEAD_TIME                                                     */
391 /********************************************************************/
392
393 void compat_save_head_date_context(Gedcom_ctxt parent)
394 {
395   compat_state[C_HEAD_TIME].vp = parent;
396 }
397
398 Gedcom_ctxt compat_generate_head_time_start(int level, struct tag_struct ts,
399                                             char* value)
400 {
401   Gedcom_ctxt parent = compat_state[C_HEAD_TIME].vp;
402   if (!value)
403     value = "-";
404   if (parent)
405     return start_element(ELT_HEAD_DATE_TIME,
406                          parent, level, ts, value,
407                          GEDCOM_MAKE_STRING(val1, value));
408   else
409     return NULL;
410 }
411
412 void compat_generate_head_time_end(Gedcom_ctxt self)
413 {
414   Gedcom_ctxt parent = compat_state[C_HEAD_TIME].vp;
415   if (parent)
416     end_element(ELT_HEAD_DATE_TIME,
417                 parent, self, GEDCOM_MAKE_NULL(val1));
418 }
419
420 /********************************************************************/
421 /*  C_INDI_ADDR                                                     */
422 /********************************************************************/
423
424 Gedcom_ctxt compat_generate_resi_start(Gedcom_ctxt parent)
425 {
426   Gedcom_ctxt self;
427   struct tag_struct ts;
428
429   ts.string = "RESI";
430   ts.value  = TAG_RESI;
431   self = start_element(ELT_SUB_INDIV_RESI, parent, 1, ts, NULL,
432                        GEDCOM_MAKE_NULL(val1));
433   return self;
434 }
435
436 void compat_generate_resi_end(Gedcom_ctxt parent, Gedcom_ctxt self)
437 {
438   end_element(ELT_SUB_INDIV_RESI, parent, self, NULL);
439 }
440
441 /********************************************************************/
442 /*  C_551_TAGS                                                      */
443 /********************************************************************/
444
445 int is_551_tag(const char* tag)
446 {
447   if (strncmp(tag, "EMAIL", 6))
448     return 1;
449   else if (strncmp(tag, "FONE", 5))
450     return 1;
451   else if (strncmp(tag, "ROMN", 5))
452     return 1;
453   else
454     return 0;
455 }
456
457 int compat_check_551_tag(const char* tag, struct safe_buffer* b)
458 {
459   if (is_551_tag(tag)) {
460     reset_buffer(b);
461     SAFE_BUF_ADDCHAR(b, '_');
462     safe_buf_append(b, tag);
463     gedcom_warning(_("Converting 5.5.1 tag '%s' to standard 5.5 user tag '%s'"),
464                    tag, get_buf_string(b));
465     return 1;
466   }
467   else
468     return 0;
469 }
470
471 /********************************************************************/
472 /*  C_NO_SLGC_FAMC                                                  */
473 /********************************************************************/
474
475 void compat_generate_slgc_famc_link(Gedcom_ctxt parent)
476 {
477   struct xref_value *xr = gedcom_parse_xref(SLGC_FAMC_LINK, XREF_USED,
478                                             XREF_FAM);
479   struct tag_struct ts;
480   Gedcom_ctxt self;
481   
482   ts.string = "FAMC";
483   ts.value  = TAG_FAMC;
484   gedcom_warning(_("Adding link to family record with xref '%s'"),
485                  SLGC_FAMC_LINK);
486   self = start_element(ELT_SUB_LIO_SLGC_FAMC,
487                        parent, 2, ts, SLGC_FAMC_LINK,
488                        GEDCOM_MAKE_XREF_PTR(val1, xr));
489   end_element(ELT_SUB_LIO_SLGC_FAMC, parent, self, NULL);
490   compat_state[C_NO_SLGC_FAMC].i++;
491 }
492
493 void compat_generate_slgc_famc_fam()
494 {
495   /* If bigger than 1, then the FAM record has already been generated */
496   if (compat_state[C_NO_SLGC_FAMC].i == 1) {
497     struct xref_value *xr = gedcom_parse_xref(SLGC_FAMC_LINK, XREF_DEFINED,
498                                               XREF_FAM);
499     struct tag_struct ts;
500     Gedcom_ctxt self;
501     
502     /* generate "0 FAM" */
503     ts.string = "FAM";
504     ts.value  = TAG_FAM;
505     self = start_record(REC_FAM, 0, GEDCOM_MAKE_XREF_PTR(val1, xr), ts,
506                         NULL, GEDCOM_MAKE_NULL(val2));
507     
508     /* close "0 FAM" */
509     end_record(REC_FAM, self, NULL);
510   }
511 }
512
513 /********************************************************************/
514 /*  C_SUBM_COMM                                                     */
515 /********************************************************************/
516
517 int compat_check_subm_comm(const char* tag, const char* parent_tag,
518                            struct safe_buffer* b)
519 {
520   if (!strcmp(tag, "COMM") && !strcmp(parent_tag, "SUBM")) {
521     reset_buffer(b);
522     SAFE_BUF_ADDCHAR(b, '_');
523     safe_buf_append(b, tag);
524     gedcom_warning(_("Converting non-standard tag '%s' to user tag '%s'"),
525                    tag, get_buf_string(b));
526     compat_state[C_SUBM_COMM].i = 1;
527     return 1;
528   }
529   else
530     return 0;
531 }
532
533 void compat_close_subm_comm()
534 {
535   compat_state[C_SUBM_COMM].i = 0;
536 }
537
538 int compat_check_subm_comm_cont(const char* tag)
539 {
540   if (compat_state[C_SUBM_COMM].i && !strcmp(tag, "CONT")) {
541     compat_state[C_SUBM_COMM].i = 2;
542     return 1;
543   }
544   else
545     return 0;
546 }
547
548 Gedcom_ctxt compat_subm_comm_cont_start(Gedcom_ctxt parent, char* str)
549 {
550   Gedcom_ctxt self = NULL;
551   struct tag_struct ts;
552
553   if (compat_state[C_SUBM_COMM].i == 2) {
554     ts.string = "_CONT";
555     ts.value  = USERTAG;
556     self = start_element(ELT_USER, parent, 2, ts, str, &val2);
557   }
558
559   return self;
560 }
561
562 void compat_subm_comm_cont_end(Gedcom_ctxt parent, Gedcom_ctxt self)
563 {
564   if (compat_state[C_SUBM_COMM].i == 2) {
565     end_element(ELT_USER, parent, self, NULL);
566     compat_state[C_SUBM_COMM].i = 1;
567   }
568 }