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

Косторной Дмитрий #238

Open
wants to merge 16 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
55 changes: 54 additions & 1 deletion MarkdownSpec.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,4 +70,57 @@ __Непарные_ символы в рамках одного абзаца н

превратится в:

\<h1>Заголовок \<strong>с \<em>разными\</em> символами\</strong>\</h1>
\<h1>Заголовок \<strong>с \<em>разными\</em> символами\</strong>\</h1>



# Ссылки

Текст, заключенный в квадратные скобки [], с последующим URL в круглых скобках (), должен выделяться тегом \<a> со следующим синтаксисом:

\[Текст ссылки](URL)

Пример:

Это текст с \[ссылкой](http://link.com)

Будет преобразовано в:

Это текст с \<a href="http://link.com">ссылкой \</a>


Внутри текста ссылки допускается использование других элементов разметки:

Это \[ссылка с \_курсивом\_](http://link.com)

Будет преобразовано в:

Это \<a href="http://link.com">ссылка с \<em>курсивом\</em>\</a>


Квадратные и круглые скобки, также как и другие специальные символы внутри текста ссылки можно экранировать:

\[Ссылка с экранированными \\\] символами\\\]\](http://link.com)

Будет преобразовано в:

\<a href="http://link.com">Ссылка с экранированными ] символами]\</a>


Если текст ссылки или URL не завершен, разметка остается неизменной.

Примеры:

