diff --git a/Markdown.Tests/Bold_Should.cs b/Markdown.Tests/Bold_Should.cs new file mode 100644 index 000000000..18314f588 --- /dev/null +++ b/Markdown.Tests/Bold_Should.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using FluentAssertions; +using Markdown.MDParser; +using NUnit.Framework; + +namespace Markdown.Tests +{ + [TestFixture] + public class Bold_Should + { + private readonly Bold _bold = new(); + + [TestCaseSource(nameof(TestCases))] + public void ReturnsRightToken((string text, Token expectedToken) td) + { + var returnedToken = _bold.TryFindToken(td.text, 0); + + returnedToken.Should().BeEquivalentTo(td.expectedToken); + } + + public static IEnumerable<(string, Token)> TestCases() + { + yield return ("__", new Token("__", TokenProperty.Normal)); + yield return ("__a", new Token("__a", TokenProperty.Normal)); + yield return ("__aa", new Token("__aa", TokenProperty.Normal)); + yield return ("____", new Token("____", TokenProperty.Normal)); + yield return (@"__a\__b\__c", new Token("__a__b__c", TokenProperty.Normal)); + yield return ("__ a__", new Token("__", TokenProperty.Normal)); + yield return ("__a a__", new Token("a a", TokenProperty.Bold)); + yield return ("__a __a", new Token("__a __a", TokenProperty.Normal)); + yield return (@"__a\__a\__ __", new Token("__a__a__ __", TokenProperty.Normal)); + } + } +} diff --git a/Markdown.Tests/Hidden_Should.cs b/Markdown.Tests/Hidden_Should.cs new file mode 100644 index 000000000..93b56fe06 --- /dev/null +++ b/Markdown.Tests/Hidden_Should.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using NUnit.Framework; +using FluentAssertions; +using Markdown.MDParser; + +namespace Markdown.Tests +{ + [TestFixture] + public class Hidden_Should + { + [TestCaseSource(nameof(TestCases))] + public void ReturnsRightToken((string text, Token expectedToken) td) + { + var returnedToken = new Hidden().TryFindToken(td.text, 0); + + returnedToken.Should().BeEquivalentTo(td.expectedToken); + } + + public static IEnumerable<(string, Token)> TestCases() + { + yield return (@"\", new Token( @"\", TokenProperty.Normal)); + yield return (@"\\", new Token( @"\", TokenProperty.Normal)); + yield return (@"\_", new Token( @"_", TokenProperty.Normal)); + yield return (@"\abra", new Token("", TokenProperty.Normal)); + } + } +} diff --git a/Markdown.Tests/Italic_Should.cs b/Markdown.Tests/Italic_Should.cs new file mode 100644 index 000000000..630944fd7 --- /dev/null +++ b/Markdown.Tests/Italic_Should.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using FluentAssertions; +using Markdown.MDParser; +using NUnit.Framework; + +namespace Markdown.Tests +{ + [TestFixture] + public class Italic_Should + { + private readonly Italic _italic = new(); + + [TestCaseSource(nameof(TestCases))] + public void ReturnsRightToken((string text, Token expectedToken) td) + { + var returnedToken = _italic.TryFindToken(td.text, 0); + + returnedToken.Should().BeEquivalentTo(td.expectedToken); + } + + public static IEnumerable<(string, Token)> TestCases() + { + yield return ("_", new Token("_", TokenProperty.Normal)); + yield return ("__a__", new Token("a", TokenProperty.Bold)); + yield return ("_a a_", new Token("_a", TokenProperty.Normal)); + yield return ("_a1a_", new Token("_a1a", TokenProperty.Normal)); + yield return ("_abc", new Token("_abc", TokenProperty.Normal)); + yield return ("_a_", new Token("a", TokenProperty.Italic)); + yield return ("_a\\_", new Token("_a", TokenProperty.Normal)); + } + } +} diff --git a/Markdown.Tests/Link_Should.cs b/Markdown.Tests/Link_Should.cs new file mode 100644 index 000000000..5a382b616 --- /dev/null +++ b/Markdown.Tests/Link_Should.cs @@ -0,0 +1,37 @@ +using Markdown.MDParser; +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using FluentAssertions; + +namespace Markdown.Tests +{ + [TestFixture] + public class Link_Should + { + private readonly Link _link = new(); + + [TestCaseSource(nameof(TestCases))] + public void ReturnsRightToken((string text, Token expectedToken) td) + { + var returnedToken = _link.TryFindToken(td.text, 0); + + returnedToken.Should().BeEquivalentTo(td.expectedToken); + } + + public static IEnumerable<(string, Token)> TestCases() + { + yield return ("[name](address)]", new Token("name"+"+++"+"address", TokenProperty.Link)); + yield return (@"[name\](address)]", new Token("[", TokenProperty.Normal)); + yield return (@"[name](address\)]", new Token("[", TokenProperty.Normal)); + yield return (@"[name", new Token("[", TokenProperty.Normal)); + yield return (@"[name]", new Token("[", TokenProperty.Normal)); + yield return (@"[name]add", new Token("[", TokenProperty.Normal)); + yield return (@"[name](", new Token("[", TokenProperty.Normal)); + yield return (@"[name](address", new Token("[", TokenProperty.Normal)); + } + } +} diff --git a/Markdown.Tests/MD.Render_Should.cs b/Markdown.Tests/MD.Render_Should.cs new file mode 100644 index 000000000..b07205956 --- /dev/null +++ b/Markdown.Tests/MD.Render_Should.cs @@ -0,0 +1,93 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using NUnit; +using FluentAssertions; +using Markdown.MDParser; +using NUnit.Framework; +using NUnit.Framework.Internal; +using Markdown.HTMLConverter; + +namespace Markdown.Tests +{ + [TestFixture] + public class Render_Should + { + private readonly Parser _parser = new(); + private readonly Converter _converter = new(); + + [Test] + public void Render_ShouldReturnItalic() + { + Md.Render( + "_italic_", _parser, _converter) + .Should().Be("italic\n"); + } + + [Test] + public void Render_ShouldReturnBold() + { + Md.Render( + "__bold__", _parser, _converter) + .Should().Be("bold\n"); + } + + [Test] + public void Render_ShouldSkipEscapedChars() + { + Md.Render(@"\_notItalic\_", _parser, _converter) + .Should().NotBe("notItalic\n"); + Md.Render(@"\_notItalic\_", _parser, _converter) + .Should().Be("_notItalic_\n"); + } + + [Test] + public void Render_ShouldNotSkipSlashWithoutMarker() + { + Md.Render(@"a\a", _parser, _converter) + .Should().Be("a\\a\n"); + } + + [Test] + public void Render_SlashHaveToBeEscapedWithAnotherSlash() + { + Md.Render(@"\\", _parser, _converter) + .Should().Be("\\\n"); + } + + [Test] + public void Render_ItalicInBold() + { + Md.Render("Внутри __двойного выделения _одинарное_ тоже__ работает", _parser, _converter) + .Should().Be("Внутри двойного выделения одинарное тоже работает\n"); + } + + [Test] + public void Render_ConvertLink() + { + Md.Render("[name](address)", _parser, _converter) + .Should().Be("name\n"); + } + + [TestCaseSource(nameof(TestCases))] + public void RenderWorksCorrectly((string text, string expectedText) td) + { + var returnedText = Md.Render(td.text, _parser, _converter); + + returnedText.Should().BeEquivalentTo(td.expectedText); + } + + public static IEnumerable<(string, string)> TestCases() + { + yield return ("# Заголовок __с _разными_ символами__", + "

Заголовок с разными символами

\n"); + yield return ("____", "____\n"); + yield return ("текста c цифрами_12_3", "текста c цифрами_12_3\n"); + yield return ("в _нач_але, и в сер_еди_не, и в кон_це._", "в начале, и в середине, и в конце.\n"); + yield return ("в ра_зных сл_овах", "в ра_зных сл_овах\n"); + } + + } +} diff --git a/Markdown.Tests/Markdown.Tests.csproj b/Markdown.Tests/Markdown.Tests.csproj new file mode 100644 index 000000000..e6bafb1f8 --- /dev/null +++ b/Markdown.Tests/Markdown.Tests.csproj @@ -0,0 +1,21 @@ + + + + Library + net8.0 + enable + enable + + + + + + + + + + + + + + diff --git a/Markdown.Tests/Normal_Should.cs b/Markdown.Tests/Normal_Should.cs new file mode 100644 index 000000000..442302ac2 --- /dev/null +++ b/Markdown.Tests/Normal_Should.cs @@ -0,0 +1,33 @@ +using FluentAssertions; +using Markdown.MDParser; +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Markdown.Tests +{ + [TestFixture] + public class Normal_Should + { + private static IEnumerable NormalTextStrings() + { + return Markers.Chars.Select(marker => "text" + marker); + } + + [TestCaseSource(nameof(TestCases))] + public void ReturnsRightToken((string text, Token expectedToken) td) + { + var returnedToken = new Normal().TryFindToken(td.text, 0); + + returnedToken.Should().BeEquivalentTo(td.expectedToken); + } + + public static IEnumerable<(string, Token)> TestCases() + { + return NormalTextStrings().Select(str => (str, new Token(str[..^1], TokenProperty.Normal))); + } + } +} diff --git a/Markdown/Complex.cs b/Markdown/Complex.cs new file mode 100644 index 000000000..e8c27286d --- /dev/null +++ b/Markdown/Complex.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Markdown +{ + public class Complex + { + public static readonly HashSet Properties = [ + TokenProperty.Head, + TokenProperty.Paragraph, + TokenProperty.Bold + ]; + } +} diff --git a/Markdown/DOM.cs b/Markdown/DOM.cs new file mode 100644 index 000000000..bba143a64 --- /dev/null +++ b/Markdown/DOM.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Markdown +{ + public class DOM(List tokens) + { + public readonly List Tokens = tokens; + } +} diff --git a/Markdown/HTMLConverter/Converter.cs b/Markdown/HTMLConverter/Converter.cs new file mode 100644 index 000000000..d2859406d --- /dev/null +++ b/Markdown/HTMLConverter/Converter.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Markdown.HTMLConverter +{ + public class Converter : IConverter + { + public static void GetContent(Token token, StringBuilder sb) + { + if (token.Children.Count == 0) + { + if (token.Property != TokenProperty.Link) + { + sb.Append(Tags.Open[token.Property]); + sb.Append(token.Value); + sb.Append(Tags.Close[token.Property]); + } + else ConvertLink(token.Value, sb); + } + else + { + sb.Append(Tags.Open[token.Property]); + foreach (var nextToken in token.Children) + GetContent(nextToken, sb); + sb.Append(Tags.Close[token.Property]); + } + return; + } + + public static void ConvertLink(string text, StringBuilder sb) + { + var i = 3; + string name; + string address; + while (true) + { + if (text[i] == '+' && text[i - 1] == '+' && text[i - 2] == '+') + { + name = text[..(i - 2)]; + address = text[(i + 1)..]; + break; + } + i++; + } + sb.Append(Tags.Open[TokenProperty.Link]); + sb.Append(address); + sb.Append('>'); + sb.Append(name); + sb.Append(Tags.Close[TokenProperty.Link]); + } + + public string Convert(DOM dom) + { + var result = new StringBuilder(); + foreach (var token in dom.Tokens) + GetContent(token, result); + return result.ToString(); + } + } +} diff --git a/Markdown/HTMLConverter/Tags.cs b/Markdown/HTMLConverter/Tags.cs new file mode 100644 index 000000000..41e875a8e --- /dev/null +++ b/Markdown/HTMLConverter/Tags.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Markdown.HTMLConverter +{ + class Tags + { + public static Dictionary Open = new() { + { TokenProperty.Bold, ""}, + { TokenProperty.Head, "

"}, + { TokenProperty.Italic, ""}, + { TokenProperty.Paragraph, string.Empty}, + { TokenProperty.Normal, string.Empty}, + { TokenProperty.Link , ""}, + { TokenProperty.Head, "

\n"}, + { TokenProperty.Italic, ""}, + { TokenProperty.Paragraph, "\n"}, + { TokenProperty.Normal, string.Empty}, + {TokenProperty.Link , ""} + }; + } +} + diff --git a/Markdown/IConverter.cs b/Markdown/IConverter.cs new file mode 100644 index 000000000..d094b0757 --- /dev/null +++ b/Markdown/IConverter.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Markdown +{ + public interface IConverter + { + public string Convert(DOM dom); + } +} diff --git a/Markdown/IParser.cs b/Markdown/IParser.cs new file mode 100644 index 000000000..37315c05c --- /dev/null +++ b/Markdown/IParser.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Markdown +{ + public interface IParser + { + public DOM BuildDom(string text); + } +} diff --git a/Markdown/ITokenizer.cs b/Markdown/ITokenizer.cs new file mode 100644 index 000000000..94951f333 --- /dev/null +++ b/Markdown/ITokenizer.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Markdown +{ + public interface ITokenizer + { + Token? TryFindToken(string text, int idx); + } +} diff --git a/Markdown/MDParser/Bold.cs b/Markdown/MDParser/Bold.cs new file mode 100644 index 000000000..c98c4b677 --- /dev/null +++ b/Markdown/MDParser/Bold.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.VisualBasic; + +namespace Markdown.MDParser +{ + public class Bold : ITokenizer + { + public Token? TryFindToken(string text, int idx) + { + var len = text.Length; + if (idx + 2 == len) + return new Token("__", TokenProperty.Normal); + if (idx + 3 == len) + return new Token(text[^3..], TokenProperty.Normal); + if (idx + 4 == len) + return new Token(text[^4..], TokenProperty.Normal); + if (text[idx + 2] == ' ') + return new Token("__", TokenProperty.Normal); + var i = idx + 4; + List hiddens = []; + while (i < len) + { + if (text[i] != '_') + { + i++; + continue; + } + + if (text[i - 1] != '_') + { + i++; + continue; + } + + if (text[i - 2] == ' ') + { + i++; + continue; + } + + if (text[i - 2] == '\\') + { + hiddens.Add(i - 2); + i++; + continue; + } + return new Token(text[(idx + 2)..(i - 1)], TokenProperty.Bold); + } + var hiddenCount = hiddens.Count; + if (hiddenCount != 0 && text[hiddens[hiddenCount - 1]] == '\\') + return new Token(GetStringWithHiddens(text, hiddens), TokenProperty.Normal); + return new Token(text[idx..i], TokenProperty.Normal); + } + + private static string GetStringWithHiddens(string text, List hiddens) + { + var result = new StringBuilder(); + var leftBorder = 0; + foreach (var hidden in hiddens) + { + result.Append(text[leftBorder..hidden]); + leftBorder = hidden + 1; + } + result.Append(text[(hiddens.Last() + 1)..]); + return result.ToString(); + } + } +} diff --git a/Markdown/MDParser/Hidden.cs b/Markdown/MDParser/Hidden.cs new file mode 100644 index 000000000..458d0a8ba --- /dev/null +++ b/Markdown/MDParser/Hidden.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Markdown.MDParser +{ + public class Hidden : ITokenizer + { + public Token TryFindToken(string text, int idx) + { + if (idx + 1 == text.Length) + return new Token("\\", TokenProperty.Normal); + + return Markers.Chars.Contains(text[idx + 1]) + ? new Token(text[idx + 1].ToString(), TokenProperty.Normal) + : new Token(string.Empty, TokenProperty.Normal); + } + } + +} diff --git a/Markdown/MDParser/Italic.cs b/Markdown/MDParser/Italic.cs new file mode 100644 index 000000000..44017fae2 --- /dev/null +++ b/Markdown/MDParser/Italic.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Markdown.MDParser +{ + public class Italic : ITokenizer + { + public Token? TryFindToken(string text, int idx) + { + if (idx + 1 == text.Length) + return new Token("_", TokenProperty.Normal); + + if (text[idx + 1] == '_') + return new Bold().TryFindToken(text, idx); + + var i = idx + 1; + var len = text.Length; + var hasDigit = false; + while (i < len) + { + if (int.TryParse(text[i].ToString(), out _)) + { + hasDigit = true; + i++; + continue; + } + + if (text[i] == ' ') + return new Token(text[idx..i], TokenProperty.Normal); + + if (text[i] != '_') + { + i++; + continue; + } + + if (text[i - 1] == '\\') + return new Token(text[idx..(i - 1)], TokenProperty.Normal); + + return hasDigit + ? new Token(text[idx..i], TokenProperty.Normal) + : new Token(text[(idx + 1)..i], TokenProperty.Italic); + } + return new Token(text[idx..], TokenProperty.Normal); + } + } +} diff --git a/Markdown/MDParser/Link.cs b/Markdown/MDParser/Link.cs new file mode 100644 index 000000000..40a315f9b --- /dev/null +++ b/Markdown/MDParser/Link.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Markdown.MDParser +{ + public class Link : ITokenizer + { + public Token TryFindToken(string text, int idx) + { + if (idx + 1 == text.Length) + return new Token("[", TokenProperty.Normal); + var i = idx + 1; + var len = text.Length; + var name = string.Empty; + var address = string.Empty; + + while (i < len ) + { + if (text[i] == ']' && text[i - 1] != '\\') + { + name = text[(idx + 1)..i]; + break; + } + i++; + } + + if (name == string.Empty || i + 1 == len || text[i + 1] != '(' || i+2 == len) + return new Token("[", TokenProperty.Normal); + i+=2; + var mark = i; + while (i < len) + { + if (text[i] == ')' && text[i - 1] != '\\') + { + address = text[(mark)..i]; + break; + } + i++; + } + return address == string.Empty + ? new Token("[", TokenProperty.Normal) + : new Token(name + "+++" + address, TokenProperty.Link); + } + } +} diff --git a/Markdown/MDParser/Markers.cs b/Markdown/MDParser/Markers.cs new file mode 100644 index 000000000..bdc1b61cb --- /dev/null +++ b/Markdown/MDParser/Markers.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Markdown.MDParser +{ + public class Markers + { + public static readonly HashSet Chars = ['_', '\\']; + } +} + diff --git a/Markdown/MDParser/Normal.cs b/Markdown/MDParser/Normal.cs new file mode 100644 index 000000000..cb2e01c15 --- /dev/null +++ b/Markdown/MDParser/Normal.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Markdown.MDParser +{ + public class Normal : ITokenizer + { + public Token TryFindToken(string text, int idx) + { + var i = idx; + var len = text.Length; + while (i < len) + { + if (Markers.Chars.Contains(text[i])) + return new Token(text[idx..i], TokenProperty.Normal); + else + { + i++; + continue; + } + } + return new Token(text[idx..], TokenProperty.Normal); + } + } +} diff --git a/Markdown/MDParser/Parser.cs b/Markdown/MDParser/Parser.cs new file mode 100644 index 000000000..843080d98 --- /dev/null +++ b/Markdown/MDParser/Parser.cs @@ -0,0 +1,97 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Markdown.MDParser +{ + public class Parser : IParser + { + public DOM BuildDom(string text) + { + var tokens = text.Split("\n", StringSplitOptions.None) + .Select(line => new Token(line, TokenProperty.Paragraph)) + .Select(t => t = t.Value.StartsWith('#') ? new Token(t.Value[1..], TokenProperty.Head) : t) + .ToList() + ; + + foreach (var token in tokens) + { + var queue = new Queue(); + queue.Enqueue(token); + while (queue.Count > 0) + { + var current = queue.Dequeue(); + FindChildren(current); + foreach (var child in current.Children) + queue.Enqueue(child); + } + } + return new DOM(tokens); + } + + private static void FindChildren(Token token) + { + if (!Complex.Properties.Contains(token.Property)) + return; + var hidden = new Hidden(); + var italic = new Italic(); + var norm = new Normal(); + var link = new Link(); + + var i = 0; + var text = token.Value; + var len = token.Value.Length; + while (i < len) + { + var start = text[i]; + switch (start) + { + case '_': + { + var child = italic.TryFindToken(text, i); + token.Children.Add(child); + + i += child.Property switch + { + TokenProperty.Italic => child.Value.Length + 2, + TokenProperty.Bold => child.Value.Length + 4, + _ => child.Value.Length, + }; + break; + } + + case '\\': + { + var child = hidden.TryFindToken(text, i); + i += child.Value.Length + 1; + if (child.Value == string.Empty) + child.Value = "\\"; + token.Children.Add(child); + break; + } + case '[': + { + var child = link.TryFindToken(text, i); + token.Children.Add(child); + i += child.Property switch + { + TokenProperty.Link => child.Value.Length + 1, + _ => 1 + }; + break; + } + + default: + { + var child = norm.TryFindToken(text, i); + token.Children.Add(child); + i += child.Value.Length; + break; + } + } + } + } + } +} diff --git a/Markdown/Markdown.csproj b/Markdown/Markdown.csproj new file mode 100644 index 000000000..2150e3797 --- /dev/null +++ b/Markdown/Markdown.csproj @@ -0,0 +1,10 @@ + + + + Exe + net8.0 + enable + enable + + + diff --git a/Markdown/Markdown.sln b/Markdown/Markdown.sln new file mode 100644 index 000000000..49d0f19d6 --- /dev/null +++ b/Markdown/Markdown.sln @@ -0,0 +1,33 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.11.35327.3 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Markdown", "Markdown.csproj", "{626959A1-0BA3-4781-A912-9F6577A6EEE4}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{ECE25A0C-4FE7-4917-8F33-021617D93100}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Markdown.Tests", "..\Markdown.Tests\Markdown.Tests.csproj", "{1F636D4E-83B2-49E0-9AC7-23A6ED7DA246}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {626959A1-0BA3-4781-A912-9F6577A6EEE4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {626959A1-0BA3-4781-A912-9F6577A6EEE4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {626959A1-0BA3-4781-A912-9F6577A6EEE4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {626959A1-0BA3-4781-A912-9F6577A6EEE4}.Release|Any CPU.Build.0 = Release|Any CPU + {1F636D4E-83B2-49E0-9AC7-23A6ED7DA246}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1F636D4E-83B2-49E0-9AC7-23A6ED7DA246}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1F636D4E-83B2-49E0-9AC7-23A6ED7DA246}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1F636D4E-83B2-49E0-9AC7-23A6ED7DA246}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {73EFC805-A857-4DF1-85A6-826827EF1C3A} + EndGlobalSection +EndGlobal diff --git a/Markdown/Md.cs b/Markdown/Md.cs new file mode 100644 index 000000000..dec241bf9 --- /dev/null +++ b/Markdown/Md.cs @@ -0,0 +1,18 @@ +using Markdown.MDParser; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Markdown +{ + public class Md + { + public static string Render(string text, IParser parser, IConverter converter) + { + var dom = parser.BuildDom(text); + return converter.Convert(dom); + } + } +} diff --git a/Markdown/Program.cs b/Markdown/Program.cs new file mode 100644 index 000000000..00cf4f4c2 --- /dev/null +++ b/Markdown/Program.cs @@ -0,0 +1,101 @@ +using Markdown.HTMLConverter; +using Markdown.MDParser; +using System.Text; + +namespace Markdown +{ + internal class Program + { + public static void FindChildren(Token token) + { + if (!Complex.Properties.Contains(token.Property)) + return; + + var i = 0; + var text = token.Value; + var len = token.Value.Length; + while (i < len) + { + var start = text[i]; + switch (start) + { + case '_': + { + var italic = new Italic(); + var child = italic.TryFindToken(text, i); + token.Children.Add(child); + + i += child.Property switch + { + TokenProperty.Italic => child.Value.Length + 2, + TokenProperty.Bold => child.Value.Length + 4, + _ => child.Value.Length, + }; + break; + } + + case '\\': + { + var hidden = new Hidden(); + var child = hidden.TryFindToken(text, i); + i += child.Value.Length + 1; + if (child.Value == string.Empty) + child.Value = "\\"; + token.Children.Add(child); + break; + } + + default: + { + var norm = new Normal(); + var child = norm.TryFindToken(text, i); + token.Children.Add(child); + i += child.Value.Length; + break; + } + } + } + } + + public static string PrintToken(Token token) + { + var result = new StringBuilder(); + var stack = new Stack(); + var closings = new Stack(); + stack.Push(token); + result.Append(Tags.Open[token.Property]); + closings.Push(Tags.Close[token.Property]); + while (stack.Count != 0) + { + var current = stack.Pop(); + foreach (var next in current.Children) + { + result.Append(Tags.Open[next.Property]); + if (next.Property is TokenProperty.Normal or TokenProperty.Italic) + { + result.Append(next.Value); + result.Append(Tags.Close[next.Property]); + } + else closings.Push(Tags.Close[next.Property]); + stack.Push(next); + } + } + while (closings.Count != 0) + result.Append(closings.Pop()); + return result.ToString(); + } + static void Main() + { + var text = "Внутри __двойного выделения _одинарное_ тоже__ работает"; + + var parser = new Parser(); + var dom = parser.BuildDom(text); + var converter = new Converter(); + + var result = new StringBuilder(); + result.Append(converter.Convert(dom)); + Console.WriteLine(result); + + } + } +} diff --git a/Markdown/Token.cs b/Markdown/Token.cs new file mode 100644 index 000000000..cce90087e --- /dev/null +++ b/Markdown/Token.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Markdown +{ + public class Token(string value, TokenProperty property) + { + public string Value { get; set; } = value; + public readonly TokenProperty Property = property; + public List Children = []; + + public override string ToString() + { + return $"{Value} {Property}"; + } + } +} diff --git a/Markdown/TokenPorperty.cs b/Markdown/TokenPorperty.cs new file mode 100644 index 000000000..5d8881f55 --- /dev/null +++ b/Markdown/TokenPorperty.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Markdown +{ + public enum TokenProperty + { + Normal, + Italic, + Bold, + Head, + Paragraph, + Link + } +}