diff --git a/cs/Markdown/Markdown.csproj b/cs/Markdown/Markdown.csproj
new file mode 100644
index 000000000..2f4fc7765
--- /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..d58cf0ad4
--- /dev/null
+++ b/cs/Markdown/Md.cs
@@ -0,0 +1,53 @@
+using Markdown.Tags;
+
+namespace Markdown;
+
+public static class Md
+{
+ private static IEnumerable Tags
+ {
+ get
+ {
+ yield return new EscapeMdTagKind();
+ yield return new SingleMdTagKind("#", "", "
");
+ yield return new PairMdTagKind("_", "", "");
+ yield return new PairMdTagKind("__", "", "");
+ }
+ }
+
+ private static IEnumerable, bool>> TagRules
+ {
+ get
+ {
+ yield return IgnoreIntersectionBetweenPairTagsRule;
+ yield return IgnorePairTagWhenParentPairTagHasGreaterLengthRule;
+ }
+ }
+
+ public static string Render(string markdownText)
+ {
+ var root = new MdTokenizer(Tags.ToList(), TagRules).Tokenize(markdownText);
+ return root.ConvertToHtml();
+ }
+
+ private static bool IgnorePairTagWhenParentPairTagHasGreaterLengthRule(Token tokenToCheck,
+ IEnumerable tokens) =>
+ tokenToCheck.Tag is PairMdTagKind
+ && tokens
+ .Where(t => t != tokenToCheck && t.Tag is PairMdTagKind)
+ .Any(parent => parent.IsChild(tokenToCheck)
+ && !(parent.Tag.MdTag.Length > tokenToCheck.Tag.MdTag.Length));
+
+
+ private static bool IgnoreIntersectionBetweenPairTagsRule(Token tokenToCheck, IEnumerable tokens) =>
+ tokenToCheck.Tag is PairMdTagKind
+ && tokens
+ .Where(t => t != tokenToCheck && t.Tag is PairMdTagKind)
+ .Any(t => IsIntersectionBetween(tokenToCheck, t)
+ || IsIntersectionBetween(t, tokenToCheck));
+
+ private static bool IsIntersectionBetween(Token token, Token otherToken) =>
+ token.Position > otherToken.Position
+ && token.Position < otherToken.Position + otherToken.Value.Length
+ && token.Position + token.Value.Length > otherToken.Position + otherToken.Value.Length;
+}
\ No newline at end of file
diff --git a/cs/Markdown/MdTokenizer.cs b/cs/Markdown/MdTokenizer.cs
new file mode 100644
index 000000000..bdbcc7c16
--- /dev/null
+++ b/cs/Markdown/MdTokenizer.cs
@@ -0,0 +1,111 @@
+using Markdown.Models;
+using Markdown.Tags;
+
+namespace Markdown;
+
+public class MdTokenizer(List tags, IEnumerable, bool>> tagRules)
+{
+ private readonly Dictionary availableTags = tags.ToDictionary(tag => tag.MdTag, tag => tag);
+ private readonly List, bool>> tagRules = tagRules.ToList();
+ private readonly List mdLenOfTagSignatures = tags
+ .Select(tag => tag.MdTag.Length)
+ .Distinct()
+ .OrderDescending()
+ .ToList();
+
+ public Token Tokenize(string text)
+ {
+ var root = new Token(text);
+
+ foreach (var line in GetLines(text))
+ {
+ var tokens = GetTokens(line.Value).OrderBy(t => t.Position).ToList();
+ foreach (var token in tokens
+ .Where(t => tagRules.Select(rule => rule(t, tokens))
+ .All(result => !result))) line.AddToken(token);
+ root.AddToken(line);
+ }
+
+ return root;
+ }
+
+ private static IEnumerable GetLines(string text)
+ {
+ var position = 0;
+ foreach (var line in text.Split(Environment.NewLine))
+ {
+ yield return new Token(line, position, new SingleMdTagKind());
+ position += line.Length + Environment.NewLine.Length;
+ }
+ }
+
+ private IEnumerable GetTokens(string text)
+ {
+ var tags = GetTags(text).ToList();
+ var escapeTokens = ParseEscapedTokens(text, tags).ToList();
+
+ return ParseTokens(text, tags).Concat(escapeTokens);
+ }
+
+ private IEnumerable GetTags(string text)
+ {
+ for (var position = 0; position < text.Length; position += 1)
+ {
+ if (!TryGetTag(text, position, out var tag)) continue;
+
+ yield return new Tag(position, tag);
+
+ position += tag.Length - 1;
+ }
+ }
+
+ private bool TryGetTag(string text, int position, out IMdTagKind mdTag)
+ {
+ foreach (var mdLenOfTagSignature in mdLenOfTagSignatures)
+ {
+ if (position + mdLenOfTagSignature > text.Length || !availableTags
+ .TryGetValue(text.Substring(position, mdLenOfTagSignature), out var tag)) continue;
+
+ mdTag = tag;
+ return true;
+ }
+
+ mdTag = null!;
+ return false;
+ }
+
+ private static IEnumerable ParseEscapedTokens(string text, List tags)
+ {
+ for (var idx = 0; idx < tags.Count - 1; idx += 1)
+ {
+ if (tags[idx].TagKind is not EscapeMdTagKind) continue;
+
+ var position = tags[idx].Position;
+ tags.Remove(tags[idx]);
+
+ if (tags[idx].Position - position == 1)
+ {
+ yield return text.CreateEscapeToken(tags[idx]);
+ tags.Remove(tags[idx]);
+ }
+
+ idx -= 1;
+ }
+ }
+
+ private static IEnumerable ParseTokens(string text, List tags)
+ {
+ for (var idx = 0; idx < tags.Count; idx += 1)
+ {
+ if (!tags[idx].TagKind.TryGetToken(text, tags[idx], tags, out var token,
+ out var closeToken)) continue;
+
+ if (closeToken != null) tags.Remove(closeToken);
+
+ yield return token;
+ tags.RemoveAt(idx);
+
+ idx -= 1;
+ }
+ }
+}
\ No newline at end of file
diff --git a/cs/Markdown/Models/Tag.cs b/cs/Markdown/Models/Tag.cs
new file mode 100644
index 000000000..1763ff138
--- /dev/null
+++ b/cs/Markdown/Models/Tag.cs
@@ -0,0 +1,5 @@
+using Markdown.Tags;
+
+namespace Markdown.Models;
+
+public record Tag(int Position, IMdTagKind TagKind);
\ No newline at end of file
diff --git a/cs/Markdown/Program.cs b/cs/Markdown/Program.cs
new file mode 100644
index 000000000..08a2138cf
--- /dev/null
+++ b/cs/Markdown/Program.cs
@@ -0,0 +1,9 @@
+namespace Markdown;
+
+class Program
+{
+ public static void Main(string[] args)
+ {
+ Console.WriteLine("Hello, World!");
+ }
+}
\ No newline at end of file
diff --git a/cs/Markdown/StringExtension.cs b/cs/Markdown/StringExtension.cs
new file mode 100644
index 000000000..6ad4f2a6b
--- /dev/null
+++ b/cs/Markdown/StringExtension.cs
@@ -0,0 +1,44 @@
+using Markdown.Models;
+using Markdown.Tags;
+
+namespace Markdown;
+
+public static class StringExtension
+{
+ public static bool IsSubstring(this string text, int position, string value, bool isForward = true)
+ {
+ if (isForward ? position + value.Length > text.Length : position - value.Length < 0) return false;
+
+ var substring = isForward
+ ? text.Substring(position, value.Length)
+ : text.Substring(position - value.Length, value.Length);
+
+ return substring == value;
+ }
+
+ public static bool? IsSubstring(this string text, int position, Predicate predicate, bool isForward = true)
+ {
+ if (isForward ? position + 1 > text.Length : position - 1 < 0) return null;
+
+ position = isForward ? position : position - 1;
+ return predicate(text[position]);
+ }
+
+ public static Token CreateToken(this string text, int startIndex, int stopIndex, IMdTagKind tag)
+ {
+ var value = text.Substring(startIndex, stopIndex - startIndex);
+ return new Token(value, startIndex, tag);
+ }
+
+ public static int GetEndOfLinePosition(this string text, int startIndex = 0)
+ {
+ var newLinePosition = text.IndexOf(Environment.NewLine, startIndex, StringComparison.Ordinal);
+ return newLinePosition != -1 ? newLinePosition + Environment.NewLine.Length : text.Length;
+ }
+
+ public static Token CreateEscapeToken(this string text, Tag escapeTag)
+ {
+ var value = text.Substring(escapeTag.Position - 1, escapeTag.TagKind.Length);
+ return new Token(value, escapeTag.Position - 1, new EscapeMdTagKind());
+ }
+}
\ No newline at end of file
diff --git a/cs/Markdown/Tags/EscapeMdTagKind.cs b/cs/Markdown/Tags/EscapeMdTagKind.cs
new file mode 100644
index 000000000..9631c7b09
--- /dev/null
+++ b/cs/Markdown/Tags/EscapeMdTagKind.cs
@@ -0,0 +1,38 @@
+using Markdown.Models;
+
+namespace Markdown.Tags;
+
+public class EscapeMdTagKind : IMdTagKind
+{
+ public string MdTag => "\\";
+ public string HtmlOpenTag => string.Empty;
+ public string HtmlCloseTag => string.Empty;
+
+ public bool TokenCanBeCreated(string text, int startIndex, int stopIndex) =>
+ text.IsSubstring(startIndex, MdTag);
+
+ public bool TryGetToken(string text, Tag openTag, List closeTags, out Token token,
+ out Tag closeTag)
+ {
+ var openTagIndex = closeTags.IndexOf(openTag);
+ var escapedTag = openTagIndex + 1 > closeTags.Count
+ ? null
+ : closeTags[openTagIndex + 1];
+
+ if (escapedTag != null)
+ {
+ closeTag = escapedTag;
+ token = text.CreateToken(openTag.Position, escapedTag.Position +
+ escapedTag.TagKind.Length, this);
+ return true;
+ }
+
+ closeTag = null!;
+ token = null!;
+ return false;
+ }
+
+ public string RemoveMdTags(string text) => text.Remove(0, MdTag.Length);
+
+ public string InsertHtmlTags(string text) => text;
+}
\ No newline at end of file
diff --git a/cs/Markdown/Tags/IMdTagKind.cs b/cs/Markdown/Tags/IMdTagKind.cs
new file mode 100644
index 000000000..03c872800
--- /dev/null
+++ b/cs/Markdown/Tags/IMdTagKind.cs
@@ -0,0 +1,16 @@
+using Markdown.Models;
+
+namespace Markdown.Tags;
+
+public interface IMdTagKind
+{
+ public string MdTag { get; }
+ public string HtmlOpenTag { get; }
+ public string HtmlCloseTag { get; }
+ public int Length => MdTag.Length;
+
+ public bool TokenCanBeCreated(string text, int startIndex, int stopIndex);
+ public bool TryGetToken(string text, Tag openTag, List closeTags, out Token token, out Tag? closeTag);
+ public string RemoveMdTags(string text);
+ public string InsertHtmlTags(string text);
+}
\ No newline at end of file
diff --git a/cs/Markdown/Tags/PairMdTagKind.cs b/cs/Markdown/Tags/PairMdTagKind.cs
new file mode 100644
index 000000000..581597ffa
--- /dev/null
+++ b/cs/Markdown/Tags/PairMdTagKind.cs
@@ -0,0 +1,56 @@
+using Markdown.Models;
+
+namespace Markdown.Tags;
+
+public class PairMdTagKind(string mdTag, string htmlOpenTag, string htmlCloseTag) : IMdTagKind
+{
+ public string MdTag => mdTag;
+ public string HtmlOpenTag => htmlOpenTag;
+ public string HtmlCloseTag => htmlCloseTag;
+
+ private bool IsValidTag(string text, int position) =>
+ text.IsSubstring(position, MdTag)
+ && text.IsSubstring(position, char.IsDigit, false) != true
+ && text.IsSubstring(position + MdTag.Length, char.IsDigit) != true;
+
+ public bool TokenCanBeCreated(string text, int startIndex, int stopIndex)
+ {
+ if (!IsValidTag(text, startIndex) || !IsValidTag(text, stopIndex - MdTag.Length)) return false;
+
+ var value = text.Substring(startIndex, stopIndex - startIndex);
+ if (value.Split(' ').Length == 1) return value.Length > MdTag.Length * 2;
+
+ return value.Split(Environment.NewLine).Length == 1
+ && text.IsSubstring(startIndex, char.IsWhiteSpace, false) != false
+ && text.IsSubstring(stopIndex, char.IsWhiteSpace) != false;
+ }
+
+ public bool TryGetToken(string text, Tag openTag, List closeTags, out Token token, out Tag closeTag)
+ {
+ foreach (var tag in closeTags.Where(t => openTag != t
+ && openTag.TagKind == t.TagKind
+ && openTag.Position <= t.Position
+ && openTag.TagKind.TokenCanBeCreated(text, openTag.Position,
+ t.Position + t.TagKind.Length)))
+ {
+ closeTag = tag;
+ token = text.CreateToken(openTag.Position,
+ tag.Position + tag.TagKind.Length, openTag.TagKind);
+ return true;
+ }
+
+ closeTag = null!;
+ token = null!;
+ return false;
+ }
+
+ public string RemoveMdTags(string text) =>
+ text
+ .Remove(text.Length - MdTag.Length)
+ .Remove(0, MdTag.Length);
+
+ public string InsertHtmlTags(string text) =>
+ text
+ .Insert(text.Length, HtmlCloseTag)
+ .Insert(0, HtmlOpenTag);
+}
diff --git a/cs/Markdown/Tags/SingleMdTagKind.cs b/cs/Markdown/Tags/SingleMdTagKind.cs
new file mode 100644
index 000000000..3c5e5b8f9
--- /dev/null
+++ b/cs/Markdown/Tags/SingleMdTagKind.cs
@@ -0,0 +1,47 @@
+using Markdown.Models;
+
+namespace Markdown.Tags;
+
+public class SingleMdTagKind(string mdTagKind, string htmlOpenTag, string htmlCloseTag) : IMdTagKind
+{
+ public string MdTag => mdTagKind;
+ public string HtmlOpenTag => htmlOpenTag;
+ public string HtmlCloseTag => htmlCloseTag;
+
+ public SingleMdTagKind() : this(string.Empty, string.Empty, string.Empty)
+ {
+ }
+
+ private bool IsValidTag(string text, int position) =>
+ text.IsSubstring(position, MdTag)
+ && (text.IsSubstring(position, Environment.NewLine, false) || position == 0);
+
+ public bool TokenCanBeCreated(string text, int startIndex, int stopIndex) =>
+ IsValidTag(text, startIndex)
+ && (text.IsSubstring(stopIndex, Environment.NewLine, false) || text.Length == stopIndex);
+
+ public bool TryGetToken(string text, Tag openTag, List closeTags, out Token token,
+ out Tag? closeTag)
+ {
+ var closeTagIndex = text.GetEndOfLinePosition(openTag.Position);
+ if (TokenCanBeCreated(text, openTag.Position, closeTagIndex))
+ {
+ closeTag = null!;
+ token = text.CreateToken(openTag.Position, closeTagIndex, this);
+ return true;
+ }
+
+ closeTag = null;
+ token = null!;
+ return false;
+ }
+
+ public string RemoveMdTags(string text) => text.Remove(0, MdTag.Length);
+
+ public string InsertHtmlTags(string text) =>
+ text
+ .Insert(text.EndsWith(Environment.NewLine)
+ ? text.Length - Environment.NewLine.Length
+ : text.Length, HtmlCloseTag)
+ .Insert(0, HtmlOpenTag);
+}
\ No newline at end of file
diff --git a/cs/Markdown/Token.cs b/cs/Markdown/Token.cs
new file mode 100644
index 000000000..efc4a0b3b
--- /dev/null
+++ b/cs/Markdown/Token.cs
@@ -0,0 +1,46 @@
+using System.Text;
+using Markdown.Tags;
+
+namespace Markdown;
+
+public class Token(string value, int position, IMdTagKind mdTagKind)
+{
+ private readonly List children = [];
+
+ public string Value => value;
+ public IMdTagKind Tag => mdTagKind;
+ public int Position { get; private set; } = position;
+
+ public Token(string value) : this(value, value.Length, new SingleMdTagKind())
+ {
+ }
+
+ public void AddToken(Token child)
+ {
+ var parent = children.FirstOrDefault(token => token.IsChild(child));
+
+ if (parent != null)
+ {
+ parent.AddToken(child);
+ child.Position -= parent.Position + parent.Tag.MdTag.Length;
+ }
+ else children.Add(child);
+ }
+
+ public string ConvertToHtml()
+ {
+ var sb = new StringBuilder(Tag.RemoveMdTags(Value));
+ foreach (var child in children.OrderByDescending(token => token.Position))
+ {
+ sb.Remove(child.Position, child.Value.Length);
+ sb.Insert(child.Position, child.ConvertToHtml());
+ }
+
+ return Tag.InsertHtmlTags(sb.ToString());
+ }
+
+ public bool IsChild(Token child) =>
+ child.Position >= Position
+ && child.Position < Position + Value.Length
+ && child.Position + child.Value.Length <= Position + Value.Length;
+}
\ No newline at end of file
diff --git a/cs/MarkdownTests/MarkdownTests.csproj b/cs/MarkdownTests/MarkdownTests.csproj
new file mode 100644
index 000000000..4f4a35e3f
--- /dev/null
+++ b/cs/MarkdownTests/MarkdownTests.csproj
@@ -0,0 +1,29 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+ false
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/cs/MarkdownTests/MdTests.cs b/cs/MarkdownTests/MdTests.cs
new file mode 100644
index 000000000..34bfa22d8
--- /dev/null
+++ b/cs/MarkdownTests/MdTests.cs
@@ -0,0 +1,126 @@
+using System.Diagnostics;
+using Markdown;
+using FluentAssertions;
+namespace MarkdownTests;
+
+[TestFixture]
+[TestOf(typeof(Md))]
+public class MdTests
+{
+ [TestCaseSource(nameof(ConvertTagsTests))]
+ [TestCaseSource(nameof(MdSpecTests))]
+ public void Render_ShouldWorkCorrectly(string input, string expected) => Md.Render(input).Should().Be(expected);
+
+ [TestCase(100, 10)]
+ [TestCase(100, 100)]
+ [TestCase(100, 1000)]
+ public void Render_ShouldWorkLinearly(int times, int inputScale)
+ {
+ const string input = "# Заголовок c _курсивным текстом_ и __полужирным текстом__";
+ var scaledInput = string.Join(Environment.NewLine, Enumerable.Repeat(input, inputScale));
+ var timeWithDefaultInput = MeasureRenderTime(input, times);
+ var timeWithScaledInput = MeasureRenderTime(scaledInput, times);
+ var avgWithDefaultInput = timeWithDefaultInput / times;
+ var avgWithScaledInput = timeWithScaledInput / (inputScale * times);
+
+ avgWithDefaultInput.Should().BeCloseTo(avgWithScaledInput, TimeSpan.FromTicks(3300));
+ }
+
+
+ private static TimeSpan MeasureRenderTime(string input, int times = 1)
+ {
+ var timer = new Stopwatch();
+
+ for (var i = 0; i < times; i++)
+ {
+ timer.Start();
+ Md.Render(input);
+ timer.Stop();
+ }
+
+ return timer.Elapsed;
+ }
+ public static IEnumerable ConvertTagsTests
+ {
+ get
+ {
+ yield return new TestCaseData(
+ $"# Заголовок{Environment.NewLine}#Заголовок", $" Заголовок
{Environment.NewLine}Заголовок
")
+ .SetName("Render_ShouldConvertHeaderTag")
+ .SetCategory(nameof(ConvertTagsTests));
+ yield return new TestCaseData(
+ $"_курсивный текст_{Environment.NewLine}_курсивный текст_",
+ $"курсивный текст{Environment.NewLine}курсивный текст")
+ .SetName("Render_ShouldConvertItalicTag")
+ .SetCategory(nameof(ConvertTagsTests));
+ yield return new TestCaseData(
+ $"__полужирный текст__{Environment.NewLine}__полужирный текст__",
+ $"полужирный текст{Environment.NewLine}полужирный текст")
+ .SetName("Render_ShouldConvertBoldTag")
+ .SetCategory(nameof(ConvertTagsTests));
+ yield return new TestCaseData(
+ "_чем\\_ 100_ __раз_ услышать.__",
+ "_чем_ 100_ __раз_ услышать.__")
+ .SetName("Render_ShouldConvertEscapeTag")
+ .SetCategory(nameof(ConvertTagsTests));
+ yield return new TestCaseData(
+ "# Заголовок c _курсивным текстом_ и __полужирным текстом__",
+ " Заголовок c курсивным текстом и полужирным текстом
")
+ .SetName("Render_ShouldConvertAllTagsInHeader")
+ .SetCategory(nameof(ConvertTagsTests));
+ }
+ }
+
+ public static IEnumerable MdSpecTests
+ {
+ get
+ {
+ yield return new TestCaseData(
+ $"#Это заголовок, а это (#) - нет.{Environment.NewLine}И это #тоже# не ##заголовок##",
+ $"Это заголовок, а это (#) - нет.
{Environment.NewLine}И это #тоже# не ##заголовок##")
+ .SetName("Render_ShouldIgnoreSingleTags_WhenNotStartsWithNewLine")
+ .SetCategory("BasicSpec");
+ yield return new TestCaseData(
+ $"Это _заголовок1_ ,а не заголовок __1 уровня__{Environment.NewLine}_4 Life CJ, _Grove __123__ Street_ 4 Life_",
+ $"Это _заголовок1_ ,а не заголовок __1 уровня__{Environment.NewLine}_4 Life CJ, Grove __123__ Street 4 Life_")
+ .SetName("Render_ShouldIgnorePairTags_WhenPlacedWithNumbers")
+ .SetCategory("MdSpec");
+ yield return new TestCaseData(
+ $"Подчерки _мо_гут вы__де__лять ча_сть_ слова{Environment.NewLine}Но в разных словах не могут",
+ $"Подчерки могут выделять часть слова{Environment.NewLine}Но в разных словах не могут")
+ .SetName("Render_ShouldConvertPairTags_WhenPartOfWordMarked")
+ .SetCategory("MdSpec");
+ yield return new TestCaseData(
+ $"Подчерки могут выделять часть слова{Environment.NewLine}Но в раз_ных сло_вах н__е мог__ут",
+ $"Подчерки могут выделять часть слова{Environment.NewLine}Но в раз_ных сло_вах н__е мог__ут")
+ .SetName("Render_ShouldIgnorePairTags_WhenPartsOfDifferentWordsMarked")
+ .SetCategory("MdSpec");
+ yield return new TestCaseData(
+ $"За подчерками, _начинающими выделение,_ должен следовать __непробельный символ__{Environment.NewLine}Иначе_ ничего_ не__ получится!__",
+ $"За подчерками, начинающими выделение, должен следовать непробельный символ{Environment.NewLine}Иначе_ ничего_ не__ получится!__")
+ .SetName("Render_ShouldIgnorePairTags_WhenMarkedWordsStartsWithNonWhitespace")
+ .SetCategory("MdSpec");
+ yield return new TestCaseData(
+ $"Подчерки, _заканчивающие выделение,_ должны следовать за __непробельным символом__{Environment.NewLine}_Иначе _ничего __не получится __!",
+ $"Подчерки, заканчивающие выделение, должны следовать за непробельным символом{Environment.NewLine}_Иначе _ничего __не получится __!")
+ .SetName("Render_ShouldIgnorePairTags_WhenMarkedWordsEndsWithNonWhitespace")
+ .SetCategory("MdSpec");
+ yield return new TestCaseData(
+ "В случае __пересечения _двойных__ и одинарных_ подчерков ни _один из __них не_ считается__ выделением",
+ "В случае __пересечения _двойных__ и одинарных_ подчерков ни _один из __них не_ считается__ выделением")
+ .SetName("Render_ShouldIgnorePairTags_WhenIntersectionBetweenDifferentKindsOfPairTags")
+ .SetCategory("MdSpec");
+ yield return new TestCaseData(
+ $"__Непарные _символы в рамках{Environment.NewLine}одного_ абзаца не считаются__ выделением",
+ $"__Непарные _символы в рамках{Environment.NewLine}одного_ абзаца не считаются__ выделением")
+ .SetName("Render_ShouldIgnorePairTags_WhenPlacedInMultiLine")
+ .SetCategory("MdSpec");
+ yield return new TestCaseData(
+ "Если внутри подчерков пустая строка ____, то они остаются символами подчерка",
+ "Если внутри подчерков пустая строка ____, то они остаются символами подчерка")
+ .SetName("Render_ShouldIgnorePairTags_WhenTextIsEmpty")
+ .SetCategory("MdSpec");
+ }
+ }
+}
+
diff --git a/cs/clean-code.sln b/cs/clean-code.sln
index 2206d54db..8b231a313 100644
--- a/cs/clean-code.sln
+++ b/cs/clean-code.sln
@@ -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", "{71910566-C5DE-4C56-93E4-16976D5D4FC6}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MarkdownTests", "MarkdownTests\MarkdownTests.csproj", "{742ED593-BF23-4F69-BD7C-255777424108}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -27,5 +31,13 @@ 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
+ {71910566-C5DE-4C56-93E4-16976D5D4FC6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {71910566-C5DE-4C56-93E4-16976D5D4FC6}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {71910566-C5DE-4C56-93E4-16976D5D4FC6}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {71910566-C5DE-4C56-93E4-16976D5D4FC6}.Release|Any CPU.Build.0 = Release|Any CPU
+ {742ED593-BF23-4F69-BD7C-255777424108}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {742ED593-BF23-4F69-BD7C-255777424108}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {742ED593-BF23-4F69-BD7C-255777424108}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {742ED593-BF23-4F69-BD7C-255777424108}.Release|Any CPU.Build.0 = Release|Any CPU
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