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
+ }
+}