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);
272 /********************************************************************/
274 /********************************************************************/
276 void compat_generate_submitter_link(Gedcom_ctxt parent)
278 struct xref_value *xr = gedcom_parse_xref(SUBMITTER_LINK, XREF_USED,
280 struct tag_struct ts;
285 gedcom_warning(_("Adding link to submitter record with xref '%s'"),
287 self = start_element(ELT_HEAD_SUBM,
288 parent, 1, ts, SUBMITTER_LINK,
289 GEDCOM_MAKE_XREF_PTR(val1, xr));
290 end_element(ELT_HEAD_SUBM, parent, self, NULL);
291 compat_state[C_NO_SUBMITTER].i = 1;
294 void compat_generate_submitter()
296 if (compat_state[C_NO_SUBMITTER].i) {
297 struct xref_value *xr = gedcom_parse_xref(SUBMITTER_LINK, XREF_DEFINED,
299 struct tag_struct ts;
300 Gedcom_ctxt self1, self2;
302 /* first generate "0 SUBM" */
305 self1 = start_record(REC_SUBM, 0, GEDCOM_MAKE_XREF_PTR(val1, xr), ts,
306 NULL, GEDCOM_MAKE_NULL(val2));
308 /* then generate "1 NAME ..." */
311 self2 = start_element(ELT_SUBM_NAME, self1, 1, ts, DEFAULT_SUBMITTER_NAME,
312 GEDCOM_MAKE_STRING(val1, DEFAULT_SUBMITTER_NAME));
314 /* close "1 NAME ..." */
315 end_element(ELT_SUBM_NAME, self1, self2, NULL);
318 end_record(REC_SUBM, self1, NULL);
319 compat_state[C_NO_SUBMITTER].i = 0;
323 /********************************************************************/
325 /********************************************************************/
327 void compat_generate_gedcom(Gedcom_ctxt parent)
329 struct tag_struct ts;
330 Gedcom_ctxt self1, self2;
332 /* first generate "1 GEDC" */
335 self1 = start_element(ELT_HEAD_GEDC, parent, 1, ts, NULL,
336 GEDCOM_MAKE_NULL(val1));
338 /* then generate "2 VERS <DEFAULT_GEDC_VERS>" */
341 self2 = start_element(ELT_HEAD_GEDC_VERS, self1, 2, ts,
343 GEDCOM_MAKE_STRING(val1, DEFAULT_GEDCOM_VERS));
346 end_element(ELT_HEAD_GEDC_VERS, self1, self2, NULL);
348 /* then generate "2 FORM <DEFAULT_GEDCOM_FORM> */
349 compat_generate_gedcom_form(self1);
352 end_element(ELT_HEAD_GEDC, parent, self1, NULL);
355 /********************************************************************/
357 /********************************************************************/
359 void compat_generate_gedcom_form(Gedcom_ctxt parent)
361 struct tag_struct ts;
364 /* generate "2 FORM <DEFAULT_GEDCOM_FORM> */
367 self = start_element(ELT_HEAD_GEDC_FORM, parent, 2, ts,
369 GEDCOM_MAKE_STRING(val1, DEFAULT_GEDCOM_FORM));
372 end_element(ELT_HEAD_GEDC_FORM, parent, self, NULL);
376 /********************************************************************/
378 /********************************************************************/
380 int compat_generate_char(Gedcom_ctxt parent)
382 struct tag_struct ts;
386 /* first generate "1 CHAR <DEFAULT_CHAR>" */
390 /* Must strdup, because default_charset is const char */
391 charset = strdup(default_charset);
395 self1 = start_element(ELT_HEAD_CHAR, parent, 1, ts, charset,
396 GEDCOM_MAKE_STRING(val1, charset));
400 end_element(ELT_HEAD_CHAR, parent, self1, NULL);
402 if (open_conv_to_internal(default_charset) == 0)
408 /********************************************************************/
410 /********************************************************************/
412 void compat_save_head_date_context(Gedcom_ctxt parent)
414 compat_state[C_HEAD_TIME].vp = parent;
417 Gedcom_ctxt compat_generate_head_time_start(int level, struct tag_struct ts,
420 if (compat_options & COMPAT_ALLOW_OUT_OF_CONTEXT) {
421 Gedcom_ctxt parent = compat_state[C_HEAD_TIME].vp;
425 return start_element(ELT_HEAD_DATE_TIME,
426 parent, level, ts, value,
427 GEDCOM_MAKE_STRING(val1, value));
432 gedcom_warning(_("Header change time '%s' lost in the compatibility (out of context)"),
438 void compat_generate_head_time_end(Gedcom_ctxt self)
440 if (compat_options & COMPAT_ALLOW_OUT_OF_CONTEXT) {
441 Gedcom_ctxt parent = compat_state[C_HEAD_TIME].vp;
443 end_element(ELT_HEAD_DATE_TIME,
444 parent, self, GEDCOM_MAKE_NULL(val1));
448 /********************************************************************/
450 /********************************************************************/
452 void compat_save_ctry_parent_context(Gedcom_ctxt parent)
454 compat_state[C_SUBM_CTRY].vp = parent;
457 Gedcom_ctxt compat_generate_addr_ctry_start(int level, struct tag_struct ts,
460 if (compat_options & COMPAT_ALLOW_OUT_OF_CONTEXT) {
461 Gedcom_ctxt parent = compat_state[C_SUBM_CTRY].vp;
465 return start_element(ELT_SUB_ADDR_CTRY,
466 parent, level, ts, value,
467 GEDCOM_MAKE_STRING(val1, value));
472 gedcom_warning(_("Country '%s' lost in the compatibility (out of context)"), value);
477 void compat_generate_addr_ctry_end(Gedcom_ctxt self)
479 if (compat_options & COMPAT_ALLOW_OUT_OF_CONTEXT) {
480 Gedcom_ctxt parent = compat_state[C_SUBM_CTRY].vp;
482 end_element(ELT_SUB_ADDR_CTRY,
483 parent, self, GEDCOM_MAKE_NULL(val1));
487 void compat_free_ctry_parent_context()
489 compat_state[C_SUBM_CTRY].vp = NULL;
492 /********************************************************************/
494 /********************************************************************/
496 Gedcom_ctxt compat_generate_resi_start(Gedcom_ctxt parent)
499 struct tag_struct ts;
503 self = start_element(ELT_SUB_INDIV_RESI, parent, 1, ts, NULL,
504 GEDCOM_MAKE_NULL(val1));
508 void compat_generate_resi_end(Gedcom_ctxt parent, Gedcom_ctxt self)
510 end_element(ELT_SUB_INDIV_RESI, parent, self, NULL);
513 /********************************************************************/
515 /********************************************************************/
517 int is_551_tag(const char* tag)
519 if (strncmp(tag, "EMAIL", 6))
521 else if (strncmp(tag, "FONE", 5))
523 else if (strncmp(tag, "ROMN", 5))
529 int compat_check_551_tag(const char* tag, struct safe_buffer* b)
531 if (is_551_tag(tag)) {
533 SAFE_BUF_ADDCHAR(b, '_');
534 safe_buf_append(b, tag);
535 gedcom_warning(_("Converting 5.5.1 tag '%s' to standard 5.5 user tag '%s'"),
536 tag, get_buf_string(b));
543 /********************************************************************/
545 /********************************************************************/
547 void compat_generate_slgc_famc_link(Gedcom_ctxt parent)
549 struct xref_value *xr = gedcom_parse_xref(SLGC_FAMC_LINK, XREF_USED,
551 struct tag_struct ts;
556 gedcom_warning(_("Adding link to family record with xref '%s'"),
558 self = start_element(ELT_SUB_LIO_SLGC_FAMC,
559 parent, 2, ts, SLGC_FAMC_LINK,
560 GEDCOM_MAKE_XREF_PTR(val1, xr));
561 end_element(ELT_SUB_LIO_SLGC_FAMC, parent, self, NULL);
562 compat_state[C_NO_SLGC_FAMC].i++;
565 void compat_generate_slgc_famc_fam()
567 /* If bigger than 1, then the FAM record has already been generated */
568 if (compat_state[C_NO_SLGC_FAMC].i == 1) {
569 struct xref_value *xr = gedcom_parse_xref(SLGC_FAMC_LINK, XREF_DEFINED,
571 struct tag_struct ts;
574 /* generate "0 FAM" */
577 self = start_record(REC_FAM, 0, GEDCOM_MAKE_XREF_PTR(val1, xr), ts,
578 NULL, GEDCOM_MAKE_NULL(val2));
581 end_record(REC_FAM, self, NULL);
585 /********************************************************************/
587 /********************************************************************/
589 int compat_check_subm_comm(const char* tag, const char* parent_tag,
590 struct safe_buffer* b)
592 if (!strcmp(tag, "COMM") && !strcmp(parent_tag, "SUBM")) {
594 SAFE_BUF_ADDCHAR(b, '_');
595 safe_buf_append(b, tag);
596 gedcom_warning(_("Converting non-standard tag '%s' to user tag '%s'"),
597 tag, get_buf_string(b));
598 compat_state[C_SUBM_COMM].i = 1;
605 void compat_close_subm_comm()
607 compat_state[C_SUBM_COMM].i = 0;
610 int compat_check_subm_comm_cont(const char* tag)
612 if (compat_state[C_SUBM_COMM].i && !strcmp(tag, "CONT")) {
613 compat_state[C_SUBM_COMM].i = 2;
620 Gedcom_ctxt compat_subm_comm_cont_start(Gedcom_ctxt parent, char* str)
622 Gedcom_ctxt self = NULL;
623 struct tag_struct ts;
625 if (compat_state[C_SUBM_COMM].i == 2) {
628 self = start_element(ELT_USER, parent, 2, ts, str, &val2);
634 void compat_subm_comm_cont_end(Gedcom_ctxt parent, Gedcom_ctxt self)
636 if (compat_state[C_SUBM_COMM].i == 2) {
637 end_element(ELT_USER, parent, self, NULL);
638 compat_state[C_SUBM_COMM].i = 1;
642 /********************************************************************/
643 /* C_NOTE_TOO_LONG */
644 /********************************************************************/
646 char compat_prefix[MAXGEDCLINELEN];
648 int compat_long_line(int level, int tag)
650 return compat_mode(C_NOTE_TOO_LONG) && (level > 0) && (tag == TAG_NOTE);
653 char* compat_long_line_get_prefix(char* str)
655 if (str && utf8_strlen(str) > MAXGEDCLINELEN - 7) {
656 int len = MAXGEDCLINELEN - 7;
657 char* ch = nth_utf8_char(str, len - 1);
658 char* nextch = next_utf8_char(ch);
659 memset(compat_prefix, 0, MAXGEDCLINELEN);
660 while (len > 1 && (*ch == ' ' || *nextch == ' ')) {
663 ch = nth_utf8_char(str, len - 1);
666 strncpy(compat_prefix, str, len);
667 compat_state[C_NOTE_TOO_LONG].vp = (void*)nextch;
668 return compat_prefix;
671 compat_state[C_NOTE_TOO_LONG].vp = NULL;
676 void compat_long_line_finish(Gedcom_ctxt parent, int level)
678 struct tag_struct ts;
682 while (compat_state[C_NOTE_TOO_LONG].vp) {
684 char* input = (char*)compat_state[C_NOTE_TOO_LONG].vp;
685 char* output = compat_long_line_get_prefix(input);
687 ctxt = start_element(ELT_SUB_CONC, parent, level + 1, ts, output,
688 GEDCOM_MAKE_STRING(val1, output));
689 end_element(ELT_SUB_CONC, parent, ctxt, GEDCOM_MAKE_NULL(val1));
693 /********************************************************************/
694 /* C_NOTE_CONC_SOUR */
695 /********************************************************************/
697 Gedcom_ctxt compat_generate_note_sour_start(Gedcom_ctxt parent,
698 int level, struct tag_struct ts,
702 struct xref_value *xr = gedcom_parse_xref(pointer, XREF_USED, XREF_SOUR);
707 self = start_element(ELT_SUB_SOUR, parent, level-1, ts, pointer,
708 GEDCOM_MAKE_XREF_PTR(val1, xr));
710 compat_state[C_NOTE_CONC_SOUR].vp = parent;
714 void compat_generate_note_sour_end(Gedcom_ctxt self)
716 if (self != (void*) -1) {
717 end_element(ELT_SUB_SOUR, compat_state[C_NOTE_CONC_SOUR].vp,
718 self, GEDCOM_MAKE_NULL(val1));
722 /********************************************************************/
723 /* C_NONSTD_SOUR_TAGS */
724 /********************************************************************/
726 int is_nonstd_sour_tag(const char* tag)
728 if (strncmp(tag, "FILN", 5))
730 else if (strncmp(tag, "URL", 4))
732 else if (strncmp(tag, "LOCA", 5))
734 else if (strncmp(tag, "REGI", 5))
736 else if (strncmp(tag, "VOL", 4))
742 int compat_check_sour_tag(const char* tag, struct safe_buffer* b)
744 if (is_nonstd_sour_tag(tag)) {
746 SAFE_BUF_ADDCHAR(b, '_');
747 safe_buf_append(b, tag);
748 gedcom_warning(_("Converting undefined tag '%s' to user tag '%s'"),
749 tag, get_buf_string(b));
756 Gedcom_ctxt compat_generate_nonstd_sour_start(Gedcom_ctxt parent, int level,
757 struct tag_struct ts,
759 struct safe_buffer* b)
761 Gedcom_ctxt self = NULL;
763 SAFE_BUF_ADDCHAR(b, '_');
764 safe_buf_append(b, ts.string);
765 gedcom_warning(_("Converting invalidly used tag '%s' to user tag '%s'"),
766 ts.string, get_buf_string(b));
767 ts.string = get_buf_string(b);
769 self = start_element(ELT_USER, parent, level, ts, value,
770 GEDCOM_MAKE_NULL_OR_STRING(val1, value));
771 compat_state[C_NONSTD_SOUR_TAGS].i = 1;
775 void compat_generate_nonstd_sour_end(Gedcom_ctxt parent, Gedcom_ctxt self)
777 end_element(ELT_USER, parent, self, NULL);
778 compat_state[C_NONSTD_SOUR_TAGS].i = 0;
781 int compat_generate_nonstd_sour_state()
783 return compat_state[C_NONSTD_SOUR_TAGS].i;