From e71ae622084b995444dffed37167dbf5e11f79ee Mon Sep 17 00:00:00 2001 From: Braidenbikher Victor Date: Mon, 25 Nov 2024 16:01:04 +0500 Subject: [PATCH 01/13] =?UTF-8?q?=D0=A1=D0=BE=D0=B7=D0=B4=D0=B0=D0=BB=20?= =?UTF-8?q?=D1=81=D1=82=D1=80=D1=83=D0=BA=D1=82=D1=83=D1=80=D1=83=20=D0=BF?= =?UTF-8?q?=D1=80=D0=BE=D0=B5=D0=BA=D1=82=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cs/Markdown/Markdown.csproj | 9 ++++ .../ConcreteMarkdownRenders/HtmlRender.cs | 17 ++++++++ .../MarkdownRenders/IMarkdownRender.cs | 14 +++++++ cs/Markdown/Md.cs | 36 ++++++++++++++++ cs/Markdown/Parsers/AstNode.cs | 32 +++++++++++++++ .../ConcreteMarkdownParsers/MarkdownParser.cs | 26 ++++++++++++ cs/Markdown/Parsers/IMarkdownParser.cs | 14 +++++++ cs/Markdown/Parsers/TokenExtensions.cs | 22 ++++++++++ cs/Markdown/Tags/ConcreteTags/DocumentTag.cs | 15 +++++++ cs/Markdown/Tags/ConcreteTags/TextTag.cs | 15 +++++++ cs/Markdown/Tags/Tag.cs | 41 +++++++++++++++++++ cs/Markdown/Tags/TagType.cs | 19 +++++++++ .../ConcreteTokenizers/Tokenizer.cs | 20 +++++++++ cs/Markdown/TokenizerClasses/ITokenizer.cs | 14 +++++++ cs/Markdown/TokenizerClasses/TokenFactory.cs | 17 ++++++++ .../Tokens/ConcreteTokens/ConcreteTokens.cs | 10 +++++ cs/Markdown/Tokens/Token.cs | 23 +++++++++++ cs/Markdown/Tokens/TokenType.cs | 22 ++++++++++ cs/Markdown_Tests/MarkdownParser_Tests.cs | 23 +++++++++++ cs/Markdown_Tests/MarkdownRenderHtml_Tests.cs | 26 ++++++++++++ cs/Markdown_Tests/MarkdownTests.csproj | 29 +++++++++++++ cs/Markdown_Tests/Tokenizer_Tests.cs | 23 +++++++++++ cs/clean-code.sln | 28 ++++++++++--- 23 files changed, 490 insertions(+), 5 deletions(-) create mode 100644 cs/Markdown/Markdown.csproj create mode 100644 cs/Markdown/MarkdownRenders/ConcreteMarkdownRenders/HtmlRender.cs create mode 100644 cs/Markdown/MarkdownRenders/IMarkdownRender.cs create mode 100644 cs/Markdown/Md.cs create mode 100644 cs/Markdown/Parsers/AstNode.cs create mode 100644 cs/Markdown/Parsers/ConcreteMarkdownParsers/MarkdownParser.cs create mode 100644 cs/Markdown/Parsers/IMarkdownParser.cs create mode 100644 cs/Markdown/Parsers/TokenExtensions.cs create mode 100644 cs/Markdown/Tags/ConcreteTags/DocumentTag.cs create mode 100644 cs/Markdown/Tags/ConcreteTags/TextTag.cs create mode 100644 cs/Markdown/Tags/Tag.cs create mode 100644 cs/Markdown/Tags/TagType.cs create mode 100644 cs/Markdown/TokenizerClasses/ConcreteTokenizers/Tokenizer.cs create mode 100644 cs/Markdown/TokenizerClasses/ITokenizer.cs create mode 100644 cs/Markdown/TokenizerClasses/TokenFactory.cs create mode 100644 cs/Markdown/Tokens/ConcreteTokens/ConcreteTokens.cs create mode 100644 cs/Markdown/Tokens/Token.cs create mode 100644 cs/Markdown/Tokens/TokenType.cs create mode 100644 cs/Markdown_Tests/MarkdownParser_Tests.cs create mode 100644 cs/Markdown_Tests/MarkdownRenderHtml_Tests.cs create mode 100644 cs/Markdown_Tests/MarkdownTests.csproj create mode 100644 cs/Markdown_Tests/Tokenizer_Tests.cs diff --git a/cs/Markdown/Markdown.csproj b/cs/Markdown/Markdown.csproj new file mode 100644 index 000000000..fa71b7ae6 --- /dev/null +++ b/cs/Markdown/Markdown.csproj @@ -0,0 +1,9 @@ + + + + net8.0 + enable + enable + + + diff --git a/cs/Markdown/MarkdownRenders/ConcreteMarkdownRenders/HtmlRender.cs b/cs/Markdown/MarkdownRenders/ConcreteMarkdownRenders/HtmlRender.cs new file mode 100644 index 000000000..c1d542c62 --- /dev/null +++ b/cs/Markdown/MarkdownRenders/ConcreteMarkdownRenders/HtmlRender.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Markdown.Parsers; + +namespace Markdown.MarkdownRenders.ConcreteMarkdownRenders +{ + public class HtmlRender : IMarkdownRender + { + public string Render(AstNode root) + { + throw new NotImplementedException(); + } + } +} diff --git a/cs/Markdown/MarkdownRenders/IMarkdownRender.cs b/cs/Markdown/MarkdownRenders/IMarkdownRender.cs new file mode 100644 index 000000000..9bb55b67e --- /dev/null +++ b/cs/Markdown/MarkdownRenders/IMarkdownRender.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Markdown.Parsers; + +namespace Markdown.MarkdownRenders +{ + public interface IMarkdownRender + { + public string Render(AstNode root); + } +} diff --git a/cs/Markdown/Md.cs b/cs/Markdown/Md.cs new file mode 100644 index 000000000..c3db84e12 --- /dev/null +++ b/cs/Markdown/Md.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Markdown.MarkdownRenders; +using Markdown.Parsers; +using Markdown.TokenizerClasses; +using Markdown.TokenizerClasses.ConcreteTokenizers; + +namespace Markdown +{ + public class Md + { + private readonly IMarkdownParser markdownParser; + + private readonly ITokenizer markdownTokenizer; + + private readonly IMarkdownRender render; + + public Md(IMarkdownParser parser, ITokenizer tokenizer, IMarkdownRender render) + { + markdownParser = parser; + markdownTokenizer = tokenizer; + this.render = render; + } + + public string Render(string text) + { + var tokens = markdownTokenizer.Tokenize(text); + var rootNode = markdownParser.Parse(tokens); + + return render.Render(rootNode); + } + } +} diff --git a/cs/Markdown/Parsers/AstNode.cs b/cs/Markdown/Parsers/AstNode.cs new file mode 100644 index 000000000..20075b02e --- /dev/null +++ b/cs/Markdown/Parsers/AstNode.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Markdown.Tags; +using Markdown.Tokens; + +namespace Markdown.Parsers +{ + public class AstNode + { + public readonly AstNode ParentToken; + + public readonly List ChildsTokens; + + public Tag Tag; + + public AstNode(AstNode parent, Tag tag) + { + ParentToken = parent; + ParentToken.ChildsTokens.Add(this); + + Tag = tag; + } + + public void AddChild(AstNode child) + { + ChildsTokens.Add(child); + } + } +} diff --git a/cs/Markdown/Parsers/ConcreteMarkdownParsers/MarkdownParser.cs b/cs/Markdown/Parsers/ConcreteMarkdownParsers/MarkdownParser.cs new file mode 100644 index 000000000..2fb371cb3 --- /dev/null +++ b/cs/Markdown/Parsers/ConcreteMarkdownParsers/MarkdownParser.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Markdown.Tags; +using Markdown.Tags.ConcreteTags; +using Markdown.Tokens; + +namespace Markdown.Parsers.ConcreteMarkdownParsers +{ + public class MarkdownParser : IMarkdownParser + { + + public AstNode Parse(List tokens) + { + var root = new AstNode(null, new DocumentTag()); + + //Некая логика + + return root; + } + } +} diff --git a/cs/Markdown/Parsers/IMarkdownParser.cs b/cs/Markdown/Parsers/IMarkdownParser.cs new file mode 100644 index 000000000..17c0f2334 --- /dev/null +++ b/cs/Markdown/Parsers/IMarkdownParser.cs @@ -0,0 +1,14 @@ +using Markdown.Tokens; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Markdown.Parsers +{ + public interface IMarkdownParser + { + public AstNode Parse(List tokens); + } +} diff --git a/cs/Markdown/Parsers/TokenExtensions.cs b/cs/Markdown/Parsers/TokenExtensions.cs new file mode 100644 index 000000000..92334ec2e --- /dev/null +++ b/cs/Markdown/Parsers/TokenExtensions.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Markdown.Tokens; + +namespace Markdown.Parsers +{ + public static class TokenExtensions + { + public static bool IsHash(this Token token) + { + throw new NotImplementedException(); + } + + public static bool IsWhiteSpace(this Token token) + { + throw new NotImplementedException(); + } + } +} diff --git a/cs/Markdown/Tags/ConcreteTags/DocumentTag.cs b/cs/Markdown/Tags/ConcreteTags/DocumentTag.cs new file mode 100644 index 000000000..16ca76554 --- /dev/null +++ b/cs/Markdown/Tags/ConcreteTags/DocumentTag.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Markdown.Tags.ConcreteTags +{ + public class DocumentTag : Tag + { + public DocumentTag() : base(TagType.Document, String.Empty, true, 0) + { + } + } +} diff --git a/cs/Markdown/Tags/ConcreteTags/TextTag.cs b/cs/Markdown/Tags/ConcreteTags/TextTag.cs new file mode 100644 index 000000000..c1eb3bd9f --- /dev/null +++ b/cs/Markdown/Tags/ConcreteTags/TextTag.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Markdown.Tags.ConcreteTags +{ + public class TextTag : Tag + { + public TextTag(string content) : base(TagType.Text, content, true, 0) + { + } + } +} diff --git a/cs/Markdown/Tags/Tag.cs b/cs/Markdown/Tags/Tag.cs new file mode 100644 index 000000000..ba6e9c2f6 --- /dev/null +++ b/cs/Markdown/Tags/Tag.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Markdown.Tags.ConcreteTags; + +namespace Markdown.Tags +{ + public abstract class Tag + { + public bool IsCompleted { get; set; } + + public bool SelfCompeted { get; protected set; } + + public TagType TagType { get; protected set; } + + public readonly string Content; + + public readonly int level; + + public Tag(TagType tagType, string content, bool selfCompleted, int level = 0) + { + TagType = tagType; + Content = content; + this.level = level; + Content = string.Empty; + + SelfCompeted = selfCompleted; + IsCompleted = false; + } + + public Tag CheckCompleted() + { + if (IsCompleted || SelfCompeted) + return this; + + return new TextTag(this.Content); + } + } +} diff --git a/cs/Markdown/Tags/TagType.cs b/cs/Markdown/Tags/TagType.cs new file mode 100644 index 000000000..39d487402 --- /dev/null +++ b/cs/Markdown/Tags/TagType.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Markdown.Tags +{ + public enum TagType + { + Document, + Header, + Italic, + Bold, + Paragraph, + BulletedList, + Text + } +} diff --git a/cs/Markdown/TokenizerClasses/ConcreteTokenizers/Tokenizer.cs b/cs/Markdown/TokenizerClasses/ConcreteTokenizers/Tokenizer.cs new file mode 100644 index 000000000..6a50284fd --- /dev/null +++ b/cs/Markdown/TokenizerClasses/ConcreteTokenizers/Tokenizer.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Cryptography.X509Certificates; +using System.Text; +using System.Threading.Tasks; +using System.Xml; +using Markdown.Tags; +using Markdown.Tokens; + +namespace Markdown.TokenizerClasses.ConcreteTokenizers +{ + public class Tokenizer : ITokenizer + { + public List Tokenize(string text) + { + throw new NotImplementedException(); + } + } +} diff --git a/cs/Markdown/TokenizerClasses/ITokenizer.cs b/cs/Markdown/TokenizerClasses/ITokenizer.cs new file mode 100644 index 000000000..6e4d332c3 --- /dev/null +++ b/cs/Markdown/TokenizerClasses/ITokenizer.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Markdown.Tokens; + +namespace Markdown.TokenizerClasses +{ + public interface ITokenizer + { + public List Tokenize(string text); + } +} diff --git a/cs/Markdown/TokenizerClasses/TokenFactory.cs b/cs/Markdown/TokenizerClasses/TokenFactory.cs new file mode 100644 index 000000000..f030c2e9e --- /dev/null +++ b/cs/Markdown/TokenizerClasses/TokenFactory.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Markdown.Tokens; + +namespace Markdown.TokenizerClasses +{ + public class TokenFactory + { + public static Token CreateSingleCharacterToken(string ch) + { + throw new NotImplementedException(); + } + } +} diff --git a/cs/Markdown/Tokens/ConcreteTokens/ConcreteTokens.cs b/cs/Markdown/Tokens/ConcreteTokens/ConcreteTokens.cs new file mode 100644 index 000000000..06fb6c287 --- /dev/null +++ b/cs/Markdown/Tokens/ConcreteTokens/ConcreteTokens.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Markdown.Tokens.ConcreteTokens +{ + // Сюда думаю можно прописать конкретные классы токенов, наследников Token +} diff --git a/cs/Markdown/Tokens/Token.cs b/cs/Markdown/Tokens/Token.cs new file mode 100644 index 000000000..74ef4ff77 --- /dev/null +++ b/cs/Markdown/Tokens/Token.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Markdown.Tags; + +namespace Markdown.Tokens +{ + public class Token + { + public readonly TokenType TokenType; + + public readonly string Content; + + public Token(TokenType tokenType, string content) + { + TokenType = tokenType; + + Content = content; + } + } +} diff --git a/cs/Markdown/Tokens/TokenType.cs b/cs/Markdown/Tokens/TokenType.cs new file mode 100644 index 000000000..c939c60c3 --- /dev/null +++ b/cs/Markdown/Tokens/TokenType.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Markdown.Tokens +{ + public enum TokenType + { + WhiteSpace, + BreakLine, + Escape, + Em_start, + Em_end, + Bold_start, + Bold_end, + Hash, + Text, + Number + } +} diff --git a/cs/Markdown_Tests/MarkdownParser_Tests.cs b/cs/Markdown_Tests/MarkdownParser_Tests.cs new file mode 100644 index 000000000..47480929c --- /dev/null +++ b/cs/Markdown_Tests/MarkdownParser_Tests.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MarkdownTests +{ + public class MarkdownParser_Tests + { + [SetUp] + public void Setup() + { + + } + + [Test] + public void Test1() + { + Assert.Pass(); + } + } +} diff --git a/cs/Markdown_Tests/MarkdownRenderHtml_Tests.cs b/cs/Markdown_Tests/MarkdownRenderHtml_Tests.cs new file mode 100644 index 000000000..58c42a0bd --- /dev/null +++ b/cs/Markdown_Tests/MarkdownRenderHtml_Tests.cs @@ -0,0 +1,26 @@ +using Markdown.TokenizerClasses; +using Markdown; +using Markdown.MarkdownRenders.ConcreteMarkdownRenders; +using Markdown.Parsers.ConcreteMarkdownParsers; +using Markdown.TokenizerClasses.ConcreteTokenizers; + + +namespace Markdown_Tests +{ + public class MarkdownRenderHtmlTests + { + private Md markdown = new Md(new MarkdownParser(), new Tokenizer(), new HtmlRender()); + + [SetUp] + public void Setup() + { + + } + + [Test] + public void Test1() + { + Assert.Pass(); + } + } +} \ No newline at end of file diff --git a/cs/Markdown_Tests/MarkdownTests.csproj b/cs/Markdown_Tests/MarkdownTests.csproj new file mode 100644 index 000000000..25f0ec504 --- /dev/null +++ b/cs/Markdown_Tests/MarkdownTests.csproj @@ -0,0 +1,29 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + + + + + + + + + + + + + diff --git a/cs/Markdown_Tests/Tokenizer_Tests.cs b/cs/Markdown_Tests/Tokenizer_Tests.cs new file mode 100644 index 000000000..ab886e4e9 --- /dev/null +++ b/cs/Markdown_Tests/Tokenizer_Tests.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using FluentAssertions; +using Markdown.TokenizerClasses; +using Markdown.TokenizerClasses.ConcreteTokenizers; +using Markdown.Tokens; + +namespace MarkdownTests +{ + public class TokenizerTests + { + private Tokenizer tokenizer = new Tokenizer(); + + [Test] + public void Tokenize_ThrowArgumentNullException_WhenArgumentIsNull() + { + Assert.Throws(() => tokenizer.Tokenize(null), "String argument text must be not null"); + } + } +} diff --git a/cs/clean-code.sln b/cs/clean-code.sln index 2206d54db..0cb047aea 100644 --- a/cs/clean-code.sln +++ b/cs/clean-code.sln @@ -1,13 +1,17 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 14 -VisualStudioVersion = 14.0.25420.1 +# Visual Studio Version 17 +VisualStudioVersion = 17.11.35327.3 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Chess", "Chess\Chess.csproj", "{DBFBE40E-EE0C-48F4-8763-EBD11C960081}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Chess", "Chess\Chess.csproj", "{DBFBE40E-EE0C-48F4-8763-EBD11C960081}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ControlDigit", "ControlDigit\ControlDigit.csproj", "{B06A4B35-9D61-4A63-9167-0673F20CA989}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ControlDigit", "ControlDigit\ControlDigit.csproj", "{B06A4B35-9D61-4A63-9167-0673F20CA989}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Samples", "Samples\Samples.csproj", "{C3EF41D7-50EF-4CE1-B30A-D1D81C93D7FA}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Samples", "Samples\Samples.csproj", "{C3EF41D7-50EF-4CE1-B30A-D1D81C93D7FA}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Markdown", "Markdown\Markdown.csproj", "{3C26D892-E2EE-47D5-811C-1915F72BEA50}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MarkdownTests", "Markdown_Tests\MarkdownTests.csproj", "{7D3EFA76-EEF6-49DF-BF0D-6688D14E1341}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -27,5 +31,19 @@ Global {C3EF41D7-50EF-4CE1-B30A-D1D81C93D7FA}.Debug|Any CPU.Build.0 = Debug|Any CPU {C3EF41D7-50EF-4CE1-B30A-D1D81C93D7FA}.Release|Any CPU.ActiveCfg = Release|Any CPU {C3EF41D7-50EF-4CE1-B30A-D1D81C93D7FA}.Release|Any CPU.Build.0 = Release|Any CPU + {3C26D892-E2EE-47D5-811C-1915F72BEA50}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3C26D892-E2EE-47D5-811C-1915F72BEA50}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3C26D892-E2EE-47D5-811C-1915F72BEA50}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3C26D892-E2EE-47D5-811C-1915F72BEA50}.Release|Any CPU.Build.0 = Release|Any CPU + {7D3EFA76-EEF6-49DF-BF0D-6688D14E1341}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7D3EFA76-EEF6-49DF-BF0D-6688D14E1341}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7D3EFA76-EEF6-49DF-BF0D-6688D14E1341}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7D3EFA76-EEF6-49DF-BF0D-6688D14E1341}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {4980313C-A4CA-4D98-A626-19568120E067} EndGlobalSection EndGlobal From 65076d56de06438026917ec11c465e692dd0dbe1 Mon Sep 17 00:00:00 2001 From: Braidenbikher Victor Date: Thu, 28 Nov 2024 00:32:40 +0500 Subject: [PATCH 02/13] =?UTF-8?q?=D0=9F=D0=B5=D1=80=D0=B5=D0=B4=D0=B5?= =?UTF-8?q?=D0=BB=D0=B0=D0=BB=20=D1=81=D1=82=D1=80=D1=83=D0=BA=D1=82=D1=83?= =?UTF-8?q?=D1=80=D1=83=20=D0=B8=20=D0=BB=D0=BE=D0=B3=D0=B8=D0=BA=D1=83=20?= =?UTF-8?q?=D0=B1=D0=B8=D0=B1=D0=BB=D0=B8=D0=BE=D1=82=D0=B5=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ConcreteConverter/HtmlConverter.cs | 26 +++ cs/Markdown/Converter/IConverter.cs | 7 + cs/Markdown/Extensions/StringExtensions.cs | 8 + .../ConcreteMarkdownRenders/HtmlRender.cs | 17 -- .../MarkdownRenders/IMarkdownRender.cs | 14 -- cs/Markdown/Md.cs | 32 ++-- cs/Markdown/ParsedLine.cs | 16 ++ cs/Markdown/Parsers/AstNode.cs | 32 ---- .../ConcreteMarkdownParsers/MarkdownParser.cs | 26 --- cs/Markdown/Parsers/IMarkdownParser.cs | 14 -- cs/Markdown/Parsers/TokenExtensions.cs | 22 --- cs/Markdown/Tags/ConcreteTags/BoldTag.cs | 9 + cs/Markdown/Tags/ConcreteTags/DocumentTag.cs | 15 -- cs/Markdown/Tags/ConcreteTags/HeaderTag.cs | 9 + cs/Markdown/Tags/ConcreteTags/ItalicTag.cs | 9 + cs/Markdown/Tags/ConcreteTags/TextTag.cs | 15 -- cs/Markdown/Tags/Tag.cs | 39 +---- cs/Markdown/Tags/TagType.cs | 13 +- .../TokenParser/ConcreteParser/LineParser.cs | 52 ++++++ .../TokenParser/Helpers/TokenGenerator.cs | 34 ++++ .../TokenParser/Helpers/TokenValidator.cs | 23 +++ cs/Markdown/TokenParser/ILineParser.cs | 7 + .../ConcreteTokenizers/Tokenizer.cs | 20 --- cs/Markdown/TokenizerClasses/ITokenizer.cs | 14 -- cs/Markdown/TokenizerClasses/TokenFactory.cs | 17 -- .../Tokens/ConcreteTokens/ConcreteTokens.cs | 10 -- cs/Markdown/Tokens/Token.cs | 19 ++- cs/Markdown/Tokens/TokenType.cs | 21 +-- cs/Markdown_Tests/LineParser_Tests.cs | 15 ++ cs/Markdown_Tests/MarkdownParser_Tests.cs | 23 --- cs/Markdown_Tests/MarkdownRenderHtml_Tests.cs | 26 --- cs/Markdown_Tests/Md_Tests.cs | 154 ++++++++++++++++++ cs/Markdown_Tests/Tokenizer_Tests.cs | 23 --- 33 files changed, 405 insertions(+), 376 deletions(-) create mode 100644 cs/Markdown/Converter/ConcreteConverter/HtmlConverter.cs create mode 100644 cs/Markdown/Converter/IConverter.cs create mode 100644 cs/Markdown/Extensions/StringExtensions.cs delete mode 100644 cs/Markdown/MarkdownRenders/ConcreteMarkdownRenders/HtmlRender.cs delete mode 100644 cs/Markdown/MarkdownRenders/IMarkdownRender.cs create mode 100644 cs/Markdown/ParsedLine.cs delete mode 100644 cs/Markdown/Parsers/AstNode.cs delete mode 100644 cs/Markdown/Parsers/ConcreteMarkdownParsers/MarkdownParser.cs delete mode 100644 cs/Markdown/Parsers/IMarkdownParser.cs delete mode 100644 cs/Markdown/Parsers/TokenExtensions.cs create mode 100644 cs/Markdown/Tags/ConcreteTags/BoldTag.cs delete mode 100644 cs/Markdown/Tags/ConcreteTags/DocumentTag.cs create mode 100644 cs/Markdown/Tags/ConcreteTags/HeaderTag.cs create mode 100644 cs/Markdown/Tags/ConcreteTags/ItalicTag.cs delete mode 100644 cs/Markdown/Tags/ConcreteTags/TextTag.cs create mode 100644 cs/Markdown/TokenParser/ConcreteParser/LineParser.cs create mode 100644 cs/Markdown/TokenParser/Helpers/TokenGenerator.cs create mode 100644 cs/Markdown/TokenParser/Helpers/TokenValidator.cs create mode 100644 cs/Markdown/TokenParser/ILineParser.cs delete mode 100644 cs/Markdown/TokenizerClasses/ConcreteTokenizers/Tokenizer.cs delete mode 100644 cs/Markdown/TokenizerClasses/ITokenizer.cs delete mode 100644 cs/Markdown/TokenizerClasses/TokenFactory.cs delete mode 100644 cs/Markdown/Tokens/ConcreteTokens/ConcreteTokens.cs create mode 100644 cs/Markdown_Tests/LineParser_Tests.cs delete mode 100644 cs/Markdown_Tests/MarkdownParser_Tests.cs delete mode 100644 cs/Markdown_Tests/MarkdownRenderHtml_Tests.cs create mode 100644 cs/Markdown_Tests/Md_Tests.cs delete mode 100644 cs/Markdown_Tests/Tokenizer_Tests.cs diff --git a/cs/Markdown/Converter/ConcreteConverter/HtmlConverter.cs b/cs/Markdown/Converter/ConcreteConverter/HtmlConverter.cs new file mode 100644 index 000000000..c50224eca --- /dev/null +++ b/cs/Markdown/Converter/ConcreteConverter/HtmlConverter.cs @@ -0,0 +1,26 @@ +using Markdown.Tags; + +namespace Markdown.Converter.ConcreteConverter +{ + public class HtmlConverter : IConverter + { + private static readonly Dictionary startTags = new() + { + { TagType.Bold, "" }, + { TagType.Italic, "" }, + { TagType.Header, "

" }, + }; + + private static readonly Dictionary closeTags = new() + { + { TagType.Bold, "" }, + { TagType.Italic, "" }, + { TagType.Header, "

" } + }; + + public string Convert(ParsedLine[] parsedLines) + { + throw new NotImplementedException(); + } + } +} diff --git a/cs/Markdown/Converter/IConverter.cs b/cs/Markdown/Converter/IConverter.cs new file mode 100644 index 000000000..9def0b7c0 --- /dev/null +++ b/cs/Markdown/Converter/IConverter.cs @@ -0,0 +1,7 @@ +namespace Markdown.Converter +{ + public interface IConverter + { + public string Convert(ParsedLine[] parsedLines); + } +} diff --git a/cs/Markdown/Extensions/StringExtensions.cs b/cs/Markdown/Extensions/StringExtensions.cs new file mode 100644 index 000000000..2f5ef1cd3 --- /dev/null +++ b/cs/Markdown/Extensions/StringExtensions.cs @@ -0,0 +1,8 @@ +namespace Markdown.Extensions +{ + public static class StringExtensions + { + public static string[] SplitIntoLines(this string text) + => text.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None); + } +} diff --git a/cs/Markdown/MarkdownRenders/ConcreteMarkdownRenders/HtmlRender.cs b/cs/Markdown/MarkdownRenders/ConcreteMarkdownRenders/HtmlRender.cs deleted file mode 100644 index c1d542c62..000000000 --- a/cs/Markdown/MarkdownRenders/ConcreteMarkdownRenders/HtmlRender.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Markdown.Parsers; - -namespace Markdown.MarkdownRenders.ConcreteMarkdownRenders -{ - public class HtmlRender : IMarkdownRender - { - public string Render(AstNode root) - { - throw new NotImplementedException(); - } - } -} diff --git a/cs/Markdown/MarkdownRenders/IMarkdownRender.cs b/cs/Markdown/MarkdownRenders/IMarkdownRender.cs deleted file mode 100644 index 9bb55b67e..000000000 --- a/cs/Markdown/MarkdownRenders/IMarkdownRender.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Markdown.Parsers; - -namespace Markdown.MarkdownRenders -{ - public interface IMarkdownRender - { - public string Render(AstNode root); - } -} diff --git a/cs/Markdown/Md.cs b/cs/Markdown/Md.cs index c3db84e12..9e7638c3c 100644 --- a/cs/Markdown/Md.cs +++ b/cs/Markdown/Md.cs @@ -1,36 +1,26 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Markdown.MarkdownRenders; -using Markdown.Parsers; -using Markdown.TokenizerClasses; -using Markdown.TokenizerClasses.ConcreteTokenizers; +using Markdown.Converter; +using Markdown.Extensions; +using Markdown.TokenParser; namespace Markdown { public class Md { - private readonly IMarkdownParser markdownParser; + private readonly ILineParser markdownTokenizer; - private readonly ITokenizer markdownTokenizer; + private readonly IConverter converter; - private readonly IMarkdownRender render; - - public Md(IMarkdownParser parser, ITokenizer tokenizer, IMarkdownRender render) + public Md(ILineParser tokenizer, IConverter converter) { - markdownParser = parser; markdownTokenizer = tokenizer; - this.render = render; + this.converter = converter; } - public string Render(string text) + public string Render(string mdString) { - var tokens = markdownTokenizer.Tokenize(text); - var rootNode = markdownParser.Parse(tokens); - - return render.Render(rootNode); + return converter.Convert(mdString.SplitIntoLines() + .Select(markdownTokenizer.ParseLine) + .ToArray()); } } } diff --git a/cs/Markdown/ParsedLine.cs b/cs/Markdown/ParsedLine.cs new file mode 100644 index 000000000..7ce3966e3 --- /dev/null +++ b/cs/Markdown/ParsedLine.cs @@ -0,0 +1,16 @@ +using Markdown.Tags; + +namespace Markdown +{ + public class ParsedLine + { + public readonly string Line; + + public readonly List Tags; + + public ParsedLine(string line, List tags) + { + throw new NotImplementedException(); + } + } +} diff --git a/cs/Markdown/Parsers/AstNode.cs b/cs/Markdown/Parsers/AstNode.cs deleted file mode 100644 index 20075b02e..000000000 --- a/cs/Markdown/Parsers/AstNode.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Markdown.Tags; -using Markdown.Tokens; - -namespace Markdown.Parsers -{ - public class AstNode - { - public readonly AstNode ParentToken; - - public readonly List ChildsTokens; - - public Tag Tag; - - public AstNode(AstNode parent, Tag tag) - { - ParentToken = parent; - ParentToken.ChildsTokens.Add(this); - - Tag = tag; - } - - public void AddChild(AstNode child) - { - ChildsTokens.Add(child); - } - } -} diff --git a/cs/Markdown/Parsers/ConcreteMarkdownParsers/MarkdownParser.cs b/cs/Markdown/Parsers/ConcreteMarkdownParsers/MarkdownParser.cs deleted file mode 100644 index 2fb371cb3..000000000 --- a/cs/Markdown/Parsers/ConcreteMarkdownParsers/MarkdownParser.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Markdown.Tags; -using Markdown.Tags.ConcreteTags; -using Markdown.Tokens; - -namespace Markdown.Parsers.ConcreteMarkdownParsers -{ - public class MarkdownParser : IMarkdownParser - { - - public AstNode Parse(List tokens) - { - var root = new AstNode(null, new DocumentTag()); - - //Некая логика - - return root; - } - } -} diff --git a/cs/Markdown/Parsers/IMarkdownParser.cs b/cs/Markdown/Parsers/IMarkdownParser.cs deleted file mode 100644 index 17c0f2334..000000000 --- a/cs/Markdown/Parsers/IMarkdownParser.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Markdown.Tokens; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Markdown.Parsers -{ - public interface IMarkdownParser - { - public AstNode Parse(List tokens); - } -} diff --git a/cs/Markdown/Parsers/TokenExtensions.cs b/cs/Markdown/Parsers/TokenExtensions.cs deleted file mode 100644 index 92334ec2e..000000000 --- a/cs/Markdown/Parsers/TokenExtensions.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Markdown.Tokens; - -namespace Markdown.Parsers -{ - public static class TokenExtensions - { - public static bool IsHash(this Token token) - { - throw new NotImplementedException(); - } - - public static bool IsWhiteSpace(this Token token) - { - throw new NotImplementedException(); - } - } -} diff --git a/cs/Markdown/Tags/ConcreteTags/BoldTag.cs b/cs/Markdown/Tags/ConcreteTags/BoldTag.cs new file mode 100644 index 000000000..9f56a52b0 --- /dev/null +++ b/cs/Markdown/Tags/ConcreteTags/BoldTag.cs @@ -0,0 +1,9 @@ +namespace Markdown.Tags.ConcreteTags +{ + public class BoldTag : Tag + { + public BoldTag(int position, bool isCloseTag) : base(TagType.Bold, position, isCloseTag) + { + } + } +} diff --git a/cs/Markdown/Tags/ConcreteTags/DocumentTag.cs b/cs/Markdown/Tags/ConcreteTags/DocumentTag.cs deleted file mode 100644 index 16ca76554..000000000 --- a/cs/Markdown/Tags/ConcreteTags/DocumentTag.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Markdown.Tags.ConcreteTags -{ - public class DocumentTag : Tag - { - public DocumentTag() : base(TagType.Document, String.Empty, true, 0) - { - } - } -} diff --git a/cs/Markdown/Tags/ConcreteTags/HeaderTag.cs b/cs/Markdown/Tags/ConcreteTags/HeaderTag.cs new file mode 100644 index 000000000..2fe3af625 --- /dev/null +++ b/cs/Markdown/Tags/ConcreteTags/HeaderTag.cs @@ -0,0 +1,9 @@ +namespace Markdown.Tags.ConcreteTags +{ + public class HeaderTag : Tag + { + public HeaderTag(int position, bool isCloseTag) : base(TagType.Header, position, isCloseTag) + { + } + } +} diff --git a/cs/Markdown/Tags/ConcreteTags/ItalicTag.cs b/cs/Markdown/Tags/ConcreteTags/ItalicTag.cs new file mode 100644 index 000000000..39b13c9e4 --- /dev/null +++ b/cs/Markdown/Tags/ConcreteTags/ItalicTag.cs @@ -0,0 +1,9 @@ +namespace Markdown.Tags.ConcreteTags +{ + public class ItalicTag : Tag + { + public ItalicTag(int position, bool isCloseTag) : base(TagType.Italic, position, isCloseTag) + { + } + } +} diff --git a/cs/Markdown/Tags/ConcreteTags/TextTag.cs b/cs/Markdown/Tags/ConcreteTags/TextTag.cs deleted file mode 100644 index c1eb3bd9f..000000000 --- a/cs/Markdown/Tags/ConcreteTags/TextTag.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Markdown.Tags.ConcreteTags -{ - public class TextTag : Tag - { - public TextTag(string content) : base(TagType.Text, content, true, 0) - { - } - } -} diff --git a/cs/Markdown/Tags/Tag.cs b/cs/Markdown/Tags/Tag.cs index ba6e9c2f6..0e89ef17e 100644 --- a/cs/Markdown/Tags/Tag.cs +++ b/cs/Markdown/Tags/Tag.cs @@ -1,41 +1,16 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Markdown.Tags.ConcreteTags; - -namespace Markdown.Tags +namespace Markdown.Tags { public abstract class Tag { - public bool IsCompleted { get; set; } - - public bool SelfCompeted { get; protected set; } - - public TagType TagType { get; protected set; } - - public readonly string Content; - - public readonly int level; + public TagType TagType { get; } + public int Position { get; } + public bool IsCloseTag { get; } - public Tag(TagType tagType, string content, bool selfCompleted, int level = 0) + public Tag(TagType tagType, int position, bool isCloseTag) { TagType = tagType; - Content = content; - this.level = level; - Content = string.Empty; - - SelfCompeted = selfCompleted; - IsCompleted = false; - } - - public Tag CheckCompleted() - { - if (IsCompleted || SelfCompeted) - return this; - - return new TextTag(this.Content); + Position = position; + IsCloseTag = isCloseTag; } } } diff --git a/cs/Markdown/Tags/TagType.cs b/cs/Markdown/Tags/TagType.cs index 39d487402..334b9cf5d 100644 --- a/cs/Markdown/Tags/TagType.cs +++ b/cs/Markdown/Tags/TagType.cs @@ -1,19 +1,12 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Markdown.Tags +namespace Markdown.Tags { public enum TagType { - Document, Header, Italic, Bold, - Paragraph, BulletedList, - Text + Escape, + UnDefined } } diff --git a/cs/Markdown/TokenParser/ConcreteParser/LineParser.cs b/cs/Markdown/TokenParser/ConcreteParser/LineParser.cs new file mode 100644 index 000000000..ee1b71857 --- /dev/null +++ b/cs/Markdown/TokenParser/ConcreteParser/LineParser.cs @@ -0,0 +1,52 @@ +using Markdown.Tags; +using Markdown.Tokens; + +namespace Markdown.TokenParser.ConcreteParser +{ + public class LineParser : ILineParser + { + public ParsedLine ParseLine(string line) + { + throw new NotImplementedException(); + } + + private List GetTokens(string line) + { + throw new NotImplementedException(); + } + + private Tag GetNewTag(Token token, int position) + { + throw new NotImplementedException(); + } + + private List EscapeTags(List tokens) + { + throw new NotImplementedException(); + } + + private List EscapeInvalidTokens(List tokens) => + throw new NotImplementedException(); + + private List EscapeNonPairTokens(List tokens) + { + throw new NotImplementedException(); + } + + private void SolveOpenAndCloseTags(Stack openTags, List tokens, int openIndex, + int closeIndex, List incorrectTags) + { + throw new NotImplementedException(); + } + + private List EscapeWrongOrder(List tokens) + { + throw new NotImplementedException(); + } + + private ParsedLine GetTagsAndCleanText(List tokens) + { + throw new NotImplementedException(); + } + } +} diff --git a/cs/Markdown/TokenParser/Helpers/TokenGenerator.cs b/cs/Markdown/TokenParser/Helpers/TokenGenerator.cs new file mode 100644 index 000000000..c9208e592 --- /dev/null +++ b/cs/Markdown/TokenParser/Helpers/TokenGenerator.cs @@ -0,0 +1,34 @@ +using Markdown.Tokens; + +namespace Markdown.TokenParser.Helpers +{ + public class TokenGenerator + { + public static Token GetTokenBySymbol(string line, int currentIndex) => + line[currentIndex] switch + { + '#' => GetHashToken(line, currentIndex), + '\\' => GetEscapeToken(), + '_' => GetUnderscoreToken(line, currentIndex), + ' ' => GetSpaceToken(), + _ => GetTextToken(line, currentIndex) + }; + + private static Token GetHashToken(string line, int currentIndex) => + throw new NotImplementedException(); + + private static Token GetSpaceToken() => + throw new NotImplementedException(); + + private static Token GetTextToken(string line, int currentIndex) + { + throw new NotImplementedException(); + } + + private static Token GetUnderscoreToken(string line, int currentPosition) => + throw new NotImplementedException(); + + private static Token GetEscapeToken() => + throw new NotImplementedException(); + } +} diff --git a/cs/Markdown/TokenParser/Helpers/TokenValidator.cs b/cs/Markdown/TokenParser/Helpers/TokenValidator.cs new file mode 100644 index 000000000..1b021e53e --- /dev/null +++ b/cs/Markdown/TokenParser/Helpers/TokenValidator.cs @@ -0,0 +1,23 @@ +using Markdown.Tags; +using Markdown.Tokens; + +namespace Markdown.TokenParser.Helpers +{ + internal class TokenValidator + { + public static bool IsTokenTagOpen(TagType tagType, List tokens, int index) + { + throw new NotImplementedException(); + } + + public static bool IsValidTagToken(List tokens, int index) + { + throw new NotImplementedException(); + } + + public static bool OrderIsCorrect(Stack openedTokens, Token token) + { + throw new NotImplementedException(); + } + } +} diff --git a/cs/Markdown/TokenParser/ILineParser.cs b/cs/Markdown/TokenParser/ILineParser.cs new file mode 100644 index 000000000..9178eebc4 --- /dev/null +++ b/cs/Markdown/TokenParser/ILineParser.cs @@ -0,0 +1,7 @@ +namespace Markdown.TokenParser +{ + public interface ILineParser + { + public ParsedLine ParseLine(string line); + } +} diff --git a/cs/Markdown/TokenizerClasses/ConcreteTokenizers/Tokenizer.cs b/cs/Markdown/TokenizerClasses/ConcreteTokenizers/Tokenizer.cs deleted file mode 100644 index 6a50284fd..000000000 --- a/cs/Markdown/TokenizerClasses/ConcreteTokenizers/Tokenizer.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Security.Cryptography.X509Certificates; -using System.Text; -using System.Threading.Tasks; -using System.Xml; -using Markdown.Tags; -using Markdown.Tokens; - -namespace Markdown.TokenizerClasses.ConcreteTokenizers -{ - public class Tokenizer : ITokenizer - { - public List Tokenize(string text) - { - throw new NotImplementedException(); - } - } -} diff --git a/cs/Markdown/TokenizerClasses/ITokenizer.cs b/cs/Markdown/TokenizerClasses/ITokenizer.cs deleted file mode 100644 index 6e4d332c3..000000000 --- a/cs/Markdown/TokenizerClasses/ITokenizer.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Markdown.Tokens; - -namespace Markdown.TokenizerClasses -{ - public interface ITokenizer - { - public List Tokenize(string text); - } -} diff --git a/cs/Markdown/TokenizerClasses/TokenFactory.cs b/cs/Markdown/TokenizerClasses/TokenFactory.cs deleted file mode 100644 index f030c2e9e..000000000 --- a/cs/Markdown/TokenizerClasses/TokenFactory.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Markdown.Tokens; - -namespace Markdown.TokenizerClasses -{ - public class TokenFactory - { - public static Token CreateSingleCharacterToken(string ch) - { - throw new NotImplementedException(); - } - } -} diff --git a/cs/Markdown/Tokens/ConcreteTokens/ConcreteTokens.cs b/cs/Markdown/Tokens/ConcreteTokens/ConcreteTokens.cs deleted file mode 100644 index 06fb6c287..000000000 --- a/cs/Markdown/Tokens/ConcreteTokens/ConcreteTokens.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Markdown.Tokens.ConcreteTokens -{ - // Сюда думаю можно прописать конкретные классы токенов, наследников Token -} diff --git a/cs/Markdown/Tokens/Token.cs b/cs/Markdown/Tokens/Token.cs index 74ef4ff77..a85b024b8 100644 --- a/cs/Markdown/Tokens/Token.cs +++ b/cs/Markdown/Tokens/Token.cs @@ -1,23 +1,24 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Markdown.Tags; +using Markdown.Tags; namespace Markdown.Tokens { public class Token { - public readonly TokenType TokenType; + public TokenType TokenType; public readonly string Content; - public Token(TokenType tokenType, string content) + public readonly TagType TagType; + + public bool IsCloseTag; + + public int PairTagPosition; + + public Token(TokenType tokenType, string content, TagType tagType = TagType.UnDefined) { TokenType = tokenType; - Content = content; + TagType = tagType; } } } diff --git a/cs/Markdown/Tokens/TokenType.cs b/cs/Markdown/Tokens/TokenType.cs index c939c60c3..6714ad8df 100644 --- a/cs/Markdown/Tokens/TokenType.cs +++ b/cs/Markdown/Tokens/TokenType.cs @@ -1,22 +1,11 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Markdown.Tokens +namespace Markdown.Tokens { public enum TokenType { - WhiteSpace, - BreakLine, - Escape, - Em_start, - Em_end, - Bold_start, - Bold_end, - Hash, + MdTag, Text, - Number + Number, + Escape, + WhiteSpace } } diff --git a/cs/Markdown_Tests/LineParser_Tests.cs b/cs/Markdown_Tests/LineParser_Tests.cs new file mode 100644 index 000000000..cccf777c1 --- /dev/null +++ b/cs/Markdown_Tests/LineParser_Tests.cs @@ -0,0 +1,15 @@ +using Markdown.TokenParser.ConcreteParser; + +namespace MarkdownTests +{ + public class LineParserTests + { + private LineParser tokenizer = new LineParser(); + + [Test] + public void ParseLine_ThrowArgumentNullException_WhenArgumentIsNull() + { + Assert.Throws(() => tokenizer.ParseLine(null), "String argument text must be not null"); + } + } +} diff --git a/cs/Markdown_Tests/MarkdownParser_Tests.cs b/cs/Markdown_Tests/MarkdownParser_Tests.cs deleted file mode 100644 index 47480929c..000000000 --- a/cs/Markdown_Tests/MarkdownParser_Tests.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace MarkdownTests -{ - public class MarkdownParser_Tests - { - [SetUp] - public void Setup() - { - - } - - [Test] - public void Test1() - { - Assert.Pass(); - } - } -} diff --git a/cs/Markdown_Tests/MarkdownRenderHtml_Tests.cs b/cs/Markdown_Tests/MarkdownRenderHtml_Tests.cs deleted file mode 100644 index 58c42a0bd..000000000 --- a/cs/Markdown_Tests/MarkdownRenderHtml_Tests.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Markdown.TokenizerClasses; -using Markdown; -using Markdown.MarkdownRenders.ConcreteMarkdownRenders; -using Markdown.Parsers.ConcreteMarkdownParsers; -using Markdown.TokenizerClasses.ConcreteTokenizers; - - -namespace Markdown_Tests -{ - public class MarkdownRenderHtmlTests - { - private Md markdown = new Md(new MarkdownParser(), new Tokenizer(), new HtmlRender()); - - [SetUp] - public void Setup() - { - - } - - [Test] - public void Test1() - { - Assert.Pass(); - } - } -} \ No newline at end of file diff --git a/cs/Markdown_Tests/Md_Tests.cs b/cs/Markdown_Tests/Md_Tests.cs new file mode 100644 index 000000000..f0b033f65 --- /dev/null +++ b/cs/Markdown_Tests/Md_Tests.cs @@ -0,0 +1,154 @@ +using FluentAssertions; +using Markdown; +using Markdown.MarkdownRenders.ConcreteMarkdownRenders; +using Markdown.TokenParser.ConcreteParser; + + +namespace Markdown_Tests +{ + public class MdTests + { + private Md markdown = new Md(new LineParser(), new HtmlConverter()); + + #region HeaderTests + + [TestCase(@"# bibo", "

bibo

")] + [TestCase(@"# # bibo", "

# bibo

")] + public void Md_ShouldCreateHeaderCorrectly_WhenHeaderNotEscaped(string input, string expected) + { + markdown.Render(input).Should().BeEquivalentTo(expected); + } + + [TestCase(@"\# bibo", @"\# bibo")] + [TestCase(@"\# # bibo", @"\# # bibo")] + public void Md_ShouldNotCreateHeaderTag_WhenHeaderEscaped(string input, string expected) + { + markdown.Render(input).Should().BeEquivalentTo(expected); + } + + [TestCase(@"\\# bibo", @"\# bibo")] + [TestCase(@"a # # bibo", @"a # # bibo")] + public void Md_ShouldNotCreateHeaderTag_WhenAreCharsBeforeHash(string input, string expected) + { + markdown.Render(input).Should().BeEquivalentTo(expected); + } + + #endregion + + #region ItalicTests + + [TestCase(@"_bibo love bubu_", @"bibo love bubu")] + [TestCase(@"bibo _love_ bubu", @"bibo love bubu")] + public void Md_ShouldCreateItalicTag_WhenTagAfterWhiteSpace(string input, string expected) + { + markdown.Render(input).Should().BeEquivalentTo(expected); + } + + [TestCase(@"_ bibo love bubu_", @"_ bibo love bubu_")] + [TestCase(@"bibo _love _ bubu", @"bibo _love _ bubu")] + [TestCase(@"bibo _ love _ bubu", @"bibo _ love _ bubu")] + public void Md_ShouldNotCreateItalicTag_WhenWhiteSpaceBeforeOrAfterText(string input, string expected) + { + markdown.Render(input).Should().BeEquivalentTo(expected); + } + + [TestCase(@"bibo _lo_ve bubu", @"bibo love bubu")] + [TestCase(@"bibo l_ove_ bubu", @"bibo love bubu")] + public void Md_ShouldCreateItalicTag_WhenTagInsideTextWithoutDigits(string input, string expected) + { + markdown.Render(input).Should().BeEquivalentTo(expected); + } + + [TestCase(@"bibo \_love_ bubu", @"bibo _love_ bubu")] + [TestCase(@"bibo _love\_ bubu", @"bibo _love_ bubu")] + [TestCase(@"bibo \_love\_ bubu", @"bibo _love_ bubu")] + public void Md_ShouldNotCreateItalicTag_WhenTagEscaped(string input, string expected) + { + markdown.Render(input).Should().BeEquivalentTo(expected); + } + + [Test] + public void Md_ShoudNotCreateItalicAndBoldTags_WhenBoldTagsInsideItalic() + { + var input = "I _want to __sleep__ tonight_"; + var expected = "I want to __sleep__ tonight"; + + markdown.Render(input).Should().BeEquivalentTo(expected); + } + + [TestCase(@" c _12_3 ", @" c _12_3 ")] + [TestCase(@"bibo love_4_ bubu", @"bibo love_4_ bubu")] + public void Md_ShouldNotCreateItalicTag_WhenTextInsideHaveDigits(string input, string expected) + { + markdown.Render(input).Should().BeEquivalentTo(expected); + } + + #endregion + + #region BoldTests + + [TestCase(@"__bibo love bubu__", @"bibo love bubu")] + [TestCase(@"bibo __love__ bubu", @"bibo love bubu")] + public void Md_ShouldCreateBoldTag_WhenTagAfterSpace(string input, string expected) + { + markdown.Render(input).Should().BeEquivalentTo(expected); + } + + [TestCase(@"__ bibo love bubu__", @"__ bibo love bubu__")] + [TestCase(@"bibo __love __ bubu", @"bibo __love __ bubu")] + [TestCase(@"bibo __ love __ bubu", @"bibo __ love __ bubu")] + public void Md_ShouldNotCreateBoldTag_WhenWhiteSpaceBeforeOrAfterText(string input, string expected) + { + markdown.Render(input).Should().BeEquivalentTo(expected); + } + + [TestCase(@"bibo \__love__ bubu", @"bibo __love__ bubu")] + [TestCase(@"bibo __love\__ bubu", @"bibo __love__ bubu")] + [TestCase(@"bibo \__love\__ bubu", @"bibo __love__ bubu")] + public void Md_ShouldNotCreateBoldTag_WhenTagEscaped(string input, string expected) + { + markdown.Render(input).Should().BeEquivalentTo(expected); + } + + [Test] + public void Md_ShoudCreateItalicAndBoldTags_WhenItalicTagsInsideBold() + { + var input = "I __want to _sleep_ tonight__"; + var expected = "I want to sleep tonight"; + + markdown.Render(input).Should().BeEquivalentTo(expected); + } + + [TestCase(@" c __12__3 ", @" c __12__3 ")] + [TestCase(@"bibo love__4__ bubu", @"bibo love__4__ bubu")] + public void Md_ShouldNotCreateBoldTag_WhenTextInsideHaveDigits(string input, string expected) + { + markdown.Render(input).Should().BeEquivalentTo(expected); + } + + #endregion + + #region IntersectionTest + + [TestCase(@"__bibo _love__ bubu_", @"__bibo _love__ bubu_")] + [TestCase(@"_bibo __love_ bubu__", @"_bibo __love_ bubu__")] + public void Md_ShouldProccessIntersectsCorrecttly(string input, string expected) + { + markdown.Render(input).Should().BeEquivalentTo(expected); + } + + #endregion + + [TestCase(@"____", @"____")] + [TestCase(@"__", @"__")] + public void Md_CanCreateEmptyItalicOrBoldTag(string input, string expected) + { + markdown.Render(input).Should().BeEquivalentTo(expected); + } + + public void Md_ShouldCreateEmptyHtml_WhenTextIsStringEmpty(string input, string expected) + { + markdown.Render(String.Empty).Should().BeEquivalentTo(String.Empty); + } + } +} \ No newline at end of file diff --git a/cs/Markdown_Tests/Tokenizer_Tests.cs b/cs/Markdown_Tests/Tokenizer_Tests.cs deleted file mode 100644 index ab886e4e9..000000000 --- a/cs/Markdown_Tests/Tokenizer_Tests.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using FluentAssertions; -using Markdown.TokenizerClasses; -using Markdown.TokenizerClasses.ConcreteTokenizers; -using Markdown.Tokens; - -namespace MarkdownTests -{ - public class TokenizerTests - { - private Tokenizer tokenizer = new Tokenizer(); - - [Test] - public void Tokenize_ThrowArgumentNullException_WhenArgumentIsNull() - { - Assert.Throws(() => tokenizer.Tokenize(null), "String argument text must be not null"); - } - } -} From 45c5f64bbe0d3ddf84a489768d085170f740a652 Mon Sep 17 00:00:00 2001 From: Braidenbikher Victor Date: Fri, 29 Nov 2024 17:11:49 +0500 Subject: [PATCH 03/13] =?UTF-8?q?=D0=A0=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7?= =?UTF-8?q?=D0=B0=D1=86=D0=B8=D1=8F=20=D0=B8=20=D1=82=D0=B5=D1=81=D1=82?= =?UTF-8?q?=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8=D0=B5=20TokenGenerat?= =?UTF-8?q?or?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cs/Markdown/Extensions/StringExtensions.cs | 5 + .../TokenParser/Helpers/ITokenGenerateRule.cs | 14 + .../TokenParser/Helpers/TokenGenerator.cs | 79 +++-- .../GenerateBoldTokenRule.cs | 22 ++ .../GenerateEscapeTokenRule.cs | 21 ++ .../GenerateHashTokenRule.cs | 21 ++ .../GenerateItalicTokenRule.cs | 22 ++ .../GenerateTextTokenRule.cs | 53 +++ .../GenerateWhiteSpaceRule.cs | 21 ++ cs/Markdown_Tests/Md_Tests.cs | 307 +++++++++--------- .../TestData/TokenGeneratorTestsData.cs | 163 ++++++++++ cs/Markdown_Tests/TokenGenerator_Tests.cs | 92 ++++++ cs/clean-code.sln.DotSettings | 3 + 13 files changed, 650 insertions(+), 173 deletions(-) create mode 100644 cs/Markdown/TokenParser/Helpers/ITokenGenerateRule.cs create mode 100644 cs/Markdown/TokenParser/Helpers/TokenGeneratorRules/GenerateBoldTokenRule.cs create mode 100644 cs/Markdown/TokenParser/Helpers/TokenGeneratorRules/GenerateEscapeTokenRule.cs create mode 100644 cs/Markdown/TokenParser/Helpers/TokenGeneratorRules/GenerateHashTokenRule.cs create mode 100644 cs/Markdown/TokenParser/Helpers/TokenGeneratorRules/GenerateItalicTokenRule.cs create mode 100644 cs/Markdown/TokenParser/Helpers/TokenGeneratorRules/GenerateTextTokenRule.cs create mode 100644 cs/Markdown/TokenParser/Helpers/TokenGeneratorRules/GenerateWhiteSpaceRule.cs create mode 100644 cs/Markdown_Tests/TestData/TokenGeneratorTestsData.cs create mode 100644 cs/Markdown_Tests/TokenGenerator_Tests.cs diff --git a/cs/Markdown/Extensions/StringExtensions.cs b/cs/Markdown/Extensions/StringExtensions.cs index 2f5ef1cd3..3da92d71e 100644 --- a/cs/Markdown/Extensions/StringExtensions.cs +++ b/cs/Markdown/Extensions/StringExtensions.cs @@ -4,5 +4,10 @@ public static class StringExtensions { public static string[] SplitIntoLines(this string text) => text.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None); + + public static bool NextCharIs(this string line, char ch, int currentIndex) + { + return currentIndex + 1 < line.Length && line[currentIndex + 1] == ch; + } } } diff --git a/cs/Markdown/TokenParser/Helpers/ITokenGenerateRule.cs b/cs/Markdown/TokenParser/Helpers/ITokenGenerateRule.cs new file mode 100644 index 000000000..e283fbf82 --- /dev/null +++ b/cs/Markdown/TokenParser/Helpers/ITokenGenerateRule.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Markdown.Tokens; + +namespace Markdown.TokenParser.Helpers +{ + public interface ITokenGenerateRule + { + public Token? GetToken(string line, int currentIndex); + } +} diff --git a/cs/Markdown/TokenParser/Helpers/TokenGenerator.cs b/cs/Markdown/TokenParser/Helpers/TokenGenerator.cs index c9208e592..3ae6b6bb0 100644 --- a/cs/Markdown/TokenParser/Helpers/TokenGenerator.cs +++ b/cs/Markdown/TokenParser/Helpers/TokenGenerator.cs @@ -1,34 +1,75 @@ -using Markdown.Tokens; +using System.Reflection; +using System.Runtime.InteropServices; +using Markdown.Tags; +using Markdown.Tokens; +using System.Text; namespace Markdown.TokenParser.Helpers { public class TokenGenerator { - public static Token GetTokenBySymbol(string line, int currentIndex) => - line[currentIndex] switch + private static IEnumerable generateRuleClasses = GetRuleClasses(); + + public static Token? GetTokenBySymbol(string line, int currentIndex) + { + foreach (var rule in generateRuleClasses) { - '#' => GetHashToken(line, currentIndex), - '\\' => GetEscapeToken(), - '_' => GetUnderscoreToken(line, currentIndex), - ' ' => GetSpaceToken(), - _ => GetTextToken(line, currentIndex) - }; + var token = rule.GetToken(line, currentIndex); + if (token != null) + return token; + } + + return null; + // return generateRuleClasses + // .Select(t => t.GetToken(line, currentIndex)) + // .SingleOrDefault(t => t != null); + } + + private static IEnumerable GetRuleClasses() + { + Type interfaceType = typeof(ITokenGenerateRule); + + var rulesTypes = Assembly.GetExecutingAssembly() + .GetTypes() + .Where(t => interfaceType.IsAssignableFrom(t) && t.IsClass) + .ToHashSet(); - private static Token GetHashToken(string line, int currentIndex) => - throw new NotImplementedException(); + var simpleRules = GetRulesNotUsesOthersRules(rulesTypes).ToList(); + var complexRules = GetRulesUsesOthersRulesLogic(rulesTypes, simpleRules).ToList(); - private static Token GetSpaceToken() => - throw new NotImplementedException(); + return simpleRules.Concat(complexRules); + } - private static Token GetTextToken(string line, int currentIndex) + private static IEnumerable GetRulesNotUsesOthersRules(HashSet rulesTypes) { - throw new NotImplementedException(); + foreach (var type in rulesTypes) + { + var constructors = type.GetConstructors(); + + if (constructors.Length == 1 && constructors[0].GetParameters().Length == 0) + { + rulesTypes.Remove(type); + yield return (ITokenGenerateRule)Activator.CreateInstance(type); + } + } } - private static Token GetUnderscoreToken(string line, int currentPosition) => - throw new NotImplementedException(); + private static IEnumerable GetRulesUsesOthersRulesLogic(HashSet rulesTypes, + IEnumerable rulesNotUsesOthersRules) + { + var getTokenFuncs = rulesNotUsesOthersRules + .Select(rule => new Func(rule.GetToken)); - private static Token GetEscapeToken() => - throw new NotImplementedException(); + foreach (var type in rulesTypes) + { + var constructor = type.GetConstructor(new[] { typeof(IEnumerable>) }); + + if (constructor == null) + throw new ArgumentNullException("TokenGeneratorRules should have only one constructor " + + "without arguments or with IEnumerable argument"); + rulesTypes.Remove(type); + yield return (ITokenGenerateRule)constructor.Invoke(new object[] { getTokenFuncs }); + } + } } } diff --git a/cs/Markdown/TokenParser/Helpers/TokenGeneratorRules/GenerateBoldTokenRule.cs b/cs/Markdown/TokenParser/Helpers/TokenGeneratorRules/GenerateBoldTokenRule.cs new file mode 100644 index 000000000..de680638b --- /dev/null +++ b/cs/Markdown/TokenParser/Helpers/TokenGeneratorRules/GenerateBoldTokenRule.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Markdown.Tags; +using Markdown.Tokens; +using Markdown.Extensions; + +namespace Markdown.TokenParser.Helpers.TokenGeneratorRules +{ + public class GenerateBoldTokenRule : ITokenGenerateRule + { + public Token? GetToken(string line, int currentIndex) + { + if (line[currentIndex] == '_' && line.NextCharIs('_', currentIndex)) + return new Token(TokenType.MdTag, "__", TagType.Bold); + + return null; + } + } +} diff --git a/cs/Markdown/TokenParser/Helpers/TokenGeneratorRules/GenerateEscapeTokenRule.cs b/cs/Markdown/TokenParser/Helpers/TokenGeneratorRules/GenerateEscapeTokenRule.cs new file mode 100644 index 000000000..1f4d6dba7 --- /dev/null +++ b/cs/Markdown/TokenParser/Helpers/TokenGeneratorRules/GenerateEscapeTokenRule.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Markdown.Tags; +using Markdown.Tokens; + +namespace Markdown.TokenParser.Helpers.TokenGeneratorRules +{ + public class GenerateEscapeTokenRule : ITokenGenerateRule + { + public Token? GetToken(string line, int currentIndex) + { + if (line[currentIndex] == '\\') + return new Token(TokenType.Escape, @"\", TagType.Escape); + + return null; + } + } +} diff --git a/cs/Markdown/TokenParser/Helpers/TokenGeneratorRules/GenerateHashTokenRule.cs b/cs/Markdown/TokenParser/Helpers/TokenGeneratorRules/GenerateHashTokenRule.cs new file mode 100644 index 000000000..df3a4d14f --- /dev/null +++ b/cs/Markdown/TokenParser/Helpers/TokenGeneratorRules/GenerateHashTokenRule.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Markdown.Tags; +using Markdown.Tokens; + +namespace Markdown.TokenParser.Helpers.TokenGeneratorRules +{ + public class GenerateHashTokenRule : ITokenGenerateRule + { + public Token? GetToken(string line, int currentIndex) + { + if (currentIndex + 1 < line.Length && line[currentIndex] == '#' && line[currentIndex + 1] == ' ') + return new Token(TokenType.MdTag, "# ", TagType.Header); + + return null; + } + } +} diff --git a/cs/Markdown/TokenParser/Helpers/TokenGeneratorRules/GenerateItalicTokenRule.cs b/cs/Markdown/TokenParser/Helpers/TokenGeneratorRules/GenerateItalicTokenRule.cs new file mode 100644 index 000000000..edd9af0b2 --- /dev/null +++ b/cs/Markdown/TokenParser/Helpers/TokenGeneratorRules/GenerateItalicTokenRule.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Markdown.Extensions; +using Markdown.Tags; +using Markdown.Tokens; + +namespace Markdown.TokenParser.Helpers.TokenGeneratorRules +{ + public class GenerateItalicTokenRule : ITokenGenerateRule + { + public Token? GetToken(string line, int currentIndex) + { + if (line[currentIndex] == '_' && !line.NextCharIs('_', currentIndex)) + return new Token(TokenType.MdTag, "_", TagType.Italic); + + return null; + } + } +} diff --git a/cs/Markdown/TokenParser/Helpers/TokenGeneratorRules/GenerateTextTokenRule.cs b/cs/Markdown/TokenParser/Helpers/TokenGeneratorRules/GenerateTextTokenRule.cs new file mode 100644 index 000000000..173c36ba3 --- /dev/null +++ b/cs/Markdown/TokenParser/Helpers/TokenGeneratorRules/GenerateTextTokenRule.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using Markdown.Tokens; + +namespace Markdown.TokenParser.Helpers.TokenGeneratorRules +{ + public class GenerateTextTokenRule : ITokenGenerateRule + { + private readonly IEnumerable> otherTokensRules; + + public GenerateTextTokenRule(IEnumerable> otherTokensRules) + { + this.otherTokensRules = otherTokensRules; + } + + private bool IsTextToken(string line, int currentIndex) + { + foreach (var rule in otherTokensRules) + { + var token = rule.Invoke(line, currentIndex); + if (token != null) + return false; + } + + return true; + } + + public Token? GetToken(string line, int currentIndex) + { + var stringBuilder = new StringBuilder(); + var tokenType = char.IsNumber(line[currentIndex]) ? TokenType.Number : TokenType.Text; + + for (var i = currentIndex; i < line.Length; i++) + { + if (tokenType == TokenType.Text && + (char.IsNumber(line[currentIndex]) || !IsTextToken(line, currentIndex))) + break; + + if (tokenType == TokenType.Number && !char.IsNumber(line[currentIndex])) + break; + + stringBuilder.Append(line[currentIndex]); + currentIndex++; + } + + return new Token(tokenType, stringBuilder.ToString()); + } + } +} diff --git a/cs/Markdown/TokenParser/Helpers/TokenGeneratorRules/GenerateWhiteSpaceRule.cs b/cs/Markdown/TokenParser/Helpers/TokenGeneratorRules/GenerateWhiteSpaceRule.cs new file mode 100644 index 000000000..1567cb125 --- /dev/null +++ b/cs/Markdown/TokenParser/Helpers/TokenGeneratorRules/GenerateWhiteSpaceRule.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Markdown.Tags; +using Markdown.Tokens; + +namespace Markdown.TokenParser.Helpers.TokenGeneratorRules +{ + public class GenerateWhiteSpaceRule : ITokenGenerateRule + { + public Token? GetToken(string line, int currentIndex) + { + if (line[currentIndex] == ' ') + return new Token(TokenType.WhiteSpace, " ", TagType.UnDefined); + + return null; + } + } +} diff --git a/cs/Markdown_Tests/Md_Tests.cs b/cs/Markdown_Tests/Md_Tests.cs index f0b033f65..93723fc14 100644 --- a/cs/Markdown_Tests/Md_Tests.cs +++ b/cs/Markdown_Tests/Md_Tests.cs @@ -1,154 +1,153 @@ -using FluentAssertions; -using Markdown; -using Markdown.MarkdownRenders.ConcreteMarkdownRenders; -using Markdown.TokenParser.ConcreteParser; - - -namespace Markdown_Tests -{ - public class MdTests - { - private Md markdown = new Md(new LineParser(), new HtmlConverter()); - - #region HeaderTests - - [TestCase(@"# bibo", "

bibo

")] - [TestCase(@"# # bibo", "

# bibo

")] - public void Md_ShouldCreateHeaderCorrectly_WhenHeaderNotEscaped(string input, string expected) - { - markdown.Render(input).Should().BeEquivalentTo(expected); - } - - [TestCase(@"\# bibo", @"\# bibo")] - [TestCase(@"\# # bibo", @"\# # bibo")] - public void Md_ShouldNotCreateHeaderTag_WhenHeaderEscaped(string input, string expected) - { - markdown.Render(input).Should().BeEquivalentTo(expected); - } - - [TestCase(@"\\# bibo", @"\# bibo")] - [TestCase(@"a # # bibo", @"a # # bibo")] - public void Md_ShouldNotCreateHeaderTag_WhenAreCharsBeforeHash(string input, string expected) - { - markdown.Render(input).Should().BeEquivalentTo(expected); - } - - #endregion - - #region ItalicTests - - [TestCase(@"_bibo love bubu_", @"bibo love bubu")] - [TestCase(@"bibo _love_ bubu", @"bibo love bubu")] - public void Md_ShouldCreateItalicTag_WhenTagAfterWhiteSpace(string input, string expected) - { - markdown.Render(input).Should().BeEquivalentTo(expected); - } - - [TestCase(@"_ bibo love bubu_", @"_ bibo love bubu_")] - [TestCase(@"bibo _love _ bubu", @"bibo _love _ bubu")] - [TestCase(@"bibo _ love _ bubu", @"bibo _ love _ bubu")] - public void Md_ShouldNotCreateItalicTag_WhenWhiteSpaceBeforeOrAfterText(string input, string expected) - { - markdown.Render(input).Should().BeEquivalentTo(expected); - } - - [TestCase(@"bibo _lo_ve bubu", @"bibo love bubu")] - [TestCase(@"bibo l_ove_ bubu", @"bibo love bubu")] - public void Md_ShouldCreateItalicTag_WhenTagInsideTextWithoutDigits(string input, string expected) - { - markdown.Render(input).Should().BeEquivalentTo(expected); - } - - [TestCase(@"bibo \_love_ bubu", @"bibo _love_ bubu")] - [TestCase(@"bibo _love\_ bubu", @"bibo _love_ bubu")] - [TestCase(@"bibo \_love\_ bubu", @"bibo _love_ bubu")] - public void Md_ShouldNotCreateItalicTag_WhenTagEscaped(string input, string expected) - { - markdown.Render(input).Should().BeEquivalentTo(expected); - } - - [Test] - public void Md_ShoudNotCreateItalicAndBoldTags_WhenBoldTagsInsideItalic() - { - var input = "I _want to __sleep__ tonight_"; - var expected = "I want to __sleep__ tonight"; - - markdown.Render(input).Should().BeEquivalentTo(expected); - } - - [TestCase(@" c _12_3 ", @" c _12_3 ")] - [TestCase(@"bibo love_4_ bubu", @"bibo love_4_ bubu")] - public void Md_ShouldNotCreateItalicTag_WhenTextInsideHaveDigits(string input, string expected) - { - markdown.Render(input).Should().BeEquivalentTo(expected); - } - - #endregion - - #region BoldTests - - [TestCase(@"__bibo love bubu__", @"bibo love bubu")] - [TestCase(@"bibo __love__ bubu", @"bibo love bubu")] - public void Md_ShouldCreateBoldTag_WhenTagAfterSpace(string input, string expected) - { - markdown.Render(input).Should().BeEquivalentTo(expected); - } - - [TestCase(@"__ bibo love bubu__", @"__ bibo love bubu__")] - [TestCase(@"bibo __love __ bubu", @"bibo __love __ bubu")] - [TestCase(@"bibo __ love __ bubu", @"bibo __ love __ bubu")] - public void Md_ShouldNotCreateBoldTag_WhenWhiteSpaceBeforeOrAfterText(string input, string expected) - { - markdown.Render(input).Should().BeEquivalentTo(expected); - } - - [TestCase(@"bibo \__love__ bubu", @"bibo __love__ bubu")] - [TestCase(@"bibo __love\__ bubu", @"bibo __love__ bubu")] - [TestCase(@"bibo \__love\__ bubu", @"bibo __love__ bubu")] - public void Md_ShouldNotCreateBoldTag_WhenTagEscaped(string input, string expected) - { - markdown.Render(input).Should().BeEquivalentTo(expected); - } - - [Test] - public void Md_ShoudCreateItalicAndBoldTags_WhenItalicTagsInsideBold() - { - var input = "I __want to _sleep_ tonight__"; - var expected = "I want to sleep tonight"; - - markdown.Render(input).Should().BeEquivalentTo(expected); - } - - [TestCase(@" c __12__3 ", @" c __12__3 ")] - [TestCase(@"bibo love__4__ bubu", @"bibo love__4__ bubu")] - public void Md_ShouldNotCreateBoldTag_WhenTextInsideHaveDigits(string input, string expected) - { - markdown.Render(input).Should().BeEquivalentTo(expected); - } - - #endregion - - #region IntersectionTest - - [TestCase(@"__bibo _love__ bubu_", @"__bibo _love__ bubu_")] - [TestCase(@"_bibo __love_ bubu__", @"_bibo __love_ bubu__")] - public void Md_ShouldProccessIntersectsCorrecttly(string input, string expected) - { - markdown.Render(input).Should().BeEquivalentTo(expected); - } - - #endregion - - [TestCase(@"____", @"____")] - [TestCase(@"__", @"__")] - public void Md_CanCreateEmptyItalicOrBoldTag(string input, string expected) - { - markdown.Render(input).Should().BeEquivalentTo(expected); - } - - public void Md_ShouldCreateEmptyHtml_WhenTextIsStringEmpty(string input, string expected) - { - markdown.Render(String.Empty).Should().BeEquivalentTo(String.Empty); - } - } -} \ No newline at end of file +//using FluentAssertions; +//using Markdown; +//using Markdown.TokenParser.ConcreteParser; + + +//namespace Markdown_Tests +//{ +// public class MdTests +// { +// private Md markdown = new Md(new LineParser(), new HtmlConverter()); + +// #region HeaderTests + +// [TestCase(@"# bibo", "

bibo

")] +// [TestCase(@"# # bibo", "

# bibo

")] +// public void Md_ShouldCreateHeaderCorrectly_WhenHeaderNotEscaped(string input, string expected) +// { +// markdown.Render(input).Should().BeEquivalentTo(expected); +// } + +// [TestCase(@"\# bibo", @"\# bibo")] +// [TestCase(@"\# # bibo", @"\# # bibo")] +// public void Md_ShouldNotCreateHeaderTag_WhenHeaderEscaped(string input, string expected) +// { +// markdown.Render(input).Should().BeEquivalentTo(expected); +// } + +// [TestCase(@"\\# bibo", @"\# bibo")] +// [TestCase(@"a # # bibo", @"a # # bibo")] +// public void Md_ShouldNotCreateHeaderTag_WhenAreCharsBeforeHash(string input, string expected) +// { +// markdown.Render(input).Should().BeEquivalentTo(expected); +// } + +// #endregion + +// #region ItalicTests + +// [TestCase(@"_bibo love bubu_", @"bibo love bubu")] +// [TestCase(@"bibo _love_ bubu", @"bibo love bubu")] +// public void Md_ShouldCreateItalicTag_WhenTagAfterWhiteSpace(string input, string expected) +// { +// markdown.Render(input).Should().BeEquivalentTo(expected); +// } + +// [TestCase(@"_ bibo love bubu_", @"_ bibo love bubu_")] +// [TestCase(@"bibo _love _ bubu", @"bibo _love _ bubu")] +// [TestCase(@"bibo _ love _ bubu", @"bibo _ love _ bubu")] +// public void Md_ShouldNotCreateItalicTag_WhenWhiteSpaceBeforeOrAfterText(string input, string expected) +// { +// markdown.Render(input).Should().BeEquivalentTo(expected); +// } + +// [TestCase(@"bibo _lo_ve bubu", @"bibo love bubu")] +// [TestCase(@"bibo l_ove_ bubu", @"bibo love bubu")] +// public void Md_ShouldCreateItalicTag_WhenTagInsideTextWithoutDigits(string input, string expected) +// { +// markdown.Render(input).Should().BeEquivalentTo(expected); +// } + +// [TestCase(@"bibo \_love_ bubu", @"bibo _love_ bubu")] +// [TestCase(@"bibo _love\_ bubu", @"bibo _love_ bubu")] +// [TestCase(@"bibo \_love\_ bubu", @"bibo _love_ bubu")] +// public void Md_ShouldNotCreateItalicTag_WhenTagEscaped(string input, string expected) +// { +// markdown.Render(input).Should().BeEquivalentTo(expected); +// } + +// [Test] +// public void Md_ShoudNotCreateItalicAndBoldTags_WhenBoldTagsInsideItalic() +// { +// var input = "I _want to __sleep__ tonight_"; +// var expected = "I want to __sleep__ tonight"; + +// markdown.Render(input).Should().BeEquivalentTo(expected); +// } + +// [TestCase(@" c _12_3 ", @" c _12_3 ")] +// [TestCase(@"bibo love_4_ bubu", @"bibo love_4_ bubu")] +// public void Md_ShouldNotCreateItalicTag_WhenTextInsideHaveDigits(string input, string expected) +// { +// markdown.Render(input).Should().BeEquivalentTo(expected); +// } + +// #endregion + +// #region BoldTests + +// [TestCase(@"__bibo love bubu__", @"bibo love bubu")] +// [TestCase(@"bibo __love__ bubu", @"bibo love bubu")] +// public void Md_ShouldCreateBoldTag_WhenTagAfterSpace(string input, string expected) +// { +// markdown.Render(input).Should().BeEquivalentTo(expected); +// } + +// [TestCase(@"__ bibo love bubu__", @"__ bibo love bubu__")] +// [TestCase(@"bibo __love __ bubu", @"bibo __love __ bubu")] +// [TestCase(@"bibo __ love __ bubu", @"bibo __ love __ bubu")] +// public void Md_ShouldNotCreateBoldTag_WhenWhiteSpaceBeforeOrAfterText(string input, string expected) +// { +// markdown.Render(input).Should().BeEquivalentTo(expected); +// } + +// [TestCase(@"bibo \__love__ bubu", @"bibo __love__ bubu")] +// [TestCase(@"bibo __love\__ bubu", @"bibo __love__ bubu")] +// [TestCase(@"bibo \__love\__ bubu", @"bibo __love__ bubu")] +// public void Md_ShouldNotCreateBoldTag_WhenTagEscaped(string input, string expected) +// { +// markdown.Render(input).Should().BeEquivalentTo(expected); +// } + +// [Test] +// public void Md_ShoudCreateItalicAndBoldTags_WhenItalicTagsInsideBold() +// { +// var input = "I __want to _sleep_ tonight__"; +// var expected = "I want to sleep tonight"; + +// markdown.Render(input).Should().BeEquivalentTo(expected); +// } + +// [TestCase(@" c __12__3 ", @" c __12__3 ")] +// [TestCase(@"bibo love__4__ bubu", @"bibo love__4__ bubu")] +// public void Md_ShouldNotCreateBoldTag_WhenTextInsideHaveDigits(string input, string expected) +// { +// markdown.Render(input).Should().BeEquivalentTo(expected); +// } + +// #endregion + +// #region IntersectionTest + +// [TestCase(@"__bibo _love__ bubu_", @"__bibo _love__ bubu_")] +// [TestCase(@"_bibo __love_ bubu__", @"_bibo __love_ bubu__")] +// public void Md_ShouldProccessIntersectsCorrecttly(string input, string expected) +// { +// markdown.Render(input).Should().BeEquivalentTo(expected); +// } + +// #endregion + +// [TestCase(@"____", @"____")] +// [TestCase(@"__", @"__")] +// public void Md_CanCreateEmptyItalicOrBoldTag(string input, string expected) +// { +// markdown.Render(input).Should().BeEquivalentTo(expected); +// } + +// public void Md_ShouldCreateEmptyHtml_WhenTextIsStringEmpty(string input, string expected) +// { +// markdown.Render(String.Empty).Should().BeEquivalentTo(String.Empty); +// } +// } +//} \ No newline at end of file diff --git a/cs/Markdown_Tests/TestData/TokenGeneratorTestsData.cs b/cs/Markdown_Tests/TestData/TokenGeneratorTestsData.cs new file mode 100644 index 000000000..44a6a8736 --- /dev/null +++ b/cs/Markdown_Tests/TestData/TokenGeneratorTestsData.cs @@ -0,0 +1,163 @@ +using Markdown.Tags; +using Markdown.Tokens; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MarkdownTests.TestData +{ + public static class TokenGeneratorTestsData + { + public static IEnumerable TextOnlyLines() + { + yield return new TestCaseData("вася", new List() + { + new Token(TokenType.Text, "вася", TagType.UnDefined), + }); + + yield return new TestCaseData("петя1223d", new List() + { + new Token(TokenType.Text, "петя", TagType.UnDefined), + new Token(TokenType.Number, "1223", TagType.UnDefined), + new Token(TokenType.Text, "d", TagType.UnDefined), + }); + + yield return new TestCaseData("1234566", new List() + { + new Token(TokenType.Number, "1234566", TagType.UnDefined), + }); + } + + public static IEnumerable WhiteSpacesOnlyLines() + { + yield return new TestCaseData(" ", new List() + { + new Token(TokenType.WhiteSpace, " ", TagType.UnDefined), + }); + + yield return new TestCaseData(" ", new List() + { + new Token(TokenType.WhiteSpace, " ", TagType.UnDefined), + new Token(TokenType.WhiteSpace, " ", TagType.UnDefined), + }); + + yield return new TestCaseData(" ", new List() + { + new Token(TokenType.WhiteSpace, " ", TagType.UnDefined), + new Token(TokenType.WhiteSpace, " ", TagType.UnDefined), + new Token(TokenType.WhiteSpace, " ", TagType.UnDefined), + new Token(TokenType.WhiteSpace, " ", TagType.UnDefined), + new Token(TokenType.WhiteSpace, " ", TagType.UnDefined), + new Token(TokenType.WhiteSpace, " ", TagType.UnDefined), + }); + } + + public static IEnumerable LinesWithHeader() + { + yield return new TestCaseData("# Заголовок", new List() + { + new Token(TokenType.MdTag, "# ", TagType.Header), + new Token(TokenType.Text, "Заголовок", TagType.UnDefined), + }); + + yield return new TestCaseData("# # # ", new List() + { + new Token(TokenType.MdTag, "# ", TagType.Header), + new Token(TokenType.MdTag, "# ", TagType.Header), + new Token(TokenType.MdTag, "# ", TagType.Header) + }); + + yield return new TestCaseData(@" # # ", new List() + { + new Token(TokenType.WhiteSpace, " ", TagType.UnDefined), + new Token(TokenType.MdTag, "# ", TagType.Header), + new Token(TokenType.MdTag, "# ", TagType.Header), + }); + } + + public static IEnumerable LinesWithEscapes() + { + yield return new TestCaseData(@"\", new List() + { + new Token(TokenType.Escape, @"\", TagType.Escape), + }); + + yield return new TestCaseData(@"\\", new List() + { + new Token(TokenType.Escape, @"\", TagType.Escape), + new Token(TokenType.Escape, @"\", TagType.Escape), + }); + + yield return new TestCaseData(@"\\\", new List() + { + new Token(TokenType.Escape, @"\", TagType.Escape), + new Token(TokenType.Escape, @"\", TagType.Escape), + new Token(TokenType.Escape, @"\", TagType.Escape), + }); + } + + public static IEnumerable LinesWithUnderscores() + { + yield return new TestCaseData("_", new List() + { + new Token(TokenType.MdTag, "_", TagType.Italic), + }); + + yield return new TestCaseData("__", new List() + { + new Token(TokenType.MdTag, "__", TagType.Bold), + }); + + yield return new TestCaseData("___", new List() + { + new Token(TokenType.MdTag, "__", TagType.Bold), + new Token(TokenType.MdTag, "_", TagType.Italic), + }); + + yield return new TestCaseData("____", new List() + { + new Token(TokenType.MdTag, "__", TagType.Bold), + new Token(TokenType.MdTag, "__", TagType.Bold), + }); + } + + public static IEnumerable LineWithMultiTokens() + { + yield return new TestCaseData("Bibo 234 _ # ", new List() + { + new Token(TokenType.Text, "Bibo", TagType.UnDefined), + new Token(TokenType.WhiteSpace, " ", TagType.UnDefined), + new Token(TokenType.Number, "234", TagType.UnDefined), + new Token(TokenType.WhiteSpace, " ", TagType.UnDefined), + new Token(TokenType.MdTag, "_", TagType.Italic), + new Token(TokenType.WhiteSpace, " ", TagType.UnDefined), + new Token(TokenType.MdTag, "# ", TagType.Header), + }); + + yield return new TestCaseData("__# _", new List() + { + new Token(TokenType.MdTag, "__", TagType.Bold), + new Token(TokenType.MdTag, "# ", TagType.Header), + new Token(TokenType.MdTag, "_", TagType.Italic), + }); + + yield return new TestCaseData("_2_3_", new List() + { + new Token(TokenType.MdTag, "_", TagType.Italic), + new Token(TokenType.Number, "2", TagType.UnDefined), + new Token(TokenType.MdTag, "_", TagType.Italic), + new Token(TokenType.Number, "3", TagType.UnDefined), + new Token(TokenType.MdTag, "_", TagType.Italic), + }); + + yield return new TestCaseData(@"\# word", new List() + { + new Token(TokenType.Escape, @"\", TagType.Escape), + new Token(TokenType.MdTag, "# ", TagType.Header), + new Token(TokenType.Text, "word", TagType.UnDefined), + }); + } + } +} diff --git a/cs/Markdown_Tests/TokenGenerator_Tests.cs b/cs/Markdown_Tests/TokenGenerator_Tests.cs new file mode 100644 index 000000000..e30eab26f --- /dev/null +++ b/cs/Markdown_Tests/TokenGenerator_Tests.cs @@ -0,0 +1,92 @@ +using Markdown.Tags; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using FluentAssertions; +using Markdown.TokenParser.Helpers; +using Markdown.Tokens; +using MarkdownTests.TestData; + +namespace MarkdownTests +{ + public class TokenGenerator_Tests + { + [TestCaseSource(typeof(TokenGeneratorTestsData), nameof(TokenGeneratorTestsData.TextOnlyLines))] + public void TokenGenerator_GetTokenBySymbolCorrectly_WhenTextOnly(string input, List expectedTokens) + { + var actuallyTokens = GetAllTokensFromLine(input); + + actuallyTokens.Should().BeEquivalentTo(expectedTokens); + } + + [TestCaseSource(typeof(TokenGeneratorTestsData), nameof(TokenGeneratorTestsData.WhiteSpacesOnlyLines))] + public void TokenGenerator_GetTokensBySymbolCorrectly_WhenWhiteSpacesOnly(string input, List expectedTokens) + { + var actuallyTokens = GetAllTokensFromLine(input); + + actuallyTokens.Should().BeEquivalentTo(expectedTokens); + } + + [Test] + public void TokenGenerator_GetTokensBySymbolCorrectly_WhenHeaderTokenOnly() + { + var actuallyTokens = GetAllTokensFromLine("# "); + + actuallyTokens.Should().BeEquivalentTo(new List() + { + new Token(TokenType.MdTag, "# ", TagType.Header) + + }); + } + + [TestCaseSource(typeof(TokenGeneratorTestsData), nameof(TokenGeneratorTestsData.LinesWithHeader))] + public void TokenGenerator_GetTokensBySymbolCorrectly_WhenLineWithHeaders(string input, List expectedTokens) + { + var actuallyTokens = GetAllTokensFromLine(input); + + actuallyTokens.Should().BeEquivalentTo(expectedTokens); + } + + [TestCaseSource(typeof(TokenGeneratorTestsData), nameof(TokenGeneratorTestsData.LinesWithEscapes))] + public void TokenGenerator_GetTokensBySymbolCorrectly_WhenLineWithEscapes(string input, List expectedTokens) + { + var actuallyTokens = GetAllTokensFromLine(input); + + actuallyTokens.Should().BeEquivalentTo(expectedTokens); + } + + [TestCaseSource(typeof(TokenGeneratorTestsData), nameof(TokenGeneratorTestsData.LinesWithUnderscores))] + public void TokenGenerator_GetTokensBySymbolCorrectly_WhenLineWithUnderscores(string input, List expectedTokens) + { + var actuallyTokens = GetAllTokensFromLine(input); + + actuallyTokens.Should().BeEquivalentTo(expectedTokens); + } + + [TestCaseSource(typeof(TokenGeneratorTestsData), nameof(TokenGeneratorTestsData.LineWithMultiTokens))] + public void TokenGenerator_GetTokensBySymbolCorrectly_WhenLineWithMultiTokens(string input, List expectedTokens) + { + var actuallyTokens = GetAllTokensFromLine(input); + + actuallyTokens.Should().BeEquivalentTo(expectedTokens); + } + + + private static List GetAllTokensFromLine(string line) + { + int i = 0; + var result = new List(); + + while (i < line.Length) + { + var token = TokenGenerator.GetTokenBySymbol(line, i); + result.Add(token); + i += token.Content.Length; + } + + return result; + } + } +} diff --git a/cs/clean-code.sln.DotSettings b/cs/clean-code.sln.DotSettings index 135b83ecb..229f449d2 100644 --- a/cs/clean-code.sln.DotSettings +++ b/cs/clean-code.sln.DotSettings @@ -1,6 +1,9 @@  <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb_AaBb" /> + <Policy><Descriptor Staticness="Instance" AccessRightKinds="Private" Description="Instance fields (private)"><ElementKinds><Kind Name="FIELD" /><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></Policy> + <Policy><Descriptor Staticness="Any" AccessRightKinds="Any" Description="Types and namespaces"><ElementKinds><Kind Name="NAMESPACE" /><Kind Name="CLASS" /><Kind Name="STRUCT" /><Kind Name="ENUM" /><Kind Name="DELEGATE" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb_AaBb" /></Policy> + True True True Imported 10.10.2016 From 6799291ccf327923581de7bde5522f331c8dd83c Mon Sep 17 00:00:00 2001 From: Braidenbikher Victor Date: Sun, 1 Dec 2024 20:38:08 +0500 Subject: [PATCH 04/13] =?UTF-8?q?=D0=A0=D0=B5=D0=BB=D0=B8=D0=B7=D0=BE?= =?UTF-8?q?=D0=B2=D0=B0=D0=BB=20=D0=BF=D1=80=D0=BE=D1=82=D0=B5=D1=81=D1=82?= =?UTF-8?q?=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=D0=BD=D1=8B=D0=B9=20LinePa?= =?UTF-8?q?rser?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cs/Markdown/Extensions/TokenExtensions.cs | 22 ++ cs/Markdown/ParsedLine.cs | 12 +- cs/Markdown/Tags/ConcreteTags/BoldTag.cs | 18 +- cs/Markdown/Tags/ConcreteTags/HeaderTag.cs | 18 +- cs/Markdown/Tags/ConcreteTags/ItalicTag.cs | 18 +- cs/Markdown/Tags/ITag.cs | 15 + cs/Markdown/Tags/Tag.cs | 16 -- .../TokenParser/ConcreteParser/LineParser.cs | 256 +++++++++++++++++- .../GenerateHashTokenRule.cs | 2 +- .../TokenParser/Helpers/TokenValidator.cs | 117 +++++++- cs/Markdown/Tokens/Token.cs | 6 +- cs/Markdown_Tests/LineParser_Tests.cs | 45 ++- cs/Markdown_Tests/TestData/LineParserData.cs | 69 +++++ .../TestData/TokenGeneratorTestsData.cs | 18 +- cs/Markdown_Tests/TokenGenerator_Tests.cs | 3 +- 15 files changed, 576 insertions(+), 59 deletions(-) create mode 100644 cs/Markdown/Extensions/TokenExtensions.cs create mode 100644 cs/Markdown/Tags/ITag.cs delete mode 100644 cs/Markdown/Tags/Tag.cs create mode 100644 cs/Markdown_Tests/TestData/LineParserData.cs diff --git a/cs/Markdown/Extensions/TokenExtensions.cs b/cs/Markdown/Extensions/TokenExtensions.cs new file mode 100644 index 000000000..7dcbbee6c --- /dev/null +++ b/cs/Markdown/Extensions/TokenExtensions.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Markdown.Tokens; + +namespace Markdown.Extensions +{ + public static class TokenExtensions + { + public static bool NextTokenIs(this List tokens, TokenType tokenType, int currentIndex) + { + return currentIndex + 1 < tokens.Count && tokens[currentIndex + 1].TokenType == tokenType; + } + + public static bool LastTokenIs(this List tokens, TokenType tokenType, int currentIndex) + { + return currentIndex - 1 >= 0 && tokens[currentIndex - 1].TokenType == tokenType; + } + } +} diff --git a/cs/Markdown/ParsedLine.cs b/cs/Markdown/ParsedLine.cs index 7ce3966e3..9bf4bb054 100644 --- a/cs/Markdown/ParsedLine.cs +++ b/cs/Markdown/ParsedLine.cs @@ -6,11 +6,17 @@ public class ParsedLine { public readonly string Line; - public readonly List Tags; + public readonly List Tags; - public ParsedLine(string line, List tags) + public ParsedLine(string line, List tags) { - throw new NotImplementedException(); + var sortedTags = tags.OrderBy(x => x.Position).ToList(); + + if (sortedTags.Any(x => x.Position > line.Length)) + throw new ArgumentException("Позиция тега не может быть больше длины строки", nameof(sortedTags)); + + this.Line = line; + this.Tags = sortedTags; } } } diff --git a/cs/Markdown/Tags/ConcreteTags/BoldTag.cs b/cs/Markdown/Tags/ConcreteTags/BoldTag.cs index 9f56a52b0..10ccd7159 100644 --- a/cs/Markdown/Tags/ConcreteTags/BoldTag.cs +++ b/cs/Markdown/Tags/ConcreteTags/BoldTag.cs @@ -1,9 +1,23 @@ namespace Markdown.Tags.ConcreteTags { - public class BoldTag : Tag + public class BoldTag : ITag { - public BoldTag(int position, bool isCloseTag) : base(TagType.Bold, position, isCloseTag) + public TagType TagType => TagType.Bold; + + public int Position { get; set; } + + public bool IsCloseTag { get; set; } + + public bool IsAutoClosing => false; + + public string OpenTag => ""; + + public string CloseTag => ""; + + public BoldTag(int position, bool isCloseTag) { + Position = position; + IsCloseTag = isCloseTag; } } } diff --git a/cs/Markdown/Tags/ConcreteTags/HeaderTag.cs b/cs/Markdown/Tags/ConcreteTags/HeaderTag.cs index 2fe3af625..2e036f4be 100644 --- a/cs/Markdown/Tags/ConcreteTags/HeaderTag.cs +++ b/cs/Markdown/Tags/ConcreteTags/HeaderTag.cs @@ -1,9 +1,23 @@ namespace Markdown.Tags.ConcreteTags { - public class HeaderTag : Tag + public class HeaderTag : ITag { - public HeaderTag(int position, bool isCloseTag) : base(TagType.Header, position, isCloseTag) + public TagType TagType => TagType.Header; + + public int Position { get; set; } + + public bool IsCloseTag { get; set; } + + public bool IsAutoClosing => true; + + public string OpenTag => "

"; + + public string CloseTag => "

"; + + public HeaderTag(int position, bool isCloseTag) { + Position = position; + IsCloseTag = isCloseTag; } } } diff --git a/cs/Markdown/Tags/ConcreteTags/ItalicTag.cs b/cs/Markdown/Tags/ConcreteTags/ItalicTag.cs index 39b13c9e4..08de8446f 100644 --- a/cs/Markdown/Tags/ConcreteTags/ItalicTag.cs +++ b/cs/Markdown/Tags/ConcreteTags/ItalicTag.cs @@ -1,9 +1,23 @@ namespace Markdown.Tags.ConcreteTags { - public class ItalicTag : Tag + public class ItalicTag : ITag { - public ItalicTag(int position, bool isCloseTag) : base(TagType.Italic, position, isCloseTag) + public TagType TagType => TagType.Italic; + + public int Position { get; set; } + + public bool IsCloseTag { get; set; } + + public bool IsAutoClosing => false; + + public string OpenTag => ""; + + public string CloseTag => ""; + + public ItalicTag(int position, bool isCloseTag) { + Position = position; + IsCloseTag = isCloseTag; } } } diff --git a/cs/Markdown/Tags/ITag.cs b/cs/Markdown/Tags/ITag.cs new file mode 100644 index 000000000..4fc37e6b0 --- /dev/null +++ b/cs/Markdown/Tags/ITag.cs @@ -0,0 +1,15 @@ +namespace Markdown.Tags +{ + public interface ITag + { + public TagType TagType { get; } + public int Position { get; protected set; } + public bool IsCloseTag { get; protected set; } + + public bool IsAutoClosing { get; } + + public string OpenTag { get; } + + public string CloseTag { get; } + } +} diff --git a/cs/Markdown/Tags/Tag.cs b/cs/Markdown/Tags/Tag.cs deleted file mode 100644 index 0e89ef17e..000000000 --- a/cs/Markdown/Tags/Tag.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace Markdown.Tags -{ - public abstract class Tag - { - public TagType TagType { get; } - public int Position { get; } - public bool IsCloseTag { get; } - - public Tag(TagType tagType, int position, bool isCloseTag) - { - TagType = tagType; - Position = position; - IsCloseTag = isCloseTag; - } - } -} diff --git a/cs/Markdown/TokenParser/ConcreteParser/LineParser.cs b/cs/Markdown/TokenParser/ConcreteParser/LineParser.cs index ee1b71857..2977d51a0 100644 --- a/cs/Markdown/TokenParser/ConcreteParser/LineParser.cs +++ b/cs/Markdown/TokenParser/ConcreteParser/LineParser.cs @@ -1,5 +1,8 @@ using Markdown.Tags; +using Markdown.Tags.ConcreteTags; +using Markdown.TokenParser.Helpers; using Markdown.Tokens; +using System.Text; namespace Markdown.TokenParser.ConcreteParser { @@ -7,46 +10,273 @@ public class LineParser : ILineParser { public ParsedLine ParseLine(string line) { - throw new NotImplementedException(); + if (line == null) + throw new ArgumentNullException("String argument text must be not null"); + + var listTokens = GetTokens(line); + listTokens = EscapeTags(listTokens); + listTokens = EscapeInvalidTokens(listTokens); + listTokens = EscapeNonPairTokens(listTokens); + listTokens = EscapeWrongOrder(listTokens); + var parseLine = GetTagsAndCleanText(listTokens); + + return parseLine; } private List GetTokens(string line) { - throw new NotImplementedException(); - } + int position = 0; + var result = new List(); - private Tag GetNewTag(Token token, int position) - { - throw new NotImplementedException(); + while (position < line.Length) + { + var token = TokenGenerator.GetTokenBySymbol(line, position); + result.Add(token); + position += token.Content.Length; + } + + return result; } private List EscapeTags(List tokens) { - throw new NotImplementedException(); + Token? previousToken = null; + var result = new List(); + + foreach (var token in tokens) + { + if (previousToken is { TokenType: TokenType.Escape }) + { + if (token.TokenType is TokenType.MdTag or TokenType.Escape) + { + token.TokenType = TokenType.Text; + previousToken = token; + result.Add(token); + } + else + { + previousToken.TokenType = TokenType.Text; + result.Add(previousToken); + result.Add(token); + previousToken = token; + } + } + else if (token.TokenType == TokenType.Escape) + previousToken = token; + else + { + result.Add(token); + previousToken = token; + } + } + + if (previousToken is not { TokenType: TokenType.Escape }) + return result; + + previousToken.TokenType = TokenType.Text; + result.Add(previousToken); + return result; } private List EscapeInvalidTokens(List tokens) => - throw new NotImplementedException(); + tokens.Select((t, index) => + t.TokenType is not TokenType.MdTag || TokenValidator.IsValidTagToken(tokens, index) + ? t + : new Token(TokenType.Text, t.Content)) + .ToList(); private List EscapeNonPairTokens(List tokens) { - throw new NotImplementedException(); + var resultTokens = new List(); + var openTagsPositions = new Stack(); + var incorrectTags = new List(); + + for (var index = 0; index < tokens.Count; index++) + { + var token = tokens[index]; + resultTokens.Add(token); + + // Пропускаем токены, которые не являются тегами + if (token.TokenType != TokenType.MdTag) continue; + + // Проверяем, является ли текущий токен открывающим тегом + if (TokenValidator.IsTokenTagOpen(token.TagType, tokens, index)) + { + openTagsPositions.Push(index); + } + else + { + // Если это закрывающий тег + if (openTagsPositions.TryPop(out var lastOpenTokenIndex)) + { + SolveOpenAndCloseTags(openTagsPositions, tokens, lastOpenTokenIndex, index, incorrectTags); + } + else + { + // Если не нашли соответствующий открывающий тег + incorrectTags.Add(token); + } + } + } + + // Добавляем оставшиеся открытые теги как некорректные + while (openTagsPositions.Count > 0) + { + var token = tokens[openTagsPositions.Pop()]; + if(!token.IsSelfCosingTag) + incorrectTags.Add(token); + } + + ChangeTypesForIncorrectTokens(incorrectTags); + + return resultTokens; + } + + private void ChangeTypesForIncorrectTokens(List incorrectTags) + { + foreach (var token in incorrectTags) + { + token.TokenType = TokenType.Text; // Изменяем тип токена на текстовый + } } private void SolveOpenAndCloseTags(Stack openTags, List tokens, int openIndex, - int closeIndex, List incorrectTags) + int closeIndex, List incorrectTags) { - throw new NotImplementedException(); + var openTagToken = tokens[openIndex]; + var closeTagToken = tokens[closeIndex]; + closeTagToken.IsCloseTag = true; + + // Проверяем, совпадают ли типы открывающего и закрывающего тегов + if (openTagToken.TagType == closeTagToken.TagType) + { + tokens[openIndex].PairTagPosition = closeIndex; + tokens[closeIndex].PairTagPosition = openIndex; + return; + } + + // Проверяем следующий тег после закрывающего + if (TryGetNextTagType(tokens, closeIndex, out var nextTagTokenPosition)) + { + if (openTags.TryPeek(out var preOpenTagIndex) && + tokens[preOpenTagIndex].TagType == closeTagToken.TagType) + { + HandleNestedTags(openTags, tokens, preOpenTagIndex, openIndex, closeIndex, nextTagTokenPosition, incorrectTags); + return; + } + + // Если следующий тег не является открывающим + if (!TokenValidator.IsTokenTagOpen(tokens[nextTagTokenPosition].TagType, tokens, nextTagTokenPosition)) + { + HandleIncorrectTag(openTags, tokens, openIndex, closeIndex, incorrectTags); + return; + } + } + + // Если не удалось найти соответствующий открывающий тег + incorrectTags.Add(tokens[openIndex]); + incorrectTags.Add(tokens[closeIndex]); + } + + private void HandleNestedTags(Stack openTags, List tokens, int preOpenTagIndex, + int openIndex, int closeIndex, int nextTagTokenPosition, List incorrectTags) + { + // Обработка вложенных тегов + if (tokens[nextTagTokenPosition].TagType == tokens[openIndex].TagType) + { + openTags.Push(openIndex); + incorrectTags.Add(tokens[closeIndex]); + } + else + { + openTags.Pop(); + tokens[preOpenTagIndex].PairTagPosition = closeIndex; + tokens[closeIndex].PairTagPosition = preOpenTagIndex; + incorrectTags.Add(tokens[openIndex]); + } + } + + private void HandleIncorrectTag(Stack openTags, List tokens, + int openIndex, int closeIndex, List incorrectTags) + { + // Обработка некорректных тегов + if (openTags.Count > 0) + { + var preOpenTagIndex = openTags.Pop(); + incorrectTags.Add(tokens[preOpenTagIndex]); + incorrectTags.Add(tokens[openIndex]); + incorrectTags.Add(tokens[closeIndex]); + } + else + { + incorrectTags.Add(tokens[openIndex]); + incorrectTags.Add(tokens[closeIndex]); + } + } + + + private bool TryGetNextTagType(List tokens, int index, out int nextTagToken) + { + for (var i = index + 1; i < tokens.Count; i++) + { + if (tokens[i].TokenType is not TokenType.MdTag) continue; + nextTagToken = i; + return true; + } + + nextTagToken = -1; + return false; } private List EscapeWrongOrder(List tokens) { - throw new NotImplementedException(); + var result = new List(); + var openTags = new Stack(); + foreach (var t in tokens) + { + result.Add(t); + if (t.TokenType is TokenType.MdTag && !t.IsCloseTag) + openTags.Push(t); + else if (t.TokenType is TokenType.MdTag) + openTags.Pop(); + if (!TokenValidator.OrderIsCorrect(openTags, t)) + { + t.TokenType = TokenType.Text; + tokens[t.PairTagPosition].TokenType = TokenType.Text; + } + } + + return result; } private ParsedLine GetTagsAndCleanText(List tokens) { - throw new NotImplementedException(); + var result = new List(); + + var line = new StringBuilder(); + foreach (var token in tokens) + { + if (token.TokenType is not TokenType.MdTag) + { + line.Append(token.Content); + continue; + } + + result.Add(GetNewTag(token, line.Length)); + } + + return new ParsedLine(line.ToString(), result); + } + + private ITag GetNewTag(Token token, int position) + { + return token.TagType switch + { + TagType.Header => new HeaderTag(position, token.IsCloseTag), + TagType.Italic => new ItalicTag(position, token.IsCloseTag), + TagType.Bold => new BoldTag(position, token.IsCloseTag), + _ => throw new NotImplementedException() + }; } } } diff --git a/cs/Markdown/TokenParser/Helpers/TokenGeneratorRules/GenerateHashTokenRule.cs b/cs/Markdown/TokenParser/Helpers/TokenGeneratorRules/GenerateHashTokenRule.cs index df3a4d14f..cb4062850 100644 --- a/cs/Markdown/TokenParser/Helpers/TokenGeneratorRules/GenerateHashTokenRule.cs +++ b/cs/Markdown/TokenParser/Helpers/TokenGeneratorRules/GenerateHashTokenRule.cs @@ -13,7 +13,7 @@ public class GenerateHashTokenRule : ITokenGenerateRule public Token? GetToken(string line, int currentIndex) { if (currentIndex + 1 < line.Length && line[currentIndex] == '#' && line[currentIndex + 1] == ' ') - return new Token(TokenType.MdTag, "# ", TagType.Header); + return new Token(TokenType.MdTag, "# ", TagType.Header, true); return null; } diff --git a/cs/Markdown/TokenParser/Helpers/TokenValidator.cs b/cs/Markdown/TokenParser/Helpers/TokenValidator.cs index 1b021e53e..218d01733 100644 --- a/cs/Markdown/TokenParser/Helpers/TokenValidator.cs +++ b/cs/Markdown/TokenParser/Helpers/TokenValidator.cs @@ -1,23 +1,130 @@ -using Markdown.Tags; +using Markdown.Extensions; +using Markdown.Tags; using Markdown.Tokens; namespace Markdown.TokenParser.Helpers { - internal class TokenValidator + public class TokenValidator { public static bool IsTokenTagOpen(TagType tagType, List tokens, int index) { - throw new NotImplementedException(); + switch (tagType) + { + case TagType.Italic: + return IsBoldOrItalicOpen(tokens, index); + case TagType.Bold: + return IsBoldOrItalicOpen(tokens, index); + case TagType.Header: + default: + return true; + } } public static bool IsValidTagToken(List tokens, int index) { - throw new NotImplementedException(); + return tokens[index].TagType switch + { + TagType.Italic => IsValidItalic(tokens, index), + TagType.Bold => IsValidBold(tokens, index), + TagType.Header => IsValidHeader(tokens, index), + _ => true + }; } public static bool OrderIsCorrect(Stack openedTokens, Token token) { - throw new NotImplementedException(); + return token.TagType != TagType.Bold || openedTokens.All(x => x.TagType != TagType.Italic); } + + private static bool IsValidHeader(List tokens, int index) + { + if (index == 0 && index + 1 < tokens.Count && tokens[index].TokenType == TokenType.MdTag) + return true; + + return false; + } + + private static bool IsValidItalic(List tokens, int index) + { + return IsValidUnderscoreTag(tokens, index); + } + + private static bool IsValidBold(List tokens, int index) + { + return IsValidUnderscoreTag(tokens, index); + } + + private static bool IsValidUnderscoreTag(List tokens, int index) + { + var isOpen = IsBoldOrItalicOpen(tokens, index); + var isClosed = IsBoldOrItalicClosed(tokens, index); + + return (isOpen ^ isClosed); + } + + #region IsBoldOrItalicOpen + + private static bool IsBoldOrItalicOpen(List tokens, int index) + { + var betweenWordsOpen = IsBoldOrItalicBetweenWordsOpen(tokens, index); + + var inOneWord = IsBoldOrItalicInOneWordOpen(tokens, index); + + return betweenWordsOpen ^ inOneWord; + } + + //Является ли тег открывающим для ситуации когда тег применятеся к нескольким + //применяется между словами: '_вася' + private static bool IsBoldOrItalicBetweenWordsOpen(List tokens, int index) + { + return tokens.NextTokenIs(TokenType.Text, index) && + (index - 1 < 0 || tokens.LastTokenIs(TokenType.WhiteSpace, index)) ; + } + + //Является ли тег открывающим для ситуации когда тег применятеся внутри + //слова: ' кр_овать' '_word_w_w_word_ + private static bool IsBoldOrItalicInOneWordOpen(List tokens, int index) + { + return tokens.NextTokenIs(TokenType.Text, index) && + tokens.LastTokenIs(TokenType.Text, index) && + !IsBoldOrItalicOpen(tokens, index - 2); + } + + #endregion + + #region IsBoldOrdItalicClosed + + private static bool IsBoldOrItalicClosed(List tokens, int index) + { + var betweenWordsOpen = IsBoldOrItalicBetweenWordsClosed(tokens, index); + + var inOneWord = IsBoldOrItalicInOneWordClosed(tokens, index); + + return betweenWordsOpen ^ inOneWord; + } + + private static bool IsBoldOrItalicBetweenWordsClosed(List tokens, int index) + { + return tokens.NextTokenIs(TokenType.WhiteSpace, index) && + tokens.LastTokenIs(TokenType.Text, index); + } + + private static bool IsBoldOrItalicInOneWordClosed(List tokens, int index) + { + return tokens.NextTokenIs(TokenType.Text, index) && + tokens.LastTokenIs(TokenType.Text, index) && + IsBoldOrItalicOpen(tokens, index - 2); + } + + #endregion + + //private static bool IsNearNumber(List tokens, int index) + //{ + // return index - 1 >= 0 && index + 1 < tokens.Count && + // (tokens[index - 1].TokenType is TokenType.Number && + // tokens[index + 1].TokenType is not TokenType.WhiteSpace || + // tokens[index + 1].TokenType is TokenType.Number && + // tokens[index - 1].TokenType is not TokenType.WhiteSpace); + //} } } diff --git a/cs/Markdown/Tokens/Token.cs b/cs/Markdown/Tokens/Token.cs index a85b024b8..444d80dde 100644 --- a/cs/Markdown/Tokens/Token.cs +++ b/cs/Markdown/Tokens/Token.cs @@ -12,13 +12,17 @@ public class Token public bool IsCloseTag; + public bool IsSelfCosingTag; + public int PairTagPosition; - public Token(TokenType tokenType, string content, TagType tagType = TagType.UnDefined) + public Token(TokenType tokenType, string content, TagType tagType = TagType.UnDefined, bool isSelfClosing = false) { TokenType = tokenType; Content = content; TagType = tagType; + + IsSelfCosingTag = isSelfClosing; } } } diff --git a/cs/Markdown_Tests/LineParser_Tests.cs b/cs/Markdown_Tests/LineParser_Tests.cs index cccf777c1..bf502d89f 100644 --- a/cs/Markdown_Tests/LineParser_Tests.cs +++ b/cs/Markdown_Tests/LineParser_Tests.cs @@ -1,15 +1,54 @@ -using Markdown.TokenParser.ConcreteParser; +using FluentAssertions; +using Markdown.Tags; +using Markdown.TokenParser.ConcreteParser; +using MarkdownTests.TestData; namespace MarkdownTests { public class LineParserTests { - private LineParser tokenizer = new LineParser(); + private LineParser parser = new LineParser(); [Test] public void ParseLine_ThrowArgumentNullException_WhenArgumentIsNull() { - Assert.Throws(() => tokenizer.ParseLine(null), "String argument text must be not null"); + Assert.Throws(() => parser.ParseLine(null), "String argument text must be not null"); + } + + [TestCaseSource(typeof(LineParserData), nameof(LineParserData.WordsOnlyLines))] + public void ParseLine_ShoudBeCorrect_WhenLineWithWordsOnly(string inLine, string expectedLine, List tags) + { + var parsedLines = parser.ParseLine(inLine); + + parsedLines.Line.Should().BeEquivalentTo(expectedLine); + parsedLines.Tags.Should().BeEquivalentTo(tags); + } + + [TestCaseSource(typeof(LineParserData), nameof(LineParserData.LineWithHeader))] + public void ParseLine_ShoudBeCorrect_WhenLineWithHeaderTags(string inLine, string expectedLine, List tags) + { + var parsedLines = parser.ParseLine(inLine); + + parsedLines.Line.Should().BeEquivalentTo(expectedLine); + parsedLines.Tags.Should().BeEquivalentTo(tags); + } + + [TestCaseSource(typeof(LineParserData), nameof(LineParserData.LineWithItalic))] + public void ParseLine_ShoudBeCorrect_WhenLineWithItalicTags(string inLine, string expectedLine, List tags) + { + var parsedLines = parser.ParseLine(inLine); + + parsedLines.Line.Should().BeEquivalentTo(expectedLine); + parsedLines.Tags.Should().BeEquivalentTo(tags); + } + + [TestCaseSource(typeof(LineParserData), nameof(LineParserData.LineWithBold))] + public void ParseLine_ShoudBeCorrect_WhenLineWithBoldTags(string inLine, string expectedLine, List tags) + { + var parsedLines = parser.ParseLine(inLine); + + parsedLines.Line.Should().BeEquivalentTo(expectedLine); + parsedLines.Tags.Should().BeEquivalentTo(tags); } } } diff --git a/cs/Markdown_Tests/TestData/LineParserData.cs b/cs/Markdown_Tests/TestData/LineParserData.cs new file mode 100644 index 000000000..e90b6683f --- /dev/null +++ b/cs/Markdown_Tests/TestData/LineParserData.cs @@ -0,0 +1,69 @@ +using Markdown.Tags.ConcreteTags; +using Markdown.Tags; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MarkdownTests.TestData +{ + public static class LineParserData + { + public static IEnumerable WordsOnlyLines() + { + yield return new TestCaseData("word bubo bibo", "word bubo bibo", new List()); + yield return new TestCaseData("Why1 word 23 bubo bibo", "Why1 word 23 bubo bibo", new List()); + } + + public static IEnumerable LineWithHeader() + { + yield return new TestCaseData("# word bubo bibo", "word bubo bibo", new List() + { + new HeaderTag(0, false), + }); + yield return new TestCaseData("# Why1 word 23 bubo bibo", "Why1 word 23 bubo bibo", new List() + { + new HeaderTag(0, false), + }); + } + + public static IEnumerable LineWithItalic() + { + yield return new TestCaseData("_word bubo_ bibo", "word bubo bibo", new List() + { + new ItalicTag(0, false), + new ItalicTag(9, true), + }); + yield return new TestCaseData("_wo_rd bubo bibo", "word bubo bibo", new List() + { + new ItalicTag(0, false), + new ItalicTag(2, true), + }); + yield return new TestCaseData("_word __bubo_ bibo__", "_word __bubo_ bibo__", new List()); + yield return new TestCaseData(@"\_word bubo_ bibo", "_word bubo_ bibo", new List()); + yield return new TestCaseData(@"_word bubo\_ bibo", "_word bubo_ bibo", new List()); + yield return new TestCaseData(@"\_word bubo\_ bibo", "_word bubo_ bibo", new List()); + yield return new TestCaseData("Why_1_ word 23 bubo bibo", "Why_1_ word 23 bubo bibo", new List()); + } + + public static IEnumerable LineWithBold() + { + yield return new TestCaseData("__word bubo__ bibo", "word bubo bibo", new List() + { + new BoldTag(0, false), + new BoldTag(9, true), + }); + yield return new TestCaseData("__wo__rd bubo bibo", "word bubo bibo", new List() + { + new BoldTag(0, false), + new BoldTag(2, true), + }); + yield return new TestCaseData("_word __bubo_ bibo__", "_word __bubo_ bibo__", new List()); + yield return new TestCaseData(@"\__word bubo__ bibo", "__word bubo__ bibo", new List()); + yield return new TestCaseData(@"__word bubo\__ bibo", "__word bubo__ bibo", new List()); + yield return new TestCaseData(@"\__word bubo\__ bibo", @"__word bubo__ bibo", new List()); + yield return new TestCaseData("Why__1__ word 23 bubo bibo", "Why__1__ word 23 bubo bibo", new List()); + } + } +} diff --git a/cs/Markdown_Tests/TestData/TokenGeneratorTestsData.cs b/cs/Markdown_Tests/TestData/TokenGeneratorTestsData.cs index 44a6a8736..ef08afbb0 100644 --- a/cs/Markdown_Tests/TestData/TokenGeneratorTestsData.cs +++ b/cs/Markdown_Tests/TestData/TokenGeneratorTestsData.cs @@ -58,22 +58,22 @@ public static IEnumerable LinesWithHeader() { yield return new TestCaseData("# Заголовок", new List() { - new Token(TokenType.MdTag, "# ", TagType.Header), + new Token(TokenType.MdTag, "# ", TagType.Header, true), new Token(TokenType.Text, "Заголовок", TagType.UnDefined), }); yield return new TestCaseData("# # # ", new List() { - new Token(TokenType.MdTag, "# ", TagType.Header), - new Token(TokenType.MdTag, "# ", TagType.Header), - new Token(TokenType.MdTag, "# ", TagType.Header) + new Token(TokenType.MdTag, "# ", TagType.Header, true), + new Token(TokenType.MdTag, "# ", TagType.Header, true), + new Token(TokenType.MdTag, "# ", TagType.Header, true) }); yield return new TestCaseData(@" # # ", new List() { new Token(TokenType.WhiteSpace, " ", TagType.UnDefined), - new Token(TokenType.MdTag, "# ", TagType.Header), - new Token(TokenType.MdTag, "# ", TagType.Header), + new Token(TokenType.MdTag, "# ", TagType.Header, true), + new Token(TokenType.MdTag, "# ", TagType.Header, true), }); } @@ -133,13 +133,13 @@ public static IEnumerable LineWithMultiTokens() new Token(TokenType.WhiteSpace, " ", TagType.UnDefined), new Token(TokenType.MdTag, "_", TagType.Italic), new Token(TokenType.WhiteSpace, " ", TagType.UnDefined), - new Token(TokenType.MdTag, "# ", TagType.Header), + new Token(TokenType.MdTag, "# ", TagType.Header, true), }); yield return new TestCaseData("__# _", new List() { new Token(TokenType.MdTag, "__", TagType.Bold), - new Token(TokenType.MdTag, "# ", TagType.Header), + new Token(TokenType.MdTag, "# ", TagType.Header, true), new Token(TokenType.MdTag, "_", TagType.Italic), }); @@ -155,7 +155,7 @@ public static IEnumerable LineWithMultiTokens() yield return new TestCaseData(@"\# word", new List() { new Token(TokenType.Escape, @"\", TagType.Escape), - new Token(TokenType.MdTag, "# ", TagType.Header), + new Token(TokenType.MdTag, "# ", TagType.Header, true), new Token(TokenType.Text, "word", TagType.UnDefined), }); } diff --git a/cs/Markdown_Tests/TokenGenerator_Tests.cs b/cs/Markdown_Tests/TokenGenerator_Tests.cs index e30eab26f..a7179f9ba 100644 --- a/cs/Markdown_Tests/TokenGenerator_Tests.cs +++ b/cs/Markdown_Tests/TokenGenerator_Tests.cs @@ -36,8 +36,7 @@ public void TokenGenerator_GetTokensBySymbolCorrectly_WhenHeaderTokenOnly() actuallyTokens.Should().BeEquivalentTo(new List() { - new Token(TokenType.MdTag, "# ", TagType.Header) - + new Token(TokenType.MdTag, "# ", TagType.Header, true) }); } From d0933ebf20509cb22417561914eb8dbf1f5b28e6 Mon Sep 17 00:00:00 2001 From: Braidenbikher Victor Date: Tue, 3 Dec 2024 15:41:54 +0500 Subject: [PATCH 05/13] =?UTF-8?q?=D0=9D=D0=B5=20=D0=BF=D1=80=D0=BE=D1=85?= =?UTF-8?q?=D0=BE=D0=B4=D0=B8=D1=82=20=D1=82=D0=B5=D1=81=D1=82=D1=8B=20?= =?UTF-8?q?=D0=BF=D0=B5=D1=80=D0=B5=D1=81=D0=B5=D1=87=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D1=8F=20=D0=B8=20=D0=BF=D0=BE=D0=B4=D1=87=D0=B5=D1=80=D0=BA?= =?UTF-8?q?=D0=B8=D0=B2=D0=B0=D0=BD=D0=B8=D1=8F=20=D0=B2=20=D1=80=D0=B0?= =?UTF-8?q?=D0=B7=D0=BD=D1=8B=D1=85=20=D1=81=D0=BB=D0=BE=D0=B2=D0=B0=D1=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ConcreteConverter/HtmlConverter.cs | 35 +- .../TokenParser/ConcreteParser/LineParser.cs | 7 +- .../TokenParser/Helpers/TokenValidator.cs | 27 +- cs/Markdown_Tests/Md_Tests.cs | 331 ++++++++++-------- 4 files changed, 222 insertions(+), 178 deletions(-) diff --git a/cs/Markdown/Converter/ConcreteConverter/HtmlConverter.cs b/cs/Markdown/Converter/ConcreteConverter/HtmlConverter.cs index c50224eca..c4040e372 100644 --- a/cs/Markdown/Converter/ConcreteConverter/HtmlConverter.cs +++ b/cs/Markdown/Converter/ConcreteConverter/HtmlConverter.cs @@ -1,26 +1,33 @@ using Markdown.Tags; +using System.Text; namespace Markdown.Converter.ConcreteConverter { public class HtmlConverter : IConverter { - private static readonly Dictionary startTags = new() + public string Convert(ParsedLine[] parsedLines) { - { TagType.Bold, "" }, - { TagType.Italic, "" }, - { TagType.Header, "

" }, - }; + var sb = new StringBuilder(); - private static readonly Dictionary closeTags = new() - { - { TagType.Bold, "" }, - { TagType.Italic, "" }, - { TagType.Header, "

" } - }; + foreach (var text in parsedLines) + { + var prevTagPos = 0; + foreach (var tag in text.Tags) + { + sb.Append(text.Line.AsSpan(prevTagPos, tag.Position - prevTagPos)); - public string Convert(ParsedLine[] parsedLines) - { - throw new NotImplementedException(); + sb.Append(tag.IsCloseTag ? + tag.CloseTag : tag.OpenTag); + + prevTagPos = tag.Position; + } + + sb.Append(text.Line.AsSpan(prevTagPos, text.Line.Length - prevTagPos)); + if (text.Tags.Count > 0 && text.Tags[0].TagType == TagType.Header) + sb.Append(text.Tags[0].CloseTag); + } + + return sb.ToString(); } } } diff --git a/cs/Markdown/TokenParser/ConcreteParser/LineParser.cs b/cs/Markdown/TokenParser/ConcreteParser/LineParser.cs index 2977d51a0..d7606ccf5 100644 --- a/cs/Markdown/TokenParser/ConcreteParser/LineParser.cs +++ b/cs/Markdown/TokenParser/ConcreteParser/LineParser.cs @@ -104,7 +104,7 @@ private List EscapeNonPairTokens(List tokens) { openTagsPositions.Push(index); } - else + else if(TokenValidator.IsTokenTagClosed(token.TagType, tokens, index)) { // Если это закрывающий тег if (openTagsPositions.TryPop(out var lastOpenTokenIndex)) @@ -117,6 +117,11 @@ private List EscapeNonPairTokens(List tokens) incorrectTags.Add(token); } } + // else + // { + // // Если не нашли соответствующий открывающий тег + // incorrectTags.Add(token); + // } } // Добавляем оставшиеся открытые теги как некорректные diff --git a/cs/Markdown/TokenParser/Helpers/TokenValidator.cs b/cs/Markdown/TokenParser/Helpers/TokenValidator.cs index 218d01733..c78e6f3a9 100644 --- a/cs/Markdown/TokenParser/Helpers/TokenValidator.cs +++ b/cs/Markdown/TokenParser/Helpers/TokenValidator.cs @@ -20,6 +20,22 @@ public static bool IsTokenTagOpen(TagType tagType, List tokens, int index } } + private static bool lastOpenWasInWord; + + public static bool IsTokenTagClosed(TagType tagType, List tokens, int index) + { + switch (tagType) + { + case TagType.Italic: + return IsBoldOrItalicClosed(tokens, index); + case TagType.Bold: + return IsBoldOrItalicClosed(tokens, index); + case TagType.Header: + default: + return true; + } + } + public static bool IsValidTagToken(List tokens, int index) { return tokens[index].TagType switch @@ -105,7 +121,7 @@ private static bool IsBoldOrItalicClosed(List tokens, int index) private static bool IsBoldOrItalicBetweenWordsClosed(List tokens, int index) { - return tokens.NextTokenIs(TokenType.WhiteSpace, index) && + return (tokens.NextTokenIs(TokenType.WhiteSpace, index) || index + 1 >= tokens.Count) && tokens.LastTokenIs(TokenType.Text, index); } @@ -117,14 +133,5 @@ private static bool IsBoldOrItalicInOneWordClosed(List tokens, int index) } #endregion - - //private static bool IsNearNumber(List tokens, int index) - //{ - // return index - 1 >= 0 && index + 1 < tokens.Count && - // (tokens[index - 1].TokenType is TokenType.Number && - // tokens[index + 1].TokenType is not TokenType.WhiteSpace || - // tokens[index + 1].TokenType is TokenType.Number && - // tokens[index - 1].TokenType is not TokenType.WhiteSpace); - //} } } diff --git a/cs/Markdown_Tests/Md_Tests.cs b/cs/Markdown_Tests/Md_Tests.cs index 93723fc14..08d826931 100644 --- a/cs/Markdown_Tests/Md_Tests.cs +++ b/cs/Markdown_Tests/Md_Tests.cs @@ -1,153 +1,178 @@ -//using FluentAssertions; -//using Markdown; -//using Markdown.TokenParser.ConcreteParser; - - -//namespace Markdown_Tests -//{ -// public class MdTests -// { -// private Md markdown = new Md(new LineParser(), new HtmlConverter()); - -// #region HeaderTests - -// [TestCase(@"# bibo", "

bibo

")] -// [TestCase(@"# # bibo", "

# bibo

")] -// public void Md_ShouldCreateHeaderCorrectly_WhenHeaderNotEscaped(string input, string expected) -// { -// markdown.Render(input).Should().BeEquivalentTo(expected); -// } - -// [TestCase(@"\# bibo", @"\# bibo")] -// [TestCase(@"\# # bibo", @"\# # bibo")] -// public void Md_ShouldNotCreateHeaderTag_WhenHeaderEscaped(string input, string expected) -// { -// markdown.Render(input).Should().BeEquivalentTo(expected); -// } - -// [TestCase(@"\\# bibo", @"\# bibo")] -// [TestCase(@"a # # bibo", @"a # # bibo")] -// public void Md_ShouldNotCreateHeaderTag_WhenAreCharsBeforeHash(string input, string expected) -// { -// markdown.Render(input).Should().BeEquivalentTo(expected); -// } - -// #endregion - -// #region ItalicTests - -// [TestCase(@"_bibo love bubu_", @"bibo love bubu")] -// [TestCase(@"bibo _love_ bubu", @"bibo love bubu")] -// public void Md_ShouldCreateItalicTag_WhenTagAfterWhiteSpace(string input, string expected) -// { -// markdown.Render(input).Should().BeEquivalentTo(expected); -// } - -// [TestCase(@"_ bibo love bubu_", @"_ bibo love bubu_")] -// [TestCase(@"bibo _love _ bubu", @"bibo _love _ bubu")] -// [TestCase(@"bibo _ love _ bubu", @"bibo _ love _ bubu")] -// public void Md_ShouldNotCreateItalicTag_WhenWhiteSpaceBeforeOrAfterText(string input, string expected) -// { -// markdown.Render(input).Should().BeEquivalentTo(expected); -// } - -// [TestCase(@"bibo _lo_ve bubu", @"bibo love bubu")] -// [TestCase(@"bibo l_ove_ bubu", @"bibo love bubu")] -// public void Md_ShouldCreateItalicTag_WhenTagInsideTextWithoutDigits(string input, string expected) -// { -// markdown.Render(input).Should().BeEquivalentTo(expected); -// } - -// [TestCase(@"bibo \_love_ bubu", @"bibo _love_ bubu")] -// [TestCase(@"bibo _love\_ bubu", @"bibo _love_ bubu")] -// [TestCase(@"bibo \_love\_ bubu", @"bibo _love_ bubu")] -// public void Md_ShouldNotCreateItalicTag_WhenTagEscaped(string input, string expected) -// { -// markdown.Render(input).Should().BeEquivalentTo(expected); -// } - -// [Test] -// public void Md_ShoudNotCreateItalicAndBoldTags_WhenBoldTagsInsideItalic() -// { -// var input = "I _want to __sleep__ tonight_"; -// var expected = "I want to __sleep__ tonight"; - -// markdown.Render(input).Should().BeEquivalentTo(expected); -// } - -// [TestCase(@" c _12_3 ", @" c _12_3 ")] -// [TestCase(@"bibo love_4_ bubu", @"bibo love_4_ bubu")] -// public void Md_ShouldNotCreateItalicTag_WhenTextInsideHaveDigits(string input, string expected) -// { -// markdown.Render(input).Should().BeEquivalentTo(expected); -// } - -// #endregion - -// #region BoldTests - -// [TestCase(@"__bibo love bubu__", @"bibo love bubu")] -// [TestCase(@"bibo __love__ bubu", @"bibo love bubu")] -// public void Md_ShouldCreateBoldTag_WhenTagAfterSpace(string input, string expected) -// { -// markdown.Render(input).Should().BeEquivalentTo(expected); -// } - -// [TestCase(@"__ bibo love bubu__", @"__ bibo love bubu__")] -// [TestCase(@"bibo __love __ bubu", @"bibo __love __ bubu")] -// [TestCase(@"bibo __ love __ bubu", @"bibo __ love __ bubu")] -// public void Md_ShouldNotCreateBoldTag_WhenWhiteSpaceBeforeOrAfterText(string input, string expected) -// { -// markdown.Render(input).Should().BeEquivalentTo(expected); -// } - -// [TestCase(@"bibo \__love__ bubu", @"bibo __love__ bubu")] -// [TestCase(@"bibo __love\__ bubu", @"bibo __love__ bubu")] -// [TestCase(@"bibo \__love\__ bubu", @"bibo __love__ bubu")] -// public void Md_ShouldNotCreateBoldTag_WhenTagEscaped(string input, string expected) -// { -// markdown.Render(input).Should().BeEquivalentTo(expected); -// } - -// [Test] -// public void Md_ShoudCreateItalicAndBoldTags_WhenItalicTagsInsideBold() -// { -// var input = "I __want to _sleep_ tonight__"; -// var expected = "I want to sleep tonight"; - -// markdown.Render(input).Should().BeEquivalentTo(expected); -// } - -// [TestCase(@" c __12__3 ", @" c __12__3 ")] -// [TestCase(@"bibo love__4__ bubu", @"bibo love__4__ bubu")] -// public void Md_ShouldNotCreateBoldTag_WhenTextInsideHaveDigits(string input, string expected) -// { -// markdown.Render(input).Should().BeEquivalentTo(expected); -// } - -// #endregion - -// #region IntersectionTest - -// [TestCase(@"__bibo _love__ bubu_", @"__bibo _love__ bubu_")] -// [TestCase(@"_bibo __love_ bubu__", @"_bibo __love_ bubu__")] -// public void Md_ShouldProccessIntersectsCorrecttly(string input, string expected) -// { -// markdown.Render(input).Should().BeEquivalentTo(expected); -// } - -// #endregion - -// [TestCase(@"____", @"____")] -// [TestCase(@"__", @"__")] -// public void Md_CanCreateEmptyItalicOrBoldTag(string input, string expected) -// { -// markdown.Render(input).Should().BeEquivalentTo(expected); -// } - -// public void Md_ShouldCreateEmptyHtml_WhenTextIsStringEmpty(string input, string expected) -// { -// markdown.Render(String.Empty).Should().BeEquivalentTo(String.Empty); -// } -// } -//} \ No newline at end of file +using FluentAssertions; +using Markdown.Converter.ConcreteConverter; +using Markdown.TokenParser.ConcreteParser; +using Markdown; + + +namespace MarkdownTests +{ + public class MdTests + { + private Md markdown = new Md(new LineParser(), new HtmlConverter()); + + #region HeaderTests + + [TestCase(@"# bibo", "

bibo

")] + [TestCase(@" # bibo", " # bibo")] + [TestCase(@"# # bibo", "

# bibo

")] + public void Md_ShouldCreateHeaderCorrectly_WhenHeaderNotEscaped(string input, string expected) + { + markdown.Render(input).Should().BeEquivalentTo(expected); + } + + [TestCase(@"\# bibo", @"# bibo")] + [TestCase(@"\# # bibo", @"# # bibo")] + public void Md_ShouldNotCreateHeaderTag_WhenHeaderEscaped(string input, string expected) + { + markdown.Render(input).Should().BeEquivalentTo(expected); + } + + [TestCase(@"\\# bibo", @"\# bibo")] + [TestCase(@"a # # bibo", @"a # # bibo")] + public void Md_ShouldNotCreateHeaderTag_WhenAreCharsBeforeHash(string input, string expected) + { + markdown.Render(input).Should().BeEquivalentTo(expected); + } + + #endregion + + #region ItalicTests + + [TestCase(@"_bibo love bubu_", @"bibo love bubu")] + [TestCase(@"bibo _love_ bubu", @"bibo love bubu")] + public void Md_ShouldCreateItalicTag_WhenTagAfterWhiteSpace(string input, string expected) + { + markdown.Render(input).Should().BeEquivalentTo(expected); + } + + [TestCase(@"_ bibo love bubu_", @"_ bibo love bubu_")] + [TestCase(@"bibo _love _ bubu", @"bibo _love _ bubu")] + [TestCase(@"bibo _ love _ bubu", @"bibo _ love _ bubu")] + public void Md_ShouldNotCreateItalicTag_WhenWhiteSpaceBeforeOrAfterText(string input, string expected) + { + markdown.Render(input).Should().BeEquivalentTo(expected); + } + + [TestCase(@"bibo _lo_ve bubu", @"bibo love bubu")] + [TestCase(@"bibo l_ove_ bubu", @"bibo love bubu")] + public void Md_ShouldCreateItalicTag_WhenTagInsideTextWithoutDigits(string input, string expected) + { + markdown.Render(input).Should().BeEquivalentTo(expected); + } + + + [TestCase(@"bibo _love bu_bu", @"bibo _love bu_bu")] + [TestCase(@"bibo l_ove bu_bu", @"bibo l_ove bu_bu")] + [TestCase(@"bibo l_ove bubu_", @"bibo l_ove bubu_")] + public void Md_ShouldNotCreateItalicTag_WhenTagInsideTextInDifferentWords(string input, string expected) + { + markdown.Render(input).Should().BeEquivalentTo(expected); + } + + [TestCase(@"bibo \_love_ bubu", @"bibo _love_ bubu")] + [TestCase(@"bibo _love\_ bubu", @"bibo _love_ bubu")] + [TestCase(@"bibo \_love\_ bubu", @"bibo _love_ bubu")] + public void Md_ShouldNotCreateItalicTag_WhenTagEscaped(string input, string expected) + { + markdown.Render(input).Should().BeEquivalentTo(expected); + } + + [Test] + public void Md_ShoudNotCreateItalicAndBoldTags_WhenBoldTagsInsideItalic() + { + var input = "I _want to __sleep__ tonight_"; + var expected = "I want to __sleep__ tonight"; + + markdown.Render(input).Should().BeEquivalentTo(expected); + } + + [TestCase(@" c _12_3 ", @" c _12_3 ")] + [TestCase(@"bibo love_4_ bubu", @"bibo love_4_ bubu")] + public void Md_ShouldNotCreateItalicTag_WhenTextInsideHaveDigits(string input, string expected) + { + markdown.Render(input).Should().BeEquivalentTo(expected); + } + + #endregion + + #region BoldTests + + [TestCase(@"__bibo love bubu__", @"bibo love bubu")] + [TestCase(@"bibo __love__ bubu", @"bibo love bubu")] + public void Md_ShouldCreateBoldTag_WhenTagAfterSpace(string input, string expected) + { + markdown.Render(input).Should().BeEquivalentTo(expected); + } + + [TestCase(@"__ bibo love bubu__", @"__ bibo love bubu__")] + [TestCase(@"bibo __love __ bubu", @"bibo __love __ bubu")] + [TestCase(@"bibo __ love __ bubu", @"bibo __ love __ bubu")] + public void Md_ShouldNotCreateBoldTag_WhenWhiteSpaceBeforeOrAfterText(string input, string expected) + { + markdown.Render(input).Should().BeEquivalentTo(expected); + } + + [TestCase(@"bibo \__love__ bubu", @"bibo __love__ bubu")] + [TestCase(@"bibo __love\__ bubu", @"bibo __love__ bubu")] + [TestCase(@"bibo \__love\__ bubu", @"bibo __love__ bubu")] + public void Md_ShouldNotCreateBoldTag_WhenTagEscaped(string input, string expected) + { + markdown.Render(input).Should().BeEquivalentTo(expected); + } + + [TestCase(@"bibo __lo__ve bubu", @"bibo love bubu")] + [TestCase(@"bibo l__ove__ bubu", @"bibo love bubu")] + public void Md_ShouldCreateBoldTag_WhenTagInsideTextWithoutDigits(string input, string expected) + { + markdown.Render(input).Should().BeEquivalentTo(expected); + } + + [TestCase(@"bibo __love bu__bu", @"bibo __love bu__bu")] + [TestCase(@"bibo l__ove bu__bu", @"bibo l__ove bu__bu")] + public void Md_ShouldNotCreateBoldTag_WhenTagInsideTextInDifferentWords(string input, string expected) + { + markdown.Render(input).Should().BeEquivalentTo(expected); + } + + [Test] + public void Md_ShoudCreateItalicAndBoldTags_WhenItalicTagsInsideBold() + { + var input = "I __want to _sleep_ tonight__"; + var expected = "I want to sleep tonight"; + + markdown.Render(input).Should().BeEquivalentTo(expected); + } + + [TestCase(@" c __12__3 ", @" c __12__3 ")] + [TestCase(@"bibo love__4__ bubu", @"bibo love__4__ bubu")] + public void Md_ShouldNotCreateBoldTag_WhenTextInsideHaveDigits(string input, string expected) + { + markdown.Render(input).Should().BeEquivalentTo(expected); + } + + #endregion + + #region IntersectionTest + + [TestCase(@"__bibo _love__ bubu_", @"__bibo _love__ bubu_")] + [TestCase(@"_bibo __love_ bubu__", @"_bibo __love_ bubu__")] + public void Md_ShouldProccessIntersectsCorrecttly(string input, string expected) + { + markdown.Render(input).Should().BeEquivalentTo(expected); + } + + #endregion + + [TestCase(@"____", @"____")] + [TestCase(@"__", @"__")] + public void Md_CanCreateEmptyItalicOrBoldTag(string input, string expected) + { + markdown.Render(input).Should().BeEquivalentTo(expected); + } + + public void Md_ShouldCreateEmptyHtml_WhenTextIsStringEmpty(string input, string expected) + { + markdown.Render(String.Empty).Should().BeEquivalentTo(String.Empty); + } + } +} \ No newline at end of file From c827718ca3e18bf3e63708b4a8a755990b622c53 Mon Sep 17 00:00:00 2001 From: Braidenbikher Victor Date: Tue, 3 Dec 2024 17:33:08 +0500 Subject: [PATCH 06/13] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=20=D1=84=D1=83=D0=BD=D0=BA=D1=86=D0=B8=D0=BE=D0=BD=D0=B0?= =?UTF-8?q?=D0=BB=20=D0=BC=D0=B0=D1=80=D0=BA=D0=B5=D1=80=D0=BE=D0=B2=D0=B0?= =?UTF-8?q?=D0=BD=D0=BD=D0=BE=D0=B3=D0=BE=20=D1=81=D0=BF=D0=B8=D1=81=D0=BA?= =?UTF-8?q?=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ConcreteConverter/HtmlConverter.cs | 20 +++++++++ cs/Markdown/Extensions/TokenExtensions.cs | 9 +--- cs/Markdown/Tags/ConcreteTags/BulletTag.cs | 18 ++++++++ .../TokenParser/ConcreteParser/LineParser.cs | 8 ++-- .../TokenParser/Helpers/ITokenGenerateRule.cs | 7 +--- .../TokenParser/Helpers/TokenGenerator.cs | 10 +---- .../GenerateBoldTokenRule.cs | 7 +--- .../GenerateBulletTokenRule.cs | 15 +++++++ .../GenerateEscapeTokenRule.cs | 7 +--- .../GenerateHashTokenRule.cs | 7 +--- .../GenerateItalicTokenRule.cs | 7 +--- .../GenerateTextTokenRule.cs | 7 +--- .../GenerateWhiteSpaceRule.cs | 7 +--- .../TokenParser/Helpers/TokenValidator.cs | 19 +++++++-- cs/Markdown_Tests/LineParser_Tests.cs | 9 ++++ cs/Markdown_Tests/Md_Tests.cs | 41 ++++++++++++++++++- cs/Markdown_Tests/TestData/LineParserData.cs | 18 ++++---- .../TestData/TokenGeneratorTestsData.cs | 37 ++++++++++++++--- cs/Markdown_Tests/TokenGenerator_Tests.cs | 17 ++++---- 19 files changed, 185 insertions(+), 85 deletions(-) create mode 100644 cs/Markdown/Tags/ConcreteTags/BulletTag.cs create mode 100644 cs/Markdown/TokenParser/Helpers/TokenGeneratorRules/GenerateBulletTokenRule.cs diff --git a/cs/Markdown/Converter/ConcreteConverter/HtmlConverter.cs b/cs/Markdown/Converter/ConcreteConverter/HtmlConverter.cs index c4040e372..2fe00d93c 100644 --- a/cs/Markdown/Converter/ConcreteConverter/HtmlConverter.cs +++ b/cs/Markdown/Converter/ConcreteConverter/HtmlConverter.cs @@ -1,4 +1,5 @@ using Markdown.Tags; +using Markdown.Tags.ConcreteTags; using System.Text; namespace Markdown.Converter.ConcreteConverter @@ -9,8 +10,24 @@ public string Convert(ParsedLine[] parsedLines) { var sb = new StringBuilder(); + var containList = false; + var startedLine = true; foreach (var text in parsedLines) { + if (!startedLine) + sb.Append('\n'); + + if (containList && text.Tags.FirstOrDefault() is not BulletTag) + { + containList = false; + sb.Append(""); + } + else if (!containList && text.Tags.FirstOrDefault() is BulletTag) + { + containList = true; + sb.Append("
    "); + } + var prevTagPos = 0; foreach (var tag in text.Tags) { @@ -25,7 +42,10 @@ public string Convert(ParsedLine[] parsedLines) sb.Append(text.Line.AsSpan(prevTagPos, text.Line.Length - prevTagPos)); if (text.Tags.Count > 0 && text.Tags[0].TagType == TagType.Header) sb.Append(text.Tags[0].CloseTag); + startedLine = false; } + if (containList) + sb.Append("
"); return sb.ToString(); } diff --git a/cs/Markdown/Extensions/TokenExtensions.cs b/cs/Markdown/Extensions/TokenExtensions.cs index 7dcbbee6c..abeb78cbc 100644 --- a/cs/Markdown/Extensions/TokenExtensions.cs +++ b/cs/Markdown/Extensions/TokenExtensions.cs @@ -1,15 +1,10 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Markdown.Tokens; +using Markdown.Tokens; namespace Markdown.Extensions { public static class TokenExtensions { - public static bool NextTokenIs(this List tokens, TokenType tokenType, int currentIndex) + public static bool NextTokenIs(this List tokens, TokenType tokenType, int currentIndex) { return currentIndex + 1 < tokens.Count && tokens[currentIndex + 1].TokenType == tokenType; } diff --git a/cs/Markdown/Tags/ConcreteTags/BulletTag.cs b/cs/Markdown/Tags/ConcreteTags/BulletTag.cs new file mode 100644 index 000000000..ed3e6fd03 --- /dev/null +++ b/cs/Markdown/Tags/ConcreteTags/BulletTag.cs @@ -0,0 +1,18 @@ +namespace Markdown.Tags.ConcreteTags; + +public class BulletTag : ITag +{ + public TagType TagType { get; } + public int Position { get; set; } + public bool IsCloseTag { get; set; } + + public bool IsAutoClosing => true; + public string OpenTag => "
  • "; + public string CloseTag => "
  • "; + + public BulletTag(int position, bool isCloseTag) + { + Position = position; + IsCloseTag = isCloseTag; + } +} \ No newline at end of file diff --git a/cs/Markdown/TokenParser/ConcreteParser/LineParser.cs b/cs/Markdown/TokenParser/ConcreteParser/LineParser.cs index d7606ccf5..6c1257d41 100644 --- a/cs/Markdown/TokenParser/ConcreteParser/LineParser.cs +++ b/cs/Markdown/TokenParser/ConcreteParser/LineParser.cs @@ -104,7 +104,7 @@ private List EscapeNonPairTokens(List tokens) { openTagsPositions.Push(index); } - else if(TokenValidator.IsTokenTagClosed(token.TagType, tokens, index)) + else /*if(TokenValidator.IsTokenTagClosed(token.TagType, tokens, index))*/ { // Если это закрывающий тег if (openTagsPositions.TryPop(out var lastOpenTokenIndex)) @@ -124,11 +124,10 @@ private List EscapeNonPairTokens(List tokens) // } } - // Добавляем оставшиеся открытые теги как некорректные while (openTagsPositions.Count > 0) { var token = tokens[openTagsPositions.Pop()]; - if(!token.IsSelfCosingTag) + if (!token.IsSelfCosingTag) incorrectTags.Add(token); } @@ -163,7 +162,7 @@ private void SolveOpenAndCloseTags(Stack openTags, List tokens, int // Проверяем следующий тег после закрывающего if (TryGetNextTagType(tokens, closeIndex, out var nextTagTokenPosition)) { - if (openTags.TryPeek(out var preOpenTagIndex) && + if (openTags.TryPeek(out var preOpenTagIndex) && tokens[preOpenTagIndex].TagType == closeTagToken.TagType) { HandleNestedTags(openTags, tokens, preOpenTagIndex, openIndex, closeIndex, nextTagTokenPosition, incorrectTags); @@ -280,6 +279,7 @@ private ITag GetNewTag(Token token, int position) TagType.Header => new HeaderTag(position, token.IsCloseTag), TagType.Italic => new ItalicTag(position, token.IsCloseTag), TagType.Bold => new BoldTag(position, token.IsCloseTag), + TagType.BulletedList => new BulletTag(position, token.IsCloseTag), _ => throw new NotImplementedException() }; } diff --git a/cs/Markdown/TokenParser/Helpers/ITokenGenerateRule.cs b/cs/Markdown/TokenParser/Helpers/ITokenGenerateRule.cs index e283fbf82..9bdcf85f2 100644 --- a/cs/Markdown/TokenParser/Helpers/ITokenGenerateRule.cs +++ b/cs/Markdown/TokenParser/Helpers/ITokenGenerateRule.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Markdown.Tokens; +using Markdown.Tokens; namespace Markdown.TokenParser.Helpers { diff --git a/cs/Markdown/TokenParser/Helpers/TokenGenerator.cs b/cs/Markdown/TokenParser/Helpers/TokenGenerator.cs index 3ae6b6bb0..15af0d34f 100644 --- a/cs/Markdown/TokenParser/Helpers/TokenGenerator.cs +++ b/cs/Markdown/TokenParser/Helpers/TokenGenerator.cs @@ -1,8 +1,5 @@ -using System.Reflection; -using System.Runtime.InteropServices; -using Markdown.Tags; -using Markdown.Tokens; -using System.Text; +using Markdown.Tokens; +using System.Reflection; namespace Markdown.TokenParser.Helpers { @@ -20,9 +17,6 @@ public class TokenGenerator } return null; - // return generateRuleClasses - // .Select(t => t.GetToken(line, currentIndex)) - // .SingleOrDefault(t => t != null); } private static IEnumerable GetRuleClasses() diff --git a/cs/Markdown/TokenParser/Helpers/TokenGeneratorRules/GenerateBoldTokenRule.cs b/cs/Markdown/TokenParser/Helpers/TokenGeneratorRules/GenerateBoldTokenRule.cs index de680638b..352d27b03 100644 --- a/cs/Markdown/TokenParser/Helpers/TokenGeneratorRules/GenerateBoldTokenRule.cs +++ b/cs/Markdown/TokenParser/Helpers/TokenGeneratorRules/GenerateBoldTokenRule.cs @@ -1,11 +1,6 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using Markdown.Extensions; using Markdown.Tags; using Markdown.Tokens; -using Markdown.Extensions; namespace Markdown.TokenParser.Helpers.TokenGeneratorRules { diff --git a/cs/Markdown/TokenParser/Helpers/TokenGeneratorRules/GenerateBulletTokenRule.cs b/cs/Markdown/TokenParser/Helpers/TokenGeneratorRules/GenerateBulletTokenRule.cs new file mode 100644 index 000000000..a79d883a9 --- /dev/null +++ b/cs/Markdown/TokenParser/Helpers/TokenGeneratorRules/GenerateBulletTokenRule.cs @@ -0,0 +1,15 @@ +using Markdown.Tags; +using Markdown.Tokens; + +namespace Markdown.TokenParser.Helpers.TokenGeneratorRules; + +public class GenerateBulletTokenRule : ITokenGenerateRule +{ + public Token? GetToken(string line, int currentIndex) + { + if (currentIndex + 1 < line.Length && line[currentIndex] == '*' && line[currentIndex + 1] == ' ') + return new Token(TokenType.MdTag, "* ", TagType.BulletedList, true); + + return null; + } +} \ No newline at end of file diff --git a/cs/Markdown/TokenParser/Helpers/TokenGeneratorRules/GenerateEscapeTokenRule.cs b/cs/Markdown/TokenParser/Helpers/TokenGeneratorRules/GenerateEscapeTokenRule.cs index 1f4d6dba7..6797209a2 100644 --- a/cs/Markdown/TokenParser/Helpers/TokenGeneratorRules/GenerateEscapeTokenRule.cs +++ b/cs/Markdown/TokenParser/Helpers/TokenGeneratorRules/GenerateEscapeTokenRule.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Markdown.Tags; +using Markdown.Tags; using Markdown.Tokens; namespace Markdown.TokenParser.Helpers.TokenGeneratorRules diff --git a/cs/Markdown/TokenParser/Helpers/TokenGeneratorRules/GenerateHashTokenRule.cs b/cs/Markdown/TokenParser/Helpers/TokenGeneratorRules/GenerateHashTokenRule.cs index cb4062850..534cc7480 100644 --- a/cs/Markdown/TokenParser/Helpers/TokenGeneratorRules/GenerateHashTokenRule.cs +++ b/cs/Markdown/TokenParser/Helpers/TokenGeneratorRules/GenerateHashTokenRule.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Markdown.Tags; +using Markdown.Tags; using Markdown.Tokens; namespace Markdown.TokenParser.Helpers.TokenGeneratorRules diff --git a/cs/Markdown/TokenParser/Helpers/TokenGeneratorRules/GenerateItalicTokenRule.cs b/cs/Markdown/TokenParser/Helpers/TokenGeneratorRules/GenerateItalicTokenRule.cs index edd9af0b2..7c79918d3 100644 --- a/cs/Markdown/TokenParser/Helpers/TokenGeneratorRules/GenerateItalicTokenRule.cs +++ b/cs/Markdown/TokenParser/Helpers/TokenGeneratorRules/GenerateItalicTokenRule.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Markdown.Extensions; +using Markdown.Extensions; using Markdown.Tags; using Markdown.Tokens; diff --git a/cs/Markdown/TokenParser/Helpers/TokenGeneratorRules/GenerateTextTokenRule.cs b/cs/Markdown/TokenParser/Helpers/TokenGeneratorRules/GenerateTextTokenRule.cs index 173c36ba3..abb933ad3 100644 --- a/cs/Markdown/TokenParser/Helpers/TokenGeneratorRules/GenerateTextTokenRule.cs +++ b/cs/Markdown/TokenParser/Helpers/TokenGeneratorRules/GenerateTextTokenRule.cs @@ -1,10 +1,5 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; +using Markdown.Tokens; using System.Text; -using System.Threading.Tasks; -using Markdown.Tokens; namespace Markdown.TokenParser.Helpers.TokenGeneratorRules { diff --git a/cs/Markdown/TokenParser/Helpers/TokenGeneratorRules/GenerateWhiteSpaceRule.cs b/cs/Markdown/TokenParser/Helpers/TokenGeneratorRules/GenerateWhiteSpaceRule.cs index 1567cb125..cbc0750a4 100644 --- a/cs/Markdown/TokenParser/Helpers/TokenGeneratorRules/GenerateWhiteSpaceRule.cs +++ b/cs/Markdown/TokenParser/Helpers/TokenGeneratorRules/GenerateWhiteSpaceRule.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Markdown.Tags; +using Markdown.Tags; using Markdown.Tokens; namespace Markdown.TokenParser.Helpers.TokenGeneratorRules diff --git a/cs/Markdown/TokenParser/Helpers/TokenValidator.cs b/cs/Markdown/TokenParser/Helpers/TokenValidator.cs index c78e6f3a9..a43103478 100644 --- a/cs/Markdown/TokenParser/Helpers/TokenValidator.cs +++ b/cs/Markdown/TokenParser/Helpers/TokenValidator.cs @@ -21,7 +21,7 @@ public static bool IsTokenTagOpen(TagType tagType, List tokens, int index } private static bool lastOpenWasInWord; - + public static bool IsTokenTagClosed(TagType tagType, List tokens, int index) { switch (tagType) @@ -43,6 +43,7 @@ public static bool IsValidTagToken(List tokens, int index) TagType.Italic => IsValidItalic(tokens, index), TagType.Bold => IsValidBold(tokens, index), TagType.Header => IsValidHeader(tokens, index), + TagType.BulletedList => IsValidBulletedTag(tokens, index), _ => true }; } @@ -60,6 +61,14 @@ private static bool IsValidHeader(List tokens, int index) return false; } + private static bool IsValidBulletedTag(List tokens, int index) + { + if (index == 0 && index + 1 < tokens.Count && tokens[index].TokenType == TokenType.MdTag) + return true; + + return false; + } + private static bool IsValidItalic(List tokens, int index) { return IsValidUnderscoreTag(tokens, index); @@ -94,7 +103,8 @@ private static bool IsBoldOrItalicOpen(List tokens, int index) private static bool IsBoldOrItalicBetweenWordsOpen(List tokens, int index) { return tokens.NextTokenIs(TokenType.Text, index) && - (index - 1 < 0 || tokens.LastTokenIs(TokenType.WhiteSpace, index)) ; + (index - 1 < 0 || tokens.LastTokenIs(TokenType.WhiteSpace, index) + || tokens.LastTokenIs(TokenType.MdTag, index)); } //Является ли тег открывающим для ситуации когда тег применятеся внутри @@ -121,8 +131,9 @@ private static bool IsBoldOrItalicClosed(List tokens, int index) private static bool IsBoldOrItalicBetweenWordsClosed(List tokens, int index) { - return (tokens.NextTokenIs(TokenType.WhiteSpace, index) || index + 1 >= tokens.Count) && - tokens.LastTokenIs(TokenType.Text, index); + return (tokens.NextTokenIs(TokenType.WhiteSpace, index) || index + 1 >= tokens.Count) && + (tokens.LastTokenIs(TokenType.Text, index) + || tokens.LastTokenIs(TokenType.MdTag, index)); } private static bool IsBoldOrItalicInOneWordClosed(List tokens, int index) diff --git a/cs/Markdown_Tests/LineParser_Tests.cs b/cs/Markdown_Tests/LineParser_Tests.cs index bf502d89f..574e6c5b8 100644 --- a/cs/Markdown_Tests/LineParser_Tests.cs +++ b/cs/Markdown_Tests/LineParser_Tests.cs @@ -33,6 +33,15 @@ public void ParseLine_ShoudBeCorrect_WhenLineWithHeaderTags(string inLine, strin parsedLines.Tags.Should().BeEquivalentTo(tags); } + [TestCaseSource(typeof(LineParserData), nameof(LineParserData.LinesWithBulletedList))] + public void ParseLine_ShoudBeCorrect_LinesWithBulletedList(string inLine, string expectedLine, List tags) + { + var parsedLines = parser.ParseLine(inLine); + + parsedLines.Line.Should().BeEquivalentTo(expectedLine); + parsedLines.Tags.Should().BeEquivalentTo(tags); + } + [TestCaseSource(typeof(LineParserData), nameof(LineParserData.LineWithItalic))] public void ParseLine_ShoudBeCorrect_WhenLineWithItalicTags(string inLine, string expectedLine, List tags) { diff --git a/cs/Markdown_Tests/Md_Tests.cs b/cs/Markdown_Tests/Md_Tests.cs index 08d826931..a770a3343 100644 --- a/cs/Markdown_Tests/Md_Tests.cs +++ b/cs/Markdown_Tests/Md_Tests.cs @@ -1,7 +1,7 @@ using FluentAssertions; +using Markdown; using Markdown.Converter.ConcreteConverter; using Markdown.TokenParser.ConcreteParser; -using Markdown; namespace MarkdownTests @@ -10,6 +10,31 @@ public class MdTests { private Md markdown = new Md(new LineParser(), new HtmlConverter()); + #region BulletedTests + + [TestCase("* \n* ", "
    • \n
    ")] + [TestCase("* ", "
    ")] + public void Md_ShouldCreateBulletedListCorrectly_WhenBulletedItemNotEscaped(string input, string expected) + { + markdown.Render(input).Should().BeEquivalentTo(expected); + } + + [TestCase(@"\* ", "* ")] + [TestCase("* \n\\* ", "
    • \n
    * ")] + public void Md_ShouldCreateBulletedListCorrectly_WhenBulletedItemEscaped(string input, string expected) + { + markdown.Render(input).Should().BeEquivalentTo(expected); + } + + [TestCase(" * \n * ", " * \n * ")] + [TestCase("d* \nd* ", "d* \nd* ")] + public void Md_ShouldNotCreateBulletedTag_WhenAreCharsBeforeTag(string input, string expected) + { + markdown.Render(input).Should().BeEquivalentTo(expected); + } + + #endregion + #region HeaderTests [TestCase(@"# bibo", "

    bibo

    ")] @@ -174,5 +199,19 @@ public void Md_ShouldCreateEmptyHtml_WhenTextIsStringEmpty(string input, string { markdown.Render(String.Empty).Should().BeEquivalentTo(String.Empty); } + + public static IEnumerable MultiLinesTestCases() + { + yield return new TestCaseData("* _ _\n* __ __", + "
    • \n
    "); + yield return new TestCaseData("# \n* _ _\n* __ __", + "

    \n
    • \n
    "); + } + + [TestCaseSource(nameof(MultiLinesTestCases))] + public void Md_ShouldRenderCorrectly_WhenTextWithMultiTags(string input, string expected) + { + markdown.Render(input).Should().BeEquivalentTo(expected); + } } } \ No newline at end of file diff --git a/cs/Markdown_Tests/TestData/LineParserData.cs b/cs/Markdown_Tests/TestData/LineParserData.cs index e90b6683f..ee6107f17 100644 --- a/cs/Markdown_Tests/TestData/LineParserData.cs +++ b/cs/Markdown_Tests/TestData/LineParserData.cs @@ -1,10 +1,5 @@ -using Markdown.Tags.ConcreteTags; -using Markdown.Tags; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using Markdown.Tags; +using Markdown.Tags.ConcreteTags; namespace MarkdownTests.TestData { @@ -47,6 +42,15 @@ public static IEnumerable LineWithItalic() yield return new TestCaseData("Why_1_ word 23 bubo bibo", "Why_1_ word 23 bubo bibo", new List()); } + public static IEnumerable LinesWithBulletedList() + { + yield return new TestCaseData("* один", "один", new List() + { + new BulletTag(0, false), + }); + yield return new TestCaseData(@"\* один", @"* один", new List()); + } + public static IEnumerable LineWithBold() { yield return new TestCaseData("__word bubo__ bibo", "word bubo bibo", new List() diff --git a/cs/Markdown_Tests/TestData/TokenGeneratorTestsData.cs b/cs/Markdown_Tests/TestData/TokenGeneratorTestsData.cs index ef08afbb0..fae861a47 100644 --- a/cs/Markdown_Tests/TestData/TokenGeneratorTestsData.cs +++ b/cs/Markdown_Tests/TestData/TokenGeneratorTestsData.cs @@ -1,10 +1,5 @@ using Markdown.Tags; using Markdown.Tokens; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace MarkdownTests.TestData { @@ -77,6 +72,38 @@ public static IEnumerable LinesWithHeader() }); } + public static IEnumerable LinesWithBulletedList() + { + yield return new TestCaseData("* Заголовок", new List() + { + new Token(TokenType.MdTag, "* ", TagType.BulletedList, true), + new Token(TokenType.Text, "Заголовок", TagType.UnDefined), + }); + + yield return new TestCaseData("* * * ", new List() + { + new Token(TokenType.MdTag, "* ", TagType.BulletedList, true), + new Token(TokenType.MdTag, "* ", TagType.BulletedList, true), + new Token(TokenType.MdTag, "* ", TagType.BulletedList, true), + }); + + yield return new TestCaseData(@" * * ", new List() + { + new Token(TokenType.WhiteSpace, " ", TagType.UnDefined), + new Token(TokenType.MdTag, "* ", TagType.BulletedList, true), + new Token(TokenType.MdTag, "* ", TagType.BulletedList, true), + }); + + yield return new TestCaseData(@"* раз * два", new List() + { + new Token(TokenType.MdTag, "* ", TagType.BulletedList, true), + new Token(TokenType.Text, "раз", TagType.UnDefined), + new Token(TokenType.WhiteSpace, " ", TagType.UnDefined), + new Token(TokenType.Text, "два", TagType.UnDefined), + new Token(TokenType.MdTag, "* ", TagType.BulletedList, true), + }); + } + public static IEnumerable LinesWithEscapes() { yield return new TestCaseData(@"\", new List() diff --git a/cs/Markdown_Tests/TokenGenerator_Tests.cs b/cs/Markdown_Tests/TokenGenerator_Tests.cs index a7179f9ba..dcb0f70d2 100644 --- a/cs/Markdown_Tests/TokenGenerator_Tests.cs +++ b/cs/Markdown_Tests/TokenGenerator_Tests.cs @@ -1,10 +1,5 @@ -using Markdown.Tags; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using FluentAssertions; +using FluentAssertions; +using Markdown.Tags; using Markdown.TokenParser.Helpers; using Markdown.Tokens; using MarkdownTests.TestData; @@ -48,6 +43,14 @@ public void TokenGenerator_GetTokensBySymbolCorrectly_WhenLineWithHeaders(string actuallyTokens.Should().BeEquivalentTo(expectedTokens); } + [TestCaseSource(typeof(TokenGeneratorTestsData), nameof(TokenGeneratorTestsData.LinesWithBulletedList))] + public void TokenGenerator_GetTokensBySymbolCorrectly_WhenLineWithBulletedLis(string input, List expectedTokens) + { + var actuallyTokens = GetAllTokensFromLine(input); + + actuallyTokens.Should().BeEquivalentTo(expectedTokens); + } + [TestCaseSource(typeof(TokenGeneratorTestsData), nameof(TokenGeneratorTestsData.LinesWithEscapes))] public void TokenGenerator_GetTokensBySymbolCorrectly_WhenLineWithEscapes(string input, List expectedTokens) { From c6291eb7129bcdbf4177f9f8b711489a1a0e5273 Mon Sep 17 00:00:00 2001 From: Braidenbikher Victor Date: Sat, 7 Dec 2024 22:34:10 +0500 Subject: [PATCH 07/13] =?UTF-8?q?=D0=9D=D0=B5=D0=BC=D0=BD=D0=BE=D0=B3?= =?UTF-8?q?=D0=BE=20=D0=BF=D0=B5=D1=80=D0=B5=D1=80=D0=B0=D0=B1=D0=BE=D1=82?= =?UTF-8?q?=D0=B0=D0=BB=20=D0=B3=D0=B5=D0=BD=D0=B5=D1=80=D0=B0=D1=86=D0=B8?= =?UTF-8?q?=D1=8E=20=D1=82=D0=BE=D0=BA=D0=B5=D0=BD=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Interfaces}/ITokenGenerateRule.cs | 2 +- .../Interfaces/ITokenGenerator.cs | 14 + .../TokenGenerator.cs | 7 +- .../GenerateBoldTokenRule.cs | 5 +- .../GenerateBulletTokenRule.cs | 5 +- .../GenerateEscapeTokenRule.cs | 5 +- .../GenerateHashTokenRule.cs | 5 +- .../GenerateItalicTokenRule.cs | 5 +- .../GenerateTextTokenRule.cs | 7 +- .../GenerateWhiteSpaceRule.cs | 5 +- cs/Markdown/Tokens/Token.cs | 22 +- .../TestData/TokenGeneratorTestsData.cs | 367 +++++++++--------- cs/Markdown_Tests/TokenGenerator_Tests.cs | 134 +++---- 13 files changed, 301 insertions(+), 282 deletions(-) rename cs/Markdown/{TokenParser/Helpers => TokenGeneratorClasses/Interfaces}/ITokenGenerateRule.cs (73%) create mode 100644 cs/Markdown/TokenGeneratorClasses/Interfaces/ITokenGenerator.cs rename cs/Markdown/{TokenParser/Helpers => TokenGeneratorClasses}/TokenGenerator.cs (92%) rename cs/Markdown/{TokenParser/Helpers => TokenGeneratorClasses}/TokenGeneratorRules/GenerateBoldTokenRule.cs (62%) rename cs/Markdown/{TokenParser/Helpers => TokenGeneratorClasses}/TokenGeneratorRules/GenerateBulletTokenRule.cs (59%) rename cs/Markdown/{TokenParser/Helpers => TokenGeneratorClasses}/TokenGeneratorRules/GenerateEscapeTokenRule.cs (57%) rename cs/Markdown/{TokenParser/Helpers => TokenGeneratorClasses}/TokenGeneratorRules/GenerateHashTokenRule.cs (62%) rename cs/Markdown/{TokenParser/Helpers => TokenGeneratorClasses}/TokenGeneratorRules/GenerateItalicTokenRule.cs (62%) rename cs/Markdown/{TokenParser/Helpers => TokenGeneratorClasses}/TokenGeneratorRules/GenerateTextTokenRule.cs (85%) rename cs/Markdown/{TokenParser/Helpers => TokenGeneratorClasses}/TokenGeneratorRules/GenerateWhiteSpaceRule.cs (59%) diff --git a/cs/Markdown/TokenParser/Helpers/ITokenGenerateRule.cs b/cs/Markdown/TokenGeneratorClasses/Interfaces/ITokenGenerateRule.cs similarity index 73% rename from cs/Markdown/TokenParser/Helpers/ITokenGenerateRule.cs rename to cs/Markdown/TokenGeneratorClasses/Interfaces/ITokenGenerateRule.cs index 9bdcf85f2..a42e71de2 100644 --- a/cs/Markdown/TokenParser/Helpers/ITokenGenerateRule.cs +++ b/cs/Markdown/TokenGeneratorClasses/Interfaces/ITokenGenerateRule.cs @@ -1,6 +1,6 @@ using Markdown.Tokens; -namespace Markdown.TokenParser.Helpers +namespace Markdown.TokenGeneratorClasses.Interfaces { public interface ITokenGenerateRule { diff --git a/cs/Markdown/TokenGeneratorClasses/Interfaces/ITokenGenerator.cs b/cs/Markdown/TokenGeneratorClasses/Interfaces/ITokenGenerator.cs new file mode 100644 index 000000000..60574d262 --- /dev/null +++ b/cs/Markdown/TokenGeneratorClasses/Interfaces/ITokenGenerator.cs @@ -0,0 +1,14 @@ +using Markdown.Tokens; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Markdown.TokenGeneratorClasses.Interfaces +{ + public interface ITokenGenerator + { + public static abstract Token? GetToken(string line, int currentIndex); + } +} diff --git a/cs/Markdown/TokenParser/Helpers/TokenGenerator.cs b/cs/Markdown/TokenGeneratorClasses/TokenGenerator.cs similarity index 92% rename from cs/Markdown/TokenParser/Helpers/TokenGenerator.cs rename to cs/Markdown/TokenGeneratorClasses/TokenGenerator.cs index 15af0d34f..87a5d76ef 100644 --- a/cs/Markdown/TokenParser/Helpers/TokenGenerator.cs +++ b/cs/Markdown/TokenGeneratorClasses/TokenGenerator.cs @@ -1,13 +1,14 @@ using Markdown.Tokens; using System.Reflection; +using Markdown.TokenGeneratorClasses.Interfaces; -namespace Markdown.TokenParser.Helpers +namespace Markdown.TokenGeneratorClasses { - public class TokenGenerator + public class TokenGenerator : ITokenGenerator { private static IEnumerable generateRuleClasses = GetRuleClasses(); - public static Token? GetTokenBySymbol(string line, int currentIndex) + public static Token? GetToken(string line, int currentIndex) { foreach (var rule in generateRuleClasses) { diff --git a/cs/Markdown/TokenParser/Helpers/TokenGeneratorRules/GenerateBoldTokenRule.cs b/cs/Markdown/TokenGeneratorClasses/TokenGeneratorRules/GenerateBoldTokenRule.cs similarity index 62% rename from cs/Markdown/TokenParser/Helpers/TokenGeneratorRules/GenerateBoldTokenRule.cs rename to cs/Markdown/TokenGeneratorClasses/TokenGeneratorRules/GenerateBoldTokenRule.cs index 352d27b03..a6bd25c36 100644 --- a/cs/Markdown/TokenParser/Helpers/TokenGeneratorRules/GenerateBoldTokenRule.cs +++ b/cs/Markdown/TokenGeneratorClasses/TokenGeneratorRules/GenerateBoldTokenRule.cs @@ -1,15 +1,16 @@ using Markdown.Extensions; using Markdown.Tags; +using Markdown.TokenGeneratorClasses.Interfaces; using Markdown.Tokens; -namespace Markdown.TokenParser.Helpers.TokenGeneratorRules +namespace Markdown.TokenGeneratorClasses.TokenGeneratorRules { public class GenerateBoldTokenRule : ITokenGenerateRule { public Token? GetToken(string line, int currentIndex) { if (line[currentIndex] == '_' && line.NextCharIs('_', currentIndex)) - return new Token(TokenType.MdTag, "__", TagType.Bold); + return new Token(TokenType.MdTag, "__", currentIndex, false, TagType.Bold); return null; } diff --git a/cs/Markdown/TokenParser/Helpers/TokenGeneratorRules/GenerateBulletTokenRule.cs b/cs/Markdown/TokenGeneratorClasses/TokenGeneratorRules/GenerateBulletTokenRule.cs similarity index 59% rename from cs/Markdown/TokenParser/Helpers/TokenGeneratorRules/GenerateBulletTokenRule.cs rename to cs/Markdown/TokenGeneratorClasses/TokenGeneratorRules/GenerateBulletTokenRule.cs index a79d883a9..e1d0bb509 100644 --- a/cs/Markdown/TokenParser/Helpers/TokenGeneratorRules/GenerateBulletTokenRule.cs +++ b/cs/Markdown/TokenGeneratorClasses/TokenGeneratorRules/GenerateBulletTokenRule.cs @@ -1,14 +1,15 @@ using Markdown.Tags; +using Markdown.TokenGeneratorClasses.Interfaces; using Markdown.Tokens; -namespace Markdown.TokenParser.Helpers.TokenGeneratorRules; +namespace Markdown.TokenGeneratorClasses.TokenGeneratorRules; public class GenerateBulletTokenRule : ITokenGenerateRule { public Token? GetToken(string line, int currentIndex) { if (currentIndex + 1 < line.Length && line[currentIndex] == '*' && line[currentIndex + 1] == ' ') - return new Token(TokenType.MdTag, "* ", TagType.BulletedList, true); + return new Token(TokenType.MdTag, "* ", currentIndex, false, TagType.BulletedListItem); return null; } diff --git a/cs/Markdown/TokenParser/Helpers/TokenGeneratorRules/GenerateEscapeTokenRule.cs b/cs/Markdown/TokenGeneratorClasses/TokenGeneratorRules/GenerateEscapeTokenRule.cs similarity index 57% rename from cs/Markdown/TokenParser/Helpers/TokenGeneratorRules/GenerateEscapeTokenRule.cs rename to cs/Markdown/TokenGeneratorClasses/TokenGeneratorRules/GenerateEscapeTokenRule.cs index 6797209a2..f0216ceda 100644 --- a/cs/Markdown/TokenParser/Helpers/TokenGeneratorRules/GenerateEscapeTokenRule.cs +++ b/cs/Markdown/TokenGeneratorClasses/TokenGeneratorRules/GenerateEscapeTokenRule.cs @@ -1,14 +1,15 @@ using Markdown.Tags; +using Markdown.TokenGeneratorClasses.Interfaces; using Markdown.Tokens; -namespace Markdown.TokenParser.Helpers.TokenGeneratorRules +namespace Markdown.TokenGeneratorClasses.TokenGeneratorRules { public class GenerateEscapeTokenRule : ITokenGenerateRule { public Token? GetToken(string line, int currentIndex) { if (line[currentIndex] == '\\') - return new Token(TokenType.Escape, @"\", TagType.Escape); + return new Token(TokenType.Escape, @"\", currentIndex, false, TagType.Escape); return null; } diff --git a/cs/Markdown/TokenParser/Helpers/TokenGeneratorRules/GenerateHashTokenRule.cs b/cs/Markdown/TokenGeneratorClasses/TokenGeneratorRules/GenerateHashTokenRule.cs similarity index 62% rename from cs/Markdown/TokenParser/Helpers/TokenGeneratorRules/GenerateHashTokenRule.cs rename to cs/Markdown/TokenGeneratorClasses/TokenGeneratorRules/GenerateHashTokenRule.cs index 534cc7480..3fb1eb4a1 100644 --- a/cs/Markdown/TokenParser/Helpers/TokenGeneratorRules/GenerateHashTokenRule.cs +++ b/cs/Markdown/TokenGeneratorClasses/TokenGeneratorRules/GenerateHashTokenRule.cs @@ -1,14 +1,15 @@ using Markdown.Tags; +using Markdown.TokenGeneratorClasses.Interfaces; using Markdown.Tokens; -namespace Markdown.TokenParser.Helpers.TokenGeneratorRules +namespace Markdown.TokenGeneratorClasses.TokenGeneratorRules { public class GenerateHashTokenRule : ITokenGenerateRule { public Token? GetToken(string line, int currentIndex) { if (currentIndex + 1 < line.Length && line[currentIndex] == '#' && line[currentIndex + 1] == ' ') - return new Token(TokenType.MdTag, "# ", TagType.Header, true); + return new Token(TokenType.MdTag, "# ", currentIndex, false, TagType.Header); return null; } diff --git a/cs/Markdown/TokenParser/Helpers/TokenGeneratorRules/GenerateItalicTokenRule.cs b/cs/Markdown/TokenGeneratorClasses/TokenGeneratorRules/GenerateItalicTokenRule.cs similarity index 62% rename from cs/Markdown/TokenParser/Helpers/TokenGeneratorRules/GenerateItalicTokenRule.cs rename to cs/Markdown/TokenGeneratorClasses/TokenGeneratorRules/GenerateItalicTokenRule.cs index 7c79918d3..55996ae00 100644 --- a/cs/Markdown/TokenParser/Helpers/TokenGeneratorRules/GenerateItalicTokenRule.cs +++ b/cs/Markdown/TokenGeneratorClasses/TokenGeneratorRules/GenerateItalicTokenRule.cs @@ -1,15 +1,16 @@ using Markdown.Extensions; using Markdown.Tags; +using Markdown.TokenGeneratorClasses.Interfaces; using Markdown.Tokens; -namespace Markdown.TokenParser.Helpers.TokenGeneratorRules +namespace Markdown.TokenGeneratorClasses.TokenGeneratorRules { public class GenerateItalicTokenRule : ITokenGenerateRule { public Token? GetToken(string line, int currentIndex) { if (line[currentIndex] == '_' && !line.NextCharIs('_', currentIndex)) - return new Token(TokenType.MdTag, "_", TagType.Italic); + return new Token(TokenType.MdTag, "_", currentIndex, false, TagType.Italic); return null; } diff --git a/cs/Markdown/TokenParser/Helpers/TokenGeneratorRules/GenerateTextTokenRule.cs b/cs/Markdown/TokenGeneratorClasses/TokenGeneratorRules/GenerateTextTokenRule.cs similarity index 85% rename from cs/Markdown/TokenParser/Helpers/TokenGeneratorRules/GenerateTextTokenRule.cs rename to cs/Markdown/TokenGeneratorClasses/TokenGeneratorRules/GenerateTextTokenRule.cs index abb933ad3..0f55d3a57 100644 --- a/cs/Markdown/TokenParser/Helpers/TokenGeneratorRules/GenerateTextTokenRule.cs +++ b/cs/Markdown/TokenGeneratorClasses/TokenGeneratorRules/GenerateTextTokenRule.cs @@ -1,7 +1,8 @@ using Markdown.Tokens; using System.Text; +using Markdown.TokenGeneratorClasses.Interfaces; -namespace Markdown.TokenParser.Helpers.TokenGeneratorRules +namespace Markdown.TokenGeneratorClasses.TokenGeneratorRules { public class GenerateTextTokenRule : ITokenGenerateRule { @@ -42,7 +43,9 @@ private bool IsTextToken(string line, int currentIndex) currentIndex++; } - return new Token(tokenType, stringBuilder.ToString()); + var text = stringBuilder.ToString(); + + return new Token(tokenType, text, currentIndex - text.Length); } } } diff --git a/cs/Markdown/TokenParser/Helpers/TokenGeneratorRules/GenerateWhiteSpaceRule.cs b/cs/Markdown/TokenGeneratorClasses/TokenGeneratorRules/GenerateWhiteSpaceRule.cs similarity index 59% rename from cs/Markdown/TokenParser/Helpers/TokenGeneratorRules/GenerateWhiteSpaceRule.cs rename to cs/Markdown/TokenGeneratorClasses/TokenGeneratorRules/GenerateWhiteSpaceRule.cs index cbc0750a4..64146beda 100644 --- a/cs/Markdown/TokenParser/Helpers/TokenGeneratorRules/GenerateWhiteSpaceRule.cs +++ b/cs/Markdown/TokenGeneratorClasses/TokenGeneratorRules/GenerateWhiteSpaceRule.cs @@ -1,14 +1,15 @@ using Markdown.Tags; +using Markdown.TokenGeneratorClasses.Interfaces; using Markdown.Tokens; -namespace Markdown.TokenParser.Helpers.TokenGeneratorRules +namespace Markdown.TokenGeneratorClasses.TokenGeneratorRules { public class GenerateWhiteSpaceRule : ITokenGenerateRule { public Token? GetToken(string line, int currentIndex) { if (line[currentIndex] == ' ') - return new Token(TokenType.WhiteSpace, " ", TagType.UnDefined); + return new Token(TokenType.WhiteSpace, " ", currentIndex); return null; } diff --git a/cs/Markdown/Tokens/Token.cs b/cs/Markdown/Tokens/Token.cs index 444d80dde..a14deb1eb 100644 --- a/cs/Markdown/Tokens/Token.cs +++ b/cs/Markdown/Tokens/Token.cs @@ -4,25 +4,19 @@ namespace Markdown.Tokens { public class Token { - public TokenType TokenType; + public TokenType TokenType { get; set; } + public string Content { get; set; } + public TagType TagType { get; set; } + public bool IsCloseTag { get; set; } + public int Position { get; set; } - public readonly string Content; - - public readonly TagType TagType; - - public bool IsCloseTag; - - public bool IsSelfCosingTag; - - public int PairTagPosition; - - public Token(TokenType tokenType, string content, TagType tagType = TagType.UnDefined, bool isSelfClosing = false) + public Token(TokenType tokenType, string content, int position, bool isCloseTag = false, TagType tagType = TagType.UnDefined) { TokenType = tokenType; Content = content; + Position = position; + IsCloseTag = isCloseTag; TagType = tagType; - - IsSelfCosingTag = isSelfClosing; } } } diff --git a/cs/Markdown_Tests/TestData/TokenGeneratorTestsData.cs b/cs/Markdown_Tests/TestData/TokenGeneratorTestsData.cs index fae861a47..cc5fe781c 100644 --- a/cs/Markdown_Tests/TestData/TokenGeneratorTestsData.cs +++ b/cs/Markdown_Tests/TestData/TokenGeneratorTestsData.cs @@ -1,190 +1,189 @@ using Markdown.Tags; using Markdown.Tokens; -namespace MarkdownTests.TestData +namespace MarkdownTests.TestData; + +public static class TokenGeneratorTestsData { - public static class TokenGeneratorTestsData + public static IEnumerable TextOnlyLines() + { + yield return new TestCaseData("вася", new List + { + new(TokenType.Text, "вася", 0) + }); + + yield return new TestCaseData("петя1223d", new List + { + new(TokenType.Text, "петя", 0), + new(TokenType.Number, "1223", 4), + new(TokenType.Text, "d",8) + }); + + yield return new TestCaseData("1234566", new List + { + new(TokenType.Number, "1234566", 0) + }); + } + + public static IEnumerable WhiteSpacesOnlyLines() + { + yield return new TestCaseData(" ", new List + { + new(TokenType.WhiteSpace, " ", 0) + }); + + yield return new TestCaseData(" ", new List + { + new(TokenType.WhiteSpace, " ", 0), + new(TokenType.WhiteSpace, " ", 1) + }); + + yield return new TestCaseData(" ", new List + { + new(TokenType.WhiteSpace, " ", 0), + new(TokenType.WhiteSpace, " ", 1), + new(TokenType.WhiteSpace, " ", 2), + new(TokenType.WhiteSpace, " ", 3), + new(TokenType.WhiteSpace, " ", 4), + new(TokenType.WhiteSpace, " ", 5) + }); + } + + public static IEnumerable LinesWithHeader() + { + yield return new TestCaseData("# Заголовок", new List + { + new(TokenType.MdTag, "# ", 0, false,TagType.Header), + new(TokenType.Text, "Заголовок", 2) + }); + + yield return new TestCaseData("# # # ", new List + { + new(TokenType.MdTag, "# ", 0, false, TagType.Header), + new(TokenType.MdTag, "# ", 2, false, TagType.Header), + new(TokenType.MdTag, "# ", 4, false, TagType.Header) + }); + + yield return new TestCaseData(@" # # ", new List + { + new(TokenType.WhiteSpace, " ", 0), + new(TokenType.MdTag, "# ", 1, false, TagType.Header), + new(TokenType.MdTag, "# ", 3, false, TagType.Header) + }); + } + + public static IEnumerable LinesWithBulletedList() + { + yield return new TestCaseData("* Заголовок", new List + { + new(TokenType.MdTag, "* ", 0, false, TagType.BulletedList), + new(TokenType.Text, "Заголовок", 2) + }); + + yield return new TestCaseData("* * * ", new List + { + new(TokenType.MdTag, "* ", 0, false, TagType.BulletedList), + new(TokenType.MdTag, "* ", 2, false, TagType.BulletedList), + new(TokenType.MdTag, "* ", 4, false, TagType.BulletedList) + }); + + yield return new TestCaseData(@" * * ", new List + { + new(TokenType.WhiteSpace, " ", 0), + new(TokenType.MdTag, "* ", 1, false, TagType.BulletedList), + new(TokenType.MdTag, "* ", 3, false, TagType.BulletedList) + }); + + yield return new TestCaseData(@"* раз * два", new List + { + new(TokenType.MdTag, "* ", 0, false, TagType.BulletedList), + new(TokenType.Text, "раз", 2), + new(TokenType.WhiteSpace, " ", 5), + new(TokenType.MdTag, "* ", 6, false, TagType.BulletedList), + new(TokenType.Text, "два", 8) + }); + } + + public static IEnumerable LinesWithEscapes() { - public static IEnumerable TextOnlyLines() - { - yield return new TestCaseData("вася", new List() - { - new Token(TokenType.Text, "вася", TagType.UnDefined), - }); - - yield return new TestCaseData("петя1223d", new List() - { - new Token(TokenType.Text, "петя", TagType.UnDefined), - new Token(TokenType.Number, "1223", TagType.UnDefined), - new Token(TokenType.Text, "d", TagType.UnDefined), - }); - - yield return new TestCaseData("1234566", new List() - { - new Token(TokenType.Number, "1234566", TagType.UnDefined), - }); - } - - public static IEnumerable WhiteSpacesOnlyLines() - { - yield return new TestCaseData(" ", new List() - { - new Token(TokenType.WhiteSpace, " ", TagType.UnDefined), - }); - - yield return new TestCaseData(" ", new List() - { - new Token(TokenType.WhiteSpace, " ", TagType.UnDefined), - new Token(TokenType.WhiteSpace, " ", TagType.UnDefined), - }); - - yield return new TestCaseData(" ", new List() - { - new Token(TokenType.WhiteSpace, " ", TagType.UnDefined), - new Token(TokenType.WhiteSpace, " ", TagType.UnDefined), - new Token(TokenType.WhiteSpace, " ", TagType.UnDefined), - new Token(TokenType.WhiteSpace, " ", TagType.UnDefined), - new Token(TokenType.WhiteSpace, " ", TagType.UnDefined), - new Token(TokenType.WhiteSpace, " ", TagType.UnDefined), - }); - } - - public static IEnumerable LinesWithHeader() - { - yield return new TestCaseData("# Заголовок", new List() - { - new Token(TokenType.MdTag, "# ", TagType.Header, true), - new Token(TokenType.Text, "Заголовок", TagType.UnDefined), - }); - - yield return new TestCaseData("# # # ", new List() - { - new Token(TokenType.MdTag, "# ", TagType.Header, true), - new Token(TokenType.MdTag, "# ", TagType.Header, true), - new Token(TokenType.MdTag, "# ", TagType.Header, true) - }); - - yield return new TestCaseData(@" # # ", new List() - { - new Token(TokenType.WhiteSpace, " ", TagType.UnDefined), - new Token(TokenType.MdTag, "# ", TagType.Header, true), - new Token(TokenType.MdTag, "# ", TagType.Header, true), - }); - } - - public static IEnumerable LinesWithBulletedList() - { - yield return new TestCaseData("* Заголовок", new List() - { - new Token(TokenType.MdTag, "* ", TagType.BulletedList, true), - new Token(TokenType.Text, "Заголовок", TagType.UnDefined), - }); - - yield return new TestCaseData("* * * ", new List() - { - new Token(TokenType.MdTag, "* ", TagType.BulletedList, true), - new Token(TokenType.MdTag, "* ", TagType.BulletedList, true), - new Token(TokenType.MdTag, "* ", TagType.BulletedList, true), - }); - - yield return new TestCaseData(@" * * ", new List() - { - new Token(TokenType.WhiteSpace, " ", TagType.UnDefined), - new Token(TokenType.MdTag, "* ", TagType.BulletedList, true), - new Token(TokenType.MdTag, "* ", TagType.BulletedList, true), - }); - - yield return new TestCaseData(@"* раз * два", new List() - { - new Token(TokenType.MdTag, "* ", TagType.BulletedList, true), - new Token(TokenType.Text, "раз", TagType.UnDefined), - new Token(TokenType.WhiteSpace, " ", TagType.UnDefined), - new Token(TokenType.Text, "два", TagType.UnDefined), - new Token(TokenType.MdTag, "* ", TagType.BulletedList, true), - }); - } - - public static IEnumerable LinesWithEscapes() - { - yield return new TestCaseData(@"\", new List() - { - new Token(TokenType.Escape, @"\", TagType.Escape), - }); - - yield return new TestCaseData(@"\\", new List() - { - new Token(TokenType.Escape, @"\", TagType.Escape), - new Token(TokenType.Escape, @"\", TagType.Escape), - }); - - yield return new TestCaseData(@"\\\", new List() - { - new Token(TokenType.Escape, @"\", TagType.Escape), - new Token(TokenType.Escape, @"\", TagType.Escape), - new Token(TokenType.Escape, @"\", TagType.Escape), - }); - } - - public static IEnumerable LinesWithUnderscores() - { - yield return new TestCaseData("_", new List() - { - new Token(TokenType.MdTag, "_", TagType.Italic), - }); - - yield return new TestCaseData("__", new List() - { - new Token(TokenType.MdTag, "__", TagType.Bold), - }); - - yield return new TestCaseData("___", new List() - { - new Token(TokenType.MdTag, "__", TagType.Bold), - new Token(TokenType.MdTag, "_", TagType.Italic), - }); - - yield return new TestCaseData("____", new List() - { - new Token(TokenType.MdTag, "__", TagType.Bold), - new Token(TokenType.MdTag, "__", TagType.Bold), - }); - } - - public static IEnumerable LineWithMultiTokens() - { - yield return new TestCaseData("Bibo 234 _ # ", new List() - { - new Token(TokenType.Text, "Bibo", TagType.UnDefined), - new Token(TokenType.WhiteSpace, " ", TagType.UnDefined), - new Token(TokenType.Number, "234", TagType.UnDefined), - new Token(TokenType.WhiteSpace, " ", TagType.UnDefined), - new Token(TokenType.MdTag, "_", TagType.Italic), - new Token(TokenType.WhiteSpace, " ", TagType.UnDefined), - new Token(TokenType.MdTag, "# ", TagType.Header, true), - }); - - yield return new TestCaseData("__# _", new List() - { - new Token(TokenType.MdTag, "__", TagType.Bold), - new Token(TokenType.MdTag, "# ", TagType.Header, true), - new Token(TokenType.MdTag, "_", TagType.Italic), - }); - - yield return new TestCaseData("_2_3_", new List() - { - new Token(TokenType.MdTag, "_", TagType.Italic), - new Token(TokenType.Number, "2", TagType.UnDefined), - new Token(TokenType.MdTag, "_", TagType.Italic), - new Token(TokenType.Number, "3", TagType.UnDefined), - new Token(TokenType.MdTag, "_", TagType.Italic), - }); - - yield return new TestCaseData(@"\# word", new List() - { - new Token(TokenType.Escape, @"\", TagType.Escape), - new Token(TokenType.MdTag, "# ", TagType.Header, true), - new Token(TokenType.Text, "word", TagType.UnDefined), - }); - } + yield return new TestCaseData(@"\", new List + { + new(TokenType.Escape, @"\", 0, false, TagType.Escape) + }); + + yield return new TestCaseData(@"\\", new List + { + new(TokenType.Escape, @"\", 0, false, TagType.Escape), + new(TokenType.Escape, @"\", 1, false,TagType.Escape) + }); + + yield return new TestCaseData(@"\\\", new List + { + new(TokenType.Escape, @"\", 0, false, TagType.Escape), + new(TokenType.Escape, @"\", 1, false,TagType.Escape), + new(TokenType.Escape, @"\", 2, false,TagType.Escape), + }); + } + + public static IEnumerable LinesWithUnderscores() + { + yield return new TestCaseData("_", new List + { + new(TokenType.MdTag, "_", 0, false, TagType.Italic) + }); + + yield return new TestCaseData("__", new List + { + new(TokenType.MdTag, "__", 0, false, TagType.Bold) + }); + + yield return new TestCaseData("___", new List + { + new(TokenType.MdTag, "__", 0, false, TagType.Bold), + new(TokenType.MdTag, "_", 2, false, TagType.Italic) + }); + + yield return new TestCaseData("____", new List + { + new(TokenType.MdTag, "__", 0, false, TagType.Bold), + new(TokenType.MdTag, "__", 2, false, TagType.Bold) + }); + } + + public static IEnumerable LineWithMultiTokens() + { + yield return new TestCaseData("Bibo 234 _ # ", new List + { + new(TokenType.Text, "Bibo", 0), + new(TokenType.WhiteSpace, " ", 4), + new(TokenType.Number, "234", 5), + new(TokenType.WhiteSpace, " ", 8), + new(TokenType.MdTag, "_", 9, false, TagType.Italic), + new(TokenType.WhiteSpace, " ", 10), + new(TokenType.MdTag, "# ", 11, false, TagType.Header) + }); + + yield return new TestCaseData("__# _", new List + { + new(TokenType.MdTag, "__", 0, false, TagType.Bold), + new(TokenType.MdTag, "# ", 2, false, TagType.Header), + new(TokenType.MdTag, "_", 4, false, TagType.Italic) + }); + + yield return new TestCaseData("_2_3_", new List + { + new(TokenType.MdTag, "_", 0, false, TagType.Italic), + new(TokenType.Number, "2", 1), + new(TokenType.MdTag, "_", 2, false, TagType.Italic), + new(TokenType.Number, "3",3), + new(TokenType.MdTag, "_", 4, false, TagType.Italic) + }); + + yield return new TestCaseData(@"\# word", new List + { + new(TokenType.Escape, @"\", 0, false, TagType.Escape), + new(TokenType.MdTag, "# ", 1, false, TagType.Header), + new(TokenType.Text, "word", 3) + }); } -} +} \ No newline at end of file diff --git a/cs/Markdown_Tests/TokenGenerator_Tests.cs b/cs/Markdown_Tests/TokenGenerator_Tests.cs index dcb0f70d2..9f7de4af0 100644 --- a/cs/Markdown_Tests/TokenGenerator_Tests.cs +++ b/cs/Markdown_Tests/TokenGenerator_Tests.cs @@ -1,94 +1,96 @@ using FluentAssertions; using Markdown.Tags; -using Markdown.TokenParser.Helpers; +using Markdown.TokenGeneratorClasses; using Markdown.Tokens; using MarkdownTests.TestData; -namespace MarkdownTests +namespace MarkdownTests; + +public class TokenGenerator_Tests { - public class TokenGenerator_Tests + [TestCaseSource(typeof(TokenGeneratorTestsData), nameof(TokenGeneratorTestsData.TextOnlyLines))] + public void TokenGenerator_GetTokenCorrectly_WhenTextOnly(string input, List expectedTokens) { - [TestCaseSource(typeof(TokenGeneratorTestsData), nameof(TokenGeneratorTestsData.TextOnlyLines))] - public void TokenGenerator_GetTokenBySymbolCorrectly_WhenTextOnly(string input, List expectedTokens) - { - var actuallyTokens = GetAllTokensFromLine(input); + var actuallyTokens = GetAllTokensFromLine(input); - actuallyTokens.Should().BeEquivalentTo(expectedTokens); - } - - [TestCaseSource(typeof(TokenGeneratorTestsData), nameof(TokenGeneratorTestsData.WhiteSpacesOnlyLines))] - public void TokenGenerator_GetTokensBySymbolCorrectly_WhenWhiteSpacesOnly(string input, List expectedTokens) - { - var actuallyTokens = GetAllTokensFromLine(input); + actuallyTokens.Should().BeEquivalentTo(expectedTokens); + } - actuallyTokens.Should().BeEquivalentTo(expectedTokens); - } + [TestCaseSource(typeof(TokenGeneratorTestsData), nameof(TokenGeneratorTestsData.WhiteSpacesOnlyLines))] + public void TokenGenerator_GetTokensBySymbolCorrectly_WhenWhiteSpacesOnly(string input, List expectedTokens) + { + var actuallyTokens = GetAllTokensFromLine(input); - [Test] - public void TokenGenerator_GetTokensBySymbolCorrectly_WhenHeaderTokenOnly() - { - var actuallyTokens = GetAllTokensFromLine("# "); + actuallyTokens.Should().BeEquivalentTo(expectedTokens); + } - actuallyTokens.Should().BeEquivalentTo(new List() - { - new Token(TokenType.MdTag, "# ", TagType.Header, true) - }); - } + [Test] + public void TokenGenerator_GetTokensBySymbolCorrectly_WhenHeaderTokenOnly() + { + var actuallyTokens = GetAllTokensFromLine("# "); - [TestCaseSource(typeof(TokenGeneratorTestsData), nameof(TokenGeneratorTestsData.LinesWithHeader))] - public void TokenGenerator_GetTokensBySymbolCorrectly_WhenLineWithHeaders(string input, List expectedTokens) + actuallyTokens.Should().BeEquivalentTo(new List { - var actuallyTokens = GetAllTokensFromLine(input); + new(TokenType.MdTag, "# ", 0, false, TagType.Header) + }); + } - actuallyTokens.Should().BeEquivalentTo(expectedTokens); - } + [TestCaseSource(typeof(TokenGeneratorTestsData), nameof(TokenGeneratorTestsData.LinesWithHeader))] + public void TokenGenerator_GetTokensBySymbolCorrectly_WhenLineWithHeaders(string input, List expectedTokens) + { + var actuallyTokens = GetAllTokensFromLine(input); - [TestCaseSource(typeof(TokenGeneratorTestsData), nameof(TokenGeneratorTestsData.LinesWithBulletedList))] - public void TokenGenerator_GetTokensBySymbolCorrectly_WhenLineWithBulletedLis(string input, List expectedTokens) - { - var actuallyTokens = GetAllTokensFromLine(input); + actuallyTokens.Should().BeEquivalentTo(expectedTokens); + } - actuallyTokens.Should().BeEquivalentTo(expectedTokens); - } + [TestCaseSource(typeof(TokenGeneratorTestsData), nameof(TokenGeneratorTestsData.LinesWithBulletedList))] + public void TokenGenerator_GetTokensBySymbolCorrectly_WhenLineWithBulletedLis(string input, + List expectedTokens) + { + var actuallyTokens = GetAllTokensFromLine(input); - [TestCaseSource(typeof(TokenGeneratorTestsData), nameof(TokenGeneratorTestsData.LinesWithEscapes))] - public void TokenGenerator_GetTokensBySymbolCorrectly_WhenLineWithEscapes(string input, List expectedTokens) - { - var actuallyTokens = GetAllTokensFromLine(input); + actuallyTokens.Should().BeEquivalentTo(expectedTokens); + } - actuallyTokens.Should().BeEquivalentTo(expectedTokens); - } + [TestCaseSource(typeof(TokenGeneratorTestsData), nameof(TokenGeneratorTestsData.LinesWithEscapes))] + public void TokenGenerator_GetTokensBySymbolCorrectly_WhenLineWithEscapes(string input, List expectedTokens) + { + var actuallyTokens = GetAllTokensFromLine(input); - [TestCaseSource(typeof(TokenGeneratorTestsData), nameof(TokenGeneratorTestsData.LinesWithUnderscores))] - public void TokenGenerator_GetTokensBySymbolCorrectly_WhenLineWithUnderscores(string input, List expectedTokens) - { - var actuallyTokens = GetAllTokensFromLine(input); + actuallyTokens.Should().BeEquivalentTo(expectedTokens); + } - actuallyTokens.Should().BeEquivalentTo(expectedTokens); - } + [TestCaseSource(typeof(TokenGeneratorTestsData), nameof(TokenGeneratorTestsData.LinesWithUnderscores))] + public void TokenGenerator_GetTokensBySymbolCorrectly_WhenLineWithUnderscores(string input, + List expectedTokens) + { + var actuallyTokens = GetAllTokensFromLine(input); - [TestCaseSource(typeof(TokenGeneratorTestsData), nameof(TokenGeneratorTestsData.LineWithMultiTokens))] - public void TokenGenerator_GetTokensBySymbolCorrectly_WhenLineWithMultiTokens(string input, List expectedTokens) - { - var actuallyTokens = GetAllTokensFromLine(input); + actuallyTokens.Should().BeEquivalentTo(expectedTokens); + } - actuallyTokens.Should().BeEquivalentTo(expectedTokens); - } + [TestCaseSource(typeof(TokenGeneratorTestsData), nameof(TokenGeneratorTestsData.LineWithMultiTokens))] + public void TokenGenerator_GetTokensBySymbolCorrectly_WhenLineWithMultiTokens(string input, + List expectedTokens) + { + var actuallyTokens = GetAllTokensFromLine(input); + actuallyTokens.Should().BeEquivalentTo(expectedTokens); + } - private static List GetAllTokensFromLine(string line) - { - int i = 0; - var result = new List(); - while (i < line.Length) - { - var token = TokenGenerator.GetTokenBySymbol(line, i); - result.Add(token); - i += token.Content.Length; - } + private static List GetAllTokensFromLine(string line) + { + var i = 0; + var result = new List(); - return result; + while (i < line.Length) + { + var token = TokenGenerator.GetToken(line, i); + result.Add(token); + i += token.Content.Length; } + + return result; } -} +} \ No newline at end of file From a14650c621ef6eba21023d126f4f1b3b4c003bcb Mon Sep 17 00:00:00 2001 From: Braidenbikher Victor Date: Sun, 8 Dec 2024 17:17:04 +0500 Subject: [PATCH 08/13] =?UTF-8?q?=D0=92=D0=B0=D0=BB=D0=B8=D0=B4=D0=BD?= =?UTF-8?q?=D1=8B=D0=B9=20=D0=BF=D0=B0=D1=80=D1=81=D0=B5=D1=80=20=D1=82?= =?UTF-8?q?=D0=BE=D0=BA=D0=B5=D0=BD=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cs/Markdown/Extensions/TokenExtensions.cs | 15 +- cs/Markdown/ParsedLine.cs | 27 +-- cs/Markdown/Tags/ConcreteTags/BoldTag.cs | 4 +- cs/Markdown/Tags/ConcreteTags/BulletTag.cs | 4 +- cs/Markdown/Tags/ConcreteTags/HeaderTag.cs | 4 +- cs/Markdown/Tags/ConcreteTags/ItalicTag.cs | 4 +- cs/Markdown/Tags/ITag.cs | 4 +- cs/Markdown/Tags/Position.cs | 15 ++ cs/Markdown/Tags/TagType.cs | 2 +- .../TokenParser/ConcreteParser/LineParser.cs | 223 +++++------------- .../TokenParser/Helpers/TokenValidator.cs | 148 ------------ cs/Markdown/TokenParser/ILineParser.cs | 7 - .../TokenParser/Interfaces/ITokenHandler.cs | 15 ++ .../Interfaces/ITokenLineParser.cs | 9 + .../TokenHandlers/BoldTokensHandler.cs | 128 ++++++++++ .../TokenHandlers/BulletedLIHandler.cs | 53 +++++ .../TokenHandlers/HeaderTokensHandler.cs | 48 ++++ .../TokenHandlers/ItalicTokensHandler.cs | 127 ++++++++++ cs/Markdown_Tests/LineParser_Tests.cs | 12 +- cs/Markdown_Tests/TestData/LineParserData.cs | 56 ++++- .../TestData/TokenGeneratorTestsData.cs | 16 +- 21 files changed, 567 insertions(+), 354 deletions(-) create mode 100644 cs/Markdown/Tags/Position.cs delete mode 100644 cs/Markdown/TokenParser/Helpers/TokenValidator.cs delete mode 100644 cs/Markdown/TokenParser/ILineParser.cs create mode 100644 cs/Markdown/TokenParser/Interfaces/ITokenHandler.cs create mode 100644 cs/Markdown/TokenParser/Interfaces/ITokenLineParser.cs create mode 100644 cs/Markdown/TokenParser/TokenHandlers/BoldTokensHandler.cs create mode 100644 cs/Markdown/TokenParser/TokenHandlers/BulletedLIHandler.cs create mode 100644 cs/Markdown/TokenParser/TokenHandlers/HeaderTokensHandler.cs create mode 100644 cs/Markdown/TokenParser/TokenHandlers/ItalicTokensHandler.cs diff --git a/cs/Markdown/Extensions/TokenExtensions.cs b/cs/Markdown/Extensions/TokenExtensions.cs index abeb78cbc..b5dec99ab 100644 --- a/cs/Markdown/Extensions/TokenExtensions.cs +++ b/cs/Markdown/Extensions/TokenExtensions.cs @@ -1,4 +1,5 @@ -using Markdown.Tokens; +using Markdown.Tags; +using Markdown.Tokens; namespace Markdown.Extensions { @@ -9,6 +10,18 @@ public static bool NextTokenIs(this List tokens, TokenType tokenType, int return currentIndex + 1 < tokens.Count && tokens[currentIndex + 1].TokenType == tokenType; } + public static bool CurrentTokenIs(this List tokens, TokenType tokenType, int currentIndex) + { + return currentIndex < tokens.Count && currentIndex >= 0 && + tokens[currentIndex].TokenType == tokenType; + } + + public static bool CurrentTokenIs(this List tokens, TagType tokenType, int currentIndex) + { + return currentIndex < tokens.Count && currentIndex >= 0 && + tokens[currentIndex].TagType == tokenType; + } + public static bool LastTokenIs(this List tokens, TokenType tokenType, int currentIndex) { return currentIndex - 1 >= 0 && tokens[currentIndex - 1].TokenType == tokenType; diff --git a/cs/Markdown/ParsedLine.cs b/cs/Markdown/ParsedLine.cs index 9bf4bb054..656fc950e 100644 --- a/cs/Markdown/ParsedLine.cs +++ b/cs/Markdown/ParsedLine.cs @@ -1,22 +1,19 @@ using Markdown.Tags; -namespace Markdown -{ - public class ParsedLine - { - public readonly string Line; +namespace Markdown; - public readonly List Tags; +public class ParsedLine +{ + public readonly string Line; - public ParsedLine(string line, List tags) - { - var sortedTags = tags.OrderBy(x => x.Position).ToList(); + public readonly List Tags; - if (sortedTags.Any(x => x.Position > line.Length)) - throw new ArgumentException("Позиция тега не может быть больше длины строки", nameof(sortedTags)); + public ParsedLine(string line, List tags) + { + if (tags.Any(x => x.Position > line.Length)) + throw new ArgumentException("Позиция тега не может быть больше длины строки", nameof(tags)); - this.Line = line; - this.Tags = sortedTags; - } + Line = line; + Tags = tags; } -} +} \ No newline at end of file diff --git a/cs/Markdown/Tags/ConcreteTags/BoldTag.cs b/cs/Markdown/Tags/ConcreteTags/BoldTag.cs index 10ccd7159..e3e81aebb 100644 --- a/cs/Markdown/Tags/ConcreteTags/BoldTag.cs +++ b/cs/Markdown/Tags/ConcreteTags/BoldTag.cs @@ -8,13 +8,11 @@ public class BoldTag : ITag public bool IsCloseTag { get; set; } - public bool IsAutoClosing => false; - public string OpenTag => ""; public string CloseTag => ""; - public BoldTag(int position, bool isCloseTag) + public BoldTag(int position, bool isCloseTag = false) { Position = position; IsCloseTag = isCloseTag; diff --git a/cs/Markdown/Tags/ConcreteTags/BulletTag.cs b/cs/Markdown/Tags/ConcreteTags/BulletTag.cs index ed3e6fd03..3b19ab8fd 100644 --- a/cs/Markdown/Tags/ConcreteTags/BulletTag.cs +++ b/cs/Markdown/Tags/ConcreteTags/BulletTag.cs @@ -6,11 +6,11 @@ public class BulletTag : ITag public int Position { get; set; } public bool IsCloseTag { get; set; } - public bool IsAutoClosing => true; public string OpenTag => "
  • "; + public string CloseTag => "
  • "; - public BulletTag(int position, bool isCloseTag) + public BulletTag(int position, bool isCloseTag = false) { Position = position; IsCloseTag = isCloseTag; diff --git a/cs/Markdown/Tags/ConcreteTags/HeaderTag.cs b/cs/Markdown/Tags/ConcreteTags/HeaderTag.cs index 2e036f4be..fcb00f43f 100644 --- a/cs/Markdown/Tags/ConcreteTags/HeaderTag.cs +++ b/cs/Markdown/Tags/ConcreteTags/HeaderTag.cs @@ -8,13 +8,11 @@ public class HeaderTag : ITag public bool IsCloseTag { get; set; } - public bool IsAutoClosing => true; - public string OpenTag => "

    "; public string CloseTag => "

    "; - public HeaderTag(int position, bool isCloseTag) + public HeaderTag(int position, bool isCloseTag = false) { Position = position; IsCloseTag = isCloseTag; diff --git a/cs/Markdown/Tags/ConcreteTags/ItalicTag.cs b/cs/Markdown/Tags/ConcreteTags/ItalicTag.cs index 08de8446f..64562673e 100644 --- a/cs/Markdown/Tags/ConcreteTags/ItalicTag.cs +++ b/cs/Markdown/Tags/ConcreteTags/ItalicTag.cs @@ -8,13 +8,11 @@ public class ItalicTag : ITag public bool IsCloseTag { get; set; } - public bool IsAutoClosing => false; - public string OpenTag => ""; public string CloseTag => ""; - public ItalicTag(int position, bool isCloseTag) + public ItalicTag(int position, bool isCloseTag = false) { Position = position; IsCloseTag = isCloseTag; diff --git a/cs/Markdown/Tags/ITag.cs b/cs/Markdown/Tags/ITag.cs index 4fc37e6b0..a9ebe1901 100644 --- a/cs/Markdown/Tags/ITag.cs +++ b/cs/Markdown/Tags/ITag.cs @@ -3,10 +3,10 @@ public interface ITag { public TagType TagType { get; } + public int Position { get; protected set; } - public bool IsCloseTag { get; protected set; } - public bool IsAutoClosing { get; } + public bool IsCloseTag { get; protected set; } public string OpenTag { get; } diff --git a/cs/Markdown/Tags/Position.cs b/cs/Markdown/Tags/Position.cs new file mode 100644 index 000000000..b1332865b --- /dev/null +++ b/cs/Markdown/Tags/Position.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Markdown.Tags +{ + public struct Position(int LineNumber, int InLinePosititon) + { + public int LineNumber; + + public int InLinePosititon; + } +} diff --git a/cs/Markdown/Tags/TagType.cs b/cs/Markdown/Tags/TagType.cs index 334b9cf5d..6450802a7 100644 --- a/cs/Markdown/Tags/TagType.cs +++ b/cs/Markdown/Tags/TagType.cs @@ -5,7 +5,7 @@ public enum TagType Header, Italic, Bold, - BulletedList, + BulletedListItem, Escape, UnDefined } diff --git a/cs/Markdown/TokenParser/ConcreteParser/LineParser.cs b/cs/Markdown/TokenParser/ConcreteParser/LineParser.cs index 6c1257d41..027d9354d 100644 --- a/cs/Markdown/TokenParser/ConcreteParser/LineParser.cs +++ b/cs/Markdown/TokenParser/ConcreteParser/LineParser.cs @@ -1,36 +1,43 @@ using Markdown.Tags; using Markdown.Tags.ConcreteTags; -using Markdown.TokenParser.Helpers; using Markdown.Tokens; using System.Text; +using Markdown.Extensions; +using Markdown.TokenGeneratorClasses; +using Markdown.TokenParser.Interfaces; +using Markdown.TokenParser.TagsGenerators; +using Markdown.TokenParser.TokenHandlers; namespace Markdown.TokenParser.ConcreteParser { - public class LineParser : ILineParser + public class LineParser : ITokenLineParser { public ParsedLine ParseLine(string line) { - if (line == null) + if(line is null) throw new ArgumentNullException("String argument text must be not null"); - var listTokens = GetTokens(line); - listTokens = EscapeTags(listTokens); - listTokens = EscapeInvalidTokens(listTokens); - listTokens = EscapeNonPairTokens(listTokens); - listTokens = EscapeWrongOrder(listTokens); - var parseLine = GetTagsAndCleanText(listTokens); + var lineTokens = GetTokensLine(line); + var escapedTokens = ResetPositions(EscapeTags(lineTokens)); + var headerTags = new HeaderTokensHandler().HandleLine(escapedTokens); + var bulletedLITags = new BulletedLIHandler().HandleLine(escapedTokens); + var italicTags = new ItalicTokensHandler().HandleLine(escapedTokens); + var boldTags = new BoldTokensHandler().HandleLine(escapedTokens); - return parseLine; + var merged = MergeTokens(escapedTokens, headerTags, boldTags, italicTags, bulletedLITags); + ProcessTokensIntersecting(merged); + + return GetTagsAndCleanText(merged); } - private List GetTokens(string line) + private List GetTokensLine(string line) { int position = 0; var result = new List(); while (position < line.Length) { - var token = TokenGenerator.GetTokenBySymbol(line, position); + var token = TokenGenerator.GetToken(line, position); result.Add(token); position += token.Content.Length; } @@ -50,6 +57,7 @@ private List EscapeTags(List tokens) if (token.TokenType is TokenType.MdTag or TokenType.Escape) { token.TokenType = TokenType.Text; + token.TagType = TagType.UnDefined; previousToken = token; result.Add(token); } @@ -78,179 +86,76 @@ private List EscapeTags(List tokens) return result; } - private List EscapeInvalidTokens(List tokens) => - tokens.Select((t, index) => - t.TokenType is not TokenType.MdTag || TokenValidator.IsValidTagToken(tokens, index) - ? t - : new Token(TokenType.Text, t.Content)) - .ToList(); - - private List EscapeNonPairTokens(List tokens) + private List ResetPositions(List tokens) { - var resultTokens = new List(); - var openTagsPositions = new Stack(); - var incorrectTags = new List(); - - for (var index = 0; index < tokens.Count; index++) - { - var token = tokens[index]; - resultTokens.Add(token); - - // Пропускаем токены, которые не являются тегами - if (token.TokenType != TokenType.MdTag) continue; - - // Проверяем, является ли текущий токен открывающим тегом - if (TokenValidator.IsTokenTagOpen(token.TagType, tokens, index)) - { - openTagsPositions.Push(index); - } - else /*if(TokenValidator.IsTokenTagClosed(token.TagType, tokens, index))*/ - { - // Если это закрывающий тег - if (openTagsPositions.TryPop(out var lastOpenTokenIndex)) - { - SolveOpenAndCloseTags(openTagsPositions, tokens, lastOpenTokenIndex, index, incorrectTags); - } - else - { - // Если не нашли соответствующий открывающий тег - incorrectTags.Add(token); - } - } - // else - // { - // // Если не нашли соответствующий открывающий тег - // incorrectTags.Add(token); - // } - } + var position = 0; - while (openTagsPositions.Count > 0) + foreach (var token in tokens) { - var token = tokens[openTagsPositions.Pop()]; - if (!token.IsSelfCosingTag) - incorrectTags.Add(token); + token.Position = position; + position += token.Content.Length; } - ChangeTypesForIncorrectTokens(incorrectTags); - - return resultTokens; - } - - private void ChangeTypesForIncorrectTokens(List incorrectTags) - { - foreach (var token in incorrectTags) - { - token.TokenType = TokenType.Text; // Изменяем тип токена на текстовый - } + return tokens; } - private void SolveOpenAndCloseTags(Stack openTags, List tokens, int openIndex, - int closeIndex, List incorrectTags) + private List MergeTokens(List allTokens, params List[] tokenLists) { - var openTagToken = tokens[openIndex]; - var closeTagToken = tokens[closeIndex]; - closeTagToken.IsCloseTag = true; + var positionMap = new Dictionary(); - // Проверяем, совпадают ли типы открывающего и закрывающего тегов - if (openTagToken.TagType == closeTagToken.TagType) + foreach (var token in allTokens) { - tokens[openIndex].PairTagPosition = closeIndex; - tokens[closeIndex].PairTagPosition = openIndex; - return; + positionMap[token.Position] = token; } - // Проверяем следующий тег после закрывающего - if (TryGetNextTagType(tokens, closeIndex, out var nextTagTokenPosition)) + foreach (var tokenList in tokenLists) { - if (openTags.TryPeek(out var preOpenTagIndex) && - tokens[preOpenTagIndex].TagType == closeTagToken.TagType) - { - HandleNestedTags(openTags, tokens, preOpenTagIndex, openIndex, closeIndex, nextTagTokenPosition, incorrectTags); - return; - } - - // Если следующий тег не является открывающим - if (!TokenValidator.IsTokenTagOpen(tokens[nextTagTokenPosition].TagType, tokens, nextTagTokenPosition)) + foreach (var token in tokenList) { - HandleIncorrectTag(openTags, tokens, openIndex, closeIndex, incorrectTags); - return; + positionMap[token.Position] = token; } } - // Если не удалось найти соответствующий открывающий тег - incorrectTags.Add(tokens[openIndex]); - incorrectTags.Add(tokens[closeIndex]); - } + // Преобразуем словарь обратно в список + var combinedTokens = positionMap.Values.ToList(); - private void HandleNestedTags(Stack openTags, List tokens, int preOpenTagIndex, - int openIndex, int closeIndex, int nextTagTokenPosition, List incorrectTags) - { - // Обработка вложенных тегов - if (tokens[nextTagTokenPosition].TagType == tokens[openIndex].TagType) - { - openTags.Push(openIndex); - incorrectTags.Add(tokens[closeIndex]); - } - else - { - openTags.Pop(); - tokens[preOpenTagIndex].PairTagPosition = closeIndex; - tokens[closeIndex].PairTagPosition = preOpenTagIndex; - incorrectTags.Add(tokens[openIndex]); - } - } + var combindedTokens = combinedTokens.OrderBy(token => token.Position) + .ToList(); - private void HandleIncorrectTag(Stack openTags, List tokens, - int openIndex, int closeIndex, List incorrectTags) - { - // Обработка некорректных тегов - if (openTags.Count > 0) - { - var preOpenTagIndex = openTags.Pop(); - incorrectTags.Add(tokens[preOpenTagIndex]); - incorrectTags.Add(tokens[openIndex]); - incorrectTags.Add(tokens[closeIndex]); - } - else - { - incorrectTags.Add(tokens[openIndex]); - incorrectTags.Add(tokens[closeIndex]); - } + return combinedTokens; } - - private bool TryGetNextTagType(List tokens, int index, out int nextTagToken) + private void ProcessTokensIntersecting(List tokens) { - for (var i = index + 1; i < tokens.Count; i++) - { - if (tokens[i].TokenType is not TokenType.MdTag) continue; - nextTagToken = i; - return true; - } - - nextTagToken = -1; - return false; - } + var stack = new Stack(); + var process = new List(); - private List EscapeWrongOrder(List tokens) - { - var result = new List(); - var openTags = new Stack(); - foreach (var t in tokens) + foreach (var token in tokens) { - result.Add(t); - if (t.TokenType is TokenType.MdTag && !t.IsCloseTag) - openTags.Push(t); - else if (t.TokenType is TokenType.MdTag) - openTags.Pop(); - if (!TokenValidator.OrderIsCorrect(openTags, t)) + if (token.TagType == TagType.Italic || token.TagType == TagType.Bold) { - t.TokenType = TokenType.Text; - tokens[t.PairTagPosition].TokenType = TokenType.Text; + process.Add(token); + if (token.IsCloseTag) + { + stack.TryPeek(out var openToken); + if (openToken != null && !openToken.IsCloseTag + && openToken.TagType == token.TagType) + { + process.Remove(stack.Pop()); + process.Remove(token); + } + else + stack.Push(token); + } + else + stack.Push(token); } } - - return result; + foreach (var token in stack) + { + token.TagType = TagType.UnDefined; + token.TokenType = TokenType.Text; + } } private ParsedLine GetTagsAndCleanText(List tokens) @@ -279,7 +184,7 @@ private ITag GetNewTag(Token token, int position) TagType.Header => new HeaderTag(position, token.IsCloseTag), TagType.Italic => new ItalicTag(position, token.IsCloseTag), TagType.Bold => new BoldTag(position, token.IsCloseTag), - TagType.BulletedList => new BulletTag(position, token.IsCloseTag), + TagType.BulletedListItem => new BulletTag(position, token.IsCloseTag), _ => throw new NotImplementedException() }; } diff --git a/cs/Markdown/TokenParser/Helpers/TokenValidator.cs b/cs/Markdown/TokenParser/Helpers/TokenValidator.cs deleted file mode 100644 index a43103478..000000000 --- a/cs/Markdown/TokenParser/Helpers/TokenValidator.cs +++ /dev/null @@ -1,148 +0,0 @@ -using Markdown.Extensions; -using Markdown.Tags; -using Markdown.Tokens; - -namespace Markdown.TokenParser.Helpers -{ - public class TokenValidator - { - public static bool IsTokenTagOpen(TagType tagType, List tokens, int index) - { - switch (tagType) - { - case TagType.Italic: - return IsBoldOrItalicOpen(tokens, index); - case TagType.Bold: - return IsBoldOrItalicOpen(tokens, index); - case TagType.Header: - default: - return true; - } - } - - private static bool lastOpenWasInWord; - - public static bool IsTokenTagClosed(TagType tagType, List tokens, int index) - { - switch (tagType) - { - case TagType.Italic: - return IsBoldOrItalicClosed(tokens, index); - case TagType.Bold: - return IsBoldOrItalicClosed(tokens, index); - case TagType.Header: - default: - return true; - } - } - - public static bool IsValidTagToken(List tokens, int index) - { - return tokens[index].TagType switch - { - TagType.Italic => IsValidItalic(tokens, index), - TagType.Bold => IsValidBold(tokens, index), - TagType.Header => IsValidHeader(tokens, index), - TagType.BulletedList => IsValidBulletedTag(tokens, index), - _ => true - }; - } - - public static bool OrderIsCorrect(Stack openedTokens, Token token) - { - return token.TagType != TagType.Bold || openedTokens.All(x => x.TagType != TagType.Italic); - } - - private static bool IsValidHeader(List tokens, int index) - { - if (index == 0 && index + 1 < tokens.Count && tokens[index].TokenType == TokenType.MdTag) - return true; - - return false; - } - - private static bool IsValidBulletedTag(List tokens, int index) - { - if (index == 0 && index + 1 < tokens.Count && tokens[index].TokenType == TokenType.MdTag) - return true; - - return false; - } - - private static bool IsValidItalic(List tokens, int index) - { - return IsValidUnderscoreTag(tokens, index); - } - - private static bool IsValidBold(List tokens, int index) - { - return IsValidUnderscoreTag(tokens, index); - } - - private static bool IsValidUnderscoreTag(List tokens, int index) - { - var isOpen = IsBoldOrItalicOpen(tokens, index); - var isClosed = IsBoldOrItalicClosed(tokens, index); - - return (isOpen ^ isClosed); - } - - #region IsBoldOrItalicOpen - - private static bool IsBoldOrItalicOpen(List tokens, int index) - { - var betweenWordsOpen = IsBoldOrItalicBetweenWordsOpen(tokens, index); - - var inOneWord = IsBoldOrItalicInOneWordOpen(tokens, index); - - return betweenWordsOpen ^ inOneWord; - } - - //Является ли тег открывающим для ситуации когда тег применятеся к нескольким - //применяется между словами: '_вася' - private static bool IsBoldOrItalicBetweenWordsOpen(List tokens, int index) - { - return tokens.NextTokenIs(TokenType.Text, index) && - (index - 1 < 0 || tokens.LastTokenIs(TokenType.WhiteSpace, index) - || tokens.LastTokenIs(TokenType.MdTag, index)); - } - - //Является ли тег открывающим для ситуации когда тег применятеся внутри - //слова: ' кр_овать' '_word_w_w_word_ - private static bool IsBoldOrItalicInOneWordOpen(List tokens, int index) - { - return tokens.NextTokenIs(TokenType.Text, index) && - tokens.LastTokenIs(TokenType.Text, index) && - !IsBoldOrItalicOpen(tokens, index - 2); - } - - #endregion - - #region IsBoldOrdItalicClosed - - private static bool IsBoldOrItalicClosed(List tokens, int index) - { - var betweenWordsOpen = IsBoldOrItalicBetweenWordsClosed(tokens, index); - - var inOneWord = IsBoldOrItalicInOneWordClosed(tokens, index); - - return betweenWordsOpen ^ inOneWord; - } - - private static bool IsBoldOrItalicBetweenWordsClosed(List tokens, int index) - { - return (tokens.NextTokenIs(TokenType.WhiteSpace, index) || index + 1 >= tokens.Count) && - (tokens.LastTokenIs(TokenType.Text, index) - || tokens.LastTokenIs(TokenType.MdTag, index)); - } - - private static bool IsBoldOrItalicInOneWordClosed(List tokens, int index) - { - return tokens.NextTokenIs(TokenType.Text, index) && - tokens.LastTokenIs(TokenType.Text, index) && - IsBoldOrItalicOpen(tokens, index - 2); - } - - #endregion - } -} diff --git a/cs/Markdown/TokenParser/ILineParser.cs b/cs/Markdown/TokenParser/ILineParser.cs deleted file mode 100644 index 9178eebc4..000000000 --- a/cs/Markdown/TokenParser/ILineParser.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Markdown.TokenParser -{ - public interface ILineParser - { - public ParsedLine ParseLine(string line); - } -} diff --git a/cs/Markdown/TokenParser/Interfaces/ITokenHandler.cs b/cs/Markdown/TokenParser/Interfaces/ITokenHandler.cs new file mode 100644 index 000000000..55b7a8bb2 --- /dev/null +++ b/cs/Markdown/TokenParser/Interfaces/ITokenHandler.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Markdown.Tags; +using Markdown.Tokens; + +namespace Markdown.TokenParser.Interfaces +{ + public interface ITokenHandler + { + List HandleLine(List line); + } +} diff --git a/cs/Markdown/TokenParser/Interfaces/ITokenLineParser.cs b/cs/Markdown/TokenParser/Interfaces/ITokenLineParser.cs new file mode 100644 index 000000000..86936687e --- /dev/null +++ b/cs/Markdown/TokenParser/Interfaces/ITokenLineParser.cs @@ -0,0 +1,9 @@ +using Markdown.Tags; + +namespace Markdown.TokenParser.Interfaces +{ + public interface ITokenLineParser + { + public ParsedLine ParseLine(string text); + } +} diff --git a/cs/Markdown/TokenParser/TokenHandlers/BoldTokensHandler.cs b/cs/Markdown/TokenParser/TokenHandlers/BoldTokensHandler.cs new file mode 100644 index 000000000..73272857f --- /dev/null +++ b/cs/Markdown/TokenParser/TokenHandlers/BoldTokensHandler.cs @@ -0,0 +1,128 @@ +using Markdown.Extensions; +using Markdown.Tags; +using Markdown.TokenParser.Interfaces; +using Markdown.Tokens; + +namespace Markdown.TokenParser.TokenHandlers; + +public class BoldTokensHandler : ITokenHandler +{ + public List HandleLine(List line) + { + var result = new List(); + var opened = new List(1); + var position = 0; + + for (var j = 0; j < line.Count; j++) + { + if (opened.Count == 0 && IsOpen(j, line)) + { + var token = line[j]; + opened.Add(new Token(TokenType.MdTag, "__", + position, false, TagType.Bold)); + } + else if (opened.Count > 0 && IsClosed(j, line)) + { + result.Add(opened[0]); + result.Add(new Token(TokenType.MdTag, "__", + position, true, TagType.Bold)); + + opened.Clear(); + } + else if (line[j].TagType == TagType.Bold) + { + result.Add(new Token(TokenType.Text, "__", position)); + } + + position += line[j].Content.Length; + } + + if (opened.Count > 0) + { + opened[0].TagType = TagType.UnDefined; + opened[0].TokenType = TokenType.Text; + + result.Add(opened[0]); + } + + return result; + } + + private bool IsOpen(int index, List tokens) + { + var isFirstInLine = IsFirstInLine(index, tokens); + var isOpenOrdinary = IsOpenOrdinary(index, tokens); + var isOpenClosed = IsOpenClosed(index, tokens); + + return isFirstInLine ^ isOpenOrdinary ^ isOpenClosed; + } + + private bool IsClosed(int index, List tokens) + { + var isLastInLine = IsLastInLine(index, tokens); + var isClosedOrdinary = IsClosedOrdinary(index, tokens); + var isOpenClosed = IsOpenClosed(index, tokens); + + return isLastInLine ^ isClosedOrdinary ^ isOpenClosed; + } + + /// + /// Определяет является ли токен одновременно и + /// открывающим и закрывающим тегом - случай + /// если тэг в слове ("пре_сп_ко_йн_ый) + /// + /// Индекс токена + /// Список токенов для проверки + /// true если тег внутри слова иначе false + private bool IsOpenClosed(int index, List tokens) + { + return tokens.LastTokenIs(TokenType.Text, index) && + tokens.CurrentTokenIs(TagType.Bold, index) & + tokens.NextTokenIs(TokenType.Text, index); + } + + #region OpenSituations + + private bool IsFirstInLine(int index, List tokens) + { + return index == 0 && tokens.CurrentTokenIs(TagType.Bold, index) && + (tokens.NextTokenIs(TokenType.Text, index) || + tokens.NextTokenIs(TokenType.MdTag, index)); + } + + private bool IsOpenOrdinary(int index, List tokens) + { + var hasWhSpaceBefore = tokens.LastTokenIs(TokenType.WhiteSpace, index); + var hasMdTagBefore = tokens.LastTokenIs(TokenType.MdTag, index); + + return (hasWhSpaceBefore || hasMdTagBefore) && + tokens.CurrentTokenIs(TagType.Bold, index) && + tokens.NextTokenIs(TokenType.Text, index); + } + + #endregion + + + #region CloseSituations + + private bool IsLastInLine(int index, List tokens) + { + var isLast = index == tokens.Count() - 1; + + return isLast && tokens.CurrentTokenIs(TagType.Bold, index) && + (tokens.LastTokenIs(TokenType.Text, index) || + tokens.LastTokenIs(TokenType.MdTag, index)); + } + + private bool IsClosedOrdinary(int index, List tokens) + { + var hasTextAfter = tokens.NextTokenIs(TokenType.WhiteSpace, index); + var hasMdTagAfter = tokens.NextTokenIs(TokenType.MdTag, index); + + return tokens.LastTokenIs(TokenType.Text, index) && + tokens.CurrentTokenIs(TagType.Bold, index) && + (hasMdTagAfter || hasTextAfter); + } + + #endregion +} \ No newline at end of file diff --git a/cs/Markdown/TokenParser/TokenHandlers/BulletedLIHandler.cs b/cs/Markdown/TokenParser/TokenHandlers/BulletedLIHandler.cs new file mode 100644 index 000000000..6b89d69bb --- /dev/null +++ b/cs/Markdown/TokenParser/TokenHandlers/BulletedLIHandler.cs @@ -0,0 +1,53 @@ +using Markdown.Tags; +using Markdown.Tokens; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Markdown.Extensions; + +namespace Markdown.TokenParser.TokenHandlers +{ + public class BulletedLIHandler + { + public List HandleLine(List line) + { + var result = new List(); + var position = 0; + var addCloseTag = false; + + for (var j = 0; j < line.Count; j++) + { + if (IsBulletedListItem(line, j) && line[j].TagType == TagType.BulletedListItem) + { + result.Add(new Token(TokenType.MdTag, "* ", + position, false, TagType.BulletedListItem)); + + addCloseTag = true; + } + else if (line[j].TagType == TagType.BulletedListItem) + { + result.Add(new Token(TokenType.Text, "* ", position)); + } + + position += line[j].Content.Length; + } + if (addCloseTag) + result.Add(CreateCloseTag(position)); + + + return result; + } + + private bool IsBulletedListItem(List tokens, int index) + { + return index == 0 && tokens.CurrentTokenIs(TagType.BulletedListItem, index); + } + + private Token CreateCloseTag(int lastIndex) + { + return new Token(TokenType.MdTag, "", lastIndex + 1, true, TagType.BulletedListItem); + } + } +} diff --git a/cs/Markdown/TokenParser/TokenHandlers/HeaderTokensHandler.cs b/cs/Markdown/TokenParser/TokenHandlers/HeaderTokensHandler.cs new file mode 100644 index 000000000..ad9cc4481 --- /dev/null +++ b/cs/Markdown/TokenParser/TokenHandlers/HeaderTokensHandler.cs @@ -0,0 +1,48 @@ +using Markdown.Extensions; +using Markdown.Tags; +using Markdown.TokenParser.Interfaces; +using Markdown.Tokens; + +namespace Markdown.TokenParser.TokenHandlers; + +public class HeaderTokensHandler : ITokenHandler +{ + public List HandleLine(List line) + { + var result = new List(); + var position = 0; + var addCloseTag = false; + + for (var j = 0; j < line.Count; j++) + { + if (IsHeader(line, j) && line[j].TagType == TagType.Header) + { + result.Add(new Token(TokenType.MdTag, "# ", + position, false, TagType.Header)); + + addCloseTag = true; + } + else if (line[j].TagType == TagType.Header) + { + result.Add(new Token(TokenType.Text, "# ", position)); + } + + position += line[j].Content.Length; + } + if(addCloseTag) + result.Add(CreateCloseTag(position)); + + + return result; + } + + private bool IsHeader(List tokens, int index) + { + return index == 0 && tokens.CurrentTokenIs(TagType.Header, index); + } + + private Token CreateCloseTag(int lastIndex) + { + return new Token(TokenType.MdTag, "", lastIndex + 1, true, TagType.Header); + } +} \ No newline at end of file diff --git a/cs/Markdown/TokenParser/TokenHandlers/ItalicTokensHandler.cs b/cs/Markdown/TokenParser/TokenHandlers/ItalicTokensHandler.cs new file mode 100644 index 000000000..2901de3b4 --- /dev/null +++ b/cs/Markdown/TokenParser/TokenHandlers/ItalicTokensHandler.cs @@ -0,0 +1,127 @@ +using Markdown.Extensions; +using Markdown.Tags; +using Markdown.TokenParser.Interfaces; +using Markdown.Tokens; + +namespace Markdown.TokenParser.TagsGenerators; + +public class ItalicTokensHandler : ITokenHandler +{ + public List HandleLine(List line) + { + var result = new List(); + var opened = new List(1); + var position = 0; + + for (var j = 0; j < line.Count; j++) + { + if (opened.Count == 0 && IsOpen(j, line)) + { + var token = line[j]; + opened.Add(new Token(TokenType.MdTag, "_", + position, false, TagType.Italic)); + } + else if (opened.Count > 0 && IsClosed(j, line)) + { + result.Add(opened[0]); + result.Add(new Token(TokenType.MdTag, "_", + position, true, TagType.Italic)); + + opened.Clear(); + } + else if (line[j].TagType == TagType.Italic) + { + result.Add(new Token(TokenType.Text, "_", position)); + } + + position += line[j].Content.Length; + } + + if (opened.Count > 0) + { + opened[0].TagType = TagType.UnDefined; + opened[0].TokenType = TokenType.Text; + + result.Add(opened[0]); + } + + return result; + } + + private bool IsOpen(int index, List tokens) + { + var isFirstInLine = IsFirstInLine(index, tokens); + var isOpenOrdinary = IsOpenOrdinary(index, tokens); + var isOpenClosed = IsOpenClosed(index, tokens); + + return isFirstInLine ^ isOpenOrdinary ^ isOpenClosed; + } + + private bool IsClosed(int index, List tokens) + { + var isLastInLine = IsLastInLine(index, tokens); + var isClosedOrdinary = IsClosedOrdinary(index, tokens); + var isOpenClosed = IsOpenClosed(index, tokens); + + return isLastInLine ^ isClosedOrdinary ^ isOpenClosed; + } + + /// + /// Определяет является ли токен одновременно и + /// открывающим и закрывающим тегом - случай + /// если тэг в слове ("пре_сп_ко_йн_ый) + /// + /// Индекс токена + /// Список токенов для проверки + /// true если тег внутри слова иначе false + private bool IsOpenClosed(int index, List tokens) + { + return tokens.LastTokenIs(TokenType.Text, index) && + tokens.CurrentTokenIs(TagType.Italic, index) & + tokens.NextTokenIs(TokenType.Text, index); + } + + #region OpenSituations + + private bool IsFirstInLine(int index, List tokens) + { + return index == 0 && tokens.CurrentTokenIs(TagType.Italic, index) && + tokens.NextTokenIs(TokenType.Text, index); + } + + private bool IsOpenOrdinary(int index, List tokens) + { + var hasWhSpaceBefore = tokens.LastTokenIs(TokenType.WhiteSpace, index); + var hasMdTagBefore = tokens.LastTokenIs(TokenType.MdTag, index); + + return (hasWhSpaceBefore || hasMdTagBefore) && + tokens.CurrentTokenIs(TagType.Italic, index) && + tokens.NextTokenIs(TokenType.Text, index); + } + + #endregion + + + #region CloseSituations + + private bool IsLastInLine(int index, List tokens) + { + var isLast = index == tokens.Count() - 1; + + return isLast && tokens.CurrentTokenIs(TagType.Italic, index) && + (tokens.LastTokenIs(TokenType.Text, index) || + tokens.LastTokenIs(TokenType.MdTag, index)); + } + + private bool IsClosedOrdinary(int index, List tokens) + { + var hasTextAfter = tokens.NextTokenIs(TokenType.WhiteSpace, index); + var hasMdTagAfter = tokens.NextTokenIs(TokenType.MdTag, index); + + return tokens.LastTokenIs(TokenType.Text, index) && + tokens.CurrentTokenIs(TagType.Italic, index) && + (hasMdTagAfter || hasTextAfter); + } + + #endregion +} \ No newline at end of file diff --git a/cs/Markdown_Tests/LineParser_Tests.cs b/cs/Markdown_Tests/LineParser_Tests.cs index 574e6c5b8..dab5d1178 100644 --- a/cs/Markdown_Tests/LineParser_Tests.cs +++ b/cs/Markdown_Tests/LineParser_Tests.cs @@ -1,13 +1,14 @@ using FluentAssertions; using Markdown.Tags; using Markdown.TokenParser.ConcreteParser; +using Markdown.TokenParser.Interfaces; using MarkdownTests.TestData; namespace MarkdownTests { public class LineParserTests { - private LineParser parser = new LineParser(); + private ITokenLineParser parser = new LineParser(); [Test] public void ParseLine_ThrowArgumentNullException_WhenArgumentIsNull() @@ -59,5 +60,14 @@ public void ParseLine_ShoudBeCorrect_WhenLineWithBoldTags(string inLine, string parsedLines.Line.Should().BeEquivalentTo(expectedLine); parsedLines.Tags.Should().BeEquivalentTo(tags); } + + [TestCaseSource(typeof(LineParserData), nameof(LineParserData.MultiTagsLine))] + public void ParseLine_ShoudBeCorrect_WhenLineWithMultiTags(string inLine, string expectedLine, List tags) + { + var parsedLines = parser.ParseLine(inLine); + + parsedLines.Line.Should().BeEquivalentTo(expectedLine); + parsedLines.Tags.Should().BeEquivalentTo(tags); + } } } diff --git a/cs/Markdown_Tests/TestData/LineParserData.cs b/cs/Markdown_Tests/TestData/LineParserData.cs index ee6107f17..590fa8fd1 100644 --- a/cs/Markdown_Tests/TestData/LineParserData.cs +++ b/cs/Markdown_Tests/TestData/LineParserData.cs @@ -16,10 +16,12 @@ public static IEnumerable LineWithHeader() yield return new TestCaseData("# word bubo bibo", "word bubo bibo", new List() { new HeaderTag(0, false), + new HeaderTag(14, true), }); yield return new TestCaseData("# Why1 word 23 bubo bibo", "Why1 word 23 bubo bibo", new List() { new HeaderTag(0, false), + new HeaderTag(22, true), }); } @@ -47,8 +49,20 @@ public static IEnumerable LinesWithBulletedList() yield return new TestCaseData("* один", "один", new List() { new BulletTag(0, false), + new BulletTag(4, true), }); - yield return new TestCaseData(@"\* один", @"* один", new List()); + yield return new TestCaseData("* _один_ __два__", "один два", new List() + { + new BulletTag(0, false), + new ItalicTag(0, false), + new ItalicTag(4, true), + new BoldTag(5, false), + new BoldTag(8, true), + new BulletTag(8, true), + }); + yield return new TestCaseData(@"\* один", "* один", new List()); + yield return new TestCaseData(" * один", " * один", new List()); + yield return new TestCaseData("горит * волбу", "горит * волбу", new List()); } public static IEnumerable LineWithBold() @@ -69,5 +83,45 @@ public static IEnumerable LineWithBold() yield return new TestCaseData(@"\__word bubo\__ bibo", @"__word bubo__ bibo", new List()); yield return new TestCaseData("Why__1__ word 23 bubo bibo", "Why__1__ word 23 bubo bibo", new List()); } + + public static IEnumerable MultiTagsLine() + { + //Это можно отнести к пересечению тегов + yield return new TestCaseData("__word _bubo___", "__word _bubo___", new List() + { + }); + //Это тоже по сути пересечение тегов + yield return new TestCaseData("___word bubo___", "___word bubo___", new List() + { + }); + yield return new TestCaseData("__word _bubo_ love__", "word bubo love", new List() + { + new BoldTag(0, false), + new ItalicTag(5, false), + new ItalicTag(9, true), + new BoldTag(14, true), + }); + yield return new TestCaseData("___word_ bubo__", "word bubo", new List() + { + new BoldTag(0, false), + new ItalicTag(0, false), + new ItalicTag(4, true), + new BoldTag(9, true), + }); + yield return new TestCaseData("__word _bu_ bo__", "word bu bo", new List() + { + new BoldTag(0, false), + new ItalicTag(5, false), + new ItalicTag(7, true), + new BoldTag(10, true), + }); + yield return new TestCaseData("_word __bu__ bo_", "word bu bo", new List() + { + new ItalicTag(0, false), + new BoldTag(5, false), + new BoldTag(7, true), + new ItalicTag(10, true), + }); + } } } diff --git a/cs/Markdown_Tests/TestData/TokenGeneratorTestsData.cs b/cs/Markdown_Tests/TestData/TokenGeneratorTestsData.cs index cc5fe781c..30312e2cc 100644 --- a/cs/Markdown_Tests/TestData/TokenGeneratorTestsData.cs +++ b/cs/Markdown_Tests/TestData/TokenGeneratorTestsData.cs @@ -76,30 +76,30 @@ public static IEnumerable LinesWithBulletedList() { yield return new TestCaseData("* Заголовок", new List { - new(TokenType.MdTag, "* ", 0, false, TagType.BulletedList), + new(TokenType.MdTag, "* ", 0, false, TagType.BulletedListItem), new(TokenType.Text, "Заголовок", 2) }); yield return new TestCaseData("* * * ", new List { - new(TokenType.MdTag, "* ", 0, false, TagType.BulletedList), - new(TokenType.MdTag, "* ", 2, false, TagType.BulletedList), - new(TokenType.MdTag, "* ", 4, false, TagType.BulletedList) + new(TokenType.MdTag, "* ", 0, false, TagType.BulletedListItem), + new(TokenType.MdTag, "* ", 2, false, TagType.BulletedListItem), + new(TokenType.MdTag, "* ", 4, false, TagType.BulletedListItem) }); yield return new TestCaseData(@" * * ", new List { new(TokenType.WhiteSpace, " ", 0), - new(TokenType.MdTag, "* ", 1, false, TagType.BulletedList), - new(TokenType.MdTag, "* ", 3, false, TagType.BulletedList) + new(TokenType.MdTag, "* ", 1, false, TagType.BulletedListItem), + new(TokenType.MdTag, "* ", 3, false, TagType.BulletedListItem) }); yield return new TestCaseData(@"* раз * два", new List { - new(TokenType.MdTag, "* ", 0, false, TagType.BulletedList), + new(TokenType.MdTag, "* ", 0, false, TagType.BulletedListItem), new(TokenType.Text, "раз", 2), new(TokenType.WhiteSpace, " ", 5), - new(TokenType.MdTag, "* ", 6, false, TagType.BulletedList), + new(TokenType.MdTag, "* ", 6, false, TagType.BulletedListItem), new(TokenType.Text, "два", 8) }); } From e031495c3d98cedd6b2608f317686c68a591e0ae Mon Sep 17 00:00:00 2001 From: Braidenbikher Victor Date: Sun, 8 Dec 2024 23:27:04 +0500 Subject: [PATCH 09/13] =?UTF-8?q?=D0=9D=D0=B5=D0=B1=D0=BE=D0=BB=D1=8C?= =?UTF-8?q?=D1=88=D0=BE=D0=B5=20=D0=B8=D0=B7=D0=BC=D0=B5=D0=BD=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D0=B5=20HtmlConverter=20=D1=81=20Md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cs/Markdown/Converter/ConcreteConverter/HtmlConverter.cs | 2 -- cs/Markdown/Md.cs | 6 +++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/cs/Markdown/Converter/ConcreteConverter/HtmlConverter.cs b/cs/Markdown/Converter/ConcreteConverter/HtmlConverter.cs index 2fe00d93c..6faf6bf1a 100644 --- a/cs/Markdown/Converter/ConcreteConverter/HtmlConverter.cs +++ b/cs/Markdown/Converter/ConcreteConverter/HtmlConverter.cs @@ -40,8 +40,6 @@ public string Convert(ParsedLine[] parsedLines) } sb.Append(text.Line.AsSpan(prevTagPos, text.Line.Length - prevTagPos)); - if (text.Tags.Count > 0 && text.Tags[0].TagType == TagType.Header) - sb.Append(text.Tags[0].CloseTag); startedLine = false; } if (containList) diff --git a/cs/Markdown/Md.cs b/cs/Markdown/Md.cs index 9e7638c3c..b3ec71ea7 100644 --- a/cs/Markdown/Md.cs +++ b/cs/Markdown/Md.cs @@ -1,16 +1,16 @@ using Markdown.Converter; using Markdown.Extensions; -using Markdown.TokenParser; +using Markdown.TokenParser.Interfaces; namespace Markdown { public class Md { - private readonly ILineParser markdownTokenizer; + private readonly ITokenLineParser markdownTokenizer; private readonly IConverter converter; - public Md(ILineParser tokenizer, IConverter converter) + public Md(ITokenLineParser tokenizer, IConverter converter) { markdownTokenizer = tokenizer; this.converter = converter; From 06c663bd2edd09db3dfa67c976c7285be716456b Mon Sep 17 00:00:00 2001 From: Braidenbikher Victor Date: Mon, 9 Dec 2024 04:10:33 +0500 Subject: [PATCH 10/13] =?UTF-8?q?=D0=A0=D0=B0=D0=B1=D0=BE=D1=87=D0=B5?= =?UTF-8?q?=D0=B5=20=D1=80=D0=B5=D1=88=D0=B5=D0=BD=D0=B8=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ConcreteConverter/HtmlConverter.cs | 78 ++--- cs/Markdown/Converter/IConverter.cs | 11 +- cs/Markdown/Extensions/StringExtensions.cs | 19 +- cs/Markdown/Extensions/TokenExtensions.cs | 41 ++- cs/Markdown/Md.cs | 34 +- cs/Markdown/ParsedLine.cs | 2 + cs/Markdown/Tags/ConcreteTags/BoldTag.cs | 27 +- cs/Markdown/Tags/ConcreteTags/BulletTag.cs | 12 +- cs/Markdown/Tags/ConcreteTags/HeaderTag.cs | 27 +- cs/Markdown/Tags/ConcreteTags/ItalicTag.cs | 27 +- cs/Markdown/Tags/ITag.cs | 19 +- cs/Markdown/Tags/Position.cs | 15 - cs/Markdown/Tags/TagType.cs | 20 +- .../Interfaces/ITokenGenerateRule.cs | 11 +- .../Interfaces/ITokenGenerator.cs | 16 +- .../TokenGeneratorClasses/TokenGenerator.cs | 99 +++--- .../GenerateBoldTokenRule.cs | 17 +- .../GenerateEscapeTokenRule.cs | 20 +- .../GenerateHashTokenRule.cs | 17 +- .../GenerateItalicTokenRule.cs | 17 +- .../GenerateTextTokenRule.cs | 73 ++-- .../GenerateWhiteSpaceRule.cs | 20 +- .../TokenParser/ConcreteParser/LineParser.cs | 325 +++++++++++------- .../TokenParser/Interfaces/ITokenHandler.cs | 19 +- .../Interfaces/ITokenLineParser.cs | 11 +- .../TokenHandlers/BoldTokensHandler.cs | 55 ++- .../TokenHandlers/BulletedLIHandler.cs | 77 ++--- .../TokenHandlers/HeaderTokensHandler.cs | 3 +- .../TokenHandlers/ItalicTokensHandler.cs | 62 +++- cs/Markdown/Tokens/Token.cs | 69 +++- cs/Markdown/Tokens/TokenType.cs | 19 +- cs/Markdown_Tests/LineParser_Tests.cs | 8 + cs/Markdown_Tests/Md_Tests.cs | 14 +- cs/Markdown_Tests/TestData/LineParserData.cs | 52 +++ .../TestData/TokenGeneratorTestsData.cs | 14 +- 35 files changed, 761 insertions(+), 589 deletions(-) delete mode 100644 cs/Markdown/Tags/Position.cs diff --git a/cs/Markdown/Converter/ConcreteConverter/HtmlConverter.cs b/cs/Markdown/Converter/ConcreteConverter/HtmlConverter.cs index 6faf6bf1a..6fc71f204 100644 --- a/cs/Markdown/Converter/ConcreteConverter/HtmlConverter.cs +++ b/cs/Markdown/Converter/ConcreteConverter/HtmlConverter.cs @@ -1,51 +1,49 @@ -using Markdown.Tags; +using System.Text; using Markdown.Tags.ConcreteTags; -using System.Text; -namespace Markdown.Converter.ConcreteConverter +namespace Markdown.Converter.ConcreteConverter; + +public class HtmlConverter : IConverter { - public class HtmlConverter : IConverter + public string Convert(ParsedLine[] parsedLines) { - public string Convert(ParsedLine[] parsedLines) + var sb = new StringBuilder(); + + var containList = false; + var startedLine = true; + foreach (var text in parsedLines) { - var sb = new StringBuilder(); + if (!startedLine) + sb.Append('\n'); - var containList = false; - var startedLine = true; - foreach (var text in parsedLines) + if (containList && text.Tags.FirstOrDefault() is not BulletTag) { - if (!startedLine) - sb.Append('\n'); - - if (containList && text.Tags.FirstOrDefault() is not BulletTag) - { - containList = false; - sb.Append(""); - } - else if (!containList && text.Tags.FirstOrDefault() is BulletTag) - { - containList = true; - sb.Append("
      "); - } - - var prevTagPos = 0; - foreach (var tag in text.Tags) - { - sb.Append(text.Line.AsSpan(prevTagPos, tag.Position - prevTagPos)); - - sb.Append(tag.IsCloseTag ? - tag.CloseTag : tag.OpenTag); - - prevTagPos = tag.Position; - } - - sb.Append(text.Line.AsSpan(prevTagPos, text.Line.Length - prevTagPos)); - startedLine = false; - } - if (containList) + containList = false; sb.Append("
    "); + } + else if (!containList && text.Tags.FirstOrDefault() is BulletTag) + { + containList = true; + sb.Append("
      "); + } - return sb.ToString(); + var prevTagPos = 0; + foreach (var tag in text.Tags) + { + sb.Append(text.Line.AsSpan(prevTagPos, tag.Position - prevTagPos)); + + sb.Append(tag.IsCloseTag ? tag.CloseTag : tag.OpenTag); + + prevTagPos = tag.Position; + } + + sb.Append(text.Line.AsSpan(prevTagPos, text.Line.Length - prevTagPos)); + startedLine = false; } + + if (containList) + sb.Append("
    "); + + return sb.ToString(); } -} +} \ No newline at end of file diff --git a/cs/Markdown/Converter/IConverter.cs b/cs/Markdown/Converter/IConverter.cs index 9def0b7c0..6a3825d67 100644 --- a/cs/Markdown/Converter/IConverter.cs +++ b/cs/Markdown/Converter/IConverter.cs @@ -1,7 +1,6 @@ -namespace Markdown.Converter +namespace Markdown.Converter; + +public interface IConverter { - public interface IConverter - { - public string Convert(ParsedLine[] parsedLines); - } -} + public string Convert(ParsedLine[] parsedLines); +} \ No newline at end of file diff --git a/cs/Markdown/Extensions/StringExtensions.cs b/cs/Markdown/Extensions/StringExtensions.cs index 3da92d71e..32b985447 100644 --- a/cs/Markdown/Extensions/StringExtensions.cs +++ b/cs/Markdown/Extensions/StringExtensions.cs @@ -1,13 +1,14 @@ -namespace Markdown.Extensions +namespace Markdown.Extensions; + +public static class StringExtensions { - public static class StringExtensions + public static string[] SplitIntoLines(this string text) { - public static string[] SplitIntoLines(this string text) - => text.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None); + return text.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None); + } - public static bool NextCharIs(this string line, char ch, int currentIndex) - { - return currentIndex + 1 < line.Length && line[currentIndex + 1] == ch; - } + public static bool NextCharIs(this string line, char ch, int currentIndex) + { + return currentIndex + 1 < line.Length && line[currentIndex + 1] == ch; } -} +} \ No newline at end of file diff --git a/cs/Markdown/Extensions/TokenExtensions.cs b/cs/Markdown/Extensions/TokenExtensions.cs index b5dec99ab..4ef6b41a4 100644 --- a/cs/Markdown/Extensions/TokenExtensions.cs +++ b/cs/Markdown/Extensions/TokenExtensions.cs @@ -1,30 +1,29 @@ using Markdown.Tags; using Markdown.Tokens; -namespace Markdown.Extensions +namespace Markdown.Extensions; + +public static class TokenExtensions { - public static class TokenExtensions + public static bool NextTokenIs(this List tokens, TokenType tokenType, int currentIndex) { - public static bool NextTokenIs(this List tokens, TokenType tokenType, int currentIndex) - { - return currentIndex + 1 < tokens.Count && tokens[currentIndex + 1].TokenType == tokenType; - } + return currentIndex + 1 < tokens.Count && tokens[currentIndex + 1].TokenType == tokenType; + } - public static bool CurrentTokenIs(this List tokens, TokenType tokenType, int currentIndex) - { - return currentIndex < tokens.Count && currentIndex >= 0 && - tokens[currentIndex].TokenType == tokenType; - } + public static bool CurrentTokenIs(this List tokens, TokenType tokenType, int currentIndex) + { + return currentIndex < tokens.Count && currentIndex >= 0 && + tokens[currentIndex].TokenType == tokenType; + } - public static bool CurrentTokenIs(this List tokens, TagType tokenType, int currentIndex) - { - return currentIndex < tokens.Count && currentIndex >= 0 && - tokens[currentIndex].TagType == tokenType; - } + public static bool CurrentTokenIs(this List tokens, TagType tokenType, int currentIndex) + { + return currentIndex < tokens.Count && currentIndex >= 0 && + tokens[currentIndex].TagType == tokenType; + } - public static bool LastTokenIs(this List tokens, TokenType tokenType, int currentIndex) - { - return currentIndex - 1 >= 0 && tokens[currentIndex - 1].TokenType == tokenType; - } + public static bool LastTokenIs(this List tokens, TokenType tokenType, int currentIndex) + { + return currentIndex - 1 >= 0 && tokens[currentIndex - 1].TokenType == tokenType; } -} +} \ No newline at end of file diff --git a/cs/Markdown/Md.cs b/cs/Markdown/Md.cs index b3ec71ea7..cc3780a63 100644 --- a/cs/Markdown/Md.cs +++ b/cs/Markdown/Md.cs @@ -2,25 +2,23 @@ using Markdown.Extensions; using Markdown.TokenParser.Interfaces; -namespace Markdown -{ - public class Md - { - private readonly ITokenLineParser markdownTokenizer; +namespace Markdown; - private readonly IConverter converter; +public class Md +{ + private readonly IConverter converter; + private readonly ITokenLineParser markdownTokenizer; - public Md(ITokenLineParser tokenizer, IConverter converter) - { - markdownTokenizer = tokenizer; - this.converter = converter; - } + public Md(ITokenLineParser tokenizer, IConverter converter) + { + markdownTokenizer = tokenizer; + this.converter = converter; + } - public string Render(string mdString) - { - return converter.Convert(mdString.SplitIntoLines() - .Select(markdownTokenizer.ParseLine) - .ToArray()); - } + public string Render(string mdString) + { + return converter.Convert(mdString.SplitIntoLines() + .Select(markdownTokenizer.ParseLine) + .ToArray()); } -} +} \ No newline at end of file diff --git a/cs/Markdown/ParsedLine.cs b/cs/Markdown/ParsedLine.cs index 656fc950e..afba1888d 100644 --- a/cs/Markdown/ParsedLine.cs +++ b/cs/Markdown/ParsedLine.cs @@ -10,6 +10,8 @@ public class ParsedLine public ParsedLine(string line, List tags) { + if (line == null || tags == null) + throw new ArgumentNullException($"Параметры Line и Tags класса ParsedLine не могут быть null"); if (tags.Any(x => x.Position > line.Length)) throw new ArgumentException("Позиция тега не может быть больше длины строки", nameof(tags)); diff --git a/cs/Markdown/Tags/ConcreteTags/BoldTag.cs b/cs/Markdown/Tags/ConcreteTags/BoldTag.cs index e3e81aebb..9b43e74c1 100644 --- a/cs/Markdown/Tags/ConcreteTags/BoldTag.cs +++ b/cs/Markdown/Tags/ConcreteTags/BoldTag.cs @@ -1,21 +1,20 @@ -namespace Markdown.Tags.ConcreteTags +namespace Markdown.Tags.ConcreteTags; + +public class BoldTag : ITag { - public class BoldTag : ITag + public BoldTag(int position, bool isCloseTag = false) { - public TagType TagType => TagType.Bold; + Position = position; + IsCloseTag = isCloseTag; + } - public int Position { get; set; } + public TagType TagType => TagType.Bold; - public bool IsCloseTag { get; set; } + public int Position { get; set; } - public string OpenTag => ""; + public bool IsCloseTag { get; set; } - public string CloseTag => ""; + public string OpenTag => ""; - public BoldTag(int position, bool isCloseTag = false) - { - Position = position; - IsCloseTag = isCloseTag; - } - } -} + public string CloseTag => ""; +} \ No newline at end of file diff --git a/cs/Markdown/Tags/ConcreteTags/BulletTag.cs b/cs/Markdown/Tags/ConcreteTags/BulletTag.cs index 3b19ab8fd..3a06f1c94 100644 --- a/cs/Markdown/Tags/ConcreteTags/BulletTag.cs +++ b/cs/Markdown/Tags/ConcreteTags/BulletTag.cs @@ -2,6 +2,12 @@ public class BulletTag : ITag { + public BulletTag(int position, bool isCloseTag = false) + { + Position = position; + IsCloseTag = isCloseTag; + } + public TagType TagType { get; } public int Position { get; set; } public bool IsCloseTag { get; set; } @@ -9,10 +15,4 @@ public class BulletTag : ITag public string OpenTag => "
  • "; public string CloseTag => "
  • "; - - public BulletTag(int position, bool isCloseTag = false) - { - Position = position; - IsCloseTag = isCloseTag; - } } \ No newline at end of file diff --git a/cs/Markdown/Tags/ConcreteTags/HeaderTag.cs b/cs/Markdown/Tags/ConcreteTags/HeaderTag.cs index fcb00f43f..d434883be 100644 --- a/cs/Markdown/Tags/ConcreteTags/HeaderTag.cs +++ b/cs/Markdown/Tags/ConcreteTags/HeaderTag.cs @@ -1,21 +1,20 @@ -namespace Markdown.Tags.ConcreteTags +namespace Markdown.Tags.ConcreteTags; + +public class HeaderTag : ITag { - public class HeaderTag : ITag + public HeaderTag(int position, bool isCloseTag = false) { - public TagType TagType => TagType.Header; + Position = position; + IsCloseTag = isCloseTag; + } - public int Position { get; set; } + public TagType TagType => TagType.Header; - public bool IsCloseTag { get; set; } + public int Position { get; set; } - public string OpenTag => "

    "; + public bool IsCloseTag { get; set; } - public string CloseTag => "

    "; + public string OpenTag => "

    "; - public HeaderTag(int position, bool isCloseTag = false) - { - Position = position; - IsCloseTag = isCloseTag; - } - } -} + public string CloseTag => "

    "; +} \ No newline at end of file diff --git a/cs/Markdown/Tags/ConcreteTags/ItalicTag.cs b/cs/Markdown/Tags/ConcreteTags/ItalicTag.cs index 64562673e..a48b8520a 100644 --- a/cs/Markdown/Tags/ConcreteTags/ItalicTag.cs +++ b/cs/Markdown/Tags/ConcreteTags/ItalicTag.cs @@ -1,21 +1,20 @@ -namespace Markdown.Tags.ConcreteTags +namespace Markdown.Tags.ConcreteTags; + +public class ItalicTag : ITag { - public class ItalicTag : ITag + public ItalicTag(int position, bool isCloseTag = false) { - public TagType TagType => TagType.Italic; + Position = position; + IsCloseTag = isCloseTag; + } - public int Position { get; set; } + public TagType TagType => TagType.Italic; - public bool IsCloseTag { get; set; } + public int Position { get; set; } - public string OpenTag => ""; + public bool IsCloseTag { get; set; } - public string CloseTag => ""; + public string OpenTag => ""; - public ItalicTag(int position, bool isCloseTag = false) - { - Position = position; - IsCloseTag = isCloseTag; - } - } -} + public string CloseTag => ""; +} \ No newline at end of file diff --git a/cs/Markdown/Tags/ITag.cs b/cs/Markdown/Tags/ITag.cs index a9ebe1901..8957484c0 100644 --- a/cs/Markdown/Tags/ITag.cs +++ b/cs/Markdown/Tags/ITag.cs @@ -1,15 +1,14 @@ -namespace Markdown.Tags +namespace Markdown.Tags; + +public interface ITag { - public interface ITag - { - public TagType TagType { get; } + public TagType TagType { get; } - public int Position { get; protected set; } + public int Position { get; protected set; } - public bool IsCloseTag { get; protected set; } + public bool IsCloseTag { get; protected set; } - public string OpenTag { get; } + public string OpenTag { get; } - public string CloseTag { get; } - } -} + public string CloseTag { get; } +} \ No newline at end of file diff --git a/cs/Markdown/Tags/Position.cs b/cs/Markdown/Tags/Position.cs deleted file mode 100644 index b1332865b..000000000 --- a/cs/Markdown/Tags/Position.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Markdown.Tags -{ - public struct Position(int LineNumber, int InLinePosititon) - { - public int LineNumber; - - public int InLinePosititon; - } -} diff --git a/cs/Markdown/Tags/TagType.cs b/cs/Markdown/Tags/TagType.cs index 6450802a7..89d31d925 100644 --- a/cs/Markdown/Tags/TagType.cs +++ b/cs/Markdown/Tags/TagType.cs @@ -1,12 +1,10 @@ -namespace Markdown.Tags +namespace Markdown.Tags; + +public enum TagType { - public enum TagType - { - Header, - Italic, - Bold, - BulletedListItem, - Escape, - UnDefined - } -} + Header, + Italic, + Bold, + BulletedListItem, + UnDefined +} \ No newline at end of file diff --git a/cs/Markdown/TokenGeneratorClasses/Interfaces/ITokenGenerateRule.cs b/cs/Markdown/TokenGeneratorClasses/Interfaces/ITokenGenerateRule.cs index a42e71de2..1478b4e48 100644 --- a/cs/Markdown/TokenGeneratorClasses/Interfaces/ITokenGenerateRule.cs +++ b/cs/Markdown/TokenGeneratorClasses/Interfaces/ITokenGenerateRule.cs @@ -1,9 +1,8 @@ using Markdown.Tokens; -namespace Markdown.TokenGeneratorClasses.Interfaces +namespace Markdown.TokenGeneratorClasses.Interfaces; + +public interface ITokenGenerateRule { - public interface ITokenGenerateRule - { - public Token? GetToken(string line, int currentIndex); - } -} + public Token? GetToken(string line, int currentIndex); +} \ No newline at end of file diff --git a/cs/Markdown/TokenGeneratorClasses/Interfaces/ITokenGenerator.cs b/cs/Markdown/TokenGeneratorClasses/Interfaces/ITokenGenerator.cs index 60574d262..9ee6cba86 100644 --- a/cs/Markdown/TokenGeneratorClasses/Interfaces/ITokenGenerator.cs +++ b/cs/Markdown/TokenGeneratorClasses/Interfaces/ITokenGenerator.cs @@ -1,14 +1,8 @@ using Markdown.Tokens; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -namespace Markdown.TokenGeneratorClasses.Interfaces +namespace Markdown.TokenGeneratorClasses.Interfaces; + +public interface ITokenGenerator { - public interface ITokenGenerator - { - public static abstract Token? GetToken(string line, int currentIndex); - } -} + public static abstract Token? GetToken(string line, int currentIndex); +} \ No newline at end of file diff --git a/cs/Markdown/TokenGeneratorClasses/TokenGenerator.cs b/cs/Markdown/TokenGeneratorClasses/TokenGenerator.cs index 87a5d76ef..e6d2c9c02 100644 --- a/cs/Markdown/TokenGeneratorClasses/TokenGenerator.cs +++ b/cs/Markdown/TokenGeneratorClasses/TokenGenerator.cs @@ -1,70 +1,69 @@ -using Markdown.Tokens; -using System.Reflection; +using System.Reflection; using Markdown.TokenGeneratorClasses.Interfaces; +using Markdown.Tokens; -namespace Markdown.TokenGeneratorClasses +namespace Markdown.TokenGeneratorClasses; + +public class TokenGenerator : ITokenGenerator { - public class TokenGenerator : ITokenGenerator - { - private static IEnumerable generateRuleClasses = GetRuleClasses(); + private static readonly IEnumerable generateRuleClasses = GetRuleClasses(); - public static Token? GetToken(string line, int currentIndex) + public static Token? GetToken(string line, int currentIndex) + { + foreach (var rule in generateRuleClasses) { - foreach (var rule in generateRuleClasses) - { - var token = rule.GetToken(line, currentIndex); - if (token != null) - return token; - } - - return null; + var token = rule.GetToken(line, currentIndex); + if (token != null) + return token; } - private static IEnumerable GetRuleClasses() - { - Type interfaceType = typeof(ITokenGenerateRule); + return null; + } + + private static IEnumerable GetRuleClasses() + { + var interfaceType = typeof(ITokenGenerateRule); - var rulesTypes = Assembly.GetExecutingAssembly() - .GetTypes() - .Where(t => interfaceType.IsAssignableFrom(t) && t.IsClass) - .ToHashSet(); + var rulesTypes = Assembly.GetExecutingAssembly() + .GetTypes() + .Where(t => interfaceType.IsAssignableFrom(t) && t.IsClass) + .ToHashSet(); - var simpleRules = GetRulesNotUsesOthersRules(rulesTypes).ToList(); - var complexRules = GetRulesUsesOthersRulesLogic(rulesTypes, simpleRules).ToList(); + var simpleRules = GetRulesNotUsesOthersRules(rulesTypes).ToList(); + var complexRules = GetRulesUsesOthersRulesLogic(rulesTypes, simpleRules).ToList(); - return simpleRules.Concat(complexRules); - } + return simpleRules.Concat(complexRules); + } - private static IEnumerable GetRulesNotUsesOthersRules(HashSet rulesTypes) + private static IEnumerable GetRulesNotUsesOthersRules(HashSet rulesTypes) + { + foreach (var type in rulesTypes) { - foreach (var type in rulesTypes) - { - var constructors = type.GetConstructors(); + var constructors = type.GetConstructors(); - if (constructors.Length == 1 && constructors[0].GetParameters().Length == 0) - { - rulesTypes.Remove(type); - yield return (ITokenGenerateRule)Activator.CreateInstance(type); - } + if (constructors.Length == 1 && constructors[0].GetParameters().Length == 0) + { + rulesTypes.Remove(type); + yield return (ITokenGenerateRule)Activator.CreateInstance(type); } } + } - private static IEnumerable GetRulesUsesOthersRulesLogic(HashSet rulesTypes, - IEnumerable rulesNotUsesOthersRules) - { - var getTokenFuncs = rulesNotUsesOthersRules - .Select(rule => new Func(rule.GetToken)); + private static IEnumerable GetRulesUsesOthersRulesLogic(HashSet rulesTypes, + IEnumerable rulesNotUsesOthersRules) + { + var getTokenFuncs = rulesNotUsesOthersRules + .Select(rule => new Func(rule.GetToken)); - foreach (var type in rulesTypes) - { - var constructor = type.GetConstructor(new[] { typeof(IEnumerable>) }); + foreach (var type in rulesTypes) + { + var constructor = type.GetConstructor(new[] { typeof(IEnumerable>) }); - if (constructor == null) - throw new ArgumentNullException("TokenGeneratorRules should have only one constructor " + - "without arguments or with IEnumerable argument"); - rulesTypes.Remove(type); - yield return (ITokenGenerateRule)constructor.Invoke(new object[] { getTokenFuncs }); - } + if (constructor == null) + throw new ArgumentNullException("TokenGeneratorRules should have only one constructor " + + "without arguments or with IEnumerable argument"); + rulesTypes.Remove(type); + yield return (ITokenGenerateRule)constructor.Invoke(new object[] { getTokenFuncs }); } } -} +} \ No newline at end of file diff --git a/cs/Markdown/TokenGeneratorClasses/TokenGeneratorRules/GenerateBoldTokenRule.cs b/cs/Markdown/TokenGeneratorClasses/TokenGeneratorRules/GenerateBoldTokenRule.cs index a6bd25c36..9524002b1 100644 --- a/cs/Markdown/TokenGeneratorClasses/TokenGeneratorRules/GenerateBoldTokenRule.cs +++ b/cs/Markdown/TokenGeneratorClasses/TokenGeneratorRules/GenerateBoldTokenRule.cs @@ -3,16 +3,15 @@ using Markdown.TokenGeneratorClasses.Interfaces; using Markdown.Tokens; -namespace Markdown.TokenGeneratorClasses.TokenGeneratorRules +namespace Markdown.TokenGeneratorClasses.TokenGeneratorRules; + +public class GenerateBoldTokenRule : ITokenGenerateRule { - public class GenerateBoldTokenRule : ITokenGenerateRule + public Token? GetToken(string line, int currentIndex) { - public Token? GetToken(string line, int currentIndex) - { - if (line[currentIndex] == '_' && line.NextCharIs('_', currentIndex)) - return new Token(TokenType.MdTag, "__", currentIndex, false, TagType.Bold); + if (line[currentIndex] == '_' && line.NextCharIs('_', currentIndex)) + return new Token(TokenType.MdTag, "__", currentIndex, false, TagType.Bold); - return null; - } + return null; } -} +} \ No newline at end of file diff --git a/cs/Markdown/TokenGeneratorClasses/TokenGeneratorRules/GenerateEscapeTokenRule.cs b/cs/Markdown/TokenGeneratorClasses/TokenGeneratorRules/GenerateEscapeTokenRule.cs index f0216ceda..393f875c6 100644 --- a/cs/Markdown/TokenGeneratorClasses/TokenGeneratorRules/GenerateEscapeTokenRule.cs +++ b/cs/Markdown/TokenGeneratorClasses/TokenGeneratorRules/GenerateEscapeTokenRule.cs @@ -1,17 +1,15 @@ -using Markdown.Tags; -using Markdown.TokenGeneratorClasses.Interfaces; +using Markdown.TokenGeneratorClasses.Interfaces; using Markdown.Tokens; -namespace Markdown.TokenGeneratorClasses.TokenGeneratorRules +namespace Markdown.TokenGeneratorClasses.TokenGeneratorRules; + +public class GenerateEscapeTokenRule : ITokenGenerateRule { - public class GenerateEscapeTokenRule : ITokenGenerateRule + public Token? GetToken(string line, int currentIndex) { - public Token? GetToken(string line, int currentIndex) - { - if (line[currentIndex] == '\\') - return new Token(TokenType.Escape, @"\", currentIndex, false, TagType.Escape); + if (line[currentIndex] == '\\') + return new Token(TokenType.Escape, @"\", currentIndex); - return null; - } + return null; } -} +} \ No newline at end of file diff --git a/cs/Markdown/TokenGeneratorClasses/TokenGeneratorRules/GenerateHashTokenRule.cs b/cs/Markdown/TokenGeneratorClasses/TokenGeneratorRules/GenerateHashTokenRule.cs index 3fb1eb4a1..46b0b16d0 100644 --- a/cs/Markdown/TokenGeneratorClasses/TokenGeneratorRules/GenerateHashTokenRule.cs +++ b/cs/Markdown/TokenGeneratorClasses/TokenGeneratorRules/GenerateHashTokenRule.cs @@ -2,16 +2,15 @@ using Markdown.TokenGeneratorClasses.Interfaces; using Markdown.Tokens; -namespace Markdown.TokenGeneratorClasses.TokenGeneratorRules +namespace Markdown.TokenGeneratorClasses.TokenGeneratorRules; + +public class GenerateHashTokenRule : ITokenGenerateRule { - public class GenerateHashTokenRule : ITokenGenerateRule + public Token? GetToken(string line, int currentIndex) { - public Token? GetToken(string line, int currentIndex) - { - if (currentIndex + 1 < line.Length && line[currentIndex] == '#' && line[currentIndex + 1] == ' ') - return new Token(TokenType.MdTag, "# ", currentIndex, false, TagType.Header); + if (currentIndex + 1 < line.Length && line[currentIndex] == '#' && line[currentIndex + 1] == ' ') + return new Token(TokenType.MdTag, "# ", currentIndex, false, TagType.Header); - return null; - } + return null; } -} +} \ No newline at end of file diff --git a/cs/Markdown/TokenGeneratorClasses/TokenGeneratorRules/GenerateItalicTokenRule.cs b/cs/Markdown/TokenGeneratorClasses/TokenGeneratorRules/GenerateItalicTokenRule.cs index 55996ae00..d9eb66496 100644 --- a/cs/Markdown/TokenGeneratorClasses/TokenGeneratorRules/GenerateItalicTokenRule.cs +++ b/cs/Markdown/TokenGeneratorClasses/TokenGeneratorRules/GenerateItalicTokenRule.cs @@ -3,16 +3,15 @@ using Markdown.TokenGeneratorClasses.Interfaces; using Markdown.Tokens; -namespace Markdown.TokenGeneratorClasses.TokenGeneratorRules +namespace Markdown.TokenGeneratorClasses.TokenGeneratorRules; + +public class GenerateItalicTokenRule : ITokenGenerateRule { - public class GenerateItalicTokenRule : ITokenGenerateRule + public Token? GetToken(string line, int currentIndex) { - public Token? GetToken(string line, int currentIndex) - { - if (line[currentIndex] == '_' && !line.NextCharIs('_', currentIndex)) - return new Token(TokenType.MdTag, "_", currentIndex, false, TagType.Italic); + if (line[currentIndex] == '_' && !line.NextCharIs('_', currentIndex)) + return new Token(TokenType.MdTag, "_", currentIndex, false, TagType.Italic); - return null; - } + return null; } -} +} \ No newline at end of file diff --git a/cs/Markdown/TokenGeneratorClasses/TokenGeneratorRules/GenerateTextTokenRule.cs b/cs/Markdown/TokenGeneratorClasses/TokenGeneratorRules/GenerateTextTokenRule.cs index 0f55d3a57..99bcab547 100644 --- a/cs/Markdown/TokenGeneratorClasses/TokenGeneratorRules/GenerateTextTokenRule.cs +++ b/cs/Markdown/TokenGeneratorClasses/TokenGeneratorRules/GenerateTextTokenRule.cs @@ -1,51 +1,50 @@ -using Markdown.Tokens; -using System.Text; +using System.Text; using Markdown.TokenGeneratorClasses.Interfaces; +using Markdown.Tokens; -namespace Markdown.TokenGeneratorClasses.TokenGeneratorRules +namespace Markdown.TokenGeneratorClasses.TokenGeneratorRules; + +public class GenerateTextTokenRule : ITokenGenerateRule { - public class GenerateTextTokenRule : ITokenGenerateRule - { - private readonly IEnumerable> otherTokensRules; + private readonly IEnumerable> otherTokensRules; - public GenerateTextTokenRule(IEnumerable> otherTokensRules) - { - this.otherTokensRules = otherTokensRules; - } + public GenerateTextTokenRule(IEnumerable> otherTokensRules) + { + this.otherTokensRules = otherTokensRules; + } - private bool IsTextToken(string line, int currentIndex) - { - foreach (var rule in otherTokensRules) - { - var token = rule.Invoke(line, currentIndex); - if (token != null) - return false; - } - - return true; - } + public Token? GetToken(string line, int currentIndex) + { + var stringBuilder = new StringBuilder(); + var tokenType = char.IsNumber(line[currentIndex]) ? TokenType.Number : TokenType.Text; - public Token? GetToken(string line, int currentIndex) + for (var i = currentIndex; i < line.Length; i++) { - var stringBuilder = new StringBuilder(); - var tokenType = char.IsNumber(line[currentIndex]) ? TokenType.Number : TokenType.Text; + if (tokenType == TokenType.Text && + (char.IsNumber(line[currentIndex]) || !IsTextToken(line, currentIndex))) + break; - for (var i = currentIndex; i < line.Length; i++) - { - if (tokenType == TokenType.Text && - (char.IsNumber(line[currentIndex]) || !IsTextToken(line, currentIndex))) - break; + if (tokenType == TokenType.Number && !char.IsNumber(line[currentIndex])) + break; - if (tokenType == TokenType.Number && !char.IsNumber(line[currentIndex])) - break; + stringBuilder.Append(line[currentIndex]); + currentIndex++; + } - stringBuilder.Append(line[currentIndex]); - currentIndex++; - } + var text = stringBuilder.ToString(); - var text = stringBuilder.ToString(); + return new Token(tokenType, text, currentIndex - text.Length); + } - return new Token(tokenType, text, currentIndex - text.Length); + private bool IsTextToken(string line, int currentIndex) + { + foreach (var rule in otherTokensRules) + { + var token = rule.Invoke(line, currentIndex); + if (token != null) + return false; } + + return true; } -} +} \ No newline at end of file diff --git a/cs/Markdown/TokenGeneratorClasses/TokenGeneratorRules/GenerateWhiteSpaceRule.cs b/cs/Markdown/TokenGeneratorClasses/TokenGeneratorRules/GenerateWhiteSpaceRule.cs index 64146beda..84892ed08 100644 --- a/cs/Markdown/TokenGeneratorClasses/TokenGeneratorRules/GenerateWhiteSpaceRule.cs +++ b/cs/Markdown/TokenGeneratorClasses/TokenGeneratorRules/GenerateWhiteSpaceRule.cs @@ -1,17 +1,15 @@ -using Markdown.Tags; -using Markdown.TokenGeneratorClasses.Interfaces; +using Markdown.TokenGeneratorClasses.Interfaces; using Markdown.Tokens; -namespace Markdown.TokenGeneratorClasses.TokenGeneratorRules +namespace Markdown.TokenGeneratorClasses.TokenGeneratorRules; + +public class GenerateWhiteSpaceRule : ITokenGenerateRule { - public class GenerateWhiteSpaceRule : ITokenGenerateRule + public Token? GetToken(string line, int currentIndex) { - public Token? GetToken(string line, int currentIndex) - { - if (line[currentIndex] == ' ') - return new Token(TokenType.WhiteSpace, " ", currentIndex); + if (line[currentIndex] == ' ') + return new Token(TokenType.WhiteSpace, " ", currentIndex); - return null; - } + return null; } -} +} \ No newline at end of file diff --git a/cs/Markdown/TokenParser/ConcreteParser/LineParser.cs b/cs/Markdown/TokenParser/ConcreteParser/LineParser.cs index 027d9354d..171fe0c0a 100644 --- a/cs/Markdown/TokenParser/ConcreteParser/LineParser.cs +++ b/cs/Markdown/TokenParser/ConcreteParser/LineParser.cs @@ -1,192 +1,251 @@ -using Markdown.Tags; +using System.Text; +using Markdown.Tags; using Markdown.Tags.ConcreteTags; -using Markdown.Tokens; -using System.Text; -using Markdown.Extensions; using Markdown.TokenGeneratorClasses; using Markdown.TokenParser.Interfaces; -using Markdown.TokenParser.TagsGenerators; using Markdown.TokenParser.TokenHandlers; +using Markdown.Tokens; + +namespace Markdown.TokenParser.ConcreteParser; -namespace Markdown.TokenParser.ConcreteParser +public class LineParser : ITokenLineParser { - public class LineParser : ITokenLineParser + private static readonly Dictionary TagPriority = new() { - public ParsedLine ParseLine(string line) - { - if(line is null) - throw new ArgumentNullException("String argument text must be not null"); - - var lineTokens = GetTokensLine(line); - var escapedTokens = ResetPositions(EscapeTags(lineTokens)); - var headerTags = new HeaderTokensHandler().HandleLine(escapedTokens); - var bulletedLITags = new BulletedLIHandler().HandleLine(escapedTokens); - var italicTags = new ItalicTokensHandler().HandleLine(escapedTokens); - var boldTags = new BoldTokensHandler().HandleLine(escapedTokens); + { TagType.Header, 1000 }, + { TagType.BulletedListItem, 1000 }, + { TagType.Bold, 500 }, + { TagType.Italic, 300 }, + { TagType.UnDefined, 0 } + }; + + public ParsedLine ParseLine(string line) + { + if (line is null) + throw new ArgumentNullException("String argument text must be not null"); - var merged = MergeTokens(escapedTokens, headerTags, boldTags, italicTags, bulletedLITags); - ProcessTokensIntersecting(merged); + var lineTokens = GetTokensLine(line); + var escapedTokens = ResetPositions(EscapeTags(lineTokens)); + var headerTags = new HeaderTokensHandler().HandleLine(escapedTokens); + var bulletedLITags = new BulletedLIHandler().HandleLine(escapedTokens); + var italicTags = new ItalicTokensHandler().HandleLine(escapedTokens); + var boldTags = new BoldTokensHandler().HandleLine(escapedTokens); - return GetTagsAndCleanText(merged); - } + var merged = MergeTokens(escapedTokens, headerTags, boldTags, italicTags, bulletedLITags); + ProcessTokensIntersecting(merged); + ProcessInnerTags(merged); - private List GetTokensLine(string line) - { - int position = 0; - var result = new List(); + return GetTagsAndCleanText(merged); + } - while (position < line.Length) - { - var token = TokenGenerator.GetToken(line, position); - result.Add(token); - position += token.Content.Length; - } + private List GetTokensLine(string line) + { + var position = 0; + var result = new List(); - return result; + while (position < line.Length) + { + var token = TokenGenerator.GetToken(line, position); + result.Add(token); + position += token.Content.Length; } - private List EscapeTags(List tokens) - { - Token? previousToken = null; - var result = new List(); + return result; + } + + private List EscapeTags(List tokens) + { + Token? previousToken = null; + var result = new List(); - foreach (var token in tokens) + foreach (var token in tokens) + if (previousToken is { TokenType: TokenType.Escape }) { - if (previousToken is { TokenType: TokenType.Escape }) + if (token.TokenType is TokenType.MdTag or TokenType.Escape) { - if (token.TokenType is TokenType.MdTag or TokenType.Escape) - { - token.TokenType = TokenType.Text; - token.TagType = TagType.UnDefined; - previousToken = token; - result.Add(token); - } - else - { - previousToken.TokenType = TokenType.Text; - result.Add(previousToken); - result.Add(token); - previousToken = token; - } - } - else if (token.TokenType == TokenType.Escape) + token.TokenType = TokenType.Text; + token.TagType = TagType.UnDefined; previousToken = token; + result.Add(token); + } else { + previousToken.TokenType = TokenType.Text; + result.Add(previousToken); result.Add(token); previousToken = token; } } + else if (token.TokenType == TokenType.Escape) + { + previousToken = token; + } + else + { + result.Add(token); + previousToken = token; + } - if (previousToken is not { TokenType: TokenType.Escape }) - return result; - - previousToken.TokenType = TokenType.Text; - result.Add(previousToken); + if (previousToken is not { TokenType: TokenType.Escape }) return result; - } - private List ResetPositions(List tokens) - { - var position = 0; + previousToken.TokenType = TokenType.Text; + result.Add(previousToken); + return result; + } - foreach (var token in tokens) - { - token.Position = position; - position += token.Content.Length; - } + private List ResetPositions(List tokens) + { + var position = 0; - return tokens; + foreach (var token in tokens) + { + token.Position = position; + position += token.Content.Length; } - private List MergeTokens(List allTokens, params List[] tokenLists) - { - var positionMap = new Dictionary(); + return tokens; + } - foreach (var token in allTokens) - { - positionMap[token.Position] = token; - } + private List MergeTokens(List allTokens, params List[] tokenLists) + { + var positionMap = new Dictionary(); - foreach (var tokenList in tokenLists) - { - foreach (var token in tokenList) - { - positionMap[token.Position] = token; - } - } + foreach (var token in allTokens) positionMap[token.Position] = token; - // Преобразуем словарь обратно в список - var combinedTokens = positionMap.Values.ToList(); + foreach (var tokenList in tokenLists) + foreach (var token in tokenList) + positionMap[token.Position] = token; - var combindedTokens = combinedTokens.OrderBy(token => token.Position) - .ToList(); + // Преобразуем словарь обратно в список + var combinedTokens = positionMap.Values.ToList(); - return combinedTokens; - } + var combindedTokens = combinedTokens.OrderBy(token => token.Position) + .ToList(); - private void ProcessTokensIntersecting(List tokens) - { - var stack = new Stack(); - var process = new List(); + return combinedTokens; + } + + private void ProcessTokensIntersecting(List tokens) + { + var stack = new Stack(); + var process = new List(); - foreach (var token in tokens) + foreach (var token in tokens) + if (token.TagType == TagType.Italic || token.TagType == TagType.Bold) { - if (token.TagType == TagType.Italic || token.TagType == TagType.Bold) + process.Add(token); + if (token.IsCloseTag) { - process.Add(token); - if (token.IsCloseTag) + stack.TryPeek(out var openToken); + if (openToken != null && !openToken.IsCloseTag + && openToken.TagType == token.TagType) { - stack.TryPeek(out var openToken); - if (openToken != null && !openToken.IsCloseTag - && openToken.TagType == token.TagType) - { - process.Remove(stack.Pop()); - process.Remove(token); - } - else - stack.Push(token); + process.Remove(stack.Pop()); + process.Remove(token); } else + { stack.Push(token); + } + } + else + { + stack.Push(token); } } - foreach (var token in stack) - { - token.TagType = TagType.UnDefined; - token.TokenType = TokenType.Text; - } - } - private ParsedLine GetTagsAndCleanText(List tokens) + foreach (var token in stack) { - var result = new List(); + token.TagType = TagType.UnDefined; + token.TokenType = TokenType.Text; + } + } - var line = new StringBuilder(); - foreach (var token in tokens) + private void ProcessInnerTags(List tokens) + { + Token handleToken = null; + var setAllToText = false; + var startIndex = -1; + var endIndex = -1; + + for (var i = 0; i < tokens.Count; i++) + if (handleToken == null) { - if (token.TokenType is not TokenType.MdTag) - { - line.Append(token.Content); - continue; - } + if (setAllToText) + ConvertToTextTags(startIndex, endIndex, tokens); - result.Add(GetNewTag(token, line.Length)); + handleToken = tokens[i]; + startIndex = i; + } + else if (TagPriority[tokens[i].TagType] > TagPriority[handleToken.TagType]) + { + setAllToText = true; } + else if (tokens[i].TagType == handleToken.TagType && tokens[i].IsCloseTag) + { + endIndex = i; + } + } + + private void ConvertToTextTags(int startIndex, int endIndex, List tokens) + { + if (startIndex == -1 || endIndex == -1) + return; + + for (var i = startIndex; i <= endIndex; i++) + if (tokens[i].TokenType == TokenType.MdTag) + tokens[i].TokenType = TokenType.Text; + } + + private List ConvertToTextTags(List tokens) + { + var result = new List(); - return new ParsedLine(line.ToString(), result); + foreach (var token in tokens) + { + if (token.TokenType == TokenType.MdTag) token.TokenType = TokenType.Text; + + result.Add(token); } - private ITag GetNewTag(Token token, int position) + return result; + } + + + private ParsedLine GetTagsAndCleanText(List tokens) + { + var result = new List(); + + var lineBuilder = new StringBuilder(); + foreach (var token in tokens) { - return token.TagType switch + if (token.TokenType is not TokenType.MdTag) { - TagType.Header => new HeaderTag(position, token.IsCloseTag), - TagType.Italic => new ItalicTag(position, token.IsCloseTag), - TagType.Bold => new BoldTag(position, token.IsCloseTag), - TagType.BulletedListItem => new BulletTag(position, token.IsCloseTag), - _ => throw new NotImplementedException() - }; + lineBuilder.Append(token.Content); + continue; + } + + result.Add(GetNewTag(token, lineBuilder.Length)); } + + //Мы можем вернуть либо пустой ParsedLine если Count == 0 или "стандартно" + //заполненный, в ином случае если Length == 0 то значит внутри тегов пусто + //И их надо превратить в текст + return lineBuilder.Length > 0 || tokens.Count == 0 + ? new ParsedLine(lineBuilder.ToString(), result) + : GetTagsAndCleanText(ConvertToTextTags(tokens)); + } + + private ITag GetNewTag(Token token, int position) + { + return token.TagType switch + { + TagType.Header => new HeaderTag(position, token.IsCloseTag), + TagType.Italic => new ItalicTag(position, token.IsCloseTag), + TagType.Bold => new BoldTag(position, token.IsCloseTag), + TagType.BulletedListItem => new BulletTag(position, token.IsCloseTag), + _ => throw new NotImplementedException() + }; } -} +} \ No newline at end of file diff --git a/cs/Markdown/TokenParser/Interfaces/ITokenHandler.cs b/cs/Markdown/TokenParser/Interfaces/ITokenHandler.cs index 55b7a8bb2..587dcd153 100644 --- a/cs/Markdown/TokenParser/Interfaces/ITokenHandler.cs +++ b/cs/Markdown/TokenParser/Interfaces/ITokenHandler.cs @@ -1,15 +1,8 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Markdown.Tags; -using Markdown.Tokens; +using Markdown.Tokens; -namespace Markdown.TokenParser.Interfaces +namespace Markdown.TokenParser.Interfaces; + +public interface ITokenHandler { - public interface ITokenHandler - { - List HandleLine(List line); - } -} + List HandleLine(List line); +} \ No newline at end of file diff --git a/cs/Markdown/TokenParser/Interfaces/ITokenLineParser.cs b/cs/Markdown/TokenParser/Interfaces/ITokenLineParser.cs index 86936687e..d63837cb9 100644 --- a/cs/Markdown/TokenParser/Interfaces/ITokenLineParser.cs +++ b/cs/Markdown/TokenParser/Interfaces/ITokenLineParser.cs @@ -1,9 +1,6 @@ -using Markdown.Tags; +namespace Markdown.TokenParser.Interfaces; -namespace Markdown.TokenParser.Interfaces +public interface ITokenLineParser { - public interface ITokenLineParser - { - public ParsedLine ParseLine(string text); - } -} + public ParsedLine ParseLine(string text); +} \ No newline at end of file diff --git a/cs/Markdown/TokenParser/TokenHandlers/BoldTokensHandler.cs b/cs/Markdown/TokenParser/TokenHandlers/BoldTokensHandler.cs index 73272857f..fc70eb7de 100644 --- a/cs/Markdown/TokenParser/TokenHandlers/BoldTokensHandler.cs +++ b/cs/Markdown/TokenParser/TokenHandlers/BoldTokensHandler.cs @@ -12,16 +12,26 @@ public List HandleLine(List line) var result = new List(); var opened = new List(1); var position = 0; + Func, int, bool> isClosed = (list, i) => false; for (var j = 0; j < line.Count; j++) { if (opened.Count == 0 && IsOpen(j, line)) { - var token = line[j]; - opened.Add(new Token(TokenType.MdTag, "__", - position, false, TagType.Bold)); + if (IsOpenInWord(j, line)) + { + var token = line[j]; + isClosed = (list, index) => IsCloseInWord(index, list, token); + opened.Add(line[j]); + } + else if (IsOpenBetweenWords(j, line)) + { + var token = line[j]; + isClosed = (list, i) => IsClosed(i, list, token); + opened.Add(line[j]); + } } - else if (opened.Count > 0 && IsClosed(j, line)) + else if (opened.Count > 0 && isClosed(line, j)) { result.Add(opened[0]); result.Add(new Token(TokenType.MdTag, "__", @@ -52,20 +62,43 @@ private bool IsOpen(int index, List tokens) { var isFirstInLine = IsFirstInLine(index, tokens); var isOpenOrdinary = IsOpenOrdinary(index, tokens); - var isOpenClosed = IsOpenClosed(index, tokens); + var isOpenClosed = IsInWord(index, tokens); - return isFirstInLine ^ isOpenOrdinary ^ isOpenClosed; + return isFirstInLine || isOpenOrdinary || isOpenClosed; } - private bool IsClosed(int index, List tokens) + private bool IsClosed(int index, List tokens, Token token) { var isLastInLine = IsLastInLine(index, tokens); var isClosedOrdinary = IsClosedOrdinary(index, tokens); - var isOpenClosed = IsOpenClosed(index, tokens); + var isOpenClosed = IsCloseInWord(index, tokens, token); - return isLastInLine ^ isClosedOrdinary ^ isOpenClosed; + return isLastInLine || isClosedOrdinary || isOpenClosed; } + private bool IsOpenBetweenWords(int index, List tokens) + { + return IsOpenOrdinary(index, tokens) ^ IsFirstInLine(index, tokens); + } + + private bool IsOpenInWord(int index, List tokens) + { + return IsInWord(index, tokens); + } + + private bool IsCloseBetweenWords(int index, List tokens) + { + return IsClosedOrdinary(index, tokens) ^ IsLastInLine(index, tokens); + } + + private bool IsCloseInWord(int index, List tokens, Token openToken) + { + return index - 2 > -1 && (IsInWord(index, tokens) || IsCloseBetweenWords(index, tokens)) + && tokens[index - 2] == openToken; + } + + #region OpenSituations + /// /// Определяет является ли токен одновременно и /// открывающим и закрывающим тегом - случай @@ -74,15 +107,13 @@ private bool IsClosed(int index, List tokens) /// Индекс токена /// Список токенов для проверки /// true если тег внутри слова иначе false - private bool IsOpenClosed(int index, List tokens) + private bool IsInWord(int index, List tokens) { return tokens.LastTokenIs(TokenType.Text, index) && tokens.CurrentTokenIs(TagType.Bold, index) & tokens.NextTokenIs(TokenType.Text, index); } - #region OpenSituations - private bool IsFirstInLine(int index, List tokens) { return index == 0 && tokens.CurrentTokenIs(TagType.Bold, index) && diff --git a/cs/Markdown/TokenParser/TokenHandlers/BulletedLIHandler.cs b/cs/Markdown/TokenParser/TokenHandlers/BulletedLIHandler.cs index 6b89d69bb..be2689045 100644 --- a/cs/Markdown/TokenParser/TokenHandlers/BulletedLIHandler.cs +++ b/cs/Markdown/TokenParser/TokenHandlers/BulletedLIHandler.cs @@ -1,53 +1,48 @@ -using Markdown.Tags; +using Markdown.Extensions; +using Markdown.Tags; using Markdown.Tokens; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Markdown.Extensions; - -namespace Markdown.TokenParser.TokenHandlers + +namespace Markdown.TokenParser.TokenHandlers; + +public class BulletedLIHandler { - public class BulletedLIHandler + public List HandleLine(List line) { - public List HandleLine(List line) + var result = new List(); + var position = 0; + var addCloseTag = false; + + for (var j = 0; j < line.Count; j++) { - var result = new List(); - var position = 0; - var addCloseTag = false; + if (IsBulletedListItem(line, j) && line[j].TagType == TagType.BulletedListItem) + { + result.Add(new Token(TokenType.MdTag, "* ", + position, false, TagType.BulletedListItem)); - for (var j = 0; j < line.Count; j++) + addCloseTag = true; + } + else if (line[j].TagType == TagType.BulletedListItem) { - if (IsBulletedListItem(line, j) && line[j].TagType == TagType.BulletedListItem) - { - result.Add(new Token(TokenType.MdTag, "* ", - position, false, TagType.BulletedListItem)); - - addCloseTag = true; - } - else if (line[j].TagType == TagType.BulletedListItem) - { - result.Add(new Token(TokenType.Text, "* ", position)); - } - - position += line[j].Content.Length; + result.Add(new Token(TokenType.Text, "* ", position)); } - if (addCloseTag) - result.Add(CreateCloseTag(position)); - - return result; + position += line[j].Content.Length; } - private bool IsBulletedListItem(List tokens, int index) - { - return index == 0 && tokens.CurrentTokenIs(TagType.BulletedListItem, index); - } + if (addCloseTag) + result.Add(CreateCloseTag(position)); - private Token CreateCloseTag(int lastIndex) - { - return new Token(TokenType.MdTag, "", lastIndex + 1, true, TagType.BulletedListItem); - } + + return result; + } + + private bool IsBulletedListItem(List tokens, int index) + { + return index == 0 && tokens.CurrentTokenIs(TagType.BulletedListItem, index); + } + + private Token CreateCloseTag(int lastIndex) + { + return new Token(TokenType.MdTag, "", lastIndex + 1, true, TagType.BulletedListItem); } -} +} \ No newline at end of file diff --git a/cs/Markdown/TokenParser/TokenHandlers/HeaderTokensHandler.cs b/cs/Markdown/TokenParser/TokenHandlers/HeaderTokensHandler.cs index ad9cc4481..1a5a596a3 100644 --- a/cs/Markdown/TokenParser/TokenHandlers/HeaderTokensHandler.cs +++ b/cs/Markdown/TokenParser/TokenHandlers/HeaderTokensHandler.cs @@ -29,7 +29,8 @@ public List HandleLine(List line) position += line[j].Content.Length; } - if(addCloseTag) + + if (addCloseTag) result.Add(CreateCloseTag(position)); diff --git a/cs/Markdown/TokenParser/TokenHandlers/ItalicTokensHandler.cs b/cs/Markdown/TokenParser/TokenHandlers/ItalicTokensHandler.cs index 2901de3b4..1d6e6e5a3 100644 --- a/cs/Markdown/TokenParser/TokenHandlers/ItalicTokensHandler.cs +++ b/cs/Markdown/TokenParser/TokenHandlers/ItalicTokensHandler.cs @@ -3,7 +3,7 @@ using Markdown.TokenParser.Interfaces; using Markdown.Tokens; -namespace Markdown.TokenParser.TagsGenerators; +namespace Markdown.TokenParser.TokenHandlers; public class ItalicTokensHandler : ITokenHandler { @@ -12,16 +12,26 @@ public List HandleLine(List line) var result = new List(); var opened = new List(1); var position = 0; + Func, int, bool> isClosed = (list, i) => false; for (var j = 0; j < line.Count; j++) { if (opened.Count == 0 && IsOpen(j, line)) { - var token = line[j]; - opened.Add(new Token(TokenType.MdTag, "_", - position, false, TagType.Italic)); + if (IsOpenInWord(j, line)) + { + var token = line[j]; + isClosed = (list, index) => IsCloseInWord(index, list, token); + opened.Add(line[j]); + } + else if (IsOpenBetweenWords(j, line)) + { + var token = line[j]; + isClosed = (list, i) => IsClosed(i, list, token); + opened.Add(line[j]); + } } - else if (opened.Count > 0 && IsClosed(j, line)) + else if (opened.Count > 0 && isClosed(line, j)) { result.Add(opened[0]); result.Add(new Token(TokenType.MdTag, "_", @@ -52,20 +62,43 @@ private bool IsOpen(int index, List tokens) { var isFirstInLine = IsFirstInLine(index, tokens); var isOpenOrdinary = IsOpenOrdinary(index, tokens); - var isOpenClosed = IsOpenClosed(index, tokens); + var isOpenClosed = IsInWord(index, tokens); - return isFirstInLine ^ isOpenOrdinary ^ isOpenClosed; + return isFirstInLine || isOpenOrdinary || isOpenClosed; } - private bool IsClosed(int index, List tokens) + private bool IsClosed(int index, List tokens, Token token) { var isLastInLine = IsLastInLine(index, tokens); var isClosedOrdinary = IsClosedOrdinary(index, tokens); - var isOpenClosed = IsOpenClosed(index, tokens); + var isOpenClosed = IsCloseInWord(index, tokens, token); - return isLastInLine ^ isClosedOrdinary ^ isOpenClosed; + return isLastInLine || isClosedOrdinary || isOpenClosed; } + private bool IsOpenBetweenWords(int index, List tokens) + { + return IsOpenOrdinary(index, tokens) ^ IsFirstInLine(index, tokens); + } + + private bool IsOpenInWord(int index, List tokens) + { + return IsInWord(index, tokens); + } + + private bool IsCloseBetweenWords(int index, List tokens) + { + return IsClosedOrdinary(index, tokens) ^ IsLastInLine(index, tokens); + } + + private bool IsCloseInWord(int index, List tokens, Token openToken) + { + return index - 2 > -1 && (IsInWord(index, tokens) || IsCloseBetweenWords(index, tokens)) + && tokens[index - 2] == openToken; + } + + #region OpenSituations + /// /// Определяет является ли токен одновременно и /// открывающим и закрывающим тегом - случай @@ -74,19 +107,18 @@ private bool IsClosed(int index, List tokens) /// Индекс токена /// Список токенов для проверки /// true если тег внутри слова иначе false - private bool IsOpenClosed(int index, List tokens) + private bool IsInWord(int index, List tokens) { return tokens.LastTokenIs(TokenType.Text, index) && tokens.CurrentTokenIs(TagType.Italic, index) & tokens.NextTokenIs(TokenType.Text, index); } - #region OpenSituations - private bool IsFirstInLine(int index, List tokens) { return index == 0 && tokens.CurrentTokenIs(TagType.Italic, index) && - tokens.NextTokenIs(TokenType.Text, index); + (tokens.NextTokenIs(TokenType.Text, index) || + tokens.NextTokenIs(TokenType.MdTag, index)); } private bool IsOpenOrdinary(int index, List tokens) @@ -94,7 +126,7 @@ private bool IsOpenOrdinary(int index, List tokens) var hasWhSpaceBefore = tokens.LastTokenIs(TokenType.WhiteSpace, index); var hasMdTagBefore = tokens.LastTokenIs(TokenType.MdTag, index); - return (hasWhSpaceBefore || hasMdTagBefore) && + return (hasWhSpaceBefore || hasMdTagBefore) && tokens.CurrentTokenIs(TagType.Italic, index) && tokens.NextTokenIs(TokenType.Text, index); } diff --git a/cs/Markdown/Tokens/Token.cs b/cs/Markdown/Tokens/Token.cs index a14deb1eb..3c2e07cd4 100644 --- a/cs/Markdown/Tokens/Token.cs +++ b/cs/Markdown/Tokens/Token.cs @@ -1,22 +1,63 @@ using Markdown.Tags; -namespace Markdown.Tokens +namespace Markdown.Tokens; + +public class Token { - public class Token + public Token(TokenType tokenType, string content, int position, bool isCloseTag = false, + TagType tagType = TagType.UnDefined) + { + TokenType = tokenType; + Content = content; + Position = position; + IsCloseTag = isCloseTag; + TagType = tagType; + } + + public TokenType TokenType { get; set; } + public string Content { get; set; } + public TagType TagType { get; set; } + public bool IsCloseTag { get; set; } + public int Position { get; set; } + + public override bool Equals(object obj) + { + if (obj is null) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != GetType()) return false; + return Equals((Token)obj); + } + + public bool Equals(Token other) { - public TokenType TokenType { get; set; } - public string Content { get; set; } - public TagType TagType { get; set; } - public bool IsCloseTag { get; set; } - public int Position { get; set; } + if (other is null) return false; + return TokenType == other.TokenType && + Content == other.Content && + TagType == other.TagType && + IsCloseTag == other.IsCloseTag && + Position == other.Position; + } - public Token(TokenType tokenType, string content, int position, bool isCloseTag = false, TagType tagType = TagType.UnDefined) + public override int GetHashCode() + { + unchecked // Overflow is fine, just wrap { - TokenType = tokenType; - Content = content; - Position = position; - IsCloseTag = isCloseTag; - TagType = tagType; + var hashCode = (int)TokenType; + hashCode = (hashCode * 397) ^ (Content != null ? Content.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (int)TagType; + hashCode = (hashCode * 397) ^ IsCloseTag.GetHashCode(); + hashCode = (hashCode * 397) ^ Position; + return hashCode; } } -} + + public static bool operator ==(Token left, Token right) + { + return EqualityComparer.Default.Equals(left, right); + } + + public static bool operator !=(Token left, Token right) + { + return !(left == right); + } +} \ No newline at end of file diff --git a/cs/Markdown/Tokens/TokenType.cs b/cs/Markdown/Tokens/TokenType.cs index 6714ad8df..fe36d922b 100644 --- a/cs/Markdown/Tokens/TokenType.cs +++ b/cs/Markdown/Tokens/TokenType.cs @@ -1,11 +1,10 @@ -namespace Markdown.Tokens +namespace Markdown.Tokens; + +public enum TokenType { - public enum TokenType - { - MdTag, - Text, - Number, - Escape, - WhiteSpace - } -} + MdTag, + Text, + Number, + Escape, + WhiteSpace +} \ No newline at end of file diff --git a/cs/Markdown_Tests/LineParser_Tests.cs b/cs/Markdown_Tests/LineParser_Tests.cs index dab5d1178..450b3494c 100644 --- a/cs/Markdown_Tests/LineParser_Tests.cs +++ b/cs/Markdown_Tests/LineParser_Tests.cs @@ -16,6 +16,14 @@ public void ParseLine_ThrowArgumentNullException_WhenArgumentIsNull() Assert.Throws(() => parser.ParseLine(null), "String argument text must be not null"); } + [Test] + public void ParseLine_ShouldBeEmpty_WhenArgumentStringIsEmpty() + { + var parsedLine = parser.ParseLine(String.Empty); + parsedLine.Line.Should().BeEmpty(); + parsedLine.Tags.Should().BeEmpty(); + } + [TestCaseSource(typeof(LineParserData), nameof(LineParserData.WordsOnlyLines))] public void ParseLine_ShoudBeCorrect_WhenLineWithWordsOnly(string inLine, string expectedLine, List tags) { diff --git a/cs/Markdown_Tests/Md_Tests.cs b/cs/Markdown_Tests/Md_Tests.cs index a770a3343..dd18aad65 100644 --- a/cs/Markdown_Tests/Md_Tests.cs +++ b/cs/Markdown_Tests/Md_Tests.cs @@ -26,8 +26,9 @@ public void Md_ShouldCreateBulletedListCorrectly_WhenBulletedItemEscaped(string markdown.Render(input).Should().BeEquivalentTo(expected); } - [TestCase(" * \n * ", " * \n * ")] + [TestCase(" * \n * ", " * \n * ")] [TestCase("d* \nd* ", "d* \nd* ")] + [TestCase("* \nd* ", "
    • \n
    d* ")] public void Md_ShouldNotCreateBulletedTag_WhenAreCharsBeforeTag(string input, string expected) { markdown.Render(input).Should().BeEquivalentTo(expected); @@ -38,7 +39,6 @@ public void Md_ShouldNotCreateBulletedTag_WhenAreCharsBeforeTag(string input, st #region HeaderTests [TestCase(@"# bibo", "

    bibo

    ")] - [TestCase(@" # bibo", " # bibo")] [TestCase(@"# # bibo", "

    # bibo

    ")] public void Md_ShouldCreateHeaderCorrectly_WhenHeaderNotEscaped(string input, string expected) { @@ -54,6 +54,7 @@ public void Md_ShouldNotCreateHeaderTag_WhenHeaderEscaped(string input, string e [TestCase(@"\\# bibo", @"\# bibo")] [TestCase(@"a # # bibo", @"a # # bibo")] + [TestCase(@" # bibo", " # bibo")] public void Md_ShouldNotCreateHeaderTag_WhenAreCharsBeforeHash(string input, string expected) { markdown.Render(input).Should().BeEquivalentTo(expected); @@ -106,7 +107,7 @@ public void Md_ShouldNotCreateItalicTag_WhenTagEscaped(string input, string expe public void Md_ShoudNotCreateItalicAndBoldTags_WhenBoldTagsInsideItalic() { var input = "I _want to __sleep__ tonight_"; - var expected = "I want to __sleep__ tonight"; + var expected = "I want to sleep tonight"; markdown.Render(input).Should().BeEquivalentTo(expected); } @@ -190,12 +191,15 @@ public void Md_ShouldProccessIntersectsCorrecttly(string input, string expected) [TestCase(@"____", @"____")] [TestCase(@"__", @"__")] - public void Md_CanCreateEmptyItalicOrBoldTag(string input, string expected) + [TestCase(@"# ", @"# ")] + [TestCase(@"* ", @"* ")] + public void Md_ShouldNotCreateHtmlTags_WhenMdTagsContainsNothing(string input, string expected) { markdown.Render(input).Should().BeEquivalentTo(expected); } - public void Md_ShouldCreateEmptyHtml_WhenTextIsStringEmpty(string input, string expected) + //[Test] + public void Md_ShouldCreateEmptyHtml_WhenTextIsStringEmpty() { markdown.Render(String.Empty).Should().BeEquivalentTo(String.Empty); } diff --git a/cs/Markdown_Tests/TestData/LineParserData.cs b/cs/Markdown_Tests/TestData/LineParserData.cs index 590fa8fd1..084d3c09a 100644 --- a/cs/Markdown_Tests/TestData/LineParserData.cs +++ b/cs/Markdown_Tests/TestData/LineParserData.cs @@ -32,11 +32,37 @@ public static IEnumerable LineWithItalic() new ItalicTag(0, false), new ItalicTag(9, true), }); + yield return new TestCaseData("_word bu_bo bibo", "_word bu_bo bibo", new List() + { + }); + yield return new TestCaseData("wo_rd bubo_ bibo", "wo_rd bubo_ bibo", new List() + { + }); + yield return new TestCaseData("wo_rd bu_bo bibo", "wo_rd bu_bo bibo", new List() + { + }); yield return new TestCaseData("_wo_rd bubo bibo", "word bubo bibo", new List() { new ItalicTag(0, false), new ItalicTag(2, true), }); + yield return new TestCaseData("_word __bubo__ bibo_", "word bubo bibo", new List() + { + new ItalicTag(0, false), + new BoldTag(5, false), + new BoldTag(9, true), + new ItalicTag(14, true), + }); + yield return new TestCaseData("l_ov_e", "love", new List() + { + new ItalicTag(1, false), + new ItalicTag(3, true), + }); + yield return new TestCaseData("l_ove_", "love", new List() + { + new ItalicTag(1, false), + new ItalicTag(4, true), + }); yield return new TestCaseData("_word __bubo_ bibo__", "_word __bubo_ bibo__", new List()); yield return new TestCaseData(@"\_word bubo_ bibo", "_word bubo_ bibo", new List()); yield return new TestCaseData(@"_word bubo\_ bibo", "_word bubo_ bibo", new List()); @@ -72,11 +98,37 @@ public static IEnumerable LineWithBold() new BoldTag(0, false), new BoldTag(9, true), }); + yield return new TestCaseData("__word bu__bo bibo", "__word bu__bo bibo", new List() + { + }); + yield return new TestCaseData("wo__rd bubo__ bibo", "wo__rd bubo__ bibo", new List() + { + }); + yield return new TestCaseData("wo__rd bu__bo bibo", "wo__rd bu__bo bibo", new List() + { + }); yield return new TestCaseData("__wo__rd bubo bibo", "word bubo bibo", new List() { new BoldTag(0, false), new BoldTag(2, true), }); + yield return new TestCaseData("__word _bubo_ bibo__", "word bubo bibo", new List() + { + new BoldTag(0, false), + new ItalicTag(5, false), + new ItalicTag(9, true), + new BoldTag(14, true) + }); + yield return new TestCaseData("l__ov__e", "love", new List() + { + new BoldTag(1, false), + new BoldTag(3, true), + }); + yield return new TestCaseData("l__ove__", "love", new List() + { + new BoldTag(1, false), + new BoldTag(4, true), + }); yield return new TestCaseData("_word __bubo_ bibo__", "_word __bubo_ bibo__", new List()); yield return new TestCaseData(@"\__word bubo__ bibo", "__word bubo__ bibo", new List()); yield return new TestCaseData(@"__word bubo\__ bibo", "__word bubo__ bibo", new List()); diff --git a/cs/Markdown_Tests/TestData/TokenGeneratorTestsData.cs b/cs/Markdown_Tests/TestData/TokenGeneratorTestsData.cs index 30312e2cc..d4e88a806 100644 --- a/cs/Markdown_Tests/TestData/TokenGeneratorTestsData.cs +++ b/cs/Markdown_Tests/TestData/TokenGeneratorTestsData.cs @@ -108,20 +108,20 @@ public static IEnumerable LinesWithEscapes() { yield return new TestCaseData(@"\", new List { - new(TokenType.Escape, @"\", 0, false, TagType.Escape) + new(TokenType.Escape, @"\", 0) }); yield return new TestCaseData(@"\\", new List { - new(TokenType.Escape, @"\", 0, false, TagType.Escape), - new(TokenType.Escape, @"\", 1, false,TagType.Escape) + new(TokenType.Escape, @"\", 0), + new(TokenType.Escape, @"\", 1) }); yield return new TestCaseData(@"\\\", new List { - new(TokenType.Escape, @"\", 0, false, TagType.Escape), - new(TokenType.Escape, @"\", 1, false,TagType.Escape), - new(TokenType.Escape, @"\", 2, false,TagType.Escape), + new(TokenType.Escape, @"\", 0), + new(TokenType.Escape, @"\", 1), + new(TokenType.Escape, @"\", 2), }); } @@ -181,7 +181,7 @@ public static IEnumerable LineWithMultiTokens() yield return new TestCaseData(@"\# word", new List { - new(TokenType.Escape, @"\", 0, false, TagType.Escape), + new(TokenType.Escape, @"\", 0), new(TokenType.MdTag, "# ", 1, false, TagType.Header), new(TokenType.Text, "word", 3) }); From f33653b45c3d6aa775470c52dc3f015d20ff9539 Mon Sep 17 00:00:00 2001 From: Braidenbikher Victor Date: Mon, 9 Dec 2024 04:44:39 +0500 Subject: [PATCH 11/13] =?UTF-8?q?=D0=A1=D0=B4=D0=B5=D0=BB=D0=B0=D0=BB=20IT?= =?UTF-8?q?ag=20=D0=BB=D0=B5=D0=B3=D1=87=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ConcreteConverter/HtmlConverter.cs | 4 ++- .../ConcreteConverter/MdTagToHtmlConverter.cs | 29 +++++++++++++++++++ cs/Markdown/Tags/ConcreteTags/BoldTag.cs | 4 --- cs/Markdown/Tags/ConcreteTags/BulletTag.cs | 6 +--- cs/Markdown/Tags/ConcreteTags/HeaderTag.cs | 4 --- cs/Markdown/Tags/ConcreteTags/ItalicTag.cs | 4 --- cs/Markdown/Tags/ITag.cs | 4 --- cs/Markdown_Tests/LineParser_Tests.cs | 2 +- 8 files changed, 34 insertions(+), 23 deletions(-) create mode 100644 cs/Markdown/Converter/ConcreteConverter/MdTagToHtmlConverter.cs diff --git a/cs/Markdown/Converter/ConcreteConverter/HtmlConverter.cs b/cs/Markdown/Converter/ConcreteConverter/HtmlConverter.cs index 6fc71f204..6bbc73442 100644 --- a/cs/Markdown/Converter/ConcreteConverter/HtmlConverter.cs +++ b/cs/Markdown/Converter/ConcreteConverter/HtmlConverter.cs @@ -32,7 +32,9 @@ public string Convert(ParsedLine[] parsedLines) { sb.Append(text.Line.AsSpan(prevTagPos, tag.Position - prevTagPos)); - sb.Append(tag.IsCloseTag ? tag.CloseTag : tag.OpenTag); + sb.Append(tag.IsCloseTag ? + MdTagToHtmlConverter.CloseTags[tag.TagType] : + MdTagToHtmlConverter.OpenTags[tag.TagType]); prevTagPos = tag.Position; } diff --git a/cs/Markdown/Converter/ConcreteConverter/MdTagToHtmlConverter.cs b/cs/Markdown/Converter/ConcreteConverter/MdTagToHtmlConverter.cs new file mode 100644 index 000000000..4e75ebaa3 --- /dev/null +++ b/cs/Markdown/Converter/ConcreteConverter/MdTagToHtmlConverter.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Markdown.Tags; +using Markdown.Tags.ConcreteTags; + +namespace Markdown.Converter.ConcreteConverter +{ + public static class MdTagToHtmlConverter + { + public static Dictionary OpenTags = new() + { + { TagType.Header, "

    "}, + { TagType.Bold, ""}, + { TagType.BulletedListItem, "
  • "}, + { TagType.Italic, ""} + }; + + public static Dictionary CloseTags = new() + { + { TagType.Header, "
  • "}, + { TagType.Bold, ""}, + { TagType.BulletedListItem, ""}, + { TagType.Italic, ""} + }; + } +} diff --git a/cs/Markdown/Tags/ConcreteTags/BoldTag.cs b/cs/Markdown/Tags/ConcreteTags/BoldTag.cs index 9b43e74c1..2f33b1fce 100644 --- a/cs/Markdown/Tags/ConcreteTags/BoldTag.cs +++ b/cs/Markdown/Tags/ConcreteTags/BoldTag.cs @@ -13,8 +13,4 @@ public BoldTag(int position, bool isCloseTag = false) public int Position { get; set; } public bool IsCloseTag { get; set; } - - public string OpenTag => ""; - - public string CloseTag => ""; } \ No newline at end of file diff --git a/cs/Markdown/Tags/ConcreteTags/BulletTag.cs b/cs/Markdown/Tags/ConcreteTags/BulletTag.cs index 3a06f1c94..886b9cf8c 100644 --- a/cs/Markdown/Tags/ConcreteTags/BulletTag.cs +++ b/cs/Markdown/Tags/ConcreteTags/BulletTag.cs @@ -8,11 +8,7 @@ public BulletTag(int position, bool isCloseTag = false) IsCloseTag = isCloseTag; } - public TagType TagType { get; } + public TagType TagType => TagType.BulletedListItem; public int Position { get; set; } public bool IsCloseTag { get; set; } - - public string OpenTag => "
  • "; - - public string CloseTag => "
  • "; } \ No newline at end of file diff --git a/cs/Markdown/Tags/ConcreteTags/HeaderTag.cs b/cs/Markdown/Tags/ConcreteTags/HeaderTag.cs index d434883be..aec3de68d 100644 --- a/cs/Markdown/Tags/ConcreteTags/HeaderTag.cs +++ b/cs/Markdown/Tags/ConcreteTags/HeaderTag.cs @@ -13,8 +13,4 @@ public HeaderTag(int position, bool isCloseTag = false) public int Position { get; set; } public bool IsCloseTag { get; set; } - - public string OpenTag => "

    "; - - public string CloseTag => "

    "; } \ No newline at end of file diff --git a/cs/Markdown/Tags/ConcreteTags/ItalicTag.cs b/cs/Markdown/Tags/ConcreteTags/ItalicTag.cs index a48b8520a..43d4aab79 100644 --- a/cs/Markdown/Tags/ConcreteTags/ItalicTag.cs +++ b/cs/Markdown/Tags/ConcreteTags/ItalicTag.cs @@ -13,8 +13,4 @@ public ItalicTag(int position, bool isCloseTag = false) public int Position { get; set; } public bool IsCloseTag { get; set; } - - public string OpenTag => ""; - - public string CloseTag => ""; } \ No newline at end of file diff --git a/cs/Markdown/Tags/ITag.cs b/cs/Markdown/Tags/ITag.cs index 8957484c0..8ac20edbd 100644 --- a/cs/Markdown/Tags/ITag.cs +++ b/cs/Markdown/Tags/ITag.cs @@ -7,8 +7,4 @@ public interface ITag public int Position { get; protected set; } public bool IsCloseTag { get; protected set; } - - public string OpenTag { get; } - - public string CloseTag { get; } } \ No newline at end of file diff --git a/cs/Markdown_Tests/LineParser_Tests.cs b/cs/Markdown_Tests/LineParser_Tests.cs index 450b3494c..f9a7c75c5 100644 --- a/cs/Markdown_Tests/LineParser_Tests.cs +++ b/cs/Markdown_Tests/LineParser_Tests.cs @@ -43,7 +43,7 @@ public void ParseLine_ShoudBeCorrect_WhenLineWithHeaderTags(string inLine, strin } [TestCaseSource(typeof(LineParserData), nameof(LineParserData.LinesWithBulletedList))] - public void ParseLine_ShoudBeCorrect_LinesWithBulletedList(string inLine, string expectedLine, List tags) + public void ParseLine_ShoudBeCorrect_WhenLinesWithBulletedList(string inLine, string expectedLine, List tags) { var parsedLines = parser.ParseLine(inLine); From 0bb4f5215b3154300c1a978bb095bc1ccd230ead Mon Sep 17 00:00:00 2001 From: Braidenbikher Victor Date: Mon, 9 Dec 2024 05:03:28 +0500 Subject: [PATCH 12/13] =?UTF-8?q?=D0=92=D1=8B=D0=BD=D0=B5=D1=81=20=D0=BE?= =?UTF-8?q?=D0=B1=D1=89=D1=83=D1=8E=20=D0=BB=D0=BE=D0=B3=D0=B8=D0=BA=D1=83?= =?UTF-8?q?=20bold=20=D0=B8=20italic=20handler=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../TokenHandlers/BoldTokensHandler.cs | 151 +--------------- .../TokenHandlers/ItalicTokensHandler.cs | 151 +--------------- .../TokenHandlers/UnderscoreTokensHandler.cs | 168 ++++++++++++++++++ 3 files changed, 170 insertions(+), 300 deletions(-) create mode 100644 cs/Markdown/TokenParser/TokenHandlers/UnderscoreTokensHandler.cs diff --git a/cs/Markdown/TokenParser/TokenHandlers/BoldTokensHandler.cs b/cs/Markdown/TokenParser/TokenHandlers/BoldTokensHandler.cs index fc70eb7de..ea1f2581a 100644 --- a/cs/Markdown/TokenParser/TokenHandlers/BoldTokensHandler.cs +++ b/cs/Markdown/TokenParser/TokenHandlers/BoldTokensHandler.cs @@ -5,155 +5,6 @@ namespace Markdown.TokenParser.TokenHandlers; -public class BoldTokensHandler : ITokenHandler +public class BoldTokensHandler() : UnderscoreTokensHandler(TagType.Bold, "__") { - public List HandleLine(List line) - { - var result = new List(); - var opened = new List(1); - var position = 0; - Func, int, bool> isClosed = (list, i) => false; - - for (var j = 0; j < line.Count; j++) - { - if (opened.Count == 0 && IsOpen(j, line)) - { - if (IsOpenInWord(j, line)) - { - var token = line[j]; - isClosed = (list, index) => IsCloseInWord(index, list, token); - opened.Add(line[j]); - } - else if (IsOpenBetweenWords(j, line)) - { - var token = line[j]; - isClosed = (list, i) => IsClosed(i, list, token); - opened.Add(line[j]); - } - } - else if (opened.Count > 0 && isClosed(line, j)) - { - result.Add(opened[0]); - result.Add(new Token(TokenType.MdTag, "__", - position, true, TagType.Bold)); - - opened.Clear(); - } - else if (line[j].TagType == TagType.Bold) - { - result.Add(new Token(TokenType.Text, "__", position)); - } - - position += line[j].Content.Length; - } - - if (opened.Count > 0) - { - opened[0].TagType = TagType.UnDefined; - opened[0].TokenType = TokenType.Text; - - result.Add(opened[0]); - } - - return result; - } - - private bool IsOpen(int index, List tokens) - { - var isFirstInLine = IsFirstInLine(index, tokens); - var isOpenOrdinary = IsOpenOrdinary(index, tokens); - var isOpenClosed = IsInWord(index, tokens); - - return isFirstInLine || isOpenOrdinary || isOpenClosed; - } - - private bool IsClosed(int index, List tokens, Token token) - { - var isLastInLine = IsLastInLine(index, tokens); - var isClosedOrdinary = IsClosedOrdinary(index, tokens); - var isOpenClosed = IsCloseInWord(index, tokens, token); - - return isLastInLine || isClosedOrdinary || isOpenClosed; - } - - private bool IsOpenBetweenWords(int index, List tokens) - { - return IsOpenOrdinary(index, tokens) ^ IsFirstInLine(index, tokens); - } - - private bool IsOpenInWord(int index, List tokens) - { - return IsInWord(index, tokens); - } - - private bool IsCloseBetweenWords(int index, List tokens) - { - return IsClosedOrdinary(index, tokens) ^ IsLastInLine(index, tokens); - } - - private bool IsCloseInWord(int index, List tokens, Token openToken) - { - return index - 2 > -1 && (IsInWord(index, tokens) || IsCloseBetweenWords(index, tokens)) - && tokens[index - 2] == openToken; - } - - #region OpenSituations - - /// - /// Определяет является ли токен одновременно и - /// открывающим и закрывающим тегом - случай - /// если тэг в слове ("пре_сп_ко_йн_ый) - /// - /// Индекс токена - /// Список токенов для проверки - /// true если тег внутри слова иначе false - private bool IsInWord(int index, List tokens) - { - return tokens.LastTokenIs(TokenType.Text, index) && - tokens.CurrentTokenIs(TagType.Bold, index) & - tokens.NextTokenIs(TokenType.Text, index); - } - - private bool IsFirstInLine(int index, List tokens) - { - return index == 0 && tokens.CurrentTokenIs(TagType.Bold, index) && - (tokens.NextTokenIs(TokenType.Text, index) || - tokens.NextTokenIs(TokenType.MdTag, index)); - } - - private bool IsOpenOrdinary(int index, List tokens) - { - var hasWhSpaceBefore = tokens.LastTokenIs(TokenType.WhiteSpace, index); - var hasMdTagBefore = tokens.LastTokenIs(TokenType.MdTag, index); - - return (hasWhSpaceBefore || hasMdTagBefore) && - tokens.CurrentTokenIs(TagType.Bold, index) && - tokens.NextTokenIs(TokenType.Text, index); - } - - #endregion - - - #region CloseSituations - - private bool IsLastInLine(int index, List tokens) - { - var isLast = index == tokens.Count() - 1; - - return isLast && tokens.CurrentTokenIs(TagType.Bold, index) && - (tokens.LastTokenIs(TokenType.Text, index) || - tokens.LastTokenIs(TokenType.MdTag, index)); - } - - private bool IsClosedOrdinary(int index, List tokens) - { - var hasTextAfter = tokens.NextTokenIs(TokenType.WhiteSpace, index); - var hasMdTagAfter = tokens.NextTokenIs(TokenType.MdTag, index); - - return tokens.LastTokenIs(TokenType.Text, index) && - tokens.CurrentTokenIs(TagType.Bold, index) && - (hasMdTagAfter || hasTextAfter); - } - - #endregion } \ No newline at end of file diff --git a/cs/Markdown/TokenParser/TokenHandlers/ItalicTokensHandler.cs b/cs/Markdown/TokenParser/TokenHandlers/ItalicTokensHandler.cs index 1d6e6e5a3..aa45d6a98 100644 --- a/cs/Markdown/TokenParser/TokenHandlers/ItalicTokensHandler.cs +++ b/cs/Markdown/TokenParser/TokenHandlers/ItalicTokensHandler.cs @@ -5,155 +5,6 @@ namespace Markdown.TokenParser.TokenHandlers; -public class ItalicTokensHandler : ITokenHandler +public class ItalicTokensHandler() : UnderscoreTokensHandler(TagType.Italic, "_") { - public List HandleLine(List line) - { - var result = new List(); - var opened = new List(1); - var position = 0; - Func, int, bool> isClosed = (list, i) => false; - - for (var j = 0; j < line.Count; j++) - { - if (opened.Count == 0 && IsOpen(j, line)) - { - if (IsOpenInWord(j, line)) - { - var token = line[j]; - isClosed = (list, index) => IsCloseInWord(index, list, token); - opened.Add(line[j]); - } - else if (IsOpenBetweenWords(j, line)) - { - var token = line[j]; - isClosed = (list, i) => IsClosed(i, list, token); - opened.Add(line[j]); - } - } - else if (opened.Count > 0 && isClosed(line, j)) - { - result.Add(opened[0]); - result.Add(new Token(TokenType.MdTag, "_", - position, true, TagType.Italic)); - - opened.Clear(); - } - else if (line[j].TagType == TagType.Italic) - { - result.Add(new Token(TokenType.Text, "_", position)); - } - - position += line[j].Content.Length; - } - - if (opened.Count > 0) - { - opened[0].TagType = TagType.UnDefined; - opened[0].TokenType = TokenType.Text; - - result.Add(opened[0]); - } - - return result; - } - - private bool IsOpen(int index, List tokens) - { - var isFirstInLine = IsFirstInLine(index, tokens); - var isOpenOrdinary = IsOpenOrdinary(index, tokens); - var isOpenClosed = IsInWord(index, tokens); - - return isFirstInLine || isOpenOrdinary || isOpenClosed; - } - - private bool IsClosed(int index, List tokens, Token token) - { - var isLastInLine = IsLastInLine(index, tokens); - var isClosedOrdinary = IsClosedOrdinary(index, tokens); - var isOpenClosed = IsCloseInWord(index, tokens, token); - - return isLastInLine || isClosedOrdinary || isOpenClosed; - } - - private bool IsOpenBetweenWords(int index, List tokens) - { - return IsOpenOrdinary(index, tokens) ^ IsFirstInLine(index, tokens); - } - - private bool IsOpenInWord(int index, List tokens) - { - return IsInWord(index, tokens); - } - - private bool IsCloseBetweenWords(int index, List tokens) - { - return IsClosedOrdinary(index, tokens) ^ IsLastInLine(index, tokens); - } - - private bool IsCloseInWord(int index, List tokens, Token openToken) - { - return index - 2 > -1 && (IsInWord(index, tokens) || IsCloseBetweenWords(index, tokens)) - && tokens[index - 2] == openToken; - } - - #region OpenSituations - - /// - /// Определяет является ли токен одновременно и - /// открывающим и закрывающим тегом - случай - /// если тэг в слове ("пре_сп_ко_йн_ый) - /// - /// Индекс токена - /// Список токенов для проверки - /// true если тег внутри слова иначе false - private bool IsInWord(int index, List tokens) - { - return tokens.LastTokenIs(TokenType.Text, index) && - tokens.CurrentTokenIs(TagType.Italic, index) & - tokens.NextTokenIs(TokenType.Text, index); - } - - private bool IsFirstInLine(int index, List tokens) - { - return index == 0 && tokens.CurrentTokenIs(TagType.Italic, index) && - (tokens.NextTokenIs(TokenType.Text, index) || - tokens.NextTokenIs(TokenType.MdTag, index)); - } - - private bool IsOpenOrdinary(int index, List tokens) - { - var hasWhSpaceBefore = tokens.LastTokenIs(TokenType.WhiteSpace, index); - var hasMdTagBefore = tokens.LastTokenIs(TokenType.MdTag, index); - - return (hasWhSpaceBefore || hasMdTagBefore) && - tokens.CurrentTokenIs(TagType.Italic, index) && - tokens.NextTokenIs(TokenType.Text, index); - } - - #endregion - - - #region CloseSituations - - private bool IsLastInLine(int index, List tokens) - { - var isLast = index == tokens.Count() - 1; - - return isLast && tokens.CurrentTokenIs(TagType.Italic, index) && - (tokens.LastTokenIs(TokenType.Text, index) || - tokens.LastTokenIs(TokenType.MdTag, index)); - } - - private bool IsClosedOrdinary(int index, List tokens) - { - var hasTextAfter = tokens.NextTokenIs(TokenType.WhiteSpace, index); - var hasMdTagAfter = tokens.NextTokenIs(TokenType.MdTag, index); - - return tokens.LastTokenIs(TokenType.Text, index) && - tokens.CurrentTokenIs(TagType.Italic, index) && - (hasMdTagAfter || hasTextAfter); - } - - #endregion } \ No newline at end of file diff --git a/cs/Markdown/TokenParser/TokenHandlers/UnderscoreTokensHandler.cs b/cs/Markdown/TokenParser/TokenHandlers/UnderscoreTokensHandler.cs new file mode 100644 index 000000000..0aa557a94 --- /dev/null +++ b/cs/Markdown/TokenParser/TokenHandlers/UnderscoreTokensHandler.cs @@ -0,0 +1,168 @@ +using Markdown.Extensions; +using Markdown.Tags; +using Markdown.Tokens; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Markdown.TokenParser.TokenHandlers +{ + public abstract class UnderscoreTokensHandler(TagType tagType, string TokenContent) + { + private readonly TagType TagType = tagType; + + private readonly string Content = TokenContent; + + public List HandleLine(List line) + { + var result = new List(); + var opened = new List(1); + var position = 0; + Func, int, bool> isClosed = (list, i) => false; + + for (var j = 0; j < line.Count; j++) + { + if (opened.Count == 0 && IsOpen(j, line)) + { + if (IsOpenInWord(j, line)) + { + var token = line[j]; + isClosed = (list, index) => IsCloseInWord(index, list, token); + opened.Add(line[j]); + } + else if (IsOpenBetweenWords(j, line)) + { + var token = line[j]; + isClosed = (list, i) => IsClosed(i, list, token); + opened.Add(line[j]); + } + } + else if (opened.Count > 0 && isClosed(line, j)) + { + result.Add(opened[0]); + result.Add(new Token(TokenType.MdTag, TokenContent, + position, true, TagType)); + + opened.Clear(); + } + else if (line[j].TagType == TagType) + { + result.Add(new Token(TokenType.Text, TokenContent, position)); + } + + position += line[j].Content.Length; + } + + if (opened.Count > 0) + { + opened[0].TagType = TagType.UnDefined; + opened[0].TokenType = TokenType.Text; + + result.Add(opened[0]); + } + + return result; + } + + private bool IsOpen(int index, List tokens) + { + var isFirstInLine = IsFirstInLine(index, tokens); + var isOpenOrdinary = IsOpenOrdinary(index, tokens); + var isOpenClosed = IsInWord(index, tokens); + + return isFirstInLine || isOpenOrdinary || isOpenClosed; + } + + private bool IsClosed(int index, List tokens, Token token) + { + var isLastInLine = IsLastInLine(index, tokens); + var isClosedOrdinary = IsClosedOrdinary(index, tokens); + var isOpenClosed = IsCloseInWord(index, tokens, token); + + return isLastInLine || isClosedOrdinary || isOpenClosed; + } + + private bool IsOpenBetweenWords(int index, List tokens) + { + return IsOpenOrdinary(index, tokens) ^ IsFirstInLine(index, tokens); + } + + private bool IsOpenInWord(int index, List tokens) + { + return IsInWord(index, tokens); + } + + private bool IsCloseBetweenWords(int index, List tokens) + { + return IsClosedOrdinary(index, tokens) ^ IsLastInLine(index, tokens); + } + + private bool IsCloseInWord(int index, List tokens, Token openToken) + { + return index - 2 > -1 && (IsInWord(index, tokens) || IsCloseBetweenWords(index, tokens)) + && tokens[index - 2] == openToken; + } + + #region OpenSituations + + /// + /// Определяет является ли токен одновременно и + /// открывающим и закрывающим тегом - случай + /// если тэг в слове ("пре_сп_ко_йн_ый) + /// + /// Индекс токена + /// Список токенов для проверки + /// true если тег внутри слова иначе false + private bool IsInWord(int index, List tokens) + { + return tokens.LastTokenIs(TokenType.Text, index) && + tokens.CurrentTokenIs(TagType, index) & + tokens.NextTokenIs(TokenType.Text, index); + } + + private bool IsFirstInLine(int index, List tokens) + { + return index == 0 && tokens.CurrentTokenIs(TagType, index) && + (tokens.NextTokenIs(TokenType.Text, index) || + tokens.NextTokenIs(TokenType.MdTag, index)); + } + + private bool IsOpenOrdinary(int index, List tokens) + { + var hasWhSpaceBefore = tokens.LastTokenIs(TokenType.WhiteSpace, index); + var hasMdTagBefore = tokens.LastTokenIs(TokenType.MdTag, index); + + return (hasWhSpaceBefore || hasMdTagBefore) && + tokens.CurrentTokenIs(TagType, index) && + tokens.NextTokenIs(TokenType.Text, index); + } + + #endregion + + + #region CloseSituations + + private bool IsLastInLine(int index, List tokens) + { + var isLast = index == tokens.Count() - 1; + + return isLast && tokens.CurrentTokenIs(TagType, index) && + (tokens.LastTokenIs(TokenType.Text, index) || + tokens.LastTokenIs(TokenType.MdTag, index)); + } + + private bool IsClosedOrdinary(int index, List tokens) + { + var hasTextAfter = tokens.NextTokenIs(TokenType.WhiteSpace, index); + var hasMdTagAfter = tokens.NextTokenIs(TokenType.MdTag, index); + + return tokens.LastTokenIs(TokenType.Text, index) && + tokens.CurrentTokenIs(TagType, index) && + (hasMdTagAfter || hasTextAfter); + } + + #endregion + } +} From 666bb9821df3c47c7b6ad6c5b2c1b589de1c77a6 Mon Sep 17 00:00:00 2001 From: Braidenbikher Victor Date: Mon, 9 Dec 2024 05:30:11 +0500 Subject: [PATCH 13/13] =?UTF-8?q?=D0=9F=D1=80=D0=BE=D0=BF=D0=B8=D1=81?= =?UTF-8?q?=D0=B0=D0=BB=20=D0=B4=D0=BE=D0=BF=D0=BE=D0=BB=D0=BD=D0=B8=D1=82?= =?UTF-8?q?=D0=B5=D0=BB=D1=8C=D0=BD=D1=8B=D0=B9=20=D1=82=D0=B5=D0=B3=20?= =?UTF-8?q?=D0=B2=20=D1=81=D0=BF=D0=B5=D1=86=D0=B8=D1=84=D0=B8=D0=BA=D0=B0?= =?UTF-8?q?=D1=86=D0=B8=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MarkdownSpec.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/MarkdownSpec.md b/MarkdownSpec.md index 886e99c95..a8fab56c7 100644 --- a/MarkdownSpec.md +++ b/MarkdownSpec.md @@ -31,7 +31,15 @@ __Выделенный двумя символами текст__ должен Символ экранирования тоже можно экранировать: \\_вот это будет выделено тегом_ \ +# Неупорядоченный список +Элементы неупорядоченного списка начинаются с символа * за которым следует пробел. + +
    • sleep
    • +
    • drink
    • +
    • eat
    + +Внутри элементов неупорядоченного списока могут присутствовать все прочие символы разметки с указанными правилами, кроме заголовка. # Взаимодействие тегов