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