diff --git a/cs/Markdown/Converter.cs b/cs/Markdown/Converter.cs new file mode 100644 index 000000000..d6b996364 --- /dev/null +++ b/cs/Markdown/Converter.cs @@ -0,0 +1,18 @@ +using System.Text; + +namespace Markdown; + +public class Converter +{ + public string ConvertWithTokens(List tokens) + { + StringBuilder result = new(); + + foreach (var token in tokens) + { + result.Append(token.Context); + } + + return result.ToString(); + } +} \ No newline at end of file diff --git a/cs/Markdown/ListOfTokens.cs b/cs/Markdown/ListOfTokens.cs new file mode 100644 index 000000000..3fc70ca83 --- /dev/null +++ b/cs/Markdown/ListOfTokens.cs @@ -0,0 +1,10 @@ +namespace Markdown; + +public class ListOfTokens : List +{ + public new void Add(T token) + { + if (!(token is null)) + base.Add(token); + } +} \ No newline at end of file diff --git a/cs/Markdown/Markdown.csproj b/cs/Markdown/Markdown.csproj new file mode 100644 index 000000000..2f4fc7765 --- /dev/null +++ b/cs/Markdown/Markdown.csproj @@ -0,0 +1,10 @@ + + + + Exe + net8.0 + enable + enable + + + diff --git a/cs/Markdown/Md.cs b/cs/Markdown/Md.cs new file mode 100644 index 000000000..bce71cd28 --- /dev/null +++ b/cs/Markdown/Md.cs @@ -0,0 +1,11 @@ +namespace Markdown; + +public class Md +{ + public static string Render(string markdown) + { + TokenGenerator tokenGenerator = new TokenGenerator(); + Converter converter = new Converter(); + return converter.ConvertWithTokens(tokenGenerator.SplitParagraphs(markdown)); + } +} \ No newline at end of file diff --git a/cs/Markdown/Parsers/BoldParser.cs b/cs/Markdown/Parsers/BoldParser.cs new file mode 100644 index 000000000..59a2f2267 --- /dev/null +++ b/cs/Markdown/Parsers/BoldParser.cs @@ -0,0 +1,118 @@ +using System.Collections; +using System.Security.Principal; +using Markdown.TagsType; + +namespace Markdown.Parsers; + +public class BoldParser : IParser +{ + private readonly Stack tagsStack; + private readonly GeneralParser.IsValidTag isValidOpenBoldTag; + private readonly GeneralParser.IsValidTag isValidCloseBoldTag; + private readonly GeneralParser.AddInsert addBoldTag; + public bool IsHaveInsideBoldTag; + public static bool IsBoldClosed; + private int index; + + public BoldParser() + { + IsBoldClosed = true; + IsHaveInsideBoldTag = false; + tagsStack = GeneralParser.tagsStack; + addBoldTag = GeneralParser.AddInsideTag; + isValidOpenBoldTag = GeneralParser.IsValidOpenTag; + isValidCloseBoldTag = GeneralParser.IsValidCloserTag; + index = 0; + } + + private Token BoldParse(string text, int i) + { + index = i; + index++; + + if (i != 0 && i != text.Length - 2) + IsHaveInsideBoldTag = true; + if (IsEmptyStringInsideBold(text, i)) + { + index = text.Length; + return new Token("____", new TextTag()); + } + + if (IsBoldClosed) + { + if (!isValidOpenBoldTag(text, i + 1)) + { + return (new Token("__", new TextTag())); + } + } + else + { + if (!isValidCloseBoldTag(text, i)) + { + if (isValidOpenBoldTag(text, i)) + { + if (tagsStack.Count != 0) + tagsStack.Pop(); + return addBoldTag(new BoldTag(), true); + } + + return new Token("__", new TextTag()); + } + } + + if (tagsStack.Count > 1 && + tagsStack.Any(p => p.Type is {MarkdownTag: "_"}) && + tagsStack.Peek().Type is {MarkdownTag: "__"}) + { + tagsStack.Pop(); + } + + if (!IsBoldClosed && i == text.Length - 1) + { + tagsStack.Pop(); + } + + var boldTag = addBoldTag(new BoldTag(), IsBoldClosed); + IsBoldClosed = !IsBoldClosed; + + + return boldTag; + } + + public bool IsNextSymbolBold(string text, int i) + { + var nextIndex = i + 1; + if (nextIndex < text.Length) + { + if (text[nextIndex] == '_') + { + return true; + } + } + + return false; + } + + private bool IsEmptyStringInsideBold(string text, int i) + { + int nextIndex = i + 1; + if (nextIndex < text.Length - 2 && IsNextSymbolBold(text, nextIndex)) + { + return true; + } + + return false; + } + + public bool TryParse(char symbol, string text, int i) + { + return symbol == '_' && IsNextSymbolBold(text, i); + } + + public Token Parse(string text, ref int i) + { + var token = BoldParse(text, i); + i = index; + return token; + } +} \ No newline at end of file diff --git a/cs/Markdown/Parsers/GeneralParser.cs b/cs/Markdown/Parsers/GeneralParser.cs new file mode 100644 index 000000000..b7e9d3369 --- /dev/null +++ b/cs/Markdown/Parsers/GeneralParser.cs @@ -0,0 +1,59 @@ +using Markdown.TagsType; + +namespace Markdown.Parsers; + +public static class GeneralParser +{ + public static Stack tagsStack = new(); + + public delegate Token AddInsert(ITagsType tags, bool isClose); + + public delegate bool IsValidTag(string text, int index); + + public static Token AddInsideTag(ITagsType tag, bool isClose) + { + var result = GenerateTagToken(tag, isClose); + if (!isClose && tagsStack.Count != 0 && tagsStack.Peek().Type?.MarkdownTag == tag.MarkdownTag) + ClosePairTag(tagsStack.Pop()); + return result; + } + + public static Token GenerateTagToken(ITagsType tag, bool isClose) + { + if (isClose) + { + var temp = new Token(tag.GetHtmlOpenTag, tag); + tagsStack.Push(temp); + return temp; + } + + if (tagsStack.Count != 0 && tagsStack.Peek().Type?.MarkdownTag == tag.MarkdownTag) + tag.HasPair = true; + return new Token(tag.GetHtmlCloseTag, tag); + } + + public static void ClosePairTag(Token token) + { + if (token.Type != null) token.Type.HasPair = true; + } + + public static bool IsValidCloserTag(string text, int index) + { + if (index == 0) + return false; + if (index > 0 && text[index - 1] == ' ') + return false; + + return true; + } + + public static bool IsValidOpenTag(string text, int index) + { + if (index == text.Length - 1) + return false; + if (index < text.Length - 1 && text[index + 1] == ' ') + return false; + + return true; + } +} \ No newline at end of file diff --git a/cs/Markdown/Parsers/HeadingParser.cs b/cs/Markdown/Parsers/HeadingParser.cs new file mode 100644 index 000000000..83edb1aba --- /dev/null +++ b/cs/Markdown/Parsers/HeadingParser.cs @@ -0,0 +1,17 @@ +using Markdown.TagsType; + +namespace Markdown.Parsers; + +public class HeadingParser : IParser +{ + public bool TryParse(char symbol, string text, int index) + { + return symbol == '#'; + } + + public Token Parse(string text, ref int index) + { + var headingTag = new HeadingTag(); + return new Token(headingTag.GetHtmlOpenTag, headingTag); + } +} \ No newline at end of file diff --git a/cs/Markdown/Parsers/IParser.cs b/cs/Markdown/Parsers/IParser.cs new file mode 100644 index 000000000..d2295f090 --- /dev/null +++ b/cs/Markdown/Parsers/IParser.cs @@ -0,0 +1,7 @@ +namespace Markdown.Parsers; + +public interface IParser +{ + bool TryParse(char symbol, string text, int index); + Token Parse(string text, ref int index); +} \ No newline at end of file diff --git a/cs/Markdown/Parsers/ItalicParser.cs b/cs/Markdown/Parsers/ItalicParser.cs new file mode 100644 index 000000000..e85370783 --- /dev/null +++ b/cs/Markdown/Parsers/ItalicParser.cs @@ -0,0 +1,75 @@ +using Markdown.TagsType; + +namespace Markdown.Parsers; + +public class ItalicParser : IParser +{ + private readonly GeneralParser.IsValidTag isValidOpenItalicTag; + private readonly GeneralParser.IsValidTag isValidCloseItalicTag; + private readonly GeneralParser.AddInsert addItalicTag; + private bool isItalicClosed; + private readonly Stack tagsStack; + public bool IsHaveInsideItalicTag; + + + public ItalicParser() + { + isItalicClosed = true; + tagsStack = GeneralParser.tagsStack; + addItalicTag = GeneralParser.AddInsideTag; + isValidOpenItalicTag = GeneralParser.IsValidOpenTag; + isValidCloseItalicTag = GeneralParser.IsValidCloserTag; + IsHaveInsideItalicTag = false; + } + + private Token ItalicParse(string text, int i) + { + if (i != 0 && i != text.Length - 1) + IsHaveInsideItalicTag = true; + if (isItalicClosed) + { + if (!isValidOpenItalicTag(text, i)) + { + return new Token("_", new TextTag()); + } + } + else + { + if (!isValidCloseItalicTag(text, i)) + { + if (isValidOpenItalicTag(text, i)) + { + if (tagsStack.Count != 0) + tagsStack.Pop(); + return addItalicTag(new ItalicTag(), true); + } + + return new Token("_", new TextTag()); + } + } + + if (tagsStack.Count > 1 && !isItalicClosed && BoldParser.IsBoldClosed) + { + tagsStack.Pop(); + } + + var result = addItalicTag(new ItalicTag(), isItalicClosed); + isItalicClosed = !isItalicClosed; + if (!isItalicClosed && i == text.Length - 1) + { + tagsStack.Pop(); + } + + return result; + } + + public bool TryParse(char symbol, string text, int i) + { + return symbol == '_'; + } + + public Token Parse(string text, ref int index) + { + return ItalicParse(text, index); + } +} \ No newline at end of file diff --git a/cs/Markdown/Parsers/LinkParser.cs b/cs/Markdown/Parsers/LinkParser.cs new file mode 100644 index 000000000..96a46b914 --- /dev/null +++ b/cs/Markdown/Parsers/LinkParser.cs @@ -0,0 +1,63 @@ +using System.Net.Mime; +using System.Text; +using Markdown.TagsType; + +namespace Markdown.Parsers; + +public class LinkParser : IParser +{ + private LinkTag linkTag; + private StringBuilder nameOfLink; + private StringBuilder urlOfLink; + public bool IsThisLink; + + public LinkParser() + { + linkTag = new LinkTag(); + nameOfLink = new StringBuilder(); + urlOfLink = new StringBuilder(); + } + + public Token LinkParse(string text) + { + IsThisLink = true; + for (int i = 0; i < text.Length; i++) + { + switch (text[i]) + { + case '[': + break; + case ']': + linkTag.LinkName = nameOfLink.ToString(); + linkTag.LinkUrl = text.Substring(i + 2, text.Length - i - 3); + return GenerateLinkToken(); + default: + nameOfLink.Append(text[i]); + break; + } + } + + nameOfLink.Append(' '); + return null!; + } + + private Token GenerateLinkToken() + { + var context = linkTag.GetHtmlOpenTag + linkTag.LinkName + linkTag.GetHtmlCloseTag; + urlOfLink.Clear(); + nameOfLink.Clear(); + return new Token(context, linkTag); + } + + public bool TryParse(char symbol, string text, int i) + { + return symbol == '['; + } + + public Token Parse(string text, ref int i) + { + var token = LinkParse(text); + i = text.Length-1; + return token; + } +} \ No newline at end of file diff --git a/cs/Markdown/Parsers/SlashParser.cs b/cs/Markdown/Parsers/SlashParser.cs new file mode 100644 index 000000000..be85dc254 --- /dev/null +++ b/cs/Markdown/Parsers/SlashParser.cs @@ -0,0 +1,54 @@ +using Markdown.TagsType; + +namespace Markdown.Parsers; + +public class SlashParser : IParser +{ + private int index; + private BoldParser boldParser; + + public SlashParser(BoldParser boldParser) + { + this.boldParser = boldParser; + index = 0; + } + + private Token QuotedToken(string text, int i, bool isNextSymbolBold) + { + i++; + index = i; + if (index < text.Length) + { + switch (text[index]) + { + case '_': + if (index + 1 < text.Length && isNextSymbolBold) + { + index++; + return new Token(new BoldTag().MarkdownTag, new TextTag()); + } + + return new Token(new ItalicTag().MarkdownTag, new TextTag()); + case '#': + return new Token(new HeadingTag().MarkdownTag, new TextTag()); + } + } + + index--; + return new Token("\\", new TextTag()); + } + + public int GetIndex() => index; + + public bool TryParse(char symbol, string text, int i) + { + return symbol == '\\'; + } + + public Token Parse(string text, ref int i) + { + var token = QuotedToken(text, i, boldParser.IsNextSymbolBold(text, i + 1)); + i = index; + return token; + } +} \ No newline at end of file diff --git a/cs/Markdown/Program.cs b/cs/Markdown/Program.cs new file mode 100644 index 000000000..db5f9fb88 --- /dev/null +++ b/cs/Markdown/Program.cs @@ -0,0 +1,6 @@ +// See https://aka.ms/new-console-template for more information + +using Markdown; + + +Console.WriteLine(Md.Render("Hello World!")); \ No newline at end of file diff --git a/cs/Markdown/TagsTypes/BoldTag.cs b/cs/Markdown/TagsTypes/BoldTag.cs new file mode 100644 index 000000000..1be81cd97 --- /dev/null +++ b/cs/Markdown/TagsTypes/BoldTag.cs @@ -0,0 +1,11 @@ +namespace Markdown.TagsType; + +public class BoldTag : ITagsType +{ + public string MarkdownTag { get; } = "__"; + public bool HasPair { get; set; } + + public string GetHtmlOpenTag => ""; + + public string GetHtmlCloseTag => ""; +} \ No newline at end of file diff --git a/cs/Markdown/TagsTypes/HeadingTag.cs b/cs/Markdown/TagsTypes/HeadingTag.cs new file mode 100644 index 000000000..914f4e833 --- /dev/null +++ b/cs/Markdown/TagsTypes/HeadingTag.cs @@ -0,0 +1,11 @@ +namespace Markdown.TagsType; + +public class HeadingTag : ITagsType +{ + public string MarkdownTag { get; } = "#"; + public bool HasPair { get; set; } = true; + + public string GetHtmlOpenTag => "

