From d9493ea0cece4e99f741f7f03bd1627a65f89fc9 Mon Sep 17 00:00:00 2001 From: Dmitriy Bessarab Date: Fri, 22 Nov 2024 13:48:11 +0500 Subject: [PATCH 1/6] to start discussion --- Markdown/Bold.cs | 16 ++++++++++++++++ Markdown/Empty.cs | 16 ++++++++++++++++ Markdown/Head.cs | 16 ++++++++++++++++ Markdown/ITokenizer.cs | 13 +++++++++++++ Markdown/Italic.cs | 16 ++++++++++++++++ Markdown/Markdown.csproj | 10 ++++++++++ Markdown/Markdown.sln | 27 +++++++++++++++++++++++++++ Markdown/Normal.cs | 16 ++++++++++++++++ Markdown/Program.cs | 10 ++++++++++ Markdown/Token.cs | 23 +++++++++++++++++++++++ Markdown/TokenPorperty.cs | 16 ++++++++++++++++ 11 files changed, 179 insertions(+) create mode 100644 Markdown/Bold.cs create mode 100644 Markdown/Empty.cs create mode 100644 Markdown/Head.cs create mode 100644 Markdown/ITokenizer.cs create mode 100644 Markdown/Italic.cs create mode 100644 Markdown/Markdown.csproj create mode 100644 Markdown/Markdown.sln create mode 100644 Markdown/Normal.cs create mode 100644 Markdown/Program.cs create mode 100644 Markdown/Token.cs create mode 100644 Markdown/TokenPorperty.cs diff --git a/Markdown/Bold.cs b/Markdown/Bold.cs new file mode 100644 index 000000000..71156634e --- /dev/null +++ b/Markdown/Bold.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Markdown +{ + public class Bold : ITokenizer + { + public Token? TryFindToken(string text, int idx) + { + throw new NotImplementedException(); + } + } +} diff --git a/Markdown/Empty.cs b/Markdown/Empty.cs new file mode 100644 index 000000000..a158ba0a1 --- /dev/null +++ b/Markdown/Empty.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Markdown +{ + public class Empty : ITokenizer + { + public Token? TryFindToken(string text, int idx) + { + throw new NotImplementedException(); + } + } +} diff --git a/Markdown/Head.cs b/Markdown/Head.cs new file mode 100644 index 000000000..3f44d3c4d --- /dev/null +++ b/Markdown/Head.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Markdown +{ + public class Head : ITokenizer + { + public Token? TryFindToken(string text, int idx) + { + throw new NotImplementedException(); + } + } +} 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/Italic.cs b/Markdown/Italic.cs new file mode 100644 index 000000000..0f8808e9c --- /dev/null +++ b/Markdown/Italic.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Markdown +{ + public class Italic : ITokenizer + { + public Token? TryFindToken(string text, int idx) + { + throw new NotImplementedException(); + } + } +} 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..d85a5e444 --- /dev/null +++ b/Markdown/Markdown.sln @@ -0,0 +1,27 @@ + +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 +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 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {73EFC805-A857-4DF1-85A6-826827EF1C3A} + EndGlobalSection +EndGlobal diff --git a/Markdown/Normal.cs b/Markdown/Normal.cs new file mode 100644 index 000000000..820b5cb48 --- /dev/null +++ b/Markdown/Normal.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Markdown +{ + public class Normal : ITokenizer + { + public Token? TryFindToken(string text, int idx) + { + throw new NotImplementedException(); + } + } +} diff --git a/Markdown/Program.cs b/Markdown/Program.cs new file mode 100644 index 000000000..0378118c7 --- /dev/null +++ b/Markdown/Program.cs @@ -0,0 +1,10 @@ +namespace Markdown +{ + internal class Program + { + static void Main(string[] args) + { + Console.WriteLine("Hello, World!"); + } + } +} diff --git a/Markdown/Token.cs b/Markdown/Token.cs new file mode 100644 index 000000000..aa546b3f4 --- /dev/null +++ b/Markdown/Token.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Markdown +{ + public class Token + { + public readonly int Position; + + public readonly string Value; + + public readonly TokenProperty Property; + + public Token(int position, string value) + { + Position = position; + Value = value; + } + } +} diff --git a/Markdown/TokenPorperty.cs b/Markdown/TokenPorperty.cs new file mode 100644 index 000000000..a63e296e5 --- /dev/null +++ b/Markdown/TokenPorperty.cs @@ -0,0 +1,16 @@ +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 + } +} From 9d5ff63470a8c72482c2f8c8790369a917830fae Mon Sep 17 00:00:00 2001 From: Dmitriy Bessarab Date: Sun, 1 Dec 2024 17:04:03 +0500 Subject: [PATCH 2/6] add MDParser --- Markdown.Tests/Bold_Should.cs | 38 +++++++++++++ Markdown.Tests/Hidden_Should.cs | 31 ++++++++++ Markdown.Tests/Italic_Should.cs | 36 ++++++++++++ Markdown.Tests/Markdown.Tests.csproj | 21 +++++++ Markdown.Tests/Normal_Should.cs | 33 +++++++++++ Markdown/Bold.cs | 16 ------ Markdown/Complex.cs | 17 ++++++ Markdown/DOM.cs | 13 +++++ Markdown/Empty.cs | 16 ------ Markdown/Head.cs | 16 ------ Markdown/IParser.cs | 13 +++++ Markdown/Italic.cs | 16 ------ Markdown/MDParser/Bold.cs | 72 +++++++++++++++++++++++ Markdown/MDParser/Hidden.cs | 22 +++++++ Markdown/MDParser/Italic.cs | 50 ++++++++++++++++ Markdown/MDParser/Markers.cs | 14 +++++ Markdown/MDParser/Normal.cs | 28 +++++++++ Markdown/MDParser/Parser.cs | 84 +++++++++++++++++++++++++++ Markdown/Markdown.sln | 6 ++ Markdown/Md.cs | 13 +++++ Markdown/Normal.cs | 16 ------ Markdown/Program.cs | 85 +++++++++++++++++++++++++++- Markdown/Token.cs | 15 ++--- Markdown/TokenPorperty.cs | 3 +- 24 files changed, 581 insertions(+), 93 deletions(-) create mode 100644 Markdown.Tests/Bold_Should.cs create mode 100644 Markdown.Tests/Hidden_Should.cs create mode 100644 Markdown.Tests/Italic_Should.cs create mode 100644 Markdown.Tests/Markdown.Tests.csproj create mode 100644 Markdown.Tests/Normal_Should.cs delete mode 100644 Markdown/Bold.cs create mode 100644 Markdown/Complex.cs create mode 100644 Markdown/DOM.cs delete mode 100644 Markdown/Empty.cs delete mode 100644 Markdown/Head.cs create mode 100644 Markdown/IParser.cs delete mode 100644 Markdown/Italic.cs create mode 100644 Markdown/MDParser/Bold.cs create mode 100644 Markdown/MDParser/Hidden.cs create mode 100644 Markdown/MDParser/Italic.cs create mode 100644 Markdown/MDParser/Markers.cs create mode 100644 Markdown/MDParser/Normal.cs create mode 100644 Markdown/MDParser/Parser.cs create mode 100644 Markdown/Md.cs delete mode 100644 Markdown/Normal.cs 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/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/Bold.cs b/Markdown/Bold.cs deleted file mode 100644 index 71156634e..000000000 --- a/Markdown/Bold.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Markdown -{ - public class Bold : ITokenizer - { - public Token? TryFindToken(string text, int idx) - { - throw new NotImplementedException(); - } - } -} 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..fd5500c89 --- /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(IEnumerable tokens) + { + public readonly List Tokens = tokens.ToList(); + } +} diff --git a/Markdown/Empty.cs b/Markdown/Empty.cs deleted file mode 100644 index a158ba0a1..000000000 --- a/Markdown/Empty.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Markdown -{ - public class Empty : ITokenizer - { - public Token? TryFindToken(string text, int idx) - { - throw new NotImplementedException(); - } - } -} diff --git a/Markdown/Head.cs b/Markdown/Head.cs deleted file mode 100644 index 3f44d3c4d..000000000 --- a/Markdown/Head.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Markdown -{ - public class Head : ITokenizer - { - public Token? TryFindToken(string text, int idx) - { - throw new NotImplementedException(); - } - } -} 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/Italic.cs b/Markdown/Italic.cs deleted file mode 100644 index 0f8808e9c..000000000 --- a/Markdown/Italic.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Markdown -{ - public class Italic : ITokenizer - { - public Token? TryFindToken(string text, int idx) - { - throw new NotImplementedException(); - } - } -} 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/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..245e2a97f --- /dev/null +++ b/Markdown/MDParser/Parser.cs @@ -0,0 +1,84 @@ +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) + ; + + 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); + } + + public 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 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; + } + + default: + { + var child = norm.TryFindToken(text, i); + token.Children.Add(child); + i += child.Value.Length; + break; + } + } + } + } + } +} diff --git a/Markdown/Markdown.sln b/Markdown/Markdown.sln index d85a5e444..49d0f19d6 100644 --- a/Markdown/Markdown.sln +++ b/Markdown/Markdown.sln @@ -7,6 +7,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Markdown", "Markdown.csproj 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 @@ -17,6 +19,10 @@ Global {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 diff --git a/Markdown/Md.cs b/Markdown/Md.cs new file mode 100644 index 000000000..e3506c948 --- /dev/null +++ b/Markdown/Md.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 Md + { + + } +} diff --git a/Markdown/Normal.cs b/Markdown/Normal.cs deleted file mode 100644 index 820b5cb48..000000000 --- a/Markdown/Normal.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Markdown -{ - public class Normal : ITokenizer - { - public Token? TryFindToken(string text, int idx) - { - throw new NotImplementedException(); - } - } -} diff --git a/Markdown/Program.cs b/Markdown/Program.cs index 0378118c7..37eb0c598 100644 --- a/Markdown/Program.cs +++ b/Markdown/Program.cs @@ -1,10 +1,89 @@ -namespace Markdown +using Markdown.MDParser; + +namespace Markdown { internal class Program { - static void Main(string[] args) + public static void FindChildren(Token token) { - Console.WriteLine("Hello, World!"); + 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; + } + } + } + } + + static void Main() + { + var text = "# Спецификация _языка_ _ раз_метки\nfs__df dfh__\n#sef"; + + 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) + ; + + 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); + } + + //Console.WriteLine(token); + //Console.WriteLine("---------------------------"); + //FindChildren(token); + //foreach (var c in token.Children) + // Console.WriteLine(c); + //Console.WriteLine("========="); + //Console.WriteLine(); + } } } } diff --git a/Markdown/Token.cs b/Markdown/Token.cs index aa546b3f4..cce90087e 100644 --- a/Markdown/Token.cs +++ b/Markdown/Token.cs @@ -6,18 +6,15 @@ namespace Markdown { - public class Token + public class Token(string value, TokenProperty property) { - public readonly int Position; - - public readonly string Value; + public string Value { get; set; } = value; + public readonly TokenProperty Property = property; + public List Children = []; - public readonly TokenProperty Property; - - public Token(int position, string value) + public override string ToString() { - Position = position; - Value = value; + return $"{Value} {Property}"; } } } diff --git a/Markdown/TokenPorperty.cs b/Markdown/TokenPorperty.cs index a63e296e5..8f53949c3 100644 --- a/Markdown/TokenPorperty.cs +++ b/Markdown/TokenPorperty.cs @@ -11,6 +11,7 @@ public enum TokenProperty Normal, Italic, Bold, - Head + Head, + Paragraph } } From a8b53126ed05c8c200df11b32163e9ef5e9918e0 Mon Sep 17 00:00:00 2001 From: Dmitriy Bessarab Date: Sun, 1 Dec 2024 17:55:35 +0500 Subject: [PATCH 3/6] Add HTMLconverter frame --- Markdown/HTMLConverter/Converter.cs | 16 ++++++++++ Markdown/IConverter.cs | 13 ++++++++ Markdown/MDParser/Parser.cs | 2 +- Markdown/Md.cs | 9 ++++-- Markdown/Program.cs | 49 +++++++++++++++-------------- 5 files changed, 63 insertions(+), 26 deletions(-) create mode 100644 Markdown/HTMLConverter/Converter.cs create mode 100644 Markdown/IConverter.cs diff --git a/Markdown/HTMLConverter/Converter.cs b/Markdown/HTMLConverter/Converter.cs new file mode 100644 index 000000000..a54f59909 --- /dev/null +++ b/Markdown/HTMLConverter/Converter.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Markdown.HTMLConverter +{ + public class Converter : IConverter + { + public string Convert(DOM dom) + { + throw new NotImplementedException(); + } + } +} 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/MDParser/Parser.cs b/Markdown/MDParser/Parser.cs index 245e2a97f..5beea7e4e 100644 --- a/Markdown/MDParser/Parser.cs +++ b/Markdown/MDParser/Parser.cs @@ -30,7 +30,7 @@ public DOM BuildDom(string text) return new DOM(tokens); } - public static void FindChildren(Token token) + private static void FindChildren(Token token) { if (!Complex.Properties.Contains(token.Property)) return; diff --git a/Markdown/Md.cs b/Markdown/Md.cs index e3506c948..dec241bf9 100644 --- a/Markdown/Md.cs +++ b/Markdown/Md.cs @@ -1,4 +1,5 @@ -using System; +using Markdown.MDParser; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -8,6 +9,10 @@ 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 index 37eb0c598..0dfa02b62 100644 --- a/Markdown/Program.cs +++ b/Markdown/Program.cs @@ -59,31 +59,34 @@ static void Main() { var text = "# Спецификация _языка_ _ раз_метки\nfs__df dfh__\n#sef"; - 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) - ; + var parser = new Parser(); + var dom = parser.BuildDom(text); - 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); - } + //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) + // ; - //Console.WriteLine(token); - //Console.WriteLine("---------------------------"); - //FindChildren(token); - //foreach (var c in token.Children) - // Console.WriteLine(c); - //Console.WriteLine("========="); - //Console.WriteLine(); - } + //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); + // } + + //Console.WriteLine(token); + //Console.WriteLine("---------------------------"); + //FindChildren(token); + //foreach (var c in token.Children) + // Console.WriteLine(c); + //Console.WriteLine("========="); + //Console.WriteLine(); + //} } } } From 3b6d469af658209ba88d359da3e59472903cae2f Mon Sep 17 00:00:00 2001 From: Dmitriy Bessarab Date: Mon, 2 Dec 2024 20:18:25 +0500 Subject: [PATCH 4/6] converter w\o tests --- Markdown/DOM.cs | 4 +- Markdown/HTMLConverter/Converter.cs | 33 ++++++++++++++- Markdown/HTMLConverter/Tags.cs | 28 +++++++++++++ Markdown/MDParser/Parser.cs | 1 + Markdown/Program.cs | 63 +++++++++++++++++------------ 5 files changed, 100 insertions(+), 29 deletions(-) create mode 100644 Markdown/HTMLConverter/Tags.cs diff --git a/Markdown/DOM.cs b/Markdown/DOM.cs index fd5500c89..bba143a64 100644 --- a/Markdown/DOM.cs +++ b/Markdown/DOM.cs @@ -6,8 +6,8 @@ namespace Markdown { - public class DOM(IEnumerable tokens) + public class DOM(List tokens) { - public readonly List Tokens = tokens.ToList(); + public readonly List Tokens = tokens; } } diff --git a/Markdown/HTMLConverter/Converter.cs b/Markdown/HTMLConverter/Converter.cs index a54f59909..cad79b1a0 100644 --- a/Markdown/HTMLConverter/Converter.cs +++ b/Markdown/HTMLConverter/Converter.cs @@ -8,9 +8,40 @@ namespace Markdown.HTMLConverter { public class Converter : IConverter { + private 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(); + } + public string Convert(DOM dom) { - throw new NotImplementedException(); + var result = new StringBuilder(); + foreach (var token in dom.Tokens) + result.Append(PrintToken(token)); + return result.ToString(); } } } diff --git a/Markdown/HTMLConverter/Tags.cs b/Markdown/HTMLConverter/Tags.cs new file mode 100644 index 000000000..725b6e107 --- /dev/null +++ b/Markdown/HTMLConverter/Tags.cs @@ -0,0 +1,28 @@ +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, ""}, + { TokenProperty.Normal, ""} + }; + + public static Dictionary Close = new() { + { TokenProperty.Bold, ""}, + { TokenProperty.Head, "

