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

Мажирин Александр #237

Open
wants to merge 22 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
41 changes: 41 additions & 0 deletions cs/Markdown/Extensions/StringExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
namespace Markdown.Extensions;

public static class StringExtensions
{
/// <summary>
/// Производит проверку наличия строки в строке на позиции i без копирования
/// </summary>
/// <param name="original">Исходная строка</param>
/// <param name="str">Проверяемая строка</param>
/// <param name="i">Позиция в исходной строке</param>
/// <returns>True, если строка содержится в исходной строке на позиции i, иначе false</returns>
public static bool ContainsSubstringOnIndex(this string original, string str, int i)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Названия аргументов сложно понять. Правильно указал название метода. Предлагаю делать origin, substring, position

{
for (var j = 0; j < str.Length; j++)
{
if (i + j >= original.Length || original[i + j] != str[j])
return false;
}

return true;
}

/// <summary>
/// Проверяет, является ли символ экранированным
/// </summary>
/// <param name="str">Строка</param>
/// <param name="index">Индекс символа</param>
/// <returns>True, если символ экранирован, иначе false</returns>
public static bool IsEscaped(this string str, int index)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Кажется этот метод не используется. И опять же переменные: лучше position, чем index

{
var previousIndex = index - 1;
var backslashCount = 0;
while (previousIndex >= 0 && str[previousIndex] == '\\')
{
backslashCount++;
previousIndex--;
}
Comment on lines +33 to +37

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Потенциально превратит обработку строки в квадратичную сложность 😨
Для подобного анализа лучше всего подходит ДКА


return backslashCount % 2 == 1;
}
}
10 changes: 10 additions & 0 deletions cs/Markdown/Markdown.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

</Project>
9 changes: 9 additions & 0 deletions cs/Markdown/Markdown/IMd.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace Markdown.Markdown;

/// <summary>
/// Интерфейс для конвертера Markdown в HTML
/// </summary>
public interface IMd
{
string Render(string md);
}
18 changes: 18 additions & 0 deletions cs/Markdown/Markdown/Md.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using Markdown.Renderer;
using Markdown.Tokenizer;

namespace Markdown.Markdown;

/// <summary>
/// Конвертер markdown в HTML
/// </summary>
public class Md : IMd
{
private readonly ITokenizer tokenizer = new MarkdownTokenizer();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Токенайзер хранит в себе состояние. А значит на каждый рендер нам нужно генерировать новую сущность

private readonly IRenderer renderer = new HtmlRenderer();
public string Render(string md)
{
var tokens = tokenizer.Tokenize(md);
return renderer.Render(tokens);
}
}
14 changes: 14 additions & 0 deletions cs/Markdown/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using Markdown.Markdown;

namespace Markdown;

class Program
{
public static void Main()
{
var mdFile = File.ReadAllText("Markdown.md");
var md = new Md();
//write to file
File.WriteAllText("Markdown.html", md.Render(mdFile));
}
}
58 changes: 58 additions & 0 deletions cs/Markdown/Renderer/HtmlRenderer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
using System.Text;
using System.Web;
using Markdown.Tokenizer;
using Markdown.Tokenizer.Tokens;

namespace Markdown.Renderer;

/// <summary>
/// HTML-рендерер. Преобразует токены в HTML-текст, экранируя спецсимволы в тексте
/// </summary>
public class HtmlRenderer : IRenderer
{
public string Render(IEnumerable<IToken> tokens)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Если делать прямо по честному дерево, то оно должно лежать в одном корне. Но так тоже можно

{
var sb = new StringBuilder();
foreach (var token in tokens)
{
sb.Append(RenderToken(token));
}

return sb.ToString();
}

private string? RenderToken(IToken token)
{
return token switch
{
TextToken textToken => HttpUtility.HtmlEncode(textToken.TextContent),
TagToken tagToken => RenderTagToken(tagToken),
_ => null
};
}

private string? RenderTagToken(TagToken tagToken)
{
var sb = new StringBuilder();
sb.Append($"<{tagToken.Tag.HtmlTag}");
foreach (var (key, value) in tagToken.Attributes)
{
sb.Append($" {key}=\"{value}\"");
}

if (tagToken.Tag.SelfClosing)
{
sb.Append(" />");
return sb.ToString();
}

sb.Append('>');
foreach (var child in tagToken.Children)
{
sb.Append(RenderToken(child));
}

sb.Append($"</{tagToken.Tag.HtmlTag}>");
return sb.ToString();
}
}
17 changes: 17 additions & 0 deletions cs/Markdown/Renderer/IRenderer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using Markdown.Tokenizer;
using Markdown.Tokenizer.Tokens;

namespace Markdown.Renderer;

/// <summary>
/// Универсальный интерфейс рендерера
/// </summary>
public interface IRenderer
{
/// <summary>
/// Переводит набор токенов в текст языка разметки
/// </summary>
/// <param name="tokens">Набор токенов</param>
/// <returns>Сгенерированный текст</returns>
string Render(IEnumerable<IToken> tokens);
}
15 changes: 15 additions & 0 deletions cs/Markdown/Tags/CursiveTag.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
namespace Markdown.Tags;

