From fd09786405fc257553cd3caeff166737476a951a Mon Sep 17 00:00:00 2001 From: Griboedoff Date: Sat, 5 Nov 2016 19:38:36 +0500 Subject: [PATCH 01/17] initial --- Markdown/HtmlToken.cs | 24 ++++++++++++ Markdown/Markdown.csproj | 65 +++++++++++++++++++++++++++++++ Markdown/Md.cs | 27 +++++++++++++ Markdown/Program.cs | 10 +++++ Markdown/Spec.md | 18 ++++----- Markdown/Test/HtmlToken_Should.cs | 16 ++++++++ Markdown/Test/MD_Should.cs | 16 ++++++++ clean-code.sln | 14 +++++++ 8 files changed, 179 insertions(+), 11 deletions(-) create mode 100644 Markdown/HtmlToken.cs create mode 100644 Markdown/Markdown.csproj create mode 100644 Markdown/Md.cs create mode 100644 Markdown/Program.cs create mode 100644 Markdown/Test/HtmlToken_Should.cs create mode 100644 Markdown/Test/MD_Should.cs diff --git a/Markdown/HtmlToken.cs b/Markdown/HtmlToken.cs new file mode 100644 index 000000000..4696a04bf --- /dev/null +++ b/Markdown/HtmlToken.cs @@ -0,0 +1,24 @@ +namespace Markdown +{ + public class HtmlToken + { + public readonly string Tag; + private readonly string data; + + public HtmlToken(string tag, string data) + { + Tag = tag; + this.data = data; + } + + private string InsertInToTags(string body) + { + return string.IsNullOrEmpty(Tag) ? body : $"<{Tag}>{body}"; + } + + public override string ToString() + { + return InsertInToTags(data); + } + } +} \ No newline at end of file diff --git a/Markdown/Markdown.csproj b/Markdown/Markdown.csproj new file mode 100644 index 000000000..de00a7a41 --- /dev/null +++ b/Markdown/Markdown.csproj @@ -0,0 +1,65 @@ + + + + + Debug + AnyCPU + {E6825F19-EE20-40A7-AE68-22051E2AF040} + {FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + Exe + Properties + Markdown + Markdown + v4.5 + 512 + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\NUnit.3.5.0\lib\net45\nunit.framework.dll + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Markdown/Md.cs b/Markdown/Md.cs new file mode 100644 index 000000000..72f679500 --- /dev/null +++ b/Markdown/Md.cs @@ -0,0 +1,27 @@ +using System; + +namespace Markdown +{ + public class Md + { + private readonly string plainMd; + private HtmlToken root; + + public Md(string plainMd) + { + this.plainMd = plainMd; + } + + public bool TryParseToHtml() + { + root = new HtmlToken("", plainMd); + return true; + } + + public string Render() + { + TryParseToHtml(); + return root.ToString(); + } + } +} \ No newline at end of file diff --git a/Markdown/Program.cs b/Markdown/Program.cs new file mode 100644 index 000000000..79106afdc --- /dev/null +++ b/Markdown/Program.cs @@ -0,0 +1,10 @@ +namespace Markdown +{ + public class Program + { + public static void Main() + { + + } + } +} \ No newline at end of file diff --git a/Markdown/Spec.md b/Markdown/Spec.md index f88af0bfc..6cb28c11d 100644 --- a/Markdown/Spec.md +++ b/Markdown/Spec.md @@ -1,21 +1,17 @@ # Спецификация языка разметки -Посмотрите этот файл в сыром виде. Сравните с тем, что показывает github. +Процессору принимает на вход строку формата описанного ниже и возвращает HTML-код эквивалентный данной строке. -Процессору на вход подается одна строка — параграф текста. -На выходе должен быть HTML-код этого параграфа. +###Описание правил: +Текст +_окруженный с двух сторон_ одинарными символами подчерка `_` +должен помещаться в HTML-тег `` -Текст _окруженный с двух сторон_ одинарными символами подчерка -должен помещаться в HTML-тег em вот так: +__Окруженный двумя символами__ `__` в тег ``. -`Текст окруженный с двух сторон одинарными символами подчерка -должен помещаться в HTML-тег em вот так:` - -Любой символ можно экранировать, чтобы он не считался частью разметки. +Любой символ можно экранировать с помошью \ , чтобы он не считался частью разметки. \_Вот это\_, не должно выделиться тегом \. -__Двумя символами__ — должен становиться жирным с помощью тега \. - Внутри __двойного выделения _одинарное_ тоже__ работает. Но не наоборот — внутри _одинарного __двойное__ не работает_. diff --git a/Markdown/Test/HtmlToken_Should.cs b/Markdown/Test/HtmlToken_Should.cs new file mode 100644 index 000000000..e58cd0488 --- /dev/null +++ b/Markdown/Test/HtmlToken_Should.cs @@ -0,0 +1,16 @@ +using NUnit.Framework; + +namespace Markdown +{ + [TestFixture] + public class HtmlToken_Should + { + [TestCase("p", "asd", ExpectedResult = "

asd

")] + [TestCase("i", "some text", ExpectedResult = "some text")] + public string ShouldInsertDataInToTags_WhenToStringCalls(string tag, string data) + { + var token = new HtmlToken(tag, data); + return token.ToString(); + } + } +} \ No newline at end of file diff --git a/Markdown/Test/MD_Should.cs b/Markdown/Test/MD_Should.cs new file mode 100644 index 000000000..44b424e63 --- /dev/null +++ b/Markdown/Test/MD_Should.cs @@ -0,0 +1,16 @@ +using NUnit.Framework; + +namespace Markdown.Test +{ + [TestFixture] + class Md_Should + { + [TestCase("qwe asd zxc", ExpectedResult = "qwe asd zxc")] + public string ParseNoMarkup(string md) + { +// throw new NotImplementedException(); + var mdParser = new Md(md); + return mdParser.Render(); + } + } +} \ No newline at end of file diff --git a/clean-code.sln b/clean-code.sln index 25a6075ae..80dc2198e 100644 --- a/clean-code.sln +++ b/clean-code.sln @@ -9,6 +9,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ControlDigit", "ControlDigi EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Samples", "Samples\Samples.csproj", "{9ED89DE1-D257-450E-B66C-3B28D10F331D}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Markdown", "Markdown\Markdown.csproj", "{E6825F19-EE20-40A7-AE68-22051E2AF040}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -53,6 +55,18 @@ Global {9ED89DE1-D257-450E-B66C-3B28D10F331D}.Release|Mixed Platforms.Build.0 = Release|Any CPU {9ED89DE1-D257-450E-B66C-3B28D10F331D}.Release|x86.ActiveCfg = Release|Any CPU {9ED89DE1-D257-450E-B66C-3B28D10F331D}.Release|x86.Build.0 = Release|Any CPU + {E6825F19-EE20-40A7-AE68-22051E2AF040}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E6825F19-EE20-40A7-AE68-22051E2AF040}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E6825F19-EE20-40A7-AE68-22051E2AF040}.Debug|Mixed Platforms.ActiveCfg = Debug|Mixed Platforms + {E6825F19-EE20-40A7-AE68-22051E2AF040}.Debug|Mixed Platforms.Build.0 = Debug|Mixed Platforms + {E6825F19-EE20-40A7-AE68-22051E2AF040}.Debug|x86.ActiveCfg = Debug|x86 + {E6825F19-EE20-40A7-AE68-22051E2AF040}.Debug|x86.Build.0 = Debug|x86 + {E6825F19-EE20-40A7-AE68-22051E2AF040}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E6825F19-EE20-40A7-AE68-22051E2AF040}.Release|Any CPU.Build.0 = Release|Any CPU + {E6825F19-EE20-40A7-AE68-22051E2AF040}.Release|Mixed Platforms.ActiveCfg = Release|Mixed Platforms + {E6825F19-EE20-40A7-AE68-22051E2AF040}.Release|Mixed Platforms.Build.0 = Release|Mixed Platforms + {E6825F19-EE20-40A7-AE68-22051E2AF040}.Release|x86.ActiveCfg = Release|x86 + {E6825F19-EE20-40A7-AE68-22051E2AF040}.Release|x86.Build.0 = Release|x86 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From 1957c0c92d0bc68467fb637504555716e39b5507 Mon Sep 17 00:00:00 2001 From: Griboedoff Date: Sat, 5 Nov 2016 20:43:45 +0500 Subject: [PATCH 02/17] add partial parse for em tag --- Markdown/HtmlToken.cs | 6 +++- Markdown/Markdown.csproj | 1 + Markdown/Md.cs | 57 ++++++++++++++++++++++++++++--- Markdown/Tag.cs | 18 ++++++++++ Markdown/Test/HtmlToken_Should.cs | 2 +- Markdown/Test/MD_Should.cs | 21 +++++++++--- 6 files changed, 94 insertions(+), 11 deletions(-) create mode 100644 Markdown/Tag.cs diff --git a/Markdown/HtmlToken.cs b/Markdown/HtmlToken.cs index 4696a04bf..c1e3bb1ad 100644 --- a/Markdown/HtmlToken.cs +++ b/Markdown/HtmlToken.cs @@ -4,6 +4,8 @@ public class HtmlToken { public readonly string Tag; private readonly string data; + public bool IsTagged => !string.IsNullOrEmpty(Tag); + public int Length => data.Length; public HtmlToken(string tag, string data) { @@ -13,7 +15,9 @@ public HtmlToken(string tag, string data) private string InsertInToTags(string body) { - return string.IsNullOrEmpty(Tag) ? body : $"<{Tag}>{body}"; + return IsTagged + ? $"<{Tag}>{body}" + : body; } public override string ToString() diff --git a/Markdown/Markdown.csproj b/Markdown/Markdown.csproj index de00a7a41..70029bb8a 100644 --- a/Markdown/Markdown.csproj +++ b/Markdown/Markdown.csproj @@ -47,6 +47,7 @@ + diff --git a/Markdown/Md.cs b/Markdown/Md.cs index 72f679500..4a47004d8 100644 --- a/Markdown/Md.cs +++ b/Markdown/Md.cs @@ -1,27 +1,74 @@ using System; +using System.Collections.Generic; +using System.Linq; namespace Markdown { public class Md { private readonly string plainMd; - private HtmlToken root; + private readonly List root; + + private static readonly Dictionary> MdTagParserFuncMatch = + new Dictionary> + { + ['_'] = ParseEmToken + }; public Md(string plainMd) { this.plainMd = plainMd; + root = new List(); + } + + #region Parser Funcs + + private static HtmlToken ParseEmToken(string plainMd, int index) + { + var i = 1; + while (index + i < plainMd.Length && plainMd[index + i] != '_') + { + i++; + } + return new HtmlToken("em", plainMd.Substring(index + 1, i - 1)); + } + + private static HtmlToken ParseNoMarkup(string plainMd, int index) + { + var i = 1; + while (index + i < plainMd.Length) + { + if (MdTagParserFuncMatch.ContainsKey(plainMd[index + i++])) + return new HtmlToken("", plainMd.Substring(index, i - 1)); + } + return new HtmlToken("", plainMd.Substring(index, i)); + } + + private static bool IsActiveTag(string plainMd, int index, Tag mdTag) + { + } + #endregion - public bool TryParseToHtml() + private void TryParseToHtml() { - root = new HtmlToken("", plainMd); - return true; + for (var i = 0; i < plainMd.Length; i++) + { + var parserFunc = MdTagParserFuncMatch.ContainsKey(plainMd[i]) + ? MdTagParserFuncMatch[plainMd[i]] + : ParseNoMarkup; + var parsedToken = parserFunc.Invoke(plainMd, i); + i += parsedToken.Length + (parsedToken.IsTagged + ? 1 + : -1); + root.Add(parsedToken); + } } public string Render() { TryParseToHtml(); - return root.ToString(); + return string.Join("", root.Select(x => x.ToString())); } } } \ No newline at end of file diff --git a/Markdown/Tag.cs b/Markdown/Tag.cs new file mode 100644 index 000000000..159dde317 --- /dev/null +++ b/Markdown/Tag.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; + +namespace Markdown +{ + public class Tag + { + public static Tag EmMd = new Tag("_"); + public static Tag StrongMd = new Tag("__"); + public static Tag EmHtml = new Tag("em"); + public static Tag StrongHtml = new Tag("strong"); + public readonly string Value; + + private Tag(string value) + { + Value = value; + } + } +} \ No newline at end of file diff --git a/Markdown/Test/HtmlToken_Should.cs b/Markdown/Test/HtmlToken_Should.cs index e58cd0488..3efccfdd7 100644 --- a/Markdown/Test/HtmlToken_Should.cs +++ b/Markdown/Test/HtmlToken_Should.cs @@ -1,6 +1,6 @@ using NUnit.Framework; -namespace Markdown +namespace Markdown.Test { [TestFixture] public class HtmlToken_Should diff --git a/Markdown/Test/MD_Should.cs b/Markdown/Test/MD_Should.cs index 44b424e63..a9d37e836 100644 --- a/Markdown/Test/MD_Should.cs +++ b/Markdown/Test/MD_Should.cs @@ -6,11 +6,24 @@ namespace Markdown.Test class Md_Should { [TestCase("qwe asd zxc", ExpectedResult = "qwe asd zxc")] - public string ParseNoMarkup(string md) + public string ParseNoMarkup(string plainMd) { -// throw new NotImplementedException(); - var mdParser = new Md(md); - return mdParser.Render(); + return new Md(plainMd).Render(); + } + + [TestCase("_asd_", ExpectedResult = "asd")] + [TestCase("_a s d_", ExpectedResult = "a s d")] + [TestCase("_a_ _s d_", ExpectedResult = "a s d")] + public string ParseEmTag_IfTextInOneUnderscore(string plainMd) + { + return new Md(plainMd).Render(); + } + + [TestCase("_ d_", ExpectedResult = "_ d_")] + [TestCase("_a _", ExpectedResult = "_a _")] + public string ParseEmTagCorrect_IfWhiteSpaceInTheStartOrEndOfToken(string plainMd) + { + return new Md(plainMd).Render(); } } } \ No newline at end of file From bcddd09cf2735e29592b52dffc05fe7acf5bc34c Mon Sep 17 00:00:00 2001 From: Griboedoff Date: Wed, 9 Nov 2016 19:35:27 +0500 Subject: [PATCH 03/17] =?UTF-8?q?=D0=BE=D0=B1=D0=BD=D0=BE=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=20=D1=81=D0=BF=D0=B5=D0=BA=D1=83=20=D0=BA=D0=BE=D1=80?= =?UTF-8?q?=D1=80=D0=B5=D0=BA=D1=82=D0=BD=D0=BE=20=D0=BE=D0=B1=D1=80=D0=B0?= =?UTF-8?q?=D0=B1=D0=B0=D1=82=D1=8B=D0=B2=D0=B0=D1=8E=20=D0=B2=D1=81=D0=B5?= =?UTF-8?q?=20=D1=81=20"=5F"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Markdown/HtmlToken.cs | 15 ++--- Markdown/Markdown.csproj | 7 +++ Markdown/Md.cs | 100 +++++++++++++++++++++++------- Markdown/Spec.md | 28 ++++++++- Markdown/Tag.cs | 13 ++++ Markdown/Test/HtmlToken_Should.cs | 12 ++-- Markdown/Test/MD_Should.cs | 5 +- Markdown/packages.config | 4 ++ 8 files changed, 143 insertions(+), 41 deletions(-) create mode 100644 Markdown/packages.config diff --git a/Markdown/HtmlToken.cs b/Markdown/HtmlToken.cs index c1e3bb1ad..3e99d26a5 100644 --- a/Markdown/HtmlToken.cs +++ b/Markdown/HtmlToken.cs @@ -2,23 +2,20 @@ { public class HtmlToken { - public readonly string Tag; + public readonly Tag Tag; private readonly string data; - public bool IsTagged => !string.IsNullOrEmpty(Tag); + public bool IsTagged => Tag != Tag.Empty; public int Length => data.Length; - public HtmlToken(string tag, string data) + public HtmlToken(Tag tag, string data) { Tag = tag; this.data = data; } - private string InsertInToTags(string body) - { - return IsTagged - ? $"<{Tag}>{body}" - : body; - } + private string InsertInToTags(string body) => IsTagged + ? $"<{Tag.MdToHtml[Tag]}>{body}" + : body; public override string ToString() { diff --git a/Markdown/Markdown.csproj b/Markdown/Markdown.csproj index 70029bb8a..bfccc7366 100644 --- a/Markdown/Markdown.csproj +++ b/Markdown/Markdown.csproj @@ -33,6 +33,12 @@ 4 + + ..\packages\FluentAssertions.4.16.0\lib\net45\FluentAssertions.dll + + + ..\packages\FluentAssertions.4.16.0\lib\net45\FluentAssertions.Core.dll + ..\packages\NUnit.3.5.0\lib\net45\nunit.framework.dll @@ -52,6 +58,7 @@ + diff --git a/Markdown/Md.cs b/Markdown/Md.cs index 4a47004d8..25e70ec1b 100644 --- a/Markdown/Md.cs +++ b/Markdown/Md.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text; namespace Markdown { @@ -9,55 +10,110 @@ public class Md private readonly string plainMd; private readonly List root; - private static readonly Dictionary> MdTagParserFuncMatch = - new Dictionary> - { - ['_'] = ParseEmToken - }; + private readonly Dictionary> mdTagParserFuncMatch; + private readonly Dictionary> validateFunctions; public Md(string plainMd) { this.plainMd = plainMd; root = new List(); + mdTagParserFuncMatch = new Dictionary> + { + [Tag.EmMd] = ParseEmToken, + [Tag.Empty] = ParseNoMarkup + }; + validateFunctions = new Dictionary> + { + [Tag.EmMd] = IsValidEmTag + }; } #region Parser Funcs - private static HtmlToken ParseEmToken(string plainMd, int index) + private HtmlToken ParseEmToken(int index, string alreadyParsed = "") { - var i = 1; - while (index + i < plainMd.Length && plainMd[index + i] != '_') + if (!IsValidEmTag(index, true)) + return ParseNoMarkup(index); + index++; + var tokenData = new StringBuilder(); + while (index < plainMd.Length && !IsValidEmTag(index, false)) { - i++; + if (plainMd[index] == '_') + { + var tag = ParseTag(index); + + if (tag == Tag.StrongMd) + { + tokenData.Append("__"); + return ParseNoMarkup(index + 2, string.Join("", "_", tokenData.ToString())); + } + } + if (plainMd[index] == '\\') + index++; + tokenData.Append(plainMd[index]); + index++; } - return new HtmlToken("em", plainMd.Substring(index + 1, i - 1)); + return new HtmlToken(Tag.EmMd, tokenData.ToString()); } - private static HtmlToken ParseNoMarkup(string plainMd, int index) + private HtmlToken ParseNoMarkup(int index, string alreadyParsed = "") { - var i = 1; - while (index + i < plainMd.Length) + var tokenData = new StringBuilder(alreadyParsed); + while (index < plainMd.Length) { - if (MdTagParserFuncMatch.ContainsKey(plainMd[index + i++])) - return new HtmlToken("", plainMd.Substring(index, i - 1)); + if (plainMd[index] == '_') + { + var tag = ParseTag(index); + + if (validateFunctions[tag].Invoke(index, true)) + break; + } + if (plainMd[index] == '\\') + index++; + tokenData.Append(plainMd[index]); + index++; } - return new HtmlToken("", plainMd.Substring(index, i)); + return new HtmlToken(Tag.Empty, tokenData.ToString()); } - private static bool IsActiveTag(string plainMd, int index, Tag mdTag) - { + #endregion + #region Validation Functions + + private bool IsValidEmTag(int tokenIndex, bool isOpenTag) + { + if (plainMd[tokenIndex] != '_') + return false; + try + { + return !(tokenIndex == plainMd.Length - 1 && isOpenTag) + && plainMd[tokenIndex + (isOpenTag ? 1 : -1)] != ' ' + && !(plainMd[tokenIndex - 1] == '_' || plainMd[tokenIndex + 1] == '_') + && (!char.IsDigit(plainMd[tokenIndex - 1]) && !char.IsDigit(plainMd[tokenIndex + 1])); + } + catch (IndexOutOfRangeException) + { + return true; + } } + #endregion + private Tag ParseTag(int tagIndex) + { + if (tagIndex != plainMd.Length - 1) + return plainMd[tagIndex + 1] == '_' ? Tag.StrongMd : Tag.EmMd; + return Tag.EmMd; + } + private void TryParseToHtml() { for (var i = 0; i < plainMd.Length; i++) { - var parserFunc = MdTagParserFuncMatch.ContainsKey(plainMd[i]) - ? MdTagParserFuncMatch[plainMd[i]] - : ParseNoMarkup; - var parsedToken = parserFunc.Invoke(plainMd, i); + var tag = Tag.Empty; + if (plainMd[i] == '_') + tag = ParseTag(i); + var parsedToken = mdTagParserFuncMatch[tag].Invoke(i, ""); i += parsedToken.Length + (parsedToken.IsTagged ? 1 : -1); diff --git a/Markdown/Spec.md b/Markdown/Spec.md index 6cb28c11d..6da59759b 100644 --- a/Markdown/Spec.md +++ b/Markdown/Spec.md @@ -6,22 +6,46 @@ Текст _окруженный с двух сторон_ одинарными символами подчерка `_` должен помещаться в HTML-тег `` + +`_abc_` -> `abc` + + +__Окруженный двумя символами__ `__` в тег ``. + +`__abc__` -> `abc` -__Окруженный двумя символами__ `__` в тег ``. Любой символ можно экранировать с помошью \ , чтобы он не считался частью разметки. \_Вот это\_, не должно выделиться тегом \. +`_abc\_abc_` -> `abc_abc` + Внутри __двойного выделения _одинарное_ тоже__ работает. -Но не наоборот — внутри _одинарного __двойное__ не работает_. +`__abc_abc_abc__` -> `abcabcabc` + +Но не наоборот — внутри _одинарного __двойное__ не работает_ и остается просто двойным подчерком. + +`_abc__abc__abc_` -> `abc__abc__abc` Подчерки внутри текста c цифрами_12_3 не считаются выделением и должны оставаться символами подчерка. +`1_2_3` -> `1_2_3` + __непарные _символы не считаются выделением. +`__abc_abc` -> `abc_abc` + За подчерками, начинающими выделение, должен следовать непробельный символ. Иначе эти_ подчерки_ не считаются выделением и остаются просто символами подчерка. +`_ abc_abc_` -> `_ abcabc` + Подчерки, заканчивающие выделение, должны следовать за непробельным символом. Иначе эти _подчерки _не считаются_ окончанием выделения и остаются просто символами подчерка. + +`_abc _` -> `abc _` + +При отстутствии закрывающаего тега, выделение продолжается до конца строки. + +`_ab cd` -> `ab cd` diff --git a/Markdown/Tag.cs b/Markdown/Tag.cs index 159dde317..d26b7aa18 100644 --- a/Markdown/Tag.cs +++ b/Markdown/Tag.cs @@ -8,11 +8,24 @@ public class Tag public static Tag StrongMd = new Tag("__"); public static Tag EmHtml = new Tag("em"); public static Tag StrongHtml = new Tag("strong"); + public static Tag Empty = new Tag(""); public readonly string Value; + public static readonly Dictionary MdToHtml = new Dictionary + { + [EmMd] = EmHtml, + [StrongMd] = StrongHtml, + [Empty] = Empty + }; + private Tag(string value) { Value = value; } + + public override string ToString() + { + return Value; + } } } \ No newline at end of file diff --git a/Markdown/Test/HtmlToken_Should.cs b/Markdown/Test/HtmlToken_Should.cs index 3efccfdd7..04e4304b6 100644 --- a/Markdown/Test/HtmlToken_Should.cs +++ b/Markdown/Test/HtmlToken_Should.cs @@ -1,16 +1,16 @@ using NUnit.Framework; - +using FluentAssertions; namespace Markdown.Test { [TestFixture] public class HtmlToken_Should { - [TestCase("p", "asd", ExpectedResult = "

asd

")] - [TestCase("i", "some text", ExpectedResult = "some text")] - public string ShouldInsertDataInToTags_WhenToStringCalls(string tag, string data) + [Test] + public void ShouldInsertDataInToTags_WhenToStringCalls() { - var token = new HtmlToken(tag, data); - return token.ToString(); + var token = new HtmlToken(Tag.EmHtml, "data"); + + token.ToString().Should().Be("data"); } } } \ No newline at end of file diff --git a/Markdown/Test/MD_Should.cs b/Markdown/Test/MD_Should.cs index a9d37e836..fe70c25e6 100644 --- a/Markdown/Test/MD_Should.cs +++ b/Markdown/Test/MD_Should.cs @@ -3,7 +3,7 @@ namespace Markdown.Test { [TestFixture] - class Md_Should + internal class Md_Should { [TestCase("qwe asd zxc", ExpectedResult = "qwe asd zxc")] public string ParseNoMarkup(string plainMd) @@ -20,7 +20,8 @@ public string ParseEmTag_IfTextInOneUnderscore(string plainMd) } [TestCase("_ d_", ExpectedResult = "_ d_")] - [TestCase("_a _", ExpectedResult = "_a _")] + [TestCase("_a _", ExpectedResult = "a _")] + [TestCase("_aas__",ExpectedResult = "_aas__")] public string ParseEmTagCorrect_IfWhiteSpaceInTheStartOrEndOfToken(string plainMd) { return new Md(plainMd).Render(); diff --git a/Markdown/packages.config b/Markdown/packages.config new file mode 100644 index 000000000..939c1055b --- /dev/null +++ b/Markdown/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file From 0e02fb4e3463c1cdd10fb75bb8578dc352dae9a9 Mon Sep 17 00:00:00 2001 From: Griboedoff Date: Thu, 10 Nov 2016 10:27:58 +0500 Subject: [PATCH 04/17] =?UTF-8?q?=D0=B7=D0=B0=D0=BA=D0=BE=D0=BD=D1=87?= =?UTF-8?q?=D0=B8=D0=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Markdown/HtmlToken.cs | 36 ++++++-- Markdown/Md.cs | 147 +++++++++++++++++++++--------- Markdown/Spec.md | 20 ++-- Markdown/Tag.cs | 40 ++++---- Markdown/Test/HtmlToken_Should.cs | 2 +- Markdown/Test/MD_Should.cs | 51 ++++++++++- 6 files changed, 210 insertions(+), 86 deletions(-) diff --git a/Markdown/HtmlToken.cs b/Markdown/HtmlToken.cs index 3e99d26a5..9cfe3fc97 100644 --- a/Markdown/HtmlToken.cs +++ b/Markdown/HtmlToken.cs @@ -1,25 +1,41 @@ -namespace Markdown +using System.Collections.Generic; +using System.Linq; + +namespace Markdown { public class HtmlToken { - public readonly Tag Tag; + private readonly Tag tag; + private readonly List parsedTokens; private readonly string data; - public bool IsTagged => Tag != Tag.Empty; - public int Length => data.Length; + private bool IsTagged => !tag.Equals(Tag.Empty); + public int Length => parsedTokens.Sum(x => x.Length) + (data ?? "").Length + escapedCharacters + tag.Md.Length * 2; + private readonly int escapedCharacters; - public HtmlToken(Tag tag, string data) + public HtmlToken(Tag tag, string data, int escapedCharacters) { - Tag = tag; + this.tag = tag; this.data = data; + this.escapedCharacters = escapedCharacters; + parsedTokens = new List(); + } + + public HtmlToken(Tag tag, List parsedTokens, int escapedCharacters) + { + this.tag = tag; + this.parsedTokens = parsedTokens; + this.escapedCharacters = escapedCharacters; } - private string InsertInToTags(string body) => IsTagged - ? $"<{Tag.MdToHtml[Tag]}>{body}" - : body; + private string InsertInToTags(string dataToInsert) => IsTagged + ? $"<{tag.Html}>{dataToInsert}" + : dataToInsert; public override string ToString() { - return InsertInToTags(data); + return parsedTokens.Count > 0 + ? InsertInToTags(string.Join("", parsedTokens.Select(token => token.ToString()))) + : InsertInToTags(data); } } } \ No newline at end of file diff --git a/Markdown/Md.cs b/Markdown/Md.cs index 25e70ec1b..5d3930a1c 100644 --- a/Markdown/Md.cs +++ b/Markdown/Md.cs @@ -10,86 +10,133 @@ public class Md private readonly string plainMd; private readonly List root; - private readonly Dictionary> mdTagParserFuncMatch; + private readonly Dictionary> mdTagParserFuncMatch; private readonly Dictionary> validateFunctions; public Md(string plainMd) { this.plainMd = plainMd; root = new List(); - mdTagParserFuncMatch = new Dictionary> + mdTagParserFuncMatch = new Dictionary> { - [Tag.EmMd] = ParseEmToken, - [Tag.Empty] = ParseNoMarkup + [Tag.Em] = ParseEmToken, + [Tag.Empty] = ParseNoMarkup, + [Tag.Strong] = ParseStrongToken }; validateFunctions = new Dictionary> { - [Tag.EmMd] = IsValidEmTag + [Tag.Em] = IsValidEmTag, + [Tag.Strong] = IsValidStrongTag, + [Tag.Empty] = (i, b) => false }; } - #region Parser Funcs - - private HtmlToken ParseEmToken(int index, string alreadyParsed = "") + private HtmlToken ParseEmToken(int index, string alreadyParsed = "", int alreadyEscaped = 0) { if (!IsValidEmTag(index, true)) return ParseNoMarkup(index); + index++; - var tokenData = new StringBuilder(); + var tokenData = new StringBuilder(alreadyParsed); + while (index < plainMd.Length && !IsValidEmTag(index, false)) { - if (plainMd[index] == '_') - { - var tag = ParseTag(index); + var tag = ParseTag(index); - if (tag == Tag.StrongMd) - { - tokenData.Append("__"); - return ParseNoMarkup(index + 2, string.Join("", "_", tokenData.ToString())); - } + if (tag.Equals(Tag.Strong)) + { + tokenData.Append("__"); + index += 2; + continue; } + if (plainMd[index] == '\\') + { index++; + alreadyEscaped++; + } + tokenData.Append(plainMd[index]); index++; } - return new HtmlToken(Tag.EmMd, tokenData.ToString()); + + return index != plainMd.Length + ? new HtmlToken(Tag.Em, tokenData.ToString(), alreadyEscaped) + : new HtmlToken(Tag.Empty, tokenData.Insert(0, '_').ToString(), alreadyEscaped); } - private HtmlToken ParseNoMarkup(int index, string alreadyParsed = "") + private HtmlToken ParseStrongToken(int index, string alreadyParsed = "", int alreadyEscaped = 0) { + if (!IsValidStrongTag(index, true)) + return ParseNoMarkup(index); + + var parsedTokens = new List(); + index += 2; var tokenData = new StringBuilder(alreadyParsed); - while (index < plainMd.Length) + + while (index < plainMd.Length && !IsValidStrongTag(index, false)) { - if (plainMd[index] == '_') - { - var tag = ParseTag(index); + var tag = ParseTag(index); - if (validateFunctions[tag].Invoke(index, true)) + if (Equals(tag, Tag.Em)) + { + parsedTokens.Add(new HtmlToken(Tag.Empty, tokenData.ToString(), alreadyEscaped)); + alreadyEscaped = 0; + tokenData.Clear(); + var htmlToken = ParseEmToken(index); + parsedTokens.Add(htmlToken); + index += htmlToken.Length; + if (index == plainMd.Length) break; } + if (plainMd[index] == '\\') + { index++; + alreadyEscaped++; + } + tokenData.Append(plainMd[index]); index++; } - return new HtmlToken(Tag.Empty, tokenData.ToString()); + + parsedTokens.Add(new HtmlToken(Tag.Empty, tokenData.ToString(), alreadyEscaped)); + return index != plainMd.Length + ? new HtmlToken(Tag.Strong, parsedTokens, 0) + : new HtmlToken(Tag.Empty, tokenData.Insert(0, "__").ToString(), alreadyEscaped); } - #endregion + private HtmlToken ParseNoMarkup(int index, string alreadyParsed = "", int alreadyEscaped = 0) + { + var tokenData = new StringBuilder(alreadyParsed); + var escaped = alreadyEscaped; + while (index < plainMd.Length) + { + var tag = ParseTag(index); - #region Validation Functions + if (validateFunctions[tag].Invoke(index, true)) + break; + if (plainMd[index] == '\\') + { + index++; + escaped++; + } + tokenData.Append(plainMd[index]); + index++; + } + return new HtmlToken(Tag.Empty, tokenData.ToString(), escaped); + } - private bool IsValidEmTag(int tokenIndex, bool isOpenTag) + private bool IsValidEmTag(int tagIndex, bool isOpenTag) { - if (plainMd[tokenIndex] != '_') + if (plainMd[tagIndex] != '_') return false; try { - return !(tokenIndex == plainMd.Length - 1 && isOpenTag) - && plainMd[tokenIndex + (isOpenTag ? 1 : -1)] != ' ' - && !(plainMd[tokenIndex - 1] == '_' || plainMd[tokenIndex + 1] == '_') - && (!char.IsDigit(plainMd[tokenIndex - 1]) && !char.IsDigit(plainMd[tokenIndex + 1])); + return !(tagIndex == plainMd.Length - 1 && isOpenTag) + && plainMd[tagIndex + (isOpenTag ? 1 : -1)] != ' ' + && !(plainMd[tagIndex - 1] == '_' || plainMd[tagIndex + 1] == '_') + && (!char.IsDigit(plainMd[tagIndex - 1]) && !char.IsDigit(plainMd[tagIndex + 1])); } catch (IndexOutOfRangeException) { @@ -97,26 +144,38 @@ private bool IsValidEmTag(int tokenIndex, bool isOpenTag) } } - #endregion + private bool IsValidStrongTag(int tagIndex, bool isOpenTag) + { + if (plainMd[tagIndex] != '_' || !ParseTag(tagIndex).Equals(Tag.Strong)) + return false; + try + { + return !(tagIndex == plainMd.Length - 1 && isOpenTag) + && plainMd[tagIndex + (isOpenTag ? 2 : -1)] != ' '; + } + catch (IndexOutOfRangeException) + { + return true; + } + } private Tag ParseTag(int tagIndex) { + if (plainMd[tagIndex] != '_') + return Tag.Empty; if (tagIndex != plainMd.Length - 1) - return plainMd[tagIndex + 1] == '_' ? Tag.StrongMd : Tag.EmMd; - return Tag.EmMd; + return plainMd[tagIndex + 1] == '_' ? Tag.Strong : Tag.Em; + return Tag.Em; } private void TryParseToHtml() { - for (var i = 0; i < plainMd.Length; i++) + var i = 0; + while (i < plainMd.Length) { - var tag = Tag.Empty; - if (plainMd[i] == '_') - tag = ParseTag(i); - var parsedToken = mdTagParserFuncMatch[tag].Invoke(i, ""); - i += parsedToken.Length + (parsedToken.IsTagged - ? 1 - : -1); + var tag = ParseTag(i); + var parsedToken = mdTagParserFuncMatch[tag].Invoke(i, "", 0); + i += parsedToken.Length; root.Add(parsedToken); } } diff --git a/Markdown/Spec.md b/Markdown/Spec.md index 6da59759b..144ddfb3a 100644 --- a/Markdown/Spec.md +++ b/Markdown/Spec.md @@ -7,7 +7,7 @@ _окруженный с двух сторон_ одинарными символами подчерка `_` должен помещаться в HTML-тег `` -`_abc_` -> `abc` +`_abc_` -> `abc` done __Окруженный двумя символами__ `__` в тег ``. @@ -18,23 +18,24 @@ __Окруженный двумя символами__ `__` в тег `` Любой символ можно экранировать с помошью \ , чтобы он не считался частью разметки. \_Вот это\_, не должно выделиться тегом \. -`_abc\_abc_` -> `abc_abc` +`_abc\_abc_` -> `abc_abc` done 01 Внутри __двойного выделения _одинарное_ тоже__ работает. -`__abc_abc_abc__` -> `abcabcabc` +`__abc_abc_abc__` -> `abcabcabc` Но не наоборот — внутри _одинарного __двойное__ не работает_ и остается просто двойным подчерком. -`_abc__abc__abc_` -> `abc__abc__abc` +`_abc__abc__abc_` -> `abc__abc__abc` done Подчерки внутри текста c цифрами_12_3 не считаются выделением и должны оставаться символами подчерка. `1_2_3` -> `1_2_3` -__непарные _символы не считаются выделением. +Любые `(__, _) или (_, __)` непарные символы не считаются выделением. -`__abc_abc` -> `abc_abc` +`__abc_abc` -> `__abc_abc` +`_abc__abc` -> `_abc__abc` За подчерками, начинающими выделение, должен следовать непробельный символ. Иначе эти_ подчерки_ не считаются выделением и остаются просто символами подчерка. @@ -44,8 +45,9 @@ __непарные _символы не считаются выделением. Подчерки, заканчивающие выделение, должны следовать за непробельным символом. Иначе эти _подчерки _не считаются_ окончанием выделения и остаются просто символами подчерка. -`_abc _` -> `abc _` +`_abc _` -> `_abc _` -При отстутствии закрывающаего тега, выделение продолжается до конца строки. +При отстутствии закрывающаего тега, выделение отсутствует. -`_ab cd` -> `ab cd` +`_ab cd` -> `_ab cd` +`__ab cd` -> `__ab cd` diff --git a/Markdown/Tag.cs b/Markdown/Tag.cs index d26b7aa18..1c686cb80 100644 --- a/Markdown/Tag.cs +++ b/Markdown/Tag.cs @@ -1,31 +1,35 @@ -using System.Collections.Generic; - -namespace Markdown +namespace Markdown { public class Tag { - public static Tag EmMd = new Tag("_"); - public static Tag StrongMd = new Tag("__"); - public static Tag EmHtml = new Tag("em"); - public static Tag StrongHtml = new Tag("strong"); - public static Tag Empty = new Tag(""); - public readonly string Value; + public string Md { get; } + public string Html { get; } + public static readonly Tag Em = new Tag("_", "em"); + public static readonly Tag Strong = new Tag("__", "strong"); + public static readonly Tag Empty = new Tag("", ""); + + + private Tag(string md, string html) - public static readonly Dictionary MdToHtml = new Dictionary { - [EmMd] = EmHtml, - [StrongMd] = StrongHtml, - [Empty] = Empty - }; + Md = md; + Html = html; + } - private Tag(string value) + public override bool Equals(object other) { - Value = value; + var tag = other as Tag; + if (tag == null) + return false; + return string.Equals(Md, tag.Md) && string.Equals(Html, tag.Html); } - public override string ToString() + public override int GetHashCode() { - return Value; + unchecked + { + return ((Md?.GetHashCode() ?? 0) * 397) ^ (Html?.GetHashCode() ?? 0); + } } } } \ No newline at end of file diff --git a/Markdown/Test/HtmlToken_Should.cs b/Markdown/Test/HtmlToken_Should.cs index 04e4304b6..6a3bd69b8 100644 --- a/Markdown/Test/HtmlToken_Should.cs +++ b/Markdown/Test/HtmlToken_Should.cs @@ -8,7 +8,7 @@ public class HtmlToken_Should [Test] public void ShouldInsertDataInToTags_WhenToStringCalls() { - var token = new HtmlToken(Tag.EmHtml, "data"); + var token = new HtmlToken(Tag.Em, "data", 0); token.ToString().Should().Be("data"); } diff --git a/Markdown/Test/MD_Should.cs b/Markdown/Test/MD_Should.cs index fe70c25e6..c2e5f9dd0 100644 --- a/Markdown/Test/MD_Should.cs +++ b/Markdown/Test/MD_Should.cs @@ -13,16 +13,59 @@ public string ParseNoMarkup(string plainMd) [TestCase("_asd_", ExpectedResult = "asd")] [TestCase("_a s d_", ExpectedResult = "a s d")] + [TestCase("_1_2_3_", ExpectedResult = "1_2_3")] [TestCase("_a_ _s d_", ExpectedResult = "a s d")] - public string ParseEmTag_IfTextInOneUnderscore(string plainMd) + [TestCase("_a_ _s d", ExpectedResult = "a _s d")] + [TestCase("_aas__abc__abc_", ExpectedResult = "aas__abc__abc")] + public string ParseEmTagCorrectly(string plainMd) { return new Md(plainMd).Render(); } [TestCase("_ d_", ExpectedResult = "_ d_")] - [TestCase("_a _", ExpectedResult = "a _")] - [TestCase("_aas__",ExpectedResult = "_aas__")] - public string ParseEmTagCorrect_IfWhiteSpaceInTheStartOrEndOfToken(string plainMd) + [TestCase("_a _", ExpectedResult = "_a _")] + [TestCase("__ d__", ExpectedResult = "__ d__")] + [TestCase("__a __", ExpectedResult = "__a __")] + [TestCase("_ abc_abc_", ExpectedResult = "_ abcabc")] + public string ParseTrailingWhitespaceCorrectly(string plainMd) + { + return new Md(plainMd).Render(); + } + + [TestCase("_ab cd", ExpectedResult = "_ab cd")] + [TestCase("__ab cd", ExpectedResult = "__ab cd")] + public string ParseNoMarkup_IfMissingCloseTag(string plainMd) + { + return new Md(plainMd).Render(); + } + + [TestCase(@"_a\_b_", ExpectedResult = "a_b")] + [TestCase(@"a\_b", ExpectedResult = "a_b")] + public string ParseEscapedCorrectly(string plainMd) + { + return new Md(plainMd).Render(); + } + + [TestCase("__abc_abc", ExpectedResult = "__abc_abc")] + [TestCase("__abc_abc_", ExpectedResult = "__abcabc")] + [TestCase("_abc__abc", ExpectedResult = "_abc__abc")] + [TestCase("_abc__abc__", ExpectedResult = "_abc__abc__")] + public string ParseUnpairTags(string plainMd) + { + return new Md(plainMd).Render(); + } + + [TestCase("__abc__", ExpectedResult = "abc")] + [TestCase("__abc_abc_abc__", ExpectedResult = "abcabcabc")] + public string ParseStrongTagCorrectrly(string plainMd) + { + return new Md(plainMd).Render(); + } + + [TestCase("r _i_ r _i_", ExpectedResult = "r i r i")] + [TestCase("r __b__ r __b__", ExpectedResult = "r b r b")] + [TestCase("_i_ __b__ r", ExpectedResult = "i b r")] + public string ParseMixedTagsCorrectrly(string plainMd) { return new Md(plainMd).Render(); } From 9de2d0dec162d8fcff0b62d4cf5ca4ef2cf56dc5 Mon Sep 17 00:00:00 2001 From: Griboedoff Date: Fri, 11 Nov 2016 15:33:01 +0500 Subject: [PATCH 05/17] fix review --- Markdown/Md.cs | 52 +++++++++++++++++++++++++++++++++----------------- 1 file changed, 35 insertions(+), 17 deletions(-) diff --git a/Markdown/Md.cs b/Markdown/Md.cs index 5d3930a1c..e689ada63 100644 --- a/Markdown/Md.cs +++ b/Markdown/Md.cs @@ -8,7 +8,6 @@ namespace Markdown public class Md { private readonly string plainMd; - private readonly List root; private readonly Dictionary> mdTagParserFuncMatch; private readonly Dictionary> validateFunctions; @@ -16,7 +15,6 @@ public class Md public Md(string plainMd) { this.plainMd = plainMd; - root = new List(); mdTagParserFuncMatch = new Dictionary> { [Tag.Em] = ParseEmToken, @@ -80,12 +78,7 @@ private HtmlToken ParseStrongToken(int index, string alreadyParsed = "", int alr if (Equals(tag, Tag.Em)) { - parsedTokens.Add(new HtmlToken(Tag.Empty, tokenData.ToString(), alreadyEscaped)); - alreadyEscaped = 0; - tokenData.Clear(); - var htmlToken = ParseEmToken(index); - parsedTokens.Add(htmlToken); - index += htmlToken.Length; + parsedTokens.Add(ParseEmInStrong(ref index, ref alreadyEscaped, parsedTokens, tokenData)); if (index == plainMd.Length) break; } @@ -106,6 +99,17 @@ private HtmlToken ParseStrongToken(int index, string alreadyParsed = "", int alr : new HtmlToken(Tag.Empty, tokenData.Insert(0, "__").ToString(), alreadyEscaped); } + private HtmlToken ParseEmInStrong(ref int index, ref int alreadyEscaped, + ICollection parsedTokens, StringBuilder tokenData) + { + parsedTokens.Add(new HtmlToken(Tag.Empty, tokenData.ToString(), alreadyEscaped)); + alreadyEscaped = 0; + tokenData.Clear(); + var htmlToken = ParseEmToken(index); + index += htmlToken.Length; + return htmlToken; + } + private HtmlToken ParseNoMarkup(int index, string alreadyParsed = "", int alreadyEscaped = 0) { var tokenData = new StringBuilder(alreadyParsed); @@ -127,16 +131,28 @@ private HtmlToken ParseNoMarkup(int index, string alreadyParsed = "", int alread return new HtmlToken(Tag.Empty, tokenData.ToString(), escaped); } + private bool NotInsideDigits(int tagIndex) + => !char.IsDigit(plainMd[tagIndex - 1]) && !char.IsDigit(plainMd[tagIndex + 1]); + + private bool IsNotStrongTag(int tagIndex) + => !(plainMd[tagIndex - 1] == '_' || plainMd[tagIndex + 1] == '_'); + + private bool NoSpaceNearMdTag(int tagIndex, int tagLength, bool isOpenTag) + => plainMd[tagIndex + (isOpenTag ? tagLength : -1)] != ' '; + + private bool IsNotOpenTagInEndOfString(int tagIndex, int tagLength, bool isOpenTag) + => !(tagIndex == plainMd.Length - tagLength && isOpenTag); + private bool IsValidEmTag(int tagIndex, bool isOpenTag) { if (plainMd[tagIndex] != '_') return false; try { - return !(tagIndex == plainMd.Length - 1 && isOpenTag) - && plainMd[tagIndex + (isOpenTag ? 1 : -1)] != ' ' - && !(plainMd[tagIndex - 1] == '_' || plainMd[tagIndex + 1] == '_') - && (!char.IsDigit(plainMd[tagIndex - 1]) && !char.IsDigit(plainMd[tagIndex + 1])); + return IsNotOpenTagInEndOfString(tagIndex, 1, isOpenTag) + && NoSpaceNearMdTag(tagIndex, 1, isOpenTag) + && IsNotStrongTag(tagIndex) + && NotInsideDigits(tagIndex); } catch (IndexOutOfRangeException) { @@ -150,8 +166,8 @@ private bool IsValidStrongTag(int tagIndex, bool isOpenTag) return false; try { - return !(tagIndex == plainMd.Length - 1 && isOpenTag) - && plainMd[tagIndex + (isOpenTag ? 2 : -1)] != ' '; + return IsNotOpenTagInEndOfString(tagIndex, 2, isOpenTag) + && NoSpaceNearMdTag(tagIndex, 2, isOpenTag); } catch (IndexOutOfRangeException) { @@ -168,9 +184,10 @@ private Tag ParseTag(int tagIndex) return Tag.Em; } - private void TryParseToHtml() + private IEnumerable TryParseToHtml() { var i = 0; + var root = new List(); while (i < plainMd.Length) { var tag = ParseTag(i); @@ -178,12 +195,13 @@ private void TryParseToHtml() i += parsedToken.Length; root.Add(parsedToken); } + return root; } public string Render() { - TryParseToHtml(); - return string.Join("", root.Select(x => x.ToString())); + var htmlTokens = TryParseToHtml(); + return string.Join("", htmlTokens.Select(x => x.ToString())); } } } \ No newline at end of file From 917b8a6704b448059a0135f0480e75dba2553e4b Mon Sep 17 00:00:00 2001 From: Griboedoff Date: Fri, 11 Nov 2016 15:47:43 +0500 Subject: [PATCH 06/17] add test for tags inside --- Markdown/Test/HtmlToken_Should.cs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/Markdown/Test/HtmlToken_Should.cs b/Markdown/Test/HtmlToken_Should.cs index 6a3bd69b8..4eb05377b 100644 --- a/Markdown/Test/HtmlToken_Should.cs +++ b/Markdown/Test/HtmlToken_Should.cs @@ -1,4 +1,5 @@ -using NUnit.Framework; +using System.Collections.Generic; +using NUnit.Framework; using FluentAssertions; namespace Markdown.Test { @@ -12,5 +13,19 @@ public void ShouldInsertDataInToTags_WhenToStringCalls() token.ToString().Should().Be("data"); } + + [Test] + public void ShouldConcatManyTags_WhenHasInsertedTags() + { + var tokenList = new List + { + new HtmlToken(Tag.Em, "italic", 0), + new HtmlToken(Tag.Empty, "empty", 0), + new HtmlToken(Tag.Strong, "bold", 0) + }; + + var token = new HtmlToken(Tag.Strong, tokenList, 0); + token.ToString().Should().Be("italicemptybold"); + } } } \ No newline at end of file From 8d9b3e01bb0798320cf016ea251daffa14741dd1 Mon Sep 17 00:00:00 2001 From: Griboedoff Date: Fri, 11 Nov 2016 17:53:51 +0500 Subject: [PATCH 07/17] fix parseTag --- Markdown/Md.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/Markdown/Md.cs b/Markdown/Md.cs index e689ada63..076b89dd0 100644 --- a/Markdown/Md.cs +++ b/Markdown/Md.cs @@ -177,11 +177,13 @@ private bool IsValidStrongTag(int tagIndex, bool isOpenTag) private Tag ParseTag(int tagIndex) { - if (plainMd[tagIndex] != '_') - return Tag.Empty; - if (tagIndex != plainMd.Length - 1) - return plainMd[tagIndex + 1] == '_' ? Tag.Strong : Tag.Em; - return Tag.Em; + if (plainMd[tagIndex] == '_') + { + if (tagIndex != plainMd.Length - 1) + return plainMd[tagIndex + 1] == '_' ? Tag.Strong : Tag.Em; + return Tag.Em; + } + return Tag.Empty; } private IEnumerable TryParseToHtml() From eb28b3a90686615a861c6f16c2d3cc03d99a9d14 Mon Sep 17 00:00:00 2001 From: Griboedoff Date: Sat, 12 Nov 2016 14:07:29 +0500 Subject: [PATCH 08/17] remove try-catch --- Markdown/Md.cs | 40 ++++++++++++++++---------------------- Markdown/Test/MD_Should.cs | 1 + 2 files changed, 18 insertions(+), 23 deletions(-) diff --git a/Markdown/Md.cs b/Markdown/Md.cs index 076b89dd0..229d5dda3 100644 --- a/Markdown/Md.cs +++ b/Markdown/Md.cs @@ -132,13 +132,21 @@ private HtmlToken ParseNoMarkup(int index, string alreadyParsed = "", int alread } private bool NotInsideDigits(int tagIndex) - => !char.IsDigit(plainMd[tagIndex - 1]) && !char.IsDigit(plainMd[tagIndex + 1]); + { + if (tagIndex + 1 == plainMd.Length || tagIndex - 1 == -1) + return true; + return !char.IsDigit(plainMd[tagIndex - 1]) && !char.IsDigit(plainMd[tagIndex + 1]); + } private bool IsNotStrongTag(int tagIndex) - => !(plainMd[tagIndex - 1] == '_' || plainMd[tagIndex + 1] == '_'); + => !(tagIndex - 1 != -1 && plainMd[tagIndex - 1] == '_' || + tagIndex + 1 != plainMd.Length && plainMd[tagIndex + 1] == '_'); private bool NoSpaceNearMdTag(int tagIndex, int tagLength, bool isOpenTag) - => plainMd[tagIndex + (isOpenTag ? tagLength : -1)] != ' '; + { + var nextIndex = tagIndex + (isOpenTag ? tagLength : -1); + return nextIndex >= 0 && nextIndex < plainMd.Length && plainMd[nextIndex] != ' '; + } private bool IsNotOpenTagInEndOfString(int tagIndex, int tagLength, bool isOpenTag) => !(tagIndex == plainMd.Length - tagLength && isOpenTag); @@ -147,32 +155,18 @@ private bool IsValidEmTag(int tagIndex, bool isOpenTag) { if (plainMd[tagIndex] != '_') return false; - try - { - return IsNotOpenTagInEndOfString(tagIndex, 1, isOpenTag) - && NoSpaceNearMdTag(tagIndex, 1, isOpenTag) - && IsNotStrongTag(tagIndex) - && NotInsideDigits(tagIndex); - } - catch (IndexOutOfRangeException) - { - return true; - } + return IsNotOpenTagInEndOfString(tagIndex, 1, isOpenTag) + && NoSpaceNearMdTag(tagIndex, 1, isOpenTag) + && IsNotStrongTag(tagIndex) + && NotInsideDigits(tagIndex); } private bool IsValidStrongTag(int tagIndex, bool isOpenTag) { if (plainMd[tagIndex] != '_' || !ParseTag(tagIndex).Equals(Tag.Strong)) return false; - try - { - return IsNotOpenTagInEndOfString(tagIndex, 2, isOpenTag) - && NoSpaceNearMdTag(tagIndex, 2, isOpenTag); - } - catch (IndexOutOfRangeException) - { - return true; - } + return IsNotOpenTagInEndOfString(tagIndex, 2, isOpenTag) + && NoSpaceNearMdTag(tagIndex, 2, isOpenTag); } private Tag ParseTag(int tagIndex) diff --git a/Markdown/Test/MD_Should.cs b/Markdown/Test/MD_Should.cs index c2e5f9dd0..b0871bb51 100644 --- a/Markdown/Test/MD_Should.cs +++ b/Markdown/Test/MD_Should.cs @@ -40,6 +40,7 @@ public string ParseNoMarkup_IfMissingCloseTag(string plainMd) } [TestCase(@"_a\_b_", ExpectedResult = "a_b")] + [TestCase(@"__a\_b__", ExpectedResult = "a_b")] [TestCase(@"a\_b", ExpectedResult = "a_b")] public string ParseEscapedCorrectly(string plainMd) { From a637fac7203c279feb62642671beaf0ee585b4ad Mon Sep 17 00:00:00 2001 From: Griboedoff Date: Sat, 12 Nov 2016 14:52:34 +0500 Subject: [PATCH 09/17] add performance test --- Markdown/Tag.cs | 10 +++++--- Markdown/Test/MD_Should.cs | 50 +++++++++++++++++++++++++++++++++++++- 2 files changed, 56 insertions(+), 4 deletions(-) diff --git a/Markdown/Tag.cs b/Markdown/Tag.cs index 1c686cb80..13e6a44a0 100644 --- a/Markdown/Tag.cs +++ b/Markdown/Tag.cs @@ -1,4 +1,7 @@ -namespace Markdown +using System; +using System.Collections.Generic; + +namespace Markdown { public class Tag { @@ -7,15 +10,16 @@ public class Tag public static readonly Tag Em = new Tag("_", "em"); public static readonly Tag Strong = new Tag("__", "strong"); public static readonly Tag Empty = new Tag("", ""); - + private static readonly List Tags = new List {Em, Strong, Empty}; private Tag(string md, string html) - { Md = md; Html = html; } + public static Tag GetRandomTag(Random rnd) => Tags[rnd.Next(Tags.Count)]; + public override bool Equals(object other) { var tag = other as Tag; diff --git a/Markdown/Test/MD_Should.cs b/Markdown/Test/MD_Should.cs index b0871bb51..71458e48f 100644 --- a/Markdown/Test/MD_Should.cs +++ b/Markdown/Test/MD_Should.cs @@ -1,4 +1,11 @@ -using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Linq.Expressions; +using System.Text; +using FluentAssertions; +using NUnit.Framework; namespace Markdown.Test { @@ -70,5 +77,46 @@ public string ParseMixedTagsCorrectrly(string plainMd) { return new Md(plainMd).Render(); } + + private static string GenerateMd(Tag tag, int length) + { + const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + var rnd = new Random(100); + return $@"{tag.Md}{new string( + Enumerable + .Repeat(chars, length) + .Select(s => s[rnd.Next(s.Length)]) + .ToArray())}{tag.Md}"; + } + + [Test] + public void PerformanceTest() + { + var iterationWatch = new Stopwatch(); + var parseWatch = new Stopwatch(); + var md = new StringBuilder(); + var rnd = new Random(10); + for (var i = 0; i < 1000; i++) + md.Append(GenerateMd(Tag.GetRandomTag(rnd), 100000)); + var plainMd = md.ToString(); + Console.WriteLine($"Length = {plainMd.Length}"); + + var c = 0; + iterationWatch.Start(); + for (var i = 0; i < plainMd.Length; i++) + c = rnd.Next(); + iterationWatch.Stop(); + + var parser = new Md(plainMd); + parseWatch.Start(); + var html = parser.Render(); + parseWatch.Stop(); + + Console.WriteLine( + $"iteration elapsed = {iterationWatch.ElapsedMilliseconds}, parse elapsed = {parseWatch.ElapsedMilliseconds}"); + (parseWatch.ElapsedMilliseconds / iterationWatch.ElapsedMilliseconds) + .Should() + .BeLessThan(iterationWatch.ElapsedMilliseconds / 10); + } } } \ No newline at end of file From 01ac99c46aff602808a7c295d3ecfeb92d611928 Mon Sep 17 00:00:00 2001 From: Griboedoff Date: Sat, 19 Nov 2016 11:53:08 +0500 Subject: [PATCH 10/17] start 2 task --- Markdown/Md.cs | 4 +++- Markdown/Test/MD_Should.cs | 2 -- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Markdown/Md.cs b/Markdown/Md.cs index 229d5dda3..cc902b234 100644 --- a/Markdown/Md.cs +++ b/Markdown/Md.cs @@ -11,10 +11,12 @@ public class Md private readonly Dictionary> mdTagParserFuncMatch; private readonly Dictionary> validateFunctions; + private readonly string baseUrl; - public Md(string plainMd) + public Md(string plainMd, string baseUrl = "") { this.plainMd = plainMd; + this.baseUrl = baseUrl; mdTagParserFuncMatch = new Dictionary> { [Tag.Em] = ParseEmToken, diff --git a/Markdown/Test/MD_Should.cs b/Markdown/Test/MD_Should.cs index 71458e48f..671b94456 100644 --- a/Markdown/Test/MD_Should.cs +++ b/Markdown/Test/MD_Should.cs @@ -1,8 +1,6 @@ using System; -using System.Collections.Generic; using System.Diagnostics; using System.Linq; -using System.Linq.Expressions; using System.Text; using FluentAssertions; using NUnit.Framework; From 4749f92ee47ad637103908e1b23ef2b18a189586 Mon Sep 17 00:00:00 2001 From: Griboedoff Date: Tue, 22 Nov 2016 00:17:58 +0500 Subject: [PATCH 11/17] add simple url parsing --- Chess/ChessProblem_Test.cs | 110 ++++++++++++------------- ControlDigit/ControlDigitExtensions.cs | 82 +++++++++--------- Markdown/HtmlToken.cs | 41 --------- Markdown/Markdown.csproj | 10 ++- Markdown/Md.cs | 89 +++++++++++++++----- Markdown/MdParserException.cs | 11 +++ Markdown/Tag.cs | 1 + Markdown/Test/HtmlToken_Should.cs | 12 +-- Markdown/Test/MD_Should.cs | 8 +- Markdown/Tokens/AHtmlToken.cs | 35 ++++++++ Markdown/Tokens/EmHtmlToken.cs | 18 ++++ Markdown/Tokens/EmptyHtmlToken.cs | 17 ++++ Markdown/Tokens/HtmlToken.cs | 44 ++++++++++ Markdown/Tokens/StrongHtmlToken.cs | 18 ++++ 14 files changed, 330 insertions(+), 166 deletions(-) delete mode 100644 Markdown/HtmlToken.cs create mode 100644 Markdown/MdParserException.cs create mode 100644 Markdown/Tokens/AHtmlToken.cs create mode 100644 Markdown/Tokens/EmHtmlToken.cs create mode 100644 Markdown/Tokens/EmptyHtmlToken.cs create mode 100644 Markdown/Tokens/HtmlToken.cs create mode 100644 Markdown/Tokens/StrongHtmlToken.cs diff --git a/Chess/ChessProblem_Test.cs b/Chess/ChessProblem_Test.cs index dd559b3d7..fc3c9beee 100644 --- a/Chess/ChessProblem_Test.cs +++ b/Chess/ChessProblem_Test.cs @@ -1,55 +1,55 @@ -using System; -using System.IO; -using NUnit.Framework; - -namespace Chess -{ - [TestFixture] - public class ChessProblem_Test - { - private static void TestOnFile(string filename) - { - var board = File.ReadAllLines(filename); - ChessProblem.LoadFrom(board); - var expectedAnswer = File.ReadAllText(Path.ChangeExtension(filename, ".ans")).Trim(); - ChessProblem.CalculateChessStatus(); - Assert.AreEqual(expectedAnswer, ChessProblem.ChessStatus.ToString().ToLower(), "Failed test " + filename); - } - - [Test] - public void RepeatedMethodCallDoNotChangeBehaviour() - { - var board = new[] - { - " ", - " ", - " ", - " q ", - " K ", - " Q ", - " ", - " ", - }; - ChessProblem.LoadFrom(board); - ChessProblem.CalculateChessStatus(); - Assert.AreEqual(ChessStatus.Check, ChessProblem.ChessStatus); - - // Now check that internal board modifictions during the first call do not change answer - ChessProblem.CalculateChessStatus(); - Assert.AreEqual(ChessStatus.Check, ChessProblem.ChessStatus); - } - - [Test] - public void FullTests() - { - var dir = TestContext.CurrentContext.TestDirectory; - var testsCount = 0; - foreach (var filename in Directory.GetFiles(Path.Combine(dir, "ChessTests"), "*.in")) - { - TestOnFile(filename); - testsCount++; - } - Console.WriteLine("Tests passed: " + testsCount); - } - } -} \ No newline at end of file +//using System; +//using System.IO; +//using NUnit.Framework; +// +//namespace Chess +//{ +// [TestFixture] +// public class ChessProblem_Test +// { +// private static void TestOnFile(string filename) +// { +// var board = File.ReadAllLines(filename); +// ChessProblem.LoadFrom(board); +// var expectedAnswer = File.ReadAllText(Path.ChangeExtension(filename, ".ans")).Trim(); +// ChessProblem.CalculateChessStatus(); +// Assert.AreEqual(expectedAnswer, ChessProblem.ChessStatus.ToString().ToLower(), "Failed test " + filename); +// } +// +// [Test] +// public void RepeatedMethodCallDoNotChangeBehaviour() +// { +// var board = new[] +// { +// " ", +// " ", +// " ", +// " q ", +// " K ", +// " Q ", +// " ", +// " ", +// }; +// ChessProblem.LoadFrom(board); +// ChessProblem.CalculateChessStatus(); +// Assert.AreEqual(ChessStatus.Check, ChessProblem.ChessStatus); +// +// // Now check that internal board modifictions during the first call do not change answer +// ChessProblem.CalculateChessStatus(); +// Assert.AreEqual(ChessStatus.Check, ChessProblem.ChessStatus); +// } +// +// [Test] +// public void FullTests() +// { +// var dir = TestContext.CurrentContext.TestDirectory; +// var testsCount = 0; +// foreach (var filename in Directory.GetFiles(Path.Combine(dir, "ChessTests"), "*.in")) +// { +// TestOnFile(filename); +// testsCount++; +// } +// Console.WriteLine("Tests passed: " + testsCount); +// } +// } +//} \ No newline at end of file diff --git a/ControlDigit/ControlDigitExtensions.cs b/ControlDigit/ControlDigitExtensions.cs index 807fc087c..d3291bdac 100644 --- a/ControlDigit/ControlDigitExtensions.cs +++ b/ControlDigit/ControlDigitExtensions.cs @@ -32,45 +32,45 @@ public static int ControlDigit2(this long number) } } - [TestFixture] - public class ControlDigitExtensions_Tests - { - [TestCase(0, ExpectedResult = 0)] - [TestCase(1, ExpectedResult = 1)] - [TestCase(2, ExpectedResult = 2)] - [TestCase(9, ExpectedResult = 9)] - [TestCase(10, ExpectedResult = 3)] - [TestCase(15, ExpectedResult = 8)] - [TestCase(17, ExpectedResult = 1)] - [TestCase(18, ExpectedResult = 0)] - public int TestControlDigit(long x) - { - return x.ControlDigit(); - } - - [Test] - public void CompareImplementations() - { - for (long i = 0; i < 100000; i++) - Assert.AreEqual(i.ControlDigit(), i.ControlDigit2()); - } - } - - [TestFixture] - public class ControlDigit_PerformanceTests - { - [Test] - public void TestControlDigitSpeed() - { - var count = 10000000; - var sw = Stopwatch.StartNew(); - for (int i = 0; i < count; i++) - 12345678L.ControlDigit(); - Console.WriteLine("Old " + sw.Elapsed); - sw.Restart(); - for (int i = 0; i < count; i++) - 12345678L.ControlDigit2(); - Console.WriteLine("New " + sw.Elapsed); - } - } +// [TestFixture] +// public class ControlDigitExtensions_Tests +// { +// [TestCase(0, ExpectedResult = 0)] +// [TestCase(1, ExpectedResult = 1)] +// [TestCase(2, ExpectedResult = 2)] +// [TestCase(9, ExpectedResult = 9)] +// [TestCase(10, ExpectedResult = 3)] +// [TestCase(15, ExpectedResult = 8)] +// [TestCase(17, ExpectedResult = 1)] +// [TestCase(18, ExpectedResult = 0)] +// public int TestControlDigit(long x) +// { +// return x.ControlDigit(); +// } +// +// [Test] +// public void CompareImplementations() +// { +// for (long i = 0; i < 100000; i++) +// Assert.AreEqual(i.ControlDigit(), i.ControlDigit2()); +// } +// } +// +// [TestFixture] +// public class ControlDigit_PerformanceTests +// { +// [Test] +// public void TestControlDigitSpeed() +// { +// var count = 10000000; +// var sw = Stopwatch.StartNew(); +// for (int i = 0; i < count; i++) +// 12345678L.ControlDigit(); +// Console.WriteLine("Old " + sw.Elapsed); +// sw.Restart(); +// for (int i = 0; i < count; i++) +// 12345678L.ControlDigit2(); +// Console.WriteLine("New " + sw.Elapsed); +// } +// } } diff --git a/Markdown/HtmlToken.cs b/Markdown/HtmlToken.cs deleted file mode 100644 index 9cfe3fc97..000000000 --- a/Markdown/HtmlToken.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System.Collections.Generic; -using System.Linq; - -namespace Markdown -{ - public class HtmlToken - { - private readonly Tag tag; - private readonly List parsedTokens; - private readonly string data; - private bool IsTagged => !tag.Equals(Tag.Empty); - public int Length => parsedTokens.Sum(x => x.Length) + (data ?? "").Length + escapedCharacters + tag.Md.Length * 2; - private readonly int escapedCharacters; - - public HtmlToken(Tag tag, string data, int escapedCharacters) - { - this.tag = tag; - this.data = data; - this.escapedCharacters = escapedCharacters; - parsedTokens = new List(); - } - - public HtmlToken(Tag tag, List parsedTokens, int escapedCharacters) - { - this.tag = tag; - this.parsedTokens = parsedTokens; - this.escapedCharacters = escapedCharacters; - } - - private string InsertInToTags(string dataToInsert) => IsTagged - ? $"<{tag.Html}>{dataToInsert}" - : dataToInsert; - - public override string ToString() - { - return parsedTokens.Count > 0 - ? InsertInToTags(string.Join("", parsedTokens.Select(token => token.ToString()))) - : InsertInToTags(data); - } - } -} \ No newline at end of file diff --git a/Markdown/Markdown.csproj b/Markdown/Markdown.csproj index bfccc7366..41f5f64c3 100644 --- a/Markdown/Markdown.csproj +++ b/Markdown/Markdown.csproj @@ -39,6 +39,9 @@ ..\packages\FluentAssertions.4.16.0\lib\net45\FluentAssertions.Core.dll + + ..\..\..\..\..\Library\Frameworks\Mono.framework\Versions\4.8.0\lib\mono\4.5-api\Microsoft.CSharp.dll + ..\packages\NUnit.3.5.0\lib\net45\nunit.framework.dll @@ -51,9 +54,14 @@ - + + + + + + diff --git a/Markdown/Md.cs b/Markdown/Md.cs index cc902b234..cf95f9efe 100644 --- a/Markdown/Md.cs +++ b/Markdown/Md.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using Markdown.Tokens; namespace Markdown { @@ -11,17 +12,17 @@ public class Md private readonly Dictionary> mdTagParserFuncMatch; private readonly Dictionary> validateFunctions; - private readonly string baseUrl; - public Md(string plainMd, string baseUrl = "") + + public Md(string plainMd) { this.plainMd = plainMd; - this.baseUrl = baseUrl; mdTagParserFuncMatch = new Dictionary> { [Tag.Em] = ParseEmToken, [Tag.Empty] = ParseNoMarkup, - [Tag.Strong] = ParseStrongToken + [Tag.Strong] = ParseStrongToken, + [Tag.A] = ParseUrl }; validateFunctions = new Dictionary> { @@ -61,8 +62,8 @@ private HtmlToken ParseEmToken(int index, string alreadyParsed = "", int already } return index != plainMd.Length - ? new HtmlToken(Tag.Em, tokenData.ToString(), alreadyEscaped) - : new HtmlToken(Tag.Empty, tokenData.Insert(0, '_').ToString(), alreadyEscaped); + ? (HtmlToken) new EmHtmlToken(tokenData.ToString(), alreadyEscaped) + : new EmptyHtmlToken(tokenData.Insert(0, '_').ToString(), alreadyEscaped); } private HtmlToken ParseStrongToken(int index, string alreadyParsed = "", int alreadyEscaped = 0) @@ -95,16 +96,16 @@ private HtmlToken ParseStrongToken(int index, string alreadyParsed = "", int alr index++; } - parsedTokens.Add(new HtmlToken(Tag.Empty, tokenData.ToString(), alreadyEscaped)); + parsedTokens.Add(new EmptyHtmlToken(tokenData.ToString(), alreadyEscaped)); return index != plainMd.Length - ? new HtmlToken(Tag.Strong, parsedTokens, 0) - : new HtmlToken(Tag.Empty, tokenData.Insert(0, "__").ToString(), alreadyEscaped); + ? (HtmlToken) new StrongHtmlToken(parsedTokens, 0) + : new EmptyHtmlToken(tokenData.Insert(0, "__").ToString(), alreadyEscaped); } private HtmlToken ParseEmInStrong(ref int index, ref int alreadyEscaped, ICollection parsedTokens, StringBuilder tokenData) { - parsedTokens.Add(new HtmlToken(Tag.Empty, tokenData.ToString(), alreadyEscaped)); + parsedTokens.Add(new EmptyHtmlToken(tokenData.ToString(), alreadyEscaped)); alreadyEscaped = 0; tokenData.Clear(); var htmlToken = ParseEmToken(index); @@ -130,7 +131,62 @@ private HtmlToken ParseNoMarkup(int index, string alreadyParsed = "", int alread tokenData.Append(plainMd[index]); index++; } - return new HtmlToken(Tag.Empty, tokenData.ToString(), escaped); + return new EmptyHtmlToken(tokenData.ToString(), escaped); + } + + private HtmlToken ParseUrl(int index, string alreadyParsed = "", int alreadyEscaped = 0) + { + var url = new StringBuilder(); + var returnedValue = ParseInsideBracers(']', index, alreadyEscaped, alreadyParsed); + var escaped = returnedValue.Escaped; + index = returnedValue.Index; + var urlText = returnedValue.Data; + + if (plainMd[index] == '(') + { + returnedValue = ParseInsideBracers(')', index, alreadyEscaped, alreadyParsed); + index = returnedValue.Index; + return new AHtmlToken((string) urlText, returnedValue.Data, escaped + returnedValue.Escaped); + } + + throw new MdParserException($"Can't parse link at index {index}"); + } + + private dynamic ParseInsideBracers(char closeBracer, int index, int escaped, string alreadyParsed) + { + var data = new StringBuilder(alreadyParsed); + index++; + while (index < plainMd.Length && plainMd[index] != closeBracer) + { + if (plainMd[index] == '\\') + { + index++; + escaped++; + } + data.Append(plainMd[index]); + index++; + } + index++; + var dataStr = data.ToString(); + return new + { + Index = index, + Escaped = escaped, + Data = dataStr + }; + } + + private Tag ParseTag(int tagIndex) + { + if (plainMd[tagIndex] == '[') + return Tag.A; + if (plainMd[tagIndex] == '_') + { + if (tagIndex != plainMd.Length - 1) + return plainMd[tagIndex + 1] == '_' ? Tag.Strong : Tag.Em; + return Tag.Em; + } + return Tag.Empty; } private bool NotInsideDigits(int tagIndex) @@ -171,17 +227,6 @@ private bool IsValidStrongTag(int tagIndex, bool isOpenTag) && NoSpaceNearMdTag(tagIndex, 2, isOpenTag); } - private Tag ParseTag(int tagIndex) - { - if (plainMd[tagIndex] == '_') - { - if (tagIndex != plainMd.Length - 1) - return plainMd[tagIndex + 1] == '_' ? Tag.Strong : Tag.Em; - return Tag.Em; - } - return Tag.Empty; - } - private IEnumerable TryParseToHtml() { var i = 0; diff --git a/Markdown/MdParserException.cs b/Markdown/MdParserException.cs new file mode 100644 index 000000000..792c18634 --- /dev/null +++ b/Markdown/MdParserException.cs @@ -0,0 +1,11 @@ +using System; + +namespace Markdown +{ + public class MdParserException : Exception + { + public MdParserException(string message) : base(message) + { + } + } +} \ No newline at end of file diff --git a/Markdown/Tag.cs b/Markdown/Tag.cs index 13e6a44a0..b54f63941 100644 --- a/Markdown/Tag.cs +++ b/Markdown/Tag.cs @@ -10,6 +10,7 @@ public class Tag public static readonly Tag Em = new Tag("_", "em"); public static readonly Tag Strong = new Tag("__", "strong"); public static readonly Tag Empty = new Tag("", ""); + public static readonly Tag A = new Tag("", "a"); private static readonly List Tags = new List {Em, Strong, Empty}; private Tag(string md, string html) diff --git a/Markdown/Test/HtmlToken_Should.cs b/Markdown/Test/HtmlToken_Should.cs index 4eb05377b..f18dac208 100644 --- a/Markdown/Test/HtmlToken_Should.cs +++ b/Markdown/Test/HtmlToken_Should.cs @@ -1,6 +1,8 @@ using System.Collections.Generic; using NUnit.Framework; using FluentAssertions; +using Markdown.Tokens; + namespace Markdown.Test { [TestFixture] @@ -9,7 +11,7 @@ public class HtmlToken_Should [Test] public void ShouldInsertDataInToTags_WhenToStringCalls() { - var token = new HtmlToken(Tag.Em, "data", 0); + var token = new EmHtmlToken("data", 0); token.ToString().Should().Be("data"); } @@ -19,12 +21,12 @@ public void ShouldConcatManyTags_WhenHasInsertedTags() { var tokenList = new List { - new HtmlToken(Tag.Em, "italic", 0), - new HtmlToken(Tag.Empty, "empty", 0), - new HtmlToken(Tag.Strong, "bold", 0) + new EmHtmlToken("italic", 0), + new EmptyHtmlToken("empty", 0), + new StrongHtmlToken("bold", 0) }; - var token = new HtmlToken(Tag.Strong, tokenList, 0); + var token = new StrongHtmlToken(tokenList, 0); token.ToString().Should().Be("italicemptybold"); } } diff --git a/Markdown/Test/MD_Should.cs b/Markdown/Test/MD_Should.cs index 671b94456..83bb36cb9 100644 --- a/Markdown/Test/MD_Should.cs +++ b/Markdown/Test/MD_Should.cs @@ -76,6 +76,12 @@ public string ParseMixedTagsCorrectrly(string plainMd) return new Md(plainMd).Render(); } + [TestCase("[url](www.url.com)", ExpectedResult = "url")] + public string ParseUrlTagCorrectrly(string plainMd) + { + return new Md(plainMd).Render(); + } + private static string GenerateMd(Tag tag, int length) { const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; @@ -107,7 +113,7 @@ public void PerformanceTest() var parser = new Md(plainMd); parseWatch.Start(); - var html = parser.Render(); + parser.Render(); parseWatch.Stop(); Console.WriteLine( diff --git a/Markdown/Tokens/AHtmlToken.cs b/Markdown/Tokens/AHtmlToken.cs new file mode 100644 index 000000000..b6065ed8e --- /dev/null +++ b/Markdown/Tokens/AHtmlToken.cs @@ -0,0 +1,35 @@ +using System.Collections.Generic; + +namespace Markdown.Tokens +{ + public class AHtmlToken : HtmlToken + { + private readonly string url; + private readonly bool isReferece; + public override int Length => Data.Length + url.Length + 4 + escapedCharacters; + public static readonly Dictionary ReferenceUrlsBase = new Dictionary(); + + public AHtmlToken(string data, string url, int escapedCharacters) : base("a", data, escapedCharacters) + { + this.url = url; + this.isReferece = isReferece; + } + + public AHtmlToken(List parsedTokens, int escapedCharacters) + : base("a", parsedTokens, escapedCharacters) + { + } + + public static void AddReferenceUrlBase(string url, string name) + { + ReferenceUrlsBase[name] = url; + } + + public override string ToString() + { + if (!isReferece) + return $"{Data}"; + return ""; + } + } +} \ No newline at end of file diff --git a/Markdown/Tokens/EmHtmlToken.cs b/Markdown/Tokens/EmHtmlToken.cs new file mode 100644 index 000000000..8a5a40a1b --- /dev/null +++ b/Markdown/Tokens/EmHtmlToken.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; + +namespace Markdown.Tokens +{ + public class EmHtmlToken : HtmlToken + { + public override int Length => base.Length + 2; + + public EmHtmlToken(string data, int escapedCharacters) : base("em", data, escapedCharacters) + { + } + + public EmHtmlToken(List parsedTokens, int escapedCharacters) + : base("em", parsedTokens, escapedCharacters) + { + } + } +} \ No newline at end of file diff --git a/Markdown/Tokens/EmptyHtmlToken.cs b/Markdown/Tokens/EmptyHtmlToken.cs new file mode 100644 index 000000000..8f3655817 --- /dev/null +++ b/Markdown/Tokens/EmptyHtmlToken.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; +using System.Linq; + +namespace Markdown.Tokens +{ + public class EmptyHtmlToken : HtmlToken + { + public EmptyHtmlToken(string data, int escapedCharacters) : base("", data, escapedCharacters) + { + } + + public EmptyHtmlToken(List parsedTokens, int escapedCharacters) + : base("", parsedTokens, escapedCharacters) + { + } + } +} \ No newline at end of file diff --git a/Markdown/Tokens/HtmlToken.cs b/Markdown/Tokens/HtmlToken.cs new file mode 100644 index 000000000..4e98ae105 --- /dev/null +++ b/Markdown/Tokens/HtmlToken.cs @@ -0,0 +1,44 @@ +using System.Collections.Generic; +using System.Linq; + +namespace Markdown.Tokens +{ + public abstract class HtmlToken + { + protected readonly string Tag; + protected readonly List ParsedTokens; + protected readonly string Data; + + protected virtual bool IsTagged => !string.IsNullOrEmpty(Tag); + + public virtual int Length => ParsedTokens.Sum(x => x.Length) + (Data ?? "").Length + escapedCharacters; + + protected readonly int escapedCharacters; + + protected HtmlToken(string tag, string data, int escapedCharacters) + { + Tag = tag; + Data = data; + this.escapedCharacters = escapedCharacters; + ParsedTokens = new List(); + } + + protected HtmlToken(string tag, List parsedTokens, int escapedCharacters) + { + Tag = tag; + ParsedTokens = parsedTokens; + this.escapedCharacters = escapedCharacters; + } + + protected virtual string InsertInToTags(string dataToInsert) => IsTagged + ? $"<{Tag}>{dataToInsert}" + : dataToInsert; + + public override string ToString() + { + return ParsedTokens.Count > 0 + ? InsertInToTags(string.Join("", ParsedTokens.Select(token => token.ToString()))) + : InsertInToTags(Data); + } + } +} \ No newline at end of file diff --git a/Markdown/Tokens/StrongHtmlToken.cs b/Markdown/Tokens/StrongHtmlToken.cs new file mode 100644 index 000000000..8ed4ebbcc --- /dev/null +++ b/Markdown/Tokens/StrongHtmlToken.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; + +namespace Markdown.Tokens +{ + public class StrongHtmlToken : HtmlToken + { + public override int Length => base.Length + 4; + + public StrongHtmlToken(string data, int escapedCharacters) : base("strong", data, escapedCharacters) + { + } + + public StrongHtmlToken(List parsedTokens, int escapedCharacters) + : base("strong", parsedTokens, escapedCharacters) + { + } + } +} \ No newline at end of file From 6d74b925383344f5cacf3f84cc0bbc2ab79c8000 Mon Sep 17 00:00:00 2001 From: Griboedoff Date: Tue, 22 Nov 2016 11:54:55 +0500 Subject: [PATCH 12/17] add css and building urls --- Markdown/CssClassInfo.cs | 14 ++++ Markdown/Markdown.csproj | 4 +- Markdown/Md.cs | 105 ++++++++++++++++-------------- Markdown/Tag.cs | 5 +- Markdown/Test/HtmlToken_Should.cs | 4 +- Markdown/Test/MD_Should.cs | 52 ++++++++++++--- Markdown/Tokens/AHtmlToken.cs | 20 ++---- Markdown/Tokens/HtmlToken.cs | 15 +++-- 8 files changed, 139 insertions(+), 80 deletions(-) create mode 100644 Markdown/CssClassInfo.cs diff --git a/Markdown/CssClassInfo.cs b/Markdown/CssClassInfo.cs new file mode 100644 index 000000000..368704822 --- /dev/null +++ b/Markdown/CssClassInfo.cs @@ -0,0 +1,14 @@ +namespace Markdown +{ + public class CssClassInfo + { + public readonly string ClassName; + public readonly string Description; + + public CssClassInfo(string className, string description) + { + Description = description; + ClassName = className; + } + } +} \ No newline at end of file diff --git a/Markdown/Markdown.csproj b/Markdown/Markdown.csproj index 41f5f64c3..59dafea81 100644 --- a/Markdown/Markdown.csproj +++ b/Markdown/Markdown.csproj @@ -10,8 +10,9 @@ Properties Markdown Markdown - v4.5 + v4.6 512 + 6 AnyCPU @@ -53,6 +54,7 @@ + diff --git a/Markdown/Md.cs b/Markdown/Md.cs index cf95f9efe..2087ebe77 100644 --- a/Markdown/Md.cs +++ b/Markdown/Md.cs @@ -9,14 +9,18 @@ namespace Markdown public class Md { private readonly string plainMd; + private readonly string baseUrl; + private readonly CssClassInfo cssClassInfo; private readonly Dictionary> mdTagParserFuncMatch; private readonly Dictionary> validateFunctions; - - public Md(string plainMd) + public Md(string plainMd, string baseUrl = "", CssClassInfo cssClassInfo = null) { this.plainMd = plainMd; + this.baseUrl = baseUrl; + this.cssClassInfo = cssClassInfo; + mdTagParserFuncMatch = new Dictionary> { [Tag.Em] = ParseEmToken, @@ -28,7 +32,8 @@ public Md(string plainMd) { [Tag.Em] = IsValidEmTag, [Tag.Strong] = IsValidStrongTag, - [Tag.Empty] = (i, b) => false + [Tag.Empty] = (i, b) => false, + [Tag.A] = IsValidATag }; } @@ -102,17 +107,6 @@ private HtmlToken ParseStrongToken(int index, string alreadyParsed = "", int alr : new EmptyHtmlToken(tokenData.Insert(0, "__").ToString(), alreadyEscaped); } - private HtmlToken ParseEmInStrong(ref int index, ref int alreadyEscaped, - ICollection parsedTokens, StringBuilder tokenData) - { - parsedTokens.Add(new EmptyHtmlToken(tokenData.ToString(), alreadyEscaped)); - alreadyEscaped = 0; - tokenData.Clear(); - var htmlToken = ParseEmToken(index); - index += htmlToken.Length; - return htmlToken; - } - private HtmlToken ParseNoMarkup(int index, string alreadyParsed = "", int alreadyEscaped = 0) { var tokenData = new StringBuilder(alreadyParsed); @@ -136,23 +130,19 @@ private HtmlToken ParseNoMarkup(int index, string alreadyParsed = "", int alread private HtmlToken ParseUrl(int index, string alreadyParsed = "", int alreadyEscaped = 0) { - var url = new StringBuilder(); var returnedValue = ParseInsideBracers(']', index, alreadyEscaped, alreadyParsed); - var escaped = returnedValue.Escaped; - index = returnedValue.Index; - var urlText = returnedValue.Data; - - if (plainMd[index] == '(') - { - returnedValue = ParseInsideBracers(')', index, alreadyEscaped, alreadyParsed); - index = returnedValue.Index; - return new AHtmlToken((string) urlText, returnedValue.Data, escaped + returnedValue.Escaped); - } - - throw new MdParserException($"Can't parse link at index {index}"); + var escaped = returnedValue.Item2; + index = returnedValue.Item1; + var urlText = returnedValue.Item3; + + if (plainMd[index] != '(') + throw new MdParserException($"Can't parse link at index {index}"); + returnedValue = ParseInsideBracers(')', index, alreadyEscaped, alreadyParsed); + index = returnedValue.Item1; + return new AHtmlToken(urlText, returnedValue.Item3, escaped + returnedValue.Item2, baseUrl); } - private dynamic ParseInsideBracers(char closeBracer, int index, int escaped, string alreadyParsed) + private Tuple ParseInsideBracers(char closeBracer, int index, int escaped, string alreadyParsed) { var data = new StringBuilder(alreadyParsed); index++; @@ -168,25 +158,18 @@ private dynamic ParseInsideBracers(char closeBracer, int index, int escaped, str } index++; var dataStr = data.ToString(); - return new - { - Index = index, - Escaped = escaped, - Data = dataStr - }; + return Tuple.Create(index, escaped, dataStr); } - private Tag ParseTag(int tagIndex) + private HtmlToken ParseEmInStrong(ref int index, ref int alreadyEscaped, + ICollection parsedTokens, StringBuilder tokenData) { - if (plainMd[tagIndex] == '[') - return Tag.A; - if (plainMd[tagIndex] == '_') - { - if (tagIndex != plainMd.Length - 1) - return plainMd[tagIndex + 1] == '_' ? Tag.Strong : Tag.Em; - return Tag.Em; - } - return Tag.Empty; + parsedTokens.Add(new EmptyHtmlToken(tokenData.ToString(), alreadyEscaped)); + alreadyEscaped = 0; + tokenData.Clear(); + var htmlToken = ParseEmToken(index); + index += htmlToken.Length; + return htmlToken; } private bool NotInsideDigits(int tagIndex) @@ -197,8 +180,10 @@ private bool NotInsideDigits(int tagIndex) } private bool IsNotStrongTag(int tagIndex) - => !(tagIndex - 1 != -1 && plainMd[tagIndex - 1] == '_' || - tagIndex + 1 != plainMd.Length && plainMd[tagIndex + 1] == '_'); + { + return !(tagIndex - 1 != -1 && plainMd[tagIndex - 1] == '_' || + tagIndex + 1 != plainMd.Length && plainMd[tagIndex + 1] == '_'); + } private bool NoSpaceNearMdTag(int tagIndex, int tagLength, bool isOpenTag) { @@ -207,7 +192,9 @@ private bool NoSpaceNearMdTag(int tagIndex, int tagLength, bool isOpenTag) } private bool IsNotOpenTagInEndOfString(int tagIndex, int tagLength, bool isOpenTag) - => !(tagIndex == plainMd.Length - tagLength && isOpenTag); + { + return !(tagIndex == plainMd.Length - tagLength && isOpenTag); + } private bool IsValidEmTag(int tagIndex, bool isOpenTag) { @@ -227,6 +214,26 @@ private bool IsValidStrongTag(int tagIndex, bool isOpenTag) && NoSpaceNearMdTag(tagIndex, 2, isOpenTag); } + private bool IsValidATag(int tagIndex, bool isOpenTag) + { + return plainMd[tagIndex] == '['; + } + + private Tag ParseTag(int tagIndex) + { + if (plainMd[tagIndex] == '[') + { + return Tag.A; + } + if (plainMd[tagIndex] == '_') + { + if (tagIndex != plainMd.Length - 1) + return plainMd[tagIndex + 1] == '_' ? Tag.Strong : Tag.Em; + return Tag.Em; + } + return Tag.Empty; + } + private IEnumerable TryParseToHtml() { var i = 0; @@ -244,7 +251,9 @@ private IEnumerable TryParseToHtml() public string Render() { var htmlTokens = TryParseToHtml(); - return string.Join("", htmlTokens.Select(x => x.ToString())); + return string.Join("", + cssClassInfo == null ? "" : cssClassInfo.Description, + string.Join("", htmlTokens.Select(x => x.Render(cssClassInfo)))); } } } \ No newline at end of file diff --git a/Markdown/Tag.cs b/Markdown/Tag.cs index b54f63941..6d1fba496 100644 --- a/Markdown/Tag.cs +++ b/Markdown/Tag.cs @@ -19,7 +19,10 @@ private Tag(string md, string html) Html = html; } - public static Tag GetRandomTag(Random rnd) => Tags[rnd.Next(Tags.Count)]; + public static Tag GetRandomTag(Random rnd) + { + return Tags[rnd.Next(Tags.Count)]; + } public override bool Equals(object other) { diff --git a/Markdown/Test/HtmlToken_Should.cs b/Markdown/Test/HtmlToken_Should.cs index f18dac208..de979fbd4 100644 --- a/Markdown/Test/HtmlToken_Should.cs +++ b/Markdown/Test/HtmlToken_Should.cs @@ -13,7 +13,7 @@ public void ShouldInsertDataInToTags_WhenToStringCalls() { var token = new EmHtmlToken("data", 0); - token.ToString().Should().Be("data"); + token.Render(null).Should().Be("data"); } [Test] @@ -27,7 +27,7 @@ public void ShouldConcatManyTags_WhenHasInsertedTags() }; var token = new StrongHtmlToken(tokenList, 0); - token.ToString().Should().Be("italicemptybold"); + token.Render(null).Should().Be("italicemptybold"); } } } \ No newline at end of file diff --git a/Markdown/Test/MD_Should.cs b/Markdown/Test/MD_Should.cs index 83bb36cb9..bfd1b502e 100644 --- a/Markdown/Test/MD_Should.cs +++ b/Markdown/Test/MD_Should.cs @@ -68,6 +68,33 @@ public string ParseStrongTagCorrectrly(string plainMd) return new Md(plainMd).Render(); } + [TestCase("[url](www.url.com)", "", ExpectedResult = "url")] + [TestCase("[url](/url)", "www.base.com", ExpectedResult = "url")] + [TestCase("[url](www.url.com)\n[url](/url)", "www.base.com", + ExpectedResult = "url\nurl")] + public string ParseUrlTagCorrectrly(string plainMd, string baseUrl) + { + return new Md(plainMd, baseUrl).Render(); + } + + [TestCase("_asd_", "css", "", ExpectedResult = "asd", TestName = "No def")] + [TestCase("_asd_ __qwe__", "css", "", ExpectedResult = "asd qwe", + TestName = "No def, may tags")] + [TestCase("_asd_", "css", "definition", ExpectedResult = "definitionasd", TestName = "Defined") + ] + public string ParseWithDefinedCss(string plainMd, string cssClassName, string cssClassDef) + { + var css = new CssClassInfo(cssClassName, cssClassDef); + + return new Md(plainMd, "", css).Render(); + } + + [TestCase("asd", ExpectedResult = "

asd

")] + public string ParseParagraphsCorrectly(string plainMd) + { + return new Md(plainMd).Render(); + } + [TestCase("r _i_ r _i_", ExpectedResult = "r i r i")] [TestCase("r __b__ r __b__", ExpectedResult = "r b r b")] [TestCase("_i_ __b__ r", ExpectedResult = "i b r")] @@ -76,21 +103,26 @@ public string ParseMixedTagsCorrectrly(string plainMd) return new Md(plainMd).Render(); } - [TestCase("[url](www.url.com)", ExpectedResult = "url")] - public string ParseUrlTagCorrectrly(string plainMd) - { - return new Md(plainMd).Render(); - } private static string GenerateMd(Tag tag, int length) { const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; var rnd = new Random(100); - return $@"{tag.Md}{new string( - Enumerable - .Repeat(chars, length) - .Select(s => s[rnd.Next(s.Length)]) - .ToArray())}{tag.Md}"; + return tag.Equals(Tag.A) + ? $@"[{new string( + Enumerable + .Repeat(chars, length) + .Select(s => s[rnd.Next(s.Length)]) + .ToArray())}]({new string( + Enumerable + .Repeat(chars, length) + .Select(s => s[rnd.Next(s.Length)]) + .ToArray())})" + : $@"{tag.Md}{new string( + Enumerable + .Repeat(chars, length) + .Select(s => s[rnd.Next(s.Length)]) + .ToArray())}{tag.Md}"; } [Test] diff --git a/Markdown/Tokens/AHtmlToken.cs b/Markdown/Tokens/AHtmlToken.cs index b6065ed8e..4bbac40cd 100644 --- a/Markdown/Tokens/AHtmlToken.cs +++ b/Markdown/Tokens/AHtmlToken.cs @@ -5,14 +5,14 @@ namespace Markdown.Tokens public class AHtmlToken : HtmlToken { private readonly string url; - private readonly bool isReferece; + private readonly string baseUrl; + private bool IsReferece => url.StartsWith("/"); public override int Length => Data.Length + url.Length + 4 + escapedCharacters; - public static readonly Dictionary ReferenceUrlsBase = new Dictionary(); - public AHtmlToken(string data, string url, int escapedCharacters) : base("a", data, escapedCharacters) + public AHtmlToken(string data, string url, int escapedCharacters, string baseUrl) : base("a", data, escapedCharacters) { this.url = url; - this.isReferece = isReferece; + this.baseUrl = baseUrl; } public AHtmlToken(List parsedTokens, int escapedCharacters) @@ -20,16 +20,10 @@ public AHtmlToken(List parsedTokens, int escapedCharacters) { } - public static void AddReferenceUrlBase(string url, string name) + public override string Render(CssClassInfo cssClassInfo) { - ReferenceUrlsBase[name] = url; - } - - public override string ToString() - { - if (!isReferece) - return $"{Data}"; - return ""; + var buildedUrl = !IsReferece ? url : string.Join("", baseUrl, url); + return $"{Data}"; } } } \ No newline at end of file diff --git a/Markdown/Tokens/HtmlToken.cs b/Markdown/Tokens/HtmlToken.cs index 4e98ae105..e99094977 100644 --- a/Markdown/Tokens/HtmlToken.cs +++ b/Markdown/Tokens/HtmlToken.cs @@ -30,15 +30,20 @@ protected HtmlToken(string tag, List parsedTokens, int escapedCharact this.escapedCharacters = escapedCharacters; } - protected virtual string InsertInToTags(string dataToInsert) => IsTagged - ? $"<{Tag}>{dataToInsert}" + protected virtual string InsertInToTags(string dataToInsert, CssClassInfo cssClassInfo) => IsTagged + ? $"<{Tag}{GetCssClassDef(cssClassInfo)}>{dataToInsert}" : dataToInsert; - public override string ToString() + protected static string GetCssClassDef(CssClassInfo cssClassInfo) + { + return cssClassInfo == null ? "" : $" class=\"{cssClassInfo.ClassName}\""; + } + + public virtual string Render(CssClassInfo cssClassInfo) { return ParsedTokens.Count > 0 - ? InsertInToTags(string.Join("", ParsedTokens.Select(token => token.ToString()))) - : InsertInToTags(Data); + ? InsertInToTags(string.Join("", ParsedTokens.Select(token => token.Render(cssClassInfo))), cssClassInfo) + : InsertInToTags(Data, cssClassInfo); } } } \ No newline at end of file From afe8c9bbb544ee90dfa768dcf5efbbdf3f832e79 Mon Sep 17 00:00:00 2001 From: Griboedoff Date: Fri, 25 Nov 2016 11:46:19 +0500 Subject: [PATCH 13/17] add paragraphs parsing --- Markdown/Markdown.csproj | 1 + Markdown/Md.cs | 199 +++++++++++++++++++--------------- Markdown/Test/MD_Should.cs | 82 +++++++------- Markdown/Tokens/AHtmlToken.cs | 2 +- Markdown/Tokens/HtmlToken.cs | 8 +- Markdown/Tokens/PHtmlToken.cs | 15 +++ 6 files changed, 179 insertions(+), 128 deletions(-) create mode 100644 Markdown/Tokens/PHtmlToken.cs diff --git a/Markdown/Markdown.csproj b/Markdown/Markdown.csproj index 59dafea81..9666ea32f 100644 --- a/Markdown/Markdown.csproj +++ b/Markdown/Markdown.csproj @@ -63,6 +63,7 @@ + diff --git a/Markdown/Md.cs b/Markdown/Md.cs index 2087ebe77..ecb59b0f7 100644 --- a/Markdown/Md.cs +++ b/Markdown/Md.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.ComTypes; using System.Text; using Markdown.Tokens; @@ -8,46 +10,53 @@ namespace Markdown { public class Md { - private readonly string plainMd; + private readonly string[] plainMd; private readonly string baseUrl; private readonly CssClassInfo cssClassInfo; - private readonly Dictionary> mdTagParserFuncMatch; - private readonly Dictionary> validateFunctions; + private readonly Dictionary> mdTagParserFuncMatch; + + private static readonly Dictionary> ValidateFunctions = new Dictionary + > + { + [Tag.Em] = IsValidEmTag, + [Tag.Strong] = IsValidStrongTag, + [Tag.Empty] = (q, w, e) => false, + [Tag.A] = IsValidATag + }; + + private int currLineIndex; + private string CurrLine => plainMd[currLineIndex]; public Md(string plainMd, string baseUrl = "", CssClassInfo cssClassInfo = null) { - this.plainMd = plainMd; + this.plainMd = plainMd.Replace("\n", "\0\n\0").Split('\0'); this.baseUrl = baseUrl; this.cssClassInfo = cssClassInfo; - mdTagParserFuncMatch = new Dictionary> + mdTagParserFuncMatch = new Dictionary> { [Tag.Em] = ParseEmToken, [Tag.Empty] = ParseNoMarkup, [Tag.Strong] = ParseStrongToken, [Tag.A] = ParseUrl }; - validateFunctions = new Dictionary> - { - [Tag.Em] = IsValidEmTag, - [Tag.Strong] = IsValidStrongTag, - [Tag.Empty] = (i, b) => false, - [Tag.A] = IsValidATag - }; + + + currLineIndex = 0; } - private HtmlToken ParseEmToken(int index, string alreadyParsed = "", int alreadyEscaped = 0) + private static HtmlToken ParseEmToken(string currLine, int index, string alreadyParsed = "", int alreadyEscaped = 0) { - if (!IsValidEmTag(index, true)) - return ParseNoMarkup(index); + if (!IsValidEmTag(currLine, index, true)) + return ParseNoMarkup(currLine, index); index++; var tokenData = new StringBuilder(alreadyParsed); - while (index < plainMd.Length && !IsValidEmTag(index, false)) + while (index < currLine.Length && !IsValidEmTag(currLine, index, false)) { - var tag = ParseTag(index); + var tag = ParseTag(currLine, index); if (tag.Equals(Tag.Strong)) { @@ -56,104 +65,125 @@ private HtmlToken ParseEmToken(int index, string alreadyParsed = "", int already continue; } - if (plainMd[index] == '\\') + if (currLine[index] == '\\') { index++; alreadyEscaped++; } - tokenData.Append(plainMd[index]); + tokenData.Append(currLine[index]); index++; } - return index != plainMd.Length + return index != currLine.Length ? (HtmlToken) new EmHtmlToken(tokenData.ToString(), alreadyEscaped) : new EmptyHtmlToken(tokenData.Insert(0, '_').ToString(), alreadyEscaped); } - private HtmlToken ParseStrongToken(int index, string alreadyParsed = "", int alreadyEscaped = 0) + private static HtmlToken ParseStrongToken(string currLine, int index, string alreadyParsed = "", + int alreadyEscaped = 0) { - if (!IsValidStrongTag(index, true)) - return ParseNoMarkup(index); + if (!IsValidStrongTag(currLine, index, true)) + return ParseNoMarkup(currLine, index); var parsedTokens = new List(); index += 2; var tokenData = new StringBuilder(alreadyParsed); - while (index < plainMd.Length && !IsValidStrongTag(index, false)) + while (index < currLine.Length && !IsValidStrongTag(currLine, index, false)) { - var tag = ParseTag(index); + var tag = ParseTag(currLine, index); if (Equals(tag, Tag.Em)) { - parsedTokens.Add(ParseEmInStrong(ref index, ref alreadyEscaped, parsedTokens, tokenData)); - if (index == plainMd.Length) + parsedTokens.Add(ParseEmInStrong(currLine, ref index, ref alreadyEscaped, parsedTokens, tokenData)); + if (index == currLine.Length) break; } - if (plainMd[index] == '\\') + if (currLine[index] == '\\') { index++; alreadyEscaped++; } - tokenData.Append(plainMd[index]); + tokenData.Append(currLine[index]); index++; } parsedTokens.Add(new EmptyHtmlToken(tokenData.ToString(), alreadyEscaped)); - return index != plainMd.Length + return index != currLine.Length ? (HtmlToken) new StrongHtmlToken(parsedTokens, 0) : new EmptyHtmlToken(tokenData.Insert(0, "__").ToString(), alreadyEscaped); } - private HtmlToken ParseNoMarkup(int index, string alreadyParsed = "", int alreadyEscaped = 0) + private static HtmlToken ParseNoMarkup(string currLine, int index, string alreadyParsed = "", int alreadyEscaped = 0) { var tokenData = new StringBuilder(alreadyParsed); var escaped = alreadyEscaped; - while (index < plainMd.Length) + while (index < currLine.Length) { - var tag = ParseTag(index); + var tag = ParseTag(currLine, index); - if (validateFunctions[tag].Invoke(index, true)) + if (ValidateFunctions[tag].Invoke(currLine, index, true)) break; - if (plainMd[index] == '\\') + if (currLine[index] == '\\') { index++; escaped++; } - tokenData.Append(plainMd[index]); + tokenData.Append(currLine[index]); index++; } return new EmptyHtmlToken(tokenData.ToString(), escaped); } - private HtmlToken ParseUrl(int index, string alreadyParsed = "", int alreadyEscaped = 0) + private HtmlToken ParseUrl(string currLine, int index, string alreadyParsed = "", int alreadyEscaped = 0) { - var returnedValue = ParseInsideBracers(']', index, alreadyEscaped, alreadyParsed); + var returnedValue = ParseInsideBracers(']', index, alreadyEscaped, alreadyParsed, currLine); var escaped = returnedValue.Item2; index = returnedValue.Item1; var urlText = returnedValue.Item3; - if (plainMd[index] != '(') + if (currLine[index] != '(') throw new MdParserException($"Can't parse link at index {index}"); - returnedValue = ParseInsideBracers(')', index, alreadyEscaped, alreadyParsed); - index = returnedValue.Item1; + returnedValue = ParseInsideBracers(')', index, alreadyEscaped, alreadyParsed, currLine); return new AHtmlToken(urlText, returnedValue.Item3, escaped + returnedValue.Item2, baseUrl); } - private Tuple ParseInsideBracers(char closeBracer, int index, int escaped, string alreadyParsed) + private HtmlToken ParseParagraph() + { + var innerTags = new List(); + + while (currLineIndex < plainMd.Length && !string.IsNullOrWhiteSpace(CurrLine)) + { + var i = 0; + while (i < CurrLine.Length) + { + var tag = ParseTag(CurrLine, i); + var parsedToken = mdTagParserFuncMatch[tag].Invoke(CurrLine, i, "", 0); + i += parsedToken.Length; + innerTags.Add(parsedToken); + } + currLineIndex++; + } + + return new PHtmlToken(innerTags); + } + + private static Tuple ParseInsideBracers(char closeBracer, int index, int escaped, + string alreadyParsed, string currLine) { var data = new StringBuilder(alreadyParsed); index++; - while (index < plainMd.Length && plainMd[index] != closeBracer) + while (index < currLine.Length && currLine[index] != closeBracer) { - if (plainMd[index] == '\\') + if (currLine[index] == '\\') { index++; escaped++; } - data.Append(plainMd[index]); + data.Append(currLine[index]); index++; } index++; @@ -161,90 +191,87 @@ private Tuple ParseInsideBracers(char closeBracer, int index, return Tuple.Create(index, escaped, dataStr); } - private HtmlToken ParseEmInStrong(ref int index, ref int alreadyEscaped, + private static HtmlToken ParseEmInStrong(string currLine, ref int index, ref int alreadyEscaped, ICollection parsedTokens, StringBuilder tokenData) { parsedTokens.Add(new EmptyHtmlToken(tokenData.ToString(), alreadyEscaped)); alreadyEscaped = 0; tokenData.Clear(); - var htmlToken = ParseEmToken(index); + var htmlToken = ParseEmToken(currLine, index); index += htmlToken.Length; return htmlToken; } - private bool NotInsideDigits(int tagIndex) + private static bool NotInsideDigits(int tagIndex, string currLine) { - if (tagIndex + 1 == plainMd.Length || tagIndex - 1 == -1) + if (tagIndex + 1 == currLine.Length || tagIndex - 1 == -1) return true; - return !char.IsDigit(plainMd[tagIndex - 1]) && !char.IsDigit(plainMd[tagIndex + 1]); + return !char.IsDigit(currLine[tagIndex - 1]) && !char.IsDigit(currLine[tagIndex + 1]); } - private bool IsNotStrongTag(int tagIndex) + private static bool IsNotStrongTag(int tagIndex, string currLine) { - return !(tagIndex - 1 != -1 && plainMd[tagIndex - 1] == '_' || - tagIndex + 1 != plainMd.Length && plainMd[tagIndex + 1] == '_'); + return !(tagIndex - 1 != -1 && currLine[tagIndex - 1] == '_' || + tagIndex + 1 != currLine.Length && currLine[tagIndex + 1] == '_'); } - private bool NoSpaceNearMdTag(int tagIndex, int tagLength, bool isOpenTag) + private static bool NoSpaceNearMdTag(int tagIndex, int tagLength, bool isOpenTag, string currLine) { var nextIndex = tagIndex + (isOpenTag ? tagLength : -1); - return nextIndex >= 0 && nextIndex < plainMd.Length && plainMd[nextIndex] != ' '; + return nextIndex >= 0 && nextIndex < currLine.Length && currLine[nextIndex] != ' '; } - private bool IsNotOpenTagInEndOfString(int tagIndex, int tagLength, bool isOpenTag) + private static bool IsNotOpenTagInEndOfString(int tagIndex, int tagLength, bool isOpenTag, string currLine) { - return !(tagIndex == plainMd.Length - tagLength && isOpenTag); + return !(tagIndex == currLine.Length - tagLength && isOpenTag); } - private bool IsValidEmTag(int tagIndex, bool isOpenTag) + private static bool IsValidEmTag(string currLine, int tagIndex, bool isOpenTag) { - if (plainMd[tagIndex] != '_') + if (currLine[tagIndex] != '_') return false; - return IsNotOpenTagInEndOfString(tagIndex, 1, isOpenTag) - && NoSpaceNearMdTag(tagIndex, 1, isOpenTag) - && IsNotStrongTag(tagIndex) - && NotInsideDigits(tagIndex); + return IsNotOpenTagInEndOfString(tagIndex, 1, isOpenTag, currLine) + && NoSpaceNearMdTag(tagIndex, 1, isOpenTag, currLine) + && IsNotStrongTag(tagIndex, currLine) + && NotInsideDigits(tagIndex, currLine); } - private bool IsValidStrongTag(int tagIndex, bool isOpenTag) + private static bool IsValidStrongTag(string currLine, int tagIndex, bool isOpenTag) { - if (plainMd[tagIndex] != '_' || !ParseTag(tagIndex).Equals(Tag.Strong)) + if (currLine[tagIndex] != '_' || !ParseTag(currLine, tagIndex).Equals(Tag.Strong)) return false; - return IsNotOpenTagInEndOfString(tagIndex, 2, isOpenTag) - && NoSpaceNearMdTag(tagIndex, 2, isOpenTag); + return IsNotOpenTagInEndOfString(tagIndex, 2, isOpenTag, currLine) + && NoSpaceNearMdTag(tagIndex, 2, isOpenTag, currLine); } - private bool IsValidATag(int tagIndex, bool isOpenTag) + private static bool IsValidATag(string currLine, int tagIndex, bool isOpenTag) { - return plainMd[tagIndex] == '['; + return currLine[tagIndex] == '['; } - private Tag ParseTag(int tagIndex) + private static Tag ParseTag(string currLine, int tagIndex) { - if (plainMd[tagIndex] == '[') - { + if (currLine[tagIndex] == '[') return Tag.A; - } - if (plainMd[tagIndex] == '_') - { - if (tagIndex != plainMd.Length - 1) - return plainMd[tagIndex + 1] == '_' ? Tag.Strong : Tag.Em; - return Tag.Em; - } - return Tag.Empty; + if (currLine[tagIndex] != '_') + return Tag.Empty; + if (tagIndex != currLine.Length - 1) + return currLine[tagIndex + 1] == '_' + ? Tag.Strong + : Tag.Em; + return Tag.Em; } private IEnumerable TryParseToHtml() { - var i = 0; var root = new List(); - while (i < plainMd.Length) + + while (currLineIndex < plainMd.Length) { - var tag = ParseTag(i); - var parsedToken = mdTagParserFuncMatch[tag].Invoke(i, "", 0); - i += parsedToken.Length; - root.Add(parsedToken); + root.Add(ParseParagraph()); + currLineIndex++; } + return root; } diff --git a/Markdown/Test/MD_Should.cs b/Markdown/Test/MD_Should.cs index bfd1b502e..2ebad1c21 100644 --- a/Markdown/Test/MD_Should.cs +++ b/Markdown/Test/MD_Should.cs @@ -10,77 +10,79 @@ namespace Markdown.Test [TestFixture] internal class Md_Should { - [TestCase("qwe asd zxc", ExpectedResult = "qwe asd zxc")] + [TestCase("qwe asd zxc", ExpectedResult = "

qwe asd zxc

")] public string ParseNoMarkup(string plainMd) { return new Md(plainMd).Render(); } - [TestCase("_asd_", ExpectedResult = "asd")] - [TestCase("_a s d_", ExpectedResult = "a s d")] - [TestCase("_1_2_3_", ExpectedResult = "1_2_3")] - [TestCase("_a_ _s d_", ExpectedResult = "a s d")] - [TestCase("_a_ _s d", ExpectedResult = "a _s d")] - [TestCase("_aas__abc__abc_", ExpectedResult = "aas__abc__abc")] + [TestCase("_asd_", ExpectedResult = "

asd

")] + [TestCase("_a s d_", ExpectedResult = "

a s d

")] + [TestCase("_1_2_3_", ExpectedResult = "

1_2_3

")] + [TestCase("_a_ _s d_", ExpectedResult = "

a s d

")] + [TestCase("_a_ _s d", ExpectedResult = "

a _s d

")] + [TestCase("_aas__abc__abc_", ExpectedResult = "

aas__abc__abc

")] public string ParseEmTagCorrectly(string plainMd) { return new Md(plainMd).Render(); } - [TestCase("_ d_", ExpectedResult = "_ d_")] - [TestCase("_a _", ExpectedResult = "_a _")] - [TestCase("__ d__", ExpectedResult = "__ d__")] - [TestCase("__a __", ExpectedResult = "__a __")] - [TestCase("_ abc_abc_", ExpectedResult = "_ abcabc")] + [TestCase("_ d_", ExpectedResult = "

_ d_

")] + [TestCase("_a _", ExpectedResult = "

_a _

")] + [TestCase("__ d__", ExpectedResult = "

__ d__

")] + [TestCase("__a __", ExpectedResult = "

__a __

")] + [TestCase("_ abc_abc_", ExpectedResult = "

_ abcabc

")] public string ParseTrailingWhitespaceCorrectly(string plainMd) { return new Md(plainMd).Render(); } - [TestCase("_ab cd", ExpectedResult = "_ab cd")] - [TestCase("__ab cd", ExpectedResult = "__ab cd")] + [TestCase("_ab cd", ExpectedResult = "

_ab cd

")] + [TestCase("__ab cd", ExpectedResult = "

__ab cd

")] public string ParseNoMarkup_IfMissingCloseTag(string plainMd) { return new Md(plainMd).Render(); } - [TestCase(@"_a\_b_", ExpectedResult = "a_b")] - [TestCase(@"__a\_b__", ExpectedResult = "a_b")] - [TestCase(@"a\_b", ExpectedResult = "a_b")] + [TestCase(@"_a\_b_", ExpectedResult = "

a_b

")] + [TestCase(@"__a\_b__", ExpectedResult = "

a_b

")] + [TestCase(@"a\_b", ExpectedResult = "

a_b

")] public string ParseEscapedCorrectly(string plainMd) { return new Md(plainMd).Render(); } - [TestCase("__abc_abc", ExpectedResult = "__abc_abc")] - [TestCase("__abc_abc_", ExpectedResult = "__abcabc")] - [TestCase("_abc__abc", ExpectedResult = "_abc__abc")] - [TestCase("_abc__abc__", ExpectedResult = "_abc__abc__")] + [TestCase("__abc_abc", ExpectedResult = "

__abc_abc

")] + [TestCase("__abc_abc_", ExpectedResult = "

__abcabc

")] + [TestCase("_abc__abc", ExpectedResult = "

_abc__abc

")] + [TestCase("_abc__abc__", ExpectedResult = "

_abc__abc__

")] public string ParseUnpairTags(string plainMd) { return new Md(plainMd).Render(); } - [TestCase("__abc__", ExpectedResult = "abc")] - [TestCase("__abc_abc_abc__", ExpectedResult = "abcabcabc")] + [TestCase("__abc__", ExpectedResult = "

abc

")] + [TestCase("__abc_abc_abc__", ExpectedResult = "

abcabcabc

")] public string ParseStrongTagCorrectrly(string plainMd) { return new Md(plainMd).Render(); } - [TestCase("[url](www.url.com)", "", ExpectedResult = "url")] - [TestCase("[url](/url)", "www.base.com", ExpectedResult = "url")] + [TestCase("[url](www.url.com)", "", ExpectedResult = "

url

")] + [TestCase("[url](/url)", "www.base.com", ExpectedResult = "

url

")] [TestCase("[url](www.url.com)\n[url](/url)", "www.base.com", - ExpectedResult = "url\nurl")] + ExpectedResult = "

url

url

")] public string ParseUrlTagCorrectrly(string plainMd, string baseUrl) { return new Md(plainMd, baseUrl).Render(); } - [TestCase("_asd_", "css", "", ExpectedResult = "asd", TestName = "No def")] - [TestCase("_asd_ __qwe__", "css", "", ExpectedResult = "asd qwe", + [TestCase("_asd_", "css", "", ExpectedResult = "

asd

", TestName = "No def")] + [TestCase("_asd_ __qwe__", "css", "", + ExpectedResult = "

asd qwe

", TestName = "No def, may tags")] - [TestCase("_asd_", "css", "definition", ExpectedResult = "definitionasd", TestName = "Defined") + [TestCase("_asd_", "css", "definition\n", ExpectedResult = "definition\n

asd

", + TestName = "Defined") ] public string ParseWithDefinedCss(string plainMd, string cssClassName, string cssClassDef) { @@ -95,16 +97,16 @@ public string ParseParagraphsCorrectly(string plainMd) return new Md(plainMd).Render(); } - [TestCase("r _i_ r _i_", ExpectedResult = "r i r i")] - [TestCase("r __b__ r __b__", ExpectedResult = "r b r b")] - [TestCase("_i_ __b__ r", ExpectedResult = "i b r")] + [TestCase("r _i_ r _i_", ExpectedResult = "

r i r i

")] + [TestCase("r __b__ r __b__", ExpectedResult = "

r b r b

")] + [TestCase("_i_ __b__ r", ExpectedResult = "

i b r

")] public string ParseMixedTagsCorrectrly(string plainMd) { return new Md(plainMd).Render(); } - private static string GenerateMd(Tag tag, int length) + private static string GenerateMdTag(Tag tag, int length) { const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; var rnd = new Random(100); @@ -125,16 +127,22 @@ private static string GenerateMd(Tag tag, int length) .ToArray())}{tag.Md}"; } + private static string GenerateMd(Random rnd) + { + var md = new StringBuilder(); + for (var i = 0; i < 1000; i++) + md.Append(GenerateMdTag(Tag.GetRandomTag(rnd), 100000)); + return md.ToString(); + } + [Test] + [Explicit] public void PerformanceTest() { var iterationWatch = new Stopwatch(); var parseWatch = new Stopwatch(); - var md = new StringBuilder(); var rnd = new Random(10); - for (var i = 0; i < 1000; i++) - md.Append(GenerateMd(Tag.GetRandomTag(rnd), 100000)); - var plainMd = md.ToString(); + var plainMd = GenerateMd(rnd); Console.WriteLine($"Length = {plainMd.Length}"); var c = 0; diff --git a/Markdown/Tokens/AHtmlToken.cs b/Markdown/Tokens/AHtmlToken.cs index 4bbac40cd..ecb40b0dd 100644 --- a/Markdown/Tokens/AHtmlToken.cs +++ b/Markdown/Tokens/AHtmlToken.cs @@ -7,7 +7,7 @@ public class AHtmlToken : HtmlToken private readonly string url; private readonly string baseUrl; private bool IsReferece => url.StartsWith("/"); - public override int Length => Data.Length + url.Length + 4 + escapedCharacters; + public override int Length => Data.Length + url.Length + 4 + EscapedCharacters; public AHtmlToken(string data, string url, int escapedCharacters, string baseUrl) : base("a", data, escapedCharacters) { diff --git a/Markdown/Tokens/HtmlToken.cs b/Markdown/Tokens/HtmlToken.cs index e99094977..0646cb172 100644 --- a/Markdown/Tokens/HtmlToken.cs +++ b/Markdown/Tokens/HtmlToken.cs @@ -11,15 +11,15 @@ public abstract class HtmlToken protected virtual bool IsTagged => !string.IsNullOrEmpty(Tag); - public virtual int Length => ParsedTokens.Sum(x => x.Length) + (Data ?? "").Length + escapedCharacters; + public virtual int Length => ParsedTokens.Sum(x => x.Length) + (Data ?? "").Length + EscapedCharacters; - protected readonly int escapedCharacters; + protected readonly int EscapedCharacters; protected HtmlToken(string tag, string data, int escapedCharacters) { Tag = tag; Data = data; - this.escapedCharacters = escapedCharacters; + this.EscapedCharacters = escapedCharacters; ParsedTokens = new List(); } @@ -27,7 +27,7 @@ protected HtmlToken(string tag, List parsedTokens, int escapedCharact { Tag = tag; ParsedTokens = parsedTokens; - this.escapedCharacters = escapedCharacters; + this.EscapedCharacters = escapedCharacters; } protected virtual string InsertInToTags(string dataToInsert, CssClassInfo cssClassInfo) => IsTagged diff --git a/Markdown/Tokens/PHtmlToken.cs b/Markdown/Tokens/PHtmlToken.cs new file mode 100644 index 000000000..4f09f0c1c --- /dev/null +++ b/Markdown/Tokens/PHtmlToken.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; + +namespace Markdown.Tokens +{ + public class PHtmlToken : HtmlToken + { + public PHtmlToken(string data) : base("p", data, 0) + { + } + + public PHtmlToken(List parsedTokens) : base("p", parsedTokens, 0) + { + } + } +} \ No newline at end of file From aabcc1517c55238c466a05e165135eb0f714f5ab Mon Sep 17 00:00:00 2001 From: Griboedoff Date: Fri, 25 Nov 2016 13:22:09 +0500 Subject: [PATCH 14/17] add header parsing --- Markdown/LineType.cs | 8 +++++ Markdown/Markdown.csproj | 2 ++ Markdown/Md.cs | 65 +++++++++++++++++++++++++---------- Markdown/Tag.cs | 1 + Markdown/Test/MD_Should.cs | 18 +++++++--- Markdown/Tokens/HHtmlToken.cs | 20 +++++++++++ Markdown/Tokens/HtmlToken.cs | 2 +- 7 files changed, 92 insertions(+), 24 deletions(-) create mode 100644 Markdown/LineType.cs create mode 100644 Markdown/Tokens/HHtmlToken.cs diff --git a/Markdown/LineType.cs b/Markdown/LineType.cs new file mode 100644 index 000000000..075c92964 --- /dev/null +++ b/Markdown/LineType.cs @@ -0,0 +1,8 @@ +namespace Markdown +{ + public enum LineType + { + Header, + Simple + } +} \ No newline at end of file diff --git a/Markdown/Markdown.csproj b/Markdown/Markdown.csproj index 9666ea32f..4d6ea6a30 100644 --- a/Markdown/Markdown.csproj +++ b/Markdown/Markdown.csproj @@ -55,6 +55,7 @@
+ @@ -62,6 +63,7 @@ + diff --git a/Markdown/Md.cs b/Markdown/Md.cs index ecb59b0f7..eb9f99943 100644 --- a/Markdown/Md.cs +++ b/Markdown/Md.cs @@ -1,8 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Runtime.InteropServices; -using System.Runtime.InteropServices.ComTypes; using System.Text; using Markdown.Tokens; @@ -14,7 +12,7 @@ public class Md private readonly string baseUrl; private readonly CssClassInfo cssClassInfo; - private readonly Dictionary> mdTagParserFuncMatch; + private readonly Dictionary> stringTagParserFuncMatch; private static readonly Dictionary> ValidateFunctions = new Dictionary > @@ -25,6 +23,8 @@ public class Md [Tag.A] = IsValidATag }; + private readonly Dictionary> lineTagParserFuncMatch; + private int currLineIndex; private string CurrLine => plainMd[currLineIndex]; @@ -34,19 +34,34 @@ public Md(string plainMd, string baseUrl = "", CssClassInfo cssClassInfo = null) this.baseUrl = baseUrl; this.cssClassInfo = cssClassInfo; - mdTagParserFuncMatch = new Dictionary> + stringTagParserFuncMatch = new Dictionary> { - [Tag.Em] = ParseEmToken, + [Tag.Em] = ParseItalic, [Tag.Empty] = ParseNoMarkup, - [Tag.Strong] = ParseStrongToken, - [Tag.A] = ParseUrl + [Tag.Strong] = ParseBold, + [Tag.A] = ParseUrl, }; + lineTagParserFuncMatch = new Dictionary> + { + [LineType.Header] = ParseHeader, + [LineType.Simple] = ParseParagraph + }; currLineIndex = 0; } - private static HtmlToken ParseEmToken(string currLine, int index, string alreadyParsed = "", int alreadyEscaped = 0) + private HtmlToken ParseHeader() + { + var headerText = CurrLine.Replace("#", ""); + headerText = headerText.Replace("\\", ""); + + var headerImportance = CurrLine.Length - headerText.Length; + + return new HHtmlToken(headerText, headerImportance); + } + + private static HtmlToken ParseItalic(string currLine, int index, string alreadyParsed = "", int alreadyEscaped = 0) { if (!IsValidEmTag(currLine, index, true)) return ParseNoMarkup(currLine, index); @@ -80,7 +95,7 @@ private static HtmlToken ParseEmToken(string currLine, int index, string already : new EmptyHtmlToken(tokenData.Insert(0, '_').ToString(), alreadyEscaped); } - private static HtmlToken ParseStrongToken(string currLine, int index, string alreadyParsed = "", + private static HtmlToken ParseBold(string currLine, int index, string alreadyParsed = "", int alreadyEscaped = 0) { if (!IsValidStrongTag(currLine, index, true)) @@ -155,13 +170,15 @@ private HtmlToken ParseParagraph() { var innerTags = new List(); - while (currLineIndex < plainMd.Length && !string.IsNullOrWhiteSpace(CurrLine)) + while (currLineIndex < plainMd.Length + && !string.IsNullOrWhiteSpace(CurrLine) + && GetLineTypeTag(CurrLine) == LineType.Simple) { var i = 0; while (i < CurrLine.Length) { var tag = ParseTag(CurrLine, i); - var parsedToken = mdTagParserFuncMatch[tag].Invoke(CurrLine, i, "", 0); + var parsedToken = stringTagParserFuncMatch[tag].Invoke(CurrLine, i, "", 0); i += parsedToken.Length; innerTags.Add(parsedToken); } @@ -197,7 +214,7 @@ private static HtmlToken ParseEmInStrong(string currLine, ref int index, ref int parsedTokens.Add(new EmptyHtmlToken(tokenData.ToString(), alreadyEscaped)); alreadyEscaped = 0; tokenData.Clear(); - var htmlToken = ParseEmToken(currLine, index); + var htmlToken = ParseItalic(currLine, index); index += htmlToken.Length; return htmlToken; } @@ -255,11 +272,18 @@ private static Tag ParseTag(string currLine, int tagIndex) return Tag.A; if (currLine[tagIndex] != '_') return Tag.Empty; - if (tagIndex != currLine.Length - 1) - return currLine[tagIndex + 1] == '_' - ? Tag.Strong - : Tag.Em; - return Tag.Em; + if (tagIndex == currLine.Length - 1) + return Tag.Em; + return currLine[tagIndex + 1] == '_' + ? Tag.Strong + : Tag.Em; + } + + private static LineType GetLineTypeTag(string currLine) + { + if (currLine.StartsWith("#")) + return LineType.Header; + return LineType.Simple; } private IEnumerable TryParseToHtml() @@ -268,8 +292,11 @@ private IEnumerable TryParseToHtml() while (currLineIndex < plainMd.Length) { - root.Add(ParseParagraph()); - currLineIndex++; + var htmlToken = lineTagParserFuncMatch[GetLineTypeTag(CurrLine)].Invoke(); + root.Add(htmlToken); + do + currLineIndex++; + while (currLineIndex < plainMd.Length && string.IsNullOrWhiteSpace(CurrLine)); } return root; diff --git a/Markdown/Tag.cs b/Markdown/Tag.cs index 6d1fba496..84d95effa 100644 --- a/Markdown/Tag.cs +++ b/Markdown/Tag.cs @@ -11,6 +11,7 @@ public class Tag public static readonly Tag Strong = new Tag("__", "strong"); public static readonly Tag Empty = new Tag("", ""); public static readonly Tag A = new Tag("", "a"); + public static readonly Tag H = new Tag("#", "h"); private static readonly List Tags = new List {Em, Strong, Empty}; private Tag(string md, string html) diff --git a/Markdown/Test/MD_Should.cs b/Markdown/Test/MD_Should.cs index 2ebad1c21..e1848e913 100644 --- a/Markdown/Test/MD_Should.cs +++ b/Markdown/Test/MD_Should.cs @@ -77,11 +77,13 @@ public string ParseUrlTagCorrectrly(string plainMd, string baseUrl) return new Md(plainMd, baseUrl).Render(); } - [TestCase("_asd_", "css", "", ExpectedResult = "

asd

", TestName = "No def")] + [TestCase("_asd_", "css", "", ExpectedResult = "

asd

", TestName = "No def") + ] [TestCase("_asd_ __qwe__", "css", "", ExpectedResult = "

asd qwe

", TestName = "No def, may tags")] - [TestCase("_asd_", "css", "definition\n", ExpectedResult = "definition\n

asd

", + [TestCase("_asd_", "css", "definition\n", + ExpectedResult = "definition\n

asd

", TestName = "Defined") ] public string ParseWithDefinedCss(string plainMd, string cssClassName, string cssClassDef) @@ -92,11 +94,13 @@ public string ParseWithDefinedCss(string plainMd, string cssClassName, string cs } [TestCase("asd", ExpectedResult = "

asd

")] + [TestCase("q\nb\nc", ExpectedResult = "

q

b

c

")] public string ParseParagraphsCorrectly(string plainMd) { return new Md(plainMd).Render(); } + [TestCase("r _i_ r _i_", ExpectedResult = "

r i r i

")] [TestCase("r __b__ r __b__", ExpectedResult = "

r b r b

")] [TestCase("_i_ __b__ r", ExpectedResult = "

i b r

")] @@ -105,6 +109,12 @@ public string ParseMixedTagsCorrectrly(string plainMd) return new Md(plainMd).Render(); } + [TestCase("##qwe", ExpectedResult = "

qwe

")] + [TestCase("qwe\n##qwe\nqwe", ExpectedResult = "

qwe

qwe

qwe

")] + public string ParseHeaderCorrectly(string plainMd) + { + return new Md(plainMd).Render(); + } private static string GenerateMdTag(Tag tag, int length) { @@ -145,10 +155,10 @@ public void PerformanceTest() var plainMd = GenerateMd(rnd); Console.WriteLine($"Length = {plainMd.Length}"); - var c = 0; iterationWatch.Start(); for (var i = 0; i < plainMd.Length; i++) - c = rnd.Next(); + // ReSharper disable once ReturnValueOfPureMethodIsNotUsed + rnd.Next(); iterationWatch.Stop(); var parser = new Md(plainMd); diff --git a/Markdown/Tokens/HHtmlToken.cs b/Markdown/Tokens/HHtmlToken.cs new file mode 100644 index 000000000..9ee6bcf9e --- /dev/null +++ b/Markdown/Tokens/HHtmlToken.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; + +namespace Markdown.Tokens +{ + public class HHtmlToken : HtmlToken + + { + private readonly int headerImportance; + + public HHtmlToken(string data, int headerImportance) : base($"h{headerImportance}", data, 0) + { + this.headerImportance = headerImportance; + } + + public HHtmlToken(List parsedTokens, int headerImportance) : base($"h{headerImportance}", parsedTokens, 0) + { + this.headerImportance = headerImportance; + } + } +} \ No newline at end of file diff --git a/Markdown/Tokens/HtmlToken.cs b/Markdown/Tokens/HtmlToken.cs index 0646cb172..5704a3a36 100644 --- a/Markdown/Tokens/HtmlToken.cs +++ b/Markdown/Tokens/HtmlToken.cs @@ -19,7 +19,7 @@ protected HtmlToken(string tag, string data, int escapedCharacters) { Tag = tag; Data = data; - this.EscapedCharacters = escapedCharacters; + EscapedCharacters = escapedCharacters; ParsedTokens = new List(); } From b4292c33c47398910341f9c12074558c78203892 Mon Sep 17 00:00:00 2001 From: Griboedoff Date: Fri, 25 Nov 2016 14:23:52 +0500 Subject: [PATCH 15/17] parce code blocks fix bug in paragraphs --- Markdown/LineType.cs | 1 + Markdown/Markdown.csproj | 1 + Markdown/Md.cs | 73 ++++++++++++++++++++------------ Markdown/Test/MD_Should.cs | 14 ++++-- Markdown/Tokens/CodeHtmlToken.cs | 20 +++++++++ Markdown/Tokens/HtmlToken.cs | 16 +++++-- Markdown/Tokens/PHtmlToken.cs | 2 + 7 files changed, 94 insertions(+), 33 deletions(-) create mode 100644 Markdown/Tokens/CodeHtmlToken.cs diff --git a/Markdown/LineType.cs b/Markdown/LineType.cs index 075c92964..614e7bd43 100644 --- a/Markdown/LineType.cs +++ b/Markdown/LineType.cs @@ -3,6 +3,7 @@ public enum LineType { Header, + CodeBlock, Simple } } \ No newline at end of file diff --git a/Markdown/Markdown.csproj b/Markdown/Markdown.csproj index 4d6ea6a30..eb5a95a6d 100644 --- a/Markdown/Markdown.csproj +++ b/Markdown/Markdown.csproj @@ -61,6 +61,7 @@ + diff --git a/Markdown/Md.cs b/Markdown/Md.cs index eb9f99943..c2aa3d15f 100644 --- a/Markdown/Md.cs +++ b/Markdown/Md.cs @@ -27,10 +27,11 @@ public class Md private int currLineIndex; private string CurrLine => plainMd[currLineIndex]; + private bool IsInPlainMd => currLineIndex < plainMd.Length; public Md(string plainMd, string baseUrl = "", CssClassInfo cssClassInfo = null) { - this.plainMd = plainMd.Replace("\n", "\0\n\0").Split('\0'); + this.plainMd = plainMd.Split('\n'); this.baseUrl = baseUrl; this.cssClassInfo = cssClassInfo; @@ -45,7 +46,8 @@ public Md(string plainMd, string baseUrl = "", CssClassInfo cssClassInfo = null) lineTagParserFuncMatch = new Dictionary> { [LineType.Header] = ParseHeader, - [LineType.Simple] = ParseParagraph + [LineType.Simple] = ParseParagraph, + [LineType.CodeBlock] = ParseCodeBlock }; currLineIndex = 0; @@ -58,9 +60,49 @@ private HtmlToken ParseHeader() var headerImportance = CurrLine.Length - headerText.Length; + currLineIndex++; return new HHtmlToken(headerText, headerImportance); } + private HtmlToken ParseCodeBlock() + { + var builder = new StringBuilder(); + + while (IsInPlainMd && GetLineTypeTag(CurrLine) == LineType.CodeBlock) + { + builder.Append(CurrLine.Substring(CurrLine.StartsWith("\t") ? 1 : 4)); + builder.Append("\n"); + currLineIndex++; + + } + + builder.Remove(builder.Length - 1, 1); + + return new CodeHtmlToken(builder.ToString()); + } + + private HtmlToken ParseParagraph() + { + var innerTags = new List(); + + while (IsInPlainMd && !string.IsNullOrWhiteSpace(CurrLine) && GetLineTypeTag(CurrLine) == LineType.Simple) + { + if (innerTags.Count != 0) + innerTags.Add(new EmptyHtmlToken("\n", 0)); + var i = 0; + while (i < CurrLine.Length) + { + var tag = ParseTag(CurrLine, i); + var parsedToken = stringTagParserFuncMatch[tag].Invoke(CurrLine, i, "", 0); + i += parsedToken.Length; + innerTags.Add(parsedToken); + } + currLineIndex++; + } + + return new PHtmlToken(innerTags); + } + private static HtmlToken ParseItalic(string currLine, int index, string alreadyParsed = "", int alreadyEscaped = 0) { if (!IsValidEmTag(currLine, index, true)) @@ -166,28 +208,6 @@ private HtmlToken ParseUrl(string currLine, int index, string alreadyParsed = "" return new AHtmlToken(urlText, returnedValue.Item3, escaped + returnedValue.Item2, baseUrl); } - private HtmlToken ParseParagraph() - { - var innerTags = new List(); - - while (currLineIndex < plainMd.Length - && !string.IsNullOrWhiteSpace(CurrLine) - && GetLineTypeTag(CurrLine) == LineType.Simple) - { - var i = 0; - while (i < CurrLine.Length) - { - var tag = ParseTag(CurrLine, i); - var parsedToken = stringTagParserFuncMatch[tag].Invoke(CurrLine, i, "", 0); - i += parsedToken.Length; - innerTags.Add(parsedToken); - } - currLineIndex++; - } - - return new PHtmlToken(innerTags); - } - private static Tuple ParseInsideBracers(char closeBracer, int index, int escaped, string alreadyParsed, string currLine) { @@ -283,6 +303,8 @@ private static LineType GetLineTypeTag(string currLine) { if (currLine.StartsWith("#")) return LineType.Header; + if (currLine.StartsWith(" ") || currLine.StartsWith("\t")) + return LineType.CodeBlock; return LineType.Simple; } @@ -294,9 +316,8 @@ private IEnumerable TryParseToHtml() { var htmlToken = lineTagParserFuncMatch[GetLineTypeTag(CurrLine)].Invoke(); root.Add(htmlToken); - do + while (currLineIndex < plainMd.Length && string.IsNullOrWhiteSpace(CurrLine) && string.IsNullOrEmpty(CurrLine)) currLineIndex++; - while (currLineIndex < plainMd.Length && string.IsNullOrWhiteSpace(CurrLine)); } return root; diff --git a/Markdown/Test/MD_Should.cs b/Markdown/Test/MD_Should.cs index e1848e913..0e6538ead 100644 --- a/Markdown/Test/MD_Should.cs +++ b/Markdown/Test/MD_Should.cs @@ -71,7 +71,7 @@ public string ParseStrongTagCorrectrly(string plainMd) [TestCase("[url](www.url.com)", "", ExpectedResult = "

url

")] [TestCase("[url](/url)", "www.base.com", ExpectedResult = "

url

")] [TestCase("[url](www.url.com)\n[url](/url)", "www.base.com", - ExpectedResult = "

url

url

")] + ExpectedResult = "

url\nurl

")] public string ParseUrlTagCorrectrly(string plainMd, string baseUrl) { return new Md(plainMd, baseUrl).Render(); @@ -94,13 +94,13 @@ public string ParseWithDefinedCss(string plainMd, string cssClassName, string cs } [TestCase("asd", ExpectedResult = "

asd

")] - [TestCase("q\nb\nc", ExpectedResult = "

q

b

c

")] + [TestCase("q\nb\nc", ExpectedResult = "

q\nb\nc

")] + [TestCase("q\n\nb", ExpectedResult = "

q

b

")] public string ParseParagraphsCorrectly(string plainMd) { return new Md(plainMd).Render(); } - [TestCase("r _i_ r _i_", ExpectedResult = "

r i r i

")] [TestCase("r __b__ r __b__", ExpectedResult = "

r b r b

")] [TestCase("_i_ __b__ r", ExpectedResult = "

i b r

")] @@ -116,6 +116,14 @@ public string ParseHeaderCorrectly(string plainMd) return new Md(plainMd).Render(); } + [TestCase(" This is a code block.", ExpectedResult = "
This is a code block.
")] + [TestCase("Here is an example of AppleScript:\n\ttell application \"Foo\"\n\t\tbeep\n\tend tell", + ExpectedResult = "

Here is an example of AppleScript:

tell application \"Foo\"\n\tbeep\nend tell
")] + public string ParseCodeBlocksCorrectly(string plainMd) + { + return new Md(plainMd).Render(); + } + private static string GenerateMdTag(Tag tag, int length) { const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; diff --git a/Markdown/Tokens/CodeHtmlToken.cs b/Markdown/Tokens/CodeHtmlToken.cs new file mode 100644 index 000000000..924046cdd --- /dev/null +++ b/Markdown/Tokens/CodeHtmlToken.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; + +namespace Markdown.Tokens +{ + public class CodeHtmlToken : HtmlToken + { + public CodeHtmlToken(string data) : base("code", data, 0) + { + } + + public CodeHtmlToken(List parsedTokens) : base("code", parsedTokens, 0) + { + } + + public override string Render(CssClassInfo cssClassInfo) + { + return InsertInToTags("pre", InsertInToTags(Data, cssClassInfo), cssClassInfo); + } + } +} \ No newline at end of file diff --git a/Markdown/Tokens/HtmlToken.cs b/Markdown/Tokens/HtmlToken.cs index 5704a3a36..cd15b314c 100644 --- a/Markdown/Tokens/HtmlToken.cs +++ b/Markdown/Tokens/HtmlToken.cs @@ -27,12 +27,20 @@ protected HtmlToken(string tag, List parsedTokens, int escapedCharact { Tag = tag; ParsedTokens = parsedTokens; - this.EscapedCharacters = escapedCharacters; + EscapedCharacters = escapedCharacters; + } + + protected virtual string InsertInToTags(string dataToInsert, CssClassInfo cssClassInfo) + { + return IsTagged + ? InsertInToTags(Tag, dataToInsert, cssClassInfo) + : dataToInsert; } - protected virtual string InsertInToTags(string dataToInsert, CssClassInfo cssClassInfo) => IsTagged - ? $"<{Tag}{GetCssClassDef(cssClassInfo)}>{dataToInsert}" - : dataToInsert; + protected virtual string InsertInToTags(string tag, string dataToInsert, CssClassInfo cssClassInfo) + { + return $"<{tag}{GetCssClassDef(cssClassInfo)}>{dataToInsert}"; + } protected static string GetCssClassDef(CssClassInfo cssClassInfo) { diff --git a/Markdown/Tokens/PHtmlToken.cs b/Markdown/Tokens/PHtmlToken.cs index 4f09f0c1c..6d9bf5254 100644 --- a/Markdown/Tokens/PHtmlToken.cs +++ b/Markdown/Tokens/PHtmlToken.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; namespace Markdown.Tokens { @@ -11,5 +12,6 @@ public PHtmlToken(string data) : base("p", data, 0) public PHtmlToken(List parsedTokens) : base("p", parsedTokens, 0) { } + } } \ No newline at end of file From 05b8c3539242a26120993dc32201662bf1ec1ca2 Mon Sep 17 00:00:00 2001 From: Griboedoff Date: Sat, 26 Nov 2016 14:23:00 +0500 Subject: [PATCH 16/17] add list parsing --- Markdown/LineType.cs | 1 + Markdown/Markdown.csproj | 2 ++ Markdown/Md.cs | 47 ++++++++++++++++++++++--- Markdown/Test/MD_Should.cs | 28 ++++++++++++--- Markdown/Tokens/ListItemHtmlToken.cs | 16 +++++++++ Markdown/Tokens/OrderedListHtmlToken.cs | 15 ++++++++ Markdown/Tokens/PHtmlToken.cs | 5 ++- 7 files changed, 103 insertions(+), 11 deletions(-) create mode 100644 Markdown/Tokens/ListItemHtmlToken.cs create mode 100644 Markdown/Tokens/OrderedListHtmlToken.cs diff --git a/Markdown/LineType.cs b/Markdown/LineType.cs index 614e7bd43..57e5235ce 100644 --- a/Markdown/LineType.cs +++ b/Markdown/LineType.cs @@ -4,6 +4,7 @@ public enum LineType { Header, CodeBlock, + OrderedList, Simple } } \ No newline at end of file diff --git a/Markdown/Markdown.csproj b/Markdown/Markdown.csproj index eb5a95a6d..17a2d2fab 100644 --- a/Markdown/Markdown.csproj +++ b/Markdown/Markdown.csproj @@ -66,6 +66,8 @@ + + diff --git a/Markdown/Md.cs b/Markdown/Md.cs index c2aa3d15f..e0e4a8727 100644 --- a/Markdown/Md.cs +++ b/Markdown/Md.cs @@ -26,7 +26,13 @@ public class Md private readonly Dictionary> lineTagParserFuncMatch; private int currLineIndex; - private string CurrLine => plainMd[currLineIndex]; + + private string CurrLine + { + get { return plainMd[currLineIndex]; } + set { plainMd[currLineIndex] = value; } + } + private bool IsInPlainMd => currLineIndex < plainMd.Length; public Md(string plainMd, string baseUrl = "", CssClassInfo cssClassInfo = null) @@ -47,7 +53,8 @@ public Md(string plainMd, string baseUrl = "", CssClassInfo cssClassInfo = null) { [LineType.Header] = ParseHeader, [LineType.Simple] = ParseParagraph, - [LineType.CodeBlock] = ParseCodeBlock + [LineType.CodeBlock] = ParseCodeBlock, + [LineType.OrderedList] = ParseOrderedList }; currLineIndex = 0; @@ -55,8 +62,7 @@ public Md(string plainMd, string baseUrl = "", CssClassInfo cssClassInfo = null) private HtmlToken ParseHeader() { - var headerText = CurrLine.Replace("#", ""); - headerText = headerText.Replace("\\", ""); + var headerText = CurrLine.Replace("#", "").Replace("\\", ""); var headerImportance = CurrLine.Length - headerText.Length; @@ -73,7 +79,6 @@ private HtmlToken ParseCodeBlock() builder.Append(CurrLine.Substring(CurrLine.StartsWith("\t") ? 1 : 4)); builder.Append("\n"); currLineIndex++; - } builder.Remove(builder.Length - 1, 1); @@ -81,6 +86,34 @@ private HtmlToken ParseCodeBlock() return new CodeHtmlToken(builder.ToString()); } + private HtmlToken ParseOrderedList() + { + var listItemsTokens = new List(); + + while (IsInPlainMd && GetLineTypeTag(CurrLine) == LineType.OrderedList) + listItemsTokens.Add(ParseListItem()); + + return new OrderedListHtmlToken(listItemsTokens); + } + + private HtmlToken ParseListItem() + { + CurrLine = CurrLine.Substring(CurrLine.IndexOf(' ') + 1); + + var parsedToken = ParseParagraph(); + + var isParagraph = false; + if (currLineIndex != plainMd.Length && string.IsNullOrWhiteSpace(CurrLine)) + { + isParagraph = true; + currLineIndex++; + } + + return isParagraph + ? new ListItemHtmlToken(new List {parsedToken}) + : ((PHtmlToken) parsedToken).ToListItem(); + } + private HtmlToken ParseParagraph() { var innerTags = new List(); @@ -89,6 +122,7 @@ private HtmlToken ParseParagraph() { if (innerTags.Count != 0) innerTags.Add(new EmptyHtmlToken("\n", 0)); + var i = 0; while (i < CurrLine.Length) { @@ -97,6 +131,7 @@ private HtmlToken ParseParagraph() i += parsedToken.Length; innerTags.Add(parsedToken); } + currLineIndex++; } @@ -305,6 +340,8 @@ private static LineType GetLineTypeTag(string currLine) return LineType.Header; if (currLine.StartsWith(" ") || currLine.StartsWith("\t")) return LineType.CodeBlock; + if (char.IsDigit(currLine[0])) + return LineType.OrderedList; return LineType.Simple; } diff --git a/Markdown/Test/MD_Should.cs b/Markdown/Test/MD_Should.cs index 0e6538ead..31e33d655 100644 --- a/Markdown/Test/MD_Should.cs +++ b/Markdown/Test/MD_Should.cs @@ -109,17 +109,35 @@ public string ParseMixedTagsCorrectrly(string plainMd) return new Md(plainMd).Render(); } - [TestCase("##qwe", ExpectedResult = "

qwe

")] - [TestCase("qwe\n##qwe\nqwe", ExpectedResult = "

qwe

qwe

qwe

")] + [TestCase("##qwe", ExpectedResult = "

qwe

", TestName = "Simple header")] + [TestCase("qwe\n##qwe\nqwe", ExpectedResult = "

qwe

qwe

qwe

", + TestName = "Heaser with paragraphs") + ] public string ParseHeaderCorrectly(string plainMd) { return new Md(plainMd).Render(); } - [TestCase(" This is a code block.", ExpectedResult = "
This is a code block.
")] + [TestCase(" This is a code block.", ExpectedResult = "
This is a code block.
", + TestName = "One line code block")] [TestCase("Here is an example of AppleScript:\n\ttell application \"Foo\"\n\t\tbeep\n\tend tell", - ExpectedResult = "

Here is an example of AppleScript:

tell application \"Foo\"\n\tbeep\nend tell
")] - public string ParseCodeBlocksCorrectly(string plainMd) + ExpectedResult = + "

Here is an example of AppleScript:

tell application \"Foo\"\n\tbeep\nend tell
", + TestName = "Multiline code block")] + [TestCase("\t__thisIsNotTag__", ExpectedResult = "
__thisIsNotTag__
", + TestName = "tags not render in code blocks")] + public string ParseCodeBlocksCorrectly(string plainMd) + { + return new Md(plainMd).Render(); + } + + [TestCase("1. Bird\n1. McHale\n1. Parish", + ExpectedResult = "
  1. Bird
  2. McHale
  3. Parish
", + TestName = "List w/o paragraphs")] + [TestCase("1. Bird\n\n1. McHale\n1. Parish", + ExpectedResult = "
  1. Bird

  2. McHale
  3. Parish
", + TestName = "List with paragraphs")] + public string ParseOrderedListCorrectly(string plainMd) { return new Md(plainMd).Render(); } diff --git a/Markdown/Tokens/ListItemHtmlToken.cs b/Markdown/Tokens/ListItemHtmlToken.cs new file mode 100644 index 000000000..34a898448 --- /dev/null +++ b/Markdown/Tokens/ListItemHtmlToken.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; +using System.Linq; + +namespace Markdown.Tokens +{ + public class ListItemHtmlToken : HtmlToken + { + public ListItemHtmlToken(string data) : base("li", data, 0) + { + } + + public ListItemHtmlToken(List parsedTokens) : base("li", parsedTokens, 0) + { + } + } +} \ No newline at end of file diff --git a/Markdown/Tokens/OrderedListHtmlToken.cs b/Markdown/Tokens/OrderedListHtmlToken.cs new file mode 100644 index 000000000..b9bbc7977 --- /dev/null +++ b/Markdown/Tokens/OrderedListHtmlToken.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; + +namespace Markdown.Tokens +{ + public class OrderedListHtmlToken : HtmlToken + { + public OrderedListHtmlToken(string data) : base("ol", data, 0) + { + } + + public OrderedListHtmlToken(List parsedTokens) : base("ol", parsedTokens, 0) + { + } + } +} \ No newline at end of file diff --git a/Markdown/Tokens/PHtmlToken.cs b/Markdown/Tokens/PHtmlToken.cs index 6d9bf5254..c2c61ac93 100644 --- a/Markdown/Tokens/PHtmlToken.cs +++ b/Markdown/Tokens/PHtmlToken.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using System.Linq; namespace Markdown.Tokens { @@ -13,5 +12,9 @@ public PHtmlToken(List parsedTokens) : base("p", parsedTokens, 0) { } + public ListItemHtmlToken ToListItem() + { + return new ListItemHtmlToken(ParsedTokens); + } } } \ No newline at end of file From 842986fd5db9ff1791c4ab4d99d72e6e8ceaad01 Mon Sep 17 00:00:00 2001 From: Griboedoff Date: Sat, 26 Nov 2016 15:07:27 +0500 Subject: [PATCH 17/17] fix p tag in all test add trim p tag --- Markdown/Test/MD_Should.cs | 77 +++++++++++++++++++++----------------- 1 file changed, 43 insertions(+), 34 deletions(-) diff --git a/Markdown/Test/MD_Should.cs b/Markdown/Test/MD_Should.cs index 31e33d655..63f14d3ec 100644 --- a/Markdown/Test/MD_Should.cs +++ b/Markdown/Test/MD_Should.cs @@ -10,71 +10,71 @@ namespace Markdown.Test [TestFixture] internal class Md_Should { - [TestCase("qwe asd zxc", ExpectedResult = "

qwe asd zxc

")] + [TestCase("qwe asd zxc", ExpectedResult = "qwe asd zxc")] public string ParseNoMarkup(string plainMd) { - return new Md(plainMd).Render(); + return TrimPTag(new Md(plainMd).Render()); } - [TestCase("_asd_", ExpectedResult = "

asd

")] - [TestCase("_a s d_", ExpectedResult = "

a s d

")] - [TestCase("_1_2_3_", ExpectedResult = "

1_2_3

")] - [TestCase("_a_ _s d_", ExpectedResult = "

a s d

")] - [TestCase("_a_ _s d", ExpectedResult = "

a _s d

")] - [TestCase("_aas__abc__abc_", ExpectedResult = "

aas__abc__abc

")] + [TestCase("_asd_", ExpectedResult = "asd")] + [TestCase("_a s d_", ExpectedResult = "a s d")] + [TestCase("_1_2_3_", ExpectedResult = "1_2_3")] + [TestCase("_a_ _s d_", ExpectedResult = "a s d")] + [TestCase("_a_ _s d", ExpectedResult = "a _s d")] + [TestCase("_aas__abc__abc_", ExpectedResult = "aas__abc__abc")] public string ParseEmTagCorrectly(string plainMd) { - return new Md(plainMd).Render(); + return TrimPTag(new Md(plainMd).Render()); } - [TestCase("_ d_", ExpectedResult = "

_ d_

")] - [TestCase("_a _", ExpectedResult = "

_a _

")] - [TestCase("__ d__", ExpectedResult = "

__ d__

")] - [TestCase("__a __", ExpectedResult = "

__a __

")] - [TestCase("_ abc_abc_", ExpectedResult = "

_ abcabc

")] + [TestCase("_ d_", ExpectedResult = "_ d_")] + [TestCase("_a _", ExpectedResult = "_a _")] + [TestCase("__ d__", ExpectedResult = "__ d__")] + [TestCase("__a __", ExpectedResult = "__a __")] + [TestCase("_ abc_abc_", ExpectedResult = "_ abcabc")] public string ParseTrailingWhitespaceCorrectly(string plainMd) { - return new Md(plainMd).Render(); + return TrimPTag(new Md(plainMd).Render()); } - [TestCase("_ab cd", ExpectedResult = "

_ab cd

")] - [TestCase("__ab cd", ExpectedResult = "

__ab cd

")] + [TestCase("_ab cd", ExpectedResult = "_ab cd")] + [TestCase("__ab cd", ExpectedResult = "__ab cd")] public string ParseNoMarkup_IfMissingCloseTag(string plainMd) { - return new Md(plainMd).Render(); + return TrimPTag(new Md(plainMd).Render()); } - [TestCase(@"_a\_b_", ExpectedResult = "

a_b

")] - [TestCase(@"__a\_b__", ExpectedResult = "

a_b

")] - [TestCase(@"a\_b", ExpectedResult = "

a_b

")] + [TestCase(@"_a\_b_", ExpectedResult = "a_b")] + [TestCase(@"__a\_b__", ExpectedResult = "a_b")] + [TestCase(@"a\_b", ExpectedResult = "a_b")] public string ParseEscapedCorrectly(string plainMd) { - return new Md(plainMd).Render(); + return TrimPTag(new Md(plainMd).Render()); } - [TestCase("__abc_abc", ExpectedResult = "

__abc_abc

")] - [TestCase("__abc_abc_", ExpectedResult = "

__abcabc

")] - [TestCase("_abc__abc", ExpectedResult = "

_abc__abc

")] - [TestCase("_abc__abc__", ExpectedResult = "

_abc__abc__

")] + [TestCase("__abc_abc", ExpectedResult = "__abc_abc")] + [TestCase("__abc_abc_", ExpectedResult = "__abcabc")] + [TestCase("_abc__abc", ExpectedResult = "_abc__abc")] + [TestCase("_abc__abc__", ExpectedResult = "_abc__abc__")] public string ParseUnpairTags(string plainMd) { - return new Md(plainMd).Render(); + return TrimPTag(new Md(plainMd).Render()); } - [TestCase("__abc__", ExpectedResult = "

abc

")] - [TestCase("__abc_abc_abc__", ExpectedResult = "

abcabcabc

")] + [TestCase("__abc__", ExpectedResult = "abc")] + [TestCase("__abc_abc_abc__", ExpectedResult = "abcabcabc")] public string ParseStrongTagCorrectrly(string plainMd) { - return new Md(plainMd).Render(); + return TrimPTag(new Md(plainMd).Render()); } - [TestCase("[url](www.url.com)", "", ExpectedResult = "

url

")] - [TestCase("[url](/url)", "www.base.com", ExpectedResult = "

url

")] + [TestCase("[url](www.url.com)", "", ExpectedResult = "url")] + [TestCase("[url](/url)", "www.base.com", ExpectedResult = "url")] [TestCase("[url](www.url.com)\n[url](/url)", "www.base.com", - ExpectedResult = "

url\nurl

")] + ExpectedResult = "url\nurl")] public string ParseUrlTagCorrectrly(string plainMd, string baseUrl) { - return new Md(plainMd, baseUrl).Render(); + return TrimPTag(new Md(plainMd, baseUrl).Render()); } [TestCase("_asd_", "css", "", ExpectedResult = "

asd

", TestName = "No def") @@ -142,6 +142,15 @@ public string ParseOrderedListCorrectly(string plainMd) return new Md(plainMd).Render(); } + private static string TrimPTag(string html) + { + if (html.StartsWith("

")) + html = html.Substring(3); + if (html.EndsWith("

")) + html = html.Substring(0, html.Length - 4); + return html; + } + private static string GenerateMdTag(Tag tag, int length) { const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";