\n"}, + { TokenProperty.Italic, ""}, + { TokenProperty.Paragraph, "\n"}, + { TokenProperty.Normal, ""} + }; + } +} + diff --git a/Markdown/MDParser/Parser.cs b/Markdown/MDParser/Parser.cs index 5beea7e4e..064ea29b4 100644 --- a/Markdown/MDParser/Parser.cs +++ b/Markdown/MDParser/Parser.cs @@ -13,6 +13,7 @@ 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) diff --git a/Markdown/Program.cs b/Markdown/Program.cs index 0dfa02b62..3e175d18d 100644 --- a/Markdown/Program.cs +++ b/Markdown/Program.cs @@ -1,4 +1,6 @@ -using Markdown.MDParser; +using Markdown.HTMLConverter; +using Markdown.MDParser; +using System.Text; namespace Markdown { @@ -55,38 +57,47 @@ public static void FindChildren(Token token) } } + 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 == TokenProperty.Normal || next.Property == 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 = "# Спецификация _языка_ _ раз_метки\nfs__df dfh__\n#sef"; + var text = "#Заголовок __с _разными_ символами__\n# Спецификация _языка_ _ раз_метки\nfs__df dfh__\n#sef"; var parser = new Parser(); var dom = parser.BuildDom(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) - // ; - - //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); - // } + var result = new StringBuilder(); + foreach (var token in dom.Tokens) + { + result.Append(PrintToken(token)); + } + Console.WriteLine(result); - //Console.WriteLine(token); - //Console.WriteLine("---------------------------"); - //FindChildren(token); - //foreach (var c in token.Children) - // Console.WriteLine(c); - //Console.WriteLine("========="); - //Console.WriteLine(); - //} } } } From 3c91b5369977dde83ef05b1aa266cfe49cac8573 Mon Sep 17 00:00:00 2001 From: Dmitriy Bessarab Date: Tue, 3 Dec 2024 22:22:50 +0500 Subject: [PATCH 5/6] add Link to possible tags --- Markdown.Tests/Link_Should.cs | 37 +++++++++++++++ Markdown.Tests/MD.Render_Should.cs | 74 +++++++++++++++++++++++++++++ Markdown/HTMLConverter/Converter.cs | 60 ++++++++++++++--------- Markdown/HTMLConverter/Tags.cs | 8 ++-- Markdown/MDParser/Link.cs | 48 +++++++++++++++++++ Markdown/MDParser/Parser.cs | 12 +++++ Markdown/Program.cs | 10 ++-- Markdown/TokenPorperty.cs | 3 +- 8 files changed, 220 insertions(+), 32 deletions(-) create mode 100644 Markdown.Tests/Link_Should.cs create mode 100644 Markdown.Tests/MD.Render_Should.cs create mode 100644 Markdown/MDParser/Link.cs 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..efaf89b74 --- /dev/null +++ b/Markdown.Tests/MD.Render_Should.cs @@ -0,0 +1,74 @@ +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"); + } + } +} diff --git a/Markdown/HTMLConverter/Converter.cs b/Markdown/HTMLConverter/Converter.cs index cad79b1a0..78b7f7fd6 100644 --- a/Markdown/HTMLConverter/Converter.cs +++ b/Markdown/HTMLConverter/Converter.cs @@ -8,39 +8,55 @@ namespace Markdown.HTMLConverter { public class Converter : IConverter { - private static string PrintToken(Token token) + public static void GetContent(Token token, StringBuilder sb) { - 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) + if (token.Children.Count == 0) { - var current = stack.Pop(); - foreach (var next in current.Children) + if (token.Property != TokenProperty.Link) { - 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); + sb.Append(Tags.Open[token.Property]); + sb.Append(token.Value); + sb.Append(Tags.Close[token.Property]); } + else ConvertLink(token.Value, sb); } - while (closings.Count != 0) - result.Append(closings.Pop()); - return result.ToString(); + 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) - result.Append(PrintToken(token)); + GetContent(token, result); return result.ToString(); } } diff --git a/Markdown/HTMLConverter/Tags.cs b/Markdown/HTMLConverter/Tags.cs index 725b6e107..41e875a8e 100644 --- a/Markdown/HTMLConverter/Tags.cs +++ b/Markdown/HTMLConverter/Tags.cs @@ -12,8 +12,9 @@ class Tags { TokenProperty.Bold, ""}, { TokenProperty.Head, "