/// <summary>
/// Тег для курсивного текста
/// </summary>
public class CursiveTag : ITag
{
public string MdTag { get; } = "_";

public string MdClosingTag => MdTag;

public string HtmlTag { get; } = "em";

public IReadOnlyCollection<ITag> DisallowedChildren { get; } = new List<ITag> { new StrongTag() };
}
12 changes: 12 additions & 0 deletions cs/Markdown/Tags/HeaderTag.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace Markdown.Tags;

/// <summary>
/// Тег для заголовка. Может быть использован только в начале строки
/// </summary>
public class HeaderTag : ITag
{
public string MdTag { get; } = "#";
public string MdClosingTag { get; } = "\n";
public string HtmlTag { get; } = "h1";
public IReadOnlyCollection<ITag> DisallowedChildren { get; } = new List<ITag>();
}
42 changes: 42 additions & 0 deletions cs/Markdown/Tags/ITag.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
namespace Markdown.Tags;

/// <summary>
/// Интерфейс тега
/// </summary>
public interface ITag

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

На мой взгляд тэг - это скорее абстрактный класс с идейной точки зрения

{
/// <summary>
/// Тег, который используется в Markdown
/// </summary>
string MdTag { get; }

/// <summary>
/// Закрывающий тег в Markdown
/// </summary>
string MdClosingTag { get; }

/// <summary>
/// Тег в HTML, соответсвующий тегу Markdown
/// </summary>
/// <see cref="MdTag"/>
string HtmlTag { get; }

/// <summary>
/// Самозакрывание тега в HTML
/// </summary>
bool SelfClosing => false;

/// <summary>
/// Запрет на вложение дочерних элементов определенного типа
/// </summary>
IReadOnlyCollection<ITag> DisallowedChildren { get; }

/// <summary>
/// Получить атрибуты для рендера в HTML
/// </summary>
/// <param name="content">Содержание тега</param>
/// <returns>Строка с атрибутами для вставки в тег</returns>
static string? GetHtmlRenderAttributes(string content) => null;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Больше похоже на GetHtmlTadAttributes


bool Matches(ITag tag) => this.GetType() == tag.GetType();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Такую штуку самописную лучше не делать. А то потом ещё от наследования проблем наберёшься

if (strangeTag is StrongTag strong){
    // работаем со strong как с нашим объектом
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

А ещё рефлексия очень медленная в таком представлении

}
23 changes: 23 additions & 0 deletions cs/Markdown/Tags/ImageTag.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
namespace Markdown.Tags;

/// <summary>
/// Тег для картинки
/// </summary>
public class ImageTag : ITag
{
public string MdTag { get; } = "![";

public string MdClosingTag { get; } = ")";

public string HtmlTag { get; } = "img";

public IReadOnlyCollection<ITag> DisallowedChildren => new List<ITag>() {new CursiveTag(), new HeaderTag(), new ImageTag(), new StrongTag()};

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

А при добавлении нового тега нам придётся не забыть его добавить и здесь. Легко забыть


public bool SelfClosing { get; } = false;

public static Dictionary<string,string> GetHtmlRenderAttributes(string content) => new()
{
{"src", content.Split(']')[1].Split('(')[1]},

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Дважды выполняем одно и то же действие content.Split(']'). Ещё и не гарантированно, что ] встретится только один раз. А если это будет объект вида ![просто текст), то выпадет исключение

{"alt", content.Split(']')[0][2..]}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

По смыслу названия переменной content - она уже без тегов, а здесь происходит отступ

};
}
13 changes: 13 additions & 0 deletions cs/Markdown/Tags/NewLineTag.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace Markdown.Tags;

/// <summary>
/// Тег для переноса строки
/// </summary>
public class NewLineTag : ITag
{
public string MdTag { get; } = "\n";
public string MdClosingTag { get; } = null;
public string HtmlTag { get; } = "br";
public bool SelfClosing { get; } = true;
public IReadOnlyCollection<ITag> DisallowedChildren { get; }

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Рискуем получить NRE. Лучше инициализировать его пустым

}
15 changes: 15 additions & 0 deletions cs/Markdown/Tags/StrongTag.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
namespace Markdown.Tags;

/// <summary>
/// Тег для полужирного текста
/// </summary>
public class StrongTag : ITag
{
public string MdTag { get; } = "__";

public string MdClosingTag => MdTag;

public string HtmlTag { get; } = "strong";

public IReadOnlyCollection<ITag> DisallowedChildren { get; } = new List<ITag>();
}
13 changes: 13 additions & 0 deletions cs/Markdown/Tokenizer/ITokenizer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using Markdown.Tokenizer.Tokens;

namespace Markdown.Tokenizer;

/// <summary>
/// Интерфейс токенайзера - переводчика строки в токены
/// </summary>
public interface ITokenizer
{
public List<IToken> Tokenize(string content);

public List<IToken> GetTokens();
}
Loading