"; + + public string GetHtmlCloseTag => "

"; +} \ No newline at end of file diff --git a/cs/Markdown/TagsTypes/ITagsType.cs b/cs/Markdown/TagsTypes/ITagsType.cs new file mode 100644 index 000000000..fde21f141 --- /dev/null +++ b/cs/Markdown/TagsTypes/ITagsType.cs @@ -0,0 +1,9 @@ +namespace Markdown.TagsType; + +public interface ITagsType +{ + public string MarkdownTag { get; } + public bool HasPair { get; set; } + public string GetHtmlOpenTag { get; } + public string GetHtmlCloseTag { get; } +} \ No newline at end of file diff --git a/cs/Markdown/TagsTypes/ItalicTag.cs b/cs/Markdown/TagsTypes/ItalicTag.cs new file mode 100644 index 000000000..d0b2486a2 --- /dev/null +++ b/cs/Markdown/TagsTypes/ItalicTag.cs @@ -0,0 +1,11 @@ +namespace Markdown.TagsType; + +public class ItalicTag : ITagsType +{ + public string MarkdownTag { get; } = "_"; + public bool HasPair { get; set; } + + public string GetHtmlOpenTag => ""; + + public string GetHtmlCloseTag => ""; +} \ No newline at end of file diff --git a/cs/Markdown/TagsTypes/LinkTag.cs b/cs/Markdown/TagsTypes/LinkTag.cs new file mode 100644 index 000000000..833a96326 --- /dev/null +++ b/cs/Markdown/TagsTypes/LinkTag.cs @@ -0,0 +1,13 @@ +namespace Markdown.TagsType; + +public class LinkTag : ITagsType +{ + public string MarkdownTag { get; } = "[]()"; + public bool HasPair { get; set; } = true; + + public string LinkName = ""; + public string LinkUrl = ""; + public string GetHtmlOpenTag => $""; + + public string GetHtmlCloseTag => ""; +} \ No newline at end of file diff --git a/cs/Markdown/TagsTypes/TextTag.cs b/cs/Markdown/TagsTypes/TextTag.cs new file mode 100644 index 000000000..bef196f3f --- /dev/null +++ b/cs/Markdown/TagsTypes/TextTag.cs @@ -0,0 +1,9 @@ +namespace Markdown.TagsType; + +public class TextTag : ITagsType +{ + public string MarkdownTag { get; } + public bool HasPair { get; set; } + public string GetHtmlOpenTag { get; } + public string GetHtmlCloseTag { get; } +} \ No newline at end of file diff --git a/cs/Markdown/Token.cs b/cs/Markdown/Token.cs new file mode 100644 index 000000000..20dbe66b1 --- /dev/null +++ b/cs/Markdown/Token.cs @@ -0,0 +1,21 @@ +using System.Collections; +using Markdown.Parsers; +using Markdown.TagsType; + +namespace Markdown; + +public class Token +{ + public string Context { get; set; } + public int Length => Context.Length; + public static int IdCounter = 0; + public readonly int Id; + public ITagsType Type { get; } + + public Token(string context, ITagsType type) + { + Context = context; + Type = type; + Id = IdCounter++; + } +} \ No newline at end of file diff --git a/cs/Markdown/TokenGenerator.cs b/cs/Markdown/TokenGenerator.cs new file mode 100644 index 000000000..15efbde55 --- /dev/null +++ b/cs/Markdown/TokenGenerator.cs @@ -0,0 +1,149 @@ +using System.Text; +using Markdown.Parsers; +using Markdown.TagsType; + +namespace Markdown; + +public class TokenGenerator +{ + private BoldParser boldParser; + private ItalicParser italicParser; + private LinkParser linkParser; + + private readonly Stack tagsStack; + private SlashParser slashParser; + private readonly List parsers; + + public TokenGenerator() + { + boldParser = new BoldParser(); + linkParser = new LinkParser(); + italicParser = new ItalicParser(); + var headingParser = new HeadingParser(); + slashParser = new SlashParser(boldParser); + parsers = new List + { + boldParser, + headingParser, + linkParser, + slashParser, + italicParser + }; + + tagsStack = GeneralParser.tagsStack; + } + + public List SplitParagraphs(string text) + { + ListOfTokens tokens = new(); + + var paragraphs = text.Split("\r\n"); + foreach (var line in paragraphs) + { + Tokenize(line, tokens); + tagsStack.Clear(); + } + + tokens.Remove(tokens.Last()); + ConvertTokensWithOutPairToMarkdown(tokens); + linkParser = new LinkParser(); + boldParser = new BoldParser(); + italicParser = new ItalicParser(); + slashParser = new SlashParser(boldParser); + return tokens; + } + + private void Tokenize(string line, ListOfTokens tokens) + { + var lastAddIndex = tokens.Count == 0 ? 0 : tokens.Count() + 1; + var words = line.Split(' '); + foreach (var word in words) + { + AddToken(word, tokens); + if (tokens.Count != 0) + tokens.Add(new Token(" ", new TextTag())); + } + + tokens.Remove(tokens.Last()); + if (lastAddIndex > -1 && tokens[lastAddIndex].Context == new HeadingTag().GetHtmlOpenTag) + { + var headingTag = new HeadingTag(); + tokens.Add(new Token(headingTag.GetHtmlCloseTag, headingTag)); + } + + tokens.Add(new Token("\n", new TextTag())); + } + + private void AddToken(string text, ListOfTokens tokens) + { + var countOfLastAddedTokens = tokens.Count(); + var isHaveDigitInside = false; + if (linkParser.IsThisLink) + { + tokens.Add(linkParser.LinkParse(text)); + return; + } + + for (int i = 0; i < text.Length; i++) + { + bool match = false; + + foreach (var parser in parsers) + { + if (parser.TryParse(text[i], text, i)) + { + match = true; + tokens.Add(parser.Parse(text, ref i)); + break; + } + } + + if (!match) + { + if (Char.IsDigit(text[i])) + isHaveDigitInside = true; + tokens.Add(new Token(text[i].ToString(), new TextTag())); + } + } + + countOfLastAddedTokens = tokens.Count - countOfLastAddedTokens; + GetValidResult(isHaveDigitInside, tokens, countOfLastAddedTokens); + } + + private void GetValidResult(bool isHaveDigitInside, ListOfTokens tokens, int countOfLastAddedTokens) + { + if (isHaveDigitInside) + MarkLastTokensToMarkdown(tokens, countOfLastAddedTokens); + if (italicParser.IsHaveInsideItalicTag && tagsStack.Count != 0) + { + tagsStack.Pop(); + } + + if (boldParser.IsHaveInsideBoldTag && tagsStack.Count != 0) + { + tagsStack.Pop(); + } + } + + private void ConvertTokensWithOutPairToMarkdown(ListOfTokens tokens) + { + foreach (var token in tokens) + { + if (token.Type is not TextTag && !token.Type.HasPair) + { + token.Context = token.Type.MarkdownTag; + } + } + } + + private void MarkLastTokensToMarkdown(ListOfTokens tokens, int countOfLastAddedTokens) + { + for (int i = tokens.Count - countOfLastAddedTokens; i < tokens.Count; i++) + { + if (tokens[i].Type is not TextTag) + { + tokens[i].Type!.HasPair = false; + } + } + } +} \ No newline at end of file diff --git a/cs/MarkdownTests/MarkdownTest.cs b/cs/MarkdownTests/MarkdownTest.cs new file mode 100644 index 000000000..db9ec6d8c --- /dev/null +++ b/cs/MarkdownTests/MarkdownTest.cs @@ -0,0 +1,141 @@ +using System.Text; +using FluentAssertions; +using FluentAssertions.Extensions; +using Markdown; +using NUnit.Framework; + +namespace MarkdownTests; + +public class MarkdownTests +{ + [Test] + public void Md_ShouldReturnCorrectString_WithHeadingTag() + { + var result = Md.Render("# Thank God It's Christmas \r\n # Queen"); + var expected = "

