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