Family Tree as Markdown Files

This commit is contained in:
2023-07-16 23:06:08 -07:00
parent 240c1ef6f9
commit b2fe53275f
15 changed files with 594 additions and 202 deletions

View File

@ -1,3 +1,5 @@
using System.Collections.ObjectModel;
namespace View_by_Distance.Shared.Models.Stateless.Methods;
internal abstract class GenealogicalDataCommunication
@ -17,13 +19,52 @@ internal abstract class GenealogicalDataCommunication
return results.ToArray();
}
internal static (string[] headerLines, Dictionary<string, List<string>> individuals, string[] footerLines) GetIndividuals(string genealogicalDataCommunicationFile, bool requireNickName)
private static List<GenealogicalDataCommunicationRelation> GetRelations(Dictionary<string, string> idToNick, List<string[]> familyGroupLines)
{
Dictionary<string, List<string>> results = new();
List<GenealogicalDataCommunicationRelation> results = new();
string? nick;
string relation;
string[] segments;
string[] familyLines;
for (int i = 0; i < familyGroupLines.Count; i++)
{
familyLines = familyGroupLines[i];
for (int j = 0; j < familyLines.Length; j++)
{
segments = familyLines[j].Split('@');
if (segments.First().Length < 3 || segments.Length != 3)
continue;
if (!idToNick.TryGetValue(segments[1], out nick))
continue;
relation = segments.First()[2..].Trim();
if (j + 1 >= familyLines.Length || familyLines[j + 1].Length < 3 || familyLines[j + 1][..3] != "2 _")
results.Add(new(i, relation, segments[1], nick, null));
else
results.Add(new(i, relation, segments[1], nick, familyLines[j + 1][2..]));
}
}
return results;
}
private static ReadOnlyDictionary<string, string[]> Convert(Dictionary<string, List<string>> keyValuePairs)
{
Dictionary<string, string[]> results = new();
foreach (KeyValuePair<string, List<string>> keyValuePair in keyValuePairs)
results.Add(keyValuePair.Key, keyValuePair.Value.ToArray());
return new(results);
}
internal static (string[], ReadOnlyDictionary<string, string[]>, List<string[]>, string[], List<GenealogicalDataCommunicationRelation> genealogicalDataCommunicationRelations) GetIndividuals(string genealogicalDataCommunicationFile, bool requireNickName)
{
ReadOnlyDictionary<string, string[]> results;
string? nick;
string[] segments;
List<string> lines = new();
const string startsWith = "0 @";
List<string> footerLines = new();
List<string[]> familyGroupLines = new();
Dictionary<string, string> idToNick = new();
Dictionary<string, List<string>> keyValuePairs = new();
string[] sourceLines = string.IsNullOrEmpty(genealogicalDataCommunicationFile) || !File.Exists(genealogicalDataCommunicationFile) ? Array.Empty<string>() : File.ReadAllLines(genealogicalDataCommunicationFile);
string[] headerLines = GetHeaderLines(startsWith, sourceLines);
for (int i = headerLines.Length; i < sourceLines.Length; i++)
@ -36,13 +77,29 @@ internal abstract class GenealogicalDataCommunication
continue;
else if (sourceLines[i].EndsWith("@ FAM"))
{
for (int j = i + 1; j < sourceLines.Length; j++)
lines.Clear();
for (int j = sourceLines.Length - 1; j >= i; j--)
{
lines.Add(sourceLines[j]);
footerLines.AddRange(lines);
if (sourceLines[j].First() == '0')
{
if (!sourceLines[j].EndsWith("@ FAM"))
footerLines.AddRange(lines);
else
{
lines.Reverse();
familyGroupLines.Add(lines.ToArray());
}
lines.Clear();
}
}
familyGroupLines.Reverse();
footerLines.Reverse();
break;
}
else if (sourceLines[i].EndsWith("@ INDI"))
{
segments = sourceLines[i].Split('@');
for (int j = i + 1; j < sourceLines.Length; j++)
{
if (sourceLines[j].StartsWith(startsWith))
@ -51,26 +108,30 @@ internal abstract class GenealogicalDataCommunication
if (!sourceLines[j].StartsWith("2 NICK "))
continue;
nick = sourceLines[j][7..];
if (segments.Length == 3)
idToNick.Add(segments[1], nick);
}
if (requireNickName && string.IsNullOrEmpty(nick))
throw new Exception(string.Join(Environment.NewLine, lines));
nick ??= Guid.NewGuid().ToString();
results.Add(nick, new());
keyValuePairs.Add(nick, new());
if (!lines.Any())
continue;
results[nick].AddRange(lines);
keyValuePairs[nick].AddRange(lines);
lines.Clear();
}
else
throw new NotSupportedException();
}
return (headerLines, results, footerLines.ToArray());
results = Convert(keyValuePairs);
List<GenealogicalDataCommunicationRelation> genealogicalDataCommunicationRelations = GetRelations(idToNick, familyGroupLines);
return (headerLines, results, familyGroupLines, footerLines.ToArray(), genealogicalDataCommunicationRelations);
}
private static string[] GetFilteredOutMapped(List<string> individualsLines)
private static string[] GetFilteredOutMapped(string[] individualsLines)
{
List<string> results = new();
for (int i = 0; i < individualsLines.Count; i++)
for (int i = 0; i < individualsLines.Length; i++)
{
if (individualsLines[i].StartsWith("0 @I"))
continue;
@ -90,19 +151,19 @@ internal abstract class GenealogicalDataCommunication
continue;
else if (individualsLines[i] == "1 BIRT")
{
if (individualsLines.Count > i + 1 && individualsLines[i + 1].StartsWith("2 DATE"))
if (individualsLines.Length > 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"))
if (individualsLines.Length > 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"))
if (individualsLines.Length > i + 1 && individualsLines[i + 1].StartsWith("2 DATE"))
i += 1;
continue;
}
@ -116,9 +177,9 @@ internal abstract class GenealogicalDataCommunication
List<string> results = new();
Models.PersonBirthday personBirthday = new(DateTime.Now);
GenealogicalDataCommunicationLines genealogicalDataCommunicationLines;
(string[] headerLines, Dictionary<string, List<string>> individuals, string[] footerLines) = GetIndividuals(genealogicalDataCommunicationFile, requireNickName);
(string[] headerLines, ReadOnlyDictionary<string, string[]> individuals, List<string[]> familyGroupLines, string[] footerLines, _) = GetIndividuals(genealogicalDataCommunicationFile, requireNickName);
results.AddRange(headerLines);
foreach (KeyValuePair<string, List<string>> keyValuePair in individuals)
foreach (KeyValuePair<string, string[]> keyValuePair in individuals)
{
genealogicalDataCommunicationLines = GetGenealogicalDataCommunicationLines(personBirthday, keyValuePair.Value);
if (!string.IsNullOrEmpty(genealogicalDataCommunicationLines.Id))
@ -139,11 +200,13 @@ internal abstract class GenealogicalDataCommunication
results.AddRange(genealogicalDataCommunicationLines.Death);
results.AddRange(genealogicalDataCommunicationLines.Changed);
}
for (int i = 0; i < familyGroupLines.Count; i++)
results.AddRange(familyGroupLines[i]);
results.AddRange(footerLines);
return results;
}
internal static GenealogicalDataCommunicationLines GetGenealogicalDataCommunicationLines(Models.PersonBirthday personBirthday, List<string> individualsLines)
internal static GenealogicalDataCommunicationLines GetGenealogicalDataCommunicationLines(Models.PersonBirthday personBirthday, string[] individualsLines)
{
GenealogicalDataCommunicationLines result;
string? idLine = null;
@ -157,7 +220,7 @@ internal abstract class GenealogicalDataCommunication
List<string> birthLines = new();
List<string> deathLines = new();
List<string> changedLines = new();
for (int i = 0; i < individualsLines.Count; i++)
for (int i = 0; i < individualsLines.Length; i++)
{
if (idLine is null && individualsLines[i].EndsWith("@ INDI"))
idLine = individualsLines[i];
@ -178,7 +241,7 @@ internal abstract class GenealogicalDataCommunication
else if (!birthLines.Any() && individualsLines[i] == "1 BIRT")
{
birthLines.Add(individualsLines[i]);
if (individualsLines.Count > i + 1 && individualsLines[i + 1].StartsWith("2 DATE"))
if (individualsLines.Length > i + 1 && individualsLines[i + 1].StartsWith("2 DATE"))
{
i += 1;
birthLines.Add(individualsLines[i]);
@ -187,7 +250,7 @@ internal abstract class GenealogicalDataCommunication
else if (!deathLines.Any() && individualsLines[i].StartsWith("1 DEAT"))
{
deathLines.Add(individualsLines[i]);
if (individualsLines.Count > i + 1 && individualsLines[i + 1].StartsWith("2 DATE"))
if (individualsLines.Length > i + 1 && individualsLines[i + 1].StartsWith("2 DATE"))
{
i += 1;
deathLines.Add(individualsLines[i]);
@ -196,7 +259,7 @@ internal abstract class GenealogicalDataCommunication
else if (!changedLines.Any() && individualsLines[i] == "1 CHAN")
{
changedLines.Add(individualsLines[i]);
if (individualsLines.Count > i + 1 && individualsLines[i + 1].StartsWith("2 DATE"))
if (individualsLines.Length > i + 1 && individualsLines[i + 1].StartsWith("2 DATE"))
{
i += 1;
changedLines.Add(individualsLines[i]);
@ -259,7 +322,7 @@ internal abstract class GenealogicalDataCommunication
return result;
}
internal static void WriteFile(string personKeyFormatted, Models.PersonName personName, List<string>? individualsLines, bool isDefaultName, string directory, Models.GenealogicalDataCommunication genealogicalDataCommunication, bool verify, bool first)
internal static void WriteFile(string personKeyFormatted, Models.PersonName personName, string[]? individualsLines, bool isDefaultName, string directory, Models.GenealogicalDataCommunication genealogicalDataCommunication, bool verify, bool first)
{
if (verify)
{
@ -287,64 +350,65 @@ internal abstract class GenealogicalDataCommunication
_ = Directory.CreateDirectory(directory);
if (first && !personKeyFormatted.EndsWith(code))
personKeyFormatted = $"{personKeyFormatted[..^2]}{code}";
List<string> pGedLines = new();
List<string> lines = new();
if (individualsLines is null || !individualsLines.Any())
pGedLines.Add($"0 @I{personKeyFormatted}@ INDI");
lines.Add($"0 @I{personKeyFormatted}@ INDI");
else
{
if (!individualsLines[0].StartsWith("0 @I"))
throw new NotSupportedException();
pGedLines.Add(individualsLines[0]);
lines.Add(individualsLines[0]);
}
pGedLines.Add($"1 NAME {personName.First.Value} /{personName.Last.Value}/{jrOrSr}");
lines.Add($"1 NAME {personName.First.Value} /{personName.Last.Value}/{jrOrSr}");
if (!string.IsNullOrEmpty(personName.First.Value))
pGedLines.Add($"2 GIVN {personName.First.Value}");
lines.Add($"2 GIVN {personName.First.Value}");
if (!string.IsNullOrEmpty(personName.Last.Value))
pGedLines.Add($"2 SURN {personName.Last.Value}");
lines.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 {genealogicalDataCommunication.Sex}");
lines.Add($"2 NSFX {jrOrSr.Trim()}");
lines.Add($"2 NICK {personKeyFormatted}");
lines.Add($"1 SEX {genealogicalDataCommunication.Sex}");
if (genealogicalDataCommunication.Birth is not null)
{
Models.PersonBirthday personBirthday = new(genealogicalDataCommunication.Birth.Value);
if (!IPersonBirthday.IsCounterPersonBirthday(personBirthday))
{
pGedLines.Add("1 BIRT");
pGedLines.Add($"2 DATE {genealogicalDataCommunication.Birth.Value:dd MMM yyyy}");
lines.Add("1 BIRT");
lines.Add($"2 DATE {genealogicalDataCommunication.Birth.Value:dd MMM yyyy}");
}
}
if (genealogicalDataCommunication.Death is not null)
{
if (genealogicalDataCommunication?.Death is null || genealogicalDataCommunication.Death == genealogicalDataCommunication.Birth || IPersonBirthday.IsCounterPersonBirthday(new(genealogicalDataCommunication.Death.Value)))
pGedLines.Add("1 DEAT Y");
lines.Add("1 DEAT Y");
else
{
pGedLines.Add("1 DEAT");
pGedLines.Add($"2 DATE {genealogicalDataCommunication.Death.Value:dd MMM yyyy}");
lines.Add("1 DEAT");
lines.Add($"2 DATE {genealogicalDataCommunication.Death.Value:dd MMM yyyy}");
}
}
if (isDefaultName)
pGedLines.Add("9 NOTE");
lines.Add("9 NOTE");
if (individualsLines is not null)
pGedLines.AddRange(GetFilteredOutMapped(individualsLines));
string text = string.Join(Environment.NewLine, pGedLines);
lines.AddRange(GetFilteredOutMapped(individualsLines));
string text = string.Join(Environment.NewLine, lines);
_ = IPath.WriteAllText(Path.Combine(directory, $"{personKeyFormatted}.pged"), text, updateDateWhenMatches: false, compareBeforeWrite: true);
}
internal static void CreateTree(string mappingDefaultName, string personBirthdayFormat, string resultAllInOne, List<Models.PersonContainer> personContainers, string[] genealogicalDataCommunicationHeaderLines, string[] genealogicalDataCommunicationFooterLines, long ticks, string a2PeopleContentDirectory, Dictionary<long, List<int>> personKeyToIds)
internal static bool CleanDisplayDirectoryAllFilesAndWriteTicksGed(string mappingDefaultName, string personBirthdayFormat, List<Models.PersonContainer> personContainers, string[] headerLines, List<string[]> familyGroupLines, string[] footerLines, long ticks, string a2PeopleContentDirectory)
{
string by;
string[] matches;
bool result = false;
string[] mdFiles;
string[] txtFiles;
const int zero = 0;
string[] jsonFiles;
string[] pGedFiles;
string[] pGedLines;
string personKeyFormatted;
List<string> lines = new();
List<long> distinct = new();
lines.AddRange(genealogicalDataCommunicationHeaderLines);
List<string> individualsLines;
Models.PersonBirthday personBirthday;
string rootDirectory = Path.Combine(a2PeopleContentDirectory, $"{ticks}-Tree");
lines.AddRange(headerLines);
foreach (Models.PersonContainer personContainer in personContainers)
{
if (personContainer.Key is null || personContainer.Birthdays is null || personContainer.Person is null || !personContainer.Birthdays.Any())
@ -356,28 +420,56 @@ internal abstract class GenealogicalDataCommunication
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);
mdFiles = (from l in personContainer.DisplayDirectoryAllFiles where l.EndsWith(".md") select l).ToArray();
txtFiles = (from l in personContainer.DisplayDirectoryAllFiles where l.EndsWith(".txt") select l).ToArray();
jsonFiles = (from l in personContainer.DisplayDirectoryAllFiles where l.EndsWith(".json") select l).ToArray();
pGedFiles = (from l in personContainer.DisplayDirectoryAllFiles where l.EndsWith(".pged") select l).ToArray();
foreach (string mdFile in mdFiles)
{
if (string.IsNullOrEmpty(personKeyFormatted))
continue;
if (!mdFile.Contains(personKeyFormatted))
{
if (!result)
result = true;
File.Delete(mdFile);
}
}
foreach (string pGedFile in pGedFiles)
{
if (string.IsNullOrEmpty(personKeyFormatted))
continue;
if (!pGedFile.Contains(personKeyFormatted))
{
if (!result)
result = true;
File.Delete(pGedFile);
continue;
}
individualsLines = File.ReadAllLines(pGedFile).ToList();
foreach (string jsonFile in jsonFiles)
{
if (!result)
result = true;
File.Delete(jsonFile);
}
foreach (string txtFile in txtFiles)
{
if (new FileInfo(txtFile).Length == 0)
{
if (!result)
result = true;
File.Delete(txtFile);
}
}
lines.AddRange(individualsLines);
}
}
lines.AddRange(genealogicalDataCommunicationFooterLines);
for (int i = 0; i < familyGroupLines.Count; i++)
lines.AddRange(familyGroupLines[i]);
lines.AddRange(footerLines);
File.WriteAllLines(Path.Combine(a2PeopleContentDirectory, $"{ticks}.ged"), lines);
return result;
}
}

View File

@ -1,3 +1,5 @@
using System.Collections.ObjectModel;
namespace View_by_Distance.Shared.Models.Stateless.Methods;
public interface IGenealogicalDataCommunication
@ -5,9 +7,9 @@ public interface IGenealogicalDataCommunication
// ...
void TestStatic_WriteFile(string personKeyFormatted, Models.PersonName personName, List<string>? individualsLines, bool isDefaultName, string directory, Models.GenealogicalDataCommunication genealogicalDataCommunication, bool verify, bool first) =>
void TestStatic_WriteFile(string personKeyFormatted, Models.PersonName personName, string[]? individualsLines, bool isDefaultName, string directory, Models.GenealogicalDataCommunication genealogicalDataCommunication, bool verify, bool first) =>
WriteFile(personKeyFormatted, personName, individualsLines, isDefaultName, directory, genealogicalDataCommunication, verify, first);
static void WriteFile(string personKeyFormatted, Models.PersonName personName, List<string>? individualsLines, bool isDefaultName, string directory, Models.GenealogicalDataCommunication genealogicalDataCommunication, bool verify, bool first) =>
static void WriteFile(string personKeyFormatted, Models.PersonName personName, string[]? individualsLines, bool isDefaultName, string directory, Models.GenealogicalDataCommunication genealogicalDataCommunication, bool verify, bool first) =>
GenealogicalDataCommunication.WriteFile(personKeyFormatted, personName, individualsLines, isDefaultName, directory, genealogicalDataCommunication, verify, first);
List<string> TestStatic_GetMappedLines(string genealogicalDataCommunicationFile, bool requireNickName) =>
@ -15,9 +17,9 @@ public interface IGenealogicalDataCommunication
static List<string> GetMappedLines(string genealogicalDataCommunicationFile, bool requireNickName) =>
GenealogicalDataCommunication.GetMappedLines(genealogicalDataCommunicationFile, requireNickName);
GenealogicalDataCommunicationLines TestStatic_GetGenealogicalDataCommunicationLines(Models.PersonBirthday personBirthday, List<string> individualsLines) =>
GenealogicalDataCommunicationLines TestStatic_GetGenealogicalDataCommunicationLines(Models.PersonBirthday personBirthday, string[] individualsLines) =>
GetGenealogicalDataCommunicationLines(individualsLines);
static GenealogicalDataCommunicationLines GetGenealogicalDataCommunicationLines(List<string> individualsLines) =>
static GenealogicalDataCommunicationLines GetGenealogicalDataCommunicationLines(string[] individualsLines) =>
GenealogicalDataCommunication.GetGenealogicalDataCommunicationLines(new(DateTime.Now), individualsLines);
Models.GenealogicalDataCommunication TestStatic_GetGenealogicalDataCommunication(GenealogicalDataCommunicationLines genealogicalDataCommunicationLines) =>
@ -25,14 +27,14 @@ public interface IGenealogicalDataCommunication
static Models.GenealogicalDataCommunication GetGenealogicalDataCommunication(GenealogicalDataCommunicationLines genealogicalDataCommunicationLines) =>
GenealogicalDataCommunication.GetGenealogicalDataCommunication(genealogicalDataCommunicationLines);
(string[] headerLines, Dictionary<string, List<string>> individuals, string[] footerLines) TestStatic_GetIndividuals(string genealogicalDataCommunicationFile, bool requireNickName) =>
(string[], ReadOnlyDictionary<string, string[]>, List<string[]>, string[], List<GenealogicalDataCommunicationRelation> genealogicalDataCommunicationRelations) TestStatic_GetIndividuals(string genealogicalDataCommunicationFile, bool requireNickName) =>
GetIndividuals(genealogicalDataCommunicationFile, requireNickName);
static (string[] headerLines, Dictionary<string, List<string>> individuals, string[] footerLines) GetIndividuals(string genealogicalDataCommunicationFile, bool requireNickName) =>
static (string[], ReadOnlyDictionary<string, string[]>, List<string[]>, string[], List<GenealogicalDataCommunicationRelation> genealogicalDataCommunicationRelations) GetIndividuals(string genealogicalDataCommunicationFile, bool requireNickName) =>
GenealogicalDataCommunication.GetIndividuals(genealogicalDataCommunicationFile, requireNickName);
void TestStatic_CreateTree(string mappingDefaultName, string personBirthdayFormat, string resultAllInOne, List<Models.PersonContainer> personContainers, string[] genealogicalDataCommunicationHeaderLines, string[] genealogicalDataCommunicationFooterLines, long ticks, string a2PeopleContentDirectory, Dictionary<long, List<int>> personKeyToIds) =>
CreateTree(mappingDefaultName, personBirthdayFormat, resultAllInOne, personContainers, genealogicalDataCommunicationHeaderLines, genealogicalDataCommunicationFooterLines, ticks, a2PeopleContentDirectory, personKeyToIds);
static void CreateTree(string mappingDefaultName, string personBirthdayFormat, string resultAllInOne, List<Models.PersonContainer> personContainers, string[] genealogicalDataCommunicationHeaderLines, string[] genealogicalDataCommunicationFooterLines, long ticks, string a2PeopleContentDirectory, Dictionary<long, List<int>> personKeyToIds) =>
GenealogicalDataCommunication.CreateTree(mappingDefaultName, personBirthdayFormat, resultAllInOne, personContainers, genealogicalDataCommunicationHeaderLines, genealogicalDataCommunicationFooterLines, ticks, a2PeopleContentDirectory, personKeyToIds);
bool TestStatic_CleanDisplayDirectoryAllFilesAndWriteTicksGed(string mappingDefaultName, string personBirthdayFormat, List<Models.PersonContainer> personContainers, string[] headerLines, List<string[]> familyGroupLines, string[] footerLines, long ticks, string a2PeopleContentDirectory) =>
CleanDisplayDirectoryAllFilesAndWriteTicksGed(mappingDefaultName, personBirthdayFormat, personContainers, headerLines, familyGroupLines, footerLines, ticks, a2PeopleContentDirectory);
static bool CleanDisplayDirectoryAllFilesAndWriteTicksGed(string mappingDefaultName, string personBirthdayFormat, List<Models.PersonContainer> personContainers, string[] headerLines, List<string[]> familyGroupLines, string[] footerLines, long ticks, string a2PeopleContentDirectory) =>
GenealogicalDataCommunication.CleanDisplayDirectoryAllFilesAndWriteTicksGed(mappingDefaultName, personBirthdayFormat, personContainers, headerLines, familyGroupLines, footerLines, ticks, a2PeopleContentDirectory);
}

View File

@ -1,3 +1,5 @@
using System.Collections.ObjectModel;
namespace View_by_Distance.Shared.Models.Stateless.Methods;
public interface IPersonContainer
@ -15,11 +17,16 @@ public interface IPersonContainer
static List<Models.PersonContainer> GetNonSpecificPeopleCollection(string mappingDefaultName, int personBirthdayFirstYear, char[] personCharacters, List<Models.PersonContainer> personContainers, long ticks) =>
PersonContainer.GetNonSpecificPeopleCollection(mappingDefaultName, personBirthdayFirstYear, personCharacters, personContainers, ticks);
List<Models.PersonContainer> TestStatic_GetPersonContainers(Properties.IStorage storage, string mappingDefaultName, string personBirthdayFormat, char[] personCharacters, string facesFileNameExtension, Dictionary<string, List<string>> individuals) =>
List<Models.PersonContainer> TestStatic_GetPersonContainers(Properties.IStorage storage, string mappingDefaultName, string personBirthdayFormat, char[] personCharacters, string facesFileNameExtension, ReadOnlyDictionary<string, string[]> individuals) =>
GetPersonContainers(storage, mappingDefaultName, personBirthdayFormat, personCharacters, facesFileNameExtension, individuals);
static List<Models.PersonContainer> GetPersonContainers(Properties.IStorage storage, string mappingDefaultName, string personBirthdayFormat, char[] personCharacters, string facesFileNameExtension, Dictionary<string, List<string>> individuals) =>
static List<Models.PersonContainer> GetPersonContainers(Properties.IStorage storage, string mappingDefaultName, string personBirthdayFormat, char[] personCharacters, string facesFileNameExtension, ReadOnlyDictionary<string, string[]> individuals) =>
PersonContainer.GetPersonContainers(storage, mappingDefaultName, personBirthdayFormat, personCharacters, facesFileNameExtension, individuals);
void TestStatic_MaybeWriteFiles(string mappingDefaultName, string personBirthdayFormat, List<GenealogicalDataCommunicationRelation> genealogicalDataCommunicationRelations, List<Models.PersonContainer> personContainers) =>
MaybeWriteFiles(mappingDefaultName, personBirthdayFormat, genealogicalDataCommunicationRelations, personContainers);
static void MaybeWriteFiles(string mappingDefaultName, string personBirthdayFormat, List<GenealogicalDataCommunicationRelation> genealogicalDataCommunicationRelations, List<Models.PersonContainer> personContainers) =>
PersonContainer.MaybeWriteFiles(mappingDefaultName, personBirthdayFormat, genealogicalDataCommunicationRelations, personContainers);
List<(long?, string)> TestStatic_GetDisplay(string personBirthdayFormat, Models.PersonContainer personContainer) =>
GetDisplay(personBirthdayFormat, personContainer);
static List<(long?, string)> GetDisplay(string personBirthdayFormat, Models.PersonContainer personContainer) =>

View File

@ -0,0 +1,132 @@
using System.Collections.ObjectModel;
using System.Text.RegularExpressions;
namespace View_by_Distance.Shared.Models.Stateless.Methods;
internal abstract class MarkDown
{
// ...
internal static void WriteFile(string personKeyFormatted, Models.PersonName personName, List<GenealogicalDataCommunicationRelation> genealogicalDataCommunicationRelations, ReadOnlyDictionary<string, string> personKeyFormattedToPersonFullName, ReadOnlyDictionary<int, List<GenealogicalDataCommunicationRelation>> familyIndexToCollection, bool isDefaultName, string directory, Models.GenealogicalDataCommunication genealogicalDataCommunication, bool first, string fullName, string lowerHyphenFullName)
{
string? personFullName;
bool hasRelation = false;
string lowerHyphenRelation;
List<GenealogicalDataCommunicationRelation>? relations;
string now = DateTime.Now.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.fffZ");
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(genealogicalDataCommunication.Death is null, genealogicalDataCommunication.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<string> lines = new()
{
"---",
"type: person",
$"created: {now}",
$"updated: {now}",
$"{nameof(fullName)}: {fullName}",
$"name: {personName.First.Value} /{personName.Last.Value}/{jrOrSr}",
};
if (!string.IsNullOrEmpty(personName.First.Value))
lines.Add($"given: {personName.First.Value}");
if (!string.IsNullOrEmpty(personName.Last.Value))
lines.Add($"surname: {personName.Last.Value}");
if (!string.IsNullOrEmpty(jrOrSr))
lines.Add($"suffix: {jrOrSr.Trim()}");
lines.Add($"sex: {genealogicalDataCommunication.Sex}");
if (genealogicalDataCommunication.Birth is not null)
{
Models.PersonBirthday personBirthday = new(genealogicalDataCommunication.Birth.Value);
if (!IPersonBirthday.IsCounterPersonBirthday(personBirthday))
lines.Add($"birthDate: {genealogicalDataCommunication.Birth.Value:dd MMM yyyy}");
}
if (genealogicalDataCommunication.Death is not null)
{
if (genealogicalDataCommunication?.Death is null || genealogicalDataCommunication.Death == genealogicalDataCommunication.Birth || IPersonBirthday.IsCounterPersonBirthday(new(genealogicalDataCommunication.Death.Value)))
lines.Add("isDead: true");
else
lines.Add($"deathDate: {genealogicalDataCommunication.Death.Value:dd MMM yyyy}");
}
if (isDefaultName)
lines.Add($"{nameof(isDefaultName)}: {isDefaultName}");
lines.Add($"{nameof(personKeyFormatted)}: {personKeyFormatted}");
lines.Add("---");
lines.Add(string.Empty);
lines.Add($"# {lowerHyphenFullName}");
lines.Add(string.Empty);
const char father = 'F';
const char mother = 'M';
const string wife = "WIFE";
const string child = "CHIL";
const string husband = "HUSB";
foreach (GenealogicalDataCommunicationRelation genealogicalDataCommunicationRelation in genealogicalDataCommunicationRelations)
{
if (genealogicalDataCommunicationRelation.Relation != child)
continue;
if (genealogicalDataCommunicationRelation.NickName != personKeyFormatted)
continue;
if (!familyIndexToCollection.TryGetValue(genealogicalDataCommunicationRelation.FamilyIndex, out relations))
continue;
foreach (GenealogicalDataCommunicationRelation relation in relations)
{
if (relation.FamilyIndex != genealogicalDataCommunicationRelation.FamilyIndex)
continue;
if (relation.Relation is husband or wife)
{
if (!hasRelation)
{
lines.Add("## Relations");
lines.Add(string.Empty);
hasRelation = true;
}
if (!personKeyFormattedToPersonFullName.TryGetValue(relation.NickName, out personFullName))
lowerHyphenRelation = relation.NickName;
else
lowerHyphenRelation = Regex.Replace(personFullName.ToLower(), "[^a-z0-9-]", "-");
if (string.IsNullOrEmpty(genealogicalDataCommunicationRelation.LineTwo))
lines.Add($"- [[{lowerHyphenRelation}]]");
else
{
if (genealogicalDataCommunicationRelation.LineTwo[1] == father)
{
if (relation.Relation == wife)
lines.Add($"- [[{lowerHyphenRelation}]] {nameof(mother)}");
else if (relation.Relation == husband)
lines.Add($"- [[{lowerHyphenRelation}]] {genealogicalDataCommunicationRelation.LineTwo.Split(' ').Last()} {nameof(father)}");
}
else if (genealogicalDataCommunicationRelation.LineTwo[1] == mother)
{
if (relation.Relation == husband)
lines.Add($"- [[{lowerHyphenRelation}]] {nameof(father)}");
else if (relation.Relation == wife)
lines.Add($"- [[{lowerHyphenRelation}]] {genealogicalDataCommunicationRelation.LineTwo.Split(' ').Last()} {nameof(mother)}");
}
}
}
}
}
if (hasRelation)
lines.Add(string.Empty);
string text = string.Join(Environment.NewLine, lines);
_ = IPath.WriteAllText(Path.Combine(directory, $"{lowerHyphenFullName}.md"), text, updateDateWhenMatches: false, compareBeforeWrite: true);
}
}

View File

@ -1,3 +1,6 @@
using System.Collections.ObjectModel;
using System.Text.RegularExpressions;
namespace View_by_Distance.Shared.Models.Stateless.Methods;
internal abstract class PersonContainer
@ -90,17 +93,19 @@ internal abstract class PersonContainer
return results;
}
private static List<Models.PersonContainer> GetPersonContainersCollections(string mappingDefaultName, string facesFileNameExtension, char[] personCharacters, PersonDirectory personDirectory, char numberSign, string personDisplayDirectory, string personDisplayDirectoryName, bool isDefaultName, int? approximateYears, List<(string PersonKeyFormatted, Models.PersonBirthday PersonBirthday)> collection)
private static List<Models.PersonContainer> GetPersonContainersCollections(string mappingDefaultName, string facesFileNameExtension, char[] personCharacters, ReadOnlyDictionary<string, string[]> individuals, PersonDirectory personDirectory, char numberSign, string personDisplayDirectory, string personDisplayDirectoryName, bool isDefaultName, int? approximateYears, List<(string PersonKeyFormatted, Models.PersonBirthday PersonBirthday)> collection)
{
List<Models.PersonContainer> results = new();
long personKey;
const int zero = 0;
Models.Person person;
string[]? individualsLines;
Models.PersonContainer personContainer;
Models.PersonBirthday[] orderedPersonBirthdays;
string[] personDisplayDirectoryAllFiles = GetFiles(facesFileNameExtension, personDisplayDirectory, isDefaultName);
foreach ((string personKeyFormatted, Models.PersonBirthday personBirthday) in collection)
{
_ = individuals.TryGetValue(personKeyFormatted, out individualsLines);
orderedPersonBirthdays = (from l in collection where !l.PersonKeyFormatted.Contains(numberSign) orderby l.PersonBirthday.Value.Ticks descending select l.PersonBirthday).ToArray();
if (!orderedPersonBirthdays.Any())
personKey = collection[zero].PersonBirthday.Value.Ticks;
@ -111,7 +116,7 @@ internal abstract class PersonContainer
personKey = orderedPersonBirthdays[zero].Value.Ticks;
}
person = IPerson.GetPerson(mappingDefaultName, personCharacters, personDisplayDirectoryName, personDisplayDirectoryAllFiles, personKey);
personContainer = new(approximateYears, orderedPersonBirthdays, personDisplayDirectoryAllFiles, personDisplayDirectoryName, personKey, person, personDirectory);
personContainer = new(approximateYears, orderedPersonBirthdays, personDisplayDirectoryAllFiles, personDisplayDirectoryName, individualsLines, personKey, person, personDirectory);
results.Add(personContainer);
}
return results;
@ -148,48 +153,7 @@ internal abstract class PersonContainer
return result;
}
private static void MaybeGenealogicalDataCommunicationWriteFile(string mappingDefaultName, string personBirthdayFormat, Dictionary<string, List<string>> individuals, PersonDirectory personDirectory, string personDisplayDirectoryName, bool isDefaultName, List<Models.PersonContainer> personContainers)
{
bool? first;
bool? male;
string[] matches;
string? directory;
bool verify = true;
const int zero = 0;
string personKeyFormatted;
List<string>? individualsLines = null;
Models.GenealogicalDataCommunication genealogicalDataCommunication;
GenealogicalDataCommunicationLines? genealogicalDataCommunicationLines;
foreach (Models.PersonContainer personContainer in personContainers)
{
if (personContainer.Key is null || personContainer.Birthdays is null || personContainer.Person is null || !personContainer.Birthdays.Any())
continue;
male = personDirectory.Sex == 'U' ? null : personDirectory.Sex == 'M' || (personDirectory.Sex == 'F' ? false : throw new Exception());
first = personDirectory.First == 'U' ? null : personDirectory.First == 'Y' || (personDirectory.First == 'N' ? false : throw new Exception());
if (first is null)
continue;
isDefaultName = IPerson.IsDefaultName(mappingDefaultName, personDisplayDirectoryName);
personKeyFormatted = IPersonBirthday.GetFormatted(personBirthdayFormat, personContainer.Key.Value);
_ = individuals.TryGetValue(personKeyFormatted, out individualsLines);
matches = (from l in personContainer.DisplayDirectoryAllFiles where l.Contains(personKeyFormatted) select l).ToArray();
if (!matches.Any())
continue;
directory = Path.GetDirectoryName(matches[zero]);
if (directory is null)
continue;
genealogicalDataCommunicationLines = individualsLines is null ? null : GenealogicalDataCommunication.GetGenealogicalDataCommunicationLines(personContainer.Birthdays[0], individualsLines);
if (genealogicalDataCommunicationLines is null)
continue;
genealogicalDataCommunication = GenealogicalDataCommunication.GetGenealogicalDataCommunication(genealogicalDataCommunicationLines);
if (genealogicalDataCommunication.Sex != personDirectory.Sex)
continue;
if (genealogicalDataCommunication.Death is null && personDirectory.Status == 'D' || genealogicalDataCommunication.Death is not null && personDirectory.Status == 'A')
continue;
GenealogicalDataCommunication.WriteFile(personKeyFormatted, personContainer.Person.Name, individualsLines, isDefaultName, directory, genealogicalDataCommunication, verify, first.Value);
}
}
private static (List<string?>, List<Models.PersonContainer>) GetPersonContainersGroup(string mappingDefaultName, string personBirthdayFormat, string facesFileNameExtension, char[] personCharacters, Dictionary<string, List<string>> individuals, PersonDirectory personDirectory, string[] personDisplayDirectories)
private static (List<string?>, List<Models.PersonContainer>) GetPersonContainersGroup(string mappingDefaultName, string personBirthdayFormat, string facesFileNameExtension, char[] personCharacters, ReadOnlyDictionary<string, string[]> individuals, PersonDirectory personDirectory, string[] personDisplayDirectories)
{
List<Models.PersonContainer> results = new();
string? minusOne;
@ -223,8 +187,7 @@ internal abstract class PersonContainer
continue;
if (collection.Any())
{
personContainers = GetPersonContainersCollections(mappingDefaultName, facesFileNameExtension, personCharacters, personDirectory, numberSign, personDisplayDirectory, personDisplayDirectoryName, isDefaultName, approximateYears, collection);
MaybeGenealogicalDataCommunicationWriteFile(mappingDefaultName, personBirthdayFormat, individuals, personDirectory, personDisplayDirectoryName, isDefaultName, personContainers);
personContainers = GetPersonContainersCollections(mappingDefaultName, facesFileNameExtension, personCharacters, individuals, personDirectory, numberSign, personDisplayDirectory, personDisplayDirectoryName, isDefaultName, approximateYears, collection);
results.AddRange(personContainers);
}
else
@ -237,7 +200,7 @@ internal abstract class PersonContainer
return new(changes, results);
}
private static (List<string?>, List<Models.PersonContainer>) GetPersonContainersInnerGroups(string mappingDefaultName, string personBirthdayFormat, string facesFileNameExtension, char[] personCharacters, Dictionary<string, List<string>> individuals, string groupDirectory, string groupDirectoryName)
private static (List<string?>, List<Models.PersonContainer>) GetPersonContainersInnerGroups(string mappingDefaultName, string personBirthdayFormat, string facesFileNameExtension, char[] personCharacters, ReadOnlyDictionary<string, string[]> individuals, string groupDirectory, string groupDirectoryName)
{
List<Models.PersonContainer> results = new();
string[] segments;
@ -284,7 +247,7 @@ internal abstract class PersonContainer
return new(allChanges, results);
}
private static List<Models.PersonContainer> GetPersonContainersGroups(string mappingDefaultName, string personBirthdayFormat, string facesFileNameExtension, char[] personCharacters, Dictionary<string, List<string>> individuals, string[] groupDirectories)
private static List<Models.PersonContainer> GetPersonContainersGroups(string mappingDefaultName, string personBirthdayFormat, string facesFileNameExtension, char[] personCharacters, ReadOnlyDictionary<string, string[]> individuals, string[] groupDirectories)
{
List<Models.PersonContainer> results;
const int zero = 0;
@ -308,7 +271,7 @@ internal abstract class PersonContainer
return results;
}
internal static List<Models.PersonContainer> GetPersonContainers(Properties.IStorage storage, string mappingDefaultName, string personBirthdayFormat, char[] personCharacters, string facesFileNameExtension, Dictionary<string, List<string>> individuals)
internal static List<Models.PersonContainer> GetPersonContainers(Properties.IStorage storage, string mappingDefaultName, string personBirthdayFormat, char[] personCharacters, string facesFileNameExtension, ReadOnlyDictionary<string, string[]> individuals)
{
List<Models.PersonContainer> results;
string a2PeopleSingletonDirectory = Path.Combine(storage.PeopleRootDirectory, "{}");
@ -391,4 +354,92 @@ internal abstract class PersonContainer
return results;
}
private static ReadOnlyDictionary<string, string> GetPersonKeyFormattedToPersonFullName(string personBirthdayFormat, List<Models.PersonContainer> personContainers)
{
Dictionary<string, string> results = new();
string? value;
string personFullName;
string personKeyFormatted;
foreach (Models.PersonContainer personContainer in personContainers)
{
if (personContainer.Key is null || personContainer.Birthdays is null || personContainer.Person is null || personContainer.PersonDirectory is null || !personContainer.Birthdays.Any())
continue;
personKeyFormatted = IPersonBirthday.GetFormatted(personBirthdayFormat, personContainer.Key.Value);
personFullName = PersonName.GetFullName(personContainer.Person.Name);
if (!results.TryGetValue(personKeyFormatted, out value))
results.Add(personKeyFormatted, personFullName);
else if (value != personFullName)
throw new NotSupportedException();
}
return new(results);
}
private static ReadOnlyDictionary<int, List<GenealogicalDataCommunicationRelation>> GetFamilyIndexToCollection(List<GenealogicalDataCommunicationRelation> genealogicalDataCommunicationRelations)
{
Dictionary<int, List<GenealogicalDataCommunicationRelation>> results = new();
List<GenealogicalDataCommunicationRelation>? relations;
foreach (GenealogicalDataCommunicationRelation genealogicalDataCommunicationRelation in genealogicalDataCommunicationRelations)
{
if (!results.TryGetValue(genealogicalDataCommunicationRelation.FamilyIndex, out relations))
{
results.Add(genealogicalDataCommunicationRelation.FamilyIndex, new());
if (!results.TryGetValue(genealogicalDataCommunicationRelation.FamilyIndex, out relations))
throw new NotSupportedException();
}
relations.Add(genealogicalDataCommunicationRelation);
}
return new(results);
}
internal static void MaybeWriteFiles(string mappingDefaultName, string personBirthdayFormat, List<GenealogicalDataCommunicationRelation> genealogicalDataCommunicationRelations, List<Models.PersonContainer> personContainers)
{
bool? male;
bool? first;
string fullName;
string[] matches;
string? directory;
bool isDefaultName;
bool verify = true;
const int zero = 0;
string personKeyFormatted;
string lowerHyphenFullName;
List<string> distinct = new();
Models.GenealogicalDataCommunication genealogicalDataCommunication;
GenealogicalDataCommunicationLines? genealogicalDataCommunicationLines;
ReadOnlyDictionary<string, string> personKeyFormattedToPersonFullName = GetPersonKeyFormattedToPersonFullName(personBirthdayFormat, personContainers);
ReadOnlyDictionary<int, List<GenealogicalDataCommunicationRelation>> familyIndexToCollection = GetFamilyIndexToCollection(genealogicalDataCommunicationRelations);
foreach (Models.PersonContainer personContainer in personContainers.OrderByDescending(l => l.Key))
{
if (personContainer.Key is null || personContainer.Birthdays is null || personContainer.Person is null || personContainer.PersonDirectory is null || !personContainer.Birthdays.Any())
continue;
male = personContainer.PersonDirectory.Sex == 'U' ? null : personContainer.PersonDirectory.Sex == 'M' || (personContainer.PersonDirectory.Sex == 'F' ? false : throw new Exception());
first = personContainer.PersonDirectory.First == 'U' ? null : personContainer.PersonDirectory.First == 'Y' || (personContainer.PersonDirectory.First == 'N' ? false : throw new Exception());
if (first is null)
continue;
isDefaultName = IPerson.IsDefaultName(mappingDefaultName, personContainer.DisplayDirectoryName);
personKeyFormatted = IPersonBirthday.GetFormatted(personBirthdayFormat, personContainer.Key.Value);
matches = (from l in personContainer.DisplayDirectoryAllFiles where l.Contains(personKeyFormatted) select l).ToArray();
if (!matches.Any())
continue;
directory = Path.GetDirectoryName(matches[zero]);
if (directory is null)
continue;
genealogicalDataCommunicationLines = personContainer.GenealogicalDataCommunicationRelationIndividualsLines is null ? null : GenealogicalDataCommunication.GetGenealogicalDataCommunicationLines(personContainer.Birthdays[zero], personContainer.GenealogicalDataCommunicationRelationIndividualsLines);
if (genealogicalDataCommunicationLines is null)
continue;
genealogicalDataCommunication = GenealogicalDataCommunication.GetGenealogicalDataCommunication(genealogicalDataCommunicationLines);
if (genealogicalDataCommunication.Sex != personContainer.PersonDirectory.Sex)
continue;
if (genealogicalDataCommunication.Death is null && personContainer.PersonDirectory.Status == 'D' || genealogicalDataCommunication.Death is not null && personContainer.PersonDirectory.Status == 'A')
continue;
GenealogicalDataCommunication.WriteFile(personKeyFormatted, personContainer.Person.Name, personContainer.GenealogicalDataCommunicationRelationIndividualsLines, isDefaultName, directory, genealogicalDataCommunication, verify, first.Value);
fullName = PersonName.GetFullName(personContainer.Person.Name);
lowerHyphenFullName = $"{Regex.Replace(fullName.ToLower(), "[^a-z0-9-]", "-")}";
if (distinct.Contains(lowerHyphenFullName))
continue;
MarkDown.WriteFile(personKeyFormatted, personContainer.Person.Name, genealogicalDataCommunicationRelations, personKeyFormattedToPersonFullName, familyIndexToCollection, isDefaultName, directory, genealogicalDataCommunication, first.Value, fullName, lowerHyphenFullName);
distinct.Add(lowerHyphenFullName);
}
}
}

View File

@ -62,12 +62,19 @@ internal abstract class PersonName
internal static string GetFullName(Models.PersonName personName)
{
StringBuilder result = new();
if (personName.First is not null)
if (personName.First is not null && !string.IsNullOrEmpty(personName.First.Value))
_ = result.Append(personName.First.Value);
if (personName.Middle is not null)
if (personName.Middle is not null && !string.IsNullOrEmpty(personName.Middle.Value))
_ = result.Append(' ').Append(personName.Middle.Value);
if (personName.Last is not null)
if (personName.Last is not null && !string.IsNullOrEmpty(personName.Last.Value))
_ = result.Append(' ').Append(personName.Last.Value);
if (personName.Alias is not null && !string.IsNullOrEmpty(personName.Alias.Value))
{
if (personName.Alias.Value.Contains(" Jr"))
_ = result.Append(" Jr");
else if (personName.Alias.Value.Contains(" Sr"))
_ = result.Append(" Sr");
}
return result.ToString();
}