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>окруженный с двух сторон\еm> одинарными символами подчерка,
+должен помещаться в HTML-тег \<еm>.
+
+
+
+# Полужирный
+
+__Выделенный двумя символами текст__ должен становиться полужирным с помощью тега \.
+
+
+
+# Экранирование
+
+Любой символ можно экранировать, чтобы он не считался частью разметки.
+\_Вот это\_, не должно выделиться тегом \<еm>.
+
+Символ экранирования исчезает из результата, только если экранирует что-то.
+Здесь сим\волы экранирования\ \должны остаться.\
+
+Символ экранирования тоже можно экранировать: \\_вот это будет выделено тегом_ \<еm>
+
+
+
+# Взаимодействие тегов
+
+Внутри __двойного выделения _одинарное_ тоже__ работает.
+
+Но не наоборот — внутри _одинарного __двойное__ не_ работает.
+
+Подчерки внутри текста c цифрами_12_3 не считаются выделением и должны оставаться символами подчерка.
+
+Однако выделять часть слова они могут: и в _нач_але, и в сер_еди_не, и в кон_це._
+
+В то же время выделение в ра_зных сл_овах не работает.
+
+__Непарные_ символы в рамках одного абзаца не считаются выделением.
+
+За подчерками, начинающими выделение, должен следовать непробельный символ. Иначе эти_ подчерки_ не считаются выделением
+и остаются просто символами подчерка.
+
+Подчерки, заканчивающие выделение, должны следовать за непробельным символом. Иначе эти _подчерки _не считаются_ окончанием выделения
+и остаются просто символами подчерка.
+
+В случае __пересечения _двойных__ и одинарных_ подчерков ни один из них не считается выделением.
+
+Если внутри подчерков пустая строка ____, то они остаются символами подчерка.
+
+
+
+# Заголовки
+
+Абзац, начинающийся с "# ", выделяется тегом \ в заголовок.
+В тексте заголовка могут присутствовать все прочие символы разметки с указанными правилами.
+
+Таким образом
+
+# Заголовок __с _разными_ символами__
+
+превратится в:
+
+\Заголовок \с \<еm>разными\е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