diff --git a/cs/MarkdownTests/Markdown.md b/cs/MarkdownTests/Markdown.md new file mode 100644 index 000000000..30a8c2eeb --- /dev/null +++ b/cs/MarkdownTests/Markdown.md @@ -0,0 +1,37 @@ +# Markdown + +Напиши процессор упрощённого markdown-подобного языка разметки. + +[Спецификация языка разметки](MarkdownSpec.md). + +## Формат + +В fork-е этого репозитория создай проект Markdown и реализуй метод Render класса Md. Он принимает в качестве аргумента текст в markdown-подобной разметке, и возвращает строку с html-кодом этого текста согласно спецификации. + +Обрати внимание, что в этой задаче запрещено использовать регулярные выражения. + +## Важные моменты +1. Проведи начальное проектирование: зафиксируй классы и их методы в коде (а также связи между классами), но не пиши внутренности методов +2. Покажи декомпозицию наставнику, получи обратную связь +3. После этого приступай к реализации методов, используя TDD +4. Помни, твой алгоритм должен работать быстро — линейно или почти линейно от размера входа. Не забудь написать такой тест! + +## Оценка + +#### Минимальные требования (на 1 балл) +1. Поддерживаются тэги `_`, `__` и `#` согласно спецификации +2. Тесты + +#### Полное решение (на 2 балла) +1. Выполнены минимальные требования +2. Решение разбито на составные части, каждая из которых легко читается + +#### Максимальное решение (на 3 балла) +1. Выполнено полное решение +2. Умеет рендериться один из дополнительных тегов: + - Маркерованный список + - Нумерованный список + - Ссылка + - Картинка +3. В файле [спецификации](MarkdownSpec.md) подробно описано, как работает новый тег +4. Обрати внимание: если упадет читаемость кода, то дополнительный балл засчитан не будет! diff --git a/cs/MarkdownTests/MarkdownSpec.md b/cs/MarkdownTests/MarkdownSpec.md new file mode 100644 index 000000000..6a9362a9a --- /dev/null +++ b/cs/MarkdownTests/MarkdownSpec.md @@ -0,0 +1,73 @@ +# Спецификация языка разметки + +Посмотрите этот файл в сыром виде. Сравните с тем, что показывает github. +Все совпадения случайны ;) + + + +# Курсив + +Текст, _окруженный с двух сторон_ одинарными символами подчерка, +должен помещаться в HTML-тег \<еm> вот так: + +Текст, \<еm>окруженный с двух сторон\ одинарными символами подчерка, +должен помещаться в HTML-тег \<еm>. + + + +# Полужирный + +__Выделенный двумя символами текст__ должен становиться полужирным с помощью тега \. + + + +# Экранирование + +Любой символ можно экранировать, чтобы он не считался частью разметки. +\_Вот это\_, не должно выделиться тегом \<еm>. + +Символ экранирования исчезает из результата, только если экранирует что-то. +Здесь сим\волы экранирования\ \должны остаться.\ + +Символ экранирования тоже можно экранировать: \\_вот это будет выделено тегом_ \<еm> + + + +# Взаимодействие тегов + +Внутри __двойного выделения _одинарное_ тоже__ работает. + +Но не наоборот — внутри _одинарного __двойное__ не_ работает. + +Подчерки внутри текста c цифрами_12_3 не считаются выделением и должны оставаться символами подчерка. + +Однако выделять часть слова они могут: и в _нач_але, и в сер_еди_не, и в кон_це._ + +В то же время выделение в ра_зных сл_овах не работает. + +__Непарные_ символы в рамках одного абзаца не считаются выделением. + +За подчерками, начинающими выделение, должен следовать непробельный символ. Иначе эти_ подчерки_ не считаются выделением +и остаются просто символами подчерка. + +Подчерки, заканчивающие выделение, должны следовать за непробельным символом. Иначе эти _подчерки _не считаются_ окончанием выделения +и остаются просто символами подчерка. + +В случае __пересечения _двойных__ и одинарных_ подчерков ни один из них не считается выделением. + +Если внутри подчерков пустая строка ____, то они остаются символами подчерка. + + + +# Заголовки + +Абзац, начинающийся с "# ", выделяется тегом \

