diff --git a/MarkdownSpec.md b/MarkdownSpec.md index 886e99c95..a8fab56c7 100644 --- a/MarkdownSpec.md +++ b/MarkdownSpec.md @@ -31,7 +31,15 @@ __Выделенный двумя символами текст__ должен Символ экранирования тоже можно экранировать: \\_вот это будет выделено тегом_ \ +# Неупорядоченный список +Элементы неупорядоченного списка начинаются с символа * за которым следует пробел. + + + +Внутри элементов неупорядоченного списока могут присутствовать все прочие символы разметки с указанными правилами, кроме заголовка. # Взаимодействие тегов diff --git a/cs/Markdown/Converter/ConcreteConverter/HtmlConverter.cs b/cs/Markdown/Converter/ConcreteConverter/HtmlConverter.cs new file mode 100644 index 000000000..6bbc73442 --- /dev/null +++ b/cs/Markdown/Converter/ConcreteConverter/HtmlConverter.cs @@ -0,0 +1,51 @@ +using System.Text; +using Markdown.Tags.ConcreteTags; + +namespace Markdown.Converter.ConcreteConverter; + +public class HtmlConverter : IConverter +{ + public string Convert(ParsedLine[] parsedLines) + { + var sb = new StringBuilder(); + + var containList = false; + var startedLine = true; + foreach (var text in parsedLines) + { + if (!startedLine) + sb.Append('\n'); + + if (containList && text.Tags.FirstOrDefault() is not BulletTag) + { + containList = false; + sb.Append(""); + } + else if (!containList && text.Tags.FirstOrDefault() is BulletTag) + { + containList = true; + sb.Append(""); + + return sb.ToString(); + } +} \ No newline at end of file diff --git a/cs/Markdown/Converter/ConcreteConverter/MdTagToHtmlConverter.cs b/cs/Markdown/Converter/ConcreteConverter/MdTagToHtmlConverter.cs new file mode 100644 index 000000000..4e75ebaa3 --- /dev/null +++ b/cs/Markdown/Converter/ConcreteConverter/MdTagToHtmlConverter.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Markdown.Tags; +using Markdown.Tags.ConcreteTags; + +namespace Markdown.Converter.ConcreteConverter +{ + public static class MdTagToHtmlConverter + { + public static Dictionary OpenTags = new() + { + { TagType.Header, "

