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

Заколюкин Степан #230

Open
wants to merge 17 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
d996f7b
Написал тесты на решение
StepanZakolukin Nov 24, 2024
d12bd7e
Добавил класс фрагмента текста
StepanZakolukin Nov 24, 2024
b90ae8e
Выполнил декомпозицию решения
StepanZakolukin Nov 24, 2024
c6ce4db
Изменил декомпозицию решения
StepanZakolukin Nov 25, 2024
9fecab8
Написал решение проходящее большую часть тестов
StepanZakolukin Nov 26, 2024
fb761e9
Исправил ошибку в тесте форматирования заголовков
StepanZakolukin Nov 30, 2024
534eaeb
Упростил код
StepanZakolukin Nov 30, 2024
25d5bc2
Внес поправки в алгоритм и отрефакторил код
StepanZakolukin Nov 30, 2024
7a6b124
Отрефакторил код
StepanZakolukin Dec 2, 2024
5ad8df2
Добавил интерфейс и классы тегов
StepanZakolukin Dec 4, 2024
6faef2b
Закончил проектирование решения и дописал кдасс md
StepanZakolukin Dec 4, 2024
b0b5ba7
Написал решение для курсива и жирного шрифта
StepanZakolukin Dec 4, 2024
7ab097b
Допсал базовую часть решения
StepanZakolukin Dec 4, 2024
99ee583
Добавил поддержку маркерованных списков
StepanZakolukin Dec 5, 2024
c281960
Добавил тест на производительность, большой тест
StepanZakolukin Dec 5, 2024
a599c77
Отрефакторил класс с тегом курсивного шрифта
StepanZakolukin Dec 5, 2024
3ca3881
Завершил рефакторинг
StepanZakolukin Dec 5, 2024
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
120 changes: 120 additions & 0 deletions cs/Markdown/BinaryTrees.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
using System.Collections;

namespace Markdown;

public class BinaryTree<T> : IEnumerable<T>
where T : IComparable
{
private TreeNode? root;

public void Add(T key)
{
var currentSubtree = root;
if (Equals(root, null))
{
root = new TreeNode(key, null);
return;
}

while (true)
if (currentSubtree != null && key.CompareTo(currentSubtree.Value) >= 0)
{
currentSubtree.HeightOfRight++;
if (currentSubtree.Right == null)
{
currentSubtree.Right = new TreeNode(key, currentSubtree);
return;
}
currentSubtree = currentSubtree.Right;
}
else
{
if (currentSubtree == null) continue;
currentSubtree.HeightOfLeft++;
if (currentSubtree.Left == null)
{
currentSubtree.Left = new TreeNode(key, currentSubtree);
return;
}

currentSubtree = currentSubtree.Left;
}
}

public T this[int i]
{
get
{
if (root != null && (root.HeightOfRight + root.HeightOfLeft < i || i < 0))
throw new IndexOutOfRangeException();

var currentSubtree = root;
var index = 0;

while (true)
{
if (currentSubtree != null && currentSubtree.HeightOfLeft + index == i)
return currentSubtree.Value;
if (currentSubtree != null && currentSubtree.HeightOfLeft + index > i)
currentSubtree = currentSubtree.Left;
else if (currentSubtree != null && currentSubtree.HeightOfLeft < i)
{
index += currentSubtree.HeightOfLeft + 1;
currentSubtree = currentSubtree.Right;
}
}
}
}

public IEnumerator<T> GetEnumerator()
{
if (root == null) yield break;

foreach (var subtree in root)
yield return subtree.Value;
}

IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}

public class TreeNode(T value, TreeNode? ancestor) : IEnumerable<TreeNode>
{
public readonly T Value = value;
public int HeightOfLeft { get; set; }
public int HeightOfRight { get; set; }

public TreeNode? Left, Right;
private readonly TreeNode? ancestor = ancestor;

IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}

public IEnumerator<TreeNode> GetEnumerator()
{
var treeNode = this;

while (!Equals(treeNode.Left, null))
treeNode = treeNode.Left;

while (true)
{
if (treeNode == null) continue;
yield return treeNode;

if (treeNode.Right != null)
{
foreach (var tree in treeNode.Right)
yield return tree;
}

if (treeNode == this) break;

treeNode = treeNode.ancestor;
}
}
}
}
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.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

