Compare commits

..

23 Commits

Author SHA1 Message Date
Alexandre Mutel
ebc79dafbd Bump version to 0.5.9 2016-06-22 08:37:37 +09:00
Alexandre Mutel
a1d2467643 Fix bug for deep nested list items that were incorrectly detected (not CommonMark compliant. Check with CommonMark specs to add a test case) 2016-06-22 08:35:42 +09:00
Alexandre Mutel
8220f0fa56 Fix ArgumentOutOfRangeException in Block.FindClosestLine. Add tests for Pragmalines 2016-06-22 08:35:08 +09:00
Alexandre Mutel
ec385acc7f Bump version to 0.5.8 2016-06-21 23:45:48 +09:00
Alexandre Mutel
04c1cc62d4 Add block extension methods for looking for a block from a position or for the closest line (typically used for syntax highlighting by MarkdownEditor) 2016-06-21 23:45:40 +09:00
Alexandre Mutel
abdbd65f60 Fix calculation of spans for nested blocks 2016-06-21 23:45:28 +09:00
Alexandre Mutel
1f32a060da Update readme and link to MarkdownEditor 2016-06-20 15:05:39 +09:00
Alexandre Mutel
699d80c150 Add missing test for issue #16 2016-06-20 13:48:22 +09:00
Alexandre Mutel
56bcac7600 Bump version to 0.5.7 2016-06-20 13:42:32 +09:00
Alexandre Mutel
cab3365104 Fix issue while detecting generic attributes that was breaking parsing (issue #16) 2016-06-20 13:42:15 +09:00
Alexandre Mutel
3821bd00fe Bump version to 0.5.6 2016-06-20 12:30:25 +09:00
Alexandre Mutel
62701fd0f1 Improve pragma line output 2016-06-20 12:29:13 +09:00
Alexandre Mutel
1be5e60506 Make to MarkdownPipeline.Setup public. Make Markdown.ToHtml returning a MarkdownDocument 2016-06-20 12:27:50 +09:00
Alexandre Mutel
c9f1512358 Bump to 0.5.5 2016-06-20 09:06:29 +09:00
Alexandre Mutel
8f23aed6af Add pragma line extension 2016-06-20 09:04:33 +09:00
Alexandre Mutel
6a62ae9c69 Add github like class for taslk lists (issue #14) 2016-06-20 09:04:13 +09:00
Alexandre Mutel
2c3de5688b Don't add a class an HtmlAttributes that is already in the list 2016-06-20 09:00:54 +09:00
Alexandre Mutel
f3c08b4ec4 Output HtmlAttributes for unordered list 2016-06-20 09:00:31 +09:00
Alexandre Mutel
69e3baafe5 Add support for callbacks to RendererBase, IMarkdownRenderer 2016-06-20 09:00:10 +09:00
Alexandre Mutel
5844ccc395 Bump to 0.5.4 2016-06-20 06:58:55 +09:00
Alexandre Mutel
be9c6fa54b Fix bug for html block parsing in StringSlice.Search (issue #12) 2016-06-20 06:56:13 +09:00
Alexandre Mutel
d14f277c7b Bump version to 0.5.3 2016-06-19 11:37:26 +09:00
Alexandre Mutel
593bf08b92 Fix bug in pipetables with a trailing | on the header row separator (issue #10) 2016-06-19 11:37:12 +09:00
35 changed files with 755 additions and 99 deletions

View File

@@ -10,12 +10,13 @@ You can **try Markdig online** and compare it to other implementations on [babel
## Features
- **Very fast parser** (no-regexp), very lightweight in terms of GC pressure. See benchmarks
- **Abstract Syntax Tree**
- **Very fast parser and html renderer** (no-regexp), very lightweight in terms of GC pressure. See benchmarks
- **Abstract Syntax Tree** with precise source code location for syntax tree, useful when building a Markdown editor.
- Checkout [MarkdownEditor for Visual Studio](https://visualstudiogallery.msdn.microsoft.com/eaab33c3-437b-4918-8354-872dfe5d1bfe) powered by Markdig!
- Converter to **HTML**
- Passing more than **600+ tests** from the latest [CommonMark specs](http://spec.commonmark.org/)
- Includes all the core elements of CommonMark:
- including GFM fenced code blocks.
- including **GFM fenced code blocks**.
- **Extensible** architecture
- Even the core Markdown/CommonMark parsing is pluggable, so it allows to disable builtin Markdown/Commonmark parsing (e.g [Disable HTML parsing](https://github.com/lunet-io/markdig/blob/7964bd0160d4c18e4155127a4c863d61ebd8944a/src/Markdig/MarkdownExtensions.cs#L306)) or change behaviour (e.g change matching `#` of a headers with `@`)
- Built-in with **20+ extensions**, including:

View File

@@ -62,6 +62,7 @@
<Compile Include="TestHtmlHelper.cs" />
<Compile Include="TestLineReader.cs" />
<Compile Include="TestLinkHelper.cs" />
<Compile Include="TestPragmaLines.cs" />
<Compile Include="TestSourcePosition.cs" />
<Compile Include="TestStringSliceList.cs" />
<Compile Include="TestPlayParser.cs" />

View File

@@ -214,6 +214,76 @@ Column delimiters `|` at the very beginning of a line or just before a line endi
</tbody>
</table>
````````````````````````````````
A pipe may be present at both the beginning/ending of each line:
```````````````````````````````` example
|a|b|
|-|-|
|0|1|
.
<table>
<thead>
<tr>
<th>a</th>
<th>b</th>
</tr>
</thead>
<tbody>
<tr>
<td>0</td>
<td>1</td>
</tr>
</tbody>
</table>
````````````````````````````````
Or may be ommitted on one side:
```````````````````````````````` example
a|b|
-|-|
0|1|
.
<table>
<thead>
<tr>
<th>a</th>
<th>b</th>
</tr>
</thead>
<tbody>
<tr>
<td>0</td>
<td>1</td>
</tr>
</tbody>
</table>
````````````````````````````````
```````````````````````````````` example
|a|b
|-|-
|0|1
.
<table>
<thead>
<tr>
<th>a</th>
<th>b</th>
</tr>
</thead>
<tbody>
<tr>
<td>0</td>
<td>1</td>
</tr>
</tbody>
</table>
````````````````````````````````
Single column table can be declared with lines starting only by a column delimiter:
```````````````````````````````` example

View File

@@ -16631,7 +16631,7 @@ namespace Markdig.Tests
TestParser.TestSpec(" a | b |\n-- | --\n| 0 | 1\n| 2 | 3 |\n 4 | 5 ", "<table>\n<thead>\n<tr>\n<th>a</th>\n<th>b</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>0</td>\n<td>1</td>\n</tr>\n<tr>\n<td>2</td>\n<td>3</td>\n</tr>\n<tr>\n<td>4</td>\n<td>5</td>\n</tr>\n</tbody>\n</table>", "pipetables");
}
}
// Single column table can be declared with lines starting only by a column delimiter:
// A pipe may be present at both the beginning/ending of each line:
[TestFixture]
public partial class TestExtensionsPipeTable
{
@@ -16642,6 +16642,110 @@ namespace Markdig.Tests
// Section: Extensions Pipe Table
//
// The following CommonMark:
// |a|b|
// |-|-|
// |0|1|
//
// Should be rendered as:
// <table>
// <thead>
// <tr>
// <th>a</th>
// <th>b</th>
// </tr>
// </thead>
// <tbody>
// <tr>
// <td>0</td>
// <td>1</td>
// </tr>
// </tbody>
// </table>
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 10, "Extensions Pipe Table");
TestParser.TestSpec("|a|b|\n|-|-|\n|0|1|", "<table>\n<thead>\n<tr>\n<th>a</th>\n<th>b</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>0</td>\n<td>1</td>\n</tr>\n</tbody>\n</table>", "pipetables");
}
}
// Or may be ommitted on one side:
[TestFixture]
public partial class TestExtensionsPipeTable
{
[Test]
public void Example011()
{
// Example 11
// Section: Extensions Pipe Table
//
// The following CommonMark:
// a|b|
// -|-|
// 0|1|
//
// Should be rendered as:
// <table>
// <thead>
// <tr>
// <th>a</th>
// <th>b</th>
// </tr>
// </thead>
// <tbody>
// <tr>
// <td>0</td>
// <td>1</td>
// </tr>
// </tbody>
// </table>
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 11, "Extensions Pipe Table");
TestParser.TestSpec("a|b|\n-|-|\n0|1|", "<table>\n<thead>\n<tr>\n<th>a</th>\n<th>b</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>0</td>\n<td>1</td>\n</tr>\n</tbody>\n</table>", "pipetables");
}
}
[TestFixture]
public partial class TestExtensionsPipeTable
{
[Test]
public void Example012()
{
// Example 12
// Section: Extensions Pipe Table
//
// The following CommonMark:
// |a|b
// |-|-
// |0|1
//
// Should be rendered as:
// <table>
// <thead>
// <tr>
// <th>a</th>
// <th>b</th>
// </tr>
// </thead>
// <tbody>
// <tr>
// <td>0</td>
// <td>1</td>
// </tr>
// </tbody>
// </table>
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 12, "Extensions Pipe Table");
TestParser.TestSpec("|a|b\n|-|-\n|0|1", "<table>\n<thead>\n<tr>\n<th>a</th>\n<th>b</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>0</td>\n<td>1</td>\n</tr>\n</tbody>\n</table>", "pipetables");
}
}
// Single column table can be declared with lines starting only by a column delimiter:
[TestFixture]
public partial class TestExtensionsPipeTable
{
[Test]
public void Example013()
{
// Example 13
// Section: Extensions Pipe Table
//
// The following CommonMark:
// | a
// | --
// | b
@@ -16664,7 +16768,7 @@ namespace Markdig.Tests
// </tbody>
// </table>
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 10, "Extensions Pipe Table");
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 13, "Extensions Pipe Table");
TestParser.TestSpec("| a\n| --\n| b\n| c ", "<table>\n<thead>\n<tr>\n<th>a</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>b</td>\n</tr>\n<tr>\n<td>c</td>\n</tr>\n</tbody>\n</table>", "pipetables");
}
}
@@ -16681,9 +16785,9 @@ namespace Markdig.Tests
public partial class TestExtensionsPipeTable
{
[Test]
public void Example011()
public void Example014()
{
// Example 11
// Example 14
// Section: Extensions Pipe Table
//
// The following CommonMark:
@@ -16712,7 +16816,7 @@ namespace Markdig.Tests
// </tbody>
// </table>
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 11, "Extensions Pipe Table");
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 14, "Extensions Pipe Table");
TestParser.TestSpec(" a | b \n-------|-------\n 0 | 1 \n 2 | 3 ", "<table>\n<thead>\n<tr>\n<th>a</th>\n<th>b</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>0</td>\n<td>1</td>\n</tr>\n<tr>\n<td>2</td>\n<td>3</td>\n</tr>\n</tbody>\n</table>", "pipetables");
}
}
@@ -16722,9 +16826,9 @@ namespace Markdig.Tests
public partial class TestExtensionsPipeTable
{
[Test]
public void Example012()
public void Example015()
{
// Example 12
// Example 15
// Section: Extensions Pipe Table
//
// The following CommonMark:
@@ -16756,7 +16860,7 @@ namespace Markdig.Tests
// </tbody>
// </table>
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 12, "Extensions Pipe Table");
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 15, "Extensions Pipe Table");
TestParser.TestSpec(" a | b | c \n:------|:-------:| ----:\n 0 | 1 | 2 \n 3 | 4 | 5 ", "<table>\n<thead>\n<tr>\n<th>a</th>\n<th style=\"text-align: center;\">b</th>\n<th style=\"text-align: right;\">c</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>0</td>\n<td style=\"text-align: center;\">1</td>\n<td style=\"text-align: right;\">2</td>\n</tr>\n<tr>\n<td>3</td>\n<td style=\"text-align: center;\">4</td>\n<td style=\"text-align: right;\">5</td>\n</tr>\n</tbody>\n</table>", "pipetables");
}
}
@@ -16765,9 +16869,9 @@ namespace Markdig.Tests
public partial class TestExtensionsPipeTable
{
[Test]
public void Example013()
public void Example016()
{
// Example 13
// Example 16
// Section: Extensions Pipe Table
//
// The following CommonMark:
@@ -16782,7 +16886,7 @@ namespace Markdig.Tests
// 0 | 1
// 2 | 3</p>
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 13, "Extensions Pipe Table");
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 16, "Extensions Pipe Table");
TestParser.TestSpec(" a | b\n-------|---x---\n 0 | 1\n 2 | 3 ", "<p>a | b\n-------|---x---\n0 | 1\n2 | 3</p> ", "pipetables");
}
}
@@ -16793,9 +16897,9 @@ namespace Markdig.Tests
public partial class TestExtensionsPipeTable
{
[Test]
public void Example014()
public void Example017()
{
// Example 14
// Example 17
// Section: Extensions Pipe Table
//
// The following CommonMark:
@@ -16824,7 +16928,7 @@ namespace Markdig.Tests
// </tbody>
// </table>
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 14, "Extensions Pipe Table");
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 17, "Extensions Pipe Table");
TestParser.TestSpec(" *a* | b\n----- |-----\n 0 | _1_\n _2 | 3* ", "<table>\n<thead>\n<tr>\n<th><em>a</em></th>\n<th>b</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>0</td>\n<td><em>1</em></td>\n</tr>\n<tr>\n<td>_2</td>\n<td>3*</td>\n</tr>\n</tbody>\n</table>", "pipetables");
}
}
@@ -16835,9 +16939,9 @@ namespace Markdig.Tests
public partial class TestExtensionsPipeTable
{
[Test]
public void Example015()
public void Example018()
{
// Example 15
// Example 18
// Section: Extensions Pipe Table
//
// The following CommonMark:
@@ -16847,7 +16951,7 @@ namespace Markdig.Tests
// Should be rendered as:
// <p>a | b <code>0 |</code></p>
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 15, "Extensions Pipe Table");
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 18, "Extensions Pipe Table");
TestParser.TestSpec("a | b `\n0 | ` ", "<p>a | b <code>0 |</code></p> ", "pipetables");
}
}
@@ -16858,9 +16962,9 @@ namespace Markdig.Tests
public partial class TestExtensionsPipeTable
{
[Test]
public void Example016()
public void Example019()
{
// Example 16
// Example 19
// Section: Extensions Pipe Table
//
// The following CommonMark:
@@ -16884,7 +16988,7 @@ namespace Markdig.Tests
// </tbody>
// </table>
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 16, "Extensions Pipe Table");
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 19, "Extensions Pipe Table");
TestParser.TestSpec("a <a href=\"\" title=\"|\"></a> | b\n-- | --\n0 | 1", "<table>\n<thead>\n<tr>\n<th>a <a href=\"\" title=\"|\"></a></th>\n<th>b</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>0</td>\n<td>1</td>\n</tr>\n</tbody>\n</table>", "pipetables");
}
}
@@ -16895,9 +16999,9 @@ namespace Markdig.Tests
public partial class TestExtensionsPipeTable
{
[Test]
public void Example017()
public void Example020()
{
// Example 17
// Example 20
// Section: Extensions Pipe Table
//
// The following CommonMark:
@@ -16921,7 +17025,7 @@ namespace Markdig.Tests
// </tbody>
// </table>
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 17, "Extensions Pipe Table");
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 20, "Extensions Pipe Table");
TestParser.TestSpec("a | b\n-- | --\n[This is a link with a | inside the label](http://google.com) | 1", "<table>\n<thead>\n<tr>\n<th>a</th>\n<th>b</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><a href=\"http://google.com\">This is a link with a | inside the label</a></td>\n<td>1</td>\n</tr>\n</tbody>\n</table>", "pipetables");
}
}
@@ -19213,15 +19317,15 @@ namespace Markdig.Tests
// - Item4
//
// Should be rendered as:
// <ul>
// <li><input disabled="disabled" type="checkbox" /> Item1</li>
// <li><input disabled="disabled" type="checkbox" checked="checked" /> Item2</li>
// <li><input disabled="disabled" type="checkbox" /> Item3</li>
// <ul class="contains-task-list">
// <li class="task-list-item"><input disabled="disabled" type="checkbox" /> Item1</li>
// <li class="task-list-item"><input disabled="disabled" type="checkbox" checked="checked" /> Item2</li>
// <li class="task-list-item"><input disabled="disabled" type="checkbox" /> Item3</li>
// <li>Item4</li>
// </ul>
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 1, "Extensions TaskLists");
TestParser.TestSpec("- [ ] Item1\n- [x] Item2\n- [ ] Item3\n- Item4", "<ul>\n<li><input disabled=\"disabled\" type=\"checkbox\" /> Item1</li>\n<li><input disabled=\"disabled\" type=\"checkbox\" checked=\"checked\" /> Item2</li>\n<li><input disabled=\"disabled\" type=\"checkbox\" /> Item3</li>\n<li>Item4</li>\n</ul>", "tasklists");
TestParser.TestSpec("- [ ] Item1\n- [x] Item2\n- [ ] Item3\n- Item4", "<ul class=\"contains-task-list\">\n<li class=\"task-list-item\"><input disabled=\"disabled\" type=\"checkbox\" /> Item1</li>\n<li class=\"task-list-item\"><input disabled=\"disabled\" type=\"checkbox\" checked=\"checked\" /> Item2</li>\n<li class=\"task-list-item\"><input disabled=\"disabled\" type=\"checkbox\" /> Item3</li>\n<li>Item4</li>\n</ul>", "tasklists");
}
}
// A task is not recognized outside a list item:

