Copied from old documentation. Removed all Gedcom_val details.
[gedcom-parse.git] / gedcom / compat.c
index c566d8e9ab8c77742fcd153705ba377c9ac423dd..ce45d41195543c56e3f7c50fd9336b5fb5259331 100644 (file)
 #include "interface.h"
 #include "encoding.h"
 #include "xref.h"
+#include "buffer.h"
 #include "gedcom_internal.h"
 #include "gedcom.h"
 
 int compat_enabled = 1;
-int compatibility  = 0; 
-int compat_at = 0;
+Gedcom_compat compat_options = 0;
+int compatibility  = 0;
+int compatibility_program = 0;
+int compatibility_version = 0;
 const char* default_charset = "";
 
+void cleanup_compat_buffer();
+struct safe_buffer compat_buffer = { NULL, 0, NULL, 0, cleanup_compat_buffer };
+
+void cleanup_compat_buffer()
+{
+  cleanup_buffer(&compat_buffer);
+}
+
 #define SUBMITTER_LINK         "@__COMPAT__SUBM__@"
+#define SLGC_FAMC_LINK         "@__COMPAT__FAM_SLGC__@"
 #define DEFAULT_SUBMITTER_NAME "Submitter"
 #define DEFAULT_GEDCOM_VERS    "5.5"
 #define DEFAULT_GEDCOM_FORM    "LINEAGE-LINKED"
 
+struct program_data {
+  const char* name;
+  int         default_compat;
+  const char* default_charset;
+};
+
+enum _COMPAT_PROGRAM {
+  CP_FTREE = 1,
+  CP_LIFELINES,
+  CP_PAF,
+  CP_FAMORIG,
+  CP_EASYTREE
+};
+
+enum _COMPAT {
+  C_FTREE        = 0x0001,
+  C_LIFELINES    = 0x0002,
+  C_PAF5         = 0x0004,
+  C_PAF2         = 0x0008,
+  C_FAMORIG      = 0x0010,
+  C_EASYTREE     = 0x0020,
+  C_PAF4         = 0x0040
+};
+
+struct program_data data[] = {
+  /* NULL */         { "", 0, "" },
+  /* CP_FTREE */     { "ftree", C_FTREE, "" },
+  /* CP_LIFELINES */ { "Lifelines", C_LIFELINES, "ANSI" },
+  /* CP_PAF */       { "Personal Ancestral File", C_PAF5, "" },
+  /* CP_FAMORIG */   { "Family Origins", C_FAMORIG, "" },
+  /* CP_EASYTREE */  { "EasyTree", C_EASYTREE, "" }
+};
+
 /* Incompatibility list (with GEDCOM 5.5):
 
     - ftree:
@@ -52,8 +97,69 @@ const char* default_charset = "";
        - HEAD.TIME instead of HEAD.DATE.TIME (will be ignored here)
        - '@' not written as '@@' in values
        - lots of missing required values
+
+    - Personal Ancestral File 5:
+        - '@' not written as '@@' in values
+       - some 5.5.1 (draft) tags are used: EMAIL, FONE, ROMN
+       - no FAMC field in SLGC
+       - uses tab character (will be converted to 8 spaces here)
+       - lines too long
+       - double dates written as e.g. '1815/1816'
+
+    - Personal Ancestral File 2:
+        - '@' not written as '@@' in values
+       - COMM tag in submitter record
+       - double dates written as e.g. '1815/1816'
+
+    - Family Origins:
+        - '@' not written as '@@' in values
+       - CONC needs an extra space
+
+    - EasyTree:
+        - no GEDC.FORM field
+       - no submitter link in the header
+       - NOTE doesn't have a value
+       - NOTE.NOTE instead of NOTE.COND
+       - NOTE.CONC.SOUR instead of NOTE.SOUR
+       - non-standard tags in SOUR records
+
+    - Personal Ancestral File 4:
+        - '@' not written as '@@' in values
+       - SUBM.CTRY instead of SUBM.ADDR.CTRY
+       - lines too long
+       - double dates written as e.g. '1815/1816'
  */
 
