using AideDeJeu.Pdf; using AideDeJeu.Tools; using AideDeJeu.ViewModels; using AideDeJeuLib; using Markdig; using Microsoft.EntityFrameworkCore; using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Net; using System.Runtime.Serialization.Json; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; using Xamarin.Forms; using YamlDotNet.Serialization; using YamlDotNet.Serialization.NamingConventions; namespace AideDeJeuCmd { class Program { //static async Task> TestMarkdown(string filename) //{ // using (var sr = new StreamReader(filename)) // { // var md = await sr.ReadToEndAsync(); // var document = Markdig.Parsers.MarkdownParser.Parse(md); // //DumpMarkdownDocument(document); // var spellss = document.ToSpells(); // Console.WriteLine("ok"); // var md2 = spellss.ToMarkdownString(); // if (md.CompareTo(md2) != 0) // { // Debug.WriteLine("failed"); // } // return spellss; // } //} static async Task> TestMarkdownMonsters(string filename) { using (var sr = new StreamReader(filename)) { var md = await sr.ReadToEndAsync(); var pipeline = new MarkdownPipelineBuilder() .UseYamlFrontMatter() .UsePipeTables() .Build(); //var document = Markdig.Parsers.MarkdownParser.Parse(md, pipeline); //DumpMarkdownDocument(document); var monsters = DependencyService.Get().ToItem(filename, md, null) as IEnumerable; // document.ToMonsters(); //document.Dump(); Console.WriteLine("ok"); //var md2 = monsters.ToMarkdownString(); //if (md.CompareTo(md2) != 0) //{ // Debug.WriteLine("failed"); //} return monsters; } } static async Task CreateIndexes() { string dataDir = @"..\..\..\..\..\Data\"; var result = string.Empty; var md = await LoadStringAsync(dataDir + "spells_hd.md"); var items = DependencyService.Get().ToItem("spells_hd", md, null) as IEnumerable; var classes = new string[] { "Barde", "Clerc", "Druide", "Ensorceleur", "Magicien", "Paladin", "Rôdeur", "Sorcier" }; var levels = new string[] { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", //"tour de magie", //"niveau 1", //"niveau 2", //"niveau 3", //"niveau 4", //"niveau 5", //"niveau 6", //"niveau 7", //"niveau 8", //"niveau 9" }; foreach (var classe in classes) { //Console.WriteLine(classe); result += string.Format("## {0}\n\n", classe); foreach (var level in levels) { //Console.WriteLine(level); var spells = items.Where(s => s.Level == level && s.Source.Contains(classe)).OrderBy(s => s.Name).Select(s => string.Format("* [{0}](spells_hd.md#{1})", s.Name, Helpers.IdFromName(s.Name))).ToList(); if (spells.Count > 0) { result += string.Format("### {0}\n\n", level == "0" ? "Tours de magie" : "Niveau " + level); result += spells.Aggregate((s1, s2) => s1 + "\n" + s2); result += "\n\n"; } } } Console.WriteLine(result); await SaveStringAsync(dataDir + "spells_hd_by_class_level.md", result); } static async Task> GetAllAnchorsAsync() { var anchors = new List(); var allitems = new Dictionary(); var names = Helpers.GetResourceNames(); foreach(var name in names) { //if (name.Contains("_hd.")) //{ var md = await Helpers.GetResourceStringAsync(name); var item = DependencyService.Get().ToItem(name, md, null); allitems.Add(name, item); //} } foreach(var allitem in allitems) { if (allitem.Value is Items) { var items = allitem.Value as Items; foreach (var item in await items.GetChildrenAsync()) { if (!string.IsNullOrWhiteSpace(item.Name)) { //Console.WriteLine(item.Name); anchors.Add(item.Name); } } } else if(allitem.Value != null) { if (!string.IsNullOrWhiteSpace(allitem.Value.Name)) { //Console.WriteLine(allitem.Value.Name); anchors.Add(allitem.Value.Name); } } } return anchors; } static async Task SearchAsync(string anchor) { var first = true; var names = Helpers.GetResourceNames(); foreach (var name in names) { if (name.EndsWith("_hd.md")) { var md = await Helpers.GetResourceStringAsync(name); using (var reader = new StringReader(md)) { var line = await reader.ReadLineAsync(); while (line != null) { if (line.FirstOrDefault() != '#' && !line.StartsWith("- AltName") && line.Contains(anchor) && !line.Contains($"[{anchor}") && !line.Contains($"{anchor}]") ) { if (first) { first = false; Console.WriteLine(); Console.WriteLine(anchor); Console.WriteLine(); } Console.WriteLine(line); Console.WriteLine(); } line = await reader.ReadLineAsync(); } } } } //Console.WriteLine(); } static async Task ReorderSpellsAsync() { string dataDir = @"..\..\..\..\..\Data\"; var mdVF = await LoadStringAsync(dataDir + "spells_hd.md"); var mdVO = await LoadStringAsync(dataDir + "spells_vo.md"); var md = mdVO; StringBuilder mdOut = new StringBuilder(); using (var writer = new StringWriter(mdOut) { NewLine = "\n" }) { using (var reader = new StringReader(md)) { var line = await reader.ReadLineAsync(); string levelType = null; string castingTime = null; string range = null; string components = null; string duration = null; string classes = null; string source = null; while (line != null) { if (line.StartsWith("- ") && !line.StartsWith("- AltName:")) { if (line.StartsWith("- LevelType:")) { levelType = line; } else if (line.StartsWith("- **Temps d'incantation :**") || line.StartsWith("- **Casting Time :**")) { castingTime = line; } else if (line.StartsWith("- **Portée :**") || line.StartsWith("- **Range :**")) { range = line; } else if (line.StartsWith("- **Composantes :**") || line.StartsWith("- **Components :**")) { components = line; } else if (line.StartsWith("- **Durée :**") || line.StartsWith("- **Duration :**")) { duration = line; } else if (line.StartsWith("- Classes:")) { classes = line; } else if (line.StartsWith("- Source:")) { source = line; } else { Console.WriteLine(line); Console.ReadLine(); } } else { if(levelType != null) { await writer.WriteLineAsync(levelType); if(castingTime != null) await writer.WriteLineAsync(castingTime); if(range != null) await writer.WriteLineAsync(range); if(components != null) await writer.WriteLineAsync(components); if(duration != null) await writer.WriteLineAsync(duration); if(classes != null) await writer.WriteLineAsync(classes); if(source != null) await writer.WriteLineAsync(source); levelType = null; castingTime = null; range = null; components = null; duration = null; classes = null; source = null; } await writer.WriteLineAsync(line); } line = await reader.ReadLineAsync(); } } } await SaveStringAsync(dataDir + "spells_vo_rev.md", mdOut.ToString()); Console.Write(mdOut); Console.ReadLine(); } static string inDir = @"..\..\..\..\..\Data\"; public static async Task PreloadAllItemsFromFilesAsync(StoreViewModel store) { foreach (var fileName in Directory.GetFiles(inDir, "*.md", new EnumerationOptions() { MatchType = MatchType.Simple, RecurseSubdirectories = false })) { //foreach (var resourceName in Tools.Helpers.GetResourceNames()) //{ var shortName = fileName.Substring(inDir.Length); var regex = new Regex(@"(?.*?)\.md"); var match = regex.Match(shortName); var source = match.Groups["name"].Value; if (!string.IsNullOrEmpty(source)) { if (!store._AllItems.ContainsKey(source)) { //ar md = await AideDeJeu.Tools.Helpers.GetResourceStringAsync(resourceName); var md = await File.ReadAllTextAsync(fileName); if (md != null) { var item = store.ToItem(source, md, store._AllItems); if (item != null) { if(item.NewId == "hd_aasimar_aasimar.md") { Debug.WriteLine(""); } var anchors = new Dictionary(); //MakeAnchors(source, anchors, item); item.RootId = $"{source}.md"; store._AllItems[source] = item; } } } } } } public static async Task> LoadMDsFromFilesAsync() { var dico = new Dictionary(); foreach (var fileName in Directory.GetFiles(inDir, "*.md", new EnumerationOptions() { MatchType = MatchType.Simple, RecurseSubdirectories = false })) { var md = await File.ReadAllTextAsync(fileName); if (md != null) { dico[fileName] = md; } } return dico; } static string outDir = @"..\..\..\..\..\Data\HD\"; static async Task Main(string[] args) { while (true) { Console.WriteLine("l : build library"); Console.WriteLine("o : check orphan links"); Console.WriteLine("p : test pdf"); Console.WriteLine("h : extract html"); Console.WriteLine("q : quitter"); var key = Console.ReadKey(true); switch (key.KeyChar) { case 'l': await BuildLibraryAsync(); break; case 'o': await CheckOrphanLinksAsync(); break; case 'p': await TestPdfAsync(); break; case 'h': await ExtractHtmlAsync(); break; case 'q': return; } } } static async Task ExtractHtmlAsync() { var parser = new HtmlParser(); var doc = new HtmlAgilityPack.HtmlDocument(); doc.Load(@"..\..\..\..\..\Ignore\tome_of_beasts\page30.html"); parser.OutputMarkdown(parser.Parse(doc)); } class HtmlParser { string key = ""; string value = ""; enum State { Before, Name, Type, TopKeyValues, Abilities, BottomKeyValues, Competencies, Actions, Reactions, } public class ParsedSpan { public string Text; public string Style; public string IdStyle; } public class FullLine : List { } public class FullText : List { } public FullText Parse(HtmlAgilityPack.HtmlDocument doc) { var styles = doc.DocumentNode.SelectSingleNode("/html/head/style").InnerText.Split('\n'); var txtDivs = doc.DocumentNode.SelectNodes("//div[@class='txt']"); var fullText = new FullText(); var fullLine = new FullLine(); foreach (var txtDiv in txtDivs) { var spans = txtDiv.Elements("span"); for (var i = 0; i < spans.Count(); i++) { var span = spans.ToArray()[i]; var spanId = span.GetAttributeValue("id", ""); var spanStyle = span.GetAttributeValue("style", ""); var spanIdStyle = new string(styles.SingleOrDefault(s => s.StartsWith($"#{spanId} ")).SkipWhile(c => c != '{').ToArray()); var parsedSpan = new ParsedSpan() { Text = span.InnerText, Style = spanStyle, IdStyle = spanIdStyle, }; if (span.InnerText.Contains("Forme immuable")) { Debug.WriteLine(""); } if (i == 0) { var previousParsedSpan = fullLine.LastOrDefault(); if (previousParsedSpan == null) { var previousFullLine = fullText.LastOrDefault(); if (previousFullLine != null) { previousParsedSpan = previousFullLine.LastOrDefault(); } } if (previousParsedSpan != null) { if (previousParsedSpan.Style != parsedSpan.Style || previousParsedSpan.IdStyle != parsedSpan.IdStyle) { fullText.Add(fullLine); fullLine = new FullLine(); } } } fullLine.Add(parsedSpan); } } fullText.Add(fullLine); return fullText; } public void OutputMarkdown(FullText fullText) { bool started = false; foreach (var line in fullText) { foreach(var spa in line) { if (spa.Style.Contains("font-size:11px")) { // nom (démarrage) started = true; } if (started) { if (!spa.IdStyle.Contains("font-family:sans-serif; font-weight:normal; font-style:normal;") && CloseKeyValue()) { } else if (spa.Style.Contains("font-size:11px")) { // nom Console.WriteLine($"# {spa.Text}"); } else if (spa.IdStyle.Contains("font-family:sans-serif; font-weight:normal; font-style:italic;") && spa.Text.Contains("taille")) { // type / size / alignment // todo : découper type / size / alignment Console.WriteLine($"-  {spa.Text}"); } else if (spa.Style.Contains("rgba(121,27,16,1)")) { // key / value key = spa.Text; //Console.WriteLine($"-  {span.InnerText}"); } else if (spa.IdStyle.Contains("font-family:sans-serif; font-weight:normal; font-style:normal;")) { value += (value.Length == 0 ? " " : "") + spa.Text; } else { //Console.Write($"{spanStyle} => {span.InnerText} "); Console.Write($"{spa.Text}"); } } //Console.Write(spa.Text); } Console.WriteLine(); //Console.WriteLine(); } } void StripLine() { } Dictionary KeyTags = new Dictionary() { { "Classe d’armure" , "ArmorClass" }, { "Points de vie", "HitPoints" }, { "Vitesse", "Speed" }, { "", "SavingThrows" }, { "Compétences", "Skills" }, { "Sens", "Senses" }, { "Langues", "Languages" }, { "Dangerosité", "Challenge" }, { "Résistance aux dégâts", "DamageResistance" }, { "Immunité contre les dégâts", "DamageImmunities" }, { "Immunité contre les états", "StateImmunities" }, //{ "", "" }, //{ "", "" }, //{ "", "" }, //{ "", "" }, //{ "", "" }, //{ "", "" }, }; List KeyCaracs = new List() { "FOR", "DEX", "CON", "INT", "SAG", "CHA" }; string caracs = null; bool CloseKeyValue() { if (!string.IsNullOrEmpty(key)) { if (KeyTags.ContainsKey(key.Trim())) { var tag = KeyTags[key.Trim()]; Console.WriteLine($"- **{key.Trim()}** {value.Trim()}"); } else if (KeyCaracs.Contains(key.Trim())) { if (key.Trim() == "FOR") { Console.WriteLine("|FOR|DEX|CON|INT|SAG|CHA|\n|---|---|---|---|---|---|"); } } else { Console.WriteLine(value); } key = ""; value = ""; return true; } return false; } } static async Task TestPdfAsync() { Tests.Xamarin.Forms.Mocks.MockForms.Init(); SQLitePCL.raw.SetProvider(new SQLitePCL.SQLite3Provider_e_sqlite3()); DependencyService.Register(); //Xamarin.Essentials.Platform.Init(this, bundle); //Xamarin.Essentials.ExperimentalFeatures.Enable(Xamarin.Essentials.ExperimentalFeatures.ShareFileRequest); var store = new StoreViewModel(); var item = await store.GetItemFromDataAsync("spells_hd", "aide"); using (var context = await StoreViewModel.GetLibraryContextAsync()) { var spells = await context.Spells.ToListAsync(); var pdfService = new PdfService(); //var pc = new AideDeJeu.ViewModels.PlayerCharacter.PlayerCharacterViewModel(); //var pce = new AideDeJeu.ViewModels.PlayerCharacter.PlayerCharacterEditorViewModel(); using (var stream = new FileStream("test.pdf", FileMode.Create)) { //pdfService.MarkdownToPdf("# mon titre\n\nhop", stream); pdfService.MarkdownToPdf(spells.Select(s => s.Markdown).ToList(), stream); //pdfService.MarkdownToPdf(new List() { item.Markdown }, stream); //var stream = new MemoryStream(); //pce.GeneratePdfToStream(pc, stream); } } } static async Task BuildLibraryAsync() { Tests.Xamarin.Forms.Mocks.MockForms.Init(); SQLitePCL.raw.SetProvider(new SQLitePCL.SQLite3Provider_e_sqlite3()); DependencyService.Register(); //var store = new StoreViewModel(); //await store.GetItemFromDataAsync("test", "truc"); var store = new StoreViewModel(); //await store.PreloadAllItemsAsync(); await PreloadAllItemsFromFilesAsync(store); var index = store._AllItems.Where(it => it.Value.RootId == "index.md").FirstOrDefault(); index.Value.Id = index.Value.RootId; index.Value.Name = "Bibliothèque"; using (var context = await StoreViewModel.GetLibraryContextAsync()) { try { await context.Database.EnsureDeletedAsync(); } catch { } await context.Database.EnsureCreatedAsync(); var flags = new Dictionary(); foreach (var it in store._AllItems.Values) { if (flags.ContainsKey(it.Id)) { Debug.WriteLine(it); } flags[it.Id] = true; } await context.Items.AddRangeAsync(store._AllItems.Values); await context.SaveChangesAsync(); var itemsSRD = await context.Items.Where(item => (item.Source != null && item.Source.Contains("SRD"))).ToListAsync(); var monsters = await context.Monsters.ToListAsync(); //var monstersHD = await context.MonstersHD.ToListAsync(); //var monstersVO = await context.MonstersVO.ToListAsync(); var spells = await context.Spells.ToListAsync(); var classes = await context.Classes.ToListAsync(); var races = await context.Races.ToListAsync(); var backgrounds = await context.Backgrounds.ToListAsync(); var items = await context.Items.ToListAsync(); foreach (ClassItem c in classes) { var parent = items.Where(it => it.Id == c.ParentLink).FirstOrDefault(); if (parent != null) { var pparent = items.Where(iit => iit.Id == parent.ParentLink).FirstOrDefault(); if (pparent != null && pparent is ClassItem) { var sc = c as SubClassItem; sc.ParentClassId = pparent.NewId; Console.WriteLine($"{pparent.Name} - {c.Name}"); } else { } } } foreach (var r in races) { r.HasSubRaces = false; } foreach (var r in races) { var parent = items.Where(it => it.Id == r.ParentLink).FirstOrDefault(); if (parent != null && parent is RaceItem) { var sr = r as SubRaceItem; sr.FullName = $"{parent.Name} - {r.Name}"; sr.ParentRaceId = parent.NewId; (parent as RaceItem).HasSubRaces = true; Console.WriteLine(sr.FullName); } else { r.FullName = r.Name; Console.WriteLine($"{r.Name}"); } } //var item1 = monsters.FirstOrDefault(); //var test1y = item1.Yaml; //var test1m = item1.Markdown; //var test1ym = item1.YamlMarkdown; //var item2 = Item.ParseYamlMarkdown(test1ym); //var test2y = item2.Yaml; //var test2m = item2.Markdown; //var test2ym = item2.YamlMarkdown; var matchids = new Dictionary(); foreach (var item in await context.Items.ToListAsync()) { matchids[item.Id] = item.NewId; if (!string.IsNullOrEmpty(item.RootId)) { matchids[item.RootId] = item.NewId; } // Helpers.RemoveDiacritics(item.Id).Replace(".md#", "_") + ".md"; } foreach (var item in await context.Items.ToListAsync()) { //await item.LoadFilteredItemsAsync(); if (item is SpellItems) { int iii = 1; } var yaml = item.YamlMarkdown; //var rx = new Regex(@"\(.*?\.md.*?\)"); //var matchess = rx.Matches(yaml); //foreach (Match match in matchess) //{ // yaml = yaml.Replace(match.Value, matchids[match.Value]); //} foreach (var matchid in matchids) { yaml = yaml.Replace($"({matchid.Key})", $"({matchid.Value})"); } var filename = Path.Combine(outDir, WebUtility.UrlEncode(item.NewId)); if (filename.Contains("%")) { Console.WriteLine(filename); } await SaveStringAsync(filename, yaml); var filtervm = item.GetNewFilterViewModel(); if (filtervm != null) { foreach (var filter in filtervm.Filters) { foreach (var kv in filter.KeyValues) { var key = kv.Key; var val = kv.Value; } } } } int i = 1; await context.SaveChangesAsync(); //var serializer = new SerializerBuilder() // .WithTagMapping("!MonsterHD", typeof(MonsterHD)) // .WithTagMapping("!MonsterVO", typeof(MonsterVO)) // .WithTagMapping("!Monsters", typeof(List)) // .EnsureRoundtrip() // .WithNamingConvention(new PascalCaseNamingConvention()) // .Build(); //var deserializer = new DeserializerBuilder() // .WithTagMapping("!MonsterHD", typeof(MonsterHD)) // .WithTagMapping("!MonsterVO", typeof(MonsterVO)) // .WithTagMapping("!Monsters", typeof(List)) // .WithNamingConvention(new PascalCaseNamingConvention()) // .Build(); //var yaml = serializer.Serialize(monsters); //var sr = new StringReader(yaml); //var deser = deserializer.Deserialize(sr); //var truc = Item.ParseYamlMarkdown(monsters.FirstOrDefault().YamlMarkdown); //Console.WriteLine(yaml); } return; } static async Task CheckOrphanLinksAsync() { Tests.Xamarin.Forms.Mocks.MockForms.Init(); //SQLitePCL.raw.SetProvider(new SQLitePCL.SQLite3Provider_e_sqlite3()); DependencyService.Register(); //var store = new StoreViewModel(); //await store.GetItemFromDataAsync("test", "truc"); //var store = new StoreViewModel(); //await store.PreloadAllItemsAsync(); //await PreloadAllItemsFromFilesAsync(store); var mds = await LoadMDsFromFilesAsync(); //await ReorderSpellsAsync(); //return; //string dataDir = @"..\..\..\..\..\Data\"; await CheckAllLinks(mds); //var anchors = await GetAllAnchorsAsync(); //foreach (var anchor in anchors) //{ // await SearchAsync(anchor); //} return; } async Task test() { var dataDir = ""; var mdVO = await LoadStringAsync(dataDir + "monsters_vo.md"); var mdVF = await LoadStringAsync(dataDir + "monsters_hd.md"); //var regex = new Regex("# (?.*?)\n- NameVO: \\[(?.*?)\\]\n"); //var matches = regex.Matches(mdVO); //foreach(Match match in matches) //{ // var nameVF = match.Groups["namevf"].Value; // var nameVO = match.Groups["namevo"].Value; // var replaceOld = string.Format("# {0}\n", nameVF); // var replaceNew = string.Format("# {0}\n- NameVO: [{1}](monsters_vo.md#{2})\n", nameVF, nameVO, Helpers.IdFromName(nameVO)); // mdVF = mdVF.Replace(replaceOld, replaceNew); //} var regex = new Regex("_\\[(?.*?)\\]_"); var matches = regex.Matches(mdVF); var names = new List(); foreach (Match match in matches) { var name = match.Groups["name"].Value; if (!mdVF.Contains($"[{name}]:")) { //Console.WriteLine(name); names.Add(name); } } //names.Sort(); names = names.OrderBy(n => n).Distinct().ToList(); foreach (var name in names) { Console.WriteLine($"[{name}]: spells_hd.md#{Helpers.IdFromName(Helpers.Capitalize(name))}"); } Console.WriteLine(mdVF); //await SaveStringAsync(dataDir + "monsters_hd_tmp.md", mdVF); return; } public static async Task CheckAllLinks(Dictionary mds) { // string dataDir = @"..\..\..\..\..\Data\"; var allmds = new Dictionary(); var allanchors = new Dictionary>(); var alllinks = new Dictionary>>(); var allnames = new Dictionary>(); //var resnames = Helpers.GetResourceNames(); foreach (var mdkv in mds) { var name = mdkv.Key; var md = mdkv.Value; allmds.Add(name, md); var anchors = GetMarkdownAnchors(md).ToList(); allanchors.Add(name, anchors); var links = GetMarkdownLinks(md).ToList(); alllinks.Add(name, links); var names = GetMarkdownAnchorNames(md).ToList(); allnames.Add(name, names); } foreach (var mdkv in mds) { var name = mdkv.Key; var md = mdkv.Value; var unlinkedrefs = GetMarkdownUnlinkedRefs(md).ToList(); if (unlinkedrefs.Count > 0) { Console.WriteLine($"{name} :"); Console.WriteLine(); foreach (var unlinkedref in unlinkedrefs.Distinct().OrderBy(i => i)) { //var file = ""; var files = new Dictionary(); foreach(var aalinks in alllinks) { var found = aalinks.Value.FirstOrDefault(t => t.Item2 == Helpers.IdFromName(unlinkedref)); if(found != null) { files[found.Item1] = $"{found.Item1}.md"; //file = $"{found.Item1}.md"; //Console.WriteLine($"[{unlinkedref}]: {file}#{Helpers.IdFromName(unlinkedref)}"); } } foreach(var aanchors in allanchors) { if(aanchors.Value.Contains(Helpers.IdFromName(unlinkedref))) { files[aanchors.Key] = $"{aanchors.Key}.md"; //file = $"{aanchors.Key}.md"; //Console.WriteLine($"[{unlinkedref}]: {file}#{Helpers.IdFromName(unlinkedref)}"); //break; } } if(files.Count == 0) { files[""] = ""; } foreach (var file in files) { Console.WriteLine($"[{unlinkedref}]: {file.Value}#{Helpers.IdFromName(unlinkedref)}"); } } Console.WriteLine(); Console.WriteLine(); } } foreach (var links in alllinks) { foreach (var link in links.Value) { var file = link.Item1; var anchor = link.Item2; if (allanchors.ContainsKey(file)) { if (!allanchors[file].Contains(anchor)) { Console.WriteLine($"{links.Key} => {file} {anchor}"); } } } } //foreach (var names in allnames) //{ // foreach (var name in names.Value) // { // foreach(var mdkv in allmds) // { // FindName(mdkv.Value, name); // } // } //} } public static void FindName(string md, string name) { var index = md.IndexOf(name); while (index >= 0) { if ((md.Substring(index - 1, 1) != "[" || md.Substring(index + name.Length, 1) != "]") && md.Substring(index - 1, 1) != "#" && md.Substring(index - 2, 2) != "# ") { Console.WriteLine(name); Console.WriteLine(md.Substring(index - 10, name.Length + 20).Replace("\n", "\\n")); Console.WriteLine(); } index = md.IndexOf(name, index + 1); } } public static IEnumerable> GetMarkdownLinks(string md) { var regex = new Regex("[ \\(](?[a-z_]*?)\\.md#(?.*?)[\\n\\)]"); var matches = regex.Matches(md); foreach (Match match in matches) { var file = match.Groups["file"].Value; var anchor = match.Groups["anchor"].Value; yield return new Tuple(file, anchor); } } public static IEnumerable GetMarkdownUnlinkedRefs(string md) { var regex = new Regex("\\[(?.+?)\\]", RegexOptions.IgnoreCase); var matches = regex.Matches(md); md = md.ToLower(); foreach (Match match in matches) { var rref = match.Groups["ref"].Value; var lref = rref.ToLower(); if (!md.Contains($"[{lref}]:") && !md.Contains($"[{lref}](") && !lref.Contains("][")) { yield return rref; } } } public static IEnumerable GetMarkdownAnchors(string md) { foreach (var name in GetMarkdownAnchorNames(md)) { yield return Helpers.IdFromName(name); } } public static IEnumerable GetMarkdownAnchorNames(string md) { var regex = new Regex($"\\n##* (?.*?)\\s*?\\n"); var matches = regex.Matches(md); foreach (Match match in matches) { var name = match.Groups["name"].Value; yield return name; } } public static string Capitalize(string text) { return string.Concat(text.Take(1)).ToUpper() + string.Concat(text.Skip(1)).ToString().ToLower(); } public static string RemoveDiacritics(string text) { if (string.IsNullOrWhiteSpace(text)) return text; text = text.Normalize(NormalizationForm.FormD); var chars = text.Where(c => CharUnicodeInfo.GetUnicodeCategory(c) != UnicodeCategory.NonSpacingMark).ToArray(); return new string(chars).Normalize(NormalizationForm.FormC); } static string IdFromName(string name) { return RemoveDiacritics(name.ToLower().Replace(" ", "-")); } private static T LoadJSon(string filename) where T : class { var serializer = new DataContractJsonSerializer(typeof(T)); using (var stream = new FileStream(filename, FileMode.Open)) { return serializer.ReadObject(stream) as T; } } private static void SaveJSon(string filename, T objT) where T : class { var settings = new DataContractJsonSerializerSettings { UseSimpleDictionaryFormat = true }; var serializer = new DataContractJsonSerializer(typeof(T)); using (var stream = new FileStream(filename, FileMode.Create)) { using (var writer = JsonReaderWriterFactory.CreateJsonWriter(stream, Encoding.UTF8, true, true, " ")) { serializer.WriteObject(writer, objT); } } } private static async Task SaveStringAsync(string filename, string text) { using (var sw = new StreamWriter(path: filename, append: false, encoding: Encoding.UTF8)) { await sw.WriteAsync(text); } } private static async Task LoadStringAsync(string filename) { using (var sr = new StreamReader(filename, Encoding.UTF8)) { return await sr.ReadToEndAsync(); } } private static async Task> LoadList(string filename) { using (var stream = new StreamReader(filename)) { var lines = new List(); var line = await stream.ReadLineAsync(); while (line != null) { if (!string.IsNullOrEmpty(line)) { lines.Add(line); } line = await stream.ReadLineAsync(); } return lines; } } } }