View File

@@ -12,10 +12,10 @@ A task list item consist of `[ ]` or `[x]` or `[X]` inside a list item (ordered
- [ ] Item3
- Item4
.
<ul>
<li><input disabled="disabled" type="checkbox" /> Item1</li>
<li><input disabled="disabled" type="checkbox" checked="checked" /> Item2</li>
<li><input disabled="disabled" type="checkbox" /> Item3</li>
<ul class="contains-task-list">
<li class="task-list-item"><input disabled="disabled" type="checkbox" /> Item1</li>
<li class="task-list-item"><input disabled="disabled" type="checkbox" checked="checked" /> Item2</li>
<li class="task-list-item"><input disabled="disabled" type="checkbox" /> Item3</li>
<li>Item4</li>
</ul>
````````````````````````````````

View File

@@ -27,6 +27,41 @@ Later in a text we are using HTML and it becomes an abbr tag HTML
Console.WriteLine(result);
}
[Test]
public void TestListBug()
{
// TODO: Add this test back to the CommonMark specs
var text = @"- item1
- item2
- item3
- item4";
TestParser.TestSpec(text, @"<ul>
<li>item1
<ul>
<li>item2
<ul>
<li>item3
<ul>
<li>item4</li>
</ul></li>
</ul></li>
</ul></li>
</ul>
");
}
[Test]
public void TestBugAdvancaed()
{
TestParser.TestSpec(@"`https://{domain}/callbacks`
#### HEADING
Paragraph
", "<p><code>https://{domain}/callbacks</code></p>\n<h4 id=\"heading\">HEADING</h4>\n<p>Paragraph</p>", "advanced");
}
[Test]
public void TestSamePipelineAllExtensions()
{

View File

@@ -0,0 +1,81 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using Markdig.Renderers;
using Markdig.Syntax;
using NUnit.Framework;
namespace Markdig.Tests
{
[TestFixture]
public class TestPragmaLines
{
[Test]
public void TestFindClosest()
{
var doc = Markdown.Parse(
"test1\n" + // 0
"\n" + // 1
"test2\n" + // 2
"\n" + // 3
"test3\n" + // 4
"\n" + // 5
"test4\n" + // 6
"\n" + // 7
"# Heading\n" + // 8
"\n" + // 9
"Long para\n" + // 10
"on multiple\n" + // 11
"lines\n" + // 12
"to check that\n" + // 13
"lines are\n" + // 14
"correctly \n" + // 15
"found\n" + // 16
"\n" + // 17
"- item1\n" + // 18
"- item2\n" + // 19
"- item3\n" + // 20
"\n" + // 21
"This is a last paragraph\n" // 22
, new MarkdownPipelineBuilder().UsePragmaLines().Build());
foreach (var exact in new int[] {0, 2, 4, 6, 8, 10, 18, 19, 20, 22})
{
Assert.AreEqual(exact, doc.FindClosestLine(exact));
}
Assert.AreEqual(22, doc.FindClosestLine(23));
Assert.AreEqual(10, doc.FindClosestLine(11));
Assert.AreEqual(10, doc.FindClosestLine(12));
Assert.AreEqual(10, doc.FindClosestLine(13));
Assert.AreEqual(18, doc.FindClosestLine(14)); // > 50% of the paragraph, we switch to next
Assert.AreEqual(18, doc.FindClosestLine(15));
Assert.AreEqual(18, doc.FindClosestLine(16));
}
[Test]
public void TestFindClosest1()
{
var text =
"- item1\n" + // 0
" - item11\n" + // 1
" - item12\n" + // 2
" - item121\n" + // 3
" - item13\n" + // 4
" - item131\n" + // 5
" - item1311\n"; // 6
var pipeline = new MarkdownPipelineBuilder().UsePragmaLines().Build();
var doc = Markdown.Parse(text, pipeline);
for (int exact = 0; exact < 7; exact++)
{
Assert.AreEqual(exact, doc.FindClosestLine(exact));
}
Assert.AreEqual(6, doc.FindClosestLine(50));
}
}
}

View File

@@ -206,19 +206,6 @@ literal ( 0, 4) 4-5
Assert.AreEqual(SourceSpan.Empty, link.TitleSpan);
}
[Test]
public void TestHtmlInline()
{
// 0123456789
Check("01<b>4</b>", @"
paragraph ( 0, 0) 0-9
literal ( 0, 0) 0-1
html ( 0, 2) 2-4
literal ( 0, 5) 5-5
html ( 0, 6) 6-9
");
}
[Test]
public void TestAutolinkInline()
{
@@ -252,6 +239,64 @@ literal ( 4, 0) 16-16
");
}
[Test]
public void TestHtmlBlock1()
{
// 0 1
// 01 2 345678901 23
Check("0\n\n<!--A-->\n1\n", @"
paragraph ( 0, 0) 0-0
literal ( 0, 0) 0-0
html ( 2, 0) 3-10
paragraph ( 3, 0) 12-12
literal ( 3, 0) 12-12
");
}
[Test]
public void TestHtmlComment()
{
// 0 1 2
// 012345678901 234567890 1234
Check("# 012345678\n<!--0-->\n123\n", @"
heading ( 0, 0) 0-10
literal ( 0, 2) 2-10
html ( 1, 0) 12-19
paragraph ( 2, 0) 21-23
literal ( 2, 0) 21-23
");
}
[Test]
public void TestHtmlInline()
{
// 0123456789
Check("01<b>4</b>", @"
paragraph ( 0, 0) 0-9
literal ( 0, 0) 0-1
html ( 0, 2) 2-4
literal ( 0, 5) 5-5
html ( 0, 6) 6-9
");
}
[Test]
public void TestHtmlInline1()
{
// 0
// 0123456789
Check("0<!--A-->1", @"
paragraph ( 0, 0) 0-9
literal ( 0, 0) 0-0
html ( 0, 1) 1-8
literal ( 0, 9) 9-9
");
}
[Test]
public void TestThematicBreak()
{

View File

@@ -98,7 +98,7 @@ namespace Markdig.Extensions.DefinitionLists
processor.Open(definitionItem);
// Update the end position
currentDefinitionList.Span.End = processor.Line.End;
currentDefinitionList.UpdateSpanEnd(processor.Line.End);
return BlockState.Continue;
}

View File

@@ -114,7 +114,7 @@ namespace Markdig.Extensions.Figures
figure.Add(caption);
}
figure.Span.End = line.End;
figure.UpdateSpanEnd(line.End);
// Don't keep the last line
return BlockState.BreakDiscard;
@@ -123,7 +123,7 @@ namespace Markdig.Extensions.Figures
// Reset the indentation to the column before the indent
processor.GoToColumn(processor.ColumnBeforeIndent);
figure.Span.End = line.End;
figure.UpdateSpanEnd(line.End);
return BlockState.Continue;
}

View File

@@ -80,7 +80,7 @@ namespace Markdig.Extensions.Footers
{
processor.NextChar(); // Skip following space
}
block.Span.End = processor.Line.End;
block.UpdateSpanEnd(processor.Line.End);
}
return result;
}

View File

@@ -46,7 +46,7 @@ namespace Markdig.Extensions.GenericAttributes
// Try to find if there is any attributes { in the info string on the first line of a FencedCodeBlock
if (line.Start < line.End)
{
var indexOfAttributes = line.Text.LastIndexOf('{', line.End);
int indexOfAttributes = line.IndexOf('{');
if (indexOfAttributes >= 0)
{
// Work on a copy

View File

@@ -0,0 +1,79 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using System;
using Markdig.Helpers;
using Markdig.Renderers;
using Markdig.Renderers.Html;
using Markdig.Syntax;
using Markdig.Syntax.Inlines;
namespace Markdig.Extensions.PragmaLines
{
/// <summary>
/// Extension to a span for each line containing the original line id (using id = pragma-line#line_number_zero_based)
/// </summary>
/// <seealso cref="Markdig.IMarkdownExtension" />
public class PragmaLineExtension : IMarkdownExtension
{
public void Setup(MarkdownPipelineBuilder pipeline)
{
pipeline.DocumentProcessed -= PipelineOnDocumentProcessed;
pipeline.DocumentProcessed += PipelineOnDocumentProcessed;
}
public void Setup(IMarkdownRenderer renderer)
{
}
private static void PipelineOnDocumentProcessed(MarkdownDocument document)
{
int index = 0;
AddPragmas(document, ref index);
}
private static void AddPragmas(Block block, ref int index)
{
var attribute = block.GetAttributes();
var pragmaId = GetPragmaId(block);
if ( attribute.Id == null)
{
attribute.Id = pragmaId;
}
else if (block.Parent != null)
{
var heading = block as HeadingBlock;
// If we have a heading, we will try to add the tag inside it
// otherwise we will add it just before
var tag = $"<a id=\"{pragmaId}\"></a>";
if (heading?.Inline?.FirstChild != null)
{
heading.Inline.FirstChild.InsertBefore(new HtmlInline() { Tag = tag });
}
else
{
block.Parent.Insert(index, new HtmlBlock(null) { Lines = new StringLineGroup(tag) });
index++;
}
}
var container = block as ContainerBlock;
if (container != null)
{
for (int i = 0; i < container.Count; i++)
{
var subBlock = container[i];
AddPragmas(subBlock, ref i);
}
}
}
private static string GetPragmaId(Block block)
{
return $"pragma-line-{block.Line}";
}
}
}

View File

@@ -28,6 +28,8 @@ namespace Markdig.Extensions.Tables
public void Setup(MarkdownPipelineBuilder pipeline)
{
// Pipe tables require precise source location
pipeline.PreciseSourceLocation = true;
if (!pipeline.BlockParsers.Contains<PipeTableBlockParser>())
{
pipeline.BlockParsers.Insert(0, new PipeTableBlockParser());

View File

@@ -444,8 +444,8 @@ namespace Markdig.Extensions.Tables
}
// Check the left side of a `|` delimiter
TableColumnAlign align;
if (!ParseHeaderString(delimiter.PreviousSibling, out align))
TableColumnAlign align = TableColumnAlign.Left;
if (delimiter.PreviousSibling != null && !ParseHeaderString(delimiter.PreviousSibling, out align))
{
break;
}

View File

@@ -3,6 +3,7 @@
// See the license.txt file in the project root for more information.
using Markdig.Helpers;
using Markdig.Parsers;
using Markdig.Renderers.Html;
using Markdig.Syntax;
namespace Markdig.Extensions.TaskLists
@@ -18,15 +19,29 @@ namespace Markdig.Extensions.TaskLists
public TaskListInlineParser()
{
OpeningCharacters = new[] {'['};
ListClass = "contains-task-list";
ListItemClass = "task-list-item";
}
/// <summary>
/// Gets or sets the list class used for a task list.
/// </summary>
public string ListClass { get; set; }
/// <summary>
/// Gets or sets the list item class used for a task list.
/// </summary>
public string ListItemClass { get; set; }
public override bool Match(InlineProcessor processor, ref StringSlice slice)
{
// A tasklist is either
// [ ]
// or [x] or [X]
if (!(processor.Block.Parent is ListItemBlock))
var listItemBlock = processor.Block.Parent as ListItemBlock;
if (listItemBlock == null)
{
return false;
}
@@ -56,6 +71,19 @@ namespace Markdig.Extensions.TaskLists
};
taskItem.Span.End = taskItem.Span.Start + 2;
processor.Inline = taskItem;
// Add proper class for task list
if (!string.IsNullOrEmpty(ListItemClass))
{
listItemBlock.GetAttributes().AddClass(ListItemClass);
}
var listBlock = (ListBlock) listItemBlock.Parent;
if (!string.IsNullOrEmpty(ListClass))
{
listBlock.GetAttributes().AddClass(ListClass);
}
return true;
}
}

View File

@@ -212,7 +212,7 @@ namespace Markdig.Helpers
index = Start + offset;
for (int i = index; i <= end; i ++)
{
if (Match(text, End, i))
if (Match(text, End, i - Start))
{
index = i + text.Length;
return true;
@@ -221,6 +221,22 @@ namespace Markdig.Helpers
return false;
}
/// <summary>
/// Searches for the specified character within this slice.
/// </summary>
/// <returns>A value >= 0 if the character was found, otherwise &lt; 0</returns>
public int IndexOf(char c)
{
for (int i = Start; i <= End; i++)
{
if (Text[i] == c)
{
return i;
}
}
return -1;
}
/// <summary>
/// Searches the specified text within this slice (matching lowercase).
/// </summary>

View File

@@ -30,13 +30,14 @@ namespace Markdig
}
/// <summary>
/// Converts a Markdown string to HTML.
/// Converts a Markdown string to HTML and output to the specified writer.
/// </summary>
/// <param name="markdown">A Markdown text.</param>
/// <param name="writer">The destination <see cref="TextWriter"/> that will receive the result of the conversion.</param>
/// <param name="pipeline">The pipeline used for the conversion.</param>
/// <returns>The Markdown document that has been parsed</returns>
/// <exception cref="System.ArgumentNullException">if reader or writer variable are null</exception>
public static void ToHtml(string markdown, TextWriter writer, MarkdownPipeline pipeline = null)
public static MarkdownDocument ToHtml(string markdown, TextWriter writer, MarkdownPipeline pipeline = null)
{
if (markdown == null) throw new ArgumentNullException(nameof(markdown));
if (writer == null) throw new ArgumentNullException(nameof(writer));
@@ -49,6 +50,8 @@ namespace Markdig
var document = Parse(markdown, pipeline);
renderer.Render(document);
writer.Flush();
return document;
}
/// <summary>

View File

@@ -19,6 +19,7 @@ using Markdig.Extensions.Hardlines;
using Markdig.Extensions.ListExtras;
using Markdig.Extensions.Mathematics;
using Markdig.Extensions.MediaLinks;
using Markdig.Extensions.PragmaLines;
using Markdig.Extensions.SmartyPants;
using Markdig.Extensions.Tables;
using Markdig.Extensions.TaskLists;
@@ -58,6 +59,17 @@ namespace Markdig
.UseGenericAttributes(); // Must be last as it is one parser that is modifying other parsers
}
/// <summary>
/// Uses pragma lines to output span with an id containing the line number (pragma-line#line_number_zero_based`)
/// </summary>
/// <param name="pipeline">The pipeline.</param>
/// <returns>The modified pipeline</returns>
public static MarkdownPipelineBuilder UsePragmaLines(this MarkdownPipelineBuilder pipeline)
{
pipeline.Extensions.AddIfNotAlready<PragmaLineExtension>();
return pipeline;
}
/// <summary>
/// Uses precise source code location (useful for syntax highlighting).
/// </summary>

View File

@@ -47,7 +47,11 @@ namespace Markdig
internal ProcessDocumentDelegate DocumentProcessed;
internal void Setup(IMarkdownRenderer renderer)
/// <summary>
/// Allows to setup a <see cref="IMarkdownRenderer"/>.
/// </summary>
/// <param name="renderer">The markdown renderer to setup</param>
public void Setup(IMarkdownRenderer renderer)
{
if (renderer == null) throw new ArgumentNullException(nameof(renderer));
foreach (var extension in Extensions)

View File

@@ -216,7 +216,7 @@ namespace Markdig.Parsers
// The line must contain only fence opening character followed only by whitespaces.
if (count <=0 && !processor.IsCodeIndent && (c == '\0' || c.IsWhitespace()) && line.TrimEnd())
{
block.Span.End = line.Start - 1;
block.UpdateSpanEnd(line.Start - 1);
// Don't keep the last line
return BlockState.BreakDiscard;

View File

@@ -185,35 +185,35 @@ namespace Markdig.Parsers
case HtmlBlockType.Comment:
if (line.Search("-->", out endof))
{
htmlBlock.Span.End = endof - 1;
htmlBlock.UpdateSpanEnd(endof - 1);
result = BlockState.Break;
}
break;
case HtmlBlockType.CData:
if (line.Search("]]>", out endof))
{
htmlBlock.Span.End = endof - 1;
htmlBlock.UpdateSpanEnd(endof - 1);
result = BlockState.Break;
}
break;
case HtmlBlockType.ProcessingInstruction:
if (line.Search("?>", out endof))
{
htmlBlock.Span.End = endof - 1;
htmlBlock.UpdateSpanEnd(endof - 1);
result = BlockState.Break;
}
break;
case HtmlBlockType.DocumentType:
if (line.Search(">", out endof))
{
htmlBlock.Span.End = endof - 1;
htmlBlock.UpdateSpanEnd(endof - 1);
result = BlockState.Break;
}
break;
case HtmlBlockType.ScriptPreOrStyle:
if (line.SearchLowercase("</script>", out endof) || line.SearchLowercase("</pre>", out endof) || line.SearchLowercase("</style>", out endof))
{
htmlBlock.Span.End = endof - 1;
htmlBlock.UpdateSpanEnd(endof - 1);
result = BlockState.Break;
}
break;

View File

@@ -48,7 +48,7 @@ namespace Markdig.Parsers
}
if (block != null)
{
block.Span.End = processor.Line.End;
block.UpdateSpanEnd(processor.Line.End);
}
return BlockState.Continue;
}

View File

@@ -153,7 +153,7 @@ namespace Markdig.Parsers
}
// Update list-item source end position
listItem.Span.End = state.Line.End;
listItem.UpdateSpanEnd(state.Line.End);
return BlockState.Continue;
}
@@ -170,11 +170,11 @@ namespace Markdig.Parsers
{
if (state.Indent > columWidth && state.IsCodeIndent)
{
state.GoToColumn(columWidth);
state.GoToColumn(state.ColumnBeforeIndent + columWidth);
}
// Update list-item source end position
listItem.Span.End = state.Line.End;
listItem.UpdateSpanEnd(state.Line.End);
return BlockState.Continue;
}
@@ -352,11 +352,11 @@ namespace Markdig.Parsers
isLastListItem = false;
}
// Update end-position for the list
if (listBlock.Count > 0)
{
listBlock.Span.End = listBlock[listBlock.Count - 1].Span.End;
}
//// Update end-position for the list
//if (listBlock.Count > 0)
//{
// listBlock.Span.End = listBlock[listBlock.Count - 1].Span.End;
//}
return true;
}

