Compare commits

...

54 Commits

Author SHA1 Message Date
Alexandre Mutel
16a9bbc84e Bump to 0.14.2 2017-11-01 07:35:07 +01:00
Alexandre Mutel
0e5338a709 Add option to disable smiley parsing in EmojiAndSmiley extension 2017-11-01 07:34:57 +01:00
Alexandre Mutel
9139e0142b Fix issue with emphasis preceded/followed by an HTML entity (#157) 2017-11-01 07:26:28 +01:00
Alexandre Mutel
9a38312df0 Add support for link reference definitions for Normalize renderer (#155) 2017-10-28 09:26:14 +02:00
Alexandre Mutel
d808dcf6f8 Bump to 0.14.1 2017-10-27 22:45:53 +02:00
Alexandre Mutel
d5985fc94c Add missing HtmlBlockRenderer 2017-10-27 22:44:58 +02:00
Alexandre Mutel
994687d5ae Use original bullet character if NormalizeOptions.ListItemCharacter is not set 2017-10-27 22:44:37 +02:00
Alexandre Mutel
2f4e958ab2 Bump to 0.14.0 2017-10-27 22:13:49 +02:00
Alexandre Mutel
26beaa81da Add comments to NormalizeOptions (#155) 2017-10-27 22:08:34 +02:00
Alexandre Mutel
de5ed11963 Add support for NormalizeOptions. Add a few more tests. Better handling of loose lists. 2017-10-27 22:01:43 +02:00
Alexandre Mutel
b557d51276 Don't output a space after a blockquote 2017-10-27 18:41:09 +02:00
Alexandre Mutel
27a8345943 Use Assert.AreEqual instead of string.Compare for better error reporting 2017-10-27 18:34:38 +02:00
Alexandre Mutel
131163ff9a Add bigger sample for normalize (#155) 2017-10-27 18:34:14 +02:00
Alexandre Mutel
241f674b99 Rename test names for normalize (#155) 2017-10-27 18:33:55 +02:00
Alexandre Mutel
194edee243 Fix normalize, don't introduce non necessary new lines (#155) 2017-10-27 18:32:43 +02:00
Alexandre Mutel
0995fa0cec Add tests for backslash and hard lines 2017-10-27 18:32:01 +02:00
Alexandre Mutel
6717be5210 Add support for escape characters for normalize (#155) 2017-10-27 18:30:32 +02:00
Alexandre Mutel
e15745f346 Fix escape inline parser to use the plain string instead of only a single string char (to allow literal continuation) 2017-10-27 18:28:57 +02:00
Alexandre Mutel
a513b0c587 Merge pull request #156 from leotsarev/add-plain-text-helper
Add simple helper that enables conversion to plain text
2017-10-27 10:54:03 +02:00
Leonid Tsarev
72adb963e8 Add simple helper that enables conversion to plain text 2017-10-27 11:35:23 +03:00
Alexandre Mutel
aac7df6b87 Merge remote-tracking branch 'origin/normalize' 2017-10-25 17:11:15 +02:00
Alexandre Mutel
4cf4bfb58f Merge pull request #154 from tthiery/normalize
Add support for normalize (core CommonMark for now)
2017-10-24 17:38:42 +02:00
T. Thiery
59630aec8e Review Fix: Make IntLog10Fast method private static. 2017-10-24 17:27:42 +02:00
Alexandre Mutel
70c4f6deda Merge pull request #140 from peinearydevelopment/master
Added EnableHtmlForBlock flag on HtmlRenderer for issue #104
2017-10-24 17:02:42 +02:00
T. Thiery
e2b3f812cb Merge remote-tracking branch 'upstream/normalize' into normalize 2017-10-24 16:53:42 +02:00
Alexandre Mutel
fc8adc70e0 Merge tag 'v0.13.4' into normalize
# Conflicts:
#	src/Markdig.Tests/Markdig.Tests.csproj
2017-10-24 16:28:58 +02:00
T. Thiery
72c2c06fcb Add normalization tests for space between leaves. 2017-10-24 15:58:44 +02:00
T. Thiery
7174e32b7a Remove incomplete UnorderedList syntax test case 2017-10-24 15:51:13 +02:00
T. Thiery
600219529c Add test approach for AST based arrange. 2017-10-20 16:10:02 +02:00
Alexandre Mutel
d7fd04d14c Bump to 0.13.4 2017-10-17 23:01:26 +02:00
Alexandre Mutel
2147e434f7 Add support for single table header row without a table body rows (#141) 2017-10-17 22:58:25 +02:00
Alexandre Mutel
7bd00d115f Merge pull request #149 from maulwuff/nomnomlSupport
Nomnoml support
2017-10-17 15:44:17 +02:00
maulwuff
80ef9d2799 doc: added nomnoml specs 2017-10-17 14:54:43 +02:00
maulwuff
d6a705d76c new: support nomnoml diagrams 2017-10-17 14:54:43 +02:00
Alexandre Mutel
42472085a6 Add mdtoc simple tool to generate a markdown TOC from a local markdown file or github link to a markdown file 2017-10-15 23:31:14 +02:00
Alexandre Mutel
3897e875ee Add KeepOpeningDigits and DiscardDots options to AutoIdentifiers to match GitHub behavior 2017-10-15 23:29:02 +02:00
T. Thiery
c63392657d Add finish block method and adjusted naming in test suite. 2017-09-18 23:28:34 +02:00
T. Thiery
34579b51a1 Relabeled unit tests. 2017-09-18 23:04:08 +02:00
T. Thiery
1bb35c5fc1 Fixed issues found in straight forward normalization test cases
- Code fences with attributes
- Escaping inline code
- Escaping link titles
- Encode hard line breaks
- HTML Entity emitting
- List intend.
2017-09-18 22:16:50 +02:00
T. Thiery
0c408951b8 Add straight forward unit tests. 2017-09-18 20:42:50 +02:00
Alexandre Mutel
218a094f0d Try to workaround tight/loose lists and paragraphs (wip #17) 2017-09-17 11:08:08 +02:00
Alexandre Mutel
3cc405b05b Add normalize renderers for core CommonMark components (wip #17) 2017-09-17 11:08:08 +02:00
Alexandre Mutel
d58db530bb wip normalize (issue #17) 2017-09-17 11:07:44 +02:00
Alexandre Mutel
3628dc3b17 Merge pull request #142 from mlaily/patch-1
Fix typos in EmojiSpecs.md
2017-09-13 22:30:24 +02:00
Melvyn Laïly
89ff42805d Fix typos in EmojiSpecs.md 2017-09-13 22:26:10 +02:00
Peineary Development
48866a2609 Added EnableHtmlForBlock flag on HtmlRenderer for issue #104 2017-09-11 22:43:23 -04:00
Alexandre Mutel
9cc5856c1c Bump to 0.13.3 2017-08-30 15:47:54 +02:00
Alexandre Mutel
a4bb174a77 Add .editorconfig 2017-08-30 15:44:08 +02:00
Alexandre Mutel
82987fa879 Merge pull request #139 from kenmuse/enhance/yaml-frontmatter
Implement Pandoc compatible front matter parsing
2017-08-30 15:36:38 +02:00
Ken Muse
2df2ff17c5 Replace tabs with spaces 2017-08-30 09:29:27 -04:00
Ken Muse
4e4825cb3f Implement Pandoc compatible front matter parsing 2017-08-29 18:49:14 -04:00
Alexandre Mutel
b20b111385 Try to workaround tight/loose lists and paragraphs (wip #17) 2016-06-28 23:55:01 +09:00
Alexandre Mutel
6ac2429e2a Add normalize renderers for core CommonMark components (wip #17) 2016-06-28 23:37:52 +09:00
Alexandre Mutel
9d52732f18 wip normalize (issue #17) 2016-06-28 10:53:08 +09:00
71 changed files with 2367 additions and 140 deletions

38
.editorconfig Normal file
View File

@@ -0,0 +1,38 @@
# EditorConfig is awesome:http://EditorConfig.org
# top-most EditorConfig file
root = true
# All Files
[*]
charset = utf-8
end_of_line = crlf
indent_style = space
indent_size = 4
insert_final_newline = false
trim_trailing_whitespace = true
# Solution Files
[*.sln]
indent_style = tab
# XML Project Files
[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}]
indent_size = 2
# Configuration Files
[*.{json,xml,yml,config,props,targets,nuspec,resx,ruleset}]
indent_size = 2
# Markdown Files
[*.md]
trim_trailing_whitespace = false
# Web Files
[*.{htm,html,js,ts,css,scss,less}]
indent_size = 2
insert_final_newline = true
# Bash Files
[*.sh]
end_of_line = lf

View File

@@ -47,7 +47,7 @@ You can **try Markdig online** and compare it to other implementations on [babel
- [**Emoji**](src/Markdig.Tests/Specs/EmojiSpecs.md) support (inspired from [Markdown-it](https://markdown-it.github.io/))
- [**SmartyPants**](src/Markdig.Tests/Specs/SmartyPantsSpecs.md) (inspired from [Daring Fireball - SmartyPants](https://daringfireball.net/projects/smartypants/))
- [**Bootstrap**](src/Markdig.Tests/Specs/BootstrapSpecs.md) class (to output bootstrap class)
- [**Diagrams**](src/Markdig.Tests/Specs/DiagramsSpecs.md) extension whenever a fenced code block contains a special keyword, it will be converted to a div block with the content as-is (currently, supports only for [`mermaid` diagrams](https://knsv.github.io/mermaid/))
- [**Diagrams**](src/Markdig.Tests/Specs/DiagramsSpecs.md) extension whenever a fenced code block contains a special keyword, it will be converted to a div block with the content as-is (currently, supports [`mermaid`](https://knsv.github.io/mermaid/) and [`nomnoml`](https://github.com/skanaar/nomnoml) diagrams)
- [**YAML frontmatter**](src/Markdig.Tests/Specs/YamlSpecs.md) to parse without evaluating the frontmatter and to discard it from the HTML output (typically used for previewing without the frontmatter in MarkdownEditor)
- [**JIRA links**](src/Markdig.Tests/Specs/JiraLinks.md) to automatically generate links for JIRA project references (Thanks to @clarkd: https://github.com/clarkd/MarkdigJiraLinker)
- Compatible with .NET 3.5, 4.0+ and .NET Core (`netstandard1.1+`)

View File

@@ -61,7 +61,9 @@
<Compile Include="TestHtmlHelper.cs" />
<Compile Include="TestLineReader.cs" />
<Compile Include="TestLinkHelper.cs" />
<Compile Include="TestNormalize.cs" />
<Compile Include="TestOrderedList.cs" />
<Compile Include="TestPlainText.cs" />
<Compile Include="TestPragmaLines.cs" />
<Compile Include="TestSourcePosition.cs" />
<Compile Include="TestStringSliceList.cs" />

View File

@@ -23,4 +23,35 @@ graph TD;
</div>
````````````````````````````````
## nomnoml diagrams
Using a fenced code block with the `nomnoml` language info will output a `<div class='nomnoml'>` instead of a `pre/code` block:
```````````````````````````````` example
```nomnoml
[example|
propertyA: Int
propertyB: string
|
methodA()
methodB()
|
[subA]--[subB]
[subA]-:>[sub C]
]
```
.
<div class="nomnoml">[example|
propertyA: Int
propertyB: string
|
methodA()
methodB()
|
[subA]--[subB]
[subA]-:>[sub C]
]
</div>
````````````````````````````````
TODO: Add other text diagram languages

View File

@@ -31,9 +31,9 @@ We all need :), it makes us :muscle:. (and :ok_hand:).
Sentences can end with Emoji:
```````````````````````````````` example
This is a sentance :ok_hand:
This is a sentence :ok_hand:
and keeps going to the next line :)
.
<p>This is a sentance 👌
<p>This is a sentence 👌
and keeps going to the next line 😃</p>
````````````````````````````````
````````````````````````````````

View File

@@ -513,6 +513,39 @@ a | b
</table>
````````````````````````````````
** Rule #9**
It is possible to have a single row header only:
```````````````````````````````` example
a | b
-- | --
.
<table>
<thead>
<tr>
<th>a</th>
<th>b</th>
</tr>
</thead>
</table>
````````````````````````````````
```````````````````````````````` example
|a|b|c
|---|---|---|
.
<table>
<thead>
<tr>
<th>a</th>
<th>b</th>
<th>c</th>
</tr>
</thead>
</table>
````````````````````````````````
** Tests **
Tests trailing spaces after pipes

View File

@@ -1,4 +1,4 @@
using System;
using System;
using NUnit.Framework;
namespace Markdig.Tests
@@ -17259,9 +17259,9 @@ namespace Markdig.Tests
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|advanced");
}
}
// ** Tests **
// ** Rule #9**
//
// Tests trailing spaces after pipes
// It is possible to have a single row header only:
[TestFixture]
public partial class TestExtensionsPipeTable
{
@@ -17272,6 +17272,64 @@ namespace Markdig.Tests
// Section: Extensions Pipe Table
//
// The following CommonMark:
// a | b
// -- | --
//
// Should be rendered as:
// <table>
// <thead>
// <tr>
// <th>a</th>
// <th>b</th>
// </tr>
// </thead>
// </table>
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 22, "Extensions Pipe Table");
TestParser.TestSpec("a | b\n-- | --", "<table>\n<thead>\n<tr>\n<th>a</th>\n<th>b</th>\n</tr>\n</thead>\n</table>", "pipetables|advanced");
}
}
[TestFixture]
public partial class TestExtensionsPipeTable
{
[Test]
public void Example023()
{
// Example 23
// Section: Extensions Pipe Table
//
// The following CommonMark:
// |a|b|c
// |---|---|---|
//
// Should be rendered as:
// <table>
// <thead>
// <tr>
// <th>a</th>
// <th>b</th>
// <th>c</th>
// </tr>
// </thead>
// </table>
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 23, "Extensions Pipe Table");
TestParser.TestSpec("|a|b|c\n|---|---|---|", "<table>\n<thead>\n<tr>\n<th>a</th>\n<th>b</th>\n<th>c</th>\n</tr>\n</thead>\n</table>", "pipetables|advanced");
}
}
// ** Tests **
//
// Tests trailing spaces after pipes
[TestFixture]
public partial class TestExtensionsPipeTable
{
[Test]
public void Example024()
{
// Example 24
// Section: Extensions Pipe Table
//
// The following CommonMark:
// | abc | def |
// |---|---|
// | cde| ddd|
@@ -17307,7 +17365,7 @@ namespace Markdig.Tests
// </tbody>
// </table>
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 22, "Extensions Pipe Table");
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 24, "Extensions Pipe Table");
TestParser.TestSpec("| abc | def | \n|---|---|\n| cde| ddd| \n| eee| fff|\n| fff | fffff | \n|gggg | ffff | ", "<table>\n<thead>\n<tr>\n<th>abc</th>\n<th>def</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>cde</td>\n<td>ddd</td>\n</tr>\n<tr>\n<td>eee</td>\n<td>fff</td>\n</tr>\n<tr>\n<td>fff</td>\n<td>fffff</td>\n</tr>\n<tr>\n<td>gggg</td>\n<td>ffff</td>\n</tr>\n</tbody>\n</table>", "pipetables|advanced");
}
}
@@ -17318,9 +17376,9 @@ namespace Markdig.Tests
public partial class TestExtensionsPipeTable
{
[Test]
public void Example023()
public void Example025()
{
// Example 23
// Example 25
// Section: Extensions Pipe Table
//
// The following CommonMark:
@@ -17346,7 +17404,7 @@ namespace Markdig.Tests
// </tbody>
// </table>
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 23, "Extensions Pipe Table");
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 25, "Extensions Pipe Table");
TestParser.TestSpec("a | b\n-- | - \n0 | 1 | 2", "<table>\n<thead>\n<tr>\n<th>a</th>\n<th>b</th>\n<th></th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>0</td>\n<td>1</td>\n<td>2</td>\n</tr>\n</tbody>\n</table>", "pipetables|advanced");
}
}
@@ -18664,15 +18722,15 @@ namespace Markdig.Tests
// Section: Extensions Emoji
//
// The following CommonMark:
// This is a sentance :ok_hand:
// This is a sentence :ok_hand:
// and keeps going to the next line :)
//
// Should be rendered as:
// <p>This is a sentance 👌
// <p>This is a sentence 👌
// and keeps going to the next line 😃</p>
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 4, "Extensions Emoji");
TestParser.TestSpec("This is a sentance :ok_hand:\nand keeps going to the next line :)", "<p>This is a sentance 👌\nand keeps going to the next line 😃</p>", "emojis|advanced+emojis");
TestParser.TestSpec("This is a sentence :ok_hand:\nand keeps going to the next line :)", "<p>This is a sentence 👌\nand keeps going to the next line 😃</p>", "emojis|advanced+emojis");
}
}
// # Extensions
@@ -20361,6 +20419,49 @@ namespace Markdig.Tests
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 1, "Extensions Mermaid diagrams");
TestParser.TestSpec("```mermaid\ngraph TD;\n A-->B;\n A-->C;\n B-->D;\n C-->D;\n```", "<div class=\"mermaid\">graph TD;\n A-->B;\n A-->C;\n B-->D;\n C-->D;\n</div>", "diagrams|advanced");
}
}
// ## nomnoml diagrams
//
// Using a fenced code block with the `nomnoml` language info will output a `<div class='nomnoml'>` instead of a `pre/code` block:
[TestFixture]
public partial class TestExtensionsnomnomldiagrams
{
[Test]
public void Example002()
{
// Example 2
// Section: Extensions nomnoml diagrams
//
// The following CommonMark:
// ```nomnoml
// [example|
// propertyA: Int
// propertyB: string
// |
// methodA()
// methodB()
// |
// [subA]--[subB]
// [subA]-:>[sub C]
// ]
// ```
//
// Should be rendered as:
// <div class="nomnoml">[example|
// propertyA: Int
// propertyB: string
// |
// methodA()
// methodB()
// |
// [subA]--[subB]
// [subA]-:>[sub C]
// ]
// </div>
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 2, "Extensions nomnoml diagrams");
TestParser.TestSpec("```nomnoml\n[example|\n propertyA: Int\n propertyB: string\n|\n methodA()\n methodB()\n|\n [subA]--[subB]\n [subA]-:>[sub C]\n]\n```", "<div class=\"nomnoml\">[example|\n propertyA: Int\n propertyB: string\n|\n methodA()\n methodB()\n|\n [subA]--[subB]\n [subA]-:>[sub C]\n]\n</div>", "diagrams|advanced");
}
}
// TODO: Add other text diagram languages
// # Extensions
@@ -20492,6 +20593,124 @@ namespace Markdig.Tests
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 3, "Extensions YAML frontmatter discard");
TestParser.TestSpec("----\nthis: is a frontmatter\n----\nThis is a text", "<hr />\n<h2>this: is a frontmatter</h2>\n<p>This is a text</p>", "yaml");
}
}
// It can end with three dots `...`:
[TestFixture]
public partial class TestExtensionsYAMLfrontmatterdiscard
{
[Test]
public void Example004()
{
// Example 4
// Section: Extensions YAML frontmatter discard
//
// The following CommonMark:
// ---
// this: is a frontmatter
// ...
// This is a text
//
// Should be rendered as:
// <p>This is a text</p>
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 4, "Extensions YAML frontmatter discard");
TestParser.TestSpec("---\nthis: is a frontmatter\n...\nThis is a text", "<p>This is a text</p>", "yaml");
}
}
// It expects exactly three dots `...`:
[TestFixture]
public partial class TestExtensionsYAMLfrontmatterdiscard
{
[Test]
public void Example005()
{
// Example 5
// Section: Extensions YAML frontmatter discard
//
// The following CommonMark:
// ---
// this: is a frontmatter
// ....
// This is a text
//
// Should be rendered as:
//
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 5, "Extensions YAML frontmatter discard");
TestParser.TestSpec("---\nthis: is a frontmatter\n....\nThis is a text", "", "yaml");
}
}
// Front matter ends with the first line containing three dots `...` or three dashes `...`:
[TestFixture]
public partial class TestExtensionsYAMLfrontmatterdiscard
{
[Test]
public void Example006()
{
// Example 6
// Section: Extensions YAML frontmatter discard
//
// The following CommonMark:
// ---
// this: is a frontmatter
// ....
//
// Hello
// ---
// This is a text
//
// Should be rendered as:
// <p>This is a text</p>
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 6, "Extensions YAML frontmatter discard");
TestParser.TestSpec("---\nthis: is a frontmatter\n....\n\nHello\n---\nThis is a text", "<p>This is a text</p>", "yaml");
}
}
// It expects whitespace can exist after the leading characters
[TestFixture]
public partial class TestExtensionsYAMLfrontmatterdiscard
{
[Test]
public void Example007()
{
// Example 7
// Section: Extensions YAML frontmatter discard
//
// The following CommonMark:
// ---
// this: is a frontmatter
// ...
// This is a text
//
// Should be rendered as:
// <p>This is a text</p>
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 7, "Extensions YAML frontmatter discard");
TestParser.TestSpec("--- \nthis: is a frontmatter\n...\nThis is a text", "<p>This is a text</p>", "yaml");
}
}
// It expects whitespace can exist after the trailing characters
[TestFixture]
public partial class TestExtensionsYAMLfrontmatterdiscard
{
[Test]
public void Example008()
{
// Example 8
// Section: Extensions YAML frontmatter discard
//
// The following CommonMark:
// ---
// this: is a frontmatter
// ...
// This is a text
//
// Should be rendered as:
// <p>This is a text</p>
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 8, "Extensions YAML frontmatter discard");
TestParser.TestSpec("---\nthis: is a frontmatter\n... \nThis is a text", "<p>This is a text</p>", "yaml");
}
}
// # Extensions
//

