1 /*
2  *  "GEDKeeper", the personal genealogical database editor.
3  *  Copyright (C) 2009-2021 by Sergey V. Zhdanovskih.
4  *
5  *  This file is part of "GEDKeeper".
6  *
7  *  This program is free software: you can redistribute it and/or modify
8  *  it under the terms of the GNU General Public License as published by
9  *  the Free Software Foundation, either version 3 of the License, or
10  *  (at your option) any later version.
11  *
12  *  This program is distributed in the hope that it will be useful,
13  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  *  GNU General Public License for more details.
16  *
17  *  You should have received a copy of the GNU General Public License
18  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
19  */
20 
21 using System.Collections.Generic;
22 using BSLib;
23 using BSLib.Calendar;
24 using BSLib.Design.Graphics;
25 using GDModel;
26 using GDModel.Providers.GEDCOM;
27 using GKCore.Interfaces;
28 using GKCore.Options;
29 using GKCore.Types;
30 
31 namespace GKCore.Export
32 {
33     /// <summary>
34     ///
35     /// </summary>
36     public sealed class PedigreeExporter : ReportExporter
37     {
38         private class PedigreePerson
39         {
40             public PedigreePerson Parent;
41             public string Id;
42             public GDMIndividualRecord IRec;
43             public int Level;
44             public readonly List<string> Sources;
45             public int FamilyOrder;
46             public int ChildIdx;
47 
PedigreePerson()48             public PedigreePerson()
49             {
50                 Sources = new List<string>();
51             }
52 
GetOrderStr()53             private string GetOrderStr()
54             {
55                 string order = ConvertHelper.AdjustNumber(FamilyOrder, 2);
56                 string result = ((Parent == null) ? order : Parent.GetOrderStr() + order);
57                 return result;
58             }
59 
GetInternalStr()60             public string GetInternalStr()
61             {
62                 return ConvertHelper.AdjustNumber(Level, 2) + GetOrderStr();
63             }
64         }
65 
66         private class PedigreeEvent
67         {
68             public readonly GDMIndividualRecord IRec;
69             public readonly GDMCustomEvent Event;
70             public readonly UDN Date;
71 
PedigreeEvent(GDMIndividualRecord iRec, GDMCustomEvent evt)72             public PedigreeEvent(GDMIndividualRecord iRec, GDMCustomEvent evt)
73             {
74                 IRec = iRec;
75                 Event = evt;
76                 Date = (evt == null) ? UDN.CreateEmpty() : evt.Date.GetUDN();
77             }
78         }
79 
80         public enum PedigreeKind
81         {
82             Ascend,
83             Descend_dAboville,
84             Descend_Konovalov
85         }
86 
87         private PedigreeFormat fFormat;
88         private PedigreeKind fKind;
89         private ExtList<PedigreePerson> fPersonList;
90         private readonly GDMIndividualRecord fRoot;
91         private readonly ShieldState fShieldState;
92         private StringList fSourceList;
93 
94         private IFont fTitleFont;
95         private IFont fChapFont;
96         private IFont fPersonFont;
97         private IFont fLinkFont;
98         private IFont fTextFont, fSupText;
99 
100         public PedigreeKind Kind
101         {
102             get { return fKind; }
103             set { fKind = value; }
104         }
105 
106         public GDMIndividualRecord Root
107         {
108             get { return fRoot; }
109         }
110 
111         public ShieldState ShieldState
112         {
113             get { return fShieldState; }
114         }
115 
PedigreeExporter(IBaseWindow baseWin, GDMIndividualRecord root)116         public PedigreeExporter(IBaseWindow baseWin, GDMIndividualRecord root) : base(baseWin, false)
117         {
118             fRoot = root;
119             fTitle = LangMan.LS(LSID.LSID_ExpPedigree) + ": " + GKUtils.GetNameString(fRoot, true, false);
120             fShieldState = baseWin.Context.ShieldState;
121         }
122 
FindPerson(GDMIndividualRecord iRec)123         private PedigreePerson FindPerson(GDMIndividualRecord iRec)
124         {
125             if (iRec == null) return null;
126 
127             PedigreePerson res = null;
128 
129             int num = fPersonList.Count;
130             for (int i = 0; i < num; i++) {
131                 PedigreePerson item = fPersonList[i];
132 
133                 if (item.IRec == iRec) {
134                     res = item;
135                     break;
136                 }
137             }
138 
139             return res;
140         }
141 
WritePerson(PedigreePerson person)142         private void WritePerson(PedigreePerson person)
143         {
144             fWriter.BeginParagraph(TextAlignment.taJustify, 6f, 6f);
145             fWriter.AddParagraphChunkAnchor(GetIdStr(person) + ". " + GKUtils.GetNameString(person.IRec, true, false), fPersonFont, person.Id);
146             fWriter.AddParagraphChunk(GKUtils.GetPedigreeLifeStr(person.IRec, fOptions.PedigreeOptions.Format), fTextFont);
147 
148             if (fOptions.PedigreeOptions.IncludeSources && person.Sources.Count > 0) {
149                 fWriter.AddParagraphChunk(" ", fTextFont);
150 
151                 int num = person.Sources.Count;
152                 for (int i = 0; i < num; i++) {
153                     string lnk = person.Sources[i];
154 
155                     if (i > 0) {
156                         fWriter.AddParagraphChunkLink(", ", fTextFont, "", true);
157                     }
158 
159                     fWriter.AddParagraphChunkLink(lnk, fSupText, "src_" + lnk, true);
160                 }
161             }
162 
163             fWriter.EndParagraph();
164 
165             switch (fFormat) {
166                 case PedigreeFormat.Excess:
167                     WriteExcessFmt(person);
168                     break;
169 
170                 case PedigreeFormat.Compact:
171                     WriteCompactFmt(person);
172                     break;
173             }
174         }
175 
idLink(GDMIndividualRecord iRec)176         private string idLink(GDMIndividualRecord iRec)
177         {
178             PedigreePerson person = FindPerson(iRec);
179             return (person == null) ? "" : person.Id;
180         }
181 
GetIdStr(PedigreePerson person)182         private string GetIdStr(PedigreePerson person)
183         {
184             string result = person.Id;
185 
186             if (fKind == PedigreeKind.Descend_Konovalov && person.Parent != null) {
187                 GDMFamilyRecord family = fTree.GetPtrValue(person.IRec.ChildToFamilyLinks[0]);
188                 string spStr = "";
189                 int idx = person.Parent.IRec.IndexOfSpouse(family);
190                 if (person.Parent.IRec.SpouseToFamilyLinks.Count > 1) {
191                     spStr = "/" + (idx + 1).ToString();
192                 }
193                 result += spStr;
194             }
195             return result;
196         }
197 
WriteExcessFmt(PedigreePerson person)198         private void WriteExcessFmt(PedigreePerson person)
199         {
200             fWriter.AddParagraph(LangMan.LS(LSID.LSID_Sex) + ": " + GKUtils.SexStr(person.IRec.Sex), fTextFont);
201 
202             string st = GKUtils.GetLifeExpectancyStr(person.IRec);
203             if (st != "?" && st != "") {
204                 fWriter.AddParagraph(LangMan.LS(LSID.LSID_LifeExpectancy) + ": " + st, fTextFont);
205             }
206 
207             GDMIndividualRecord father, mother;
208             fTree.GetParents(person.IRec, out father, out mother);
209 
210             if (father != null) {
211                 fWriter.AddParagraphLink(LangMan.LS(LSID.LSID_Father) + ": " + GKUtils.GetNameString(father, true, false) + " ", fTextFont, idLink(father), fLinkFont);
212             }
213 
214             if (mother != null) {
215                 fWriter.AddParagraphLink(LangMan.LS(LSID.LSID_Mother) + ": " + GKUtils.GetNameString(mother, true, false) + " ", fTextFont, idLink(mother), fLinkFont);
216             }
217 
218             var evList = new ExtList<PedigreeEvent>(true);
219             try {
220                 int i;
221                 if (person.IRec.HasEvents) {
222                     fWriter.AddParagraph(LangMan.LS(LSID.LSID_Events) + ":", fTextFont);
223 
224                     int num = person.IRec.Events.Count;
225                     for (i = 0; i < num; i++) {
226                         GDMCustomEvent evt = person.IRec.Events[i];
227                         if (!(evt is GDMIndividualAttribute) || fOptions.PedigreeOptions.IncludeAttributes) {
228                             evList.Add(new PedigreeEvent(person.IRec, evt));
229                         }
230                     }
231                     WriteEventList(person, evList);
232                 }
233 
234                 int num2 = person.IRec.SpouseToFamilyLinks.Count;
235                 for (i = 0; i < num2; i++) {
236                     GDMFamilyRecord family = fTree.GetPtrValue(person.IRec.SpouseToFamilyLinks[i]);
237                     if (!fBase.Context.IsRecordAccess(family.Restriction)) continue;
238 
239                     GDMIndividualRecord spRec;
240                     string unk;
241                     if (person.IRec.Sex == GDMSex.svMale) {
242                         spRec = fTree.GetPtrValue(family.Wife);
243                         st = LangMan.LS(LSID.LSID_Wife) + ": ";
244                         unk = LangMan.LS(LSID.LSID_UnkFemale);
245                     } else {
246                         spRec = fTree.GetPtrValue(family.Husband);
247                         st = LangMan.LS(LSID.LSID_Husband) + ": ";
248                         unk = LangMan.LS(LSID.LSID_UnkMale);
249                     }
250 
251                     string sps;
252                     if (spRec != null) {
253                         sps = st + GKUtils.GetNameString(spRec, true, false) + GKUtils.GetPedigreeLifeStr(spRec, fOptions.PedigreeOptions.Format)/* + this.idLink(this.FindPerson(irec))*/;
254                     } else {
255                         sps = st + unk;
256                     }
257 
258                     fWriter.AddParagraph(sps, fTextFont);
259 
260                     evList.Clear();
261                     int childrenCount = family.Children.Count;
262                     for (int j = 0; j < childrenCount; j++) {
263                         GDMIndividualRecord child = fTree.GetPtrValue(family.Children[j]);
264                         evList.Add(new PedigreeEvent(child, child.FindEvent(GEDCOMTagType.BIRT)));
265                     }
266                     WriteEventList(person, evList);
267                 }
268             } finally {
269                 evList.Dispose();
270             }
271 
272             if (fOptions.PedigreeOptions.IncludeNotes && person.IRec.HasNotes) {
273                 fWriter.AddParagraph(LangMan.LS(LSID.LSID_RPNotes) + ":", fTextFont);
274 
275                 fWriter.BeginList();
276 
277                 int notesCount = person.IRec.Notes.Count;
278                 for (int i = 0; i < notesCount; i++) {
279                     GDMLines noteLines = fTree.GetNoteLines(person.IRec.Notes[i]);
280                     fWriter.AddListItem(" " + GKUtils.MergeStrings(noteLines), fTextFont);
281                 }
282 
283                 fWriter.EndList();
284             }
285         }
286 
WriteCompactFmt(PedigreePerson person)287         private void WriteCompactFmt(PedigreePerson person)
288         {
289             if (fOptions.PedigreeOptions.IncludeNotes && person.IRec.HasNotes) {
290                 int num = person.IRec.Notes.Count;
291                 for (int i = 0; i < num; i++) {
292                     GDMLines noteLines = fTree.GetNoteLines(person.IRec.Notes[i]);
293                     fWriter.AddParagraph(GKUtils.MergeStrings(noteLines), fTextFont);
294                 }
295             }
296 
297             bool spIndex = person.IRec.SpouseToFamilyLinks.Count > 1;
298 
299             int num2 = person.IRec.SpouseToFamilyLinks.Count;
300             for (int i = 0; i < num2; i++) {
301                 GDMFamilyRecord family = fTree.GetPtrValue(person.IRec.SpouseToFamilyLinks[i]);
302                 if (fBase.Context.IsRecordAccess(family.Restriction)) {
303                     GDMIndividualRecord spRec;
304                     string st;
305                     string unk;
306                     if (person.IRec.Sex == GDMSex.svMale) {
307                         spRec = fTree.GetPtrValue(family.Wife);
308                         st = LangMan.LS(LSID.LSID_WifeSign);
309                         unk = LangMan.LS(LSID.LSID_UnkFemale);
310                     } else {
311                         spRec = fTree.GetPtrValue(family.Husband);
312                         st = LangMan.LS(LSID.LSID_HusbSign);
313                         unk = LangMan.LS(LSID.LSID_UnkMale);
314                     }
315 
316                     if (spIndex) {
317                         st += (i + 1).ToString();
318                     }
319                     st += " - ";
320 
321                     if (spRec != null) {
322                         st = st + GKUtils.GetNameString(spRec, true, false) + GKUtils.GetPedigreeLifeStr(spRec, fOptions.PedigreeOptions.Format)/* + this.idLink(this.FindPerson(irec))*/;
323                     } else {
324                         st += unk;
325                     }
326 
327                     fWriter.AddParagraph(st, fTextFont);
328                 }
329             }
330         }
331 
EventsCompare(PedigreeEvent item1, PedigreeEvent item2)332         private static int EventsCompare(PedigreeEvent item1, PedigreeEvent item2)
333         {
334             return item1.Date.CompareTo(item2.Date);
335         }
336 
WriteEventList(PedigreePerson person, ExtList<PedigreeEvent> evList)337         private void WriteEventList(PedigreePerson person, ExtList<PedigreeEvent> evList)
338         {
339             evList.QuickSort(EventsCompare);
340 
341             int evtNum = evList.Count;
342             for (int i = 0; i < evtNum; i++) {
343                 GDMCustomEvent evt = evList[i].Event;
344                 if (evt != null && Equals(evList[i].IRec, person.IRec)) {
345                     var evtType = evt.GetTagType();
346 
347                     if (evtType == GEDCOMTagType.BIRT) {
348                         evList.Exchange(i, 0);
349                     } else if (evtType == GEDCOMTagType.DEAT) {
350                         evList.Exchange(i, evtNum - 1);
351                     }
352                 }
353             }
354 
355             fWriter.BeginList();
356 
357             var dateFormat = GlobalOptions.Instance.DefDateFormat;
358             for (int i = 0; i < evtNum; i++) {
359                 PedigreeEvent evObj = evList[i];
360                 GDMCustomEvent evt = evObj.Event;
361                 string li;
362 
363                 if (evObj.IRec == person.IRec) {
364                     var evtName = evt.GetTagName();
365                     int ev = GKUtils.GetPersonEventIndex(evtName);
366                     string st;
367                     if (ev == 0) {
368                         st = evt.Classification;
369                     } else {
370                         st = (ev > 0) ? LangMan.LS(GKData.PersonEvents[ev].Name) : evtName;
371                     }
372 
373                     string dt = GKUtils.GEDCOMEventToDateStr(evt, dateFormat, false);
374                     li = dt + ": " + st + ".";
375                     if (evt.HasPlace && evt.Place.StringValue != "") {
376                         li = li + " " + LangMan.LS(LSID.LSID_Place) + ": " + evt.Place.StringValue;
377                     }
378 
379                     fWriter.AddListItem(" " + li, fTextFont);
380                 } else {
381                     string dt = (evt == null) ? "?" : GKUtils.GEDCOMEventToDateStr(evt, dateFormat, false);
382 
383                     string st = (evObj.IRec.Sex == GDMSex.svMale) ? LangMan.LS(LSID.LSID_HeWasBorn) : LangMan.LS(LSID.LSID_SheWasBorn);
384 
385                     li = string.Format("{0}: {1} {2}", dt, st, GKUtils.GetNameString(evObj.IRec, true, false));
386                     PedigreePerson prs = FindPerson(evObj.IRec);
387                     string id = (prs != null) ? prs.Id : "";
388 
389                     fWriter.AddListItemLink(" " + li + " ", fTextFont, id, fLinkFont);
390                 }
391             }
392 
393             fWriter.EndList();
394         }
395 
GenStep(PedigreePerson parent, GDMIndividualRecord iRec, int level, int familyOrder)396         private void GenStep(PedigreePerson parent, GDMIndividualRecord iRec, int level, int familyOrder)
397         {
398             if (iRec == null) return;
399 
400             PedigreePerson res = new PedigreePerson();
401             res.Parent = parent;
402             res.IRec = iRec;
403             res.Level = level;
404             res.ChildIdx = 0;
405             res.FamilyOrder = familyOrder;
406             fPersonList.Add(res);
407 
408             if (fOptions.PedigreeOptions.IncludeSources && iRec.HasSourceCitations) {
409                 int num = iRec.SourceCitations.Count;
410                 for (int i = 0; i < num; i++) {
411                     var sourceRec = fTree.GetPtrValue<GDMSourceRecord>(iRec.SourceCitations[i]);
412                     if (sourceRec == null) continue;
413 
414                     int srcIndex = fSourceList.IndexOfObject(sourceRec);
415                     if (srcIndex < 0) {
416                         string srcName = sourceRec.ShortTitle;
417                         string srcTitle = GKUtils.MergeStrings(sourceRec.Title.Lines);
418                         if (!string.IsNullOrEmpty(srcName) && !string.IsNullOrEmpty(srcTitle)) {
419                             srcName += "\n";
420                         }
421                         srcName += srcTitle;
422 
423                         srcIndex = fSourceList.AddObject(srcName, sourceRec);
424                     }
425 
426                     res.Sources.Add((srcIndex + 1).ToString());
427                 }
428             }
429 
430             if (fKind == PedigreeKind.Ascend) {
431                 if (iRec.ChildToFamilyLinks.Count > 0) {
432                     GDMFamilyRecord family = fTree.GetPtrValue(iRec.ChildToFamilyLinks[0]);
433                     if (fBase.Context.IsRecordAccess(family.Restriction)) {
434                         GDMIndividualRecord father, mother;
435                         fBase.Context.Tree.GetSpouses(family, out father, out mother);
436 
437                         GenStep(res, father, level + 1, 1);
438                         GenStep(res, mother, level + 1, 1);
439                     }
440                 }
441             } else {
442                 int num2 = iRec.SpouseToFamilyLinks.Count;
443                 for (int j = 0; j < num2; j++) {
444                     GDMFamilyRecord family = fTree.GetPtrValue(iRec.SpouseToFamilyLinks[j]);
445                     if (!fBase.Context.IsRecordAccess(family.Restriction)) continue;
446 
447                     fBase.Context.ProcessFamily(family);
448 
449                     int num3 = family.Children.Count;
450                     for (int i = 0; i < num3; i++) {
451                         GDMIndividualRecord child = fTree.GetPtrValue(family.Children[i]);
452                         GenStep(res, child, level + 1, i + 1);
453                     }
454                 }
455             }
456         }
457 
PersonsCompare(PedigreePerson item1, PedigreePerson item2)458         private static int PersonsCompare(PedigreePerson item1, PedigreePerson item2)
459         {
460             return string.CompareOrdinal(item1.GetInternalStr(), item2.GetInternalStr());
461         }
462 
ReIndex()463         private void ReIndex()
464         {
465             fPersonList.QuickSort(PersonsCompare);
466 
467             int num3 = fPersonList.Count;
468             for (int i = 0; i < num3; i++) {
469                 PedigreePerson obj = fPersonList[i];
470 
471                 switch (fKind) {
472                     case PedigreeKind.Descend_dAboville:
473                         if (obj.Parent == null) {
474                             obj.Id = "1";
475                         } else {
476                             obj.Parent.ChildIdx++;
477                             obj.Id = obj.Parent.Id + "." + obj.Parent.ChildIdx.ToString();
478                         }
479                         break;
480 
481                     case PedigreeKind.Ascend:
482                     case PedigreeKind.Descend_Konovalov:
483                         obj.Id = (i + 1).ToString();
484                         if (obj.Parent != null) {
485                             string pid = obj.Parent.Id;
486 
487                             int p = pid.IndexOf("-");
488                             if (p >= 0) pid = pid.Substring(0, p);
489 
490                             obj.Id = obj.Id + "-" + pid;
491                         }
492                         break;
493                 }
494             }
495         }
496 
InternalGenerate()497         protected override void InternalGenerate()
498         {
499             bool isRtf = false;
500 #if !NETSTANDARD
501             isRtf = (fWriter is RTFWriter);
502 #endif
503 
504             IColor clrBlack = AppHost.GfxProvider.CreateColor(0x000000);
505             IColor clrBlue = AppHost.GfxProvider.CreateColor(0x0000FF);
506 
507             fTitleFont = fWriter.CreateFont("", 16f/*20f*/, true, false, clrBlack);
508             fChapFont = fWriter.CreateFont("", 14f/*16f*/, true, false, clrBlack);
509             fPersonFont = fWriter.CreateFont("", 12f/*10f*/, true, false, clrBlack);
510             fLinkFont = fWriter.CreateFont("", 10f/*8f*/, false, true, clrBlue);
511             fTextFont = fWriter.CreateFont("", 10f/*8f*/, false, false, clrBlack);
512             fSupText = fWriter.CreateFont("", (isRtf ? 12f : 5f) /*5f*/, false, false, clrBlue);
513 
514             fFormat = fOptions.PedigreeOptions.Format;
515 
516             bool includeGens = fOptions.PedigreeOptions.IncludeGenerations;
517 
518             fWriter.AddParagraph(fTitle, fTitleFont, TextAlignment.taCenter);
519 
520             fPersonList = new ExtList<PedigreePerson>(true);
521             fSourceList = new StringList();
522             try {
523                 GenStep(null, fRoot, 1, 1);
524                 ReIndex();
525 
526                 int curLevel = 0;
527                 int num = fPersonList.Count;
528                 for (int i = 0; i < num; i++) {
529                     PedigreePerson person = fPersonList[i];
530 
531                     if (includeGens && curLevel != person.Level) {
532                         curLevel = person.Level;
533                         string genTitle = LangMan.LS(LSID.LSID_Generation) + " " + ConvertHelper.GetRome(curLevel);
534 
535                         fWriter.BeginParagraph(TextAlignment.taLeft, 12f, 6f);
536                         fWriter.AddParagraphChunk(genTitle, fChapFont);
537                         fWriter.EndParagraph();
538                     }
539 
540                     WritePerson(person);
541                 }
542 
543                 if (fSourceList.Count > 0) {
544                     fWriter.BeginParagraph(TextAlignment.taCenter, 12f, 6f);
545                     fWriter.AddParagraphChunk(LangMan.LS(LSID.LSID_RPSources), fChapFont);
546                     fWriter.EndParagraph();
547 
548                     int num2 = fSourceList.Count;
549                     for (int j = 0; j < num2; j++) {
550                         string sn = (j + 1).ToString();
551                         string sst = sn + ". " + fSourceList[j];
552                         string sanc = "src_" + sn;
553 
554                         fWriter.AddParagraphAnchor(sst, fTextFont, sanc);
555                     }
556                 }
557             } finally {
558                 fSourceList.Dispose();
559                 fPersonList.Dispose();
560             }
561         }
562     }
563 }
564