View File

@@ -40,7 +40,7 @@ namespace Markdig.Parsers
return TryParseSetexHeading(processor, block);
}
block.Span.End = processor.Line.End;
block.UpdateSpanEnd(processor.Line.End);
return BlockState.Continue;
}
@@ -139,7 +139,7 @@ namespace Markdig.Parsers
return BlockState.BreakDiscard;
}
block.Span.End = state.Line.End;
block.UpdateSpanEnd(state.Line.End);
return BlockState.Continue;
}

View File

@@ -61,7 +61,6 @@ namespace Markdig.Parsers
var c = processor.CurrentChar;
if (c != quote.QuoteChar)
{
block.Span.End = processor.Start - 1;
return processor.IsBlankLine ? BlockState.BreakDiscard : BlockState.None;
}
@@ -71,18 +70,8 @@ namespace Markdig.Parsers
processor.NextChar(); // Skip following space
}
block.Span.End = processor.Line.End;
block.UpdateSpanEnd(processor.Line.End);
return BlockState.Continue;
}
public override bool Close(BlockProcessor processor, Block block)
{
var quoteBlock = block as QuoteBlock;
if (quoteBlock?.LastChild != null)
{
quoteBlock.Span.End = quoteBlock.LastChild.Span.End;
}
return true;
}
}
}