View File

@@ -1,4 +1,4 @@
<#/*
<#/*
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.The MIT License (MIT)
@@ -36,7 +36,8 @@ SOFTWARE.
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.CodeDom" #>
<#@ import namespace="System.CodeDom.Compiler" #>
<#@ output extension=".cs" #><#
<#@ output extension=".cs" encoding="utf-8"#>
<#
var specFiles = new KeyValuePair<string, string>[] {
new KeyValuePair<string, string>("https://raw.githubusercontent.com/jgm/CommonMark/4ec06917c3a3632be4a935ffa0973092bd2621be/spec.txt", string.Empty), // 0.28
new KeyValuePair<string, string>(Host.ResolvePath("PipeTableSpecs.md"), "pipetables|advanced"),

View File

@@ -42,3 +42,62 @@ This is a text
<p>This is a text</p>
````````````````````````````````
It can end with three dots `...`:
```````````````````````````````` example
---
this: is a frontmatter
...
This is a text
.
<p>This is a text</p>
````````````````````````````````
It expects exactly three dots `...`:
```````````````````````````````` example
---
this: is a frontmatter
....
This is a text
.
````````````````````````````````
Front matter ends with the first line containing three dots `...` or three dashes `...`:
```````````````````````````````` example
---
this: is a frontmatter
....
Hello
---
This is a text
.
<p>This is a text</p>
````````````````````````````````
It expects whitespace can exist after the leading characters
```````````````````````````````` example
---
this: is a frontmatter
...
This is a text
.
<p>This is a text</p>
````````````````````````````````
It expects whitespace can exist after the trailing characters
```````````````````````````````` example
---
this: is a frontmatter
...
This is a text
.
<p>This is a text</p>
````````````````````````````````

View File

@@ -0,0 +1,452 @@
// 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 NUnit.Framework;
using Markdig.Syntax;
using Markdig.Syntax.Inlines;
using System.IO;
using Markdig.Renderers.Normalize;
using Markdig.Helpers;
namespace Markdig.Tests
{
[TestFixture]
public class TestNormalize
{
[Test]
public void SyntaxCodeBlock()
{
AssertSyntax("````csharp\npublic void HelloWorld()\n{\n}\n````", new FencedCodeBlock(null)
{
FencedChar = '`',
FencedCharCount = 4,
Info = "csharp",
Lines = new StringLineGroup(4)
{
new StringSlice("public void HelloWorld()"),
new StringSlice("{"),
new StringSlice("}"),
}
});
AssertSyntax(" public void HelloWorld()\n {\n }", new CodeBlock(null)
{
Lines = new StringLineGroup(4)
{
new StringSlice("public void HelloWorld()"),
new StringSlice("{"),
new StringSlice("}"),
}
});
}
[Test]
public void SyntaxHeadline()
{
AssertSyntax("## Headline", new HeadingBlock(null)
{
HeaderChar = '#',
Level = 2,
Inline = new ContainerInline().AppendChild(new LiteralInline("Headline")),
});
}
[Test]
public void SyntaxParagraph()
{
AssertSyntax("This is a normal paragraph", new ParagraphBlock()
{
Inline = new ContainerInline()
.AppendChild(new LiteralInline("This is a normal paragraph")),
});
AssertSyntax("This is a\nnormal\nparagraph", new ParagraphBlock()
{
Inline = new ContainerInline()
.AppendChild(new LiteralInline("This is a"))
.AppendChild(new LineBreakInline())
.AppendChild(new LiteralInline("normal"))
.AppendChild(new LineBreakInline())
.AppendChild(new LiteralInline("paragraph")),
});
}
[Test]
public void CodeBlock()
{
AssertNormalizeNoTrim(" public void HelloWorld();\n {\n }");
AssertNormalizeNoTrim(" public void HelloWorld();\n {\n }\n\ntext after two newlines");
AssertNormalizeNoTrim("````\npublic void HelloWorld();\n{\n}\n````\n\ntext after two newlines");
AssertNormalizeNoTrim("````csharp\npublic void HelloWorld();\n{\n}\n````");
AssertNormalizeNoTrim("````csharp hideNewKeyword=true\npublic void HelloWorld();\n{\n}\n````");
}
[Test]
public void Heading()
{
AssertNormalizeNoTrim("# Heading");
AssertNormalizeNoTrim("## Heading");
AssertNormalizeNoTrim("### Heading");
AssertNormalizeNoTrim("#### Heading");
AssertNormalizeNoTrim("##### Heading");
AssertNormalizeNoTrim("###### Heading");
AssertNormalizeNoTrim("###### Heading\n\ntext after two newlines");
AssertNormalizeNoTrim("# Heading\nAnd Text1\n\nAndText2", options: new NormalizeOptions() { EmptyLineAfterHeading = false });
AssertNormalizeNoTrim("Heading\n=======\n\ntext after two newlines", "# Heading\n\ntext after two newlines");
}
[Test]
public void Backslash()
{
AssertNormalizeNoTrim("This is a hardline \nAnd this is another hardline\\\nThis is standard newline");
AssertNormalizeNoTrim("This is a line\nWith another line\nAnd a last line");
}
[Test]
public void HtmlBlock()
{
/*AssertNormalizeNoTrim(@"<div id=""foo"" class=""bar
baz"">
</ div >");*/ // TODO: Bug: Throws Exception during emit
}
[Test]
public void Paragraph()
{
AssertNormalizeNoTrim("This is a plain paragraph");
AssertNormalizeNoTrim(@"This
is
a
plain
paragraph");
}
[Test]
public void ParagraphMulti()
{
AssertNormalizeNoTrim(@"line1
line2
line3");
}
[Test]
public void ListUnordered()
{
AssertNormalizeNoTrim(@"- a
- b
- c");
}
[Test]
public void ListUnorderedLoose()
{
AssertNormalizeNoTrim(@"- a
- b
- c");
}
[Test]
public void ListOrderedLooseAndCodeBlock()
{
AssertNormalizeNoTrim(@"1. ```
foo
```
bar");
}
[Test, Ignore("Not sure this is the correct normalize for this one. Need to check the specs")]
public void ListUnorderedLooseTop()
{
AssertNormalizeNoTrim(@"* foo
* bar
baz", options: new NormalizeOptions() { ListItemCharacter = '*' });
}
[Test]
public void ListUnorderedLooseMultiParagraph()
{
AssertNormalizeNoTrim(
@"- a
And another paragraph a
- b
And another paragraph b
- c");
}
[Test]
public void ListOrdered()
{
AssertNormalizeNoTrim(@"1. a
2. b
3. c");
}
[Test]
public void ListOrderedAndIntended()
{
AssertNormalizeNoTrim(@"1. a
2. b
- foo
- bar
a) 1234
b) 1324
3. c
4. c
5. c
6. c
7. c
8. c
9. c
10. c
- Foo
- Bar
11. c
12. c");
}
[Test]
public void HeaderAndParagraph()
{
AssertNormalizeNoTrim(@"# heading
paragraph
paragraph2 without newlines");
}
[Test]
public void QuoteBlock()
{
AssertNormalizeNoTrim(@"> test1
>
> test2");
AssertNormalizeNoTrim(@"> test1
This is a continuation
> test2",
@"> test1
> This is a continuation
> test2"
);
AssertNormalizeNoTrim(@"> test1
> -foobar
asdf
> test2
> -foobar sen.");
}
[Test]
public void ThematicBreak()
{
AssertNormalizeNoTrim("***\n");
AssertNormalizeNoTrim("* * *\n", "***\n");
}
[Test]
public void AutolinkInline()
{
AssertNormalizeNoTrim("This has a <auto.link.com>");
}
[Test]
public void CodeInline()
{
AssertNormalizeNoTrim("This has a `HelloWorld()` in it");
AssertNormalizeNoTrim(@"This has a ``Hello`World()`` in it");
}
[Test]
public void EmphasisInline()
{
AssertNormalizeNoTrim("This is a plain **paragraph**");
AssertNormalizeNoTrim("This is a plain *paragraph*");
AssertNormalizeNoTrim("This is a plain _paragraph_");
AssertNormalizeNoTrim("This is a plain __paragraph__");
AssertNormalizeNoTrim("This is a pl*ai*n **paragraph**");
}
[Test]
public void LineBreakInline()
{
AssertNormalizeNoTrim("normal\nline break");
AssertNormalizeNoTrim("hard \nline break");
AssertNormalizeNoTrim("This is a hardline \nAnd this is another hardline\\\nThis is standard newline");
AssertNormalizeNoTrim("This is a line\nWith another line\nAnd a last line");
}
[Test]
public void LinkInline()
{
AssertNormalizeNoTrim("This is a [link](http://company.com)");
AssertNormalizeNoTrim("This is an ![image](http://company.com)");
AssertNormalizeNoTrim(@"This is a [link](http://company.com ""Crazy Company"")");
AssertNormalizeNoTrim(@"This is a [link](http://company.com ""Crazy \"" Company"")");
}
[Test]
public void LinkReferenceDefinition()
{
// Full link
AssertNormalizeNoTrim("This is a [link][MyLink]\n\n[MyLink]: http://company.com");
AssertNormalizeNoTrim("[MyLink]: http://company.com\nThis is a [link][MyLink]",
"This is a [link][MyLink]\n\n[MyLink]: http://company.com");
AssertNormalizeNoTrim("This is a [link][MyLink] a normal link [link](http://google.com) and another def link [link2][MyLink2]\n\n[MyLink]: http://company.com\n[MyLink2]: http://company2.com");
// Collapsed link
AssertNormalizeNoTrim("This is a [link][]\n\n[link]: http://company.com");
// Shortcut link
AssertNormalizeNoTrim("This is a [link]\n\n[link]: http://company.com");
}
[Test]
public void EscapeInline()
{
AssertNormalizeNoTrim("This is an escape \\* with another \\[");
}
[Test]
public void HtmlEntityInline()
{
AssertNormalizeNoTrim("This is a &auml; blank");
}
[Test]
public void HtmlInline()
{
AssertNormalizeNoTrim("foo <hr/> bar");
AssertNormalizeNoTrim(@"foo <hr foo=""bar""/> bar");
}
[Test]
public void SpaceBetweenNodes()
{
AssertNormalizeNoTrim("# Hello World\nFoobar is a better bar.",
"# Hello World\n\nFoobar is a better bar.");
}
[Test]
public void SpaceBetweenNodesEvenForHeadlines()
{
AssertNormalizeNoTrim("# Hello World\n## Chapter 1\nFoobar is a better bar.",
"# Hello World\n\n## Chapter 1\n\nFoobar is a better bar.");
}
[Test]
public void SpaceRemoveAtStartAndEnd()
{
AssertNormalizeNoTrim("\n\n# Hello World\n## Chapter 1\nFoobar is a better bar.\n\n",
"# Hello World\n\n## Chapter 1\n\nFoobar is a better bar.");
}
[Test]
public void SpaceShortenBetweenNodes()
{
AssertNormalizeNoTrim("# Hello World\n\n\n\nFoobar is a better bar.",
"# Hello World\n\nFoobar is a better bar.");
}
[Test]
public void BiggerSample()
{
var input = @"# Heading 1
This is a paragraph
This is another paragraph
- This is a list item 1
- This is a list item 2
- This is a list item 3
```C#
This is a code block
```
> This is a quote block
This is an indented code block
line 2 of indented
This is a last line";
AssertNormalizeNoTrim(input);
}
private static void AssertSyntax(string expected, MarkdownObject syntax)
{
var writer = new StringWriter();
var normalizer = new NormalizeRenderer(writer);
var document = new MarkdownDocument();
if (syntax is Block)
{
document.Add(syntax as Block);
}
else
{
throw new InvalidOperationException();
}
normalizer.Render(document);
var actual = writer.ToString();
Assert.AreEqual(expected, actual);
}
public void AssertNormalizeNoTrim(string input, string expected = null, NormalizeOptions options = null)
=> AssertNormalize(input, expected, false, options);
public void AssertNormalize(string input, string expected = null, bool trim = true, NormalizeOptions options = null)
{
expected = expected ?? input;
input = NormText(input, trim);
expected = NormText(expected, trim);
var result = Markdown.Normalize(input, options);
result = NormText(result, trim);
Console.WriteLine("```````````````````Source");
Console.WriteLine(TestParser.DisplaySpaceAndTabs(input));
Console.WriteLine("```````````````````Result");
Console.WriteLine(TestParser.DisplaySpaceAndTabs(result));
Console.WriteLine("```````````````````Expected");
Console.WriteLine(TestParser.DisplaySpaceAndTabs(expected));
Console.WriteLine("```````````````````");
Console.WriteLine();
TextAssert.AreEqual(expected, result);
}
private static string NormText(string text, bool trim)
{
if (trim)
{
text = text.Trim();
}
return text.Replace("\r\n", "\n").Replace('\r', '\n');
}
}
}

View File

@@ -1,4 +1,4 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// 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;
@@ -6,11 +6,19 @@ using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using Markdig.Extensions.JiraLinks;
using NUnit.Framework;
namespace Markdig.Tests
{
public class TestParser
{
[Test]
public void TestEmphasisAndHtmlEntity()
{
var markdownText = "*Unlimited-Fun&#174;*&#174;";
TestSpec(markdownText, "<p><em>Unlimited-Fun®</em>®</p>");
}
public static void TestSpec(string inputText, string expectedOutputText, string extensions = null)
{
foreach (var pipeline in GetPipeline(extensions))

View File

@@ -0,0 +1,17 @@
using NUnit.Framework;
namespace Markdig.Tests
{
[TestFixture]
public class TestPlainText
{
[Test]
public void TestPlain()
{
var markdownText = "*Hello*, [world](http://example.com)!";
var expected = "Hello, world!";
var actual = Markdown.ToPlainText(markdownText);
Assert.AreEqual(expected, actual);
}
}
}

View File

@@ -1,4 +1,4 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// 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;
@@ -86,7 +86,7 @@ namespace Markdig.Tests
//output.WriteLine();
}
Assert.True(string.CompareOrdinal(expectedValue, actualValue) == 0, "strings are differing");
Assert.AreEqual(expectedValue, actualValue);
}
private static string ToSafeString(this char c)

View File

@@ -1,4 +1,4 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// 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.
@@ -143,7 +143,12 @@ namespace Markdig.Extensions.AutoIdentifiers
stripRenderer.Render(headingBlock.Inline);
var headingText = headingWriter.ToString();
headingWriter.GetStringBuilder().Length = 0;
headingText = LinkHelper.Urilize(headingText, (options & AutoIdentifierOptions.AllowOnlyAscii) != 0);
// TODO: Should we have a struct with more configure optionss for LinkHelper.Urilize?
headingText = LinkHelper.Urilize(headingText,
(options & AutoIdentifierOptions.AllowOnlyAscii) != 0,
(options & AutoIdentifierOptions.KeepOpeningDigits) != 0,
(options & AutoIdentifierOptions.DiscardDots) != 0);
var baseHeadingId = string.IsNullOrEmpty(headingText) ? "section" : headingText;
int index = 0;

View File

@@ -1,4 +1,4 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// 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;
@@ -21,6 +21,11 @@ namespace Markdig.Extensions.AutoIdentifiers
/// </summary>
Default = AutoLink | AllowOnlyAscii,
/// <summary>
/// Renders auto identifiers like GitHub.
/// </summary>
GitHub = Default | KeepOpeningDigits | DiscardDots,
/// <summary>
/// Allows to link to a header by using the same text as the header for the link label. Default is <c>true</c>
/// </summary>
@@ -30,5 +35,15 @@ namespace Markdig.Extensions.AutoIdentifiers
/// Allows only ASCII characters in the url (HTML 5 allows to have UTF8 characters). Default is <c>true</c>
/// </summary>
AllowOnlyAscii = 2,
/// <summary>
/// Allows to keep digits starting a heading (by default, it keeps only characters starting from the first letter)
/// </summary>
KeepOpeningDigits = 4,
/// <summary>
/// Discard dots when computing an identifier.
/// </summary>
DiscardDots = 8
}
}

View File

@@ -25,6 +25,7 @@ namespace Markdig.Extensions.Diagrams
var codeRenderer = htmlRenderer.ObjectRenderers.FindExact<CodeBlockRenderer>();
// TODO: Add other well known diagram languages
codeRenderer.BlocksAsDiv.Add("mermaid");
codeRenderer.BlocksAsDiv.Add("nomnoml");
}
}
}

View File

@@ -1,4 +1,4 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// 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.
@@ -12,12 +12,19 @@ namespace Markdig.Extensions.Emoji
/// <seealso cref="Markdig.IMarkdownExtension" />
public class EmojiExtension : IMarkdownExtension
{
private readonly bool _enableSmiley;
public EmojiExtension(bool enableSmiley = true)
{
_enableSmiley = enableSmiley;
}
public void Setup(MarkdownPipelineBuilder pipeline)
{
if (!pipeline.InlineParsers.Contains<EmojiParser>())
{
// Insert the parser before any other parsers
pipeline.InlineParsers.Insert(0, new EmojiParser());
pipeline.InlineParsers.Insert(0, new EmojiParser(_enableSmiley));
}
}

View File

@@ -1,4 +1,4 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// 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.
@@ -24,13 +24,19 @@ namespace Markdig.Extensions.Emoji
/// <summary>
/// Initializes a new instance of the <see cref="EmojiParser"/> class.
/// </summary>
public EmojiParser()
public EmojiParser(bool enableSmiley = true)
{
EnableSmiley = enableSmiley;
OpeningCharacters = null;
EmojiToUnicode = new Dictionary<string, string>(EmojiToUnicodeDefault);
SmileyToEmoji = new Dictionary<string, string>(SmileyToEmojiDefault);
}
/// <summary>
/// Gets or sets a boolean indicating whether to process smiley.
/// </summary>
public bool EnableSmiley { get; set; }
/// <summary>
/// Gets the emoji to unicode mapping. This can be modified before this parser is initialized.
/// </summary>
@@ -81,11 +87,14 @@ namespace Markdig.Extensions.Emoji
return false;
}
// If we have a smiley, we decode it to emoji
string emoji;
if (!SmileyToEmoji.TryGetValue(match, out emoji))
string emoji = match;
if (EnableSmiley)
{
emoji = match;
// If we have a smiley, we decode it to emoji
if (!SmileyToEmoji.TryGetValue(match, out emoji))
{
emoji = match;
}
}
// Decode the eomji to unicode

View File

@@ -85,7 +85,7 @@ namespace Markdig.Extensions.Tables
{
renderer.Write($" rowspan=\"{cell.RowSpan}\"");
}
if (table.ColumnDefinitions != null)
if (table.ColumnDefinitions.Count > 0)
{
var columnIndex = cell.ColumnIndex < 0 || cell.ColumnIndex >= table.ColumnDefinitions.Count
? i

View File

@@ -1,4 +1,4 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// 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.Extensions.Tables

View File

@@ -440,12 +440,13 @@ namespace Markdig.Extensions.Tables
// If we have a header row, we can remove it
// TODO: we could optimize this by merging FindHeaderRow and the previous loop
var tableRow = (TableRow)table[0];
tableRow.IsHeader = Options.RequireHeaderSeparator;
if (aligns != null)
{
table.RemoveAt(1);
var tableRow = (TableRow) table[0];
table.ColumnDefinitions.AddRange(aligns);
tableRow.IsHeader = true;
table.RemoveAt(1);
table.ColumnDefinitions.AddRange(aligns);
}
// Perform delimiter processor that are coming after this processor
@@ -532,6 +533,13 @@ namespace Markdig.Extensions.Tables
? columnDelimiter.FirstChild
: delimiter.NextSibling;
// If there is no content after
if (IsNullOrSpace(nextSibling))
{
isValidRow = true;
break;
}
if (!ParseHeaderString(nextSibling, out align))
{
break;
@@ -606,6 +614,20 @@ namespace Markdig.Extensions.Tables
}
}
private static bool IsNullOrSpace(Inline inline)
{
if (inline == null)
{
return true;
}
var literal = inline as LiteralInline;
if (literal != null)
{
return literal.Content.IsEmptyOrWhitespace();
}
return false;
}
private class TableState
{
public TableState()

View File

@@ -33,7 +33,7 @@ namespace Markdig.Extensions.Tables
/// <summary>
/// Gets or sets the column alignments. May be null.
/// </summary>
public List<TableColumnDefinition> ColumnDefinitions { get; private set; }
public List<TableColumnDefinition> ColumnDefinitions { get; }
/// <summary>
/// Checks if the table structure is valid.

View File

@@ -10,7 +10,7 @@ namespace Markdig.Extensions.Yaml
/// A YAML frontmatter block.
/// </summary>
/// <seealso cref="Markdig.Syntax.CodeBlock" />
public class YamlFrontMatterBlock : CodeBlock, IFencedBlock
public class YamlFrontMatterBlock : CodeBlock
{
/// <summary>
/// Initializes a new instance of the <see cref="YamlFrontMatterBlock"/> class.
@@ -19,13 +19,5 @@ namespace Markdig.Extensions.Yaml
public YamlFrontMatterBlock(BlockParser parser) : base(parser)
{
}
public string Info { get; set; }
public string Arguments { get; set; }
public int FencedCharCount { get; set; }
public char FencedChar { get; set; }
}
}

View File

@@ -1,44 +1,128 @@
// 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.Helpers;
using Markdig.Parsers;
using Markdig.Syntax;
namespace Markdig.Extensions.Yaml
{
/// <summary>
/// Block parser for a YAML frontmatter.
/// </summary>
/// <seealso cref="Markdig.Parsers.FencedBlockParserBase{YamlFrontMatterBlock}" />
public class YamlFrontMatterParser : FencedBlockParserBase<YamlFrontMatterBlock>
/// <seealso cref="YamlFrontMatterBlock" />
public class YamlFrontMatterParser : BlockParser
{
// We reuse a FencedCodeBlock parser to grab a frontmatter, only active if it happens on the first line of the document.
/// <summary>
/// Initializes a new instance of the <see cref="FencedCodeBlockParser"/> class.
/// Initializes a new instance of the <see cref="YamlFrontMatterParser"/> class.
/// </summary>
public YamlFrontMatterParser()
{
OpeningCharacters = new[] { '-' };
InfoPrefix = null;
// We expect only 3 --- at the beginning of the file no more, no less
MinimumMatchCount = 3;
MaximumMatchCount = 3;
this.OpeningCharacters = new[] { '-' };
}
protected override YamlFrontMatterBlock CreateFencedBlock(BlockProcessor processor)
/// <summary>
/// Creates the front matter block.
/// </summary>
/// <param name="processor">The block processor</param>
/// <returns>The front matter block</returns>
protected virtual YamlFrontMatterBlock CreateFrontMatterBlock(BlockProcessor processor)
{
return new YamlFrontMatterBlock(this);
}
/// <summary>
/// Tries to match a block opening.
/// </summary>
/// <param name="processor">The parser processor.</param>
/// <returns>The result of the match</returns>
public override BlockState TryOpen(BlockProcessor processor)
{
// Only accept a frontmatter at the beginning of the file
if (processor.LineIndex != 0)
// We expect no indentation for a fenced code block.
if (processor.IsCodeIndent)
{
return BlockState.None;
}
return base.TryOpen(processor);
// Only accept a frontmatter at the beginning of the file
if (processor.Start != 0)
{
return BlockState.None;
}
int count = 0;
var line = processor.Line;
char c = line.CurrentChar;
// Must consist of exactly three dashes
while (c == '-' && count < 4)
{
count++;
c = line.NextChar();
}
// If three dashes (optionally followed by whitespace)
// this is a YAML front matter blcok
if (count == 3 && (c == '\0' || c.IsWhitespace()) && line.TrimEnd())
{
// Create a front matter block
var block = this.CreateFrontMatterBlock(processor);
block.Column = processor.Column;
block.Span.Start = 0;
block.Span.End = line.Start;
// Store the number of matched string into the context
processor.NewBlocks.Push(block);
// Discard the current line as it is already parsed
return BlockState.ContinueDiscard;
}
return BlockState.None;
}
/// <summary>
/// Tries to continue matching a block already opened.
/// </summary>
/// <param name="processor">The parser processor.</param>
/// <param name="block">The block already opened.</param>
/// <returns>The result of the match. By default, don't expect any newline</returns>
public override BlockState TryContinue(BlockProcessor processor, Block block)
{
char matchChar;
int count = 0;
var c = processor.CurrentChar;
// Determine if we have a closing fence.
// It can start or end with either <c>---</c> or <c>...</c>
var line = processor.Line;
if (processor.Column == 0 && (c == '-' || c == '.'))
{
matchChar = c;
while (c == matchChar)
{
c = line.NextChar();
count++;
}
// If we have a closing fence, close it and discard the current line
// The line must contain only fence characters and optional following whitespace.
if (count == 3 && !processor.IsCodeIndent && (c == '\0' || c.IsWhitespace()) && line.TrimEnd())
{
block.UpdateSpanEnd(line.Start - 1);
// Don't keep the last line
return BlockState.BreakDiscard;
}
}
// Reset the indentation to the column before the indent
processor.GoToColumn(processor.ColumnBeforeIndent);
return BlockState.Continue;
}
}
}

View File

@@ -1,4 +1,4 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// 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;
@@ -18,10 +18,10 @@ namespace Markdig.Helpers
return TryParseAutolink(ref text, out link, out isEmail);
}
public static string Urilize(string headingText, bool allowOnlyAscii)
public static string Urilize(string headingText, bool allowOnlyAscii, bool keepOpeningDigits = false, bool discardDots = false)
{
var headingBuffer = StringBuilderCache.Local();
bool hasLetter = false;
bool hasLetter = keepOpeningDigits && headingText.Length > 0 && char.IsLetterOrDigit(headingText[0]);
bool previousIsSpace = false;
for (int i = 0; i < headingText.Length; i++)
{
@@ -47,7 +47,7 @@ namespace Markdig.Helpers
}
else if (hasLetter)
{
if (IsReservedPunctuation(c))
if (IsReservedPunctuation(c, discardDots))
{
if (previousIsSpace)
{
@@ -67,7 +67,7 @@ namespace Markdig.Helpers
else if (!previousIsSpace && c.IsWhitespace())
{
var pc = headingBuffer[headingBuffer.Length - 1];
if (!IsReservedPunctuation(pc))
if (!IsReservedPunctuation(pc, discardDots))
{
headingBuffer.Append('-');
}
@@ -81,7 +81,7 @@ namespace Markdig.Helpers
while (headingBuffer.Length > 0)
{
var c = headingBuffer[headingBuffer.Length - 1];
if (IsReservedPunctuation(c))
if (IsReservedPunctuation(c, false))
{
headingBuffer.Length--;
}
@@ -97,9 +97,9 @@ namespace Markdig.Helpers
}
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
private static bool IsReservedPunctuation(char c)
private static bool IsReservedPunctuation(char c, bool discardDots)
{
return c == '_' || c == '-' || c == '.';
return c == '_' || c == '-' || (!discardDots && c == '.');
}
public static bool TryParseAutolink(ref StringSlice text, out string link, out bool isEmail)

View File

@@ -5,7 +5,7 @@
<Copyright>Alexandre Mutel</Copyright>
<AssemblyTitle>Markdig</AssemblyTitle>
<NeutralLanguage>en-US</NeutralLanguage>
<VersionPrefix>0.13.2</VersionPrefix>
<VersionPrefix>0.14.2</VersionPrefix>
<Authors>Alexandre Mutel</Authors>
<TargetFrameworks>net35;net40;portable40-net40+sl5+win8+wp8+wpa81;netstandard1.1;uap10.0</TargetFrameworks>
<AssemblyName>Markdig</AssemblyName>
@@ -13,12 +13,20 @@
<PackageId Condition="'$(SignAssembly)' == 'true'">Markdig.Signed</PackageId>
<PackageTags>Markdown CommonMark md html md2html</PackageTags>
<PackageReleaseNotes>
&gt; 0.13.2
- Add support for UAP10.0 (#137)
&gt; 0.13.1
- Fix indenting issue after a double digit list block using a tab (#134)
&gt; 0.13.0
- Update to latest CommonMark specs 0.28
&gt; 0.14.2
- Fix issue with emphasis preceded/followed by an HTML entity (#157)
- Add support for link reference definitions for Normalize renderer (#155)
- Add option to disable smiley parsing in EmojiAndSmiley extension
&gt; 0.14.1
- Fix crash in Markdown.Normalize to handle HtmlBlock correctly
- Add better handling of bullet character for lists in Markdown.Normalize
&gt; 0.14.0
- Add Markdown.ToPlainText, Add option HtmlRenderer.EnableHtmlForBlock.
- Add Markdown.Normalize, to allow to normalize a markdown document. Add NormalizeRenderer, to render a MarkdownDocument back to markdown.
-
&gt; 0.13.4
- Add support for single table header row without a table body rows (#141)
- ADd support for `nomnoml` diagrams
</PackageReleaseNotes>
<PackageIconUrl>https://raw.githubusercontent.com/lunet-io/markdig/master/img/markdig.png</PackageIconUrl>
<PackageProjectUrl>https://github.com/lunet-io/markdig</PackageProjectUrl>

View File

@@ -1,4 +1,4 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// 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;
@@ -7,6 +7,7 @@ using System.Reflection;
using Markdig.Extensions.SelfPipeline;
using Markdig.Parsers;
using Markdig.Renderers;
using Markdig.Renderers.Normalize;
using Markdig.Syntax;
namespace Markdig
@@ -22,6 +23,44 @@ namespace Markdig
public static readonly string Version = ((AssemblyFileVersionAttribute) typeof(Markdown).Assembly.GetCustomAttributes(typeof(AssemblyFileVersionAttribute), false)[0]).Version;
#endif
/// <summary>
/// Normalizes the specified markdown to a normalized markdown text.
/// </summary>
/// <param name="markdown">The markdown.</param>
/// <param name="options">The normalize options</param>
/// <param name="pipeline">The pipeline.</param>
/// <returns>A normalized markdown text.</returns>
public static string Normalize(string markdown, NormalizeOptions options = null, MarkdownPipeline pipeline = null)
{
var writer = new StringWriter();
Normalize(markdown, writer, options, pipeline);
return writer.ToString();
}
/// <summary>
/// Normalizes the specified markdown to a normalized markdown text.
/// </summary>
/// <param name="markdown">The markdown.</param>
/// <param name="writer">The destination <see cref="TextWriter"/> that will receive the result of the conversion.</param>
/// <param name="options">The normalize options</param>
/// <param name="pipeline">The pipeline.</param>
/// <returns>A normalized markdown text.</returns>
public static MarkdownDocument Normalize(string markdown, TextWriter writer, NormalizeOptions options = null, MarkdownPipeline pipeline = null)
{
pipeline = pipeline ?? new MarkdownPipelineBuilder().Build();
pipeline = CheckForSelfPipeline(pipeline, markdown);
// We override the renderer with our own writer
var renderer = new NormalizeRenderer(writer, options);
pipeline.Setup(renderer);
var document = Parse(markdown, pipeline);
renderer.Render(document);
writer.Flush();
return document;
}
/// <summary>
/// Converts a Markdown string to HTML.
/// </summary>
@@ -119,5 +158,50 @@ namespace Markdig
}
return pipeline;
}
/// <summary>
/// Converts a Markdown string to Plain text 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 MarkdownDocument ToPlainText(string markdown, TextWriter writer, MarkdownPipeline pipeline = null)
{
if (markdown == null) throw new ArgumentNullException(nameof(markdown));
if (writer == null) throw new ArgumentNullException(nameof(writer));
pipeline = pipeline ?? new MarkdownPipelineBuilder().Build();
pipeline = CheckForSelfPipeline(pipeline, markdown);
// We override the renderer with our own writer
var renderer = new HtmlRenderer(writer)
{
EnableHtmlForBlock = false,
EnableHtmlForInline = false
};
pipeline.Setup(renderer);
var document = Parse(markdown, pipeline);
renderer.Render(document);
writer.Flush();
return document;
}
/// <summary>
/// Converts a Markdown string to HTML.
/// </summary>
/// <param name="markdown">A Markdown text.</param>
/// <param name="pipeline">The pipeline used for the conversion.</param>
/// <returns>The result of the conversion</returns>
/// <exception cref="System.ArgumentNullException">if markdown variable is null</exception>
public static string ToPlainText(string markdown, MarkdownPipeline pipeline = null)
{
if (markdown == null) throw new ArgumentNullException(nameof(markdown));
var writer = new StringWriter();
ToPlainText(markdown, writer, pipeline);
return writer.ToString();
}
}
}

View File

@@ -1,4 +1,4 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// 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.
@@ -409,10 +409,14 @@ namespace Markdig
/// Uses the emoji and smiley extension.
/// </summary>
/// <param name="pipeline">The pipeline.</param>
/// <param name="enableSmiley">Enable smiley in addition to Emoji, <c>true</c> by default.</param>
/// <returns>The modified pipeline</returns>
public static MarkdownPipelineBuilder UseEmojiAndSmiley(this MarkdownPipelineBuilder pipeline)
public static MarkdownPipelineBuilder UseEmojiAndSmiley(this MarkdownPipelineBuilder pipeline, bool enableSmiley = true)
{
pipeline.Extensions.AddIfNotAlready<EmojiExtension>();
if (!pipeline.Extensions.Contains<EmojiExtension>())
{
pipeline.Extensions.Add(new EmojiExtension(enableSmiley));
}
return pipeline;
}

View File

@@ -101,7 +101,7 @@ namespace Markdig.Parsers.Inlines
}
// Move current_position forward in the delimiter stack (if needed) until
// we find the first potential closer with delimiter * or _. (This will be the potential closer closest to the beginning of the input the first one in parse order.)
// we find the first potential closer with delimiter * or _. (This will be the potential closer closest to the beginning of the input the first one in parse order.)
var child = container.LastChild;
while (child != null)
{
@@ -138,12 +138,24 @@ namespace Markdig.Parsers.Inlines
var delimiterChar = slice.CurrentChar;
var emphasisDesc = emphasisMap[delimiterChar];
var pc = slice.PeekCharExtra(-1);
if (pc == delimiterChar && slice.PeekCharExtra(-2) != '\\')
{
return false;
}
var pc = (char)0;
if (processor.Inline is HtmlEntityInline)
{
var htmlEntityInline = (HtmlEntityInline) processor.Inline;
if (htmlEntityInline.Transcoded.Length > 0)
{
pc = htmlEntityInline.Transcoded[htmlEntityInline.Transcoded.End];
}
}
if (pc == 0)
{
pc = slice.PeekCharExtra(-1);
if (pc == delimiterChar && slice.PeekCharExtra(-2) != '\\')
{
return false;
}
}
var startPosition = slice.Start;
int delimiterCount = 0;
@@ -161,6 +173,14 @@ namespace Markdig.Parsers.Inlines
return false;
}
// The following character is actually an entity, we need to decode it
int htmlLength;
string htmlString;
if (HtmlEntityParser.TryParse(ref slice, out htmlString, out htmlLength))
{
c = htmlString[0];
}
// Calculate Open-Close for current character
bool canOpen;
bool canClose;
@@ -204,7 +224,7 @@ namespace Markdig.Parsers.Inlines
// at the end of the CommonMark specs.
// Move current_position forward in the delimiter stack (if needed) until
// we find the first potential closer with delimiter * or _. (This will be the potential closer closest to the beginning of the input the first one in parse order.)
// we find the first potential closer with delimiter * or _. (This will be the potential closer closest to the beginning of the input the first one in parse order.)
for (int i = 0; i < delimiters.Count; i++)
{
var closeDelimiter = delimiters[i];
@@ -219,7 +239,7 @@ namespace Markdig.Parsers.Inlines
while (true)
{
// Now, look back in the stack (staying above stack_bottom and the openers_bottom for this delimiter type)
// for the first matching potential opener (“matching” means same delimiter).
// for the first matching potential opener (“matching” means same delimiter).
EmphasisDelimiterInline openDelimiter = null;
int openDelimiterIndex = -1;
for (int j = i - 1; j >= 0; j--)

View File

@@ -28,10 +28,11 @@ namespace Markdig.Parsers.Inlines
{
processor.Inline = new LiteralInline()
{
Content = new StringSlice(new string(c, 1)),
Content = new StringSlice(slice.Text, slice.Start, slice.Start),
Span = { Start = processor.GetSourcePosition(startPosition, out line, out column) },
Line = line,
Column = column
Column = column,
IsFirstCharacterEscaped = true,
};
processor.Inline.Span.End = processor.Inline.Span.Start + 1;
slice.NextChar();
@@ -44,6 +45,7 @@ namespace Markdig.Parsers.Inlines
processor.Inline = new LineBreakInline()
{
IsHard = true,
IsBackslash = true,
Span = { Start = processor.GetSourcePosition(startPosition, out line, out column) },
Line = line,
Column = column

View File

@@ -21,18 +21,18 @@ namespace Markdig.Parsers.Inlines
OpeningCharacters = new[] {'&'};
}
public override bool Match(InlineProcessor processor, ref StringSlice slice)
public static bool TryParse(ref StringSlice slice, out string literal, out int match)
{
literal = null;
string entityName;
int entityValue;
var startPosition = slice.Start;
int match = HtmlHelper.ScanEntity(slice.Text, slice.Start, slice.Length, out entityName, out entityValue);
match = HtmlHelper.ScanEntity(slice.Text, slice.Start, slice.Length, out entityName, out entityValue);
if (match == 0)
{
return false;
}
string literal = null;
if (entityName != null)
{
literal = EntityHelper.DecodeEntity(entityName);
@@ -41,6 +41,19 @@ namespace Markdig.Parsers.Inlines
{
literal = (entityValue == 0 ? null : EntityHelper.DecodeEntity(entityValue)) ?? CharHelper.ZeroSafeString;
}
return true;
}
public override bool Match(InlineProcessor processor, ref StringSlice slice)
{
int match;
string literal;
if (!TryParse(ref slice, out literal, out match))
{
return false;
}
var startPosition = slice.Start;
if (literal != null)
{

View File

@@ -86,7 +86,7 @@ namespace Markdig.Parsers.Inlines
}
}
// If we dont find one, we return a literal slice node ].
// If we dont find one, we return a literal slice node ].
// (Done after by the LiteralInline parser)
return false;
}
@@ -95,7 +95,7 @@ namespace Markdig.Parsers.Inlines
return false;
}
private bool ProcessLinkReference(InlineProcessor state, string label, SourceSpan labelSpan, LinkDelimiterInline parent, int endPosition)
private bool ProcessLinkReference(InlineProcessor state, string label, bool isShortcut, SourceSpan labelSpan, LinkDelimiterInline parent, int endPosition)
{
bool isValidLink = false;
LinkReferenceDefinition linkRef;
@@ -120,6 +120,7 @@ namespace Markdig.Parsers.Inlines
LabelSpan = labelSpan,
UrlSpan = linkRef.UrlSpan,
IsImage = parent.IsImage,
IsShortcut = isShortcut,
Reference = linkRef,
Span = new SourceSpan(parent.Span.Start, endPosition),
Line = parent.Line,
@@ -189,7 +190,7 @@ namespace Markdig.Parsers.Inlines
if (openParent != null)
{
// If we do find one, but its not active,
// If we do find one, but its not active,
// we remove the inactive delimiter from the stack,
// and return a literal text node ].
if (!openParent.IsActive)
@@ -205,7 +206,7 @@ namespace Markdig.Parsers.Inlines
return false;
}
// If we find one and its active,
// If we find one and its active,
// then we parse ahead to see if we have
// an inline link/image, reference link/image,
// compact reference link/image,
@@ -261,6 +262,8 @@ namespace Markdig.Parsers.Inlines
var labelSpan = SourceSpan.Empty;
string label = null;
bool isLabelSpanLocal = true;
bool isShortcut = false;
// Handle Collapsed links
if (text.CurrentChar == '[')
{
@@ -276,6 +279,7 @@ namespace Markdig.Parsers.Inlines
else
{
label = openParent.Label;
isShortcut = true;
}
if (label != null || LinkHelper.TryParseLabel(ref text, true, out label, out labelSpan))
@@ -285,7 +289,7 @@ namespace Markdig.Parsers.Inlines
labelSpan = inlineState.GetSourcePositionFromLocalSpan(labelSpan);
}
if (ProcessLinkReference(inlineState, label, labelSpan, openParent, inlineState.GetSourcePosition(text.Start - 1)))
if (ProcessLinkReference(inlineState, label, isShortcut, labelSpan, openParent, inlineState.GetSourcePosition(text.Start - 1)))
{
// Remove the open parent
openParent.Remove();

View File

@@ -88,7 +88,9 @@ namespace Markdig.Parsers
processor.NewBlocks.Push(new ThematicBreakBlock(this)
{
Column = processor.Column,
Span = new SourceSpan(startPosition, line.End)
Span = new SourceSpan(startPosition, line.End),
ThematicChar = breakChar,
ThematicCharCount = breakCharCount
});
return BlockState.BreakDiscard;
}

View File

@@ -42,29 +42,50 @@ namespace Markdig.Renderers.Html
// We are replacing the HTML attribute `language-mylang` by `mylang` only for a div block
// NOTE that we are allocating a closure here
renderer.Write("<div")
.WriteAttributes(obj.TryGetAttributes(),
cls => cls.StartsWith(infoPrefix) ? cls.Substring(infoPrefix.Length) : cls)
.Write(">");
if (renderer.EnableHtmlForBlock)
{
renderer.Write("<div")
.WriteAttributes(obj.TryGetAttributes(),
cls => cls.StartsWith(infoPrefix) ? cls.Substring(infoPrefix.Length) : cls)
.Write(">");
}
renderer.WriteLeafRawLines(obj, true, true, true);
renderer.WriteLine("</div>");
if (renderer.EnableHtmlForBlock)
{
renderer.WriteLine("</div>");
}
}
else
{
renderer.Write("<pre");
if (OutputAttributesOnPre)
if (renderer.EnableHtmlForBlock)
{
renderer.WriteAttributes(obj);
renderer.Write("<pre");
if (OutputAttributesOnPre)
{
renderer.WriteAttributes(obj);
}
renderer.Write("><code");
if (!OutputAttributesOnPre)
{
renderer.WriteAttributes(obj);
}
renderer.Write(">");
}
renderer.Write("><code");
if (!OutputAttributesOnPre)
{
renderer.WriteAttributes(obj);
}
renderer.Write(">");
renderer.WriteLeafRawLines(obj, true, true);
renderer.WriteLine("</code></pre>");
if (renderer.EnableHtmlForBlock)
{
renderer.WriteLine("</code></pre>");
}
}
}
}

View File

@@ -27,9 +27,17 @@ namespace Markdig.Renderers.Html
? HeadingTexts[obj.Level - 1]
: "<h" + obj.Level.ToString(CultureInfo.InvariantCulture);
renderer.Write("<").Write(headingText).WriteAttributes(obj).Write(">");
if (renderer.EnableHtmlForBlock)
{
renderer.Write("<").Write(headingText).WriteAttributes(obj).Write(">");
}
renderer.WriteLeafInline(obj);
renderer.Write("</").Write(headingText).WriteLine(">");
if (renderer.EnableHtmlForBlock)
{
renderer.Write("</").Write(headingText).WriteLine(">");
}
}
}
}

View File

@@ -15,27 +15,31 @@ namespace Markdig.Renderers.Html
protected override void Write(HtmlRenderer renderer, ListBlock listBlock)
{
renderer.EnsureLine();
if (listBlock.IsOrdered)
if (renderer.EnableHtmlForBlock)
{
renderer.Write("<ol");
if (listBlock.BulletType != '1')
if (listBlock.IsOrdered)
{
renderer.Write(" type=\"").Write(listBlock.BulletType).Write("\"");
}
renderer.Write("<ol");
if (listBlock.BulletType != '1')
{
renderer.Write(" type=\"").Write(listBlock.BulletType).Write("\"");
}
if (listBlock.OrderedStart != null && (listBlock.OrderedStart != "1"))
{
renderer.Write(" start=\"").Write(listBlock.OrderedStart).Write("\"");
if (listBlock.OrderedStart != null && (listBlock.OrderedStart != "1"))
{
renderer.Write(" start=\"").Write(listBlock.OrderedStart).Write("\"");
}
renderer.WriteAttributes(listBlock);
renderer.WriteLine(">");
}
else
{
renderer.Write("<ul");
renderer.WriteAttributes(listBlock);
renderer.WriteLine(">");
}
renderer.WriteAttributes(listBlock);
renderer.WriteLine(">");
}
else
{
renderer.Write("<ul");
renderer.WriteAttributes(listBlock);
renderer.WriteLine(">");
}
foreach (var item in listBlock)
{
var listItem = (ListItemBlock)item;
@@ -43,13 +47,25 @@ namespace Markdig.Renderers.Html
renderer.ImplicitParagraph = !listBlock.IsLoose;
renderer.EnsureLine();
renderer.Write("<li").WriteAttributes(listItem).Write(">");
if (renderer.EnableHtmlForBlock)
{
renderer.Write("<li").WriteAttributes(listItem).Write(">");
}
renderer.WriteChildren(listItem);
renderer.WriteLine("</li>");
if (renderer.EnableHtmlForBlock)
{
renderer.WriteLine("</li>");
}
renderer.ImplicitParagraph = previousImplicit;
}
renderer.WriteLine(listBlock.IsOrdered ? "</ol>" : "</ul>");
if (renderer.EnableHtmlForBlock)
{
renderer.WriteLine(listBlock.IsOrdered ? "</ol>" : "</ul>");
}
}
}
}

View File

@@ -13,16 +13,17 @@ namespace Markdig.Renderers.Html
{
protected override void Write(HtmlRenderer renderer, ParagraphBlock obj)
{
if (!renderer.ImplicitParagraph)
if (!renderer.ImplicitParagraph && renderer.EnableHtmlForBlock)
{
if (!renderer.IsFirstInContainer)
{
renderer.EnsureLine();
}
renderer.Write("<p").WriteAttributes(obj).Write(">");
}
renderer.WriteLeafInline(obj);
if (!renderer.ImplicitParagraph)
if (!renderer.ImplicitParagraph && renderer.EnableHtmlForBlock)
{
renderer.WriteLine("</p>");
}

View File

@@ -14,12 +14,18 @@ namespace Markdig.Renderers.Html
protected override void Write(HtmlRenderer renderer, QuoteBlock obj)
{
renderer.EnsureLine();
renderer.Write("<blockquote").WriteAttributes(obj).WriteLine(">");
if (renderer.EnableHtmlForBlock)
{
renderer.Write("<blockquote").WriteAttributes(obj).WriteLine(">");
}
var savedImplicitParagraph = renderer.ImplicitParagraph;
renderer.ImplicitParagraph = false;
renderer.WriteChildren(obj);
renderer.ImplicitParagraph = savedImplicitParagraph;
renderer.WriteLine("</blockquote>");
if (renderer.EnableHtmlForBlock)
{
renderer.WriteLine("</blockquote>");
}
}
}
}

View File

@@ -13,7 +13,10 @@ namespace Markdig.Renderers.Html
{
protected override void Write(HtmlRenderer renderer, ThematicBreakBlock obj)
{
renderer.Write("<hr").WriteAttributes(obj).WriteLine(" />");
if (renderer.EnableHtmlForBlock)
{
renderer.Write("<hr").WriteAttributes(obj).WriteLine(" />");
}
}
}
}