в заголовок. +В тексте заголовка могут присутствовать все прочие символы разметки с указанными правилами. + +Таким образом + +# Заголовок __с _разными_ символами__ + +превратится в: + +\

Заголовок \с \<еm>разными\ символами\\

\ No newline at end of file diff --git a/cs/MarkdownTests/MarkdownTests.csproj b/cs/MarkdownTests/MarkdownTests.csproj new file mode 100644 index 000000000..9e6c35fd8 --- /dev/null +++ b/cs/MarkdownTests/MarkdownTests.csproj @@ -0,0 +1,29 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + + + + + + + + + + + + + diff --git a/cs/MarkdownTests/MdShould.cs b/cs/MarkdownTests/MdShould.cs new file mode 100644 index 000000000..7a00fa6b7 --- /dev/null +++ b/cs/MarkdownTests/MdShould.cs @@ -0,0 +1,78 @@ +using FluentAssertions; + +namespace Markdown.Tests; + +[TestFixture] +public class MdShould +{ + private Md _markdown = new(); + + [TestCase("test, _te x t_ tt", ExpectedResult = "test, te x t tt", TestName = "Render italic text with surrounding regular text")] + [TestCase("_be_gin", ExpectedResult = "begin", TestName = "Render italic text at word start")] + [TestCase("mi_dd_le", ExpectedResult = "middle", TestName = "Render italic text in word middle")] + [TestCase("the e_nd._", ExpectedResult = "the end.", TestName = "Render italic text at word end")] + public string RenderItalicText_ShouldParseCorrectly(string markdownText) => + _markdown.Render(markdownText); + + [TestCase("with numbers_12_3 text", ExpectedResult = "with numbers_12_3 text", TestName = "Should not parse when contains numbers")] + [TestCase("_text___", ExpectedResult = "_text___", TestName = "Should not parse with unmatched underscores")] + [TestCase("text_ text_", ExpectedResult = "text_ text_", TestName = "Should not parse with spaces after tags")] + [TestCase("_text text _text", ExpectedResult = "_text text _text", TestName = "Should not parse with space before end tag")] + [TestCase("text _text text", ExpectedResult = "text _text text", TestName = "Should not parse with only start tag")] + [TestCase("text t_ext ___te_xt", ExpectedResult = "text t_ext ___te_xt", TestName = "Should not parse with multiple underscores")] + public string RenderItalicText_ShouldNotParseInvalidCases(string markdownText) => + _markdown.Render(markdownText); + + [TestCase("__text__", ExpectedResult = "text", TestName = "Render basic bold text")] + public string RenderBoldText_ShouldParseCorrectly(string markdownText) => + _markdown.Render(markdownText); + + [TestCase(@"\_text\_", ExpectedResult = @"_text_", TestName = "Handle single escaped underscore")] + [TestCase(@"\\\_text\\\_", ExpectedResult = @"\_text\_", TestName = "Handle triple escaped underscore")] + public string RenderEscapedText_ShouldHandleEscapedUnderscores(string markdownText) => + _markdown.Render(markdownText); + + [TestCase(@"\\_text\\_", ExpectedResult = @"\text\", TestName = "Handle double escaped underscore")] + [TestCase(@"\\\\_text\\\\_", ExpectedResult = @"\\text\\", TestName = "Handle quad escaped underscore")] + public string RenderEscapedText_ShouldPreserveEscapedCharacters(string markdownText) => + _markdown.Render(markdownText); + + [TestCase("__t_t_t_t__", ExpectedResult = "ttt_t", TestName = "Render italic inside bold")] + [TestCase("__t_t___t__", ExpectedResult = "t_t___t", TestName = "Handle multiple underscores inside bold")] + [TestCase(@"\__t_t___t__", ExpectedResult = @"__t_t___t__", TestName = "Handle escaped bold start tag")] + [TestCase("t _t __t__ t_ t", ExpectedResult = @"t t __t__ t t", TestName = "Handle bold inside italic")] + [TestCase("t __t _t_ t__ t", ExpectedResult = @"t t t t t", TestName = "Handle nested italic in bold")] + [TestCase("t _t t __t t_ t__ t", ExpectedResult = "t _t t __t t_ t__ t", TestName = "Handle overlapping tags")] + [TestCase("__t _t t__ t t_ _t t__ t t_ t__ t", ExpectedResult = "__t _t t__ t t_ t t__ t t t__ t", TestName = "Handle complex tag intersections")] + [TestCase(@"Here sim\bols of shielding\ \should stay.\", ExpectedResult = @"Here sim\bols of shielding\ \should stay.\", TestName = "Preserve escaped characters")] + public string RenderMixedFormatting_ShouldHandleComplexCases(string markdownText) => + _markdown.Render(markdownText); + + [TestCase(" # text", ExpectedResult = "

text

", TestName = "Render header with leading spaces")] + [TestCase("# text", ExpectedResult = "

text

", TestName = "Render basic header")] + [TestCase("# _text_", ExpectedResult = "

text

", TestName = "Render header with italic text")] + [TestCase("# t __t _t_ t__", ExpectedResult = "

t t t t

", TestName = "Render header with mixed formatting")] + public string RenderHeaders_ShouldParseCorrectly(string markdownText) => + _markdown.Render(markdownText); + + [TestCase("#_text_", ExpectedResult = "#text", TestName = "Should not parse header without space")] + public string RenderHeaders_ShouldNotParseInvalidCases(string markdownText) => + _markdown.Render(markdownText); + + [Test] + [TestCase("Markdown.md", TestName = "Convert Markdown file to HTML")] + [TestCase("MarkdownSpec.md", TestName = "Convert MarkdownSpec file to HTML")] + public void RenderMarkdownFile_ShouldCreateHtmlFile(string markdownPath) + { + var markdownContent = File.ReadAllText(markdownPath); + var htmlPath = Path.ChangeExtension(markdownPath, ".html"); + File.Delete(htmlPath); + + using (var htmlWriter = File.CreateText(htmlPath)) + { + htmlWriter.WriteLine(_markdown.Render(markdownContent)); + } + + File.Exists(htmlPath).Should().BeTrue(); + } +} \ No newline at end of file diff --git a/cs/clean-code.sln b/cs/clean-code.sln index 09e1ec7aa..3e4431c93 100644 --- a/cs/clean-code.sln +++ b/cs/clean-code.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 -VisualStudioVersion = 17.12.35506.116 d17.12 +VisualStudioVersion = 17.12.35506.116 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Chess", "Chess\Chess.csproj", "{DBFBE40E-EE0C-48F4-8763-EBD11C960081}" EndProject @@ -11,6 +11,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Samples", "Samples\Samples. EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Markdown", "Markdown\Markdown.csproj", "{C2E421BA-68A4-42D6-92CC-57FE3FAD2E90}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MarkdownTests", "MarkdownTests\MarkdownTests.csproj", "{ADFA7A5D-9F1C-47A3-B996-75D6072F39F9}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -33,6 +35,10 @@ Global {C2E421BA-68A4-42D6-92CC-57FE3FAD2E90}.Debug|Any CPU.Build.0 = Debug|Any CPU {C2E421BA-68A4-42D6-92CC-57FE3FAD2E90}.Release|Any CPU.ActiveCfg = Release|Any CPU {C2E421BA-68A4-42D6-92CC-57FE3FAD2E90}.Release|Any CPU.Build.0 = Release|Any CPU + {ADFA7A5D-9F1C-47A3-B996-75D6072F39F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ADFA7A5D-9F1C-47A3-B996-75D6072F39F9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ADFA7A5D-9F1C-47A3-B996-75D6072F39F9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ADFA7A5D-9F1C-47A3-B996-75D6072F39F9}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE