1015 lines
45 KiB
C#
1015 lines
45 KiB
C#
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<string> HeaderLines,
|
|
ReadOnlyDictionary<long, ReadOnlyCollection<string>> IndividualsToLines,
|
|
ReadOnlyCollection<ReadOnlyCollection<string>> FamilyGroupLines,
|
|
ReadOnlyCollection<string> FooterLines);
|
|
|
|
private record GenealogicalDataCommunicationRelation(int FamilyIndex,
|
|
string Relation,
|
|
int Id,
|
|
string? LineTwo);
|
|
|
|
private record PersonHour(char Status,
|
|
char Sex,
|
|
char First);
|
|
|
|
private record Collections(ReadOnlyDictionary<long, long> IdToPersonKey,
|
|
ReadOnlyDictionary<long, string> IdToName,
|
|
ReadOnlyDictionary<long, string> IdToGivenName,
|
|
ReadOnlyCollection<PersonExport> PersonExportCollection);
|
|
|
|
private record PersonExport(long Id,
|
|
ReadOnlyCollection<string> 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<ReadOnlyCollection<string>>))]
|
|
internal partial class CollectionSourceGenerationContext : JsonSerializerContext
|
|
{
|
|
}
|
|
|
|
[JsonSourceGenerationOptions(WriteIndented = true)]
|
|
[JsonSerializable(typeof(ReadOnlyDictionary<string, ReadOnlyCollection<ReadOnlyCollection<string>>>))]
|
|
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 string GetKey(Family family) =>
|
|
$"{family.Id}-{family.Index}".Trim('-');
|
|
|
|
private static ReadOnlyDictionary<long, ReadOnlyCollection<string>> Convert(Dictionary<long, List<string>> keyValuePairs)
|
|
{
|
|
Dictionary<long, ReadOnlyCollection<string>> results = [];
|
|
foreach (KeyValuePair<long, List<string>> keyValuePair in keyValuePairs)
|
|
results.Add(keyValuePair.Key, new(keyValuePair.Value));
|
|
return new(results);
|
|
}
|
|
|
|
private static Dictionary<string, ReadOnlyCollection<ReadOnlyCollection<string>>> Convert(Dictionary<string, List<ReadOnlyCollection<string>>> keyValuePairs)
|
|
{
|
|
Dictionary<string, ReadOnlyCollection<ReadOnlyCollection<string>>> results = [];
|
|
foreach (KeyValuePair<string, List<ReadOnlyCollection<string>>> keyValuePair in keyValuePairs)
|
|
results.Add(keyValuePair.Key, new(keyValuePair.Value));
|
|
return new(results);
|
|
}
|
|
|
|
private static ReadOnlyCollection<string> GetObjectCollection(Person? person)
|
|
{
|
|
List<string> results;
|
|
if (person is null)
|
|
results = ["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 =
|
|
[
|
|
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 ReadOnlyCollection<string> GetDistinctSortedKeys(List<Family> familyCollection, char personTitleFilter)
|
|
{
|
|
string[] results;
|
|
string key;
|
|
List<(string? Index, string Key)> collection = [];
|
|
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 Dictionary<string, List<ReadOnlyCollection<string>>> Convert(ReadOnlyCollection<string> distinctSortedKKeys)
|
|
{
|
|
Dictionary<string, List<ReadOnlyCollection<string>>> results = [];
|
|
ReadOnlyCollection<string> collection;
|
|
List<ReadOnlyCollection<string>>? objectCollection;
|
|
foreach (string key in distinctSortedKKeys)
|
|
{
|
|
if (results.ContainsKey(key))
|
|
continue;
|
|
if (!results.TryGetValue(key, out objectCollection))
|
|
{
|
|
results.Add(key, []);
|
|
if (!results.TryGetValue(key, out objectCollection))
|
|
throw new NotSupportedException();
|
|
}
|
|
collection = GetObjectCollection(person: null);
|
|
objectCollection.Add(collection);
|
|
}
|
|
return results;
|
|
}
|
|
|
|
private static ReadOnlyCollection<string> GetHeaderLines(string startsWith, string[] sourceLines)
|
|
{
|
|
List<string> results = [];
|
|
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<string, List<string>> GetTxtFileCollection(Input input)
|
|
{
|
|
Dictionary<string, List<string>> results = [];
|
|
string[] lines;
|
|
string[] directories;
|
|
string directoryName;
|
|
string? sourceDirectory;
|
|
string? parentDirectory;
|
|
List<string>? collectionA;
|
|
List<string>? collectionB;
|
|
string siblingDirectoryName;
|
|
string[] files = input.SingletonDirectory is null || !Directory.Exists(input.SingletonDirectory) ? [] : 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, []);
|
|
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, []);
|
|
if (!results.TryGetValue(siblingDirectoryName, out collectionB))
|
|
throw new Exception();
|
|
}
|
|
collectionB.AddRange(collectionA);
|
|
collectionB.Add(lines[0]);
|
|
}
|
|
}
|
|
return results;
|
|
}
|
|
|
|
private static (int, Name) GetName(ReadOnlyCollection<string> 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<string, List<string>> keyValuePairs, ReadOnlyCollection<string> lines, int i)
|
|
{
|
|
Birth result;
|
|
string seven;
|
|
string? note = null;
|
|
bool moreAdded = false;
|
|
List<string>? collection;
|
|
DateOnly? dateOnly = null;
|
|
List<string> distinct = [];
|
|
List<string> @continue = [];
|
|
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++)
|
|
{
|
|
if (lines[k][0] == '1')
|
|
break;
|
|
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<string> lines, int i)
|
|
{
|
|
Death result;
|
|
string seven;
|
|
string? note = null;
|
|
DateOnly? dateOnly = null;
|
|
List<string> @continue = [];
|
|
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++)
|
|
{
|
|
if (lines[k][0] == '1')
|
|
break;
|
|
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<string> lines, int i)
|
|
{
|
|
Change result;
|
|
string seven;
|
|
string? note = null;
|
|
DateOnly? dateOnly = null;
|
|
List<string> @continue = [];
|
|
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++)
|
|
{
|
|
if (lines[k][0] == '1')
|
|
break;
|
|
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<string> lines, Birth? birth)
|
|
{
|
|
List<string> results = [];
|
|
string six;
|
|
string text;
|
|
string seven;
|
|
List<string> @continue = birth is null ? [] : 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<long> GetIdsWhenPersonHasTitle(ReadOnlyDictionary<long, Person> people)
|
|
{
|
|
List<long> results = [];
|
|
foreach (KeyValuePair<long, Person> keyValuePair in people)
|
|
{
|
|
if (keyValuePair.Value.Title is null)
|
|
continue;
|
|
results.Add(keyValuePair.Key);
|
|
}
|
|
return results;
|
|
}
|
|
|
|
private static ReadOnlyCollection<GenealogicalDataCommunicationRelation> GetRelations(ReadOnlyCollection<ReadOnlyCollection<string>> familyGroupLines)
|
|
{
|
|
List<GenealogicalDataCommunicationRelation> results = [];
|
|
int id;
|
|
string relation;
|
|
string[] segments;
|
|
ReadOnlyCollection<string> 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 ReadOnlyDictionary<string, ReadOnlyCollection<ReadOnlyCollection<string>>> GetKeyValuePairs(List<Family> familyCollection, char personTitleFilter)
|
|
{
|
|
Dictionary<string, ReadOnlyCollection<ReadOnlyCollection<string>>> results;
|
|
Dictionary<string, List<ReadOnlyCollection<string>>> keyValuePairs;
|
|
string id;
|
|
string key;
|
|
ReadOnlyCollection<string> collection;
|
|
List<ReadOnlyCollection<string>>? objectCollection;
|
|
ReadOnlyCollection<string> 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 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<string> 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<string> lines = [];
|
|
List<string> footerLines = [];
|
|
const string startsWith = "0 @";
|
|
Dictionary<long, List<string>> keyValuePairs = [];
|
|
List<ReadOnlyCollection<string>> familyGroupLines = [];
|
|
string[] sourceLines = string.IsNullOrEmpty(input.GenealogicalDataCommunicationFile) || !File.Exists(input.GenealogicalDataCommunicationFile) ? [] : File.ReadAllLines(input.GenealogicalDataCommunicationFile);
|
|
ReadOnlyCollection<string> 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, []);
|
|
if (lines.Count == 0)
|
|
continue;
|
|
keyValuePairs[id.Value].AddRange(lines);
|
|
lines.Clear();
|
|
}
|
|
else
|
|
throw new NotSupportedException();
|
|
}
|
|
ReadOnlyDictionary<long, ReadOnlyCollection<string>> individualsToLines = Convert(keyValuePairs);
|
|
result = new(headerLines, individualsToLines, new(familyGroupLines), new(footerLines));
|
|
return result;
|
|
}
|
|
|
|
private static ReadOnlyDictionary<long, Person> GetPeople(Input input, GenealogicalDataCommunicationCollections genealogicalDataCommunicationCollections)
|
|
{
|
|
Dictionary<long, Person> results = [];
|
|
long? id;
|
|
char? sex;
|
|
Name? name;
|
|
string six;
|
|
string? uId;
|
|
Birth? birth;
|
|
Death? death;
|
|
Person person;
|
|
string? title;
|
|
Change? change;
|
|
bool? moreAdded;
|
|
ReadOnlyCollection<string> lines;
|
|
Dictionary<string, List<string>> keyValuePairs = GetTxtFileCollection(input);
|
|
foreach (KeyValuePair<long, ReadOnlyCollection<string>> 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 Collections GetCollections(AppSettings appSettings, ReadOnlyDictionary<long, Person> people)
|
|
{
|
|
Collections result;
|
|
long personKey;
|
|
char[] ageCollection;
|
|
List<PersonExport> collection = [];
|
|
Dictionary<long, string> idToName = [];
|
|
Dictionary<long, long> idToPersonKey = [];
|
|
Dictionary<long, string> idToGivenName = [];
|
|
int length = appSettings.PersonBirthdayFormat.Length;
|
|
foreach (KeyValuePair<long, Person> 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 ? [] : 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));
|
|
}
|
|
result = new(new(idToPersonKey), new(idToName), new(idToGivenName), new(collection));
|
|
return result;
|
|
}
|
|
|
|
private static List<Family> GetFamilyCollection(ReadOnlyCollection<ReadOnlyCollection<string>> familyGroupLines, ReadOnlyDictionary<long, Person> people, ReadOnlyDictionary<long, long> idToPersonKey, ReadOnlyDictionary<long, string> idToName, ReadOnlyDictionary<long, string> idToGivenName)
|
|
{
|
|
List<Family> results = [];
|
|
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<long> family = GetIdsWhenPersonHasTitle(people);
|
|
ReadOnlyCollection<GenealogicalDataCommunicationRelation> 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<long, Person> 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 void WriteJsonFiles(AppSettings appSettings, Input input, GenealogicalDataCommunicationCollections genealogicalDataCommunicationCollections, ReadOnlyDictionary<long, Person> people, List<Family> familyCollection)
|
|
{
|
|
string json;
|
|
if (people.Count != genealogicalDataCommunicationCollections.IndividualsToLines.Count)
|
|
throw new NotSupportedException();
|
|
ReadOnlyDictionary<string, ReadOnlyCollection<ReadOnlyCollection<string>>> keyValuePairs;
|
|
json = JsonSerializer.Serialize(new(people), PeopleSourceGenerationContext.Default.DictionaryInt64Person);
|
|
File.WriteAllText(Path.Combine(input.GenealogicalDataCommunicationDirectory, "people.json"), json);
|
|
Dictionary<long, Person>? 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<Worker> logger, Input input, GenealogicalDataCommunicationCollections genealogicalDataCommunicationCollections, ReadOnlyDictionary<long, Person> people)
|
|
{
|
|
List<string> lines = [];
|
|
List<string> allLines = [];
|
|
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<string>? collection;
|
|
foreach (KeyValuePair<long, Person> 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<string> 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<Family> 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<long, string> idToName, ReadOnlyCollection<PersonExport> personExportCollection)
|
|
{
|
|
int age;
|
|
string text;
|
|
string? name;
|
|
string directory;
|
|
string hourGroup;
|
|
string? yearGroup;
|
|
long count = ticks;
|
|
string rootDirectory;
|
|
string approximateYears;
|
|
List<string> distinct = [];
|
|
List<string> duplicates = [];
|
|
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();
|
|
}
|
|
|
|
internal static void FileSystemToGenealogicalDataCommunication(AppSettings appSettings, ILogger<Worker> logger, List<string> 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<long, Person> people = GetPeople(input, genealogicalDataCommunicationCollections);
|
|
Collections collections = GetCollections(appSettings, people);
|
|
if (collections.IdToPersonKey.Count != people.Count || collections.IdToPersonKey.Count != collections.IdToName.Count || collections.IdToPersonKey.Count != collections.IdToGivenName.Count)
|
|
throw new NotSupportedException();
|
|
List<Family> familyCollection = GetFamilyCollection(genealogicalDataCommunicationCollections.FamilyGroupLines, people, collections.IdToPersonKey, collections.IdToName, collections.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, collections.IdToName, collections.PersonExportCollection);
|
|
if (string.IsNullOrEmpty(input.GenealogicalDataCommunicationFile))
|
|
logger.LogInformation("{file} is null?", input.GenealogicalDataCommunicationDirectory);
|
|
}
|
|
|
|
} |