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

Ослина Анастасия #221

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

public enum LiteralType
{
Number,
Text,
None
}
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>
27 changes: 27 additions & 0 deletions cs/Markdown/Md.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using Markdown.Renderers;
using Markdown.Tokenizers;

namespace Markdown;

public class Md
{
private readonly ITokenizer tokenizer;
private readonly ITokenConverter tokenConverter;

private Md(ITokenizer tokenizer, ITokenConverter tokenConverter)
{
this.tokenizer = tokenizer;
this.tokenConverter = tokenConverter;
}

public static Md Create(ITokenizer tokenizer, ITokenConverter tokenConverter)
{
return new Md(tokenizer, tokenConverter);
}

public string Render(string markdownStr)
{
var tokens = tokenizer.SplitToTokens(markdownStr);
return tokenConverter.ConvertTokens(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.Renderers;
using Markdown.Tokenizers;

namespace Markdown;

public static class Program
{
public static void Main()
{
var input = "Подчерки внутри текста c цифрами_12_3 ";
var md = Md.Create(new Tokenizer(), new HtmlTokenConverter()).Render(input);
Console.WriteLine(md);
}
}
22 changes: 22 additions & 0 deletions cs/Markdown/Renderers/HtmlTokenConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System.Text;
using Markdown.Tokens;

namespace Markdown.Renderers;

public class HtmlTokenConverter : ITokenConverter
{
public string ConvertTokens(List<Token> tokens)
{
var result = new StringBuilder();
foreach (var token in tokens)
result.Append(ConvertToTag(token));
return result.ToString();
}

private static string ConvertToTag(Token token)
{
if (token.TagWrapper == null)
return token.Content;
return token.IsClosing ? $"</{token.TagWrapper}>" : $"<{token.TagWrapper}>";
}
}
8 changes: 8 additions & 0 deletions cs/Markdown/Renderers/ITokenConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using Markdown.Tokens;

namespace Markdown.Renderers;

public interface ITokenConverter
{
public string ConvertTokens(List<Token> tokens);
}
8 changes: 8 additions & 0 deletions cs/Markdown/Tokenizers/ITokenizer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using Markdown.Tokens;

namespace Markdown.Tokenizers;

public interface ITokenizer
{
public List<Token> SplitToTokens(string text);
}
189 changes: 189 additions & 0 deletions cs/Markdown/Tokenizers/Tokenizer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
using Markdown.Enums;
using Markdown.Tokens;

namespace Markdown.Tokenizers;

public class Tokenizer : ITokenizer
{
private List<Token> tokens = [];
public List<Token> SplitToTokens(string text)
{
var result = new List<Token>();
var paragraphs = text.Split('\n');
for (var i = 0; i < paragraphs.Length; i++)
{
tokens = GetTokens(paragraphs[i]);
tokens = ProcessScreeningTokens(tokens);
ProcessInvalidTokens();
ProcessNonPairTokens();
ProcessWrongOrder();
result.AddRange(tokens);
if (paragraphs.Length > 1 && i < paragraphs.Length - 1)
result.Add(new LiteralToken("\n", LiteralType.Text));
tokens.Clear();
}
return result;
}

private static List<Token> GetTokens(string text)
{
var tokens = new List<Token>();
var index = 0;
while (index < text.Length)
{
tokens.Add(TokenFactory.GenerateToken(text, index));
index += tokens.Last().Content.Length;
}
if (tokens.FirstOrDefault() is ParagraphToken)
tokens.Add(new ParagraphToken(tokens.First().Content));
return tokens;
}

private static List<Token> ProcessScreeningTokens(List<Token> tokens)
{
Token? previousToken = null;
var result = new List<Token>();
foreach (var token in tokens)
{
if (previousToken is ScreeningToken)
{
if (!token.IsTag && token is not ScreeningToken)
{
var newToken = new LiteralToken(previousToken);
result.Add(newToken);
result.Add(token);
previousToken = token;

}
else
{
var newToken = new LiteralToken(token);
previousToken = newToken;
result.Add(newToken);
}
}
else
{
if (token is not ScreeningToken)
result.Add(token);
previousToken = token;
}
}

if (previousToken is not ScreeningToken)
return result;
result.Add(new LiteralToken(previousToken));
return result;
}

private void ProcessInvalidTokens()
{
for (var i = 0; i < tokens.Count; i++)
tokens[i] = tokens[i].Validate(tokens, i) ? tokens[i] : new LiteralToken(tokens[i]);
}

private void ProcessNonPairTokens()
{
var openTokensIndexes = new Stack<int>();
var incorrectTokensIndexes = new List<int>();
for (var i = 0; i < tokens.Count; i++)
{
var token = tokens[i];
if (!token.IsTag)
continue;
if (token.IsOpen(tokens, i))
openTokensIndexes.Push(i);
else
{
if (openTokensIndexes.Count == 0)
incorrectTokensIndexes.Add(i);
else
CheckOpenAndCloseTokens(openTokensIndexes, openTokensIndexes.Pop(), i, incorrectTokensIndexes);
}
}
incorrectTokensIndexes.AddRange(openTokensIndexes);

foreach (var index in incorrectTokensIndexes)
tokens[index] = new LiteralToken(tokens[index]);
}

private void ProcessWrongOrder()
{
var openedTokens = new Stack<Token>();
for (var i = 0; i < tokens.Count; i++)
{
var token = tokens[i];
if (!token.IsTag) continue;
if (token.IsClosing)
openedTokens.Pop();
else
openedTokens.Push(token);
if (IsCorrectOrder(openedTokens, token))
continue;
tokens[i] = new LiteralToken(token);
tokens[tokens[i].PairedTokenIndex] = new LiteralToken(tokens[tokens[i].PairedTokenIndex]);
}
}

private static bool IsCorrectOrder(Stack<Token> openedTokens, Token token)
=> token is not BoldToken || openedTokens.All(x => x is not ItalicsToken);

private void CheckOpenAndCloseTokens(Stack<int> openTokens, int openIndex, int closeIndex, List<int> incorrectTokens)
{
var openToken = tokens[openIndex];
var closeToken = tokens[closeIndex];
closeToken.IsClosing = true;
if (openToken.GetType() == closeToken.GetType())
{
SetPairedTokens(openIndex, closeIndex);
return;
}
var nextIndex = GetNextTokenIndex(closeIndex);
if (openTokens.Count == 0)
{
if (nextIndex > -1)
{
if (tokens[nextIndex].IsOpen(tokens, nextIndex) ||
tokens[nextIndex].GetType() == tokens[openIndex].GetType())
{
openTokens.Push(openIndex);
incorrectTokens.Add(closeIndex);
}
if (tokens[nextIndex].GetType() == tokens[openIndex].GetType())
return;
}
incorrectTokens.Add(openIndex);
incorrectTokens.Add(closeIndex);
return;
}
var preOpenIndex = openTokens.Peek();
if (tokens[preOpenIndex].GetType() != closeToken.GetType())
return;
if (nextIndex > -1 && !tokens[nextIndex].IsOpen(tokens, nextIndex)
&& tokens[nextIndex].GetType() == openToken.GetType())
{
openTokens.Pop();
incorrectTokens.Add(preOpenIndex);
incorrectTokens.Add(openIndex);
incorrectTokens.Add(closeIndex);
return;
}

openTokens.Pop();
SetPairedTokens(preOpenIndex, closeIndex);
incorrectTokens.Add(openIndex);
}

private void SetPairedTokens(int openIndex, int closeIndex)
{
tokens[openIndex].PairedTokenIndex = closeIndex;
tokens[closeIndex].PairedTokenIndex = openIndex;
}

private int GetNextTokenIndex(int index)
{
for (var i = index + 1; i < tokens.Count; i++)
if (tokens[i].IsTag) return i;
return -1;
}
}
76 changes: 76 additions & 0 deletions cs/Markdown/Tokens/BoldToken.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
namespace Markdown.Tokens;

public class BoldToken : Token
{
public override string TagWrapper => "strong";
public override bool IsTag => true;

public BoldToken(string content) : base(content) {}

public override bool Validate(List<Token> tokens, int index)
{
return !IsNearNumber(tokens, index) && (IsValid(tokens, index) || CanBeInWord(tokens, index));
}

public override bool IsOpen(List<Token> tokens, int index)
{
return CanBeOpen(tokens, index) || (!IsInClosingPosition(tokens, index) && CanBeInWord(tokens, index));
}

private static bool CanBeClose(List<Token> tokens, int index)
{
return index - 1 > 0 && tokens[index - 1] is not SpaceToken &&
(index + 1 >= tokens.Count || tokens[index + 1].IsTag || tokens[index + 1] is SpaceToken);
}

private static bool CanBeOpen(List<Token> tokens, int index)
{
return index + 1 < tokens.Count && tokens[index + 1] is not SpaceToken &&
(index - 1 < 0 || tokens[index - 1].IsTag || tokens[index - 1] is SpaceToken);
}

private static bool IsValid(List<Token> tokens, int index)
{
return CanBeClose(tokens, index) != CanBeOpen(tokens, index);
}

private static bool IsNearNumber(List<Token> tokens, int index)
{
return index - 1 >= 0 && index + 1 < tokens.Count &&
((tokens[index - 1] is LiteralToken && ((LiteralToken)tokens[index - 1]).IsNumber() &&
tokens[index + 1] is not SpaceToken) ||
(tokens[index + 1] is LiteralToken && ((LiteralToken)tokens[index + 1]).IsNumber() &&
tokens[index - 1] is not SpaceToken));
}

private static bool CanBeInWord(List<Token> tokens, int index)
{
var neededToken = tokens[index].GetType();
var wordCount = 0;
var amountInWord = 0;
while (index - 1 >= 0 && tokens[index - 1] is not SpaceToken)
index--;
while (index < tokens.Count && tokens[index] is not SpaceToken)
{
if (tokens[index].GetType() == neededToken)
amountInWord++;
else
wordCount++;
index++;
}
return wordCount > 0 && amountInWord % 2 == 0;
}

private static bool IsInClosingPosition(List<Token> tokens, int index)
{
var currentIndex = index - 1;
var countBefore = 1;
while (currentIndex >= 0 && tokens[currentIndex] is not SpaceToken)
{
if (tokens[currentIndex].GetType() == tokens[index].GetType())
countBefore++;
currentIndex--;
}
return countBefore % 2 == 0;
}
}
Loading