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