View File

@@ -45,18 +45,27 @@ namespace Markdig.Renderers
ObjectRenderers.Add(new LinkInlineRenderer());
ObjectRenderers.Add(new LiteralInlineRenderer());
EnableHtmlForBlock = true;
EnableHtmlForInline = true;
EnableHtmlEscape = true;
}
/// <summary>
/// Gets or sets a value indicating whether to ouput HTML tags when rendering. See remarks.
/// Gets or sets a value indicating whether to output HTML tags when rendering. See remarks.
/// </summary>
/// <remarks>
/// This is used by some renderers to disable HTML tags when rendering some inlines (for image links).
/// This is used by some renderers to disable HTML tags when rendering some inline elements (for image links).
/// </remarks>
public bool EnableHtmlForInline { get; set; }
/// <summary>
/// Gets or sets a value indicating whether to output HTML tags when rendering. See remarks.
/// </summary>
/// <remarks>
/// This is used by some renderers to disable HTML tags when rendering some block elements (for image links).
/// </remarks>
public bool EnableHtmlForBlock { get; set; }
public bool EnableHtmlEscape { get; set; }
/// <summary>

View File

@@ -0,0 +1,55 @@
// 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.Html;
using Markdig.Syntax;
namespace Markdig.Renderers.Normalize
{
/// <summary>
/// An Normalize renderer for a <see cref="CodeBlock"/> and <see cref="FencedCodeBlock"/>.
/// </summary>
/// <seealso cref="Markdig.Renderers.Normalize.NormalizeObjectRenderer{Markdig.Syntax.CodeBlock}" />
public class CodeBlockRenderer : NormalizeObjectRenderer<CodeBlock>
{
public bool OutputAttributesOnPre { get; set; }
protected override void Write(NormalizeRenderer renderer, CodeBlock obj)
{
var fencedCodeBlock = obj as FencedCodeBlock;
if (fencedCodeBlock != null)
{
var opening = new string(fencedCodeBlock.FencedChar, fencedCodeBlock.FencedCharCount);
renderer.Write(opening);
if (fencedCodeBlock.Info != null)
{
renderer.Write(fencedCodeBlock.Info);
}
if (!string.IsNullOrEmpty(fencedCodeBlock.Arguments))
{
renderer.Write(" ").Write(fencedCodeBlock.Arguments);
}
/* TODO do we need this causes a empty space and would render html attributes to markdown.
var attributes = obj.TryGetAttributes();
if (attributes != null)
{
renderer.Write(" ");
renderer.Write(attributes);
}
*/
renderer.WriteLine();
renderer.WriteLeafRawLines(obj, true);
renderer.Write(opening);
}
else
{
renderer.WriteLeafRawLines(obj, false, true);
}
renderer.FinishBlock(renderer.Options.EmptyLineAfterCodeBlock);
}
}
}

