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 BSLib; 23 using GKCore; 24 using GKCore.Interfaces; 25 using GKCore.Types; 26 27 namespace GDModel.Providers.GEDCOM 28 { 29 /// <summary> 30 /// Class to check the GEDCOM format. 31 /// </summary> 32 public class GEDCOMChecker 33 { 34 private IBaseContext fBaseContext; 35 private GEDCOMFormat fFormat; 36 private IProgressController fProgress; 37 private GDMTree fTree; 38 GEDCOMChecker(IBaseContext baseContext, IProgressController progress)39 private GEDCOMChecker(IBaseContext baseContext, IProgressController progress) 40 { 41 fBaseContext = baseContext; 42 fTree = fBaseContext.Tree; 43 fFormat = GEDCOMProvider.GetGEDCOMFormat(fTree); 44 fProgress = progress; 45 } 46 TransformNote(GDMNotes note)47 private void TransformNote(GDMNotes note) 48 { 49 GDMNoteRecord noteRec = fTree.CreateNote(); 50 noteRec.Lines.Assign(note.Lines); 51 52 note.Clear(); 53 note.XRef = noteRec.XRef; 54 } 55 TransformMultimediaLink(GDMMultimediaLink mmLink)56 private void TransformMultimediaLink(GDMMultimediaLink mmLink) 57 { 58 string title = mmLink.Title; 59 GDMMultimediaRecord mmRec = fTree.CreateMultimedia(); 60 61 int num = mmLink.FileReferences.Count; 62 for (int i = 0; i < num; i++) { 63 GDMFileReference srcFileRef = mmLink.FileReferences[i]; 64 GDMFileReferenceWithTitle tgtFileRef = new GDMFileReferenceWithTitle(); 65 66 tgtFileRef.LinkFile(srcFileRef.StringValue); 67 68 if (srcFileRef.MultimediaFormat != GDMMultimediaFormat.mfNone) { 69 tgtFileRef.MultimediaFormat = srcFileRef.MultimediaFormat; 70 } 71 if (srcFileRef.MediaType != GDMMediaType.mtUnknown) { 72 tgtFileRef.MediaType = srcFileRef.MediaType; 73 } 74 tgtFileRef.Title = title; 75 76 mmRec.FileReferences.Add(tgtFileRef); 77 } 78 79 mmLink.Clear(); 80 mmLink.XRef = mmRec.XRef; 81 } 82 TransformSourceCitation(GDMSourceCitation sourCit)83 private void TransformSourceCitation(GDMSourceCitation sourCit) 84 { 85 GDMSourceRecord sourRec = fTree.CreateSource(); 86 87 sourRec.Title.Lines.Assign(sourCit.Description); 88 sourRec.Text.Lines.Assign(sourCit.Text.Lines); 89 90 // transfers notes and multimedia from the citation to the source record 91 sourRec.AssignList(sourCit.Notes, sourRec.Notes); 92 sourRec.AssignList(sourCit.MultimediaLinks, sourRec.MultimediaLinks); 93 94 sourCit.Description.Clear(); 95 sourCit.Text.Clear(); 96 sourCit.Notes.Clear(); 97 sourCit.MultimediaLinks.Clear(); 98 sourCit.XRef = sourRec.XRef; 99 100 CheckTagWithNotes(sourRec); 101 CheckTagWithMultimediaLinks(sourRec); 102 } 103 CheckTagWithNotes(IGDMStructWithNotes tag)104 private void CheckTagWithNotes(IGDMStructWithNotes tag) 105 { 106 if (!tag.HasNotes) return; 107 108 for (int i = tag.Notes.Count - 1; i >= 0; i--) { 109 GDMNotes note = tag.Notes[i]; 110 if (!note.IsPointer) { 111 TransformNote(note); 112 } else { 113 var noteRec = fTree.GetPtrValue<GDMNoteRecord>(note); 114 if (noteRec == null) tag.Notes.DeleteAt(i); 115 } 116 } 117 } 118 CheckTagWithSourceCitations(IGDMStructWithSourceCitations tag)119 private void CheckTagWithSourceCitations(IGDMStructWithSourceCitations tag) 120 { 121 if (!tag.HasSourceCitations) return; 122 123 for (int i = tag.SourceCitations.Count - 1; i >= 0; i--) { 124 GDMSourceCitation sourCit = tag.SourceCitations[i]; 125 if (!sourCit.IsPointer) { 126 TransformSourceCitation(sourCit); 127 } else { 128 var sourRec = fTree.GetPtrValue<GDMSourceRecord>(sourCit); 129 if (sourRec == null) tag.SourceCitations.DeleteAt(i); 130 } 131 } 132 } 133 CheckTagWithMultimediaLinks(IGDMStructWithMultimediaLinks tag)134 private void CheckTagWithMultimediaLinks(IGDMStructWithMultimediaLinks tag) 135 { 136 if (!tag.HasMultimediaLinks) return; 137 138 for (int i = tag.MultimediaLinks.Count - 1; i >= 0; i--) { 139 GDMMultimediaLink mmLink = tag.MultimediaLinks[i]; 140 if (!mmLink.IsPointer) { 141 TransformMultimediaLink(mmLink); 142 } else { 143 var mmRec = fTree.GetPtrValue<GDMMultimediaRecord>(mmLink); 144 if (mmRec == null) tag.MultimediaLinks.DeleteAt(i); 145 } 146 } 147 } 148 CheckPointerWithNotes(GDMPointerWithNotes ptr)149 private void CheckPointerWithNotes(GDMPointerWithNotes ptr) 150 { 151 GDMRecord val = fTree.GetPtrValue<GDMRecord>(ptr); 152 if (!string.IsNullOrEmpty(ptr.XRef) && val == null) { 153 ptr.XRef = string.Empty; 154 } 155 156 CheckTagWithNotes(ptr); 157 } 158 CheckStructWL(IGDMStructWithLists swl)159 private void CheckStructWL(IGDMStructWithLists swl) 160 { 161 CheckTagWithNotes(swl); 162 CheckTagWithMultimediaLinks(swl); 163 CheckTagWithSourceCitations(swl); 164 } 165 CheckEventPlace(GDMPlace place)166 private void CheckEventPlace(GDMPlace place) 167 { 168 GDMPointer placeLocation = place.Location; 169 GDMLocationRecord locRec = fTree.GetPtrValue<GDMLocationRecord>(placeLocation); 170 171 if (placeLocation.XRef != "" && locRec == null) { 172 placeLocation.XRef = ""; 173 } 174 175 if (place.StringValue != "") { 176 if (locRec != null && place.StringValue != locRec.LocationName) { 177 place.StringValue = locRec.LocationName; 178 } 179 } 180 181 CheckTagWithNotes(place); 182 } 183 CheckEvent(GDMCustomEvent evt)184 private void CheckEvent(GDMCustomEvent evt) 185 { 186 CheckStructWL(evt); 187 188 // Fix for Family Tree Maker 2008 which exports occupation as generic EVEN events 189 if (fFormat == GEDCOMFormat.gf_FamilyTreeMaker) { 190 string subtype = evt.Classification.ToLower(); 191 if (evt.Id == (int)GEDCOMTagType.EVEN && subtype == "occupation") { 192 evt.SetName(GEDCOMTagType.OCCU); 193 evt.Classification = string.Empty; 194 } 195 } 196 197 if (evt.HasPlace) { 198 CheckEventPlace(evt.Place); 199 } 200 } 201 CheckUserRef(GDMRecord rec, GDMUserReference userRef)202 private void CheckUserRef(GDMRecord rec, GDMUserReference userRef) 203 { 204 } 205 CheckPersonalName(GDMPersonalName persName)206 private void CheckPersonalName(GDMPersonalName persName) 207 { 208 CheckTagWithNotes(persName); 209 CheckTagWithSourceCitations(persName); 210 211 fBaseContext.CollectNameLangs(persName); 212 } 213 CheckIndividualRecord(GDMIndividualRecord iRec)214 private void CheckIndividualRecord(GDMIndividualRecord iRec) 215 { 216 if (iRec.HasEvents) { 217 for (int i = 0, num = iRec.Events.Count; i < num; i++) { 218 GDMCustomEvent evt = iRec.Events[i]; 219 220 CheckEvent(evt); 221 222 fBaseContext.CollectEventValues(evt); 223 } 224 } 225 226 for (int i = 0, num = iRec.PersonalNames.Count; i < num; i++) { 227 CheckPersonalName(iRec.PersonalNames[i]); 228 } 229 230 for (int i = iRec.ChildToFamilyLinks.Count - 1; i >= 0; i--) { 231 var cfl = iRec.ChildToFamilyLinks[i]; 232 if (fTree.GetPtrValue(cfl) == null) { 233 iRec.ChildToFamilyLinks.DeleteAt(i); 234 } else { 235 CheckPointerWithNotes(cfl); 236 } 237 } 238 239 for (int i = iRec.SpouseToFamilyLinks.Count - 1; i >= 0; i--) { 240 var sfl = iRec.SpouseToFamilyLinks[i]; 241 if (fTree.GetPtrValue(sfl) == null) { 242 iRec.SpouseToFamilyLinks.DeleteAt(i); 243 } else { 244 CheckPointerWithNotes(sfl); 245 } 246 } 247 248 if (iRec.HasAssociations) { 249 for (int i = 0, num = iRec.Associations.Count; i < num; i++) { 250 var asso = iRec.Associations[i]; 251 CheckPointerWithNotes(asso); 252 CheckTagWithSourceCitations(asso); 253 } 254 } 255 256 fBaseContext.ImportNames(iRec); 257 } 258 CheckChildLink(GDMFamilyRecord fam, int index)259 private void CheckChildLink(GDMFamilyRecord fam, int index) 260 { 261 GDMIndividualLink childLink = fam.Children[index]; 262 var childRec = fTree.GetPtrValue<GDMIndividualRecord>(childLink); 263 if (childRec == null) { 264 fam.Children.DeleteAt(index); 265 return; 266 } 267 268 if (fFormat == GEDCOMFormat.gf_AGES) { 269 var frelTag = FindSubTagValue(childLink, "_FREL"); 270 var mrelTag = FindSubTagValue(childLink, "_MREL"); 271 if (frelTag == "ADOPTED" && mrelTag == "ADOPTED") { 272 GDMChildToFamilyLink ctfLink = childRec.FindChildToFamilyLink(fam); 273 if (ctfLink != null) { 274 ctfLink.PedigreeLinkageType = GDMPedigreeLinkageType.plAdopted; 275 276 childLink.DeleteTag("_FREL"); 277 childLink.DeleteTag("_MREL"); 278 } 279 } 280 } 281 } 282 FindSubTagValue(GDMTag tag, string subTagName)283 private static string FindSubTagValue(GDMTag tag, string subTagName) 284 { 285 var subTag = tag.FindTag(subTagName, 0); 286 return (subTag == null) ? string.Empty : subTag.StringValue; 287 } 288 CheckFamilyRecord(GDMFamilyRecord fam)289 private void CheckFamilyRecord(GDMFamilyRecord fam) 290 { 291 if (fam.HasEvents) { 292 for (int i = 0, num = fam.Events.Count; i < num; i++) { 293 GDMCustomEvent evt = fam.Events[i]; 294 CheckEvent(evt); 295 } 296 } 297 298 for (int i = fam.Children.Count - 1; i >= 0; i--) { 299 CheckChildLink(fam, i); 300 } 301 302 GDMRecord val = fTree.GetPtrValue<GDMIndividualRecord>(fam.Husband); 303 if (!string.IsNullOrEmpty(fam.Husband.XRef) && val == null) { 304 fam.Husband.XRef = string.Empty; 305 } 306 307 val = fTree.GetPtrValue<GDMIndividualRecord>(fam.Wife); 308 if (!string.IsNullOrEmpty(fam.Wife.XRef) && val == null) { 309 fam.Wife.XRef = string.Empty; 310 } 311 } 312 CheckGroupRecord(GDMGroupRecord group)313 private void CheckGroupRecord(GDMGroupRecord group) 314 { 315 for (int i = group.Members.Count - 1; i >= 0; i--) { 316 GDMIndividualRecord mbr = fTree.GetPtrValue(group.Members[i]); 317 if (mbr == null) { 318 group.Members.DeleteAt(i); 319 } else { 320 if (mbr.IndexOfGroup(group) < 0) { 321 group.Members.DeleteAt(i); 322 } 323 } 324 } 325 } 326 CheckSourceRecord(GDMSourceRecord src)327 private void CheckSourceRecord(GDMSourceRecord src) 328 { 329 for (int i = src.RepositoryCitations.Count - 1; i >= 0; i--) { 330 GDMRecord val = fTree.GetPtrValue<GDMRecord>(src.RepositoryCitations[i]); 331 if (val == null) { 332 src.RepositoryCitations.DeleteAt(i); 333 } 334 } 335 } 336 CheckMultimediaRecord(GDMMultimediaRecord mmRec, int fileVer)337 private void CheckMultimediaRecord(GDMMultimediaRecord mmRec, int fileVer) 338 { 339 for (int i = 0; i < mmRec.FileReferences.Count; i++) { 340 GDMFileReferenceWithTitle fileRef = mmRec.FileReferences[i]; 341 342 GDMMultimediaFormat mmFormat = fileRef.MultimediaFormat; 343 if (mmFormat == GDMMultimediaFormat.mfUnknown || mmFormat == GDMMultimediaFormat.mfNone) { 344 // tag 'FORM' can be corrupted or GEDCOMCore in past not recognize format attempt recovery 345 fileRef.MultimediaFormat = GDMFileReference.RecognizeFormat(fileRef.StringValue); 346 } 347 348 if (fFormat == GEDCOMFormat.gf_Native && fileVer == 39) { 349 // the transition to normalized names after GKv39 350 // only for not direct references AND not relative references (platform specific paths) 351 352 var mediaStore = GKUtils.GetStoreType(fileRef); 353 if (mediaStore.StoreType != MediaStoreType.mstReference 354 && mediaStore.StoreType != MediaStoreType.mstRelativeReference) { 355 fileRef.StringValue = FileHelper.NormalizeFilename(fileRef.StringValue); 356 } 357 } 358 } 359 } 360 CheckRecord(GDMRecord rec, int fileVer)361 private void CheckRecord(GDMRecord rec, int fileVer) 362 { 363 CheckStructWL(rec); 364 365 if (rec.HasUserReferences) { 366 for (int i = 0, num = rec.UserReferences.Count; i < num; i++) { 367 CheckUserRef(rec, rec.UserReferences[i]); 368 } 369 } 370 371 // TODO 372 // INDI: remove AFN, RFN - discuss??? 373 // INDI,FAM: remove SUBM - discuss??? 374 375 switch (rec.RecordType) { 376 case GDMRecordType.rtIndividual: 377 CheckIndividualRecord(rec as GDMIndividualRecord); 378 break; 379 380 case GDMRecordType.rtFamily: 381 CheckFamilyRecord(rec as GDMFamilyRecord); 382 break; 383 384 case GDMRecordType.rtGroup: 385 CheckGroupRecord(rec as GDMGroupRecord); 386 break; 387 388 case GDMRecordType.rtSource: 389 CheckSourceRecord(rec as GDMSourceRecord); 390 break; 391 392 case GDMRecordType.rtMultimedia: 393 CheckMultimediaRecord(rec as GDMMultimediaRecord, fileVer); 394 break; 395 } 396 } 397 CheckRecordXRef(GDMRecord record)398 private bool CheckRecordXRef(GDMRecord record) 399 { 400 string stdSign = GEDCOMUtils.GetSignByRecord(record); 401 string xrefNum = record.GetXRefNum(); 402 string recXRef = record.XRef; 403 404 return ((recXRef == stdSign + xrefNum) && record.GetId() >= 0); 405 } 406 ConvertIdentifiers()407 private void ConvertIdentifiers() 408 { 409 fProgress.ProgressInit(LangMan.LS(LSID.LSID_IDsCorrect), fTree.RecordsCount * 2); 410 GDMXRefReplacer repMap = new GDMXRefReplacer(); 411 try { 412 int recsCount = fTree.RecordsCount; 413 for (int i = 0; i < recsCount; i++) { 414 GDMRecord rec = fTree[i]; 415 if (!CheckRecordXRef(rec)) { 416 string oldXRef = rec.XRef; 417 string newXRef = fTree.NewXRef(rec, true); 418 repMap.AddXRef(rec, oldXRef, newXRef); 419 } 420 fProgress.ProgressStep(); 421 } 422 423 fTree.Header.ReplaceXRefs(repMap); 424 for (int i = 0; i < recsCount; i++) { 425 GDMRecord rec = fTree[i]; 426 rec.ReplaceXRefs(repMap); 427 fProgress.ProgressStep(); 428 } 429 } finally { 430 repMap.Dispose(); 431 fProgress.ProgressDone(); 432 } 433 } 434 CheckFormat()435 private bool CheckFormat() 436 { 437 bool result = false; 438 439 try { 440 int fileVer; 441 // remove a deprecated features 442 if (fFormat == GEDCOMFormat.gf_Native) { 443 GDMHeader header = fTree.Header; 444 GDMTag tag; 445 446 tag = header.FindTag("_ADVANCED", 0); 447 if (tag != null) header.DeleteTag("_ADVANCED"); 448 449 tag = header.FindTag("_EXT_NAME", 0); 450 if (tag != null) header.DeleteTag("_EXT_NAME"); 451 452 fileVer = ConvertHelper.ParseInt(header.Source.Version, GKData.APP_FORMAT_DEFVER); 453 } else { 454 fileVer = -1; 455 } 456 457 fProgress.ProgressInit(LangMan.LS(LSID.LSID_FormatCheck), 100); 458 try { 459 bool xrefValid = true; 460 bool isExtraneous = (fFormat != GEDCOMFormat.gf_Native); 461 462 int progress = 0; 463 int num = fTree.RecordsCount; 464 for (int i = 0; i < num; i++) { 465 GDMRecord rec = fTree[i]; 466 CheckRecord(rec, fileVer); 467 468 if (isExtraneous && xrefValid && !CheckRecordXRef(rec)) { 469 xrefValid = false; 470 } 471 472 int newProgress = (int)Math.Min(100, ((i + 1) * 100.0f) / num); 473 if (progress != newProgress) { 474 progress = newProgress; 475 fProgress.ProgressStep(progress); 476 } 477 } 478 479 // obsolete: AppHost.StdDialogs.ShowQuestionYN(LangMan.LS(LSID.LSID_IDsCorrectNeed)) 480 if (!xrefValid) { 481 ConvertIdentifiers(); 482 } 483 484 fTree.TrimExcess(); 485 486 result = true; 487 } finally { 488 fProgress.ProgressDone(); 489 } 490 } catch (Exception ex) { 491 Logger.WriteError("GEDCOMChecker.CheckFormat()", ex); 492 AppHost.StdDialogs.ShowError(LangMan.LS(LSID.LSID_CheckGedComFailed)); 493 } 494 495 return result; 496 } 497 CheckGEDCOMFormat(IBaseContext baseContext, IProgressController pc)498 public static bool CheckGEDCOMFormat(IBaseContext baseContext, IProgressController pc) 499 { 500 if (baseContext == null) 501 throw new ArgumentNullException("baseContext"); 502 503 if (pc == null) 504 throw new ArgumentNullException("pc"); 505 506 var instance = new GEDCOMChecker(baseContext, pc); 507 return instance.CheckFormat(); 508 } 509 } 510 } 511