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