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 GDModel.Providers.GEDCOM; 23 using GKCore.Types; 24 25 namespace GDModel 26 { 27 // Standard: [M]Male / [F]Female / [U]Unknown (gedcom-5.5.1, p.61) 28 // Tamura Jones: +[X]Intersex (GEDCOM 5.5.1 Specification Annotated Edition) 29 public enum GDMSex 30 { 31 svUnknown, 32 svMale, 33 svFemale, 34 svIntersex, 35 36 svLast = svFemale 37 } 38 39 40 public sealed class GDMIndividualRecord : GDMRecordWithEvents, IGDMIndividualRecord 41 { 42 private GDMList<GDMAssociation> fAssociations; 43 private GDMList<GDMChildToFamilyLink> fChildToFamilyLinks; 44 private GDMList<GDMPointer> fGroups; 45 private GDMList<GDMPersonalName> fPersonalNames; 46 private GDMList<GDMSpouseToFamilyLink> fSpouseToFamilyLinks; 47 private GDMSex fSex; 48 49 50 public bool HasAssociations 51 { 52 get { return fAssociations != null && fAssociations.Count != 0; } 53 } 54 55 public GDMList<GDMAssociation> Associations 56 { 57 get { 58 if (fAssociations == null) { 59 fAssociations = new GDMList<GDMAssociation>(); 60 } 61 62 return fAssociations; 63 } 64 } 65 66 public bool Bookmark 67 { 68 get { 69 return FindTag(GEDCOMTagName._BOOKMARK, 0) != null; 70 } 71 set { 72 if (value) { 73 if (FindTag(GEDCOMTagName._BOOKMARK, 0) == null) { 74 AddTag(new GDMValueTag((int)GEDCOMTagType._BOOKMARK, "")); 75 } 76 } else { 77 DeleteTag(GEDCOMTagName._BOOKMARK); 78 } 79 } 80 } 81 82 public GDMList<GDMChildToFamilyLink> ChildToFamilyLinks 83 { 84 get { return fChildToFamilyLinks; } 85 } 86 87 public bool HasGroups 88 { 89 get { return fGroups != null && fGroups.Count != 0; } 90 } 91 92 public GDMList<GDMPointer> Groups 93 { 94 get { 95 if (fGroups == null) { 96 fGroups = new GDMList<GDMPointer>(); 97 } 98 99 return fGroups; 100 } 101 } 102 103 public bool Patriarch 104 { 105 get { 106 return FindTag(GEDCOMTagName._PATRIARCH, 0) != null; 107 } 108 set { 109 if (value) { 110 if (FindTag(GEDCOMTagName._PATRIARCH, 0) == null) { 111 AddTag(new GDMValueTag((int)GEDCOMTagType._PATRIARCH, "")); 112 } 113 } else { 114 DeleteTag(GEDCOMTagName._PATRIARCH); 115 } 116 } 117 } 118 119 public GDMList<GDMPersonalName> PersonalNames 120 { 121 get { return fPersonalNames; } 122 } 123 124 public GDMSex Sex 125 { 126 get { return fSex; } 127 set { fSex = value; } 128 } 129 130 public GDMList<GDMSpouseToFamilyLink> SpouseToFamilyLinks 131 { 132 get { return fSpouseToFamilyLinks; } 133 } 134 135 GDMIndividualRecord(GDMTree tree)136 public GDMIndividualRecord(GDMTree tree) : base(tree) 137 { 138 SetName(GEDCOMTagType.INDI); 139 140 fChildToFamilyLinks = new GDMList<GDMChildToFamilyLink>(); 141 fPersonalNames = new GDMList<GDMPersonalName>(); 142 fSpouseToFamilyLinks = new GDMList<GDMSpouseToFamilyLink>(); 143 } 144 Dispose(bool disposing)145 protected override void Dispose(bool disposing) 146 { 147 if (disposing) { 148 if (fAssociations != null) fAssociations.Dispose(); 149 fChildToFamilyLinks.Dispose(); 150 if (fGroups != null) fGroups.Dispose(); 151 fPersonalNames.Dispose(); 152 fSpouseToFamilyLinks.Dispose(); 153 } 154 base.Dispose(disposing); 155 } 156 TrimExcess()157 internal override void TrimExcess() 158 { 159 base.TrimExcess(); 160 161 if (fAssociations != null) fAssociations.TrimExcess(); 162 fChildToFamilyLinks.TrimExcess(); 163 if (fGroups != null) fGroups.TrimExcess(); 164 fPersonalNames.TrimExcess(); 165 fSpouseToFamilyLinks.TrimExcess(); 166 } 167 AddEvent(GDMCustomEvent evt)168 public override GDMCustomEvent AddEvent(GDMCustomEvent evt) 169 { 170 if (evt != null) { 171 if (evt is GDMIndividualEvent || evt is GDMIndividualAttribute) { 172 Events.Add(evt); 173 } else { 174 throw new ArgumentException(@"Event has the invalid type", "evt"); 175 } 176 } 177 178 return evt; 179 } 180 AddPersonalName(GDMPersonalName value)181 public GDMPersonalName AddPersonalName(GDMPersonalName value) 182 { 183 if (value != null) { 184 fPersonalNames.Add(value); 185 } 186 return value; 187 } 188 Clear()189 public override void Clear() 190 { 191 base.Clear(); 192 193 fSex = GDMSex.svUnknown; 194 195 for (int i = fChildToFamilyLinks.Count - 1; i >= 0; i--) { 196 var family = fTree.GetPtrValue<GDMFamilyRecord>(fChildToFamilyLinks[i]); 197 family.DeleteChild(this); 198 } 199 fChildToFamilyLinks.Clear(); 200 201 for (int i = fSpouseToFamilyLinks.Count - 1; i >= 0; i--) { 202 var family = fTree.GetPtrValue<GDMFamilyRecord>(fSpouseToFamilyLinks[i]); 203 family.RemoveSpouse(this); 204 } 205 fSpouseToFamilyLinks.Clear(); 206 207 if (fGroups != null) { 208 for (int i = fGroups.Count - 1; i >= 0; i--) { 209 var group = fTree.GetPtrValue<GDMGroupRecord>(fGroups[i]); 210 group.RemoveMember(this); 211 } 212 fGroups.Clear(); 213 } 214 215 if (fAssociations != null) fAssociations.Clear(); 216 217 fPersonalNames.Clear(); 218 } 219 IsEmpty()220 public override bool IsEmpty() 221 { 222 return base.IsEmpty() && (fSex == GDMSex.svUnknown) && (fPersonalNames.Count == 0) 223 && (fChildToFamilyLinks.Count == 0) && (fSpouseToFamilyLinks.Count == 0) 224 && (fAssociations == null || fAssociations.Count == 0) 225 && (fGroups == null || fGroups.Count == 0); 226 } 227 IndexOfGroup(GDMGroupRecord groupRec)228 public int IndexOfGroup(GDMGroupRecord groupRec) 229 { 230 if (groupRec != null && fGroups != null) { 231 int num = fGroups.Count; 232 for (int i = 0; i < num; i++) { 233 if (fGroups[i].XRef == groupRec.XRef) { 234 return i; 235 } 236 } 237 } 238 239 return -1; 240 } 241 IndexOfSpouse(GDMFamilyRecord familyRec)242 public int IndexOfSpouse(GDMFamilyRecord familyRec) 243 { 244 if (familyRec != null) { 245 int num = fSpouseToFamilyLinks.Count; 246 for (int i = 0; i < num; i++) { 247 if (fSpouseToFamilyLinks[i].XRef == familyRec.XRef) { 248 return i; 249 } 250 } 251 } 252 253 return -1; 254 } 255 DeleteSpouseToFamilyLink(GDMFamilyRecord familyRec)256 public void DeleteSpouseToFamilyLink(GDMFamilyRecord familyRec) 257 { 258 if (familyRec == null) return; 259 260 int num = fSpouseToFamilyLinks.Count; 261 for (int i = 0; i < num; i++) { 262 if (fSpouseToFamilyLinks[i].XRef == familyRec.XRef) { 263 fSpouseToFamilyLinks.DeleteAt(i); 264 break; 265 } 266 } 267 } 268 DeleteChildToFamilyLink(GDMFamilyRecord familyRec)269 public void DeleteChildToFamilyLink(GDMFamilyRecord familyRec) 270 { 271 if (familyRec == null) return; 272 273 int num = fChildToFamilyLinks.Count; 274 for (int i = 0; i < num; i++) { 275 if (fChildToFamilyLinks[i].XRef == familyRec.XRef) { 276 fChildToFamilyLinks.DeleteAt(i); 277 break; 278 } 279 } 280 } 281 FindChildToFamilyLink(GDMFamilyRecord familyRec)282 public GDMChildToFamilyLink FindChildToFamilyLink(GDMFamilyRecord familyRec) 283 { 284 if (familyRec == null) return null; 285 286 for (int i = 0, num = fChildToFamilyLinks.Count; i < num; i++) { 287 var childLink = fChildToFamilyLinks[i]; 288 if (childLink.XRef == familyRec.XRef) { 289 return childLink; 290 } 291 } 292 293 return null; 294 } 295 ExchangeSpouses(int index1, int index2)296 public void ExchangeSpouses(int index1, int index2) 297 { 298 fSpouseToFamilyLinks.Exchange(index1, index2); 299 } 300 IsLive()301 public bool IsLive() 302 { 303 return FindEvent(GEDCOMTagType.DEAT) == null; 304 } 305 Assign(GDMTag source)306 public override void Assign(GDMTag source) 307 { 308 GDMIndividualRecord sourceRec = source as GDMIndividualRecord; 309 if (sourceRec == null) 310 throw new ArgumentException(@"Argument is null or wrong type", "source"); 311 312 base.Assign(source); 313 314 fSex = sourceRec.fSex; 315 316 foreach (GDMPersonalName srcName in sourceRec.fPersonalNames) { 317 GDMPersonalName copyName = new GDMPersonalName(); 318 copyName.Assign(srcName); 319 AddPersonalName(copyName); 320 } 321 } 322 MoveTo(GDMRecord targetRecord)323 public override void MoveTo(GDMRecord targetRecord) 324 { 325 GDMIndividualRecord targetIndi = targetRecord as GDMIndividualRecord; 326 if (targetIndi == null) { 327 throw new ArgumentException(@"Argument is null or wrong type", "targetRecord"); 328 } 329 330 base.MoveTo(targetRecord); 331 332 targetIndi.Sex = fSex; 333 334 while (fPersonalNames.Count > 0) { 335 GDMPersonalName obj = fPersonalNames.Extract(0); 336 targetIndi.AddPersonalName(obj); 337 } 338 339 string currentXRef = this.XRef; 340 string targetXRef = targetRecord.XRef; 341 342 while (fChildToFamilyLinks.Count > 0) { 343 GDMChildToFamilyLink ctfLink = fChildToFamilyLinks.Extract(0); 344 var family = fTree.GetPtrValue<GDMFamilyRecord>(ctfLink); 345 346 int num = family.Children.Count; 347 for (int i = 0; i < num; i++) { 348 GDMIndividualLink childPtr = family.Children[i]; 349 350 if (childPtr.XRef == currentXRef) { 351 childPtr.XRef = targetXRef; 352 } 353 } 354 355 targetIndi.ChildToFamilyLinks.Add(ctfLink); 356 } 357 358 while (fSpouseToFamilyLinks.Count > 0) { 359 GDMSpouseToFamilyLink stfLink = fSpouseToFamilyLinks.Extract(0); 360 var family = fTree.GetPtrValue<GDMFamilyRecord>(stfLink); 361 362 if (family.Husband.XRef == currentXRef) { 363 family.Husband.XRef = targetXRef; 364 } else if (family.Wife.XRef == currentXRef) { 365 family.Wife.XRef = targetXRef; 366 } 367 368 targetIndi.SpouseToFamilyLinks.Add(stfLink); 369 } 370 371 while (fAssociations != null && fAssociations.Count > 0) { 372 GDMAssociation obj = fAssociations.Extract(0); 373 targetIndi.Associations.Add(obj); 374 } 375 376 while (fGroups != null && fGroups.Count > 0) { 377 GDMPointer obj = fGroups.Extract(0); 378 targetIndi.Groups.Add(obj); 379 } 380 } 381 ReplaceXRefs(GDMXRefReplacer map)382 public override void ReplaceXRefs(GDMXRefReplacer map) 383 { 384 base.ReplaceXRefs(map); 385 386 if (fAssociations != null) fAssociations.ReplaceXRefs(map); 387 fChildToFamilyLinks.ReplaceXRefs(map); 388 if (fGroups != null) fGroups.ReplaceXRefs(map); 389 fPersonalNames.ReplaceXRefs(map); 390 fSpouseToFamilyLinks.ReplaceXRefs(map); 391 } 392 393 public sealed class LifeDatesRet 394 { 395 public readonly GDMCustomEvent BirthEvent; 396 public readonly GDMCustomEvent DeathEvent; 397 LifeDatesRet(GDMCustomEvent birthEvent, GDMCustomEvent deathEvent)398 public LifeDatesRet(GDMCustomEvent birthEvent, GDMCustomEvent deathEvent) 399 { 400 BirthEvent = birthEvent; 401 DeathEvent = deathEvent; 402 } 403 } 404 GetLifeDates()405 public LifeDatesRet GetLifeDates() 406 { 407 GDMCustomEvent birthEvent = null; 408 GDMCustomEvent deathEvent = null; 409 410 int num = Events.Count; 411 for (int i = 0; i < num; i++) { 412 GDMCustomEvent evt = Events[i]; 413 var evtType = evt.GetTagType(); 414 415 if (evtType == GEDCOMTagType.BIRT && birthEvent == null) { 416 birthEvent = evt; 417 } else if (evtType == GEDCOMTagType.DEAT && deathEvent == null) { 418 deathEvent = evt; 419 } 420 } 421 422 return new LifeDatesRet(birthEvent, deathEvent); 423 } 424 GetPrimaryFullName()425 public string GetPrimaryFullName() 426 { 427 string result = (fPersonalNames.Count <= 0) ? string.Empty : fPersonalNames[0].FullName; 428 return result; 429 } 430 GetComparableName(bool onlyFirstPart)431 private string GetComparableName(bool onlyFirstPart) 432 { 433 string resName; 434 435 if (fPersonalNames.Count > 0) { 436 GDMPersonalName np = fPersonalNames[0]; 437 438 if (onlyFirstPart) { 439 resName = np.FirstPart; 440 } else { 441 resName = np.StringValue; 442 } 443 } else { 444 resName = ""; 445 } 446 447 return resName; 448 } 449 IsMatch(GDMTag tag, MatchParams matchParams)450 public override float IsMatch(GDMTag tag, MatchParams matchParams) 451 { 452 GDMIndividualRecord indi = tag as GDMIndividualRecord; 453 if (indi == null) return 0.0f; 454 455 if (Sex != indi.Sex) return 0.0f; 456 457 bool womanMode = (Sex == GDMSex.svFemale); 458 459 float matchesCount = 0.0f; 460 float nameMatch = 0.0f; 461 float birthMatch = 0.0f; 462 float deathMatch = 0.0f; 463 464 // check name 465 /*for (int i = 0; i < indi.PersonalNames.Count; i++) 466 { 467 for (int k = 0; k < fPersonalNames.Count; k++) 468 { 469 float currentNameMatch = fPersonalNames[k].IsMatch(indi.PersonalNames[i]); 470 nameMatch = Math.Max(nameMatch, currentNameMatch); 471 } 472 }*/ 473 474 string iName = GetComparableName(womanMode); 475 string kName = indi.GetComparableName(womanMode); 476 477 if (!string.IsNullOrEmpty(iName) && !string.IsNullOrEmpty(kName)) { 478 nameMatch = GetStrMatch(iName, kName, matchParams); 479 matchesCount++; 480 } 481 482 // 0% name match would be pointless checking other details 483 if (nameMatch != 0.0f && matchParams.DatesCheck) 484 { 485 var dates = GetLifeDates(); 486 var indiDates = indi.GetLifeDates(); 487 488 if (dates.BirthEvent != null && indiDates.BirthEvent != null) { 489 birthMatch = dates.BirthEvent.IsMatch(indiDates.BirthEvent, matchParams); 490 matchesCount++; 491 } else if (dates.BirthEvent == null && indiDates.BirthEvent == null) { 492 birthMatch = 100.0f; 493 matchesCount++; 494 } else { 495 matchesCount++; 496 } 497 498 /*if (death != null && indiDeath != null) { 499 deathMatch = death.IsMatch(indiDeath, matchParams); 500 matches++; 501 } else if (death == null && indiDeath == null) { 502 deathMatch = 100.0f; 503 matches++; 504 } else { 505 matches++; 506 }*/ 507 } 508 509 float match = (nameMatch + birthMatch + deathMatch) / matchesCount; 510 return match; 511 } 512 AddAssociation(string relation, GDMIndividualRecord relPerson)513 public GDMAssociation AddAssociation(string relation, GDMIndividualRecord relPerson) 514 { 515 GDMAssociation result = new GDMAssociation(); 516 result.Relation = relation; 517 result.XRef = (relPerson == null) ? string.Empty : relPerson.XRef; 518 Associations.Add(result); 519 return result; 520 } 521 SetPrimaryMultimediaLink(GDMMultimediaRecord mediaRec)522 public GDMMultimediaLink SetPrimaryMultimediaLink(GDMMultimediaRecord mediaRec) 523 { 524 if (mediaRec == null) return null; 525 GDMMultimediaLink mmLink = null; 526 527 if (HasMultimediaLinks) { 528 int num = MultimediaLinks.Count; 529 for (int i = 0; i < num; i++) { 530 GDMMultimediaLink lnk = MultimediaLinks[i]; 531 532 if (lnk.XRef == mediaRec.XRef) { 533 mmLink = lnk; 534 break; 535 } 536 } 537 } 538 539 if (mmLink == null) { 540 mmLink = this.AddMultimedia(mediaRec); 541 } 542 543 mmLink.IsPrimary = true; 544 return mmLink; 545 } 546 GetPrimaryMultimediaLink()547 public GDMMultimediaLink GetPrimaryMultimediaLink() 548 { 549 GDMMultimediaLink result = null; 550 if (!HasMultimediaLinks) return result; 551 552 int num = MultimediaLinks.Count; 553 for (int i = 0; i < num; i++) { 554 GDMMultimediaLink mmLink = MultimediaLinks[i]; 555 if (mmLink.IsPrimary) { 556 result = mmLink; 557 break; 558 } 559 } 560 561 return result; 562 } 563 } 564 } 565