View File

@@ -0,0 +1,36 @@
// 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.Globalization;
using Markdig.Syntax;
namespace Markdig.Renderers.Normalize
{
/// <summary>
/// An Normalize renderer for a <see cref="HeadingBlock"/>.
/// </summary>
/// <seealso cref="Markdig.Renderers.Normalize.NormalizeObjectRenderer{Markdig.Syntax.HeadingBlock}" />
public class HeadingRenderer : NormalizeObjectRenderer<HeadingBlock>
{
private static readonly string[] HeadingTexts = {
"#",
"##",
"###",
"####",
"#####",
"######",
};
protected override void Write(NormalizeRenderer renderer, HeadingBlock obj)
{
var headingText = obj.Level > 0 && obj.Level <= 6
? HeadingTexts[obj.Level - 1]
: new string('#', obj.Level);
renderer.Write(headingText).Write(' ');
renderer.WriteLeafInline(obj);
renderer.FinishBlock(renderer.Options.EmptyLineAfterHeading);
}
}
}

View File

@@ -0,0 +1,16 @@
// 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.Syntax;
namespace Markdig.Renderers.Normalize
{
public class HtmlBlockRenderer : NormalizeObjectRenderer<HtmlBlock>
{
protected override void Write(NormalizeRenderer renderer, HtmlBlock obj)
{
renderer.WriteLeafRawLines(obj, true, false);
}
}
}

