Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Кашин Александр #223

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions cs/Markdown/IMarkdownRenderer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace Markdown;

public interface IMarkdownRenderer
{
string Render(string markdown);
}
15 changes: 15 additions & 0 deletions cs/Markdown/Markdown.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.12.2"/>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0"/>
<PackageReference Include="NUnit" Version="4.2.2"/>
</ItemGroup>

</Project>
32 changes: 32 additions & 0 deletions cs/Markdown/MarkdownRenderer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using Markdown.Render;
using Markdown.Tokenizer;
using Markdown.Tokenizer.Handlers;
using Markdown.TreeBuilder;

namespace Markdown;

public class MarkdownRenderer : IMarkdownRenderer
{
private readonly ITreeRenderer treeRenderer = new TreeRenderer();
private readonly ITokenizer tokenizer;

private readonly List<IHandler> handlers = new()
{
new HeaderHandler(),
new ItalicHandler(),
new BoldHandler(),
};

public MarkdownRenderer()
{
tokenizer = new MarkdownTokenizer(new HandlerManager(handlers), new TagProcessor());
}

public string Render(string markdown)
{
var tokens = tokenizer.Tokenize(markdown);
var tree = new TreeBuilder.TreeBuilder(new NodeFactory()).Build(tokens);

return treeRenderer.Render(tree);
}
}
8 changes: 8 additions & 0 deletions cs/Markdown/Render/ITreeRenderer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using Markdown.TreeBuilder.Nodes;

namespace Markdown.Render;

public interface ITreeRenderer
{
string Render(Node tokens);
}
25 changes: 25 additions & 0 deletions cs/Markdown/Render/TreeRenderer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System.Text;
using Markdown.TreeBuilder.Nodes;

namespace Markdown.Render;

public class TreeRenderer : ITreeRenderer
{
public string Render(Node tokens)
{
var sb = new StringBuilder();
foreach (var token in tokens.Children)
sb.Append(RenderToken(token));

return sb.ToString();
}

private string? RenderToken(Node node)
{
return node switch
{
TextNode textNode => textNode.Value,
_ => $"{node.OpenTag}{Render(node)}{node.CloseTag}"
};
}
}
47 changes: 47 additions & 0 deletions cs/Markdown/Tests/Markdown/MarkdownTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using NUnit.Framework;

namespace Markdown.Tests.Markdown;

[TestFixture]
public class MarkdownTests
{
private IMarkdownRenderer renderer;

[SetUp]
public void SetUp()
{
renderer = new MarkdownRenderer();
}

[TestCaseSource(nameof(MarkdownRendererTestCases))]
public string MarkdownRenderer_Verify(string input) => renderer.Render(input);

private static TestCaseData[] MarkdownRendererTestCases =
[
new TestCaseData("# Header").Returns("<h1>Header</h1>").SetDescription("Простой заголовок."),
new TestCaseData("\\# Header").Returns("# Header").SetDescription("Экранированный заголовок."),
new TestCaseData("\\\\# Header").Returns("\\<h1>Header</h1>").SetDescription("Экранирован экранирования."),
new TestCaseData("_Italic text_").Returns("<em>Italic text</em>").SetDescription("Курсив"),
new TestCaseData("\\_Text_").Returns("_Text_").SetDescription("Экранирование курсива."),
new TestCaseData("\\\\_Italic text_").Returns("\\<em>Italic text</em>")
.SetDescription("Экранирование экранирования курсива."),
new TestCaseData("_Italic text").Returns("_Italic text").SetDescription("Одинокий открывающий тэг."),
new TestCaseData("Italic text_").Returns("Italic text_").SetDescription("Одинокий закрывающий тэг."),
new TestCaseData("Italic_ text_").Returns("Italic_ text_").SetDescription("Два закрывающих тэга."),
new TestCaseData("_Italic _text").Returns("_Italic _text").SetDescription("Два открывающих тэга."),
new TestCaseData("_нач_але").Returns("<em>нач</em>але").SetDescription("Курсив в начале слова."),
new TestCaseData("сер_еди_не").Returns("сер<em>еди</em>не").SetDescription("Курсив в середине слова."),
new TestCaseData("кон_це._").Returns("кон<em>це.</em>").SetDescription("Курсив в конце слова."),
new TestCaseData("цифры_1_12_3").Returns("цифры_1_12_3").SetDescription("Между цифр - подчерки."),
new TestCaseData("в ра_зных сл_овах не").Returns("в ра_зных сл_овах не")
.SetDescription("В разных словах - не работает."),
new TestCaseData("__bold__").Returns("<strong>bold</strong>").SetDescription("Полужирный"),
new TestCaseData("_Text__").Returns("_Text__").SetDescription("Разные тэги 1"),
new TestCaseData("__Text_").Returns("__Text_").SetDescription("Разные тэги 2"),
new TestCaseData("__Italic __text").Returns("__Italic __text").SetDescription("Два открывающих тэга."),
new TestCaseData("__два _один_ может__").Returns("<strong>два <em>один</em> может</strong>")
.SetDescription("Курсив в полужирном."),
new TestCaseData("_одинарного __двойное__ не_").Returns("<em>одинарного __двойное__ не</em>")
.SetDescription("Полужирный в курсиве - не работает."),
];
}
54 changes: 54 additions & 0 deletions cs/Markdown/Tests/Tokenizer/BoldHandlerTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using FluentAssertions;
using Markdown.Tokenizer;
using Markdown.Tokenizer.Handlers;
using Markdown.Tokenizer.Tags;
using Markdown.TreeBuilder;
using NUnit.Framework;