View File

@@ -25,6 +25,6 @@ namespace Markdig
{
public static partial class Markdown
{
public const string Version = "0.5.2";
public const string Version = "0.5.9";
}
}

View File

@@ -44,9 +44,14 @@ namespace Markdig.Renderers.Html
if (name == null) throw new ArgumentNullException(nameof(name));
if (Classes == null)
{
Classes = new List<string>(2); // Use half list compare to default capacity (4), as we don't expect lots of classes
Classes = new List<string>(2);
// Use half list compare to default capacity (4), as we don't expect lots of classes
}
if (!Classes.Contains(name))
{
Classes.Add(name);
}
Classes.Add(name);
}
/// <summary>

View File

@@ -32,7 +32,9 @@ namespace Markdig.Renderers.Html
}
else
{
renderer.WriteLine("<ul>");
renderer.Write("<ul");
renderer.WriteAttributes(listBlock);
renderer.WriteLine(">");
}
foreach (var item in listBlock)
{

View File

@@ -1,6 +1,8 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using System;
using Markdig.Syntax;
using Markdig.Syntax.Inlines;
@@ -11,6 +13,16 @@ namespace Markdig.Renderers
/// </summary>
public interface IMarkdownRenderer
{
/// <summary>
/// Occurs when before writing an object.
/// </summary>
event Action<IMarkdownRenderer, MarkdownObject> ObjectWriteBefore;
/// <summary>
/// Occurs when after writing an object.
/// </summary>
event Action<IMarkdownRenderer, MarkdownObject> ObjectWriteAfter;
/// <summary>
/// Gets the object renderers that will render <see cref="Block"/> and <see cref="Inline"/> elements.
/// </summary>

View File

@@ -35,6 +35,16 @@ namespace Markdig.Renderers
public bool IsLastInContainer { get; private set; }
/// <summary>
/// Occurs when before writing an object.
/// </summary>
public event Action<IMarkdownRenderer, MarkdownObject> ObjectWriteBefore;
/// <summary>
/// Occurs when after writing an object.
/// </summary>
public event Action<IMarkdownRenderer, MarkdownObject> ObjectWriteAfter;
/// <summary>
/// Writes the children of the specified <see cref="ContainerBlock"/>.
/// </summary>
@@ -105,6 +115,10 @@ namespace Markdig.Renderers
var objectType = obj.GetType();
// Calls before writing an object
var writeBefore = ObjectWriteBefore;
writeBefore?.Invoke(this, obj);
// Handle regular renderers
IMarkdownObjectRenderer renderer = previousObjectType == objectType ? previousRenderer : null;
if (renderer == null && !renderersPerType.TryGetValue(objectType, out renderer))
@@ -142,6 +156,10 @@ namespace Markdig.Renderers
previousObjectType = objectType;
previousRenderer = renderer;
// Calls after writing an object
var writeAfter = ObjectWriteAfter;
writeAfter?.Invoke(this, obj);
}
}
}

