-
Notifications
You must be signed in to change notification settings - Fork 319
Глейзер Роман #252
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
Open
RomanGleyzer
wants to merge
21
commits into
kontur-courses:master
Choose a base branch
from
RomanGleyzer:master
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Глейзер Роман #252
Changes from all commits
Commits
Show all changes
21 commits
Select commit
Hold shift + click to select a range
1f02013
Предпроверка markdown
RomanGleyzer ffa7354
Подправил текущую реализацию
RomanGleyzer 9b17faa
Реализация сегментера и замена интерфейса IBlock на класс Block
RomanGleyzer 39c6c27
Первичная реализация парсера (нужно будет протестировать) с заменой и…
RomanGleyzer bd7fc44
Тесты для парсера
RomanGleyzer 6588773
Правки тестов
RomanGleyzer 51148eb
Поправил проверку на оставшиеся открытые выделения
RomanGleyzer 94823a7
Правки тестов и парсера
RomanGleyzer d63c22a
Поправил баг c неправильным сбросом буфера при закрытии _ внутри откр…
RomanGleyzer e36bf9a
Исправил баг с закрытием em, убрал ненужные и добавил новые тесты для…
RomanGleyzer 1ae1901
Добавил html обработчик и переписал тесты
RomanGleyzer 7b48e1d
Доработал парсер, сделал упор на улучшение читаемости с соблюдением SRP
RomanGleyzer 1b44b9f
Доработал парсер, добавил тест на производительность и добавил новые …
RomanGleyzer 02adb0b
Рефакторинг Md для использования di и упрощения BlockSegmenter
RomanGleyzer 22c4b3a
Рефакторинг HtmlRenderer. Добавление методов и констант
RomanGleyzer d32c6e4
Inlines в Block теперь без сеттера. Рефакторинг Md, CommitText, убрал…
RomanGleyzer 1644149
Рефакторинг парсера и обработки экранирования
RomanGleyzer 63d8c03
Удалил .sln файлы из репозитория
RomanGleyzer d19907c
Улучшил тест производительности и добавил тест по вложенности
RomanGleyzer f1fe8e8
Поправил тесты, добавил новые сценарии, упростил названия
RomanGleyzer cc310eb
add .sln
RomanGleyzer File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| using Markdown.Entities; | ||
| using static Markdown.Inlines.InlineSyntax; | ||
|
|
||
| namespace Markdown; | ||
|
|
||
| public class BlockSegmenter | ||
| { | ||
| private const string CarriageReturnParagraphSeparator = "\r\n\r\n"; | ||
| private const string ParagraphSeparator = "\n\n"; | ||
|
|
||
| public IReadOnlyList<Block> Segment(string text) | ||
| { | ||
| var paragraphs = SplitToParagraphs(text); | ||
| var blocks = new List<Block>(); | ||
|
|
||
| foreach (var paragraph in paragraphs) | ||
| { | ||
| var blockType = paragraph.StartsWith(Sharp) ? BlockType.Heading : BlockType.Paragraph; | ||
| var rawText = paragraph.TrimStart('#', ' '); | ||
| var block = new Block(rawText, blockType); | ||
| blocks.Add(block); | ||
| } | ||
|
|
||
| return blocks; | ||
| } | ||
|
|
||
| private static string[] SplitToParagraphs(string text) | ||
| { | ||
| return text.Split([CarriageReturnParagraphSeparator, ParagraphSeparator], StringSplitOptions.RemoveEmptyEntries); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| namespace Markdown.Entities; | ||
|
|
||
| public class Block(string rawText, BlockType type) | ||
| { | ||
| public string RawText { get; } = rawText; | ||
|
|
||
| public BlockType Type { get; } = type; | ||
|
|
||
| public IReadOnlyList<Node> Inlines { get; init; } = []; | ||
| } | ||
|
|
||
| public enum BlockType | ||
| { | ||
| Heading, | ||
| Paragraph | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| namespace Markdown.Entities; | ||
|
|
||
| public class Node(string? text, NodeType type) | ||
| { | ||
| public string? Text { get; } = text; | ||
|
|
||
| public NodeType Type { get; } = type; | ||
| } | ||
|
|
||
| public enum NodeType | ||
| { | ||
| Text, | ||
| Strong, | ||
| Em | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,122 @@ | ||
| namespace Markdown; | ||
|
|
||
| using Markdown.Entities; | ||
| using System.Text; | ||
|
|
||
| public class HtmlRenderer | ||
| { | ||
| private const string EmOpen = "<em>"; | ||
| private const string EmClose = "</em>"; | ||
| private const string StrongOpen = "<strong>"; | ||
| private const string StrongClose = "</strong>"; | ||
| private const string HeadingOpen = "<h1>"; | ||
| private const string HeadingClose = "</h1>"; | ||
| private const string ParagraphOpen = "<p>"; | ||
| private const string ParagraphClose = "</p>"; | ||
|
|
||
| public string Render(IReadOnlyList<Block> blocks) | ||
| { | ||
| var sb = new StringBuilder(); | ||
|
|
||
| for (int i = 0; i < blocks.Count; i++) | ||
| { | ||
| var block = blocks[i]; | ||
| var inner = RenderInlines(block.Inlines, block.Type); | ||
|
|
||
| switch (block.Type) | ||
| { | ||
| case BlockType.Heading: | ||
| sb.Append(HeadingOpen).Append(inner).Append(HeadingClose); | ||
| break; | ||
|
|
||
| case BlockType.Paragraph: | ||
| sb.Append(ParagraphOpen).Append(inner).Append(ParagraphClose); | ||
| break; | ||
| } | ||
|
|
||
| if (i < blocks.Count - 1) | ||
| sb.AppendLine(); | ||
| } | ||
|
|
||
| return sb.ToString(); | ||
| } | ||
|
|
||
| private static string RenderInlines(IReadOnlyList<Node> inlines, BlockType blockType) | ||
| { | ||
| var sb = new StringBuilder(); | ||
|
|
||
| for (int i = 0; i < inlines.Count; i++) | ||
| { | ||
| var node = inlines[i]; | ||
|
|
||
| switch (node.Type) | ||
| { | ||
| case NodeType.Text: | ||
| if (!string.IsNullOrEmpty(node.Text)) | ||
| sb.Append(node.Text); | ||
| break; | ||
|
|
||
| case NodeType.Em: | ||
| RenderEm(sb, node); | ||
| break; | ||
|
|
||
| case NodeType.Strong: | ||
| if (blockType == BlockType.Heading) | ||
| RenderHeadingStrong(sb, inlines, ref i); | ||
| else | ||
| RenderStrong(sb, node); | ||
| break; | ||
| } | ||
| } | ||
|
|
||
| return sb.ToString(); | ||
| } | ||
|
|
||
| private static void RenderEm(StringBuilder sb, Node node) | ||
| { | ||
| if (!string.IsNullOrEmpty(node.Text)) | ||
| sb.Append(EmOpen).Append(node.Text).Append(EmClose); | ||
| } | ||
|
|
||
| private static void RenderStrong(StringBuilder sb, Node node) | ||
| { | ||
| if (!string.IsNullOrEmpty(node.Text)) | ||
| sb.Append(StrongOpen).Append(node.Text).Append(StrongClose); | ||
| } | ||
|
|
||
| private static void RenderHeadingStrong(StringBuilder sb, IReadOnlyList<Node> inlines, ref int currentIndex) | ||
| { | ||
| sb.Append(StrongOpen); | ||
|
|
||
| var currentNode = inlines[currentIndex]; | ||
| if (!string.IsNullOrEmpty(currentNode.Text)) | ||
| sb.Append(currentNode.Text); | ||
|
|
||
| var nextIndex = currentIndex + 1; | ||
| while (nextIndex < inlines.Count) | ||
| { | ||
| var nextNode = inlines[nextIndex]; | ||
|
|
||
| if (nextNode.Type == NodeType.Em) | ||
| { | ||
| if (!string.IsNullOrEmpty(nextNode.Text)) | ||
| sb.Append(EmOpen).Append(nextNode.Text).Append(EmClose); | ||
|
|
||
| nextIndex++; | ||
| continue; | ||
| } | ||
|
|
||
| if (nextNode.Type == NodeType.Strong) | ||
| { | ||
| if (!string.IsNullOrEmpty(nextNode.Text)) | ||
| sb.Append(nextNode.Text); | ||
|
|
||
| nextIndex++; | ||
| continue; | ||
| } | ||
| } | ||
|
|
||
| sb.Append(StrongClose); | ||
| currentIndex = nextIndex - 1; | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,91 @@ | ||
| using Markdown.Entities; | ||
| using System.Text; | ||
| using static Markdown.Inlines.InlineSyntax; | ||
|
|
||
| namespace Markdown.Inlines; | ||
|
|
||
| public static class InlineEscapesNormalizer | ||
| { | ||
| public static string Normalize(string text) | ||
| { | ||
| if (string.IsNullOrEmpty(text)) | ||
| return text; | ||
|
|
||
| var result = new StringBuilder(text.Length); | ||
|
|
||
| for (var index = 0; index < text.Length;) | ||
| { | ||
| var current = text[index]; | ||
|
|
||
| if (current == Escape) | ||
| { | ||
| index = HandleEscape(text, index, result); | ||
| continue; | ||
| } | ||
|
|
||
| result.Append(current); | ||
| index++; | ||
| } | ||
|
|
||
| return result.ToString(); | ||
| } | ||
|
|
||
| private static int HandleEscape(string text, int index, StringBuilder result) | ||
| { | ||
| if (index + 1 >= text.Length) | ||
| { | ||
| result.Append(Escape); | ||
| return index + 1; | ||
| } | ||
|
|
||
| var nextSymbol = text[index + 1]; | ||
|
|
||
| if (nextSymbol == Underscore) | ||
| { | ||
| result.Append(PlaceholderUnderscore); | ||
| return index + 2; | ||
| } | ||
|
|
||
| if (nextSymbol == Sharp) | ||
| { | ||
| result.Append(PlaceholderHash); | ||
| return index + 2; | ||
| } | ||
|
|
||
| if (nextSymbol == Escape) | ||
| return HandleEscapedEscape(text, index, result); | ||
|
|
||
| result.Append(Escape).Append(nextSymbol); | ||
| return index + 2; | ||
| } | ||
|
|
||
| private static int HandleEscapedEscape(string text, int index, StringBuilder result) | ||
| { | ||
| var afterSymbol = index + 2 < text.Length ? text[index + 2] : EndOfText; | ||
|
|
||
| if (afterSymbol == Underscore || afterSymbol == Sharp) | ||
| result.Append(PlaceholderBackslash); | ||
| else | ||
| result.Append(Escape).Append(Escape); | ||
|
|
||
| return index + 2; | ||
| } | ||
|
|
||
| public static void RestorePlaceholders(List<Node> nodes) | ||
| { | ||
| for (var i = 0; i < nodes.Count; i++) | ||
| { | ||
| var text = nodes[i].Text; | ||
| if (text is null) | ||
| continue; | ||
|
|
||
| var restored = text | ||
| .Replace(PlaceholderUnderscore, Underscore) | ||
| .Replace(PlaceholderBackslash, Escape) | ||
| .Replace(PlaceholderHash, Sharp); | ||
|
|
||
| if (!ReferenceEquals(restored, text)) | ||
| nodes[i] = new Node(restored, nodes[i].Type); | ||
| } | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| using Markdown.Entities; | ||
| using System.Text; | ||
|
|
||
| namespace Markdown.Inlines; | ||
|
|
||
| public static class InlineExtensions | ||
| { | ||
| public static void CommitText(this StringBuilder builder, List<Node> nodes) | ||
| { | ||
| if (builder.Length == 0) return; | ||
|
|
||
| nodes.Add(new Node(builder.ToString(), NodeType.Text)); | ||
| builder.Clear(); | ||
| } | ||
|
|
||
| public static void MergeTextNodes(this List<Node> nodes) | ||
| { | ||
| for (int i = 0; i < nodes.Count - 1;) | ||
| { | ||
| if (nodes[i].Type == NodeType.Text && nodes[i + 1].Type == NodeType.Text) | ||
| { | ||
| nodes[i] = new Node(nodes[i].Text + nodes[i + 1].Text, NodeType.Text); | ||
| nodes.RemoveAt(i + 1); | ||
| } | ||
| else | ||
| { | ||
| i++; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| public static void InsertFromEnd(this StringBuilder target, List<(int index, string text)> inserts) | ||
| { | ||
| if (inserts.Count == 0) return; | ||
|
|
||
| inserts.Sort((a, b) => b.index.CompareTo(a.index)); | ||
| foreach (var (index, text) in inserts) | ||
| target.Insert(index, text); | ||
| } | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
set можно заменить на init в Inlines