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 void cleanup_compat_buffer();
40 struct safe_buffer compat_buffer = { NULL, 0, NULL, 0, cleanup_compat_buffer };
42 void cleanup_compat_buffer()
44 cleanup_buffer(&compat_buffer);
47 #define SUBMITTER_LINK "@__COMPAT__SUBM__@"
48 #define SLGC_FAMC_LINK "@__COMPAT__FAM_SLGC__@"
49 #define DEFAULT_SUBMITTER_NAME "Submitter"
50 #define DEFAULT_GEDCOM_VERS "5.5"
51 #define DEFAULT_GEDCOM_FORM "LINEAGE-LINKED"
56 const char* default_charset;
59 enum _COMPAT_PROGRAM {
77 struct program_data data[] = {
78 /* NULL */ { "", 0, "" },
79 /* CP_FTREE */ { "ftree", C_FTREE, "" },
80 /* CP_LIFELINES */ { "Lifelines", C_LIFELINES, "ANSI" },
81 /* CP_PAF */ { "Personal Ancestral File", C_PAF5, "" },
82 /* CP_FAMORIG */ { "Family Origins", C_FAMORIG, "" },
83 /* CP_EASYTREE */ { "EasyTree", C_EASYTREE, "" }
86 /* Incompatibility list (with GEDCOM 5.5):
89 - no submitter record, no submitter link in the header
90 - INDI.ADDR instead of INDI.RESI.ADDR
91 - NOTE doesn't have a value
94 - no submitter record, no submitter link in the header
95 - no GEDC field in the header
96 - no CHAR field in the header
97 - HEAD.TIME instead of HEAD.DATE.TIME (will be ignored here)
98 - '@' not written as '@@' in values
99 - lots of missing required values
101 - Personal Ancestral File 5:
102 - '@' not written as '@@' in values
103 - some 5.5.1 (draft) tags are used: EMAIL, FONE, ROMN
104 - no FAMC field in SLGC
105 - uses tab character (will be converted to 8 spaces here)
107 - double dates written as e.g. '1815/1816'
109 - Personal Ancestral File 2:
110 - '@' not written as '@@' in values
111 - COMM tag in submitter record
112 - double dates written as e.g. '1815/1816'
115 - '@' not written as '@@' in values
116 - CONC needs an extra space
120 - no submitter link in the header
121 - NOTE doesn't have a value
122 - NOTE.NOTE instead of NOTE.COND
123 - NOTE.CONC.SOUR instead of NOTE.SOUR
124 - non-standard tags in SOUR records
126 - Personal Ancestral File 4:
127 - '@' not written as '@@' in values
128 - SUBM.CTRY instead of SUBM.ADDR.CTRY
130 - double dates written as e.g. '1815/1816'
133 int compat_matrix[] =
135 /* C_NO_SUBMITTER */ C_FTREE | C_LIFELINES | C_PAF2 | C_EASYTREE,
136 /* C_INDI_ADDR */ C_FTREE,
137 /* C_NOTE_NO_VALUE */ C_FTREE | C_EASYTREE,
138 /* C_NO_GEDC */ C_LIFELINES | C_PAF2,
139 /* C_NO_CHAR */ C_LIFELINES,
140 /* C_HEAD_TIME */ C_LIFELINES,
141 /* C_NO_DOUBLE_AT */ C_LIFELINES | C_PAF5 | C_PAF2 | C_FAMORIG
143 /* C_NO_REQUIRED_VALUES */ C_LIFELINES | C_PAF5 | C_EASYTREE,
144 /* C_551_TAGS */ C_PAF5,
145 /* C_NO_SLGC_FAMC */ C_PAF5,
146 /* C_SUBM_COMM */ C_PAF2,
147 /* C_DOUBLE_DATES_4 */ C_PAF2 | C_PAF5 | C_PAF4,
148 /* C_CONC_NEEDS_SPACE */ C_FAMORIG,
149 /* C_NO_GEDC_FORM */ C_EASYTREE,
150 /* C_NOTE_NOTE */ C_EASYTREE,
151 /* C_TAB_CHARACTER */ C_PAF5,
152 /* C_SUBM_CTRY */ C_PAF4,
153 /* C_NOTE_TOO_LONG */ C_PAF4 | C_PAF5,
154 /* C_NOTE_CONC_SOUR */ C_EASYTREE,
155 /* C_NONSTD_SOUR_TAGS */ C_EASYTREE,
158 union _COMPAT_STATE {
161 } compat_state[C_NR_OF_RULES];
163 /* Compatibility handling */
165 void gedcom_set_compat_handling(int enable_compat)
167 compat_enabled = enable_compat;
170 void gedcom_set_compat_options(Gedcom_compat options)
172 compat_options = options;
175 void enable_compat_msg(const char* program_name, int version)
178 gedcom_warning(_("Enabling compatibility with '%s', version %d"),
179 program_name, version);
181 gedcom_warning(_("Enabling compatibility with '%s'"),
185 int program_equal(const char* program, const char* compare)
187 return !strncmp(program, compare, strlen(compare)+1);
190 int program_equal_continued(const char* program, const char* compare)
192 size_t len = strlen(compare);
193 int result = strncmp(program, compare, len);
195 if (strlen(program) > len)
196 set_compatibility_version(program + len);
201 void set_compatibility_program(const char* program)
203 compatibility_program = 0;
204 if (compat_enabled) {
205 if (program_equal(program, "ftree")) {
206 compatibility_program = CP_FTREE;
208 else if (program_equal_continued(program, "LIFELINES")) {
209 compatibility_program = CP_LIFELINES;
211 else if (program_equal_continued(program, "PAF")) {
212 compatibility_program = CP_PAF;
214 else if (program_equal(program, "FamilyOrigins")) {
215 compatibility_program = CP_FAMORIG;
217 else if (program_equal(program, "EasyTree")) {
218 compatibility_program = CP_EASYTREE;
223 void compute_compatibility()
225 /* Reinitialize compatibility */
228 default_charset = "";
230 for (i = 0; i < C_NR_OF_RULES; i++)
231 compat_state[i].i = 0;
233 switch (compatibility_program) {
235 if (compatibility_version >= 20000 && compatibility_version < 30000) {
236 compatibility = C_PAF2;
239 if (compatibility_version >= 40000 && compatibility_version < 50000) {
240 compatibility = C_PAF4;
243 else if (compatibility_version >= 50000) {
244 compatibility = C_PAF5;
249 compatibility = data[compatibility_program].default_compat;
253 default_charset = data[compatibility_program].default_charset;
254 enable_compat_msg(data[compatibility_program].name, version);
258 void set_compatibility_version(const char* version)
260 if (compat_enabled) {
261 unsigned int major=0, minor=0, patch=0;
264 result = sscanf(version, " %u.%u.%u", &major, &minor, &patch);
266 gedcom_debug_print("Setting compat version to %u.%u.%u",
267 major, minor, patch);
268 compatibility_version = major * 10000 + minor * 100 + patch;
273 int compat_mode(Compat_rule rule)
275 return (compat_matrix[rule] & compatibility);
280 compatibility_program = 0;
284 /********************************************************************/
286 /********************************************************************/
288 void compat_generate_submitter_link(Gedcom_ctxt parent)
290 struct xref_value *xr = gedcom_parse_xref(SUBMITTER_LINK, XREF_USED,
292 struct tag_struct ts;
297 gedcom_warning(_("Adding link to submitter record with xref '%s'"),
299 self = start_element(ELT_HEAD_SUBM,
300 parent, 1, ts, SUBMITTER_LINK,
301 GEDCOM_MAKE_XREF_PTR(val1, xr));
302 end_element(ELT_HEAD_SUBM, parent, self, NULL);
303 compat_state[C_NO_SUBMITTER].i = 1;
306 void compat_generate_submitter()
308 if (compat_state[C_NO_SUBMITTER].i) {
309 struct xref_value *xr = gedcom_parse_xref(SUBMITTER_LINK, XREF_DEFINED,
311 struct tag_struct ts;
312 Gedcom_ctxt self1, self2;
314 /* first generate "0 SUBM" */
317 self1 = start_record(REC_SUBM, 0, GEDCOM_MAKE_XREF_PTR(val1, xr), ts,
318 NULL, GEDCOM_MAKE_NULL(val2));
320 /* then generate "1 NAME ..." */
323 self2 = start_element(ELT_SUBM_NAME, self1, 1, ts, DEFAULT_SUBMITTER_NAME,
324 GEDCOM_MAKE_STRING(val1, DEFAULT_SUBMITTER_NAME));
326 /* close "1 NAME ..." */
327 end_element(ELT_SUBM_NAME, self1, self2, NULL);
330 end_record(REC_SUBM, self1, NULL);
331 compat_state[C_NO_SUBMITTER].i = 0;
335 /********************************************************************/
337 /********************************************************************/
339 void compat_generate_gedcom(Gedcom_ctxt parent)
341 struct tag_struct ts;
342 Gedcom_ctxt self1, self2;
344 /* first generate "1 GEDC" */
347 self1 = start_element(ELT_HEAD_GEDC, parent, 1, ts, NULL,
348 GEDCOM_MAKE_NULL(val1));
350 /* then generate "2 VERS <DEFAULT_GEDC_VERS>" */
353 self2 = start_element(ELT_HEAD_GEDC_VERS, self1, 2, ts,
355 GEDCOM_MAKE_STRING(val1, DEFAULT_GEDCOM_VERS));
358 end_element(ELT_HEAD_GEDC_VERS, self1, self2, NULL);
360 /* then generate "2 FORM <DEFAULT_GEDCOM_FORM> */
361 compat_generate_gedcom_form(self1);
364 end_element(ELT_HEAD_GEDC, parent, self1, NULL);
367 /********************************************************************/
369 /********************************************************************/
371 void compat_generate_gedcom_form(Gedcom_ctxt parent)
373 struct tag_struct ts;
376 /* generate "2 FORM <DEFAULT_GEDCOM_FORM> */
379 self = start_element(ELT_HEAD_GEDC_FORM, parent, 2, ts,
381 GEDCOM_MAKE_STRING(val1, DEFAULT_GEDCOM_FORM));
384 end_element(ELT_HEAD_GEDC_FORM, parent, self, NULL);
388 /********************************************************************/
390 /********************************************************************/
392 int compat_generate_char(Gedcom_ctxt parent)
394 struct tag_struct ts;
398 /* first generate "1 CHAR <DEFAULT_CHAR>" */
402 /* Must strdup, because default_charset is const char */
403 charset = strdup(default_charset);
407 self1 = start_element(ELT_HEAD_CHAR, parent, 1, ts, charset,
408 GEDCOM_MAKE_STRING(val1, charset));
412 end_element(ELT_HEAD_CHAR, parent, self1, NULL);
414 if (open_conv_to_internal(default_charset) == 0)
420 /********************************************************************/
422 /********************************************************************/
424 void compat_save_head_date_context(Gedcom_ctxt parent)
426 compat_state[C_HEAD_TIME].vp = parent;
429 Gedcom_ctxt compat_generate_head_time_start(int level, struct tag_struct ts,
432 if (compat_options & COMPAT_ALLOW_OUT_OF_CONTEXT) {
433 Gedcom_ctxt parent = compat_state[C_HEAD_TIME].vp;
437 return start_element(ELT_HEAD_DATE_TIME,
438 parent, level, ts, value,
439 GEDCOM_MAKE_STRING(val1, value));
444 gedcom_warning(_("Header change time '%s' lost in the compatibility (out of context)"),
450 void compat_generate_head_time_end(Gedcom_ctxt self)
452 if (compat_options & COMPAT_ALLOW_OUT_OF_CONTEXT) {
453 Gedcom_ctxt parent = compat_state[C_HEAD_TIME].vp;
455 end_element(ELT_HEAD_DATE_TIME,
456 parent, self, GEDCOM_MAKE_NULL(val1));
460 /********************************************************************/
462 /********************************************************************/
464 void compat_save_ctry_parent_context(Gedcom_ctxt parent)
466 compat_state[C_SUBM_CTRY].vp = parent;
469 Gedcom_ctxt compat_generate_addr_ctry_start(int level, struct tag_struct ts,
472 if (compat_options & COMPAT_ALLOW_OUT_OF_CONTEXT) {
473 Gedcom_ctxt parent = compat_state[C_SUBM_CTRY].vp;
477 return start_element(ELT_SUB_ADDR_CTRY,
478 parent, level, ts, value,
479 GEDCOM_MAKE_STRING(val1, value));
484 gedcom_warning(_("Country '%s' lost in the compatibility (out of context)"), value);
489 void compat_generate_addr_ctry_end(Gedcom_ctxt self)
491 if (compat_options & COMPAT_ALLOW_OUT_OF_CONTEXT) {
492 Gedcom_ctxt parent = compat_state[C_SUBM_CTRY].vp;
494 end_element(ELT_SUB_ADDR_CTRY,
495 parent, self, GEDCOM_MAKE_NULL(val1));
499 void compat_free_ctry_parent_context()
501 compat_state[C_SUBM_CTRY].vp = NULL;
504 /********************************************************************/
506 /********************************************************************/
508 Gedcom_ctxt compat_generate_resi_start(Gedcom_ctxt parent)
511 struct tag_struct ts;
515 self = start_element(ELT_SUB_INDIV_RESI, parent, 1, ts, NULL,
516 GEDCOM_MAKE_NULL(val1));
520 void compat_generate_resi_end(Gedcom_ctxt parent, Gedcom_ctxt self)
522 end_element(ELT_SUB_INDIV_RESI, parent, self, NULL);
525 /********************************************************************/
527 /********************************************************************/
529 int is_551_tag(const char* tag)
531 if (strncmp(tag, "EMAIL", 6))
533 else if (strncmp(tag, "FONE", 5))
535 else if (strncmp(tag, "ROMN", 5))
541 int compat_check_551_tag(const char* tag, struct safe_buffer* b)
543 if (is_551_tag(tag)) {
545 SAFE_BUF_ADDCHAR(b, '_');
546 safe_buf_append(b, tag);
547 gedcom_warning(_("Converting 5.5.1 tag '%s' to standard 5.5 user tag '%s'"),
548 tag, get_buf_string(b));
555 /********************************************************************/
557 /********************************************************************/
559 void compat_generate_slgc_famc_link(Gedcom_ctxt parent)
561 struct xref_value *xr = gedcom_parse_xref(SLGC_FAMC_LINK, XREF_USED,
563 struct tag_struct ts;
568 gedcom_warning(_("Adding link to family record with xref '%s'"),
570 self = start_element(ELT_SUB_LIO_SLGC_FAMC,
571 parent, 2, ts, SLGC_FAMC_LINK,
572 GEDCOM_MAKE_XREF_PTR(val1, xr));
573 end_element(ELT_SUB_LIO_SLGC_FAMC, parent, self, NULL);
574 compat_state[C_NO_SLGC_FAMC].i++;
577 void compat_generate_slgc_famc_fam()
579 /* If bigger than 1, then the FAM record has already been generated */
580 if (compat_state[C_NO_SLGC_FAMC].i == 1) {
581 struct xref_value *xr = gedcom_parse_xref(SLGC_FAMC_LINK, XREF_DEFINED,
583 struct tag_struct ts;
586 /* generate "0 FAM" */
589 self = start_record(REC_FAM, 0, GEDCOM_MAKE_XREF_PTR(val1, xr), ts,
590 NULL, GEDCOM_MAKE_NULL(val2));
593 end_record(REC_FAM, self, NULL);
597 /********************************************************************/
599 /********************************************************************/
601 int compat_check_subm_comm(const char* tag, const char* parent_tag,
602 struct safe_buffer* b)
604 if (!strcmp(tag, "COMM") && !strcmp(parent_tag, "SUBM")) {
606 SAFE_BUF_ADDCHAR(b, '_');
607 safe_buf_append(b, tag);
608 gedcom_warning(_("Converting non-standard tag '%s' to user tag '%s'"),
609 tag, get_buf_string(b));
610 compat_state[C_SUBM_COMM].i = 1;
617 void compat_close_subm_comm()
619 compat_state[C_SUBM_COMM].i = 0;
622 int compat_check_subm_comm_cont(const char* tag)
624 if (compat_state[C_SUBM_COMM].i && !strcmp(tag, "CONT")) {
625 compat_state[C_SUBM_COMM].i = 2;
632 Gedcom_ctxt compat_subm_comm_cont_start(Gedcom_ctxt parent, char* str)
634 Gedcom_ctxt self = NULL;
635 struct tag_struct ts;
637 if (compat_state[C_SUBM_COMM].i == 2) {
640 self = start_element(ELT_USER, parent, 2, ts, str, &val2);
646 void compat_subm_comm_cont_end(Gedcom_ctxt parent, Gedcom_ctxt self)
648 if (compat_state[C_SUBM_COMM].i == 2) {
649 end_element(ELT_USER, parent, self, NULL);
650 compat_state[C_SUBM_COMM].i = 1;
654 /********************************************************************/
655 /* C_DOUBLE_DATES_4 */
656 /********************************************************************/
658 void compat_date_start()
660 if (compat_mode(C_DOUBLE_DATES_4)) {
661 reset_buffer(&compat_buffer);
662 compat_state[C_DOUBLE_DATES_4].i = 0;
666 int compat_double_date_check(char* year2)
668 return (compat_mode(C_DOUBLE_DATES_4)
669 && !compat_state[C_DOUBLE_DATES_4].i
670 && strlen(year2) == 4);
673 int compat_double_date_final(struct date_value* dv, const char** curr_line)
675 char* compat_line_value = get_buf_string(&compat_buffer);
676 compat_state[C_DOUBLE_DATES_4].i = 1;
677 if (compat_line_value && compat_line_value[0]
678 && (dv->type == DV_NO_MODIFIER || dv->type == DV_ABOUT)
679 && dv->date1.day == -1
680 && dv->date1.month == -1) {
681 gedcom_warning(_("Converting '%s' to standard '%s'"),
682 *curr_line, compat_line_value);
683 *curr_line = compat_line_value;
688 int compat_date_check(struct date_value* dv, const char** curr_line)
690 if (compat_mode(C_DOUBLE_DATES_4)
691 && compat_double_date_final(dv, curr_line)) {
699 /********************************************************************/
700 /* C_NOTE_TOO_LONG */
701 /********************************************************************/
703 char compat_prefix[MAXGEDCLINELEN];
705 int compat_long_line(int level, int tag)
707 return compat_mode(C_NOTE_TOO_LONG) && (level > 0) && (tag == TAG_NOTE);
710 char* compat_long_line_get_prefix(char* str)
712 if (str && utf8_strlen(str) > MAXGEDCLINELEN - 7) {
713 int len = MAXGEDCLINELEN - 7;
714 char* ch = nth_utf8_char(str, len - 1);
715 char* nextch = next_utf8_char(ch);
716 memset(compat_prefix, 0, MAXGEDCLINELEN);
717 while (len > 1 && (*ch == ' ' || *nextch == ' ')) {
720 ch = nth_utf8_char(str, len - 1);
723 strncpy(compat_prefix, str, len);
724 compat_state[C_NOTE_TOO_LONG].vp = (void*)nextch;
725 return compat_prefix;
728 compat_state[C_NOTE_TOO_LONG].vp = NULL;
733 void compat_long_line_finish(Gedcom_ctxt parent, int level)
735 struct tag_struct ts;
739 while (compat_state[C_NOTE_TOO_LONG].vp) {
741 char* input = (char*)compat_state[C_NOTE_TOO_LONG].vp;
742 char* output = compat_long_line_get_prefix(input);
744 ctxt = start_element(ELT_SUB_CONC, parent, level + 1, ts, output,
745 GEDCOM_MAKE_STRING(val1, output));
746 end_element(ELT_SUB_CONC, parent, ctxt, GEDCOM_MAKE_NULL(val1));
750 /********************************************************************/
751 /* C_NOTE_CONC_SOUR */
752 /********************************************************************/
754 Gedcom_ctxt compat_generate_note_sour_start(Gedcom_ctxt parent,
755 int level, struct tag_struct ts,
759 struct xref_value *xr = gedcom_parse_xref(pointer, XREF_USED, XREF_SOUR);
764 self = start_element(ELT_SUB_SOUR, parent, level-1, ts, pointer,
765 GEDCOM_MAKE_XREF_PTR(val1, xr));
767 compat_state[C_NOTE_CONC_SOUR].vp = parent;
771 void compat_generate_note_sour_end(Gedcom_ctxt self)
773 if (self != (void*) -1) {
774 end_element(ELT_SUB_SOUR, compat_state[C_NOTE_CONC_SOUR].vp,
775 self, GEDCOM_MAKE_NULL(val1));
779 /********************************************************************/
780 /* C_NONSTD_SOUR_TAGS */
781 /********************************************************************/
783 int is_nonstd_sour_tag(const char* tag)
785 if (strncmp(tag, "FILN", 5))
787 else if (strncmp(tag, "URL", 4))
789 else if (strncmp(tag, "LOCA", 5))
791 else if (strncmp(tag, "REGI", 5))
793 else if (strncmp(tag, "VOL", 4))
799 int compat_check_sour_tag(const char* tag, struct safe_buffer* b)
801 if (is_nonstd_sour_tag(tag)) {
803 SAFE_BUF_ADDCHAR(b, '_');
804 safe_buf_append(b, tag);
805 gedcom_warning(_("Converting undefined tag '%s' to user tag '%s'"),
806 tag, get_buf_string(b));
813 Gedcom_ctxt compat_generate_nonstd_sour_start(Gedcom_ctxt parent, int level,
814 struct tag_struct ts,
816 struct safe_buffer* b)
818 Gedcom_ctxt self = NULL;
820 SAFE_BUF_ADDCHAR(b, '_');
821 safe_buf_append(b, ts.string);
822 gedcom_warning(_("Converting invalidly used tag '%s' to user tag '%s'"),
823 ts.string, get_buf_string(b));
824 ts.string = get_buf_string(b);
826 self = start_element(ELT_USER, parent, level, ts, value,
827 GEDCOM_MAKE_NULL_OR_STRING(val1, value));
828 compat_state[C_NONSTD_SOUR_TAGS].i = 1;
832 void compat_generate_nonstd_sour_end(Gedcom_ctxt parent, Gedcom_ctxt self)
834 end_element(ELT_USER, parent, self, NULL);
835 compat_state[C_NONSTD_SOUR_TAGS].i = 0;
838 int compat_generate_nonstd_sour_state()
840 return compat_state[C_NONSTD_SOUR_TAGS].i;