"}, + { TagType.Bold, ""}, + { TagType.BulletedListItem, "
  • "}, + { TagType.Italic, ""} + }; + + public static Dictionary CloseTags = new() + { + { TagType.Header, "
  • "}, + { TagType.Bold, ""}, + { TagType.BulletedListItem, ""}, + { TagType.Italic, ""} + }; + } +} diff --git a/cs/Markdown/Converter/IConverter.cs b/cs/Markdown/Converter/IConverter.cs new file mode 100644 index 000000000..6a3825d67 --- /dev/null +++ b/cs/Markdown/Converter/IConverter.cs @@ -0,0 +1,6 @@ +namespace Markdown.Converter; + +public interface IConverter +{ + public string Convert(ParsedLine[] parsedLines); +} \ No newline at end of file diff --git a/cs/Markdown/Extensions/StringExtensions.cs b/cs/Markdown/Extensions/StringExtensions.cs new file mode 100644 index 000000000..32b985447 --- /dev/null +++ b/cs/Markdown/Extensions/StringExtensions.cs @@ -0,0 +1,14 @@ +namespace Markdown.Extensions; + +public static class StringExtensions +{ + public static string[] SplitIntoLines(this string text) + { + return text.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None); + } + + public static bool NextCharIs(this string line, char ch, int currentIndex) + { + return currentIndex + 1 < line.Length && line[currentIndex + 1] == ch; + } +} \ No newline at end of file diff --git a/cs/Markdown/Extensions/TokenExtensions.cs b/cs/Markdown/Extensions/TokenExtensions.cs new file mode 100644 index 000000000..4ef6b41a4 --- /dev/null +++ b/cs/Markdown/Extensions/TokenExtensions.cs @@ -0,0 +1,29 @@ +using Markdown.Tags; +using Markdown.Tokens; + +namespace Markdown.Extensions; + +public static class TokenExtensions +{ + public static bool NextTokenIs(this List tokens, TokenType tokenType, int currentIndex) + { + return currentIndex + 1 < tokens.Count && tokens[currentIndex + 1].TokenType == tokenType; + } + + public static bool CurrentTokenIs(this List tokens, TokenType tokenType, int currentIndex) + { + return currentIndex < tokens.Count && currentIndex >= 0 && + tokens[currentIndex].TokenType == tokenType; + } + + public static bool CurrentTokenIs(this List tokens, TagType tokenType, int currentIndex) + { + return currentIndex < tokens.Count && currentIndex >= 0 && + tokens[currentIndex].TagType == tokenType; + } + + public static bool LastTokenIs(this List tokens, TokenType tokenType, int currentIndex) + { + return currentIndex - 1 >= 0 && tokens[currentIndex - 1].TokenType == tokenType; + } +} \ No newline at end of file diff --git a/cs/Markdown/Markdown.csproj b/cs/Markdown/Markdown.csproj new file mode 100644 index 000000000..fa71b7ae6 --- /dev/null +++ b/cs/Markdown/Markdown.csproj @@ -0,0 +1,9 @@ + + + + net8.0 + enable + enable + + + diff --git a/cs/Markdown/Md.cs b/cs/Markdown/Md.cs new file mode 100644 index 000000000..cc3780a63 --- /dev/null +++ b/cs/Markdown/Md.cs @@ -0,0 +1,24 @@ +using Markdown.Converter; +using Markdown.Extensions; +using Markdown.TokenParser.Interfaces; + +namespace Markdown; + +public class Md +{ + private readonly IConverter converter; + private readonly ITokenLineParser markdownTokenizer; + + public Md(ITokenLineParser tokenizer, IConverter converter) + { + markdownTokenizer = tokenizer; + this.converter = converter; + } + + public string Render(string mdString) + { + return converter.Convert(mdString.SplitIntoLines() + .Select(markdownTokenizer.ParseLine) + .ToArray()); + } +} \ No newline at end of file diff --git a/cs/Markdown/ParsedLine.cs b/cs/Markdown/ParsedLine.cs new file mode 100644 index 000000000..afba1888d --- /dev/null +++ b/cs/Markdown/ParsedLine.cs @@ -0,0 +1,21 @@ +using Markdown.Tags; + +namespace Markdown; + +public class ParsedLine +{ + public readonly string Line; + + public readonly List Tags; + + public ParsedLine(string line, List tags) + { + if (line == null || tags == null) + throw new ArgumentNullException($"Параметры Line и Tags класса ParsedLine не могут быть null"); + if (tags.Any(x => x.Position > line.Length)) + throw new ArgumentException("Позиция тега не может быть больше длины строки", nameof(tags)); + + Line = line; + Tags = tags; + } +} \ No newline at end of file diff --git a/cs/Markdown/Tags/ConcreteTags/BoldTag.cs b/cs/Markdown/Tags/ConcreteTags/BoldTag.cs new file mode 100644 index 000000000..2f33b1fce --- /dev/null +++ b/cs/Markdown/Tags/ConcreteTags/BoldTag.cs @@ -0,0 +1,16 @@ +namespace Markdown.Tags.ConcreteTags; + +public class BoldTag : ITag +{ + public BoldTag(int position, bool isCloseTag = false) + { + Position = position; + IsCloseTag = isCloseTag; + } + + public TagType TagType => TagType.Bold; + + public int Position { get; set; } + + public bool IsCloseTag { get; set; } +} \ No newline at end of file diff --git a/cs/Markdown/Tags/ConcreteTags/BulletTag.cs b/cs/Markdown/Tags/ConcreteTags/BulletTag.cs new file mode 100644 index 000000000..886b9cf8c --- /dev/null +++ b/cs/Markdown/Tags/ConcreteTags/BulletTag.cs @@ -0,0 +1,14 @@ +namespace Markdown.Tags.ConcreteTags; + +public class BulletTag : ITag +{ + public BulletTag(int position, bool isCloseTag = false) + { + Position = position; + IsCloseTag = isCloseTag; + } + + public TagType TagType => TagType.BulletedListItem; + public int Position { get; set; } + public bool IsCloseTag { get; set; } +} \ No newline at end of file diff --git a/cs/Markdown/Tags/ConcreteTags/HeaderTag.cs b/cs/Markdown/Tags/ConcreteTags/HeaderTag.cs new file mode 100644 index 000000000..aec3de68d --- /dev/null +++ b/cs/Markdown/Tags/ConcreteTags/HeaderTag.cs @@ -0,0 +1,16 @@ +namespace Markdown.Tags.ConcreteTags; + +public class HeaderTag : ITag +{ + public HeaderTag(int position, bool isCloseTag = false) + { + Position = position; + IsCloseTag = isCloseTag; + } + + public TagType TagType => TagType.Header; + + public int Position { get; set; } + + public bool IsCloseTag { get; set; } +} \ No newline at end of file diff --git a/cs/Markdown/Tags/ConcreteTags/ItalicTag.cs b/cs/Markdown/Tags/ConcreteTags/ItalicTag.cs new file mode 100644 index 000000000..43d4aab79 --- /dev/null +++ b/cs/Markdown/Tags/ConcreteTags/ItalicTag.cs @@ -0,0 +1,16 @@ +namespace Markdown.Tags.ConcreteTags; + +public class ItalicTag : ITag +{ + public ItalicTag(int position, bool isCloseTag = false) + { + Position = position; + IsCloseTag = isCloseTag; + } + + public TagType TagType => TagType.Italic; + + public int Position { get; set; } + + public bool IsCloseTag { get; set; } +} \ 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..8ac20edbd --- /dev/null +++ b/cs/Markdown/Tags/ITag.cs @@ -0,0 +1,10 @@ +namespace Markdown.Tags; + +public interface ITag +{ + public TagType TagType { get; } + + public int Position { get; protected set; } + + public bool IsCloseTag { get; protected set; } +} \ No newline at end of file diff --git a/cs/Markdown/Tags/TagType.cs b/cs/Markdown/Tags/TagType.cs new file mode 100644 index 000000000..89d31d925 --- /dev/null +++ b/cs/Markdown/Tags/TagType.cs @@ -0,0 +1,10 @@ +namespace Markdown.Tags; + +public enum TagType +{ + Header, + Italic, + Bold, + BulletedListItem, + UnDefined +} \ No newline at end of file diff --git a/cs/Markdown/TokenGeneratorClasses/Interfaces/ITokenGenerateRule.cs b/cs/Markdown/TokenGeneratorClasses/Interfaces/ITokenGenerateRule.cs new file mode 100644 index 000000000..1478b4e48 --- /dev/null +++ b/cs/Markdown/TokenGeneratorClasses/Interfaces/ITokenGenerateRule.cs @@ -0,0 +1,8 @@ +using Markdown.Tokens; + +namespace Markdown.TokenGeneratorClasses.Interfaces; + +public interface ITokenGenerateRule +{ + public Token? GetToken(string line, int currentIndex); +} \ No newline at end of file diff --git a/cs/Markdown/TokenGeneratorClasses/Interfaces/ITokenGenerator.cs b/cs/Markdown/TokenGeneratorClasses/Interfaces/ITokenGenerator.cs new file mode 100644 index 000000000..9ee6cba86 --- /dev/null +++ b/cs/Markdown/TokenGeneratorClasses/Interfaces/ITokenGenerator.cs @@ -0,0 +1,8 @@ +using Markdown.Tokens; + +namespace Markdown.TokenGeneratorClasses.Interfaces; + +public interface ITokenGenerator +{ + public static abstract Token? GetToken(string line, int currentIndex); +} \ No newline at end of file diff --git a/cs/Markdown/TokenGeneratorClasses/TokenGenerator.cs b/cs/Markdown/TokenGeneratorClasses/TokenGenerator.cs new file mode 100644 index 000000000..e6d2c9c02 --- /dev/null +++ b/cs/Markdown/TokenGeneratorClasses/TokenGenerator.cs @@ -0,0 +1,69 @@ +using System.Reflection; +using Markdown.TokenGeneratorClasses.Interfaces; +using Markdown.Tokens; + +namespace Markdown.TokenGeneratorClasses; + +public class TokenGenerator : ITokenGenerator +{ + private static readonly IEnumerable generateRuleClasses = GetRuleClasses(); + + public static Token? GetToken(string line, int currentIndex) + { + foreach (var rule in generateRuleClasses) + { + var token = rule.GetToken(line, currentIndex); + if (token != null) + return token; + } + + return null; + } + + private static IEnumerable GetRuleClasses() + { + var interfaceType = typeof(ITokenGenerateRule); + + var rulesTypes = Assembly.GetExecutingAssembly() + .GetTypes() + .Where(t => interfaceType.IsAssignableFrom(t) && t.IsClass) + .ToHashSet(); + + var simpleRules = GetRulesNotUsesOthersRules(rulesTypes).ToList(); + var complexRules = GetRulesUsesOthersRulesLogic(rulesTypes, simpleRules).ToList(); + + return simpleRules.Concat(complexRules); + } + + private static IEnumerable GetRulesNotUsesOthersRules(HashSet rulesTypes) + { + foreach (var type in rulesTypes) + { + var constructors = type.GetConstructors(); + + if (constructors.Length == 1 && constructors[0].GetParameters().Length == 0) + { + rulesTypes.Remove(type); + yield return (ITokenGenerateRule)Activator.CreateInstance(type); + } + } + } + + private static IEnumerable GetRulesUsesOthersRulesLogic(HashSet rulesTypes, + IEnumerable rulesNotUsesOthersRules) + { + var getTokenFuncs = rulesNotUsesOthersRules + .Select(rule => new Func(rule.GetToken)); + + foreach (var type in rulesTypes) + { + var constructor = type.GetConstructor(new[] { typeof(IEnumerable>) }); + + if (constructor == null) + throw new ArgumentNullException("TokenGeneratorRules should have only one constructor " + + "without arguments or with IEnumerable argument"); + rulesTypes.Remove(type); + yield return (ITokenGenerateRule)constructor.Invoke(new object[] { getTokenFuncs }); + } + } +} \ No newline at end of file diff --git a/cs/Markdown/TokenGeneratorClasses/TokenGeneratorRules/GenerateBoldTokenRule.cs b/cs/Markdown/TokenGeneratorClasses/TokenGeneratorRules/GenerateBoldTokenRule.cs new file mode 100644 index 000000000..9524002b1 --- /dev/null +++ b/cs/Markdown/TokenGeneratorClasses/TokenGeneratorRules/GenerateBoldTokenRule.cs @@ -0,0 +1,17 @@ +using Markdown.Extensions; +using Markdown.Tags; +using Markdown.TokenGeneratorClasses.Interfaces; +using Markdown.Tokens; + +namespace Markdown.TokenGeneratorClasses.TokenGeneratorRules; + +public class GenerateBoldTokenRule : ITokenGenerateRule +{ + public Token? GetToken(string line, int currentIndex) + { + if (line[currentIndex] == '_' && line.NextCharIs('_', currentIndex)) + return new Token(TokenType.MdTag, "__", currentIndex, false, TagType.Bold); + + return null; + } +} \ No newline at end of file diff --git a/cs/Markdown/TokenGeneratorClasses/TokenGeneratorRules/GenerateBulletTokenRule.cs b/cs/Markdown/TokenGeneratorClasses/TokenGeneratorRules/GenerateBulletTokenRule.cs new file mode 100644 index 000000000..e1d0bb509 --- /dev/null +++ b/cs/Markdown/TokenGeneratorClasses/TokenGeneratorRules/GenerateBulletTokenRule.cs @@ -0,0 +1,16 @@ +using Markdown.Tags; +using Markdown.TokenGeneratorClasses.Interfaces; +using Markdown.Tokens; + +namespace Markdown.TokenGeneratorClasses.TokenGeneratorRules; + +public class GenerateBulletTokenRule : ITokenGenerateRule +{ + public Token? GetToken(string line, int currentIndex) + { + if (currentIndex + 1 < line.Length && line[currentIndex] == '*' && line[currentIndex + 1] == ' ') + return new Token(TokenType.MdTag, "* ", currentIndex, false, TagType.BulletedListItem); + + return null; + } +} \ No newline at end of file diff --git a/cs/Markdown/TokenGeneratorClasses/TokenGeneratorRules/GenerateEscapeTokenRule.cs b/cs/Markdown/TokenGeneratorClasses/TokenGeneratorRules/GenerateEscapeTokenRule.cs new file mode 100644 index 000000000..393f875c6 --- /dev/null +++ b/cs/Markdown/TokenGeneratorClasses/TokenGeneratorRules/GenerateEscapeTokenRule.cs @@ -0,0 +1,15 @@ +using Markdown.TokenGeneratorClasses.Interfaces; +using Markdown.Tokens; + +namespace Markdown.TokenGeneratorClasses.TokenGeneratorRules; + +public class GenerateEscapeTokenRule : ITokenGenerateRule +{ + public Token? GetToken(string line, int currentIndex) + { + if (line[currentIndex] == '\\') + return new Token(TokenType.Escape, @"\", currentIndex); + + return null; + } +} \ No newline at end of file diff --git a/cs/Markdown/TokenGeneratorClasses/TokenGeneratorRules/GenerateHashTokenRule.cs b/cs/Markdown/TokenGeneratorClasses/TokenGeneratorRules/GenerateHashTokenRule.cs new file mode 100644 index 000000000..46b0b16d0 --- /dev/null +++ b/cs/Markdown/TokenGeneratorClasses/TokenGeneratorRules/GenerateHashTokenRule.cs @@ -0,0 +1,16 @@ +using Markdown.Tags; +using Markdown.TokenGeneratorClasses.Interfaces; +using Markdown.Tokens; + +namespace Markdown.TokenGeneratorClasses.TokenGeneratorRules; + +public class GenerateHashTokenRule : ITokenGenerateRule +{ + public Token? GetToken(string line, int currentIndex) + { + if (currentIndex + 1 < line.Length && line[currentIndex] == '#' && line[currentIndex + 1] == ' ') + return new Token(TokenType.MdTag, "# ", currentIndex, false, TagType.Header); + + return null; + } +} \ No newline at end of file diff --git a/cs/Markdown/TokenGeneratorClasses/TokenGeneratorRules/GenerateItalicTokenRule.cs b/cs/Markdown/TokenGeneratorClasses/TokenGeneratorRules/GenerateItalicTokenRule.cs new file mode 100644 index 000000000..d9eb66496 --- /dev/null +++ b/cs/Markdown/TokenGeneratorClasses/TokenGeneratorRules/GenerateItalicTokenRule.cs @@ -0,0 +1,17 @@ +using Markdown.Extensions; +using Markdown.Tags; +using Markdown.TokenGeneratorClasses.Interfaces; +using Markdown.Tokens; + +namespace Markdown.TokenGeneratorClasses.TokenGeneratorRules; + +public class GenerateItalicTokenRule : ITokenGenerateRule +{ + public Token? GetToken(string line, int currentIndex) + { + if (line[currentIndex] == '_' && !line.NextCharIs('_', currentIndex)) + return new Token(TokenType.MdTag, "_", currentIndex, false, TagType.Italic); + + return null; + } +} \ No newline at end of file diff --git a/cs/Markdown/TokenGeneratorClasses/TokenGeneratorRules/GenerateTextTokenRule.cs b/cs/Markdown/TokenGeneratorClasses/TokenGeneratorRules/GenerateTextTokenRule.cs new file mode 100644 index 000000000..99bcab547 --- /dev/null +++ b/cs/Markdown/TokenGeneratorClasses/TokenGeneratorRules/GenerateTextTokenRule.cs @@ -0,0 +1,50 @@ +using System.Text; +using Markdown.TokenGeneratorClasses.Interfaces; +using Markdown.Tokens; + +namespace Markdown.TokenGeneratorClasses.TokenGeneratorRules; + +public class GenerateTextTokenRule : ITokenGenerateRule +{ + private readonly IEnumerable> otherTokensRules; + + public GenerateTextTokenRule(IEnumerable> otherTokensRules) + { + this.otherTokensRules = otherTokensRules; + } + + public Token? GetToken(string line, int currentIndex) + { + var stringBuilder = new StringBuilder(); + var tokenType = char.IsNumber(line[currentIndex]) ? TokenType.Number : TokenType.Text; + + for (var i = currentIndex; i < line.Length; i++) + { + if (tokenType == TokenType.Text && + (char.IsNumber(line[currentIndex]) || !IsTextToken(line, currentIndex))) + break; + + if (tokenType == TokenType.Number && !char.IsNumber(line[currentIndex])) + break; + + stringBuilder.Append(line[currentIndex]); + currentIndex++; + } + + var text = stringBuilder.ToString(); + + return new Token(tokenType, text, currentIndex - text.Length); + } + + private bool IsTextToken(string line, int currentIndex) + { + foreach (var rule in otherTokensRules) + { + var token = rule.Invoke(line, currentIndex); + if (token != null) + return false; + } + + return true; + } +} \ No newline at end of file diff --git a/cs/Markdown/TokenGeneratorClasses/TokenGeneratorRules/GenerateWhiteSpaceRule.cs b/cs/Markdown/TokenGeneratorClasses/TokenGeneratorRules/GenerateWhiteSpaceRule.cs new file mode 100644 index 000000000..84892ed08 --- /dev/null +++ b/cs/Markdown/TokenGeneratorClasses/TokenGeneratorRules/GenerateWhiteSpaceRule.cs @@ -0,0 +1,15 @@ +using Markdown.TokenGeneratorClasses.Interfaces; +using Markdown.Tokens; + +namespace Markdown.TokenGeneratorClasses.TokenGeneratorRules; + +public class GenerateWhiteSpaceRule : ITokenGenerateRule +{ + public Token? GetToken(string line, int currentIndex) + { + if (line[currentIndex] == ' ') + return new Token(TokenType.WhiteSpace, " ", currentIndex); + + return null; + } +} \ No newline at end of file diff --git a/cs/Markdown/TokenParser/ConcreteParser/LineParser.cs b/cs/Markdown/TokenParser/ConcreteParser/LineParser.cs new file mode 100644 index 000000000..171fe0c0a --- /dev/null +++ b/cs/Markdown/TokenParser/ConcreteParser/LineParser.cs @@ -0,0 +1,251 @@ +using System.Text; +using Markdown.Tags; +using Markdown.Tags.ConcreteTags; +using Markdown.TokenGeneratorClasses; +using Markdown.TokenParser.Interfaces; +using Markdown.TokenParser.TokenHandlers; +using Markdown.Tokens; + +namespace Markdown.TokenParser.ConcreteParser; + +public class LineParser : ITokenLineParser +{ + private static readonly Dictionary TagPriority = new() + { + { TagType.Header, 1000 }, + { TagType.BulletedListItem, 1000 }, + { TagType.Bold, 500 }, + { TagType.Italic, 300 }, + { TagType.UnDefined, 0 } + }; + + public ParsedLine ParseLine(string line) + { + if (line is null) + throw new ArgumentNullException("String argument text must be not null"); + + var lineTokens = GetTokensLine(line); + var escapedTokens = ResetPositions(EscapeTags(lineTokens)); + var headerTags = new HeaderTokensHandler().HandleLine(escapedTokens); + var bulletedLITags = new BulletedLIHandler().HandleLine(escapedTokens); + var italicTags = new ItalicTokensHandler().HandleLine(escapedTokens); + var boldTags = new BoldTokensHandler().HandleLine(escapedTokens); + + var merged = MergeTokens(escapedTokens, headerTags, boldTags, italicTags, bulletedLITags); + ProcessTokensIntersecting(merged); + ProcessInnerTags(merged); + + return GetTagsAndCleanText(merged); + } + + private List GetTokensLine(string line) + { + var position = 0; + var result = new List(); + + while (position < line.Length) + { + var token = TokenGenerator.GetToken(line, position); + result.Add(token); + position += token.Content.Length; + } + + return result; + } + + private List EscapeTags(List tokens) + { + Token? previousToken = null; + var result = new List(); + + foreach (var token in tokens) + if (previousToken is { TokenType: TokenType.Escape }) + { + if (token.TokenType is TokenType.MdTag or TokenType.Escape) + { + token.TokenType = TokenType.Text; + token.TagType = TagType.UnDefined; + previousToken = token; + result.Add(token); + } + else + { + previousToken.TokenType = TokenType.Text; + result.Add(previousToken); + result.Add(token); + previousToken = token; + } + } + else if (token.TokenType == TokenType.Escape) + { + previousToken = token; + } + else + { + result.Add(token); + previousToken = token; + } + + if (previousToken is not { TokenType: TokenType.Escape }) + return result; + + previousToken.TokenType = TokenType.Text; + result.Add(previousToken); + return result; + } + + private List ResetPositions(List tokens) + { + var position = 0; + + foreach (var token in tokens) + { + token.Position = position; + position += token.Content.Length; + } + + return tokens; + } + + private List MergeTokens(List allTokens, params List[] tokenLists) + { + var positionMap = new Dictionary(); + + foreach (var token in allTokens) positionMap[token.Position] = token; + + foreach (var tokenList in tokenLists) + foreach (var token in tokenList) + positionMap[token.Position] = token; + + // Преобразуем словарь обратно в список + var combinedTokens = positionMap.Values.ToList(); + + var combindedTokens = combinedTokens.OrderBy(token => token.Position) + .ToList(); + + return combinedTokens; + } + + private void ProcessTokensIntersecting(List tokens) + { + var stack = new Stack(); + var process = new List(); + + foreach (var token in tokens) + if (token.TagType == TagType.Italic || token.TagType == TagType.Bold) + { + process.Add(token); + if (token.IsCloseTag) + { + stack.TryPeek(out var openToken); + if (openToken != null && !openToken.IsCloseTag + && openToken.TagType == token.TagType) + { + process.Remove(stack.Pop()); + process.Remove(token); + } + else + { + stack.Push(token); + } + } + else + { + stack.Push(token); + } + } + + foreach (var token in stack) + { + token.TagType = TagType.UnDefined; + token.TokenType = TokenType.Text; + } + } + + private void ProcessInnerTags(List tokens) + { + Token handleToken = null; + var setAllToText = false; + var startIndex = -1; + var endIndex = -1; + + for (var i = 0; i < tokens.Count; i++) + if (handleToken == null) + { + if (setAllToText) + ConvertToTextTags(startIndex, endIndex, tokens); + + handleToken = tokens[i]; + startIndex = i; + } + else if (TagPriority[tokens[i].TagType] > TagPriority[handleToken.TagType]) + { + setAllToText = true; + } + else if (tokens[i].TagType == handleToken.TagType && tokens[i].IsCloseTag) + { + endIndex = i; + } + } + + private void ConvertToTextTags(int startIndex, int endIndex, List tokens) + { + if (startIndex == -1 || endIndex == -1) + return; + + for (var i = startIndex; i <= endIndex; i++) + if (tokens[i].TokenType == TokenType.MdTag) + tokens[i].TokenType = TokenType.Text; + } + + private List ConvertToTextTags(List tokens) + { + var result = new List(); + + foreach (var token in tokens) + { + if (token.TokenType == TokenType.MdTag) token.TokenType = TokenType.Text; + + result.Add(token); + } + + return result; + } + + + private ParsedLine GetTagsAndCleanText(List tokens) + { + var result = new List(); + + var lineBuilder = new StringBuilder(); + foreach (var token in tokens) + { + if (token.TokenType is not TokenType.MdTag) + { + lineBuilder.Append(token.Content); + continue; + } + + result.Add(GetNewTag(token, lineBuilder.Length)); + } + + //Мы можем вернуть либо пустой ParsedLine если Count == 0 или "стандартно" + //заполненный, в ином случае если Length == 0 то значит внутри тегов пусто + //И их надо превратить в текст + return lineBuilder.Length > 0 || tokens.Count == 0 + ? new ParsedLine(lineBuilder.ToString(), result) + : GetTagsAndCleanText(ConvertToTextTags(tokens)); + } + + private ITag GetNewTag(Token token, int position) + { + return token.TagType switch + { + TagType.Header => new HeaderTag(position, token.IsCloseTag), + TagType.Italic => new ItalicTag(position, token.IsCloseTag), + TagType.Bold => new BoldTag(position, token.IsCloseTag), + TagType.BulletedListItem => new BulletTag(position, token.IsCloseTag), + _ => throw new NotImplementedException() + }; + } +} \ No newline at end of file diff --git a/cs/Markdown/TokenParser/Interfaces/ITokenHandler.cs b/cs/Markdown/TokenParser/Interfaces/ITokenHandler.cs new file mode 100644 index 000000000..587dcd153 --- /dev/null +++ b/cs/Markdown/TokenParser/Interfaces/ITokenHandler.cs @@ -0,0 +1,8 @@ +using Markdown.Tokens; + +namespace Markdown.TokenParser.Interfaces; + +public interface ITokenHandler +{ + List HandleLine(List line); +} \ No newline at end of file diff --git a/cs/Markdown/TokenParser/Interfaces/ITokenLineParser.cs b/cs/Markdown/TokenParser/Interfaces/ITokenLineParser.cs new file mode 100644 index 000000000..d63837cb9 --- /dev/null +++ b/cs/Markdown/TokenParser/Interfaces/ITokenLineParser.cs @@ -0,0 +1,6 @@ +namespace Markdown.TokenParser.Interfaces; + +public interface ITokenLineParser +{ + public ParsedLine ParseLine(string text); +} \ No newline at end of file diff --git a/cs/Markdown/TokenParser/TokenHandlers/BoldTokensHandler.cs b/cs/Markdown/TokenParser/TokenHandlers/BoldTokensHandler.cs new file mode 100644 index 000000000..ea1f2581a --- /dev/null +++ b/cs/Markdown/TokenParser/TokenHandlers/BoldTokensHandler.cs @@ -0,0 +1,10 @@ +using Markdown.Extensions; +using Markdown.Tags; +using Markdown.TokenParser.Interfaces; +using Markdown.Tokens; + +namespace Markdown.TokenParser.TokenHandlers; + +public class BoldTokensHandler() : UnderscoreTokensHandler(TagType.Bold, "__") +{ +} \ No newline at end of file diff --git a/cs/Markdown/TokenParser/TokenHandlers/BulletedLIHandler.cs b/cs/Markdown/TokenParser/TokenHandlers/BulletedLIHandler.cs new file mode 100644 index 000000000..be2689045 --- /dev/null +++ b/cs/Markdown/TokenParser/TokenHandlers/BulletedLIHandler.cs @@ -0,0 +1,48 @@ +using Markdown.Extensions; +using Markdown.Tags; +using Markdown.Tokens; + +namespace Markdown.TokenParser.TokenHandlers; + +public class BulletedLIHandler +{ + public List HandleLine(List line) + { + var result = new List(); + var position = 0; + var addCloseTag = false; + + for (var j = 0; j < line.Count; j++) + { + if (IsBulletedListItem(line, j) && line[j].TagType == TagType.BulletedListItem) + { + result.Add(new Token(TokenType.MdTag, "* ", + position, false, TagType.BulletedListItem)); + + addCloseTag = true; + } + else if (line[j].TagType == TagType.BulletedListItem) + { + result.Add(new Token(TokenType.Text, "* ", position)); + } + + position += line[j].Content.Length; + } + + if (addCloseTag) + result.Add(CreateCloseTag(position)); + + + return result; + } + + private bool IsBulletedListItem(List tokens, int index) + { + return index == 0 && tokens.CurrentTokenIs(TagType.BulletedListItem, index); + } + + private Token CreateCloseTag(int lastIndex) + { + return new Token(TokenType.MdTag, "", lastIndex + 1, true, TagType.BulletedListItem); + } +} \ No newline at end of file diff --git a/cs/Markdown/TokenParser/TokenHandlers/HeaderTokensHandler.cs b/cs/Markdown/TokenParser/TokenHandlers/HeaderTokensHandler.cs new file mode 100644 index 000000000..1a5a596a3 --- /dev/null +++ b/cs/Markdown/TokenParser/TokenHandlers/HeaderTokensHandler.cs @@ -0,0 +1,49 @@ +using Markdown.Extensions; +using Markdown.Tags; +using Markdown.TokenParser.Interfaces; +using Markdown.Tokens; + +namespace Markdown.TokenParser.TokenHandlers; + +public class HeaderTokensHandler : ITokenHandler +{ + public List HandleLine(List line) + { + var result = new List(); + var position = 0; + var addCloseTag = false; + + for (var j = 0; j < line.Count; j++) + { + if (IsHeader(line, j) && line[j].TagType == TagType.Header) + { + result.Add(new Token(TokenType.MdTag, "# ", + position, false, TagType.Header)); + + addCloseTag = true; + } + else if (line[j].TagType == TagType.Header) + { + result.Add(new Token(TokenType.Text, "# ", position)); + } + + position += line[j].Content.Length; + } + + if (addCloseTag) + result.Add(CreateCloseTag(position)); + + + return result; + } + + private bool IsHeader(List tokens, int index) + { + return index == 0 && tokens.CurrentTokenIs(TagType.Header, index); + } + + private Token CreateCloseTag(int lastIndex) + { + return new Token(TokenType.MdTag, "", lastIndex + 1, true, TagType.Header); + } +} \ No newline at end of file diff --git a/cs/Markdown/TokenParser/TokenHandlers/ItalicTokensHandler.cs b/cs/Markdown/TokenParser/TokenHandlers/ItalicTokensHandler.cs new file mode 100644 index 000000000..aa45d6a98 --- /dev/null +++ b/cs/Markdown/TokenParser/TokenHandlers/ItalicTokensHandler.cs @@ -0,0 +1,10 @@ +using Markdown.Extensions; +using Markdown.Tags; +using Markdown.TokenParser.Interfaces; +using Markdown.Tokens; + +namespace Markdown.TokenParser.TokenHandlers; + +public class ItalicTokensHandler() : UnderscoreTokensHandler(TagType.Italic, "_") +{ +} \ No newline at end of file diff --git a/cs/Markdown/TokenParser/TokenHandlers/UnderscoreTokensHandler.cs b/cs/Markdown/TokenParser/TokenHandlers/UnderscoreTokensHandler.cs new file mode 100644 index 000000000..0aa557a94 --- /dev/null +++ b/cs/Markdown/TokenParser/TokenHandlers/UnderscoreTokensHandler.cs @@ -0,0 +1,168 @@ +using Markdown.Extensions; +using Markdown.Tags; +using Markdown.Tokens; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Markdown.TokenParser.TokenHandlers +{ + public abstract class UnderscoreTokensHandler(TagType tagType, string TokenContent) + { + private readonly TagType TagType = tagType; + + private readonly string Content = TokenContent; + + public List HandleLine(List line) + { + var result = new List(); + var opened = new List(1); + var position = 0; + Func, int, bool> isClosed = (list, i) => false; + + for (var j = 0; j < line.Count; j++) + { + if (opened.Count == 0 && IsOpen(j, line)) + { + if (IsOpenInWord(j, line)) + { + var token = line[j]; + isClosed = (list, index) => IsCloseInWord(index, list, token); + opened.Add(line[j]); + } + else if (IsOpenBetweenWords(j, line)) + { + var token = line[j]; + isClosed = (list, i) => IsClosed(i, list, token); + opened.Add(line[j]); + } + } + else if (opened.Count > 0 && isClosed(line, j)) + { + result.Add(opened[0]); + result.Add(new Token(TokenType.MdTag, TokenContent, + position, true, TagType)); + + opened.Clear(); + } + else if (line[j].TagType == TagType) + { + result.Add(new Token(TokenType.Text, TokenContent, position)); + } + + position += line[j].Content.Length; + } + + if (opened.Count > 0) + { + opened[0].TagType = TagType.UnDefined; + opened[0].TokenType = TokenType.Text; + + result.Add(opened[0]); + } + + return result; + } + + private bool IsOpen(int index, List tokens) + { + var isFirstInLine = IsFirstInLine(index, tokens); + var isOpenOrdinary = IsOpenOrdinary(index, tokens); + var isOpenClosed = IsInWord(index, tokens); + + return isFirstInLine || isOpenOrdinary || isOpenClosed; + } + + private bool IsClosed(int index, List tokens, Token token) + { + var isLastInLine = IsLastInLine(index, tokens); + var isClosedOrdinary = IsClosedOrdinary(index, tokens); + var isOpenClosed = IsCloseInWord(index, tokens, token); + + return isLastInLine || isClosedOrdinary || isOpenClosed; + } + + private bool IsOpenBetweenWords(int index, List tokens) + { + return IsOpenOrdinary(index, tokens) ^ IsFirstInLine(index, tokens); + } + + private bool IsOpenInWord(int index, List tokens) + { + return IsInWord(index, tokens); + } + + private bool IsCloseBetweenWords(int index, List tokens) + { + return IsClosedOrdinary(index, tokens) ^ IsLastInLine(index, tokens); + } + + private bool IsCloseInWord(int index, List tokens, Token openToken) + { + return index - 2 > -1 && (IsInWord(index, tokens) || IsCloseBetweenWords(index, tokens)) + && tokens[index - 2] == openToken; + } + + #region OpenSituations + + /// + /// Определяет является ли токен одновременно и + /// открывающим и закрывающим тегом - случай + /// если тэг в слове ("пре_сп_ко_йн_ый) + /// + /// Индекс токена + /// Список токенов для проверки + /// true если тег внутри слова иначе false + private bool IsInWord(int index, List tokens) + { + return tokens.LastTokenIs(TokenType.Text, index) && + tokens.CurrentTokenIs(TagType, index) & + tokens.NextTokenIs(TokenType.Text, index); + } + + private bool IsFirstInLine(int index, List tokens) + { + return index == 0 && tokens.CurrentTokenIs(TagType, index) && + (tokens.NextTokenIs(TokenType.Text, index) || + tokens.NextTokenIs(TokenType.MdTag, index)); + } + + private bool IsOpenOrdinary(int index, List tokens) + { + var hasWhSpaceBefore = tokens.LastTokenIs(TokenType.WhiteSpace, index); + var hasMdTagBefore = tokens.LastTokenIs(TokenType.MdTag, index); + + return (hasWhSpaceBefore || hasMdTagBefore) && + tokens.CurrentTokenIs(TagType, index) && + tokens.NextTokenIs(TokenType.Text, index); + } + + #endregion + + + #region CloseSituations + + private bool IsLastInLine(int index, List tokens) + { + var isLast = index == tokens.Count() - 1; + + return isLast && tokens.CurrentTokenIs(TagType, index) && + (tokens.LastTokenIs(TokenType.Text, index) || + tokens.LastTokenIs(TokenType.MdTag, index)); + } + + private bool IsClosedOrdinary(int index, List tokens) + { + var hasTextAfter = tokens.NextTokenIs(TokenType.WhiteSpace, index); + var hasMdTagAfter = tokens.NextTokenIs(TokenType.MdTag, index); + + return tokens.LastTokenIs(TokenType.Text, index) && + tokens.CurrentTokenIs(TagType, index) && + (hasMdTagAfter || hasTextAfter); + } + + #endregion + } +} diff --git a/cs/Markdown/Tokens/Token.cs b/cs/Markdown/Tokens/Token.cs new file mode 100644 index 000000000..3c2e07cd4 --- /dev/null +++ b/cs/Markdown/Tokens/Token.cs @@ -0,0 +1,63 @@ +using Markdown.Tags; + +namespace Markdown.Tokens; + +public class Token +{ + public Token(TokenType tokenType, string content, int position, bool isCloseTag = false, + TagType tagType = TagType.UnDefined) + { + TokenType = tokenType; + Content = content; + Position = position; + IsCloseTag = isCloseTag; + TagType = tagType; + } + + public TokenType TokenType { get; set; } + public string Content { get; set; } + public TagType TagType { get; set; } + public bool IsCloseTag { get; set; } + public int Position { get; set; } + + public override bool Equals(object obj) + { + if (obj is null) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != GetType()) return false; + return Equals((Token)obj); + } + + public bool Equals(Token other) + { + if (other is null) return false; + return TokenType == other.TokenType && + Content == other.Content && + TagType == other.TagType && + IsCloseTag == other.IsCloseTag && + Position == other.Position; + } + + public override int GetHashCode() + { + unchecked // Overflow is fine, just wrap + { + var hashCode = (int)TokenType; + hashCode = (hashCode * 397) ^ (Content != null ? Content.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (int)TagType; + hashCode = (hashCode * 397) ^ IsCloseTag.GetHashCode(); + hashCode = (hashCode * 397) ^ Position; + return hashCode; + } + } + + public static bool operator ==(Token left, Token right) + { + return EqualityComparer.Default.Equals(left, right); + } + + public static bool operator !=(Token left, Token right) + { + return !(left == right); + } +} \ No newline at end of file diff --git a/cs/Markdown/Tokens/TokenType.cs b/cs/Markdown/Tokens/TokenType.cs new file mode 100644 index 000000000..fe36d922b --- /dev/null +++ b/cs/Markdown/Tokens/TokenType.cs @@ -0,0 +1,10 @@ +namespace Markdown.Tokens; + +public enum TokenType +{ + MdTag, + Text, + Number, + Escape, + WhiteSpace +} \ No newline at end of file diff --git a/cs/Markdown_Tests/LineParser_Tests.cs b/cs/Markdown_Tests/LineParser_Tests.cs new file mode 100644 index 000000000..f9a7c75c5 --- /dev/null +++ b/cs/Markdown_Tests/LineParser_Tests.cs @@ -0,0 +1,81 @@ +using FluentAssertions; +using Markdown.Tags; +using Markdown.TokenParser.ConcreteParser; +using Markdown.TokenParser.Interfaces; +using MarkdownTests.TestData; + +namespace MarkdownTests +{ + public class LineParserTests + { + private ITokenLineParser parser = new LineParser(); + + [Test] + public void ParseLine_ThrowArgumentNullException_WhenArgumentIsNull() + { + Assert.Throws(() => parser.ParseLine(null), "String argument text must be not null"); + } + + [Test] + public void ParseLine_ShouldBeEmpty_WhenArgumentStringIsEmpty() + { + var parsedLine = parser.ParseLine(String.Empty); + parsedLine.Line.Should().BeEmpty(); + parsedLine.Tags.Should().BeEmpty(); + } + + [TestCaseSource(typeof(LineParserData), nameof(LineParserData.WordsOnlyLines))] + public void ParseLine_ShoudBeCorrect_WhenLineWithWordsOnly(string inLine, string expectedLine, List tags) + { + var parsedLines = parser.ParseLine(inLine); + + parsedLines.Line.Should().BeEquivalentTo(expectedLine); + parsedLines.Tags.Should().BeEquivalentTo(tags); + } + + [TestCaseSource(typeof(LineParserData), nameof(LineParserData.LineWithHeader))] + public void ParseLine_ShoudBeCorrect_WhenLineWithHeaderTags(string inLine, string expectedLine, List tags) + { + var parsedLines = parser.ParseLine(inLine); + + parsedLines.Line.Should().BeEquivalentTo(expectedLine); + parsedLines.Tags.Should().BeEquivalentTo(tags); + } + + [TestCaseSource(typeof(LineParserData), nameof(LineParserData.LinesWithBulletedList))] + public void ParseLine_ShoudBeCorrect_WhenLinesWithBulletedList(string inLine, string expectedLine, List tags) + { + var parsedLines = parser.ParseLine(inLine); + + parsedLines.Line.Should().BeEquivalentTo(expectedLine); + parsedLines.Tags.Should().BeEquivalentTo(tags); + } + + [TestCaseSource(typeof(LineParserData), nameof(LineParserData.LineWithItalic))] + public void ParseLine_ShoudBeCorrect_WhenLineWithItalicTags(string inLine, string expectedLine, List tags) + { + var parsedLines = parser.ParseLine(inLine); + + parsedLines.Line.Should().BeEquivalentTo(expectedLine); + parsedLines.Tags.Should().BeEquivalentTo(tags); + } + + [TestCaseSource(typeof(LineParserData), nameof(LineParserData.LineWithBold))] + public void ParseLine_ShoudBeCorrect_WhenLineWithBoldTags(string inLine, string expectedLine, List tags) + { + var parsedLines = parser.ParseLine(inLine); + + parsedLines.Line.Should().BeEquivalentTo(expectedLine); + parsedLines.Tags.Should().BeEquivalentTo(tags); + } + + [TestCaseSource(typeof(LineParserData), nameof(LineParserData.MultiTagsLine))] + public void ParseLine_ShoudBeCorrect_WhenLineWithMultiTags(string inLine, string expectedLine, List tags) + { + var parsedLines = parser.ParseLine(inLine); + + parsedLines.Line.Should().BeEquivalentTo(expectedLine); + parsedLines.Tags.Should().BeEquivalentTo(tags); + } + } +} diff --git a/cs/Markdown_Tests/MarkdownTests.csproj b/cs/Markdown_Tests/MarkdownTests.csproj new file mode 100644 index 000000000..25f0ec504 --- /dev/null +++ b/cs/Markdown_Tests/MarkdownTests.csproj @@ -0,0 +1,29 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + + + + + + + + + + + + + diff --git a/cs/Markdown_Tests/Md_Tests.cs b/cs/Markdown_Tests/Md_Tests.cs new file mode 100644 index 000000000..dd18aad65 --- /dev/null +++ b/cs/Markdown_Tests/Md_Tests.cs @@ -0,0 +1,221 @@ +using FluentAssertions; +using Markdown; +using Markdown.Converter.ConcreteConverter; +using Markdown.TokenParser.ConcreteParser; + + +namespace MarkdownTests +{ + public class MdTests + { + private Md markdown = new Md(new LineParser(), new HtmlConverter()); + + #region BulletedTests + + [TestCase("* \n* ", "
    • \n
    ")] + [TestCase("* ", "
    ")] + public void Md_ShouldCreateBulletedListCorrectly_WhenBulletedItemNotEscaped(string input, string expected) + { + markdown.Render(input).Should().BeEquivalentTo(expected); + } + + [TestCase(@"\* ", "* ")] + [TestCase("* \n\\* ", "
    • \n
    * ")] + public void Md_ShouldCreateBulletedListCorrectly_WhenBulletedItemEscaped(string input, string expected) + { + markdown.Render(input).Should().BeEquivalentTo(expected); + } + + [TestCase(" * \n * ", " * \n * ")] + [TestCase("d* \nd* ", "d* \nd* ")] + [TestCase("* \nd* ", "
    • \n
    d* ")] + public void Md_ShouldNotCreateBulletedTag_WhenAreCharsBeforeTag(string input, string expected) + { + markdown.Render(input).Should().BeEquivalentTo(expected); + } + + #endregion + + #region HeaderTests + + [TestCase(@"# bibo", "

    bibo

    ")] + [TestCase(@"# # bibo", "

    # bibo

    ")] + public void Md_ShouldCreateHeaderCorrectly_WhenHeaderNotEscaped(string input, string expected) + { + markdown.Render(input).Should().BeEquivalentTo(expected); + } + + [TestCase(@"\# bibo", @"# bibo")] + [TestCase(@"\# # bibo", @"# # bibo")] + public void Md_ShouldNotCreateHeaderTag_WhenHeaderEscaped(string input, string expected) + { + markdown.Render(input).Should().BeEquivalentTo(expected); + } + + [TestCase(@"\\# bibo", @"\# bibo")] + [TestCase(@"a # # bibo", @"a # # bibo")] + [TestCase(@" # bibo", " # bibo")] + public void Md_ShouldNotCreateHeaderTag_WhenAreCharsBeforeHash(string input, string expected) + { + markdown.Render(input).Should().BeEquivalentTo(expected); + } + + #endregion + + #region ItalicTests + + [TestCase(@"_bibo love bubu_", @"bibo love bubu")] + [TestCase(@"bibo _love_ bubu", @"bibo love bubu")] + public void Md_ShouldCreateItalicTag_WhenTagAfterWhiteSpace(string input, string expected) + { + markdown.Render(input).Should().BeEquivalentTo(expected); + } + + [TestCase(@"_ bibo love bubu_", @"_ bibo love bubu_")] + [TestCase(@"bibo _love _ bubu", @"bibo _love _ bubu")] + [TestCase(@"bibo _ love _ bubu", @"bibo _ love _ bubu")] + public void Md_ShouldNotCreateItalicTag_WhenWhiteSpaceBeforeOrAfterText(string input, string expected) + { + markdown.Render(input).Should().BeEquivalentTo(expected); + } + + [TestCase(@"bibo _lo_ve bubu", @"bibo love bubu")] + [TestCase(@"bibo l_ove_ bubu", @"bibo love bubu")] + public void Md_ShouldCreateItalicTag_WhenTagInsideTextWithoutDigits(string input, string expected) + { + markdown.Render(input).Should().BeEquivalentTo(expected); + } + + + [TestCase(@"bibo _love bu_bu", @"bibo _love bu_bu")] + [TestCase(@"bibo l_ove bu_bu", @"bibo l_ove bu_bu")] + [TestCase(@"bibo l_ove bubu_", @"bibo l_ove bubu_")] + public void Md_ShouldNotCreateItalicTag_WhenTagInsideTextInDifferentWords(string input, string expected) + { + markdown.Render(input).Should().BeEquivalentTo(expected); + } + + [TestCase(@"bibo \_love_ bubu", @"bibo _love_ bubu")] + [TestCase(@"bibo _love\_ bubu", @"bibo _love_ bubu")] + [TestCase(@"bibo \_love\_ bubu", @"bibo _love_ bubu")] + public void Md_ShouldNotCreateItalicTag_WhenTagEscaped(string input, string expected) + { + markdown.Render(input).Should().BeEquivalentTo(expected); + } + + [Test] + public void Md_ShoudNotCreateItalicAndBoldTags_WhenBoldTagsInsideItalic() + { + var input = "I _want to __sleep__ tonight_"; + var expected = "I want to sleep tonight"; + + markdown.Render(input).Should().BeEquivalentTo(expected); + } + + [TestCase(@" c _12_3 ", @" c _12_3 ")] + [TestCase(@"bibo love_4_ bubu", @"bibo love_4_ bubu")] + public void Md_ShouldNotCreateItalicTag_WhenTextInsideHaveDigits(string input, string expected) + { + markdown.Render(input).Should().BeEquivalentTo(expected); + } + + #endregion + + #region BoldTests + + [TestCase(@"__bibo love bubu__", @"bibo love bubu")] + [TestCase(@"bibo __love__ bubu", @"bibo love bubu")] + public void Md_ShouldCreateBoldTag_WhenTagAfterSpace(string input, string expected) + { + markdown.Render(input).Should().BeEquivalentTo(expected); + } + + [TestCase(@"__ bibo love bubu__", @"__ bibo love bubu__")] + [TestCase(@"bibo __love __ bubu", @"bibo __love __ bubu")] + [TestCase(@"bibo __ love __ bubu", @"bibo __ love __ bubu")] + public void Md_ShouldNotCreateBoldTag_WhenWhiteSpaceBeforeOrAfterText(string input, string expected) + { + markdown.Render(input).Should().BeEquivalentTo(expected); + } + + [TestCase(@"bibo \__love__ bubu", @"bibo __love__ bubu")] + [TestCase(@"bibo __love\__ bubu", @"bibo __love__ bubu")] + [TestCase(@"bibo \__love\__ bubu", @"bibo __love__ bubu")] + public void Md_ShouldNotCreateBoldTag_WhenTagEscaped(string input, string expected) + { + markdown.Render(input).Should().BeEquivalentTo(expected); + } + + [TestCase(@"bibo __lo__ve bubu", @"bibo love bubu")] + [TestCase(@"bibo l__ove__ bubu", @"bibo love bubu")] + public void Md_ShouldCreateBoldTag_WhenTagInsideTextWithoutDigits(string input, string expected) + { + markdown.Render(input).Should().BeEquivalentTo(expected); + } + + [TestCase(@"bibo __love bu__bu", @"bibo __love bu__bu")] + [TestCase(@"bibo l__ove bu__bu", @"bibo l__ove bu__bu")] + public void Md_ShouldNotCreateBoldTag_WhenTagInsideTextInDifferentWords(string input, string expected) + { + markdown.Render(input).Should().BeEquivalentTo(expected); + } + + [Test] + public void Md_ShoudCreateItalicAndBoldTags_WhenItalicTagsInsideBold() + { + var input = "I __want to _sleep_ tonight__"; + var expected = "I want to sleep tonight"; + + markdown.Render(input).Should().BeEquivalentTo(expected); + } + + [TestCase(@" c __12__3 ", @" c __12__3 ")] + [TestCase(@"bibo love__4__ bubu", @"bibo love__4__ bubu")] + public void Md_ShouldNotCreateBoldTag_WhenTextInsideHaveDigits(string input, string expected) + { + markdown.Render(input).Should().BeEquivalentTo(expected); + } + + #endregion + + #region IntersectionTest + + [TestCase(@"__bibo _love__ bubu_", @"__bibo _love__ bubu_")] + [TestCase(@"_bibo __love_ bubu__", @"_bibo __love_ bubu__")] + public void Md_ShouldProccessIntersectsCorrecttly(string input, string expected) + { + markdown.Render(input).Should().BeEquivalentTo(expected); + } + + #endregion + + [TestCase(@"____", @"____")] + [TestCase(@"__", @"__")] + [TestCase(@"# ", @"# ")] + [TestCase(@"* ", @"* ")] + public void Md_ShouldNotCreateHtmlTags_WhenMdTagsContainsNothing(string input, string expected) + { + markdown.Render(input).Should().BeEquivalentTo(expected); + } + + //[Test] + public void Md_ShouldCreateEmptyHtml_WhenTextIsStringEmpty() + { + markdown.Render(String.Empty).Should().BeEquivalentTo(String.Empty); + } + + public static IEnumerable MultiLinesTestCases() + { + yield return new TestCaseData("* _ _\n* __ __", + "
    • \n
    "); + yield return new TestCaseData("# \n* _ _\n* __ __", + "

    \n
    • \n
    "); + } + + [TestCaseSource(nameof(MultiLinesTestCases))] + public void Md_ShouldRenderCorrectly_WhenTextWithMultiTags(string input, string expected) + { + markdown.Render(input).Should().BeEquivalentTo(expected); + } + } +} \ No newline at end of file diff --git a/cs/Markdown_Tests/TestData/LineParserData.cs b/cs/Markdown_Tests/TestData/LineParserData.cs new file mode 100644 index 000000000..084d3c09a --- /dev/null +++ b/cs/Markdown_Tests/TestData/LineParserData.cs @@ -0,0 +1,179 @@ +using Markdown.Tags; +using Markdown.Tags.ConcreteTags; + +namespace MarkdownTests.TestData +{ + public static class LineParserData + { + public static IEnumerable WordsOnlyLines() + { + yield return new TestCaseData("word bubo bibo", "word bubo bibo", new List()); + yield return new TestCaseData("Why1 word 23 bubo bibo", "Why1 word 23 bubo bibo", new List()); + } + + public static IEnumerable LineWithHeader() + { + yield return new TestCaseData("# word bubo bibo", "word bubo bibo", new List() + { + new HeaderTag(0, false), + new HeaderTag(14, true), + }); + yield return new TestCaseData("# Why1 word 23 bubo bibo", "Why1 word 23 bubo bibo", new List() + { + new HeaderTag(0, false), + new HeaderTag(22, true), + }); + } + + public static IEnumerable LineWithItalic() + { + yield return new TestCaseData("_word bubo_ bibo", "word bubo bibo", new List() + { + new ItalicTag(0, false), + new ItalicTag(9, true), + }); + yield return new TestCaseData("_word bu_bo bibo", "_word bu_bo bibo", new List() + { + }); + yield return new TestCaseData("wo_rd bubo_ bibo", "wo_rd bubo_ bibo", new List() + { + }); + yield return new TestCaseData("wo_rd bu_bo bibo", "wo_rd bu_bo bibo", new List() + { + }); + yield return new TestCaseData("_wo_rd bubo bibo", "word bubo bibo", new List() + { + new ItalicTag(0, false), + new ItalicTag(2, true), + }); + yield return new TestCaseData("_word __bubo__ bibo_", "word bubo bibo", new List() + { + new ItalicTag(0, false), + new BoldTag(5, false), + new BoldTag(9, true), + new ItalicTag(14, true), + }); + yield return new TestCaseData("l_ov_e", "love", new List() + { + new ItalicTag(1, false), + new ItalicTag(3, true), + }); + yield return new TestCaseData("l_ove_", "love", new List() + { + new ItalicTag(1, false), + new ItalicTag(4, true), + }); + yield return new TestCaseData("_word __bubo_ bibo__", "_word __bubo_ bibo__", new List()); + yield return new TestCaseData(@"\_word bubo_ bibo", "_word bubo_ bibo", new List()); + yield return new TestCaseData(@"_word bubo\_ bibo", "_word bubo_ bibo", new List()); + yield return new TestCaseData(@"\_word bubo\_ bibo", "_word bubo_ bibo", new List()); + yield return new TestCaseData("Why_1_ word 23 bubo bibo", "Why_1_ word 23 bubo bibo", new List()); + } + + public static IEnumerable LinesWithBulletedList() + { + yield return new TestCaseData("* один", "один", new List() + { + new BulletTag(0, false), + new BulletTag(4, true), + }); + yield return new TestCaseData("* _один_ __два__", "один два", new List() + { + new BulletTag(0, false), + new ItalicTag(0, false), + new ItalicTag(4, true), + new BoldTag(5, false), + new BoldTag(8, true), + new BulletTag(8, true), + }); + yield return new TestCaseData(@"\* один", "* один", new List()); + yield return new TestCaseData(" * один", " * один", new List()); + yield return new TestCaseData("горит * волбу", "горит * волбу", new List()); + } + + public static IEnumerable LineWithBold() + { + yield return new TestCaseData("__word bubo__ bibo", "word bubo bibo", new List() + { + new BoldTag(0, false), + new BoldTag(9, true), + }); + yield return new TestCaseData("__word bu__bo bibo", "__word bu__bo bibo", new List() + { + }); + yield return new TestCaseData("wo__rd bubo__ bibo", "wo__rd bubo__ bibo", new List() + { + }); + yield return new TestCaseData("wo__rd bu__bo bibo", "wo__rd bu__bo bibo", new List() + { + }); + yield return new TestCaseData("__wo__rd bubo bibo", "word bubo bibo", new List() + { + new BoldTag(0, false), + new BoldTag(2, true), + }); + yield return new TestCaseData("__word _bubo_ bibo__", "word bubo bibo", new List() + { + new BoldTag(0, false), + new ItalicTag(5, false), + new ItalicTag(9, true), + new BoldTag(14, true) + }); + yield return new TestCaseData("l__ov__e", "love", new List() + { + new BoldTag(1, false), + new BoldTag(3, true), + }); + yield return new TestCaseData("l__ove__", "love", new List() + { + new BoldTag(1, false), + new BoldTag(4, true), + }); + yield return new TestCaseData("_word __bubo_ bibo__", "_word __bubo_ bibo__", new List()); + yield return new TestCaseData(@"\__word bubo__ bibo", "__word bubo__ bibo", new List()); + yield return new TestCaseData(@"__word bubo\__ bibo", "__word bubo__ bibo", new List()); + yield return new TestCaseData(@"\__word bubo\__ bibo", @"__word bubo__ bibo", new List()); + yield return new TestCaseData("Why__1__ word 23 bubo bibo", "Why__1__ word 23 bubo bibo", new List()); + } + + public static IEnumerable MultiTagsLine() + { + //Это можно отнести к пересечению тегов + yield return new TestCaseData("__word _bubo___", "__word _bubo___", new List() + { + }); + //Это тоже по сути пересечение тегов + yield return new TestCaseData("___word bubo___", "___word bubo___", new List() + { + }); + yield return new TestCaseData("__word _bubo_ love__", "word bubo love", new List() + { + new BoldTag(0, false), + new ItalicTag(5, false), + new ItalicTag(9, true), + new BoldTag(14, true), + }); + yield return new TestCaseData("___word_ bubo__", "word bubo", new List() + { + new BoldTag(0, false), + new ItalicTag(0, false), + new ItalicTag(4, true), + new BoldTag(9, true), + }); + yield return new TestCaseData("__word _bu_ bo__", "word bu bo", new List() + { + new BoldTag(0, false), + new ItalicTag(5, false), + new ItalicTag(7, true), + new BoldTag(10, true), + }); + yield return new TestCaseData("_word __bu__ bo_", "word bu bo", new List() + { + new ItalicTag(0, false), + new BoldTag(5, false), + new BoldTag(7, true), + new ItalicTag(10, true), + }); + } + } +} diff --git a/cs/Markdown_Tests/TestData/TokenGeneratorTestsData.cs b/cs/Markdown_Tests/TestData/TokenGeneratorTestsData.cs new file mode 100644 index 000000000..d4e88a806 --- /dev/null +++ b/cs/Markdown_Tests/TestData/TokenGeneratorTestsData.cs @@ -0,0 +1,189 @@ +using Markdown.Tags; +using Markdown.Tokens; + +namespace MarkdownTests.TestData; + +public static class TokenGeneratorTestsData +{ + public static IEnumerable TextOnlyLines() + { + yield return new TestCaseData("вася", new List + { + new(TokenType.Text, "вася", 0) + }); + + yield return new TestCaseData("петя1223d", new List + { + new(TokenType.Text, "петя", 0), + new(TokenType.Number, "1223", 4), + new(TokenType.Text, "d",8) + }); + + yield return new TestCaseData("1234566", new List + { + new(TokenType.Number, "1234566", 0) + }); + } + + public static IEnumerable WhiteSpacesOnlyLines() + { + yield return new TestCaseData(" ", new List + { + new(TokenType.WhiteSpace, " ", 0) + }); + + yield return new TestCaseData(" ", new List + { + new(TokenType.WhiteSpace, " ", 0), + new(TokenType.WhiteSpace, " ", 1) + }); + + yield return new TestCaseData(" ", new List + { + new(TokenType.WhiteSpace, " ", 0), + new(TokenType.WhiteSpace, " ", 1), + new(TokenType.WhiteSpace, " ", 2), + new(TokenType.WhiteSpace, " ", 3), + new(TokenType.WhiteSpace, " ", 4), + new(TokenType.WhiteSpace, " ", 5) + }); + } + + public static IEnumerable LinesWithHeader() + { + yield return new TestCaseData("# Заголовок", new List + { + new(TokenType.MdTag, "# ", 0, false,TagType.Header), + new(TokenType.Text, "Заголовок", 2) + }); + + yield return new TestCaseData("# # # ", new List + { + new(TokenType.MdTag, "# ", 0, false, TagType.Header), + new(TokenType.MdTag, "# ", 2, false, TagType.Header), + new(TokenType.MdTag, "# ", 4, false, TagType.Header) + }); + + yield return new TestCaseData(@" # # ", new List + { + new(TokenType.WhiteSpace, " ", 0), + new(TokenType.MdTag, "# ", 1, false, TagType.Header), + new(TokenType.MdTag, "# ", 3, false, TagType.Header) + }); + } + + public static IEnumerable LinesWithBulletedList() + { + yield return new TestCaseData("* Заголовок", new List + { + new(TokenType.MdTag, "* ", 0, false, TagType.BulletedListItem), + new(TokenType.Text, "Заголовок", 2) + }); + + yield return new TestCaseData("* * * ", new List + { + new(TokenType.MdTag, "* ", 0, false, TagType.BulletedListItem), + new(TokenType.MdTag, "* ", 2, false, TagType.BulletedListItem), + new(TokenType.MdTag, "* ", 4, false, TagType.BulletedListItem) + }); + + yield return new TestCaseData(@" * * ", new List + { + new(TokenType.WhiteSpace, " ", 0), + new(TokenType.MdTag, "* ", 1, false, TagType.BulletedListItem), + new(TokenType.MdTag, "* ", 3, false, TagType.BulletedListItem) + }); + + yield return new TestCaseData(@"* раз * два", new List + { + new(TokenType.MdTag, "* ", 0, false, TagType.BulletedListItem), + new(TokenType.Text, "раз", 2), + new(TokenType.WhiteSpace, " ", 5), + new(TokenType.MdTag, "* ", 6, false, TagType.BulletedListItem), + new(TokenType.Text, "два", 8) + }); + } + + public static IEnumerable LinesWithEscapes() + { + yield return new TestCaseData(@"\", new List + { + new(TokenType.Escape, @"\", 0) + }); + + yield return new TestCaseData(@"\\", new List + { + new(TokenType.Escape, @"\", 0), + new(TokenType.Escape, @"\", 1) + }); + + yield return new TestCaseData(@"\\\", new List + { + new(TokenType.Escape, @"\", 0), + new(TokenType.Escape, @"\", 1), + new(TokenType.Escape, @"\", 2), + }); + } + + public static IEnumerable LinesWithUnderscores() + { + yield return new TestCaseData("_", new List + { + new(TokenType.MdTag, "_", 0, false, TagType.Italic) + }); + + yield return new TestCaseData("__", new List + { + new(TokenType.MdTag, "__", 0, false, TagType.Bold) + }); + + yield return new TestCaseData("___", new List + { + new(TokenType.MdTag, "__", 0, false, TagType.Bold), + new(TokenType.MdTag, "_", 2, false, TagType.Italic) + }); + + yield return new TestCaseData("____", new List + { + new(TokenType.MdTag, "__", 0, false, TagType.Bold), + new(TokenType.MdTag, "__", 2, false, TagType.Bold) + }); + } + + public static IEnumerable LineWithMultiTokens() + { + yield return new TestCaseData("Bibo 234 _ # ", new List + { + new(TokenType.Text, "Bibo", 0), + new(TokenType.WhiteSpace, " ", 4), + new(TokenType.Number, "234", 5), + new(TokenType.WhiteSpace, " ", 8), + new(TokenType.MdTag, "_", 9, false, TagType.Italic), + new(TokenType.WhiteSpace, " ", 10), + new(TokenType.MdTag, "# ", 11, false, TagType.Header) + }); + + yield return new TestCaseData("__# _", new List + { + new(TokenType.MdTag, "__", 0, false, TagType.Bold), + new(TokenType.MdTag, "# ", 2, false, TagType.Header), + new(TokenType.MdTag, "_", 4, false, TagType.Italic) + }); + + yield return new TestCaseData("_2_3_", new List + { + new(TokenType.MdTag, "_", 0, false, TagType.Italic), + new(TokenType.Number, "2", 1), + new(TokenType.MdTag, "_", 2, false, TagType.Italic), + new(TokenType.Number, "3",3), + new(TokenType.MdTag, "_", 4, false, TagType.Italic) + }); + + yield return new TestCaseData(@"\# word", new List + { + new(TokenType.Escape, @"\", 0), + new(TokenType.MdTag, "# ", 1, false, TagType.Header), + new(TokenType.Text, "word", 3) + }); + } +} \ No newline at end of file diff --git a/cs/Markdown_Tests/TokenGenerator_Tests.cs b/cs/Markdown_Tests/TokenGenerator_Tests.cs new file mode 100644 index 000000000..9f7de4af0 --- /dev/null +++ b/cs/Markdown_Tests/TokenGenerator_Tests.cs @@ -0,0 +1,96 @@ +using FluentAssertions; +using Markdown.Tags; +using Markdown.TokenGeneratorClasses; +using Markdown.Tokens; +using MarkdownTests.TestData; + +namespace MarkdownTests; + +public class TokenGenerator_Tests +{ + [TestCaseSource(typeof(TokenGeneratorTestsData), nameof(TokenGeneratorTestsData.TextOnlyLines))] + public void TokenGenerator_GetTokenCorrectly_WhenTextOnly(string input, List expectedTokens) + { + var actuallyTokens = GetAllTokensFromLine(input); + + actuallyTokens.Should().BeEquivalentTo(expectedTokens); + } + + [TestCaseSource(typeof(TokenGeneratorTestsData), nameof(TokenGeneratorTestsData.WhiteSpacesOnlyLines))] + public void TokenGenerator_GetTokensBySymbolCorrectly_WhenWhiteSpacesOnly(string input, List expectedTokens) + { + var actuallyTokens = GetAllTokensFromLine(input); + + actuallyTokens.Should().BeEquivalentTo(expectedTokens); + } + + [Test] + public void TokenGenerator_GetTokensBySymbolCorrectly_WhenHeaderTokenOnly() + { + var actuallyTokens = GetAllTokensFromLine("# "); + + actuallyTokens.Should().BeEquivalentTo(new List + { + new(TokenType.MdTag, "# ", 0, false, TagType.Header) + }); + } + + [TestCaseSource(typeof(TokenGeneratorTestsData), nameof(TokenGeneratorTestsData.LinesWithHeader))] + public void TokenGenerator_GetTokensBySymbolCorrectly_WhenLineWithHeaders(string input, List expectedTokens) + { + var actuallyTokens = GetAllTokensFromLine(input); + + actuallyTokens.Should().BeEquivalentTo(expectedTokens); + } + + [TestCaseSource(typeof(TokenGeneratorTestsData), nameof(TokenGeneratorTestsData.LinesWithBulletedList))] + public void TokenGenerator_GetTokensBySymbolCorrectly_WhenLineWithBulletedLis(string input, + List expectedTokens) + { + var actuallyTokens = GetAllTokensFromLine(input); + + actuallyTokens.Should().BeEquivalentTo(expectedTokens); + } + + [TestCaseSource(typeof(TokenGeneratorTestsData), nameof(TokenGeneratorTestsData.LinesWithEscapes))] + public void TokenGenerator_GetTokensBySymbolCorrectly_WhenLineWithEscapes(string input, List expectedTokens) + { + var actuallyTokens = GetAllTokensFromLine(input); + + actuallyTokens.Should().BeEquivalentTo(expectedTokens); + } + + [TestCaseSource(typeof(TokenGeneratorTestsData), nameof(TokenGeneratorTestsData.LinesWithUnderscores))] + public void TokenGenerator_GetTokensBySymbolCorrectly_WhenLineWithUnderscores(string input, + List expectedTokens) + { + var actuallyTokens = GetAllTokensFromLine(input); + + actuallyTokens.Should().BeEquivalentTo(expectedTokens); + } + + [TestCaseSource(typeof(TokenGeneratorTestsData), nameof(TokenGeneratorTestsData.LineWithMultiTokens))] + public void TokenGenerator_GetTokensBySymbolCorrectly_WhenLineWithMultiTokens(string input, + List expectedTokens) + { + var actuallyTokens = GetAllTokensFromLine(input); + + actuallyTokens.Should().BeEquivalentTo(expectedTokens); + } + + + private static List GetAllTokensFromLine(string line) + { + var i = 0; + var result = new List(); + + while (i < line.Length) + { + var token = TokenGenerator.GetToken(line, i); + result.Add(token); + i += token.Content.Length; + } + + return result; + } +} \ No newline at end of file diff --git a/cs/clean-code.sln b/cs/clean-code.sln index 2206d54db..0cb047aea 100644 --- a/cs/clean-code.sln +++ b/cs/clean-code.sln @@ -1,13 +1,17 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 14 -VisualStudioVersion = 14.0.25420.1 +# Visual Studio Version 17 +VisualStudioVersion = 17.11.35327.3 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Chess", "Chess\Chess.csproj", "{DBFBE40E-EE0C-48F4-8763-EBD11C960081}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Chess", "Chess\Chess.csproj", "{DBFBE40E-EE0C-48F4-8763-EBD11C960081}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ControlDigit", "ControlDigit\ControlDigit.csproj", "{B06A4B35-9D61-4A63-9167-0673F20CA989}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ControlDigit", "ControlDigit\ControlDigit.csproj", "{B06A4B35-9D61-4A63-9167-0673F20CA989}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Samples", "Samples\Samples.csproj", "{C3EF41D7-50EF-4CE1-B30A-D1D81C93D7FA}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Samples", "Samples\Samples.csproj", "{C3EF41D7-50EF-4CE1-B30A-D1D81C93D7FA}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Markdown", "Markdown\Markdown.csproj", "{3C26D892-E2EE-47D5-811C-1915F72BEA50}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MarkdownTests", "Markdown_Tests\MarkdownTests.csproj", "{7D3EFA76-EEF6-49DF-BF0D-6688D14E1341}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -27,5 +31,19 @@ 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 + {3C26D892-E2EE-47D5-811C-1915F72BEA50}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3C26D892-E2EE-47D5-811C-1915F72BEA50}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3C26D892-E2EE-47D5-811C-1915F72BEA50}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3C26D892-E2EE-47D5-811C-1915F72BEA50}.Release|Any CPU.Build.0 = Release|Any CPU + {7D3EFA76-EEF6-49DF-BF0D-6688D14E1341}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7D3EFA76-EEF6-49DF-BF0D-6688D14E1341}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7D3EFA76-EEF6-49DF-BF0D-6688D14E1341}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7D3EFA76-EEF6-49DF-BF0D-6688D14E1341}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {4980313C-A4CA-4D98-A626-19568120E067} 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