</Project>
9 changes: 9 additions & 0 deletions cs/Markdown/MarkupSpecification/ISpecificationProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using Markdown.Tags;
using Markdown.Tags.TagSpecification;

namespace Markdown.MarkupSpecification;

public interface ISpecificationProvider
{
public IEnumerable<BaseTag> GetMarkupSpecification();
}
18 changes: 18 additions & 0 deletions cs/Markdown/MarkupSpecification/MdToHtmlSpecificationBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using Markdown.Tags;
using Markdown.Tags.TagSpecification;

namespace Markdown.MarkupSpecification;

public class MdToHtmlSpecificationBuilder : ISpecificationProvider
{
public IEnumerable<BaseTag> GetMarkupSpecification()
{
return
[
new BoldTag(),
new HeaderTag(),
new ItalicsTag(),
new BulletedListTag()
];
}
}
117 changes: 117 additions & 0 deletions cs/Markdown/Md.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
using System.Text;
using Markdown.MarkupSpecification;
using Markdown.Tags;
using Markdown.Tags.TagSpecification;

namespace Markdown;

public class Md(ISpecificationProvider specificationProvider)
{
public string Render(string markdown)
{
var markupSpecification = specificationProvider.GetMarkupSpecification().ToArray();

var tagReplacements = FindAllTags(markdown, markupSpecification);
var renderedString = PerformTextFormatting(markdown, tagReplacements.ToArray());

return RemoveEscapingOfControlSubstrings(renderedString, markupSpecification);
}

private IEnumerable<TagReplacementSpecification> FindAllTags(string text, BaseTag[] markupSpecification)
{
var result = new BinaryTree<TagReplacementSpecification>();

foreach (var tagSpecification in markupSpecification)
{
var fragment = tagSpecification.FindNextPairOfTags(text, 0, tagSpecification);

while (fragment is not null)
{
if (fragment.OpeningTag.StartIndex > fragment.ClosingTag.StartIndex)
throw new Exception("Открывающийся тег должен находиться перед закрывающимся");

result.Add(fragment.OpeningTag);
result.Add(fragment.ClosingTag);

var nextStartIndex = fragment.ClosingTag.StartIndex + fragment.ClosingTag.Tag.Old.Length;
if (nextStartIndex >= text.Length) break;

fragment = tagSpecification.FindNextPairOfTags(text, nextStartIndex, tagSpecification);
}
}

return EliminateTagConflictsAndIntersections(result);
}

private BinaryTree<TagReplacementSpecification> EliminateTagConflictsAndIntersections(
BinaryTree<TagReplacementSpecification> tags)
{
var stack = new Stack<TagReplacementSpecification>();
TagReplacementSpecification? openingTag = null;
var binaryTree = new BinaryTree<TagReplacementSpecification>();
var len = tags.Count();

for (var i = 0; i < len; i++)
{
if (openingTag is not null && openingTag.Markup.Closing == tags[i].Tag)
{
binaryTree.Add(openingTag);
binaryTree.Add(tags[i]);
openingTag = stack.Count != 0 ? stack.Pop() : null;
}
else if (tags[i].Tag == tags[i].Markup.Opening)
{
if ((openingTag is null || !openingTag.Markup.DidConflict(tags[i].Markup)) &&
!stack.Any(specification => specification.Markup.DidConflict(tags[i].Markup)))
{
if (openingTag is not null) stack.Push(openingTag);
openingTag = tags[i];
}
}
if (stack.Any(specification => specification.Markup == tags[i].Markup))
{
openingTag = stack.Pop();
while(openingTag.Markup != tags[i].Markup)
openingTag = stack.Pop();
}
}

return binaryTree;
}

private string PerformTextFormatting(string text, TagReplacementSpecification[] replacements)
{
if (replacements.Length == 0)
return text;

var result = new StringBuilder();
var endOfLastReplacement = -1;

for (var i = 0; i < replacements.Length; i++)
{
result.Append(text[(endOfLastReplacement + 1)..replacements[i].StartIndex]);
result.Append(replacements[i].Markup.PerformTagFormatting(
replacements[i],
i != 0 ? replacements[i - 1].Markup : null,
i < replacements.Length - 1 ? replacements[i + 1].Markup : null));
endOfLastReplacement = replacements[i].StartIndex + replacements[i].Tag.Old.Length - 1;
}

if (endOfLastReplacement + 1 != text.Length)
result.Append(text[(endOfLastReplacement + 1)..text.Length]);

return result.ToString();
}

private string RemoveEscapingOfControlSubstrings(string text, IEnumerable<BaseTag> tags)
{
foreach (var tag in tags)
{
text = text.Replace('\\' + tag.Opening.Old, tag.Opening.Old);
if (tag.Closing.Old != tag.Opening.Old)
text = text.Replace('\\' + tag.Closing.Old, tag.Closing.Old);
}

return text.Replace(@"\\", "\\");
}
}
8 changes: 8 additions & 0 deletions cs/Markdown/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace Markdown;