+int compat_matrix[] =
+{
+  /* C_NO_SUBMITTER */        C_FTREE | C_LIFELINES | C_PAF2 | C_EASYTREE,
+  /* C_INDI_ADDR */           C_FTREE,
+  /* C_NOTE_NO_VALUE */       C_FTREE | C_EASYTREE,
+  /* C_NO_GEDC */             C_LIFELINES | C_PAF2,
+  /* C_NO_CHAR */             C_LIFELINES,
+  /* C_HEAD_TIME */           C_LIFELINES,
+  /* C_NO_DOUBLE_AT */        C_LIFELINES | C_PAF5 | C_PAF2 | C_FAMORIG
+                               | C_PAF4,
+  /* C_NO_REQUIRED_VALUES */  C_LIFELINES | C_PAF5 | C_EASYTREE,
+  /* C_551_TAGS */            C_PAF5,
+  /* C_NO_SLGC_FAMC */        C_PAF5,
+  /* C_SUBM_COMM */           C_PAF2,
+  /* C_DOUBLE_DATES_4 */      C_PAF2 | C_PAF5 | C_PAF4,
+  /* C_CONC_NEEDS_SPACE */    C_FAMORIG,
+  /* C_NO_GEDC_FORM */        C_EASYTREE,
+  /* C_NOTE_NOTE */           C_EASYTREE,
+  /* C_TAB_CHARACTER */       C_PAF5,
+  /* C_SUBM_CTRY */           C_PAF4,
+  /* C_NOTE_TOO_LONG */       C_PAF4 | C_PAF5,
+  /* C_NOTE_CONC_SOUR */      C_EASYTREE,
+  /* C_NONSTD_SOUR_TAGS */    C_EASYTREE,
+};
+
+union _COMPAT_STATE {
+  int i;
+  void* vp;
+} compat_state[C_NR_OF_RULES];
+
 /* Compatibility handling */
 
 void gedcom_set_compat_handling(int enable_compat)
@@ -61,33 +167,124 @@ void gedcom_set_compat_handling(int enable_compat)
   compat_enabled = enable_compat;
 }
 