View File

@@ -0,0 +1,19 @@
// 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.Syntax.Inlines;
namespace Markdig.Renderers.Normalize.Inlines
{
/// <summary>
/// A Normalize renderer for an <see cref="AutolinkInline"/>.
/// </summary>
/// <seealso cref="Markdig.Renderers.Normalize.NormalizeObjectRenderer{Markdig.Syntax.Inlines.AutolinkInline}" />
public class AutolinkInlineRenderer : NormalizeObjectRenderer<AutolinkInline>
{
protected override void Write(NormalizeRenderer renderer, AutolinkInline obj)
{
renderer.Write('<').Write(obj.Url).Write('>');
}
}
}

View File

@@ -0,0 +1,23 @@
// 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.Syntax.Inlines;
namespace Markdig.Renderers.Normalize.Inlines
{
/// <summary>
/// A Normalize renderer for a <see cref="CodeInline"/>.
/// </summary>
/// <seealso cref="Markdig.Renderers.Normalize.NormalizeObjectRenderer{Markdig.Syntax.Inlines.CodeInline}" />
public class CodeInlineRenderer : NormalizeObjectRenderer<CodeInline>
{
protected override void Write(NormalizeRenderer renderer, CodeInline obj)
{
var delimiter = obj.Content.Contains(obj.Delimiter + "") ? new string(obj.Delimiter, 2) : obj.Delimiter + "";
renderer.Write(delimiter);
renderer.Write(obj.Content);
renderer.Write(delimiter);
}
}
}

