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