\[незавершённая ссылка(http://link.com)

\[незавершённая ссылка](http://link.com

Ссылки могут быть вложены в другие элементы разметки, такие как заголовки, курсив или полужирный текст с теми же правилами, что и остальные теги

# Пример сложной вложенности с ссылками:

\# h \_\_E \_e_ \[ссылка](http://link.com) E__ \_e_

Будет преобразовано в:

\<h1>h \<strong>E \<em>e\</em> \<a href="http://link.com">ссылка \</a> E\</strong> \<em>e\</em>\</h1>
8 changes: 8 additions & 0 deletions cs/Markdown/Interfaces/IMarkdownParser.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using Markdown.Tokens;

namespace Markdown.Interfaces;

public interface IMarkdownParser
{
IEnumerable<BaseToken> ParseTokens(string markdownText);
}
8 changes: 8 additions & 0 deletions cs/Markdown/Interfaces/IRenderer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using Markdown.Tokens;

namespace Markdown.Interfaces;

public interface IRenderer
{
string Render(IEnumerable<BaseToken> tokens);
}
9 changes: 9 additions & 0 deletions cs/Markdown/Interfaces/ITokenConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using System.Text;
using Markdown.Tokens;

namespace Markdown.Interfaces;

public interface ITokenConverter
{
void Render(BaseToken baseToken, StringBuilder result);
}
9 changes: 9 additions & 0 deletions cs/Markdown/Interfaces/ITokenHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using Markdown.Parsers;

namespace Markdown.Interfaces;

public interface ITokenHandler
{
bool CanHandle(char current, char next, MarkdownParseContext context);
void Handle(MarkdownParseContext context);
}
9 changes: 9 additions & 0 deletions cs/Markdown/Markdown.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">

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

</Project>
21 changes: 21 additions & 0 deletions cs/Markdown/Md.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using Markdown.Interfaces;

namespace Markdown;

public class Md
{
private readonly IRenderer renderer;
private readonly IMarkdownParser parser;

public Md(IRenderer renderer, IMarkdownParser parser)
{
this.renderer = renderer;
this.parser = parser;
}

public string Render(string markdownText)
{
var tokens = parser.ParseTokens(markdownText);
return renderer.Render(tokens);
}
}
63 changes: 63 additions & 0 deletions cs/Markdown/Parsers/MarkdownParser.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
using Markdown.Interfaces;
using Markdown.TokenHandlers;
using Markdown.Tokens;

namespace Markdown.Parsers;

public class MarkdownParser : IMarkdownParser
{
private readonly List<ITokenHandler> handlers =
[
new StrongTokenHandler(),
new HeaderTokenHandler(),
new EmphasisTokenHandler(),
new NewLineHandler(),
new EscapeCharacterHandler(),
new LinkTokenHandler()
];

public IEnumerable<BaseToken> ParseTokens(string markdownText)
{
ArgumentNullException.ThrowIfNull(markdownText);

var context = new MarkdownParseContext
{
MarkdownText = markdownText,
Parser = this
};

while (context.CurrentIndex < context.MarkdownText.Length)
{
var current = context.MarkdownText[context.CurrentIndex];
var next = context.CurrentIndex + 1 < context.MarkdownText.Length
? context.MarkdownText[context.CurrentIndex + 1]
: '\0';

var handler = handlers.FirstOrDefault(h => h.CanHandle(current, next, context));
if (handler != null)
{
handler.Handle(context);
}
else
{
context.Buffer.Append(current);
context.CurrentIndex++;
}
}

AddToken(context, TokenType.Text);
return context.Tokens;
}

public static void AddToken(MarkdownParseContext context, TokenType type)
{
if (context.Buffer.Length == 0) return;
var token = new BaseToken(type, context.Buffer.ToString());
context.Buffer.Clear();

if (context.Stack.Count > 0)
context.Stack.Peek().Children.Add(token);
else
context.Tokens.Add(token);
}
}
17 changes: 17 additions & 0 deletions cs/Markdown/Parsers/MarkdownParserContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System.Text;
using Markdown.Interfaces;
using Markdown.Tokens;

namespace Markdown.Parsers;

public class MarkdownParseContext
{
public Stack<BaseToken> Stack { get; } = new();
public StringBuilder Buffer { get; } = new();
public List<BaseToken> Tokens { get; } = [];
public List<int> IntersectedIndexes { get; } = [];
public string MarkdownText { get; init; } = "";
public int CurrentIndex { get; set; }
public int HeaderLevel { get; set; }
public required IMarkdownParser Parser { get; init; }
}
22 changes: 22 additions & 0 deletions cs/Markdown/Renderers/HtmlRenderer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System.Text;
using Markdown.Interfaces;
using Markdown.TokenConverters;
using Markdown.Tokens;

namespace Markdown.Renderers;

public class HtmlRenderer : IRenderer
{
public string Render(IEnumerable<BaseToken> tokens)
{
var result = new StringBuilder();
foreach (var token in tokens)
{
var converter = TokenConverterFactory.GetConverter(token.Type);
converter.Render(token, result);
}
var text = result.ToString();
text = TagReplacer.SimilarTagsNester(text);
return text;
}
}
15 changes: 15 additions & 0 deletions cs/Markdown/Renderers/SimilarTagsNester.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using Markdown.TokenConverters;

namespace Markdown.Renderers;

public static class TagReplacer
{
public static string SimilarTagsNester(string text)
{
foreach (var pair in TokenConverterFactory.GetTagPairs())
{
text = text.Replace(pair.Key, pair.Value);
}
return text;
}
}
14 changes: 14 additions & 0 deletions cs/Markdown/TokenConverters/EmphasisConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System.Text;
using Markdown.Tokens;

namespace Markdown.TokenConverters;

public class EmphasisConverter : TokenConverterBase
{
public override void Render(BaseToken baseToken, StringBuilder result)
{
result.Append("<em>");
RenderChildren(baseToken, result);
result.Append("</em>");
}
}
15 changes: 15 additions & 0 deletions cs/Markdown/TokenConverters/HeaderConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System.Text;
using Markdown.Tokens;

namespace Markdown.TokenConverters;

public class HeaderConverter : TokenConverterBase
{
public override void Render(BaseToken baseToken, StringBuilder result)
{
var level = baseToken.HeaderLevel;
result.Append($"<h{level}>");
RenderChildren(baseToken, result);
result.Append($"</h{level}>");
}
}
16 changes: 16 additions & 0 deletions cs/Markdown/TokenConverters/LinkConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using System.Text;
using Markdown.Tokens;

namespace Markdown.TokenConverters;

public class LinkConverter : TokenConverterBase
{
public override void Render(BaseToken token, StringBuilder result)
{
var linkToken = (LinkToken)token;

result.Append($"<a href=\"{linkToken.Url}\">");
RenderChildren(linkToken, result);
result.Append("</a>");
}
}
14 changes: 14 additions & 0 deletions cs/Markdown/TokenConverters/StrongConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System.Text;
using Markdown.Tokens;

namespace Markdown.TokenConverters;

public class StrongConverter : TokenConverterBase
{
public override void Render(BaseToken baseToken, StringBuilder result)
{
result.Append("<strong>");
RenderChildren(baseToken, result);
result.Append("</strong>");
}
}
13 changes: 13 additions & 0 deletions cs/Markdown/TokenConverters/TextConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System.Text;
using Markdown.Interfaces;
using Markdown.Tokens;

namespace Markdown.TokenConverters;

public class TextConverter : ITokenConverter
{
public void Render(BaseToken baseToken, StringBuilder result)
{
result.Append(baseToken.Content);
}
}
19 changes: 19 additions & 0 deletions cs/Markdown/TokenConverters/TokenConverterBase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System.Text;
using Markdown.Interfaces;
using Markdown.Tokens;

namespace Markdown.TokenConverters;

public abstract class TokenConverterBase : ITokenConverter
{
public abstract void Render(BaseToken baseToken, StringBuilder result);

protected static void RenderChildren(BaseToken baseToken, StringBuilder result)
{
foreach (var child in baseToken.Children)
{
var converter = TokenConverterFactory.GetConverter(child.Type);
converter.Render(child, result);
}
}
}
Loading