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 - non-standard date formats
101 - Personal Ancestral File 2:
102 - '@' not written as '@@' in values
103 - COMM tag in submitter record
104 - double dates written as e.g. '1815/1816' instead of '1815/16'
105 - non-standard date formats
108 - '@' not written as '@@' in values
109 - CONC needs an extra space
113 - no submitter link in the header
114 - NOTE doesn't have a value
115 - NOTE.NOTE instead of NOTE.COND
116 - NOTE.CONC.SOUR instead of NOTE.SOUR
117 - non-standard tags in SOUR records
119 - Personal Ancestral File 4:
120 - '@' not written as '@@' in values
121 - SUBM.CTRY instead of SUBM.ADDR.CTRY
123 - non-standard date formats
126 int compat_matrix[] =
128 /* C_NO_SUBMITTER */ C_FTREE | C_LIFELINES | C_PAF2 | C_EASYTREE,
129 /* C_INDI_ADDR */ C_FTREE,
130 /* C_NOTE_NO_VALUE */ C_FTREE | C_EASYTREE,
131 /* C_NO_GEDC */ C_LIFELINES | C_PAF2,
132 /* C_NO_CHAR */ C_LIFELINES,
133 /* C_HEAD_TIME */ C_LIFELINES,
134 /* C_NO_DOUBLE_AT */ C_LIFELINES | C_PAF5 | C_PAF2 | C_FAMORIG
136 /* C_NO_REQUIRED_VALUES */ C_LIFELINES | C_PAF5 | C_EASYTREE,
137 /* C_551_TAGS */ C_PAF5,
138 /* C_NO_SLGC_FAMC */ C_PAF5,
139 /* C_SUBM_COMM */ C_PAF2,
140 /* C_DOUBLE_DATES_4 */ C_PAF2 | C_PAF5 | C_PAF4,
141 /* C_CONC_NEEDS_SPACE */ C_FAMORIG,
142 /* C_NO_GEDC_FORM */ C_EASYTREE,
143 /* C_NOTE_NOTE */ C_EASYTREE,
144 /* C_TAB_CHARACTER */ C_PAF5,
145 /* C_SUBM_CTRY */ C_PAF4,
146 /* C_NOTE_TOO_LONG */ C_PAF4 | C_PAF5,
147 /* C_NOTE_CONC_SOUR */ C_EASYTREE,
148 /* C_NONSTD_SOUR_TAGS */ C_EASYTREE,
149 /* C_PAF_DATES */ C_PAF2 | C_PAF4 | C_PAF5
152 union _COMPAT_STATE {
155 } compat_state[C_NR_OF_RULES];
157 /* Compatibility handling */
159 void gedcom_set_compat_handling(int enable_compat)
161 compat_enabled = enable_compat;
164 void gedcom_set_compat_options(Gedcom_compat options)
166 compat_options = options;
169 void enable_compat_msg(const char* program_name, int version)
172 gedcom_warning(_("Enabling compatibility with '%s', version %d"),
173 program_name, version);
175 gedcom_warning(_("Enabling compatibility with '%s'"),
179 int program_equal(const char* program, const char* compare)
181 return !strncmp(program, compare, strlen(compare)+1);
184 int program_equal_continued(const char* program, const char* compare)
186 size_t len = strlen(compare);
187 int result = strncmp(program, compare, len);
189 if (strlen(program) > len)
190 set_compatibility_version(program + len);
195 void set_compatibility_program(const char* program)
197 compatibility_program = 0;
198 if (compat_enabled) {
199 if (program_equal(program, "ftree")) {
200 compatibility_program = CP_FTREE;
202 else if (program_equal_continued(program, "LIFELINES")) {
203 compatibility_program = CP_LIFELINES;
205 else if (program_equal_continued(program, "PAF")) {
206 compatibility_program = CP_PAF;
208 else if (program_equal(program, "FamilyOrigins")) {
209 compatibility_program = CP_FAMORIG;
211 else if (program_equal(program, "EasyTree")) {
212 compatibility_program = CP_EASYTREE;
217 void compute_compatibility()
219 /* Reinitialize compatibility */
222 default_charset = "";
224 for (i = 0; i < C_NR_OF_RULES; i++)
225 compat_state[i].i = 0;
227 switch (compatibility_program) {
229 if (compatibility_version >= 20000 && compatibility_version < 30000) {
230 compatibility = C_PAF2;
233 if (compatibility_version >= 40000 && compatibility_version < 50000) {
234 compatibility = C_PAF4;
237 else if (compatibility_version >= 50000) {
238 compatibility = C_PAF5;
243 compatibility = data[compatibility_program].default_compat;
247 default_charset = data[compatibility_program].default_charset;
248 enable_compat_msg(data[compatibility_program].name, version);
252 void set_compatibility_version(const char* version)
254 if (compat_enabled) {
255 unsigned int major=0, minor=0, patch=0;
258 result = sscanf(version, " %u.%u.%u", &major, &minor, &patch);
260 gedcom_debug_print("Setting compat version to %u.%u.%u",
261 major, minor, patch);
262 compatibility_version = major * 10000 + minor * 100 + patch;
267 int compat_mode(Compat_rule rule)
269 return (compat_matrix[rule] & compatibility);
274 compatibility_program = 0;
278 /********************************************************************/
280 /********************************************************************/
282 void compat_generate_submitter_link(Gedcom_ctxt parent)
284 struct xref_value *xr = gedcom_parse_xref(SUBMITTER_LINK, XREF_USED,
286 struct tag_struct ts;
291 gedcom_warning(_("Adding link to submitter record with xref '%s'"),
293 self = start_element(ELT_HEAD_SUBM,
294 parent, 1, ts, SUBMITTER_LINK,
295 GEDCOM_MAKE_XREF_PTR(val1, xr));
296 end_element(ELT_HEAD_SUBM, parent, self, NULL);
297 compat_state[C_NO_SUBMITTER].i = 1;
300 void compat_generate_submitter()
302 if (compat_state[C_NO_SUBMITTER].i) {
303 struct xref_value *xr = gedcom_parse_xref(SUBMITTER_LINK, XREF_DEFINED,
305 struct tag_struct ts;
306 Gedcom_ctxt self1, self2;
308 /* first generate "0 SUBM" */
311 self1 = start_record(REC_SUBM, 0, GEDCOM_MAKE_XREF_PTR(val1, xr), ts,
312 NULL, GEDCOM_MAKE_NULL(val2));
314 /* then generate "1 NAME ..." */
317 self2 = start_element(ELT_SUBM_NAME, self1, 1, ts, DEFAULT_SUBMITTER_NAME,
318 GEDCOM_MAKE_STRING(val1, DEFAULT_SUBMITTER_NAME));
320 /* close "1 NAME ..." */
321 end_element(ELT_SUBM_NAME, self1, self2, NULL);
324 end_record(REC_SUBM, self1, NULL);
325 compat_state[C_NO_SUBMITTER].i = 0;
329 /********************************************************************/
331 /********************************************************************/
333 void compat_generate_gedcom(Gedcom_ctxt parent)
335 struct tag_struct ts;
336 Gedcom_ctxt self1, self2;
338 /* first generate "1 GEDC" */
341 self1 = start_element(ELT_HEAD_GEDC, parent, 1, ts, NULL,
342 GEDCOM_MAKE_NULL(val1));
344 /* then generate "2 VERS <DEFAULT_GEDC_VERS>" */
347 self2 = start_element(ELT_HEAD_GEDC_VERS, self1, 2, ts,
349 GEDCOM_MAKE_STRING(val1, DEFAULT_GEDCOM_VERS));
352 end_element(ELT_HEAD_GEDC_VERS, self1, self2, NULL);
354 /* then generate "2 FORM <DEFAULT_GEDCOM_FORM> */
355 compat_generate_gedcom_form(self1);
358 end_element(ELT_HEAD_GEDC, parent, self1, NULL);
361 /********************************************************************/
363 /********************************************************************/
365 void compat_generate_gedcom_form(Gedcom_ctxt parent)
367 struct tag_struct ts;
370 /* generate "2 FORM <DEFAULT_GEDCOM_FORM> */
373 self = start_element(ELT_HEAD_GEDC_FORM, parent, 2, ts,
375 GEDCOM_MAKE_STRING(val1, DEFAULT_GEDCOM_FORM));
378 end_element(ELT_HEAD_GEDC_FORM, parent, self, NULL);
382 /********************************************************************/
384 /********************************************************************/
386 int compat_generate_char(Gedcom_ctxt parent)
388 struct tag_struct ts;
392 /* first generate "1 CHAR <DEFAULT_CHAR>" */
396 /* Must strdup, because default_charset is const char */
397 charset = strdup(default_charset);
401 self1 = start_element(ELT_HEAD_CHAR, parent, 1, ts, charset,
402 GEDCOM_MAKE_STRING(val1, charset));
406 end_element(ELT_HEAD_CHAR, parent, self1, NULL);
408 if (open_conv_to_internal(default_charset) == 0)
414 /********************************************************************/
416 /********************************************************************/
418 void compat_save_head_date_context(Gedcom_ctxt parent)
420 compat_state[C_HEAD_TIME].vp = parent;
423 Gedcom_ctxt compat_generate_head_time_start(int level, struct tag_struct ts,
426 if (compat_options & COMPAT_ALLOW_OUT_OF_CONTEXT) {
427 Gedcom_ctxt parent = compat_state[C_HEAD_TIME].vp;
431 return start_element(ELT_HEAD_DATE_TIME,
432 parent, level, ts, value,
433 GEDCOM_MAKE_STRING(val1, value));
438 gedcom_warning(_("Header change time '%s' lost in the compatibility (out of context)"),
444 void compat_generate_head_time_end(Gedcom_ctxt self)
446 if (compat_options & COMPAT_ALLOW_OUT_OF_CONTEXT) {
447 Gedcom_ctxt parent = compat_state[C_HEAD_TIME].vp;
449 end_element(ELT_HEAD_DATE_TIME,
450 parent, self, GEDCOM_MAKE_NULL(val1));
454 /********************************************************************/
456 /********************************************************************/
458 void compat_save_ctry_parent_context(Gedcom_ctxt parent)
460 compat_state[C_SUBM_CTRY].vp = parent;
463 Gedcom_ctxt compat_generate_addr_ctry_start(int level, struct tag_struct ts,
466 if (compat_options & COMPAT_ALLOW_OUT_OF_CONTEXT) {
467 Gedcom_ctxt parent = compat_state[C_SUBM_CTRY].vp;
471 return start_element(ELT_SUB_ADDR_CTRY,
472 parent, level, ts, value,
473 GEDCOM_MAKE_STRING(val1, value));
478 gedcom_warning(_("Country '%s' lost in the compatibility (out of context)"), value);
483 void compat_generate_addr_ctry_end(Gedcom_ctxt self)
485 if (compat_options & COMPAT_ALLOW_OUT_OF_CONTEXT) {
486 Gedcom_ctxt parent = compat_state[C_SUBM_CTRY].vp;
488 end_element(ELT_SUB_ADDR_CTRY,
489 parent, self, GEDCOM_MAKE_NULL(val1));
493 void compat_free_ctry_parent_context()
495 compat_state[C_SUBM_CTRY].vp = NULL;
498 /********************************************************************/
500 /********************************************************************/
502 Gedcom_ctxt compat_generate_resi_start(Gedcom_ctxt parent)
505 struct tag_struct ts;
509 self = start_element(ELT_SUB_INDIV_RESI, parent, 1, ts, NULL,
510 GEDCOM_MAKE_NULL(val1));
514 void compat_generate_resi_end(Gedcom_ctxt parent, Gedcom_ctxt self)
516 end_element(ELT_SUB_INDIV_RESI, parent, self, NULL);
519 /********************************************************************/
521 /********************************************************************/
523 int is_551_tag(const char* tag)
525 if (strncmp(tag, "EMAIL", 6))
527 else if (strncmp(tag, "FONE", 5))
529 else if (strncmp(tag, "ROMN", 5))
535 int compat_check_551_tag(const char* tag, struct safe_buffer* b)
537 if (is_551_tag(tag)) {
539 SAFE_BUF_ADDCHAR(b, '_');
540 safe_buf_append(b, tag);
541 gedcom_warning(_("Converting 5.5.1 tag '%s' to standard 5.5 user tag '%s'"),
542 tag, get_buf_string(b));
549 /********************************************************************/
551 /********************************************************************/
553 void compat_generate_slgc_famc_link(Gedcom_ctxt parent)
555 struct xref_value *xr = gedcom_parse_xref(SLGC_FAMC_LINK, XREF_USED,
557 struct tag_struct ts;
562 gedcom_warning(_("Adding link to family record with xref '%s'"),
564 self = start_element(ELT_SUB_LIO_SLGC_FAMC,
565 parent, 2, ts, SLGC_FAMC_LINK,
566 GEDCOM_MAKE_XREF_PTR(val1, xr));
567 end_element(ELT_SUB_LIO_SLGC_FAMC, parent, self, NULL);
568 compat_state[C_NO_SLGC_FAMC].i++;
571 void compat_generate_slgc_famc_fam()
573 /* If bigger than 1, then the FAM record has already been generated */
574 if (compat_state[C_NO_SLGC_FAMC].i == 1) {
575 struct xref_value *xr = gedcom_parse_xref(SLGC_FAMC_LINK, XREF_DEFINED,
577 struct tag_struct ts;
580 /* generate "0 FAM" */
583 self = start_record(REC_FAM, 0, GEDCOM_MAKE_XREF_PTR(val1, xr), ts,
584 NULL, GEDCOM_MAKE_NULL(val2));
587 end_record(REC_FAM, self, NULL);
591 /********************************************************************/
593 /********************************************************************/
595 int compat_check_subm_comm(const char* tag, const char* parent_tag,
596 struct safe_buffer* b)
598 if (!strcmp(tag, "COMM") && !strcmp(parent_tag, "SUBM")) {
600 SAFE_BUF_ADDCHAR(b, '_');
601 safe_buf_append(b, tag);
602 gedcom_warning(_("Converting non-standard tag '%s' to user tag '%s'"),
603 tag, get_buf_string(b));
604 compat_state[C_SUBM_COMM].i = 1;
611 void compat_close_subm_comm()
613 compat_state[C_SUBM_COMM].i = 0;
616 int compat_check_subm_comm_cont(const char* tag)
618 if (compat_state[C_SUBM_COMM].i && !strcmp(tag, "CONT")) {
619 compat_state[C_SUBM_COMM].i = 2;
626 Gedcom_ctxt compat_subm_comm_cont_start(Gedcom_ctxt parent, char* str)
628 Gedcom_ctxt self = NULL;
629 struct tag_struct ts;
631 if (compat_state[C_SUBM_COMM].i == 2) {
634 self = start_element(ELT_USER, parent, 2, ts, str, &val2);
640 void compat_subm_comm_cont_end(Gedcom_ctxt parent, Gedcom_ctxt self)
642 if (compat_state[C_SUBM_COMM].i == 2) {
643 end_element(ELT_USER, parent, self, NULL);
644 compat_state[C_SUBM_COMM].i = 1;
648 /********************************************************************/
649 /* C_NOTE_TOO_LONG */
650 /********************************************************************/
652 char compat_prefix[MAXGEDCLINELEN];
654 int compat_long_line(int level, int tag)
656 return compat_mode(C_NOTE_TOO_LONG) && (level > 0) && (tag == TAG_NOTE);
659 char* compat_long_line_get_prefix(char* str)
661 if (str && utf8_strlen(str) > MAXGEDCLINELEN - 7) {
662 int len = MAXGEDCLINELEN - 7;
663 char* ch = nth_utf8_char(str, len - 1);
664 char* nextch = next_utf8_char(ch);
665 memset(compat_prefix, 0, MAXGEDCLINELEN);
666 while (len > 1 && (*ch == ' ' || *nextch == ' ')) {
669 ch = nth_utf8_char(str, len - 1);
672 strncpy(compat_prefix, str, len);
673 compat_state[C_NOTE_TOO_LONG].vp = (void*)nextch;
674 return compat_prefix;
677 compat_state[C_NOTE_TOO_LONG].vp = NULL;
682 void compat_long_line_finish(Gedcom_ctxt parent, int level)
684 struct tag_struct ts;
688 while (compat_state[C_NOTE_TOO_LONG].vp) {
690 char* input = (char*)compat_state[C_NOTE_TOO_LONG].vp;
691 char* output = compat_long_line_get_prefix(input);
693 ctxt = start_element(ELT_SUB_CONC, parent, level + 1, ts, output,
694 GEDCOM_MAKE_STRING(val1, output));
695 end_element(ELT_SUB_CONC, parent, ctxt, GEDCOM_MAKE_NULL(val1));
699 /********************************************************************/
700 /* C_NOTE_CONC_SOUR */
701 /********************************************************************/
703 Gedcom_ctxt compat_generate_note_sour_start(Gedcom_ctxt parent,
704 int level, struct tag_struct ts,
708 struct xref_value *xr = gedcom_parse_xref(pointer, XREF_USED, XREF_SOUR);
713 self = start_element(ELT_SUB_SOUR, parent, level-1, ts, pointer,
714 GEDCOM_MAKE_XREF_PTR(val1, xr));
716 compat_state[C_NOTE_CONC_SOUR].vp = parent;
720 void compat_generate_note_sour_end(Gedcom_ctxt self)
722 if (self != (void*) -1) {
723 end_element(ELT_SUB_SOUR, compat_state[C_NOTE_CONC_SOUR].vp,
724 self, GEDCOM_MAKE_NULL(val1));
728 /********************************************************************/
729 /* C_NONSTD_SOUR_TAGS */
730 /********************************************************************/
732 int is_nonstd_sour_tag(const char* tag)
734 if (strncmp(tag, "FILN", 5))
736 else if (strncmp(tag, "URL", 4))
738 else if (strncmp(tag, "LOCA", 5))
740 else if (strncmp(tag, "REGI", 5))
742 else if (strncmp(tag, "VOL", 4))
748 int compat_check_sour_tag(const char* tag, struct safe_buffer* b)
750 if (is_nonstd_sour_tag(tag)) {
752 SAFE_BUF_ADDCHAR(b, '_');
753 safe_buf_append(b, tag);
754 gedcom_warning(_("Converting undefined tag '%s' to user tag '%s'"),
755 tag, get_buf_string(b));
762 Gedcom_ctxt compat_generate_nonstd_sour_start(Gedcom_ctxt parent, int level,
763 struct tag_struct ts,
765 struct safe_buffer* b)
767 Gedcom_ctxt self = NULL;
769 SAFE_BUF_ADDCHAR(b, '_');
770 safe_buf_append(b, ts.string);
771 gedcom_warning(_("Converting invalidly used tag '%s' to user tag '%s'"),
772 ts.string, get_buf_string(b));
773 ts.string = get_buf_string(b);
775 self = start_element(ELT_USER, parent, level, ts, value,
776 GEDCOM_MAKE_NULL_OR_STRING(val1, value));
777 compat_state[C_NONSTD_SOUR_TAGS].i = 1;
781 void compat_generate_nonstd_sour_end(Gedcom_ctxt parent, Gedcom_ctxt self)
783 end_element(ELT_USER, parent, self, NULL);
784 compat_state[C_NONSTD_SOUR_TAGS].i = 0;
787 int compat_generate_nonstd_sour_state()
789 return compat_state[C_NONSTD_SOUR_TAGS].i;