Thank God It's Christmas

\n

Queen

"; + result.Should().BeEquivalentTo(expected); + } + + [Test] + public void Md_ShouldReturnCorrectString_WithMixTags() + { + var result = Md.Render("# __Princes _of the_ Universe__ [littleLink](Yes)"); + var expected = "

Princes of the Universe littleLink

"; + result.Should().BeEquivalentTo(expected); + } + + [Test] + public void Md_ShouldReturnCorrectString_WithItalicInsideBoldTag() + { + var result = Md.Render("__Too Much _Love Will_ Kill You__"); + var expected = "Too Much Love Will Kill You"; + result.Should().BeEquivalentTo(expected); + } + + [Test] + public void Md_ShouldReturnCorrectString_WithItaBoldInsideItalicTag() + { + var result = Md.Render("_We __Will Rock__ You_"); + var expected = "We __Will Rock__ You"; + result.Should().BeEquivalentTo(expected); + } + + [Test] + public void Md_ShouldReturnCorrectString_WithUnpairTagsInSameParagraph() + { + var result = Md.Render("__Another One_ Bites the Dust \n __Another _One Bites the Dust"); + var expected = "__Another One_ Bites the Dust \n __Another _One Bites the Dust"; + result.Should().BeEquivalentTo(expected); + } + + [TestCase("п_24_54", ExpectedResult = "п_24_54")] + [TestCase("п__24__54", ExpectedResult = "п__24__54")] + public string Md_ShouldReturnCorrectString_WithNumbers(string text) + { + return Md.Render(text); + } + + [TestCase("____", ExpectedResult = "____")] + [TestCase("__", ExpectedResult = "__")] + public string Md_ShouldReturnCorrectString_WithEmptyTags(string text) + { + return Md.Render(text); + } + + [TestCase("\\", ExpectedResult = "\\")] + [TestCase("\\__", ExpectedResult = "__")] + [TestCase("\\_", ExpectedResult = "_")] + [TestCase("\\#", ExpectedResult = "#")] + [TestCase("\\w", ExpectedResult = "\\w")] + [TestCase("\\# I want to break free", ExpectedResult = "# I want to break free")] + [TestCase("\\# I \\__wan\\__t \\_to\\_ break free", ExpectedResult = "# I __wan__t _to_ break free")] + + public string Md_ShouldReturnCorrectString_WithSlashes(string text) + { + return Md.Render(text); + } + + [TestCase("don't_ stop_ me_ now_", ExpectedResult = "don't_ stop_ me_ now_")] + [TestCase("don't__ stop__ me__ now__", ExpectedResult = "don't__ stop__ me__ now__")] + public string Md_ShouldReturnCorrectString_WithSpacesAfterOpenTags(string text) + { + return Md.Render(text); + } + + [TestCase("_Bohemian _Rhapsody by Queen_", ExpectedResult = "_Bohemian Rhapsody by Queen")] + [TestCase("__Bohemian __Rhapsody by Queen__", ExpectedResult = "__Bohemian Rhapsody by Queen")] + public string Md_ShouldReturnCorrectString_WithSpacesBeforeCloserTags(string text) + { + return Md.Render(text); + } + + + [TestCase("__The _Show__ Must_ Go On", ExpectedResult = "__The _Show__ Must_ Go On")] + [TestCase("_The __Show_ Must__ Go On", ExpectedResult = "_The __Show_ Must__ Go On")] + public string Md_ShouldReturnCorrectString_WithCrossTags(string text) + { + return Md.Render(text); + } + + [TestCase("I Wa_nt to Br_eak Free", ExpectedResult = "I Wa_nt to Br_eak Free")] + [TestCase("I Wa__nt to Br__eak Free", ExpectedResult = "I Wa__nt to Br__eak Free")] + public string Md_ShouldReturnCorrectString_WithTagsInDifferentWords(string text) + { + return Md.Render(text); + } + + [TestCase("A K_in_d of Magic", ExpectedResult = "A Kind of Magic")] + [TestCase("A _Kin_d of Magic", ExpectedResult = "A Kind of Magic")] + [TestCase("A Kin_d_ of Magic", ExpectedResult = "A Kind of Magic")] + [TestCase("A K__in__d of Magic", ExpectedResult = "A Kind of Magic")] + [TestCase("A __Kin__d of Magic", ExpectedResult = "A Kind of Magic")] + [TestCase("A Kin__d__ of Magic", ExpectedResult = "A Kind of Magic")] + public string Md_ShouldReturnCorrectString_WithTagsInSameWords(string text) + { + return Md.Render(text); + } + + [TestCase("[Let's listen it](https://www.youtube.com/watch?v=-tJYN-eG1zk)", + ExpectedResult = "Let's listen it")] + [TestCase("[Queen](https://en.wikipedia.org/wiki/Queen_(band))", + ExpectedResult = "Queen")] + public string Md_ShouldReturnCorrectString_WithLink(string text) + { + return Md.Render(text); + } + + [Test] + public void Md_ShouldEnd_WithLinearTime() + { + const string markdown = "# __Princes _of the_ Universe__"; + var markdownSb = new StringBuilder(); + for (int i = 0; i < 100000; i++) + { + markdownSb.Append(markdown); + } + + var renderMd = () => { Md.Render(markdownSb.ToString()); }; + + renderMd.ExecutionTime().Should().BeLessThan(10.Seconds()); + } +} \ No newline at end of file diff --git a/cs/MarkdownTests/MarkdownTests.csproj b/cs/MarkdownTests/MarkdownTests.csproj new file mode 100644 index 000000000..7f2fbad5a --- /dev/null +++ b/cs/MarkdownTests/MarkdownTests.csproj @@ -0,0 +1,20 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + + + + + + diff --git a/cs/clean-code.sln b/cs/clean-code.sln index 2206d54db..53cf30e51 100644 --- a/cs/clean-code.sln +++ b/cs/clean-code.sln @@ -9,6 +9,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ControlDigit", "ControlDigi EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Samples", "Samples\Samples.csproj", "{C3EF41D7-50EF-4CE1-B30A-D1D81C93D7FA}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Markdown", "Markdown\Markdown.csproj", "{71168D0A-8E37-49D3-B718-51AD51BDF895}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -27,5 +29,9 @@ 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 + {71168D0A-8E37-49D3-B718-51AD51BDF895}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {71168D0A-8E37-49D3-B718-51AD51BDF895}.Debug|Any CPU.Build.0 = Debug|Any CPU + {71168D0A-8E37-49D3-B718-51AD51BDF895}.Release|Any CPU.ActiveCfg = Release|Any CPU + {71168D0A-8E37-49D3-B718-51AD51BDF895}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal