namespace View_by_Distance.Shared.Models.Stateless.Methods; internal abstract class GenealogicalDataCommunication { // ... private static string[] GetHeaderLines(string startsWith, string[] sourceLines) { List results = new(); for (int i = 0; i < sourceLines.Length; i++) { if (sourceLines[i].StartsWith(startsWith)) break; results.Add(sourceLines[i]); } return results.ToArray(); } internal static (string[] headerLines, Dictionary> individuals, string[] footerLines) GetIndividuals(string genealogicalDataCommunicationFile, bool requireNickName) { Dictionary> results = new(); string? nick; List lines = new(); const string startsWith = "0 @"; List footerLines = new(); string[] sourceLines = string.IsNullOrEmpty(genealogicalDataCommunicationFile) || !File.Exists(genealogicalDataCommunicationFile) ? Array.Empty() : File.ReadAllLines(genealogicalDataCommunicationFile); string[] headerLines = GetHeaderLines(startsWith, sourceLines); for (int i = headerLines.Length; i < sourceLines.Length; i++) { if (!sourceLines[i].StartsWith(startsWith)) continue; nick = null; lines.Add(sourceLines[i]); if (sourceLines[i].EndsWith("@ SOUR") || sourceLines[i].EndsWith("@ SUBM") || sourceLines[i].EndsWith("@ OBJE") || sourceLines[i].EndsWith("@ REPO")) continue; else if (sourceLines[i].EndsWith("@ FAM")) { for (int j = i + 1; j < sourceLines.Length; j++) lines.Add(sourceLines[j]); footerLines.AddRange(lines); break; } else if (sourceLines[i].EndsWith("@ INDI")) { for (int j = i + 1; j < sourceLines.Length; j++) { if (sourceLines[j].StartsWith(startsWith)) break; lines.Add(sourceLines[j]); if (!sourceLines[j].StartsWith("2 NICK ")) continue; nick = sourceLines[j][7..]; } if (requireNickName && string.IsNullOrEmpty(nick)) throw new Exception(string.Join(Environment.NewLine, lines)); nick ??= Guid.NewGuid().ToString(); results.Add(nick, new()); if (!lines.Any()) continue; results[nick].AddRange(lines); lines.Clear(); } else throw new NotSupportedException(); } return (headerLines, results, footerLines.ToArray()); } private static string[] GetFilteredOutMapped(List individualsLines) { List results = new(); for (int i = 0; i < individualsLines.Count; i++) { if (individualsLines[i].StartsWith("0 @I")) continue; else if (individualsLines[i].StartsWith("1 _UID")) continue; else if (individualsLines[i].StartsWith("1 NAME")) continue; else if (individualsLines[i].StartsWith("2 GIVN")) continue; else if (individualsLines[i].StartsWith("2 SURN")) continue; else if (individualsLines[i].StartsWith("2 NSFX")) continue; else if (individualsLines[i].StartsWith("2 NICK")) continue; else if (individualsLines[i].StartsWith("1 SEX")) continue; else if (individualsLines[i] == "1 BIRT") { if (individualsLines.Count > i + 1 && individualsLines[i + 1].StartsWith("2 DATE")) i += 1; continue; } else if (individualsLines[i].StartsWith("1 DEAT")) { if (individualsLines.Count > i + 1 && individualsLines[i + 1].StartsWith("2 DATE")) i += 1; continue; } else if (individualsLines[i] == "1 CHAN") { if (individualsLines.Count > i + 1 && individualsLines[i + 1].StartsWith("2 DATE")) i += 1; continue; } results.Add(individualsLines[i]); } return results.ToArray(); } internal static List GetMappedLines(string genealogicalDataCommunicationFile, bool requireNickName) { List results = new(); GenealogicalDataCommunicationLines genealogicalDataCommunicationLines; (string[] headerLines, Dictionary> individuals, string[] footerLines) = GetIndividuals(genealogicalDataCommunicationFile, requireNickName); results.AddRange(headerLines); foreach (KeyValuePair> keyValuePair in individuals) { genealogicalDataCommunicationLines = GetGenealogicalDataCommunicationLines(keyValuePair.Value); if (!string.IsNullOrEmpty(genealogicalDataCommunicationLines.Id)) results.Add(genealogicalDataCommunicationLines.Id); if (!string.IsNullOrEmpty(genealogicalDataCommunicationLines.UId)) results.Add(genealogicalDataCommunicationLines.UId); if (!string.IsNullOrEmpty(genealogicalDataCommunicationLines.Name)) results.Add(genealogicalDataCommunicationLines.Name); if (!string.IsNullOrEmpty(genealogicalDataCommunicationLines.GivenName)) results.Add(genealogicalDataCommunicationLines.GivenName); if (!string.IsNullOrEmpty(genealogicalDataCommunicationLines.SurName)) results.Add(genealogicalDataCommunicationLines.SurName); if (!string.IsNullOrEmpty(genealogicalDataCommunicationLines.NickName)) results.Add(genealogicalDataCommunicationLines.NickName); if (!string.IsNullOrEmpty(genealogicalDataCommunicationLines.Sex)) results.Add(genealogicalDataCommunicationLines.Sex); results.AddRange(genealogicalDataCommunicationLines.Birth); results.AddRange(genealogicalDataCommunicationLines.Death); results.AddRange(genealogicalDataCommunicationLines.Changed); } results.AddRange(footerLines); return results; } internal static GenealogicalDataCommunicationLines GetGenealogicalDataCommunicationLines(List individualsLines) { GenealogicalDataCommunicationLines result; string? idLine = null; string? sexLine = null; string? uIdLine = null; string? nameLine = null; string? suffixLine = null; string? surNameLine = null; string? nickNameLine = null; string? givenNameLine = null; List birthLines = new(); List deathLines = new(); List changedLines = new(); for (int i = 0; i < individualsLines.Count; i++) { if (idLine is null && individualsLines[i].EndsWith("@ INDI")) idLine = individualsLines[i]; else if (uIdLine is null && individualsLines[i].StartsWith("1 _UID")) uIdLine = individualsLines[i]; else if (nameLine is null && individualsLines[i].StartsWith("1 NAME")) nameLine = individualsLines[i]; else if (givenNameLine is null && individualsLines[i].StartsWith("2 GIVN")) givenNameLine = individualsLines[i]; else if (surNameLine is null && individualsLines[i].StartsWith("2 SURN")) surNameLine = individualsLines[i]; else if (suffixLine is null && individualsLines[i].StartsWith("2 NSFX")) suffixLine = individualsLines[i]; else if (nickNameLine is null && individualsLines[i].StartsWith("2 NICK")) nickNameLine = individualsLines[i]; else if (sexLine is null && individualsLines[i].StartsWith("1 SEX")) sexLine = individualsLines[i]; else if (!birthLines.Any() && individualsLines[i] == "1 BIRT") { birthLines.Add(individualsLines[i]); if (individualsLines.Count > i + 1 && individualsLines[i + 1].StartsWith("2 DATE")) { i += 1; birthLines.Add(individualsLines[i]); } } else if (!deathLines.Any() && individualsLines[i].StartsWith("1 DEAT")) { deathLines.Add(individualsLines[i]); if (individualsLines.Count > i + 1 && individualsLines[i + 1].StartsWith("2 DATE")) { i += 1; deathLines.Add(individualsLines[i]); } } else if (!changedLines.Any() && individualsLines[i] == "1 CHAN") { changedLines.Add(individualsLines[i]); if (individualsLines.Count > i + 1 && individualsLines[i + 1].StartsWith("2 DATE")) { i += 1; changedLines.Add(individualsLines[i]); } } } result = new(idLine, uIdLine, nameLine, givenNameLine, surNameLine, suffixLine, nickNameLine, sexLine, birthLines, deathLines, changedLines); return result; } internal static Models.GenealogicalDataCommunication GetGenealogicalDataCommunication(Models.PersonBirthday personBirthday, GenealogicalDataCommunicationLines genealogicalDataCommunicationLines) { Models.GenealogicalDataCommunication result; DateTime? birth; DateTime? death; DateTime? changed; char sex = string.IsNullOrEmpty(genealogicalDataCommunicationLines.Sex) || !genealogicalDataCommunicationLines.Sex.Contains("1 SEX ") ? 'U' : genealogicalDataCommunicationLines.Sex.Split("1 SEX ")[1][0]; string? uId = string.IsNullOrEmpty(genealogicalDataCommunicationLines.UId) || !genealogicalDataCommunicationLines.UId.Contains("1 _UID ") ? null : genealogicalDataCommunicationLines.UId.Split("1 _UID ")[1]; string? name = string.IsNullOrEmpty(genealogicalDataCommunicationLines.Name) || !genealogicalDataCommunicationLines.Name.Contains("1 NAME ") ? null : genealogicalDataCommunicationLines.Name.Split("1 NAME ")[1]; string? suffix = string.IsNullOrEmpty(genealogicalDataCommunicationLines.Suffix) || !genealogicalDataCommunicationLines.Suffix.Contains("2 NSFX ") ? null : genealogicalDataCommunicationLines.Suffix.Split("2 NSFX ")[1]; string? surName = string.IsNullOrEmpty(genealogicalDataCommunicationLines.SurName) || !genealogicalDataCommunicationLines.SurName.Contains("2 SURN ") ? null : genealogicalDataCommunicationLines.SurName.Split("2 SURN ")[1]; string? nickName = string.IsNullOrEmpty(genealogicalDataCommunicationLines.NickName) || !genealogicalDataCommunicationLines.NickName.Contains("2 NICK ") ? null : genealogicalDataCommunicationLines.NickName.Split("2 NICK ")[1]; string? givenName = string.IsNullOrEmpty(genealogicalDataCommunicationLines.GivenName) || !genealogicalDataCommunicationLines.GivenName.Contains("2 GIVN ") ? null : genealogicalDataCommunicationLines.GivenName.Split("2 GIVN ")[1]; string? id = string.IsNullOrEmpty(genealogicalDataCommunicationLines.Id) || !genealogicalDataCommunicationLines.Id.Contains("0 @I") || !genealogicalDataCommunicationLines.Id.Contains("@ INDI") ? null : genealogicalDataCommunicationLines.Id.Split('@')[1][1..]; string[] birthLines = (from l in genealogicalDataCommunicationLines.Birth where l.Contains("2 DATE ") select l.Split("2 DATE ")[1]).ToArray(); string[] deathLines = (from l in genealogicalDataCommunicationLines.Death where l.Contains("2 DATE ") select l.Split("2 DATE ")[1]).ToArray(); string[] changedLines = (from l in genealogicalDataCommunicationLines.Changed where l.Contains("2 DATE ") select l.Split("2 DATE ")[1]).ToArray(); if (!birthLines.Any() || !DateTime.TryParse(birthLines[0], out DateTime parseBirth)) birth = null; else birth = parseBirth; if (!deathLines.Any() || !DateTime.TryParse(deathLines[0], out DateTime parseDeath)) death = null; else death = parseDeath; if (!changedLines.Any() || !DateTime.TryParse(changedLines[0], out DateTime parseChanged)) changed = null; else changed = parseChanged; if (birth is not null) { bool alive = death is null && !genealogicalDataCommunicationLines.Death.Any(l => l == "1 DEAT Y"); (int age, _) = IAge.GetAge(DateTime.Now.Ticks, birth.Value.Ticks); int hours = IPersonBirthday.GetHour(alive, sex); if (death == birth) death = death.Value.AddHours(hours); birth = birth.Value.AddHours(hours); if (age < 1) birth = null; if (death is null && (!alive || age > 110)) death = birth; } death ??= !genealogicalDataCommunicationLines.Death.Any(l => l == "1 DEAT Y") ? null : birth is not null ? birth : personBirthday.Value; result = new(id, uId, name, givenName, surName, suffix, nickName, sex, birth, death, changed); return result; } internal static void WriteFile(string personKeyFormatted, Models.PersonBirthday personBirthday, Models.PersonName personName, List? individualsLines, bool isDefaultName, string directory, Models.GenealogicalDataCommunication? genealogicalDataCommunication, bool verify) { char sex; bool alive; bool first; if (personKeyFormatted[^2..] is "15") (sex, alive, first) = ('M', false, true); else if (personKeyFormatted[^2..] is "14") (sex, alive, first) = ('F', false, true); else if (personKeyFormatted[^2..] is "05") (sex, alive, first) = ('M', true, true); else if (personKeyFormatted[^2..] is "04") (sex, alive, first) = ('F', true, true); else if (personKeyFormatted[^2..] is "23" or "21" or "19" or "17") (sex, alive, first) = ('M', false, false); else if (personKeyFormatted[^2..] is "22" or "20" or "18" or "16") (sex, alive, first) = ('F', false, false); else if (personKeyFormatted[^2..] is "13" or "11" or "09" or "07") (sex, alive, first) = ('M', true, false); else if (personKeyFormatted[^2..] is "12" or "10" or "08" or "06") (sex, alive, first) = ('F', true, false); else (sex, alive, first) = ('U', true, false); if (verify && genealogicalDataCommunication is not null) { if (genealogicalDataCommunication.SurName != personName.Last.Value) throw new Exception($"{genealogicalDataCommunication.Name} - {personKeyFormatted}"); if (genealogicalDataCommunication.Sex != sex) throw new Exception($"{genealogicalDataCommunication.Name} - {personKeyFormatted}"); if (genealogicalDataCommunication.Death is not null && alive || genealogicalDataCommunication.Death is null && !alive) throw new Exception($"{genealogicalDataCommunication.Name} - {personKeyFormatted}"); if (genealogicalDataCommunication.Birth is not null && genealogicalDataCommunication.Birth.Value.ToString("yyyy-MM-dd") != personBirthday.Value.ToString("yyyy-MM-dd")) throw new Exception($"{genealogicalDataCommunication.Name} - {personKeyFormatted}"); } string jrOrSr; if (string.IsNullOrEmpty(personName.Alias.Value)) jrOrSr = string.Empty; else { if (personName.Alias.Value.Contains(" Jr")) jrOrSr = " Jr"; else if (personName.Alias.Value.Contains(" Sr")) jrOrSr = " Sr"; else jrOrSr = string.Empty; } string code = IPersonBirthday.GetHour(alive, sex).ToString("00"); if (directory.EndsWith("00")) directory = string.Concat(directory[..^2], code); else if (directory.EndsWith("01")) directory = string.Concat(directory[..^2], code); if (!Directory.Exists(directory)) _ = Directory.CreateDirectory(directory); if (first && !personKeyFormatted.EndsWith(code)) personKeyFormatted = $"{personKeyFormatted[..^2]}{code}"; List pGedLines = new(); if (individualsLines is null || !individualsLines.Any()) pGedLines.Add($"0 @I{personKeyFormatted}@ INDI"); else { if (!individualsLines[0].StartsWith("0 @I")) throw new NotSupportedException(); pGedLines.Add(individualsLines[0]); } pGedLines.Add($"1 NAME {personName.First.Value} /{personName.Last.Value}/{jrOrSr}"); if (!string.IsNullOrEmpty(personName.First.Value)) pGedLines.Add($"2 GIVN {personName.First.Value}"); if (!string.IsNullOrEmpty(personName.Last.Value)) pGedLines.Add($"2 SURN {personName.Last.Value}"); if (!string.IsNullOrEmpty(jrOrSr)) pGedLines.Add($"2 NSFX {jrOrSr.Trim()}"); pGedLines.Add($"2 NICK {personKeyFormatted}"); pGedLines.Add($"1 SEX {sex}"); if (!IPersonBirthday.IsCounterPersonBirthday(personBirthday)) { pGedLines.Add("1 BIRT"); pGedLines.Add($"2 DATE {personBirthday.Value:dd MMM yyyy}"); } if (!alive) { if (genealogicalDataCommunication?.Death is null || genealogicalDataCommunication.Death == genealogicalDataCommunication.Birth || IPersonBirthday.IsCounterPersonBirthday(new(genealogicalDataCommunication.Death.Value))) pGedLines.Add("1 DEAT Y"); else { pGedLines.Add("1 DEAT"); pGedLines.Add($"2 DATE {genealogicalDataCommunication.Death.Value:dd MMM yyyy}"); } } if (isDefaultName) pGedLines.Add("9 NOTE"); if (individualsLines is not null) pGedLines.AddRange(GetFilteredOutMapped(individualsLines)); string text = string.Join(Environment.NewLine, pGedLines); _ = IPath.WriteAllText(Path.Combine(directory, $"{personKeyFormatted}.pged"), text, updateDateWhenMatches: false, compareBeforeWrite: true); } internal static void CreateTree(string mappingDefaultName, string personBirthdayFormat, string resultAllInOne, Models.PersonContainer[] personContainers, string[] genealogicalDataCommunicationHeaderLines, string[] genealogicalDataCommunicationFooterLines, long ticks, string a2PeopleContentDirectory, Dictionary> personKeyToIds) { string by; string[] matches; const int zero = 0; string[] pGedFiles; string[] pGedLines; string personKeyFormatted; List lines = new(); List distinct = new(); lines.AddRange(genealogicalDataCommunicationHeaderLines); Models.PersonBirthday personBirthday; string rootDirectory = Path.Combine(a2PeopleContentDirectory, $"{ticks}-Tree"); foreach (Models.PersonContainer personContainer in personContainers) { if (personContainer.Key is null || personContainer.Birthdays is null || personContainer.Person is null || !personContainer.Birthdays.Any()) continue; if (IPerson.IsDefaultName(mappingDefaultName, personContainer.DisplayDirectoryName) || IPerson.IsDefaultName(mappingDefaultName, personContainer.Person)) continue; if (distinct.Contains(personContainer.Key.Value)) continue; distinct.Add(personContainer.Key.Value); personBirthday = personContainer.Birthdays[zero]; personKeyFormatted = IPersonBirthday.GetFormatted(personBirthdayFormat, personBirthday); by = IPersonBirthday.IsCounterPersonBirthday(personBirthday) ? resultAllInOne : "People"; matches = (from l in personContainer.DisplayDirectoryAllFiles where !string.IsNullOrEmpty(personKeyFormatted) && l.Contains(personKeyFormatted) select l).ToArray(); if (!matches.Any()) continue; pGedFiles = (from l in matches where l.EndsWith(".pged") select l).ToArray(); if (!pGedFiles.Any()) continue; pGedLines = File.ReadAllLines(pGedFiles[0]); lines.AddRange(pGedLines); if (!personKeyToIds.ContainsKey(personContainer.Key.Value)) lines.Add("1 NOTE"); // segments = personContainer.DisplayDirectoryName.Split(_Configuration.PersonCharacters.ToArray()); // if (segments.Length < 2) // directory = Path.Combine(rootDirectory, $"000 {personKeyFormatted} {personContainer.DisplayDirectoryName}"); // else // directory = Path.Combine(rootDirectory, $"{segments[1].PadLeft(3, '0')} {personKeyFormatted} {personContainer.DisplayDirectoryName}"); // if (Directory.Exists(directory)) // continue; // _ = Directory.CreateDirectory(directory); } lines.AddRange(genealogicalDataCommunicationFooterLines); File.WriteAllLines(Path.Combine(a2PeopleContentDirectory, $"{ticks}.ged"), lines); } }