View File

@@ -0,0 +1,20 @@
// 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.Syntax.Inlines;
namespace Markdig.Renderers.Normalize.Inlines
{
/// <summary>
/// A Normalize renderer for a <see cref="DelimiterInline"/>.
/// </summary>
/// <seealso cref="Markdig.Renderers.Normalize.NormalizeObjectRenderer{Markdig.Syntax.Inlines.DelimiterInline}" />
public class DelimiterInlineRenderer : NormalizeObjectRenderer<DelimiterInline>
{
protected override void Write(NormalizeRenderer renderer, DelimiterInline obj)
{
renderer.Write(obj.ToLiteral());
renderer.WriteChildren(obj);
}
}
}

View File

@@ -0,0 +1,22 @@
// 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.Syntax.Inlines;
namespace Markdig.Renderers.Normalize.Inlines
{
/// <summary>
/// A Normalize renderer for an <see cref="EmphasisInline"/>.
/// </summary>
/// <seealso cref="Markdig.Renderers.Normalize.NormalizeObjectRenderer{Markdig.Syntax.Inlines.EmphasisInline}" />
public class EmphasisInlineRenderer : NormalizeObjectRenderer<EmphasisInline>
{
protected override void Write(NormalizeRenderer renderer, EmphasisInline obj)
{
var emphasisText = new string(obj.DelimiterChar, obj.IsDouble ? 2 : 1);
renderer.Write(emphasisText);
renderer.WriteChildren(obj);
renderer.Write(emphasisText);
}
}
}

View File

