Keep parsed values in date if parse was OK, but no valid date.
[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 #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"
44
45 struct program_data {
46   const char* name;
47   int         default_compat;
48   const char* default_charset;
49 };
50
51 enum _COMPAT_PROGRAM {
52   CP_FTREE = 1,
53   CP_LIFELINES,
54   CP_PAF,
55   CP_FAMORIG,
56   CP_EASYTREE
57 };
58
59 enum _COMPAT {
60   C_FTREE        = 0x0001,
61   C_LIFELINES    = 0x0002,
62   C_PAF5         = 0x0004,
63   C_PAF2         = 0x0008,
64   C_FAMORIG      = 0x0010,
65   C_EASYTREE     = 0x0020,
66   C_PAF4         = 0x0040
67 };
68
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, "" }
76 };
77
78 /* Incompatibility list (with GEDCOM 5.5):
79
80     - ftree:
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
84
85     - Lifelines (3.0.2):
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
92
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)
98         - lines too long
99         - non-standard date formats
100
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
106
107     - Family Origins:
108         - '@' not written as '@@' in values
109         - CONC needs an extra space
110
111     - EasyTree:
112         - no GEDC.FORM field
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
118
119     - Personal Ancestral File 4:
120         - '@' not written as '@@' in values
121         - SUBM.CTRY instead of SUBM.ADDR.CTRY
122         - lines too long
123         - non-standard date formats
124  */
125
126 int compat_matrix[] =
127 {
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
135                                | C_PAF4,
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
150 };
151
152 union _COMPAT_STATE {
153   int i;
154   void* vp;
155 } compat_state[C_NR_OF_RULES];
156
157 /* Compatibility handling */
158
159 void gedcom_set_compat_handling(int enable_compat)
160 {
161   compat_enabled = enable_compat;
162 }
163
164 void gedcom_set_compat_options(Gedcom_compat options)
165 {
166   compat_options = options;
167 }
168
169 void enable_compat_msg(const char* program_name, int version)
170 {
171   if (version > 0)
172     gedcom_warning(_("Enabling compatibility with '%s', version %d"),
173                    program_name, version);
174   else
175     gedcom_warning(_("Enabling compatibility with '%s'"),
176                    program_name);
177 }
178
179 int program_equal(const char* program, const char* compare)
180 {
181   return !strncmp(program, compare, strlen(compare)+1);
182 }
183
184 int program_equal_continued(const char* program, const char* compare)
185 {
186   size_t len = strlen(compare);
187   int result = strncmp(program, compare, len);
188   if (result == 0) {
189     if (strlen(program) > len)
190       set_compatibility_version(program + len);
191   }
192   return !result;
193 }
194
195 void set_compatibility_program(const char* program)
196 {
197   compatibility_program = 0;
198   if (compat_enabled) {
199     if (program_equal(program, "ftree")) {
200       compatibility_program = CP_FTREE;
201     }
202     else if (program_equal_continued(program, "LIFELINES")) {
203       compatibility_program = CP_LIFELINES;
204     }
205     else if (program_equal_continued(program, "PAF")) {
206       compatibility_program = CP_PAF;
207     }
208     else if (program_equal(program, "FamilyOrigins")) {
209       compatibility_program = CP_FAMORIG;
210     }
211     else if (program_equal(program, "EasyTree")) {
212       compatibility_program = CP_EASYTREE;
213     }
214   }
215 }
216
217 void compute_compatibility()
218 {
219   /* Reinitialize compatibility */
220   int i;
221   int version = 0;
222   default_charset = "";
223   compatibility = 0;
224   for (i = 0; i < C_NR_OF_RULES; i++)
225     compat_state[i].i = 0;
226
227   switch (compatibility_program) {
228     case CP_PAF:
229       if (compatibility_version >= 20000 && compatibility_version < 30000) {
230         compatibility = C_PAF2;
231         version = 2;
232       }
233       if (compatibility_version >= 40000 && compatibility_version < 50000) {
234         compatibility = C_PAF4;
235         version = 4;
236       }
237       else if (compatibility_version >= 50000) {
238         compatibility = C_PAF5;
239         version = 5;
240       }
241       break;
242     default:
243       compatibility = data[compatibility_program].default_compat;
244       break;
245   }
246   if (compatibility) {
247     default_charset = data[compatibility_program].default_charset;
248     enable_compat_msg(data[compatibility_program].name, version);
249   }
250 }
251
252 void set_compatibility_version(const char* version)
253 {
254   if (compat_enabled) {
255     unsigned int major=0, minor=0, patch=0;
256     int result;
257     
258     result = sscanf(version, " %u.%u.%u", &major, &minor, &patch);
259     if (result > 0) {
260       gedcom_debug_print("Setting compat version to %u.%u.%u",
261                          major, minor, patch);
262       compatibility_version = major * 10000 + minor * 100 + patch;
263     }
264   }
265 }
266
267 int compat_mode(Compat_rule rule)
268 {
269   return (compat_matrix[rule] & compatibility);
270 }
271
272 /********************************************************************/
273 /*  C_NO_SUBMITTER                                                  */
274 /********************************************************************/
275
276 void compat_generate_submitter_link(Gedcom_ctxt parent)
277 {
278   struct xref_value *xr = gedcom_parse_xref(SUBMITTER_LINK, XREF_USED,
279                                             XREF_SUBM);
280   struct tag_struct ts;
281   Gedcom_ctxt self;
282   
283   ts.string = "SUBM";
284   ts.value  = TAG_SUBM;
285   gedcom_warning(_("Adding link to submitter record with xref '%s'"),
286                  SUBMITTER_LINK);
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;
292 }
293
294 void compat_generate_submitter()
295 {
296   if (compat_state[C_NO_SUBMITTER].i) {
297     struct xref_value *xr = gedcom_parse_xref(SUBMITTER_LINK, XREF_DEFINED,
298                                               XREF_SUBM);
299     struct tag_struct ts;
300     Gedcom_ctxt self1, self2;
301     
302     /* first generate "0 SUBM" */
303     ts.string = "SUBM";
304     ts.value  = TAG_SUBM;
305     self1 = start_record(REC_SUBM, 0, GEDCOM_MAKE_XREF_PTR(val1, xr), ts,
306                          NULL, GEDCOM_MAKE_NULL(val2));
307     
308     /* then generate "1 NAME ..." */
309     ts.string = "NAME";
310     ts.value  = TAG_NAME;
311     self2 = start_element(ELT_SUBM_NAME, self1, 1, ts, DEFAULT_SUBMITTER_NAME,
312                           GEDCOM_MAKE_STRING(val1, DEFAULT_SUBMITTER_NAME));
313     
314     /* close "1 NAME ..." */
315     end_element(ELT_SUBM_NAME, self1, self2, NULL);
316     
317     /* close "0 SUBM" */
318     end_record(REC_SUBM, self1, NULL);
319     compat_state[C_NO_SUBMITTER].i = 0;
320   }
321 }
322
323 /********************************************************************/
324 /*  C_NO_GEDC                                                       */
325 /********************************************************************/
326
327 void compat_generate_gedcom(Gedcom_ctxt parent)
328 {
329   struct tag_struct ts;
330   Gedcom_ctxt self1, self2;
331   
332   /* first generate "1 GEDC" */
333   ts.string = "GEDC";
334   ts.value  = TAG_GEDC;
335   self1 = start_element(ELT_HEAD_GEDC, parent, 1, ts, NULL,
336                         GEDCOM_MAKE_NULL(val1));
337   
338   /* then generate "2 VERS <DEFAULT_GEDC_VERS>" */
339   ts.string = "VERS";
340   ts.value  = TAG_VERS;
341   self2 = start_element(ELT_HEAD_GEDC_VERS, self1, 2, ts,
342                         DEFAULT_GEDCOM_VERS,
343                         GEDCOM_MAKE_STRING(val1, DEFAULT_GEDCOM_VERS));
344   
345   /* close "2 VERS" */
346   end_element(ELT_HEAD_GEDC_VERS, self1, self2, NULL);
347   
348   /* then generate "2 FORM <DEFAULT_GEDCOM_FORM> */
349   compat_generate_gedcom_form(self1);
350   
351   /* close "1 GEDC" */
352   end_element(ELT_HEAD_GEDC, parent, self1, NULL);
353 }
354
355 /********************************************************************/
356 /*  C_NO_GEDC_FORM                                                  */
357 /********************************************************************/
358
359 void compat_generate_gedcom_form(Gedcom_ctxt parent)
360 {
361   struct tag_struct ts;
362   Gedcom_ctxt self;
363   
364   /* generate "2 FORM <DEFAULT_GEDCOM_FORM> */
365   ts.string = "FORM";
366   ts.value  = TAG_FORM;
367   self = start_element(ELT_HEAD_GEDC_FORM, parent, 2, ts,
368                        DEFAULT_GEDCOM_FORM,
369                        GEDCOM_MAKE_STRING(val1, DEFAULT_GEDCOM_FORM));
370   
371   /* close "2 FORM" */
372   end_element(ELT_HEAD_GEDC_FORM, parent, self, NULL);
373   
374 }
375   
376 /********************************************************************/
377 /*  C_NO_CHAR                                                       */
378 /********************************************************************/
379
380 int compat_generate_char(Gedcom_ctxt parent)
381 {
382   struct tag_struct ts;
383   Gedcom_ctxt self1;
384   char* charset;
385   
386   /* first generate "1 CHAR <DEFAULT_CHAR>" */
387   ts.string = "CHAR";
388   ts.value  = TAG_CHAR;
389
390   /* Must strdup, because default_charset is const char */
391   charset   = strdup(default_charset);
392   if (! charset)
393     MEMORY_ERROR;
394   else {
395     self1 = start_element(ELT_HEAD_CHAR, parent, 1, ts, charset,
396                           GEDCOM_MAKE_STRING(val1, charset));
397     free(charset);
398     
399     /* close "1 CHAR" */
400     end_element(ELT_HEAD_CHAR, parent, self1, NULL);
401   }
402   if (open_conv_to_internal(default_charset) == 0)
403     return 1;
404   else
405     return 0;
406 }
407
408 /********************************************************************/
409 /*  C_HEAD_TIME                                                     */
410 /********************************************************************/
411
412 void compat_save_head_date_context(Gedcom_ctxt parent)
413 {
414   compat_state[C_HEAD_TIME].vp = parent;
415 }
416
417 Gedcom_ctxt compat_generate_head_time_start(int level, struct tag_struct ts,
418                                             char* value)
419 {
420   if (compat_options & COMPAT_ALLOW_OUT_OF_CONTEXT) {
421     Gedcom_ctxt parent = compat_state[C_HEAD_TIME].vp;
422     if (!value)
423       value = "-";
424     if (parent)
425       return start_element(ELT_HEAD_DATE_TIME,
426                            parent, level, ts, value,
427                            GEDCOM_MAKE_STRING(val1, value));
428     else
429       return NULL;
430   }
431   else {
432     gedcom_warning(_("Header change time '%s' lost in the compatibility (out of context)"),
433                    value);
434     return NULL;
435   }
436 }
437
438 void compat_generate_head_time_end(Gedcom_ctxt self)
439 {
440   if (compat_options & COMPAT_ALLOW_OUT_OF_CONTEXT) {
441     Gedcom_ctxt parent = compat_state[C_HEAD_TIME].vp;
442     if (parent)
443       end_element(ELT_HEAD_DATE_TIME,
444                   parent, self, GEDCOM_MAKE_NULL(val1));
445   }
446 }
447
448 /********************************************************************/
449 /*  C_SUBM_CTRY                                                     */
450 /********************************************************************/
451
452 void compat_save_ctry_parent_context(Gedcom_ctxt parent)
453 {
454   compat_state[C_SUBM_CTRY].vp = parent;
455 }
456
457 Gedcom_ctxt compat_generate_addr_ctry_start(int level, struct tag_struct ts,
458                                             char* value)
459 {
460   if (compat_options & COMPAT_ALLOW_OUT_OF_CONTEXT) {
461     Gedcom_ctxt parent = compat_state[C_SUBM_CTRY].vp;
462     if (!value)
463       value = "-";
464     if (parent)
465       return start_element(ELT_SUB_ADDR_CTRY,
466                            parent, level, ts, value,
467                            GEDCOM_MAKE_STRING(val1, value));
468     else
469       return NULL;
470   }
471   else {
472     gedcom_warning(_("Country '%s' lost in the compatibility (out of context)"), value);
473     return NULL;
474   }
475 }
476
477 void compat_generate_addr_ctry_end(Gedcom_ctxt self)
478 {
479   if (compat_options & COMPAT_ALLOW_OUT_OF_CONTEXT) {
480     Gedcom_ctxt parent = compat_state[C_SUBM_CTRY].vp;
481     if (parent)
482       end_element(ELT_SUB_ADDR_CTRY,
483                   parent, self, GEDCOM_MAKE_NULL(val1));
484   }
485 }
486
487 void compat_free_ctry_parent_context()
488 {
489   compat_state[C_SUBM_CTRY].vp = NULL;
490 }
491
492 /********************************************************************/
493 /*  C_INDI_ADDR                                                     */
494 /********************************************************************/
495
496 Gedcom_ctxt compat_generate_resi_start(Gedcom_ctxt parent)
497 {
498   Gedcom_ctxt self;
499   struct tag_struct ts;
500
501   ts.string = "RESI";
502   ts.value  = TAG_RESI;
503   self = start_element(ELT_SUB_INDIV_RESI, parent, 1, ts, NULL,
504                        GEDCOM_MAKE_NULL(val1));
505   return self;
506 }
507
508 void compat_generate_resi_end(Gedcom_ctxt parent, Gedcom_ctxt self)
509 {
510   end_element(ELT_SUB_INDIV_RESI, parent, self, NULL);
511 }
512
513 /********************************************************************/
514 /*  C_551_TAGS                                                      */
515 /********************************************************************/
516
517 int is_551_tag(const char* tag)
518 {
519   if (strncmp(tag, "EMAIL", 6))
520     return 1;
521   else if (strncmp(tag, "FONE", 5))
522     return 1;
523   else if (strncmp(tag, "ROMN", 5))
524     return 1;
525   else
526     return 0;
527 }
528
529 int compat_check_551_tag(const char* tag, struct safe_buffer* b)
530 {
531   if (is_551_tag(tag)) {
532     reset_buffer(b);
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));
537     return 1;
538   }
539   else
540     return 0;
541 }
542
543 /********************************************************************/
544 /*  C_NO_SLGC_FAMC                                                  */
545 /********************************************************************/
546
547 void compat_generate_slgc_famc_link(Gedcom_ctxt parent)
548 {
549   struct xref_value *xr = gedcom_parse_xref(SLGC_FAMC_LINK, XREF_USED,
550                                             XREF_FAM);
551   struct tag_struct ts;
552   Gedcom_ctxt self;
553   
554   ts.string = "FAMC";
555   ts.value  = TAG_FAMC;
556   gedcom_warning(_("Adding link to family record with xref '%s'"),
557                  SLGC_FAMC_LINK);
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++;
563 }
564
565 void compat_generate_slgc_famc_fam()
566 {
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,
570                                               XREF_FAM);
571     struct tag_struct ts;
572     Gedcom_ctxt self;
573     
574     /* generate "0 FAM" */
575     ts.string = "FAM";
576     ts.value  = TAG_FAM;
577     self = start_record(REC_FAM, 0, GEDCOM_MAKE_XREF_PTR(val1, xr), ts,
578                         NULL, GEDCOM_MAKE_NULL(val2));
579     
580     /* close "0 FAM" */
581     end_record(REC_FAM, self, NULL);
582   }
583 }
584
585 /********************************************************************/
586 /*  C_SUBM_COMM                                                     */
587 /********************************************************************/
588
589 int compat_check_subm_comm(const char* tag, const char* parent_tag,
590                            struct safe_buffer* b)
591 {
592   if (!strcmp(tag, "COMM") && !strcmp(parent_tag, "SUBM")) {
593     reset_buffer(b);
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;
599     return 1;
600   }
601   else
602     return 0;
603 }
604
605 void compat_close_subm_comm()
606 {
607   compat_state[C_SUBM_COMM].i = 0;
608 }
609
610 int compat_check_subm_comm_cont(const char* tag)
611 {
612   if (compat_state[C_SUBM_COMM].i && !strcmp(tag, "CONT")) {
613     compat_state[C_SUBM_COMM].i = 2;
614     return 1;
615   }
616   else
617     return 0;
618 }
619
620 Gedcom_ctxt compat_subm_comm_cont_start(Gedcom_ctxt parent, char* str)
621 {
622   Gedcom_ctxt self = NULL;
623   struct tag_struct ts;
624
625   if (compat_state[C_SUBM_COMM].i == 2) {
626     ts.string = "_CONT";
627     ts.value  = USERTAG;
628     self = start_element(ELT_USER, parent, 2, ts, str, &val2);
629   }
630
631   return self;
632 }
633
634 void compat_subm_comm_cont_end(Gedcom_ctxt parent, Gedcom_ctxt self)
635 {
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;
639   }
640 }
641
642 /********************************************************************/
643 /*  C_NOTE_TOO_LONG                                                 */
644 /********************************************************************/
645
646 char compat_prefix[MAXGEDCLINELEN];
647
648 int compat_long_line(int level, int tag)
649 {
650   return compat_mode(C_NOTE_TOO_LONG) && (level > 0) && (tag == TAG_NOTE);
651 }
652
653 char* compat_long_line_get_prefix(char* str)
654 {
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 == ' ')) {
661       len--;
662       nextch = ch;
663       ch     = nth_utf8_char(str, len - 1);
664     }
665     len = nextch - str;
666     strncpy(compat_prefix, str, len);
667     compat_state[C_NOTE_TOO_LONG].vp = (void*)nextch;
668     return compat_prefix;
669   }
670   else {
671     compat_state[C_NOTE_TOO_LONG].vp = NULL;
672     return str;
673   }
674 }
675
676 void compat_long_line_finish(Gedcom_ctxt parent, int level)
677 {
678   struct tag_struct ts;
679   ts.string = "CONC";
680   ts.value  = TAG_CONC;
681   
682   while (compat_state[C_NOTE_TOO_LONG].vp) {
683     Gedcom_ctxt ctxt;
684     char* input  = (char*)compat_state[C_NOTE_TOO_LONG].vp;
685     char* output = compat_long_line_get_prefix(input);
686
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));
690   }
691 }
692
693 /********************************************************************/
694 /*  C_NOTE_CONC_SOUR                                                */
695 /********************************************************************/
696
697 Gedcom_ctxt compat_generate_note_sour_start(Gedcom_ctxt parent,
698                                             int level, struct tag_struct ts,
699                                             char* pointer)
700 {
701   Gedcom_ctxt self;
702   struct xref_value *xr = gedcom_parse_xref(pointer, XREF_USED, XREF_SOUR);
703   if (xr == NULL) {
704     self = (void*)-1;
705   }
706   else {
707     self = start_element(ELT_SUB_SOUR, parent, level-1, ts, pointer,
708                          GEDCOM_MAKE_XREF_PTR(val1, xr));
709   }
710   compat_state[C_NOTE_CONC_SOUR].vp = parent;
711   return self;
712 }
713
714 void compat_generate_note_sour_end(Gedcom_ctxt self)
715 {
716   if (self != (void*) -1) {
717     end_element(ELT_SUB_SOUR, compat_state[C_NOTE_CONC_SOUR].vp,
718                 self, GEDCOM_MAKE_NULL(val1));
719   }
720 }
721
722 /********************************************************************/
723 /*  C_NONSTD_SOUR_TAGS                                              */
724 /********************************************************************/
725
726 int is_nonstd_sour_tag(const char* tag)
727 {
728   if (strncmp(tag, "FILN", 5))
729     return 1;
730   else if (strncmp(tag, "URL", 4))
731     return 1;
732   else if (strncmp(tag, "LOCA", 5))
733     return 1;
734   else if (strncmp(tag, "REGI", 5))
735     return 1;
736   else if (strncmp(tag, "VOL", 4))
737     return 1;
738   else
739     return 0;
740 }
741
742 int compat_check_sour_tag(const char* tag, struct safe_buffer* b)
743 {
744   if (is_nonstd_sour_tag(tag)) {
745     reset_buffer(b);
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));
750     return 1;
751   }
752   else
753     return 0;
754 }
755
756 Gedcom_ctxt compat_generate_nonstd_sour_start(Gedcom_ctxt parent, int level,
757                                               struct tag_struct ts,
758                                               char* value,
759                                               struct safe_buffer* b)
760 {
761   Gedcom_ctxt self = NULL;
762   reset_buffer(b);
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);
768
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;
772   return self;
773 }
774
775 void compat_generate_nonstd_sour_end(Gedcom_ctxt parent, Gedcom_ctxt self)
776 {
777   end_element(ELT_USER, parent, self, NULL);
778   compat_state[C_NONSTD_SOUR_TAGS].i = 0;
779 }
780
781 int compat_generate_nonstd_sour_state()
782 {
783   return compat_state[C_NONSTD_SOUR_TAGS].i;
784 }