-
Notifications
You must be signed in to change notification settings - Fork 300
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
Рушкова Ольга #225
base: master
Are you sure you want to change the base?
Рушкова Ольга #225
Changes from 6 commits
1431c40
6ff02e1
1e18efe
c3a7f7d
3dc4f62
1f6af33
aa5f6b9
9f49f5d
bdccdd8
9008d8f
8cb2da9
de60389
75a3bcf
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<OutputType>Exe</OutputType> | ||
<TargetFramework>net8.0</TargetFramework> | ||
<ImplicitUsings>enable</ImplicitUsings> | ||
<Nullable>enable</Nullable> | ||
<StartupObject>Markdown.Program</StartupObject> | ||
</PropertyGroup> | ||
|
||
</Project> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
using Markdown.Tags; | ||
using System.Text; | ||
|
||
namespace Markdown | ||
{ | ||
public class Md | ||
{ | ||
public static readonly IReadOnlyDictionary<char, Dictionary<string, Func<string, int, Tag>>> MdTags = new Dictionary<char, Dictionary<string, Func<string, int, Tag>>>() | ||
{ | ||
{ | ||
'_', new Dictionary<string, Func<string, int, Tag>> | ||
{ | ||
{ "_", Italic.CreateInstance}, | ||
{ "__", Bold.CreateInstance} | ||
} | ||
}, | ||
{ | ||
'#', new Dictionary<string, Func<string, int, Tag>> | ||
{ | ||
{ "#", (markdown, tagStart) => new Header(markdown, tagStart) } | ||
} | ||
}, | ||
{ | ||
'\\', | ||
new Dictionary<string, Func<string, int, Tag>> | ||
{ | ||
{ "\\", (markdown, tagStart) => new Escape(markdown, tagStart) } | ||
} | ||
} | ||
}; | ||
|
||
internal static Tag GetOpenTag(int tagStart, string markdownText, out int contextStart) | ||
{ | ||
var tagBegin = markdownText[tagStart]; | ||
var tags = MdTags[tagBegin]; | ||
var tag = tagBegin == '_' && tagStart != markdownText.Length - 1 && markdownText[tagStart + 1] == '_' | ||
? "__" | ||
: tagBegin.ToString(); | ||
contextStart = tagStart + tag.Length; | ||
return tags[tag](markdownText, tagStart); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Общий метод знает слишком много про спецификацию, будет сложно расширять, нужно помнить о том, что здесь есть логика именно про _ и __ There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Сложно читается |
||
} | ||
|
||
public string Render(string markdownText) | ||
{ | ||
var parser = new MdParser(markdownText); | ||
var tags = parser.GetTags(); | ||
var result = new StringBuilder(); | ||
var tagsStart = tags.ToDictionary(t => t.TagStart); | ||
for (var i = 0; i < markdownText.Length; ) | ||
{ | ||
if (tagsStart.ContainsKey(i)) | ||
{ | ||
result.Append(tagsStart[i].RenderToHtml()); | ||
i = tagsStart[i].TagEnd++; | ||
} | ||
else | ||
{ | ||
result.Append(markdownText[i]); | ||
i++; | ||
} | ||
} | ||
return result.ToString(); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
using Markdown.Tags; | ||
|
||
namespace Markdown; | ||
|
||
public class MdParser(string markdownText) | ||
{ | ||
private readonly MdRulesForNestedTags rules = new(); | ||
|
||
private class ReaderPosition | ||
{ | ||
public int Position { get; set; } | ||
} | ||
|
||
public Tag[] GetTags() | ||
{ | ||
var result = new List<Tag>(); | ||
var current = new ReaderPosition(); | ||
while (current.Position < markdownText.Length) | ||
{ | ||
if (Md.MdTags.ContainsKey(markdownText[current.Position])) | ||
{ | ||
var newTag = CreateTag(current, []); | ||
if (newTag.IsTagClosed) | ||
result.Add(newTag); | ||
} | ||
else current.Position++; | ||
} | ||
return result.ToArray(); | ||
} | ||
|
||
private Tag CreateTag(ReaderPosition current, List<Tag> external) | ||
{ | ||
var openTag = Md.GetOpenTag(current.Position, markdownText, out int contextStart); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Лапша получается. Класс Md создает MdParser, MdParser обращается к Md. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. var |
||
current.Position = contextStart; | ||
var isContextCorrect = true; | ||
var nested = new List<Tag>(); | ||
while (current.Position < markdownText.Length && !openTag.AcceptIfContextEnd(current.Position)) | ||
{ | ||
if (Md.MdTags.ContainsKey(markdownText[current.Position]) && openTag is not Escape) | ||
{ | ||
var newTag = CreateTag(current, [openTag]); | ||
if (newTag.IsTagClosed) | ||
{ | ||
nested.Add(newTag); | ||
} | ||
} | ||
else | ||
{ | ||
isContextCorrect = isContextCorrect && openTag.AcceptIfContextCorrect(current.Position); | ||
current.Position++; | ||
} | ||
} | ||
|
||
if ((external.Count == 0 || rules.IsNestedTagWorks(external[^1], openTag)) && isContextCorrect) | ||
{ | ||
openTag.TryCloseTag(current.Position, markdownText, out int tagEnd, nested); | ||
current.Position = tagEnd; | ||
} | ||
else | ||
{ | ||
current.Position = openTag.SkipTag(current.Position); | ||
} | ||
|
||
return openTag; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
using Markdown.Tags; | ||
|
||
namespace Markdown; | ||
|
||
public class MdRulesForNestedTags | ||
{ | ||
private readonly Dictionary<Type, HashSet<Type>> possibleNested = new() | ||
{ | ||
{typeof(Header), new () { typeof(Bold), typeof(Italic), typeof(Escape)}}, | ||
{typeof(Bold), new () { typeof(Italic), typeof(Escape)}}, | ||
{typeof(Italic), new () { typeof(Escape)}} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. По типам сохранять - плохая практика. Введи enum и соответствующее поле, если хочешь использовать в таком виде. Через наследование ты сможешь его везде затребовать. |
||
}; | ||
|
||
public bool IsNestedTagWorks(Tag external, Tag nested) => possibleNested.ContainsKey(external.GetType()) && possibleNested[external.GetType()].Contains(nested.GetType()); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
namespace Markdown | ||
{ | ||
public class Program | ||
{ | ||
public static void Main(string[] args) | ||
{ | ||
|
||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
namespace Markdown.Tags; | ||
|
||
public class Bold : PairTag | ||
{ | ||
protected Bold(string mdText, int tagStart) : base(mdText, tagStart) | ||
{ | ||
MarkdownText = mdText; | ||
TagStart = tagStart; | ||
} | ||
|
||
public static Bold CreateInstance(string markdownText, int tagStart) | ||
{ | ||
if (tagStart > 0 && char.IsLetter(markdownText[tagStart - 1])) | ||
return new BoldSelectPartWord(markdownText, tagStart); | ||
|
||
return new BoldSelectFewWords(markdownText, tagStart); | ||
} | ||
|
||
protected string MarkdownText; | ||
protected override string MdTag => "__"; | ||
protected override string HtmlTag => "strong"; | ||
|
||
public override bool AcceptIfContextEnd(int currentPosition) | ||
{ | ||
return currentPosition != MarkdownText.Length - 1; | ||
} | ||
|
||
public override bool AcceptIfContextCorrect(int currentPosition) | ||
{ | ||
return !((MarkdownText[currentPosition] == '_' && MarkdownText[currentPosition + 1] == '_') | ||
|| char.IsDigit(MarkdownText[currentPosition]) | ||
|| (currentPosition == TagStart + MdTag.Length && MarkdownText[currentPosition] == ' ')); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
namespace Markdown.Tags | ||
{ | ||
internal class BoldSelectFewWords(string markdownText, int tagStart) : Bold(markdownText, tagStart) | ||
{ | ||
protected string MdTagClose => "__ "; | ||
|
||
public override bool AcceptIfContextEnd(int currentPosition) | ||
{ | ||
isCloseByFindCloseTag = base.AcceptIfContextEnd(currentPosition) && (IsCloseTagPositionedInWordEnd(currentPosition) | ||
|| IsStringEndByCloseTag(currentPosition)) | ||
&& MarkdownText[currentPosition - 1] != ' '; | ||
return isCloseByFindCloseTag; | ||
} | ||
|
||
private bool IsStringEndByCloseTag(int currentPosition) | ||
{ | ||
return MarkdownText.Substring(currentPosition, MdTag.Length) == MdTag && | ||
currentPosition + MdTag.Length == MarkdownText.Length; | ||
} | ||
|
||
private bool IsCloseTagPositionedInWordEnd(int currentPosition) | ||
{ | ||
return currentPosition + MdTagClose.Length < MarkdownText.Length | ||
&& (MarkdownText.Substring(currentPosition, MdTagClose.Length) == MdTagClose); | ||
} | ||
|
||
public override Type GetType() => typeof(Bold); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Зачем? typeof(object?) не подходит? |
||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
namespace Markdown.Tags | ||
{ | ||
internal class BoldSelectPartWord(string markdownText, int tagStart) : Bold(markdownText, tagStart) | ||
{ | ||
public override bool AcceptIfContextEnd(int currentPosition) | ||
{ | ||
isCloseByFindCloseTag = base.AcceptIfContextEnd(currentPosition) | ||
&& currentPosition + MdTag.Length < MarkdownText.Length | ||
&& MarkdownText.Substring(currentPosition, MdTag.Length) == MdTag; | ||
return isCloseByFindCloseTag; | ||
} | ||
|
||
public override bool AcceptIfContextCorrect(int currentPosition) | ||
{ | ||
return MarkdownText[currentPosition] == ' ' && base.AcceptIfContextCorrect(currentPosition); | ||
} | ||
public override Type GetType() => typeof(Bold); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
namespace Markdown.Tags; | ||
|
||
public class Escape(string markdownText, int tagStart) : Tag(markdownText, tagStart) | ||
{ | ||
protected override string MdTag => "\\"; | ||
protected override string HtmlTag => ""; | ||
|
||
public override string RenderToHtml() | ||
{ | ||
return $"{Context.GetValue()}"; | ||
} | ||
|
||
public override bool AcceptIfContextEnd(int currentPosition) | ||
{ | ||
return currentPosition > tagStart + 1; | ||
} | ||
|
||
public override bool AcceptIfContextCorrect(int currentPosition) | ||
{ | ||
return base.AcceptIfContextCorrect(currentPosition) | ||
&& currentPosition < markdownText.Length && Md.MdTags.ContainsKey(markdownText[currentPosition]); | ||
} | ||
|
||
public override void TryCloseTag(int contextEnd, string sourceMdText, out int tagEnd, List<Tag>? nested = null) | ||
{ | ||
Context = Md.MdTags.ContainsKey(markdownText[tagStart + 1]) | ||
? new Token(tagStart + 1, markdownText, 1) | ||
: new Token(tagStart, markdownText, 1); | ||
tagEnd = contextEnd; | ||
TagEnd = tagEnd; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
namespace Markdown.Tags; | ||
|
||
public class Header(string markdownText, int tagStart) : Tag(markdownText, tagStart) | ||
{ | ||
protected override string MdTag => "# "; | ||
protected override string HtmlTag => "h1"; | ||
|
||
public override bool AcceptIfContextEnd(int currentPosition) | ||
{ | ||
return currentPosition > markdownText.Length - 1 || markdownText[currentPosition] == '\n'; | ||
} | ||
|
||
public override bool AcceptIfContextCorrect(int currentPosition) | ||
{ | ||
return tagStart == 0 || tagStart - 1 == '\n'; | ||
} | ||
|
||
public override void TryCloseTag(int contextEnd, string sourceMdText, out int tagEnd, List<Tag>? nested = null) | ||
{ | ||
var contextStart = tagStart + MdTag.Length; | ||
tagEnd = contextEnd == sourceMdText.Length - 1 ? contextEnd : contextEnd + 1; | ||
Context = new Token(contextStart, sourceMdText, contextEnd - contextStart); | ||
NestedTags = nested; | ||
TagEnd = tagEnd; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
namespace Markdown.Tags; | ||
|
||
public class Italic: PairTag | ||
{ | ||
protected Italic(string mdText, int tagStart) : base(mdText, tagStart) | ||
{ | ||
MarkdownText = mdText; | ||
TagStart = tagStart; | ||
} | ||
|
||
public static Italic CreateInstance(string markdownText, int tagStart) | ||
{ | ||
if (tagStart > 0 && char.IsLetter(markdownText[tagStart - 1])) | ||
return new ItalicSelectPartOfOneWord(markdownText, tagStart); | ||
|
||
return new ItalicSelectFewWords(markdownText, tagStart); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Чет странное. Кмк лучше выделить отдельно фабрику для тегов, если они чем-то отличаются, а не два создания класса в один пихать |
||
|
||
protected string MarkdownText; | ||
protected override string MdTag => "_"; | ||
protected override string HtmlTag => "em"; | ||
|
||
public override bool AcceptIfContextEnd(int currentPosition) | ||
{ | ||
isCloseByFindCloseTag = MarkdownText[currentPosition] == '_'; | ||
return isCloseByFindCloseTag; | ||
} | ||
public override bool AcceptIfContextCorrect(int currentPosition) | ||
{ | ||
return base.AcceptIfContextCorrect(currentPosition) && !((MarkdownText[currentPosition] == '_' && MarkdownText[currentPosition + 1] == '_') | ||
|| char.IsDigit(MarkdownText[currentPosition]) | ||
|| (currentPosition == TagStart + MdTag.Length && MarkdownText[currentPosition] == ' ')); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
namespace Markdown.Tags | ||
{ | ||
internal class ItalicSelectFewWords(string markdownText, int tagStart) : Italic(markdownText, tagStart) | ||
{ | ||
protected string MdTagClose => "_ "; | ||
|
||
public override bool AcceptIfContextEnd(int currentPosition) | ||
{ | ||
return base.AcceptIfContextEnd(currentPosition) && (IsCloseTagPositionedInWordEnd(currentPosition) | ||
|| IsStringEndByCloseTag(currentPosition)) | ||
&& MarkdownText[currentPosition - 1] != ' '; | ||
} | ||
|
||
private bool IsStringEndByCloseTag(int currentPosition) | ||
{ | ||
return MarkdownText.Substring(currentPosition, MdTag.Length) == MdTag && | ||
currentPosition + MdTag.Length == MarkdownText.Length; | ||
} | ||
|
||
private bool IsCloseTagPositionedInWordEnd(int currentPosition) | ||
{ | ||
return currentPosition + MdTagClose.Length < MarkdownText.Length | ||
&& (MarkdownText.Substring(currentPosition, MdTagClose.Length) == MdTagClose); | ||
} | ||
public override Type GetType() => typeof(Italic); | ||
} | ||
} |
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.
tagStart + 1 - не контролируется, что возможен выход за массив