using File_Folder_Helper.Models; using Microsoft.Extensions.Logging; using System.Collections.ObjectModel; using System.Globalization; using System.Text.Json; using System.Text.Json.Serialization; using System.Text.RegularExpressions; namespace File_Folder_Helper.Helpers; internal static partial class HelperGenealogicalDataCommunication { private record Input(string GenealogicalDataCommunicationDirectory, string? GenealogicalDataCommunicationFile, string? SingletonDirectory, string? Destination); private record GenealogicalDataCommunicationCollections(ReadOnlyCollection HeaderLines, ReadOnlyDictionary> IndividualsToLines, ReadOnlyCollection> FamilyGroupLines, ReadOnlyCollection FooterLines); private record GenealogicalDataCommunicationRelation(int FamilyIndex, string Relation, int Id, string? LineTwo); private record PersonHour(char Status, char Sex, char First); private record PersonExport(long Id, ReadOnlyCollection Lines, string PersonKeyFormatted, char[] AgeCollection, DateTime DateTime, long PersonKey); private record Family(string? Title, string? Id, string? Index, string PersonName, Person Person, long PersonKey, string? LineTwo); [JsonSourceGenerationOptions(WriteIndented = true)] [JsonSerializable(typeof(ReadOnlyCollection>))] internal partial class CollectionSourceGenerationContext : JsonSerializerContext { } [JsonSourceGenerationOptions(WriteIndented = true)] [JsonSerializable(typeof(ReadOnlyDictionary>>))] internal partial class DictionarySourceGenerationContext : JsonSerializerContext { } private static string? GetFaceBook(Person person) => person.Birth?.Continue.Where(l => !l.Contains("profile.php?id=") && l.StartsWith("https://www.facebook.com/")).Select(l => l[25..].Split('/')[0]).FirstOrDefault(); private static string? GetFaceBookId(Person person) => person.Birth?.Continue.Where(l => l.StartsWith("https://www.facebook.com/profile.php?id=")).Select(l => l[40..].Split('&')[0]).FirstOrDefault(); private static ReadOnlyDictionary> Convert(Dictionary> keyValuePairs) { Dictionary> results = new(); foreach (KeyValuePair> keyValuePair in keyValuePairs) results.Add(keyValuePair.Key, new(keyValuePair.Value)); return new(results); } private static Dictionary>> Convert(Dictionary>> keyValuePairs) { Dictionary>> results = new(); foreach (KeyValuePair>> keyValuePair in keyValuePairs) results.Add(keyValuePair.Key, new(keyValuePair.Value)); return new(results); } private static ReadOnlyCollection GetObjectCollection(Person? person) { List results; if (person is null) results = new() { "Id", "First-Name", "Last-Name", "Birth-Date", "Sex", "Address", "City", "State", "Zip", "Phone", "E-mail", "Facebook", "Facebook-Id", "Comment", "U-Id" }; else { string? facebook = GetFaceBook(person); string? facebookId = GetFaceBookId(person); results = new() { person.Id.ToString(), string.Concat(person.Name?.Given), string.Concat(person.Name?.Sur), string.Concat(person.Birth?.Date.ToString()), string.Concat(person.Sex), string.Empty, string.Empty, "NM", string.Empty, string.Empty, string.Empty, string.Concat(facebook), string.Concat(facebookId), string.Empty, string.Concat(person.UId) }; } return new(results); } private static string GetKey(Family family) => $"{family.Id}-{family.Index}".Trim('-'); private static ReadOnlyCollection 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 new(results); } private static long? GetId(string line) { long? result; string[] segments = line.Split('@'); result = segments[1].Length < 2 || !long.TryParse(segments[1][1..], out long idValue) ? null : idValue; return result; } private static Dictionary>> Convert(ReadOnlyCollection distinctSortedKKeys) { Dictionary>> results = new(); ReadOnlyCollection collection; List>? objectCollection; foreach (string key in distinctSortedKKeys) { if (results.ContainsKey(key)) continue; if (!results.TryGetValue(key, out objectCollection)) { results.Add(key, new()); if (!results.TryGetValue(key, out objectCollection)) throw new NotSupportedException(); } collection = GetObjectCollection(person: null); objectCollection.Add(collection); } return results; } private static Dictionary> GetTxtFileCollection(Input input) { Dictionary> results = new(); string[] lines; string[] directories; string directoryName; string? sourceDirectory; string? parentDirectory; List? collectionA; List? collectionB; string siblingDirectoryName; string[] files = input.SingletonDirectory is null || !Directory.Exists(input.SingletonDirectory) ? Array.Empty() : Directory.GetFiles(input.SingletonDirectory, "*.txt", SearchOption.AllDirectories); foreach (string file in files) { sourceDirectory = Path.GetDirectoryName(file); if (sourceDirectory is null) continue; parentDirectory = Path.GetDirectoryName(sourceDirectory); if (parentDirectory is null) continue; lines = File.ReadAllLines(file); if (lines.Length != 1 || lines.Length == 2 && string.IsNullOrEmpty(lines[1])) continue; directoryName = Path.GetFileName(sourceDirectory); if (!results.TryGetValue(directoryName, out collectionA)) { results.Add(directoryName, new()); if (!results.TryGetValue(directoryName, out collectionA)) throw new Exception(); } collectionA.Add(lines[0]); directories = Directory.GetDirectories(parentDirectory, "*", SearchOption.TopDirectoryOnly); foreach (string directory in directories) { siblingDirectoryName = Path.GetFileName(directory); collectionA.Add(siblingDirectoryName); } foreach (string directory in directories) { siblingDirectoryName = Path.GetFileName(directory); if (!results.TryGetValue(siblingDirectoryName, out collectionB)) { results.Add(siblingDirectoryName, new()); if (!results.TryGetValue(siblingDirectoryName, out collectionB)) throw new Exception(); } collectionB.AddRange(collectionA); collectionB.Add(lines[0]); } } return results; } private static (int, Name) GetName(ReadOnlyCollection lines, int i) { Name result; string seven; string? surName = null; string? nickName = null; string? givenName = null; string? suffixName = null; string? forwardSlashFullName = lines[i][..7] == "1 NAME " ? lines[i][7..] : null; for (int j = i + 1; j < lines.Count; j++) { if (lines[j][0] == '1') break; i++; if (lines[j].Length < 7) throw new NotImplementedException(); seven = lines[j][..7]; if (seven == "2 GIVN ") givenName = lines[j][7..]; else if (seven == "2 SURN ") surName = lines[j][7..]; else if (seven == "2 NICK ") nickName = lines[j][7..]; else if (seven == "2 NSFX ") suffixName = lines[j][7..]; else throw new NotImplementedException(); } result = new(forwardSlashFullName, givenName, surName, nickName, suffixName); return (i, result); } private static (int, Birth, bool) GetBirth(Dictionary> keyValuePairs, ReadOnlyCollection lines, int i) { Birth result; string seven; string? note = null; bool moreAdded = false; List? collection; DateOnly? dateOnly = null; List distinct = new(); List @continue = new(); for (int j = i + 1; j < lines.Count; j++) { if (lines[j][0] == '1') break; i++; if (lines[j].Length < 7) throw new NotImplementedException(); seven = lines[j][..7]; if (seven == "2 DATE ") dateOnly = !DateOnly.TryParseExact(lines[j][7..], "d MMM yyyy", CultureInfo.InvariantCulture, DateTimeStyles.None, out DateOnly dateOnlyValue) ? null : dateOnlyValue; else if (seven == "2 NOTE ") note = lines[j][7..]; else if (seven == "3 CONT ") { for (int k = j; k < lines.Count; k++) { seven = lines[k][..7]; if (seven != "3 CONT ") break; j++; @continue.Add(lines[k][7..]); } if (note is not null && keyValuePairs.TryGetValue(note, out collection)) { distinct.Add(note); distinct.AddRange(@continue); foreach (string text in collection) { if (distinct.Contains(text)) continue; distinct.Add(text); if (!moreAdded) moreAdded = true; @continue.Add(text); } } } else continue; } result = new(dateOnly, note, @continue); return (i, result, moreAdded); } private static (int, Death) GetDeath(ReadOnlyCollection lines, int i) { Death result; string seven; string? note = null; DateOnly? dateOnly = null; List @continue = new(); bool? isDead = lines[i].Length == 8 && lines[i][..8] == "1 DEAT Y" ? true : lines[i].Length == 8 && lines[i][..8] == "1 DEAT N" ? false : null; for (int j = i + 1; j < lines.Count; j++) { if (lines[j][0] == '1') break; i++; if (lines[j].Length < 7) throw new NotImplementedException(); seven = lines[j][..7]; if (seven == "2 DATE ") dateOnly = !DateOnly.TryParseExact(lines[j][7..], "d MMM yyyy", CultureInfo.InvariantCulture, DateTimeStyles.None, out DateOnly dateOnlyValue) ? null : dateOnlyValue; else if (seven == "2 NOTE ") note = lines[j][7..]; else if (seven == "3 CONT ") { for (int k = j; k < lines.Count; k++) { seven = lines[k][..7]; if (seven != "3 CONT ") break; j++; @continue.Add(lines[k][7..]); } } else throw new NotImplementedException(); } result = new(dateOnly is not null ? true : isDead, dateOnly, note, @continue); return (i, result); } private static (int, Change) GetChange(ReadOnlyCollection lines, int i) { Change result; string seven; string? note = null; DateOnly? dateOnly = null; List @continue = new(); for (int j = i + 1; j < lines.Count; j++) { if (lines[j][0] == '1') break; i++; if (lines[j].Length < 7) throw new NotImplementedException(); seven = lines[j][..7]; if (seven == "2 DATE ") dateOnly = !DateOnly.TryParseExact(lines[j][7..], "d MMM yyyy", CultureInfo.InvariantCulture, DateTimeStyles.None, out DateOnly dateOnlyValue) ? null : dateOnlyValue; else if (seven == "2 NOTE ") note = lines[j][7..]; else if (seven == "3 CONT ") { for (int k = j; k < lines.Count; k++) { seven = lines[k][..7]; if (seven != "3 CONT ") break; j++; @continue.Add(lines[k][7..]); } } else throw new NotImplementedException(); } result = new(dateOnly, note, @continue); return (i, result); } private static string[] GetNewLines(ReadOnlyCollection lines, Birth? birth) { List results = new(); string six; string text; string seven; List @continue = birth is null ? new() : birth.Continue.ToList(); for (int i = 0; i < lines.Count; i++) { if (birth is null) throw new NotSupportedException(); if (lines[i].Length < 6) throw new NotImplementedException(); results.Add(lines[i]); six = lines[i][..6]; if (lines[i][0] == '1') { if (six != "1 BIRT") continue; for (int j = i + 1; j < lines.Count; j++) { if (lines[j].Length < 7) throw new NotImplementedException(); if (lines[j][0] == '1') break; i++; seven = lines[j][..7]; if (seven != "3 CONT ") results.Add(lines[j]); else { text = lines[j][7..]; if (@continue.Contains(text)) { results.Add(lines[j]); _ = @continue.Remove(text); } } } results.AddRange(from l in @continue orderby l select $"3 CONT {l}"); } } return results.ToArray(); } [GeneratedRegex("[\\\\,\\/,\\:,\\*,\\?,\\\",\\<,\\>,\\|]")] private static partial Regex WindowsFileSystem(); private static List GetIdsWhenPersonHasTitle(ReadOnlyDictionary people) { List results = new(); foreach (KeyValuePair keyValuePair in people) { if (keyValuePair.Value.Title is null) continue; results.Add(keyValuePair.Key); } return results; } private static ReadOnlyCollection GetRelations(ReadOnlyCollection> familyGroupLines) { List results = new(); int id; string relation; string[] segments; ReadOnlyCollection familyLines; for (int i = 0; i < familyGroupLines.Count; i++) { familyLines = familyGroupLines[i]; for (int j = 0; j < familyLines.Count; j++) { segments = familyLines[j].Split('@'); if (segments[0].Length < 3 || segments.Length != 3) continue; if (!int.TryParse(segments[1][1..], out id)) continue; relation = segments[0][2..].Trim(); if (j + 1 >= familyLines.Count || familyLines[j + 1].Length < 3 || familyLines[j + 1][..3] != "2 _") results.Add(new(i, relation, id, null)); else results.Add(new(i, relation, id, familyLines[j + 1][2..])); } } return new(results.OrderBy(l => l.FamilyIndex).ToArray()); } private static ReadOnlyCollection GetDistinctSortedKeys(List familyCollection, char personTitleFilter) { string[] results; string key; List<(string? Index, string Key)> collection = new(); foreach (Family family in familyCollection) { if (family.Id is null) continue; if (string.IsNullOrEmpty(family.Title) || family.Title[0] != personTitleFilter) continue; key = GetKey(family); collection.Add((family.Index, key)); } results = (from l in collection orderby l.Key, l.Index?.Length descending select l.Key).Distinct().ToArray(); return new(results); } private static string GetHourGroup(string personDisplayDirectoryName, int hour) => hour == 0 ? "Unknown-Unknown-Unknown" : hour == 1 ? "Unknown-Unknown-Unknown" : hour == 2 ? "Unknown-Unknown-Unknown" : hour == 3 ? "Alive-Unknown-Yes" : hour == 4 ? "Alive-Female-Yes" : hour == 5 ? "Alive-Male-Yes" : hour == 6 ? "Alive-Female-No" : hour == 7 ? "Alive-Male-No" : hour == 13 ? "Dead-Unknown-Yes" : hour == 14 ? "Dead-Female-Yes" : hour == 15 ? "Dead-Male-Yes" : hour == 16 ? "Dead-Female-No" : hour == 17 ? "Dead-Male-No" : throw new NotImplementedException(personDisplayDirectoryName); private static (int, TimeSpan) GetAge(long minuendTicks, long subtrahendTicks) { TimeSpan result; int years = 0; DateTime check = new(subtrahendTicks); for (int i = 0; i < int.MaxValue; i++) { check = check.AddYears(1); if (check.Ticks > minuendTicks) break; years += 1; } result = new(minuendTicks - check.AddYears(-1).Ticks); return (years, result); } private static string? GetYearGroup(string year) => !int.TryParse(year[2..], out int part) ? null : string.Concat(year[..^2], part < 50 ? "--" : "++"); private static Input GetInput(List args) { Input result; string? destination = null; string? singletonDirectory = null; string genealogicalDataCommunicationRootDirectory = Path.GetFullPath(args[0]); string fileName = Path.GetFileName(genealogicalDataCommunicationRootDirectory); string[] files = Directory.GetFiles(genealogicalDataCommunicationRootDirectory, $"{fileName}.ged", SearchOption.TopDirectoryOnly); string genealogicalDataCommunicationDirectory = Path.Combine(genealogicalDataCommunicationRootDirectory, fileName); if (!Directory.Exists(genealogicalDataCommunicationDirectory)) _ = Directory.CreateDirectory(genealogicalDataCommunicationDirectory); for (int i = 1; i < args.Count; i++) { if (args[i].Length == 2 && i + 1 < args.Count) { if (args[i][1] == 's') singletonDirectory = Path.GetFullPath(args[i + 1]); else if (args[i][1] == 'd') destination = Path.GetFullPath(args[i + 1]); i++; } } string? genealogicalDataCommunicationFile = files.Length != 1 ? null : files[0]; if (destination is not null) { string? root = Path.GetPathRoot(destination); if (root is null || !Directory.Exists(root)) throw new NotSupportedException($"This method requires frontMatterYamlLines valid -d path <{root}>!"); if (!Directory.Exists(destination)) _ = Directory.CreateDirectory(destination); } result = new(genealogicalDataCommunicationDirectory, genealogicalDataCommunicationFile, singletonDirectory, destination); return result; } private static GenealogicalDataCommunicationCollections GetGenealogicalDataCommunicationCollections(Input input) { GenealogicalDataCommunicationCollections result; long? id; List lines = new(); const string startsWith = "0 @"; List footerLines = new(); Dictionary> keyValuePairs = new(); List> familyGroupLines = new(); string[] sourceLines = string.IsNullOrEmpty(input.GenealogicalDataCommunicationFile) || !File.Exists(input.GenealogicalDataCommunicationFile) ? Array.Empty() : File.ReadAllLines(input.GenealogicalDataCommunicationFile); ReadOnlyCollection headerLines = GetHeaderLines(startsWith, sourceLines); for (int i = headerLines.Count; i < sourceLines.Length; i++) { if (!sourceLines[i].StartsWith(startsWith)) continue; if (sourceLines[i].EndsWith("@ SOUR") || sourceLines[i].EndsWith("@ SUBM") || sourceLines[i].EndsWith("@ OBJE") || sourceLines[i].EndsWith("@ REPO")) continue; lines.Add(sourceLines[i]); if (sourceLines[i].EndsWith("@ FAM")) { lines.Clear(); for (int j = sourceLines.Length - 1; j >= i; j--) { lines.Add(sourceLines[j]); if (sourceLines[j][0] == '0') { if (!sourceLines[j].EndsWith("@ FAM")) footerLines.AddRange(lines); else { lines.Reverse(); familyGroupLines.Add(new(lines.ToArray())); } lines.Clear(); } } familyGroupLines.Reverse(); footerLines.Reverse(); break; } else if (sourceLines[i].EndsWith("@ INDI")) { id = GetId(sourceLines[i]); for (int j = i + 1; j < sourceLines.Length; j++) { if (sourceLines[j].StartsWith(startsWith)) break; lines.Add(sourceLines[j]); } if (id is null) throw new Exception(string.Join(Environment.NewLine, lines)); keyValuePairs.Add(id.Value, new()); if (lines.Count == 0) continue; keyValuePairs[id.Value].AddRange(lines); lines.Clear(); } else throw new NotSupportedException(); } ReadOnlyDictionary> individualsToLines = Convert(keyValuePairs); result = new(headerLines, individualsToLines, new(familyGroupLines), new(footerLines)); return result; } private static ReadOnlyDictionary GetPeople(Input input, GenealogicalDataCommunicationCollections genealogicalDataCommunicationCollections) { Dictionary results = new(); long? id; char? sex; Name? name; string six; string? uId; Birth? birth; Death? death; Person person; string? title; Change? change; bool? moreAdded; ReadOnlyCollection lines; Dictionary> keyValuePairs = GetTxtFileCollection(input); foreach (KeyValuePair> keyValuePair in genealogicalDataCommunicationCollections.IndividualsToLines) { id = null; sex = null; name = null; uId = null; birth = null; death = null; title = null; change = null; moreAdded = null; lines = keyValuePair.Value; for (int i = 0; i < lines.Count; i++) { if (lines[i].Length < 6) throw new NotImplementedException(); six = lines[i][..6]; if (lines[i][0] == '0') { if (lines[i][^6..] == "@ INDI") id = GetId(lines[i]); else throw new NotImplementedException(); } else if (lines[i][0] == '1') { if (six == "1 NAME") (i, name) = GetName(lines, i); else if (six == "1 SEX ") sex = lines[i].Length != 7 ? null : lines[i][6]; else if (six is "1 UID " or "1 _UID") uId = lines[i].Length == 6 ? null : lines[i][7..]; else if (six == "1 BIRT") (i, birth, moreAdded) = GetBirth(keyValuePairs, lines, i); else if (six == "1 DEAT") (i, death) = GetDeath(lines, i); else if (six == "1 TITL") title = lines[i].Length == 6 ? null : lines[i][7..]; else if (six == "1 CHAN") (i, change) = GetChange(lines, i); else if (six is "1 FAMC" or "1 FAMS") continue; else throw new NotImplementedException(); } } if (id is null) throw new Exception(string.Join(Environment.NewLine, lines)); if (moreAdded is null || !moreAdded.Value) person = new(id.Value, name, sex, uId, birth, title, death, change, lines.ToArray()); else person = new(id.Value, name, sex, uId, birth, title, death, change, GetNewLines(lines, birth)); results.Add(id.Value, person); } return new(results); } private static (ReadOnlyDictionary, ReadOnlyDictionary, ReadOnlyDictionary, ReadOnlyCollection) GetCollections(AppSettings appSettings, ReadOnlyDictionary people) { long personKey; char[] ageCollection; Dictionary idToName = new(); Dictionary idToPersonKey = new(); Dictionary idToGivenName = new(); int length = appSettings.PersonBirthdayFormat.Length; List collection = new(); foreach (KeyValuePair keyValuePair in people) { if (keyValuePair.Value.Birth?.Note is null) continue; if (string.IsNullOrEmpty(keyValuePair.Value.Name?.ForwardSlashFull)) continue; if (keyValuePair.Value.Birth.Note.Length != length) continue; if (!DateTime.TryParseExact(keyValuePair.Value.Birth.Note, appSettings.PersonBirthdayFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTime dateTime)) continue; personKey = dateTime.Ticks; idToPersonKey.Add(keyValuePair.Key, personKey); idToName.Add(keyValuePair.Key, WindowsFileSystem().Replace(keyValuePair.Value.Name.ForwardSlashFull, "_")); ageCollection = keyValuePair.Value.Birth.Continue.Count == 0 ? Array.Empty() : keyValuePair.Value.Birth.Continue[0].ToArray(); idToGivenName.Add(keyValuePair.Key, string.IsNullOrEmpty(keyValuePair.Value.Name.Given) ? WindowsFileSystem().Replace(keyValuePair.Value.Name.ForwardSlashFull, "_") : WindowsFileSystem().Replace(keyValuePair.Value.Name.Given, "_")); collection.Add(new(keyValuePair.Key, new(keyValuePair.Value.Lines), keyValuePair.Value.Birth.Note, ageCollection, dateTime, personKey)); } return (new(idToPersonKey), new(idToName), new(idToGivenName), new(collection)); } private static List GetFamilyCollection(ReadOnlyCollection> familyGroupLines, ReadOnlyDictionary people, ReadOnlyDictionary idToPersonKey, ReadOnlyDictionary idToName, ReadOnlyDictionary idToGivenName) { List results = new(); string? name; long personKey; Person? person; string? givenName; string familyIndex; const string wife = "WIFE"; string? familyTitle = null; const string child = "CHIL"; const string husband = "HUSB"; string wifeName = string.Empty; string? lastFamilyIndex = null; string husbandName = string.Empty; List family = GetIdsWhenPersonHasTitle(people); ReadOnlyCollection genealogicalDataCommunicationRelations = GetRelations(familyGroupLines); foreach (GenealogicalDataCommunicationRelation genealogicalDataCommunicationRelation in genealogicalDataCommunicationRelations) { if (idToName.Count == 0 || idToGivenName.Count == 0) break; if (!idToName.TryGetValue(genealogicalDataCommunicationRelation.Id, out name)) continue; if (!people.TryGetValue(genealogicalDataCommunicationRelation.Id, out person)) continue; if (!idToGivenName.TryGetValue(genealogicalDataCommunicationRelation.Id, out givenName)) continue; familyIndex = genealogicalDataCommunicationRelation.FamilyIndex.ToString("0000"); if (lastFamilyIndex is not null && lastFamilyIndex != familyIndex) { familyTitle = null; wifeName = string.Empty; husbandName = string.Empty; } lastFamilyIndex = familyIndex; if (genealogicalDataCommunicationRelation.Relation == husband) { husbandName = givenName; if (person.Title is not null) familyTitle = person.Title; continue; } if (genealogicalDataCommunicationRelation.Relation == wife) { wifeName = givenName; if (person.Title is not null) familyTitle = person.Title; continue; } if (genealogicalDataCommunicationRelation.Relation != child) continue; if (!idToPersonKey.TryGetValue(genealogicalDataCommunicationRelation.Id, out personKey)) continue; if (person.Title is not null) familyTitle = person.Title; _ = family.Remove(genealogicalDataCommunicationRelation.Id); results.Add(new(familyTitle, $"{husbandName}-{wifeName}".Trim('-'), familyIndex, name, person, personKey, genealogicalDataCommunicationRelation.LineTwo)); } foreach (KeyValuePair keyValuePair in people) { if (!family.Contains(keyValuePair.Key)) continue; if (!idToName.TryGetValue(keyValuePair.Key, out name)) continue; if (!idToPersonKey.TryGetValue(keyValuePair.Key, out personKey)) continue; if (!idToGivenName.TryGetValue(keyValuePair.Key, out givenName)) continue; if (!family.Remove(keyValuePair.Key)) continue; results.Add(new(keyValuePair.Value.Title, givenName, null, name, keyValuePair.Value, personKey, null)); } if (family.Count > 0) throw new NotSupportedException(); foreach (GenealogicalDataCommunicationRelation genealogicalDataCommunicationRelation in genealogicalDataCommunicationRelations) { if (idToName.Count == 0 || idToGivenName.Count == 0) break; if (!idToName.TryGetValue(genealogicalDataCommunicationRelation.Id, out name)) continue; if (!people.TryGetValue(genealogicalDataCommunicationRelation.Id, out person)) continue; if (!idToPersonKey.TryGetValue(genealogicalDataCommunicationRelation.Id, out personKey)) continue; results.Add(new(null, null, null, name, person, personKey, genealogicalDataCommunicationRelation.LineTwo)); } return results; } private static ReadOnlyDictionary>> GetKeyValuePairs(List familyCollection, char personTitleFilter) { Dictionary>> results; Dictionary>> keyValuePairs; string id; string key; ReadOnlyCollection collection; List>? objectCollection; ReadOnlyCollection distinctSortedKeys = GetDistinctSortedKeys(familyCollection, personTitleFilter); keyValuePairs = Convert(distinctSortedKeys); foreach (Family family in familyCollection) { if (family.Id is null) continue; if (string.IsNullOrEmpty(family.Title) || family.Title[0] != personTitleFilter) continue; id = family.Person.Id.ToString(); key = GetKey(family); if (!keyValuePairs.TryGetValue(key, out objectCollection)) throw new NotSupportedException(); collection = GetObjectCollection(family.Person); objectCollection.Add(collection); } results = Convert(keyValuePairs); return new(results); } private static void WriteJsonFiles(AppSettings appSettings, Input input, GenealogicalDataCommunicationCollections genealogicalDataCommunicationCollections, ReadOnlyDictionary people, List familyCollection) { string json; if (people.Count != genealogicalDataCommunicationCollections.IndividualsToLines.Count) throw new NotSupportedException(); ReadOnlyDictionary>> keyValuePairs; json = JsonSerializer.Serialize(new(people), PeopleSourceGenerationContext.Default.DictionaryInt64Person); File.WriteAllText(Path.Combine(input.GenealogicalDataCommunicationDirectory, "people.json"), json); Dictionary? result = JsonSerializer.Deserialize(json, PeopleSourceGenerationContext.Default.DictionaryInt64Person); if (result is null) throw new NullReferenceException(nameof(result)); json = JsonSerializer.Serialize(genealogicalDataCommunicationCollections.FamilyGroupLines, CollectionSourceGenerationContext.Default.ReadOnlyCollectionReadOnlyCollectionString); File.WriteAllText(Path.Combine(input.GenealogicalDataCommunicationDirectory, "family.json"), json); foreach (char personTitleFilter in appSettings.PersonTitleFilters) { keyValuePairs = GetKeyValuePairs(familyCollection, personTitleFilter); json = JsonSerializer.Serialize(keyValuePairs, DictionarySourceGenerationContext.Default.ReadOnlyDictionaryStringReadOnlyCollectionReadOnlyCollectionString); File.WriteAllText(Path.Combine(input.GenealogicalDataCommunicationDirectory, $"{personTitleFilter}.json"), json); } } private static void WriteGenealogicalDataCommunicationCollections(ILogger logger, Input input, GenealogicalDataCommunicationCollections genealogicalDataCommunicationCollections, ReadOnlyDictionary people) { List lines = new(); List allLines = new(); if (genealogicalDataCommunicationCollections.HeaderLines.Count > 0) { allLines.AddRange(genealogicalDataCommunicationCollections.HeaderLines); File.WriteAllLines(Path.Combine(input.GenealogicalDataCommunicationDirectory, "a.pged"), genealogicalDataCommunicationCollections.HeaderLines); } if (people.Count > 0) { lines.Clear(); ReadOnlyCollection? collection; foreach (KeyValuePair keyValuePair in people) { if (!genealogicalDataCommunicationCollections.IndividualsToLines.TryGetValue(keyValuePair.Key, out collection)) throw new NotSupportedException(); if (keyValuePair.Value.Lines.Length != collection.Count) logger.LogInformation("{name} has been changed", keyValuePair.Value.Name?.ForwardSlashFull); lines.AddRange(keyValuePair.Value.Lines); } allLines.AddRange(lines); File.WriteAllLines(Path.Combine(input.GenealogicalDataCommunicationDirectory, "b.pged"), lines); } if (genealogicalDataCommunicationCollections.FamilyGroupLines.Count > 0) { lines.Clear(); foreach (ReadOnlyCollection keyValuePair in genealogicalDataCommunicationCollections.FamilyGroupLines) lines.AddRange(keyValuePair); allLines.AddRange(lines); File.WriteAllLines(Path.Combine(input.GenealogicalDataCommunicationDirectory, "c.pged"), lines); } if (genealogicalDataCommunicationCollections.FooterLines.Count > 0) { allLines.AddRange(genealogicalDataCommunicationCollections.FooterLines); File.WriteAllLines(Path.Combine(input.GenealogicalDataCommunicationDirectory, "d.pged"), genealogicalDataCommunicationCollections.FooterLines); } File.WriteAllLines(Path.Combine(input.GenealogicalDataCommunicationDirectory, "e.ged"), allLines); } private static void ExportFamilies(AppSettings appSettings, Input input, List familyCollection) { string directory; DateTime dateTime; string? destinationRoot; string destinationDirectory; foreach (Family family in familyCollection) { destinationRoot = Path.GetDirectoryName(input.GenealogicalDataCommunicationFile); if (string.IsNullOrEmpty(destinationRoot)) continue; dateTime = new(family.PersonKey); directory = family.Id is null ? "A-A-0000" : $"{family.Title ?? "O"}-{family.Id}-{family.Index}".Trim('-'); destinationDirectory = Path.Combine(destinationRoot, directory, family.PersonName, dateTime.ToString(appSettings.PersonBirthdayFormat)); if (!Directory.Exists(destinationDirectory)) _ = Directory.CreateDirectory(destinationDirectory); File.WriteAllText(Path.Combine(destinationDirectory, $"{family.PersonName}.txt"), family.LineTwo); } } private static void Export(Input input, long ticks, ReadOnlyDictionary idToName, ReadOnlyCollection personExportCollection) { int age; string text; string? name; string directory; string hourGroup; string? yearGroup; long count = ticks; string rootDirectory; string approximateYears; List distinct = new(); List duplicates = new(); string personDisplayDirectoryName; foreach (PersonExport personExport in personExportCollection) { if (input.Destination is null) break; if (!idToName.TryGetValue(personExport.Id, out name)) continue; hourGroup = GetHourGroup(name, personExport.DateTime.Hour); (age, _) = GetAge(DateTime.Now.Ticks, personExport.PersonKey); for (int i = 1; i < 3; i++) { if (i == 2) { yearGroup = GetYearGroup(personExport.DateTime.Year.ToString()); personDisplayDirectoryName = name; if (string.IsNullOrEmpty(yearGroup)) continue; } else if (i == 1) { yearGroup = personExport.AgeCollection[0].ToString(); approximateYears = yearGroup[0] == '^' ? $"^{age}" : new string(personExport.AgeCollection); personDisplayDirectoryName = $"{name}{approximateYears}"; if (distinct.Contains(personDisplayDirectoryName)) { duplicates.Add(personDisplayDirectoryName); continue; } distinct.Add(personDisplayDirectoryName); } else throw new NotSupportedException(); rootDirectory = i == 1 ? input.Destination : i == 2 ? input.GenealogicalDataCommunicationDirectory : throw new NotSupportedException(); directory = Path.Combine(rootDirectory, yearGroup, hourGroup, personDisplayDirectoryName, personExport.PersonKeyFormatted); if (!Directory.Exists(directory)) _ = Directory.CreateDirectory(directory); if (i == 2) { text = string.Join(Environment.NewLine, personExport.Lines); count += 1; File.WriteAllText(Path.Combine(directory, $"{count}-A.pged"), text); } text = string.Join(Environment.NewLine, personExport.Lines); if (!string.IsNullOrEmpty(text)) { count += 1; if (i == 2) File.WriteAllText(Path.Combine(directory, $"{count}-B.pged"), text); else File.WriteAllText(Path.Combine(directory, $"{personExport.PersonKeyFormatted}.pged"), text); } } } if (duplicates.Count > 0) throw new NotSupportedException(); } [Obsolete] private static PersonHour GetPersonHour(string personDisplayDirectoryName, int hour) => hour == 0 ? new('U', 'U', 'U') : hour == 1 ? new('U', 'U', 'U') : hour == 2 ? new('U', 'U', 'U') : hour == 3 ? new('A', 'U', 'Y') : hour == 4 ? new('A', 'F', 'Y') : hour == 5 ? new('A', 'M', 'Y') : hour == 6 ? new('A', 'F', 'N') : hour == 7 ? new('A', 'M', 'N') : hour == 13 ? new('D', 'U', 'Y') : hour == 14 ? new('D', 'F', 'Y') : hour == 15 ? new('D', 'M', 'Y') : hour == 16 ? new('D', 'F', 'N') : hour == 17 ? new('D', 'M', 'N') : throw new NotImplementedException(personDisplayDirectoryName); [Obsolete] private static string[] GetNewLines(ReadOnlyCollection lines, Name? name, string[] kFiles, string[] mFiles, string[] k2Files, string[] m2Files) { List results = new(); string six; int? birthLastLine = null; string slugName = WindowsFileSystem().Replace(string.Concat(name?.ForwardSlashFull), "_"); string? title = name?.ForwardSlashFull is null ? null : kFiles.Contains(slugName) ? "1 TITL K" : k2Files.Contains(slugName) ? "1 TITL K2" : mFiles.Contains(slugName) ? "1 TITL M" : m2Files.Contains(slugName) ? "1 TITL M2" : null; for (int i = 0; i < lines.Count; i++) { if (lines[i].Length < 6) throw new NotImplementedException(); results.Add(lines[i]); six = lines[i][..6]; if (lines[i][0] == '1') { if (six != "1 BIRT") continue; for (int j = i + 1; j < lines.Count; j++) { results.Add(lines[j]); if (lines[j].Length < 7) throw new NotImplementedException(); if (lines[j][0] == '1') { birthLastLine = j; if (title is not null) results.Insert(j, title); break; } i++; } } } if (title is not null && birthLastLine is null) results.Add(title); return results.ToArray(); } internal static void FileSystemToGenealogicalDataCommunication(AppSettings appSettings, ILogger logger, List args) { Input input = GetInput(args); long ticks = DateTime.Now.Ticks; logger.LogInformation("{ticks}", ticks); logger.LogInformation("{old} {days} day(s) => {new}", 638258293638438812, 4, new DateTime(638258293638438812).AddDays(4.0001).Ticks); GenealogicalDataCommunicationCollections genealogicalDataCommunicationCollections = GetGenealogicalDataCommunicationCollections(input); ReadOnlyDictionary people = GetPeople(input, genealogicalDataCommunicationCollections); (ReadOnlyDictionary idToPersonKey, ReadOnlyDictionary idToName, ReadOnlyDictionary idToGivenName, ReadOnlyCollection personExportCollection) = GetCollections(appSettings, people); if (idToPersonKey.Count != people.Count || idToPersonKey.Count != idToName.Count || idToPersonKey.Count != idToGivenName.Count) throw new NotSupportedException(); List familyCollection = GetFamilyCollection(genealogicalDataCommunicationCollections.FamilyGroupLines, people, idToPersonKey, idToName, idToGivenName); WriteJsonFiles(appSettings, input, genealogicalDataCommunicationCollections, people, familyCollection); WriteGenealogicalDataCommunicationCollections(logger, input, genealogicalDataCommunicationCollections, people); if (input.Destination is not null) ExportFamilies(appSettings, input, familyCollection); if (input.Destination is not null) Export(input, ticks, idToName, personExportCollection); if (string.IsNullOrEmpty(input.GenealogicalDataCommunicationFile)) logger.LogInformation("{file} is null?", input.GenealogicalDataCommunicationDirectory); } }