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.
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.
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.
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
25 #include "interface.h"
29 #include "gedcom_internal.h"
32 int compat_enabled = 1;
33 Gedcom_compat compat_options = 0;
34 int compatibility = 0;
35 int compatibility_program = 0;
36 int compatibility_version = 0;
37 const char* default_charset = "";
39 #define SUBMITTER_LINK "@__COMPAT__SUBM__@"
40 #define SLGC_FAMC_LINK "@__COMPAT__FAM_SLGC__@"
41 #define DEFAULT_SUBMITTER_NAME "Submitter"
42 #define DEFAULT_GEDCOM_VERS "5.5"
43 #define DEFAULT_GEDCOM_FORM "LINEAGE-LINKED"
48 const char* default_charset;
51 enum _COMPAT_PROGRAM {
69 struct program_data data[] = {
70 /* NULL */ { "", 0, "" },
71 /* CP_FTREE */ { "ftree", C_FTREE, "" },
72 /* CP_LIFELINES */ { "Lifelines", C_LIFELINES, "ANSI" },
73 /* CP_PAF */ { "Personal Ancestral File", C_PAF5, "" },
74 /* CP_FAMORIG */ { "Family Origins", C_FAMORIG, "" },
75 /* CP_EASYTREE */ { "EasyTree", C_EASYTREE, "" }
78 /* Incompatibility list (with GEDCOM 5.5):
81 - no submitter record, no submitter link in the header
82 - INDI.ADDR instead of INDI.RESI.ADDR
83 - NOTE doesn't have a value
86 - no submitter record, no submitter link in the header
87 - no GEDC field in the header
88 - no CHAR field in the header
89 - HEAD.TIME instead of HEAD.DATE.TIME (will be ignored here)
90 - '@' not written as '@@' in values
91 - lots of missing required values
93 - Personal Ancestral File 5:
94 - '@' not written as '@@' in values
95 - some 5.5.1 (draft) tags are used: EMAIL, FONE, ROMN
96 - no FAMC field in SLGC
97 - uses tab character (will be converted to 8 spaces here)
99 - Personal Ancestral File 2:
100 - '@' not written as '@@' in values
101 - COMM tag in submitter record
102 - double dates written as e.g. '1815/1816' instead of '1815/16'
105 - '@' not written as '@@' in values
106 - CONC needs an extra space
110 - no submitter link in the header
111 - NOTE doesn't have a value
112 - NOTE.NOTE instead of NOTE.COND
114 - Personal Ancestral File 4:
115 - '@' not written as '@@' in values
116 - SUBM.CTRY instead of SUBM.ADDR.CTRY
120 int compat_matrix[] =
122 /* C_NO_SUBMITTER */ C_FTREE | C_LIFELINES | C_PAF2 | C_EASYTREE,
123 /* C_INDI_ADDR */ C_FTREE,
124 /* C_NOTE_NO_VALUE */ C_FTREE | C_EASYTREE,
125 /* C_NO_GEDC */ C_LIFELINES | C_PAF2,
126 /* C_NO_CHAR */ C_LIFELINES,
127 /* C_HEAD_TIME */ C_LIFELINES,
128 /* C_NO_DOUBLE_AT */ C_LIFELINES | C_PAF5 | C_PAF2 | C_FAMORIG
130 /* C_NO_REQUIRED_VALUES */ C_LIFELINES | C_PAF5,
131 /* C_551_TAGS */ C_PAF5,
132 /* C_NO_SLGC_FAMC */ C_PAF5,
133 /* C_SUBM_COMM */ C_PAF2,
134 /* C_DOUBLE_DATES_4 */ C_PAF2 | C_PAF5 | C_PAF4,
135 /* C_CONC_NEEDS_SPACE */ C_FAMORIG,
136 /* C_NO_GEDC_FORM */ C_EASYTREE,
137 /* C_NOTE_NOTE */ C_EASYTREE,
138 /* C_TAB_CHARACTER */ C_PAF5,
139 /* C_SUBM_CTRY */ C_PAF4,
140 /* C_NOTE_TOO_LONG */ C_PAF4
143 union _COMPAT_STATE {
146 } compat_state[C_NR_OF_RULES];
148 /* Compatibility handling */
150 void gedcom_set_compat_handling(int enable_compat)
152 compat_enabled = enable_compat;
155 void gedcom_set_compat_options(Gedcom_compat options)
157 compat_options = options;
160 void enable_compat_msg(const char* program_name, int version)
163 gedcom_warning(_("Enabling compatibility with '%s', version %d"),
164 program_name, version);
166 gedcom_warning(_("Enabling compatibility with '%s'"),
170 int program_equal(const char* program, const char* compare)
172 return !strncmp(program, compare, strlen(compare)+1);
175 int program_equal_continued(const char* program, const char* compare)
177 size_t len = strlen(compare);
178 int result = strncmp(program, compare, len);
180 if (strlen(program) > len)
181 set_compatibility_version(program + len);
186 void set_compatibility_program(const char* program)
188 compatibility_program = 0;
189 if (compat_enabled) {
190 if (program_equal(program, "ftree")) {
191 compatibility_program = CP_FTREE;
193 else if (program_equal_continued(program, "LIFELINES")) {
194 compatibility_program = CP_LIFELINES;
196 else if (program_equal_continued(program, "PAF")) {
197 compatibility_program = CP_PAF;
199 else if (program_equal(program, "FamilyOrigins")) {
200 compatibility_program = CP_FAMORIG;
202 else if (program_equal(program, "EasyTree")) {
203 compatibility_program = CP_EASYTREE;
208 void compute_compatibility()
210 /* Reinitialize compatibility */
213 default_charset = "";
215 for (i = 0; i < C_NR_OF_RULES; i++)
216 compat_state[i].i = 0;
218 switch (compatibility_program) {
220 if (compatibility_version >= 20000 && compatibility_version < 30000) {
221 compatibility = C_PAF2;
224 if (compatibility_version >= 40000 && compatibility_version < 50000) {
225 compatibility = C_PAF4;
228 else if (compatibility_version >= 50000) {
229 compatibility = C_PAF5;
234 compatibility = data[compatibility_program].default_compat;
238 default_charset = data[compatibility_program].default_charset;
239 enable_compat_msg(data[compatibility_program].name, version);
243 void set_compatibility_version(const char* version)
245 if (compat_enabled) {
246 unsigned int major=0, minor=0, patch=0;
249 result = sscanf(version, " %u.%u.%u", &major, &minor, &patch);
251 gedcom_debug_print("Setting compat version to %u.%u.%u",
252 major, minor, patch);
253 compatibility_version = major * 10000 + minor * 100 + patch;
258 int compat_mode(Compat_rule rule)
260 return (compat_matrix[rule] & compatibility);
263 /********************************************************************/
265 /********************************************************************/
267 void compat_generate_submitter_link(Gedcom_ctxt parent)
269 struct xref_value *xr = gedcom_parse_xref(SUBMITTER_LINK, XREF_USED,
271 struct tag_struct ts;
276 gedcom_warning(_("Adding link to submitter record with xref '%s'"),
278 self = start_element(ELT_HEAD_SUBM,
279 parent, 1, ts, SUBMITTER_LINK,
280 GEDCOM_MAKE_XREF_PTR(val1, xr));
281 end_element(ELT_HEAD_SUBM, parent, self, NULL);
282 compat_state[C_NO_SUBMITTER].i = 1;
285 void compat_generate_submitter()
287 if (compat_state[C_NO_SUBMITTER].i) {
288 struct xref_value *xr = gedcom_parse_xref(SUBMITTER_LINK, XREF_DEFINED,
290 struct tag_struct ts;
291 Gedcom_ctxt self1, self2;
293 /* first generate "0 SUBM" */
296 self1 = start_record(REC_SUBM, 0, GEDCOM_MAKE_XREF_PTR(val1, xr), ts,
297 NULL, GEDCOM_MAKE_NULL(val2));
299 /* then generate "1 NAME ..." */
302 self2 = start_element(ELT_SUBM_NAME, self1, 1, ts, DEFAULT_SUBMITTER_NAME,
303 GEDCOM_MAKE_STRING(val1, DEFAULT_SUBMITTER_NAME));
305 /* close "1 NAME ..." */
306 end_element(ELT_SUBM_NAME, self1, self2, NULL);
309 end_record(REC_SUBM, self1, NULL);
310 compat_state[C_NO_SUBMITTER].i = 0;
314 /********************************************************************/
316 /********************************************************************/
318 void compat_generate_gedcom(Gedcom_ctxt parent)
320 struct tag_struct ts;
321 Gedcom_ctxt self1, self2;
323 /* first generate "1 GEDC" */
326 self1 = start_element(ELT_HEAD_GEDC, parent, 1, ts, NULL,
327 GEDCOM_MAKE_NULL(val1));
329 /* then generate "2 VERS <DEFAULT_GEDC_VERS>" */
332 self2 = start_element(ELT_HEAD_GEDC_VERS, self1, 2, ts,
334 GEDCOM_MAKE_STRING(val1, DEFAULT_GEDCOM_VERS));
337 end_element(ELT_HEAD_GEDC_VERS, self1, self2, NULL);
339 /* then generate "2 FORM <DEFAULT_GEDCOM_FORM> */
340 compat_generate_gedcom_form(self1);
343 end_element(ELT_HEAD_GEDC, parent, self1, NULL);
346 /********************************************************************/
348 /********************************************************************/
350 void compat_generate_gedcom_form(Gedcom_ctxt parent)
352 struct tag_struct ts;
355 /* generate "2 FORM <DEFAULT_GEDCOM_FORM> */
358 self = start_element(ELT_HEAD_GEDC_FORM, parent, 2, ts,
360 GEDCOM_MAKE_STRING(val1, DEFAULT_GEDCOM_FORM));
363 end_element(ELT_HEAD_GEDC_FORM, parent, self, NULL);
367 /********************************************************************/
369 /********************************************************************/
371 int compat_generate_char(Gedcom_ctxt parent)
373 struct tag_struct ts;
377 /* first generate "1 CHAR <DEFAULT_CHAR>" */
381 /* Must strdup, because default_charset is const char */
382 charset = strdup(default_charset);
386 self1 = start_element(ELT_HEAD_CHAR, parent, 1, ts, charset,
387 GEDCOM_MAKE_STRING(val1, charset));
391 end_element(ELT_HEAD_CHAR, parent, self1, NULL);
393 if (open_conv_to_internal(default_charset) == 0)
399 /********************************************************************/
401 /********************************************************************/
403 void compat_save_head_date_context(Gedcom_ctxt parent)
405 compat_state[C_HEAD_TIME].vp = parent;
408 Gedcom_ctxt compat_generate_head_time_start(int level, struct tag_struct ts,
411 if (compat_options & COMPAT_ALLOW_OUT_OF_CONTEXT) {
412 Gedcom_ctxt parent = compat_state[C_HEAD_TIME].vp;
416 return start_element(ELT_HEAD_DATE_TIME,
417 parent, level, ts, value,
418 GEDCOM_MAKE_STRING(val1, value));
423 gedcom_warning(_("Header change time '%s' lost in the compatibility (out of context)"),
429 void compat_generate_head_time_end(Gedcom_ctxt self)
431 if (compat_options & COMPAT_ALLOW_OUT_OF_CONTEXT) {
432 Gedcom_ctxt parent = compat_state[C_HEAD_TIME].vp;
434 end_element(ELT_HEAD_DATE_TIME,
435 parent, self, GEDCOM_MAKE_NULL(val1));
439 /********************************************************************/
441 /********************************************************************/
443 void compat_save_ctry_parent_context(Gedcom_ctxt parent)
445 compat_state[C_SUBM_CTRY].vp = parent;
448 Gedcom_ctxt compat_generate_addr_ctry_start(int level, struct tag_struct ts,
451 if (compat_options & COMPAT_ALLOW_OUT_OF_CONTEXT) {
452 Gedcom_ctxt parent = compat_state[C_SUBM_CTRY].vp;
456 return start_element(ELT_SUB_ADDR_CTRY,
457 parent, level, ts, value,
458 GEDCOM_MAKE_STRING(val1, value));
463 gedcom_warning(_("Country '%s' lost in the compatibility (out of context)"), value);
468 void compat_generate_addr_ctry_end(Gedcom_ctxt self)
470 if (compat_options & COMPAT_ALLOW_OUT_OF_CONTEXT) {
471 Gedcom_ctxt parent = compat_state[C_SUBM_CTRY].vp;
473 end_element(ELT_SUB_ADDR_CTRY,
474 parent, self, GEDCOM_MAKE_NULL(val1));
478 void compat_free_ctry_parent_context()
480 compat_state[C_SUBM_CTRY].vp = NULL;
483 /********************************************************************/
485 /********************************************************************/
487 Gedcom_ctxt compat_generate_resi_start(Gedcom_ctxt parent)
490 struct tag_struct ts;
494 self = start_element(ELT_SUB_INDIV_RESI, parent, 1, ts, NULL,
495 GEDCOM_MAKE_NULL(val1));
499 void compat_generate_resi_end(Gedcom_ctxt parent, Gedcom_ctxt self)
501 end_element(ELT_SUB_INDIV_RESI, parent, self, NULL);
504 /********************************************************************/
506 /********************************************************************/
508 int is_551_tag(const char* tag)
510 if (strncmp(tag, "EMAIL", 6))
512 else if (strncmp(tag, "FONE", 5))
514 else if (strncmp(tag, "ROMN", 5))
520 int compat_check_551_tag(const char* tag, struct safe_buffer* b)
522 if (is_551_tag(tag)) {
524 SAFE_BUF_ADDCHAR(b, '_');
525 safe_buf_append(b, tag);
526 gedcom_warning(_("Converting 5.5.1 tag '%s' to standard 5.5 user tag '%s'"),
527 tag, get_buf_string(b));
534 /********************************************************************/
536 /********************************************************************/
538 void compat_generate_slgc_famc_link(Gedcom_ctxt parent)
540 struct xref_value *xr = gedcom_parse_xref(SLGC_FAMC_LINK, XREF_USED,
542 struct tag_struct ts;
547 gedcom_warning(_("Adding link to family record with xref '%s'"),
549 self = start_element(ELT_SUB_LIO_SLGC_FAMC,
550 parent, 2, ts, SLGC_FAMC_LINK,
551 GEDCOM_MAKE_XREF_PTR(val1, xr));
552 end_element(ELT_SUB_LIO_SLGC_FAMC, parent, self, NULL);
553 compat_state[C_NO_SLGC_FAMC].i++;
556 void compat_generate_slgc_famc_fam()
558 /* If bigger than 1, then the FAM record has already been generated */
559 if (compat_state[C_NO_SLGC_FAMC].i == 1) {
560 struct xref_value *xr = gedcom_parse_xref(SLGC_FAMC_LINK, XREF_DEFINED,
562 struct tag_struct ts;
565 /* generate "0 FAM" */
568 self = start_record(REC_FAM, 0, GEDCOM_MAKE_XREF_PTR(val1, xr), ts,
569 NULL, GEDCOM_MAKE_NULL(val2));
572 end_record(REC_FAM, self, NULL);
576 /********************************************************************/
578 /********************************************************************/
580 int compat_check_subm_comm(const char* tag, const char* parent_tag,
581 struct safe_buffer* b)
583 if (!strcmp(tag, "COMM") && !strcmp(parent_tag, "SUBM")) {
585 SAFE_BUF_ADDCHAR(b, '_');
586 safe_buf_append(b, tag);
587 gedcom_warning(_("Converting non-standard tag '%s' to user tag '%s'"),
588 tag, get_buf_string(b));
589 compat_state[C_SUBM_COMM].i = 1;
596 void compat_close_subm_comm()
598 compat_state[C_SUBM_COMM].i = 0;
601 int compat_check_subm_comm_cont(const char* tag)
603 if (compat_state[C_SUBM_COMM].i && !strcmp(tag, "CONT")) {
604 compat_state[C_SUBM_COMM].i = 2;
611 Gedcom_ctxt compat_subm_comm_cont_start(Gedcom_ctxt parent, char* str)
613 Gedcom_ctxt self = NULL;
614 struct tag_struct ts;
616 if (compat_state[C_SUBM_COMM].i == 2) {
619 self = start_element(ELT_USER, parent, 2, ts, str, &val2);
625 void compat_subm_comm_cont_end(Gedcom_ctxt parent, Gedcom_ctxt self)
627 if (compat_state[C_SUBM_COMM].i == 2) {
628 end_element(ELT_USER, parent, self, NULL);
629 compat_state[C_SUBM_COMM].i = 1;
633 /********************************************************************/
634 /* C_NOTE_TOO_LONG */
635 /********************************************************************/
637 char compat_prefix[MAXGEDCLINELEN];
639 int compat_long_line(int level, int tag)
641 return compat_mode(C_NOTE_TOO_LONG) && (level > 0) && (tag == TAG_NOTE);
644 char* compat_long_line_get_prefix(char* str)
646 if (str && utf8_strlen(str) > MAXGEDCLINELEN - 7) {
647 int len = MAXGEDCLINELEN - 7;
648 char* ch = nth_utf8_char(str, len - 1);
649 char* nextch = next_utf8_char(ch);
650 memset(compat_prefix, 0, MAXGEDCLINELEN);
651 while (len > 1 && (*ch == ' ' || *nextch == ' ')) {
654 ch = nth_utf8_char(str, len - 1);
657 strncpy(compat_prefix, str, len);
658 compat_state[C_NOTE_TOO_LONG].vp = (void*)nextch;
659 return compat_prefix;
662 compat_state[C_NOTE_TOO_LONG].vp = NULL;
667 void compat_long_line_finish(Gedcom_ctxt parent, int level)
669 struct tag_struct ts;
673 while (compat_state[C_NOTE_TOO_LONG].vp) {
675 char* input = (char*)compat_state[C_NOTE_TOO_LONG].vp;
676 char* output = compat_long_line_get_prefix(input);
678 ctxt = start_element(ELT_SUB_CONC, parent, level + 1, ts, output,
679 GEDCOM_MAKE_STRING(val1, output));
680 end_element(ELT_SUB_CONC, parent, ctxt, GEDCOM_MAKE_NULL(val1));