public class Program
{
public static void Main()
{
}
}
4 changes: 4 additions & 0 deletions cs/Markdown/Tags/Tag.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
namespace Markdown.Tags;


public record Tag(string Old, string New);
13 changes: 13 additions & 0 deletions cs/Markdown/Tags/TagReplacementSpecification.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using Markdown.Tags.TagSpecification;

namespace Markdown.Tags;

public record TagReplacementSpecification(Tag Tag, BaseTag Markup, int StartIndex) : IComparable
{
public int CompareTo(object? obj)
{
if (obj is TagReplacementSpecification specification)
return StartIndex.CompareTo(specification.StartIndex);
throw new ArgumentException($"Object must be of type {nameof(TagReplacementSpecification)}");
}
}
50 changes: 50 additions & 0 deletions cs/Markdown/Tags/TagSpecification/BaseTag.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
namespace Markdown.Tags.TagSpecification;

public abstract class BaseTag(Tag opening, Tag closing)
{
public Tag Opening { get; } = opening;
public Tag Closing { get; } = closing;

public virtual bool DidConflict(BaseTag tag) => false;

public virtual TextFragment? FindNextPairOfTags(string text, int startIndex, BaseTag tagSpecification)
{
var opening = FindNextTag(text, startIndex, Opening, tagSpecification);
if (opening is null) return null;

startIndex = opening.StartIndex + Opening.Old.Length;
var closing = FindNextTag(text, startIndex, Closing, tagSpecification);

if (closing is null) return null;
if (startIndex == closing.StartIndex)
return FindNextPairOfTags(text, closing.StartIndex + closing.Tag.Old.Length, tagSpecification);

return new TextFragment(opening, closing);
}

protected virtual TagReplacementSpecification? FindNextTag(string text, int startIndex, Tag tag, BaseTag tagSpecification)
{
var numberOfEscapeCharacters = 0;

for (var i = startIndex; i <= text.Length - tag.Old.Length; i++)
{
if (text[i] == '\\') numberOfEscapeCharacters++;
else if (numberOfEscapeCharacters % 2 == 0 && text.Substring(i, tag.Old.Length) == tag.Old &&
AdditionallyCheckCurrentPosition(text, i, tag))
{
return new TagReplacementSpecification(tag, tagSpecification, i);
}
else numberOfEscapeCharacters = 0;
}

return null;
}

public virtual string PerformTagFormatting(TagReplacementSpecification replacement,
BaseTag? previousTag, BaseTag? nextTag)
{
return replacement.Tag == Opening ? Opening.New : Closing.New;
}

protected virtual bool AdditionallyCheckCurrentPosition(string text, int currentIndex, Tag tag) => true;
}
14 changes: 14 additions & 0 deletions cs/Markdown/Tags/TagSpecification/BasicSingleTag.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
namespace Markdown.Tags.TagSpecification;

public abstract class BasicSingleTag(Tag opening, Tag closing) : BaseTag(opening, closing)
{
protected override bool AdditionallyCheckCurrentPosition(string text, int currentIndex, Tag tag)
{
var startIndex = Math.Max(currentIndex - Environment.NewLine.Length, 0);

if (tag == Opening)
return currentIndex == 0 || text[startIndex..currentIndex] == Environment.NewLine;

return true;
}
}
3 changes: 3 additions & 0 deletions cs/Markdown/Tags/TagSpecification/BoldTag.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
namespace Markdown.Tags.TagSpecification;

public class BoldTag() : BaseTag(new Tag("__", "<strong>"), new Tag("__", "</strong>"));
Loading