private TaggedItem ParseLine(string[] prefixTags, string line, TaggedItem parent) { string bullet = Regex.Match(line, "(^[\\*\\-\\+] )|(^\\d+\\. )").Value; string content = line.Substring(bullet.Length); (string[] Tags, string Remaining)tagContent = MDLineExtractTags(content); var newItem = new TaggedItem() { Content = tagContent.Remaining, Tags = prefixTags.Union(tagContent.Tags).Union(parent?.Tags ?? new string[] { }).Distinct().ToArray(), Children = null }; if (parent != null) { if (parent.Children == null) { parent.Children = new List <TaggedItem>(); } parent.Children.Add(newItem); newItem.Parent = parent; } return(newItem); }
/// <summary> /// Parse a single markdown file as KMD - notice all markdowns are assumed KMD /// </summary> private KMDResource ParseMarkdown(string path, string[] fileTags) { // State preparation string filename = Path.GetFileNameWithoutExtension(path); PureFileNameExtractTags(filename, out filename); Stack <string[]> prefixTags = new Stack <string[]>(); void ReinitializePrefixTags() { prefixTags.Clear(); if (!filename.StartsWith('@')) { prefixTags.Push(fileTags.Append(filename).ToArray()); } else { prefixTags.Push(fileTags); } } ReinitializePrefixTags(); string nextHeaderLevel = "#"; List <TaggedItem> items = new List <TaggedItem>(); // Indetation status Stack <TaggedItem> parents = new Stack <TaggedItem>(); TaggedItem current = null; string currentIndentation = ""; // LFR resources bool insideFileEndLFRSection = false; string lfrName = null; Dictionary <string, Fragment> fragments = new Dictionary <string, Fragment>(); bool insideCodeSection = false; string codeSectionMark = null; // Enumerate lines foreach (var line in File.ReadAllLines(path)) { if (!insideFileEndLFRSection) { #region Regular Sections // Collect list lines // Todo: This is not considering cases where *-+ and numbers are mixed, pending if (Regex.IsMatch(line, "(^[\\*\\-\\+] .*)|(^\\d+\\. .*)")) { // Clear indentation parents.Clear(); ReinitializePrefixTags(); currentIndentation = ""; // Parse line current = ParseLine(prefixTags.First(), line, null); items.Add(current); } // Handle indentation else if (Regex.IsMatch(line, "^\\s+(([\\*\\-\\+] .*)|(\\d+\\. .*))")) { if (parents.Count == 0 && current == null) { throw new ApplicationException($"Dangling sibling at line: `{line}`."); } string white = Regex.Match(line, "^\\s*").Value; string actualLine = line.Substring(white.Length); // Normalize tab white white = white.Replace("\t", " "); // Progress indentation if (white.Length > currentIndentation.Length) { // Update indentation currentIndentation = white; parents.Push(current); prefixTags.Push(current.Tags); // Parse line current = ParseLine(prefixTags.First(), actualLine, parents.First()); } else if (white.Length == currentIndentation.Length) { current = ParseLine(prefixTags.First(), actualLine, parents.First()); } // Revert indentation else if (white.Length < currentIndentation.Length) { // Update indentation currentIndentation = white; parents.Pop(); prefixTags.Pop(); if (parents.Count == 0) { throw new ApplicationException($"Inconsistent indentation at line: `{line}`."); } // Parse line current = ParseLine(prefixTags.First(), actualLine, parents.First()); } items.Add(current); } // Special handle header else if (line.StartsWith("# General Indexed Note") || line.StartsWith("# General Indexed Notes")) { continue; } else if (line.StartsWith("# Local Fragment Reference") || line.StartsWith("# Local Fragment References") || line.StartsWith("# LFR")) { insideFileEndLFRSection = true; } // Collect headers else if (line.StartsWith(nextHeaderLevel)) { prefixTags.Push(prefixTags.First().Append(line.Substring(nextHeaderLevel.Length + 1)).ToArray()); nextHeaderLevel += '#'; } #endregion } else { #region LFR if (!insideCodeSection && line.StartsWith("## ")) { lfrName = line.Substring("## ".Length); // A resource that's not referenced if (!fragments.ContainsKey(lfrName)) { fragments[lfrName] = new Fragment(lfrName, string.Empty, null); } continue; } else if (line.StartsWith("```")) { if (insideCodeSection) { insideCodeSection = false; codeSectionMark = null; } else { insideCodeSection = true; codeSectionMark = Regex.Match(line, "^(`+).*").Groups[1].Value; } } if (lfrName != null) { fragments[lfrName].Content += line + '\n'; } #endregion } } // Connect fragments with items ConnectFragments(items, fragments); return(new KMDResource(items, fragments)); }
private static string ReproduceContent(string originalReference, List <TaggedItem> filteredResult) { // Get largest comment set to reduce repeating items string[] commonTags = filteredResult.Count > 1 ? filteredResult.SelectMany(r => r.Tags).Distinct().ToArray() : new string[] { }; filteredResult.ForEach(r => commonTags = commonTags.Intersect(r.Tags).ToArray()); // Generate output StringBuilder builder = new StringBuilder($"# Indexed Notes ({string.Join(", ", commonTags)})\n\n"); // Iterate and show items in hierarchical fashion Stack <TaggedItem> last = new Stack <TaggedItem>(); for (int i = 0; i < filteredResult.Count; i++) { TaggedItem item = filteredResult[i]; // Root if (item.Parent == null || !filteredResult.Contains(item.Parent)) { last.Clear(); string tagString = string.Join(", ", item.Tags); tagString = string.IsNullOrEmpty(tagString) ? string.Empty : $"({tagString}) "; string formatted = $"* {tagString}{item.Content} " + $"<!-- {item.Children?.Count ?? 0} children; Is {(item.Parent == null ? "not " : "")}a child"; builder.Append(formatted); } // Children else { if (last.Count == 0) { last.Push(item.Parent); } else if (item.Parent != last.First()) { if (last.Contains(item.Parent)) { last.Pop(); } else { last.Push(item.Parent); } } string tagString = string.Join(", ", item.Tags.Except(commonTags).Except(item.Parent.Tags)); tagString = string.IsNullOrEmpty(tagString) ? string.Empty : $"**({tagString})** "; string formatted = $"{new string('\t', last.Count)}* {tagString}{item.Content} " + $"*{item.Children?.Count ?? 0} children; Is {(item.Parent == null ? "not " : "")}a child"; builder.Append(formatted); } // Line number int number = originalReference.Substring(0, originalReference.IndexOf(item.Content)).Where(c => c == '\n').Count() + 1; builder.Append($" - Line Number: {number} -->"); builder.Append("\n"); } builder.AppendLine("\n"); // Fragments var fragments = filteredResult.SelectMany(f => f.References ?? new List <Fragment>()).Distinct(); builder.AppendLine("# LFR\n"); foreach (var f in fragments) { builder.AppendLine($"## {f.Name}"); builder.AppendLine(); builder.AppendLine(f.Content.Trim()); builder.AppendLine(); } return(builder.ToString()); }