View File

@@ -75,5 +75,19 @@ namespace Markdig.Syntax
{
ProcessInlinesEnd?.Invoke(state, null);
}
public void UpdateSpanEnd(int spanEnd)
{
// Update parent spans
var parent = this;
while (parent != null)
{
if (spanEnd > parent.Span.End)
{
parent.Span.End = spanEnd;
}
parent = parent.Parent;
}
}
}
}

View File

@@ -0,0 +1,138 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
namespace Markdig.Syntax
{
/// <summary>
/// Extensions for <see cref="Block"/>
/// </summary>
public static class BlockExtensions
{
// TODO: Add test for this code
public static Block FindBlockAtPosition(this Block rootBlock, int position)
{
var contains = rootBlock.CompareToPosition(position) == 0;
var blocks = rootBlock as ContainerBlock;
if (blocks == null || blocks.Count == 0 || !contains)
{
return contains ? rootBlock : null;
}
var lowerIndex = 0;
var upperIndex = blocks.Count - 1;
// binary search on lines
Block block = null;
while (lowerIndex <= upperIndex)
{
int midIndex = (upperIndex - lowerIndex) / 2 + lowerIndex;
block = blocks[midIndex];
int comparison = block.CompareToPosition(position);
if (comparison == 0)
{
break;
}
block = null;
if (comparison < 0)
lowerIndex = midIndex + 1;
else
upperIndex = midIndex - 1;
}
if (block == null)
{
return rootBlock;
}
// Recursively go deep into the block
return FindBlockAtPosition(block, position);
}
public static int FindClosestLine(this MarkdownDocument root, int line)
{
var closestBlock = root.FindClosestBlock(line);
return closestBlock?.Line ?? 0;
}
public static Block FindClosestBlock(this Block rootBlock, int line)
{
var blocks = rootBlock as ContainerBlock;
if (blocks == null || blocks.Count == 0)
{
return rootBlock.Line == line ? rootBlock : null;
}
var lowerIndex = 0;
var upperIndex = blocks.Count - 1;
// binary search on lines
while (lowerIndex <= upperIndex)
{
int midIndex = (upperIndex - lowerIndex) / 2 + lowerIndex;
var block = blocks[midIndex];
int comparison = block.Line.CompareTo(line);
if (comparison == 0)
{
return block;
}
if (comparison < 0)
lowerIndex = midIndex + 1;
else
upperIndex = midIndex - 1;
}
// If we are between two lines, try to find the best spot
if (lowerIndex > 0 && lowerIndex < blocks.Count)
{
var prevBlock = blocks[lowerIndex - 1].FindClosestBlock(line) ?? blocks[lowerIndex - 1];
var nextBlock = blocks[lowerIndex].FindClosestBlock(line) ?? blocks[lowerIndex];
if (prevBlock.Line == line)
{
return prevBlock;
}
if (nextBlock.Line == line)
{
return nextBlock;
}
// we calculate the position of the current line relative to the line found and previous line
var prevLine = prevBlock.Line;
var nextLine = nextBlock.Line;
var middle = (line - prevLine) * 1.0 / (nextLine - prevLine);
// If relative position < 0.5, we select the previous line, otherwise we select the line found
return middle < 0.5 ? prevBlock : nextBlock;
}
if (lowerIndex == 0)
{
var prevBlock = blocks[lowerIndex].FindClosestBlock(line) ?? blocks[lowerIndex];
return prevBlock;
}
if (lowerIndex == blocks.Count)
{
var prevBlock = blocks[lowerIndex - 1].FindClosestBlock(line) ?? blocks[lowerIndex - 1];
return prevBlock;
}
return null;
}
public static bool ContainsPosition(this Block block, int position)
{
return CompareToPosition(block, position) == 0;
}
public static int CompareToPosition(this Block block, int position)
{
return position < block.Span.Start ? 1 : position > block.Span.End + 1 ? -1 : 0;
}
}
}

