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;
22 using System.Collections.Generic;
23 using System.Threading;
24 using BSLib;
25 using BSLib.DataViz.SmartGraph;
26 using BSLib.Design.MVP.Controls;
27 using GDModel;
28 using GDModel.Providers.GEDCOM;
29 using GKCore.Controllers;
30 using GKCore.Interfaces;
31 using GKCore.Types;
32 
33 namespace GKCore.Tools
34 {
35     /// <summary>
36     ///
37     /// </summary>
38     public static class TreeTools
39     {
40         private const string CRLF = "\r\n";
41 
42 
43         #region Patriarchs Search
44 
PL_SearchAnc(GDMTree tree, GDMIndividualRecord descendant, GDMIndividualRecord searchRec, bool onlyMaleLine)45         public static bool PL_SearchAnc(GDMTree tree, GDMIndividualRecord descendant, GDMIndividualRecord searchRec, bool onlyMaleLine)
46         {
47             if (descendant == null) return false;
48 
49             bool res = (descendant == searchRec);
50 
51             if (!res && descendant.ChildToFamilyLinks.Count > 0)
52             {
53                 GDMFamilyRecord family = tree.GetPtrValue(descendant.ChildToFamilyLinks[0]);
54 
55                 GDMIndividualRecord ancestor = tree.GetPtrValue(family.Husband);
56                 if (ancestor != null) {
57                     res = PL_SearchAnc(tree, ancestor, searchRec, onlyMaleLine);
58                     if (res) return true;
59                 }
60 
61                 /*if (!onlyMaleLine) {
62 					ancestor = family.GetWife();
63 					if (ancestor != null) {
64 						res = PL_SearchAnc2(ancestor, searchRec, onlyMaleLine);
65 						if (res) return true;
66 					}
67 				}*/
68             }
69 
70             return res;
71         }
72 
73         /// <summary>
74         /// Search of crossing of two individuals.
75         /// </summary>
76         /// <param name="ancestorRec"></param>
77         /// <param name="searchRec"></param>
78         /// <returns>crossing of two individuals</returns>
PL_SearchDesc(GDMTree tree, GDMIndividualRecord ancestorRec, GDMIndividualRecord searchRec)79         public static GDMIndividualRecord PL_SearchDesc(GDMTree tree, GDMIndividualRecord ancestorRec, GDMIndividualRecord searchRec)
80         {
81             GDMIndividualRecord cross = null;
82 
83             int num = ancestorRec.SpouseToFamilyLinks.Count;
84             for (int i = 0; i < num; i++) {
85                 GDMFamilyRecord family = tree.GetPtrValue(ancestorRec.SpouseToFamilyLinks[i]);
86                 GDMIndividualRecord spouse = tree.GetSpouseBy(family, ancestorRec);
87 
88                 if (spouse != null) {
89                     bool res = PL_SearchAnc(tree, spouse, searchRec, (ancestorRec.Sex == GDMSex.svFemale));
90                     if (res) {
91                         cross = ancestorRec;
92                         return cross;
93                     }
94                 }
95 
96                 if (ancestorRec.Sex == GDMSex.svMale) {
97                     int num2 = family.Children.Count;
98                     for (int j = 0; j < num2; j++) {
99                         GDMIndividualRecord child = tree.GetPtrValue(family.Children[j]);
100                         cross = PL_SearchDesc(tree, child, searchRec);
101                         if (cross != null) return cross;
102                     }
103                 }
104             }
105 
106             return null;
107         }
108 
PL_SearchIntersection(GDMTree tree, GDMIndividualRecord ancestor, GDMIndividualRecord searchRec)109         public static GDMFamilyRecord PL_SearchIntersection(GDMTree tree, GDMIndividualRecord ancestor, GDMIndividualRecord searchRec)
110         {
111             int num = ancestor.SpouseToFamilyLinks.Count;
112             for (int i = 0; i < num; i++) {
113                 GDMFamilyRecord family = tree.GetPtrValue(ancestor.SpouseToFamilyLinks[i]);
114                 GDMIndividualRecord spouse = tree.GetSpouseBy(family, ancestor);
115 
116                 if (spouse != null) {
117                     bool res = PL_SearchAnc(tree, spouse, searchRec, (ancestor.Sex == GDMSex.svFemale));
118                     if (res) return family;
119                 }
120 
121                 if (ancestor.Sex == GDMSex.svMale) {
122                     int num2 = family.Children.Count;
123                     for (int j = 0; j < num2; j++) {
124                         GDMIndividualRecord child = tree.GetPtrValue(family.Children[j]);
125 
126                         GDMFamilyRecord res = PL_SearchIntersection(tree, child, searchRec);
127                         if (res != null) return res;
128                     }
129                 }
130             }
131 
132             return null;
133         }
134 
GenPatriarchsGraphviz(IBaseWindow baseWin, string outpath, int minGens, bool loneSuppress = true)135         public static void GenPatriarchsGraphviz(IBaseWindow baseWin, string outpath, int minGens, bool loneSuppress = true)
136         {
137             if (baseWin == null)
138                 throw new ArgumentNullException("baseWin");
139 
140             string[] options = { "ratio=auto" };
141             GraphvizWriter gvw = new GraphvizWriter("Family Tree", options);
142 
143             using (ExtList<PatriarchObj> patList = PatriarchsMan.GetPatriarchsLinks(baseWin.Context, minGens, false, loneSuppress))
144             {
145                 int num = patList.Count;
146                 for (int i = 0; i < num; i++) {
147                     PatriarchObj pObj = patList[i];
148 
149                     if (!loneSuppress || pObj.HasLinks) {
150                         string color = (pObj.IRec.Sex == GDMSex.svFemale) ? "pink" : "blue";
151                         gvw.WriteNode(pObj.IRec.XRef, GKUtils.GetNameString(pObj.IRec, true, false), "filled", color, "box");
152                     }
153                 }
154 
155                 for (int i = 0; i < num; i++) {
156                     PatriarchObj pat1 = patList[i];
157 
158                     int num2 = pat1.Links.Count;
159                     for (int k = 0; k < num2; k++) {
160                         PatriarchObj pat2 = pat1.Links[k];
161                         gvw.WriteEdge(pat1.IRec.XRef, pat2.IRec.XRef);
162                     }
163                 }
164             }
165 
166             gvw.SaveFile(outpath);
167         }
168 
169         #endregion
170 
171         #region Tree Walk
172 
173         public enum TreeWalkMode
174         {
175             twmAll,
176             twmFamily,
177             twmAncestors,
178             twmDescendants,
179             twmNone
180         }
181 
WalkProc(GDMIndividualRecord iRec, TreeWalkMode mode, object extData)182         public delegate bool WalkProc(GDMIndividualRecord iRec, TreeWalkMode mode, object extData);
183 
WalkTree(GDMTree tree, GDMIndividualRecord iRec, TreeWalkMode mode, WalkProc walkProc, object extData)184         public static void WalkTree(GDMTree tree, GDMIndividualRecord iRec, TreeWalkMode mode, WalkProc walkProc, object extData)
185         {
186             if (tree == null)
187                 throw new ArgumentNullException("tree");
188 
189             if (iRec == null)
190                 throw new ArgumentNullException("iRec");
191 
192             if (walkProc == null)
193                 throw new ArgumentNullException("walkProc");
194 
195             if (extData == null)
196                 throw new ArgumentNullException("extData");
197 
198             WalkTreeInt(tree, iRec, mode, walkProc, extData);
199         }
200 
WalkTree(GDMTree tree, GDMIndividualRecord iRec, TreeWalkMode mode, List<GDMRecord> walkList)201         public static void WalkTree(GDMTree tree, GDMIndividualRecord iRec, TreeWalkMode mode, List<GDMRecord> walkList)
202         {
203             if (tree == null)
204                 throw new ArgumentNullException("tree");
205 
206             if (iRec == null)
207                 throw new ArgumentNullException("iRec");
208 
209             if (walkList == null)
210                 throw new ArgumentNullException("walkList");
211 
212             WalkTreeInt(tree, iRec, mode, DefaultWalkProc, walkList);
213         }
214 
DefaultWalkProc(GDMIndividualRecord iRec, TreeWalkMode mode, object extData)215         private static bool DefaultWalkProc(GDMIndividualRecord iRec, TreeWalkMode mode, object extData)
216         {
217             List<GDMRecord> walkList = (List<GDMRecord>)extData;
218             bool resContinue = (iRec != null && !walkList.Contains(iRec));
219             if (resContinue) {
220                 walkList.Add(iRec);
221             }
222             return resContinue;
223         }
224 
WalkTreeInt(GDMTree tree, GDMIndividualRecord iRec, TreeWalkMode mode, WalkProc walkProc, object extData)225         private static void WalkTreeInt(GDMTree tree, GDMIndividualRecord iRec, TreeWalkMode mode, WalkProc walkProc, object extData)
226         {
227             if (!walkProc(iRec, mode, extData)) return;
228 
229             if (mode == TreeWalkMode.twmNone) return;
230 
231             if (mode == TreeWalkMode.twmAll || mode == TreeWalkMode.twmAncestors) {
232                 GDMFamilyRecord family = tree.GetParentsFamily(iRec);
233                 if (family != null) {
234                     GDMIndividualRecord father, mother;
235                     tree.GetSpouses(family, out father, out mother);
236 
237                     WalkTreeInt(tree, father, mode, walkProc, extData);
238                     WalkTreeInt(tree, mother, mode, walkProc, extData);
239                 }
240             }
241 
242             // twmAll, twmFamily, twmDescendants
243             if (mode < TreeWalkMode.twmAncestors || mode == TreeWalkMode.twmDescendants) {
244                 int num = iRec.SpouseToFamilyLinks.Count;
245                 for (int i = 0; i < num; i++) {
246                     GDMFamilyRecord family = tree.GetPtrValue(iRec.SpouseToFamilyLinks[i]);
247                     GDMIndividualRecord spouse = (iRec.Sex == GDMSex.svMale) ? tree.GetPtrValue(family.Wife) : tree.GetPtrValue(family.Husband);
248 
249                     TreeWalkMode intMode = ((mode == TreeWalkMode.twmAll) ? TreeWalkMode.twmAll : TreeWalkMode.twmNone);
250                     WalkTreeInt(tree, spouse, intMode, walkProc, extData);
251 
252                     switch (mode) {
253                         case TreeWalkMode.twmAll:
254                             intMode = TreeWalkMode.twmAll;
255                             break;
256 
257                         case TreeWalkMode.twmFamily:
258                             intMode = TreeWalkMode.twmNone;
259                             break;
260 
261                         case TreeWalkMode.twmDescendants:
262                             intMode = TreeWalkMode.twmDescendants;
263                             break;
264                     }
265 
266                     int num2 = family.Children.Count;
267                     for (int j = 0; j < num2; j++) {
268                         GDMIndividualRecord child = tree.GetPtrValue(family.Children[j]);
269                         WalkTreeInt(tree, child, intMode, walkProc, extData);
270                     }
271                 }
272             }
273         }
274 
275         #endregion
276 
277         #region Detect cycles
278 
279         private enum DCFlag { dcfAncWalk, dcfDescWalk }
280 
SetIndiFlag(GKVarCache<GDMIndividualRecord, int> indiFlags, GDMIndividualRecord iRec, DCFlag flag)281         private static void SetIndiFlag(GKVarCache<GDMIndividualRecord, int> indiFlags, GDMIndividualRecord iRec, DCFlag flag)
282         {
283             int flags = indiFlags[iRec];
284             flags = BitHelper.SetBit(flags, (int)flag);
285             indiFlags[iRec] = flags;
286         }
287 
HasIndiFlag(GKVarCache<GDMIndividualRecord, int> indiFlags, GDMIndividualRecord iRec, DCFlag flag)288         private static bool HasIndiFlag(GKVarCache<GDMIndividualRecord, int> indiFlags, GDMIndividualRecord iRec, DCFlag flag)
289         {
290             int flags = indiFlags[iRec];
291             return BitHelper.IsSetBit(flags, (int)flag);
292         }
293 
DetectCycleAncestors(GDMTree tree, GDMIndividualRecord iRec, Stack<GDMIndividualRecord> stack, GKVarCache<GDMIndividualRecord, int> indiFlags)294         private static GDMIndividualRecord DetectCycleAncestors(GDMTree tree, GDMIndividualRecord iRec,
295                                                                 Stack<GDMIndividualRecord> stack,
296                                                                 GKVarCache<GDMIndividualRecord, int> indiFlags)
297         {
298             if (iRec == null) return null;
299 
300             if (stack.Contains(iRec)) return iRec;
301 
302             SetIndiFlag(indiFlags, iRec, DCFlag.dcfAncWalk);
303 
304             stack.Push(iRec);
305 
306             GDMFamilyRecord family = tree.GetParentsFamily(iRec);
307             if (family != null) {
308                 var res = DetectCycleAncestors(tree, tree.GetPtrValue(family.Husband), stack, indiFlags);
309                 if (res != null) return res;
310 
311                 res = DetectCycleAncestors(tree, tree.GetPtrValue(family.Wife), stack, indiFlags);
312                 if (res != null) return res;
313             }
314 
315             stack.Pop();
316             return null;
317         }
318 
DetectCycleDescendants(GDMTree tree, GDMIndividualRecord iRec, Stack<GDMIndividualRecord> stack, GKVarCache<GDMIndividualRecord, int> indiFlags)319         private static GDMIndividualRecord DetectCycleDescendants(GDMTree tree, GDMIndividualRecord iRec,
320                                                                   Stack<GDMIndividualRecord> stack,
321                                                                   GKVarCache<GDMIndividualRecord, int> indiFlags)
322         {
323             if (iRec == null) return null;
324 
325             if (stack.Contains(iRec)) return iRec;
326 
327             SetIndiFlag(indiFlags, iRec, DCFlag.dcfDescWalk);
328 
329             stack.Push(iRec);
330 
331             int num = iRec.SpouseToFamilyLinks.Count;
332             for (int i = 0; i < num; i++) {
333                 GDMFamilyRecord family = tree.GetPtrValue(iRec.SpouseToFamilyLinks[i]);
334                 if (family == null) continue;
335 
336                 int num2 = family.Children.Count;
337                 for (int j = 0; j < num2; j++) {
338                     GDMIndividualRecord child = tree.GetPtrValue(family.Children[j]);
339                     if (child == null) continue;
340 
341                     var res = DetectCycleDescendants(tree, child, stack, indiFlags);
342                     if (res != null) return res;
343                 }
344             }
345 
346             stack.Pop();
347             return null;
348         }
349 
DetectCycle(GDMTree tree, GDMIndividualRecord iRec)350         public static string DetectCycle(GDMTree tree, GDMIndividualRecord iRec)
351         {
352             var stack = new Stack<GDMIndividualRecord>();
353             var indiDCFlags = new GKVarCache<GDMIndividualRecord, int>();
354 
355             var hasCycle = DetectCycleAncestors(tree, iRec, stack, indiDCFlags);
356             if (hasCycle != null) {
357                 var lastRec = stack.Pop();
358                 return iRec.XRef + " ... " + lastRec.XRef + " -> " + hasCycle.XRef;
359             }
360 
361             stack.Clear();
362 
363             hasCycle = DetectCycleDescendants(tree, iRec, stack, indiDCFlags);
364             if (hasCycle != null) {
365                 var lastRec = stack.Pop();
366                 return iRec.XRef + " ... " + lastRec.XRef + " -> " + hasCycle.XRef;
367             }
368 
369             return string.Empty;
370         }
371 
CheckCycle(GDMTree tree, GDMIndividualRecord iRec)372         private static string CheckCycle(GDMTree tree, GDMIndividualRecord iRec)
373         {
374             var stack = new Stack<GDMIndividualRecord>();
375             GDMIndividualRecord hasCycle = null;
376             var indiDCFlags = new GKVarCache<GDMIndividualRecord, int>();
377 
378             if (!HasIndiFlag(indiDCFlags, iRec, DCFlag.dcfAncWalk)) {
379                 hasCycle = DetectCycleAncestors(tree, iRec, stack, indiDCFlags);
380                 if (hasCycle != null) {
381                     var lastRec = stack.Pop();
382                     return iRec.XRef + " ... " + lastRec.XRef + " -> " + hasCycle.XRef;
383                 }
384                 stack.Clear();
385             }
386 
387             if (!HasIndiFlag(indiDCFlags, iRec, DCFlag.dcfDescWalk)) {
388                 hasCycle = DetectCycleDescendants(tree, iRec, stack, indiDCFlags);
389                 if (hasCycle != null) {
390                     var lastRec = stack.Pop();
391                     return iRec.XRef + " ... " + lastRec.XRef + " -> " + hasCycle.XRef;
392                 }
393             }
394 
395             return string.Empty;
396         }
397 
398         #endregion
399 
400         #region Merge trees and records
401 
MergeTree(GDMTree mainTree, GDMTree extTree, ITextBox logBox, bool selfTest = false)402         public static void MergeTree(GDMTree mainTree, GDMTree extTree, ITextBox logBox, bool selfTest = false)
403         {
404             if (mainTree == null)
405                 throw new ArgumentNullException("mainTree");
406 
407             if (extTree == null)
408                 throw new ArgumentNullException("extTree");
409 
410             if (logBox != null) {
411                 logBox.Clear();
412                 logBox.AppendText(string.Format(LangMan.LS(LSID.LSID_MainBaseSize), mainTree.RecordsCount.ToString()) + CRLF);
413             }
414 
415             List<int> fragments = new List<int>();
416             if (selfTest) {
417                 var tmpFrags = TreeTools.SearchTreeFragments(mainTree, null);
418                 for (int i = 0; i < tmpFrags.Count; i++) {
419                     fragments.Add(tmpFrags[i].Count);
420                 }
421 
422                 tmpFrags = TreeTools.SearchTreeFragments(extTree, null);
423                 for (int i = 0; i < tmpFrags.Count; i++) {
424                     fragments.Add(tmpFrags[i].Count);
425                 }
426             }
427 
428             using (var repMap = new GDMXRefReplacer()) {
429                 extTree.Header.Clear();
430                 while (extTree.RecordsCount > 0) {
431                     GDMRecord rec = extTree.Extract(0);
432                     var oldXRef = rec.XRef;
433                     var newXRef = mainTree.NewXRef(rec);
434                     repMap.AddXRef(rec, oldXRef, newXRef);
435                     rec.ResetTree(mainTree);
436                     mainTree.AddRecord(rec);
437                 }
438 
439                 for (int i = 0, num = repMap.Count; i < num; i++) {
440                     GDMRecord rec = repMap[i].Rec;
441                     rec.ReplaceXRefs(repMap);
442                 }
443 
444                 if (logBox != null) {
445                     logBox.AppendText(string.Format(LangMan.LS(LSID.LSID_MainBaseSize), mainTree.RecordsCount.ToString()) + CRLF);
446                 }
447             }
448 
449             if (selfTest) {
450                 var tmpFrags = TreeTools.SearchTreeFragments(mainTree, null);
451                 if (fragments.Count != tmpFrags.Count) {
452                     ThrowError(logBox, "The number of fragments is not as expected.");
453                 }
454                 for (int i = 0; i < tmpFrags.Count; i++) {
455                     if (fragments[i] != tmpFrags[i].Count) {
456                         ThrowError(logBox, "The number of persons in the fragment is not as expected.");
457                     }
458                 }
459             }
460         }
461 
ThrowError(ITextBox logBox, string message)462         private static void ThrowError(ITextBox logBox, string message)
463         {
464             if (logBox != null) {
465                 logBox.AppendText(message + CRLF);
466             } else {
467                 throw new GKException(message);
468             }
469         }
470 
MergeTreeFile(GDMTree mainTree, string fileName, ITextBox logBox, bool selfTest = false)471         public static void MergeTreeFile(GDMTree mainTree, string fileName, ITextBox logBox, bool selfTest = false)
472         {
473             if (mainTree == null)
474                 throw new ArgumentNullException("mainTree");
475 
476             if (string.IsNullOrEmpty(fileName))
477                 throw new ArgumentNullException("fileName");
478 
479             using (var extTree = new GDMTree()) {
480                 var gedcomProvider = new GEDCOMProvider(extTree);
481                 gedcomProvider.LoadFromFile(fileName);
482 
483                 MergeTree(mainTree, extTree, logBox, selfTest);
484             }
485         }
486 
MergeRecord(IBaseWindow baseWin, GDMRecord targetRec, GDMRecord sourceRec, bool bookmark)487         public static void MergeRecord(IBaseWindow baseWin, GDMRecord targetRec, GDMRecord sourceRec, bool bookmark)
488         {
489             if (baseWin == null)
490                 throw new ArgumentNullException("baseWin");
491 
492             if (targetRec == null)
493                 throw new ArgumentNullException("targetRec");
494 
495             if (sourceRec == null)
496                 throw new ArgumentNullException("sourceRec");
497 
498             using (var repMap = new GDMXRefReplacer()) {
499                 repMap.AddXRef(sourceRec, sourceRec.XRef, targetRec.XRef);
500 
501                 GDMTree tree = baseWin.Context.Tree;
502                 int num = tree.RecordsCount;
503                 for (int i = 0; i < num; i++) {
504                     tree[i].ReplaceXRefs(repMap);
505                 }
506 
507                 sourceRec.MoveTo(targetRec);
508                 bool res = baseWin.Context.DeleteRecord(sourceRec);
509 
510                 if (targetRec.RecordType == GDMRecordType.rtIndividual && bookmark) {
511                     ((GDMIndividualRecord)targetRec).Bookmark = true;
512                 }
513 
514                 baseWin.NotifyRecord(targetRec, RecordAction.raEdit);
515                 baseWin.RefreshLists(false);
516             }
517         }
518 
519         #endregion
520 
521         #region Base Checks
522 
523         public enum CheckDiag
524         {
525             cdPersonLonglived,
526             cdPersonSexless,
527             cdLiveYearsInvalid,
528             cdStrangeSpouse,
529             cdStrangeParent,
530             cdEmptyFamily,
531             cdFatherAsChild,
532             cdMotherAsChild,
533             cdDuplicateChildren,
534             csDateInvalid,
535             csCycle,
536             cdChildWithoutParents,
537             cdFamilyRecordWithoutFamily,
538             cdMediaRecordWithoutFiles,
539             cdStgNotFound,
540             cdArcNotFound,
541             cdFileNotFound,
542         }
543 
544         public enum CheckSolve
545         {
546             csSkip,
547             csSetIsDead,
548             csDefineSex,
549             csRemove,
550             csEdit,
551         }
552 
553         public sealed class CheckObj
554         {
555             public string Comment;
556             public CheckDiag Diag;
557             public GDMRecord Rec;
558             public CheckSolve Solve;
559 
CheckObj(GDMRecord rec, CheckDiag diag, CheckSolve solve)560             public CheckObj(GDMRecord rec, CheckDiag diag, CheckSolve solve)
561             {
562                 Rec = rec;
563                 Diag = diag;
564                 Solve = solve;
565             }
566 
GetRecordName(GDMTree tree)567             public string GetRecordName(GDMTree tree)
568             {
569                 string result = string.Empty;
570 
571                 switch (Rec.RecordType) {
572                     case GDMRecordType.rtIndividual:
573                         result = GKUtils.GetNameString(((GDMIndividualRecord)Rec), true, false);
574                         break;
575 
576                     case GDMRecordType.rtFamily:
577                         result = GKUtils.GetFamilyString(tree, (GDMFamilyRecord)Rec);
578                         break;
579                 }
580 
581                 result = string.Concat(result, " [ ", Rec.XRef, " ]");
582 
583                 return result;
584             }
585         }
586 
CheckRecordWithEvents(GDMRecordWithEvents rec, List<CheckObj> checksList)587         private static void CheckRecordWithEvents(GDMRecordWithEvents rec, List<CheckObj> checksList)
588         {
589             var dateZero = new DateTime(0);
590 
591             int num = rec.Events.Count;
592             for (int i = 0; i < num; i++) {
593                 GDMCustomEvent evt = rec.Events[i];
594 
595                 bool invalid = false;
596                 try {
597                     var dtx = evt.Date.GetDateTime();
598 
599                     /*if (dtx == dateZero) {
600                         invalid = true;
601                     }*/
602                 } catch {
603                     invalid = true;
604                 }
605 
606                 if (invalid) {
607                     CheckObj checkObj = new CheckObj(rec, CheckDiag.csDateInvalid, CheckSolve.csEdit);
608                     checkObj.Comment = LangMan.LS(LSID.LSID_DateInvalid) + " (" + evt.Date.StringValue + ")";
609                     checksList.Add(checkObj);
610                 }
611             }
612         }
613 
CheckIndividualRecord(GDMTree tree, GDMIndividualRecord iRec, List<CheckObj> checksList)614         private static void CheckIndividualRecord(GDMTree tree, GDMIndividualRecord iRec, List<CheckObj> checksList)
615         {
616             CheckRecordWithEvents(iRec, checksList);
617 
618             if (iRec.FindEvent(GEDCOMTagType.DEAT) == null) {
619                 int age = GKUtils.GetAge(iRec, -1);
620 
621                 if (age != -1 && age >= GKData.PROVED_LIFE_LENGTH) {
622                     CheckObj checkObj = new CheckObj(iRec, CheckDiag.cdPersonLonglived, CheckSolve.csSetIsDead);
623                     checkObj.Comment = string.Format(LangMan.LS(LSID.LSID_PersonLonglived), age);
624                     checksList.Add(checkObj);
625                 }
626             }
627 
628             GDMSex sex = iRec.Sex;
629             if (sex < GDMSex.svMale || sex > GDMSex.svFemale) {
630                 CheckObj checkObj = new CheckObj(iRec, CheckDiag.cdPersonSexless, CheckSolve.csDefineSex);
631                 checkObj.Comment = LangMan.LS(LSID.LSID_PersonSexless);
632                 checksList.Add(checkObj);
633             }
634 
635             int yBirth = iRec.GetChronologicalYear(GEDCOMTagName.BIRT);
636             int yDeath = iRec.GetChronologicalYear(GEDCOMTagName.DEAT);
637             if (yBirth != 0 && yDeath != 0) {
638                 int delta = (yDeath - yBirth);
639                 if (delta < 0) {
640                     CheckObj checkObj = new CheckObj(iRec, CheckDiag.cdLiveYearsInvalid, CheckSolve.csSkip);
641                     checkObj.Comment = LangMan.LS(LSID.LSID_LiveYearsInvalid);
642                     checksList.Add(checkObj);
643                 }
644             }
645 
646             int iAge = GKUtils.GetMarriageAge(tree, iRec);
647             if (iAge > 0 && (iAge <= 13 || iAge >= 50)) {
648                 CheckObj checkObj = new CheckObj(iRec, CheckDiag.cdStrangeSpouse, CheckSolve.csSkip);
649                 checkObj.Comment = string.Format(LangMan.LS(LSID.LSID_StrangeSpouse), iAge.ToString());
650                 checksList.Add(checkObj);
651             }
652 
653             iAge = GKUtils.GetFirstbornAge(iRec, GKUtils.GetFirstborn(tree, iRec));
654             if (iAge > 0 && (iAge <= 13 || iAge >= 50)) {
655                 CheckObj checkObj = new CheckObj(iRec, CheckDiag.cdStrangeParent, CheckSolve.csSkip);
656                 checkObj.Comment = string.Format(LangMan.LS(LSID.LSID_StrangeParent), iAge.ToString());
657                 checksList.Add(checkObj);
658             }
659 
660             string cycle = CheckCycle(tree, iRec);
661             if (!string.IsNullOrEmpty(cycle)) {
662                 CheckObj checkObj = new CheckObj(iRec, CheckDiag.csCycle, CheckSolve.csSkip);
663                 checkObj.Comment = string.Format(LangMan.LS(LSID.LSID_DetectedDataLoop), cycle);
664                 checksList.Add(checkObj);
665             }
666         }
667 
CheckFamilyRecord(GDMTree tree, GDMFamilyRecord fRec, List<CheckObj> checksList)668         private static void CheckFamilyRecord(GDMTree tree, GDMFamilyRecord fRec, List<CheckObj> checksList)
669         {
670             CheckRecordWithEvents(fRec, checksList);
671 
672             var husb = tree.GetPtrValue<GDMIndividualRecord>(fRec.Husband);
673             var wife = tree.GetPtrValue<GDMIndividualRecord>(fRec.Wife);
674 
675             bool empty = (!fRec.HasNotes && !fRec.HasSourceCitations && !fRec.HasMultimediaLinks && !fRec.HasUserReferences);
676             empty = empty && (!fRec.HasEvents && fRec.Children.Count == 0);
677             empty = empty && (husb == null && wife == null);
678 
679             if (empty) {
680                 CheckObj checkObj = new CheckObj(fRec, CheckDiag.cdEmptyFamily, CheckSolve.csRemove);
681                 checkObj.Comment = LangMan.LS(LSID.LSID_EmptyFamily);
682                 checksList.Add(checkObj);
683             } else {
684                 int chNum = fRec.Children.Count;
685 
686                 if (husb == null && wife == null) {
687                     if (chNum > 0) {
688                         CheckObj checkObj = new CheckObj(fRec, CheckDiag.cdChildWithoutParents, CheckSolve.csSkip);
689                         checkObj.Comment = LangMan.LS(LSID.LSID_ChildWithoutParents);
690                         checksList.Add(checkObj);
691                     }
692                     else {
693                         CheckObj checkObj = new CheckObj(fRec, CheckDiag.cdFamilyRecordWithoutFamily, CheckSolve.csSkip);
694                         checkObj.Comment = LangMan.LS(LSID.LSID_FamilyRecordWithoutFamily);
695                         checksList.Add(checkObj);
696                     }
697                 }
698                 else {
699                     if (fRec.IndexOfChild(husb) >= 0) {
700                         CheckObj checkObj = new CheckObj(fRec, CheckDiag.cdFatherAsChild, CheckSolve.csRemove);
701                         checkObj.Comment = LangMan.LS(LSID.LSID_FatherAsChild);
702                         checksList.Add(checkObj);
703                     }
704 
705                     if (fRec.IndexOfChild(wife) >= 0) {
706                         CheckObj checkObj = new CheckObj(fRec, CheckDiag.cdMotherAsChild, CheckSolve.csRemove);
707                         checkObj.Comment = LangMan.LS(LSID.LSID_MotherAsChild);
708                         checksList.Add(checkObj);
709                     }
710                 }
711 
712                 bool hasDup = false;
713                 for (int i = 0; i < chNum; i++) {
714                     var child1 = fRec.Children[i];
715                     for (int k = i + 1; k < chNum; k++) {
716                         var child2 = fRec.Children[k];
717                         if (child2.XRef == child1.XRef) {
718                             hasDup = true;
719                             break;
720                         }
721                     }
722                     if (hasDup) break;
723                 }
724                 if (hasDup) {
725                     CheckObj checkObj = new CheckObj(fRec, CheckDiag.cdDuplicateChildren, CheckSolve.csEdit);
726                     checkObj.Comment = LangMan.LS(LSID.LSID_DuplicateChildrenInFamily);
727                     checksList.Add(checkObj);
728                 }
729             }
730         }
731 
732         private static bool StgNotFound;
733         private static bool ArcNotFound;
734 
CheckMultimediaRecord(IBaseContext baseContext, GDMMultimediaRecord mmRec, List<CheckObj> checksList)735         private static void CheckMultimediaRecord(IBaseContext baseContext, GDMMultimediaRecord mmRec, List<CheckObj> checksList)
736         {
737             if (mmRec.FileReferences.Count <= 0) {
738                 CheckObj checkObj = new CheckObj(mmRec, CheckDiag.cdMediaRecordWithoutFiles, CheckSolve.csRemove);
739                 checkObj.Comment = LangMan.LS(LSID.LSID_MediaRecordWithoutFiles);
740                 checksList.Add(checkObj);
741             }
742 
743             string fileName;
744             MediaStoreStatus storeStatus = baseContext.VerifyMediaFile(mmRec.FileReferences[0], out fileName);
745 
746             switch (storeStatus) {
747                 case MediaStoreStatus.mssExists:
748                     break;
749 
750                 case MediaStoreStatus.mssFileNotFound:
751                     {
752                         CheckObj checkObj = new CheckObj(mmRec, CheckDiag.cdFileNotFound, CheckSolve.csSkip);
753                         checkObj.Comment = LangMan.LS(LSID.LSID_FileNotFound, fileName);
754                         checksList.Add(checkObj);
755                     }
756                     break;
757 
758                 case MediaStoreStatus.mssStgNotFound:
759                     if (!StgNotFound) {
760                         CheckObj checkObj = new CheckObj(mmRec, CheckDiag.cdStgNotFound, CheckSolve.csSkip);
761                         checkObj.Comment = LangMan.LS(LSID.LSID_StgNotFound);
762                         checksList.Add(checkObj);
763                         StgNotFound = true;
764                     }
765                     break;
766 
767                 case MediaStoreStatus.mssArcNotFound:
768                     if (!ArcNotFound) {
769                         CheckObj checkObj = new CheckObj(mmRec, CheckDiag.cdArcNotFound, CheckSolve.csSkip);
770                         checkObj.Comment = LangMan.LS(LSID.LSID_ArcNotFound);
771                         checksList.Add(checkObj);
772                         ArcNotFound = true;
773                     }
774                     break;
775 
776                 case MediaStoreStatus.mssBadData:
777                     // TODO: can be deleted?
778                     break;
779             }
780         }
781 
CheckBase(IBaseWindow baseWin, List<CheckObj> checksList)782         public static void CheckBase(IBaseWindow baseWin, List<CheckObj> checksList)
783         {
784             if (baseWin == null)
785                 throw new ArgumentNullException("baseWin");
786 
787             if (checksList == null)
788                 throw new ArgumentNullException("checksList");
789 
790             StgNotFound = false;
791             ArcNotFound = false;
792 
793             IProgressController progress = AppHost.Progress;
794             try {
795                 GDMTree tree = baseWin.Context.Tree;
796                 progress.ProgressInit(LangMan.LS(LSID.LSID_ToolOp_7), tree.RecordsCount);
797                 checksList.Clear();
798 
799                 for (int i = 0, num = tree.RecordsCount; i < num; i++) {
800                     progress.ProgressStep();
801 
802                     GDMRecord rec = tree[i];
803                     switch (rec.RecordType) {
804                         case GDMRecordType.rtIndividual:
805                             CheckIndividualRecord(tree, rec as GDMIndividualRecord, checksList);
806                             break;
807 
808                         case GDMRecordType.rtFamily:
809                             CheckFamilyRecord(tree, rec as GDMFamilyRecord, checksList);
810                             break;
811 
812                         case GDMRecordType.rtMultimedia:
813                             CheckMultimediaRecord(baseWin.Context, rec as GDMMultimediaRecord, checksList);
814                             break;
815                     }
816                 }
817             } finally {
818                 progress.ProgressDone();
819             }
820         }
821 
RepairProblem(IBaseWindow baseWin, CheckObj checkObj)822         public static void RepairProblem(IBaseWindow baseWin, CheckObj checkObj)
823         {
824             if (baseWin == null)
825                 throw new ArgumentNullException("baseWin");
826 
827             if (checkObj == null)
828                 throw new ArgumentNullException("checkObj");
829 
830             GDMTree tree = baseWin.Context.Tree;
831             GDMIndividualRecord iRec;
832 
833             switch (checkObj.Diag) {
834                 case CheckDiag.cdPersonLonglived:
835                     iRec = checkObj.Rec as GDMIndividualRecord;
836                     baseWin.Context.CreateEventEx(iRec, GEDCOMTagName.DEAT, "", "");
837                     baseWin.NotifyRecord(iRec, RecordAction.raEdit);
838                     break;
839 
840                 case CheckDiag.cdPersonSexless:
841                     iRec = checkObj.Rec as GDMIndividualRecord;
842                     baseWin.Context.CheckPersonSex(iRec);
843                     baseWin.NotifyRecord(iRec, RecordAction.raEdit);
844                     break;
845 
846                 case CheckDiag.cdEmptyFamily:
847                     tree.DeleteRecord(checkObj.Rec);
848                     break;
849 
850                 case CheckDiag.cdFatherAsChild:
851                     {
852                         var fRec = ((GDMFamilyRecord)checkObj.Rec);
853                         fRec.DeleteChild(fRec.Husband);
854                     }
855                     break;
856 
857                 case CheckDiag.cdMotherAsChild:
858                     {
859                         var fRec = ((GDMFamilyRecord)checkObj.Rec);
860                         fRec.DeleteChild(fRec.Wife);
861                     }
862                     break;
863 
864                 case CheckDiag.cdDuplicateChildren:
865                     if (checkObj.Solve == CheckSolve.csEdit) {
866                         BaseController.EditRecord(baseWin, checkObj.Rec);
867                     }
868                     break;
869 
870                 case CheckDiag.csDateInvalid:
871                     if (checkObj.Solve == CheckSolve.csEdit) {
872                         BaseController.EditRecord(baseWin, checkObj.Rec);
873                     }
874                     break;
875             }
876         }
877 
878         #endregion
879 
880         #region Tree Split
881 
CheckRelations_AddRel(List<GDMRecord> splitList, GDMRecord rec)882         private static void CheckRelations_AddRel(List<GDMRecord> splitList, GDMRecord rec)
883         {
884             if (rec != null && splitList.IndexOf(rec) < 0) {
885                 splitList.Add(rec);
886             }
887         }
888 
CheckRelations_CheckNotes(GDMTree tree, List<GDMRecord> splitList, IGDMStructWithNotes tag)889         private static void CheckRelations_CheckNotes(GDMTree tree, List<GDMRecord> splitList, IGDMStructWithNotes tag)
890         {
891             if (tag == null || !tag.HasNotes) return;
892 
893             for (int i = 0, num = tag.Notes.Count; i < num; i++) {
894                 CheckRelations_CheckRecord(tree, splitList, tree.GetPtrValue<GDMRecord>(tag.Notes[i]));
895             }
896         }
897 
CheckRelations_CheckSourceCit(GDMTree tree, List<GDMRecord> splitList, IGDMStructWithSourceCitations tag)898         private static void CheckRelations_CheckSourceCit(GDMTree tree, List<GDMRecord> splitList, IGDMStructWithSourceCitations tag)
899         {
900             if (tag == null || !tag.HasSourceCitations) return;
901 
902             for (int i = 0, num = tag.SourceCitations.Count; i < num; i++) {
903                 CheckRelations_CheckRecord(tree, splitList, tree.GetPtrValue<GDMRecord>(tag.SourceCitations[i]));
904             }
905         }
906 
CheckRelations_CheckMediaLink(GDMTree tree, List<GDMRecord> splitList, IGDMStructWithMultimediaLinks tag)907         private static void CheckRelations_CheckMediaLink(GDMTree tree, List<GDMRecord> splitList, IGDMStructWithMultimediaLinks tag)
908         {
909             if (tag == null || !tag.HasMultimediaLinks) return;
910 
911             for (int i = 0, num = tag.MultimediaLinks.Count; i < num; i++) {
912                 CheckRelations_CheckRecord(tree, splitList, tree.GetPtrValue<GDMRecord>(tag.MultimediaLinks[i]));
913             }
914         }
915 
CheckRelations_CheckSWL(GDMTree tree, List<GDMRecord> splitList, IGDMStructWithLists tag)916         private static void CheckRelations_CheckSWL(GDMTree tree, List<GDMRecord> splitList, IGDMStructWithLists tag)
917         {
918             if (tag == null) return;
919 
920             CheckRelations_CheckNotes(tree, splitList, tag);
921             CheckRelations_CheckSourceCit(tree, splitList, tag);
922             CheckRelations_CheckMediaLink(tree, splitList, tag);
923         }
924 
CheckRelations_CheckRecord(GDMTree tree, List<GDMRecord> splitList, GDMRecord rec)925         private static void CheckRelations_CheckRecord(GDMTree tree, List<GDMRecord> splitList, GDMRecord rec)
926         {
927             if (rec == null) return;
928 
929             CheckRelations_CheckSWL(tree, splitList, rec);
930         }
931 
CheckRelations_CheckIndividual(GDMTree tree, List<GDMRecord> splitList, GDMIndividualRecord iRec)932         private static void CheckRelations_CheckIndividual(GDMTree tree, List<GDMRecord> splitList, GDMIndividualRecord iRec)
933         {
934             if (iRec == null) return;
935 
936             CheckRelations_CheckRecord(tree, splitList, iRec);
937 
938             for (int i = 0, num = iRec.ChildToFamilyLinks.Count; i < num; i++) {
939                 var cfl = iRec.ChildToFamilyLinks[i];
940                 CheckRelations_CheckNotes(tree, splitList, cfl);
941                 CheckRelations_CheckFamily(tree, splitList, tree.GetPtrValue(cfl));
942             }
943 
944             for (int i = 0, num = iRec.SpouseToFamilyLinks.Count; i < num; i++) {
945                 var sfl = iRec.SpouseToFamilyLinks[i];
946                 CheckRelations_CheckNotes(tree, splitList, sfl);
947                 CheckRelations_CheckFamily(tree, splitList, tree.GetPtrValue(sfl));
948             }
949 
950             if (iRec.HasEvents) {
951                 for (int i = 0, num = iRec.Events.Count; i < num; i++) {
952                     CheckRelations_CheckSWL(tree, splitList, iRec.Events[i]);
953                 }
954             }
955 
956             if (iRec.HasAssociations) {
957                 for (int i = 0, num = iRec.Associations.Count; i < num; i++) {
958                     var asso = iRec.Associations[i];
959                     CheckRelations_CheckNotes(tree, splitList, asso);
960                     CheckRelations_CheckSourceCit(tree, splitList, asso);
961                     CheckRelations_CheckIndividual(tree, splitList, tree.GetPtrValue(asso));
962                 }
963             }
964 
965             if (iRec.HasGroups) {
966                 for (int i = 0, num = iRec.Groups.Count; i < num; i++) {
967                     CheckRelations_CheckRecord(tree, splitList, tree.GetPtrValue<GDMRecord>(iRec.Groups[i]));
968                 }
969             }
970         }
971 
CheckRelations_CheckFamily(GDMTree tree, List<GDMRecord> splitList, GDMFamilyRecord fRec)972         private static void CheckRelations_CheckFamily(GDMTree tree, List<GDMRecord> splitList, GDMFamilyRecord fRec)
973         {
974             if (fRec == null) return;
975 
976             CheckRelations_CheckRecord(tree, splitList, fRec);
977 
978             CheckRelations_CheckRecord(tree, splitList, tree.GetPtrValue<GDMRecord>(fRec.Husband));
979             CheckRelations_CheckRecord(tree, splitList, tree.GetPtrValue<GDMRecord>(fRec.Wife));
980 
981             for (int i = 0, num = fRec.Children.Count; i < num; i++) {
982                 CheckRelations_CheckRecord(tree, splitList, tree.GetPtrValue<GDMRecord>(fRec.Children[i]));
983             }
984 
985             if (fRec.HasEvents) {
986                 for (int i = 0, num = fRec.Events.Count; i < num; i++) {
987                     CheckRelations_CheckSWL(tree, splitList, fRec.Events[i]);
988                 }
989             }
990         }
991 
CheckRelations_CheckSource(GDMTree tree, List<GDMRecord> splitList, GDMSourceRecord sRec)992         private static void CheckRelations_CheckSource(GDMTree tree, List<GDMRecord> splitList, GDMSourceRecord sRec)
993         {
994             if (sRec == null) return;
995 
996             CheckRelations_CheckRecord(tree, splitList, sRec);
997 
998             for (int i = 0, num = sRec.RepositoryCitations.Count; i < num; i++) {
999                 CheckRelations_CheckRecord(tree, splitList, tree.GetPtrValue<GDMRecord>(sRec.RepositoryCitations[i]));
1000             }
1001         }
1002 
CheckRelations(GDMTree tree, List<GDMRecord> splitList)1003         public static void CheckRelations(GDMTree tree, List<GDMRecord> splitList)
1004         {
1005             if (splitList == null)
1006                 throw new ArgumentNullException("splitList");
1007 
1008             int num = splitList.Count;
1009             for (int i = 0; i < num; i++) {
1010                 GDMRecord rec = splitList[i];
1011                 switch (rec.RecordType) {
1012                     case GDMRecordType.rtIndividual:
1013                         CheckRelations_CheckIndividual(tree, splitList, rec as GDMIndividualRecord);
1014                         break;
1015 
1016                     case GDMRecordType.rtFamily:
1017                         CheckRelations_CheckFamily(tree, splitList, rec as GDMFamilyRecord);
1018                         break;
1019 
1020                     case GDMRecordType.rtNote:
1021                         CheckRelations_CheckRecord(tree, splitList, rec);
1022                         break;
1023 
1024                     case GDMRecordType.rtMultimedia:
1025                         CheckRelations_CheckRecord(tree, splitList, rec);
1026                         break;
1027 
1028                     case GDMRecordType.rtSource:
1029                         CheckRelations_CheckSource(tree, splitList, rec as GDMSourceRecord);
1030                         break;
1031 
1032                     case GDMRecordType.rtRepository:
1033                         CheckRelations_CheckRecord(tree, splitList, rec);
1034                         break;
1035 
1036                     case GDMRecordType.rtSubmitter:
1037                         CheckRelations_CheckRecord(tree, splitList, rec);
1038                         break;
1039                 }
1040             }
1041         }
1042 
1043         #endregion
1044 
1045         #region Tree Compare
1046 
1047         public class IndividualRecordComparer: IComparer<ULIndividual>
1048         {
Compare(ULIndividual x, ULIndividual y)1049             public int Compare(ULIndividual x, ULIndividual y)
1050             {
1051                 return string.Compare(x.Family, y.Family, false);
1052             }
1053         }
1054 
1055         public class ULIndividual
1056         {
1057             public string Family;
1058             public GDMIndividualRecord IRec;
1059         }
1060 
GetUnlinkedNamesakes(IBaseWindow baseWin)1061         public static List<ULIndividual> GetUnlinkedNamesakes(IBaseWindow baseWin)
1062         {
1063             if (baseWin == null)
1064                 throw new ArgumentNullException("baseWin");
1065 
1066             GDMTree tree = baseWin.Context.Tree;
1067 
1068             List<ULIndividual> result = new List<ULIndividual>();
1069 
1070             Dictionary<string, List<GDMIndividualRecord>> families = new Dictionary<string, List<GDMIndividualRecord>>();
1071 
1072             IProgressController progress = AppHost.Progress;
1073             progress.ProgressInit(LangMan.LS(LSID.LSID_Stage) + "1", tree.RecordsCount, true);
1074 
1075             // make a table of surnames and persons, related to these surnames
1076             int num = tree.RecordsCount;
1077             for (int i = 0; i < num; i++) {
1078                 GDMRecord rec = tree[i];
1079 
1080                 if (rec.RecordType == GDMRecordType.rtIndividual) {
1081                     GDMIndividualRecord iRec = (GDMIndividualRecord)rec;
1082 
1083                     string[] fams = baseWin.Context.Culture.GetSurnames(iRec);
1084 
1085                     for (int k = 0; k < fams.Length; k++) {
1086                         string f = fams[k];
1087                         if (f.Length > 1) {
1088                             List<GDMIndividualRecord> ps;
1089                             if (!families.TryGetValue(f, out ps)) {
1090                                 ps = new List<GDMIndividualRecord>();
1091                                 families.Add(f, ps);
1092                             }
1093                             ps.Add(iRec);
1094                         }
1095                     }
1096                 }
1097 
1098                 if (progress.IsCanceled) break;
1099                 progress.ProgressStep();
1100             }
1101 
1102             progress.ProgressInit(LangMan.LS(LSID.LSID_Stage) + "2", families.Count, true);
1103 
1104             // find all persons of one surname, not related by ties of kinship
1105             foreach (KeyValuePair<string, List<GDMIndividualRecord>> entry in families) {
1106                 string fam = entry.Key;
1107                 List<GDMIndividualRecord> ps = entry.Value;
1108 
1109                 int i = 0;
1110                 while (i < ps.Count) {
1111                     GDMIndividualRecord iRec = ps[i];
1112 
1113                     List<GDMRecord> lst = new List<GDMRecord>();
1114                     WalkTree(tree, iRec, TreeWalkMode.twmAll, lst);
1115 
1116                     int num3 = lst.Count;
1117                     for (int k = 0; k < num3; k++) {
1118                         GDMIndividualRecord item = lst[k] as GDMIndividualRecord;
1119 
1120                         int idx = ps.IndexOf(item);
1121                         if (item != iRec && idx >= 0 && idx > i) ps.RemoveAt(idx);
1122                     }
1123 
1124                     i++;
1125                 }
1126 
1127                 int num2 = ps.Count;
1128                 for (i = 0; i < num2; i++) {
1129                     ULIndividual indiv = new ULIndividual();
1130                     indiv.Family = fam;
1131                     indiv.IRec = ps[i];
1132                     result.Add(indiv);
1133                 }
1134 
1135                 if (progress.IsCanceled) break;
1136                 progress.ProgressStep();
1137             }
1138 
1139             result.Sort(new IndividualRecordComparer());
1140 
1141             progress.ProgressDone();
1142 
1143             return result;
1144         }
1145 
DuplicateFoundFunc(GDMIndividualRecord indivA, GDMIndividualRecord indivB)1146         public delegate void DuplicateFoundFunc(GDMIndividualRecord indivA, GDMIndividualRecord indivB);
1147 
FindDuplicates(GDMTree treeA, GDMTree treeB, float matchThreshold, DuplicateFoundFunc foundFunc, IProgressController pc)1148         public static void FindDuplicates(GDMTree treeA, GDMTree treeB, float matchThreshold,
1149                                           DuplicateFoundFunc foundFunc, IProgressController pc)
1150         {
1151             if (treeA == null)
1152                 throw new ArgumentNullException("treeA");
1153 
1154             if (treeB == null)
1155                 throw new ArgumentNullException("treeB");
1156 
1157             if (foundFunc == null)
1158                 throw new ArgumentNullException("foundFunc");
1159 
1160             if (pc == null)
1161                 throw new ArgumentNullException("pc");
1162 
1163             MatchParams mParams;
1164             //mParams.IndistinctMatching = true;
1165             mParams.NamesIndistinctThreshold = 90.0f / 100.0f;
1166             mParams.DatesCheck = true;
1167             mParams.YearsInaccuracy = 3;
1168             mParams.CheckEventPlaces = false;
1169 
1170             pc.ProgressInit(LangMan.LS(LSID.LSID_DuplicatesSearch), treeA.RecordsCount, true);
1171             try {
1172                 for (int i = 0; i < treeA.RecordsCount; i++) {
1173                     GDMRecord recA = treeA[i];
1174                     if (recA.RecordType == GDMRecordType.rtIndividual) {
1175                         for (int k = 0; k < treeB.RecordsCount; k++) {
1176                             GDMRecord recB = treeB[k];
1177                             if (recB.RecordType == GDMRecordType.rtIndividual) {
1178                                 GDMIndividualRecord indivA = (GDMIndividualRecord)recA;
1179                                 GDMIndividualRecord indivB = (GDMIndividualRecord)recB;
1180 
1181                                 if (indivA != indivB && indivA.IsMatch(indivB, mParams) >= matchThreshold) {
1182                                     foundFunc(indivA, indivB);
1183                                 }
1184                             }
1185                         }
1186                     }
1187 
1188                     if (pc.IsCanceled) break;
1189                     pc.ProgressStep();
1190                     Thread.Sleep(1);
1191                 }
1192             } finally {
1193                 pc.ProgressDone();
1194             }
1195         }
1196 
CompareTree(IBaseContext context, string fileName, ITextBox logBox)1197         public static void CompareTree(IBaseContext context, string fileName, ITextBox logBox)
1198         {
1199             if (context == null)
1200                 throw new ArgumentNullException("context");
1201 
1202             if (logBox == null)
1203                 throw new ArgumentNullException("logBox");
1204 
1205             using (var tempTree = new GDMTree()) {
1206                 var gedcomProvider = new GEDCOMProvider(tempTree);
1207                 gedcomProvider.LoadFromFile(fileName);
1208 
1209                 CompareTree(context, tempTree, logBox);
1210             }
1211         }
1212 
CompareTree(IBaseContext context, GDMTree tempTree, ITextBox logBox)1213         public static void CompareTree(IBaseContext context, GDMTree tempTree, ITextBox logBox)
1214         {
1215             if (context == null)
1216                 throw new ArgumentNullException("context");
1217 
1218             if (tempTree == null)
1219                 throw new ArgumentNullException("tempTree");
1220 
1221             if (logBox == null)
1222                 throw new ArgumentNullException("logBox");
1223 
1224             GDMTree mainTree = context.Tree;
1225 
1226             StringList fams = new StringList();
1227             StringList names = new StringList();
1228 
1229             try {
1230                 logBox.AppendText(LangMan.LS(LSID.LSID_SearchMatches) + CRLF);
1231 
1232                 int mainCount = mainTree.RecordsCount;
1233                 for (int i = 0; i < mainCount; i++) {
1234                     GDMIndividualRecord iRec = mainTree[i] as GDMIndividualRecord;
1235                     if (iRec != null) {
1236                         int idx = names.AddObject(GKUtils.GetNameString(iRec, true, false), new ExtList<GDMIndividualRecord>());
1237                         ((ExtList<GDMIndividualRecord>)names.GetObject(idx)).Add(iRec);
1238 
1239                         var parts = GKUtils.GetNameParts(mainTree, iRec);
1240                         fams.AddObject(context.Culture.NormalizeSurname(parts.Surname, iRec.Sex == GDMSex.svFemale), null);
1241                     }
1242                 }
1243 
1244                 int tempCount = tempTree.RecordsCount;
1245                 for (int i = 0; i < tempCount; i++) {
1246                     GDMIndividualRecord iRec = tempTree[i] as GDMIndividualRecord;
1247                     if (iRec != null) {
1248                         string tm = GKUtils.GetNameString(iRec, true, false);
1249                         int idx = names.IndexOf(tm);
1250                         if (idx >= 0) {
1251                             ((ExtList<GDMIndividualRecord>)names.GetObject(idx)).Add(iRec);
1252                         }
1253 
1254                         var parts = GKUtils.GetNameParts(tempTree, iRec);
1255                         tm = context.Culture.NormalizeSurname(parts.Surname, iRec.Sex == GDMSex.svFemale);
1256                         idx = fams.IndexOf(tm);
1257                         if (idx >= 0) {
1258                             fams.SetObject(idx, 1);
1259                         }
1260                     }
1261                 }
1262 
1263                 for (int i = fams.Count - 1; i >= 0; i--) {
1264                     if (fams.GetObject(i) == null || fams[i] == "?")
1265                         fams.Delete(i);
1266                 }
1267 
1268                 for (int i = names.Count - 1; i >= 0; i--) {
1269                     ExtList<GDMIndividualRecord> lst = (ExtList<GDMIndividualRecord>)names.GetObject(i);
1270 
1271                     if (lst.Count == 1) {
1272                         lst.Dispose();
1273                         names.Delete(i);
1274                     }
1275                 }
1276 
1277                 int famsCount = fams.Count;
1278                 if (famsCount != 0) {
1279                     logBox.AppendText(LangMan.LS(LSID.LSID_SimilarSurnames) + CRLF);
1280                     for (int i = 0; i < famsCount; i++) {
1281                         logBox.AppendText("    " + fams[i] + CRLF);
1282                     }
1283                 }
1284 
1285                 int namesCount = names.Count;
1286                 if (namesCount != 0) {
1287                     logBox.AppendText(LangMan.LS(LSID.LSID_SimilarNames) + CRLF);
1288                     for (int i = 0; i < namesCount; i++) {
1289                         logBox.AppendText("    " + names[i] + CRLF);
1290                         ExtList<GDMIndividualRecord> lst = (ExtList<GDMIndividualRecord>)names.GetObject(i);
1291 
1292                         int num5 = lst.Count;
1293                         for (int j = 0; j < num5; j++) {
1294                             GDMIndividualRecord iRec = lst[j];
1295                             logBox.AppendText("      * " + GKUtils.GetNameString(iRec, true, false) + " " + GKUtils.GetLifeStr(iRec) + CRLF);
1296                         }
1297                     }
1298                 }
1299 
1300                 if (famsCount == 0 && namesCount == 0) {
1301                     logBox.AppendText(LangMan.LS(LSID.LSID_MatchesNotFound) + CRLF);
1302                 }
1303             } finally {
1304                 int namesCount = names.Count;
1305                 for (int i = 0; i < namesCount; i++) {
1306                     IDisposable inst = names.GetObject(i) as IDisposable;
1307                     if (inst != null) inst.Dispose();
1308                 }
1309                 names.Dispose();
1310 
1311                 fams.Dispose();
1312             }
1313         }
1314 
1315         #endregion
1316 
1317         #region Places Management
1318 
SearchPlaces_Clear(StringList placesList)1319         public static void SearchPlaces_Clear(StringList placesList)
1320         {
1321             if (placesList == null)
1322                 throw new ArgumentNullException("placesList");
1323 
1324             for (int i = placesList.Count - 1; i >= 0; i--) ((PlaceObj)placesList.GetObject(i)).Dispose();
1325             placesList.Clear();
1326         }
1327 
SearchPlaces_CheckEventPlace(GDMTree tree, StringList placesList, GDMCustomEvent evt, bool checkLocation)1328         private static void SearchPlaces_CheckEventPlace(GDMTree tree, StringList placesList, GDMCustomEvent evt, bool checkLocation)
1329         {
1330             if (!evt.HasPlace) return;
1331             string placeStr = evt.Place.StringValue;
1332             if (string.IsNullOrEmpty(placeStr)) return;
1333 
1334             if (checkLocation) {
1335                 var locRec = tree.GetPtrValue<GDMLocationRecord>(evt.Place.Location);
1336                 if (locRec != null) {
1337                     placeStr = "[*] " + placeStr;
1338                 }
1339             }
1340 
1341             int idx = placesList.IndexOf(placeStr);
1342 
1343             PlaceObj placeObj;
1344             if (idx >= 0) {
1345                 placeObj = (PlaceObj)placesList.GetObject(idx);
1346             } else {
1347                 placeObj = new PlaceObj(placeStr);
1348                 placesList.AddObject(placeStr, placeObj);
1349             }
1350             placeObj.Facts.Add(evt);
1351         }
1352 
SearchPlaces(GDMTree tree, StringList placesList, IProgressController pc, bool checkLocation = true)1353         public static void SearchPlaces(GDMTree tree, StringList placesList, IProgressController pc, bool checkLocation = true)
1354         {
1355             if (tree == null)
1356                 throw new ArgumentNullException("tree");
1357 
1358             if (placesList == null)
1359                 throw new ArgumentNullException("placesList");
1360 
1361             if (pc == null)
1362                 throw new ArgumentNullException("pc");
1363 
1364             SearchPlaces_Clear(placesList);
1365 
1366             try {
1367                 int recsCount = tree.RecordsCount;
1368                 pc.ProgressInit(LangMan.LS(LSID.LSID_PlacesPrepare), recsCount);
1369 
1370                 for (int i = 0; i < recsCount; i++) {
1371                     pc.ProgressStep();
1372 
1373                     var evsRec = tree[i] as GDMRecordWithEvents;
1374                     if (evsRec != null && evsRec.HasEvents) {
1375                         int num2 = evsRec.Events.Count;
1376                         for (int j = 0; j < num2; j++) {
1377                             GDMCustomEvent evt = evsRec.Events[j];
1378 
1379                             SearchPlaces_CheckEventPlace(tree, placesList, evt, checkLocation);
1380                         }
1381                     }
1382                 }
1383             } finally {
1384                 pc.ProgressDone();
1385             }
1386         }
1387 
1388         #endregion
1389 
1390         #region Tree fragments
1391 
SearchTreeFragments(GDMTree tree, IProgressController progress)1392         public static List<List<GDMRecord>> SearchTreeFragments(GDMTree tree, IProgressController progress)
1393         {
1394             List<List<GDMRecord>> result = new List<List<GDMRecord>>();
1395 
1396             if (progress != null) {
1397                 progress.ProgressInit(LangMan.LS(LSID.LSID_CheckFamiliesConnection), tree.RecordsCount);
1398             }
1399 
1400             List<GDMRecord> prepared = new List<GDMRecord>();
1401             try {
1402                 int num = tree.RecordsCount;
1403                 for (int i = 0; i < num; i++) {
1404                     GDMIndividualRecord iRec = tree[i] as GDMIndividualRecord;
1405                     if (iRec != null) {
1406                         if (prepared.IndexOf(iRec) < 0) {
1407                             var groupRecords = new List<GDMRecord>();
1408                             WalkTree(tree, iRec, TreeWalkMode.twmAll, groupRecords);
1409                             result.Add(groupRecords);
1410                             prepared.AddRange(groupRecords);
1411                         }
1412                     }
1413 
1414                     if (progress != null) {
1415                         progress.ProgressStep();
1416                     }
1417                 }
1418             } finally {
1419                 prepared.Clear();
1420 
1421                 if (progress != null) {
1422                     progress.ProgressDone();
1423                 }
1424             }
1425 
1426             return result;
1427         }
1428 
1429         #endregion
1430     }
1431 }
1432