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