/*============================================ * llrpt_detachnode -- Remove node from GEDCOM tree * usage: detachnode(NODE) -> VOID * (This is the historic deletenode) *==========================================*/ PVALUE llrpt_detachnode (PNODE node, SYMTAB stab, BOOLEAN *eflg) { PNODE arg = iargs(node); NODE dead, prnt; PVALUE val = eval_and_coerce(PGNODE, arg, stab, eflg); if (*eflg) { prog_var_error(node, stab, arg, val, nonnod1, "detachnode"); delete_pvalue(val); return NULL; } dead = pvalue_to_node(val); if ((prnt = nparent(dead))) { NODE prev = NULL, next; NODE curs = nchild(prnt); while (curs && curs != dead) { prev = curs; curs = nsibling(curs); } ASSERT(curs); /* else broken tree: dead was not child of its parent */ next = nsibling(dead); if (prev == NULL) nchild(prnt) = next; else nsibling(prev) = next; } /* unparent node, but ensure its locking is only releative to new parent */ dolock_node_in_cache(dead, FALSE); nparent(dead) = NULL; dolock_node_in_cache(dead, TRUE); nsibling(dead) = NULL; /* we don't actually delete the node, garbage collection must get it */ return NULL; }
/*===================================== * check_node -- check node for bad pointers * and bad levels, and continue traverse * 2001/02/18, Perry Rapp *===================================*/ static void check_node (CNSTRING n0key, NODE node, INT level) { BOOLEAN lineage=FALSE; /* ignore lineage links - they are checked elsewhere */ if (level==1) { INT i; for (i=0; i<ARRSIZE(lineage_tags); i++) { if (eqstr(ntag(node), lineage_tags[i])) { lineage=TRUE; break; } } } /* TO DO: How do we tell non-pointers that *ought* to be pointers, eg "1 SOUR <FamilyHistory>" ? */ if (!lineage) { STRING skey = rmvat(nval(node)); if (skey) { NODE xnode = qkey_to_type(skey); if (!xnode) { report_error(ERR_BADPOINTER , _("Bad pointer (in %s): %s") , n0key, nval(node)); } } } if (nchild(node)) check_node(n0key, nchild(node), level+1); if (nsibling(node)) check_node(n0key, nsibling(node), level); }
/*======================================================================== * difference_nodes -- Return difference of two node lists -- all in node1 * that are not in node2 * UNUSED CODE *======================================================================*/ NODE difference_nodes (NODE node1, NODE node2, BOOLEAN kids) /* children matter */ { NODE prev1, next1, curs1, curs2; node1 = copy_nodes(node1, TRUE, TRUE); prev1 = NULL; curs1 = node1; while (curs1) { curs2 = node2; while (curs2 && !iso_nodes(curs1, curs2, kids, FALSE)) curs2 = nsibling(curs2); if (curs2) { next1 = nsibling(curs1); nsibling(curs1) = NULL; free_nodes(curs1); if (!prev1) node1 = next1; else nsibling(prev1) = next1; curs1 = next1; } else { prev1 = curs1; curs1 = nsibling(curs1); } } return node1; }
/*======================================== * string_to_node -- Read tree from string * (modifies string -- adds 0s between lines) *======================================*/ NODE string_to_node (STRING str) { INT lev; INT lev0; STRING xref; STRING tag; STRING val; INT curlev; NODE root=NULL, node, curnode; STRING msg; flineno = 0; if (!string_to_line(&str, &lev, &xref, &tag, &val, &msg)) goto string_to_node_fail; lev0 = curlev = lev; root = curnode = create_node(xref, tag, val, NULL); while (string_to_line(&str, &lev, &xref, &tag, &val, &msg)) { if (lev == curlev) { node = create_node(xref, tag, val, nparent(curnode)); nsibling(curnode) = node; curnode = node; } else if (lev == curlev + 1) { node = create_node(xref, tag, val, curnode); nchild(curnode) = node; curnode = node; curlev = lev; } else if (lev < curlev) { if (lev < lev0) { llwprintf("Error: line %d: illegal level", flineno); goto string_to_node_fail; } while (lev < curlev) { curnode = nparent(curnode); curlev--; } node = create_node(xref, tag, val, nparent(curnode)); nsibling(curnode) = node; curnode = node; } else { llwprintf("Error: line %d: illegal level", flineno); goto string_to_node_fail; } } if (!msg) { nodechk(root, "string_to_node"); return root; } string_to_node_fail: free_nodes(root); return NULL; }
/*========================================================= * intersect_nodes -- Return intersection of two node trees * UNUSED CODE *=======================================================*/ NODE intersect_nodes (NODE node1, NODE node2, BOOLEAN kids) /* children matter */ { NODE prev1, curs1, next1, prev2, curs2, next2; NODE node3, curs3; if (!node1 || !node2) return NULL; node1 = copy_nodes(node1, TRUE, TRUE); node2 = copy_nodes(node2, TRUE, TRUE); node3 = curs3 = NULL; prev1 = NULL; curs1 = node1; while (curs1) { prev2 = NULL; curs2 = node2; while (curs2 && !iso_nodes(curs1, curs2, kids, FALSE)) { prev2 = curs2; curs2 = nsibling(curs2); } if (curs2) { next2 = nsibling(curs2); nsibling(curs2) = NULL; if (node3) curs3 = nsibling(curs3) = curs2; else node3 = curs3 = curs2; if (prev2) nsibling(prev2) = next2; else node2 = next2; next1 = nsibling(curs1); nsibling(curs1) = NULL; free_nodes(curs1); if (prev1) nsibling(prev1) = next1; else node1 = next1; curs1 = next1; } else { prev1 = curs1; curs1 = nsibling(curs1); } } free_nodes(node1); free_nodes(node2); return node3; }
/*=============================================== * index_by_refn - Index node tree by REFN values * Adds all REFN values in node tree to refn index * Assumes all are new (only for use with brand new records) *=============================================*/ void index_by_refn (NODE node, STRING key) { if (!node || !key) return; for (node = nchild(node); node; node = nsibling(node)) { if (eqstr("REFN", ntag(node)) && nval(node)) add_refn(nval(node), key); } }
/*================================================================ * add_indi_no_cache -- Add new person to database * does not insert into cache * (used by import) * (no user interaction) *==============================================================*/ BOOLEAN add_indi_no_cache (NODE indi) { NODE node, name, refn, sex, body, famc, fams; STRING str, key; split_indi_old(indi, &name, &refn, &sex, &body, &famc, &fams); key = rmvat(nxref(indi)); for (node = name; node; node = nsibling(node)) add_name(nval(node), key); for (node = refn; node; node = nsibling(node)) if (nval(node)) add_refn(nval(node), key); join_indi(indi, name, refn, sex, body, famc, fams); resolve_refn_links(indi); str = node_to_string(indi); store_record(key, str, strlen(str)); stdfree(str); return TRUE; }
/*================================================================ * value_in_nodes -- See if a list of nodes contains a given value * UNUSED CODE *==============================================================*/ BOOLEAN value_in_nodes (NODE node, STRING value) { while (node) { if (eqstr(value, nval(node))) return TRUE; node = nsibling(node); } return FALSE; }
/*============================================== * check_node_recursively -- Check node and all descendant nodes *============================================*/ static void check_node_recursively (NODE node, NODE parent, INT level, CNSTRING key, CNSTRING scope) { NODE child=0; check_node(node, parent, level, key, scope); for (child = nchild(node); child; child = nsibling(child)) { check_node_recursively(child, node, level+1, key, scope); } }
/*======================================================================== * classify_nodes -- Convert two value lists to three lists - first * returned list holds all values that were only in original first * list - second returned list holds all values that were just in * original second list - third returned list holds all values that were * in both original lists * pnode1 & pnode2 are the input lists to compare * pnode1, pnode2, & pnode3 are the output lists. *======================================================================*/ void classify_nodes (NODE *pnode1, NODE *pnode2, NODE *pnode3) { NODE node1, node2, node3, curs1, curs2, curs3; NODE prev1, prev2, next2; curs1 = node1 = unique_nodes(*pnode1, FALSE); curs2 = node2 = unique_nodes(*pnode2, FALSE); curs3 = node3 = prev1 = prev2 = NULL; while (curs1 && curs2) { if (!nvaldiff(curs1, curs2)) { if (node3) curs3 = nsibling(curs3) = curs1; else node3 = curs3 = curs1; curs1 = nsibling(curs1); nsibling(curs3) = NULL; if (prev1) nsibling(prev1) = curs1; else node1 = curs1; next2 = nsibling(curs2); if (prev2) nsibling(prev2) = next2; else node2 = next2; nsibling(curs2) = NULL; free_nodes(curs2); curs2 = next2; continue; } prev2 = curs2; if ((curs2 = nsibling(curs2))) continue; prev1 = curs1; curs1 = nsibling(curs1); prev2 = NULL; curs2 = node2; } *pnode1 = node1; *pnode2 = node2; *pnode3 = node3; }
/*================================================= * count_nodes -- count descendent nodes *===============================================*/ static void count_nodes (NODE node, INT gen, INT maxgen, INT * count) { (*count)++; if (gen < maxgen) { NODE child; for (child = nchild(node); child; child = nsibling(child)) { count_nodes(child, gen+1, maxgen, count); } } }
/*============================================ * cgn_callback -- callback for name traversal * for checking for ghost names * Created: 2001/01/01, Perry Rapp *==========================================*/ static BOOLEAN cgn_callback (TRAV_NAMES_FUNC_ARGS(key, name, newset, param)) { /* a name record which points at indi=key */ RECORD indi0 = NULL; NODE indi = NULL; param=param; /* unused */ /* bail out immediately if not INDI */ if (key[0] != 'I') { report_error(ERR_NONINDINAME , _("Non-indi name, key=%s, name=%s") , key, name); return 1; /* continue traversal */ } indi0 = qkey_to_irecord(key); indi = nztop(indi0); if (newset) { finish_and_delete_nameset(); soundexseq = create_indiseq_sval(); } append_indiseq_sval(soundexseq, strsave(key), (STRING)name, strsave(name) , TRUE, TRUE); /* sure, alloc */ if (!indi) { report_error(ERR_ORPHANNAME, _("Orphaned name: %s"), name); if (todo.fix_ghosts) enqueue_list(tofix, (VPTR)alloc_namerefn(name, key, ERR_ORPHANNAME)); } else { NODE node; BOOLEAN found=FALSE; NODE nam, refn, sex, body, famc, fams; split_indi_old(indi, &nam, &refn, &sex, &body, &famc, &fams); for (node = nam; node; node = nsibling(node)) { if (!strcmp(nval(node), name)) found=TRUE; } join_indi(indi, nam, refn, sex, body, famc, fams); if (!found) { report_error(ERR_GHOSTNAME, _("Ghost name: %s -> %s"), name, key); if (todo.fix_ghosts) enqueue_list(tofix, (VPTR)alloc_namerefn(name, key, ERR_GHOSTNAME)); } } if (noisy) report_progress("Name: %s", name); return 1; /* continue traversal */ }
/*====================================================================== * unique_nodes -- Remove duplicates from list of nodes -- original list * is modified *====================================================================*/ NODE unique_nodes (NODE node, BOOLEAN kids) /* children matter */ { NODE node0 = node, prev, this, next; if (!node) return NULL; while (node) { prev = node; this = nsibling(node); while (this) { if (iso_nodes(node, this, kids, FALSE)) { nsibling(prev) = next = nsibling(this); nsibling(this) = NULL; free_nodes(this); this = next; } else { prev = this; this = nsibling(this); } } node = nsibling(node); } return node0; }
/*===================================== * swrite_nodes -- Write tree to string *===================================*/ static STRING swrite_nodes (INT levl, /* level */ NODE node, /* root */ STRING p) /* write string */ { while (node) { p = swrite_node(levl, node, p); if (nchild(node)) p = swrite_nodes(levl + 1, nchild(node), p); node = nsibling(node); } return p; }
/*================================= * trav_pre_print_nod -- traverse node tree, * printing nodes in preorder * Created: 2001/01/27, Perry Rapp *===============================*/ static void trav_pre_print_nod (NODE node, INT * row, INT gen, INT indent, CANVASDATA canvas, INT gdvw) { NODE child; struct node_print_param_s npp; npp.node = node; npp.gdvw = gdvw; /* all display printing passes thru generic print_to_screen, which handles scrolling */ print_to_screen(gen, indent, row, &node_lineprint, &npp, canvas); for (child=nchild(node); child; child=nsibling(child)) trav_pre_print_nod(child, row, gen+1, indent, canvas, gdvw); }
/*=============================== * valid_fam_tree -- Validate FAM tree * fam1, [IN] family to validate * pmsg: [OUT] error message, if any * fam0: [IN] family to match - may be NULL * Should be replaced by valid_fam(RECORD,...) ? *=============================*/ BOOLEAN valid_fam_tree (NODE fam1, STRING *pmsg, NODE fam0) { NODE refn0, husb0, wife0, chil0, body0; NODE refn1, husb1, wife1, chil1, body1; if (!fam1) { *pmsg = _(qSbademp); return FALSE; } if (nestr("FAM", ntag(fam1))) { *pmsg = _(qSbadfm0); return FALSE; } if (nsibling(fam1)) { *pmsg = _(qSbadmul); return FALSE; } refn0 = husb0 = wife0 = chil0 = body0 = NULL; if (fam0) split_fam(fam0, &refn0, &husb0, &wife0, &chil0, &body0); split_fam(fam1, &refn1, &husb1, &wife1, &chil1, &body1); if (fam0 && !iso_nodes(fam1, fam0, FALSE, TRUE)) { *pmsg = _(qSbadfam); goto bad3; } if (!iso_nodes(husb1, husb0, FALSE, TRUE)) { *pmsg = _(qSbadhsb); goto bad3; } if (!iso_nodes(wife1, wife0, FALSE, TRUE)) { *pmsg = _(qSbadwif); goto bad3; } if (!iso_nodes(chil1, chil0, FALSE, TRUE)) { *pmsg = _(qSbadchl); goto bad3; } if (fam0) join_fam(fam0, refn0, husb0, wife0, chil0, body0); join_fam(fam1, refn1, husb1, wife1, chil1, body1); return TRUE; bad3: if (fam0) join_fam(fam0, refn0, husb0, wife0, chil0, body0); join_fam(fam1, refn1, husb1, wife1, chil1, body1); return FALSE; }
/*========================================================== * add_new_indi_to_db -- Add newly created person to database * (no user interaction) * creates record & adds to cache *========================================================*/ static void add_new_indi_to_db (RECORD indi0) { NODE name, refn, sex, body, dumb, node; char key[MAXKEYWIDTH]=""; INT keynum=0; NODE indi = nztop(indi0); split_indi_old(indi, &name, &refn, &sex, &body, &dumb, &dumb); keynum = getixrefnum(); sprintf(key, "I%ld", keynum); init_new_record(indi0, key); for (node = name; node; node = nsibling(node)) { add_name(nval(node), key); } for (node = refn; node; node = nsibling(node)) { if (nval(node)) add_refn(nval(node), key); } join_indi(indi, name, refn, sex, body, NULL, NULL); resolve_refn_links(indi); indi_to_dbase(indi); add_new_indi_to_cache(indi0); }
/*========================================== * write_nodes -- Write NODEs to GEDCOM file *========================================*/ void write_nodes (INT levl, /* level */ FILE *fp, /* file */ XLAT ttm, /* char map */ NODE node, /* root */ BOOLEAN indent, /* indent? */ BOOLEAN kids, /* output kids? */ BOOLEAN sibs) /* output sibs? */ { if (!node) return; write_node(levl, fp, ttm, node, indent); if (kids) write_nodes(levl+1, fp, ttm, nchild(node), indent, TRUE, TRUE); if (sibs) write_nodes(levl, fp, ttm, nsibling(node), indent, kids, TRUE); }
/*=================================== * find_xref -- Search node for a cross-reference key * 2001/01/21, Perry Rapp *=================================*/ static BOOLEAN find_xref (CNSTRING key, NODE node, CNSTRING tag1, CNSTRING tag2) { NODE node2; CACHEEL ncel2; BOOLEAN found=FALSE; ncel2 = node_to_cacheel_old(node); lock_cache(ncel2); for (node2 = nchild(node); node2; node2 = nsibling(node2)) { if (eqstr(tag1, ntag(node2)) || (tag2 && eqstr(tag2, ntag(node2)))) { STRING key2 = rmvat(nval(node2)); if (key2 && eqstr(key, key2)) { found = TRUE; goto exit_find; } } } exit_find: unlock_cache(ncel2); return found; }
/*============================================== * union_nodes -- Return union of two node trees *============================================*/ NODE union_nodes (NODE node1, NODE node2, BOOLEAN kids, /* children matter */ BOOLEAN copy) /* copy operands first */ { NODE curs1, next1, prev1, curs2, prev2; if (copy) node1 = copy_nodes(node1, TRUE, TRUE); if (copy) node2 = copy_nodes(node2, TRUE, TRUE); prev2 = NULL; curs2 = node2; while (curs2) { prev1 = NULL; curs1 = node1; while (curs1 && !iso_nodes(curs1, curs2, kids, FALSE)) { prev1 = curs1; curs1 = nsibling(curs1); } if (curs1) { next1 = nsibling(curs1); nsibling(curs1) = NULL; free_nodes(curs1); if (prev1) nsibling(prev1) = next1; else node1 = next1; } prev2 = curs2; curs2 = nsibling(curs2); } if (prev2) { nsibling(prev2) = node1; return node2; } return node1; }
/*=================================== * valid_indi_tree -- Validate person tree * indi1: [IN] person to validate * pmsg: [OUT] error message, if any * orig: [IN] person to match - may be NULL * rtn: FALSE for bad * Should be replaced by valid_indi(RECORD,...) ? *=================================*/ BOOLEAN valid_indi_tree (NODE indi1, STRING *pmsg, NODE orig) { NODE name1, refn1, sex1, body1, famc1, fams1, node; NODE name0, refn0, sex0, body0, famc0, fams0; INT isex, num; STRING *keys, ukey; if (!indi1) { *pmsg = _(qSbademp); return FALSE; } if (nestr("INDI", ntag(indi1))) { *pmsg = _(qSbadin0); return FALSE; } if (nsibling(indi1)) { *pmsg = _(qSbadmul); return FALSE; } split_indi_old(indi1, &name1, &refn1, &sex1, &body1, &famc1, &fams1); if (getlloptint("RequireNames", 0) && !name1) { *pmsg = _("This person record does not have a name line."); goto bad2; } for (node = name1; node; node = nsibling(node)) { if (!valid_name(nval(node))) { *pmsg = _(qSbadenm); goto bad2; } } name0 = refn0 = sex0 = body0 = famc0 = fams0 = NULL; if (orig) split_indi_old(orig, &name0, &refn0, &sex0, &body0, &famc0, &fams0); if (orig && !iso_nodes(indi1, orig, FALSE, FALSE)) { *pmsg = _(qSbadind); goto bad1; } if (!iso_nodes(famc1, famc0, FALSE, TRUE)) { *pmsg = _(qSbadfmc); goto bad1; } if (!iso_nodes(fams1, fams0, FALSE, TRUE)) { *pmsg = _(qSbadfms); goto bad1; } isex = val_to_sex(sex0); if (!fams0) isex = SEX_UNKNOWN; if (isex != SEX_UNKNOWN && isex != val_to_sex(sex1)) { *pmsg = _(qSbadparsex); goto bad1; } ukey = (refn1 ? nval(refn1) : NULL); get_refns(ukey, &num, &keys, 'I'); if (num > 1 || (num == 1 && (!orig || nestr(keys[0], rmvat(nxref(indi1)))))) { *pmsg = _(qSbadirefn); goto bad1; } if (orig) join_indi(orig, name0, refn0, sex0, body0, famc0, fams0); join_indi(indi1, name1, refn1, sex1, body1, famc1, fams1); return TRUE; bad1: if (orig) join_indi(orig, name0, refn0, sex0, body0, famc0, fams0); bad2: join_indi(indi1, name1, refn1, sex1, body1, famc1, fams1); return FALSE; }
/*============================================================== * next_fp_to_node -- Convert next GEDCOM record in file to tree * * fp: [IN] file that holds GEDCOM record/s * list: [IN] can be list at level 0? * ttm: [IN] character translation table * pmsg: [OUT] possible error message * peof: [OUT] set true if file is at end of file * callers should probably be converted to calling next_fp_to_record *============================================================*/ NODE next_fp_to_node (FILE *fp, BOOLEAN list, XLAT ttm, STRING *pmsg, BOOLEAN *peof) { INT curlev, bcode, rc; NODE root, node, curnode; static char scratch[100]; /*we return errors with this, keep it around*/ *pmsg = NULL; *peof = FALSE; if (ateof) { ateof = *peof = TRUE; lahead = FALSE; return NULL; } if (!lahead) { rc = file_to_line(fp, ttm, &lev, &xref, &tag, &val, pmsg); if (rc == DONE) { ateof = *peof = TRUE; return NULL; } else if (rc == ERROR) return NULL; lahead = TRUE; } curlev = lev; if (curlev != lev0) { *pmsg = _(qSrerwlv); return NULL; } root = curnode = create_node(xref, tag, val, NULL); bcode = OKAY; rc = file_to_line(fp, ttm, &lev, &xref, &tag, &val, pmsg); while (rc == OKAY) { if (lev == curlev) { if (lev == lev0 && !list) { bcode = DONE; break; } node = create_node(xref, tag, val, nparent(curnode)); nsibling(curnode) = node; curnode = node; } else if (lev == curlev + 1) { node = create_node(xref, tag, val, curnode); nchild(curnode) = node; curnode = node; curlev = lev; } else if (lev < curlev) { if (lev < lev0) { sprintf(scratch, _(qSrerilv), flineno); *pmsg = scratch; bcode = ERROR; break; } if (lev == lev0 && !list) { bcode = DONE; break; } while (lev < curlev) { curnode = nparent(curnode); curlev--; } node = create_node(xref, tag, val, nparent(curnode)); nsibling(curnode) = node; curnode = node; } else { sprintf(scratch, _(qSrerilv), flineno); *pmsg = scratch; bcode = ERROR; break; } rc = file_to_line(fp, ttm, &lev, &xref, &tag, &val, pmsg); } if (bcode == DONE) return root; if (bcode == ERROR || rc == ERROR) { free_nodes(root); return NULL; } lahead = FALSE; ateof = *peof = TRUE; return root; }
/*===================================== * process_fam -- process indi record * checking in pass 1, fixing in pass 2 *===================================*/ static void process_fam (RECORD rec) { NODE fam0, fam1; NODE fref1, husb1, wife1, chil1, rest1; NODE node1; INT members = 0; BOOLEAN altered=FALSE; BOOLEAN needfix=FALSE; CNSTRING key = nzkey(rec); fam0 = nztop(rec); if (todo.pass==1) { fam1 = fam0; } else { fam1 = copy_node_subtree(fam0); } split_fam(fam1, &fref1, &husb1, &wife1, &chil1, &rest1); /* check refns */ for (node1 = fref1; node1; node1 = nsibling(node1)) { /* STRING refn=nval(node1); */ /* TO DO: verify that refn is in db */ } /* check husbs */ for (node1 = husb1; node1; node1 = nsibling(node1)) { STRING husbkey=rmvat(nval(node1)); NODE husb = qkey_to_indi(husbkey); members++; if (!husb) { if (todo.pass == 1) { report_error(ERR_BADHUSBREF , _("Bad HUSB reference (%s) in family %s") , husbkey, key); needfix=TRUE; } else { if (fix_bad_pointer(key, rec, node1)) { report_fix(ERR_BADHUSBREF , _("Fixed Bad HUSB reference (%s) in family %s") , husbkey, key); altered=TRUE; } } } else { /* look for family (key) in husb */ if (!find_xref(key, husb, "FAMS", NULL)) { report_error(ERR_EXTRAHUSB , _("Improper HUSB (%s) in family (%s)") , husbkey, key); } } } /* check wives */ for (node1 = wife1; node1; node1 = nsibling(node1)) { STRING wifekey=rmvat(nval(node1)); NODE wife = qkey_to_indi(wifekey); members++; if (!wife) { if (todo.pass == 1) { report_error(ERR_BADWIFEREF , _("Bad wife reference (%s) in family %s") , wifekey, key); needfix=TRUE; } else { if (fix_bad_pointer(key, rec, node1)) { report_fix(ERR_BADWIFEREF , _("Fixed Bad wife reference (%s) in family %s") , printkey(wifekey), key); altered=TRUE; } } } else { /* look for family (key) in wife */ if (!find_xref(key, wife, "FAMS", NULL)) { report_error(ERR_EXTRAWIFE , _("Improper wife (%s) in family (%s)") , printkey(wifekey), key); } } } /* check children */ for (node1 = chil1; node1; node1 = nsibling(node1)) { STRING chilkey=rmvat(nval(node1)); NODE child = qkey_to_indi(chilkey); members++; if (!child) { if (todo.pass == 1) { report_error(ERR_BADCHILDREF , _("Bad child reference (%s) in family %s") , printkey(chilkey), key); needfix=TRUE; } else { if (fix_bad_pointer(key, rec, node1)) { report_fix(ERR_BADCHILDREF , _("Fixed bad child reference (%s) in family %s") , printkey(chilkey), key); altered=TRUE; } } } else { /* look for family (key) in child */ if (!find_xref(key, child, "FAMC", NULL)) { report_error(ERR_EXTRACHILD , _("Improper child: Child (%s) without FAMC reference to family (%s)") , printkey(chilkey), key); } } } join_fam(fam1, fref1, husb1, wife1, chil1, rest1); /* check for undersized family */ if (!members) { report_error(ERR_EMPTYFAM, _("Empty family (%s)"), key); } else if (members == 1) { report_error(ERR_SOLOFAM, _("Single person family (%s)"), key); } if (altered) { /* must normalize, as some lineage references may have been altered to non-lineage tags to fix broken pointers */ normalize_fam(fam1); /* write to database */ replace_fam(fam0, fam1); } else if (needfix) { enqueue_list(tofix, strsave(key)); } }
/*===================================== * process_indi -- process indi record * checking in pass 1, fixing in pass 2 *===================================*/ static void process_indi (RECORD rec) { NODE indi0, indi1; NODE name1, refn1, sex1, body1, famc1, fams1; NODE node1; BOOLEAN altered=FALSE; BOOLEAN needfix=FALSE; CNSTRING key = nzkey(rec); indi0 = nztop(rec); if (todo.pass==1) { indi1 = indi0; } else { indi1 = copy_node_subtree(indi0); } split_indi_old(indi1, &name1, &refn1, &sex1, &body1, &famc1, &fams1); if (todo.pass == 1) { /* check names */ for (node1 = name1; node1; node1 = nsibling(node1)) { STRING name=nval(node1); if (!valid_name(name)) { report_error(ERR_BADNAME, _("Bad name for individual %s: %s"), key, name); } else { /* TO DO: verify that name is in db */ } } /* check refns */ for (node1 = refn1; node1; node1 = nsibling(node1)) { /* STRING refn=nval(node1); */ /* TO DO: verify that refn is in db */ } } /* check parents */ for (node1 = famc1; node1; node1 = nsibling(node1)) { STRING famkey=rmvat(nval(node1)); NODE fam2 = qkey_to_fam(famkey); if (!fam2) { if (todo.pass == 1) { report_error(ERR_BADFAMREF, _("Bad family reference (%s) individual %s"), famkey, key); } } else { /* look for indi1 (key) in fam2's children */ if (!find_xref(key, fam2, "CHIL", NULL)) { if (todo.pass == 1) { report_error(ERR_MISSINGCHILD, _("Missing child (%s) in family (%s)"), key, famkey); needfix=TRUE; } else { if (fix_bad_pointer(key, rec, node1)) { report_fix(ERR_MISSINGCHILD, _("Fixed missing child (%s) in family (%s)"), key, famkey); altered=TRUE; } } } } } /* check spouses */ for (node1 = fams1; node1; node1 = nsibling(node1)) { STRING famkey=rmvat(nval(node1)); NODE fam2 = qkey_to_fam(famkey); if (!fam2) { if (todo.pass == 1) { report_error(ERR_BADFAMREF, _("Bad family reference (%s) individual %s"), famkey, key); } } else { /* look for indi1 (key) in fam2's spouses */ if (!find_xref(key, fam2, "HUSB", "WIFE")) { if (todo.pass == 1) { report_error(ERR_MISSINGSPOUSE, _("Missing spouse (%s) in family (%s)"), key, famkey); needfix=TRUE; } else { if (fix_bad_pointer(key, rec, node1)) { report_fix(ERR_MISSINGSPOUSE, _("Fixed missing spouse (%s) in family (%s)"), key, famkey); altered=TRUE; } } } } } join_indi(indi1, name1, refn1, sex1, body1, famc1, fams1); if (altered) { /* must normalize, as some lineage references may have been altered to non-lineage tags to fix broken pointers */ normalize_indi(indi1); /* write to database */ replace_indi(indi0, indi1); } else if (needfix) { enqueue_list(tofix, strsave(key)); } }
/*================================================ * edit_add_record -- Add record to database by editing * recstr: [IN] default record * redt: [IN] re-edit message * ntype, [IN] S, E, or X * cfrm: [IN] confirm message *==============================================*/ static RECORD edit_add_record (STRING recstr, STRING redt, STRING redtopt, char ntype, STRING cfrm) { FILE *fp; NODE node=0, refn; STRING msg, key; BOOLEAN emp; XLAT ttmi = transl_get_predefined_xlat(MEDIN); STRING (*getreffnc)(void) = NULL; /* get next internal key */ void (*todbasefnc)(NODE) = NULL; /* write record to dbase */ void (*tocachefnc)(NODE) = NULL; /* write record to cache */ /* set up functions according to type */ if (ntype == 'S') { getreffnc = getsxref; todbasefnc = sour_to_dbase; tocachefnc = sour_to_cache; } else if (ntype == 'E') { getreffnc = getexref; todbasefnc = even_to_dbase; tocachefnc = even_to_cache; } else { /* X */ getreffnc = getxxref; todbasefnc = othr_to_dbase; tocachefnc = othr_to_cache; } /* Create template for user to edit */ if (!(fp = fopen(editfile, LLWRITETEXT))) { msg_error(_(qSnofopn), editfile); return FALSE; } prefix_file_for_edit(fp); fprintf(fp, "%s\n", recstr); /* Have user edit new record */ fclose(fp); do_edit(); while (TRUE) { INT cnt; node = file_to_node(editfile, ttmi, &msg, &emp); if (!node) { if (ask_yes_or_no_msg(msg, redt)) { /* yes, edit again */ do_edit(); continue; } break; } cnt = resolve_refn_links(node); /* check validation & allow user to reedit if invalid */ /* this is a showstopper, so alternative is to abort */ if (!valid_node_type(node, ntype, &msg, NULL)) { if (ask_yes_or_no_msg(msg, redt)) { do_edit(); continue; } free_nodes(node); node = NULL; /* fail out */ break; } /* Allow user to reedit if desired if any refn links unresolved */ /* this is not a showstopper, so alternative is to continue */ if (cnt > 0) { char msgb[120]; snprintf(msgb, sizeof(msgb) , get_unresolved_ref_error_string(cnt), cnt); if (ask_yes_or_no_msg(msgb, redtopt)) { write_node_to_editfile(node); do_edit(); continue; } } break; } if (!node || !ask_yes_or_no(cfrm)) { if (node) free_nodes(node); return NULL; } nxref(node) = strsave((STRING)(*getreffnc)()); key = rmvat(nxref(node)); for (refn = nchild(node); refn; refn = nsibling(refn)) { if (eqstr("REFN", ntag(refn)) && nval(refn)) add_refn(nval(refn), key); } (*todbasefnc)(node); (*tocachefnc)(node); return key_to_record(key); }
/*======================================= * edit_record -- Edit record in database * root1: [IN] record to edit (may be NULL) * idedt: [IN] user id prompt * letr: [IN] record type (E, S, or X) * redt: [IN] reedit prompt displayed if hard error after editing * redtopt: [IN] reedit prompt displayed if soft error (unresolved links) * val: [IN] callback to validate routine * cfrm: [IN] confirmation msg string * tag: [IN] tag (SOUR, EVEN, or NULL) * todbase: [IN] callback to write record to dbase * gdmsg: [IN] success message * rfmt: [IN] display reformatter *=====================================*/ static BOOLEAN edit_record (RECORD rec1, STRING idedt, INT letr, STRING redt, STRING redtopt , BOOLEAN (*val)(NODE, STRING *, NODE) , STRING cfrm, void (*todbase)(NODE), STRING gdmsg , RFMT rfmt) { XLAT ttmi = transl_get_predefined_xlat(MEDIN); STRING msg, key; BOOLEAN emp; NODE root0=0, root1=0, root2=0; NODE refn1=0, refn2=0, refnn=0, refn1n=0; NODE body=0, node=0; /* Identify record if need be */ if (!rec1) { rec1 = ask_for_record(idedt, letr); } root1 = nztop(rec1); if (!root1) { message(_(qSnosuchrec)); return FALSE; } /* Have user edit record */ annotate_with_supplemental(root1, rfmt); write_node_to_editfile(root1); resolve_refn_links(root1); do_edit(); if (readonly) { root2 = file_to_node(editfile, ttmi, &msg, &emp); if (!equal_tree(root1, root2)) message(_(qSronlye)); free_nodes(root2); return FALSE; } while (TRUE) { INT cnt; root2 = file_to_node(editfile, ttmi, &msg, &emp); if (!root2) { if (ask_yes_or_no_msg(msg, redt)) { do_edit(); continue; } break; } cnt = resolve_refn_links(root2); /* check validation & allow user to reedit if invalid */ /* this is a showstopper, so alternative is to abort */ if (!(*val)(root2, &msg, root1)) { if (ask_yes_or_no_msg(msg, redt)) { do_edit(); continue; } free_nodes(root2); root2 = NULL; break; } /* Allow user to reedit if desired if any refn links unresolved */ /* this is not a showstopper, so alternative is to continue */ if (cnt > 0) { char msgb[120]; snprintf(msgb, sizeof(msgb) , get_unresolved_ref_error_string(cnt), cnt); if (ask_yes_or_no_msg(msgb, redtopt)) { write_node_to_editfile(root2); do_edit(); continue; } } break; } /* If error or no change or user backs out return */ if (!root2) return FALSE; if (equal_tree(root1, root2) || !ask_yes_or_no(cfrm)) { free_nodes(root2); return FALSE; } /* Prepare to change database */ /* Move root1 data into root0 & save refns */ split_othr(root1, &refn1, &body); root0 = copy_node(root1); join_othr(root0, NULL, body); /* delete root0 tree & root1 node (root1 is solitary node) */ free_nodes(root0); root0 = 0; free_nodes(root1); root1 = 0; /* now copy root2 node into root1, then root2 tree under it */ root1 = copy_node(root2); split_othr(root2, &refn2, &body); refnn = copy_nodes(refn2, TRUE, TRUE); join_othr(root1, refn2, body); /* now root2 is solitary node, delete it */ free_node(root2); root2 = 0; /* Change the database */ (*todbase)(root1); key = rmvat(nxref(root1)); /* remove deleted refns & add new ones */ classify_nodes(&refn1, &refnn, &refn1n); for (node = refn1; node; node = nsibling(node)) if (nval(node)) remove_refn(nval(node), key); for (node = refnn; node; node = nsibling(node)) if (nval(node)) add_refn(nval(node), key); free_nodes(refn1); free_nodes(refnn); free_nodes(refn1n); msg_info(gdmsg); return TRUE; }
/*================================================= * add_dnodes -- add dnodes to dnode tree * recursively, traversing NODE tree & building corresponding * dnode tree * if a line overflows, give it succeeding sibling dnodes * also, check for subordinate CONT & CONC dnodes to be assimilated *===============================================*/ static DISPNODE add_dnodes (NODE node, INT gen, INT indent, INT maxgen, INT * count, CANVASDATA canvas) { DISPNODE tn; DISPNODE tn0, tn1, tn2; NODE child, anode; INT width = (canvas->rect->right - canvas->rect->left) - 2 - gen*indent; static char line[MAXLINELEN], output[MAXLINELEN]; /* must be same size */ STRING ptr=output; INT leader; LIST list=NULL; INT mylen=sizeof(output), mylenorig; if (mylen>width) mylen = width; mylenorig = mylen; /* build xref & tag into line */ line[0] = 0; ptr = line; mylen = sizeof(line); if (nxref(node)) { llstrcatn(&ptr, nxref(node), &mylen); llstrcatn(&ptr, " ", &mylen); } if (ntag(node)) { llstrcatn(&ptr, ntag(node), &mylen); llstrcatn(&ptr, " ", &mylen); } leader = ptr-line; width -= leader; if (width < 10) { /* insufficient space */ return NULL; } /* output is available as scratch */ list = text_to_list("", width, LISTDOFREE); if (nval(node)) { STRING valtxt = nval(node); append_to_text_list(list, valtxt, width, FALSE); } /* anode is first child */ anode = nchild(node); /* check for text continuation nodes to assimilate */ if (nchild(node)) { for ( ; anode && !nchild(anode); anode = nsibling(anode)) { BOOLEAN newline=FALSE; STRING valtxt=NULL; if (eqstr(ntag(anode), "CONC")) { append_to_text_list(list, " ", width, FALSE); newline = FALSE; } else if (eqstr(ntag(anode), "CONT")) { newline = TRUE; } else { break; } valtxt = nval(anode); append_to_text_list(list, valtxt, width, newline); } } /* anode is now first non-assimilated child */ /* now add all list elements to tree as siblings first one will be tn, which we return as our result tn0 refers to previous one, for the nsibling links */ tn = tn0 = tn1 = 0; FORLIST(list, el) tn1 = alloc_displaynode(); if (!tn) { INT i; tn = tn1; /* ptr & mylen still point after leader */ llstrcatn(&ptr, el, &mylen); /* put original line */ tn1->str = strsave(line); /* now build leader we will keep reusing */ for (i=0; i<leader; i++) line[i] = '.'; line[leader-1] = ' '; } else { llstrcatn(&ptr, el, &mylen); tn1->str = strsave(line); } /* now we keep resetting ptr & mylen to after blank leader */ /* so we keep reusing that leader we built in line earlier */ ptr=line+leader; mylen=mylenorig-leader; tn1->firstchild = 0; tn1->nextsib = 0; if (tn0) tn0->nextsib = tn1; tn0 = tn1; (*count)++; ENDLIST /* special handling for empty list, which didn't get its leader */ if (is_empty_list(list)) { tn1 = alloc_displaynode(); tn = tn1; tn1->str = strsave(line); tn1->firstchild = 0; tn1->nextsib = 0; tn0 = tn1; (*count)++; } destroy_list(list); list=0; if (gen < maxgen) { /* our children hang off of tn2, which is last node of our sibling tree; tn0 is previously added child */ tn2 = tn1; tn0 = 0; /* anode was last unassimilated child */ for (child = anode; child; child = nsibling(child)) { tn1 = add_dnodes(child, gen+1, indent, maxgen, count, canvas); if (!tn1) continue; /* child was skipped */ /* link new displaynode into tree we're building */ if (tn0) tn0 = tn0->nextsib = tn1; else /* first child - first time thru loop */ tn0 = tn2->firstchild = tn1; /* child displaynode might have (overflow or assimilated) siblings */ while (tn0->nextsib) tn0 = tn0->nextsib; } } return tn; }
/*======================================= * llrpt_addnode -- Add a node to a GEDCOM tree * usage: addnode(NODE, NODE, NODE) -> VOID * args: (node being added, parent, previous child) *=====================================*/ PVALUE llrpt_addnode (PNODE node, SYMTAB stab, BOOLEAN *eflg) { PNODE arg = iargs(node); NODE newchild, next, prnt, prev; /* first argument, node (must be nonnull) */ PVALUE val = eval_and_coerce(PGNODE, arg, stab, eflg); if (*eflg) { prog_var_error(node, stab, arg, val, nonnodx, "addnode", "1"); delete_pvalue(val); return NULL; } newchild = remove_node_and_delete_pvalue(&val); if (!newchild) { prog_var_error(node, stab, arg, val, nonnodx, "addnode", "1"); return NULL; } /* second argument, parent (must be nonnull) */ val = eval_and_coerce(PGNODE, arg=inext(arg), stab, eflg); if (*eflg) { prog_var_error(node, stab, arg, val, nonnodx, "addnode", "2"); return NULL; } prnt = remove_node_and_delete_pvalue(&val); if (!prnt) { prog_var_error(node, stab, arg, val, nonnodx, "addnode", "2"); return NULL; } /* third argument, prior sibling (may be null) */ val = eval_and_coerce(PGNODE, arg=inext(arg), stab, eflg); if (*eflg) { prog_var_error(node, stab, arg, val, nonnodx, "addnode", "3"); delete_pvalue(val); return NULL; } prev = remove_node_and_delete_pvalue(&val); if (prev) { /* Check that previous sibling actually is child of new parent */ if (prnt != nparent(prev)) { prog_error(node, "2nd arg to addnode must be parent of 3rd arg"); *eflg = 1; return NULL; } } /* reparent node, but ensure its locking is only relative to new parent */ dolock_node_in_cache(newchild, FALSE); nparent(newchild) = prnt; newchild->n_cel = prnt->n_cel; set_temp_node(newchild, is_temp_node(prnt)); dolock_node_in_cache(newchild, TRUE); if (prev == NULL) { next = nchild(prnt); nchild(prnt) = newchild; } else { next = nsibling(prev); nsibling(prev) = newchild; } nsibling(newchild) = next; return NULL; }