().Select(x => x.Value).ToArray();
72 | }
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 |
4 | # User-specific files
5 | *.suo
6 | *.user
7 | *.sln.docstates
8 |
9 | # Build results
10 | [Dd]ebug/
11 | [Dd]ebugPublic/
12 | [Rr]elease/
13 | x64/
14 | [Bb]uild/
15 | bld/
16 | [Bb]in/
17 | [Oo]bj/
18 |
19 | # Roslyn cache directories
20 | *.ide/
21 |
22 | # MSTest test Results
23 | [Tt]est[Rr]esult*/
24 | [Bb]uild[Ll]og.*
25 |
26 | #NUNIT
27 | *.VisualState.xml
28 | TestResult.xml
29 |
30 | # Build Results of an ATL Project
31 | [Dd]ebugPS/
32 | [Rr]eleasePS/
33 | dlldata.c
34 |
35 | *_i.c
36 | *_p.c
37 | *_i.h
38 | *.ilk
39 | *.meta
40 | *.obj
41 | *.pch
42 | *.pdb
43 | *.pgc
44 | *.pgd
45 | *.rsp
46 | *.sbr
47 | *.tlb
48 | *.tli
49 | *.tlh
50 | *.tmp
51 | *.tmp_proj
52 | *.log
53 | *.vspscc
54 | *.vssscc
55 | .builds
56 | *.pidb
57 | *.svclog
58 | *.scc
59 |
60 | # Chutzpah Test files
61 | _Chutzpah*
62 |
63 | # Visual C++ cache files
64 | ipch/
65 | *.aps
66 | *.ncb
67 | *.opensdf
68 | *.sdf
69 | *.cachefile
70 |
71 | # Visual Studio profiler
72 | *.psess
73 | *.vsp
74 | *.vspx
75 |
76 | # TFS 2012 Local Workspace
77 | $tf/
78 |
79 | # Guidance Automation Toolkit
80 | *.gpState
81 |
82 | # ReSharper is a .NET coding add-in
83 | _ReSharper*/
84 | *.[Rr]e[Ss]harper
85 | *.DotSettings.user
86 |
87 | # JustCode is a .NET coding addin-in
88 | .JustCode
89 |
90 | # TeamCity is a build add-in
91 | _TeamCity*
92 |
93 | # DotCover is a Code Coverage Tool
94 | *.dotCover
95 |
96 | # NCrunch
97 | _NCrunch_*
98 | .*crunch*.local.xml
99 |
100 | # MightyMoose
101 | *.mm.*
102 | AutoTest.Net/
103 |
104 | # Web workbench (sass)
105 | .sass-cache/
106 |
107 | # Installshield output folder
108 | [Ee]xpress/
109 |
110 | # DocProject is a documentation generator add-in
111 | DocProject/buildhelp/
112 | DocProject/Help/*.HxT
113 | DocProject/Help/*.HxC
114 | DocProject/Help/*.hhc
115 | DocProject/Help/*.hhk
116 | DocProject/Help/*.hhp
117 | DocProject/Help/Html2
118 | DocProject/Help/html
119 |
120 | # Click-Once directory
121 | publish/
122 |
123 | # Publish Web Output
124 | *.[Pp]ublish.xml
125 | *.azurePubxml
126 | ## TODO: Comment the next line if you want to checkin your
127 | ## web deploy settings but do note that will include unencrypted
128 | ## passwords
129 | #*.pubxml
130 |
131 | # NuGet Packages Directory
132 | packages/*
133 | [Nn]u[Gg]et.exe
134 | ## TODO: If the tool you use requires repositories.config
135 | ## uncomment the next line
136 | #!packages/repositories.config
137 |
138 | # Enable "build/" folder in the NuGet Packages folder since
139 | # NuGet packages use it for MSBuild targets.
140 | # This line needs to be after the ignore of the build folder
141 | # (and the packages folder if the line above has been uncommented)
142 | !packages/build/
143 |
144 | # Windows Azure Build Output
145 | csx/
146 | *.build.csdef
147 |
148 | # Windows Store app package directory
149 | AppPackages/
150 |
151 | # Others
152 | sql/
153 | *.Cache
154 | ClientBin/
155 | [Ss]tyle[Cc]op.*
156 | ~$*
157 | *~
158 | *.dbmdl
159 | *.dbproj.schemaview
160 | *.pfx
161 | *.publishsettings
162 | node_modules/
163 |
164 | # RIA/Silverlight projects
165 | Generated_Code/
166 |
167 | # Backup & report files from converting an old project file
168 | # to a newer Visual Studio version. Backup files are not needed,
169 | # because we have git ;-)
170 | _UpgradeReport_Files/
171 | Backup*/
172 | UpgradeLog*.XML
173 | UpgradeLog*.htm
174 |
175 | # SQL Server files
176 | *.mdf
177 | *.ldf
178 |
179 | # Business Intelligence projects
180 | *.rdl.data
181 | *.bim.layout
182 | *.bim_*.settings
183 |
184 | # Microsoft Fakes
185 | FakesAssemblies/
186 |
187 | # Visual Studio
188 | .vs/
189 |
190 | # LightSwitch generated files
191 | GeneratedArtifacts/
192 | _Pvt_Extensions/
193 | ModelManifest.xml
194 |
--------------------------------------------------------------------------------
/src/MarkedNet.Tests/MarkedTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using NUnit;
3 | using NUnit.Framework;
4 |
5 |
6 | namespace MarkedNet.Tests
7 | {
8 | [TestFixture]
9 | public class MarkedTests
10 | {
11 | private Marked _marked;
12 |
13 |
14 | [SetUp]
15 | public virtual void SetUp()
16 | {
17 | _marked = new Marked();
18 | }
19 |
20 | [Test]
21 | public void Parse_Null_Null()
22 | {
23 | //action
24 | var actual = _marked.Parse(null);
25 |
26 | //assert
27 | Assert.IsNull(actual);
28 | }
29 |
30 | [Test]
31 | public void Parse_EmptyString_EmptyString()
32 | {
33 | //action
34 | var actual = _marked.Parse(String.Empty);
35 |
36 | //assert
37 | Assert.IsEmpty(actual);
38 | }
39 |
40 | [TestCase("# Hello World", "Hello World
\n")]
41 | public void Parse_MarkdownText_HtmlText(string source, string expected)
42 | {
43 | //action
44 | var actual = _marked.Parse(source);
45 |
46 | //assert
47 | Assert.AreEqual(actual, expected);
48 | }
49 |
50 | [TestCase("Hot keys: Ctrl+[ and Ctrl+]", "Hot keys: Ctrl+[ and Ctrl+]
\n")]
51 | [TestCase("Some text here
", "Some text here
")]
52 | public void Parse_MarkdownWithHtmlTags_HtmlText(string source, string expected)
53 | {
54 | //action
55 | var actual = _marked.Parse(source);
56 |
57 | //assert
58 | Assert.AreEqual(expected, actual);
59 | }
60 |
61 | [Test]
62 | public void Parse_MarkdownTableWithMissingAlign_HtmlText()
63 | {
64 | //arrange
65 | var table = @"| | Header1 | Header2 |
66 | ----------------- | ---------------------------- | ------------------
67 | | Single backticks | `'Isn't this fun?'` | 'Isn't this fun?' |
68 | | Quotes | `""Isn't this fun?""` | ""Isn't this fun?"" |
69 | | Dashes | `-- is en-dash, --- is em-dash` | -- is en-dash, --- is em-dash |";
70 |
71 |
72 | var expected = "\n\n\n | \n | \nHeader1 | \nHeader2 | \n
\n\n\n\n | \nSingle backticks | \n'Isn't this fun?' | \n'Isn't this fun?' | \n | \n
\n\n | \nQuotes | \n"Isn't this fun?" | \n"Isn't this fun?" | \n | \n
\n\n | \nDashes | \n-- is en-dash, --- is em-dash | \n-- is en-dash, --- is em-dash | \n | \n
\n\n
\n";
73 |
74 | //action
75 | var actual = _marked.Parse(table);
76 |
77 | //assert
78 | Assert.AreEqual(expected, actual);
79 |
80 | }
81 |
82 | [Test]
83 | public void Parse_ComplexMarkdownText_HtmlText()
84 | {
85 | // arrange
86 | var markdown = @"
87 | Heading
88 | =======
89 |
90 | Sub-heading
91 | -----------
92 |
93 | ### Another deeper heading
94 |
95 | Paragraphs are separated
96 | by a blank line.
97 |
98 | Leave 2 spaces at the end of a line to do a
99 | line break
100 |
101 | Text attributes *italic*, **bold**,
102 | `monospace`, ~~strikethrough~~ .
103 |
104 | A [link](http://example.com).
105 |
106 | Shopping list:
107 |
108 | * apples
109 | * oranges
110 | * pears
111 |
112 | Numbered list:
113 |
114 | 1. apples
115 | 2. oranges
116 | 3. pears
117 | ";
118 |
119 | var expected = "Heading
\nSub-heading
\nAnother deeper heading
\nParagraphs are separated\nby a blank line.
\nLeave 2 spaces at the end of a line to do a
line break
\nText attributes italic, bold, \nmonospace, strikethrough .
\nA link.
\nShopping list:
\n\n- apples
\n- oranges
\n- pears
\n
\nNumbered list:
\n\n- apples
\n- oranges
\n- pears
\n
\n";
120 |
121 | //action
122 | var html = _marked.Parse(markdown);
123 |
124 | //assert
125 | Assert.AreEqual(expected, html);
126 | }
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/src/MarkedNet/InlineRules.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Text.RegularExpressions;
6 |
7 |
8 | namespace MarkedNet
9 | {
10 | ///
11 | /// Inline-Level Grammar
12 | ///
13 | public class InlineRules
14 | {
15 | #region Fields
16 |
17 | private static readonly Regex escape = new Regex(@"^\\([\\`*{}\[\]()#+\-.!_>])");
18 | private static readonly Regex autoLink = new Regex(@"^<([^ >]+(@|:\/)[^ >]+)>");
19 | private static readonly Regex url = new Regex(""); // noop
20 | private static readonly Regex tag = new Regex(@"^|^<\/?\w+(?:""[^""]*""|'[^']*'|[^'"">])*?>");
21 | private static readonly Regex link = new Regex(@"^!?\[((?:\[[^\]]*\]|[^\[\]]|\](?=[^\[]*\]))*)\]\(\s*([\s\S]*?)>?(?:\s+['""]([\s\S]*?)['""])?\s*\)");
22 | private static readonly Regex refLink = new Regex(@"^!?\[((?:\[[^\]]*\]|[^\[\]]|\](?=[^\[]*\]))*)\]\s*\[([^\]]*)\]");
23 | private static readonly Regex noLink = new Regex(@"^!?\[((?:\[[^\]]*\]|[^\[\]])*)\]");
24 | private static readonly Regex strong = new Regex(@"^__([\s\S]+?)__(?!_)|^\*\*([\s\S]+?)\*\*(?!\*)");
25 | private static readonly Regex em = new Regex(@"^\b_((?:__|[\s\S])+?)_\b|^\*((?:\*\*|[\s\S])+?)\*(?!\*)");
26 | private static readonly Regex code = new Regex(@"^(`+)\s*([\s\S]*?[^`])\s*\1(?!`)");
27 | private static readonly Regex br = new Regex(@"^ {2,}\n(?!\s*$)");
28 | private static readonly Regex del = new Regex(""); // noop
29 | private static readonly Regex text = new Regex(@"^[\s\S]+?(?=[\\
53 | /// Normal Inline Grammar
54 | ///
55 | public class NormalInlineRules : InlineRules
56 | {
57 | }
58 |
59 | ///
60 | /// Pedantic Inline Grammar
61 | ///
62 | public class PedanticInlineRules : InlineRules
63 | {
64 | #region Fields
65 |
66 | private static readonly Regex strong = new Regex(@"^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)");
67 | private static readonly Regex em = new Regex(@"^_(?=\S)([\s\S]*?\S)_(?!_)|^\*(?=\S)([\s\S]*?\S)\*(?!\*)");
68 |
69 | #endregion
70 |
71 | #region Properties
72 |
73 | public override Regex Strong { get { return strong; } }
74 | public override Regex Em { get { return em; } }
75 |
76 | #endregion
77 | }
78 |
79 | ///
80 | /// GFM Inline Grammar
81 | ///
82 | public class GfmInlineRules : InlineRules
83 | {
84 | #region Fields
85 |
86 | private static readonly Regex escape = new Regex(@"^\\([\\`*{}\[\]()#+\-.!_>~|])");
87 | private static readonly Regex url = new Regex(@"^(https?:\/\/[^\s<]+[^<.,:;""')\]\s])");
88 | private static readonly Regex del = new Regex(@"^~~(?=\S)([\s\S]*?\S)~~");
89 | private static readonly Regex text = new Regex(@"^[\s\S]+?(?=[\\
104 | /// GFM + Line Breaks Inline Grammar
105 | ///
106 | public class BreaksInlineRules : GfmInlineRules
107 | {
108 | #region Fields
109 |
110 | private static readonly Regex br = new Regex(@"^ *\n(?!\s*$)");
111 | private static readonly Regex text = new Regex(@"^[\s\S]+?(?=[\\
11 | /// Block-Level Grammar
12 | ///
13 | public class BlockRules
14 | {
15 | #region Fields
16 |
17 | private static readonly Regex newline = new Regex(@"^\n+");
18 | private static readonly Regex code = new Regex(@"^( {4}[^\n]+\n*)+");
19 | private static readonly Regex fences = new Regex(""); // noop
20 | private static readonly Regex hr = new Regex(@"^( *[-*_]){3,} *(?:\n+|$)");
21 | private static readonly Regex heading = new Regex(@"^ *(#{1,6}) *([^\n]+?) *#* *(?:\n+|$)");
22 | private static readonly Regex npTable = new Regex(""); // noop
23 | private static readonly Regex lHeading = new Regex(@"^([^\n]+)\n *(=|-){2,} *(?:\n+|$)");
24 | private static readonly Regex blockquote = new Regex(@"^( *>[^\n]+(\n(?! *\[([^\]]+)\]: *([^\s>]+)>?(?: +[""(]([^\n]+)["")])? *(?:\n+|$))[^\n]+)*\n*)+");
25 | private static readonly Regex list = new Regex(@"^( *)((?:[*+-]|\d+\.)) [\s\S]+?(?:\n+(?=\1?(?:[-*_] *){3,}(?:\n+|$))|\n+(?= *\[([^\]]+)\]: *([^\s>]+)>?(?: +[""(]([^\n]+)["")])? *(?:\n+|$))|\n{2,}(?! )(?!\1(?:[*+-]|\d+\.) )\n*|\s*$)");
26 | private static readonly Regex html = new Regex(@"^ *(?:|<((?!(?:a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)\b)\w+(?!:\/|[^\w\s@]*@)\b)[\s\S]+?<\/\1>|<(?!(?:a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)\b)\w+(?!:\/|[^\w\s@]*@)\b(?:""[^""]*""|'[^']*'|[^'"">])*?>) *(?:\n{2,}|\s*$)");
27 | private static readonly Regex def = new Regex(@"^ *\[([^\]]+)\]: *([^\s>]+)>?(?: +[""(]([^\n]+)["")])? *(?:\n+|$)");
28 | private static readonly Regex table = new Regex(""); // noop
29 | private static readonly Regex paragraph = new Regex(@"^((?:[^\n]+\n?(?!( *[-*_]){3,} *(?:\n+|$)| *(#{1,6}) *([^\n]+?) *#* *(?:\n+|$)|([^\n]+)\n *(=|-){2,} *(?:\n+|$)|( *>[^\n]+(\n(?! *\[([^\]]+)\]: *([^\s>]+)>?(?: +[""(]([^\n]+)["")])? *(?:\n+|$))[^\n]+)*\n*)+|<(?!(?:a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)\b)\w+(?!:\/|[^\w\s@]*@)\b| *\[([^\]]+)\]: *([^\s>]+)>?(?: +[""(]([^\n]+)["")])? *(?:\n+|$)))+)\n*");
30 | private static readonly Regex text = new Regex(@"^[^\n]+");
31 | private static readonly Regex bullet = new Regex(@"(?:[*+-]|\d+\.)");
32 | private static readonly Regex item = new Regex(@"^( *)((?:[*+-]|\d+\.)) [^\n]*(?:\n(?!\1(?:[*+-]|\d+\.) )[^\n]*)*", RegexOptions.Multiline);
33 |
34 | #endregion
35 |
36 | #region Properties
37 |
38 | public virtual Regex Newline { get { return newline; } }
39 | public virtual Regex Сode { get { return code; } }
40 | public virtual Regex Fences { get { return fences; } } // noop
41 | public virtual Regex Hr { get { return hr; } }
42 | public virtual Regex Heading { get { return heading; } }
43 | public virtual Regex NpTable { get { return npTable; } } // noop
44 | public virtual Regex LHeading { get { return lHeading; } }
45 | public virtual Regex Blockquote { get { return blockquote; } }
46 | public virtual Regex List { get { return list; } }
47 | public virtual Regex Html { get { return html; } }
48 | public virtual Regex Def { get { return def; } }
49 | public virtual Regex Table { get { return table; } } // noop
50 | public virtual Regex Paragraph { get { return paragraph; } }
51 | public virtual Regex Text { get { return text; } }
52 | public virtual Regex Bullet { get { return bullet; } }
53 | public virtual Regex Item { get { return item; } }
54 |
55 | #endregion
56 | }
57 |
58 | ///
59 | /// Normal Block Grammar
60 | ///
61 | public class NormalBlockRules : BlockRules
62 | {
63 | }
64 |
65 | ///
66 | /// GFM Block Grammar
67 | ///
68 | public class GfmBlockRules : BlockRules
69 | {
70 | #region Fields
71 |
72 | private static readonly Regex fences = new Regex(@"^ *(`{3,}|~{3,}) *(\S+)? *\n([\s\S]+?)\s*\1 *(?:\n+|$)");
73 | private static readonly Regex paragraph = new Regex(@"^((?:[^\n]+\n?(?! *(`{3,}|~{3,}) *(\S+)? *\n([\s\S]+?)\s*\2 *(?:\n+|$)|( *)((?:[*+-]|\d+\.)) [\s\S]+?(?:\n+(?=\3?(?:[-*_] *){3,}(?:\n+|$))|\n+(?= *\[([^\]]+)\]: *([^\s>]+)>?(?: +[""(]([^\n]+)["")])? *(?:\n+|$))|\n{2,}(?! )(?!\1(?:[*+-]|\d+\.) )\n*|\s*$)|( *[-*_]){3,} *(?:\n+|$)| *(#{1,6}) *([^\n]+?) *#* *(?:\n+|$)|([^\n]+)\n *(=|-){2,} *(?:\n+|$)|( *>[^\n]+(\n(?! *\[([^\]]+)\]: *([^\s>]+)>?(?: +[""(]([^\n]+)["")])? *(?:\n+|$))[^\n]+)*\n*)+|<(?!(?:a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)\b)\w+(?!:\/|[^\w\s@]*@)\b| *\[([^\]]+)\]: *([^\s>]+)>?(?: +[""(]([^\n]+)["")])? *(?:\n+|$)))+)\n*");
74 | private static readonly Regex heading = new Regex(@"^ *(#{1,6}) *([^\n]+?) *#* *(?:\n+|$)");
75 |
76 | #endregion
77 |
78 | #region Properties
79 |
80 | public override Regex Fences { get { return fences; } }
81 | public override Regex Paragraph { get { return paragraph; } }
82 | public override Regex Heading { get { return heading; } }
83 |
84 | #endregion
85 | }
86 |
87 | ///
88 | /// GFM + Tables Block Grammar
89 | ///
90 | public class TablesBlockRules : GfmBlockRules
91 | {
92 | #region Fields
93 |
94 | private static readonly Regex npTable = new Regex(@"^ *(\S.*\|.*)\n *([-:]+ *\|[-| :]*)\n((?:.*\|.*(?:\n|$))*)\n*");
95 | private static readonly Regex table = new Regex(@"^ *\|(.+)\n *\|( *[-:]+[-| :]*)\n((?: *\|.*(?:\n|$))*)\n*");
96 |
97 | #endregion
98 |
99 | #region Properties
100 |
101 | public override Regex NpTable { get { return npTable; } }
102 | public override Regex Table { get { return table; } }
103 |
104 | #endregion
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/src/MarkedNet/Renderer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using System.Collections.Generic;
4 | using System.Text.RegularExpressions;
5 |
6 |
7 | namespace MarkedNet
8 | {
9 | public class Renderer
10 | {
11 | #region Properties
12 |
13 | public Options Options { get; set; }
14 |
15 | #endregion
16 |
17 | #region Constructors
18 |
19 | public Renderer()
20 | : this(null)
21 | {
22 | }
23 |
24 | public Renderer(Options options)
25 | {
26 | Options = options ?? new Options();
27 | }
28 |
29 | #endregion
30 |
31 | #region Methods
32 |
33 | #region Block Level Renderer
34 |
35 | public virtual string Code(string code, string lang, bool escaped)
36 | {
37 | var transformedCode = code;
38 |
39 | if (Options.Highlight != null)
40 | {
41 | var output = Options.Highlight(code, lang);
42 | if (output != null && output != code)
43 | {
44 | escaped = true;
45 | transformedCode = output;
46 | }
47 | }
48 |
49 | transformedCode = escaped ? transformedCode : StringHelper.Escape(transformedCode, true);
50 | var langClass = Options.LangPrefix + StringHelper.Escape(lang ?? string.Empty, true);
51 |
52 | return $"{transformedCode}\n
\n";
53 | }
54 |
55 | public virtual string Blockquote(string quote)
56 | {
57 | return $"\n{quote}
\n";
58 | }
59 |
60 | public virtual string Html(string html)
61 | {
62 | return html;
63 | }
64 |
65 | public virtual string Heading(string text, int level, string raw)
66 | {
67 | return $"\n";
68 | }
69 |
70 | public virtual string Hr()
71 | {
72 | return Options.XHtml ? "
\n" : "
\n";
73 | }
74 |
75 | public virtual string List(string body, bool ordered)
76 | {
77 | var type = ordered ? "ol" : "ul";
78 | return $"<{type}>\n{body}{type}>\n";
79 | }
80 |
81 | public virtual string ListItem(string text)
82 | {
83 | return $"{text}\n";
84 | }
85 |
86 | public virtual string Paragraph(string text)
87 | {
88 | return $"{text}
\n";
89 | }
90 |
91 | public virtual string Table(string header, string body)
92 | {
93 | return $"\n";
94 | }
95 |
96 | public virtual string TableRow(string content)
97 | {
98 | return $"\n{content}
\n";
99 | }
100 |
101 | public virtual string TableCell(string content, TableCellFlags flags)
102 | {
103 | var type = flags.Header ? "th" : "td";
104 | var tag = !string.IsNullOrEmpty(flags.Align)
105 | ? $"<{type} style='text-align:{flags.Align}'>"
106 | : $"<{type}>";
107 |
108 | return tag + content + $"{type}>\n";
109 | }
110 |
111 | #endregion
112 |
113 | #region Span Level Renderer
114 |
115 | public virtual string Strong(string text)
116 | {
117 | return $"{text}";
118 | }
119 |
120 | public virtual string Em(string text)
121 | {
122 | return $"{text}";
123 | }
124 |
125 | public virtual string Codespan(string text)
126 | {
127 | return $"{text}";
128 | }
129 |
130 | public virtual string Br()
131 | {
132 | return Options.XHtml ? "
" : "
";
133 | }
134 |
135 | public virtual string Del(string text)
136 | {
137 | return $"{text}";
138 | }
139 |
140 | public virtual string Link(string href, string title, string text)
141 | {
142 | if (Options.Sanitize)
143 | {
144 | string prot = null;
145 |
146 | try
147 | {
148 | prot = Regex.Replace(StringHelper.DecodeURIComponent(StringHelper.Unescape(href)), @"[^\w:]", String.Empty).ToLower();
149 | }
150 | catch (Exception)
151 | {
152 | return string.Empty;
153 | }
154 |
155 | if (prot.IndexOf("javascript:") == 0 || prot.IndexOf("vbscript:") == 0)
156 | {
157 | return string.Empty;
158 | }
159 | }
160 |
161 | var output = $"{text}";
174 | return output;
175 | }
176 |
177 | public virtual string Image(string href, string title, string text)
178 | {
179 | var output = $"
" : ">";
187 | return output;
188 | }
189 |
190 | public virtual string Text(string text)
191 | {
192 | return text;
193 | }
194 |
195 | #endregion
196 |
197 | #region Helpers
198 |
199 | public static string AttributesToString(IDictionary attributes)
200 | {
201 | if (attributes == null || attributes.Count == 0)
202 | {
203 | return string.Empty;
204 | }
205 |
206 | return string.Join(string.Empty, attributes.Select(kv => $" {kv.Key}='{kv.Value}'"));
207 | }
208 |
209 | #endregion
210 |
211 | #endregion
212 | }
213 | }
214 |
--------------------------------------------------------------------------------
/src/MarkedNet/Parser.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 |
6 |
7 | namespace MarkedNet
8 | {
9 | public class Parser
10 | {
11 | private Options _options;
12 | private InlineLexer inline;
13 | private Stack tokens;
14 | private Token token;
15 |
16 |
17 | public Parser(Options options)
18 | {
19 | this.tokens = new Stack();
20 | this.token = null;
21 | _options = options ?? new Options();
22 | }
23 |
24 |
25 | ///
26 | /// Static Parse Method
27 | ///
28 | public static string Parse(TokensResult src, Options options)
29 | {
30 | var parser = new Parser(options);
31 | return parser.Parse(src);
32 | }
33 |
34 | ///
35 | /// Parse Loop
36 | ///
37 | public virtual string Parse(TokensResult src)
38 | {
39 | this.inline = new InlineLexer(src.Links, this._options);
40 | this.tokens = new Stack(src.Reverse());
41 |
42 | var @out = String.Empty;
43 | while (Next() != null)
44 | {
45 | @out += Tok();
46 | }
47 |
48 | return @out;
49 | }
50 |
51 |
52 | ///
53 | /// Next Token
54 | ///
55 | protected virtual Token Next()
56 | {
57 | return this.token = (this.tokens.Any()) ? this.tokens.Pop() : null;
58 | }
59 |
60 |
61 | ///
62 | /// Preview Next Token
63 | ///
64 | protected virtual Token Peek()
65 | {
66 | return this.tokens.Peek() ?? new Token();
67 | }
68 |
69 |
70 | ///
71 | /// Parse Text Tokens
72 | ///
73 | protected virtual string ParseText()
74 | {
75 | var body = this.token.Text;
76 |
77 | while (this.Peek().Type == "text")
78 | {
79 | body += '\n' + this.Next().Text;
80 | }
81 |
82 | return this.inline.Output(body);
83 | }
84 |
85 | ///
86 | /// Parse Current Token
87 | ///
88 | protected virtual string Tok()
89 | {
90 | switch (this.token.Type)
91 | {
92 | case "space":
93 | {
94 | return String.Empty;
95 | }
96 | case "hr":
97 | {
98 | return _options.Renderer.Hr();
99 | }
100 | case "heading":
101 | {
102 | return _options.Renderer.Heading(this.inline.Output(this.token.Text), this.token.Depth, this.token.Text);
103 | }
104 | case "code":
105 | {
106 | return _options.Renderer.Code(this.token.Text, this.token.Lang, this.token.Escaped);
107 | }
108 | case "table":
109 | {
110 | string header = String.Empty, body = String.Empty;
111 |
112 | // header
113 | var cell = String.Empty;
114 | for (int i = 0; i < this.token.Header.Count; i++)
115 | {
116 | cell += _options.Renderer.TableCell(
117 | this.inline.Output(this.token.Header[i]),
118 | new TableCellFlags { Header = true, Align = i < this.token.Align.Count ? this.token.Align[i] : null }
119 | );
120 | }
121 | header += _options.Renderer.TableRow(cell);
122 |
123 | for (int i = 0; i < this.token.Cells.Count; i++)
124 | {
125 | var row = this.token.Cells[i];
126 |
127 | cell = String.Empty;
128 | for (int j = 0; j < row.Count; j++)
129 | {
130 | cell += _options.Renderer.TableCell(
131 | this.inline.Output(row[j]),
132 | new TableCellFlags { Header = false, Align = j < this.token.Align.Count ? this.token.Align[j] : null }
133 | );
134 | }
135 |
136 | body += _options.Renderer.TableRow(cell);
137 | }
138 | return _options.Renderer.Table(header, body);
139 | }
140 | case "blockquote_start":
141 | {
142 | var body = String.Empty;
143 |
144 | while (this.Next().Type != "blockquote_end")
145 | {
146 | body += this.Tok();
147 | }
148 |
149 | return _options.Renderer.Blockquote(body);
150 | }
151 | case "list_start":
152 | {
153 | var body = String.Empty;
154 | var ordered = this.token.Ordered;
155 |
156 | while (this.Next().Type != "list_end")
157 | {
158 | body += this.Tok();
159 | }
160 |
161 | return _options.Renderer.List(body, ordered);
162 | }
163 | case "list_item_start":
164 | {
165 | var body = String.Empty;
166 |
167 | while (this.Next().Type != "list_item_end")
168 | {
169 | body += this.token.Type == "text"
170 | ? this.ParseText()
171 | : this.Tok();
172 | }
173 |
174 | return _options.Renderer.ListItem(body);
175 | }
176 | case "loose_item_start":
177 | {
178 | var body = String.Empty;
179 |
180 | while (this.Next().Type != "list_item_end")
181 | {
182 | body += this.Tok();
183 | }
184 |
185 | return _options.Renderer.ListItem(body);
186 | }
187 | case "html":
188 | {
189 | var html = !this.token.Pre && !this._options.Pedantic
190 | ? this.inline.Output(this.token.Text)
191 | : this.token.Text;
192 | return _options.Renderer.Html(html);
193 | }
194 | case "paragraph":
195 | {
196 | return _options.Renderer.Paragraph(this.inline.Output(this.token.Text));
197 | }
198 | case "text":
199 | {
200 | return _options.Renderer.Paragraph(this.ParseText());
201 | }
202 | }
203 |
204 | throw new Exception();
205 | }
206 | }
207 | }
208 |
--------------------------------------------------------------------------------
/src/MarkedNet/InlineLexer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Text.RegularExpressions;
6 |
7 |
8 | namespace MarkedNet
9 | {
10 | ///
11 | /// Inline Lexer and Compiler
12 | ///
13 | public class InlineLexer
14 | {
15 | private Random _random = new Random();
16 |
17 | private Options _options;
18 | private InlineRules _rules;
19 | private IDictionary links;
20 | private bool inLink;
21 |
22 |
23 | public InlineLexer(IDictionary links, Options options)
24 | {
25 | _options = options ?? new Options();
26 |
27 | this.links = links;
28 | this._rules = new NormalInlineRules();
29 |
30 | if (this.links == null)
31 | {
32 | throw new Exception("Tokens array requires a `links` property.");
33 | }
34 |
35 | if (_options.Gfm)
36 | {
37 | if (this._options.Breaks)
38 | {
39 | _rules = new BreaksInlineRules();
40 | }
41 | else
42 | {
43 | _rules = new GfmInlineRules();
44 | }
45 | }
46 | else if (this._options.Pedantic)
47 | {
48 | _rules = new PedanticInlineRules();
49 | }
50 | }
51 |
52 |
53 |
54 | ///
55 | /// Lexing/Compiling
56 | ///
57 | public virtual string Output(string src)
58 | {
59 | var @out = String.Empty;
60 | LinkObj link;
61 | string text;
62 | string href;
63 | IList cap;
64 |
65 | while (!String.IsNullOrEmpty(src))
66 | {
67 | // escape
68 | cap = this._rules.Escape.Exec(src);
69 | if (cap.Any())
70 | {
71 | src = src.Substring(cap[0].Length);
72 | @out += cap[1];
73 | continue;
74 | }
75 |
76 | // autolink
77 | cap = this._rules.AutoLink.Exec(src);
78 | if (cap.Any())
79 | {
80 | src = src.Substring(cap[0].Length);
81 | if (cap[2] == "@")
82 | {
83 | text = cap[1][6] == ':'
84 | ? this.Mangle(cap[1].Substring(7))
85 | : this.Mangle(cap[1]);
86 | href = this.Mangle("mailto:") + text;
87 | }
88 | else
89 | {
90 | text = StringHelper.Escape(cap[1]);
91 | href = text;
92 | }
93 | @out += _options.Renderer.Link(href, null, text);
94 | continue;
95 | }
96 |
97 | // url (gfm)
98 | if (!this.inLink && (cap = this._rules.Url.Exec(src)).Any())
99 | {
100 | src = src.Substring(cap[0].Length);
101 | text = StringHelper.Escape(cap[1]);
102 | href = text;
103 | @out += _options.Renderer.Link(href, null, text);
104 | continue;
105 | }
106 |
107 | // tag
108 | cap = this._rules.Tag.Exec(src);
109 | if (cap.Any())
110 | {
111 | if (!this.inLink && Regex.IsMatch(cap[0], "^", RegexOptions.IgnoreCase))
116 | {
117 | this.inLink = false;
118 | }
119 | src = src.Substring(cap[0].Length);
120 | @out += this._options.Sanitize
121 | ? (this._options.Sanitizer != null)
122 | ? this._options.Sanitizer(cap[0])
123 | : StringHelper.Escape(cap[0])
124 | : cap[0];
125 | continue;
126 | }
127 |
128 | // link
129 | cap = this._rules.Link.Exec(src);
130 | if (cap.Any())
131 | {
132 | src = src.Substring(cap[0].Length);
133 | this.inLink = true;
134 | @out += this.OutputLink(cap, new LinkObj
135 | {
136 | Href = cap[2],
137 | Title = cap[3]
138 | });
139 | this.inLink = false;
140 | continue;
141 | }
142 |
143 | // reflink, nolink
144 | if ((cap = this._rules.RefLink.Exec(src)).Any() || (cap = this._rules.NoLink.Exec(src)).Any())
145 | {
146 | src = src.Substring(cap[0].Length);
147 | var linkStr = (StringHelper.NotEmpty(cap, 2, 1)).ReplaceRegex(@"\s+", " ");
148 |
149 | this.links.TryGetValue(linkStr.ToLower(), out link);
150 |
151 | if (link == null || String.IsNullOrEmpty(link.Href))
152 | {
153 | @out += cap[0][0];
154 | src = cap[0].Substring(1) + src;
155 | continue;
156 | }
157 | this.inLink = true;
158 | @out += this.OutputLink(cap, link);
159 | this.inLink = false;
160 | continue;
161 | }
162 |
163 | // strong
164 | if ((cap = this._rules.Strong.Exec(src)).Any())
165 | {
166 | src = src.Substring(cap[0].Length);
167 | @out += _options.Renderer.Strong(this.Output(StringHelper.NotEmpty(cap, 2, 1)));
168 | continue;
169 | }
170 |
171 | // em
172 | cap = this._rules.Em.Exec(src);
173 | if (cap.Any())
174 | {
175 | src = src.Substring(cap[0].Length);
176 | @out += _options.Renderer.Em(this.Output(StringHelper.NotEmpty(cap, 2, 1)));
177 | continue;
178 | }
179 |
180 | // code
181 | cap = this._rules.Code.Exec(src);
182 | if (cap.Any())
183 | {
184 | src = src.Substring(cap[0].Length);
185 | @out += _options.Renderer.Codespan(StringHelper.Escape(cap[2], true));
186 | continue;
187 | }
188 |
189 | // br
190 | cap = this._rules.Br.Exec(src);
191 | if (cap.Any())
192 | {
193 | src = src.Substring(cap[0].Length);
194 | @out += _options.Renderer.Br();
195 | continue;
196 | }
197 |
198 | // del (gfm)
199 | cap = this._rules.Del.Exec(src);
200 | if (cap.Any())
201 | {
202 | src = src.Substring(cap[0].Length);
203 | @out += _options.Renderer.Del(this.Output(cap[1]));
204 | continue;
205 | }
206 |
207 | // text
208 | cap = this._rules.Text.Exec(src);
209 | if (cap.Any())
210 | {
211 | src = src.Substring(cap[0].Length);
212 | @out += _options.Renderer.Text(StringHelper.Escape(this.Smartypants(cap[0])));
213 | continue;
214 | }
215 |
216 | if (!String.IsNullOrEmpty(src))
217 | {
218 | throw new Exception("Infinite loop on byte: " + (int)src[0]);
219 | }
220 | }
221 |
222 | return @out;
223 | }
224 |
225 | ///
226 | /// Compile Link
227 | ///
228 | protected virtual string OutputLink(IList cap, LinkObj link)
229 | {
230 | string href = StringHelper.Escape(link.Href),
231 | title = !String.IsNullOrEmpty(link.Title) ? StringHelper.Escape(link.Title) : null;
232 |
233 | return cap[0][0] != '!'
234 | ? _options.Renderer.Link(href, title, this.Output(cap[1]))
235 | : _options.Renderer.Image(href, title, StringHelper.Escape(cap[1]));
236 | }
237 |
238 | ///
239 | /// Mangle Links
240 | ///
241 | protected virtual string Mangle(string text)
242 | {
243 | if (!this._options.Mangle) return text;
244 | var @out = String.Empty;
245 |
246 | for (int i = 0; i < text.Length; i++)
247 | {
248 | var ch = text[i].ToString();
249 | if (_random.NextDouble() > 0.5)
250 | {
251 | ch = 'x' + Convert.ToString((int)ch[0], 16);
252 | }
253 | @out += "" + ch + ";";
254 | }
255 |
256 | return @out;
257 | }
258 |
259 | ///
260 | /// Smartypants Transformations
261 | ///
262 | protected virtual string Smartypants(string text)
263 | {
264 | if (!this._options.Smartypants) return text;
265 |
266 | return text
267 | // em-dashes
268 | .Replace("---", "\u2014")
269 | // en-dashes
270 | .Replace("--", "\u2013")
271 | // opening singles
272 | .ReplaceRegex(@"(^|[-\u2014/(\[{""\s])'", "$1\u2018")
273 | // closing singles & apostrophes
274 | .Replace("'", "\u2019")
275 | // opening doubles
276 | .ReplaceRegex(@"(^|[-\u2014/(\[{\u2018\s])""", "$1\u201c")
277 | // closing doubles
278 | .Replace("\"", "\u201d")
279 | // ellipses
280 | .Replace("...", "\u2026");
281 | }
282 | }
283 | }
284 |
--------------------------------------------------------------------------------
/src/MarkedNet/Lexer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Text.RegularExpressions;
6 |
7 |
8 | namespace MarkedNet
9 | {
10 | public class Lexer
11 | {
12 | private readonly Options options;
13 | private readonly BlockRules rules;
14 |
15 |
16 | public Lexer(Options options)
17 | {
18 | this.options = options ?? new Options();
19 | this.rules = new NormalBlockRules();
20 |
21 | if (this.options.Gfm)
22 | {
23 | if (this.options.Tables)
24 | {
25 | this.rules = new TablesBlockRules();
26 | }
27 | else
28 | {
29 | this.rules = new GfmBlockRules();
30 | }
31 | }
32 | }
33 |
34 |
35 | ///
36 | /// Static Lex Method
37 | ///
38 | public static TokensResult Lex(string src, Options options)
39 | {
40 | var lexer = new Lexer(options);
41 | return lexer.Lex(src);
42 | }
43 |
44 | ///
45 | /// Preprocessing
46 | ///
47 | protected virtual TokensResult Lex(string src)
48 | {
49 | src = src
50 | .ReplaceRegex(@"\r\n|\r", "\n")
51 | .Replace("\t", " ")
52 | .Replace("\u00a0", " ")
53 | .Replace("\u2424", "\n");
54 |
55 | return Token(src, true);
56 | }
57 |
58 | ///
59 | /// Lexing
60 | ///
61 | protected virtual TokensResult Token(string srcOrig, bool top, TokensResult result = null)
62 | {
63 | var src = Regex.Replace(srcOrig, @"^ +$", "", RegexOptions.Multiline);
64 | IList cap;
65 | var tokens = result ?? new TokensResult();
66 |
67 | while (!String.IsNullOrEmpty(src))
68 | {
69 | // newline
70 | if ((cap = this.rules.Newline.Exec(src)).Any())
71 | {
72 | src = src.Substring(cap[0].Length);
73 | if (cap[0].Length > 1)
74 | {
75 | tokens.Add(new Token
76 | {
77 | Type = "space"
78 | });
79 | }
80 | }
81 |
82 | // code
83 | if ((cap = this.rules.Сode.Exec(src)).Any())
84 | {
85 | src = src.Substring(cap[0].Length);
86 | var capStr = Regex.Replace(cap[0], @"^ {4}", "", RegexOptions.Multiline);
87 | tokens.Add(new Token
88 | {
89 | Type = "code",
90 | Text = !this.options.Pedantic
91 | ? Regex.Replace(capStr, @"\n+$", "")
92 | : capStr
93 | });
94 | continue;
95 | }
96 |
97 | // fences (gfm)
98 | if ((cap = this.rules.Fences.Exec(src)).Any())
99 | {
100 | src = src.Substring(cap[0].Length);
101 | tokens.Add(new Token
102 | {
103 | Type = "code",
104 | Lang = cap[2],
105 | Text = cap[3]
106 | });
107 | continue;
108 | }
109 |
110 | // heading
111 | if ((cap = this.rules.Heading.Exec(src)).Any())
112 | {
113 | src = src.Substring(cap[0].Length);
114 | tokens.Add(new Token
115 | {
116 | Type = "heading",
117 | Depth = cap[1].Length,
118 | Text = cap[2]
119 | });
120 | continue;
121 | }
122 |
123 | // table no leading pipe (gfm)
124 | if (top && (cap = this.rules.NpTable.Exec(src)).Any())
125 | {
126 | src = src.Substring(cap[0].Length);
127 |
128 | var item = new Token
129 | {
130 | Type = "table",
131 | Header = cap[1].ReplaceRegex(@"^ *| *\| *$", "").SplitRegex(@" *\| *"),
132 | Align = cap[2].ReplaceRegex(@"^ *|\| *$", "").SplitRegex(@" *\| *"),
133 | Cells = cap[3].ReplaceRegex(@"\n$", "").Split('\n').Select(x => new string[] { x }).ToArray()
134 | };
135 |
136 | for (int i = 0; i < item.Align.Count; i++)
137 | {
138 | if (Regex.IsMatch(item.Align[i], @"^ *-+: *$"))
139 | {
140 | item.Align[i] = "right";
141 | }
142 | else if (Regex.IsMatch(item.Align[i], @"^ *:-+: *$"))
143 | {
144 | item.Align[i] = "center";
145 | }
146 | else if (Regex.IsMatch(item.Align[i], @"^ *:-+ *$"))
147 | {
148 | item.Align[i] = "left";
149 | }
150 | else
151 | {
152 | item.Align[i] = null;
153 | }
154 | }
155 |
156 | for (int i = 0; i < item.Cells.Count; i++)
157 | {
158 | item.Cells[i] = item.Cells[i][0].SplitRegex(@" *\| *");
159 | }
160 |
161 | tokens.Add(item);
162 |
163 | continue;
164 | }
165 |
166 | // lheading
167 | if ((cap = this.rules.LHeading.Exec(src)).Any())
168 | {
169 | src = src.Substring(cap[0].Length);
170 | tokens.Add(new Token
171 | {
172 | Type = "heading",
173 | Depth = cap[2] == "=" ? 1 : 2,
174 | Text = cap[1]
175 | });
176 | continue;
177 | }
178 |
179 | // hr
180 | if ((cap = this.rules.Hr.Exec(src)).Any())
181 | {
182 | src = src.Substring(cap[0].Length);
183 | tokens.Add(new Token
184 | {
185 | Type = "hr"
186 | });
187 | continue;
188 | }
189 |
190 | // blockquote
191 | if ((cap = this.rules.Blockquote.Exec(src)).Any())
192 | {
193 | src = src.Substring(cap[0].Length);
194 |
195 | tokens.Add(new Token
196 | {
197 | Type = "blockquote_start"
198 | });
199 |
200 | var capStr = Regex.Replace(cap[0], @"^ *> ?", "", RegexOptions.Multiline);
201 |
202 | // Pass `top` to keep the current
203 | // "toplevel" state. This is exactly
204 | // how markdown.pl works.
205 | Token(capStr, top, tokens); //, true);
206 |
207 | tokens.Add(new Token
208 | {
209 | Type = "blockquote_end"
210 | });
211 |
212 | continue;
213 | }
214 |
215 | // list
216 | if ((cap = this.rules.List.Exec(src)).Any())
217 | {
218 | src = src.Substring(cap[0].Length);
219 | var bull = cap[2];
220 |
221 | tokens.Add(new Token
222 | {
223 | Type = "list_start",
224 | Ordered = bull.Length > 1
225 | });
226 |
227 | // Get each top-level item.
228 | cap = cap[0].Match(this.rules.Item);
229 |
230 | var next = false;
231 | var l = cap.Count;
232 | int i = 0;
233 |
234 | for (; i < l; i++)
235 | {
236 | var item = cap[i];
237 |
238 | // Remove the list item's bullet
239 | // so it is seen as the next token.
240 | var space = item.Length;
241 | item = item.ReplaceRegex(@"^ *([*+-]|\d+\.) +", "");
242 |
243 | // Outdent whatever the
244 | // list item contains. Hacky.
245 | if (item.IndexOf("\n ") > -1)
246 | {
247 | space -= item.Length;
248 | item = !this.options.Pedantic
249 | ? Regex.Replace(item, "^ {1," + space + "}", "", RegexOptions.Multiline)
250 | : Regex.Replace(item, @"/^ {1,4}", "", RegexOptions.Multiline);
251 | }
252 |
253 | // Determine whether the next list item belongs here.
254 | // Backpedal if it does not belong in this list.
255 | if (this.options.SmartLists && i != l - 1)
256 | {
257 | var b = this.rules.Bullet.Exec(cap[i + 1])[0]; // !!!!!!!!!!!
258 | if (bull != b && !(bull.Length > 1 && b.Length > 1))
259 | {
260 | src = String.Join("\n", cap.Skip(i + 1)) + src;
261 | i = l - 1;
262 | }
263 | }
264 |
265 | // Determine whether item is loose or not.
266 | // Use: /(^|\n)(?! )[^\n]+\n\n(?!\s*$)/
267 | // for discount behavior.
268 | var loose = next || Regex.IsMatch(item, @"\n\n(?!\s*$)");
269 | if (i != l - 1)
270 | {
271 | next = item[item.Length - 1] == '\n';
272 | if (!loose) loose = next;
273 | }
274 |
275 | tokens.Add(new Token
276 | {
277 | Type = loose
278 | ? "loose_item_start"
279 | : "list_item_start"
280 | });
281 |
282 | // Recurse.
283 | Token(item, false, tokens);
284 |
285 | tokens.Add(new Token
286 | {
287 | Type = "list_item_end"
288 | });
289 | }
290 |
291 | tokens.Add(new Token
292 | {
293 | Type = "list_end"
294 | });
295 |
296 | continue;
297 | }
298 |
299 | // html
300 | if ((cap = this.rules.Html.Exec(src)).Any())
301 | {
302 | src = src.Substring(cap[0].Length);
303 | tokens.Add(new Token
304 | {
305 | Type = this.options.Sanitize
306 | ? "paragraph"
307 | : "html",
308 | Pre = (this.options.Sanitizer == null)
309 | && (cap[1] == "pre" || cap[1] == "script" || cap[1] == "style"),
310 | Text = cap[0]
311 | });
312 | continue;
313 | }
314 |
315 | // def
316 | if ((top) && (cap = this.rules.Def.Exec(src)).Any())
317 | {
318 | src = src.Substring(cap[0].Length);
319 | tokens.Links[cap[1].ToLower()] = new LinkObj
320 | {
321 | Href = cap[2],
322 | Title = cap[3]
323 | };
324 | continue;
325 | }
326 |
327 | // table (gfm)
328 | if (top && (cap = this.rules.Table.Exec(src)).Any())
329 | {
330 | src = src.Substring(cap[0].Length);
331 |
332 | var item = new Token
333 | {
334 | Type = "table",
335 | Header = cap[1].ReplaceRegex(@"^ *| *\| *$", "").SplitRegex(@" *\| *"),
336 | Align = cap[2].ReplaceRegex(@"^ *|\| *$", "").SplitRegex(@" *\| *"),
337 | Cells = cap[3].ReplaceRegex(@"(?: *\| *)?\n$", "").Split('\n').Select(x => new string[] { x }).ToArray()
338 | };
339 |
340 | for (int i = 0; i < item.Align.Count; i++)
341 | {
342 | if (Regex.IsMatch(item.Align[i], @"^ *-+: *$"))
343 | {
344 | item.Align[i] = "right";
345 | }
346 | else if (Regex.IsMatch(item.Align[i], @"^ *:-+: *$"))
347 | {
348 | item.Align[i] = "center";
349 | }
350 | else if (Regex.IsMatch(item.Align[i], @"^ *:-+ *$"))
351 | {
352 | item.Align[i] = "left";
353 | }
354 | else
355 | {
356 | item.Align[i] = null;
357 | }
358 | }
359 |
360 | for (int i = 0; i < item.Cells.Count; i++)
361 | {
362 | item.Cells[i] = item.Cells[i][0]
363 | .ReplaceRegex(@"^ *\| *| *\| *$", "")
364 | .SplitRegex(@" *\| *");
365 | }
366 |
367 | tokens.Add(item);
368 |
369 | continue;
370 | }
371 |
372 | // top-level paragraph
373 | if (top && (cap = this.rules.Paragraph.Exec(src)).Any())
374 | {
375 | src = src.Substring(cap[0].Length);
376 | tokens.Add(new Token
377 | {
378 | Type = "paragraph",
379 | Text = cap[1][cap[1].Length - 1] == '\n'
380 | ? cap[1].Substring(0, cap[1].Length - 1)
381 | : cap[1]
382 | });
383 | continue;
384 | }
385 |
386 | // text
387 | if ((cap = this.rules.Text.Exec(src)).Any())
388 | {
389 | // Top-level should never reach here.
390 | src = src.Substring(cap[0].Length);
391 | tokens.Add(new Token
392 | {
393 | Type = "text",
394 | Text = cap[0]
395 | });
396 | continue;
397 | }
398 |
399 | if (!String.IsNullOrEmpty(src))
400 | {
401 | throw new Exception("Infinite loop on byte: " + (int)src[0]);
402 | }
403 | }
404 |
405 | return tokens;
406 | }
407 | }
408 | }
409 |
--------------------------------------------------------------------------------