diff --git a/cs/Markdown/IMarkdown.cs b/cs/Markdown/IMarkdownRenderer.cs similarity index 63% rename from cs/Markdown/IMarkdown.cs rename to cs/Markdown/IMarkdownRenderer.cs index 7ac783395..ead0a1aae 100644 --- a/cs/Markdown/IMarkdown.cs +++ b/cs/Markdown/IMarkdownRenderer.cs @@ -1,6 +1,6 @@ namespace Markdown; -public interface IMarkdown +public interface IMarkdownRenderer { string Render(string markdown); } \ No newline at end of file diff --git a/cs/Markdown/Markdown.csproj b/cs/Markdown/Markdown.csproj index fbc0a9283..d1bae7ba4 100644 --- a/cs/Markdown/Markdown.csproj +++ b/cs/Markdown/Markdown.csproj @@ -10,11 +10,6 @@ - - - - - diff --git a/cs/Markdown/MarkdownRenderer.cs b/cs/Markdown/MarkdownRenderer.cs index 1327312d8..f42041825 100644 --- a/cs/Markdown/MarkdownRenderer.cs +++ b/cs/Markdown/MarkdownRenderer.cs @@ -1,101 +1,31 @@ using Markdown.Render; using Markdown.Tokenizer; using Markdown.Tokenizer.Handlers; -using Markdown.Tokenizer.Nodes; -using Markdown.Tokenizer.Tags; +using Markdown.TreeBuilder; namespace Markdown; -public class MarkdownRenderer : IMarkdown +public class MarkdownRenderer : IMarkdownRenderer { + private readonly ITreeRenderer treeRenderer = new TreeRenderer(); + private readonly ITokenizer tokenizer; private readonly List handlers = new() { new HeaderHandler(), new ItalicHandler(), new BoldHandler(), }; - public string Render(string markdown) + + public MarkdownRenderer() { - var tokenizer = new MarkdownTokenizer(new HandlerManager(handlers), new TagProcessor()); - var renderer = new HtmlRenderer(); - var tokens = tokenizer.Tokenize(markdown); - var tree = ToTree(tokens); - return renderer.Render(tree); + tokenizer = new MarkdownTokenizer(new HandlerManager(handlers), new TagProcessor()); } - private Node ToTree(List tokens) + public string Render(string markdown) { - Node mainNode = new MainNode(); - Node currentNode = mainNode; - for (int i = 0; i < tokens.Count; i++) - { - if (tokens[i].TagStatus == TagStatus.Broken) - { - currentNode.Children.Add(new TextNode{Value = tokens[i].Value}); - continue; - } - - if (tokens[i] is ItalicTag tag) - { - if(tag.TagStatus == TagStatus.Open) - { - var node = new ItalicNode(); - currentNode.Children.Add(node); - node.Parent = currentNode; - currentNode = node; - continue; - } - - if (tag.TagStatus == TagStatus.Closed) - { - currentNode = currentNode.Parent; - continue; - } - } - - if (tokens[i] is BoldTag boldTag) - { - if(boldTag.TagStatus == TagStatus.Open) - { - var node = new BoldNode(); - currentNode.Children.Add(node); - node.Parent = currentNode; - currentNode = node; - continue; - } - - if (boldTag.TagStatus == TagStatus.Closed) - { - currentNode = currentNode.Parent; - continue; - } - } - - if (tokens[i] is HeaderTag) - { - var node = new HeaderNode(); - currentNode.Children.Add(node); - node.Parent = currentNode; - currentNode = node; - continue; - } - - if (tokens[i] is NewLineToken) - { - if (currentNode is HeaderNode) - { - currentNode = currentNode.Parent; - } - continue; - } - - if (tokens[i] is TextToken textToken) - { - currentNode.Children.Add(new TextNode { Value = textToken.Value }); - continue; - } - } + var tokens = tokenizer.Tokenize(markdown); + var tree = new TreeBuilder.TreeBuilder(new NodeFactory()).Build(tokens); - return currentNode.Parent ?? currentNode; + return treeRenderer.Render(tree); } } \ No newline at end of file diff --git a/cs/Markdown/Render/ITokenRenderer.cs b/cs/Markdown/Render/ITokenRenderer.cs deleted file mode 100644 index 12d3d2928..000000000 --- a/cs/Markdown/Render/ITokenRenderer.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Markdown.Tokenizer.Nodes; - -namespace Markdown.Render; - -public interface ITokenRenderer -{ - string Render(Node tokens); -} \ No newline at end of file diff --git a/cs/Markdown/Render/ITreeRenderer.cs b/cs/Markdown/Render/ITreeRenderer.cs new file mode 100644 index 000000000..ffcc74676 --- /dev/null +++ b/cs/Markdown/Render/ITreeRenderer.cs @@ -0,0 +1,8 @@ +using Markdown.TreeBuilder.Nodes; + +namespace Markdown.Render; + +public interface ITreeRenderer +{ + string Render(Node tokens); +} \ No newline at end of file diff --git a/cs/Markdown/Render/HtmlRenderer.cs b/cs/Markdown/Render/TreeRenderer.cs similarity index 54% rename from cs/Markdown/Render/HtmlRenderer.cs rename to cs/Markdown/Render/TreeRenderer.cs index 63d9d72ac..74eda954f 100644 --- a/cs/Markdown/Render/HtmlRenderer.cs +++ b/cs/Markdown/Render/TreeRenderer.cs @@ -1,9 +1,9 @@ using System.Text; -using Markdown.Tokenizer.Nodes; +using Markdown.TreeBuilder.Nodes; namespace Markdown.Render; -public class HtmlRenderer : ITokenRenderer +public class TreeRenderer : ITreeRenderer { public string Render(Node tokens) { @@ -19,10 +19,7 @@ public string Render(Node tokens) return node switch { TextNode textNode => textNode.Value, - HeaderNode => $"

{Render(node)}

", - ItalicNode => $"{Render(node)}", - BoldNode => $"{Render(node)}", - _ => throw new Exception($"Unknown token type: {node.GetType()}") + _ => $"{node.OpenTag}{Render(node)}{node.CloseTag}" }; } } \ No newline at end of file diff --git a/cs/Markdown/Tests/Markdown/MarkdownTests.cs b/cs/Markdown/Tests/Markdown/MarkdownTests.cs index a2e1df0c5..83a8c2166 100644 --- a/cs/Markdown/Tests/Markdown/MarkdownTests.cs +++ b/cs/Markdown/Tests/Markdown/MarkdownTests.cs @@ -1,93 +1,43 @@ -namespace Markdown.Tests.Markdown; +using NUnit.Framework; + +namespace Markdown.Tests.Markdown; [TestFixture] public class MarkdownTests { - private static readonly VerifySettings Settings = new(); - private static readonly MarkdownRenderer Renderer = new(); + private IMarkdownRenderer renderer; - [OneTimeSetUp] - public void OneTimeSetUp() + [SetUp] + public void SetUp() { - Settings.UseDirectory("snapshots"); + renderer = new MarkdownRenderer(); } - [TestCaseSource(nameof(ItalicTestCases))] - public string Test_1(string input) => Renderer.Render(input); + [TestCaseSource(nameof(MarkdownRendererTestCases))] + public string MarkdownRenderer_Verify(string input) => renderer.Render(input); - private static TestCaseData[] ItalicTestCases = + private static TestCaseData[] MarkdownRendererTestCases = [ - new TestCaseData("# Header").Returns("

Header

"), - new TestCaseData("\\# Header").Returns("# Header"), - new TestCaseData("\\\\# Header").Returns("\\

Header

"), - new TestCaseData("_Italic text_").Returns("Italic text"), - new TestCaseData("\\_Text_").Returns("_Text_"), - new TestCaseData("\\\\_Italic text_").Returns("\\Italic text"), - new TestCaseData("_Italic text").Returns("_Italic text"), - new TestCaseData("Italic text_").Returns("Italic text_"), - new TestCaseData("Italic_ text_").Returns("Italic_ text_"), - new TestCaseData("_Italic _text").Returns("_Italic _text"), - new TestCaseData("_нач_але").Returns("начале"), - new TestCaseData("сер_еди_не").Returns("середине"), - new TestCaseData("цифры_1_12_3").Returns("цифры_1_12_3"), - new TestCaseData("кон_це._").Returns("конце."), - new TestCaseData("в ра_зных сл_овах не").Returns("в ра_зных сл_овах не"), - new TestCaseData("__bold__").Returns("bold"), - new TestCaseData("_Text__").Returns("_Text__"), - new TestCaseData("__Text_").Returns("__Text_"), - new TestCaseData("__Italic __text").Returns("__Italic __text"), - new TestCaseData("__два _один_ может__").Returns("два один может"), - new TestCaseData("_одинарного __двойное__ не_").Returns( "одинарного __двойное__ не") + new TestCaseData("# Header").Returns("

Header

").SetDescription("Простой заголовок."), + new TestCaseData("\\# Header").Returns("# Header").SetDescription("Экранированный заголовок."), + new TestCaseData("\\\\# Header").Returns("\\

Header

").SetDescription("Экранирован экранирования."), + new TestCaseData("_Italic text_").Returns("Italic text").SetDescription("Курсив"), + new TestCaseData("\\_Text_").Returns("_Text_").SetDescription("Экранирование курсива."), + new TestCaseData("\\\\_Italic text_").Returns("\\Italic text").SetDescription("Экранирование экранирования курсива."), + new TestCaseData("_Italic text").Returns("_Italic text").SetDescription("Одинокий открывающий тэг."), + new TestCaseData("Italic text_").Returns("Italic text_").SetDescription("Одинокий закрывающий тэг."), + new TestCaseData("Italic_ text_").Returns("Italic_ text_").SetDescription("Два закрывающих тэга."), + new TestCaseData("_Italic _text").Returns("_Italic _text").SetDescription("Два открывающих тэга."), + new TestCaseData("_нач_але").Returns("начале").SetDescription("Курсив в начале слова."), + new TestCaseData("сер_еди_не").Returns("середине").SetDescription("Курсив в середине слова."), + new TestCaseData("кон_це._").Returns("конце.").SetDescription("Курсив в конце слова."), + new TestCaseData("цифры_1_12_3").Returns("цифры_1_12_3").SetDescription("Между цифр - подчерки."), + new TestCaseData("в ра_зных сл_овах не").Returns("в ра_зных сл_овах не").SetDescription("В разных словах - не работает."), + new TestCaseData("__bold__").Returns("bold").SetDescription("Полужирный"), + new TestCaseData("_Text__").Returns("_Text__").SetDescription("Разные тэги 1"), + new TestCaseData("__Text_").Returns("__Text_").SetDescription("Разные тэги 2"), + new TestCaseData("__Italic __text").Returns("__Italic __text").SetDescription("Два открывающих тэга."), + new TestCaseData("__два _один_ может__").Returns("два один может").SetDescription("Курсив в полужирном."), + new TestCaseData("_одинарного __двойное__ не_").Returns( "одинарного __двойное__ не").SetDescription("Полужирный в курсиве - не работает."), ]; - - private static Task Verify(string target) => - Verifier.Verify(target, Settings); - - [Test] - public void SimpleText_Render_Verify() => - Verify(Renderer.Render("Text")); - - [Test] - public void EscapedCharacter_Render_Verify() => - Verify(Renderer.Render(@"\_Text_")); - - [Test] - public void ItalicText_Render_Verify() => - Verify(Renderer.Render("_Italic text_")); - - [Test] - public void BoldText_Render_Verify() => - Verify(Renderer.Render("__Bold text__")); - - [Test] - public void BoldWithItalicText_Render_Verify() => - Verify(Renderer.Render("__Bold _with italic_ text__")); - - [Test] - public void SimpleHeader_Render_Verify() => - Verify(Renderer.Render("# Header")); - - [Test] - public void TwoHeaders_Render_Verify() => - Verify(Renderer.Render("# Header one \n# Header two")); - // - // [Test] - // public void HeaderWithItalic_Render_Verify() => - // Verify(Renderer.Render("# Header with _italic text_")); - // - // [Test] - // public void HeaderWithBoldAndItalic_Render_Verify() => - // Verify(Renderer.Render("# Header with _italic_ and __bold__ text")); - // - // [Test] - // public void HeaderWithItalicInBold_Render_Verify() => - // Verify(Renderer.Render("# Header ___italic_ in bold__ text")); - // - // [Test] - // public void SimpleList_Render_Verify() => - // Verify(Renderer.Render("- item1\n- item2")); - // - // [Test] - // public void ListWithItalicAndBold_Render_Verify() => - // Verify(Renderer.Render("- _item1_\n- __item2__")); } \ No newline at end of file diff --git a/cs/Markdown/Tests/Markdown/snapshots/MarkdownTests.SimpleHeader_Render_Verify.received.txt b/cs/Markdown/Tests/Markdown/snapshots/MarkdownTests.SimpleHeader_Render_Verify.received.txt deleted file mode 100644 index 35ba349aa..000000000 --- a/cs/Markdown/Tests/Markdown/snapshots/MarkdownTests.SimpleHeader_Render_Verify.received.txt +++ /dev/null @@ -1 +0,0 @@ -

Header

\ No newline at end of file diff --git a/cs/Markdown/Tests/Markdown/snapshots/MarkdownTests.SimpleHeader_Render_Verify.verified.txt b/cs/Markdown/Tests/Markdown/snapshots/MarkdownTests.SimpleHeader_Render_Verify.verified.txt deleted file mode 100644 index 35ba349aa..000000000 --- a/cs/Markdown/Tests/Markdown/snapshots/MarkdownTests.SimpleHeader_Render_Verify.verified.txt +++ /dev/null @@ -1 +0,0 @@ -

Header

\ No newline at end of file diff --git a/cs/Markdown/Tests/Markdown/snapshots/MarkdownTests.TwoHeaders_Render_Verify.received.txt b/cs/Markdown/Tests/Markdown/snapshots/MarkdownTests.TwoHeaders_Render_Verify.received.txt deleted file mode 100644 index 71b2c8c9a..000000000 --- a/cs/Markdown/Tests/Markdown/snapshots/MarkdownTests.TwoHeaders_Render_Verify.received.txt +++ /dev/null @@ -1 +0,0 @@ -

Header one

Header two

\ No newline at end of file diff --git a/cs/Markdown/Tests/Markdown/snapshots/MarkdownTests.TwoHeaders_Render_Verify.verified.txt b/cs/Markdown/Tests/Markdown/snapshots/MarkdownTests.TwoHeaders_Render_Verify.verified.txt deleted file mode 100644 index 71b2c8c9a..000000000 --- a/cs/Markdown/Tests/Markdown/snapshots/MarkdownTests.TwoHeaders_Render_Verify.verified.txt +++ /dev/null @@ -1 +0,0 @@ -

Header one

Header two

\ No newline at end of file diff --git a/cs/Markdown/Tests/Tokenizer/BoldHandlerTests.cs b/cs/Markdown/Tests/Tokenizer/BoldHandlerTests.cs index a1519b5a0..f6c553d13 100644 --- a/cs/Markdown/Tests/Tokenizer/BoldHandlerTests.cs +++ b/cs/Markdown/Tests/Tokenizer/BoldHandlerTests.cs @@ -2,23 +2,32 @@ using Markdown.Tokenizer; using Markdown.Tokenizer.Handlers; using Markdown.Tokenizer.Tags; +using Markdown.TreeBuilder; +using NUnit.Framework; namespace Markdown.Tests.Tokenizer; [TestFixture] public class BoldHandlerTests { + private ITokenizer tokenizer; + + [SetUp] + public void SetUp() + { + var handlers = new List { new HeaderHandler(), new ItalicHandler(), new BoldHandler() }; + tokenizer = new MarkdownTokenizer(new HandlerManager(handlers), new TagProcessor()); + } + [TestCaseSource(nameof(BoldTokenSource))] public void BoldTokenizerTests((string input, Token[] tags) testCase) { - var handlers = new List() { new HeaderHandler(), new ItalicHandler(), new BoldHandler() }; - var tokenizer = new MarkdownTokenizer(new HandlerManager(handlers), new TagProcessor()); - var res = tokenizer.Tokenize(testCase.input).ToArray(); + var tokens = tokenizer.Tokenize(testCase.input).ToArray(); for (var i = 0; i < testCase.tags.Length; i++) { - res[i].Value.Should().Be(testCase.tags[i].Value); - res[i].TokenType.Should().Be(testCase.tags[i].TokenType); + tokens[i].Value.Should().Be(testCase.tags[i].Value); + tokens[i].TokenType.Should().Be(testCase.tags[i].TokenType); } } diff --git a/cs/Markdown/Tests/Tokenizer/HeaderHandlerTests.cs b/cs/Markdown/Tests/Tokenizer/HeaderHandlerTests.cs index 5f81ce304..97a07735e 100644 --- a/cs/Markdown/Tests/Tokenizer/HeaderHandlerTests.cs +++ b/cs/Markdown/Tests/Tokenizer/HeaderHandlerTests.cs @@ -2,23 +2,32 @@ using Markdown.Tokenizer; using Markdown.Tokenizer.Handlers; using Markdown.Tokenizer.Tags; +using Markdown.TreeBuilder; +using NUnit.Framework; namespace Markdown.Tests.Tokenizer; [TestFixture] public class HeaderHandlerTests { + private ITokenizer tokenizer; + + [SetUp] + public void SetUp() + { + var handlers = new List { new HeaderHandler(), new ItalicHandler(), new BoldHandler() }; + tokenizer = new MarkdownTokenizer(new HandlerManager(handlers), new TagProcessor()); + } + [TestCaseSource(nameof(HeaderTokenSource))] public void HeaderTokenizerTests((string input, Token[] tags) testCase) { - var handlers = new List() { new HeaderHandler(), new ItalicHandler(), new BoldHandler() }; - var tokenizer = new MarkdownTokenizer(new HandlerManager(handlers), new TagProcessor()); - var res = tokenizer.Tokenize(testCase.input).ToArray(); + var tokens = tokenizer.Tokenize(testCase.input).ToArray(); for (var i = 0; i < testCase.tags.Length; i++) { - res[i].Value.Should().Be(testCase.tags[i].Value); - res[i].TokenType.Should().Be(testCase.tags[i].TokenType); + tokens[i].Value.Should().Be(testCase.tags[i].Value); + tokens[i].TokenType.Should().Be(testCase.tags[i].TokenType); } } diff --git a/cs/Markdown/Tests/Tokenizer/ItalicHandlerTests.cs b/cs/Markdown/Tests/Tokenizer/ItalicHandlerTests.cs index c2b68e3c5..45bbc1ce9 100644 --- a/cs/Markdown/Tests/Tokenizer/ItalicHandlerTests.cs +++ b/cs/Markdown/Tests/Tokenizer/ItalicHandlerTests.cs @@ -2,6 +2,8 @@ using Markdown.Tokenizer; using Markdown.Tokenizer.Handlers; using Markdown.Tokenizer.Tags; +using Markdown.TreeBuilder; +using NUnit.Framework; namespace Markdown.Tests.Tokenizer; @@ -9,17 +11,24 @@ namespace Markdown.Tests.Tokenizer; [TestFixture] public class ItalicParserTests { + private ITokenizer tokenizer; + + [SetUp] + public void SetUp() + { + var handlers = new List { new HeaderHandler(), new ItalicHandler(), new BoldHandler() }; + tokenizer = new MarkdownTokenizer(new HandlerManager(handlers), new TagProcessor()); + } + [TestCaseSource(nameof(ItalicTokenSource))] public void ItalicTokenizerTests((string input, Token[] tags) testCase) { - var handlers = new List() { new HeaderHandler(), new ItalicHandler(), new BoldHandler() }; - var tokenizer = new MarkdownTokenizer(new HandlerManager(handlers), new TagProcessor()); - var res = tokenizer.Tokenize(testCase.input).ToArray(); + var tokens = tokenizer.Tokenize(testCase.input).ToArray(); for (var i = 0; i < testCase.tags.Length; i++) { - res[i].Value.Should().Be(testCase.tags[i].Value); - res[i].TokenType.Should().Be(testCase.tags[i].TokenType); + tokens[i].Value.Should().Be(testCase.tags[i].Value); + tokens[i].TokenType.Should().Be(testCase.tags[i].TokenType); } } diff --git a/cs/Markdown/Tokenizer/Handlers/ItalicHandler.cs b/cs/Markdown/Tokenizer/Handlers/ItalicHandler.cs index 8af2fb70f..1a26c97e8 100644 --- a/cs/Markdown/Tokenizer/Handlers/ItalicHandler.cs +++ b/cs/Markdown/Tokenizer/Handlers/ItalicHandler.cs @@ -8,10 +8,7 @@ public class ItalicHandler : IHandler { var symbol = ctx.Current; - if(symbol != '_') - return null; - - if(ctx.Next == '_') + if(symbol != '_' || ctx.Next == '_') return null; if (char.IsDigit(ctx.Previous ?? ' ') || char.IsDigit(ctx.Next ?? ' ')) diff --git a/cs/Markdown/Tokenizer/Nodes/BoldNode.cs b/cs/Markdown/Tokenizer/Nodes/BoldNode.cs deleted file mode 100644 index 3e42a57e0..000000000 --- a/cs/Markdown/Tokenizer/Nodes/BoldNode.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Markdown.Tokenizer.Nodes; - -public class BoldNode : Node -{ - -} \ No newline at end of file diff --git a/cs/Markdown/Tokenizer/Nodes/HeaderNode.cs b/cs/Markdown/Tokenizer/Nodes/HeaderNode.cs deleted file mode 100644 index aaf3bfa87..000000000 --- a/cs/Markdown/Tokenizer/Nodes/HeaderNode.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace Markdown.Tokenizer.Nodes; - -public class HeaderNode : Node -{ -} \ No newline at end of file diff --git a/cs/Markdown/Tokenizer/Nodes/ItalicNode.cs b/cs/Markdown/Tokenizer/Nodes/ItalicNode.cs deleted file mode 100644 index 528b72f7f..000000000 --- a/cs/Markdown/Tokenizer/Nodes/ItalicNode.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Markdown.Tokenizer.Nodes; - -public class ItalicNode : Node -{ - -} \ No newline at end of file diff --git a/cs/Markdown/Tokenizer/Nodes/MainNode.cs b/cs/Markdown/Tokenizer/Nodes/MainNode.cs deleted file mode 100644 index f1db39b3c..000000000 --- a/cs/Markdown/Tokenizer/Nodes/MainNode.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Markdown.Tokenizer.Nodes; - -public class MainNode : Node -{ - -} \ No newline at end of file diff --git a/cs/Markdown/Tokenizer/Nodes/Node.cs b/cs/Markdown/Tokenizer/Nodes/Node.cs deleted file mode 100644 index 91ddd509a..000000000 --- a/cs/Markdown/Tokenizer/Nodes/Node.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Markdown.Tokenizer.Nodes; - -public abstract class Node -{ - public string? Value { get; set; } - public List Children { get; } = new(); - public Node? Parent { get; set; } -} \ No newline at end of file diff --git a/cs/Markdown/Tokenizer/Nodes/NodeType.cs b/cs/Markdown/Tokenizer/Nodes/NodeType.cs deleted file mode 100644 index 2d70e7319..000000000 --- a/cs/Markdown/Tokenizer/Nodes/NodeType.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Markdown.Tokenizer.Nodes; - -public enum NodeType -{ - Header -} \ No newline at end of file diff --git a/cs/Markdown/Tokenizer/Nodes/TextNode.cs b/cs/Markdown/Tokenizer/Nodes/TextNode.cs deleted file mode 100644 index 0ca76bc6f..000000000 --- a/cs/Markdown/Tokenizer/Nodes/TextNode.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Markdown.Tokenizer.Nodes; - -public class TextNode : Node -{ - -} \ No newline at end of file diff --git a/cs/Markdown/Tokenizer/TagProcessor.cs b/cs/Markdown/Tokenizer/TagProcessor.cs index 8fe8747c4..5f6f9c68e 100644 --- a/cs/Markdown/Tokenizer/TagProcessor.cs +++ b/cs/Markdown/Tokenizer/TagProcessor.cs @@ -6,12 +6,12 @@ public class TagProcessor : ITagProcessor { public void Process(List tags, Stack tagStack) { - ProceedEscaped(tags); - ProceedInWords(tags); - ProceedTags(tagStack); + ProceedEscapedTags(tags); + ProceedInWordsTags(tags); + ProceedPairTags(tagStack); } - private void ProceedInWords(List tags) + private void ProceedInWordsTags(List tags) { for (var i = 0; i < tags.Count; i++) { @@ -42,7 +42,7 @@ private void ProceedInWords(List tags) } } - private void ProceedEscaped(List tags) + private void ProceedEscapedTags(List tags) { for (var i = 0; i < tags.Count - 1; i++) { @@ -64,7 +64,7 @@ private void ProceedEscaped(List tags) } } - private void ProceedTags(Stack tagStack) + private void ProceedPairTags(Stack tagStack) { var tempStack = new Stack(); diff --git a/cs/Markdown/Tokenizer/Tags/TagStatus.cs b/cs/Markdown/Tokenizer/Tags/TagStatus.cs index 4b2830560..525959910 100644 --- a/cs/Markdown/Tokenizer/Tags/TagStatus.cs +++ b/cs/Markdown/Tokenizer/Tags/TagStatus.cs @@ -7,6 +7,5 @@ public enum TagStatus Broken, Escaped, InWord, - Undefined, Single } \ No newline at end of file diff --git a/cs/Markdown/Tokenizer/Tags/Token.cs b/cs/Markdown/Tokenizer/Tags/Token.cs index 44fbfa9d5..21279f838 100644 --- a/cs/Markdown/Tokenizer/Tags/Token.cs +++ b/cs/Markdown/Tokenizer/Tags/Token.cs @@ -1,8 +1,8 @@ namespace Markdown.Tokenizer.Tags; -public abstract class Token +public class Token { - public virtual TagStatus TagStatus { get; set; } + public TagStatus TagStatus { get; set; } public virtual TokenType TokenType { get; } - public string Value { get; set; } + public string Value = string.Empty; } \ No newline at end of file diff --git a/cs/Markdown/Tokenizer/TokenizerContext.cs b/cs/Markdown/Tokenizer/TokenizerContext.cs index 7829feaf5..4b3526e5e 100644 --- a/cs/Markdown/Tokenizer/TokenizerContext.cs +++ b/cs/Markdown/Tokenizer/TokenizerContext.cs @@ -1,14 +1,8 @@ namespace Markdown.Tokenizer; -public class TokenizerContext +public class TokenizerContext(string text) { - private int position; - private readonly string text; - public TokenizerContext(string text) - { - this.text = text; - position = 0; - } + private int position = 0; public bool IsEnd => position >= text.Length; public char Current => text[position]; public int Position => position; diff --git a/cs/Markdown/TreeBuilder/INodeFactory.cs b/cs/Markdown/TreeBuilder/INodeFactory.cs new file mode 100644 index 000000000..48dc44819 --- /dev/null +++ b/cs/Markdown/TreeBuilder/INodeFactory.cs @@ -0,0 +1,8 @@ +using Markdown.Tokenizer.Tags; + +namespace Markdown.TreeBuilder; + +public interface INodeFactory +{ + NodeAction? CreateNode(Token token); +} \ No newline at end of file diff --git a/cs/Markdown/Tokenizer/MarkdownTokenizer.cs b/cs/Markdown/TreeBuilder/MarkdownTokenizer.cs similarity index 89% rename from cs/Markdown/Tokenizer/MarkdownTokenizer.cs rename to cs/Markdown/TreeBuilder/MarkdownTokenizer.cs index 1d30c8779..54d1bb373 100644 --- a/cs/Markdown/Tokenizer/MarkdownTokenizer.cs +++ b/cs/Markdown/TreeBuilder/MarkdownTokenizer.cs @@ -1,14 +1,14 @@ using System.Text; -using Markdown.Tokenizer.Handlers; +using Markdown.Tokenizer; using Markdown.Tokenizer.Tags; using Token = Markdown.Tokenizer.Tags.Token; -namespace Markdown.Tokenizer; +namespace Markdown.TreeBuilder; public class MarkdownTokenizer(IHandlerManager handlerManager, ITagProcessor tagProcessor) : ITokenizer { - private readonly StringBuilder buffer = new(); - private List tags = new(); + private readonly StringBuilder buffer = new(); + private readonly List tags = new(); private readonly Stack tagStack = new(); public List Tokenize(string text) diff --git a/cs/Markdown/TreeBuilder/NodeAction.cs b/cs/Markdown/TreeBuilder/NodeAction.cs new file mode 100644 index 000000000..57eadaa14 --- /dev/null +++ b/cs/Markdown/TreeBuilder/NodeAction.cs @@ -0,0 +1,16 @@ +using Markdown.TreeBuilder.Nodes; + +namespace Markdown.TreeBuilder; + +public abstract class NodeAction +{ + public class OpenNode : NodeAction + { + public Node Node { get; } + public OpenNode(Node node) => Node = node; + } + + public class CloseNode : NodeAction { } + + public class SkipNode : NodeAction { } +} \ No newline at end of file diff --git a/cs/Markdown/TreeBuilder/NodeFactory.cs b/cs/Markdown/TreeBuilder/NodeFactory.cs new file mode 100644 index 000000000..13a99a88d --- /dev/null +++ b/cs/Markdown/TreeBuilder/NodeFactory.cs @@ -0,0 +1,23 @@ +using Markdown.Tokenizer.Tags; +using Markdown.TreeBuilder.Nodes; + +namespace Markdown.TreeBuilder; + +public class NodeFactory : INodeFactory +{ + public NodeAction? CreateNode(Token token) + { + return token switch + { + { TagStatus: TagStatus.Broken } => null, + ItalicTag { TagStatus: TagStatus.Open } => new NodeAction.OpenNode(new ItalicNode()), + ItalicTag { TagStatus: TagStatus.Closed } => new NodeAction.CloseNode(), + BoldTag { TagStatus: TagStatus.Open } => new NodeAction.OpenNode(new BoldNode()), + BoldTag { TagStatus: TagStatus.Closed } => new NodeAction.CloseNode(), + SlashToken { TagStatus: TagStatus.Escaped} => new NodeAction.SkipNode(), + HeaderTag => new NodeAction.OpenNode(new HeaderNode()), + NewLineToken => new NodeAction.SkipNode(), + _ => null + }; + } +} \ No newline at end of file diff --git a/cs/Markdown/TreeBuilder/Nodes/BoldNode.cs b/cs/Markdown/TreeBuilder/Nodes/BoldNode.cs new file mode 100644 index 000000000..c83686c86 --- /dev/null +++ b/cs/Markdown/TreeBuilder/Nodes/BoldNode.cs @@ -0,0 +1,7 @@ +namespace Markdown.TreeBuilder.Nodes; + +public class BoldNode : Node +{ + public override string OpenTag => ""; + public override string CloseTag => ""; +} \ No newline at end of file diff --git a/cs/Markdown/TreeBuilder/Nodes/HeaderNode.cs b/cs/Markdown/TreeBuilder/Nodes/HeaderNode.cs new file mode 100644 index 000000000..f7f608ef5 --- /dev/null +++ b/cs/Markdown/TreeBuilder/Nodes/HeaderNode.cs @@ -0,0 +1,7 @@ +namespace Markdown.TreeBuilder.Nodes; + +public class HeaderNode : Node +{ + public override string OpenTag => "

"; + public override string CloseTag => "

"; +} \ No newline at end of file diff --git a/cs/Markdown/TreeBuilder/Nodes/ItalicNode.cs b/cs/Markdown/TreeBuilder/Nodes/ItalicNode.cs new file mode 100644 index 000000000..fb8caa564 --- /dev/null +++ b/cs/Markdown/TreeBuilder/Nodes/ItalicNode.cs @@ -0,0 +1,7 @@ +namespace Markdown.TreeBuilder.Nodes; + +public class ItalicNode : Node +{ + public override string OpenTag => ""; + public override string CloseTag => ""; +} \ No newline at end of file diff --git a/cs/Markdown/TreeBuilder/Nodes/MainNode.cs b/cs/Markdown/TreeBuilder/Nodes/MainNode.cs new file mode 100644 index 000000000..a67b493e0 --- /dev/null +++ b/cs/Markdown/TreeBuilder/Nodes/MainNode.cs @@ -0,0 +1,4 @@ +namespace Markdown.TreeBuilder.Nodes; + +public class MainNode : Node +{ } \ No newline at end of file diff --git a/cs/Markdown/TreeBuilder/Nodes/Node.cs b/cs/Markdown/TreeBuilder/Nodes/Node.cs new file mode 100644 index 000000000..cafc9062c --- /dev/null +++ b/cs/Markdown/TreeBuilder/Nodes/Node.cs @@ -0,0 +1,10 @@ +namespace Markdown.TreeBuilder.Nodes; + +public abstract class Node +{ + public List Children { get; } = new(); + public Node? Parent { get; set; } + + public virtual string OpenTag => string.Empty; + public virtual string CloseTag => string.Empty; +} \ No newline at end of file diff --git a/cs/Markdown/TreeBuilder/Nodes/TextNode.cs b/cs/Markdown/TreeBuilder/Nodes/TextNode.cs new file mode 100644 index 000000000..62a4c96b2 --- /dev/null +++ b/cs/Markdown/TreeBuilder/Nodes/TextNode.cs @@ -0,0 +1,6 @@ +namespace Markdown.TreeBuilder.Nodes; + +public class TextNode : Node +{ + public string? Value { get; init; } = string.Empty; +} \ No newline at end of file diff --git a/cs/Markdown/TreeBuilder/TreeBuilder.cs b/cs/Markdown/TreeBuilder/TreeBuilder.cs new file mode 100644 index 000000000..f5fbbb44d --- /dev/null +++ b/cs/Markdown/TreeBuilder/TreeBuilder.cs @@ -0,0 +1,42 @@ +using Markdown.Tokenizer.Tags; +using Markdown.TreeBuilder.Nodes; + +namespace Markdown.TreeBuilder; + +public class TreeBuilder(INodeFactory nodeFactory) +{ + public Node Build(List tokens) + { + Node mainNode = new MainNode(); + var currentNode = mainNode; + + foreach (var token in tokens) + { + var nodeAction = nodeFactory.CreateNode(token); + + if (nodeAction == null) + { + currentNode.Children.Add(new TextNode { Value = token.Value }); + continue; + } + + switch (nodeAction) + { + case NodeAction.OpenNode openNode: + currentNode.Children.Add(openNode.Node); + openNode.Node.Parent = currentNode; + currentNode = openNode.Node; + break; + + case NodeAction.CloseNode: + currentNode = currentNode.Parent ?? currentNode; + break; + + case NodeAction.SkipNode: + break; + } + } + + return mainNode; + } +} \ No newline at end of file