namespace Markdown.Tests.Tokenizer;

[TestFixture]
public class BoldHandlerTests
{
private ITokenizer tokenizer;

[SetUp]
public void SetUp()
{
var handlers = new List<IHandler> { new HeaderHandler(), new ItalicHandler(), new BoldHandler() };
tokenizer = new MarkdownTokenizer(new HandlerManager(handlers), new TagProcessor());
}

[TestCaseSource(nameof(BoldTokenSource))]
public void BoldTokenizerTests((string input, Token[] tags) testCase)
{
var tokens = tokenizer.Tokenize(testCase.input).ToArray();

for (var i = 0; i < testCase.tags.Length; i++)
{
tokens[i].Value.Should().Be(testCase.tags[i].Value);
tokens[i].TokenType.Should().Be(testCase.tags[i].TokenType);
}
}

public static IEnumerable<(string input, Token[] result)> BoldTokenSource()
{
yield return ("__abc__", [
new BoldTag(TagStatus.Open),
new TextToken("abc"),
new BoldTag(TagStatus.Closed)
]);

yield return ("_abc__", [
new ItalicTag(TagStatus.Open),
new TextToken("abc"),
new BoldTag(TagStatus.Closed)
]);

yield return ("__abc_", [
new BoldTag(TagStatus.Open),
new TextToken("abc"),
new ItalicTag(TagStatus.Closed)
]);
}
}
49 changes: 49 additions & 0 deletions cs/Markdown/Tests/Tokenizer/HeaderHandlerTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
using FluentAssertions;
using Markdown.Tokenizer;
using Markdown.Tokenizer.Handlers;
using Markdown.Tokenizer.Tags;
using Markdown.TreeBuilder;
using NUnit.Framework;

namespace Markdown.Tests.Tokenizer;

