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 /** Allows to enable/disable the compatibility mode.
167 \param enable_compat This argument can be:
168 - 0 Disable compatibility mode
169 - 1 Allow compatibility mode (this is the default)
171 void gedcom_set_compat_handling(int enable_compat)
173 compat_enabled = enable_compat;
176 /** Allows to set some options for the compatibility handling.
178 \param options Can be an OR'ed combination of the options listed defined
179 by the enum \ref Gedcom_compat.
181 void gedcom_set_compat_options(Gedcom_compat options)
183 compat_options = options;
186 void enable_compat_msg(const char* program_name, int version)
189 gedcom_warning(_("Enabling compatibility with '%s', version %d"),
190 program_name, version);
192 gedcom_warning(_("Enabling compatibility with '%s'"),
196 int program_equal(const char* program, const char* compare)
198 return !strncmp(program, compare, strlen(compare)+1);
201 int program_equal_continued(const char* program, const char* compare)
203 size_t len = strlen(compare);
204 int result = strncmp(program, compare, len);
206 if (strlen(program) > len)
207 set_compatibility_version(program + len);
212 void set_compatibility_program(const char* program)
214 compatibility_program = 0;
215 if (compat_enabled) {
216 if (program_equal(program, "ftree")) {
217 compatibility_program = CP_FTREE;
219 else if (program_equal_continued(program, "LIFELINES")) {
220 compatibility_program = CP_LIFELINES;
222 else if (program_equal_continued(program, "PAF")) {
223 compatibility_program = CP_PAF;
225 else if (program_equal(program, "FamilyOrigins")) {
226 compatibility_program = CP_FAMORIG;
228 else if (program_equal(program, "EasyTree")) {
229 compatibility_program = CP_EASYTREE;
234 void compute_compatibility()
236 /* Reinitialize compatibility */
239 default_charset = "";
241 for (i = 0; i < C_NR_OF_RULES; i++)
242 compat_state[i].i = 0;
244 switch (compatibility_program) {
246 if (compatibility_version >= 20000 && compatibility_version < 30000) {
247 compatibility = C_PAF2;
250 if (compatibility_version >= 40000 && compatibility_version < 50000) {
251 compatibility = C_PAF4;
254 else if (compatibility_version >= 50000) {
255 compatibility = C_PAF5;
260 compatibility = data[compatibility_program].default_compat;
264 default_charset = data[compatibility_program].default_charset;
265 enable_compat_msg(data[compatibility_program].name, version);
269 void set_compatibility_version(const char* version)
271 if (compat_enabled) {
272 unsigned int major=0, minor=0, patch=0;
275 result = sscanf(version, " %u.%u.%u", &major, &minor, &patch);
277 gedcom_debug_print("Setting compat version to %u.%u.%u",
278 major, minor, patch);
279 compatibility_version = major * 10000 + minor * 100 + patch;
284 int compat_mode(Compat_rule rule)
286 return (compat_matrix[rule] & compatibility);
291 compatibility_program = 0;
295 /********************************************************************/
297 /********************************************************************/
299 void compat_generate_submitter_link(Gedcom_ctxt parent)
301 struct xref_value *xr = gedcom_parse_xref(SUBMITTER_LINK, XREF_USED,
303 struct tag_struct ts;
308 gedcom_warning(_("Adding link to submitter record with xref '%s'"),
310 self = start_element(ELT_HEAD_SUBM,
311 parent, 1, ts, SUBMITTER_LINK,
312 GEDCOM_MAKE_XREF_PTR(val1, xr));
313 end_element(ELT_HEAD_SUBM, parent, self, NULL);
314 compat_state[C_NO_SUBMITTER].i = 1;
317 void compat_generate_submitter()
319 if (compat_state[C_NO_SUBMITTER].i) {
320 struct xref_value *xr = gedcom_parse_xref(SUBMITTER_LINK, XREF_DEFINED,
322 struct tag_struct ts;
323 Gedcom_ctxt self1, self2;
325 /* first generate "0 SUBM" */
328 self1 = start_record(REC_SUBM, 0, GEDCOM_MAKE_XREF_PTR(val1, xr), ts,
329 NULL, GEDCOM_MAKE_NULL(val2));
331 /* then generate "1 NAME ..." */
334 self2 = start_element(ELT_SUBM_NAME, self1, 1, ts, DEFAULT_SUBMITTER_NAME,
335 GEDCOM_MAKE_STRING(val1, DEFAULT_SUBMITTER_NAME));
337 /* close "1 NAME ..." */
338 end_element(ELT_SUBM_NAME, self1, self2, NULL);
341 end_record(REC_SUBM, self1, NULL);
342 compat_state[C_NO_SUBMITTER].i = 0;
346 /********************************************************************/
348 /********************************************************************/
350 void compat_generate_gedcom(Gedcom_ctxt parent)
352 struct tag_struct ts;
353 Gedcom_ctxt self1, self2;
355 /* first generate "1 GEDC" */
358 self1 = start_element(ELT_HEAD_GEDC, parent, 1, ts, NULL,
359 GEDCOM_MAKE_NULL(val1));
361 /* then generate "2 VERS <DEFAULT_GEDC_VERS>" */
364 self2 = start_element(ELT_HEAD_GEDC_VERS, self1, 2, ts,
366 GEDCOM_MAKE_STRING(val1, DEFAULT_GEDCOM_VERS));
369 end_element(ELT_HEAD_GEDC_VERS, self1, self2, NULL);
371 /* then generate "2 FORM <DEFAULT_GEDCOM_FORM> */
372 compat_generate_gedcom_form(self1);
375 end_element(ELT_HEAD_GEDC, parent, self1, NULL);
378 /********************************************************************/
380 /********************************************************************/
382 void compat_generate_gedcom_form(Gedcom_ctxt parent)
384 struct tag_struct ts;
387 /* generate "2 FORM <DEFAULT_GEDCOM_FORM> */
390 self = start_element(ELT_HEAD_GEDC_FORM, parent, 2, ts,
392 GEDCOM_MAKE_STRING(val1, DEFAULT_GEDCOM_FORM));
395 end_element(ELT_HEAD_GEDC_FORM, parent, self, NULL);
399 /********************************************************************/
401 /********************************************************************/
403 int compat_generate_char(Gedcom_ctxt parent)
405 struct tag_struct ts;
409 /* first generate "1 CHAR <DEFAULT_CHAR>" */
413 /* Must strdup, because default_charset is const char */
414 charset = strdup(default_charset);
418 self1 = start_element(ELT_HEAD_CHAR, parent, 1, ts, charset,
419 GEDCOM_MAKE_STRING(val1, charset));
423 end_element(ELT_HEAD_CHAR, parent, self1, NULL);
425 if (open_conv_to_internal(default_charset) == 0)
431 /********************************************************************/
433 /********************************************************************/
435 void compat_save_head_date_context(Gedcom_ctxt parent)
437 compat_state[C_HEAD_TIME].vp = parent;
440 Gedcom_ctxt compat_generate_head_time_start(int level, struct tag_struct ts,
443 if (compat_options & COMPAT_ALLOW_OUT_OF_CONTEXT) {
444 Gedcom_ctxt parent = compat_state[C_HEAD_TIME].vp;
448 return start_element(ELT_HEAD_DATE_TIME,
449 parent, level, ts, value,
450 GEDCOM_MAKE_STRING(val1, value));
455 gedcom_warning(_("Header change time '%s' lost in the compatibility (out of context)"),
461 void compat_generate_head_time_end(Gedcom_ctxt self)
463 if (compat_options & COMPAT_ALLOW_OUT_OF_CONTEXT) {
464 Gedcom_ctxt parent = compat_state[C_HEAD_TIME].vp;
466 end_element(ELT_HEAD_DATE_TIME,
467 parent, self, GEDCOM_MAKE_NULL(val1));
471 /********************************************************************/
473 /********************************************************************/
475 void compat_save_ctry_parent_context(Gedcom_ctxt parent)
477 compat_state[C_SUBM_CTRY].vp = parent;
480 Gedcom_ctxt compat_generate_addr_ctry_start(int level, struct tag_struct ts,
483 if (compat_options & COMPAT_ALLOW_OUT_OF_CONTEXT) {
484 Gedcom_ctxt parent = compat_state[C_SUBM_CTRY].vp;
488 return start_element(ELT_SUB_ADDR_CTRY,
489 parent, level, ts, value,
490 GEDCOM_MAKE_STRING(val1, value));
495 gedcom_warning(_("Country '%s' lost in the compatibility (out of context)"), value);
500 void compat_generate_addr_ctry_end(Gedcom_ctxt self)
502 if (compat_options & COMPAT_ALLOW_OUT_OF_CONTEXT) {
503 Gedcom_ctxt parent = compat_state[C_SUBM_CTRY].vp;
505 end_element(ELT_SUB_ADDR_CTRY,
506 parent, self, GEDCOM_MAKE_NULL(val1));
510 void compat_free_ctry_parent_context()
512 compat_state[C_SUBM_CTRY].vp = NULL;
515 /********************************************************************/
517 /********************************************************************/
519 Gedcom_ctxt compat_generate_resi_start(Gedcom_ctxt parent)
522 struct tag_struct ts;
526 self = start_element(ELT_SUB_INDIV_RESI, parent, 1, ts, NULL,
527 GEDCOM_MAKE_NULL(val1));
531 void compat_generate_resi_end(Gedcom_ctxt parent, Gedcom_ctxt self)
533 end_element(ELT_SUB_INDIV_RESI, parent, self, NULL);
536 /********************************************************************/
538 /********************************************************************/
540 int is_551_tag(const char* tag)
542 if (strncmp(tag, "EMAIL", 6))
544 else if (strncmp(tag, "FONE", 5))
546 else if (strncmp(tag, "ROMN", 5))
552 int compat_check_551_tag(const char* tag, struct safe_buffer* b)
554 if (is_551_tag(tag)) {
556 SAFE_BUF_ADDCHAR(b, '_');
557 safe_buf_append(b, tag);
558 gedcom_warning(_("Converting 5.5.1 tag '%s' to standard 5.5 user tag '%s'"),
559 tag, get_buf_string(b));
566 /********************************************************************/
568 /********************************************************************/
570 void compat_generate_slgc_famc_link(Gedcom_ctxt parent)
572 struct xref_value *xr = gedcom_parse_xref(SLGC_FAMC_LINK, XREF_USED,
574 struct tag_struct ts;
579 gedcom_warning(_("Adding link to family record with xref '%s'"),
581 self = start_element(ELT_SUB_LIO_SLGC_FAMC,
582 parent, 2, ts, SLGC_FAMC_LINK,
583 GEDCOM_MAKE_XREF_PTR(val1, xr));
584 end_element(ELT_SUB_LIO_SLGC_FAMC, parent, self, NULL);
585 compat_state[C_NO_SLGC_FAMC].i++;
588 void compat_generate_slgc_famc_fam()
590 /* If bigger than 1, then the FAM record has already been generated */
591 if (compat_state[C_NO_SLGC_FAMC].i == 1) {
592 struct xref_value *xr = gedcom_parse_xref(SLGC_FAMC_LINK, XREF_DEFINED,
594 struct tag_struct ts;
597 /* generate "0 FAM" */
600 self = start_record(REC_FAM, 0, GEDCOM_MAKE_XREF_PTR(val1, xr), ts,
601 NULL, GEDCOM_MAKE_NULL(val2));
604 end_record(REC_FAM, self, NULL);
608 /********************************************************************/
610 /********************************************************************/
612 int compat_check_subm_comm(const char* tag, const char* parent_tag,
613 struct safe_buffer* b)
615 if (!strcmp(tag, "COMM") && !strcmp(parent_tag, "SUBM")) {
617 SAFE_BUF_ADDCHAR(b, '_');
618 safe_buf_append(b, tag);
619 gedcom_warning(_("Converting non-standard tag '%s' to user tag '%s'"),
620 tag, get_buf_string(b));
621 compat_state[C_SUBM_COMM].i = 1;
628 void compat_close_subm_comm()
630 compat_state[C_SUBM_COMM].i = 0;
633 int compat_check_subm_comm_cont(const char* tag)
635 if (compat_state[C_SUBM_COMM].i && !strcmp(tag, "CONT")) {
636 compat_state[C_SUBM_COMM].i = 2;
643 Gedcom_ctxt compat_subm_comm_cont_start(Gedcom_ctxt parent, char* str)
645 Gedcom_ctxt self = NULL;
646 struct tag_struct ts;
648 if (compat_state[C_SUBM_COMM].i == 2) {
651 self = start_element(ELT_USER, parent, 2, ts, str, &val2);
657 void compat_subm_comm_cont_end(Gedcom_ctxt parent, Gedcom_ctxt self)
659 if (compat_state[C_SUBM_COMM].i == 2) {
660 end_element(ELT_USER, parent, self, NULL);
661 compat_state[C_SUBM_COMM].i = 1;
665 /********************************************************************/
666 /* C_DOUBLE_DATES_4 */
667 /********************************************************************/
669 void compat_date_start()
671 if (compat_mode(C_DOUBLE_DATES_4)) {
672 reset_buffer(&compat_buffer);
673 compat_state[C_DOUBLE_DATES_4].i = 0;
677 int compat_double_date_check(char* year2)
679 return (compat_mode(C_DOUBLE_DATES_4)
680 && !compat_state[C_DOUBLE_DATES_4].i
681 && strlen(year2) == 4);
684 int compat_double_date_final(struct date_value* dv, const char** curr_line)
686 char* compat_line_value = get_buf_string(&compat_buffer);
687 compat_state[C_DOUBLE_DATES_4].i = 1;
688 if (compat_line_value && compat_line_value[0]
689 && (dv->type == DV_NO_MODIFIER || dv->type == DV_ABOUT)
690 && dv->date1.day == -1
691 && dv->date1.month == -1) {
692 gedcom_warning(_("Converting '%s' to standard '%s'"),
693 *curr_line, compat_line_value);
694 *curr_line = compat_line_value;
699 int compat_date_check(struct date_value* dv, const char** curr_line)
701 if (compat_mode(C_DOUBLE_DATES_4)
702 && compat_double_date_final(dv, curr_line)) {
710 /********************************************************************/
711 /* C_NOTE_TOO_LONG */
712 /********************************************************************/
714 char compat_prefix[MAXGEDCLINELEN];
716 int compat_long_line(int level, int tag)
718 return compat_mode(C_NOTE_TOO_LONG) && (level > 0) && (tag == TAG_NOTE);
721 char* compat_long_line_get_prefix(char* str)
723 if (str && utf8_strlen(str) > MAXGEDCLINELEN - 7) {
724 int len = MAXGEDCLINELEN - 7;
725 char* ch = nth_utf8_char(str, len - 1);
726 char* nextch = next_utf8_char(ch);
727 memset(compat_prefix, 0, MAXGEDCLINELEN);
728 while (len > 1 && (*ch == ' ' || *nextch == ' ')) {
731 ch = nth_utf8_char(str, len - 1);
734 strncpy(compat_prefix, str, len);
735 compat_state[C_NOTE_TOO_LONG].vp = (void*)nextch;
736 return compat_prefix;
739 compat_state[C_NOTE_TOO_LONG].vp = NULL;
744 void compat_long_line_finish(Gedcom_ctxt parent, int level)
746 struct tag_struct ts;
750 while (compat_state[C_NOTE_TOO_LONG].vp) {
752 char* input = (char*)compat_state[C_NOTE_TOO_LONG].vp;
753 char* output = compat_long_line_get_prefix(input);
755 ctxt = start_element(ELT_SUB_CONC, parent, level + 1, ts, output,
756 GEDCOM_MAKE_STRING(val1, output));
757 end_element(ELT_SUB_CONC, parent, ctxt, GEDCOM_MAKE_NULL(val1));
761 /********************************************************************/
762 /* C_NOTE_CONC_SOUR */
763 /********************************************************************/
765 Gedcom_ctxt compat_generate_note_sour_start(Gedcom_ctxt parent,
766 int level, struct tag_struct ts,
770 struct xref_value *xr = gedcom_parse_xref(pointer, XREF_USED, XREF_SOUR);
775 self = start_element(ELT_SUB_SOUR, parent, level-1, ts, pointer,
776 GEDCOM_MAKE_XREF_PTR(val1, xr));
778 compat_state[C_NOTE_CONC_SOUR].vp = parent;
782 void compat_generate_note_sour_end(Gedcom_ctxt self)
784 if (self != (void*) -1) {
785 end_element(ELT_SUB_SOUR, compat_state[C_NOTE_CONC_SOUR].vp,
786 self, GEDCOM_MAKE_NULL(val1));
790 /********************************************************************/
791 /* C_NONSTD_SOUR_TAGS */
792 /********************************************************************/
794 int is_nonstd_sour_tag(const char* tag)
796 if (strncmp(tag, "FILN", 5))
798 else if (strncmp(tag, "URL", 4))
800 else if (strncmp(tag, "LOCA", 5))
802 else if (strncmp(tag, "REGI", 5))
804 else if (strncmp(tag, "VOL", 4))
810 int compat_check_sour_tag(const char* tag, struct safe_buffer* b)
812 if (is_nonstd_sour_tag(tag)) {
814 SAFE_BUF_ADDCHAR(b, '_');
815 safe_buf_append(b, tag);
816 gedcom_warning(_("Converting undefined tag '%s' to user tag '%s'"),
817 tag, get_buf_string(b));
824 Gedcom_ctxt compat_generate_nonstd_sour_start(Gedcom_ctxt parent, int level,
825 struct tag_struct ts,
827 struct safe_buffer* b)
829 Gedcom_ctxt self = NULL;
831 SAFE_BUF_ADDCHAR(b, '_');
832 safe_buf_append(b, ts.string);
833 gedcom_warning(_("Converting invalidly used tag '%s' to user tag '%s'"),
834 ts.string, get_buf_string(b));
835 ts.string = get_buf_string(b);
837 self = start_element(ELT_USER, parent, level, ts, value,
838 GEDCOM_MAKE_NULL_OR_STRING(val1, value));
839 compat_state[C_NONSTD_SOUR_TAGS].i = 1;
843 void compat_generate_nonstd_sour_end(Gedcom_ctxt parent, Gedcom_ctxt self)
845 end_element(ELT_USER, parent, self, NULL);
846 compat_state[C_NONSTD_SOUR_TAGS].i = 0;
849 int compat_generate_nonstd_sour_state()
851 return compat_state[C_NONSTD_SOUR_TAGS].i;