View File

@@ -69,10 +69,7 @@ namespace Markdig.Syntax
children[Count++] = item;
item.Parent = this;
if (item.Span.End > Span.End)
{
Span.End = item.Span.End;
}
UpdateSpanEnd(item.Span.End);
}
private void EnsureCapacity(int min)

View File

@@ -1,6 +1,6 @@
{
"title": "Markdig",
"version": "0.5.2",
"version": "0.5.9",
"authors": [ "Alexandre Mutel" ],
"description": "A fast, powerfull, CommonMark compliant, extensible Markdown processor for .NET",
"copyright": "Alexandre Mutel",
@@ -11,7 +11,7 @@
"projectUrl": "https://github.com/lunet-io/markdig",
"iconUrl": "https://raw.githubusercontent.com/lunet-io/markdig/master/img/markdig.png",
"requireLicenseAcceptance": false,
"releaseNotes": "> 0.5.2:\nAdd better support for precise source location for link parts (label, url...)\n> 0.5.1:\nAdd support for task lists.\nAdd support for precise source location pipelineBuilder.UsePreciseSourceLocation.\nBreaking change: Markdown.ToHtml() accept now only a string instead of a TextReader as this improve the overall performance.",
"releaseNotes": "- Fix ArgumentOutOfRangeException for FindClosestBlock\n- Fix bug in nested list items\n",
"tags": [ "Markdown CommonMark md html md2html" ]
},
"configurations": {