"}, { TokenProperty.Italic, ""}, - { TokenProperty.Paragraph, ""}, - { TokenProperty.Normal, ""} + { TokenProperty.Paragraph, string.Empty}, + { TokenProperty.Normal, string.Empty}, + { TokenProperty.Link , "\n"}, { TokenProperty.Italic, ""}, { TokenProperty.Paragraph, "\n"}, - { TokenProperty.Normal, ""} + { TokenProperty.Normal, string.Empty}, + {TokenProperty.Link , ""} }; } } 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/Parser.cs b/Markdown/MDParser/Parser.cs index 064ea29b4..843080d98 100644 --- a/Markdown/MDParser/Parser.cs +++ b/Markdown/MDParser/Parser.cs @@ -38,6 +38,7 @@ private static void FindChildren(Token token) var hidden = new Hidden(); var italic = new Italic(); var norm = new Normal(); + var link = new Link(); var i = 0; var text = token.Value; @@ -70,6 +71,17 @@ private static void FindChildren(Token token) 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: { diff --git a/Markdown/Program.cs b/Markdown/Program.cs index 3e175d18d..00cf4f4c2 100644 --- a/Markdown/Program.cs +++ b/Markdown/Program.cs @@ -71,7 +71,7 @@ public static string PrintToken(Token token) foreach (var next in current.Children) { result.Append(Tags.Open[next.Property]); - if (next.Property == TokenProperty.Normal || next.Property == TokenProperty.Italic) + if (next.Property is TokenProperty.Normal or TokenProperty.Italic) { result.Append(next.Value); result.Append(Tags.Close[next.Property]); @@ -86,16 +86,14 @@ public static string PrintToken(Token token) } static void Main() { - var text = "#Заголовок __с _разными_ символами__\n# Спецификация _языка_ _ раз_метки\nfs__df dfh__\n#sef"; + var text = "Внутри __двойного выделения _одинарное_ тоже__ работает"; var parser = new Parser(); var dom = parser.BuildDom(text); + var converter = new Converter(); var result = new StringBuilder(); - foreach (var token in dom.Tokens) - { - result.Append(PrintToken(token)); - } + result.Append(converter.Convert(dom)); Console.WriteLine(result); } diff --git a/Markdown/TokenPorperty.cs b/Markdown/TokenPorperty.cs index 8f53949c3..5d8881f55 100644 --- a/Markdown/TokenPorperty.cs +++ b/Markdown/TokenPorperty.cs @@ -12,6 +12,7 @@ public enum TokenProperty Italic, Bold, Head, - Paragraph + Paragraph, + Link } } From 963514b72c385674498ea0c34d6b7f86d3849698 Mon Sep 17 00:00:00 2001 From: Dmitriy Bessarab Date: Tue, 3 Dec 2024 23:00:32 +0500 Subject: [PATCH 6/6] add some tests --- Markdown.Tests/MD.Render_Should.cs | 19 +++++++++++++++++++ Markdown/HTMLConverter/Converter.cs | 2 +- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/Markdown.Tests/MD.Render_Should.cs b/Markdown.Tests/MD.Render_Should.cs index efaf89b74..b07205956 100644 --- a/Markdown.Tests/MD.Render_Should.cs +++ b/Markdown.Tests/MD.Render_Should.cs @@ -70,5 +70,24 @@ 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/HTMLConverter/Converter.cs b/Markdown/HTMLConverter/Converter.cs index 78b7f7fd6..d2859406d 100644 --- a/Markdown/HTMLConverter/Converter.cs +++ b/Markdown/HTMLConverter/Converter.cs @@ -47,7 +47,7 @@ public static void ConvertLink(string text, StringBuilder sb) } sb.Append(Tags.Open[TokenProperty.Link]); sb.Append(address); - sb.Append(">"); + sb.Append('>'); sb.Append(name); sb.Append(Tags.Close[TokenProperty.Link]); }