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 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/HelperFunctions.cs b/cs/Markdown/HelperFunctions.cs new file mode 100644 index 000000000..63aa3f920 --- /dev/null +++ b/cs/Markdown/HelperFunctions.cs @@ -0,0 +1,107 @@ +using Markdown.Tags; +namespace Markdown; + +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) && IsValidCloseSymbol(text, i)) + return i; + } + return -1; + } + + 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; + } + + public static string ProcessNestedTag(ref string text) + { + List tagHandlers = new List + { + new BoldTag(), + new ItalicTag(), + new EscapeTag(), + new DefaultTagHandler() + }; + Md md = new Md(tagHandlers); + return md.Render(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] == '_'); + + 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(); + 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(Tuple segment1, Tuple segment2) + { + return segment1.Item2 >= segment2.Item1 && segment1.Item1 <= segment2.Item2; + } + + public static bool AreSegmentsNested(Tuple segment1, Tuple segment2) + { + 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(' ', StringSplitOptions.RemoveEmptyEntries)); + } +} diff --git a/cs/Markdown/Markdown.csproj b/cs/Markdown/Markdown.csproj new file mode 100644 index 000000000..cb9d759b7 --- /dev/null +++ b/cs/Markdown/Markdown.csproj @@ -0,0 +1,11 @@ + + + + net8.0 + enable + enable + + false + false + + \ No newline at end of file diff --git a/cs/Markdown/Md.cs b/cs/Markdown/Md.cs new file mode 100644 index 000000000..5200ca575 --- /dev/null +++ b/cs/Markdown/Md.cs @@ -0,0 +1,41 @@ +using Markdown.Tags; + +namespace Markdown; + +public class Md +{ + private readonly List tagHandlers; + + public Md(List? customTagHandlers = null) + { + tagHandlers = customTagHandlers ?? new List + { + new BoldTag(), + new ItalicTag(), + new HeadingTag(), + new EscapeTag(), + new LinkTag(), + new DefaultTagHandler() + }; + } + + public string Render(string markdownString) + { + var index = 0; + while (index < markdownString.Length) + TryProcessTag(ref markdownString, ref index); + + return markdownString; + } + + private void TryProcessTag(ref string markdownString, ref int index) + { + foreach (var handler in tagHandlers) + { + if (handler.IsTagStart(markdownString, index)){ + index = handler.ProcessTag(ref markdownString, index); + break; + } + } + } +} \ 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..597f28ec0 --- /dev/null +++ b/cs/Markdown/Tags/BaseTagHandler.cs @@ -0,0 +1,159 @@ +using System.Runtime.InteropServices; + +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 virtual int ProcessTag(ref string text, int startIndex) + { + if (HelperFunctions.ContainsOnlyDash(text.Substring(startIndex))) + return text.Length; + if (HelperFunctions.ContainsUnderscore(text) && CheckTagIntersections(text)) + return text.Length; + + var endIndex = 0; + if (startIndex + Symbol.Length < text.Length && char.IsDigit(text[startIndex + Symbol.Length])) + endIndex = FindEndIndexForDigit(text, startIndex); + else + endIndex = FindEndIndex(text, startIndex); + + if (endIndex == -1) + return startIndex + Symbol.Length; + + var content = ExtractContent(text, startIndex, endIndex); + if (HelperFunctions.ContainsOnlySpases(content)) + return StringOnlySpaces(ref text, endIndex, Symbol.Length); + if (!AreTagsCorrectlyPositioned(text, startIndex, endIndex, content)) + return startIndex + content.Length; + + content = ProcessNestedTag(ref content); + var replacement = WrapWithHtmlTag(content); + text = ReplaceText(text, startIndex, endIndex, replacement); + + return startIndex + replacement.Length; + } + + private int FindEndIndexForDigit(string text, int startIndex) + { + var 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) + { + var 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); + + 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++) + { + 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)) + { + return true; + } + } + } + + return false; + } + + private static 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); + } + + 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); + } + + protected virtual int StringOnlySpaces(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 new file mode 100644 index 000000000..5e74ab727 --- /dev/null +++ b/cs/Markdown/Tags/BoldTag.cs @@ -0,0 +1,9 @@ +namespace Markdown.Tags; +public class BoldTag : BaseTagHandler +{ + protected override string Symbol => "__"; + protected override string HtmlTag => "strong"; + + 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/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/EscapeTag.cs b/cs/Markdown/Tags/EscapeTag.cs new file mode 100644 index 000000000..bc0497f8d --- /dev/null +++ b/cs/Markdown/Tags/EscapeTag.cs @@ -0,0 +1,25 @@ +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) => 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 new file mode 100644 index 000000000..dad6f8b84 --- /dev/null +++ b/cs/Markdown/Tags/HeadingTag.cs @@ -0,0 +1,65 @@ +namespace Markdown.Tags; +public class HeadingTag : BaseTagHandler +{ + protected override string Symbol => "#"; + protected override string HtmlTag => "h1"; + + public override bool IsTagStart(string text, int index) + { + 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) + { + var endIndex = FindEndIndex(text, startIndex); + var content = ExtractContent(text, startIndex, endIndex); + content = HelperFunctions.RemoveExtraSpaces(content); + if (HelperFunctions.ContainsOnlySpases(content) || HelperFunctions.ContainsOnlyHeading(content)) + return StringOnlySpaces(ref text, endIndex, 0); + + content = ProcessNestedTag(ref content); + var replacement = WrapWithHtmlTag(content); + text = ReplaceText(text, startIndex, endIndex, replacement); + + return startIndex + replacement.Length; + } + + protected override string ProcessNestedTag(ref string text) => + HelperFunctions.ProcessNestedTag(ref text); + + protected override string ExtractContent(string text, int startIndex, int endIndex) + { + startIndex = FindStartIndex(text, startIndex + 1, endIndex); + if (startIndex == -1) + return ""; + return text.Substring(startIndex, endIndex - startIndex); + } + + protected override int FindEndIndex(string text, int startIndex) + { + var endIndex = text.IndexOf('\n', startIndex + 1); + return endIndex == -1 ? text.Length : endIndex; + } + + private int FindStartIndex(string text, int startIndex, int endIndex) + { + if (string.IsNullOrWhiteSpace(text.Substring(startIndex))) + return -1; + + for (int i = startIndex; i < endIndex; 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') + 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/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 new file mode 100644 index 000000000..112db9d02 --- /dev/null +++ b/cs/Markdown/Tags/ItalicTag.cs @@ -0,0 +1,13 @@ +namespace Markdown.Tags; +public class ItalicTag : BaseTagHandler +{ + protected override string Symbol => "_"; + protected override string HtmlTag => "em"; + + public override bool IsTagStart(string text, int index) => text[index].ToString() == Symbol; + + protected override int FindEndIndex(string text, int startIndex) => + HelperFunctions.FindCorrectCloseSymbolForItalic(text, startIndex + 1); + + protected override string ProcessNestedTag(ref string text) => text; +} \ No newline at end of file diff --git a/cs/Markdown/Tags/LinkTag.cs b/cs/Markdown/Tags/LinkTag.cs new file mode 100644 index 000000000..a6ecfc254 --- /dev/null +++ b/cs/Markdown/Tags/LinkTag.cs @@ -0,0 +1,71 @@ +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); + nameLink = ProcessNestedTag(ref nameLink); + 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/BoldTests.cs b/cs/MarkdownTests/BoldTests.cs new file mode 100644 index 000000000..3ecec90cf --- /dev/null +++ b/cs/MarkdownTests/BoldTests.cs @@ -0,0 +1,76 @@ +using FluentAssertions; +using Markdown; + +namespace MarkdownTests; + +public class BoldTests +{ + private Md md; + + [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("__aaaa__").Should().Be("aaaa"); + } + + [Test] + public void Test_StandartBoldWords() + { + 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 __

"); + 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() + { + using var scope = new FluentAssertions.Execution.AssertionScope(); + 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() + { + using var scope = new FluentAssertions.Execution.AssertionScope(); + md.Render("ра__зных словах__").Should().Be("ра__зных словах__"); + md.Render("ра__зных сл__овах").Should().Be("ра__зных сл__овах"); + } + + [Test] + public void Test_BoldTextWithSpaces() + { + using var scope = new FluentAssertions.Execution.AssertionScope(); + 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 new file mode 100644 index 000000000..a168e99d8 --- /dev/null +++ b/cs/MarkdownTests/BorderlineCases.cs @@ -0,0 +1,64 @@ +using FluentAssertions; +using Markdown; +namespace MarkdownTests; + +public class BorderlineCasesTests +{ + private Md md; + + [SetUp] + public void SetUp(){ + md = new Md(); + } + + [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"); + 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() + { + using var scope = new FluentAssertions.Execution.AssertionScope(); + md.Render("____").Should().Be("____"); + md.Render("___").Should().Be("___"); + md.Render("__").Should().Be("__"); + md.Render("_").Should().Be("_"); + } + [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"); + md.Render("__s _s__").Should().Be("s _s"); + md.Render("__s_ s__").Should().Be("s_ s"); + } + + [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_"); + 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 new file mode 100644 index 000000000..fbadc2ec4 --- /dev/null +++ b/cs/MarkdownTests/HeadingTests.cs @@ -0,0 +1,102 @@ +using Markdown; +using FluentAssertions; + +namespace MarkdownTests; + +public class HeadingTests +{ + private Md md; + + [SetUp] + public void SetUp() + { + md = new Md(); + } + [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"); + } + [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(""); + md.Render("###").Should().Be(""); + } + + [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

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

bold italic text

\n"); + } + + [Test] + public void Test_HeadingWithTrailingSpaces() + { + using var scope = new FluentAssertions.Execution.AssertionScope(); + md.Render("# Заголовок").Should().Be("

Заголовок

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

Заголовок

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

Заголовок ф

"); + } + + [Test] + public void Test_InvalidHeadingWithoutSpace() + { + using var scope = new FluentAssertions.Execution.AssertionScope(); + md.Render("##Heading").Should().Be("##Heading"); + md.Render("#Heading").Should().Be("#Heading"); + } + + [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"); + md.Render("# abb" + "\n" + "ba_").Should().Be("

abb

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

Заголовок

\n\n

Второй

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

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

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

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

"); + } + + [Test] + public void Test_HeadingWithOnlyWhitespace() + { + using var scope = new FluentAssertions.Execution.AssertionScope(); + md.Render("# ").Should().Be(""); + md.Render("#\t\t").Should().Be(""); + } + + [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

Второй

"); + } + + [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

"); + } +} \ No newline at end of file diff --git a/cs/MarkdownTests/ItalicTests.cs b/cs/MarkdownTests/ItalicTests.cs new file mode 100644 index 000000000..c7f929df5 --- /dev/null +++ b/cs/MarkdownTests/ItalicTests.cs @@ -0,0 +1,67 @@ +using Markdown; +using FluentAssertions; +using System.Reflection.Metadata; +using System.ComponentModel.DataAnnotations; +namespace MarkdownTests; + +public class ItailcTests +{ + private Md md; + + [SetUp] + public void SetUp(){ + md = new Md(); + } + [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"); + 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() + { + 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"); + md.Render("_aaa __b__ a_").Should().Be("aaa __b__ a"); + } + [Test] + public void Test_ItalicPartOfWord() + { + using var scope = new FluentAssertions.Execution.AssertionScope(); + md.Render("_нач_але").Should().Be("начале"); + md.Render("сер_еди_не").Should().Be("середине"); + md.Render("кон_це._").Should().Be("конце."); + } + + [Test] + public void Test_ItalicSeveralWords() + { + using var scope = new FluentAssertions.Execution.AssertionScope(); + md.Render("ра_зных сл_овах").Should().Be("ра_зных сл_овах"); + md.Render("ра_зных словах_").Should().Be("ра_зных словах_"); + } + + [Test] + public void Test_ItalicTextWithSpaces() + { + using var scope = new FluentAssertions.Execution.AssertionScope(); + md.Render("_ _").Should().Be(""); + md.Render("_ подчерки_").Should().Be("_ подчерки_"); + md.Render("_подчерки _").Should().Be("_подчерки _"); + } +} \ No newline at end of file diff --git a/cs/MarkdownTests/LinkTests.cs b/cs/MarkdownTests/LinkTests.cs new file mode 100644 index 000000000..de68eaafe --- /dev/null +++ b/cs/MarkdownTests/LinkTests.cs @@ -0,0 +1,70 @@ +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"); + } + + [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 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..6676e9dbc --- /dev/null +++ b/cs/MarkdownTests/ScreenTests.cs @@ -0,0 +1,30 @@ +using Markdown; +using FluentAssertions; +namespace MarkdownTests; + +public class ScreenTests +{ + private Md md; + + [SetUp] + public void SetUp(){ + md = new Md(); + } + [Test] + public void Test_ScreenTests() + { + 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 экранируемых символа"); + 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 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