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

Рушкова Ольга #225

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
11 changes: 11 additions & 0 deletions cs/Markdown/Markdown.csproj
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>
65 changes: 65 additions & 0 deletions cs/Markdown/Md.cs
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] == '_'

Choose a reason for hiding this comment

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

tagStart + 1 - не контролируется, что возможен выход за массив

? "__"
: tagBegin.ToString();
contextStart = tagStart + tag.Length;
return tags[tag](markdownText, tagStart);

Choose a reason for hiding this comment

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

Общий метод знает слишком много про спецификацию, будет сложно расширять, нужно помнить о том, что здесь есть логика именно про _ и __

Choose a reason for hiding this comment

The 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();
}
}
}
66 changes: 66 additions & 0 deletions cs/Markdown/MdParser.cs
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);

Choose a reason for hiding this comment

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

Лапша получается. Класс Md создает MdParser, MdParser обращается к Md.
Желаемая архитектура все же когда у тебя нижележащие сервисы не только не обращаются к вышележащим, но и ничего про них не знают вообще

Choose a reason for hiding this comment

The 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;
}
}
15 changes: 15 additions & 0 deletions cs/Markdown/MdRulesForNestedTags.cs
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)}}

Choose a reason for hiding this comment

The 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());
}
10 changes: 10 additions & 0 deletions cs/Markdown/Program.cs
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)
{

}
}
}
34 changes: 34 additions & 0 deletions cs/Markdown/Tags/Bold.cs
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] == ' '));
}
}
29 changes: 29 additions & 0 deletions cs/Markdown/Tags/BoldSelectFewWords.cs
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);

Choose a reason for hiding this comment

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

Зачем? typeof(object?) не подходит?

}
}
19 changes: 19 additions & 0 deletions cs/Markdown/Tags/BoldSelectPartWord.cs
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);
}
}
32 changes: 32 additions & 0 deletions cs/Markdown/Tags/Escape.cs
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;
}
}
26 changes: 26 additions & 0 deletions cs/Markdown/Tags/Header.cs
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;
}
}
34 changes: 34 additions & 0 deletions cs/Markdown/Tags/Italic.cs
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);
}

Choose a reason for hiding this comment

The 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] == ' '));
}
}
27 changes: 27 additions & 0 deletions cs/Markdown/Tags/ItalicSelectFewWords.cs
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);
}
}
Loading