-void set_compatibility(const char* program)
+void gedcom_set_compat_options(Gedcom_compat options)
+{
+  compat_options = options;
+}
+
+void enable_compat_msg(const char* program_name, int version)
+{
+  if (version > 0)
+    gedcom_warning(_("Enabling compatibility with '%s', version %d"),
+                  program_name, version);
+  else
+    gedcom_warning(_("Enabling compatibility with '%s'"),
+                  program_name);
+}
+
+int program_equal(const char* program, const char* compare)
+{
+  return !strncmp(program, compare, strlen(compare)+1);
+}
+
+int program_equal_continued(const char* program, const char* compare)
+{
+  size_t len = strlen(compare);
+  int result = strncmp(program, compare, len);
+  if (result == 0) {
+    if (strlen(program) > len)
+      set_compatibility_version(program + len);
+  }
+  return !result;
+}
+
+void set_compatibility_program(const char* program)
+{
+  compatibility_program = 0;
+  if (compat_enabled) {
+    if (program_equal(program, "ftree")) {
+      compatibility_program = CP_FTREE;
+    }
+    else if (program_equal_continued(program, "LIFELINES")) {
+      compatibility_program = CP_LIFELINES;
+    }
+    else if (program_equal_continued(program, "PAF")) {
+      compatibility_program = CP_PAF;
+    }
+    else if (program_equal(program, "FamilyOrigins")) {
+      compatibility_program = CP_FAMORIG;
+    }
+    else if (program_equal(program, "EasyTree")) {
+      compatibility_program = CP_EASYTREE;
+    }
+  }
+}
+
+void compute_compatibility()
 {
   /* Reinitialize compatibility */
-  compat_at = 0;
+  int i;
+  int version = 0;
   default_charset = "";
   compatibility = 0;
-  
+  for (i = 0; i < C_NR_OF_RULES; i++)
+    compat_state[i].i = 0;
+
+  switch (compatibility_program) {
+    case CP_PAF:
+      if (compatibility_version >= 20000 && compatibility_version < 30000) {
+       compatibility = C_PAF2;
+       version = 2;
+      }
+      if (compatibility_version >= 40000 && compatibility_version < 50000) {
+       compatibility = C_PAF4;
+       version = 4;
+      }
+      else if (compatibility_version >= 50000) {
+       compatibility = C_PAF5;
+       version = 5;
+      }
+      break;
+    default:
+      compatibility = data[compatibility_program].default_compat;
+      break;
+  }
+  if (compatibility) {
+    default_charset = data[compatibility_program].default_charset;
+    enable_compat_msg(data[compatibility_program].name, version);
+  }
+}
+
+void set_compatibility_version(const char* version)
+{
   if (compat_enabled) {
-    if (! strncmp(program, "ftree", 6)) {
-      gedcom_warning(_("Enabling compatibility with 'ftree'"));
-      compatibility = C_FTREE;
-    }
-    else if (! strncmp(program, "LIFELINES", 9)) {
-      /* Matches "LIFELINES 3.0.2" */
-      gedcom_warning(_("Enabling compatibility with 'Lifelines'"));
-      compatibility = C_LIFELINES;
-      default_charset = "ANSI";
-      compat_at = 1;
+    unsigned int major=0, minor=0, patch=0;
+    int result;
+    
+    result = sscanf(version, " %u.%u.%u", &major, &minor, &patch);
+    if (result > 0) {
+      gedcom_debug_print("Setting compat version to %u.%u.%u",
+                        major, minor, patch);
+      compatibility_version = major * 10000 + minor * 100 + patch;
     }
   }
 }
 
-int compat_mode(int compat_flags)
+int compat_mode(Compat_rule rule)
+{
+  return (compat_matrix[rule] & compatibility);
+}
+
+void compat_close()
 {
-  return (compat_flags & compatibility);
+  compatibility_program = 0;
+  compatibility = 0;
 }
 
+/********************************************************************/
+/*  C_NO_SUBMITTER                                                  */
+/********************************************************************/
+
 void compat_generate_submitter_link(Gedcom_ctxt parent)
 {
   struct xref_value *xr = gedcom_parse_xref(SUBMITTER_LINK, XREF_USED,
@@ -97,38 +294,48 @@ void compat_generate_submitter_link(Gedcom_ctxt parent)
   
   ts.string = "SUBM";
   ts.value  = TAG_SUBM;
+  gedcom_warning(_("Adding link to submitter record with xref '%s'"),
+                SUBMITTER_LINK);
   self = start_element(ELT_HEAD_SUBM,
                       parent, 1, ts, SUBMITTER_LINK,
                       GEDCOM_MAKE_XREF_PTR(val1, xr));
   end_element(ELT_HEAD_SUBM, parent, self, NULL);
+  compat_state[C_NO_SUBMITTER].i = 1;
 }
 
 void compat_generate_submitter()
 {
-  struct xref_value *xr = gedcom_parse_xref(SUBMITTER_LINK, XREF_DEFINED,
-                                           XREF_SUBM);
-  struct tag_struct ts;
-  Gedcom_ctxt self1, self2;
-
-  /* first generate "0 SUBM" */
-  ts.string = "SUBM";
-  ts.value  = TAG_SUBM;
-  self1 = start_record(REC_SUBM, 0, GEDCOM_MAKE_XREF_PTR(val1, xr), ts,
-                      NULL, GEDCOM_MAKE_NULL(val2));
-
-  /* then generate "1 NAME ..." */
-  ts.string = "NAME";
-  ts.value  = TAG_NAME;
-  self2 = start_element(ELT_SUBM_NAME, self1, 1, ts, DEFAULT_SUBMITTER_NAME,
-                       GEDCOM_MAKE_STRING(val1, DEFAULT_SUBMITTER_NAME));
-
-  /* close "1 NAME ..." */
-  end_element(ELT_SUBM_NAME, self1, self2, NULL);
-
-  /* close "0 SUBM" */
-  end_record(REC_SUBM, self1);
+  if (compat_state[C_NO_SUBMITTER].i) {
+    struct xref_value *xr = gedcom_parse_xref(SUBMITTER_LINK, XREF_DEFINED,
+                                             XREF_SUBM);
+    struct tag_struct ts;
+    Gedcom_ctxt self1, self2;
+    
+    /* first generate "0 SUBM" */
+    ts.string = "SUBM";
+    ts.value  = TAG_SUBM;
+    self1 = start_record(REC_SUBM, 0, GEDCOM_MAKE_XREF_PTR(val1, xr), ts,
+                        NULL, GEDCOM_MAKE_NULL(val2));
+    
+    /* then generate "1 NAME ..." */
+    ts.string = "NAME";
+    ts.value  = TAG_NAME;
+    self2 = start_element(ELT_SUBM_NAME, self1, 1, ts, DEFAULT_SUBMITTER_NAME,
+                         GEDCOM_MAKE_STRING(val1, DEFAULT_SUBMITTER_NAME));
+    
+    /* close "1 NAME ..." */
+    end_element(ELT_SUBM_NAME, self1, self2, NULL);
+    
+    /* close "0 SUBM" */
+    end_record(REC_SUBM, self1, NULL);
+    compat_state[C_NO_SUBMITTER].i = 0;
+  }
 }
 
+/********************************************************************/
+/*  C_NO_GEDC                                                       */
+/********************************************************************/
+
 void compat_generate_gedcom(Gedcom_ctxt parent)
 {
   struct tag_struct ts;
@@ -151,38 +358,153 @@ void compat_generate_gedcom(Gedcom_ctxt parent)
   end_element(ELT_HEAD_GEDC_VERS, self1, self2, NULL);
   
   /* then generate "2 FORM <DEFAULT_GEDCOM_FORM> */
+  compat_generate_gedcom_form(self1);
+  
+  /* close "1 GEDC" */
+  end_element(ELT_HEAD_GEDC, parent, self1, NULL);
+}
+
+/********************************************************************/
+/*  C_NO_GEDC_FORM                                                  */
+/********************************************************************/
+
+void compat_generate_gedcom_form(Gedcom_ctxt parent)
+{
+  struct tag_struct ts;
+  Gedcom_ctxt self;
+  
+  /* generate "2 FORM <DEFAULT_GEDCOM_FORM> */
   ts.string = "FORM";
   ts.value  = TAG_FORM;
-  self2 = start_element(ELT_HEAD_GEDC_FORM, self1, 2, ts,
-                       DEFAULT_GEDCOM_FORM,
-                       GEDCOM_MAKE_STRING(val1, DEFAULT_GEDCOM_FORM));
+  self = start_element(ELT_HEAD_GEDC_FORM, parent, 2, ts,
+                      DEFAULT_GEDCOM_FORM,
+                      GEDCOM_MAKE_STRING(val1, DEFAULT_GEDCOM_FORM));
   
   /* close "2 FORM" */
-  end_element(ELT_HEAD_GEDC_FORM, self1, self2, NULL);
+  end_element(ELT_HEAD_GEDC_FORM, parent, self, NULL);
   
-  /* close "1 GEDC" */
-  end_element(ELT_HEAD_GEDC, parent, self1, NULL);
 }
+  
+/********************************************************************/
+/*  C_NO_CHAR                                                       */
+/********************************************************************/
 
 int compat_generate_char(Gedcom_ctxt parent)
 {
   struct tag_struct ts;
   Gedcom_ctxt self1;
+  char* charset;
   
   /* first generate "1 CHAR <DEFAULT_CHAR>" */
   ts.string = "CHAR";
   ts.value  = TAG_CHAR;
-  self1 = start_element(ELT_HEAD_CHAR, parent, 1, ts, (char*)default_charset,
-                       GEDCOM_MAKE_STRING(val1, (char*)default_charset));
-  
-  /* close "1 CHAR" */
-  end_element(ELT_HEAD_CHAR, parent, self1, NULL);
+
+  /* Must strdup, because default_charset is const char */
+  charset   = strdup(default_charset);
+  if (! charset)
+    MEMORY_ERROR;
+  else {
+    self1 = start_element(ELT_HEAD_CHAR, parent, 1, ts, charset,
+                         GEDCOM_MAKE_STRING(val1, charset));
+    free(charset);
+    
+    /* close "1 CHAR" */
+    end_element(ELT_HEAD_CHAR, parent, self1, NULL);
+  }
   if (open_conv_to_internal(default_charset) == 0)
     return 1;
   else
     return 0;
 }
 
+/********************************************************************/
+/*  C_HEAD_TIME                                                     */
+/********************************************************************/
+
+void compat_save_head_date_context(Gedcom_ctxt parent)
+{
+  compat_state[C_HEAD_TIME].vp = parent;
+}
+
+Gedcom_ctxt compat_generate_head_time_start(int level, struct tag_struct ts,
+                                           char* value)
+{
+  if (compat_options & COMPAT_ALLOW_OUT_OF_CONTEXT) {
+    Gedcom_ctxt parent = compat_state[C_HEAD_TIME].vp;
+    if (!value)
+      value = "-";
+    if (parent)
+      return start_element(ELT_HEAD_DATE_TIME,
+                          parent, level, ts, value,
+                          GEDCOM_MAKE_STRING(val1, value));
+    else
+      return NULL;
+  }
+  else {
+    gedcom_warning(_("Header change time '%s' lost in the compatibility (out of context)"),
+                  value);
+    return NULL;
+  }
+}
+
+void compat_generate_head_time_end(Gedcom_ctxt self)
+{
+  if (compat_options & COMPAT_ALLOW_OUT_OF_CONTEXT) {
+    Gedcom_ctxt parent = compat_state[C_HEAD_TIME].vp;
+    if (parent)
+      end_element(ELT_HEAD_DATE_TIME,
+                 parent, self, GEDCOM_MAKE_NULL(val1));
+  }
+}
+
+/********************************************************************/
+/*  C_SUBM_CTRY                                                     */
+/********************************************************************/
+
+void compat_save_ctry_parent_context(Gedcom_ctxt parent)
+{
+  compat_state[C_SUBM_CTRY].vp = parent;
+}
+
+Gedcom_ctxt compat_generate_addr_ctry_start(int level, struct tag_struct ts,
+                                           char* value)
+{
+  if (compat_options & COMPAT_ALLOW_OUT_OF_CONTEXT) {
+    Gedcom_ctxt parent = compat_state[C_SUBM_CTRY].vp;
+    if (!value)
+      value = "-";
+    if (parent)
+      return start_element(ELT_SUB_ADDR_CTRY,
+                          parent, level, ts, value,
+                          GEDCOM_MAKE_STRING(val1, value));
+    else
+      return NULL;
+  }
+  else {
+    gedcom_warning(_("Country '%s' lost in the compatibility (out of context)"), value);
+    return NULL;
+  }
+}
+
+void compat_generate_addr_ctry_end(Gedcom_ctxt self)
+{
+  if (compat_options & COMPAT_ALLOW_OUT_OF_CONTEXT) {
+    Gedcom_ctxt parent = compat_state[C_SUBM_CTRY].vp;
+    if (parent)
+      end_element(ELT_SUB_ADDR_CTRY,
+                 parent, self, GEDCOM_MAKE_NULL(val1));
+  }
+}
+
+void compat_free_ctry_parent_context()
+{
+  compat_state[C_SUBM_CTRY].vp = NULL;
+}
+
+/********************************************************************/
+/*  C_INDI_ADDR                                                     */
+/********************************************************************/
+
 Gedcom_ctxt compat_generate_resi_start(Gedcom_ctxt parent)
 {
   Gedcom_ctxt self;
@@ -199,3 +521,321 @@ void compat_generate_resi_end(Gedcom_ctxt parent, Gedcom_ctxt self)
 {
   end_element(ELT_SUB_INDIV_RESI, parent, self, NULL);
 }
+
+/********************************************************************/
+/*  C_551_TAGS                                                      */
+/********************************************************************/
+
+int is_551_tag(const char* tag)
+{
+  if (strncmp(tag, "EMAIL", 6))
+    return 1;
+  else if (strncmp(tag, "FONE", 5))
+    return 1;
+  else if (strncmp(tag, "ROMN", 5))
+    return 1;
+  else
+    return 0;
+}
+
+int compat_check_551_tag(const char* tag, struct safe_buffer* b)
+{
+  if (is_551_tag(tag)) {
+    reset_buffer(b);
+    SAFE_BUF_ADDCHAR(b, '_');
+    safe_buf_append(b, tag);
+    gedcom_warning(_("Converting 5.5.1 tag '%s' to standard 5.5 user tag '%s'"),
+                  tag, get_buf_string(b));
+    return 1;
+  }
+  else
+    return 0;
+}
+
+/********************************************************************/
+/*  C_NO_SLGC_FAMC                                                  */
+/********************************************************************/
+
+void compat_generate_slgc_famc_link(Gedcom_ctxt parent)
+{
+  struct xref_value *xr = gedcom_parse_xref(SLGC_FAMC_LINK, XREF_USED,
+                                           XREF_FAM);
+  struct tag_struct ts;
+  Gedcom_ctxt self;
+  
+  ts.string = "FAMC";
+  ts.value  = TAG_FAMC;
+  gedcom_warning(_("Adding link to family record with xref '%s'"),
+                SLGC_FAMC_LINK);
+  self = start_element(ELT_SUB_LIO_SLGC_FAMC,
+                      parent, 2, ts, SLGC_FAMC_LINK,
+                      GEDCOM_MAKE_XREF_PTR(val1, xr));
+  end_element(ELT_SUB_LIO_SLGC_FAMC, parent, self, NULL);
+  compat_state[C_NO_SLGC_FAMC].i++;
+}
+
+void compat_generate_slgc_famc_fam()
+{
+  /* If bigger than 1, then the FAM record has already been generated */
+  if (compat_state[C_NO_SLGC_FAMC].i == 1) {
+    struct xref_value *xr = gedcom_parse_xref(SLGC_FAMC_LINK, XREF_DEFINED,
+                                             XREF_FAM);
+    struct tag_struct ts;
+    Gedcom_ctxt self;
+    
+    /* generate "0 FAM" */
+    ts.string = "FAM";
+    ts.value  = TAG_FAM;
+    self = start_record(REC_FAM, 0, GEDCOM_MAKE_XREF_PTR(val1, xr), ts,
+                       NULL, GEDCOM_MAKE_NULL(val2));
+    
+    /* close "0 FAM" */
+    end_record(REC_FAM, self, NULL);
+  }
+}
+
+/********************************************************************/
+/*  C_SUBM_COMM                                                     */
+/********************************************************************/
+
+int compat_check_subm_comm(const char* tag, const char* parent_tag,
+                          struct safe_buffer* b)
+{
+  if (!strcmp(tag, "COMM") && !strcmp(parent_tag, "SUBM")) {
+    reset_buffer(b);
+    SAFE_BUF_ADDCHAR(b, '_');
+    safe_buf_append(b, tag);
+    gedcom_warning(_("Converting non-standard tag '%s' to user tag '%s'"),
+                  tag, get_buf_string(b));
+    compat_state[C_SUBM_COMM].i = 1;
+    return 1;
+  }
+  else
+    return 0;
+}
+
+void compat_close_subm_comm()
+{
+  compat_state[C_SUBM_COMM].i = 0;
+}
+
+int compat_check_subm_comm_cont(const char* tag)
+{
+  if (compat_state[C_SUBM_COMM].i && !strcmp(tag, "CONT")) {
+    compat_state[C_SUBM_COMM].i = 2;
+    return 1;
+  }
+  else
+    return 0;
+}
+
+Gedcom_ctxt compat_subm_comm_cont_start(Gedcom_ctxt parent, char* str)
+{
+  Gedcom_ctxt self = NULL;
+  struct tag_struct ts;
+
+  if (compat_state[C_SUBM_COMM].i == 2) {
+    ts.string = "_CONT";
+    ts.value  = USERTAG;
+    self = start_element(ELT_USER, parent, 2, ts, str, &val2);
+  }
+
+  return self;
+}
+
+void compat_subm_comm_cont_end(Gedcom_ctxt parent, Gedcom_ctxt self)
+{
+  if (compat_state[C_SUBM_COMM].i == 2) {
+    end_element(ELT_USER, parent, self, NULL);
+    compat_state[C_SUBM_COMM].i = 1;
+  }
+}
+
+/********************************************************************/
+/*  C_DOUBLE_DATES_4                                                */
+/********************************************************************/
+
+void compat_date_start()
+{
+  if (compat_mode(C_DOUBLE_DATES_4)) {
+    reset_buffer(&compat_buffer);
+    compat_state[C_DOUBLE_DATES_4].i = 0;
+  }
+}
+
+int compat_double_date_check(char* year2)
+{
+  return (compat_mode(C_DOUBLE_DATES_4)
+         && !compat_state[C_DOUBLE_DATES_4].i
+         && strlen(year2) == 4);
+}
+
+int compat_double_date_final(struct date_value* dv, const char** curr_line)
+{
+  char* compat_line_value = get_buf_string(&compat_buffer);
+  compat_state[C_DOUBLE_DATES_4].i = 1;
+  if (compat_line_value && compat_line_value[0]
+      && (dv->type == DV_NO_MODIFIER || dv->type == DV_ABOUT)
+      && dv->date1.day == -1
+      && dv->date1.month == -1) {
+    gedcom_warning(_("Converting '%s' to standard '%s'"),
+                  *curr_line, compat_line_value);
+    *curr_line = compat_line_value;
+  }
+  return 1;
+}
+
+int compat_date_check(struct date_value* dv, const char** curr_line)
+{
+  if (compat_mode(C_DOUBLE_DATES_4)
+      && compat_double_date_final(dv, curr_line)) {
+    return 1;
+  }
+  else {
+    return 0;
+  }
+}
+
+/********************************************************************/
+/*  C_NOTE_TOO_LONG                                                 */
+/********************************************************************/
+
+char compat_prefix[MAXGEDCLINELEN];
+
+int compat_long_line(int level, int tag)
+{
+  return compat_mode(C_NOTE_TOO_LONG) && (level > 0) && (tag == TAG_NOTE);
+}
+
+char* compat_long_line_get_prefix(char* str)
+{
+  if (str && utf8_strlen(str) > MAXGEDCLINELEN - 7) {
+    int len = MAXGEDCLINELEN - 7;
+    char* ch     = nth_utf8_char(str, len - 1);
+    char* nextch = next_utf8_char(ch);
+    memset(compat_prefix, 0, MAXGEDCLINELEN);
+    while (len > 1 && (*ch == ' ' || *nextch == ' ')) {
+      len--;
+      nextch = ch;
+      ch     = nth_utf8_char(str, len - 1);
+    }
+    len = nextch - str;
+    strncpy(compat_prefix, str, len);
+    compat_state[C_NOTE_TOO_LONG].vp = (void*)nextch;
+    return compat_prefix;
+  }
+  else {
+    compat_state[C_NOTE_TOO_LONG].vp = NULL;
+    return str;
+  }
+}
+
+void compat_long_line_finish(Gedcom_ctxt parent, int level)
+{
+  struct tag_struct ts;
+  ts.string = "CONC";
+  ts.value  = TAG_CONC;
+  
+  while (compat_state[C_NOTE_TOO_LONG].vp) {
+    Gedcom_ctxt ctxt;
+    char* input  = (char*)compat_state[C_NOTE_TOO_LONG].vp;
+    char* output = compat_long_line_get_prefix(input);
+
+    ctxt = start_element(ELT_SUB_CONC, parent, level + 1, ts, output,
+                        GEDCOM_MAKE_STRING(val1, output));
+    end_element(ELT_SUB_CONC, parent, ctxt, GEDCOM_MAKE_NULL(val1));
+  }
+}
+
+/********************************************************************/
+/*  C_NOTE_CONC_SOUR                                                */
+/********************************************************************/
+
+Gedcom_ctxt compat_generate_note_sour_start(Gedcom_ctxt parent,
+                                           int level, struct tag_struct ts,
+                                           char* pointer)
+{
+  Gedcom_ctxt self;
+  struct xref_value *xr = gedcom_parse_xref(pointer, XREF_USED, XREF_SOUR);
+  if (xr == NULL) {
+    self = (void*)-1;
+  }
+  else {
+    self = start_element(ELT_SUB_SOUR, parent, level-1, ts, pointer,
+                        GEDCOM_MAKE_XREF_PTR(val1, xr));
+  }
+  compat_state[C_NOTE_CONC_SOUR].vp = parent;
+  return self;
+}
+
+void compat_generate_note_sour_end(Gedcom_ctxt self)
+{
+  if (self != (void*) -1) {
+    end_element(ELT_SUB_SOUR, compat_state[C_NOTE_CONC_SOUR].vp,
+               self, GEDCOM_MAKE_NULL(val1));
+  }
+}
+
+/********************************************************************/
+/*  C_NONSTD_SOUR_TAGS                                              */
+/********************************************************************/
+
+int is_nonstd_sour_tag(const char* tag)
+{
+  if (strncmp(tag, "FILN", 5))
+    return 1;
+  else if (strncmp(tag, "URL", 4))
+    return 1;
+  else if (strncmp(tag, "LOCA", 5))
+    return 1;
+  else if (strncmp(tag, "REGI", 5))
+    return 1;
+  else if (strncmp(tag, "VOL", 4))
+    return 1;
+  else
+    return 0;
+}
+
+int compat_check_sour_tag(const char* tag, struct safe_buffer* b)
+{
+  if (is_nonstd_sour_tag(tag)) {
+    reset_buffer(b);
+    SAFE_BUF_ADDCHAR(b, '_');
+    safe_buf_append(b, tag);
+    gedcom_warning(_("Converting undefined tag '%s' to user tag '%s'"),
+                  tag, get_buf_string(b));
+    return 1;
+  }
+  else
+    return 0;
+}
+
+Gedcom_ctxt compat_generate_nonstd_sour_start(Gedcom_ctxt parent, int level,
+                                             struct tag_struct ts,
+                                             char* value,
+                                             struct safe_buffer* b)
+{
+  Gedcom_ctxt self = NULL;
+  reset_buffer(b);
+  SAFE_BUF_ADDCHAR(b, '_');
+  safe_buf_append(b, ts.string);
+  gedcom_warning(_("Converting invalidly used tag '%s' to user tag '%s'"),
+                ts.string, get_buf_string(b));
+  ts.string = get_buf_string(b);
+
+  self = start_element(ELT_USER, parent, level, ts, value,
+                      GEDCOM_MAKE_NULL_OR_STRING(val1, value));
+  compat_state[C_NONSTD_SOUR_TAGS].i = 1;
+  return self;
+}
+
+void compat_generate_nonstd_sour_end(Gedcom_ctxt parent, Gedcom_ctxt self)
+{
+  end_element(ELT_USER, parent, self, NULL);
+  compat_state[C_NONSTD_SOUR_TAGS].i = 0;
+}
+
+int compat_generate_nonstd_sour_state()
+{
+  return compat_state[C_NONSTD_SOUR_TAGS].i;
+}