diff --git a/cs/Markdown/Markdown.csproj b/cs/Markdown/Markdown.csproj new file mode 100644 index 000000000..2150e3797 --- /dev/null +++ b/cs/Markdown/Markdown.csproj @@ -0,0 +1,10 @@ + + + + Exe + net8.0 + enable + enable + + + diff --git a/cs/Markdown/Md.cs b/cs/Markdown/Md.cs new file mode 100644 index 000000000..f8542ae68 --- /dev/null +++ b/cs/Markdown/Md.cs @@ -0,0 +1,30 @@ +using System.Text; +using Markdown.Token; + +namespace Markdown; + +public static class Md +{ + public static string Render(string text) + { + var parser = new TagParser(); + return GenerateHtml(text, parser.GetTokens(text)); + } + + private static string GenerateHtml(string text, List tokens) + { + var currentTokens = tokens.OrderBy(t => t.Position).ToList(); + int position = 0; + var sb = new StringBuilder(); + foreach (var token in currentTokens) + { + sb.Append(text[position..token.Position]); + sb.Append(token.ConvertedText); + position = token.Position + token.SourceText.Length > text.Length ? text.Length : token.Position + token.SourceText.Length; + } + + sb.Append(text[position..text.Length]); + + return sb.ToString(); + } +} \ No newline at end of file diff --git a/cs/Markdown/Program.cs b/cs/Markdown/Program.cs new file mode 100644 index 000000000..6e1c6f14c --- /dev/null +++ b/cs/Markdown/Program.cs @@ -0,0 +1,3 @@ +using Markdown; + +Console.WriteLine(Md.Render("__test _ _markdown_ text__ another text")); diff --git a/cs/Markdown/Rules/BoldRule.cs b/cs/Markdown/Rules/BoldRule.cs new file mode 100644 index 000000000..d866406e5 --- /dev/null +++ b/cs/Markdown/Rules/BoldRule.cs @@ -0,0 +1,99 @@ +using Markdown.Tags; + +namespace Markdown.Rules; + +public class BoldRule : IRule +{ + private readonly ITag tag = new BoldTag(); + private List tokens = new(); + private bool isItalicInStart; + private bool isItalicInMiddle; + private bool isTagClosed; + private readonly int[] italicStartsInBoldState = [10, 12]; + private readonly int boldStartsInItalicState = 9; + private readonly int fakeItalicStartState = 13; + private int currentState; + + public TagKind MoveByRule(char ch, int position) + { + var symbol = SymbolStatusParser.ParseSymbolStatus(ch); + + if (!tag.UsedSymbols.Contains(symbol)) + { + symbol = SymbolStatus.anotherSymbol; + } + + currentState = tag.States[currentState][symbol]; + + if (currentState == 0) + { + ClearTokens(); + } + + if (currentState == tag.InputStateNumber) + { + tokens.Add(new Token.Token(tag.MdView, tag.Head, position - (tag.MdView.Length - 1))); + } + else if (currentState == tag.OutputStateNumber) + { + if (symbol == SymbolStatus.eof) + { + tokens.Add(new Token.Token(tag.MdView, tag.Tail, position - (tag.MdView.Length - 1))); + } + else + { + tokens.Add(new Token.Token(tag.MdView, tag.Tail, position - tag.MdView.Length)); + } + + isTagClosed = true; + } + else if (currentState == boldStartsInItalicState) + { + if (isItalicInMiddle) + { + isItalicInStart = false; + isItalicInMiddle = false; + isTagClosed = false; + currentState = 0; + tokens.Clear(); + } + else + { + isItalicInStart = !isItalicInStart; + } + } + else if (italicStartsInBoldState.Contains(currentState)) + { + if (isItalicInStart) + { + isItalicInStart = false; + isItalicInMiddle = false; + isTagClosed = false; + currentState = 0; + tokens.Clear(); + } + else + { + isItalicInMiddle = !isItalicInMiddle; + } + + } + else if (currentState == fakeItalicStartState) + { + isItalicInMiddle = !isItalicInMiddle; + } + + if (isTagClosed && (!isItalicInMiddle && !isItalicInStart || symbol == SymbolStatus.eof)) + { + isTagClosed = false; + currentState = 0; + return TagKind.Close; + } + + return TagKind.None; + } + + public List GetTokens() { return tokens; } + + public void ClearTokens() { tokens.Clear(); } +} \ No newline at end of file diff --git a/cs/Markdown/Rules/EscapeRule.cs b/cs/Markdown/Rules/EscapeRule.cs new file mode 100644 index 000000000..d7ea5dbf8 --- /dev/null +++ b/cs/Markdown/Rules/EscapeRule.cs @@ -0,0 +1,40 @@ +using Markdown.Tags; + +namespace Markdown.Rules; + +public class EscapeRule : IRule +{ + private readonly ITag tag = new EscapeTag(); + private List tokens = new(); + private int currentState; + + public TagKind MoveByRule(char ch, int position) + { + var symbol = SymbolStatusParser.ParseSymbolStatus(ch); + + if (!tag.UsedSymbols.Contains(symbol)) + { + symbol = SymbolStatus.anotherSymbol; + } + + currentState = tag.States[currentState][symbol]; + + if (currentState == tag.InputStateNumber) + { + tokens.Add(new Token.Token(tag.MdView, tag.Head, position - (tag.MdView.Length - 1))); + return TagKind.Open; + } + else if (currentState == tag.OutputStateNumber) + { + tokens.Add(new Token.Token(ch.ToString(), ch.ToString(), position - (tag.MdView.Length - 1))); + currentState = 0; + return TagKind.Close; + } + + return TagKind.None; + } + + public List GetTokens() { return tokens; } + + public void ClearTokens() { tokens.Clear(); } +} \ No newline at end of file diff --git a/cs/Markdown/Rules/H1Rule.cs b/cs/Markdown/Rules/H1Rule.cs new file mode 100644 index 000000000..29b49f6c3 --- /dev/null +++ b/cs/Markdown/Rules/H1Rule.cs @@ -0,0 +1,47 @@ +using Markdown.Tags; + +namespace Markdown.Rules; + +public class H1Rule : IRule +{ + private readonly ITag tag = new H1Tag(); + private List tokens = new(); + private int currentState; + + public TagKind MoveByRule(char ch, int position) + { + var symbol = SymbolStatusParser.ParseSymbolStatus(ch); + + if (!tag.UsedSymbols.Contains(symbol)) + { + symbol = SymbolStatus.anotherSymbol; + } + + currentState = tag.States[currentState][symbol]; + + if (currentState == tag.InputStateNumber) + { + tokens.Add(new Token.Token(tag.MdView, tag.Head, position - (tag.MdView.Length - 1))); + } + else if (currentState == tag.OutputStateNumber) + { + if (symbol == SymbolStatus.eof) + { + tokens.Add(new Token.Token("", tag.Tail, position + 1)); + } + else + { + tokens.Add(new Token.Token("", tag.Tail, position)); + } + + currentState = 0; + return TagKind.Close; + } + + return TagKind.None; + } + + public List GetTokens() { return tokens; } + + public void ClearTokens() { tokens.Clear(); } +} \ No newline at end of file diff --git a/cs/Markdown/Rules/IRule.cs b/cs/Markdown/Rules/IRule.cs new file mode 100644 index 000000000..2d10c51f6 --- /dev/null +++ b/cs/Markdown/Rules/IRule.cs @@ -0,0 +1,12 @@ +using Markdown.Tags; + +namespace Markdown.Rules; + +public interface IRule +{ + public TagKind MoveByRule(char ch, int position); + + public List GetTokens(); + + public void ClearTokens(); +} \ No newline at end of file diff --git a/cs/Markdown/Rules/ItalicRule.cs b/cs/Markdown/Rules/ItalicRule.cs new file mode 100644 index 000000000..406a98d0c --- /dev/null +++ b/cs/Markdown/Rules/ItalicRule.cs @@ -0,0 +1,95 @@ +using Markdown.Tags; + +namespace Markdown.Rules; + +public class ItalicRule : IRule +{ + private readonly ITag tag = new ItalicTextTag(); + private List tokens = new(); + private bool isBoldInMiddle; + private bool isBoldInStart; + private bool isTagClosed; + private readonly int anotherStartState = 18; + private readonly int italicStartsInBoldState = 7; + private readonly int boldStartsInItalicState = 10; + private int currentState; + + public TagKind MoveByRule(char ch, int position) + { + + var symbol = SymbolStatusParser.ParseSymbolStatus(ch); + + if (!tag.UsedSymbols.Contains(symbol)) + { + symbol = SymbolStatus.anotherSymbol; + } + + currentState = tag.States[currentState][symbol]; + + if (currentState == 0) + { + ClearTokens(); + } + + if (currentState == tag.InputStateNumber || currentState == anotherStartState) + { + tokens.Add(new Token.Token(tag.MdView, tag.Head, position - tag.MdView.Length)); + } + else if (currentState == tag.OutputStateNumber) + { + if (symbol == SymbolStatus.eof) + { + tokens.Add(new Token.Token(tag.MdView, tag.Tail, position)); + } + else + { + tokens.Add(new Token.Token(tag.MdView, tag.Tail, position - tag.MdView.Length)); + } + isTagClosed = true; + } + else if (currentState == boldStartsInItalicState) + { + if (isBoldInMiddle) + { + isBoldInMiddle = false; + isBoldInStart = false; + isTagClosed = false; + currentState = 0; + tokens.Clear(); + } + else + { + isBoldInStart = !isBoldInStart; + } + } + else if (currentState == italicStartsInBoldState) + { + if (isBoldInStart) + { + isBoldInMiddle = false; + isBoldInStart = false; + isTagClosed = false; + currentState = 0; + tokens.Clear(); + + } + else + { + isBoldInMiddle = !isBoldInMiddle; + } + } + + if (isTagClosed && (!isBoldInMiddle || symbol == SymbolStatus.eof)) + { + isTagClosed = false; + currentState = 0; + return TagKind.Close; + } + + return TagKind.None; + } + + public List GetTokens() { return tokens; } + + public void ClearTokens() { tokens.Clear(); } +} \ No newline at end of file diff --git a/cs/Markdown/SymbolStatus.cs b/cs/Markdown/SymbolStatus.cs new file mode 100644 index 000000000..dfee05115 --- /dev/null +++ b/cs/Markdown/SymbolStatus.cs @@ -0,0 +1,15 @@ +namespace Markdown; + +public enum SymbolStatus +{ + text, + digit, + underscore, + backslash, + eof, + newline, + space, + sharp, + anotherSymbol, + none +} \ No newline at end of file diff --git a/cs/Markdown/SymbolStatusParser.cs b/cs/Markdown/SymbolStatusParser.cs new file mode 100644 index 000000000..32ce1abd7 --- /dev/null +++ b/cs/Markdown/SymbolStatusParser.cs @@ -0,0 +1,25 @@ +namespace Markdown; + +public static class SymbolStatusParser +{ + public static SymbolStatus ParseSymbolStatus(char symbol) + { + if (char.IsLetter(symbol)) + return SymbolStatus.text; + if (char.IsDigit(symbol)) + return SymbolStatus.digit; + if (symbol == ' ') + return SymbolStatus.space; + if (symbol == '_') + return SymbolStatus.underscore; + if (symbol == '\\') + return SymbolStatus.backslash; + if (symbol == '\n') + return SymbolStatus.newline; + if (symbol == '\0') + return SymbolStatus.eof; + if (symbol == '#') + return SymbolStatus.sharp; + return SymbolStatus.none; + } +} \ No newline at end of file diff --git a/cs/Markdown/TagParser.cs b/cs/Markdown/TagParser.cs new file mode 100644 index 000000000..c5c971e76 --- /dev/null +++ b/cs/Markdown/TagParser.cs @@ -0,0 +1,119 @@ +using Markdown.Rules; +using Markdown.Tags; +using Markdown.Token; + +namespace Markdown; + +public class TagParser +{ + private List Rules = + [ + new BoldRule(), + new ItalicRule(), + new H1Rule() + ]; + + private EscapeRule escapeRule = new(); + + public bool TryGoNextSymbol(int textPointer, string text) + { + if (textPointer + 1 < text.Length) + { + return true; + } + return false; + } + + public List GetTokens(string text) + { + var tokens = new List {}; + var textPointer = 0; + var isPointerTeleported = false; + var isStateNotChanged = true; + var skip = 0; + while (textPointer != text.Length) + { + if (skip != 0) + { + skip--; + } + else + { + var res = escapeRule.MoveByRule(text[textPointer], textPointer); + + if (res == TagKind.Open) + { + if (TryGoNextSymbol(textPointer, text)) + { + textPointer++; + res = escapeRule.MoveByRule(text[textPointer], textPointer); + + if (res == TagKind.Close) + { + tokens.AddRange(escapeRule.GetTokens()); + escapeRule.ClearTokens(); + if (text[textPointer] == '\\') + { + textPointer--; + skip += 1; + } + else + { + textPointer++; + isPointerTeleported = true; + isStateNotChanged = false; + } + } + else + { + textPointer--; + } + + if (!TryGoNextSymbol(textPointer, text)) + { + break; + } + } + } + } + + if (isPointerTeleported && isStateNotChanged) + { + textPointer++; + isPointerTeleported = false; + } + + + foreach (var rule in Rules) + { + var result = rule.MoveByRule(text[textPointer], textPointer); + if (result == TagKind.Close) + { + tokens.AddRange(rule.GetTokens()); + rule.ClearTokens(); + } + } + + if (!isPointerTeleported) + { + textPointer++; + } + else + { + isStateNotChanged = true; + } + + } + + foreach (var rule in Rules) + { + var result = rule.MoveByRule('\0', textPointer - 1); + if (result == TagKind.Close) + { + tokens.AddRange(rule.GetTokens()); + } + } + + return tokens; + } +} \ No newline at end of file diff --git a/cs/Markdown/Tags/BoldTag.cs b/cs/Markdown/Tags/BoldTag.cs new file mode 100644 index 000000000..97430c6af --- /dev/null +++ b/cs/Markdown/Tags/BoldTag.cs @@ -0,0 +1,159 @@ +namespace Markdown.Tags; + +public class BoldTag : ITag +{ + public string Head => ""; + public string Tail => ""; + public string MdView => "__"; + + public int InputStateNumber { get; } = 2; + public int OutputStateNumber { get; } = 11; + + public Dictionary> States { get; } = InitialzeStates(); + + public SymbolStatus[] UsedSymbols { get; } = + { + SymbolStatus.text, SymbolStatus.digit, SymbolStatus.underscore + , SymbolStatus.eof, SymbolStatus.newline, SymbolStatus.space, + SymbolStatus.anotherSymbol + }; + + public static Dictionary> InitialzeStates() + { + var states = new Dictionary>(); + + states.Add(0, new Dictionary()); + states.Add(1, new Dictionary()); + states.Add(2, new Dictionary()); + states.Add(3, new Dictionary()); + states.Add(4, new Dictionary()); + states.Add(5, new Dictionary()); + states.Add(6, new Dictionary()); + states.Add(7, new Dictionary()); + states.Add(8, new Dictionary()); + states.Add(9, new Dictionary()); + states.Add(10, new Dictionary()); + states.Add(11, new Dictionary()); + states.Add(12, new Dictionary()); + states.Add(13, new Dictionary()); + + states[0].Add(SymbolStatus.text, 0); + states[0].Add(SymbolStatus.digit, 0); + states[0].Add(SymbolStatus.underscore, 1); + states[0].Add(SymbolStatus.eof, 0); + states[0].Add(SymbolStatus.newline, 0); + states[0].Add(SymbolStatus.space, 0); + states[0].Add(SymbolStatus.anotherSymbol, 0); + + states[1].Add(SymbolStatus.text, 9); + states[1].Add(SymbolStatus.digit, 0); + states[1].Add(SymbolStatus.underscore, 2); + states[1].Add(SymbolStatus.eof, 0); + states[1].Add(SymbolStatus.newline, 0); + states[1].Add(SymbolStatus.space, 0); + states[1].Add(SymbolStatus.anotherSymbol, 9); + + // Tag Open + states[2].Add(SymbolStatus.text, 4); + states[2].Add(SymbolStatus.digit, 0); + states[2].Add(SymbolStatus.underscore, 3); + states[2].Add(SymbolStatus.eof, 0); + states[2].Add(SymbolStatus.newline, 0); + states[2].Add(SymbolStatus.space, 5); + states[2].Add(SymbolStatus.anotherSymbol, 4); + + states[3].Add(SymbolStatus.text, 0); + states[3].Add(SymbolStatus.digit, 0); + states[3].Add(SymbolStatus.underscore, 3); + states[3].Add(SymbolStatus.eof, 0); + states[3].Add(SymbolStatus.newline, 0); + states[3].Add(SymbolStatus.space, 0); + states[3].Add(SymbolStatus.anotherSymbol, 0); + + states[4].Add(SymbolStatus.text, 4); + states[4].Add(SymbolStatus.digit, 0); + states[4].Add(SymbolStatus.underscore, 7); + states[4].Add(SymbolStatus.eof, 0); + states[4].Add(SymbolStatus.newline, 0); + states[4].Add(SymbolStatus.space, 5); + states[4].Add(SymbolStatus.anotherSymbol, 4); + + states[5].Add(SymbolStatus.text, 6); + states[5].Add(SymbolStatus.digit, 0); + states[5].Add(SymbolStatus.underscore, 12); + states[5].Add(SymbolStatus.eof, 0); + states[5].Add(SymbolStatus.newline, 0); + states[5].Add(SymbolStatus.space, 5); + states[5].Add(SymbolStatus.anotherSymbol, 6); + + states[6].Add(SymbolStatus.text, 6); + states[6].Add(SymbolStatus.digit, 0); + states[6].Add(SymbolStatus.underscore, 7); + states[6].Add(SymbolStatus.eof, 0); + states[6].Add(SymbolStatus.newline, 0); + states[6].Add(SymbolStatus.space, 5); + states[6].Add(SymbolStatus.anotherSymbol, 6); + + states[7].Add(SymbolStatus.text, 10); + states[7].Add(SymbolStatus.digit, 0); + states[7].Add(SymbolStatus.underscore, 8); + states[7].Add(SymbolStatus.eof, 0); + states[7].Add(SymbolStatus.newline, 0); + states[7].Add(SymbolStatus.space, 10); + states[7].Add(SymbolStatus.anotherSymbol, 10); + + states[8].Add(SymbolStatus.text, 11); + states[8].Add(SymbolStatus.digit, 11); + states[8].Add(SymbolStatus.underscore, 2); + states[8].Add(SymbolStatus.eof, 11); + states[8].Add(SymbolStatus.newline, 11); + states[8].Add(SymbolStatus.space, 11); + states[8].Add(SymbolStatus.anotherSymbol, 11); + + // Maybe Bold in Italic + states[9].Add(SymbolStatus.text, 0); + states[9].Add(SymbolStatus.digit, 0); + states[9].Add(SymbolStatus.underscore, 1); + states[9].Add(SymbolStatus.eof, 0); + states[9].Add(SymbolStatus.newline, 0); + states[9].Add(SymbolStatus.space, 0); + states[9].Add(SymbolStatus.anotherSymbol, 0); + + // Maybe Italic in Bold + states[10].Add(SymbolStatus.text, 6); + states[10].Add(SymbolStatus.digit, 0); + states[10].Add(SymbolStatus.underscore, 7); + states[10].Add(SymbolStatus.eof, 0); + states[10].Add(SymbolStatus.newline, 0); + states[10].Add(SymbolStatus.space, 5); + states[10].Add(SymbolStatus.anotherSymbol, 6); + + // TagClose + states[11].Add(SymbolStatus.text, 0); + states[11].Add(SymbolStatus.digit, 0); + states[11].Add(SymbolStatus.underscore, 0); + states[11].Add(SymbolStatus.eof, 0); + states[11].Add(SymbolStatus.newline, 0); + states[11].Add(SymbolStatus.space, 0); + states[11].Add(SymbolStatus.anotherSymbol, 0); + + // Maybe Italic in Bold + states[12].Add(SymbolStatus.text, 4); + states[12].Add(SymbolStatus.digit, 0); + states[12].Add(SymbolStatus.underscore, 12); + states[12].Add(SymbolStatus.eof, 0); + states[12].Add(SymbolStatus.newline, 0); + states[12].Add(SymbolStatus.space, 13); + states[12].Add(SymbolStatus.anotherSymbol, 4); + + states[13].Add(SymbolStatus.text, 6); + states[13].Add(SymbolStatus.digit, 0); + states[13].Add(SymbolStatus.underscore, 12); + states[13].Add(SymbolStatus.eof, 0); + states[13].Add(SymbolStatus.newline, 0); + states[13].Add(SymbolStatus.space, 5); + states[13].Add(SymbolStatus.anotherSymbol, 6); + + return states; + } +} \ No newline at end of file diff --git a/cs/Markdown/Tags/EscapeTag.cs b/cs/Markdown/Tags/EscapeTag.cs new file mode 100644 index 000000000..b17d9cd7b --- /dev/null +++ b/cs/Markdown/Tags/EscapeTag.cs @@ -0,0 +1,44 @@ +namespace Markdown.Tags; + +public class EscapeTag : ITag +{ + public string Head => ""; + private string symbol; + public string Tail => symbol; + public string MdView => """\"""; + + public int InputStateNumber { get; } = 1; + public int OutputStateNumber { get; } = 2; + + public Dictionary> States { get; } = InitialzeStates(); + + public SymbolStatus[] UsedSymbols { get; } = + { + SymbolStatus.backslash, SymbolStatus.underscore, SymbolStatus.anotherSymbol + }; + + public static Dictionary> InitialzeStates() + { + var states = new Dictionary>(); + + states.Add(0, new Dictionary()); + states.Add(1, new Dictionary()); + states.Add(2, new Dictionary()); + + states[0].Add(SymbolStatus.backslash, 1); + states[0].Add(SymbolStatus.underscore, 0); + states[0].Add(SymbolStatus.anotherSymbol, 0); + + // Tag Open + states[1].Add(SymbolStatus.backslash, 2); + states[1].Add(SymbolStatus.underscore, 2); + states[1].Add(SymbolStatus.anotherSymbol, 0); + + // Tag Close + states[2].Add(SymbolStatus.backslash, 0); + states[2].Add(SymbolStatus.underscore, 0); + states[2].Add(SymbolStatus.anotherSymbol, 0); + + return states; + } +} \ No newline at end of file diff --git a/cs/Markdown/Tags/H1Tag.cs b/cs/Markdown/Tags/H1Tag.cs new file mode 100644 index 000000000..405f08c28 --- /dev/null +++ b/cs/Markdown/Tags/H1Tag.cs @@ -0,0 +1,71 @@ +namespace Markdown.Tags; + +public class H1Tag : ITag +{ + public string Head => "

"; + public string Tail => "

"; + public string MdView => "# "; + + public int InputStateNumber { get; } = 3; + public int OutputStateNumber { get; } = 5; + + public Dictionary> States { get; } = InitialzeStates(); + + public SymbolStatus[] UsedSymbols { get; } = + { + SymbolStatus.eof, SymbolStatus.newline, SymbolStatus.space, + SymbolStatus.sharp, SymbolStatus.anotherSymbol + }; + + public static Dictionary> InitialzeStates() + { + var states = new Dictionary>(); + + states.Add(0, new Dictionary()); + states.Add(1, new Dictionary()); + states.Add(2, new Dictionary()); + states.Add(3, new Dictionary()); + states.Add(4, new Dictionary()); + states.Add(5, new Dictionary()); + + states[0].Add(SymbolStatus.sharp, 1); + states[0].Add(SymbolStatus.space, 2); + states[0].Add(SymbolStatus.newline, 0); + states[0].Add(SymbolStatus.eof, 0); + states[0].Add(SymbolStatus.anotherSymbol, 2); + + states[1].Add(SymbolStatus.sharp, 0); + states[1].Add(SymbolStatus.space, 3); + states[1].Add(SymbolStatus.newline, 0); + states[1].Add(SymbolStatus.eof, 0); + states[1].Add(SymbolStatus.anotherSymbol, 0); + + states[2].Add(SymbolStatus.sharp, 2); + states[2].Add(SymbolStatus.space, 2); + states[2].Add(SymbolStatus.newline, 0); + states[2].Add(SymbolStatus.eof, 0); + states[2].Add(SymbolStatus.anotherSymbol, 2); + + // Tag Open + states[3].Add(SymbolStatus.sharp, 4); + states[3].Add(SymbolStatus.space, 4); + states[3].Add(SymbolStatus.newline, 5); + states[3].Add(SymbolStatus.eof, 5); + states[3].Add(SymbolStatus.anotherSymbol, 4); + + states[4].Add(SymbolStatus.sharp, 4); + states[4].Add(SymbolStatus.space, 4); + states[4].Add(SymbolStatus.newline, 5); + states[4].Add(SymbolStatus.eof, 5); + states[4].Add(SymbolStatus.anotherSymbol, 4); + + // Tag Close + states[5].Add(SymbolStatus.sharp, 0); + states[5].Add(SymbolStatus.space, 0); + states[5].Add(SymbolStatus.newline, 0); + states[5].Add(SymbolStatus.eof, 0); + states[5].Add(SymbolStatus.anotherSymbol, 0); + + return states; + } +} \ No newline at end of file diff --git a/cs/Markdown/Tags/ITag.cs b/cs/Markdown/Tags/ITag.cs new file mode 100644 index 000000000..93dfa5218 --- /dev/null +++ b/cs/Markdown/Tags/ITag.cs @@ -0,0 +1,14 @@ +namespace Markdown.Tags; + +public interface ITag +{ + public string MdView { get; } + public string Head { get;} + public string Tail { get;} + + public SymbolStatus[] UsedSymbols { get; } + public Dictionary> States { get; } + + public int InputStateNumber { get; } + public int OutputStateNumber { get; } +} \ No newline at end of file diff --git a/cs/Markdown/Tags/ItalicTextTag.cs b/cs/Markdown/Tags/ItalicTextTag.cs new file mode 100644 index 000000000..41c64f692 --- /dev/null +++ b/cs/Markdown/Tags/ItalicTextTag.cs @@ -0,0 +1,204 @@ +namespace Markdown.Tags; + +public class ItalicTextTag : ITag +{ + public string Head => ""; + public string Tail => ""; + public string MdView => "_"; + + public int InputStateNumber { get; } = 17; + public int OutputStateNumber { get; } = 12; + + public Dictionary> States { get; } = InitialzeStates(); + + public SymbolStatus[] UsedSymbols { get; } = + { + SymbolStatus.text, SymbolStatus.digit, SymbolStatus.underscore + , SymbolStatus.eof, SymbolStatus.newline, SymbolStatus.space, + SymbolStatus.anotherSymbol + }; + + public static Dictionary> InitialzeStates() + { + var states = new Dictionary>(); + + states.Add(0, new Dictionary()); + states.Add(1, new Dictionary()); + states.Add(2, new Dictionary()); + states.Add(3, new Dictionary()); + states.Add(4, new Dictionary()); + states.Add(5, new Dictionary()); + states.Add(6, new Dictionary()); + states.Add(7, new Dictionary()); + states.Add(8, new Dictionary()); + states.Add(9, new Dictionary()); + states.Add(10, new Dictionary()); + states.Add(11, new Dictionary()); + states.Add(12, new Dictionary()); + states.Add(13, new Dictionary()); + states.Add(14, new Dictionary()); + states.Add(15, new Dictionary()); + states.Add(16, new Dictionary()); + states.Add(17, new Dictionary()); + states.Add(18, new Dictionary()); + + states[0].Add(SymbolStatus.text, 13); + states[0].Add(SymbolStatus.digit, 0); + states[0].Add(SymbolStatus.underscore, 1); + states[0].Add(SymbolStatus.eof, 0); + states[0].Add(SymbolStatus.newline, 0); + states[0].Add(SymbolStatus.space, 0); + states[0].Add(SymbolStatus.anotherSymbol, 13); + + states[1].Add(SymbolStatus.text, 17); + states[1].Add(SymbolStatus.digit, 0); + states[1].Add(SymbolStatus.underscore, 9); + states[1].Add(SymbolStatus.eof, 0); + states[1].Add(SymbolStatus.newline, 0); + states[1].Add(SymbolStatus.space, 0); + states[1].Add(SymbolStatus.anotherSymbol, 17); + + states[2].Add(SymbolStatus.text, 2); + states[2].Add(SymbolStatus.digit, 0); + states[2].Add(SymbolStatus.underscore, 5); + states[2].Add(SymbolStatus.eof, 0); + states[2].Add(SymbolStatus.newline, 0); + states[2].Add(SymbolStatus.space, 3); + states[2].Add(SymbolStatus.anotherSymbol, 2); + + states[3].Add(SymbolStatus.text, 4); + states[3].Add(SymbolStatus.digit, 0); + states[3].Add(SymbolStatus.underscore, 8); + states[3].Add(SymbolStatus.eof, 0); + states[3].Add(SymbolStatus.newline, 0); + states[3].Add(SymbolStatus.space, 3); + states[3].Add(SymbolStatus.anotherSymbol, 4); + + states[4].Add(SymbolStatus.text, 4); + states[4].Add(SymbolStatus.digit, 0); + states[4].Add(SymbolStatus.underscore, 5); + states[4].Add(SymbolStatus.eof, 0); + states[4].Add(SymbolStatus.newline, 0); + states[4].Add(SymbolStatus.space, 3); + states[4].Add(SymbolStatus.anotherSymbol, 4); + + states[5].Add(SymbolStatus.text, 12); + states[5].Add(SymbolStatus.digit, 12); + states[5].Add(SymbolStatus.underscore, 6); + states[5].Add(SymbolStatus.eof, 12); + states[5].Add(SymbolStatus.newline, 12); + states[5].Add(SymbolStatus.space, 12); + states[5].Add(SymbolStatus.anotherSymbol, 12); + + states[6].Add(SymbolStatus.text, 7); + states[6].Add(SymbolStatus.digit, 0); + states[6].Add(SymbolStatus.underscore, 8); + states[6].Add(SymbolStatus.eof, 0); + states[6].Add(SymbolStatus.newline, 0); + states[6].Add(SymbolStatus.space, 7); + states[6].Add(SymbolStatus.anotherSymbol, 7); + + // Maybe Bold in Italic + states[7].Add(SymbolStatus.text, 2); + states[7].Add(SymbolStatus.digit, 0); + states[7].Add(SymbolStatus.underscore, 5); + states[7].Add(SymbolStatus.eof, 0); + states[7].Add(SymbolStatus.newline, 0); + states[7].Add(SymbolStatus.space, 3); + states[7].Add(SymbolStatus.anotherSymbol, 2); + + states[8].Add(SymbolStatus.text, 2); + states[8].Add(SymbolStatus.digit, 0); + states[8].Add(SymbolStatus.underscore, 8); + states[8].Add(SymbolStatus.eof, 0); + states[8].Add(SymbolStatus.newline, 0); + states[8].Add(SymbolStatus.space, 3); + states[8].Add(SymbolStatus.anotherSymbol, 2); + + states[9].Add(SymbolStatus.text, 10); + states[9].Add(SymbolStatus.digit, 0); + states[9].Add(SymbolStatus.underscore, 11); + states[9].Add(SymbolStatus.eof, 0); + states[9].Add(SymbolStatus.newline, 0); + states[9].Add(SymbolStatus.space, 0); + states[9].Add(SymbolStatus.anotherSymbol, 10); + + // Maybe Italic in Bold + states[10].Add(SymbolStatus.text, 0); + states[10].Add(SymbolStatus.digit, 0); + states[10].Add(SymbolStatus.underscore, 1); + states[10].Add(SymbolStatus.eof, 0); + states[10].Add(SymbolStatus.newline, 0); + states[10].Add(SymbolStatus.space, 0); + states[10].Add(SymbolStatus.anotherSymbol, 0); + + states[11].Add(SymbolStatus.text, 0); + states[11].Add(SymbolStatus.digit, 0); + states[11].Add(SymbolStatus.underscore, 11); + states[11].Add(SymbolStatus.eof, 0); + states[11].Add(SymbolStatus.newline, 0); + states[11].Add(SymbolStatus.space, 0); + states[11].Add(SymbolStatus.anotherSymbol, 11); + + // Tag Close + states[12].Add(SymbolStatus.text, 0); + states[12].Add(SymbolStatus.digit, 0); + states[12].Add(SymbolStatus.underscore, 0); + states[12].Add(SymbolStatus.eof, 0); + states[12].Add(SymbolStatus.newline, 0); + states[12].Add(SymbolStatus.space, 0); + states[12].Add(SymbolStatus.anotherSymbol, 0); + + states[13].Add(SymbolStatus.text, 13); + states[13].Add(SymbolStatus.digit, 0); + states[13].Add(SymbolStatus.underscore, 14); + states[13].Add(SymbolStatus.eof, 0); + states[13].Add(SymbolStatus.newline, 0); + states[13].Add(SymbolStatus.space, 0); + states[13].Add(SymbolStatus.anotherSymbol, 13); + + states[14].Add(SymbolStatus.text, 18); + states[14].Add(SymbolStatus.digit, 0); + states[14].Add(SymbolStatus.underscore, 9); + states[14].Add(SymbolStatus.eof, 0); + states[14].Add(SymbolStatus.newline, 0); + states[14].Add(SymbolStatus.space, 0); + states[14].Add(SymbolStatus.anotherSymbol, 18); + + states[15].Add(SymbolStatus.text, 15); + states[15].Add(SymbolStatus.digit, 0); + states[15].Add(SymbolStatus.underscore, 16); + states[15].Add(SymbolStatus.eof, 0); + states[15].Add(SymbolStatus.newline, 0); + states[15].Add(SymbolStatus.space, 0); + states[15].Add(SymbolStatus.anotherSymbol, 15); + + states[16].Add(SymbolStatus.text, 12); + states[16].Add(SymbolStatus.digit, 12); + states[16].Add(SymbolStatus.underscore, 14); + states[16].Add(SymbolStatus.eof, 12); + states[16].Add(SymbolStatus.newline, 12); + states[16].Add(SymbolStatus.space, 12); + states[16].Add(SymbolStatus.anotherSymbol, 12); + + // Tag Open + states[17].Add(SymbolStatus.text, 2); + states[17].Add(SymbolStatus.digit, 0); + states[17].Add(SymbolStatus.underscore, 5); + states[17].Add(SymbolStatus.eof, 0); + states[17].Add(SymbolStatus.newline, 0); + states[17].Add(SymbolStatus.space, 3); + states[17].Add(SymbolStatus.anotherSymbol, 2); + + // Tag Open + states[18].Add(SymbolStatus.text, 15); + states[18].Add(SymbolStatus.digit, 0); + states[18].Add(SymbolStatus.underscore, 16); + states[18].Add(SymbolStatus.eof, 0); + states[18].Add(SymbolStatus.newline, 0); + states[18].Add(SymbolStatus.space, 0); + states[18].Add(SymbolStatus.anotherSymbol, 15); + + return states; + } +} \ No newline at end of file diff --git a/cs/Markdown/Tags/TagKind.cs b/cs/Markdown/Tags/TagKind.cs new file mode 100644 index 000000000..b48209736 --- /dev/null +++ b/cs/Markdown/Tags/TagKind.cs @@ -0,0 +1,8 @@ +namespace Markdown.Tags; + +public enum TagKind +{ + None, + Open, + Close +} \ No newline at end of file diff --git a/cs/Markdown/Token/IToken.cs b/cs/Markdown/Token/IToken.cs new file mode 100644 index 000000000..65541815f --- /dev/null +++ b/cs/Markdown/Token/IToken.cs @@ -0,0 +1,8 @@ +namespace Markdown.Token; + +public interface IToken +{ + public string SourceText { get; } + public string ConvertedText { get; } + public int Position { get; set; } +} \ No newline at end of file diff --git a/cs/Markdown/Token/Token.cs b/cs/Markdown/Token/Token.cs new file mode 100644 index 000000000..647f4e6a0 --- /dev/null +++ b/cs/Markdown/Token/Token.cs @@ -0,0 +1,8 @@ +namespace Markdown.Token; + +public class Token(string sourceText, string convertedText, int position) : IToken +{ + public string SourceText { get; } = sourceText; + public string ConvertedText { get; } = convertedText; + public int Position { get; set; } = position; +} \ No newline at end of file diff --git a/cs/MarkdownTests/MarkdownSpeedTest.cs b/cs/MarkdownTests/MarkdownSpeedTest.cs new file mode 100644 index 000000000..8a5392a1f --- /dev/null +++ b/cs/MarkdownTests/MarkdownSpeedTest.cs @@ -0,0 +1,44 @@ +using FluentAssertions; +using Markdown; +using System.Diagnostics; +using System.Text; + +namespace MarkdownTests; + +[TestFixture] +public class MarkdownSpeedTest +{ + [Test] + public void Render_ShouldWorkFast() + { + var sw = new Stopwatch(); + var results = new List(); + + for (var length = 640; length <= 5120; length *= 2) + { + var text = GetRandomString(length); + sw.Start(); + Md.Render(text); + sw.Stop(); + results.Add(sw.Elapsed); + sw.Reset(); + } + + for (var i = 1; i < results.Count; i++) + (results[i].Ticks / results[i - 1].Ticks).Should().BeLessThan(4); + } + + private string GetRandomString(int length) + { + Random rand = new Random(); + + var variants = new List + { + " ", "_", "__", " ", Environment.NewLine, ((char)rand.Next('A', 'Z' + 1)).ToString() + }; + var text = new StringBuilder(); + var rnd = new Random(); + for (var i = 0; i < length; i++) text.Append(variants[rnd.Next(variants.Count)]); + return text.ToString(); + } +} \ No newline at end of file diff --git a/cs/MarkdownTests/MarkdownTest.cs b/cs/MarkdownTests/MarkdownTest.cs new file mode 100644 index 000000000..1b63ea59b --- /dev/null +++ b/cs/MarkdownTests/MarkdownTest.cs @@ -0,0 +1,107 @@ +using System.Collections; +using FluentAssertions; +using Markdown; + +namespace MarkdownTests; + +[TestFixture] +public class MarkdownTest +{ + [Test] + public void MarkdownRender_ReturnTextWhenNoTags() + { + var text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis feugiat placerat sagittis."; + + var generatedText = Md.Render(text); + + generatedText.Should().Be(text); + } + + [TestCaseSource(nameof(ItalicAndBoldTestCases))] + public void MarkdownRender_CorrectlyWork_WithItalicAndBoldTags(string text, string expected) + { + var generatedText = Md.Render(text); + + generatedText.Should().Be(expected); + } + + public static IEnumerable ItalicAndBoldTestCases + { + get + { + yield return new TestCaseData("__openBold word _openItalic word closeItalic word_ closeBold__", + "openBold word openItalic word closeItalic word closeBold") + .SetName("Italic tags in Bold are allowed"); + yield return new TestCaseData("_a", "_a").SetName("italic is not closed"); + yield return new TestCaseData("_a_", "a").SetName("italic closed within the word"); + yield return new TestCaseData("__a__", "a").SetName("italic closed within the word"); + yield return new TestCaseData("__BoldItalic_Italic_ItalicBold__", + "BoldItalicItalicItalicBold") + .SetName("bold and italic closed within the word"); + yield return new TestCaseData("a_a a_a", "a_a a_a").SetName("italics in different words in the middle"); + yield return new TestCaseData("__asd_das__", "asd_das").SetName("not closed italics in words"); + yield return new TestCaseData("____", "____").SetName("underscore without letters"); + yield return new TestCaseData("_bold__", "_bold__").SetName("not paired tags"); + yield return new TestCaseData("__intersection _bold__ and italic_", "__intersection _bold__ and italic_") + .SetName("intersection of bold and italic"); + yield return new TestCaseData("_ab__cd_", "ab__cd").SetName("bold inside italics within a word"); + yield return new TestCaseData("__a _b_ _c_ d__", "a b c d") + .SetName("multiple closing tags inside the bold"); + yield return new TestCaseData("_a __c__ d_", "a __c__ d").SetName("bold inside italics"); + yield return new TestCaseData("_ab_ad", "abad").SetName("italics opens at the beginning closes in the middle"); + yield return new TestCaseData("a_bad_", "abad").SetName("italics opens in the middle closes at the end"); + yield return new TestCaseData("__bold _ab_ad bold__", "bold abad bold") + .SetName("italic opens at the beginning closes in the middle inside bold"); + yield return new TestCaseData("_a123_", "_a123_").SetName("italic not works with digit"); + yield return new TestCaseData("__a123__", "__a123__").SetName("bold not works with digit"); + yield return new TestCaseData("__test _ _markdown_ text__ another text", "test _ markdown text another text") + .SetName("underscore and space is not opening tag"); + } + } + + [TestCaseSource(nameof(ShieldingTestCases))] + public void MarkdownRender_CorrectlyWork_WithShielding(string text, string expected) + { + var generatedText = Md.Render(text); + + generatedText.Should().Be(expected); + } + + public static IEnumerable ShieldingTestCases + { + get + { + yield return new TestCaseData("\\_a_", "_a_").SetName("shielded opening tag"); + yield return new TestCaseData("\\\\_a_", "\\a").SetName("ignore shield backslash"); + yield return new TestCaseData("_\\a_", "\\a").SetName("does not escape the letter"); + yield return new TestCaseData("_a\\_", "_a_").SetName("shielded closing tag"); + yield return new TestCaseData("\\__a_", "_a").SetName("shields the bold turning into italic"); + yield return new TestCaseData("__test \\_ _markdown_ text__ another text", "test _ markdown text another text") + .SetName("shielding does not interfere with the work of other tags"); + } + } + + [TestCaseSource(nameof(HeaderTestCases))] + public void CorrectlyWork_WithHeader(string text, string expected) + { + var generatedText = Md.Render(text); + + generatedText.Should().Be(expected); + } + + public static IEnumerable HeaderTestCases + { + get + { + yield return new TestCaseData("# aba", "

aba

").SetName("correctly header"); + yield return new TestCaseData("#aba", "#aba").SetName("sharp with word"); + yield return new TestCaseData("# __a _b_ _c_ d__", + "

a b c d

") + .SetName("header works correctly with other tags"); + yield return new TestCaseData("ab #", "ab #").SetName("sharp in end"); + yield return new TestCaseData("ab # ab", "ab # ab").SetName("sharp in middle of string"); + yield return new TestCaseData("# abc\n# abc", "

abc

\n

abc

") + .SetName("correct work with multiple paragraphs"); + } + } +} \ No newline at end of file diff --git a/cs/MarkdownTests/MarkdownTests.csproj b/cs/MarkdownTests/MarkdownTests.csproj new file mode 100644 index 000000000..9e6c35fd8 --- /dev/null +++ b/cs/MarkdownTests/MarkdownTests.csproj @@ -0,0 +1,29 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + + + + + + + + + + + + + diff --git a/cs/clean-code.sln b/cs/clean-code.sln index 2206d54db..ae4e5d792 100644 --- a/cs/clean-code.sln +++ b/cs/clean-code.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 14 -VisualStudioVersion = 14.0.25420.1 +# Visual Studio Version 17 +VisualStudioVersion = 17.12.35514.174 d17.12 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Chess", "Chess\Chess.csproj", "{DBFBE40E-EE0C-48F4-8763-EBD11C960081}" EndProject @@ -9,6 +9,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ControlDigit", "ControlDigi EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Samples", "Samples\Samples.csproj", "{C3EF41D7-50EF-4CE1-B30A-D1D81C93D7FA}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Markdown", "Markdown\Markdown.csproj", "{2A88730A-1C3F-42B6-AD70-FF890AEAC047}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MarkdownTests", "MarkdownTests\MarkdownTests.csproj", "{5928AADB-6ACE-429E-B801-BE58A76299AC}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -27,5 +31,16 @@ Global {C3EF41D7-50EF-4CE1-B30A-D1D81C93D7FA}.Debug|Any CPU.Build.0 = Debug|Any CPU {C3EF41D7-50EF-4CE1-B30A-D1D81C93D7FA}.Release|Any CPU.ActiveCfg = Release|Any CPU {C3EF41D7-50EF-4CE1-B30A-D1D81C93D7FA}.Release|Any CPU.Build.0 = Release|Any CPU + {2A88730A-1C3F-42B6-AD70-FF890AEAC047}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2A88730A-1C3F-42B6-AD70-FF890AEAC047}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2A88730A-1C3F-42B6-AD70-FF890AEAC047}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2A88730A-1C3F-42B6-AD70-FF890AEAC047}.Release|Any CPU.Build.0 = Release|Any CPU + {5928AADB-6ACE-429E-B801-BE58A76299AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5928AADB-6ACE-429E-B801-BE58A76299AC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5928AADB-6ACE-429E-B801-BE58A76299AC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5928AADB-6ACE-429E-B801-BE58A76299AC}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE EndGlobalSection EndGlobal diff --git a/cs/clean-code.sln.DotSettings b/cs/clean-code.sln.DotSettings index 135b83ecb..229f449d2 100644 --- a/cs/clean-code.sln.DotSettings +++ b/cs/clean-code.sln.DotSettings @@ -1,6 +1,9 @@  <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb_AaBb" /> + <Policy><Descriptor Staticness="Instance" AccessRightKinds="Private" Description="Instance fields (private)"><ElementKinds><Kind Name="FIELD" /><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></Policy> + <Policy><Descriptor Staticness="Any" AccessRightKinds="Any" Description="Types and namespaces"><ElementKinds><Kind Name="NAMESPACE" /><Kind Name="CLASS" /><Kind Name="STRUCT" /><Kind Name="ENUM" /><Kind Name="DELEGATE" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb_AaBb" /></Policy> + True True True Imported 10.10.2016