@@ -0,0 +1,28 @@
// 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.Syntax.Inlines;
namespace Markdig.Renderers.Normalize.Inlines
{
/// <summary>
/// A Normalize renderer for a <see cref="LineBreakInline"/>.
/// </summary>
/// <seealso cref="Markdig.Renderers.Normalize.NormalizeObjectRenderer{Markdig.Syntax.Inlines.LineBreakInline}" />
public class LineBreakInlineRenderer : NormalizeObjectRenderer<LineBreakInline>
{
/// <summary>
/// Gets or sets a value indicating whether to render this softline break as a Normalize hardline break tag (&lt;br /&gt;)
/// </summary>
public bool RenderAsHardlineBreak { get; set; }
protected override void Write(NormalizeRenderer renderer, LineBreakInline obj)
{
if (obj.IsHard)
{
renderer.Write(obj.IsBackslash ? "\\" : " ");
}
renderer.WriteLine();
}
}
}

View File

@@ -0,0 +1,60 @@
// 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.Syntax.Inlines;
namespace Markdig.Renderers.Normalize.Inlines
{
/// <summary>
/// A Normalize renderer for a <see cref="LinkInline"/>.
/// </summary>
/// <seealso cref="Markdig.Renderers.Normalize.NormalizeObjectRenderer{Markdig.Syntax.Inlines.LinkInline}" />
public class LinkInlineRenderer : NormalizeObjectRenderer<LinkInline>
{
protected override void Write(NormalizeRenderer renderer, LinkInline link)
{
if (link.IsImage)
{
renderer.Write('!');
}
renderer.Write('[');
renderer.WriteChildren(link);
renderer.Write(']');
if (link.Label != null)
{
var literal = link.FirstChild as LiteralInline;
if (literal != null && literal.Content.Match(link.Label) && literal.Content.Length == link.Label.Length)
{
// collapsed reference and shortcut links
if (!link.IsShortcut)
{
renderer.Write("[]");
}
}
else
{
// full link
renderer.Write('[').Write(link.Label).Write(']');
}
}
else
{
if (!string.IsNullOrEmpty(link.Url))
{
renderer.Write('(').Write(link.Url);
if (!string.IsNullOrEmpty(link.Title))
{
renderer.Write(" \"");
renderer.Write(link.Title.Replace(@"""", @"\"""));
renderer.Write("\"");
}
renderer.Write(')');
}
}
}
}
}

View File

@@ -0,0 +1,25 @@
// 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.Helpers;
using Markdig.Syntax.Inlines;
namespace Markdig.Renderers.Normalize.Inlines
{
/// <summary>
/// A Normalize renderer for a <see cref="LiteralInline"/>.
/// </summary>
/// <seealso cref="Markdig.Renderers.Normalize.NormalizeObjectRenderer{Markdig.Syntax.Inlines.LiteralInline}" />
public class LiteralInlineRenderer : NormalizeObjectRenderer<LiteralInline>
{
protected override void Write(NormalizeRenderer renderer, LiteralInline obj)
{
if (obj.IsFirstCharacterEscaped && obj.Content.Length > 0 && obj.Content[obj.Content.Start].IsAsciiPunctuation())
{
renderer.Write('\\');
}
renderer.Write(ref obj.Content);
}
}
}

View File

@@ -0,0 +1,18 @@
// 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.Syntax.Inlines;
namespace Markdig.Renderers.Normalize.Inlines
{
/// <summary>
/// A Normalize renderer for a <see cref="HtmlEntityInline"/>.
/// </summary>
public class NormalizeHtmlEntityInlineRenderer : NormalizeObjectRenderer<HtmlEntityInline>
{
protected override void Write(NormalizeRenderer renderer, HtmlEntityInline obj)
{
renderer.Write(obj.Original);
}
}
}

View File

@@ -0,0 +1,18 @@
// 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.Syntax.Inlines;
namespace Markdig.Renderers.Normalize.Inlines
{
/// <summary>
/// A Normalize renderer for a <see cref="HtmlInline"/>.
/// </summary>
public class NormalizeHtmlInlineRenderer : NormalizeObjectRenderer<HtmlInline>
{
protected override void Write(NormalizeRenderer renderer, HtmlInline obj)
{
renderer.Write(obj.Tag);
}
}
}

View File

@@ -0,0 +1,18 @@
// 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.Syntax;
namespace Markdig.Renderers.Normalize
{
public class LinkReferenceDefinitionGroupRenderer : NormalizeObjectRenderer<LinkReferenceDefinitionGroup>
{
protected override void Write(NormalizeRenderer renderer, LinkReferenceDefinitionGroup obj)
{
renderer.EnsureLine();
renderer.WriteChildren(obj);
renderer.FinishBlock(false);
}
}
}

View File

@@ -0,0 +1,28 @@
// 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.Syntax;
namespace Markdig.Renderers.Normalize
{
public class LinkReferenceDefinitionRenderer : NormalizeObjectRenderer<LinkReferenceDefinition>
{
protected override void Write(NormalizeRenderer renderer, LinkReferenceDefinition linkDef)
{
renderer.EnsureLine();
renderer.Write('[');
renderer.Write(linkDef.Label);
renderer.Write("]: ");
renderer.Write(linkDef.Url);
if (linkDef.Title != null)
{
renderer.Write(" \"");
renderer.Write(linkDef.Title.Replace("\"", "\\\""));
renderer.Write('"');
}
renderer.FinishBlock(false);
}
}
}

View File

@@ -0,0 +1,93 @@
// 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.Globalization;
using Markdig.Syntax;
namespace Markdig.Renderers.Normalize
{
/// <summary>
/// A Normalize renderer for a <see cref="ListBlock"/>.
/// </summary>
/// <seealso cref="Markdig.Renderers.Normalize.NormalizeObjectRenderer{Markdig.Syntax.ListBlock}" />
public class ListRenderer : NormalizeObjectRenderer<ListBlock>
{
protected override void Write(NormalizeRenderer renderer, ListBlock listBlock)
{
renderer.EnsureLine();
var compact = renderer.CompactParagraph;
renderer.CompactParagraph = !listBlock.IsLoose;
if (listBlock.IsOrdered)
{
int index = 0;
if (listBlock.OrderedStart != null)
{
switch (listBlock.BulletType)
{
case '1':
int.TryParse(listBlock.OrderedStart, out index);
break;
}
}
for (var i = 0; i < listBlock.Count; i++)
{
var item = listBlock[i];
var listItem = (ListItemBlock) item;
renderer.EnsureLine();
renderer.Write(index.ToString(CultureInfo.InvariantCulture));
renderer.Write(listBlock.OrderedDelimiter);
renderer.Write(' ');
renderer.PushIndent(new string(' ', IntLog10Fast(index) + 3));
renderer.WriteChildren(listItem);
renderer.PopIndent();
switch (listBlock.BulletType)
{
case '1':
index++;
break;
}
if (i + 1 < listBlock.Count && listBlock.IsLoose)
{
renderer.EnsureLine();
renderer.WriteLine();
}
}
}
else
{
for (var i = 0; i < listBlock.Count; i++)
{
var item = listBlock[i];
var listItem = (ListItemBlock) item;
renderer.EnsureLine();
renderer.Write(renderer.Options.ListItemCharacter ?? listBlock.BulletType);
renderer.Write(' ');
renderer.PushIndent(" ");
renderer.WriteChildren(listItem);
renderer.PopIndent();
if (i + 1 < listBlock.Count && listBlock.IsLoose)
{
renderer.EnsureLine();
renderer.WriteLine();
}
}
}
renderer.CompactParagraph = compact;
renderer.FinishBlock(true);
}
private static int IntLog10Fast(int input) =>
(input < 10) ? 0 :
(input < 100) ? 1 :
(input < 1000) ? 2 :
(input < 10000) ? 3 :
(input < 100000) ? 4 :
(input < 1000000) ? 5 :
(input < 10000000) ? 6 :
(input < 100000000) ? 7 :
(input < 1000000000) ? 8 : 9;
}
}

View File

@@ -0,0 +1,16 @@
// 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.Syntax;
namespace Markdig.Renderers.Normalize
{
/// <summary>
/// A base class for Normalize rendering <see cref="Block"/> and <see cref="Markdig.Syntax.Inlines.Inline"/> Markdown objects.
/// </summary>
/// <typeparam name="TObject">The type of the object.</typeparam>
/// <seealso cref="Markdig.Renderers.IMarkdownObjectRenderer" />
public abstract class NormalizeObjectRenderer<TObject> : MarkdownObjectRenderer<NormalizeRenderer, TObject> where TObject : MarkdownObject
{
}
}

View File

@@ -0,0 +1,48 @@
// 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.Renderers.Normalize
{
/// <summary>
/// Defines the options used by <see cref="NormalizeRenderer"/>
/// </summary>
public class NormalizeOptions
{
/// <summary>
/// Initialize a new instance of <see cref="NormalizeOptions"/>
/// </summary>
public NormalizeOptions()
{
SpaceAfterQuoteBlock = true;
EmptyLineAfterCodeBlock = true;
EmptyLineAfterHeading = true;
EmptyLineAfterThematicBreak = true;
ListItemCharacter = null;
}
/// <summary>
/// Adds a space after a QuoteBlock &gt;. Default is <c>true</c>
/// </summary>
public bool SpaceAfterQuoteBlock { get; set; }
/// <summary>
/// Adds an empty line after a code block (fenced and tabbed). Default is <c>true</c>
/// </summary>
public bool EmptyLineAfterCodeBlock { get; set; }
/// <summary>
/// Adds an empty line after an heading. Default is <c>true</c>
/// </summary>
public bool EmptyLineAfterHeading { get; set; }
/// <summary>
/// Adds an empty line after an thematic break. Default is <c>true</c>
/// </summary>
public bool EmptyLineAfterThematicBreak { get; set; }
/// <summary>
/// The bullet character used for list items. Default is <c>null</c> leaving the original bullet character as-is.
/// </summary>
public char? ListItemCharacter { get; set; }
}
}

View File

@@ -0,0 +1,161 @@
// 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 System.IO;
using Markdig.Syntax;
using Markdig.Renderers.Normalize.Inlines;
namespace Markdig.Renderers.Normalize
{
/// <summary>
/// Default HTML renderer for a Markdown <see cref="MarkdownDocument"/> object.
/// </summary>
/// <seealso cref="Markdig.Renderers.TextRendererBase{Markdig.Renderers.Normalize.NormalizeRenderer}" />
public class NormalizeRenderer : TextRendererBase<NormalizeRenderer>
{
/// <summary>
/// Initializes a new instance of the <see cref="NormalizeRenderer"/> class.
/// </summary>
/// <param name="writer">The writer.</param>
/// <param name="options">The normalize options</param>
public NormalizeRenderer(TextWriter writer, NormalizeOptions options = null) : base(writer)
{
Options = options ?? new NormalizeOptions();
// Default block renderers
ObjectRenderers.Add(new CodeBlockRenderer());
ObjectRenderers.Add(new ListRenderer());
ObjectRenderers.Add(new HeadingRenderer());
ObjectRenderers.Add(new HtmlBlockRenderer());
ObjectRenderers.Add(new ParagraphRenderer());
ObjectRenderers.Add(new QuoteBlockRenderer());
ObjectRenderers.Add(new ThematicBreakRenderer());
ObjectRenderers.Add(new LinkReferenceDefinitionGroupRenderer());
ObjectRenderers.Add(new LinkReferenceDefinitionRenderer());
// Default inline renderers
ObjectRenderers.Add(new AutolinkInlineRenderer());
ObjectRenderers.Add(new CodeInlineRenderer());
ObjectRenderers.Add(new DelimiterInlineRenderer());
ObjectRenderers.Add(new EmphasisInlineRenderer());
ObjectRenderers.Add(new LineBreakInlineRenderer());
ObjectRenderers.Add(new NormalizeHtmlInlineRenderer());
ObjectRenderers.Add(new NormalizeHtmlEntityInlineRenderer());
ObjectRenderers.Add(new LinkInlineRenderer());
ObjectRenderers.Add(new LiteralInlineRenderer());
}
public NormalizeOptions Options { get; }
public bool CompactParagraph { get; set; }
public void FinishBlock(bool emptyLine)
{
if (!IsLastInContainer)
{
WriteLine();
if (emptyLine)
{
WriteLine();
}
}
}
///// <summary>
///// Writes the attached <see cref="HtmlAttributes"/> on the specified <see cref="MarkdownObject"/>.
///// </summary>
///// <param name="obj">The object.</param>
///// <returns></returns>
//public NormalizeRenderer WriteAttributes(MarkdownObject obj)
//{
// if (obj == null) throw new ArgumentNullException(nameof(obj));
// return WriteAttributes(obj.TryGetAttributes());
//}
///// <summary>
///// Writes the specified <see cref="HtmlAttributes"/>.
///// </summary>
///// <param name="attributes">The attributes to render.</param>
///// <returns>This instance</returns>
//public NormalizeRenderer WriteAttributes(HtmlAttributes attributes)
//{
// if (attributes == null)
// {
// return this;
// }
// if (attributes.Id != null)
// {
// Write(" id=\"").WriteEscape(attributes.Id).Write("\"");
// }
// if (attributes.Classes != null && attributes.Classes.Count > 0)
// {
// Write(" class=\"");
// for (int i = 0; i < attributes.Classes.Count; i++)
// {
// var cssClass = attributes.Classes[i];
// if (i > 0)
// {
// Write(" ");
// }
// WriteEscape(cssClass);
// }
// Write("\"");
// }
// if (attributes.Properties != null && attributes.Properties.Count > 0)
// {
// foreach (var property in attributes.Properties)
// {
// Write(" ").Write(property.Key);
// if (property.Value != null)
// {
// Write("=").Write("\"");
// WriteEscape(property.Value);
// Write("\"");
// }
// }
// }
// return this;
//}
/// <summary>
/// Writes the lines of a <see cref="LeafBlock"/>
/// </summary>
/// <param name="leafBlock">The leaf block.</param>
/// <param name="writeEndOfLines">if set to <c>true</c> write end of lines.</param>
/// <returns>This instance</returns>
public NormalizeRenderer WriteLeafRawLines(LeafBlock leafBlock, bool writeEndOfLines, bool indent = false)
{
if (leafBlock == null) throw new ArgumentNullException(nameof(leafBlock));
if (leafBlock.Lines.Lines != null)
{
var lines = leafBlock.Lines;
var slices = lines.Lines;
for (int i = 0; i < lines.Count; i++)
{
if (!writeEndOfLines && i > 0)
{
WriteLine();
}
if (indent)
{
Write(" ");
}
Write(ref slices[i].Slice);
if (writeEndOfLines)
{
WriteLine();
}
}
}
return this;
}
}
}

View File

@@ -0,0 +1,20 @@
// 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.Syntax;
namespace Markdig.Renderers.Normalize
{
/// <summary>
/// A Normalize renderer for a <see cref="ParagraphBlock"/>.
/// </summary>
/// <seealso cref="Markdig.Renderers.Normalize.NormalizeObjectRenderer{Markdig.Syntax.ParagraphBlock}" />
public class ParagraphRenderer : NormalizeObjectRenderer<ParagraphBlock>
{
protected override void Write(NormalizeRenderer renderer, ParagraphBlock obj)
{
renderer.WriteLeafInline(obj);
renderer.FinishBlock(!renderer.CompactParagraph);
}
}
}

View File

@@ -0,0 +1,24 @@
// 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.Syntax;
namespace Markdig.Renderers.Normalize
{
/// <summary>
/// A Normalize renderer for a <see cref="QuoteBlock"/>.
/// </summary>
/// <seealso cref="Markdig.Renderers.Normalize.NormalizeObjectRenderer{Markdig.Syntax.QuoteBlock}" />
public class QuoteBlockRenderer : NormalizeObjectRenderer<QuoteBlock>
{
protected override void Write(NormalizeRenderer renderer, QuoteBlock obj)
{
var quoteIndent = renderer.Options.SpaceAfterQuoteBlock ? obj.QuoteChar + " " : obj.QuoteChar.ToString();
renderer.PushIndent(quoteIndent);
renderer.WriteChildren(obj);
renderer.PopIndent();
renderer.FinishBlock(true);
}
}
}

View File

@@ -0,0 +1,21 @@
// 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.Syntax;
namespace Markdig.Renderers.Normalize
{
/// <summary>
/// A Normalize renderer for a <see cref="ThematicBreakBlock"/>.
/// </summary>
/// <seealso cref="Markdig.Renderers.Normalize.NormalizeObjectRenderer{Markdig.Syntax.ThematicBreakBlock}" />
public class ThematicBreakRenderer : NormalizeObjectRenderer<ThematicBreakBlock>
{
protected override void Write(NormalizeRenderer renderer, ThematicBreakBlock obj)
{
renderer.WriteLine(new string(obj.ThematicChar, obj.ThematicCharCount));
renderer.FinishBlock(renderer.Options.EmptyLineAfterThematicBreak);
}
}
}

View File

@@ -2,6 +2,7 @@
// 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 System.Collections.Generic;
using System.IO;
using System.Runtime.CompilerServices;
using Markdig.Helpers;
@@ -70,6 +71,7 @@ namespace Markdig.Renderers
{
private bool previousWasLine;
private char[] buffer;
private readonly List<string> indents;
/// <summary>
/// Initializes a new instance of the <see cref="TextRendererBase{T}"/> class.
@@ -80,6 +82,7 @@ namespace Markdig.Renderers
buffer = new char[1024];
// We assume that we are starting as if we had previously a newline
previousWasLine = true;
indents = new List<string>();
}
/// <summary>
@@ -95,6 +98,31 @@ namespace Markdig.Renderers
return (T)this;
}
public void PushIndent(string indent)
{
if (indent == null) throw new ArgumentNullException(nameof(indent));
indents.Add(indent);
}
public void PopIndent()
{
// TODO: Check
indents.RemoveAt(indents.Count - 1);
}
private void WriteIndent()
{
if (previousWasLine)
{
previousWasLine = false;
for (int i = 0; i < indents.Count; i++)
{
Writer.Write(indents[i]);
}
}
}
/// <summary>
/// Writes the specified content.
/// </summary>
@@ -103,6 +131,7 @@ namespace Markdig.Renderers
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
public T Write(string content)
{
WriteIndent();
previousWasLine = false;
Writer.Write(content);
return (T) this;
@@ -142,6 +171,7 @@ namespace Markdig.Renderers
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
public T Write(char content)
{
WriteIndent();
previousWasLine = content == '\n';
Writer.Write(content);
return (T) this;
@@ -161,6 +191,7 @@ namespace Markdig.Renderers
return (T) this;
}
WriteIndent();
previousWasLine = false;
if (offset == 0 && content.Length == length)
{
@@ -189,6 +220,7 @@ namespace Markdig.Renderers
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
public T WriteLine()
{
WriteIndent();
Writer.WriteLine();
previousWasLine = true;
return (T) this;
@@ -202,6 +234,7 @@ namespace Markdig.Renderers
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
public T WriteLine(string content)
{
WriteIndent();
previousWasLine = true;
Writer.WriteLine(content);
return (T) this;

View File

@@ -10,5 +10,7 @@ namespace Markdig.Syntax.Inlines
public class LineBreakInline : LeafInline
{
public bool IsHard { get; set; }
public bool IsBackslash { get; set; }
}
}

View File

@@ -64,6 +64,11 @@ namespace Markdig.Syntax.Inlines
/// </summary>
public bool IsImage { get; set; }
/// <summary>
/// Gets or sets a boolean indicating if this link is a shortcut link to a <see cref="LinkReferenceDefinition"/>
/// </summary>
public bool IsShortcut { get; set; }
/// <summary>
/// Gets or sets the reference this link is attached to. May be null.
/// </summary>

View File

@@ -48,6 +48,11 @@ namespace Markdig.Syntax.Inlines
/// </summary>
public StringSlice Content;
/// <summary>
/// A boolean indicating whether the first character of this literal is escaped by `\`.
/// </summary>
public bool IsFirstCharacterEscaped { get; set; }
public override string ToString()
{
return Content.ToString();

View File

@@ -17,5 +17,9 @@ namespace Markdig.Syntax
public ThematicBreakBlock(BlockParser parser) : base(parser)
{
}
public char ThematicChar { get; set; }
public int ThematicCharCount { get; set; }
}
}

View File

@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26430.16
VisualStudioVersion = 15.0.26730.16
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{061866E2-005C-4D13-A338-EA464BBEC60F}"
ProjectSection(SolutionItems) = preProject
@@ -26,6 +26,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Markdig.WebApp", "Markdig.W
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnicodeNormDApp", "UnicodeNormDApp\UnicodeNormDApp.csproj", "{33FFC0B9-0187-44F9-9424-BB5AF5B4FB84}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "mdtoc", "mdtoc\mdtoc.csproj", "{E3CDFF0F-5BFC-42E9-BDBA-2797651900A2}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -52,8 +54,15 @@ Global
{33FFC0B9-0187-44F9-9424-BB5AF5B4FB84}.Debug|Any CPU.Build.0 = Debug|Any CPU
{33FFC0B9-0187-44F9-9424-BB5AF5B4FB84}.Release|Any CPU.ActiveCfg = Release|Any CPU
{33FFC0B9-0187-44F9-9424-BB5AF5B4FB84}.Release|Any CPU.Build.0 = Release|Any CPU
{E3CDFF0F-5BFC-42E9-BDBA-2797651900A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E3CDFF0F-5BFC-42E9-BDBA-2797651900A2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E3CDFF0F-5BFC-42E9-BDBA-2797651900A2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E3CDFF0F-5BFC-42E9-BDBA-2797651900A2}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {D068F7B6-6ACC-456C-A2E1-10EA746D956D}
EndGlobalSection
EndGlobal

6
src/mdtoc/App.config Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" />
</startup>
</configuration>

101
src/mdtoc/Program.cs Normal file
View File

@@ -0,0 +1,101 @@
// 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 System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using Markdig;
using Markdig.Extensions.AutoIdentifiers;
using Markdig.Renderers;
using Markdig.Renderers.Html;
using Markdig.Syntax;
namespace mdtoc
{
/// <summary>
/// A tool to generate a markdown TOC from a markdown local file or a github link to a markdown file.
/// </summary>
class Program
{
static void Error(string message)
{
Console.WriteLine(message);
Environment.Exit(1);
}
static void Main(string[] args)
{
if (args.Length != 1 || args[0] == "--help" || args[0] == "-help" || args[0] == "/?" || args[0] == "/help")
{
Error("Usage: mdtoc [markdown file path | http github URL]");
return;
}
var path = args[0];
string markdown = null;
if (path.StartsWith("https:"))
{
Uri uri;
if (!Uri.TryCreate(path, UriKind.Absolute, out uri))
{
Error($"Unable to parse Uri `{path}`");
return;
}
// Special handling of github URL to access the raw content instead
if (uri.Host == "github.com")
{
// https://github.com/lunet-io/scriban/blob/master/doc/language.md
// https://raw.githubusercontent.com/lunet-io/scriban/master/doc/language.md
var newPath = uri.AbsolutePath;
var paths = new List<string>(newPath.Split(new char[] {'/'}, StringSplitOptions.RemoveEmptyEntries));
if (paths.Count < 5 || paths[2] != "blob")
{
Error($"Invalid github.com URL `{path}`");
return;
}
paths.RemoveAt(2); // remove blob
uri = new Uri($"https://raw.githubusercontent.com/{(string.Join("/", paths))}");
}
var httpClient = new HttpClient();
markdown = httpClient.GetStringAsync(uri).ConfigureAwait(false).GetAwaiter().GetResult();
}
else
{
markdown = File.ReadAllText(path);
}
var pipeline = new MarkdownPipelineBuilder().UseAutoIdentifiers(AutoIdentifierOptions.GitHub).Build();
var doc = Markdown.Parse(markdown, pipeline);
// Precomputes the minHeading
var headings = doc.Descendants<HeadingBlock>().ToList();
int minHeading = int.MaxValue;
int maxHeading = int.MinValue;
foreach (var heading in headings)
{
minHeading = Math.Min(minHeading, heading.Level);
maxHeading = Math.Max(maxHeading, heading.Level);
}
var writer = Console.Out;
// Use this htmlWriter to write content of headings into link label
var htmlWriter = new HtmlRenderer(writer) {EnableHtmlForInline = true};
foreach (var heading in headings)
{
var indent = heading.Level - minHeading;
for (int i = 0; i < indent; i++)
{
// - Start Of Heading
writer.Write(" ");
}
writer.Write("- [");
htmlWriter.WriteLeafInline(heading);
writer.Write($"](#{heading.GetAttributes().Id})");
writer.WriteLine();
}
}
}
}

View File

@@ -0,0 +1,39 @@
// 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.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("mdtoc")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Alexandre Mutel")]
[assembly: AssemblyProduct("mdtoc")]
[assembly: AssemblyCopyright("Copyright © 2017 - Alexandre Mutel")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("e3cdff0f-5bfc-42e9-bdba-2797651900a2")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

58
src/mdtoc/mdtoc.csproj Normal file
View File

@@ -0,0 +1,58 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{E3CDFF0F-5BFC-42E9-BDBA-2797651900A2}</ProjectGuid>
<OutputType>Exe</OutputType>
<RootNamespace>mdtoc</RootNamespace>
<AssemblyName>mdtoc</AssemblyName>
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Markdig\Markdig.csproj">
<Project>{8a58a7e2-627c-4f41-933f-5ac92adfab48}</Project>
<Name>Markdig</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>