// twho.c // // by John D. de Boer #include "twist.h" extern TimeGlobs Time; int PEOPLE; // number of people Person ***People; // the people const bool Verbose = 1; // whether broken hyperlinks are reported as errors static stringlist UPList; // person names that were not found // bool VoidIsPers(const void *V) { return V && ((Person *)V)->leg=='PERS'; } // Allocation void InitPeople(void) { PEOPLE=0; People=(Person ***)handle(0); initstringlist(&UPList); } Person *NewPerson(bool AddToList) { Person *R; R=pointer(sizeof(Person)); if (AddToList) { minimumsize(People,PEOPLE + 1,sizeof(Person *)); (*People)[PEOPLE++]=R; } R->leg='PERS'; R->name=R->surname=R->aka=R->info=R->ref=R->src=R->cap=NULL; R->sex='M'; R->trans=0; R->nocc=0; R->ambig=0; R->followed=0; InitSpan(&(R->life)); R->nrgn=0; R->inbnd.num=0; R->inbnd.link=NULL; R->nmarr=0; R->wed=NULL; R->father=R->mother=NULL; R->faref=R->moref=NULL; R->fagrand=R->mogrand=0; R->nchild=-1; R->children=NULL; R->chgrand=NULL; R->gen1=R->gen2=NULL; R->mingen=1000.0; R->home=NULL; R->nation=NULL; R->pob=NULL; R->pod=NULL; R->trav=NULL; R->pict=NULL; return R; } static void OldPerson(Person *P) { if (!P) return; old(P->name); old(P->surname); old(P->aka); old(P->info); old(P->ref); old(P->src); old(P->gen1); old(P->gen2); release(P->children); release(P->chgrand); OldTravels(P->trav); release(P->inbnd.link); old(P); } void PurgePeople(void) { while (PEOPLE>0) OldPerson((*People)[--PEOPLE]); purgestringlist(&UPList); } // void AddOccupation(Person *P, int OCC) { if (!P || P->nocc>=MAXOCC || OCC<0) return; P->occ[P->nocc++]=OCC; } static bool BornEarlier(const void *A, const void *B) { Span *P,*Q; if (!A || !B) return 0; P=&(((Person *)A)->life); Q=&(((Person *)B)->life); if ((P->start.kind==timeInval)!=(Q->start.kind==timeInval)) return Q->start.kind==timeInval; return P->start.jd<=Q->start.jd; } void OrderPeopleByDOB(void) { lock(People); sort(*People,PEOPLE,&BornEarlier); unlock(People); } // Resolving references to people static void UnknownPerson(const char *S) { if (stringisinlist(&UPList,S)) return; NoteOld(cat(0,"Unresolved reference: ",(char *)S)); addstringtolist(&UPList,copyof(S)); } static void CircularPersonLink(const Person *P, const char *S) { SetItemFileName(P); NoteOld(cat(0,"Self-referential link: ",(char *)S)); } static Person *FindPers(const char *S, const Span *L, bool GenErr) { int i; Person *P; if (!S || !*S) return NULL; for (i=0;iname) && !streq(S,P->surname) && !streq(S,P->aka)) continue; if (GenErr && Verbose && P->ambig && (streq(S,P->name) || streq(S,P->surname))) WarnOld(cat(0,"Ambiguous reference to a person: ",(char *)S)); if (!L || SpansOverlap(L,&(P->life),0)) return P; } return NULL; } Person *FindPerson(const char *S, const Span *L, bool GenErr, const void *From) { Person *P; if (!S) return NULL; if (L) { P=FindPers(S,L,GenErr); if (P) { if (P==From) CircularPersonLink(P,S); return P; } } P=FindPers(S,NULL,GenErr); if (P || !GenErr) return P; SetItemFileName(From); UnknownPerson(S); return NULL; } // Verification after reading in a new person record static const long EightyYears = 29220L; static const int GestPeriod = 270; static bool isromannumeral(const char *S) { if (!S) return 0; do { if (!firstocc(*S,"IVX")) return 0; S++; } while (*S); return 1; } static void GuessSurname(Person *P) { char *S; bool RN; if (!P) return; P->surname=copyof(P->name); S=last(P->surname); RN=isromannumeral(S); old(S); if (RN) return; if (position(" of ",P->surname) || position(" the ",P->surname)) return; while (firstocc(' ',P->surname) && uppercase(P->surname[0])) { S=P->surname; P->surname=firstrem(S); old(S); } if (FamilyMember(P)) P->surname=cat3(1,leftstr(P->name,1),". ",P->surname); } static void VerifyParent(Person *C, Person *P, bool Grand) { Span S; if (!C || C->life.start.kind!=timeJD) return; if (!P || P->life.start.kind!=timeJD) return; if (C->life.start.jd<=P->life.start.jd) { SetItemFileName(C); WarnOld(cat4(0,"Misidentification of ",P->name," as ancestor of ",C->name)); return; } if (Grand) return; S=P->life; S.start.jd+= 10 * 365; if (P->sex=='M') S.end.jd+=GestPeriod; if (DateInSpan(&(C->life.start),&S,0)) return; SetItemFileName(C); WarnOld(cat4(0,"Possible misidentification of ",P->name," as parent of ",C->name)); } static void CheckForDuplicate(Person *P) { Person *Dup; bool Overlap; if (!P) return; Dup=FindPerson(P->name,NULL/*&(P->life)*/,0,NULL); if (!Dup || Dup==P) return; P->ambig=1; Dup->ambig=1; if (P->aka && Dup->aka && !streq(P->aka,Dup->aka)) return; if (P->aka && !Dup->aka) return; if (Dup->aka && !P->aka) return; Overlap=SpansOverlap(&(P->life),&(Dup->life),1); if (!Overlap) WarnOld(cat4(0,"Similarly-named people: ",Dup->name,", ",P->name)); else WarnOld(cat4(0,"Possible duplicate person: ",Dup->name,", ",P->name)); } void VerifyNewPerson(Person *P) { Day *Bi,*De; if (!P) return; Bi=&(P->life.start); De=&(P->life.end); if (!P->name) { Warning("Person with no name"); return; } if (P->nocc<1) AddOccupation(P,ParseOccupation("other",NULL)); if (!P->surname && P->name) GuessSurname(P); if (De->kind==timeInval && Bi->kind==timeJD && Bi->jd>2406254L/*about 1876 Jan 1*/) { De->jd=Time.jd2; /*Latter value can change*/ De->unc=0; De->kind=timeStill; } if (De->kind==timeInval && Bi->kind==timeJD) { *De=*Bi; De->jd+=EightyYears; De->unc+= 20 * 365; De->guess=1; } if (Bi->kind==timeInval && De->kind==timeJD) { *Bi=*De; Bi->jd-=EightyYears; Bi->unc+= 20 * 365; Bi->guess=1; } if (De->kind==timeInval/* && Bi->kind==timeInval*/) DangerOld(cat3(0,"Date of death of ",P->name," is missing or has bad format")); if (De->kind==timeJD && Bi->kind==timeJD && Bi->jd>De->jd) DangerOld(cat3(0,"Dates of birth and death of ",P->name," are in wrong order")); if (P->nrgn>0) { Span *Rg; Rg= P->reign; if (Rg->start.jdjd) WarnOld(cat3(0,"Term of office for ",P->name," begins before lifespan")); Rg= P->reign + P->nrgn - 1; if (Rg->end.kind==timeInval) Rg->end=*De; if (Rg->end.jd>De->jd) WarnOld(cat3(0,"Term of office for ",P->name," extends beyond lifespan")); } if (P->trav) VerifyTravels(P->trav,&(P->life),P->name); if (!P->home) P->home=P->pob; if (!P->home) P->home=P->pod; if (!P->home) P->home=P->nation; if (!P->home) WarnOld(cat(0,"No place of residence given for ",P->name)); PlaceReference(P->nation,&(P->life)); PlaceReference(P->home,&(P->life)); PlaceReference(P->pob,&(P->life)); PlaceReference(P->pod,&(P->life)); if (Verbose) CheckForDuplicate(P); } // Database delta static int NumberOfLinks(char *S) { int H; char *F; H=0; if (!S) return 0; while (F=firstocc('@',S)) { S= F + 1; H++; } return H / 3; } void CheckPersonDelta(Person *P) { Person *Ex; bool Guess,Still; if (!P) return; Ex=FindPerson(P->name,NULL,0,NULL); if (Ex) { Guess=(P->life.start.kind==timeInval && Ex->life.start.guess); if (!DatesEqual(&(P->life.start),&(Ex->life.start)) && !Guess) NoteOld(cat(0,"Birthdate changed for ",P->name)); Guess=(P->life.end.kind==timeInval && Ex->life.end.guess); Still=(P->life.end.kind==timeInval && Ex->life.end.kind==timeStill); if (!DatesEqual(&(P->life.end),&(Ex->life.end)) && !Guess && !Still) NoteOld(cat(0,"Date of death changed for ",P->name)); if (P->nmarr>Ex->nmarr) NoteOld(cat(0,"Marriage data changed for ",P->name)); if (P->pob && !Ex->pob) NoteOld(cat(0,"Place of birth added for ",P->name)); if (!P->pob && Ex->pob) NoteOld(cat(0,"Place of birth missing for ",P->name)); if (!streq(P->aka,Ex->aka)) NoteOld(cat3(0,"Alternate name of ",P->name," changed")); if (P->surname && !streq(P->surname,Ex->surname)) NoteOld(cat3(0,"Surname of ",P->name," changed")); if (abso(length(P->info) - length(Ex->info)) > 5) NoteOld(cat3(0,"Info para for ",P->name," changed significantly")); //else if (!streq(P->info,Ex->info)) NoteOld(cat3(0,"Info para for ",P->name," changed")); if (NumberOfLinks(P->info)!=NumberOfLinks(Ex->info)) NoteOld(cat3(0,"Number of hyperlinks from ",P->name," changed")); } else WarnOld(cat(0,"New person definition: ",P->name)); OldPerson(P); } // Marriages static bool BetterDate(Day A, Day B) { if (A.kind!=timeJD) return 0; if (B.kind!=timeJD) return 1; return A.uncon),&(D->on),0); } static void ReciprocateWedding(Wedding *W, Person *P) { int i; Person *B; Wedding *X; if (!W || !P) return; B=W->to; if (!B) return; if (!B->wed) B->wed=(Wedding ***)handle(0); for (i=0;inmarr;i++) { X=(*(B->wed))[i]; if (!X) continue; //if (!DatesEqual(&(W->on),&(X->on))) continue; if (X->spref) { if (streq(X->spref,P->name) || streq(X->spref,P->surname) || streq(X->spref,P->aka)) { X->to=(Person *)P; old(X->spref); X->spref=NULL; } } if (X->to==P) { if (BetterDate(W->on,X->on)) X->on=W->on; if (BetterDate(X->on,W->on)) W->on=X->on; if (W->on.kind==timeJD && X->on.kind==timeJD && W->on.jd!=X->on.jd) WarnOld(cat4(0,"Inconsistent dates in marriage fields for ",P->name," and ",B->name)); if (X->at && !W->at) W->at=X->at; if (W->at && !X->at) X->at=W->at; if (W->at && X->at && W->at!=X->at) WarnOld(cat4(0,"Inconsistent places in marriage fields for ",P->name," and ",B->name)); return; } } X=pointer(sizeof(Wedding)); *X=*W; X->to=P; minimumsize(B->wed,B->nmarr + 1,sizeof(Wedding *)); (*(B->wed))[B->nmarr++]=X; lock(B->wed); sort(*(B->wed),B->nmarr,&MarriageOrder); unlock(B->wed); } static void ResolveSpouse(Person *P, Wedding *W, bool GenErr) { if (!P || !W || !W->spref) return; W->to=FindPerson(W->spref,&(P->life),GenErr,P); if (W->to) { old(W->spref); W->spref=NULL; ReciprocateWedding(W,P); } } static void ResolveSpouses(Person *P, bool GenErr) { int i; Wedding *W; if (!P || !P->wed) return; for (i=0;inmarr;i++) { W=(*(P->wed))[i]; ResolveSpouse(P,W,GenErr); } // } void AddMarriage(Person *P, string Sp, Day D, Place *C) { Wedding *W; if (!P || !Sp) return; if (!P->wed) P->wed=(Wedding ***)handle(0); W=pointer(sizeof(Wedding)); minimumsize(P->wed,P->nmarr + 1,sizeof(Wedding *)); (*(P->wed))[P->nmarr++]=W; W->spref=Sp; W->on=D; W->at=C; ResolveSpouse(P,W,0); } // Genealogy support void MakeChildList(Person *P) { int i; Person *C; bool Grand; if (!P) return; P->nchild=0; if (!P->children) { P->children=(Person ***)handle(0); P->chgrand=(bool **)handle(0); } for (i=0;ifather!=P && C->mother!=P) continue; if (C->life.start.jd<=P->life.start.jd) continue; minimumsize(P->children,P->nchild + 1,sizeof(Person *)); minimumsize(P->chgrand,P->nchild + 1,sizeof(bool)); Grand= (C->father==P) ? C->fagrand : C->mogrand; (*(P->children))[P->nchild]=C; (*(P->chgrand))[P->nchild++]=Grand; } for (i=0;inchild;i++) { C=(*(P->children))[i]; if (C) MakeChildList(C); } } void PurgeGenCalcs(void) { int i; Person *P; for (i=0;ichildren); P->children=NULL; release(P->chgrand); P->chgrand=NULL; P->nchild=-1; } } static void SetMinGen(Person *P, Person *C) { // gets called during delta check, but harmless real Years; if (!P || P->life.start.kind!=timeJD || !C || C->life.start.kind!=timeJD) return; Years= (C->life.start.jd - P->life.start.jd) / 365.2422; if (P->mingen>Years) P->mingen=Years; } void ResolveFather(Person *P, bool GenErr) { Span B; if (!P || !P->faref) return; B=P->life; B.start.jd-=GestPeriod; B.end=B.start; P->father=FindPerson(P->faref,&B,GenErr,P); if (!P->father) return; old(P->faref); P->faref=NULL; SetMinGen(P->father,P); } void ResolveMother(Person *P, bool GenErr) { Span B; if (!P || !P->moref) return; B=P->life; B.end=B.start; P->mother=FindPerson(P->moref,&B,GenErr,P); if (!P->mother) return; old(P->moref); P->moref=NULL; SetMinGen(P->mother,P); } void ResolvePersonReferences(Person *P) { ResolveFather(P,1); if (P->father) { AddLinkIn(P->father,P); VerifyParent(P,P->father,P->fagrand); } ResolveMother(P,1); if (P->mother) { AddLinkIn(P->mother,P); VerifyParent(P,P->mother,P->mogrand); } ResolveSpouses(P,1); CheckInfoText(P,copyof(P->info),&(P->life),Verbose); SpellCheck(P->name,0); SpellCheck(P->info,1); } // Query function bool Female(Person *P) { return P && P->sex=='F'; } // Getting a person's current location Place *PersonLocation(Person *P, Day *D) { Travels *T; Place *W; if (!P) return NULL; T=P->trav; if (!T || !D || D->kind!=timeJD) return P->home; W=CurrentLocation(T,D); if (W) return W; return P->home; } // Keeping a list of reverse hyperlinks void AddLinkIn(void *To, const void *From) { listoflinks *L; int i; if (!To || !From || To==From) return; if (VoidIsPers(To)) { Person *P; P=(Person *)To; L=&(P->inbnd); } else if (VoidIsEvent(To)) { Event *E; E=(Event *)To; L=&(E->inbnd); } else return; if (!L->link) L->link=(void ***)handle(0); for (i=0;inum;i++) if ((*(L->link))[i]==From) return; minimumsize(L->link,L->num + 1,sizeof(void *)); (*(L->link))[L->num++]=(void *)From; }