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)
100 - Personal Ancestral File 2:
101 - '@' not written as '@@' in values
102 - COMM tag in submitter record
103 - double dates written as e.g. '1815/1816' instead of '1815/16'
106 - '@' not written as '@@' in values
107 - CONC needs an extra space
111 - no submitter link in the header
112 - NOTE doesn't have a value
113 - NOTE.NOTE instead of NOTE.COND
114 - NOTE.CONC.SOUR instead of NOTE.SOUR
115 - non-standard tags in SOUR records
117 - Personal Ancestral File 4:
118 - '@' not written as '@@' in values
119 - SUBM.CTRY instead of SUBM.ADDR.CTRY
123 int compat_matrix[] =
125 /* C_NO_SUBMITTER */ C_FTREE | C_LIFELINES | C_PAF2 | C_EASYTREE,
126 /* C_INDI_ADDR */ C_FTREE,
127 /* C_NOTE_NO_VALUE */ C_FTREE | C_EASYTREE,
128 /* C_NO_GEDC */ C_LIFELINES | C_PAF2,
129 /* C_NO_CHAR */ C_LIFELINES,
130 /* C_HEAD_TIME */ C_LIFELINES,
131 /* C_NO_DOUBLE_AT */ C_LIFELINES | C_PAF5 | C_PAF2 | C_FAMORIG
133 /* C_NO_REQUIRED_VALUES */ C_LIFELINES | C_PAF5 | C_EASYTREE,
134 /* C_551_TAGS */ C_PAF5,
135 /* C_NO_SLGC_FAMC */ C_PAF5,
136 /* C_SUBM_COMM */ C_PAF2,
137 /* C_DOUBLE_DATES_4 */ C_PAF2 | C_PAF5 | C_PAF4,
138 /* C_CONC_NEEDS_SPACE */ C_FAMORIG,
139 /* C_NO_GEDC_FORM */ C_EASYTREE,
140 /* C_NOTE_NOTE */ C_EASYTREE,
141 /* C_TAB_CHARACTER */ C_PAF5,
142 /* C_SUBM_CTRY */ C_PAF4,
143 /* C_NOTE_TOO_LONG */ C_PAF4 | C_PAF5,
144 /* C_NOTE_CONC_SOUR */ C_EASYTREE,
145 /* C_NONSTD_SOUR_TAGS */ C_EASYTREE
148 union _COMPAT_STATE {
151 } compat_state[C_NR_OF_RULES];
153 /* Compatibility handling */
155 void gedcom_set_compat_handling(int enable_compat)
157 compat_enabled = enable_compat;
160 void gedcom_set_compat_options(Gedcom_compat options)
162 compat_options = options;
165 void enable_compat_msg(const char* program_name, int version)
168 gedcom_warning(_("Enabling compatibility with '%s', version %d"),
169 program_name, version);
171 gedcom_warning(_("Enabling compatibility with '%s'"),
175 int program_equal(const char* program, const char* compare)
177 return !strncmp(program, compare, strlen(compare)+1);
180 int program_equal_continued(const char* program, const char* compare)
182 size_t len = strlen(compare);
183 int result = strncmp(program, compare, len);
185 if (strlen(program) > len)
186 set_compatibility_version(program + len);
191 void set_compatibility_program(const char* program)
193 compatibility_program = 0;
194 if (compat_enabled) {
195 if (program_equal(program, "ftree")) {
196 compatibility_program = CP_FTREE;
198 else if (program_equal_continued(program, "LIFELINES")) {
199 compatibility_program = CP_LIFELINES;
201 else if (program_equal_continued(program, "PAF")) {
202 compatibility_program = CP_PAF;
204 else if (program_equal(program, "FamilyOrigins")) {
205 compatibility_program = CP_FAMORIG;
207 else if (program_equal(program, "EasyTree")) {
208 compatibility_program = CP_EASYTREE;
213 void compute_compatibility()
215 /* Reinitialize compatibility */
218 default_charset = "";
220 for (i = 0; i < C_NR_OF_RULES; i++)
221 compat_state[i].i = 0;
223 switch (compatibility_program) {
225 if (compatibility_version >= 20000 && compatibility_version < 30000) {
226 compatibility = C_PAF2;
229 if (compatibility_version >= 40000 && compatibility_version < 50000) {
230 compatibility = C_PAF4;
233 else if (compatibility_version >= 50000) {
234 compatibility = C_PAF5;
239 compatibility = data[compatibility_program].default_compat;
243 default_charset = data[compatibility_program].default_charset;
244 enable_compat_msg(data[compatibility_program].name, version);
248 void set_compatibility_version(const char* version)
250 if (compat_enabled) {
251 unsigned int major=0, minor=0, patch=0;
254 result = sscanf(version, " %u.%u.%u", &major, &minor, &patch);
256 gedcom_debug_print("Setting compat version to %u.%u.%u",
257 major, minor, patch);
258 compatibility_version = major * 10000 + minor * 100 + patch;
263 int compat_mode(Compat_rule rule)
265 return (compat_matrix[rule] & compatibility);
268 /********************************************************************/
270 /********************************************************************/
272 void compat_generate_submitter_link(Gedcom_ctxt parent)
274 struct xref_value *xr = gedcom_parse_xref(SUBMITTER_LINK, XREF_USED,
276 struct tag_struct ts;
281 gedcom_warning(_("Adding link to submitter record with xref '%s'"),
283 self = start_element(ELT_HEAD_SUBM,
284 parent, 1, ts, SUBMITTER_LINK,
285 GEDCOM_MAKE_XREF_PTR(val1, xr));
286 end_element(ELT_HEAD_SUBM, parent, self, NULL);
287 compat_state[C_NO_SUBMITTER].i = 1;
290 void compat_generate_submitter()
292 if (compat_state[C_NO_SUBMITTER].i) {
293 struct xref_value *xr = gedcom_parse_xref(SUBMITTER_LINK, XREF_DEFINED,
295 struct tag_struct ts;
296 Gedcom_ctxt self1, self2;
298 /* first generate "0 SUBM" */
301 self1 = start_record(REC_SUBM, 0, GEDCOM_MAKE_XREF_PTR(val1, xr), ts,
302 NULL, GEDCOM_MAKE_NULL(val2));
304 /* then generate "1 NAME ..." */
307 self2 = start_element(ELT_SUBM_NAME, self1, 1, ts, DEFAULT_SUBMITTER_NAME,
308 GEDCOM_MAKE_STRING(val1, DEFAULT_SUBMITTER_NAME));
310 /* close "1 NAME ..." */
311 end_element(ELT_SUBM_NAME, self1, self2, NULL);
314 end_record(REC_SUBM, self1, NULL);
315 compat_state[C_NO_SUBMITTER].i = 0;
319 /********************************************************************/
321 /********************************************************************/
323 void compat_generate_gedcom(Gedcom_ctxt parent)
325 struct tag_struct ts;
326 Gedcom_ctxt self1, self2;
328 /* first generate "1 GEDC" */
331 self1 = start_element(ELT_HEAD_GEDC, parent, 1, ts, NULL,
332 GEDCOM_MAKE_NULL(val1));
334 /* then generate "2 VERS <DEFAULT_GEDC_VERS>" */
337 self2 = start_element(ELT_HEAD_GEDC_VERS, self1, 2, ts,
339 GEDCOM_MAKE_STRING(val1, DEFAULT_GEDCOM_VERS));
342 end_element(ELT_HEAD_GEDC_VERS, self1, self2, NULL);
344 /* then generate "2 FORM <DEFAULT_GEDCOM_FORM> */
345 compat_generate_gedcom_form(self1);
348 end_element(ELT_HEAD_GEDC, parent, self1, NULL);
351 /********************************************************************/
353 /********************************************************************/
355 void compat_generate_gedcom_form(Gedcom_ctxt parent)
357 struct tag_struct ts;
360 /* generate "2 FORM <DEFAULT_GEDCOM_FORM> */
363 self = start_element(ELT_HEAD_GEDC_FORM, parent, 2, ts,
365 GEDCOM_MAKE_STRING(val1, DEFAULT_GEDCOM_FORM));
368 end_element(ELT_HEAD_GEDC_FORM, parent, self, NULL);
372 /********************************************************************/
374 /********************************************************************/
376 int compat_generate_char(Gedcom_ctxt parent)
378 struct tag_struct ts;
382 /* first generate "1 CHAR <DEFAULT_CHAR>" */
386 /* Must strdup, because default_charset is const char */
387 charset = strdup(default_charset);
391 self1 = start_element(ELT_HEAD_CHAR, parent, 1, ts, charset,
392 GEDCOM_MAKE_STRING(val1, charset));
396 end_element(ELT_HEAD_CHAR, parent, self1, NULL);
398 if (open_conv_to_internal(default_charset) == 0)
404 /********************************************************************/
406 /********************************************************************/
408 void compat_save_head_date_context(Gedcom_ctxt parent)
410 compat_state[C_HEAD_TIME].vp = parent;
413 Gedcom_ctxt compat_generate_head_time_start(int level, struct tag_struct ts,
416 if (compat_options & COMPAT_ALLOW_OUT_OF_CONTEXT) {
417 Gedcom_ctxt parent = compat_state[C_HEAD_TIME].vp;
421 return start_element(ELT_HEAD_DATE_TIME,
422 parent, level, ts, value,
423 GEDCOM_MAKE_STRING(val1, value));
428 gedcom_warning(_("Header change time '%s' lost in the compatibility (out of context)"),
434 void compat_generate_head_time_end(Gedcom_ctxt self)
436 if (compat_options & COMPAT_ALLOW_OUT_OF_CONTEXT) {
437 Gedcom_ctxt parent = compat_state[C_HEAD_TIME].vp;
439 end_element(ELT_HEAD_DATE_TIME,
440 parent, self, GEDCOM_MAKE_NULL(val1));
444 /********************************************************************/
446 /********************************************************************/
448 void compat_save_ctry_parent_context(Gedcom_ctxt parent)
450 compat_state[C_SUBM_CTRY].vp = parent;
453 Gedcom_ctxt compat_generate_addr_ctry_start(int level, struct tag_struct ts,
456 if (compat_options & COMPAT_ALLOW_OUT_OF_CONTEXT) {
457 Gedcom_ctxt parent = compat_state[C_SUBM_CTRY].vp;
461 return start_element(ELT_SUB_ADDR_CTRY,
462 parent, level, ts, value,
463 GEDCOM_MAKE_STRING(val1, value));
468 gedcom_warning(_("Country '%s' lost in the compatibility (out of context)"), value);
473 void compat_generate_addr_ctry_end(Gedcom_ctxt self)
475 if (compat_options & COMPAT_ALLOW_OUT_OF_CONTEXT) {
476 Gedcom_ctxt parent = compat_state[C_SUBM_CTRY].vp;
478 end_element(ELT_SUB_ADDR_CTRY,
479 parent, self, GEDCOM_MAKE_NULL(val1));
483 void compat_free_ctry_parent_context()
485 compat_state[C_SUBM_CTRY].vp = NULL;
488 /********************************************************************/
490 /********************************************************************/
492 Gedcom_ctxt compat_generate_resi_start(Gedcom_ctxt parent)
495 struct tag_struct ts;
499 self = start_element(ELT_SUB_INDIV_RESI, parent, 1, ts, NULL,
500 GEDCOM_MAKE_NULL(val1));
504 void compat_generate_resi_end(Gedcom_ctxt parent, Gedcom_ctxt self)
506 end_element(ELT_SUB_INDIV_RESI, parent, self, NULL);
509 /********************************************************************/
511 /********************************************************************/
513 int is_551_tag(const char* tag)
515 if (strncmp(tag, "EMAIL", 6))
517 else if (strncmp(tag, "FONE", 5))
519 else if (strncmp(tag, "ROMN", 5))
525 int compat_check_551_tag(const char* tag, struct safe_buffer* b)
527 if (is_551_tag(tag)) {
529 SAFE_BUF_ADDCHAR(b, '_');
530 safe_buf_append(b, tag);
531 gedcom_warning(_("Converting 5.5.1 tag '%s' to standard 5.5 user tag '%s'"),
532 tag, get_buf_string(b));
539 /********************************************************************/
541 /********************************************************************/
543 void compat_generate_slgc_famc_link(Gedcom_ctxt parent)
545 struct xref_value *xr = gedcom_parse_xref(SLGC_FAMC_LINK, XREF_USED,
547 struct tag_struct ts;
552 gedcom_warning(_("Adding link to family record with xref '%s'"),
554 self = start_element(ELT_SUB_LIO_SLGC_FAMC,
555 parent, 2, ts, SLGC_FAMC_LINK,
556 GEDCOM_MAKE_XREF_PTR(val1, xr));
557 end_element(ELT_SUB_LIO_SLGC_FAMC, parent, self, NULL);
558 compat_state[C_NO_SLGC_FAMC].i++;
561 void compat_generate_slgc_famc_fam()
563 /* If bigger than 1, then the FAM record has already been generated */
564 if (compat_state[C_NO_SLGC_FAMC].i == 1) {
565 struct xref_value *xr = gedcom_parse_xref(SLGC_FAMC_LINK, XREF_DEFINED,
567 struct tag_struct ts;
570 /* generate "0 FAM" */
573 self = start_record(REC_FAM, 0, GEDCOM_MAKE_XREF_PTR(val1, xr), ts,
574 NULL, GEDCOM_MAKE_NULL(val2));
577 end_record(REC_FAM, self, NULL);
581 /********************************************************************/
583 /********************************************************************/
585 int compat_check_subm_comm(const char* tag, const char* parent_tag,
586 struct safe_buffer* b)
588 if (!strcmp(tag, "COMM") && !strcmp(parent_tag, "SUBM")) {
590 SAFE_BUF_ADDCHAR(b, '_');
591 safe_buf_append(b, tag);
592 gedcom_warning(_("Converting non-standard tag '%s' to user tag '%s'"),
593 tag, get_buf_string(b));
594 compat_state[C_SUBM_COMM].i = 1;
601 void compat_close_subm_comm()
603 compat_state[C_SUBM_COMM].i = 0;
606 int compat_check_subm_comm_cont(const char* tag)
608 if (compat_state[C_SUBM_COMM].i && !strcmp(tag, "CONT")) {
609 compat_state[C_SUBM_COMM].i = 2;
616 Gedcom_ctxt compat_subm_comm_cont_start(Gedcom_ctxt parent, char* str)
618 Gedcom_ctxt self = NULL;
619 struct tag_struct ts;
621 if (compat_state[C_SUBM_COMM].i == 2) {
624 self = start_element(ELT_USER, parent, 2, ts, str, &val2);
630 void compat_subm_comm_cont_end(Gedcom_ctxt parent, Gedcom_ctxt self)
632 if (compat_state[C_SUBM_COMM].i == 2) {
633 end_element(ELT_USER, parent, self, NULL);
634 compat_state[C_SUBM_COMM].i = 1;
638 /********************************************************************/
639 /* C_NOTE_TOO_LONG */
640 /********************************************************************/
642 char compat_prefix[MAXGEDCLINELEN];
644 int compat_long_line(int level, int tag)
646 return compat_mode(C_NOTE_TOO_LONG) && (level > 0) && (tag == TAG_NOTE);
649 char* compat_long_line_get_prefix(char* str)
651 if (str && utf8_strlen(str) > MAXGEDCLINELEN - 7) {
652 int len = MAXGEDCLINELEN - 7;
653 char* ch = nth_utf8_char(str, len - 1);
654 char* nextch = next_utf8_char(ch);
655 memset(compat_prefix, 0, MAXGEDCLINELEN);
656 while (len > 1 && (*ch == ' ' || *nextch == ' ')) {
659 ch = nth_utf8_char(str, len - 1);
662 strncpy(compat_prefix, str, len);
663 compat_state[C_NOTE_TOO_LONG].vp = (void*)nextch;
664 return compat_prefix;
667 compat_state[C_NOTE_TOO_LONG].vp = NULL;
672 void compat_long_line_finish(Gedcom_ctxt parent, int level)
674 struct tag_struct ts;
678 while (compat_state[C_NOTE_TOO_LONG].vp) {
680 char* input = (char*)compat_state[C_NOTE_TOO_LONG].vp;
681 char* output = compat_long_line_get_prefix(input);
683 ctxt = start_element(ELT_SUB_CONC, parent, level + 1, ts, output,
684 GEDCOM_MAKE_STRING(val1, output));
685 end_element(ELT_SUB_CONC, parent, ctxt, GEDCOM_MAKE_NULL(val1));
689 /********************************************************************/
690 /* C_NOTE_CONC_SOUR */
691 /********************************************************************/
693 Gedcom_ctxt compat_generate_note_sour_start(Gedcom_ctxt parent,
694 int level, struct tag_struct ts,
698 struct xref_value *xr = gedcom_parse_xref(pointer, XREF_USED, XREF_SOUR);
703 self = start_element(ELT_SUB_SOUR, parent, level-1, ts, pointer,
704 GEDCOM_MAKE_XREF_PTR(val1, xr));
706 compat_state[C_NOTE_CONC_SOUR].vp = parent;
710 void compat_generate_note_sour_end(Gedcom_ctxt self)
712 if (self != (void*) -1) {
713 end_element(ELT_SUB_SOUR, compat_state[C_NOTE_CONC_SOUR].vp,
714 self, GEDCOM_MAKE_NULL(val1));
718 /********************************************************************/
719 /* C_NONSTD_SOUR_TAGS */
720 /********************************************************************/
722 int is_nonstd_sour_tag(const char* tag)
724 if (strncmp(tag, "FILN", 5))
726 else if (strncmp(tag, "URL", 4))
728 else if (strncmp(tag, "LOCA", 5))
730 else if (strncmp(tag, "REGI", 5))
732 else if (strncmp(tag, "VOL", 4))
738 int compat_check_sour_tag(const char* tag, struct safe_buffer* b)
740 if (is_nonstd_sour_tag(tag)) {
742 SAFE_BUF_ADDCHAR(b, '_');
743 safe_buf_append(b, tag);
744 gedcom_warning(_("Converting undefined tag '%s' to user tag '%s'"),
745 tag, get_buf_string(b));
752 Gedcom_ctxt compat_generate_nonstd_sour_start(Gedcom_ctxt parent, int level,
753 struct tag_struct ts,
755 struct safe_buffer* b)
757 Gedcom_ctxt self = NULL;
759 SAFE_BUF_ADDCHAR(b, '_');
760 safe_buf_append(b, ts.string);
761 gedcom_warning(_("Converting invalidly used tag '%s' to user tag '%s'"),
762 ts.string, get_buf_string(b));
763 ts.string = get_buf_string(b);
765 self = start_element(ELT_USER, parent, level, ts, value,
766 GEDCOM_MAKE_NULL_OR_STRING(val1, value));
767 compat_state[C_NONSTD_SOUR_TAGS].i = 1;
771 void compat_generate_nonstd_sour_end(Gedcom_ctxt parent, Gedcom_ctxt self)
773 end_element(ELT_USER, parent, self, NULL);
774 compat_state[C_NONSTD_SOUR_TAGS].i = 0;
777 int compat_generate_nonstd_sour_state()
779 return compat_state[C_NONSTD_SOUR_TAGS].i;