c53465b609319744a7ed2ab63f4e051a7b94562d
[gedcom-parse.git] / gedcom / compat.c
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.
5
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.
10
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.
15
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
19    02111-1307 USA.  */
20
21 /* $Id$ */
22 /* $Name$ */
23
24 #include "compat.h"
25 #include "interface.h"
26 #include "encoding.h"
27 #include "xref.h"
28 #include "buffer.h"
29 #include "gedcom_internal.h"
30 #include "gedcom.h"
31
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 = "";
38
39 void cleanup_compat_buffer();
40 struct safe_buffer compat_buffer = { NULL, 0, NULL, 0, cleanup_compat_buffer };
41
42 void cleanup_compat_buffer()
43 {
44   cleanup_buffer(&compat_buffer);
45 }
46
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"
52
53 struct program_data {
54   const char* name;
55   int         default_compat;
56   const char* default_charset;
57 };
58
59 enum _COMPAT_PROGRAM {
60   CP_FTREE = 1,
61   CP_LIFELINES,
62   CP_PAF,
63   CP_FAMORIG,
64   CP_EASYTREE
65 };
66
67 enum _COMPAT {
68   C_FTREE        = 0x0001,
69   C_LIFELINES    = 0x0002,
70   C_PAF5         = 0x0004,
71   C_PAF2         = 0x0008,
72   C_FAMORIG      = 0x0010,
73   C_EASYTREE     = 0x0020,
74   C_PAF4         = 0x0040
75 };
76
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, "" }
84 };
85
86 /* Incompatibility list (with GEDCOM 5.5):
87
88     - ftree:
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
92
93     - Lifelines (3.0.2):
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
100
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)
106         - lines too long
107         - double dates written as e.g. '1815/1816'
108
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'
113
114     - Family Origins:
115         - '@' not written as '@@' in values
116         - CONC needs an extra space
117
118     - EasyTree:
119         - no GEDC.FORM field
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
125
126     - Personal Ancestral File 4:
127         - '@' not written as '@@' in values
128         - SUBM.CTRY instead of SUBM.ADDR.CTRY
129         - lines too long
130         - double dates written as e.g. '1815/1816'
131  */
132
133 int compat_matrix[] =
134 {
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
142                                | C_PAF4,
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,
156 };
157
158 union _COMPAT_STATE {
159   int i;
160   void* vp;
161 } compat_state[C_NR_OF_RULES];
162
163 /* Compatibility handling */
164
165 /** Allows to enable/disable the compatibility mode.
166
167     \param enable_compat  This argument can be:
168       - 0  Disable compatibility mode
169       - 1  Allow compatibility mode (this is the default)
170  */
171 void gedcom_set_compat_handling(int enable_compat)
172 {
173   compat_enabled = enable_compat;
174 }
175
176 /** Allows to set some options for the compatibility handling.
177
178     \param options  Can be an OR'ed combination of the options listed defined
179     by the enum \ref Gedcom_compat.
180 */
181 void gedcom_set_compat_options(Gedcom_compat options)
182 {
183   compat_options = options;
184 }
185
186 void enable_compat_msg(const char* program_name, int version)
187 {
188   if (version > 0)
189     gedcom_warning(_("Enabling compatibility with '%s', version %d"),
190                    program_name, version);
191   else
192     gedcom_warning(_("Enabling compatibility with '%s'"),
193                    program_name);
194 }
195
196 int program_equal(const char* program, const char* compare)
197 {
198   return !strncmp(program, compare, strlen(compare)+1);
199 }
200
201 int program_equal_continued(const char* program, const char* compare)
202 {
203   size_t len = strlen(compare);
204   int result = strncmp(program, compare, len);
205   if (result == 0) {
206     if (strlen(program) > len)
207       set_compatibility_version(program + len);
208   }
209   return !result;
210 }
211
212 void set_compatibility_program(const char* program)
213 {
214   compatibility_program = 0;
215   if (compat_enabled) {
216     if (program_equal(program, "ftree")) {
217       compatibility_program = CP_FTREE;
218     }
219     else if (program_equal_continued(program, "LIFELINES")) {
220       compatibility_program = CP_LIFELINES;
221     }
222     else if (program_equal_continued(program, "PAF")) {
223       compatibility_program = CP_PAF;
224     }
225     else if (program_equal(program, "FamilyOrigins")) {
226       compatibility_program = CP_FAMORIG;
227     }
228     else if (program_equal(program, "EasyTree")) {
229       compatibility_program = CP_EASYTREE;
230     }
231   }
232 }
233
234 void compute_compatibility()
235 {
236   /* Reinitialize compatibility */
237   int i;
238   int version = 0;
239   default_charset = "";
240   compatibility = 0;
241   for (i = 0; i < C_NR_OF_RULES; i++)
242     compat_state[i].i = 0;
243
244   switch (compatibility_program) {
245     case CP_PAF:
246       if (compatibility_version >= 20000 && compatibility_version < 30000) {
247         compatibility = C_PAF2;
248         version = 2;
249       }
250       if (compatibility_version >= 40000 && compatibility_version < 50000) {
251         compatibility = C_PAF4;
252         version = 4;
253       }
254       else if (compatibility_version >= 50000) {
255         compatibility = C_PAF5;
256         version = 5;
257       }
258       break;
259     default:
260       compatibility = data[compatibility_program].default_compat;
261       break;
262   }
263   if (compatibility) {
264     default_charset = data[compatibility_program].default_charset;
265     enable_compat_msg(data[compatibility_program].name, version);
266   }
267 }
268
269 void set_compatibility_version(const char* version)
270 {
271   if (compat_enabled) {
272     unsigned int major=0, minor=0, patch=0;
273     int result;
274     
275     result = sscanf(version, " %u.%u.%u", &major, &minor, &patch);
276     if (result > 0) {
277       gedcom_debug_print("Setting compat version to %u.%u.%u",
278                          major, minor, patch);
279       compatibility_version = major * 10000 + minor * 100 + patch;
280     }
281   }
282 }
283
284 int compat_mode(Compat_rule rule)
285 {
286   return (compat_matrix[rule] & compatibility);
287 }
288
289 void compat_close()
290 {
291   compatibility_program = 0;
292   compatibility = 0;
293 }
294
295 /********************************************************************/
296 /*  C_NO_SUBMITTER                                                  */
297 /********************************************************************/
298
299 void compat_generate_submitter_link(Gedcom_ctxt parent)
300 {
301   struct xref_value *xr = gedcom_parse_xref(SUBMITTER_LINK, XREF_USED,
302                                             XREF_SUBM);
303   struct tag_struct ts;
304   Gedcom_ctxt self;
305   
306   ts.string = "SUBM";
307   ts.value  = TAG_SUBM;
308   gedcom_warning(_("Adding link to submitter record with xref '%s'"),
309                  SUBMITTER_LINK);
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;
315 }
316
317 void compat_generate_submitter()
318 {
319   if (compat_state[C_NO_SUBMITTER].i) {
320     struct xref_value *xr = gedcom_parse_xref(SUBMITTER_LINK, XREF_DEFINED,
321                                               XREF_SUBM);
322     struct tag_struct ts;
323     Gedcom_ctxt self1, self2;
324     
325     /* first generate "0 SUBM" */
326     ts.string = "SUBM";
327     ts.value  = TAG_SUBM;
328     self1 = start_record(REC_SUBM, 0, GEDCOM_MAKE_XREF_PTR(val1, xr), ts,
329                          NULL, GEDCOM_MAKE_NULL(val2));
330     
331     /* then generate "1 NAME ..." */
332     ts.string = "NAME";
333     ts.value  = TAG_NAME;
334     self2 = start_element(ELT_SUBM_NAME, self1, 1, ts, DEFAULT_SUBMITTER_NAME,
335                           GEDCOM_MAKE_STRING(val1, DEFAULT_SUBMITTER_NAME));
336     
337     /* close "1 NAME ..." */
338     end_element(ELT_SUBM_NAME, self1, self2, NULL);
339     
340     /* close "0 SUBM" */
341     end_record(REC_SUBM, self1, NULL);
342     compat_state[C_NO_SUBMITTER].i = 0;
343   }
344 }
345
346 /********************************************************************/
347 /*  C_NO_GEDC                                                       */
348 /********************************************************************/
349
350 void compat_generate_gedcom(Gedcom_ctxt parent)
351 {
352   struct tag_struct ts;
353   Gedcom_ctxt self1, self2;
354   
355   /* first generate "1 GEDC" */
356   ts.string = "GEDC";
357   ts.value  = TAG_GEDC;
358   self1 = start_element(ELT_HEAD_GEDC, parent, 1, ts, NULL,
359                         GEDCOM_MAKE_NULL(val1));
360   
361   /* then generate "2 VERS <DEFAULT_GEDC_VERS>" */
362   ts.string = "VERS";
363   ts.value  = TAG_VERS;
364   self2 = start_element(ELT_HEAD_GEDC_VERS, self1, 2, ts,
365                         DEFAULT_GEDCOM_VERS,
366                         GEDCOM_MAKE_STRING(val1, DEFAULT_GEDCOM_VERS));
367   
368   /* close "2 VERS" */
369   end_element(ELT_HEAD_GEDC_VERS, self1, self2, NULL);
370   
371   /* then generate "2 FORM <DEFAULT_GEDCOM_FORM> */
372   compat_generate_gedcom_form(self1);
373   
374   /* close "1 GEDC" */
375   end_element(ELT_HEAD_GEDC, parent, self1, NULL);
376 }
377
378 /********************************************************************/
379 /*  C_NO_GEDC_FORM                                                  */
380 /********************************************************************/
381
382 void compat_generate_gedcom_form(Gedcom_ctxt parent)
383 {
384   struct tag_struct ts;
385   Gedcom_ctxt self;
386   
387   /* generate "2 FORM <DEFAULT_GEDCOM_FORM> */
388   ts.string = "FORM";
389   ts.value  = TAG_FORM;
390   self = start_element(ELT_HEAD_GEDC_FORM, parent, 2, ts,
391                        DEFAULT_GEDCOM_FORM,
392                        GEDCOM_MAKE_STRING(val1, DEFAULT_GEDCOM_FORM));
393   
394   /* close "2 FORM" */
395   end_element(ELT_HEAD_GEDC_FORM, parent, self, NULL);
396   
397 }
398   
399 /********************************************************************/
400 /*  C_NO_CHAR                                                       */
401 /********************************************************************/
402
403 int compat_generate_char(Gedcom_ctxt parent)
404 {
405   struct tag_struct ts;
406   Gedcom_ctxt self1;
407   char* charset;
408   
409   /* first generate "1 CHAR <DEFAULT_CHAR>" */
410   ts.string = "CHAR";
411   ts.value  = TAG_CHAR;
412
413   /* Must strdup, because default_charset is const char */
414   charset   = strdup(default_charset);
415   if (! charset)
416     MEMORY_ERROR;
417   else {
418     self1 = start_element(ELT_HEAD_CHAR, parent, 1, ts, charset,
419                           GEDCOM_MAKE_STRING(val1, charset));
420     free(charset);
421     
422     /* close "1 CHAR" */
423     end_element(ELT_HEAD_CHAR, parent, self1, NULL);
424   }
425   if (open_conv_to_internal(default_charset) == 0)
426     return 1;
427   else
428     return 0;
429 }
430
431 /********************************************************************/
432 /*  C_HEAD_TIME                                                     */
433 /********************************************************************/
434
435 void compat_save_head_date_context(Gedcom_ctxt parent)
436 {
437   compat_state[C_HEAD_TIME].vp = parent;
438 }
439
440 Gedcom_ctxt compat_generate_head_time_start(int level, struct tag_struct ts,
441                                             char* value)
442 {
443   if (compat_options & COMPAT_ALLOW_OUT_OF_CONTEXT) {
444     Gedcom_ctxt parent = compat_state[C_HEAD_TIME].vp;
445     if (!value)
446       value = "-";
447     if (parent)
448       return start_element(ELT_HEAD_DATE_TIME,
449                            parent, level, ts, value,
450                            GEDCOM_MAKE_STRING(val1, value));
451     else
452       return NULL;
453   }
454   else {
455     gedcom_warning(_("Header change time '%s' lost in the compatibility (out of context)"),
456                    value);
457     return NULL;
458   }
459 }
460
461 void compat_generate_head_time_end(Gedcom_ctxt self)
462 {
463   if (compat_options & COMPAT_ALLOW_OUT_OF_CONTEXT) {
464     Gedcom_ctxt parent = compat_state[C_HEAD_TIME].vp;
465     if (parent)
466       end_element(ELT_HEAD_DATE_TIME,
467                   parent, self, GEDCOM_MAKE_NULL(val1));
468   }
469 }
470
471 /********************************************************************/
472 /*  C_SUBM_CTRY                                                     */
473 /********************************************************************/
474
475 void compat_save_ctry_parent_context(Gedcom_ctxt parent)
476 {
477   compat_state[C_SUBM_CTRY].vp = parent;
478 }
479
480 Gedcom_ctxt compat_generate_addr_ctry_start(int level, struct tag_struct ts,
481                                             char* value)
482 {
483   if (compat_options & COMPAT_ALLOW_OUT_OF_CONTEXT) {
484     Gedcom_ctxt parent = compat_state[C_SUBM_CTRY].vp;
485     if (!value)
486       value = "-";
487     if (parent)
488       return start_element(ELT_SUB_ADDR_CTRY,
489                            parent, level, ts, value,
490                            GEDCOM_MAKE_STRING(val1, value));
491     else
492       return NULL;
493   }
494   else {
495     gedcom_warning(_("Country '%s' lost in the compatibility (out of context)"), value);
496     return NULL;
497   }
498 }
499
500 void compat_generate_addr_ctry_end(Gedcom_ctxt self)
501 {
502   if (compat_options & COMPAT_ALLOW_OUT_OF_CONTEXT) {
503     Gedcom_ctxt parent = compat_state[C_SUBM_CTRY].vp;
504     if (parent)
505       end_element(ELT_SUB_ADDR_CTRY,
506                   parent, self, GEDCOM_MAKE_NULL(val1));
507   }
508 }
509
510 void compat_free_ctry_parent_context()
511 {
512   compat_state[C_SUBM_CTRY].vp = NULL;
513 }
514
515 /********************************************************************/
516 /*  C_INDI_ADDR                                                     */
517 /********************************************************************/
518
519 Gedcom_ctxt compat_generate_resi_start(Gedcom_ctxt parent)
520 {
521   Gedcom_ctxt self;
522   struct tag_struct ts;
523
524   ts.string = "RESI";
525   ts.value  = TAG_RESI;
526   self = start_element(ELT_SUB_INDIV_RESI, parent, 1, ts, NULL,
527                        GEDCOM_MAKE_NULL(val1));
528   return self;
529 }
530
531 void compat_generate_resi_end(Gedcom_ctxt parent, Gedcom_ctxt self)
532 {
533   end_element(ELT_SUB_INDIV_RESI, parent, self, NULL);
534 }
535
536 /********************************************************************/
537 /*  C_551_TAGS                                                      */
538 /********************************************************************/
539
540 int is_551_tag(const char* tag)
541 {
542   if (strncmp(tag, "EMAIL", 6))
543     return 1;
544   else if (strncmp(tag, "FONE", 5))
545     return 1;
546   else if (strncmp(tag, "ROMN", 5))
547     return 1;
548   else
549     return 0;
550 }
551
552 int compat_check_551_tag(const char* tag, struct safe_buffer* b)
553 {
554   if (is_551_tag(tag)) {
555     reset_buffer(b);
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));
560     return 1;
561   }
562   else
563     return 0;
564 }
565
566 /********************************************************************/
567 /*  C_NO_SLGC_FAMC                                                  */
568 /********************************************************************/
569
570 void compat_generate_slgc_famc_link(Gedcom_ctxt parent)
571 {
572   struct xref_value *xr = gedcom_parse_xref(SLGC_FAMC_LINK, XREF_USED,
573                                             XREF_FAM);
574   struct tag_struct ts;
575   Gedcom_ctxt self;
576   
577   ts.string = "FAMC";
578   ts.value  = TAG_FAMC;
579   gedcom_warning(_("Adding link to family record with xref '%s'"),
580                  SLGC_FAMC_LINK);
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++;
586 }
587
588 void compat_generate_slgc_famc_fam()
589 {
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,
593                                               XREF_FAM);
594     struct tag_struct ts;
595     Gedcom_ctxt self;
596     
597     /* generate "0 FAM" */
598     ts.string = "FAM";
599     ts.value  = TAG_FAM;
600     self = start_record(REC_FAM, 0, GEDCOM_MAKE_XREF_PTR(val1, xr), ts,
601                         NULL, GEDCOM_MAKE_NULL(val2));
602     
603     /* close "0 FAM" */
604     end_record(REC_FAM, self, NULL);
605   }
606 }
607
608 /********************************************************************/
609 /*  C_SUBM_COMM                                                     */
610 /********************************************************************/
611
612 int compat_check_subm_comm(const char* tag, const char* parent_tag,
613                            struct safe_buffer* b)
614 {
615   if (!strcmp(tag, "COMM") && !strcmp(parent_tag, "SUBM")) {
616     reset_buffer(b);
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;
622     return 1;
623   }
624   else
625     return 0;
626 }
627
628 void compat_close_subm_comm()
629 {
630   compat_state[C_SUBM_COMM].i = 0;
631 }
632
633 int compat_check_subm_comm_cont(const char* tag)
634 {
635   if (compat_state[C_SUBM_COMM].i && !strcmp(tag, "CONT")) {
636     compat_state[C_SUBM_COMM].i = 2;
637     return 1;
638   }
639   else
640     return 0;
641 }
642
643 Gedcom_ctxt compat_subm_comm_cont_start(Gedcom_ctxt parent, char* str)
644 {
645   Gedcom_ctxt self = NULL;
646   struct tag_struct ts;
647
648   if (compat_state[C_SUBM_COMM].i == 2) {
649     ts.string = "_CONT";
650     ts.value  = USERTAG;
651     self = start_element(ELT_USER, parent, 2, ts, str, &val2);
652   }
653
654   return self;
655 }
656
657 void compat_subm_comm_cont_end(Gedcom_ctxt parent, Gedcom_ctxt self)
658 {
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;
662   }
663 }
664
665 /********************************************************************/
666 /*  C_DOUBLE_DATES_4                                                */
667 /********************************************************************/
668
669 void compat_date_start()
670 {
671   if (compat_mode(C_DOUBLE_DATES_4)) {
672     reset_buffer(&compat_buffer);
673     compat_state[C_DOUBLE_DATES_4].i = 0;
674   }
675 }
676
677 int compat_double_date_check(char* year2)
678 {
679   return (compat_mode(C_DOUBLE_DATES_4)
680           && !compat_state[C_DOUBLE_DATES_4].i
681           && strlen(year2) == 4);
682 }
683
684 int compat_double_date_final(struct date_value* dv, const char** curr_line)
685 {
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;
695   }
696   return 1;
697 }
698
699 int compat_date_check(struct date_value* dv, const char** curr_line)
700 {
701   if (compat_mode(C_DOUBLE_DATES_4)
702       && compat_double_date_final(dv, curr_line)) {
703     return 1;
704   }
705   else {
706     return 0;
707   }
708 }
709
710 /********************************************************************/
711 /*  C_NOTE_TOO_LONG                                                 */
712 /********************************************************************/
713
714 char compat_prefix[MAXGEDCLINELEN];
715
716 int compat_long_line(int level, int tag)
717 {
718   return compat_mode(C_NOTE_TOO_LONG) && (level > 0) && (tag == TAG_NOTE);
719 }
720
721 char* compat_long_line_get_prefix(char* str)
722 {
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 == ' ')) {
729       len--;
730       nextch = ch;
731       ch     = nth_utf8_char(str, len - 1);
732     }
733     len = nextch - str;
734     strncpy(compat_prefix, str, len);
735     compat_state[C_NOTE_TOO_LONG].vp = (void*)nextch;
736     return compat_prefix;
737   }
738   else {
739     compat_state[C_NOTE_TOO_LONG].vp = NULL;
740     return str;
741   }
742 }
743
744 void compat_long_line_finish(Gedcom_ctxt parent, int level)
745 {
746   struct tag_struct ts;
747   ts.string = "CONC";
748   ts.value  = TAG_CONC;
749   
750   while (compat_state[C_NOTE_TOO_LONG].vp) {
751     Gedcom_ctxt ctxt;
752     char* input  = (char*)compat_state[C_NOTE_TOO_LONG].vp;
753     char* output = compat_long_line_get_prefix(input);
754
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));
758   }
759 }
760
761 /********************************************************************/
762 /*  C_NOTE_CONC_SOUR                                                */
763 /********************************************************************/
764
765 Gedcom_ctxt compat_generate_note_sour_start(Gedcom_ctxt parent,
766                                             int level, struct tag_struct ts,
767                                             char* pointer)
768 {
769   Gedcom_ctxt self;
770   struct xref_value *xr = gedcom_parse_xref(pointer, XREF_USED, XREF_SOUR);
771   if (xr == NULL) {
772     self = (void*)-1;
773   }
774   else {
775     self = start_element(ELT_SUB_SOUR, parent, level-1, ts, pointer,
776                          GEDCOM_MAKE_XREF_PTR(val1, xr));
777   }
778   compat_state[C_NOTE_CONC_SOUR].vp = parent;
779   return self;
780 }
781
782 void compat_generate_note_sour_end(Gedcom_ctxt self)
783 {
784   if (self != (void*) -1) {
785     end_element(ELT_SUB_SOUR, compat_state[C_NOTE_CONC_SOUR].vp,
786                 self, GEDCOM_MAKE_NULL(val1));
787   }
788 }
789
790 /********************************************************************/
791 /*  C_NONSTD_SOUR_TAGS                                              */
792 /********************************************************************/
793
794 int is_nonstd_sour_tag(const char* tag)
795 {
796   if (strncmp(tag, "FILN", 5))
797     return 1;
798   else if (strncmp(tag, "URL", 4))
799     return 1;
800   else if (strncmp(tag, "LOCA", 5))
801     return 1;
802   else if (strncmp(tag, "REGI", 5))
803     return 1;
804   else if (strncmp(tag, "VOL", 4))
805     return 1;
806   else
807     return 0;
808 }
809
810 int compat_check_sour_tag(const char* tag, struct safe_buffer* b)
811 {
812   if (is_nonstd_sour_tag(tag)) {
813     reset_buffer(b);
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));
818     return 1;
819   }
820   else
821     return 0;
822 }
823
824 Gedcom_ctxt compat_generate_nonstd_sour_start(Gedcom_ctxt parent, int level,
825                                               struct tag_struct ts,
826                                               char* value,
827                                               struct safe_buffer* b)
828 {
829   Gedcom_ctxt self = NULL;
830   reset_buffer(b);
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);
836
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;
840   return self;
841 }
842
843 void compat_generate_nonstd_sour_end(Gedcom_ctxt parent, Gedcom_ctxt self)
844 {
845   end_element(ELT_USER, parent, self, NULL);
846   compat_state[C_NONSTD_SOUR_TAGS].i = 0;
847 }
848
849 int compat_generate_nonstd_sour_state()
850 {
851   return compat_state[C_NONSTD_SOUR_TAGS].i;
852 }