using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using MLEM.Ui.Elements;
namespace MLEM.Ui.Parsers {
///
/// A class for parsing Markdown strings into a set of MLEM.Ui elements with styling for each individual .
/// To parse, use or . To style the parsed output, use before parsing.
///
///
/// Note that this parser is rather rudimentary and doesn't deal well with very complex Markdown documents. Missing features are as follows:
///
/// - Lines that end without a double space are still converted to distinct lines rather than being merged with the next line
/// - Better list handling, including nested lists
/// - Horizontal rules
/// - Tables
///
///
public class UiMarkdownParser : UiParser {
///
/// Creates a new UI markdown parser and optionally initializes some default style settings.
///
/// Whether default style settings should be applied.
public UiMarkdownParser(bool applyDefaultStyling = true) : base(applyDefaultStyling) {}
///
protected override IEnumerable<(ElementType, Element)> ParseUnstyled(string raw) {
var inCodeBlock = false;
foreach (var line in raw.Split('\n')) {
// code blocks
if (line.Trim().StartsWith("```")) {
inCodeBlock = !inCodeBlock;
continue;
}
// code block content
if (inCodeBlock) {
yield return (ElementType.CodeBlock, new Paragraph(Anchor.AutoLeft, 1, $"{line}"));
continue;
}
// quotes
if (line.StartsWith(">")) {
yield return (ElementType.Blockquote, new Paragraph(Anchor.AutoLeft, 1, this.ParseParagraph(line.Substring(1).Trim())));
continue;
}
// vertical space (empty lines)
if (line.Trim().Length <= 0) {
yield return (ElementType.VerticalSpace, new VerticalSpace(0));
continue;
}
// images
var imageMatch = Regex.Match(line, @"!\[\]\(([^)]+)\)");
if (imageMatch.Success) {
yield return (ElementType.Image, this.ParseImage(imageMatch.Groups[1].Value));
continue;
}
// headers
var parsedHeader = false;
for (var h = 6; h >= 1; h--) {
if (line.StartsWith(new string('#', h))) {
var type = UiParser.ElementTypes[Array.IndexOf(UiParser.ElementTypes, ElementType.Header1) + h - 1];
yield return (type, new Paragraph(Anchor.AutoLeft, 1, this.ParseParagraph(line.Substring(h).Trim())));
parsedHeader = true;
break;
}
}
if (parsedHeader)
continue;
// parse everything else as a paragraph (with formatting)
yield return (ElementType.Paragraph, new Paragraph(Anchor.AutoLeft, 1, this.ParseParagraph(line)));
}
}
private string ParseParagraph(string par) {
// replace links
par = Regex.Replace(par, @"<([^>]+)>", "$1");
par = Regex.Replace(par, @"\[([^\]]+)\]\(([^)]+)\)", "$1");
// replace formatting
par = Regex.Replace(par, @"\*\*([^\*]+)\*\*", "$1");
par = Regex.Replace(par, @"__([^_]+)__", "$1");
par = Regex.Replace(par, @"\*([^\*]+)\*", "$1");
par = Regex.Replace(par, @"_([^_]+)_", "$1");
par = Regex.Replace(par, @"~~([^~]+)~~", "$1");
// replace inline code with custom code font
par = Regex.Replace(par, @"`([^`]+)`", $"$1");
return par;
}
}
}