[TestFixture]
public class HeaderHandlerTests
{
private ITokenizer tokenizer;

[SetUp]
public void SetUp()
{
var handlers = new List<IHandler> { new HeaderHandler(), new ItalicHandler(), new BoldHandler() };
tokenizer = new MarkdownTokenizer(new HandlerManager(handlers), new TagProcessor());
}

[TestCaseSource(nameof(HeaderTokenSource))]
public void HeaderTokenizerTests((string input, Token[] tags) testCase)
{
var tokens = tokenizer.Tokenize(testCase.input).ToArray();

for (var i = 0; i < testCase.tags.Length; i++)
{
tokens[i].Value.Should().Be(testCase.tags[i].Value);
tokens[i].TokenType.Should().Be(testCase.tags[i].TokenType);
}
}

private static IEnumerable<(string input, Token[] tags)> HeaderTokenSource()
{
yield return ("abc", [new TextToken("abc")]);
yield return ("# abc", [new HeaderTag(), new TextToken("abc")]);
yield return ("f# abc", [new TextToken("f#"), new TextToken(" abc")]);
yield return ("\\# abc", [new SlashToken(), new HeaderTag(), new TextToken("abc")]);
yield return ("\\\\# abc", [new SlashToken(), new SlashToken(), new HeaderTag(), new TextToken("abc")]);
yield return ("# abc\n# qwe", [
new HeaderTag(),
new TextToken("abc"),
new NewLineToken(),
new HeaderTag(),
new TextToken("qwe")
]);
}
}
80 changes: 80 additions & 0 deletions cs/Markdown/Tests/Tokenizer/ItalicHandlerTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
using FluentAssertions;
using Markdown.Tokenizer;
using Markdown.Tokenizer.Handlers;
using Markdown.Tokenizer.Tags;
using Markdown.TreeBuilder;
using NUnit.Framework;


namespace Markdown.Tests.Tokenizer;

[TestFixture]
public class ItalicParserTests
{
private ITokenizer tokenizer;

[SetUp]
public void SetUp()
{
var handlers = new List<IHandler> { new HeaderHandler(), new ItalicHandler(), new BoldHandler() };
tokenizer = new MarkdownTokenizer(new HandlerManager(handlers), new TagProcessor());
}

[TestCaseSource(nameof(ItalicTokenSource))]
public void ItalicTokenizerTests((string input, Token[] tags) testCase)
{
var tokens = tokenizer.Tokenize(testCase.input).ToArray();

for (var i = 0; i < testCase.tags.Length; i++)
{
tokens[i].Value.Should().Be(testCase.tags[i].Value);
tokens[i].TokenType.Should().Be(testCase.tags[i].TokenType);
}
}

private static IEnumerable<(string input, Token[] tags)> ItalicTokenSource()
{
yield return ("abc", [new TextToken("abc")]);
yield return ("_abc", [new ItalicTag(TagStatus.Open), new TextToken("abc")]);
yield return ("abc_", [new TextToken("abc"), new ItalicTag(TagStatus.Closed)]);
yield return ("a_bc_", [
new TextToken("a"),
new ItalicTag(TagStatus.InWord),
new TextToken("bc"),
new ItalicTag(TagStatus.Closed)
]);
yield return ("_a_bc", [
new ItalicTag(TagStatus.Open),
new TextToken("a"),
new ItalicTag(TagStatus.InWord),
new TextToken("bc")
]);

yield return ("_a_bc_", [
new ItalicTag(TagStatus.Open),
new TextToken("a"),
new ItalicTag(TagStatus.InWord),
new TextToken("bc"),
new ItalicTag(TagStatus.Closed)
]);

yield return ("_abc_", [
new ItalicTag(TagStatus.Open),
new TextToken("abc"),
new ItalicTag(TagStatus.Closed)
]);

yield return ("\\_abc", [
new SlashToken(),
new ItalicTag(TagStatus.Open),
new TextToken("abc")
]);

yield return ("\\\\_abc", [
new SlashToken(),
new SlashToken(),
new ItalicTag(TagStatus.Open),
new TextToken("abc")
]);
}
}
33 changes: 33 additions & 0 deletions cs/Markdown/Tokenizer/HandlerManager.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using System.Text;
using Markdown.Tokenizer.Handlers;
using Markdown.Tokenizer.Tags;

namespace Markdown.Tokenizer;

public class HandlerManager(IEnumerable<IHandler> handlers) : IHandlerManager
{
private readonly List<IHandler> handlers = handlers.ToList();

public void TryHandle(TokenizerContext context, StringBuilder buffer, List<Token> tags, Stack<Token> tagStack)
{
foreach (var handler in handlers)
{
var tag = handler.ProceedSymbol(context);
if (tag != null)
{
if (buffer.Length > 0)
{
var token = new TextToken(buffer.ToString());
tags.Add(token);
buffer.Clear();
}

tags.Add(tag);
tagStack.Push(tag);
return;
}
}

buffer.Append(context.Current);
}
}
Loading