From f70afaba6494732356e429aabde56a3fcc997b15 Mon Sep 17 00:00:00 2001 From: crycrash Date: Sat, 23 Nov 2024 14:55:37 +0500 Subject: [PATCH 01/14] Added structure of project --- cs/Markdown/ConverterHtml.cs | 9 +++++++++ cs/Markdown/Markdown.csproj | 12 +++++++++++ cs/Markdown/Md.cs | 20 ++++++++++++++++++ cs/Markdown/Tags/BoldTag.cs | 18 +++++++++++++++++ cs/Markdown/Tags/HeadingTag.cs | 18 +++++++++++++++++ cs/Markdown/Tags/ItalicTag.cs | 19 ++++++++++++++++++ cs/Markdown/Tags/ScreenTag.cs | 18 +++++++++++++++++ cs/Markdown/Tags/TypesTags.cs | 9 +++++++++ cs/Markdown/Token.cs | 16 +++++++++++++++ cs/Markdown/Tokenizer.cs | 9 +++++++++ cs/Markdown/TypesToken.cs | 7 +++++++ cs/MarkdownTests/BoldTests.cs | 15 ++++++++++++++ cs/MarkdownTests/BorderlineCases.cs | 15 ++++++++++++++ cs/MarkdownTests/HeadingTests.cs | 15 ++++++++++++++ cs/MarkdownTests/ItalicTests.cs | 15 ++++++++++++++ cs/MarkdownTests/MarkdownTests.csproj | 29 +++++++++++++++++++++++++++ cs/MarkdownTests/ScreenTests.cs | 15 ++++++++++++++ cs/clean-code.sln | 16 +++++++++++++++ 18 files changed, 275 insertions(+) create mode 100644 cs/Markdown/ConverterHtml.cs create mode 100644 cs/Markdown/Markdown.csproj create mode 100644 cs/Markdown/Md.cs create mode 100644 cs/Markdown/Tags/BoldTag.cs create mode 100644 cs/Markdown/Tags/HeadingTag.cs create mode 100644 cs/Markdown/Tags/ItalicTag.cs create mode 100644 cs/Markdown/Tags/ScreenTag.cs create mode 100644 cs/Markdown/Tags/TypesTags.cs create mode 100644 cs/Markdown/Token.cs create mode 100644 cs/Markdown/Tokenizer.cs create mode 100644 cs/Markdown/TypesToken.cs create mode 100644 cs/MarkdownTests/BoldTests.cs create mode 100644 cs/MarkdownTests/BorderlineCases.cs create mode 100644 cs/MarkdownTests/HeadingTests.cs create mode 100644 cs/MarkdownTests/ItalicTests.cs create mode 100644 cs/MarkdownTests/MarkdownTests.csproj create mode 100644 cs/MarkdownTests/ScreenTests.cs diff --git a/cs/Markdown/ConverterHtml.cs b/cs/Markdown/ConverterHtml.cs new file mode 100644 index 000000000..415ac52be --- /dev/null +++ b/cs/Markdown/ConverterHtml.cs @@ -0,0 +1,9 @@ +namespace Markdown; + +public class ConverterHtml +{ + public string resultingString = ""; + public string ConvertTokens(List tokens){ + throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/cs/Markdown/Markdown.csproj b/cs/Markdown/Markdown.csproj new file mode 100644 index 000000000..5877cd731 --- /dev/null +++ b/cs/Markdown/Markdown.csproj @@ -0,0 +1,12 @@ + + + + net8.0 + enable + enable + + false + false + Exe + + \ No newline at end of file diff --git a/cs/Markdown/Md.cs b/cs/Markdown/Md.cs new file mode 100644 index 000000000..38f9ffbb2 --- /dev/null +++ b/cs/Markdown/Md.cs @@ -0,0 +1,20 @@ +namespace Markdown; + +public class Md +{ + public string Render(string markdownString){ + var tokens = ParseToTokens(markdownString); + var result = ProcessingTokens(tokens); + return result; + } + + private List ParseToTokens(string markdownString){ + var tokenizer = new Tokenizer().Tokenize(markdownString); + return tokenizer; + } + + private string ProcessingTokens(List tokens){ + var processingString = new ConverterHtml().ConvertTokens(tokens); + return processingString; + } +} \ No newline at end of file diff --git a/cs/Markdown/Tags/BoldTag.cs b/cs/Markdown/Tags/BoldTag.cs new file mode 100644 index 000000000..26ca83a00 --- /dev/null +++ b/cs/Markdown/Tags/BoldTag.cs @@ -0,0 +1,18 @@ +namespace Markdown.Tags; + +public class BoldTag +{ + public BoldTag(string markdownString, int index) + { + Content = markdownString; + Index = index; + } + + public TypesTags TypeTag = TypesTags.Bold; + public string Content; + public int Index; + private string symbol = "__"; + public bool CheckIsBoldTag(){ + throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/cs/Markdown/Tags/HeadingTag.cs b/cs/Markdown/Tags/HeadingTag.cs new file mode 100644 index 000000000..4b75c2ec3 --- /dev/null +++ b/cs/Markdown/Tags/HeadingTag.cs @@ -0,0 +1,18 @@ +namespace Markdown.Tags; + +public class HeadingTag +{ + public HeadingTag(string markdownString, int index) + { + Content = markdownString; + Index = index; + } + + public TypesTags TypeTag = TypesTags.Heading; + public string Content; + public int Index; + private string symbol = "#"; + public bool CheckIsHeadingTag(){ + throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/cs/Markdown/Tags/ItalicTag.cs b/cs/Markdown/Tags/ItalicTag.cs new file mode 100644 index 000000000..57affa3e1 --- /dev/null +++ b/cs/Markdown/Tags/ItalicTag.cs @@ -0,0 +1,19 @@ +namespace Markdown.Tags; + +public class ItalicTag +{ + public ItalicTag(string markdownString, int index) + { + Content = markdownString; + Index = index; + } + + public TypesTags TypeTag = TypesTags.Italic; + public string Content; + public int Index; + private string symbol = "_"; + + public bool CheckIsItalicTag(){ + throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/cs/Markdown/Tags/ScreenTag.cs b/cs/Markdown/Tags/ScreenTag.cs new file mode 100644 index 000000000..f406955b1 --- /dev/null +++ b/cs/Markdown/Tags/ScreenTag.cs @@ -0,0 +1,18 @@ +namespace Markdown.Tags; + +public class ScreenTag +{ + public ScreenTag(string markdownString, int index) + { + Content = markdownString; + Index = index; + } + + public TypesTags TypeTag = TypesTags.Screen; + public string Content; + public int Index; + private string symbol = "\\"; + public bool CheckIsScreenTag(){ + throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/cs/Markdown/Tags/TypesTags.cs b/cs/Markdown/Tags/TypesTags.cs new file mode 100644 index 000000000..7c4674700 --- /dev/null +++ b/cs/Markdown/Tags/TypesTags.cs @@ -0,0 +1,9 @@ +namespace Markdown.Tags; + +public enum TypesTags +{ + Bold, + Italic, + Heading, + Screen +} \ No newline at end of file diff --git a/cs/Markdown/Token.cs b/cs/Markdown/Token.cs new file mode 100644 index 000000000..348a2662c --- /dev/null +++ b/cs/Markdown/Token.cs @@ -0,0 +1,16 @@ +namespace Markdown; + +public class Token +{ + public Token(string markdownString, int index, TypesToken typeToken) + { + Content = markdownString; + Index = index; + TypeToken = typeToken; + } + + public TypesToken TypeToken; + public string Content; + public int Index; + public List NestedTags = new List(); +} diff --git a/cs/Markdown/Tokenizer.cs b/cs/Markdown/Tokenizer.cs new file mode 100644 index 000000000..b7d05e7dd --- /dev/null +++ b/cs/Markdown/Tokenizer.cs @@ -0,0 +1,9 @@ +namespace Markdown; + +public class Tokenizer +{ + public List tokens = new List(); + public List Tokenize(string markdownString){ + throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/cs/Markdown/TypesToken.cs b/cs/Markdown/TypesToken.cs new file mode 100644 index 000000000..7e2007432 --- /dev/null +++ b/cs/Markdown/TypesToken.cs @@ -0,0 +1,7 @@ +namespace Markdown; + +public enum TypesToken +{ + Tag, + Text +} \ No newline at end of file diff --git a/cs/MarkdownTests/BoldTests.cs b/cs/MarkdownTests/BoldTests.cs new file mode 100644 index 000000000..486e5e87e --- /dev/null +++ b/cs/MarkdownTests/BoldTests.cs @@ -0,0 +1,15 @@ +namespace MarkdownTests; + +public class BoldTests +{ + [SetUp] + public void Setup() + { + } + + [Test] + public void Test1() + { + Assert.Pass(); + } +} \ No newline at end of file diff --git a/cs/MarkdownTests/BorderlineCases.cs b/cs/MarkdownTests/BorderlineCases.cs new file mode 100644 index 000000000..8659ea2ee --- /dev/null +++ b/cs/MarkdownTests/BorderlineCases.cs @@ -0,0 +1,15 @@ +namespace MarkdownTests; + +public class BorderlineCasesTests +{ + [SetUp] + public void Setup() + { + } + + [Test] + public void Test1() + { + Assert.Pass(); + } +} \ No newline at end of file diff --git a/cs/MarkdownTests/HeadingTests.cs b/cs/MarkdownTests/HeadingTests.cs new file mode 100644 index 000000000..ed38b51db --- /dev/null +++ b/cs/MarkdownTests/HeadingTests.cs @@ -0,0 +1,15 @@ +namespace MarkdownTests; + +public class HeadingTests +{ + [SetUp] + public void Setup() + { + } + + [Test] + public void Test1() + { + Assert.Pass(); + } +} \ No newline at end of file diff --git a/cs/MarkdownTests/ItalicTests.cs b/cs/MarkdownTests/ItalicTests.cs new file mode 100644 index 000000000..35c8585d5 --- /dev/null +++ b/cs/MarkdownTests/ItalicTests.cs @@ -0,0 +1,15 @@ +namespace MarkdownTests; + +public class ItailcTests +{ + [SetUp] + public void Setup() + { + } + + [Test] + public void Test1() + { + Assert.Pass(); + } +} \ No newline at end of file diff --git a/cs/MarkdownTests/MarkdownTests.csproj b/cs/MarkdownTests/MarkdownTests.csproj new file mode 100644 index 000000000..25c84e0f0 --- /dev/null +++ b/cs/MarkdownTests/MarkdownTests.csproj @@ -0,0 +1,29 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + + + + + + + + + + + + + diff --git a/cs/MarkdownTests/ScreenTests.cs b/cs/MarkdownTests/ScreenTests.cs new file mode 100644 index 000000000..234626e85 --- /dev/null +++ b/cs/MarkdownTests/ScreenTests.cs @@ -0,0 +1,15 @@ +namespace MarkdownTests; + +public class ScreenTests +{ + [SetUp] + public void Setup() + { + } + + [Test] + public void Test1() + { + Assert.Pass(); + } +} \ No newline at end of file diff --git a/cs/clean-code.sln b/cs/clean-code.sln index 2206d54db..9c5ca0a4c 100644 --- a/cs/clean-code.sln +++ b/cs/clean-code.sln @@ -9,6 +9,10 @@ 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", "{58D91A68-12CD-4CF1-B593-2F0A0F3B0F05}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MarkdownTests", "MarkdownTests\MarkdownTests.csproj", "{70BA5D76-422C-4B82-ADA9-CC244D68468F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -27,5 +31,17 @@ 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 + {58D91A68-12CD-4CF1-B593-2F0A0F3B0F05}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {58D91A68-12CD-4CF1-B593-2F0A0F3B0F05}.Debug|Any CPU.Build.0 = Debug|Any CPU + {58D91A68-12CD-4CF1-B593-2F0A0F3B0F05}.Release|Any CPU.ActiveCfg = Release|Any CPU + {58D91A68-12CD-4CF1-B593-2F0A0F3B0F05}.Release|Any CPU.Build.0 = Release|Any CPU + {F73079E3-EC6D-4300-B54D-977A5FB3B5A8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F73079E3-EC6D-4300-B54D-977A5FB3B5A8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F73079E3-EC6D-4300-B54D-977A5FB3B5A8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F73079E3-EC6D-4300-B54D-977A5FB3B5A8}.Release|Any CPU.Build.0 = Release|Any CPU + {70BA5D76-422C-4B82-ADA9-CC244D68468F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {70BA5D76-422C-4B82-ADA9-CC244D68468F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {70BA5D76-422C-4B82-ADA9-CC244D68468F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {70BA5D76-422C-4B82-ADA9-CC244D68468F}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal From f3b8fc898fa69654945aa75b6776f4debf3e75d9 Mon Sep 17 00:00:00 2001 From: crycrash Date: Sun, 24 Nov 2024 15:18:37 +0500 Subject: [PATCH 02/14] Added tag processing --- cs/Chess/ChessProblem.cs | 36 ++++++------- cs/ControlDigit/ControlDigit.csproj | 14 +++-- cs/ControlDigit/Snils/SnilsExtensions.cs | 59 +++++++++++++++++++-- cs/ControlDigit/Upc/UpcExtensions.cs | 11 +++- cs/Markdown/ConverterHtml.cs | 9 ---- cs/Markdown/HelperFunctions.cs | 67 ++++++++++++++++++++++++ cs/Markdown/Md.cs | 50 +++++++++++++----- cs/Markdown/Tags/BoldTag.cs | 43 ++++++++++----- cs/Markdown/Tags/HeadingTag.cs | 53 ++++++++++++++----- cs/Markdown/Tags/ITagHandler.cs | 7 +++ cs/Markdown/Tags/ItalicTag.cs | 43 ++++++++++----- cs/Markdown/Tags/ScreenTag.cs | 18 ------- cs/Markdown/Token.cs | 16 ------ cs/Markdown/Tokenizer.cs | 9 ---- cs/Markdown/TypesToken.cs | 7 --- cs/MarkdownTests/BoldTests.cs | 45 ++++++++++++++-- cs/MarkdownTests/BorderlineCases.cs | 29 ++++++++-- cs/MarkdownTests/HeadingTests.cs | 20 +++++-- cs/MarkdownTests/ItalicTests.cs | 36 +++++++++++-- cs/MarkdownTests/ScreenTests.cs | 16 +++--- 20 files changed, 432 insertions(+), 156 deletions(-) delete mode 100644 cs/Markdown/ConverterHtml.cs create mode 100644 cs/Markdown/HelperFunctions.cs create mode 100644 cs/Markdown/Tags/ITagHandler.cs delete mode 100644 cs/Markdown/Tags/ScreenTag.cs delete mode 100644 cs/Markdown/Token.cs delete mode 100644 cs/Markdown/Tokenizer.cs delete mode 100644 cs/Markdown/TypesToken.cs diff --git a/cs/Chess/ChessProblem.cs b/cs/Chess/ChessProblem.cs index f0d6ab430..fe56611a0 100644 --- a/cs/Chess/ChessProblem.cs +++ b/cs/Chess/ChessProblem.cs @@ -1,4 +1,6 @@ -namespace Chess +using System.Drawing; + +namespace Chess { public class ChessProblem { @@ -13,21 +15,22 @@ public static void LoadFrom(string[] lines) // Определяет мат, шах или пат белым. public static void CalculateChessStatus() { - var isCheck = IsCheckForWhite(); + var isCheck = IsCheck(PieceColor.White); var hasMoves = false; foreach (var locFrom in board.GetPieces(PieceColor.White)) { foreach (var locTo in board.GetPiece(locFrom).GetMoves(locFrom, board)) { - var old = board.GetPiece(locTo); - board.Set(locTo, board.GetPiece(locFrom)); - board.Set(locFrom, null); - if (!IsCheckForWhite()) + var previousPosition = board.GetPiece(locTo); + using var tempMove = board.PerformTemporaryMove(locFrom, locTo); + if (!IsCheck(PieceColor.White)) hasMoves = true; - board.Set(locFrom, board.GetPiece(locTo)); - board.Set(locTo, old); } } + SetChessStatus(isCheck, hasMoves); + } + + private static void SetChessStatus(bool isCheck, bool hasMoves){ if (isCheck) if (hasMoves) ChessStatus = ChessStatus.Check; @@ -37,21 +40,18 @@ public static void CalculateChessStatus() } // check — это шах - private static bool IsCheckForWhite() + private static bool IsCheck(PieceColor color) { - var isCheck = false; - foreach (var loc in board.GetPieces(PieceColor.Black)) + var oppositeColor = (color == PieceColor.White) ? PieceColor.Black : PieceColor.White; + foreach (var locFrom in board.GetPieces(oppositeColor)) { - var piece = board.GetPiece(loc); - var moves = piece.GetMoves(loc, board); - foreach (var destination in moves) + foreach (var locTo in board.GetPiece(locFrom).GetMoves(locFrom, board)) { - if (Piece.Is(board.GetPiece(destination), - PieceColor.White, PieceType.King)) - isCheck = true; + if (Piece.Is(board.GetPiece(locTo), + color, PieceType.King)) + return true; } } - if (isCheck) return true; return false; } } diff --git a/cs/ControlDigit/ControlDigit.csproj b/cs/ControlDigit/ControlDigit.csproj index 863b30396..5177d2fff 100644 --- a/cs/ControlDigit/ControlDigit.csproj +++ b/cs/ControlDigit/ControlDigit.csproj @@ -1,15 +1,23 @@ - 8 + 6 net6 false - + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + - + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + diff --git a/cs/ControlDigit/Snils/SnilsExtensions.cs b/cs/ControlDigit/Snils/SnilsExtensions.cs index e4dd90326..402cee909 100644 --- a/cs/ControlDigit/Snils/SnilsExtensions.cs +++ b/cs/ControlDigit/Snils/SnilsExtensions.cs @@ -1,12 +1,65 @@ using System; +using System.Collections; +using System.Collections.Generic; +using System.Threading.Tasks; +using NUnit.Framework.Internal.Execution; +using System.Linq; namespace ControlDigit { public static class SnilsExtensions { - public static int CalculateSnils(this long number) - { - throw new NotImplementedException(); + public static int CalculateSnils(this long number){ + var dict = NumberDecompotion(number); + var M = SummDecompotion(dict); + var result = MakeControlDigit(M); + return result; + } + + private static Dictionary NumberDecompotion(this long number){ + var counter = 1; + var dict = new Dictionary(); + while(number / 10 >= 1){ + dict.Add(counter, (int)number % 10); + counter++; + number = number / 10; + } + dict.Add(counter, (int)number % 10); + return dict; + } + + private static int SummDecompotion(Dictionary dict){ + var summ = 0; + foreach(var pair in dict){ + summ += pair.Key * pair.Value; + } + return summ; + } + + private static IEnumerable MakeFactorsSnils(int length){ + return Enumerable.Range(1, length); + } + + private static IEnumerable MakeFactorsUpc(int length){ + return Enumerable.Range(1, length); + } + + private static int CountControlDigit(IEnumerable values, Func> evaluateFactors, int length){ + var factors = evaluateFactors(length); + var summ = 0; + //for(int i = 0;i < length;i++){ + //summ += factors[i] * values[i]; + //} + var ggygu = factors.Zip(values) + } + + private static int MakeControlDigit(int summ){ + if (summ > 101) + summ = summ % 101; + if (summ < 100) + return summ; + else + return 0; } } } diff --git a/cs/ControlDigit/Upc/UpcExtensions.cs b/cs/ControlDigit/Upc/UpcExtensions.cs index d1c376330..b55f6443f 100644 --- a/cs/ControlDigit/Upc/UpcExtensions.cs +++ b/cs/ControlDigit/Upc/UpcExtensions.cs @@ -6,7 +6,16 @@ public static class UpcExtensions { public static int CalculateUpc(this long number) { - throw new NotImplementedException(); + var numberToString = number.ToString(); + var summ = 0; + for(int i = numberToString.Length - 1;i>=0;i-=2){ + summ += int.Parse(numberToString[i].ToString()) * 3; + } + for(int i = numberToString.Length - 2;i>=0;i-=2){ + summ += int.Parse(numberToString[i].ToString()); + } + var M = (summ % 10 == 0)? 0: 10 - summ % 10; + return M; } } } diff --git a/cs/Markdown/ConverterHtml.cs b/cs/Markdown/ConverterHtml.cs deleted file mode 100644 index 415ac52be..000000000 --- a/cs/Markdown/ConverterHtml.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Markdown; - -public class ConverterHtml -{ - public string resultingString = ""; - public string ConvertTokens(List tokens){ - throw new NotImplementedException(); - } -} \ No newline at end of file diff --git a/cs/Markdown/HelperFunctions.cs b/cs/Markdown/HelperFunctions.cs new file mode 100644 index 000000000..1301baf81 --- /dev/null +++ b/cs/Markdown/HelperFunctions.cs @@ -0,0 +1,67 @@ + + +using Microsoft.VisualBasic; + +namespace Markdown; + +public class HelperFunctions +{ + private static char[] forbiddenChars = {'_', '#'}; + public static int ScreeningCheck(ref string text, int startIndex) + { + if (IsScreening(text, startIndex)) + { + int endIndex = text.IndexOf('\\', startIndex + 1); + if (endIndex == -1) + { + return startIndex + 1; + } + text = text.Remove(startIndex - 1, 1).Remove(endIndex - 1, 1); + return endIndex - 1; + } + return startIndex; + } + + private static bool IsScreening(string text, int index) => + index - 1 >= 0 && text[index - 1] == '\\'; + + public static int FindCorrectCloseSymbolForItalic(string markdownString, int startIndex) + { + for (int i = startIndex + 1; i < markdownString.Length; i++) + { + if (markdownString[i] == '_') + { + if (i > 0 && markdownString[i - 1] == '\\') + continue; + if (i + 1 < markdownString.Length && markdownString[i + 1] == '_') + continue; + if (i - 1 > 0 && markdownString[i - 1] == '_') + continue; + if (i > 0 && char.IsWhiteSpace(markdownString[i - 1])) + continue; + if (i + 1 < markdownString.Length && char.IsWhiteSpace(markdownString[i + 1])) + continue; + if (HelperFunctions.IsDigit(markdownString, i)) + continue; + return i; + } + } + return -1; + } + public static bool IsDigit(string text, int index) => + index - 1 >= 0 && char.IsDigit(text[index - 1]); + + + public static string ProcessNestedTag(ref string text) + { + text = Md.Render(text); + return text; + } + + public static bool ContainsWhiteSpaces(string text) => + text.Contains(" ") || string.IsNullOrWhiteSpace(text); + + public static bool ContainsOnlyDash(string text) => + text.All(symbol => forbiddenChars.Contains(symbol)); + +} \ No newline at end of file diff --git a/cs/Markdown/Md.cs b/cs/Markdown/Md.cs index 38f9ffbb2..d77cd4ccd 100644 --- a/cs/Markdown/Md.cs +++ b/cs/Markdown/Md.cs @@ -1,20 +1,44 @@ namespace Markdown; +using Markdown.Tags; -public class Md +public static class Md { - public string Render(string markdownString){ - var tokens = ParseToTokens(markdownString); - var result = ProcessingTokens(tokens); - return result; - } - - private List ParseToTokens(string markdownString){ - var tokenizer = new Tokenizer().Tokenize(markdownString); - return tokenizer; + public static void Main(){ + } + private static readonly List tagHandlers = new List + { + new BoldTag(), + new ItalicTag(), + new HeadingTag() + }; + + public static string Render(string markdownString) + { + int index = 0; + if (HelperFunctions.ContainsOnlyDash(markdownString)) + return markdownString; - private string ProcessingTokens(List tokens){ - var processingString = new ConverterHtml().ConvertTokens(tokens); - return processingString; + while (index < markdownString.Length) + { + bool tagProcessed = false; + if (!char.IsLetter(markdownString[index])) + { + foreach (var handler in tagHandlers) + { + if (handler.IsTagStart(markdownString, index)) + { + index = handler.ProcessTag(ref markdownString, index); + tagProcessed = true; + break; + } + } + } + if (!tagProcessed) + { + index++; + } + } + return markdownString; } } \ No newline at end of file diff --git a/cs/Markdown/Tags/BoldTag.cs b/cs/Markdown/Tags/BoldTag.cs index 26ca83a00..310a4bf5c 100644 --- a/cs/Markdown/Tags/BoldTag.cs +++ b/cs/Markdown/Tags/BoldTag.cs @@ -1,18 +1,37 @@ namespace Markdown.Tags; - -public class BoldTag +public class BoldTag : ITagHandler { - public BoldTag(string markdownString, int index) + private const string Symbols = "__"; + private const string HtmlTag = "strong"; + + + public bool IsTagStart(string text, int index) { - Content = markdownString; - Index = index; + return index + 2 < text.Length && text.Substring(index, 2) == Symbols + && !char.IsWhiteSpace(text[index + 1]); + } + public int ProcessTag(ref string text, int startIndex) + { + var newIndex = HelperFunctions.ScreeningCheck(ref text, startIndex); + if (newIndex == startIndex) + { + int endIndex = text.IndexOf(Symbols, startIndex + 1); + + if (endIndex == -1) + return startIndex + 2; - public TypesTags TypeTag = TypesTags.Bold; - public string Content; - public int Index; - private string symbol = "__"; - public bool CheckIsBoldTag(){ - throw new NotImplementedException(); + string content = text.Substring(startIndex + 2, endIndex - startIndex - 2); + if (HelperFunctions.ContainsWhiteSpaces(content)) + return startIndex + content.Length; + content = HelperFunctions.ProcessNestedTag(ref content); + string replacement = $"<{HtmlTag}>{content}"; + text = text.Substring(0, startIndex) + replacement + text.Substring(endIndex + 2); + return startIndex + replacement.Length; + } + else + { + return newIndex; + } } -} \ No newline at end of file +} diff --git a/cs/Markdown/Tags/HeadingTag.cs b/cs/Markdown/Tags/HeadingTag.cs index 4b75c2ec3..5a84234c9 100644 --- a/cs/Markdown/Tags/HeadingTag.cs +++ b/cs/Markdown/Tags/HeadingTag.cs @@ -1,18 +1,47 @@ namespace Markdown.Tags; - -public class HeadingTag +public class HeadingTag : ITagHandler { - public HeadingTag(string markdownString, int index) + private const string Symbol = "#"; + private const string HtmlTag = "h1"; + + public bool IsTagStart(string text, int index) { - Content = markdownString; - Index = index; + return text[index].ToString() == Symbol; } - public TypesTags TypeTag = TypesTags.Heading; - public string Content; - public int Index; - private string symbol = "#"; - public bool CheckIsHeadingTag(){ - throw new NotImplementedException(); + public int ProcessTag(ref string text, int startIndex) + { + var newIndex = HelperFunctions.ScreeningCheck(ref text, startIndex); + if (newIndex == startIndex) + { + int endIndex = text.IndexOf('\n', startIndex + 1); + string replacement; + string content; + if (endIndex == -1) + { + content = text.Substring(startIndex + 1, text.Length - startIndex - 1); + if (HelperFunctions.ContainsWhiteSpaces(content)) + return startIndex + content.Length; + content = HelperFunctions.ProcessNestedTag(ref content); + replacement = $"<{HtmlTag}>{content}"; + text = text.Substring(0, startIndex) + replacement; + + return startIndex + replacement.Length; + } + + content = text.Substring(startIndex + 1, endIndex - startIndex - 1); + if (HelperFunctions.ContainsWhiteSpaces(content)) + return startIndex + content.Length; + content = HelperFunctions.ProcessNestedTag(ref content); + replacement = $"<{HtmlTag}>{content}" + '\n'; + text = text.Substring(0, startIndex) + replacement + text.Substring(endIndex + 1); + + return startIndex + replacement.Length; + } + else + { + return newIndex; + } + } -} \ No newline at end of file +} diff --git a/cs/Markdown/Tags/ITagHandler.cs b/cs/Markdown/Tags/ITagHandler.cs new file mode 100644 index 000000000..0b15435c3 --- /dev/null +++ b/cs/Markdown/Tags/ITagHandler.cs @@ -0,0 +1,7 @@ +namespace Markdown.Tags; +public interface ITagHandler +{ + bool IsTagStart(string text, int index); + + int ProcessTag(ref string text, int startIndex); +} diff --git a/cs/Markdown/Tags/ItalicTag.cs b/cs/Markdown/Tags/ItalicTag.cs index 57affa3e1..59e38ca38 100644 --- a/cs/Markdown/Tags/ItalicTag.cs +++ b/cs/Markdown/Tags/ItalicTag.cs @@ -1,19 +1,38 @@ namespace Markdown.Tags; - -public class ItalicTag +public class ItalicTag : ITagHandler { - public ItalicTag(string markdownString, int index) + private const string Symbol = "_"; + private const string HtmlTag = "em"; + + public bool IsTagStart(string text, int index) { - Content = markdownString; - Index = index; + return text[index].ToString() == Symbol + && index + 1 < text.Length + && !char.IsWhiteSpace(text[index + 1]); } - public TypesTags TypeTag = TypesTags.Italic; - public string Content; - public int Index; - private string symbol = "_"; + public int ProcessTag(ref string text, int startIndex) + { + + var newIndex = HelperFunctions.ScreeningCheck(ref text, startIndex); + if (newIndex == startIndex) + { + int endIndex = HelperFunctions.FindCorrectCloseSymbolForItalic(text, startIndex + 1); + + if (endIndex == -1) + return startIndex + 1; + + string content = text.Substring(startIndex + 1, endIndex - startIndex - 1); + if (HelperFunctions.ContainsWhiteSpaces(content)) + return startIndex + content.Length; + string replacement = $"<{HtmlTag}>{content}"; + text = text.Substring(0, startIndex) + replacement + text.Substring(endIndex + 1); - public bool CheckIsItalicTag(){ - throw new NotImplementedException(); + return startIndex + replacement.Length; + } + else + { + return newIndex; + } } -} \ No newline at end of file +} diff --git a/cs/Markdown/Tags/ScreenTag.cs b/cs/Markdown/Tags/ScreenTag.cs deleted file mode 100644 index f406955b1..000000000 --- a/cs/Markdown/Tags/ScreenTag.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace Markdown.Tags; - -public class ScreenTag -{ - public ScreenTag(string markdownString, int index) - { - Content = markdownString; - Index = index; - } - - public TypesTags TypeTag = TypesTags.Screen; - public string Content; - public int Index; - private string symbol = "\\"; - public bool CheckIsScreenTag(){ - throw new NotImplementedException(); - } -} \ No newline at end of file diff --git a/cs/Markdown/Token.cs b/cs/Markdown/Token.cs deleted file mode 100644 index 348a2662c..000000000 --- a/cs/Markdown/Token.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace Markdown; - -public class Token -{ - public Token(string markdownString, int index, TypesToken typeToken) - { - Content = markdownString; - Index = index; - TypeToken = typeToken; - } - - public TypesToken TypeToken; - public string Content; - public int Index; - public List NestedTags = new List(); -} diff --git a/cs/Markdown/Tokenizer.cs b/cs/Markdown/Tokenizer.cs deleted file mode 100644 index b7d05e7dd..000000000 --- a/cs/Markdown/Tokenizer.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Markdown; - -public class Tokenizer -{ - public List tokens = new List(); - public List Tokenize(string markdownString){ - throw new NotImplementedException(); - } -} \ No newline at end of file diff --git a/cs/Markdown/TypesToken.cs b/cs/Markdown/TypesToken.cs deleted file mode 100644 index 7e2007432..000000000 --- a/cs/Markdown/TypesToken.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Markdown; - -public enum TypesToken -{ - Tag, - Text -} \ No newline at end of file diff --git a/cs/MarkdownTests/BoldTests.cs b/cs/MarkdownTests/BoldTests.cs index 486e5e87e..06c7a1c0c 100644 --- a/cs/MarkdownTests/BoldTests.cs +++ b/cs/MarkdownTests/BoldTests.cs @@ -1,15 +1,52 @@ +using FluentAssertions; +using Markdown; + namespace MarkdownTests; public class BoldTests { - [SetUp] - public void Setup() + [Test] + public void Test_StandartBoldWord() + { + Md.Render("__WORD__").Should().Be("WORD"); + Md.Render("__aaaa__").Should().Be("aaaa"); + } + + [Test] + public void Test_StandartBoldWords() + { + Md.Render("__WORD__ BOB __PON__").Should().Be("WORD BOB PON"); + Md.Render("__aaaa__bbbb__cc__").Should().Be("aaaabbbbcc"); + } + + [Test] + public void Test_BoldWithOtherTags() { + Md.Render("#__aaa__").Should().Be("

aaa

"); + Md.Render("__aaa_b_a__").Should().Be("aaaba"); } [Test] - public void Test1() + public void Test_BoldInPartOfWord() { - Assert.Pass(); + Md.Render("__нач__але").Should().Be("начале"); + Md.Render("сер__еди__не").Should().Be("середине"); + Md.Render("кон__це.__").Should().Be("конце."); } + + [Test] + public void Test_BoldSeveralWords() + { + Md.Render("__нач__але").Should().Be("начале"); + Md.Render("сер__еди__не").Should().Be("середине"); + Md.Render("кон__це.__").Should().Be("конце."); + } + + [Test] + public void Test_BoldTextWithSpaces() + { + Md.Render("__ подчерки__").Should().Be("__ подчерки__"); + Md.Render("__подчерки __").Should().Be("__подчерки __"); + } + } \ No newline at end of file diff --git a/cs/MarkdownTests/BorderlineCases.cs b/cs/MarkdownTests/BorderlineCases.cs index 8659ea2ee..29cfe673e 100644 --- a/cs/MarkdownTests/BorderlineCases.cs +++ b/cs/MarkdownTests/BorderlineCases.cs @@ -1,15 +1,36 @@ +using FluentAssertions; +using Markdown; namespace MarkdownTests; public class BorderlineCasesTests { - [SetUp] - public void Setup() + + [Test] + public void Test_TextWithDigits() + { + Md.Render("_aaa12_3_a_").Should().Be("aaa12_3_a"); + Md.Render("123_44__9").Should().Be("123_44__9"); + } + + [Test] + public void Test_TagsWithoutText() + { + Md.Render("____").Should().Be("____"); + Md.Render("___").Should().Be("___"); + Md.Render("__").Should().Be("__"); + Md.Render("_").Should().Be("_"); + } + [Test] + public void Test_TextWithUnpairedTags() { + Md.Render("__Непарные_").Should().Be("__Непарные_"); + Md.Render("_Непарные__").Should().Be("_Непарные__"); } [Test] - public void Test1() + public void Test_IntersectionOfTags() { - Assert.Pass(); + Md.Render("__пересечения _двойных__ и одинарных_").Should().Be("__пересечения _двойных__ и одинарных_"); + Md.Render("_пересечения __двойных_ и одинарных__").Should().Be("_пересечения __двойных_ и одинарных__"); } } \ No newline at end of file diff --git a/cs/MarkdownTests/HeadingTests.cs b/cs/MarkdownTests/HeadingTests.cs index ed38b51db..c2d46dc05 100644 --- a/cs/MarkdownTests/HeadingTests.cs +++ b/cs/MarkdownTests/HeadingTests.cs @@ -1,15 +1,27 @@ +using Markdown; +using FluentAssertions; + namespace MarkdownTests; public class HeadingTests { - [SetUp] - public void Setup() + [Test] + public void Test_StandartHeading() + { + Md.Render("#aaaa").Should().Be("

aaaa

"); + Md.Render("#aaa" + '\n' + "bbb").Should().Be("

aaa

" + '\n' + "bbb"); + Md.Render("#aaa" + '\n' + "#bbb").Should().Be("

aaa

" + '\n' + "

bbb

"); + } + [Test] + public void Test_HeadingTagsWithoutText() { + Md.Render("#").Should().Be("#"); + Md.Render("##").Should().Be("##"); } [Test] - public void Test1() + public void Test_HeadingWithOtherTags() { - Assert.Pass(); + Md.Render("#__a__" + "\n" + "#_b_").Should().Be("

a

" + '\n' + "

b

"); } } \ No newline at end of file diff --git a/cs/MarkdownTests/ItalicTests.cs b/cs/MarkdownTests/ItalicTests.cs index 35c8585d5..dfb65f517 100644 --- a/cs/MarkdownTests/ItalicTests.cs +++ b/cs/MarkdownTests/ItalicTests.cs @@ -1,15 +1,43 @@ +using Markdown; +using FluentAssertions; namespace MarkdownTests; public class ItailcTests { - [SetUp] - public void Setup() + [Test] + public void Test_StandartItalicText() + { + Md.Render("_aaaa_").Should().Be("aaaa"); + Md.Render("_aaaa_bbbb_cc_").Should().Be("aaaabbbbcc"); + Md.Render("#_aaa_").Should().Be("

aaa

"); + } + + [Test] + public void Test_NestedInItalicTags() + { + Md.Render("_aaa__b__a_").Should().Be("aaa__b__a"); + + } + [Test] + public void Test_ItalicPartOfWord() + { + Md.Render("_нач_але").Should().Be("начале"); + Md.Render("сер_еди_не").Should().Be("середине"); + Md.Render("кон_це._").Should().Be("конце."); + } + + [Test] + public void Test_ItalicSeveralWords() { + Md.Render("ра_зных сл_овах").Should().Be("ра_зных сл_овах"); + Md.Render("ра__зных сл__овах").Should().Be("ра__зных сл__овах"); + Md.Render("кон_це._").Should().Be("конце."); } [Test] - public void Test1() + public void Test_ItalicTextWithSpaces() { - Assert.Pass(); + Md.Render("_ подчерки_").Should().Be("_ подчерки_"); + Md.Render("_подчерки _").Should().Be("_подчерки _"); } } \ No newline at end of file diff --git a/cs/MarkdownTests/ScreenTests.cs b/cs/MarkdownTests/ScreenTests.cs index 234626e85..344598d74 100644 --- a/cs/MarkdownTests/ScreenTests.cs +++ b/cs/MarkdownTests/ScreenTests.cs @@ -1,15 +1,17 @@ +using Markdown; +using FluentAssertions; namespace MarkdownTests; public class ScreenTests { - [SetUp] - public void Setup() - { - } - [Test] - public void Test1() + public void Test_BoldWithТestedItalic() { - Assert.Pass(); + Md.Render("\\aaa").Should().Be("\\aaa"); + Md.Render("\\__aaa__\\").Should().Be("__aaa__"); + Md.Render("\\_aaa_\\").Should().Be("_aaa_"); + Md.Render("\\#aaa\\").Should().Be("#aaa"); + Md.Render("\\\\#aa\\").Should().Be("\\#aa"); + Md.Render("\\#aa").Should().Be("\\#aa"); } } \ No newline at end of file From 2f9afb40561f988fc0454473e1384a5337371a7b Mon Sep 17 00:00:00 2001 From: crycrash Date: Sun, 1 Dec 2024 13:34:52 +0500 Subject: [PATCH 03/14] Improved algorithm, eliminated code duplication --- cs/Markdown/HelperFunctions.cs | 56 ++++++++++++----------------- cs/Markdown/Markdown.csproj | 1 - cs/Markdown/Md.cs | 58 +++++++++++++++--------------- cs/Markdown/Tags/BaseTagHandler.cs | 56 +++++++++++++++++++++++++++++ cs/Markdown/Tags/BoldTag.cs | 39 ++++---------------- cs/Markdown/Tags/HeadingTag.cs | 47 +++++++----------------- cs/Markdown/Tags/ItalicTag.cs | 39 +++++++------------- 7 files changed, 138 insertions(+), 158 deletions(-) create mode 100644 cs/Markdown/Tags/BaseTagHandler.cs diff --git a/cs/Markdown/HelperFunctions.cs b/cs/Markdown/HelperFunctions.cs index 1301baf81..c0d0d09de 100644 --- a/cs/Markdown/HelperFunctions.cs +++ b/cs/Markdown/HelperFunctions.cs @@ -1,67 +1,55 @@ - - -using Microsoft.VisualBasic; - namespace Markdown; public class HelperFunctions { - private static char[] forbiddenChars = {'_', '#'}; + private static readonly char[] forbiddenChars = ['_', '#']; public static int ScreeningCheck(ref string text, int startIndex) { if (IsScreening(text, startIndex)) { int endIndex = text.IndexOf('\\', startIndex + 1); if (endIndex == -1) - { return startIndex + 1; - } text = text.Remove(startIndex - 1, 1).Remove(endIndex - 1, 1); return endIndex - 1; } return startIndex; } - private static bool IsScreening(string text, int index) => - index - 1 >= 0 && text[index - 1] == '\\'; - - public static int FindCorrectCloseSymbolForItalic(string markdownString, int startIndex) + public static int FindCorrectCloseSymbolForItalic(string text, int startIndex) { - for (int i = startIndex + 1; i < markdownString.Length; i++) + for (int i = startIndex + 1; i < text.Length; i++) { - if (markdownString[i] == '_') + if (text[i] == '_' && !IsScreening(text, i) + && !IsPartOfDoubleUnderscore(text, i) && !IsSurroundedByWhitespaceOrDigit(text, i)) { - if (i > 0 && markdownString[i - 1] == '\\') - continue; - if (i + 1 < markdownString.Length && markdownString[i + 1] == '_') - continue; - if (i - 1 > 0 && markdownString[i - 1] == '_') - continue; - if (i > 0 && char.IsWhiteSpace(markdownString[i - 1])) - continue; - if (i + 1 < markdownString.Length && char.IsWhiteSpace(markdownString[i + 1])) - continue; - if (HelperFunctions.IsDigit(markdownString, i)) - continue; return i; } } return -1; } + + private static bool IsScreening(string text, int index) => + index - 1 >= 0 && text[index - 1] == '\\'; + public static bool IsDigit(string text, int index) => index - 1 >= 0 && char.IsDigit(text[index - 1]); - - public static string ProcessNestedTag(ref string text) - { - text = Md.Render(text); - return text; - } + public static string ProcessNestedTag(ref string text) => + Md.Render(text); public static bool ContainsWhiteSpaces(string text) => - text.Contains(" ") || string.IsNullOrWhiteSpace(text); + text.Contains(' ') || string.IsNullOrWhiteSpace(text); public static bool ContainsOnlyDash(string text) => - text.All(symbol => forbiddenChars.Contains(symbol)); + text.All(symbol => forbiddenChars.Contains(symbol)); + + private static bool IsPartOfDoubleUnderscore(string text, int index) => + (index + 1 < text.Length && text[index + 1] == '_') || + (index > 0 && text[index - 1] == '_'); -} \ No newline at end of file + private static bool IsSurroundedByWhitespaceOrDigit(string text, int index) => + (index > 0 && char.IsWhiteSpace(text[index - 1])) || + (index + 1 < text.Length && char.IsWhiteSpace(text[index + 1])) || + (index > 0 && char.IsDigit(text[index - 1])); +} diff --git a/cs/Markdown/Markdown.csproj b/cs/Markdown/Markdown.csproj index 5877cd731..cb9d759b7 100644 --- a/cs/Markdown/Markdown.csproj +++ b/cs/Markdown/Markdown.csproj @@ -7,6 +7,5 @@ false false - Exe \ No newline at end of file diff --git a/cs/Markdown/Md.cs b/cs/Markdown/Md.cs index d77cd4ccd..89cbe50d3 100644 --- a/cs/Markdown/Md.cs +++ b/cs/Markdown/Md.cs @@ -1,44 +1,42 @@ -namespace Markdown; using Markdown.Tags; -public static class Md +namespace Markdown; + +public class Md { - public static void Main(){ - - } - private static readonly List tagHandlers = new List - { - new BoldTag(), - new ItalicTag(), - new HeadingTag() - }; + private static readonly List TagHandlers = + [ + new BoldTag(), + new ItalicTag(), + new HeadingTag() + ]; public static string Render(string markdownString) { - int index = 0; - if (HelperFunctions.ContainsOnlyDash(markdownString)) + if (string.IsNullOrEmpty(markdownString) || HelperFunctions.ContainsOnlyDash(markdownString)) return markdownString; + int index = 0; while (index < markdownString.Length) { - bool tagProcessed = false; - if (!char.IsLetter(markdownString[index])) - { - foreach (var handler in tagHandlers) - { - if (handler.IsTagStart(markdownString, index)) - { - index = handler.ProcessTag(ref markdownString, index); - tagProcessed = true; - break; - } - } - } - if (!tagProcessed) + if (TryProcessTag(ref markdownString, ref index)) + continue; + index++; + } + + return markdownString; + } + + private static bool TryProcessTag(ref string markdownString, ref int index) + { + foreach (var handler in TagHandlers) + { + if (handler.IsTagStart(markdownString, index)) { - index++; + index = handler.ProcessTag(ref markdownString, index); + return true; } } - return markdownString; + return false; } -} \ No newline at end of file +} diff --git a/cs/Markdown/Tags/BaseTagHandler.cs b/cs/Markdown/Tags/BaseTagHandler.cs new file mode 100644 index 000000000..a0bd3e883 --- /dev/null +++ b/cs/Markdown/Tags/BaseTagHandler.cs @@ -0,0 +1,56 @@ +namespace Markdown.Tags; + +public abstract class BaseTagHandler : ITagHandler +{ + protected abstract string Symbol { get; } + protected abstract string HtmlTag { get; } + + public abstract bool IsTagStart(string text, int index); + + public int ProcessTag(ref string text, int startIndex) + { + var newIndex = HelperFunctions.ScreeningCheck(ref text, startIndex); + if (newIndex != startIndex) + return newIndex; + + int endIndex = FindEndIndex(text, startIndex); + + if (endIndex == -1) + return startIndex + Symbol.Length; + + string content = ExtractContent(text, startIndex, endIndex); + if (HelperFunctions.ContainsWhiteSpaces(content)) + return startIndex + content.Length; + + content = ProcessNestedTag(ref content); + string replacement = WrapWithHtmlTag(content); + text = ReplaceText(text, startIndex, endIndex, replacement); + + return startIndex + replacement.Length; + } + + protected virtual string ProcessNestedTag(ref string text) + { + return HelperFunctions.ProcessNestedTag(ref text); + } + + protected virtual int FindEndIndex(string text, int startIndex) + { + return text.IndexOf(Symbol, startIndex + Symbol.Length); + } + + protected virtual string ExtractContent(string text, int startIndex, int endIndex) + { + return text.Substring(startIndex + Symbol.Length, endIndex - startIndex - Symbol.Length); + } + + protected virtual string WrapWithHtmlTag(string content) + { + return $"<{HtmlTag}>{content}"; + } + + protected virtual string ReplaceText(string text, int startIndex, int endIndex, string replacement) + { + return text.Substring(0, startIndex) + replacement + text.Substring(endIndex + Symbol.Length); + } +} \ No newline at end of file diff --git a/cs/Markdown/Tags/BoldTag.cs b/cs/Markdown/Tags/BoldTag.cs index 310a4bf5c..4b27b2fe3 100644 --- a/cs/Markdown/Tags/BoldTag.cs +++ b/cs/Markdown/Tags/BoldTag.cs @@ -1,37 +1,12 @@ namespace Markdown.Tags; -public class BoldTag : ITagHandler +public class BoldTag : BaseTagHandler { - private const string Symbols = "__"; - private const string HtmlTag = "strong"; + protected override string Symbol => "__"; + protected override string HtmlTag => "strong"; - - public bool IsTagStart(string text, int index) - { - return index + 2 < text.Length && text.Substring(index, 2) == Symbols - && !char.IsWhiteSpace(text[index + 1]); - - } - public int ProcessTag(ref string text, int startIndex) + public override bool IsTagStart(string text, int index) { - var newIndex = HelperFunctions.ScreeningCheck(ref text, startIndex); - if (newIndex == startIndex) - { - int endIndex = text.IndexOf(Symbols, startIndex + 1); - - if (endIndex == -1) - return startIndex + 2; - - string content = text.Substring(startIndex + 2, endIndex - startIndex - 2); - if (HelperFunctions.ContainsWhiteSpaces(content)) - return startIndex + content.Length; - content = HelperFunctions.ProcessNestedTag(ref content); - string replacement = $"<{HtmlTag}>{content}"; - text = text.Substring(0, startIndex) + replacement + text.Substring(endIndex + 2); - return startIndex + replacement.Length; - } - else - { - return newIndex; - } + return index + 2 < text.Length && text.Substring(index, 2) == Symbol + && !char.IsWhiteSpace(text[index + 1]); } -} +} \ No newline at end of file diff --git a/cs/Markdown/Tags/HeadingTag.cs b/cs/Markdown/Tags/HeadingTag.cs index 5a84234c9..60281f6e6 100644 --- a/cs/Markdown/Tags/HeadingTag.cs +++ b/cs/Markdown/Tags/HeadingTag.cs @@ -1,47 +1,26 @@ namespace Markdown.Tags; -public class HeadingTag : ITagHandler +public class HeadingTag : BaseTagHandler { - private const string Symbol = "#"; - private const string HtmlTag = "h1"; + protected override string Symbol => "#"; + protected override string HtmlTag => "h1"; - public bool IsTagStart(string text, int index) + public override bool IsTagStart(string text, int index) { return text[index].ToString() == Symbol; } - public int ProcessTag(ref string text, int startIndex) + protected override int FindEndIndex(string text, int startIndex) { - var newIndex = HelperFunctions.ScreeningCheck(ref text, startIndex); - if (newIndex == startIndex) - { - int endIndex = text.IndexOf('\n', startIndex + 1); - string replacement; - string content; - if (endIndex == -1) - { - content = text.Substring(startIndex + 1, text.Length - startIndex - 1); - if (HelperFunctions.ContainsWhiteSpaces(content)) - return startIndex + content.Length; - content = HelperFunctions.ProcessNestedTag(ref content); - replacement = $"<{HtmlTag}>{content}"; - text = text.Substring(0, startIndex) + replacement; - - return startIndex + replacement.Length; - } - - content = text.Substring(startIndex + 1, endIndex - startIndex - 1); - if (HelperFunctions.ContainsWhiteSpaces(content)) - return startIndex + content.Length; - content = HelperFunctions.ProcessNestedTag(ref content); - replacement = $"<{HtmlTag}>{content}" + '\n'; - text = text.Substring(0, startIndex) + replacement + text.Substring(endIndex + 1); + int endIndex = text.IndexOf('\n', startIndex + 1); + return endIndex == -1 ? text.Length : endIndex; + } - return startIndex + replacement.Length; - } - else + protected override string ReplaceText(string text, int startIndex, int endIndex, string replacement) + { + if (endIndex < text.Length && text[endIndex] == '\n') { - return newIndex; + return text.Substring(0, startIndex) + replacement + '\n' + text.Substring(endIndex + 1); } - + return text.Substring(0, startIndex) + replacement + text.Substring(endIndex); } } diff --git a/cs/Markdown/Tags/ItalicTag.cs b/cs/Markdown/Tags/ItalicTag.cs index 59e38ca38..8ec723d8d 100644 --- a/cs/Markdown/Tags/ItalicTag.cs +++ b/cs/Markdown/Tags/ItalicTag.cs @@ -1,38 +1,23 @@ namespace Markdown.Tags; -public class ItalicTag : ITagHandler +public class ItalicTag : BaseTagHandler { - private const string Symbol = "_"; - private const string HtmlTag = "em"; + protected override string Symbol => "_"; + protected override string HtmlTag => "em"; - public bool IsTagStart(string text, int index) + public override bool IsTagStart(string text, int index) { return text[index].ToString() == Symbol && index + 1 < text.Length && !char.IsWhiteSpace(text[index + 1]); } - public int ProcessTag(ref string text, int startIndex) + protected override int FindEndIndex(string text, int startIndex) { - - var newIndex = HelperFunctions.ScreeningCheck(ref text, startIndex); - if (newIndex == startIndex) - { - int endIndex = HelperFunctions.FindCorrectCloseSymbolForItalic(text, startIndex + 1); - - if (endIndex == -1) - return startIndex + 1; - - string content = text.Substring(startIndex + 1, endIndex - startIndex - 1); - if (HelperFunctions.ContainsWhiteSpaces(content)) - return startIndex + content.Length; - string replacement = $"<{HtmlTag}>{content}"; - text = text.Substring(0, startIndex) + replacement + text.Substring(endIndex + 1); - - return startIndex + replacement.Length; - } - else - { - return newIndex; - } + return HelperFunctions.FindCorrectCloseSymbolForItalic(text, startIndex + 1); + } + + protected override string ProcessNestedTag(ref string text) + { + return text; } -} +} \ No newline at end of file From 01e48b5d7d2da70c4cae52fbcde4c60ae1af3593 Mon Sep 17 00:00:00 2001 From: crycrash Date: Mon, 2 Dec 2024 11:33:20 +0500 Subject: [PATCH 04/14] The header logic has been corrected --- cs/Markdown/Md.cs | 10 +++++--- cs/Markdown/Tags/BaseTagHandler.cs | 2 +- cs/Markdown/Tags/HeadingTag.cs | 39 +++++++++++++++++++++++++++++- cs/MarkdownTests/BoldTests.cs | 7 +++--- cs/MarkdownTests/HeadingTests.cs | 11 ++++++--- cs/MarkdownTests/ItalicTests.cs | 5 ++-- cs/MarkdownTests/ScreenTests.cs | 13 +++++----- 7 files changed, 65 insertions(+), 22 deletions(-) diff --git a/cs/Markdown/Md.cs b/cs/Markdown/Md.cs index 89cbe50d3..8fded3e51 100644 --- a/cs/Markdown/Md.cs +++ b/cs/Markdown/Md.cs @@ -8,7 +8,8 @@ public class Md [ new BoldTag(), new ItalicTag(), - new HeadingTag() + new HeadingTag(), + new BulletedList() ]; public static string Render(string markdownString) @@ -19,8 +20,11 @@ public static string Render(string markdownString) int index = 0; while (index < markdownString.Length) { - if (TryProcessTag(ref markdownString, ref index)) - continue; + if (!char.IsLetter(markdownString[index])) + { + if (TryProcessTag(ref markdownString, ref index)) + continue; + } index++; } diff --git a/cs/Markdown/Tags/BaseTagHandler.cs b/cs/Markdown/Tags/BaseTagHandler.cs index a0bd3e883..fb9f71e3d 100644 --- a/cs/Markdown/Tags/BaseTagHandler.cs +++ b/cs/Markdown/Tags/BaseTagHandler.cs @@ -7,7 +7,7 @@ public abstract class BaseTagHandler : ITagHandler public abstract bool IsTagStart(string text, int index); - public int ProcessTag(ref string text, int startIndex) + public virtual int ProcessTag(ref string text, int startIndex) { var newIndex = HelperFunctions.ScreeningCheck(ref text, startIndex); if (newIndex != startIndex) diff --git a/cs/Markdown/Tags/HeadingTag.cs b/cs/Markdown/Tags/HeadingTag.cs index 60281f6e6..356093155 100644 --- a/cs/Markdown/Tags/HeadingTag.cs +++ b/cs/Markdown/Tags/HeadingTag.cs @@ -6,7 +6,30 @@ public class HeadingTag : BaseTagHandler public override bool IsTagStart(string text, int index) { - return text[index].ToString() == Symbol; + return text[index].ToString() == Symbol && index + 1 < text.Length && + char.IsWhiteSpace(text[index + 1]); + } + + public override int ProcessTag(ref string text, int startIndex) + { + var newIndex = HelperFunctions.ScreeningCheck(ref text, startIndex); + if (newIndex != startIndex) + return newIndex; + + int endIndex = FindEndIndex(text, startIndex); + string content = ExtractContent(text, startIndex, endIndex); + + content = ProcessNestedTag(ref content); + string replacement = WrapWithHtmlTag(content); + text = ReplaceText(text, startIndex, endIndex, replacement); + + return startIndex + replacement.Length; + } + + protected override string ExtractContent(string text, int startIndex, int endIndex) + { + startIndex = FindStartIndex(text, startIndex + 1); + return text.Substring(startIndex, endIndex - startIndex); } protected override int FindEndIndex(string text, int startIndex) @@ -15,6 +38,20 @@ protected override int FindEndIndex(string text, int startIndex) return endIndex == -1 ? text.Length : endIndex; } + private int FindStartIndex(string text, int startIndex) + { + if (string.IsNullOrEmpty(text)) + return -1; + + for (int i = startIndex; i < text.Length; i++) + { + if (!char.IsWhiteSpace(text[i])) + return i; + } + + return -1; + } + protected override string ReplaceText(string text, int startIndex, int endIndex, string replacement) { if (endIndex < text.Length && text[endIndex] == '\n') diff --git a/cs/MarkdownTests/BoldTests.cs b/cs/MarkdownTests/BoldTests.cs index 06c7a1c0c..de0aeeccd 100644 --- a/cs/MarkdownTests/BoldTests.cs +++ b/cs/MarkdownTests/BoldTests.cs @@ -22,7 +22,7 @@ public void Test_StandartBoldWords() [Test] public void Test_BoldWithOtherTags() { - Md.Render("#__aaa__").Should().Be("

aaa

"); + Md.Render("# __aaa__").Should().Be("

aaa

"); Md.Render("__aaa_b_a__").Should().Be("aaaba"); } @@ -37,9 +37,8 @@ public void Test_BoldInPartOfWord() [Test] public void Test_BoldSeveralWords() { - Md.Render("__нач__але").Should().Be("начале"); - Md.Render("сер__еди__не").Should().Be("середине"); - Md.Render("кон__це.__").Should().Be("конце."); + Md.Render("ра__зных сл__овах").Should().Be("ра__зных сл__овах"); + Md.Render("ра__зных словах__").Should().Be("ра__зных словах__"); } [Test] diff --git a/cs/MarkdownTests/HeadingTests.cs b/cs/MarkdownTests/HeadingTests.cs index c2d46dc05..f4e6c6c8c 100644 --- a/cs/MarkdownTests/HeadingTests.cs +++ b/cs/MarkdownTests/HeadingTests.cs @@ -8,9 +8,12 @@ public class HeadingTests [Test] public void Test_StandartHeading() { - Md.Render("#aaaa").Should().Be("

aaaa

"); - Md.Render("#aaa" + '\n' + "bbb").Should().Be("

aaa

" + '\n' + "bbb"); - Md.Render("#aaa" + '\n' + "#bbb").Should().Be("

aaa

" + '\n' + "

bbb

"); + Md.Render("# aaa" + '\n' + "# bbb").Should().Be("

aaa

" + '\n' + "

bbb

"); + Md.Render("# aaa bbb" + '\n' + "# bbb").Should().Be("

aaa bbb

" + '\n' + "

bbb

"); + Md.Render("# aaaa").Should().Be("

aaaa

"); + Md.Render("#aaaa").Should().Be("#aaaa"); + Md.Render("# aaa" + '\n' + "bbb").Should().Be("

aaa

" + '\n' + "bbb"); + } [Test] public void Test_HeadingTagsWithoutText() @@ -22,6 +25,6 @@ public void Test_HeadingTagsWithoutText() [Test] public void Test_HeadingWithOtherTags() { - Md.Render("#__a__" + "\n" + "#_b_").Should().Be("

a

" + '\n' + "

b

"); + Md.Render("# __a__" + "\n" + "# _b_").Should().Be("

a

" + '\n' + "

b

"); } } \ No newline at end of file diff --git a/cs/MarkdownTests/ItalicTests.cs b/cs/MarkdownTests/ItalicTests.cs index dfb65f517..022793de9 100644 --- a/cs/MarkdownTests/ItalicTests.cs +++ b/cs/MarkdownTests/ItalicTests.cs @@ -9,7 +9,7 @@ public void Test_StandartItalicText() { Md.Render("_aaaa_").Should().Be("aaaa"); Md.Render("_aaaa_bbbb_cc_").Should().Be("aaaabbbbcc"); - Md.Render("#_aaa_").Should().Be("

aaa

"); + Md.Render("# _aaa_").Should().Be("

aaa

"); } [Test] @@ -30,8 +30,7 @@ public void Test_ItalicPartOfWord() public void Test_ItalicSeveralWords() { Md.Render("ра_зных сл_овах").Should().Be("ра_зных сл_овах"); - Md.Render("ра__зных сл__овах").Should().Be("ра__зных сл__овах"); - Md.Render("кон_це._").Should().Be("конце."); + Md.Render("ра_зных словах_").Should().Be("ра_зных словах_"); } [Test] diff --git a/cs/MarkdownTests/ScreenTests.cs b/cs/MarkdownTests/ScreenTests.cs index 344598d74..3f62dab4b 100644 --- a/cs/MarkdownTests/ScreenTests.cs +++ b/cs/MarkdownTests/ScreenTests.cs @@ -7,11 +7,12 @@ public class ScreenTests [Test] public void Test_BoldWithТestedItalic() { - Md.Render("\\aaa").Should().Be("\\aaa"); - Md.Render("\\__aaa__\\").Should().Be("__aaa__"); - Md.Render("\\_aaa_\\").Should().Be("_aaa_"); - Md.Render("\\#aaa\\").Should().Be("#aaa"); - Md.Render("\\\\#aa\\").Should().Be("\\#aa"); - Md.Render("\\#aa").Should().Be("\\#aa"); + Console.WriteLine("\\"); + Md.Render("\\aaa").Should().Be("\\aaa", "Один экранированный символ и строка без тэгов. Ему нечего экранировать"); + Md.Render("\\__aaa__\\").Should().Be("__aaa__", "Экранированный символ в конце и начале. Экранирует жирный тэг"); + Md.Render("\\_aaa_\\").Should().Be("_aaa_", "Экранированный символ в конце и начале. Экранирует курсивный тэг"); + Md.Render("\\# aaa\\").Should().Be("# aaa", "Экранированный символ в конце и начале. Экранирует заголовочный тэг"); + Md.Render("\\\\# aa\\").Should().Be("\\# aa", "Экранированный символ в конце и начале, и в теле тэга. Экранированный символ тоже можно экранировать"); + Md.Render("\\#aa\\").Should().Be("\\#aa\\", "Экранированный символ в конце и начале, но тэг неправильно написан. Нет экранизации"); } } \ No newline at end of file From b43171f6fc1614c695e0d5ebecc289ab57034d76 Mon Sep 17 00:00:00 2001 From: crycrash Date: Mon, 2 Dec 2024 16:03:09 +0500 Subject: [PATCH 05/14] Fixed screening --- cs/Markdown/HelperFunctions.cs | 21 +-------------------- cs/Markdown/Md.cs | 5 +---- cs/Markdown/Tags/BaseTagHandler.cs | 5 ++--- cs/Markdown/Tags/EscapeTag.cs | 30 ++++++++++++++++++++++++++++++ cs/Markdown/Tags/HeadingTag.cs | 4 ---- cs/Markdown/Tags/ItalicTag.cs | 2 +- cs/MarkdownTests/ScreenTests.cs | 16 +++++++++++----- 7 files changed, 46 insertions(+), 37 deletions(-) create mode 100644 cs/Markdown/Tags/EscapeTag.cs diff --git a/cs/Markdown/HelperFunctions.cs b/cs/Markdown/HelperFunctions.cs index c0d0d09de..a1c21ae8b 100644 --- a/cs/Markdown/HelperFunctions.cs +++ b/cs/Markdown/HelperFunctions.cs @@ -3,25 +3,12 @@ namespace Markdown; public class HelperFunctions { private static readonly char[] forbiddenChars = ['_', '#']; - public static int ScreeningCheck(ref string text, int startIndex) - { - if (IsScreening(text, startIndex)) - { - int endIndex = text.IndexOf('\\', startIndex + 1); - if (endIndex == -1) - return startIndex + 1; - text = text.Remove(startIndex - 1, 1).Remove(endIndex - 1, 1); - return endIndex - 1; - } - return startIndex; - } public static int FindCorrectCloseSymbolForItalic(string text, int startIndex) { for (int i = startIndex + 1; i < text.Length; i++) { - if (text[i] == '_' && !IsScreening(text, i) - && !IsPartOfDoubleUnderscore(text, i) && !IsSurroundedByWhitespaceOrDigit(text, i)) + if (text[i] == '_' && !IsPartOfDoubleUnderscore(text, i) && !IsSurroundedByWhitespaceOrDigit(text, i)) { return i; } @@ -29,12 +16,6 @@ public static int FindCorrectCloseSymbolForItalic(string text, int startIndex) return -1; } - private static bool IsScreening(string text, int index) => - index - 1 >= 0 && text[index - 1] == '\\'; - - public static bool IsDigit(string text, int index) => - index - 1 >= 0 && char.IsDigit(text[index - 1]); - public static string ProcessNestedTag(ref string text) => Md.Render(text); diff --git a/cs/Markdown/Md.cs b/cs/Markdown/Md.cs index 8fded3e51..1c7a5e0a4 100644 --- a/cs/Markdown/Md.cs +++ b/cs/Markdown/Md.cs @@ -9,14 +9,11 @@ public class Md new BoldTag(), new ItalicTag(), new HeadingTag(), - new BulletedList() + new EscapeTag() ]; public static string Render(string markdownString) { - if (string.IsNullOrEmpty(markdownString) || HelperFunctions.ContainsOnlyDash(markdownString)) - return markdownString; - int index = 0; while (index < markdownString.Length) { diff --git a/cs/Markdown/Tags/BaseTagHandler.cs b/cs/Markdown/Tags/BaseTagHandler.cs index fb9f71e3d..d5d89a5c2 100644 --- a/cs/Markdown/Tags/BaseTagHandler.cs +++ b/cs/Markdown/Tags/BaseTagHandler.cs @@ -9,9 +9,8 @@ public abstract class BaseTagHandler : ITagHandler public virtual int ProcessTag(ref string text, int startIndex) { - var newIndex = HelperFunctions.ScreeningCheck(ref text, startIndex); - if (newIndex != startIndex) - return newIndex; + if (HelperFunctions.ContainsOnlyDash(text.Substring(startIndex))) + return text.Length; int endIndex = FindEndIndex(text, startIndex); diff --git a/cs/Markdown/Tags/EscapeTag.cs b/cs/Markdown/Tags/EscapeTag.cs new file mode 100644 index 000000000..3fbe7fe56 --- /dev/null +++ b/cs/Markdown/Tags/EscapeTag.cs @@ -0,0 +1,30 @@ +namespace Markdown.Tags; +public class EscapeTag : BaseTagHandler +{ + protected override string Symbol => "\\"; + protected override string HtmlTag => "\\"; + + private readonly char[] forbiddenChars = ['_', '\\']; + + public override bool IsTagStart(string text, int index) + { + return text[index].ToString() == Symbol; + + } + + private bool CheckTag(string text, int startIndex) + => (startIndex + 1 < text.Length && forbiddenChars.Contains(text[startIndex + 1])) + || (startIndex + 3 < text.Length && text.Substring(startIndex + 1, 2) == "# "); + + + public override int ProcessTag(ref string text, int startIndex) + { + if (CheckTag(text, startIndex)) + { + text = text.Remove(startIndex, 1); + if (startIndex + 2 < text.Length && text.Substring(startIndex, 2) == "__") + return startIndex + 2; + } + return startIndex + 1; + } +} \ No newline at end of file diff --git a/cs/Markdown/Tags/HeadingTag.cs b/cs/Markdown/Tags/HeadingTag.cs index 356093155..28cb3cbfc 100644 --- a/cs/Markdown/Tags/HeadingTag.cs +++ b/cs/Markdown/Tags/HeadingTag.cs @@ -12,10 +12,6 @@ public override bool IsTagStart(string text, int index) public override int ProcessTag(ref string text, int startIndex) { - var newIndex = HelperFunctions.ScreeningCheck(ref text, startIndex); - if (newIndex != startIndex) - return newIndex; - int endIndex = FindEndIndex(text, startIndex); string content = ExtractContent(text, startIndex, endIndex); diff --git a/cs/Markdown/Tags/ItalicTag.cs b/cs/Markdown/Tags/ItalicTag.cs index 8ec723d8d..7ec3a7600 100644 --- a/cs/Markdown/Tags/ItalicTag.cs +++ b/cs/Markdown/Tags/ItalicTag.cs @@ -15,7 +15,7 @@ protected override int FindEndIndex(string text, int startIndex) { return HelperFunctions.FindCorrectCloseSymbolForItalic(text, startIndex + 1); } - + protected override string ProcessNestedTag(ref string text) { return text; diff --git a/cs/MarkdownTests/ScreenTests.cs b/cs/MarkdownTests/ScreenTests.cs index 3f62dab4b..188cfe275 100644 --- a/cs/MarkdownTests/ScreenTests.cs +++ b/cs/MarkdownTests/ScreenTests.cs @@ -8,11 +8,17 @@ public class ScreenTests public void Test_BoldWithТestedItalic() { Console.WriteLine("\\"); - Md.Render("\\aaa").Should().Be("\\aaa", "Один экранированный символ и строка без тэгов. Ему нечего экранировать"); - Md.Render("\\__aaa__\\").Should().Be("__aaa__", "Экранированный символ в конце и начале. Экранирует жирный тэг"); - Md.Render("\\_aaa_\\").Should().Be("_aaa_", "Экранированный символ в конце и начале. Экранирует курсивный тэг"); - Md.Render("\\# aaa\\").Should().Be("# aaa", "Экранированный символ в конце и начале. Экранирует заголовочный тэг"); - Md.Render("\\\\# aa\\").Should().Be("\\# aa", "Экранированный символ в конце и начале, и в теле тэга. Экранированный символ тоже можно экранировать"); + Md.Render("\\# aaa").Should().Be("# aaa", "Экранированный символ в начале. Экранирует заголовочный тэг"); Md.Render("\\#aa\\").Should().Be("\\#aa\\", "Экранированный символ в конце и начале, но тэг неправильно написан. Нет экранизации"); + Md.Render("\\\\\\aaa").Should().Be("\\\\aaa", "Три экранированных символов и строка без тэгов. Экранируется 2 экранируемых символа"); + Md.Render("\\__aaa__").Should().Be("__aaa__", "Экранированный символ в начале. Экранирует жирный тэг. У второго жирного тэга нет начала, поэтому он так остается"); + Md.Render("\\__aaa\\_").Should().Be("__aaa_", "Первый экранирует жирный. Второй курсив"); + Md.Render("\\\\aaa").Should().Be("\\aaa", "Два экранированных символов и строка без тэгов. Экранируется экранируемый символ"); + Md.Render("\\aaa").Should().Be("\\aaa", "Один экранированный символ и строка без тэгов. Ему нечего экранировать"); + Md.Render("\\__aaa\\__").Should().Be("__aaa__", "Два экранированных символа. Экранируют жирные тэги"); + Md.Render("\\_aaa_").Should().Be("_aaa_", "Экранированный символ в начале. Экранирует курсивный тэг. У второго курсивного тэга нет начала, поэтому он так остается"); + Md.Render("\\_aaa_\\").Should().Be("_aaa_\\", "Первый экранирует курсив. Второму нечего"); + Md.Render("\\__aaa\\").Should().Be("__aaa\\", "Первый экранирует жирный. Второму нечего"); + Md.Render("\\\\_aa_").Should().Be("\\aa", "Экранированный символ в начале, экранирует экранируемый."); } } \ No newline at end of file From 294f78563d136b9650656f9a0e3d345b3aebaf9e Mon Sep 17 00:00:00 2001 From: crycrash Date: Fri, 6 Dec 2024 21:12:21 +0500 Subject: [PATCH 06/14] extended tests have been added, the logic of working with spaces and nested tags has been changed --- cs/Markdown/HelperFunctions.cs | 60 ++++++++++++++++++++++--- cs/Markdown/Md.cs | 2 +- cs/Markdown/Tags/BaseTagHandler.cs | 68 +++++++++++++++++++++++++++-- cs/Markdown/Tags/BoldTag.cs | 9 +++- cs/Markdown/Tags/ItalicTag.cs | 10 +++-- cs/Markdown/Tags/TypesTags.cs | 9 ---- cs/MarkdownTests/BoldTests.cs | 15 +++++-- cs/MarkdownTests/BorderlineCases.cs | 17 +++++++- cs/MarkdownTests/HeadingTests.cs | 7 ++- cs/MarkdownTests/ItalicTests.cs | 16 ++++++- 10 files changed, 183 insertions(+), 30 deletions(-) delete mode 100644 cs/Markdown/Tags/TypesTags.cs diff --git a/cs/Markdown/HelperFunctions.cs b/cs/Markdown/HelperFunctions.cs index a1c21ae8b..e7f1d990f 100644 --- a/cs/Markdown/HelperFunctions.cs +++ b/cs/Markdown/HelperFunctions.cs @@ -4,17 +4,27 @@ public class HelperFunctions { private static readonly char[] forbiddenChars = ['_', '#']; + private static readonly char[] tagChars = ['_', '#', '\\']; + public static int FindCorrectCloseSymbolForItalic(string text, int startIndex) { for (int i = startIndex + 1; i < text.Length; i++) { - if (text[i] == '_' && !IsPartOfDoubleUnderscore(text, i) && !IsSurroundedByWhitespaceOrDigit(text, i)) + if (text[i] == '_' && !IsPartOfDoubleUnderscore(text, i) && IsNotFollowedByDigit(text, i)) { return i; } } return -1; } + private static bool IsNotFollowedByDigit(string text, int index) + { + if (index + 1 < text.Length && char.IsDigit(text[index + 1])) + return false; + if (index - 1 >= 0 && char.IsDigit(text[index - 1])) + return false; + return true; + } public static string ProcessNestedTag(ref string text) => Md.Render(text); @@ -29,8 +39,48 @@ private static bool IsPartOfDoubleUnderscore(string text, int index) => (index + 1 < text.Length && text[index + 1] == '_') || (index > 0 && text[index - 1] == '_'); - private static bool IsSurroundedByWhitespaceOrDigit(string text, int index) => - (index > 0 && char.IsWhiteSpace(text[index - 1])) || - (index + 1 < text.Length && char.IsWhiteSpace(text[index + 1])) || - (index > 0 && char.IsDigit(text[index - 1])); + public static bool CanBeTag(char symbol) => + tagChars.Contains(symbol); + + public static bool ContainsUnderscore(string text) => text.Contains('_'); + + public static (List, List) GetUnderscoreIndexes(string text) + { + List singleUnderscoreIndexes = new List(); + List doubleUnderscoreIndexes = new List(); + for (int i = 0; i < text.Length; i++) + { + if (text[i] == '_' && ((i + 1 < text.Length && text[i + 1] != '_') || i == text.Length - 1)) + { + singleUnderscoreIndexes.Add(i); + } + if (i + 1 < text.Length && text[i] == '_' && text[i + 1] == '_') + { + doubleUnderscoreIndexes.Add(i); + i++; + } + } + return (singleUnderscoreIndexes, doubleUnderscoreIndexes); + } + + public static bool HasUnpairedTags(List indexes1, List indexes2) + { + if (indexes1.Count < 2 || indexes2.Count < 2 || indexes1.Count % 2 == 1 || indexes2.Count % 2 == 1) + { + return false; + } + return true; + } + + public static bool AreSegmentsIntersecting(int[] segment1, int[] segment2) + { + return segment1[1] >= segment2[0] && segment1[0] <= segment2[1]; + } + + public static bool AreSegmentsNested(int[] segment1, int[] segment2) + { + return (segment1[0] >= segment2[0] && segment1[1] <= segment2[1]) || + (segment2[0] >= segment1[0] && segment2[1] <= segment1[1]); + } + } diff --git a/cs/Markdown/Md.cs b/cs/Markdown/Md.cs index 1c7a5e0a4..23474ad93 100644 --- a/cs/Markdown/Md.cs +++ b/cs/Markdown/Md.cs @@ -17,7 +17,7 @@ public static string Render(string markdownString) int index = 0; while (index < markdownString.Length) { - if (!char.IsLetter(markdownString[index])) + if (HelperFunctions.CanBeTag(markdownString[index])) { if (TryProcessTag(ref markdownString, ref index)) continue; diff --git a/cs/Markdown/Tags/BaseTagHandler.cs b/cs/Markdown/Tags/BaseTagHandler.cs index d5d89a5c2..b532165f3 100644 --- a/cs/Markdown/Tags/BaseTagHandler.cs +++ b/cs/Markdown/Tags/BaseTagHandler.cs @@ -11,14 +11,19 @@ public virtual int ProcessTag(ref string text, int startIndex) { if (HelperFunctions.ContainsOnlyDash(text.Substring(startIndex))) return text.Length; + if (HelperFunctions.ContainsUnderscore(text)) + { + if (CheckTagIntersections(text)) + return text.Length; + } int endIndex = FindEndIndex(text, startIndex); - if (endIndex == -1) return startIndex + Symbol.Length; string content = ExtractContent(text, startIndex, endIndex); - if (HelperFunctions.ContainsWhiteSpaces(content)) + + if (!AreTagsCorrectlyPositioned(text, startIndex, endIndex, content)) return startIndex + content.Length; content = ProcessNestedTag(ref content); @@ -28,6 +33,46 @@ public virtual int ProcessTag(ref string text, int startIndex) return startIndex + replacement.Length; } + private bool CheckTagIntersections(string text) + { + (List singleUnderscoreIndexes, List doubleUnderscoreIndexes) = HelperFunctions.GetUnderscoreIndexes(text); + + if (!HelperFunctions.HasUnpairedTags(singleUnderscoreIndexes, doubleUnderscoreIndexes)) + return false; + + for (int i = 0; i < singleUnderscoreIndexes.Count - 1; i++) + { + for (int j = 0; j < doubleUnderscoreIndexes.Count - 1; j++) + { + int[] segment1 = { singleUnderscoreIndexes[i], singleUnderscoreIndexes[i + 1] }; + int[] segment2 = { doubleUnderscoreIndexes[j], doubleUnderscoreIndexes[j + 1] }; + if (HelperFunctions.AreSegmentsIntersecting(segment1, segment2)) + { + if (HelperFunctions.AreSegmentsNested(segment1, segment2)) + continue; + + return true; + } + } + } + + return false; + } + + + private bool AreTagsCorrectlyPositioned(string text, int startIndex, int endIndex, string content) + { + if (!content.Contains(' ')) + return true; + if (startIndex - 1 >= 0 && (char.IsLetter(text[startIndex - 1]) || char.IsDigit(text[startIndex - 1]))) + return false; + if (endIndex + 1 < text.Length && (char.IsLetter(text[endIndex + 1]) || char.IsDigit(text[endIndex + 1]))) + return false; + if (char.IsWhiteSpace(content.First()) || char.IsWhiteSpace(content.Last())) + return false; + return true; + } + protected virtual string ProcessNestedTag(ref string text) { return HelperFunctions.ProcessNestedTag(ref text); @@ -35,9 +80,26 @@ protected virtual string ProcessNestedTag(ref string text) protected virtual int FindEndIndex(string text, int startIndex) { - return text.IndexOf(Symbol, startIndex + Symbol.Length); + int currentIndex = startIndex + Symbol.Length; + + while (currentIndex < text.Length) + { + currentIndex = text.IndexOf(Symbol, currentIndex); + if (currentIndex == -1) + return -1; + if (currentIndex + Symbol.Length < text.Length && char.IsDigit(text[currentIndex + Symbol.Length])) + { + currentIndex += Symbol.Length; + continue; + } + + return currentIndex; + } + + return -1; } + protected virtual string ExtractContent(string text, int startIndex, int endIndex) { return text.Substring(startIndex + Symbol.Length, endIndex - startIndex - Symbol.Length); diff --git a/cs/Markdown/Tags/BoldTag.cs b/cs/Markdown/Tags/BoldTag.cs index 4b27b2fe3..88e8e3d57 100644 --- a/cs/Markdown/Tags/BoldTag.cs +++ b/cs/Markdown/Tags/BoldTag.cs @@ -6,7 +6,12 @@ public class BoldTag : BaseTagHandler public override bool IsTagStart(string text, int index) { - return index + 2 < text.Length && text.Substring(index, 2) == Symbol - && !char.IsWhiteSpace(text[index + 1]); + if (index + 2 >= text.Length || text.Substring(index, 2) != Symbol) + return false; + if (index + 1 < text.Length && char.IsWhiteSpace(text[index + 1])) + return false; + if (index - 1 >= 0 && char.IsDigit(text[index - 1])) + return false; + return true; } } \ No newline at end of file diff --git a/cs/Markdown/Tags/ItalicTag.cs b/cs/Markdown/Tags/ItalicTag.cs index 7ec3a7600..7cfeb0e28 100644 --- a/cs/Markdown/Tags/ItalicTag.cs +++ b/cs/Markdown/Tags/ItalicTag.cs @@ -6,9 +6,13 @@ public class ItalicTag : BaseTagHandler public override bool IsTagStart(string text, int index) { - return text[index].ToString() == Symbol - && index + 1 < text.Length - && !char.IsWhiteSpace(text[index + 1]); + if (text[index].ToString() != Symbol) + return false; + if (index + 1 < text.Length && char.IsWhiteSpace(text[index + 1])) + return false; + if (index - 1 >= 0 && char.IsDigit(text[index - 1])) + return false; + return true; } protected override int FindEndIndex(string text, int startIndex) diff --git a/cs/Markdown/Tags/TypesTags.cs b/cs/Markdown/Tags/TypesTags.cs deleted file mode 100644 index 7c4674700..000000000 --- a/cs/Markdown/Tags/TypesTags.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Markdown.Tags; - -public enum TypesTags -{ - Bold, - Italic, - Heading, - Screen -} \ No newline at end of file diff --git a/cs/MarkdownTests/BoldTests.cs b/cs/MarkdownTests/BoldTests.cs index de0aeeccd..0d91580d1 100644 --- a/cs/MarkdownTests/BoldTests.cs +++ b/cs/MarkdownTests/BoldTests.cs @@ -8,8 +8,10 @@ public class BoldTests [Test] public void Test_StandartBoldWord() { - Md.Render("__WORD__").Should().Be("WORD"); + Md.Render("__aa bb__ __cc aa__").Should().Be("aa bb cc aa"); + Md.Render("__aa bb__").Should().Be("aa bb"); Md.Render("__aaaa__").Should().Be("aaaa"); + Md.Render("__aa__ __bb__" + '\n' + "__aa__ __bb__").Should().Be("aa bb" + '\n' + "aa bb"); } [Test] @@ -24,6 +26,12 @@ public void Test_BoldWithOtherTags() { Md.Render("# __aaa__").Should().Be("

aaa

"); Md.Render("__aaa_b_a__").Should().Be("aaaba"); + Md.Render("# __aaa __").Should().Be("

__aaa __

"); + Md.Render("_a __bbb__").Should().Be("_a bbb"); + Md.Render("__a _bbb__").Should().Be("a _bbb"); + Md.Render("# __aaa__ __bb__").Should().Be("

aaa bb

"); + Md.Render("__aaa _b_ _cc_ a__").Should().Be("aaa b cc a"); + Md.Render("__aaa _b_ _cc _ a__").Should().Be("aaa b _cc _ a"); } [Test] @@ -32,13 +40,15 @@ public void Test_BoldInPartOfWord() Md.Render("__нач__але").Should().Be("начале"); Md.Render("сер__еди__не").Should().Be("середине"); Md.Render("кон__це.__").Should().Be("конце."); + Md.Render("aa сер__еди__не").Should().Be("aa середине"); + Md.Render("кон__це.__ bb").Should().Be("конце. bb"); } [Test] public void Test_BoldSeveralWords() { - Md.Render("ра__зных сл__овах").Should().Be("ра__зных сл__овах"); Md.Render("ра__зных словах__").Should().Be("ра__зных словах__"); + Md.Render("ра__зных сл__овах").Should().Be("ра__зных сл__овах"); } [Test] @@ -47,5 +57,4 @@ public void Test_BoldTextWithSpaces() Md.Render("__ подчерки__").Should().Be("__ подчерки__"); Md.Render("__подчерки __").Should().Be("__подчерки __"); } - } \ No newline at end of file diff --git a/cs/MarkdownTests/BorderlineCases.cs b/cs/MarkdownTests/BorderlineCases.cs index 29cfe673e..cc70f99b3 100644 --- a/cs/MarkdownTests/BorderlineCases.cs +++ b/cs/MarkdownTests/BorderlineCases.cs @@ -9,7 +9,14 @@ public class BorderlineCasesTests public void Test_TextWithDigits() { Md.Render("_aaa12_3_a_").Should().Be("aaa12_3_a"); - Md.Render("123_44__9").Should().Be("123_44__9"); + Md.Render("__aaa12_3_a__").Should().Be("aaa12_3_a"); + Md.Render("_123_").Should().Be("123"); + Md.Render("__123__").Should().Be("123"); + Md.Render("__12__3").Should().Be("__12__3"); + Md.Render("_12_3").Should().Be("_12_3"); + Md.Render("__aaa12__3__a").Should().Be("aaa12__3a"); + Md.Render("_aaa12__3__a_").Should().Be("aaa12__3__a"); + Md.Render("__aaa12__3__a__").Should().Be("aaa12__3__a"); } [Test] @@ -25,11 +32,19 @@ public void Test_TextWithUnpairedTags() { Md.Render("__Непарные_").Should().Be("__Непарные_"); Md.Render("_Непарные__").Should().Be("_Непарные__"); + Md.Render("__s_ __s").Should().Be("__s_ __s"); + Md.Render("__s _s__").Should().Be("s _s"); + Md.Render("__s_ s__").Should().Be("s_ s"); } [Test] public void Test_IntersectionOfTags() { + Md.Render("_a __bbb__ a_").Should().Be("a __bbb__ a"); + Md.Render("__s _s__ _d_").Should().Be("s _s d"); + Md.Render("__s _s__ _d_").Should().Be("s _s d"); + Md.Render("__a _bbb__ a_").Should().Be("__a _bbb__ a_"); + Md.Render("a__ b_ _c __bb").Should().Be("a__ b_ _c __bb"); Md.Render("__пересечения _двойных__ и одинарных_").Should().Be("__пересечения _двойных__ и одинарных_"); Md.Render("_пересечения __двойных_ и одинарных__").Should().Be("_пересечения __двойных_ и одинарных__"); } diff --git a/cs/MarkdownTests/HeadingTests.cs b/cs/MarkdownTests/HeadingTests.cs index f4e6c6c8c..fd0eeea93 100644 --- a/cs/MarkdownTests/HeadingTests.cs +++ b/cs/MarkdownTests/HeadingTests.cs @@ -13,7 +13,9 @@ public void Test_StandartHeading() Md.Render("# aaaa").Should().Be("

aaaa

"); Md.Render("#aaaa").Should().Be("#aaaa"); Md.Render("# aaa" + '\n' + "bbb").Should().Be("

aaa

" + '\n' + "bbb"); - + Md.Render("# aa# aa# aa# aa# aa# aa").Should().Be("

aa# aa# aa# aa# aa# aa

"); + Md.Render("# abb" + "\n" + "ba_").Should().Be("

abb

ba_"); + } [Test] public void Test_HeadingTagsWithoutText() @@ -26,5 +28,8 @@ public void Test_HeadingTagsWithoutText() public void Test_HeadingWithOtherTags() { Md.Render("# __a__" + "\n" + "# _b_").Should().Be("

a

" + '\n' + "

b

"); + Md.Render("# Заголовок __с _разными_ символами__").Should().Be("

Заголовок с разными символами

"); + Md.Render("# __bold _italic_ text__").Should().Be("

bold italic text

"); + Md.Render("# __bold _italic_ text__\n").Should().Be("

bold italic text

\n"); } } \ No newline at end of file diff --git a/cs/MarkdownTests/ItalicTests.cs b/cs/MarkdownTests/ItalicTests.cs index 022793de9..c1dba0fff 100644 --- a/cs/MarkdownTests/ItalicTests.cs +++ b/cs/MarkdownTests/ItalicTests.cs @@ -1,5 +1,7 @@ using Markdown; using FluentAssertions; +using System.Reflection.Metadata; +using System.ComponentModel.DataAnnotations; namespace MarkdownTests; public class ItailcTests @@ -7,16 +9,26 @@ public class ItailcTests [Test] public void Test_StandartItalicText() { + Md.Render("_abc w_ a _b s_").Should().Be("abc w a b s"); Md.Render("_aaaa_").Should().Be("aaaa"); Md.Render("_aaaa_bbbb_cc_").Should().Be("aaaabbbbcc"); - Md.Render("# _aaa_").Should().Be("

aaa

"); + Md.Render("_aaaa_ bbb").Should().Be("aaaa bbb"); + Md.Render("_aaaa_ _bbbb_ _cc_").Should().Be("aaaa bbbb cc"); + Md.Render("_aaa bbb_").Should().Be("aaa bbb"); + Md.Render("_abc_4_").Should().Be("abc_4"); + Md.Render("ab_c_de").Should().Be("abcde"); + Md.Render("ab_c d_a").Should().Be("ab_c d_a"); + Md.Render("\\ _a_").Should().Be("\\ a"); + Md.Render("_bbb" + "\n" + "bb_").Should().Be("bbb" + "\n" + "bb"); } [Test] public void Test_NestedInItalicTags() { + Md.Render("_some __text__ in_").Should().Be("some __text__ in"); + Md.Render("# _aaa_").Should().Be("

aaa

"); Md.Render("_aaa__b__a_").Should().Be("aaa__b__a"); - + Md.Render("_aaa __b__ a_").Should().Be("aaa __b__ a"); } [Test] public void Test_ItalicPartOfWord() From cb9a4052c9ccaa1db47ec7a90344b09baf3bc812 Mon Sep 17 00:00:00 2001 From: crycrash Date: Fri, 6 Dec 2024 22:20:32 +0500 Subject: [PATCH 07/14] Fixed strings with numbers --- cs/Markdown/HelperFunctions.cs | 10 ++- cs/Markdown/Tags/BaseTagHandler.cs | 99 ++++++++++++++++++++--------- cs/Markdown/Tags/BoldTag.cs | 2 - cs/Markdown/Tags/ItalicTag.cs | 4 +- cs/MarkdownTests/BoldTests.cs | 2 + cs/MarkdownTests/BorderlineCases.cs | 12 ++-- cs/MarkdownTests/ItalicTests.cs | 3 +- 7 files changed, 91 insertions(+), 41 deletions(-) diff --git a/cs/Markdown/HelperFunctions.cs b/cs/Markdown/HelperFunctions.cs index e7f1d990f..cff64ee0f 100644 --- a/cs/Markdown/HelperFunctions.cs +++ b/cs/Markdown/HelperFunctions.cs @@ -10,19 +10,25 @@ public static int FindCorrectCloseSymbolForItalic(string text, int startIndex) { for (int i = startIndex + 1; i < text.Length; i++) { - if (text[i] == '_' && !IsPartOfDoubleUnderscore(text, i) && IsNotFollowedByDigit(text, i)) + if (text[i] == '_' && !IsPartOfDoubleUnderscore(text, i) && IsValidCloseSymbol(text, i)) { return i; } } return -1; } - private static bool IsNotFollowedByDigit(string text, int index) + + private static bool IsValidCloseSymbol(string text, int index) { if (index + 1 < text.Length && char.IsDigit(text[index + 1])) return false; if (index - 1 >= 0 && char.IsDigit(text[index - 1])) + { + if (index + 1 == text.Length || text.Substring(index + 1).All(char.IsWhiteSpace)) + return true; + return false; + } return true; } diff --git a/cs/Markdown/Tags/BaseTagHandler.cs b/cs/Markdown/Tags/BaseTagHandler.cs index b532165f3..152b591d2 100644 --- a/cs/Markdown/Tags/BaseTagHandler.cs +++ b/cs/Markdown/Tags/BaseTagHandler.cs @@ -1,3 +1,5 @@ +using System.Runtime.InteropServices; + namespace Markdown.Tags; public abstract class BaseTagHandler : ITagHandler @@ -11,13 +13,15 @@ public virtual int ProcessTag(ref string text, int startIndex) { if (HelperFunctions.ContainsOnlyDash(text.Substring(startIndex))) return text.Length; - if (HelperFunctions.ContainsUnderscore(text)) - { - if (CheckTagIntersections(text)) - return text.Length; - } + if (HelperFunctions.ContainsUnderscore(text) && CheckTagIntersections(text)) + return text.Length; + + int endIndex; + if (startIndex + Symbol.Length < text.Length && char.IsDigit(text[startIndex + Symbol.Length])) + endIndex = FindEndIndexForDigit(text, startIndex); + else + endIndex = FindEndIndex(text, startIndex); - int endIndex = FindEndIndex(text, startIndex); if (endIndex == -1) return startIndex + Symbol.Length; @@ -33,6 +37,65 @@ public virtual int ProcessTag(ref string text, int startIndex) return startIndex + replacement.Length; } + private int FindEndIndexForDigit(string text, int startIndex) + { + int currentIndex = startIndex + Symbol.Length; + + while (currentIndex < text.Length) + { + currentIndex = text.IndexOf(Symbol, currentIndex); + if (currentIndex == -1) + return -1; + + if (currentIndex + Symbol.Length < text.Length && char.IsDigit(text[currentIndex + Symbol.Length])) + { + currentIndex += Symbol.Length; + continue; + } + + if (currentIndex + Symbol.Length == text.Length || char.IsWhiteSpace(text[currentIndex + Symbol.Length])) + return currentIndex; + + currentIndex += Symbol.Length; + } + + return -1; + } + + protected virtual int FindEndIndex(string text, int startIndex) + { + int currentIndex = startIndex + Symbol.Length; + + while (currentIndex < text.Length) + { + currentIndex = text.IndexOf(Symbol, currentIndex); + if (currentIndex == -1) + return -1; + + if (currentIndex + Symbol.Length < text.Length && char.IsDigit(text[currentIndex + Symbol.Length])) + { + currentIndex += Symbol.Length; + continue; + } + if (currentIndex > 0 && char.IsDigit(text[currentIndex - 1])) + { + if (currentIndex + Symbol.Length == text.Length || + text.Substring(currentIndex + Symbol.Length).All(char.IsWhiteSpace)) + { + return currentIndex; + } + + currentIndex += Symbol.Length; + continue; + } + + return currentIndex; + } + + return -1; + } + + private bool CheckTagIntersections(string text) { (List singleUnderscoreIndexes, List doubleUnderscoreIndexes) = HelperFunctions.GetUnderscoreIndexes(text); @@ -50,7 +113,7 @@ private bool CheckTagIntersections(string text) { if (HelperFunctions.AreSegmentsNested(segment1, segment2)) continue; - + return true; } } @@ -78,28 +141,6 @@ protected virtual string ProcessNestedTag(ref string text) return HelperFunctions.ProcessNestedTag(ref text); } - protected virtual int FindEndIndex(string text, int startIndex) - { - int currentIndex = startIndex + Symbol.Length; - - while (currentIndex < text.Length) - { - currentIndex = text.IndexOf(Symbol, currentIndex); - if (currentIndex == -1) - return -1; - if (currentIndex + Symbol.Length < text.Length && char.IsDigit(text[currentIndex + Symbol.Length])) - { - currentIndex += Symbol.Length; - continue; - } - - return currentIndex; - } - - return -1; - } - - protected virtual string ExtractContent(string text, int startIndex, int endIndex) { return text.Substring(startIndex + Symbol.Length, endIndex - startIndex - Symbol.Length); diff --git a/cs/Markdown/Tags/BoldTag.cs b/cs/Markdown/Tags/BoldTag.cs index 88e8e3d57..e98488dd7 100644 --- a/cs/Markdown/Tags/BoldTag.cs +++ b/cs/Markdown/Tags/BoldTag.cs @@ -10,8 +10,6 @@ public override bool IsTagStart(string text, int index) return false; if (index + 1 < text.Length && char.IsWhiteSpace(text[index + 1])) return false; - if (index - 1 >= 0 && char.IsDigit(text[index - 1])) - return false; return true; } } \ No newline at end of file diff --git a/cs/Markdown/Tags/ItalicTag.cs b/cs/Markdown/Tags/ItalicTag.cs index 7cfeb0e28..058b73daa 100644 --- a/cs/Markdown/Tags/ItalicTag.cs +++ b/cs/Markdown/Tags/ItalicTag.cs @@ -10,8 +10,8 @@ public override bool IsTagStart(string text, int index) return false; if (index + 1 < text.Length && char.IsWhiteSpace(text[index + 1])) return false; - if (index - 1 >= 0 && char.IsDigit(text[index - 1])) - return false; + // if (index - 1 >= 0 && char.IsDigit(text[index - 1])) + // return false; return true; } diff --git a/cs/MarkdownTests/BoldTests.cs b/cs/MarkdownTests/BoldTests.cs index 0d91580d1..b6eb6ab54 100644 --- a/cs/MarkdownTests/BoldTests.cs +++ b/cs/MarkdownTests/BoldTests.cs @@ -8,6 +8,8 @@ public class BoldTests [Test] public void Test_StandartBoldWord() { + Md.Render("__abc__4__").Should().Be("abc__4"); + Md.Render("__abc__4").Should().Be("__abc__4"); Md.Render("__aa bb__ __cc aa__").Should().Be("aa bb cc aa"); Md.Render("__aa bb__").Should().Be("aa bb"); Md.Render("__aaaa__").Should().Be("aaaa"); diff --git a/cs/MarkdownTests/BorderlineCases.cs b/cs/MarkdownTests/BorderlineCases.cs index cc70f99b3..954bdc7f6 100644 --- a/cs/MarkdownTests/BorderlineCases.cs +++ b/cs/MarkdownTests/BorderlineCases.cs @@ -8,15 +8,17 @@ public class BorderlineCasesTests [Test] public void Test_TextWithDigits() { - Md.Render("_aaa12_3_a_").Should().Be("aaa12_3_a"); - Md.Render("__aaa12_3_a__").Should().Be("aaa12_3_a"); - Md.Render("_123_").Should().Be("123"); - Md.Render("__123__").Should().Be("123"); + Md.Render("__aaa12__3__a").Should().Be("__aaa12__3__a"); Md.Render("__12__3").Should().Be("__12__3"); Md.Render("_12_3").Should().Be("_12_3"); - Md.Render("__aaa12__3__a").Should().Be("aaa12__3a"); + Md.Render("_aaa12_3_a_").Should().Be("aaa12_3_a"); + Md.Render("__aaa12_3_a__").Should().Be("aaa12_3_a"); Md.Render("_aaa12__3__a_").Should().Be("aaa12__3__a"); Md.Render("__aaa12__3__a__").Should().Be("aaa12__3__a"); + Md.Render("_123_").Should().Be("123"); + Md.Render("__123__").Should().Be("123"); + Md.Render("_123_ _a_").Should().Be("123 a"); + Md.Render("__123__ __a__").Should().Be("123 a"); } [Test] diff --git a/cs/MarkdownTests/ItalicTests.cs b/cs/MarkdownTests/ItalicTests.cs index c1dba0fff..57ef308ed 100644 --- a/cs/MarkdownTests/ItalicTests.cs +++ b/cs/MarkdownTests/ItalicTests.cs @@ -9,13 +9,14 @@ public class ItailcTests [Test] public void Test_StandartItalicText() { + Md.Render("_abc_4").Should().Be("_abc_4"); + Md.Render("_abc_4_").Should().Be("abc_4"); Md.Render("_abc w_ a _b s_").Should().Be("abc w a b s"); Md.Render("_aaaa_").Should().Be("aaaa"); Md.Render("_aaaa_bbbb_cc_").Should().Be("aaaabbbbcc"); Md.Render("_aaaa_ bbb").Should().Be("aaaa bbb"); Md.Render("_aaaa_ _bbbb_ _cc_").Should().Be("aaaa bbbb cc"); Md.Render("_aaa bbb_").Should().Be("aaa bbb"); - Md.Render("_abc_4_").Should().Be("abc_4"); Md.Render("ab_c_de").Should().Be("abcde"); Md.Render("ab_c d_a").Should().Be("ab_c d_a"); Md.Render("\\ _a_").Should().Be("\\ a"); From 9142d1f32328b75ce5d05ae87fcc160df6cb7fa6 Mon Sep 17 00:00:00 2001 From: crycrash Date: Fri, 6 Dec 2024 22:54:51 +0500 Subject: [PATCH 08/14] Fixed header in header --- cs/Markdown/HelperFunctions.cs | 6 ++- cs/Markdown/Md.cs | 12 +++++- cs/Markdown/Tags/BaseTagHandler.cs | 1 - cs/Markdown/Tags/HeadingTag.cs | 5 +++ cs/MarkdownTests/BoldTests.cs | 56 +++++++++++++++------------ cs/MarkdownTests/BorderlineCases.cs | 60 ++++++++++++++++------------- cs/MarkdownTests/HeadingTests.cs | 33 +++++++++------- cs/MarkdownTests/ItalicTests.cs | 52 ++++++++++++++----------- cs/MarkdownTests/ScreenTests.cs | 32 ++++++++------- 9 files changed, 150 insertions(+), 107 deletions(-) diff --git a/cs/Markdown/HelperFunctions.cs b/cs/Markdown/HelperFunctions.cs index cff64ee0f..40779d777 100644 --- a/cs/Markdown/HelperFunctions.cs +++ b/cs/Markdown/HelperFunctions.cs @@ -32,8 +32,10 @@ private static bool IsValidCloseSymbol(string text, int index) return true; } - public static string ProcessNestedTag(ref string text) => - Md.Render(text); + public static string ProcessNestedTag(ref string text){ + Md md = new Md(['_', '\\']); + return md.Render(text); + } public static bool ContainsWhiteSpaces(string text) => text.Contains(' ') || string.IsNullOrWhiteSpace(text); diff --git a/cs/Markdown/Md.cs b/cs/Markdown/Md.cs index 23474ad93..75fa2bdfd 100644 --- a/cs/Markdown/Md.cs +++ b/cs/Markdown/Md.cs @@ -11,13 +11,21 @@ public class Md new HeadingTag(), new EscapeTag() ]; + private readonly char[] tagChars; - public static string Render(string markdownString) + public Md(char[]? customTagChars = null) + { + tagChars = customTagChars ?? ['_', '#', '\\']; + } + + private bool CanBeTag(char symbol) => tagChars.Contains(symbol); + + public string Render(string markdownString) { int index = 0; while (index < markdownString.Length) { - if (HelperFunctions.CanBeTag(markdownString[index])) + if (CanBeTag(markdownString[index])) { if (TryProcessTag(ref markdownString, ref index)) continue; diff --git a/cs/Markdown/Tags/BaseTagHandler.cs b/cs/Markdown/Tags/BaseTagHandler.cs index 152b591d2..bd213fe90 100644 --- a/cs/Markdown/Tags/BaseTagHandler.cs +++ b/cs/Markdown/Tags/BaseTagHandler.cs @@ -122,7 +122,6 @@ private bool CheckTagIntersections(string text) return false; } - private bool AreTagsCorrectlyPositioned(string text, int startIndex, int endIndex, string content) { if (!content.Contains(' ')) diff --git a/cs/Markdown/Tags/HeadingTag.cs b/cs/Markdown/Tags/HeadingTag.cs index 28cb3cbfc..e463920f2 100644 --- a/cs/Markdown/Tags/HeadingTag.cs +++ b/cs/Markdown/Tags/HeadingTag.cs @@ -22,6 +22,11 @@ public override int ProcessTag(ref string text, int startIndex) return startIndex + replacement.Length; } + protected override string ProcessNestedTag(ref string text) + { + return HelperFunctions.ProcessNestedTag(ref text); + } + protected override string ExtractContent(string text, int startIndex, int endIndex) { startIndex = FindStartIndex(text, startIndex + 1); diff --git a/cs/MarkdownTests/BoldTests.cs b/cs/MarkdownTests/BoldTests.cs index b6eb6ab54..acc738f6d 100644 --- a/cs/MarkdownTests/BoldTests.cs +++ b/cs/MarkdownTests/BoldTests.cs @@ -5,58 +5,64 @@ namespace MarkdownTests; public class BoldTests { + Md md; + + [SetUp] + public void SetUp(){ + md = new Md(); + } [Test] public void Test_StandartBoldWord() { - Md.Render("__abc__4__").Should().Be("abc__4"); - Md.Render("__abc__4").Should().Be("__abc__4"); - Md.Render("__aa bb__ __cc aa__").Should().Be("aa bb cc aa"); - Md.Render("__aa bb__").Should().Be("aa bb"); - Md.Render("__aaaa__").Should().Be("aaaa"); - Md.Render("__aa__ __bb__" + '\n' + "__aa__ __bb__").Should().Be("aa bb" + '\n' + "aa bb"); + md.Render("__abc__4__").Should().Be("abc__4"); + md.Render("__abc__4").Should().Be("__abc__4"); + md.Render("__aa bb__ __cc aa__").Should().Be("aa bb cc aa"); + md.Render("__aa bb__").Should().Be("aa bb"); + md.Render("__aaaa__").Should().Be("aaaa"); + md.Render("__aa__ __bb__" + '\n' + "__aa__ __bb__").Should().Be("aa bb" + '\n' + "aa bb"); } [Test] public void Test_StandartBoldWords() { - Md.Render("__WORD__ BOB __PON__").Should().Be("WORD BOB PON"); - Md.Render("__aaaa__bbbb__cc__").Should().Be("aaaabbbbcc"); + md.Render("__WORD__ BOB __PON__").Should().Be("WORD BOB PON"); + md.Render("__aaaa__bbbb__cc__").Should().Be("aaaabbbbcc"); } [Test] public void Test_BoldWithOtherTags() { - Md.Render("# __aaa__").Should().Be("

aaa

"); - Md.Render("__aaa_b_a__").Should().Be("aaaba"); - Md.Render("# __aaa __").Should().Be("

__aaa __

"); - Md.Render("_a __bbb__").Should().Be("_a bbb"); - Md.Render("__a _bbb__").Should().Be("a _bbb"); - Md.Render("# __aaa__ __bb__").Should().Be("

aaa bb

"); - Md.Render("__aaa _b_ _cc_ a__").Should().Be("aaa b cc a"); - Md.Render("__aaa _b_ _cc _ a__").Should().Be("aaa b _cc _ a"); + md.Render("# __aaa__").Should().Be("

aaa

"); + md.Render("__aaa_b_a__").Should().Be("aaaba"); + md.Render("# __aaa __").Should().Be("

__aaa __

"); + md.Render("_a __bbb__").Should().Be("_a bbb"); + md.Render("__a _bbb__").Should().Be("a _bbb"); + md.Render("# __aaa__ __bb__").Should().Be("

aaa bb

"); + md.Render("__aaa _b_ _cc_ a__").Should().Be("aaa b cc a"); + md.Render("__aaa _b_ _cc _ a__").Should().Be("aaa b _cc _ a"); } [Test] public void Test_BoldInPartOfWord() { - Md.Render("__нач__але").Should().Be("начале"); - Md.Render("сер__еди__не").Should().Be("середине"); - Md.Render("кон__це.__").Should().Be("конце."); - Md.Render("aa сер__еди__не").Should().Be("aa середине"); - Md.Render("кон__це.__ bb").Should().Be("конце. bb"); + md.Render("__нач__але").Should().Be("начале"); + md.Render("сер__еди__не").Should().Be("середине"); + md.Render("кон__це.__").Should().Be("конце."); + md.Render("aa сер__еди__не").Should().Be("aa середине"); + md.Render("кон__це.__ bb").Should().Be("конце. bb"); } [Test] public void Test_BoldSeveralWords() { - Md.Render("ра__зных словах__").Should().Be("ра__зных словах__"); - Md.Render("ра__зных сл__овах").Should().Be("ра__зных сл__овах"); + md.Render("ра__зных словах__").Should().Be("ра__зных словах__"); + md.Render("ра__зных сл__овах").Should().Be("ра__зных сл__овах"); } [Test] public void Test_BoldTextWithSpaces() { - Md.Render("__ подчерки__").Should().Be("__ подчерки__"); - Md.Render("__подчерки __").Should().Be("__подчерки __"); + md.Render("__ подчерки__").Should().Be("__ подчерки__"); + md.Render("__подчерки __").Should().Be("__подчерки __"); } } \ No newline at end of file diff --git a/cs/MarkdownTests/BorderlineCases.cs b/cs/MarkdownTests/BorderlineCases.cs index 954bdc7f6..250940c7d 100644 --- a/cs/MarkdownTests/BorderlineCases.cs +++ b/cs/MarkdownTests/BorderlineCases.cs @@ -4,50 +4,56 @@ namespace MarkdownTests; public class BorderlineCasesTests { + Md md; + + [SetUp] + public void SetUp(){ + md = new Md(); + } [Test] public void Test_TextWithDigits() { - Md.Render("__aaa12__3__a").Should().Be("__aaa12__3__a"); - Md.Render("__12__3").Should().Be("__12__3"); - Md.Render("_12_3").Should().Be("_12_3"); - Md.Render("_aaa12_3_a_").Should().Be("aaa12_3_a"); - Md.Render("__aaa12_3_a__").Should().Be("aaa12_3_a"); - Md.Render("_aaa12__3__a_").Should().Be("aaa12__3__a"); - Md.Render("__aaa12__3__a__").Should().Be("aaa12__3__a"); - Md.Render("_123_").Should().Be("123"); - Md.Render("__123__").Should().Be("123"); - Md.Render("_123_ _a_").Should().Be("123 a"); - Md.Render("__123__ __a__").Should().Be("123 a"); + md.Render("__aaa12__3__a").Should().Be("__aaa12__3__a"); + md.Render("__12__3").Should().Be("__12__3"); + md.Render("_12_3").Should().Be("_12_3"); + md.Render("_aaa12_3_a_").Should().Be("aaa12_3_a"); + md.Render("__aaa12_3_a__").Should().Be("aaa12_3_a"); + md.Render("_aaa12__3__a_").Should().Be("aaa12__3__a"); + md.Render("__aaa12__3__a__").Should().Be("aaa12__3__a"); + md.Render("_123_").Should().Be("123"); + md.Render("__123__").Should().Be("123"); + md.Render("_123_ _a_").Should().Be("123 a"); + md.Render("__123__ __a__").Should().Be("123 a"); } [Test] public void Test_TagsWithoutText() { - Md.Render("____").Should().Be("____"); - Md.Render("___").Should().Be("___"); - Md.Render("__").Should().Be("__"); - Md.Render("_").Should().Be("_"); + md.Render("____").Should().Be("____"); + md.Render("___").Should().Be("___"); + md.Render("__").Should().Be("__"); + md.Render("_").Should().Be("_"); } [Test] public void Test_TextWithUnpairedTags() { - Md.Render("__Непарные_").Should().Be("__Непарные_"); - Md.Render("_Непарные__").Should().Be("_Непарные__"); - Md.Render("__s_ __s").Should().Be("__s_ __s"); - Md.Render("__s _s__").Should().Be("s _s"); - Md.Render("__s_ s__").Should().Be("s_ s"); + md.Render("__Непарные_").Should().Be("__Непарные_"); + md.Render("_Непарные__").Should().Be("_Непарные__"); + md.Render("__s_ __s").Should().Be("__s_ __s"); + md.Render("__s _s__").Should().Be("s _s"); + md.Render("__s_ s__").Should().Be("s_ s"); } [Test] public void Test_IntersectionOfTags() { - Md.Render("_a __bbb__ a_").Should().Be("a __bbb__ a"); - Md.Render("__s _s__ _d_").Should().Be("s _s d"); - Md.Render("__s _s__ _d_").Should().Be("s _s d"); - Md.Render("__a _bbb__ a_").Should().Be("__a _bbb__ a_"); - Md.Render("a__ b_ _c __bb").Should().Be("a__ b_ _c __bb"); - Md.Render("__пересечения _двойных__ и одинарных_").Should().Be("__пересечения _двойных__ и одинарных_"); - Md.Render("_пересечения __двойных_ и одинарных__").Should().Be("_пересечения __двойных_ и одинарных__"); + md.Render("_a __bbb__ a_").Should().Be("a __bbb__ a"); + md.Render("__s _s__ _d_").Should().Be("s _s d"); + md.Render("__s _s__ _d_").Should().Be("s _s d"); + md.Render("__a _bbb__ a_").Should().Be("__a _bbb__ a_"); + md.Render("a__ b_ _c __bb").Should().Be("a__ b_ _c __bb"); + md.Render("__пересечения _двойных__ и одинарных_").Should().Be("__пересечения _двойных__ и одинарных_"); + md.Render("_пересечения __двойных_ и одинарных__").Should().Be("_пересечения __двойных_ и одинарных__"); } } \ No newline at end of file diff --git a/cs/MarkdownTests/HeadingTests.cs b/cs/MarkdownTests/HeadingTests.cs index fd0eeea93..7d658f69d 100644 --- a/cs/MarkdownTests/HeadingTests.cs +++ b/cs/MarkdownTests/HeadingTests.cs @@ -5,31 +5,36 @@ namespace MarkdownTests; public class HeadingTests { + Md md; + + [SetUp] + public void SetUp(){ + md = new Md(); + } [Test] public void Test_StandartHeading() { - Md.Render("# aaa" + '\n' + "# bbb").Should().Be("

aaa

" + '\n' + "

bbb

"); - Md.Render("# aaa bbb" + '\n' + "# bbb").Should().Be("

aaa bbb

" + '\n' + "

bbb

"); - Md.Render("# aaaa").Should().Be("

aaaa

"); - Md.Render("#aaaa").Should().Be("#aaaa"); - Md.Render("# aaa" + '\n' + "bbb").Should().Be("

aaa

" + '\n' + "bbb"); - Md.Render("# aa# aa# aa# aa# aa# aa").Should().Be("

aa# aa# aa# aa# aa# aa

"); - Md.Render("# abb" + "\n" + "ba_").Should().Be("

abb

ba_"); - + md.Render("# aa# aa# aa# aa# aa# aa").Should().Be("

aa# aa# aa# aa# aa# aa

"); + md.Render("# aaa" + '\n' + "# bbb").Should().Be("

aaa

" + '\n' + "

bbb

"); + md.Render("# aaa bbb" + '\n' + "# bbb").Should().Be("

aaa bbb

" + '\n' + "

bbb

"); + md.Render("# aaaa").Should().Be("

aaaa

"); + md.Render("#aaaa").Should().Be("#aaaa"); + md.Render("# aaa" + '\n' + "bbb").Should().Be("

aaa

" + '\n' + "bbb"); + md.Render("# abb" + "\n" + "ba_").Should().Be("

abb

" + "\n" + "ba_"); } [Test] public void Test_HeadingTagsWithoutText() { - Md.Render("#").Should().Be("#"); - Md.Render("##").Should().Be("##"); + md.Render("#").Should().Be("#"); + md.Render("##").Should().Be("##"); } [Test] public void Test_HeadingWithOtherTags() { - Md.Render("# __a__" + "\n" + "# _b_").Should().Be("

a

" + '\n' + "

b

"); - Md.Render("# Заголовок __с _разными_ символами__").Should().Be("

Заголовок с разными символами

"); - Md.Render("# __bold _italic_ text__").Should().Be("

bold italic text

"); - Md.Render("# __bold _italic_ text__\n").Should().Be("

bold italic text

\n"); + md.Render("# __a__" + "\n" + "# _b_").Should().Be("

a

" + '\n' + "

b

"); + md.Render("# Заголовок __с _разными_ символами__").Should().Be("

Заголовок с разными символами

"); + md.Render("# __bold _italic_ text__").Should().Be("

bold italic text

"); + md.Render("# __bold _italic_ text__\n").Should().Be("

bold italic text

\n"); } } \ No newline at end of file diff --git a/cs/MarkdownTests/ItalicTests.cs b/cs/MarkdownTests/ItalicTests.cs index 57ef308ed..8aab642c8 100644 --- a/cs/MarkdownTests/ItalicTests.cs +++ b/cs/MarkdownTests/ItalicTests.cs @@ -6,50 +6,56 @@ namespace MarkdownTests; public class ItailcTests { + Md md; + + [SetUp] + public void SetUp(){ + md = new Md(); + } [Test] public void Test_StandartItalicText() { - Md.Render("_abc_4").Should().Be("_abc_4"); - Md.Render("_abc_4_").Should().Be("abc_4"); - Md.Render("_abc w_ a _b s_").Should().Be("abc w a b s"); - Md.Render("_aaaa_").Should().Be("aaaa"); - Md.Render("_aaaa_bbbb_cc_").Should().Be("aaaabbbbcc"); - Md.Render("_aaaa_ bbb").Should().Be("aaaa bbb"); - Md.Render("_aaaa_ _bbbb_ _cc_").Should().Be("aaaa bbbb cc"); - Md.Render("_aaa bbb_").Should().Be("aaa bbb"); - Md.Render("ab_c_de").Should().Be("abcde"); - Md.Render("ab_c d_a").Should().Be("ab_c d_a"); - Md.Render("\\ _a_").Should().Be("\\ a"); - Md.Render("_bbb" + "\n" + "bb_").Should().Be("bbb" + "\n" + "bb"); + md.Render("_abc_4").Should().Be("_abc_4"); + md.Render("_abc_4_").Should().Be("abc_4"); + md.Render("_abc w_ a _b s_").Should().Be("abc w a b s"); + md.Render("_aaaa_").Should().Be("aaaa"); + md.Render("_aaaa_bbbb_cc_").Should().Be("aaaabbbbcc"); + md.Render("_aaaa_ bbb").Should().Be("aaaa bbb"); + md.Render("_aaaa_ _bbbb_ _cc_").Should().Be("aaaa bbbb cc"); + md.Render("_aaa bbb_").Should().Be("aaa bbb"); + md.Render("ab_c_de").Should().Be("abcde"); + md.Render("ab_c d_a").Should().Be("ab_c d_a"); + md.Render("\\ _a_").Should().Be("\\ a"); + md.Render("_bbb" + "\n" + "bb_").Should().Be("bbb" + "\n" + "bb"); } [Test] public void Test_NestedInItalicTags() { - Md.Render("_some __text__ in_").Should().Be("some __text__ in"); - Md.Render("# _aaa_").Should().Be("

aaa

"); - Md.Render("_aaa__b__a_").Should().Be("aaa__b__a"); - Md.Render("_aaa __b__ a_").Should().Be("aaa __b__ a"); + md.Render("_some __text__ in_").Should().Be("some __text__ in"); + md.Render("# _aaa_").Should().Be("

aaa

"); + md.Render("_aaa__b__a_").Should().Be("aaa__b__a"); + md.Render("_aaa __b__ a_").Should().Be("aaa __b__ a"); } [Test] public void Test_ItalicPartOfWord() { - Md.Render("_нач_але").Should().Be("начале"); - Md.Render("сер_еди_не").Should().Be("середине"); - Md.Render("кон_це._").Should().Be("конце."); + md.Render("_нач_але").Should().Be("начале"); + md.Render("сер_еди_не").Should().Be("середине"); + md.Render("кон_це._").Should().Be("конце."); } [Test] public void Test_ItalicSeveralWords() { - Md.Render("ра_зных сл_овах").Should().Be("ра_зных сл_овах"); - Md.Render("ра_зных словах_").Should().Be("ра_зных словах_"); + md.Render("ра_зных сл_овах").Should().Be("ра_зных сл_овах"); + md.Render("ра_зных словах_").Should().Be("ра_зных словах_"); } [Test] public void Test_ItalicTextWithSpaces() { - Md.Render("_ подчерки_").Should().Be("_ подчерки_"); - Md.Render("_подчерки _").Should().Be("_подчерки _"); + md.Render("_ подчерки_").Should().Be("_ подчерки_"); + md.Render("_подчерки _").Should().Be("_подчерки _"); } } \ No newline at end of file diff --git a/cs/MarkdownTests/ScreenTests.cs b/cs/MarkdownTests/ScreenTests.cs index 188cfe275..bf9d82232 100644 --- a/cs/MarkdownTests/ScreenTests.cs +++ b/cs/MarkdownTests/ScreenTests.cs @@ -4,21 +4,27 @@ namespace MarkdownTests; public class ScreenTests { + Md md; + + [SetUp] + public void SetUp(){ + md = new Md(); + } [Test] - public void Test_BoldWithТestedItalic() + public void Test_ScreenTests() { Console.WriteLine("\\"); - Md.Render("\\# aaa").Should().Be("# aaa", "Экранированный символ в начале. Экранирует заголовочный тэг"); - Md.Render("\\#aa\\").Should().Be("\\#aa\\", "Экранированный символ в конце и начале, но тэг неправильно написан. Нет экранизации"); - Md.Render("\\\\\\aaa").Should().Be("\\\\aaa", "Три экранированных символов и строка без тэгов. Экранируется 2 экранируемых символа"); - Md.Render("\\__aaa__").Should().Be("__aaa__", "Экранированный символ в начале. Экранирует жирный тэг. У второго жирного тэга нет начала, поэтому он так остается"); - Md.Render("\\__aaa\\_").Should().Be("__aaa_", "Первый экранирует жирный. Второй курсив"); - Md.Render("\\\\aaa").Should().Be("\\aaa", "Два экранированных символов и строка без тэгов. Экранируется экранируемый символ"); - Md.Render("\\aaa").Should().Be("\\aaa", "Один экранированный символ и строка без тэгов. Ему нечего экранировать"); - Md.Render("\\__aaa\\__").Should().Be("__aaa__", "Два экранированных символа. Экранируют жирные тэги"); - Md.Render("\\_aaa_").Should().Be("_aaa_", "Экранированный символ в начале. Экранирует курсивный тэг. У второго курсивного тэга нет начала, поэтому он так остается"); - Md.Render("\\_aaa_\\").Should().Be("_aaa_\\", "Первый экранирует курсив. Второму нечего"); - Md.Render("\\__aaa\\").Should().Be("__aaa\\", "Первый экранирует жирный. Второму нечего"); - Md.Render("\\\\_aa_").Should().Be("\\aa", "Экранированный символ в начале, экранирует экранируемый."); + md.Render("\\# aaa").Should().Be("# aaa", "Экранированный символ в начале. Экранирует заголовочный тэг"); + md.Render("\\#aa\\").Should().Be("\\#aa\\", "Экранированный символ в конце и начале, но тэг неправильно написан. Нет экранизации"); + md.Render("\\\\\\aaa").Should().Be("\\\\aaa", "Три экранированных символов и строка без тэгов. Экранируется 2 экранируемых символа"); + md.Render("\\__aaa__").Should().Be("__aaa__", "Экранированный символ в начале. Экранирует жирный тэг. У второго жирного тэга нет начала, поэтому он так остается"); + md.Render("\\__aaa\\_").Should().Be("__aaa_", "Первый экранирует жирный. Второй курсив"); + md.Render("\\\\aaa").Should().Be("\\aaa", "Два экранированных символов и строка без тэгов. Экранируется экранируемый символ"); + md.Render("\\aaa").Should().Be("\\aaa", "Один экранированный символ и строка без тэгов. Ему нечего экранировать"); + md.Render("\\__aaa\\__").Should().Be("__aaa__", "Два экранированных символа. Экранируют жирные тэги"); + md.Render("\\_aaa_").Should().Be("_aaa_", "Экранированный символ в начале. Экранирует курсивный тэг. У второго курсивного тэга нет начала, поэтому он так остается"); + md.Render("\\_aaa_\\").Should().Be("_aaa_\\", "Первый экранирует курсив. Второму нечего"); + md.Render("\\__aaa\\").Should().Be("__aaa\\", "Первый экранирует жирный. Второму нечего"); + md.Render("\\\\_aa_").Should().Be("\\aa", "Экранированный символ в начале, экранирует экранируемый."); } } \ No newline at end of file From bb22cb92f8d21578798f507be02aa36bb0dd87e4 Mon Sep 17 00:00:00 2001 From: crycrash Date: Sat, 7 Dec 2024 10:48:40 +0500 Subject: [PATCH 09/14] Added extra tests for headings --- cs/Markdown/HelperFunctions.cs | 16 ++++++-- cs/Markdown/Tags/BaseTagHandler.cs | 12 ++++-- cs/Markdown/Tags/BoldTag.cs | 10 +---- cs/Markdown/Tags/HeadingTag.cs | 17 +++++--- cs/Markdown/Tags/ItalicTag.cs | 11 +---- cs/MarkdownTests/BoldTests.cs | 1 + cs/MarkdownTests/HeadingTests.cs | 66 ++++++++++++++++++++++++++---- cs/MarkdownTests/ItalicTests.cs | 1 + 8 files changed, 95 insertions(+), 39 deletions(-) diff --git a/cs/Markdown/HelperFunctions.cs b/cs/Markdown/HelperFunctions.cs index 40779d777..e3e2e3d03 100644 --- a/cs/Markdown/HelperFunctions.cs +++ b/cs/Markdown/HelperFunctions.cs @@ -32,17 +32,21 @@ private static bool IsValidCloseSymbol(string text, int index) return true; } - public static string ProcessNestedTag(ref string text){ + public static string ProcessNestedTag(ref string text) + { Md md = new Md(['_', '\\']); return md.Render(text); } - public static bool ContainsWhiteSpaces(string text) => - text.Contains(' ') || string.IsNullOrWhiteSpace(text); - public static bool ContainsOnlyDash(string text) => text.All(symbol => forbiddenChars.Contains(symbol)); + public static bool ContainsOnlyHeading(string text) => + text.All(symbol => symbol == '#'); + + public static bool ContainsOnlySpases(string text) => + string.IsNullOrWhiteSpace(text) || string.IsNullOrEmpty(text); + private static bool IsPartOfDoubleUnderscore(string text, int index) => (index + 1 < text.Length && text[index + 1] == '_') || (index > 0 && text[index - 1] == '_'); @@ -91,4 +95,8 @@ public static bool AreSegmentsNested(int[] segment1, int[] segment2) (segment2[0] >= segment1[0] && segment2[1] <= segment1[1]); } + public static string RemoveExtraSpaces(string input) + { + return string.Join(" ", input.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries)); + } } diff --git a/cs/Markdown/Tags/BaseTagHandler.cs b/cs/Markdown/Tags/BaseTagHandler.cs index bd213fe90..2ee6ac3ad 100644 --- a/cs/Markdown/Tags/BaseTagHandler.cs +++ b/cs/Markdown/Tags/BaseTagHandler.cs @@ -26,7 +26,8 @@ public virtual int ProcessTag(ref string text, int startIndex) return startIndex + Symbol.Length; string content = ExtractContent(text, startIndex, endIndex); - + if (HelperFunctions.ContainsOnlySpases(content)) + return StringOnlySpases(ref text, endIndex, Symbol.Length); if (!AreTagsCorrectlyPositioned(text, startIndex, endIndex, content)) return startIndex + content.Length; @@ -81,14 +82,12 @@ protected virtual int FindEndIndex(string text, int startIndex) { if (currentIndex + Symbol.Length == text.Length || text.Substring(currentIndex + Symbol.Length).All(char.IsWhiteSpace)) - { + return currentIndex; - } currentIndex += Symbol.Length; continue; } - return currentIndex; } @@ -154,4 +153,9 @@ protected virtual string ReplaceText(string text, int startIndex, int endIndex, { return text.Substring(0, startIndex) + replacement + text.Substring(endIndex + Symbol.Length); } + + protected virtual int StringOnlySpases(ref string text, int endIndex, int symbolLength){ + text = text.Substring(endIndex + symbolLength); + return endIndex; + } } \ No newline at end of file diff --git a/cs/Markdown/Tags/BoldTag.cs b/cs/Markdown/Tags/BoldTag.cs index e98488dd7..5e74ab727 100644 --- a/cs/Markdown/Tags/BoldTag.cs +++ b/cs/Markdown/Tags/BoldTag.cs @@ -4,12 +4,6 @@ public class BoldTag : BaseTagHandler protected override string Symbol => "__"; protected override string HtmlTag => "strong"; - public override bool IsTagStart(string text, int index) - { - if (index + 2 >= text.Length || text.Substring(index, 2) != Symbol) - return false; - if (index + 1 < text.Length && char.IsWhiteSpace(text[index + 1])) - return false; - return true; - } + public override bool IsTagStart(string text, int index) => index + 2 <= text.Length + && text.Substring(index, 2) == Symbol; } \ No newline at end of file diff --git a/cs/Markdown/Tags/HeadingTag.cs b/cs/Markdown/Tags/HeadingTag.cs index e463920f2..a197745ea 100644 --- a/cs/Markdown/Tags/HeadingTag.cs +++ b/cs/Markdown/Tags/HeadingTag.cs @@ -6,14 +6,17 @@ public class HeadingTag : BaseTagHandler public override bool IsTagStart(string text, int index) { - return text[index].ToString() == Symbol && index + 1 < text.Length && - char.IsWhiteSpace(text[index + 1]); + return (text[index].ToString() == Symbol && index + 1 < text.Length && + char.IsWhiteSpace(text[index + 1])) || HelperFunctions.ContainsOnlyHeading(text); } public override int ProcessTag(ref string text, int startIndex) { int endIndex = FindEndIndex(text, startIndex); string content = ExtractContent(text, startIndex, endIndex); + content = HelperFunctions.RemoveExtraSpaces(content); + if (HelperFunctions.ContainsOnlySpases(content) || HelperFunctions.ContainsOnlyHeading(content)) + return StringOnlySpases(ref text, endIndex, 0); content = ProcessNestedTag(ref content); string replacement = WrapWithHtmlTag(content); @@ -29,7 +32,9 @@ protected override string ProcessNestedTag(ref string text) protected override string ExtractContent(string text, int startIndex, int endIndex) { - startIndex = FindStartIndex(text, startIndex + 1); + startIndex = FindStartIndex(text, startIndex + 1, endIndex); + if (startIndex == -1) + return ""; return text.Substring(startIndex, endIndex - startIndex); } @@ -39,12 +44,12 @@ protected override int FindEndIndex(string text, int startIndex) return endIndex == -1 ? text.Length : endIndex; } - private int FindStartIndex(string text, int startIndex) + private int FindStartIndex(string text, int startIndex, int endIndex) { - if (string.IsNullOrEmpty(text)) + if (string.IsNullOrWhiteSpace(text.Substring(startIndex))) return -1; - for (int i = startIndex; i < text.Length; i++) + for (int i = startIndex; i < endIndex; i++) { if (!char.IsWhiteSpace(text[i])) return i; diff --git a/cs/Markdown/Tags/ItalicTag.cs b/cs/Markdown/Tags/ItalicTag.cs index 058b73daa..d0ddd36a7 100644 --- a/cs/Markdown/Tags/ItalicTag.cs +++ b/cs/Markdown/Tags/ItalicTag.cs @@ -4,16 +4,7 @@ public class ItalicTag : BaseTagHandler protected override string Symbol => "_"; protected override string HtmlTag => "em"; - public override bool IsTagStart(string text, int index) - { - if (text[index].ToString() != Symbol) - return false; - if (index + 1 < text.Length && char.IsWhiteSpace(text[index + 1])) - return false; - // if (index - 1 >= 0 && char.IsDigit(text[index - 1])) - // return false; - return true; - } + public override bool IsTagStart(string text, int index) => text[index].ToString() == Symbol; protected override int FindEndIndex(string text, int startIndex) { diff --git a/cs/MarkdownTests/BoldTests.cs b/cs/MarkdownTests/BoldTests.cs index acc738f6d..571ccb04c 100644 --- a/cs/MarkdownTests/BoldTests.cs +++ b/cs/MarkdownTests/BoldTests.cs @@ -62,6 +62,7 @@ public void Test_BoldSeveralWords() [Test] public void Test_BoldTextWithSpaces() { + md.Render("__ __").Should().Be(""); md.Render("__ подчерки__").Should().Be("__ подчерки__"); md.Render("__подчерки __").Should().Be("__подчерки __"); } diff --git a/cs/MarkdownTests/HeadingTests.cs b/cs/MarkdownTests/HeadingTests.cs index 7d658f69d..4e6060144 100644 --- a/cs/MarkdownTests/HeadingTests.cs +++ b/cs/MarkdownTests/HeadingTests.cs @@ -8,25 +8,24 @@ public class HeadingTests Md md; [SetUp] - public void SetUp(){ + public void SetUp() + { md = new Md(); } [Test] public void Test_StandartHeading() { md.Render("# aa# aa# aa# aa# aa# aa").Should().Be("

aa# aa# aa# aa# aa# aa

"); - md.Render("# aaa" + '\n' + "# bbb").Should().Be("

aaa

" + '\n' + "

bbb

"); - md.Render("# aaa bbb" + '\n' + "# bbb").Should().Be("

aaa bbb

" + '\n' + "

bbb

"); md.Render("# aaaa").Should().Be("

aaaa

"); md.Render("#aaaa").Should().Be("#aaaa"); - md.Render("# aaa" + '\n' + "bbb").Should().Be("

aaa

" + '\n' + "bbb"); - md.Render("# abb" + "\n" + "ba_").Should().Be("

abb

" + "\n" + "ba_"); } [Test] public void Test_HeadingTagsWithoutText() { - md.Render("#").Should().Be("#"); - md.Render("##").Should().Be("##"); + md.Render("#\n# Заголовок").Should().Be("\n

Заголовок

"); + md.Render("#").Should().Be(""); + md.Render("##").Should().Be(""); + md.Render("###").Should().Be(""); } [Test] @@ -37,4 +36,57 @@ public void Test_HeadingWithOtherTags() md.Render("# __bold _italic_ text__").Should().Be("

bold italic text

"); md.Render("# __bold _italic_ text__\n").Should().Be("

bold italic text

\n"); } + + [Test] + public void Test_HeadingWithTrailingSpaces() + { + md.Render("# Заголовок").Should().Be("

Заголовок

"); + md.Render("# Заголовок ").Should().Be("

Заголовок

"); + md.Render("# Заголовок ф").Should().Be("

Заголовок ф

"); + } + + [Test] + public void Test_InvalidHeadingWithoutSpace() + { + md.Render("##Heading").Should().Be("##Heading"); + md.Render("#Heading").Should().Be("#Heading"); + } + + [Test] + public void Test_HeadingWithEmptyLines() + { + md.Render("# aaa" + '\n' + "# bbb").Should().Be("

aaa

" + '\n' + "

bbb

"); + md.Render("# aaa bbb" + '\n' + "# bbb").Should().Be("

aaa bbb

" + '\n' + "

bbb

"); + md.Render("# aaa" + '\n' + "bbb").Should().Be("

aaa

" + '\n' + "bbb"); + md.Render("# abb" + "\n" + "ba_").Should().Be("

abb

" + "\n" + "ba_"); + md.Render("# Заголовок\n\n# Второй").Should().Be("

Заголовок

\n\n

Второй

"); + } + + [Test] + public void Test_HeadingWithSpecialCharacters() + { + md.Render("# @#$%^&*() Заголовок").Should().Be("

@#$%^&*() Заголовок

"); + md.Render("# Заголовок с символами !@#").Should().Be("

Заголовок с символами !@#

"); + } + + [Test] + public void Test_HeadingWithOnlyWhitespace() + { + md.Render("# ").Should().Be(""); + md.Render("#\t\t").Should().Be(""); + } + + [Test] + public void Test_HeadingMixedWithOtherText() + { + md.Render("Текст перед # Заголовок\nЕще текст").Should().Be("Текст перед

Заголовок

\nЕще текст"); + md.Render("# Заголовок\nОбычный текст\n# Второй").Should().Be("

Заголовок

\nОбычный текст\n

Второй

"); + } + + [Test] + public void Test_NestedTagsInsideHeading() + { + md.Render("# _italic_ **bold** text").Should().Be("

italic **bold** text

"); + md.Render("# **bold** _italic_ text").Should().Be("

**bold** italic text

"); + } } \ No newline at end of file diff --git a/cs/MarkdownTests/ItalicTests.cs b/cs/MarkdownTests/ItalicTests.cs index 8aab642c8..343d5cf22 100644 --- a/cs/MarkdownTests/ItalicTests.cs +++ b/cs/MarkdownTests/ItalicTests.cs @@ -55,6 +55,7 @@ public void Test_ItalicSeveralWords() [Test] public void Test_ItalicTextWithSpaces() { + md.Render("_ _").Should().Be(""); md.Render("_ подчерки_").Should().Be("_ подчерки_"); md.Render("_подчерки _").Should().Be("_подчерки _"); } From 3a0d8b9bd5d97c4086971b03f9fbcd7bdad2f118 Mon Sep 17 00:00:00 2001 From: crycrash Date: Sat, 7 Dec 2024 10:52:36 +0500 Subject: [PATCH 10/14] Fixed formatting --- cs/Markdown/HelperFunctions.cs | 5 ----- cs/Markdown/Tags/BaseTagHandler.cs | 5 +++-- cs/Markdown/Tags/EscapeTag.cs | 7 +------ cs/Markdown/Tags/HeadingTag.cs | 10 +++------- cs/Markdown/Tags/ItalicTag.cs | 11 +++-------- 5 files changed, 10 insertions(+), 28 deletions(-) diff --git a/cs/Markdown/HelperFunctions.cs b/cs/Markdown/HelperFunctions.cs index e3e2e3d03..e65b215e4 100644 --- a/cs/Markdown/HelperFunctions.cs +++ b/cs/Markdown/HelperFunctions.cs @@ -11,9 +11,7 @@ public static int FindCorrectCloseSymbolForItalic(string text, int startIndex) for (int i = startIndex + 1; i < text.Length; i++) { if (text[i] == '_' && !IsPartOfDoubleUnderscore(text, i) && IsValidCloseSymbol(text, i)) - { return i; - } } return -1; } @@ -51,9 +49,6 @@ private static bool IsPartOfDoubleUnderscore(string text, int index) => (index + 1 < text.Length && text[index + 1] == '_') || (index > 0 && text[index - 1] == '_'); - public static bool CanBeTag(char symbol) => - tagChars.Contains(symbol); - public static bool ContainsUnderscore(string text) => text.Contains('_'); public static (List, List) GetUnderscoreIndexes(string text) diff --git a/cs/Markdown/Tags/BaseTagHandler.cs b/cs/Markdown/Tags/BaseTagHandler.cs index 2ee6ac3ad..c6f568e90 100644 --- a/cs/Markdown/Tags/BaseTagHandler.cs +++ b/cs/Markdown/Tags/BaseTagHandler.cs @@ -82,7 +82,7 @@ protected virtual int FindEndIndex(string text, int startIndex) { if (currentIndex + Symbol.Length == text.Length || text.Substring(currentIndex + Symbol.Length).All(char.IsWhiteSpace)) - + return currentIndex; currentIndex += Symbol.Length; @@ -154,7 +154,8 @@ protected virtual string ReplaceText(string text, int startIndex, int endIndex, return text.Substring(0, startIndex) + replacement + text.Substring(endIndex + Symbol.Length); } - protected virtual int StringOnlySpases(ref string text, int endIndex, int symbolLength){ + protected virtual int StringOnlySpases(ref string text, int endIndex, int symbolLength) + { text = text.Substring(endIndex + symbolLength); return endIndex; } diff --git a/cs/Markdown/Tags/EscapeTag.cs b/cs/Markdown/Tags/EscapeTag.cs index 3fbe7fe56..bc0497f8d 100644 --- a/cs/Markdown/Tags/EscapeTag.cs +++ b/cs/Markdown/Tags/EscapeTag.cs @@ -6,17 +6,12 @@ public class EscapeTag : BaseTagHandler private readonly char[] forbiddenChars = ['_', '\\']; - public override bool IsTagStart(string text, int index) - { - return text[index].ToString() == Symbol; - - } + public override bool IsTagStart(string text, int index) => text[index].ToString() == Symbol; private bool CheckTag(string text, int startIndex) => (startIndex + 1 < text.Length && forbiddenChars.Contains(text[startIndex + 1])) || (startIndex + 3 < text.Length && text.Substring(startIndex + 1, 2) == "# "); - public override int ProcessTag(ref string text, int startIndex) { if (CheckTag(text, startIndex)) diff --git a/cs/Markdown/Tags/HeadingTag.cs b/cs/Markdown/Tags/HeadingTag.cs index a197745ea..11a02a98f 100644 --- a/cs/Markdown/Tags/HeadingTag.cs +++ b/cs/Markdown/Tags/HeadingTag.cs @@ -25,10 +25,8 @@ public override int ProcessTag(ref string text, int startIndex) return startIndex + replacement.Length; } - protected override string ProcessNestedTag(ref string text) - { - return HelperFunctions.ProcessNestedTag(ref text); - } + protected override string ProcessNestedTag(ref string text) => + HelperFunctions.ProcessNestedTag(ref text); protected override string ExtractContent(string text, int startIndex, int endIndex) { @@ -54,16 +52,14 @@ private int FindStartIndex(string text, int startIndex, int endIndex) if (!char.IsWhiteSpace(text[i])) return i; } - return -1; } protected override string ReplaceText(string text, int startIndex, int endIndex, string replacement) { if (endIndex < text.Length && text[endIndex] == '\n') - { return text.Substring(0, startIndex) + replacement + '\n' + text.Substring(endIndex + 1); - } + return text.Substring(0, startIndex) + replacement + text.Substring(endIndex); } } diff --git a/cs/Markdown/Tags/ItalicTag.cs b/cs/Markdown/Tags/ItalicTag.cs index d0ddd36a7..112db9d02 100644 --- a/cs/Markdown/Tags/ItalicTag.cs +++ b/cs/Markdown/Tags/ItalicTag.cs @@ -6,13 +6,8 @@ public class ItalicTag : BaseTagHandler public override bool IsTagStart(string text, int index) => text[index].ToString() == Symbol; - protected override int FindEndIndex(string text, int startIndex) - { - return HelperFunctions.FindCorrectCloseSymbolForItalic(text, startIndex + 1); - } + protected override int FindEndIndex(string text, int startIndex) => + HelperFunctions.FindCorrectCloseSymbolForItalic(text, startIndex + 1); - protected override string ProcessNestedTag(ref string text) - { - return text; - } + protected override string ProcessNestedTag(ref string text) => text; } \ No newline at end of file From 2201109a1c2e3a57ef2d1835f6ce2b8ac8831f12 Mon Sep 17 00:00:00 2001 From: crycrash Date: Sat, 14 Dec 2024 11:46:48 +0500 Subject: [PATCH 11/14] Small fixes --- cs/Markdown/HelperFunctions.cs | 22 ++++++++++----- cs/Markdown/Md.cs | 43 +++++++++++------------------ cs/Markdown/Tags/BaseTagHandler.cs | 26 ++++++++--------- cs/Markdown/Tags/DefaultTag.cs | 14 ++++++++++ cs/Markdown/Tags/HeadingTag.cs | 10 +++---- cs/MarkdownTests/BoldTests.cs | 19 +++++++++---- cs/MarkdownTests/BorderlineCases.cs | 7 ++++- cs/MarkdownTests/HeadingTests.cs | 12 +++++++- cs/MarkdownTests/ItalicTests.cs | 7 ++++- cs/MarkdownTests/ScreenTests.cs | 4 +-- 10 files changed, 100 insertions(+), 64 deletions(-) create mode 100644 cs/Markdown/Tags/DefaultTag.cs diff --git a/cs/Markdown/HelperFunctions.cs b/cs/Markdown/HelperFunctions.cs index e65b215e4..7e2b55d12 100644 --- a/cs/Markdown/HelperFunctions.cs +++ b/cs/Markdown/HelperFunctions.cs @@ -1,3 +1,4 @@ +using Markdown.Tags; namespace Markdown; public class HelperFunctions @@ -32,7 +33,14 @@ private static bool IsValidCloseSymbol(string text, int index) public static string ProcessNestedTag(ref string text) { - Md md = new Md(['_', '\\']); + List tagHandlers = new List + { + new BoldTag(), + new ItalicTag(), + new EscapeTag(), + new DefaultTagHandler() + }; + Md md = new Md(tagHandlers); return md.Render(text); } @@ -79,19 +87,19 @@ public static bool HasUnpairedTags(List indexes1, List indexes2) return true; } - public static bool AreSegmentsIntersecting(int[] segment1, int[] segment2) + public static bool AreSegmentsIntersecting(Tuple segment1, Tuple segment2) { - return segment1[1] >= segment2[0] && segment1[0] <= segment2[1]; + return segment1.Item2 >= segment2.Item1 && segment1.Item1 <= segment2.Item2; } - public static bool AreSegmentsNested(int[] segment1, int[] segment2) + public static bool AreSegmentsNested(Tuple segment1, Tuple segment2) { - return (segment1[0] >= segment2[0] && segment1[1] <= segment2[1]) || - (segment2[0] >= segment1[0] && segment2[1] <= segment1[1]); + return (segment1.Item1 >= segment2.Item1 && segment1.Item2 <= segment2.Item2) || + (segment2.Item1 >= segment1.Item1 && segment2.Item2 <= segment1.Item2); } public static string RemoveExtraSpaces(string input) { - return string.Join(" ", input.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries)); + return string.Join(" ", input.Split(' ', StringSplitOptions.RemoveEmptyEntries)); } } diff --git a/cs/Markdown/Md.cs b/cs/Markdown/Md.cs index 75fa2bdfd..9f9dd6be2 100644 --- a/cs/Markdown/Md.cs +++ b/cs/Markdown/Md.cs @@ -4,48 +4,37 @@ namespace Markdown; public class Md { - private static readonly List TagHandlers = - [ + private readonly List tagHandlers; + + public Md(List? customTagHandlers = null) + { + tagHandlers = customTagHandlers ?? new List + { new BoldTag(), new ItalicTag(), new HeadingTag(), - new EscapeTag() - ]; - private readonly char[] tagChars; - - public Md(char[]? customTagChars = null) - { - tagChars = customTagChars ?? ['_', '#', '\\']; + new EscapeTag(), + new DefaultTagHandler() + }; } - private bool CanBeTag(char symbol) => tagChars.Contains(symbol); - public string Render(string markdownString) { - int index = 0; + var index = 0; while (index < markdownString.Length) - { - if (CanBeTag(markdownString[index])) - { - if (TryProcessTag(ref markdownString, ref index)) - continue; - } - index++; - } + TryProcessTag(ref markdownString, ref index); return markdownString; } - private static bool TryProcessTag(ref string markdownString, ref int index) + private void TryProcessTag(ref string markdownString, ref int index) { - foreach (var handler in TagHandlers) + foreach (var handler in tagHandlers) { - if (handler.IsTagStart(markdownString, index)) - { + if (handler.IsTagStart(markdownString, index)){ index = handler.ProcessTag(ref markdownString, index); - return true; + break; } } - return false; } -} +} \ No newline at end of file diff --git a/cs/Markdown/Tags/BaseTagHandler.cs b/cs/Markdown/Tags/BaseTagHandler.cs index c6f568e90..5784d82ee 100644 --- a/cs/Markdown/Tags/BaseTagHandler.cs +++ b/cs/Markdown/Tags/BaseTagHandler.cs @@ -16,7 +16,7 @@ public virtual int ProcessTag(ref string text, int startIndex) if (HelperFunctions.ContainsUnderscore(text) && CheckTagIntersections(text)) return text.Length; - int endIndex; + var endIndex = 0; if (startIndex + Symbol.Length < text.Length && char.IsDigit(text[startIndex + Symbol.Length])) endIndex = FindEndIndexForDigit(text, startIndex); else @@ -25,14 +25,14 @@ public virtual int ProcessTag(ref string text, int startIndex) if (endIndex == -1) return startIndex + Symbol.Length; - string content = ExtractContent(text, startIndex, endIndex); + var content = ExtractContent(text, startIndex, endIndex); if (HelperFunctions.ContainsOnlySpases(content)) - return StringOnlySpases(ref text, endIndex, Symbol.Length); + return StringOnlySpaces(ref text, endIndex, Symbol.Length); if (!AreTagsCorrectlyPositioned(text, startIndex, endIndex, content)) return startIndex + content.Length; content = ProcessNestedTag(ref content); - string replacement = WrapWithHtmlTag(content); + var replacement = WrapWithHtmlTag(content); text = ReplaceText(text, startIndex, endIndex, replacement); return startIndex + replacement.Length; @@ -40,7 +40,7 @@ public virtual int ProcessTag(ref string text, int startIndex) private int FindEndIndexForDigit(string text, int startIndex) { - int currentIndex = startIndex + Symbol.Length; + var currentIndex = startIndex + Symbol.Length; while (currentIndex < text.Length) { @@ -65,7 +65,7 @@ private int FindEndIndexForDigit(string text, int startIndex) protected virtual int FindEndIndex(string text, int startIndex) { - int currentIndex = startIndex + Symbol.Length; + var currentIndex = startIndex + Symbol.Length; while (currentIndex < text.Length) { @@ -106,13 +106,11 @@ private bool CheckTagIntersections(string text) { for (int j = 0; j < doubleUnderscoreIndexes.Count - 1; j++) { - int[] segment1 = { singleUnderscoreIndexes[i], singleUnderscoreIndexes[i + 1] }; - int[] segment2 = { doubleUnderscoreIndexes[j], doubleUnderscoreIndexes[j + 1] }; - if (HelperFunctions.AreSegmentsIntersecting(segment1, segment2)) + var segment1 = Tuple.Create(singleUnderscoreIndexes[i], singleUnderscoreIndexes[i + 1]); + var segment2 = Tuple.Create(doubleUnderscoreIndexes[j], doubleUnderscoreIndexes[j + 1]); + if (HelperFunctions.AreSegmentsIntersecting(segment1, segment2) && + !HelperFunctions.AreSegmentsNested(segment1, segment2)) { - if (HelperFunctions.AreSegmentsNested(segment1, segment2)) - continue; - return true; } } @@ -121,7 +119,7 @@ private bool CheckTagIntersections(string text) return false; } - private bool AreTagsCorrectlyPositioned(string text, int startIndex, int endIndex, string content) + private static bool AreTagsCorrectlyPositioned(string text, int startIndex, int endIndex, string content) { if (!content.Contains(' ')) return true; @@ -154,7 +152,7 @@ protected virtual string ReplaceText(string text, int startIndex, int endIndex, return text.Substring(0, startIndex) + replacement + text.Substring(endIndex + Symbol.Length); } - protected virtual int StringOnlySpases(ref string text, int endIndex, int symbolLength) + protected virtual int StringOnlySpaces(ref string text, int endIndex, int symbolLength) { text = text.Substring(endIndex + symbolLength); return endIndex; diff --git a/cs/Markdown/Tags/DefaultTag.cs b/cs/Markdown/Tags/DefaultTag.cs new file mode 100644 index 000000000..64ef96144 --- /dev/null +++ b/cs/Markdown/Tags/DefaultTag.cs @@ -0,0 +1,14 @@ +namespace Markdown.Tags; + +public class DefaultTagHandler : ITagHandler +{ + public bool IsTagStart(string text, int index) + { + return true; + } + + public int ProcessTag(ref string text, int startIndex) + { + return startIndex + 1; + } +} diff --git a/cs/Markdown/Tags/HeadingTag.cs b/cs/Markdown/Tags/HeadingTag.cs index 11a02a98f..dad6f8b84 100644 --- a/cs/Markdown/Tags/HeadingTag.cs +++ b/cs/Markdown/Tags/HeadingTag.cs @@ -12,14 +12,14 @@ public override bool IsTagStart(string text, int index) public override int ProcessTag(ref string text, int startIndex) { - int endIndex = FindEndIndex(text, startIndex); - string content = ExtractContent(text, startIndex, endIndex); + var endIndex = FindEndIndex(text, startIndex); + var content = ExtractContent(text, startIndex, endIndex); content = HelperFunctions.RemoveExtraSpaces(content); if (HelperFunctions.ContainsOnlySpases(content) || HelperFunctions.ContainsOnlyHeading(content)) - return StringOnlySpases(ref text, endIndex, 0); + return StringOnlySpaces(ref text, endIndex, 0); content = ProcessNestedTag(ref content); - string replacement = WrapWithHtmlTag(content); + var replacement = WrapWithHtmlTag(content); text = ReplaceText(text, startIndex, endIndex, replacement); return startIndex + replacement.Length; @@ -38,7 +38,7 @@ protected override string ExtractContent(string text, int startIndex, int endInd protected override int FindEndIndex(string text, int startIndex) { - int endIndex = text.IndexOf('\n', startIndex + 1); + var endIndex = text.IndexOf('\n', startIndex + 1); return endIndex == -1 ? text.Length : endIndex; } diff --git a/cs/MarkdownTests/BoldTests.cs b/cs/MarkdownTests/BoldTests.cs index 571ccb04c..3ecec90cf 100644 --- a/cs/MarkdownTests/BoldTests.cs +++ b/cs/MarkdownTests/BoldTests.cs @@ -5,33 +5,37 @@ namespace MarkdownTests; public class BoldTests { - Md md; + private Md md; [SetUp] - public void SetUp(){ + public void SetUp() + { md = new Md(); } [Test] public void Test_StandartBoldWord() { + using var scope = new FluentAssertions.Execution.AssertionScope(); md.Render("__abc__4__").Should().Be("abc__4"); md.Render("__abc__4").Should().Be("__abc__4"); - md.Render("__aa bb__ __cc aa__").Should().Be("aa bb cc aa"); - md.Render("__aa bb__").Should().Be("aa bb"); md.Render("__aaaa__").Should().Be("aaaa"); - md.Render("__aa__ __bb__" + '\n' + "__aa__ __bb__").Should().Be("aa bb" + '\n' + "aa bb"); } [Test] public void Test_StandartBoldWords() { - md.Render("__WORD__ BOB __PON__").Should().Be("WORD BOB PON"); + using var scope = new FluentAssertions.Execution.AssertionScope(); md.Render("__aaaa__bbbb__cc__").Should().Be("aaaabbbbcc"); + md.Render("__WORD__ BOB __PON__").Should().Be("WORD BOB PON"); + md.Render("__aa bb__ __cc aa__").Should().Be("aa bb cc aa"); + md.Render("__aa bb__").Should().Be("aa bb"); + md.Render("__aa__ __bb__" + '\n' + "__aa__ __bb__").Should().Be("aa bb" + '\n' + "aa bb"); } [Test] public void Test_BoldWithOtherTags() { + using var scope = new FluentAssertions.Execution.AssertionScope(); md.Render("# __aaa__").Should().Be("

aaa

"); md.Render("__aaa_b_a__").Should().Be("aaaba"); md.Render("# __aaa __").Should().Be("

__aaa __

"); @@ -45,6 +49,7 @@ public void Test_BoldWithOtherTags() [Test] public void Test_BoldInPartOfWord() { + using var scope = new FluentAssertions.Execution.AssertionScope(); md.Render("__нач__але").Should().Be("начале"); md.Render("сер__еди__не").Should().Be("середине"); md.Render("кон__це.__").Should().Be("конце."); @@ -55,6 +60,7 @@ public void Test_BoldInPartOfWord() [Test] public void Test_BoldSeveralWords() { + using var scope = new FluentAssertions.Execution.AssertionScope(); md.Render("ра__зных словах__").Should().Be("ра__зных словах__"); md.Render("ра__зных сл__овах").Should().Be("ра__зных сл__овах"); } @@ -62,6 +68,7 @@ public void Test_BoldSeveralWords() [Test] public void Test_BoldTextWithSpaces() { + using var scope = new FluentAssertions.Execution.AssertionScope(); md.Render("__ __").Should().Be(""); md.Render("__ подчерки__").Should().Be("__ подчерки__"); md.Render("__подчерки __").Should().Be("__подчерки __"); diff --git a/cs/MarkdownTests/BorderlineCases.cs b/cs/MarkdownTests/BorderlineCases.cs index 250940c7d..a168e99d8 100644 --- a/cs/MarkdownTests/BorderlineCases.cs +++ b/cs/MarkdownTests/BorderlineCases.cs @@ -4,7 +4,7 @@ namespace MarkdownTests; public class BorderlineCasesTests { - Md md; + private Md md; [SetUp] public void SetUp(){ @@ -14,6 +14,7 @@ public void SetUp(){ [Test] public void Test_TextWithDigits() { + using var scope = new FluentAssertions.Execution.AssertionScope(); md.Render("__aaa12__3__a").Should().Be("__aaa12__3__a"); md.Render("__12__3").Should().Be("__12__3"); md.Render("_12_3").Should().Be("_12_3"); @@ -30,6 +31,7 @@ public void Test_TextWithDigits() [Test] public void Test_TagsWithoutText() { + using var scope = new FluentAssertions.Execution.AssertionScope(); md.Render("____").Should().Be("____"); md.Render("___").Should().Be("___"); md.Render("__").Should().Be("__"); @@ -38,6 +40,7 @@ public void Test_TagsWithoutText() [Test] public void Test_TextWithUnpairedTags() { + using var scope = new FluentAssertions.Execution.AssertionScope(); md.Render("__Непарные_").Should().Be("__Непарные_"); md.Render("_Непарные__").Should().Be("_Непарные__"); md.Render("__s_ __s").Should().Be("__s_ __s"); @@ -48,7 +51,9 @@ public void Test_TextWithUnpairedTags() [Test] public void Test_IntersectionOfTags() { + using var scope = new FluentAssertions.Execution.AssertionScope(); md.Render("_a __bbb__ a_").Should().Be("a __bbb__ a"); + md.Render("__a _bbb_ a__").Should().Be("a bbb a"); md.Render("__s _s__ _d_").Should().Be("s _s d"); md.Render("__s _s__ _d_").Should().Be("s _s d"); md.Render("__a _bbb__ a_").Should().Be("__a _bbb__ a_"); diff --git a/cs/MarkdownTests/HeadingTests.cs b/cs/MarkdownTests/HeadingTests.cs index 4e6060144..fbadc2ec4 100644 --- a/cs/MarkdownTests/HeadingTests.cs +++ b/cs/MarkdownTests/HeadingTests.cs @@ -5,7 +5,7 @@ namespace MarkdownTests; public class HeadingTests { - Md md; + private Md md; [SetUp] public void SetUp() @@ -15,6 +15,7 @@ public void SetUp() [Test] public void Test_StandartHeading() { + using var scope = new FluentAssertions.Execution.AssertionScope(); md.Render("# aa# aa# aa# aa# aa# aa").Should().Be("

aa# aa# aa# aa# aa# aa

"); md.Render("# aaaa").Should().Be("

aaaa

"); md.Render("#aaaa").Should().Be("#aaaa"); @@ -22,6 +23,7 @@ public void Test_StandartHeading() [Test] public void Test_HeadingTagsWithoutText() { + using var scope = new FluentAssertions.Execution.AssertionScope(); md.Render("#\n# Заголовок").Should().Be("\n

Заголовок

"); md.Render("#").Should().Be(""); md.Render("##").Should().Be(""); @@ -31,6 +33,7 @@ public void Test_HeadingTagsWithoutText() [Test] public void Test_HeadingWithOtherTags() { + using var scope = new FluentAssertions.Execution.AssertionScope(); md.Render("# __a__" + "\n" + "# _b_").Should().Be("

a

" + '\n' + "

b

"); md.Render("# Заголовок __с _разными_ символами__").Should().Be("

Заголовок с разными символами

"); md.Render("# __bold _italic_ text__").Should().Be("

bold italic text

"); @@ -40,6 +43,7 @@ public void Test_HeadingWithOtherTags() [Test] public void Test_HeadingWithTrailingSpaces() { + using var scope = new FluentAssertions.Execution.AssertionScope(); md.Render("# Заголовок").Should().Be("

Заголовок

"); md.Render("# Заголовок ").Should().Be("

Заголовок

"); md.Render("# Заголовок ф").Should().Be("

Заголовок ф

"); @@ -48,6 +52,7 @@ public void Test_HeadingWithTrailingSpaces() [Test] public void Test_InvalidHeadingWithoutSpace() { + using var scope = new FluentAssertions.Execution.AssertionScope(); md.Render("##Heading").Should().Be("##Heading"); md.Render("#Heading").Should().Be("#Heading"); } @@ -55,6 +60,7 @@ public void Test_InvalidHeadingWithoutSpace() [Test] public void Test_HeadingWithEmptyLines() { + using var scope = new FluentAssertions.Execution.AssertionScope(); md.Render("# aaa" + '\n' + "# bbb").Should().Be("

aaa

" + '\n' + "

bbb

"); md.Render("# aaa bbb" + '\n' + "# bbb").Should().Be("

aaa bbb

" + '\n' + "

bbb

"); md.Render("# aaa" + '\n' + "bbb").Should().Be("

aaa

" + '\n' + "bbb"); @@ -65,6 +71,7 @@ public void Test_HeadingWithEmptyLines() [Test] public void Test_HeadingWithSpecialCharacters() { + using var scope = new FluentAssertions.Execution.AssertionScope(); md.Render("# @#$%^&*() Заголовок").Should().Be("

@#$%^&*() Заголовок

"); md.Render("# Заголовок с символами !@#").Should().Be("

Заголовок с символами !@#

"); } @@ -72,6 +79,7 @@ public void Test_HeadingWithSpecialCharacters() [Test] public void Test_HeadingWithOnlyWhitespace() { + using var scope = new FluentAssertions.Execution.AssertionScope(); md.Render("# ").Should().Be(""); md.Render("#\t\t").Should().Be(""); } @@ -79,6 +87,7 @@ public void Test_HeadingWithOnlyWhitespace() [Test] public void Test_HeadingMixedWithOtherText() { + using var scope = new FluentAssertions.Execution.AssertionScope(); md.Render("Текст перед # Заголовок\nЕще текст").Should().Be("Текст перед

Заголовок

\nЕще текст"); md.Render("# Заголовок\nОбычный текст\n# Второй").Should().Be("

Заголовок

\nОбычный текст\n

Второй

"); } @@ -86,6 +95,7 @@ public void Test_HeadingMixedWithOtherText() [Test] public void Test_NestedTagsInsideHeading() { + using var scope = new FluentAssertions.Execution.AssertionScope(); md.Render("# _italic_ **bold** text").Should().Be("

italic **bold** text

"); md.Render("# **bold** _italic_ text").Should().Be("

**bold** italic text

"); } diff --git a/cs/MarkdownTests/ItalicTests.cs b/cs/MarkdownTests/ItalicTests.cs index 343d5cf22..c7f929df5 100644 --- a/cs/MarkdownTests/ItalicTests.cs +++ b/cs/MarkdownTests/ItalicTests.cs @@ -6,7 +6,7 @@ namespace MarkdownTests; public class ItailcTests { - Md md; + private Md md; [SetUp] public void SetUp(){ @@ -15,6 +15,7 @@ public void SetUp(){ [Test] public void Test_StandartItalicText() { + using var scope = new FluentAssertions.Execution.AssertionScope(); md.Render("_abc_4").Should().Be("_abc_4"); md.Render("_abc_4_").Should().Be("abc_4"); md.Render("_abc w_ a _b s_").Should().Be("abc w a b s"); @@ -32,6 +33,7 @@ public void Test_StandartItalicText() [Test] public void Test_NestedInItalicTags() { + using var scope = new FluentAssertions.Execution.AssertionScope(); md.Render("_some __text__ in_").Should().Be("some __text__ in"); md.Render("# _aaa_").Should().Be("

aaa

"); md.Render("_aaa__b__a_").Should().Be("aaa__b__a"); @@ -40,6 +42,7 @@ public void Test_NestedInItalicTags() [Test] public void Test_ItalicPartOfWord() { + using var scope = new FluentAssertions.Execution.AssertionScope(); md.Render("_нач_але").Should().Be("начале"); md.Render("сер_еди_не").Should().Be("середине"); md.Render("кон_це._").Should().Be("конце."); @@ -48,6 +51,7 @@ public void Test_ItalicPartOfWord() [Test] public void Test_ItalicSeveralWords() { + using var scope = new FluentAssertions.Execution.AssertionScope(); md.Render("ра_зных сл_овах").Should().Be("ра_зных сл_овах"); md.Render("ра_зных словах_").Should().Be("ра_зных словах_"); } @@ -55,6 +59,7 @@ public void Test_ItalicSeveralWords() [Test] public void Test_ItalicTextWithSpaces() { + using var scope = new FluentAssertions.Execution.AssertionScope(); md.Render("_ _").Should().Be(""); md.Render("_ подчерки_").Should().Be("_ подчерки_"); md.Render("_подчерки _").Should().Be("_подчерки _"); diff --git a/cs/MarkdownTests/ScreenTests.cs b/cs/MarkdownTests/ScreenTests.cs index bf9d82232..6676e9dbc 100644 --- a/cs/MarkdownTests/ScreenTests.cs +++ b/cs/MarkdownTests/ScreenTests.cs @@ -4,7 +4,7 @@ namespace MarkdownTests; public class ScreenTests { - Md md; + private Md md; [SetUp] public void SetUp(){ @@ -13,7 +13,7 @@ public void SetUp(){ [Test] public void Test_ScreenTests() { - Console.WriteLine("\\"); + using var scope = new FluentAssertions.Execution.AssertionScope(); md.Render("\\# aaa").Should().Be("# aaa", "Экранированный символ в начале. Экранирует заголовочный тэг"); md.Render("\\#aa\\").Should().Be("\\#aa\\", "Экранированный символ в конце и начале, но тэг неправильно написан. Нет экранизации"); md.Render("\\\\\\aaa").Should().Be("\\\\aaa", "Три экранированных символов и строка без тэгов. Экранируется 2 экранируемых символа"); From 883722fddb37b76d4b216c78c3e0204bfecb53cd Mon Sep 17 00:00:00 2001 From: crycrash Date: Sat, 14 Dec 2024 13:15:21 +0500 Subject: [PATCH 12/14] Added links --- cs/Markdown/HelperFunctions.cs | 2 + cs/Markdown/Md.cs | 1 + cs/Markdown/Tags/BaseTagHandler.cs | 1 - cs/Markdown/Tags/LinkTag.cs | 70 ++++++++++++++++++++++++++++++ cs/MarkdownTests/LinkTests.cs | 61 ++++++++++++++++++++++++++ 5 files changed, 134 insertions(+), 1 deletion(-) create mode 100644 cs/Markdown/Tags/LinkTag.cs create mode 100644 cs/MarkdownTests/LinkTests.cs diff --git a/cs/Markdown/HelperFunctions.cs b/cs/Markdown/HelperFunctions.cs index 7e2b55d12..63aa3f920 100644 --- a/cs/Markdown/HelperFunctions.cs +++ b/cs/Markdown/HelperFunctions.cs @@ -59,6 +59,8 @@ private static bool IsPartOfDoubleUnderscore(string text, int index) => public static bool ContainsUnderscore(string text) => text.Contains('_'); + public static bool ContainsSquareBrackets(string text) => text.Contains('['); + public static (List, List) GetUnderscoreIndexes(string text) { List singleUnderscoreIndexes = new List(); diff --git a/cs/Markdown/Md.cs b/cs/Markdown/Md.cs index 9f9dd6be2..5200ca575 100644 --- a/cs/Markdown/Md.cs +++ b/cs/Markdown/Md.cs @@ -14,6 +14,7 @@ public Md(List? customTagHandlers = null) new ItalicTag(), new HeadingTag(), new EscapeTag(), + new LinkTag(), new DefaultTagHandler() }; } diff --git a/cs/Markdown/Tags/BaseTagHandler.cs b/cs/Markdown/Tags/BaseTagHandler.cs index 5784d82ee..597f28ec0 100644 --- a/cs/Markdown/Tags/BaseTagHandler.cs +++ b/cs/Markdown/Tags/BaseTagHandler.cs @@ -94,7 +94,6 @@ protected virtual int FindEndIndex(string text, int startIndex) return -1; } - private bool CheckTagIntersections(string text) { (List singleUnderscoreIndexes, List doubleUnderscoreIndexes) = HelperFunctions.GetUnderscoreIndexes(text); diff --git a/cs/Markdown/Tags/LinkTag.cs b/cs/Markdown/Tags/LinkTag.cs new file mode 100644 index 000000000..f8b3239d3 --- /dev/null +++ b/cs/Markdown/Tags/LinkTag.cs @@ -0,0 +1,70 @@ +namespace Markdown.Tags; + +public class LinkTag : BaseTagHandler +{ + protected override string Symbol => "["; + protected override string HtmlTag => "a"; + private string HtmlTagLink => " href="; + public override bool IsTagStart(string text, int index) => + text[index].ToString() == Symbol; + + public override int ProcessTag(ref string text, int startIndex) + { + var endIndexOfNameLink = FindEndIndex(text, startIndex); + if (endIndexOfNameLink == -1) + return startIndex + 1; + var nameLink = ExtractContent(text, startIndex, endIndexOfNameLink); + var startIndexOfLink = FindStartIndexOfLink(text, endIndexOfNameLink); + if (startIndexOfLink == -1) + return startIndex + 1; + var endIndexOfLink = FindEndIndexOfLink(text, endIndexOfNameLink); + if (endIndexOfLink == -1) + return startIndex + 1; + var link = ExtractContent(text, startIndexOfLink, endIndexOfLink); + var replacement = WrapWithHtmlTag(nameLink, link); + if (HelperFunctions.ContainsOnlySpases(nameLink)) + replacement = ""; + text = ReplaceText(text, startIndex, endIndexOfLink, replacement); + return startIndex + replacement.Length; + + } + protected override int FindEndIndex(string text, int startIndex) + { + int bracketDepth = 0; + for (int i = startIndex; i < text.Length; i++) + { + if (text[i] == '[') + bracketDepth++; + else if (text[i] == ']') + { + bracketDepth--; + if (bracketDepth == 0) + { + if (i + 1 < text.Length && text[i + 1] == '(') + return i; + } + } + } + return -1; + } + + private int FindStartIndexOfLink(string text, int endIndexOfNameLink) + { + if (endIndexOfNameLink + 1 <= text.Length && text[endIndexOfNameLink + 1] == '(') + { + return endIndexOfNameLink + 1; + } + return -1; + } + + private int FindEndIndexOfLink(string text, int endIndexOfNameLink) + { + var currentIndex = text.IndexOf(')', endIndexOfNameLink); + return currentIndex; + } + + protected string WrapWithHtmlTag(string contentName, string contentLink) + { + return $"<{HtmlTag}{HtmlTagLink}{contentLink}>{contentName}"; + } +} diff --git a/cs/MarkdownTests/LinkTests.cs b/cs/MarkdownTests/LinkTests.cs new file mode 100644 index 000000000..cb05d2146 --- /dev/null +++ b/cs/MarkdownTests/LinkTests.cs @@ -0,0 +1,61 @@ +using FluentAssertions; +using Markdown; + +namespace MarkdownTests; + +public class LinkTests +{ + private Md md; + + [SetUp] + public void SetUp() + { + md = new Md(); + } + [Test] + public void Test_StandartLinks() + { + using var scope = new FluentAssertions.Execution.AssertionScope(); + md.Render("[Link](https://link.com)").Should().Be("Link"); + md.Render("[Link](https://example.com/?query=test&value=1)").Should().Be("Link"); + md.Render("[Тестовая ссылка](https://пример.рф)").Should().Be("Тестовая ссылка"); + md.Render("[Click me](https://example.com/#anchor)").Should().Be("Click me"); + } + + [Test] + public void Test_LinksWithoutName() + { + using var scope = new FluentAssertions.Execution.AssertionScope(); + md.Render("[]()").Should().Be(""); + md.Render("[](aaa)").Should().Be(""); + md.Render("[ ](aaa)").Should().Be(""); + md.Render("(This is not a link)").Should().Be("(This is not a link)"); + } + + [Test] + public void Test_LinksWithoutLink() + { + using var scope = new FluentAssertions.Execution.AssertionScope(); + md.Render("[asd]()").Should().Be("asd"); + md.Render("[This is not a link]").Should().Be("[This is not a link]"); + } + + [Test] + public void Test_LinksWithNestedBrackets() + { + using var scope = new FluentAssertions.Execution.AssertionScope(); + md.Render("[Nes [ted [Link]](https://example.com)").Should().Be("[Nes ted [Link]"); + md.Render("[Nested [Link]](https://example.com)").Should().Be("Nested [Link]"); + md.Render("[Link with ()](https://example.com)").Should().Be("Link with ()"); + md.Render("[Nested Link]](https://example.com)").Should().Be("[Nested Link]](https://example.com)"); + } + + [Test] + public void Test_InvalidLinkFormatting() + { + using var scope = new FluentAssertions.Execution.AssertionScope(); + md.Render("[UnclosedLink](https://example.com").Should().Be("[UnclosedLink](https://example.com"); + md.Render("Link](https://example.com)").Should().Be("Link](https://example.com)"); + md.Render("[Link]https://example.com").Should().Be("[Link]https://example.com"); + } +} \ No newline at end of file From c887712167f0f9876ff1f3cb72f806f2ad9611b1 Mon Sep 17 00:00:00 2001 From: crycrash Date: Sat, 14 Dec 2024 13:19:26 +0500 Subject: [PATCH 13/14] Processing link names with nested tags --- cs/Markdown/Tags/LinkTag.cs | 1 + cs/MarkdownTests/LinkTests.cs | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/cs/Markdown/Tags/LinkTag.cs b/cs/Markdown/Tags/LinkTag.cs index f8b3239d3..a6ecfc254 100644 --- a/cs/Markdown/Tags/LinkTag.cs +++ b/cs/Markdown/Tags/LinkTag.cs @@ -14,6 +14,7 @@ public override int ProcessTag(ref string text, int startIndex) if (endIndexOfNameLink == -1) return startIndex + 1; var nameLink = ExtractContent(text, startIndex, endIndexOfNameLink); + nameLink = ProcessNestedTag(ref nameLink); var startIndexOfLink = FindStartIndexOfLink(text, endIndexOfNameLink); if (startIndexOfLink == -1) return startIndex + 1; diff --git a/cs/MarkdownTests/LinkTests.cs b/cs/MarkdownTests/LinkTests.cs index cb05d2146..de68eaafe 100644 --- a/cs/MarkdownTests/LinkTests.cs +++ b/cs/MarkdownTests/LinkTests.cs @@ -58,4 +58,13 @@ public void Test_InvalidLinkFormatting() md.Render("Link](https://example.com)").Should().Be("Link](https://example.com)"); md.Render("[Link]https://example.com").Should().Be("[Link]https://example.com"); } + + [Test] + public void Test_LinksWithNestedFormatting() + { + using var scope = new FluentAssertions.Execution.AssertionScope(); + md.Render("[__Bold Link__](https://example.com)").Should().Be("Bold Link"); + md.Render("[_Italic Link_](https://example.com)").Should().Be("Italic Link"); + md.Render("[__Bold__ and _Italic_](https://example.com)").Should().Be("Bold and Italic"); + } } \ No newline at end of file From 02c538a96fc8775eabdff64cddc7373048de2e1f Mon Sep 17 00:00:00 2001 From: crycrash Date: Sat, 14 Dec 2024 13:28:23 +0500 Subject: [PATCH 14/14] Specification added --- MarkdownSpec.md | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/MarkdownSpec.md b/MarkdownSpec.md index 886e99c95..c1e161b46 100644 --- a/MarkdownSpec.md +++ b/MarkdownSpec.md @@ -70,4 +70,27 @@ __Непарные_ символы в рамках одного абзаца н превратится в: -\

Заголовок \с \разными\ символами\\

\ No newline at end of file +\

Заголовок \с \разными\ символами\\

+ +# Ссылки + +Формат: [Название ссылки](Ссылка) + +Текст помещенный в квадратные скобки является названием ссылки. +Это название будет видно пользователю при взаимодействии с ссылками, вместо самой ссылки. + +В круглые скобки помещается сама ссылка, которая будет скрываться за именем. + +Преобразованный вид: Название ссылки +Ключевое словов href обозначает начало ссылки, это слово помещается внутри открывающегося тэга <а>. +Далее идет навзание ссылки и затем закрывающийся тэг . + +В названии ссылки могут использоваться жирный и курсивный тэг. +[__Жирный выглядит так__](https://example.com) +[_Курсив выглядит так_](https://example.com) + +Ссылка должна иметь имя, чтобы корректно отображаться. +[](Это не ссылка) + +В отличие от этого имя может не иметь ссылки и это будет корректно. +[Например так]() \ No newline at end of file