mirror of
https://github.com/xoofx/markdig.git
synced 2026-02-04 05:44:50 +00:00
Compare commits
148 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
571e04fe28 | ||
|
|
c847996146 | ||
|
|
2782d3b4c3 | ||
|
|
582a76f8f0 | ||
|
|
030d676497 | ||
|
|
0794213eef | ||
|
|
427dc849f7 | ||
|
|
e598047964 | ||
|
|
15546732dd | ||
|
|
7bd238852d | ||
|
|
0f406039a0 | ||
|
|
1cd9cdfca0 | ||
|
|
812c4fabe6 | ||
|
|
85f8f59786 | ||
|
|
b0839f114c | ||
|
|
431fecb1c6 | ||
|
|
7620b2b760 | ||
|
|
ad18514824 | ||
|
|
c68a488717 | ||
|
|
e2f0f00831 | ||
|
|
fea2ca5adf | ||
|
|
f77bd0b36d | ||
|
|
20243a79a0 | ||
|
|
a097247272 | ||
|
|
f3cb0712ca | ||
|
|
eedfc3cd9c | ||
|
|
5f4b049ce0 | ||
|
|
20edf26c40 | ||
|
|
4192a00e20 | ||
|
|
4346c52ef0 | ||
|
|
42683e043d | ||
|
|
96c469018e | ||
|
|
f64ac47841 | ||
|
|
c29b7d2942 | ||
|
|
0f7e3b8c52 | ||
|
|
6dff16a612 | ||
|
|
8e15c8bc6a | ||
|
|
558db1fd70 | ||
|
|
796b143316 | ||
|
|
3402805ebb | ||
|
|
c3600c2ba5 | ||
|
|
4ba98f594a | ||
|
|
544a64e5c9 | ||
|
|
f17320702a | ||
|
|
d883694ac4 | ||
|
|
42fba26ea2 | ||
|
|
595dacf213 | ||
|
|
75adfa3fe8 | ||
|
|
0bb8139450 | ||
|
|
60784901b2 | ||
|
|
5020fdd3b1 | ||
|
|
bd4ea56d2a | ||
|
|
fbc8a4116c | ||
|
|
9af318d334 | ||
|
|
88e561c3ae | ||
|
|
1ca82eb058 | ||
|
|
66007c6bbf | ||
|
|
14d4286fd7 | ||
|
|
58b1a48f5b | ||
|
|
5e1eaf8590 | ||
|
|
c946295d96 | ||
|
|
97cbf11f8b | ||
|
|
8d4394e7c6 | ||
|
|
fbdb8cf063 | ||
|
|
964538ec79 | ||
|
|
9a30883e2a | ||
|
|
6408705f82 | ||
|
|
523582e588 | ||
|
|
aaff022e7c | ||
|
|
37eb6aa529 | ||
|
|
9b7356b05e | ||
|
|
33d3bc1330 | ||
|
|
07a2980d5b | ||
|
|
2d8872f2a1 | ||
|
|
16a9bbc84e | ||
|
|
0e5338a709 | ||
|
|
9139e0142b | ||
|
|
9a38312df0 | ||
|
|
d808dcf6f8 | ||
|
|
d5985fc94c | ||
|
|
994687d5ae | ||
|
|
2f4e958ab2 | ||
|
|
26beaa81da | ||
|
|
de5ed11963 | ||
|
|
b557d51276 | ||
|
|
27a8345943 | ||
|
|
131163ff9a | ||
|
|
241f674b99 | ||
|
|
194edee243 | ||
|
|
0995fa0cec | ||
|
|
6717be5210 | ||
|
|
e15745f346 | ||
|
|
a513b0c587 | ||
|
|
72adb963e8 | ||
|
|
aac7df6b87 | ||
|
|
4cf4bfb58f | ||
|
|
59630aec8e | ||
|
|
70c4f6deda | ||
|
|
e2b3f812cb | ||
|
|
fc8adc70e0 | ||
|
|
72c2c06fcb | ||
|
|
7174e32b7a | ||
|
|
600219529c | ||
|
|
d7fd04d14c | ||
|
|
2147e434f7 | ||
|
|
7bd00d115f | ||
|
|
80ef9d2799 | ||
|
|
d6a705d76c | ||
|
|
42472085a6 | ||
|
|
3897e875ee | ||
|
|
c63392657d | ||
|
|
34579b51a1 | ||
|
|
1bb35c5fc1 | ||
|
|
0c408951b8 | ||
|
|
218a094f0d | ||
|
|
3cc405b05b | ||
|
|
d58db530bb | ||
|
|
3628dc3b17 | ||
|
|
89ff42805d | ||
|
|
48866a2609 | ||
|
|
9cc5856c1c | ||
|
|
a4bb174a77 | ||
|
|
82987fa879 | ||
|
|
2df2ff17c5 | ||
|
|
4e4825cb3f | ||
|
|
ed2371beae | ||
|
|
2c2898769e | ||
|
|
001a99ab77 | ||
|
|
9233ec220c | ||
|
|
bb30dc21c5 | ||
|
|
c761fa2243 | ||
|
|
9d172ffcc1 | ||
|
|
1863e22328 | ||
|
|
c591d1950f | ||
|
|
339fcc9152 | ||
|
|
3a54f06540 | ||
|
|
3f305a25a8 | ||
|
|
5a210223b6 | ||
|
|
a7786d934d | ||
|
|
cb3c1f1505 | ||
|
|
50b33b8512 | ||
|
|
80790b5038 | ||
|
|
311c28ae60 | ||
|
|
9906a0554f | ||
|
|
7e92f1881d | ||
|
|
b20b111385 | ||
|
|
6ac2429e2a | ||
|
|
9d52732f18 |
37
.editorconfig
Normal file
37
.editorconfig
Normal file
@@ -0,0 +1,37 @@
|
||||
# EditorConfig is awesome:http://EditorConfig.org
|
||||
|
||||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
# All Files
|
||||
[*]
|
||||
charset = utf-8
|
||||
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
|
||||
@@ -29,7 +29,12 @@ build:
|
||||
project: src/Markdig.sln
|
||||
verbosity: minimal
|
||||
before_package:
|
||||
- cmd: msbuild /t:pack /p:VersionSuffix="%MARKDIG_VERSION_SUFFIX%" /p:Configuration=Release Markdig/Markdig.csproj
|
||||
- cmd: >-
|
||||
msbuild /t:pack /p:VersionSuffix="%MARKDIG_VERSION_SUFFIX%" /p:Configuration=Release Markdig/Markdig.csproj
|
||||
|
||||
msbuild /t:Clean Markdig/Markdig.csproj
|
||||
|
||||
msbuild /t:pack /p:VersionSuffix="%MARKDIG_VERSION_SUFFIX%" /p:Configuration=Release;SignAssembly=true Markdig/Markdig.csproj
|
||||
artifacts:
|
||||
- path: src\Markdig\Bin\Release\*.nupkg
|
||||
name: Markdig Nugets
|
||||
|
||||
130
changelog.md
Normal file
130
changelog.md
Normal file
@@ -0,0 +1,130 @@
|
||||
# Changelog
|
||||
|
||||
## 0.15.1 (10 July 2018)
|
||||
- Add support for `netstandard2.0`
|
||||
- Make AutoIdentifierExtension thread safe
|
||||
|
||||
## 0.15.0 (4 Apr 2018)
|
||||
- Add `ConfigureNewLine` extension method to `MarkdownPipelineBuilder` ([(PR #214)](https://github.com/lunet-io/markdig/pull/214))
|
||||
- Add alternative `Use` extension method to `MarkdownPipelineBuilder` that receives an object instance ([(PR #213)](https://github.com/lunet-io/markdig/pull/213))
|
||||
- Added class attribute to media link extension ([(PR #203)](https://github.com/lunet-io/markdig/pull/203))
|
||||
- Optional link rewriter func for HtmlRenderer #143 ([(PR #201)](https://github.com/lunet-io/markdig/pull/201))
|
||||
- Upgrade NUnit3TestAdapter from 3.2 to 3.9 to address Resharper test runner problems ([(PR #199)](https://github.com/lunet-io/markdig/pull/199))
|
||||
- HTML renderer supports converting relative URLs on links and images to absolute #143 ([(PR #197)](https://github.com/lunet-io/markdig/pull/197))
|
||||
|
||||
## 0.14.9 (15 Jan 2018)
|
||||
- AutoLinkParser should to remove mailto: in outputted text ([(PR #195)](https://github.com/lunet-io/markdig/pull/195))
|
||||
- Add support for `music.yandex.ru` and `ok.ru` for MediaLinks extension ([(PR #193)](https://github.com/lunet-io/markdig/pull/193))
|
||||
## 0.14.8 (05 Dec 2017)
|
||||
- Fix potential StackOverflow exception when processing deep nested `|` delimiters (#179)
|
||||
## 0.14.7 (25 Nov 2017)
|
||||
- Fix autolink attached attributes not being displayed properly (#175)
|
||||
## 0.14.6 (21 Nov 2017)
|
||||
- Fix yaml frontmatter issue when ending with a empty line (#170)
|
||||
## 0.14.5 (18 Nov 2017)
|
||||
- Fix changelog link from nuget package
|
||||
## 0.14.4 (18 Nov 2017)
|
||||
- Add changelog.md
|
||||
- Fix bug when a thematic break is inside a fenced code block inside a pending list (#164)
|
||||
- Add support for GFM autolinks (#165, #169)
|
||||
- Better handle YAML frontmatter in case the opening `---` is never actually closed (#160)
|
||||
- Fix link conflict between a link to an image definition and heading auto-identifiers (#159)
|
||||
## 0.14.3
|
||||
- Make EmojiExtension.EnableSmiley public
|
||||
## 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
|
||||
## 0.14.1
|
||||
- Fix crash in Markdown.Normalize to handle HtmlBlock correctly
|
||||
- Add better handling of bullet character for lists in Markdown.Normalize
|
||||
## 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.
|
||||
## 0.13.4
|
||||
- Add support for single table header row without a table body rows (#141)
|
||||
- ADd support for `nomnoml` diagrams
|
||||
## 0.13.3
|
||||
- Add support for Pandoc YAML frontmatter (#138)
|
||||
## 0.13.2
|
||||
- Add support for UAP10.0 (#137)
|
||||
## 0.13.1
|
||||
- Fix indenting issue after a double digit list block using a tab (#134)
|
||||
## 0.13.0
|
||||
- Update to latest CommonMark specs 0.28
|
||||
## 0.12.3
|
||||
- Fix issue with HTML blocks for heading h2,h3,h4,h5,h6 that were not correctly identified as HTML blocks as per CommonMark spec
|
||||
## 0.12.2
|
||||
- Fix issue with generic attributes used just before a pipe table (issue #121)
|
||||
## 0.12.1
|
||||
- Fix issue with media links extension when a URL to video is used, an unexpected closing `</iframe>` was inserted (issue #119)
|
||||
## 0.12.0
|
||||
- Add new extension JiraLink support (thanks to @clarkd)
|
||||
- Fix issue in html attributes not parsing correctly properties (thanks to @meziantou)
|
||||
- Fix issues detected by an automatic static code analysis tool
|
||||
## 0.11.0
|
||||
- Fix issue with math extension and $$ block parsing not handling correctly beginning of a $$ as a inline math instead (issue #107)
|
||||
- Fix issue with custom attributes for emphasis
|
||||
- Add support for new special custom arrows emoji (`->` `<-` `<->` `<=` `=>` `<==>`)
|
||||
## 0.10.7
|
||||
- Fix issue when an url ends by a dot `.`
|
||||
## 0.10.6
|
||||
- Fix emphasis with HTML entities
|
||||
## 0.10.5
|
||||
- Several minor fixes
|
||||
## 0.10.4
|
||||
- Fix issue with autolinks
|
||||
- Normalize number of columns for tables
|
||||
## 0.10.3
|
||||
- Fix issue with pipetables shifting a cell to a new column (issue #73)
|
||||
## 0.10.2
|
||||
- Fix exception when trying to urlize an url with an unicode character outside the supported range by NormD (issue #75)
|
||||
## 0.10.1
|
||||
- Update to latest CommonMark specs
|
||||
- Fix source span for LinkReferenceDefinition
|
||||
## 0.10.0
|
||||
- Breaking change of the IMarkdownExtension to allow to receive the MarkdownPipeline for the renderers setup
|
||||
## 0.9.1
|
||||
- Fix regression bug with conflicts between autolink extension and html inline/regular links
|
||||
## 0.9.0
|
||||
- Add new Autolink extension
|
||||
## 0.8.5
|
||||
- Allow to force table column alignment to left
|
||||
## 0.8.4
|
||||
- Fix issue when calculating the span of an indented code block within a list. Make sure to include first whitespace on the line
|
||||
## 0.8.3
|
||||
- fix NullReferenceException with Gridtables extension when a single `+` is entered on a line
|
||||
## 0.8.2
|
||||
- fix potential cast exception with Abreviation extension and empty literals
|
||||
## 0.8.1
|
||||
- new extension to disable URI escaping for non-US-ASCII characters to workaround a bug in Edge/IE
|
||||
- Fix an issue with abbreviations with left/right multiple non-punctuation/space characters
|
||||
## 0.8.0
|
||||
- Update to latest CommonMark specs
|
||||
- Fix empty literal
|
||||
- Add YAML frontmatter extension
|
||||
## 0.7.5
|
||||
- several bug fixes (pipe tables, disable HTML, special attributes, inline maths, abbreviations...)
|
||||
- add support for rowspan in grid tables
|
||||
## 0.7.4
|
||||
- Fix bug with strong emphasis starting at the beginning of a line
|
||||
## 0.7.3
|
||||
- Fix threading issue with pipeline
|
||||
## 0.7.2
|
||||
- Fix rendering of table colspan with non english locale
|
||||
- Fix grid table colspan parsing
|
||||
- Add nofollow extension for links
|
||||
## 0.7.1
|
||||
- Fix issue in smarty pants which could lead to an InvalidCastException
|
||||
- Update parsers to latest CommonMark specs
|
||||
## 0.7.0
|
||||
- Update to latest NETStandard.Library 1.6.0
|
||||
- Fix issue with digits in auto-identifier extension
|
||||
- Fix incorrect start of span calculated for code indented blocks
|
||||
## 0.6.2
|
||||
- Handle latest CommonMark specs for corner cases for emphasis (See https://talk.commonmark.org/t/emphasis-strong-emphasis-corner-cases/2123/1 )
|
||||
## 0.6.1:
|
||||
- Fix issue with autoidentifier extension overriding manual HTML attributes id on headings
|
||||
## 0.6.0
|
||||
- Fix conflicts between PipeTables and SmartyPants extensions
|
||||
- Add SelfPipeline extension
|
||||
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2016, Alexandre Mutel
|
||||
Copyright (c) 2018, Alexandre Mutel
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification
|
||||
|
||||
12
readme.md
12
readme.md
@@ -14,7 +14,7 @@ You can **try Markdig online** and compare it to other implementations on [babel
|
||||
- **Abstract Syntax Tree** with precise source code location for syntax tree, useful when building a Markdown editor.
|
||||
- Checkout [MarkdownEditor for Visual Studio](https://visualstudiogallery.msdn.microsoft.com/eaab33c3-437b-4918-8354-872dfe5d1bfe) powered by Markdig!
|
||||
- Converter to **HTML**
|
||||
- Passing more than **600+ tests** from the latest [CommonMark specs (0.27)](http://spec.commonmark.org/)
|
||||
- Passing more than **600+ tests** from the latest [CommonMark specs (0.28)](http://spec.commonmark.org/)
|
||||
- Includes all the core elements of CommonMark:
|
||||
- including **GFM fenced code blocks**.
|
||||
- **Extensible** architecture
|
||||
@@ -47,11 +47,17 @@ 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+`)
|
||||
|
||||
### Third Party Extensions
|
||||
|
||||
- [**WPF/XAML Markdown Renderer**: `markdig.wpf`](https://github.com/Kryptos-FR/markdig.wpf)
|
||||
- [**Syntax highlighting**: `Markdig.SyntaxHighlighting`](https://github.com/RichardSlater/Markdig.SyntaxHighlighting)
|
||||
- [**Embedded C# scripting**: `Markdig.Extensions.ScriptCs`](https://github.com/macaba/Markdig.Extensions.ScriptCs)
|
||||
|
||||
## Documentation
|
||||
|
||||
> The repository is under construction. There will be a dedicated website and proper documentation at some point!
|
||||
@@ -64,6 +70,8 @@ In the meantime, you can have a "behind the scene" article about Markdig in my b
|
||||
|
||||
Markdig is available as a NuGet package: [](https://www.nuget.org/packages/Markdig/)
|
||||
|
||||
Also [Markdig.Signed](https://www.nuget.org/packages/Markdig.Signed/) NuGet package provides signed assemblies.
|
||||
|
||||
## Usage
|
||||
|
||||
The main entry point for the API is the `Markdig.Markdown` class:
|
||||
|
||||
@@ -57,12 +57,17 @@
|
||||
<DependentUpon>Specs.tt</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Specs\TestEmphasisPlus.cs" />
|
||||
<Compile Include="TestConfigureNewLine.cs" />
|
||||
<Compile Include="TestHtmlAttributes.cs" />
|
||||
<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="TestLinkRewriter.cs" />
|
||||
<Compile Include="TestRelativeUrlReplacement.cs" />
|
||||
<Compile Include="TestSourcePosition.cs" />
|
||||
<Compile Include="TestStringSliceList.cs" />
|
||||
<Compile Include="TestPlayParser.cs" />
|
||||
|
||||
@@ -77,3 +77,24 @@ Abbreviations should match whole word only, even if the word is the entire conte
|
||||
.
|
||||
<p>1.1A</p>
|
||||
````````````````````````````````
|
||||
|
||||
Abbreviations should match whole word only, even if there is another glossary term:
|
||||
|
||||
```````````````````````````````` example
|
||||
*[SCO]: First
|
||||
*[SCOM]: Second
|
||||
|
||||
SCOM
|
||||
.
|
||||
<p><abbr title="Second">SCOM</abbr></p>
|
||||
````````````````````````````````
|
||||
|
||||
Abbreviations should only match when surrounded by whitespace:
|
||||
|
||||
```````````````````````````````` example
|
||||
*[PR]: Pull Request
|
||||
|
||||
PRAA
|
||||
.
|
||||
<p>PRAA</p>
|
||||
````````````````````````````````
|
||||
@@ -12,7 +12,7 @@ Allows to automatically creates an identifier for a heading:
|
||||
<h1 id="this-is-a-heading">This is a heading</h1>
|
||||
````````````````````````````````
|
||||
|
||||
Only punctuation `-`, `_` and `.` is kept, all over non letter characters are discarded.
|
||||
Only punctuation `-`, `_` and `.` is kept, all other non letter characters are discarded.
|
||||
Consecutive same character `-`, `_` or `.` are rendered into a single one
|
||||
Characters `-`, `_` and `.` at the end of the string are also discarded.
|
||||
|
||||
@@ -96,3 +96,14 @@ The text of the link can be changed:
|
||||
<p><a href="#this-is-a-heading">With a new text</a></p>
|
||||
<h1 id="this-is-a-heading">This is a heading</h1>
|
||||
````````````````````````````````
|
||||
|
||||
An autoidentifier should not conflict with an existing link:
|
||||
|
||||
```````````````````````````````` example
|
||||
![scenario image][scenario]
|
||||
## Scenario
|
||||
[scenario]: ./scenario.png
|
||||
.
|
||||
<p><img src="./scenario.png" alt="scenario image" /></p>
|
||||
<h2 id="scenario">Scenario</h2>
|
||||
````````````````````````````````
|
||||
|
||||
@@ -19,7 +19,7 @@ And a plain www.google.com
|
||||
.
|
||||
<p>This is a <a href="http://www.google.com">http://www.google.com</a> URL and <a href="https://www.google.com">https://www.google.com</a>
|
||||
This is a <a href="ftp://test.com">ftp://test.com</a>
|
||||
And a <a href="mailto:email@toto.com">mailto:email@toto.com</a>
|
||||
And a <a href="mailto:email@toto.com">email@toto.com</a>
|
||||
And a plain <a href="http://www.google.com">www.google.com</a></p>
|
||||
````````````````````````````````
|
||||
|
||||
@@ -76,3 +76,67 @@ Check **http://www.a.com** or __http://www.b.com__
|
||||
.
|
||||
<p>Check <strong><a href="http://www.a.com">http://www.a.com</a></strong> or <strong><a href="http://www.b.com">http://www.b.com</a></strong></p>
|
||||
````````````````````````````````
|
||||
|
||||
### GFM Support
|
||||
|
||||
Extract from [GFM Autolinks extensions specs](https://github.github.com/gfm/#autolinks-extension-)
|
||||
|
||||
```````````````````````````````` example
|
||||
www.commonmark.org
|
||||
.
|
||||
<p><a href="http://www.commonmark.org">www.commonmark.org</a></p>
|
||||
````````````````````````````````
|
||||
|
||||
```````````````````````````````` example
|
||||
Visit www.commonmark.org/help for more information.
|
||||
.
|
||||
<p>Visit <a href="http://www.commonmark.org/help">www.commonmark.org/help</a> for more information.</p>
|
||||
````````````````````````````````
|
||||
|
||||
```````````````````````````````` example
|
||||
Visit www.commonmark.org.
|
||||
|
||||
Visit www.commonmark.org/a.b.
|
||||
.
|
||||
<p>Visit <a href="http://www.commonmark.org">www.commonmark.org</a>.</p>
|
||||
<p>Visit <a href="http://www.commonmark.org/a.b">www.commonmark.org/a.b</a>.</p>
|
||||
````````````````````````````````
|
||||
|
||||
|
||||
```````````````````````````````` example
|
||||
www.google.com/search?q=Markup+(business)
|
||||
|
||||
(www.google.com/search?q=Markup+(business))
|
||||
.
|
||||
<p><a href="http://www.google.com/search?q=Markup+(business)">www.google.com/search?q=Markup+(business)</a></p>
|
||||
<p>(<a href="http://www.google.com/search?q=Markup+(business)">www.google.com/search?q=Markup+(business)</a>)</p>
|
||||
````````````````````````````````
|
||||
|
||||
|
||||
```````````````````````````````` example
|
||||
www.google.com/search?q=commonmark&hl=en
|
||||
|
||||
www.google.com/search?q=commonmark&hl;
|
||||
.
|
||||
<p><a href="http://www.google.com/search?q=commonmark&hl=en">www.google.com/search?q=commonmark&hl=en</a></p>
|
||||
<p><a href="http://www.google.com/search?q=commonmark">www.google.com/search?q=commonmark</a>&hl;</p>
|
||||
````````````````````````````````
|
||||
|
||||
|
||||
```````````````````````````````` example
|
||||
www.commonmark.org/he<lp
|
||||
.
|
||||
<p><a href="http://www.commonmark.org/he">www.commonmark.org/he</a><lp</p>
|
||||
````````````````````````````````
|
||||
|
||||
```````````````````````````````` example
|
||||
http://commonmark.org
|
||||
|
||||
(Visit https://encrypted.google.com/search?q=Markup+(business))
|
||||
|
||||
Anonymous FTP is available at ftp://foo.bar.baz.
|
||||
.
|
||||
<p><a href="http://commonmark.org">http://commonmark.org</a></p>
|
||||
<p>(Visit <a href="https://encrypted.google.com/search?q=Markup+(business)">https://encrypted.google.com/search?q=Markup+(business)</a>)</p>
|
||||
<p>Anonymous FTP is available at <a href="ftp://foo.bar.baz">ftp://foo.bar.baz</a>.</p>
|
||||
````````````````````````````````
|
||||
|
||||
@@ -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
|
||||
@@ -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>
|
||||
````````````````````````````````
|
||||
````````````````````````````````
|
||||
|
||||
@@ -12,8 +12,14 @@ Allows to embed audio/video links to popular website:
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
.
|
||||
<p><iframe src="https://www.youtube.com/embed/mswPy5bt3TQ" width="500" height="281" frameborder="0" allowfullscreen></iframe></p>
|
||||
<p><iframe src="https://player.vimeo.com/video/8607834" width="500" height="281" frameborder="0" allowfullscreen></iframe></p>
|
||||
<p><video width="500" height="281" controls><source type="video/mp4" src="https://sample.com/video.mp4"></source></video></p>
|
||||
<p><iframe src="https://music.yandex.ru/iframe/#track/4402274/411845/" width="500" height="281" frameborder="0"></iframe></p>
|
||||
<p><iframe src="https://ok.ru/videoembed/26870090463" width="500" height="281" frameborder="0" allowfullscreen></iframe></p>
|
||||
````````````````````````````````
|
||||
@@ -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
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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,9 +36,10 @@ 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/791b1c121f16d3d7e80837c6f52917e57bbb2f61/spec.txt", string.Empty), // 0.27
|
||||
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"),
|
||||
new KeyValuePair<string, string>(Host.ResolvePath("FootnotesSpecs.md"), "footnotes|advanced"),
|
||||
new KeyValuePair<string, string>(Host.ResolvePath("GenericAttributesSpecs.md"), "attributes|advanced"),
|
||||
@@ -147,7 +148,7 @@ private class Spec
|
||||
|
||||
public string Name
|
||||
{
|
||||
get { return string.Format("Example{0:000}", Example); }
|
||||
get { return string.Format("{0}_Example{1:000}", SecHeadingCompact, Example); }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,13 +13,13 @@ namespace Markdig.Tests
|
||||
[Test]
|
||||
public void StrongNormal()
|
||||
{
|
||||
TestParser.TestSpec("***Strong emphasis*** normal", "<p><strong><em>Strong emphasis</em></strong> normal</p>", "");
|
||||
TestParser.TestSpec("***Strong emphasis*** normal", "<p><em><strong>Strong emphasis</strong></em> normal</p>", "");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void NormalStrongNormal()
|
||||
{
|
||||
TestParser.TestSpec("normal ***Strong emphasis*** normal", "<p>normal <strong><em>Strong emphasis</em></strong> normal</p>", "");
|
||||
TestParser.TestSpec("normal ***Strong emphasis*** normal", "<p>normal <em><strong>Strong emphasis</strong></em> normal</p>", "");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -42,3 +42,79 @@ 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>
|
||||
````````````````````````````````
|
||||
|
||||
If the end front matter marker (`...` or `---`) is not present, it will render the `---` has a `<hr>`:
|
||||
|
||||
```````````````````````````````` example
|
||||
---
|
||||
this: is a frontmatter
|
||||
This is a text
|
||||
.
|
||||
<hr />
|
||||
<p>this: is a frontmatter
|
||||
This is a text</p>
|
||||
````````````````````````````````
|
||||
|
||||
It expects exactly three dots `...`:
|
||||
|
||||
```````````````````````````````` example
|
||||
---
|
||||
this: is a frontmatter
|
||||
....
|
||||
This is a text
|
||||
.
|
||||
<hr />
|
||||
<p>this: is a frontmatter
|
||||
....
|
||||
This is a text</p>
|
||||
````````````````````````````````
|
||||
|
||||
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>
|
||||
````````````````````````````````
|
||||
|
||||
|
||||
|
||||
|
||||
25
src/Markdig.Tests/TestConfigureNewLine.cs
Normal file
25
src/Markdig.Tests/TestConfigureNewLine.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace Markdig.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
public class TestConfigureNewLine
|
||||
{
|
||||
[Test]
|
||||
[TestCase(/* newLineForWriting: */ "\n", /* markdownText: */ "*1*\n*2*\n", /* expected: */ "<p><em>1</em>\n<em>2</em></p>\n")]
|
||||
[TestCase(/* newLineForWriting: */ "\n", /* markdownText: */ "*1*\r\n*2*\r\n", /* expected: */ "<p><em>1</em>\n<em>2</em></p>\n")]
|
||||
[TestCase(/* newLineForWriting: */ "\r\n", /* markdownText: */ "*1*\n*2*\n", /* expected: */ "<p><em>1</em>\r\n<em>2</em></p>\r\n")]
|
||||
[TestCase(/* newLineForWriting: */ "\r\n", /* markdownText: */ "*1*\r\n*2*\r\n", /* expected: */ "<p><em>1</em>\r\n<em>2</em></p>\r\n")]
|
||||
[TestCase(/* newLineForWriting: */ "!!!" , /* markdownText: */ "*1*\n*2*\n", /* expected: */ "<p><em>1</em>!!!<em>2</em></p>!!!")]
|
||||
[TestCase(/* newLineForWriting: */ "!!!" , /* markdownText: */ "*1*\r\n*2*\r\n", /* expected: */ "<p><em>1</em>!!!<em>2</em></p>!!!")]
|
||||
public void TestHtmlOutputWhenConfiguringNewLine(string newLineForWriting, string markdownText, string expected)
|
||||
{
|
||||
var pipeline = new MarkdownPipelineBuilder()
|
||||
.ConfigureNewLine(newLineForWriting)
|
||||
.Build();
|
||||
|
||||
var actual = Markdown.ToHtml(markdownText, pipeline);
|
||||
Assert.AreEqual(expected, actual);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -39,7 +39,7 @@ namespace Markdig.Tests
|
||||
{
|
||||
var text = new StringSlice(uri);
|
||||
string link;
|
||||
Assert.True(LinkHelper.TryParseUrl(ref text, out link));
|
||||
Assert.True(LinkHelper.TryParseUrl(ref text, out link, true));
|
||||
Assert.AreEqual("http://google.com", link);
|
||||
Assert.AreEqual('.', text.CurrentChar);
|
||||
}
|
||||
|
||||
44
src/Markdig.Tests/TestLinkRewriter.cs
Normal file
44
src/Markdig.Tests/TestLinkRewriter.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using Markdig.Parsers;
|
||||
using Markdig.Renderers;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace Markdig.Tests
|
||||
{
|
||||
public class TestLinkRewriter
|
||||
{
|
||||
[Test]
|
||||
public void ReplacesRelativeLinks()
|
||||
{
|
||||
TestSpec(s => "abc" + s, "Link: [hello](/relative.jpg)", "abc/relative.jpg");
|
||||
TestSpec(s => s + "xyz", "Link: [hello](relative.jpg)", "relative.jpgxyz");
|
||||
TestSpec(null, "Link: [hello](relative.jpg)", "relative.jpg");
|
||||
TestSpec(null, "Link: [hello](/relative.jpg)", "/relative.jpg");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ReplacesRelativeImageSources()
|
||||
{
|
||||
TestSpec(s => "abc" + s, "Image: ", "abc/image.jpg");
|
||||
TestSpec(s => "abc" + s, "Image: ", "abcimage.jpg");
|
||||
TestSpec(null, "Image: ", "/image.jpg");
|
||||
}
|
||||
|
||||
public static void TestSpec(Func<string,string> linkRewriter, string markdown, string expectedLink)
|
||||
{
|
||||
var pipeline = new MarkdownPipelineBuilder().Build();
|
||||
|
||||
var writer = new StringWriter();
|
||||
var renderer = new HtmlRenderer(writer);
|
||||
renderer.LinkRewriter = linkRewriter;
|
||||
pipeline.Setup(renderer);
|
||||
|
||||
var document = MarkdownParser.Parse(markdown, pipeline);
|
||||
renderer.Render(document);
|
||||
writer.Flush();
|
||||
|
||||
Assert.That(writer.ToString(), Contains.Substring("=\"" + expectedLink + "\""));
|
||||
}
|
||||
}
|
||||
}
|
||||
495
src/Markdig.Tests/TestNormalize.cs
Normal file
495
src/Markdig.Tests/TestNormalize.cs
Normal file
@@ -0,0 +1,495 @@
|
||||
// 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 ");
|
||||
|
||||
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 ä 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);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TaskLists()
|
||||
{
|
||||
AssertNormalizeNoTrim("- [X] This is done");
|
||||
AssertNormalizeNoTrim("- [x] This is done",
|
||||
"- [X] This is done");
|
||||
AssertNormalizeNoTrim("- [ ] This is not done");
|
||||
|
||||
// ignore
|
||||
AssertNormalizeNoTrim("[x] This is not a task list");
|
||||
AssertNormalizeNoTrim("[ ] This is not a task list");
|
||||
}
|
||||
|
||||
|
||||
|
||||
[Test]
|
||||
public void JiraLinks()
|
||||
{
|
||||
AssertNormalizeNoTrim("FOO-1234");
|
||||
AssertNormalizeNoTrim("AB-1");
|
||||
|
||||
AssertNormalizeNoTrim("**Hello World AB-1**");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AutoLinks()
|
||||
{
|
||||
AssertNormalizeNoTrim("Hello from http://example.com/foo", "Hello from [http://example.com/foo](http://example.com/foo)", new NormalizeOptions() { ExpandAutoLinks = true, });
|
||||
AssertNormalizeNoTrim("Hello from www.example.com/foo", "Hello from [www.example.com/foo](http://www.example.com/foo)", new NormalizeOptions() { ExpandAutoLinks = true, });
|
||||
AssertNormalizeNoTrim("Hello from ftp://example.com", "Hello from [ftp://example.com](ftp://example.com)", new NormalizeOptions() { ExpandAutoLinks = true, });
|
||||
AssertNormalizeNoTrim("Hello from mailto:hello@example.com", "Hello from [hello@example.com](mailto:hello@example.com)", new NormalizeOptions() { ExpandAutoLinks = true, });
|
||||
|
||||
AssertNormalizeNoTrim("Hello from http://example.com/foo", "Hello from http://example.com/foo", new NormalizeOptions() { ExpandAutoLinks = false, });
|
||||
AssertNormalizeNoTrim("Hello from www.example.com/foo", "Hello from http://www.example.com/foo", new NormalizeOptions() { ExpandAutoLinks = false, });
|
||||
AssertNormalizeNoTrim("Hello from mailto:hello@example.com", "Hello from mailto:hello@example.com", new NormalizeOptions() { ExpandAutoLinks = false, });
|
||||
}
|
||||
|
||||
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 pipeline = new MarkdownPipelineBuilder()
|
||||
.UseAutoLinks()
|
||||
.UseJiraLinks(new Extensions.JiraLinks.JiraLinkOptions("https://jira.example.com"))
|
||||
.UseTaskLists()
|
||||
.Build();
|
||||
|
||||
var result = Markdown.Normalize(input, options, pipeline: pipeline);
|
||||
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');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,43 @@
|
||||
// Copyright (c) Alexandre Mutel. All rights reserved.
|
||||
// This file is licensed under the BSD-Clause 2 license.
|
||||
// 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.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®*®";
|
||||
TestSpec(markdownText, "<p><em>Unlimited-Fun®</em>®</p>");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestThematicInsideCodeBlockInsideList()
|
||||
{
|
||||
var input = @"1. In the :
|
||||
|
||||
```
|
||||
Id DisplayName Description
|
||||
-- ----------- -----------
|
||||
62375ab9-6b52-47ed-826b-58e47e0e304b Group.Unified ...
|
||||
```";
|
||||
TestSpec(input, @"<ol>
|
||||
<li><p>In the :</p>
|
||||
<pre><code>Id DisplayName Description
|
||||
-- ----------- -----------
|
||||
62375ab9-6b52-47ed-826b-58e47e0e304b Group.Unified ...
|
||||
</code></pre></li>
|
||||
</ol>");
|
||||
}
|
||||
|
||||
public static void TestSpec(string inputText, string expectedOutputText, string extensions = null)
|
||||
{
|
||||
foreach (var pipeline in GetPipeline(extensions))
|
||||
|
||||
17
src/Markdig.Tests/TestPlainText.cs
Normal file
17
src/Markdig.Tests/TestPlainText.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -12,6 +12,26 @@ namespace Markdig.Tests
|
||||
[TestFixture]
|
||||
public class TestPlayParser
|
||||
{
|
||||
[Test]
|
||||
public void TestLink()
|
||||
{
|
||||
var doc = Markdown.Parse("There is a ");
|
||||
var link = doc.Descendants<ParagraphBlock>().SelectMany(x => x.Inline.Descendants<LinkInline>()).FirstOrDefault(l => l.IsImage);
|
||||
Assert.AreEqual("/yoyo", link?.Url);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestListBug2()
|
||||
{
|
||||
TestParser.TestSpec("10.\t*test* – test\n\n11.\t__test__ test\n\n", @"<ol start=""10"">
|
||||
<li><p><em>test</em> – test</p>
|
||||
</li>
|
||||
<li><p><strong>test</strong> test</p>
|
||||
</li>
|
||||
</ol>
|
||||
");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSimple()
|
||||
{
|
||||
|
||||
48
src/Markdig.Tests/TestRelativeUrlReplacement.cs
Normal file
48
src/Markdig.Tests/TestRelativeUrlReplacement.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using Markdig.Parsers;
|
||||
using Markdig.Renderers;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace Markdig.Tests
|
||||
{
|
||||
public class TestRelativeUrlReplacement
|
||||
{
|
||||
[Test]
|
||||
public void ReplacesRelativeLinks()
|
||||
{
|
||||
TestSpec("https://example.com", "Link: [hello](/relative.jpg)", "https://example.com/relative.jpg");
|
||||
TestSpec("https://example.com", "Link: [hello](relative.jpg)", "https://example.com/relative.jpg");
|
||||
TestSpec("https://example.com/", "Link: [hello](/relative.jpg?a=b)", "https://example.com/relative.jpg?a=b");
|
||||
TestSpec("https://example.com/", "Link: [hello](relative.jpg#x)", "https://example.com/relative.jpg#x");
|
||||
TestSpec(null, "Link: [hello](relative.jpg)", "relative.jpg");
|
||||
TestSpec(null, "Link: [hello](/relative.jpg)", "/relative.jpg");
|
||||
TestSpec("https://example.com", "Link: [hello](/relative.jpg)", "https://example.com/relative.jpg");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ReplacesRelativeImageSources()
|
||||
{
|
||||
TestSpec("https://example.com", "Image: ", "https://example.com/image.jpg");
|
||||
TestSpec("https://example.com", "Image: ", "https://example.com/image.jpg");
|
||||
TestSpec(null, "Image: ", "/image.jpg");
|
||||
}
|
||||
|
||||
public static void TestSpec(string baseUrl, string markdown, string expectedLink)
|
||||
{
|
||||
var pipeline = new MarkdownPipelineBuilder().Build();
|
||||
|
||||
var writer = new StringWriter();
|
||||
var renderer = new HtmlRenderer(writer);
|
||||
if (baseUrl != null)
|
||||
renderer.BaseUrl = new Uri(baseUrl);
|
||||
pipeline.Setup(renderer);
|
||||
|
||||
var document = MarkdownParser.Parse(markdown, pipeline);
|
||||
renderer.Render(document);
|
||||
writer.Flush();
|
||||
|
||||
Assert.That(writer.ToString(), Contains.Substring("=\"" + expectedLink + "\""));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -19,6 +19,6 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"NUnit": "3.2.0",
|
||||
"NUnit3TestAdapter": "3.2.0"
|
||||
"NUnit3TestAdapter": "3.9.0"
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
@@ -6,6 +6,13 @@ namespace Markdig.WebApp
|
||||
{
|
||||
public class ApiController : Controller
|
||||
{
|
||||
[HttpGet()]
|
||||
[Route("")]
|
||||
public string Empty()
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
// GET api/to_html?text=xxx&extensions=advanced
|
||||
[Route("api/to_html")]
|
||||
[HttpGet()]
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"ProviderId": "Microsoft.ApplicationInsights.ConnectedService.ConnectedServiceProvider",
|
||||
"Version": "8.9.809.2",
|
||||
"GettingStartedDocument": {
|
||||
"Uri": "https://go.microsoft.com/fwlink/?LinkID=798432"
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,14 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp1.0</TargetFramework>
|
||||
<TargetFramework>netcoreapp2.0</TargetFramework>
|
||||
<PreserveCompilationContext>true</PreserveCompilationContext>
|
||||
<AssemblyName>Markdig.WebApp</AssemblyName>
|
||||
<OutputType>Exe</OutputType>
|
||||
<PackageId>Markdig.WebApp</PackageId>
|
||||
<RuntimeFrameworkVersion>1.0.4</RuntimeFrameworkVersion>
|
||||
<PackageTargetFallback>$(PackageTargetFallback);dotnet5.6;dnxcore50;portable-net45+win8</PackageTargetFallback>
|
||||
<RuntimeFrameworkVersion>2.0.0</RuntimeFrameworkVersion>
|
||||
<ApplicationInsightsResourceId>/subscriptions/b6745039-70e7-4641-994b-5457cb220e2a/resourcegroups/Default-ApplicationInsights-EastUS/providers/microsoft.insights/components/Markdig.WebApp</ApplicationInsightsResourceId>
|
||||
<ApplicationInsightsAnnotationResourceId>/subscriptions/b6745039-70e7-4641-994b-5457cb220e2a/resourcegroups/Default-ApplicationInsights-EastUS/providers/microsoft.insights/components/Markdig.WebApp</ApplicationInsightsAnnotationResourceId>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -21,16 +22,20 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" Version="1.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="1.0.3" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Server.IISIntegration" Version="1.0.2" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="1.0.3" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="1.0.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="1.0.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="1.0.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="1.0.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="1.0.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="1.0.2" />
|
||||
<PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" Version="2.1.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Server.IISIntegration" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="2.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<WCFMetadata Include="Connected Services" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
@@ -13,6 +13,7 @@ namespace Markdig.WebApp
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
var host = new WebHostBuilder()
|
||||
.UseApplicationInsights()
|
||||
.UseKestrel()
|
||||
.UseContentRoot(Directory.GetCurrentDirectory())
|
||||
.UseIISIntegration()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
@@ -46,10 +46,6 @@ namespace Markdig.WebApp
|
||||
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
|
||||
loggerFactory.AddDebug();
|
||||
|
||||
app.UseApplicationInsightsRequestTelemetry();
|
||||
|
||||
app.UseApplicationInsightsExceptionTelemetry();
|
||||
|
||||
app.UseMvc();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
{
|
||||
"Logging": {
|
||||
"IncludeScopes": false,
|
||||
"LogLevel": {
|
||||
@@ -6,5 +6,8 @@
|
||||
"System": "Information",
|
||||
"Microsoft": "Information"
|
||||
}
|
||||
},
|
||||
"ApplicationInsights": {
|
||||
"InstrumentationKey": "5d12f113-76b2-41fe-a35a-db454b104bf9"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,7 @@ namespace Markdig.Extensions.Abbreviations
|
||||
/// </summary>
|
||||
public AbbreviationParser()
|
||||
{
|
||||
OpeningCharacters = new[] {'*'};
|
||||
OpeningCharacters = new[] { '*' };
|
||||
}
|
||||
|
||||
public override BlockState TryOpen(BlockProcessor processor)
|
||||
@@ -90,7 +90,7 @@ namespace Markdig.Extensions.Abbreviations
|
||||
|
||||
inlineProcessor.LiteralInlineParser.PostMatch += (InlineProcessor processor, ref StringSlice slice) =>
|
||||
{
|
||||
var literal = (LiteralInline) processor.Inline;
|
||||
var literal = (LiteralInline)processor.Inline;
|
||||
var originalLiteral = literal;
|
||||
|
||||
ContainerInline container = null;
|
||||
@@ -145,11 +145,13 @@ namespace Markdig.Extensions.Abbreviations
|
||||
container.AppendChild(literal);
|
||||
}
|
||||
|
||||
literal.Span.End = abbrInline.Span.Start - 1;
|
||||
// Truncate it before the abbreviation
|
||||
literal.Content.End = i - 1;
|
||||
}
|
||||
|
||||
literal.Span.End = abbrInline.Span.Start - 1;
|
||||
// Truncate it before the abbreviation
|
||||
literal.Content.End = i - 1;
|
||||
|
||||
|
||||
// Appned the abbreviation
|
||||
container.AppendChild(abbrInline);
|
||||
|
||||
@@ -193,25 +195,41 @@ namespace Markdig.Extensions.Abbreviations
|
||||
while (index >= content.Start)
|
||||
{
|
||||
var c = content.PeekCharAbsolute(index);
|
||||
if (!(c == '\0' || c.IsAsciiPunctuation() || c.IsWhitespace()))
|
||||
if (!(c == '\0' || c.IsWhitespace() || c.IsAsciiPunctuation()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!c.IsAsciiPunctuation())
|
||||
|
||||
if (c.IsAlphaNumeric())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!c.IsAsciiPunctuation() || c.IsWhitespace())
|
||||
{
|
||||
break;
|
||||
}
|
||||
index--;
|
||||
}
|
||||
|
||||
// This will check if the next char at the end of the StringSlice is whitespace, punctuation or \0.
|
||||
var contentNew = content;
|
||||
contentNew.End = content.End + 1;
|
||||
index = matchIndex + match.Length;
|
||||
while (index <= content.End)
|
||||
while (index <= contentNew.End)
|
||||
{
|
||||
var c = content.PeekCharAbsolute(index);
|
||||
if (!(c == '\0' || c.IsAsciiPunctuation() || c.IsWhitespace()))
|
||||
var c = contentNew.PeekCharAbsolute(index);
|
||||
if (!(c == '\0' || c.IsWhitespace() || c.IsAsciiPunctuation()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!c.IsAsciiPunctuation())
|
||||
|
||||
if (c.IsAlphaNumeric())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (c.IsWhitespace())
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1,165 +1,199 @@
|
||||
// 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.Collections.Generic;
|
||||
using System.IO;
|
||||
using Markdig.Helpers;
|
||||
using Markdig.Parsers;
|
||||
using Markdig.Renderers;
|
||||
using Markdig.Renderers.Html;
|
||||
using Markdig.Syntax;
|
||||
using Markdig.Syntax.Inlines;
|
||||
|
||||
namespace Markdig.Extensions.AutoIdentifiers
|
||||
{
|
||||
/// <summary>
|
||||
/// The auto-identifier extension
|
||||
/// </summary>
|
||||
/// <seealso cref="Markdig.IMarkdownExtension" />
|
||||
public class AutoIdentifierExtension : IMarkdownExtension
|
||||
{
|
||||
private const string AutoIdentifierKey = "AutoIdentifier";
|
||||
private readonly HtmlRenderer stripRenderer;
|
||||
private readonly StringWriter headingWriter;
|
||||
private readonly AutoIdentifierOptions options;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AutoIdentifierExtension"/> class.
|
||||
/// </summary>
|
||||
/// <param name="options">The options.</param>
|
||||
public AutoIdentifierExtension(AutoIdentifierOptions options)
|
||||
{
|
||||
this.options = options;
|
||||
headingWriter = new StringWriter();
|
||||
// Use internally a HtmlRenderer to strip links from a heading
|
||||
stripRenderer = new HtmlRenderer(headingWriter)
|
||||
{
|
||||
// Set to false both to avoid having any HTML tags in the output
|
||||
EnableHtmlForInline = false,
|
||||
EnableHtmlEscape = false
|
||||
};
|
||||
}
|
||||
|
||||
public void Setup(MarkdownPipelineBuilder pipeline)
|
||||
{
|
||||
var headingBlockParser = pipeline.BlockParsers.Find<HeadingBlockParser>();
|
||||
if (headingBlockParser != null)
|
||||
{
|
||||
// Install a hook on the HeadingBlockParser when a HeadingBlock is actually processed
|
||||
headingBlockParser.Closed -= HeadingBlockParser_Closed;
|
||||
headingBlockParser.Closed += HeadingBlockParser_Closed;
|
||||
}
|
||||
var paragraphBlockParser = pipeline.BlockParsers.FindExact<ParagraphBlockParser>();
|
||||
if (paragraphBlockParser != null)
|
||||
{
|
||||
// Install a hook on the ParagraphBlockParser when a HeadingBlock is actually processed as a Setex heading
|
||||
paragraphBlockParser.Closed -= HeadingBlockParser_Closed;
|
||||
paragraphBlockParser.Closed += HeadingBlockParser_Closed;
|
||||
}
|
||||
}
|
||||
|
||||
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process on a new <see cref="HeadingBlock"/>
|
||||
/// </summary>
|
||||
/// <param name="processor">The processor.</param>
|
||||
/// <param name="block">The heading block.</param>
|
||||
private void HeadingBlockParser_Closed(BlockProcessor processor, Block block)
|
||||
{
|
||||
// We may have a ParagraphBlock here as we have a hook on the ParagraphBlockParser
|
||||
var headingBlock = block as HeadingBlock;
|
||||
if (headingBlock == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// If the AutoLink options is set, we register a LinkReferenceDefinition at the document level
|
||||
if ((options & AutoIdentifierOptions.AutoLink) != 0)
|
||||
{
|
||||
var headingLine = headingBlock.Lines.Lines[0];
|
||||
|
||||
var text = headingLine.ToString();
|
||||
|
||||
var linkRef = new HeadingLinkReferenceDefinition()
|
||||
{
|
||||
Heading = headingBlock,
|
||||
CreateLinkInline = CreateLinkInlineForHeading
|
||||
};
|
||||
processor.Document.SetLinkReferenceDefinition(text, linkRef);
|
||||
}
|
||||
|
||||
// Then we register after inline have been processed to actually generate the proper #id
|
||||
headingBlock.ProcessInlinesEnd += HeadingBlock_ProcessInlinesEnd;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Callback when there is a reference to found to a heading.
|
||||
/// Note that reference are only working if they are declared after.
|
||||
/// </summary>
|
||||
private Inline CreateLinkInlineForHeading(InlineProcessor inlineState, LinkReferenceDefinition linkRef, Inline child)
|
||||
{
|
||||
var headingRef = (HeadingLinkReferenceDefinition) linkRef;
|
||||
return new LinkInline()
|
||||
{
|
||||
// Use GetDynamicUrl to allow late binding of the Url (as a link may occur before the heading is declared and
|
||||
// the inlines of the heading are actually processed by HeadingBlock_ProcessInlinesEnd)
|
||||
GetDynamicUrl = () => HtmlHelper.Unescape("#" + headingRef.Heading.GetAttributes().Id),
|
||||
Title = HtmlHelper.Unescape(linkRef.Title),
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process the inlines of the heading to create a unique identifier
|
||||
/// </summary>
|
||||
/// <param name="processor">The processor.</param>
|
||||
/// <param name="inline">The inline.</param>
|
||||
private void HeadingBlock_ProcessInlinesEnd(InlineProcessor processor, Inline inline)
|
||||
{
|
||||
var identifiers = processor.Document.GetData(AutoIdentifierKey) as HashSet<string>;
|
||||
if (identifiers == null)
|
||||
{
|
||||
identifiers = new HashSet<string>();
|
||||
processor.Document.SetData(AutoIdentifierKey, identifiers);
|
||||
}
|
||||
|
||||
var headingBlock = (HeadingBlock) processor.Block;
|
||||
if (headingBlock.Inline == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// If id is already set, don't try to modify it
|
||||
var attributes = processor.Block.GetAttributes();
|
||||
if (attributes.Id != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Use a HtmlRenderer with
|
||||
stripRenderer.Render(headingBlock.Inline);
|
||||
var headingText = headingWriter.ToString();
|
||||
headingWriter.GetStringBuilder().Length = 0;
|
||||
headingText = LinkHelper.Urilize(headingText, (options & AutoIdentifierOptions.AllowOnlyAscii) != 0);
|
||||
|
||||
var baseHeadingId = string.IsNullOrEmpty(headingText) ? "section" : headingText;
|
||||
int index = 0;
|
||||
var headingId = baseHeadingId;
|
||||
var headingBuffer = StringBuilderCache.Local();
|
||||
while (!identifiers.Add(headingId))
|
||||
{
|
||||
index++;
|
||||
headingBuffer.Append(baseHeadingId);
|
||||
headingBuffer.Append('-');
|
||||
headingBuffer.Append(index);
|
||||
headingId = headingBuffer.ToString();
|
||||
headingBuffer.Length = 0;
|
||||
}
|
||||
|
||||
attributes.Id = headingId;
|
||||
}
|
||||
}
|
||||
// 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 Markdig.Helpers;
|
||||
using Markdig.Parsers;
|
||||
using Markdig.Renderers;
|
||||
using Markdig.Renderers.Html;
|
||||
using Markdig.Syntax;
|
||||
using Markdig.Syntax.Inlines;
|
||||
|
||||
namespace Markdig.Extensions.AutoIdentifiers
|
||||
{
|
||||
/// <summary>
|
||||
/// The auto-identifier extension
|
||||
/// </summary>
|
||||
/// <seealso cref="Markdig.IMarkdownExtension" />
|
||||
public class AutoIdentifierExtension : IMarkdownExtension
|
||||
{
|
||||
private const string AutoIdentifierKey = "AutoIdentifier";
|
||||
private readonly AutoIdentifierOptions options;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AutoIdentifierExtension"/> class.
|
||||
/// </summary>
|
||||
/// <param name="options">The options.</param>
|
||||
public AutoIdentifierExtension(AutoIdentifierOptions options)
|
||||
{
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
public void Setup(MarkdownPipelineBuilder pipeline)
|
||||
{
|
||||
var headingBlockParser = pipeline.BlockParsers.Find<HeadingBlockParser>();
|
||||
if (headingBlockParser != null)
|
||||
{
|
||||
// Install a hook on the HeadingBlockParser when a HeadingBlock is actually processed
|
||||
headingBlockParser.Closed -= HeadingBlockParser_Closed;
|
||||
headingBlockParser.Closed += HeadingBlockParser_Closed;
|
||||
}
|
||||
var paragraphBlockParser = pipeline.BlockParsers.FindExact<ParagraphBlockParser>();
|
||||
if (paragraphBlockParser != null)
|
||||
{
|
||||
// Install a hook on the ParagraphBlockParser when a HeadingBlock is actually processed as a Setex heading
|
||||
paragraphBlockParser.Closed -= HeadingBlockParser_Closed;
|
||||
paragraphBlockParser.Closed += HeadingBlockParser_Closed;
|
||||
}
|
||||
}
|
||||
|
||||
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process on a new <see cref="HeadingBlock"/>
|
||||
/// </summary>
|
||||
/// <param name="processor">The processor.</param>
|
||||
/// <param name="block">The heading block.</param>
|
||||
private void HeadingBlockParser_Closed(BlockProcessor processor, Block block)
|
||||
{
|
||||
// We may have a ParagraphBlock here as we have a hook on the ParagraphBlockParser
|
||||
var headingBlock = block as HeadingBlock;
|
||||
if (headingBlock == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// If the AutoLink options is set, we register a LinkReferenceDefinition at the document level
|
||||
if ((options & AutoIdentifierOptions.AutoLink) != 0)
|
||||
{
|
||||
var headingLine = headingBlock.Lines.Lines[0];
|
||||
|
||||
var text = headingLine.ToString();
|
||||
|
||||
var linkRef = new HeadingLinkReferenceDefinition()
|
||||
{
|
||||
Heading = headingBlock,
|
||||
CreateLinkInline = CreateLinkInlineForHeading
|
||||
};
|
||||
|
||||
var doc = processor.Document;
|
||||
var dictionary = doc.GetData(this) as Dictionary<string, HeadingLinkReferenceDefinition>;
|
||||
if (dictionary == null)
|
||||
{
|
||||
dictionary = new Dictionary<string, HeadingLinkReferenceDefinition>();
|
||||
doc.SetData(this, dictionary);
|
||||
doc.ProcessInlinesBegin += DocumentOnProcessInlinesBegin;
|
||||
}
|
||||
dictionary[text] = linkRef;
|
||||
}
|
||||
|
||||
// Then we register after inline have been processed to actually generate the proper #id
|
||||
headingBlock.ProcessInlinesEnd += HeadingBlock_ProcessInlinesEnd;
|
||||
}
|
||||
|
||||
private void DocumentOnProcessInlinesBegin(InlineProcessor processor, Inline inline)
|
||||
{
|
||||
var doc = processor.Document;
|
||||
doc.ProcessInlinesBegin -= DocumentOnProcessInlinesBegin;
|
||||
var dictionary = (Dictionary<string, HeadingLinkReferenceDefinition>)doc.GetData(this);
|
||||
foreach (var keyPair in dictionary)
|
||||
{
|
||||
// Here we make sure that auto-identifiers will not override an existing link definition
|
||||
// defined in the document
|
||||
// If it is the case, we skip the auto identifier for the Heading
|
||||
if (!doc.TryGetLinkReferenceDefinition(keyPair.Key, out var linkDef))
|
||||
{
|
||||
doc.SetLinkReferenceDefinition(keyPair.Key, keyPair.Value);
|
||||
}
|
||||
}
|
||||
// Once we are done, we don't need to keep the intermediate dictionary arround
|
||||
doc.RemoveData(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Callback when there is a reference to found to a heading.
|
||||
/// Note that reference are only working if they are declared after.
|
||||
/// </summary>
|
||||
private Inline CreateLinkInlineForHeading(InlineProcessor inlineState, LinkReferenceDefinition linkRef, Inline child)
|
||||
{
|
||||
var headingRef = (HeadingLinkReferenceDefinition) linkRef;
|
||||
return new LinkInline()
|
||||
{
|
||||
// Use GetDynamicUrl to allow late binding of the Url (as a link may occur before the heading is declared and
|
||||
// the inlines of the heading are actually processed by HeadingBlock_ProcessInlinesEnd)
|
||||
GetDynamicUrl = () => HtmlHelper.Unescape("#" + headingRef.Heading.GetAttributes().Id),
|
||||
Title = HtmlHelper.Unescape(linkRef.Title),
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process the inlines of the heading to create a unique identifier
|
||||
/// </summary>
|
||||
/// <param name="processor">The processor.</param>
|
||||
/// <param name="inline">The inline.</param>
|
||||
private void HeadingBlock_ProcessInlinesEnd(InlineProcessor processor, Inline inline)
|
||||
{
|
||||
var identifiers = processor.Document.GetData(AutoIdentifierKey) as HashSet<string>;
|
||||
if (identifiers == null)
|
||||
{
|
||||
identifiers = new HashSet<string>();
|
||||
processor.Document.SetData(AutoIdentifierKey, identifiers);
|
||||
}
|
||||
|
||||
var headingBlock = (HeadingBlock) processor.Block;
|
||||
if (headingBlock.Inline == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// If id is already set, don't try to modify it
|
||||
var attributes = processor.Block.GetAttributes();
|
||||
if (attributes.Id != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Use internally a HtmlRenderer to strip links from a heading
|
||||
var headingWriter = new StringWriter();
|
||||
var stripRenderer = new HtmlRenderer(headingWriter)
|
||||
{
|
||||
// Set to false both to avoid having any HTML tags in the output
|
||||
EnableHtmlForInline = false,
|
||||
EnableHtmlEscape = false
|
||||
};
|
||||
|
||||
stripRenderer.Render(headingBlock.Inline);
|
||||
var headingText = headingWriter.ToString();
|
||||
headingWriter.GetStringBuilder().Length = 0;
|
||||
|
||||
// Urilize the link
|
||||
headingText = (options & AutoIdentifierOptions.GitHub) != 0
|
||||
? LinkHelper.UrilizeAsGfm(headingText)
|
||||
: LinkHelper.Urilize(headingText, (options & AutoIdentifierOptions.AllowOnlyAscii) != 0);
|
||||
|
||||
// If the heading is empty, use the word "section" instead
|
||||
var baseHeadingId = string.IsNullOrEmpty(headingText) ? "section" : headingText;
|
||||
|
||||
// Add a trailing -1, -2, -3...etc. in case of collision
|
||||
int index = 0;
|
||||
var headingId = baseHeadingId;
|
||||
var headingBuffer = StringBuilderCache.Local();
|
||||
while (!identifiers.Add(headingId))
|
||||
{
|
||||
index++;
|
||||
headingBuffer.Append(baseHeadingId);
|
||||
headingBuffer.Append('-');
|
||||
headingBuffer.Append(index);
|
||||
headingId = headingBuffer.ToString();
|
||||
headingBuffer.Length = 0;
|
||||
}
|
||||
|
||||
attributes.Id = headingId;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -30,5 +30,10 @@ 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>
|
||||
/// Renders auto identifiers like GitHub.
|
||||
/// </summary>
|
||||
GitHub = 4,
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
// 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 Markdig.Renderers;
|
||||
using Markdig.Syntax.Inlines;
|
||||
using Markdig.Renderers.Normalize;
|
||||
using Markdig.Renderers.Normalize.Inlines;
|
||||
|
||||
namespace Markdig.Extensions.AutoLinks
|
||||
{
|
||||
@@ -24,6 +25,11 @@ namespace Markdig.Extensions.AutoLinks
|
||||
|
||||
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
|
||||
{
|
||||
var normalizeRenderer = renderer as NormalizeRenderer;
|
||||
if (normalizeRenderer != null && !normalizeRenderer.ObjectRenderers.Contains<NormalizeAutoLinkRenderer>())
|
||||
{
|
||||
normalizeRenderer.ObjectRenderers.InsertBefore<LinkInlineRenderer>(new NormalizeAutoLinkRenderer());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -30,11 +30,18 @@ namespace Markdig.Extensions.AutoLinks
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
private static bool IsValidPreviousCharacter(char c)
|
||||
{
|
||||
// All such recognized autolinks can only come at the beginning of a line, after whitespace, or any of the delimiting characters *, _, ~, and (.
|
||||
return c.IsWhiteSpaceOrZero() || c == '*' || c == '_' || c == '~' || c == '(';
|
||||
}
|
||||
|
||||
public override bool Match(InlineProcessor processor, ref StringSlice slice)
|
||||
{
|
||||
// Previous char must be a whitespace or a punctuation
|
||||
var previousChar = slice.PeekCharExtra(-1);
|
||||
if (!previousChar.IsAsciiPunctuation() && !previousChar.IsWhiteSpaceOrZero())
|
||||
if (!IsValidPreviousCharacter(previousChar))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -72,7 +79,7 @@ namespace Markdig.Extensions.AutoLinks
|
||||
break;
|
||||
|
||||
case 'w':
|
||||
if (!slice.MatchLowercase("ww.", 1) || previousChar == '/') // We won't match http:/www. or /www.xxx
|
||||
if (!slice.MatchLowercase("ww.", 1)) // We won't match http:/www. or /www.xxx
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -81,7 +88,7 @@ namespace Markdig.Extensions.AutoLinks
|
||||
|
||||
// Parse URL
|
||||
string link;
|
||||
if (!LinkHelper.TryParseUrl(ref slice, out link))
|
||||
if (!LinkHelper.TryParseUrl(ref slice, out link, true))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -151,7 +158,11 @@ namespace Markdig.Extensions.AutoLinks
|
||||
Column = column,
|
||||
Url = c == 'w' ? "http://" + link : link,
|
||||
IsClosed = true,
|
||||
IsAutoLink = true,
|
||||
};
|
||||
|
||||
var skipFromBeginning = c == 'm' ? 7 : 0; // For mailto: skip "mailto:" for content
|
||||
|
||||
inline.Span.End = inline.Span.Start + link.Length - 1;
|
||||
inline.UrlSpan = inline.Span;
|
||||
inline.AppendChild(new LiteralInline()
|
||||
@@ -159,7 +170,7 @@ namespace Markdig.Extensions.AutoLinks
|
||||
Span = inline.Span,
|
||||
Line = line,
|
||||
Column = column,
|
||||
Content = new StringSlice(slice.Text, startPosition, startPosition + link.Length - 1),
|
||||
Content = new StringSlice(slice.Text, startPosition + skipFromBeginning, startPosition + link.Length - 1),
|
||||
IsClosed = true
|
||||
});
|
||||
processor.Inline = inline;
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
using Markdig.Renderers;
|
||||
using Markdig.Renderers.Normalize;
|
||||
using Markdig.Syntax;
|
||||
using Markdig.Syntax.Inlines;
|
||||
|
||||
namespace Markdig.Extensions.AutoLinks
|
||||
{
|
||||
public class NormalizeAutoLinkRenderer : NormalizeObjectRenderer<LinkInline>
|
||||
{
|
||||
public override bool Accept(RendererBase renderer, MarkdownObject obj)
|
||||
{
|
||||
if (base.Accept(renderer, obj))
|
||||
{
|
||||
var normalizeRenderer = renderer as NormalizeRenderer;
|
||||
var link = obj as LinkInline;
|
||||
|
||||
return normalizeRenderer != null && link != null && !normalizeRenderer.Options.ExpandAutoLinks && link.IsAutoLink;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
protected override void Write(NormalizeRenderer renderer, LinkInline obj)
|
||||
{
|
||||
renderer.Write(obj.Url);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
public EmojiExtension(bool enableSmiley = true)
|
||||
{
|
||||
EnableSmiley = enableSmiley;
|
||||
}
|
||||
|
||||
public bool EnableSmiley { get; set; }
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// 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 Markdig.Parsers.Inlines;
|
||||
using Markdig.Renderers;
|
||||
using Markdig.Renderers.Normalize.Inlines;
|
||||
using Markdig.Renderers.Normalize;
|
||||
|
||||
namespace Markdig.Extensions.JiraLinks
|
||||
{
|
||||
@@ -30,7 +32,13 @@ namespace Markdig.Extensions.JiraLinks
|
||||
|
||||
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
|
||||
{
|
||||
// Nothing to setup, JiraLinks used a normal LinkInlineRenderer
|
||||
// No HTML renderer required, since JiraLink type derives from InlineLink (which already has an HTML renderer)
|
||||
|
||||
var normalizeRenderer = renderer as NormalizeRenderer;
|
||||
if (normalizeRenderer != null && !normalizeRenderer.ObjectRenderers.Contains<NormalizeJiraLinksRenderer>())
|
||||
{
|
||||
normalizeRenderer.ObjectRenderers.InsertBefore<LinkInlineRenderer>(new NormalizeJiraLinksRenderer());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
using Markdig.Renderers.Normalize;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Markdig.Extensions.JiraLinks
|
||||
{
|
||||
public class NormalizeJiraLinksRenderer : NormalizeObjectRenderer<JiraLink>
|
||||
{
|
||||
protected override void Write(NormalizeRenderer renderer, JiraLink obj)
|
||||
{
|
||||
renderer.Write(obj.ProjectKey);
|
||||
renderer.Write("-");
|
||||
renderer.Write(obj.Issue);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
// 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;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Markdig.Renderers;
|
||||
using Markdig.Renderers.Html;
|
||||
using Markdig.Renderers.Html.Inlines;
|
||||
@@ -47,88 +49,178 @@ namespace Markdig.Extensions.MediaLinks
|
||||
|
||||
private bool TryLinkInlineRenderer(HtmlRenderer renderer, LinkInline linkInline)
|
||||
{
|
||||
if (linkInline.IsImage && linkInline.Url != null)
|
||||
if (!linkInline.IsImage || linkInline.Url == null)
|
||||
{
|
||||
Uri uri;
|
||||
// Only process absolute Uri
|
||||
if (Uri.TryCreate(linkInline.Url, UriKind.RelativeOrAbsolute, out uri) && uri.IsAbsoluteUri)
|
||||
return false;
|
||||
}
|
||||
|
||||
Uri uri;
|
||||
// Only process absolute Uri
|
||||
if (!Uri.TryCreate(linkInline.Url, UriKind.RelativeOrAbsolute, out uri) || !uri.IsAbsoluteUri)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (TryRenderIframeFromKnownProviders(uri, renderer, linkInline))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (TryGuessAudioVideoFile(uri, renderer, linkInline))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static HtmlAttributes GetHtmlAttributes(LinkInline linkInline)
|
||||
{
|
||||
var htmlAttributes = new HtmlAttributes();
|
||||
var fromAttributes = linkInline.TryGetAttributes();
|
||||
if (fromAttributes != null)
|
||||
{
|
||||
fromAttributes.CopyTo(htmlAttributes, false, false);
|
||||
}
|
||||
|
||||
return htmlAttributes;
|
||||
}
|
||||
|
||||
private bool TryGuessAudioVideoFile(Uri uri, HtmlRenderer renderer, LinkInline linkInline)
|
||||
{
|
||||
var path = uri.GetComponents(UriComponents.Path, UriFormat.Unescaped);
|
||||
// Otherwise try to detect if we have an audio/video from the file extension
|
||||
string mimeType;
|
||||
var lastDot = path.LastIndexOf('.');
|
||||
if (lastDot >= 0 &&
|
||||
Options.ExtensionToMimeType.TryGetValue(path.Substring(lastDot), out mimeType))
|
||||
{
|
||||
var htmlAttributes = GetHtmlAttributes(linkInline);
|
||||
var isAudio = mimeType.StartsWith("audio");
|
||||
var tagType = isAudio ? "audio" : "video";
|
||||
|
||||
renderer.Write($"<{tagType}");
|
||||
htmlAttributes.AddPropertyIfNotExist("width", Options.Width);
|
||||
if (!isAudio)
|
||||
{
|
||||
var htmlAttributes = new HtmlAttributes();
|
||||
var fromAttributes = linkInline.TryGetAttributes();
|
||||
if (fromAttributes != null)
|
||||
{
|
||||
fromAttributes.CopyTo(htmlAttributes, false, false);
|
||||
}
|
||||
|
||||
// TODO: this code is not pluggable, so for now, we handle only the following web providers:
|
||||
// - youtube
|
||||
// - vimeo
|
||||
var path = uri.GetComponents(UriComponents.Path, UriFormat.Unescaped);
|
||||
|
||||
string iFrameUrl = null;
|
||||
if (uri.Host.StartsWith("www.youtube.com", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var query = SplitQuery(uri);
|
||||
if (query.Length > 0 && query[0].StartsWith("v="))
|
||||
{
|
||||
iFrameUrl = $"https://www.youtube.com/embed/{query[0].Substring(2)}";
|
||||
}
|
||||
}
|
||||
else if (uri.Host.StartsWith("vimeo.com", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var items = path.Split('/');
|
||||
if (items.Length > 0)
|
||||
{
|
||||
iFrameUrl = $"https://player.vimeo.com/video/{items[items.Length - 1]}";
|
||||
}
|
||||
}
|
||||
|
||||
if (iFrameUrl != null)
|
||||
{
|
||||
renderer.Write($"<iframe src=\"{iFrameUrl}\"");
|
||||
htmlAttributes.AddPropertyIfNotExist("width", Options.Width);
|
||||
htmlAttributes.AddPropertyIfNotExist("height", Options.Height);
|
||||
htmlAttributes.AddPropertyIfNotExist("frameborder", "0");
|
||||
htmlAttributes.AddPropertyIfNotExist("allowfullscreen", null);
|
||||
renderer.WriteAttributes(htmlAttributes);
|
||||
renderer.Write("></iframe>");
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Otherwise try to detect if we have an audio/video from the file extension
|
||||
string mimeType;
|
||||
var lastDot = path.LastIndexOf('.');
|
||||
if (lastDot >= 0 &&
|
||||
Options.ExtensionToMimeType.TryGetValue(path.Substring(lastDot), out mimeType))
|
||||
{
|
||||
var isAudio = mimeType.StartsWith("audio");
|
||||
var tagType = isAudio ? "audio" : "video";
|
||||
|
||||
renderer.Write($"<{tagType}");
|
||||
htmlAttributes.AddPropertyIfNotExist("width", Options.Width);
|
||||
if (!isAudio)
|
||||
{
|
||||
htmlAttributes.AddPropertyIfNotExist("height", Options.Height);
|
||||
}
|
||||
htmlAttributes.AddPropertyIfNotExist("controls", null);
|
||||
renderer.WriteAttributes(htmlAttributes);
|
||||
|
||||
renderer.Write($"><source type=\"{mimeType}\" src=\"{linkInline.Url}\"></source></{tagType}>");
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
htmlAttributes.AddPropertyIfNotExist("height", Options.Height);
|
||||
}
|
||||
htmlAttributes.AddPropertyIfNotExist("controls", null);
|
||||
renderer.WriteAttributes(htmlAttributes);
|
||||
|
||||
renderer.Write($"><source type=\"{mimeType}\" src=\"{linkInline.Url}\"></source></{tagType}>");
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#region Known providers
|
||||
|
||||
private class KnownProvider
|
||||
{
|
||||
public string HostPrefix { get; set; }
|
||||
public Func<Uri, string> Delegate { get; set; }
|
||||
public bool AllowFullScreen { get; set; } = true; //Should be false for audio embedding
|
||||
}
|
||||
|
||||
private static readonly List<KnownProvider> KnownHosts = new List<KnownProvider>()
|
||||
{
|
||||
new KnownProvider {HostPrefix = "www.youtube.com", Delegate = YouTube},
|
||||
new KnownProvider {HostPrefix = "vimeo.com", Delegate = Vimeo},
|
||||
new KnownProvider {HostPrefix = "music.yandex.ru", Delegate = Yandex, AllowFullScreen = false},
|
||||
new KnownProvider {HostPrefix = "ok.ru", Delegate = Odnoklassniki},
|
||||
};
|
||||
|
||||
|
||||
private bool TryRenderIframeFromKnownProviders(Uri uri, HtmlRenderer renderer, LinkInline linkInline)
|
||||
{
|
||||
var foundProvider =
|
||||
KnownHosts
|
||||
.Where(pair => uri.Host.StartsWith(pair.HostPrefix, StringComparison.OrdinalIgnoreCase)) // when host is match
|
||||
.Select(provider =>
|
||||
new
|
||||
{
|
||||
provider.AllowFullScreen,
|
||||
Result = provider.Delegate(uri) // try to call delegate to get iframeUrl
|
||||
}
|
||||
)
|
||||
.FirstOrDefault(provider => provider.Result != null); // use first success
|
||||
|
||||
if (foundProvider == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var htmlAttributes = GetHtmlAttributes(linkInline);
|
||||
renderer.Write($"<iframe src=\"{foundProvider.Result}\"");
|
||||
|
||||
if(!string.IsNullOrEmpty(Options.Width))
|
||||
htmlAttributes.AddPropertyIfNotExist("width", Options.Width);
|
||||
|
||||
if (!string.IsNullOrEmpty(Options.Height))
|
||||
htmlAttributes.AddPropertyIfNotExist("height", Options.Height);
|
||||
|
||||
if (!string.IsNullOrEmpty(Options.Class))
|
||||
htmlAttributes.AddPropertyIfNotExist("class", Options.Class);
|
||||
|
||||
htmlAttributes.AddPropertyIfNotExist("frameborder", "0");
|
||||
if (foundProvider.AllowFullScreen)
|
||||
{
|
||||
htmlAttributes.AddPropertyIfNotExist("allowfullscreen", null);
|
||||
}
|
||||
renderer.WriteAttributes(htmlAttributes);
|
||||
renderer.Write("></iframe>");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static readonly string[] SplitAnd = {"&"};
|
||||
private static string[] SplitQuery(Uri uri)
|
||||
{
|
||||
var query = uri.Query.Substring(uri.Query.IndexOf('?') + 1);
|
||||
return query.Split(SplitAnd, StringSplitOptions.RemoveEmptyEntries);
|
||||
}
|
||||
|
||||
private static string YouTube(Uri uri)
|
||||
{
|
||||
var query = SplitQuery(uri);
|
||||
return query.Length > 0 && query[0].StartsWith("v=")
|
||||
? $"https://www.youtube.com/embed/{query[0].Substring(2)}"
|
||||
: null;
|
||||
}
|
||||
|
||||
private static string Vimeo(Uri uri)
|
||||
{
|
||||
var items = uri.GetComponents(UriComponents.Path, UriFormat.Unescaped).Split('/');
|
||||
return items.Length > 0 ? $"https://player.vimeo.com/video/{items[items.Length - 1]}" : null;
|
||||
}
|
||||
|
||||
private static string Odnoklassniki(Uri uri)
|
||||
{
|
||||
var items = uri.GetComponents(UriComponents.Path, UriFormat.Unescaped).Split('/');
|
||||
return items.Length > 0 ? $"https://ok.ru/videoembed/{items[items.Length - 1]}" : null;
|
||||
}
|
||||
|
||||
private static string Yandex(Uri uri)
|
||||
{
|
||||
var items = uri.GetComponents(UriComponents.Path, UriFormat.Unescaped).Split('/');
|
||||
var albumKeyword
|
||||
= items.Skip(0).FirstOrDefault();
|
||||
var albumId
|
||||
= items.Skip(1).FirstOrDefault();
|
||||
var trackKeyword
|
||||
= items.Skip(2).FirstOrDefault();
|
||||
var trackId
|
||||
= items.Skip(3).FirstOrDefault();
|
||||
|
||||
if (albumKeyword != "album" || albumId == null || trackKeyword != "track" || trackId == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return $"https://music.yandex.ru/iframe/#track/{trackId}/{albumId}/";
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@ namespace Markdig.Extensions.MediaLinks
|
||||
{
|
||||
Width = "500";
|
||||
Height = "281";
|
||||
Class = "";
|
||||
ExtensionToMimeType = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
{".3gp", "video/3gpp"},
|
||||
@@ -68,6 +69,7 @@ namespace Markdig.Extensions.MediaLinks
|
||||
{".wma", "audio/x-ms-wma"},
|
||||
{".wax", "audio/x-ms-wax"},
|
||||
{".mid", "audio/midi"},
|
||||
{".mp3", "audio/mpeg"},
|
||||
{".mpga", "audio/mpeg"},
|
||||
{".mp4a", "audio/mp4"},
|
||||
{".ecelp4800", "audio/vnd.nuera.ecelp4800"},
|
||||
@@ -86,6 +88,8 @@ namespace Markdig.Extensions.MediaLinks
|
||||
|
||||
public string Height { get; set; }
|
||||
|
||||
public string Class { get; set; }
|
||||
|
||||
public Dictionary<string, string> ExtensionToMimeType { get; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
using Markdig.Renderers.Normalize;
|
||||
|
||||
namespace Markdig.Extensions.TaskLists
|
||||
{
|
||||
public class NormalizeTaskListRenderer : NormalizeObjectRenderer<TaskList>
|
||||
{
|
||||
protected override void Write(NormalizeRenderer renderer, TaskList obj)
|
||||
{
|
||||
renderer.Write("[");
|
||||
renderer.Write(obj.Checked ? "X" : " ");
|
||||
renderer.Write("]");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
// 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 Markdig.Parsers.Inlines;
|
||||
using Markdig.Renderers;
|
||||
using Markdig.Renderers.Normalize;
|
||||
|
||||
namespace Markdig.Extensions.TaskLists
|
||||
{
|
||||
@@ -28,6 +29,12 @@ namespace Markdig.Extensions.TaskLists
|
||||
{
|
||||
htmlRenderer.ObjectRenderers.AddIfNotAlready<HtmlTaskListRenderer>();
|
||||
}
|
||||
|
||||
var normalizeRenderer = renderer as NormalizeRenderer;
|
||||
if (normalizeRenderer != null)
|
||||
{
|
||||
normalizeRenderer.ObjectRenderers.AddIfNotAlready<NormalizeTaskListRenderer>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
using Markdig.Renderers;
|
||||
|
||||
namespace Markdig.Extensions.TextRenderer
|
||||
{
|
||||
/// <summary>
|
||||
/// Extension that allows setting line-endings for any IMarkdownRenderer
|
||||
/// that inherits from <see cref="Markdig.Renderers.TextRendererBase"/>
|
||||
/// </summary>
|
||||
/// <seealso cref="Markdig.IMarkdownExtension" />
|
||||
public class ConfigureNewLineExtension : IMarkdownExtension
|
||||
{
|
||||
private readonly string newLine;
|
||||
|
||||
public ConfigureNewLineExtension(string newLine)
|
||||
{
|
||||
this.newLine = newLine;
|
||||
}
|
||||
|
||||
public void Setup(MarkdownPipelineBuilder pipeline)
|
||||
{
|
||||
}
|
||||
|
||||
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
|
||||
{
|
||||
var textRenderer = renderer as TextRendererBase;
|
||||
if (textRenderer == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
textRenderer.Writer.NewLine = newLine;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -1,44 +1,166 @@
|
||||
// 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())
|
||||
{
|
||||
bool hasFullYamlFrontMatter = false;
|
||||
// We make sure that there is a closing frontmatter somewhere in the document
|
||||
// so here we work on the full document instead of just the line
|
||||
var fullLine = new StringSlice(line.Text, line.Start, line.Text.Length - 1);
|
||||
c = fullLine.CurrentChar;
|
||||
while (c != '\0')
|
||||
{
|
||||
c = fullLine.NextChar();
|
||||
if (c == '\n' || c == '\r')
|
||||
{
|
||||
var nc = fullLine.PeekChar();
|
||||
if (c == '\r' && nc == '\n')
|
||||
{
|
||||
c = fullLine.NextChar();
|
||||
}
|
||||
nc = fullLine.PeekChar();
|
||||
if (nc == '-')
|
||||
{
|
||||
if (fullLine.NextChar() == '-' && fullLine.NextChar() == '-' && fullLine.NextChar() == '-' && (fullLine.NextChar() == '\0' || fullLine.SkipSpacesToEndOfLineOrEndOfDocument()))
|
||||
{
|
||||
hasFullYamlFrontMatter = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (nc == '.')
|
||||
{
|
||||
if (fullLine.NextChar() == '.' && fullLine.NextChar() == '.' && fullLine.NextChar() == '.' && (fullLine.NextChar() == '\0' || fullLine.SkipSpacesToEndOfLineOrEndOfDocument()))
|
||||
{
|
||||
hasFullYamlFrontMatter = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hasFullYamlFrontMatter)
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,7 @@ namespace Markdig.Helpers
|
||||
public const string ZeroSafeString = "\uFFFD";
|
||||
|
||||
// We don't support LCDM
|
||||
private static IDictionary<char, int> romanMap = new Dictionary<char, int> { { 'I', 1 }, { 'V', 5 }, { 'X', 10 } };
|
||||
private static readonly Dictionary<char, int> romanMap = new Dictionary<char, int> { { 'I', 1 }, { 'V', 5 }, { 'X', 10 } };
|
||||
|
||||
private static readonly char[] punctuationExceptions = { '−', '-', '†', '‡' };
|
||||
|
||||
|
||||
@@ -115,35 +115,44 @@ namespace Markdig.Helpers
|
||||
/// <param name="start">The start.</param>
|
||||
/// <param name="end">The end.</param>
|
||||
/// <returns>Index position within the string of the first opening character found in the specified text; if not found, returns -1</returns>
|
||||
public unsafe int IndexOfOpeningCharacter(string text, int start, int end)
|
||||
public int IndexOfOpeningCharacter(string text, int start, int end)
|
||||
{
|
||||
var maxChar = isOpeningCharacter.Length;
|
||||
#if SUPPORT_UNSAFE
|
||||
unsafe
|
||||
#endif
|
||||
{
|
||||
#if SUPPORT_FIXED_STRING
|
||||
fixed (char* pText = text)
|
||||
#else
|
||||
var pText = text;
|
||||
var pText = text;
|
||||
#endif
|
||||
#if SUPPORT_UNSAFE
|
||||
fixed (bool* openingChars = isOpeningCharacter)
|
||||
#else
|
||||
var openingChars = isOpeningCharacter;
|
||||
#endif
|
||||
fixed (bool* openingChars = isOpeningCharacter)
|
||||
{
|
||||
if (nonAsciiMap == null)
|
||||
{
|
||||
for (int i = start; i <= end; i++)
|
||||
if (nonAsciiMap == null)
|
||||
{
|
||||
var c = pText[i];
|
||||
if (c < maxChar && openingChars[c])
|
||||
for (int i = start; i <= end; i++)
|
||||
{
|
||||
return i;
|
||||
var c = pText[i];
|
||||
if (c < maxChar && openingChars[c])
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = start; i <= end; i++)
|
||||
else
|
||||
{
|
||||
var c = pText[i];
|
||||
if ((c < maxChar && openingChars[c]) || nonAsciiMap.ContainsKey(c))
|
||||
for (int i = start; i <= end; i++)
|
||||
{
|
||||
return i;
|
||||
var c = pText[i];
|
||||
if ((c < maxChar && openingChars[c]) || nonAsciiMap.ContainsKey(c))
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -472,10 +472,10 @@ namespace Markdig.Helpers
|
||||
}
|
||||
else if (c == '&')
|
||||
{
|
||||
string namedEntity;
|
||||
int entityNameStart;
|
||||
int entityNameLength;
|
||||
int numericEntity;
|
||||
var match = ScanEntity(text, searchPos, text.Length - searchPos, out namedEntity,
|
||||
out numericEntity);
|
||||
var match = ScanEntity(new StringSlice(text, searchPos, text.Length - 1), out numericEntity, out entityNameStart, out entityNameLength);
|
||||
if (match == 0)
|
||||
{
|
||||
searchPos++;
|
||||
@@ -484,9 +484,10 @@ namespace Markdig.Helpers
|
||||
{
|
||||
searchPos += match;
|
||||
|
||||
if (namedEntity != null)
|
||||
if (entityNameLength > 0)
|
||||
{
|
||||
var decoded = EntityHelper.DecodeEntity(namedEntity);
|
||||
var namedEntity = new StringSlice(text, entityNameStart, entityNameStart + entityNameLength - 1);
|
||||
var decoded = EntityHelper.DecodeEntity(namedEntity.ToString());
|
||||
if (decoded != null)
|
||||
{
|
||||
sb.Append(text, lastPos, searchPos - match - lastPos);
|
||||
@@ -533,7 +534,7 @@ namespace Markdig.Helpers
|
||||
/// Scans an entity.
|
||||
/// Returns number of chars matched.
|
||||
/// </summary>
|
||||
public static int ScanEntity(string s, int pos, int length, out string namedEntity, out int numericEntity)
|
||||
public static int ScanEntity<T>(T slice, out int numericEntity, out int namedEntityStart, out int namedEntityLength) where T : ICharIterator
|
||||
{
|
||||
// Credits: code from CommonMark.NET
|
||||
// Copyright (c) 2014, Kārlis Gaņģis All rights reserved.
|
||||
@@ -545,29 +546,29 @@ namespace Markdig.Helpers
|
||||
.? { return 0; }
|
||||
*/
|
||||
|
||||
var lastPos = pos + length;
|
||||
|
||||
namedEntity = null;
|
||||
numericEntity = 0;
|
||||
namedEntityStart = 0;
|
||||
namedEntityLength = 0;
|
||||
|
||||
if (pos + 3 >= lastPos)
|
||||
return 0;
|
||||
|
||||
if (s[pos] != '&')
|
||||
return 0;
|
||||
|
||||
char c;
|
||||
int i;
|
||||
int counter = 0;
|
||||
if (s[pos + 1] == '#')
|
||||
if (slice.CurrentChar != '&' || slice.PeekChar(3) == '\0')
|
||||
{
|
||||
c = s[pos + 2];
|
||||
return 0;
|
||||
}
|
||||
|
||||
var start = slice.Start;
|
||||
char c = slice.NextChar();
|
||||
int counter = 0;
|
||||
|
||||
if (c == '#')
|
||||
{
|
||||
c = slice.PeekChar();
|
||||
if (c == 'x' || c == 'X')
|
||||
{
|
||||
c = slice.NextChar(); // skip #
|
||||
// expect 1-8 hex digits starting from pos+3
|
||||
for (i = pos + 3; i < lastPos; i++)
|
||||
while (c != '\0')
|
||||
{
|
||||
c = s[i];
|
||||
c = slice.NextChar();
|
||||
if (c >= '0' && c <= '9')
|
||||
{
|
||||
if (++counter == 9) return 0;
|
||||
@@ -588,7 +589,7 @@ namespace Markdig.Helpers
|
||||
}
|
||||
|
||||
if (c == ';')
|
||||
return counter == 0 ? 0 : i - pos + 1;
|
||||
return counter == 0 ? 0 : slice.Start - start + 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -596,9 +597,10 @@ namespace Markdig.Helpers
|
||||
else
|
||||
{
|
||||
// expect 1-8 digits starting from pos+2
|
||||
for (i = pos + 2; i < lastPos; i++)
|
||||
while (c != '\0')
|
||||
{
|
||||
c = s[i];
|
||||
c = slice.NextChar();
|
||||
|
||||
if (c >= '0' && c <= '9')
|
||||
{
|
||||
if (++counter == 9) return 0;
|
||||
@@ -607,7 +609,7 @@ namespace Markdig.Helpers
|
||||
}
|
||||
|
||||
if (c == ';')
|
||||
return counter == 0 ? 0 : i - pos + 1;
|
||||
return counter == 0 ? 0 : slice.Start - start + 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -616,25 +618,26 @@ namespace Markdig.Helpers
|
||||
else
|
||||
{
|
||||
// expect a letter and 1-31 letters or digits
|
||||
c = s[pos + 1];
|
||||
if (!((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')))
|
||||
return 0;
|
||||
|
||||
for (i = pos + 2; i < lastPos; i++)
|
||||
namedEntityStart = slice.Start;
|
||||
namedEntityLength++;
|
||||
|
||||
while (c != '\0')
|
||||
{
|
||||
c = s[i];
|
||||
c = slice.NextChar();
|
||||
if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'))
|
||||
{
|
||||
if (++counter == 32)
|
||||
return 0;
|
||||
|
||||
namedEntityLength++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (c == ';')
|
||||
{
|
||||
namedEntity = s.Substring(pos + 1, counter + 1);
|
||||
return counter == 0 ? 0 : i - pos + 1;
|
||||
return counter == 0 ? 0 : slice.Start - start + 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -34,8 +34,9 @@ namespace Markdig.Helpers
|
||||
/// <summary>
|
||||
/// Peeks at the next character, without incrementing the <see cref="Start"/> position.
|
||||
/// </summary>
|
||||
/// <param name="offset"></param>
|
||||
/// <returns>The next character. `\0` is end of the iteration.</returns>
|
||||
char PeekChar();
|
||||
char PeekChar(int offset = 1);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this instance is empty.
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
// 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;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using Markdig.Parsers.Inlines;
|
||||
using Markdig.Syntax;
|
||||
|
||||
namespace Markdig.Helpers
|
||||
@@ -18,10 +19,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)
|
||||
{
|
||||
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++)
|
||||
{
|
||||
@@ -96,6 +97,23 @@ namespace Markdig.Helpers
|
||||
return text;
|
||||
}
|
||||
|
||||
public static string UrilizeAsGfm(string headingText)
|
||||
{
|
||||
// Following https://github.com/jch/html-pipeline/blob/master/lib/html/pipeline/toc_filter.rb
|
||||
var headingBuffer = StringBuilderCache.Local();
|
||||
for (int i = 0; i < headingText.Length; i++)
|
||||
{
|
||||
var c = char.ToLowerInvariant(headingText[i]);
|
||||
if (char.IsLetterOrDigit(c) || c == ' ' || c == '-' || c == '_')
|
||||
{
|
||||
headingBuffer.Append(c == ' ' ? '-' : c);
|
||||
}
|
||||
}
|
||||
var result = headingBuffer.ToString();
|
||||
headingBuffer.Length = 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
|
||||
private static bool IsReservedPunctuation(char c)
|
||||
{
|
||||
@@ -496,7 +514,7 @@ namespace Markdig.Helpers
|
||||
return TryParseUrl(ref text, out link);
|
||||
}
|
||||
|
||||
public static bool TryParseUrl<T>(ref T text, out string link) where T : ICharIterator
|
||||
public static bool TryParseUrl<T>(ref T text, out string link, bool isAutoLink = false) where T : ICharIterator
|
||||
{
|
||||
bool isValid = false;
|
||||
var buffer = StringBuilderCache.Local();
|
||||
@@ -561,10 +579,6 @@ namespace Markdig.Helpers
|
||||
{
|
||||
if (!hasEscape)
|
||||
{
|
||||
if (openedParent > 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
openedParent++;
|
||||
}
|
||||
}
|
||||
@@ -597,22 +611,41 @@ namespace Markdig.Helpers
|
||||
|
||||
hasEscape = false;
|
||||
|
||||
if (IsEndOfUri(c))
|
||||
if (IsEndOfUri(c, isAutoLink))
|
||||
{
|
||||
isValid = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (c == '.' && IsEndOfUri(text.PeekChar()))
|
||||
if (isAutoLink)
|
||||
{
|
||||
isValid = true;
|
||||
break;
|
||||
if (c == '&')
|
||||
{
|
||||
int entityNameStart;
|
||||
int entityNameLength;
|
||||
int entityValue;
|
||||
if (HtmlHelper.ScanEntity(text, out entityValue, out entityNameStart, out entityNameLength) > 0)
|
||||
{
|
||||
isValid = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (IsTrailingUrlStopCharacter(c) && IsEndOfUri(text.PeekChar(), true))
|
||||
{
|
||||
isValid = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
buffer.Append(c);
|
||||
|
||||
c = text.NextChar();
|
||||
}
|
||||
|
||||
if (openedParent > 0)
|
||||
{
|
||||
isValid = false;
|
||||
}
|
||||
}
|
||||
|
||||
link = isValid ? buffer.ToString() : null;
|
||||
@@ -620,9 +653,17 @@ namespace Markdig.Helpers
|
||||
return isValid;
|
||||
}
|
||||
|
||||
private static bool IsEndOfUri(char c)
|
||||
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
|
||||
private static bool IsTrailingUrlStopCharacter(char c)
|
||||
{
|
||||
return c == '\0' || c.IsSpaceOrTab() || c.IsControl(); // TODO: specs unclear. space is strict or relaxed? (includes tabs?)
|
||||
// Trailing punctuation (specifically, ?, !, ., ,, :, *, _, and ~) will not be considered part of the autolink, though they may be included in the interior of the link:
|
||||
return c == '?' || c == '!' || c == '.' || c == ',' || c == ':' || c == '*' || c == '*' || c == '_' || c == '~';
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
|
||||
private static bool IsEndOfUri(char c, bool isAutoLink)
|
||||
{
|
||||
return c == '\0' || c.IsSpaceOrTab() || c.IsControl() || (isAutoLink && c == '<'); // TODO: specs unclear. space is strict or relaxed? (includes tabs?)
|
||||
}
|
||||
|
||||
public static bool TryParseLinkReferenceDefinition<T>(T text, out string label, out string url,
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -197,14 +197,14 @@ namespace Markdig.Helpers
|
||||
/// <seealso cref="ICharIterator" />
|
||||
public struct Iterator : ICharIterator
|
||||
{
|
||||
private readonly StringLineGroup lines;
|
||||
private int offset;
|
||||
private readonly StringLineGroup _lines;
|
||||
private int _offset;
|
||||
|
||||
public Iterator(StringLineGroup lines)
|
||||
{
|
||||
this.lines = lines;
|
||||
this._lines = lines;
|
||||
Start = -1;
|
||||
offset = -1;
|
||||
_offset = -1;
|
||||
SliceIndex = 0;
|
||||
CurrentChar = '\0';
|
||||
End = -2;
|
||||
@@ -228,45 +228,47 @@ namespace Markdig.Helpers
|
||||
public char NextChar()
|
||||
{
|
||||
Start++;
|
||||
offset++;
|
||||
_offset++;
|
||||
if (Start <= End)
|
||||
{
|
||||
var slice = (StringSlice)lines.Lines[SliceIndex];
|
||||
if (offset < slice.Length)
|
||||
var slice = (StringSlice)_lines.Lines[SliceIndex];
|
||||
if (_offset < slice.Length)
|
||||
{
|
||||
CurrentChar = slice[slice.Start + offset];
|
||||
CurrentChar = slice[slice.Start + _offset];
|
||||
}
|
||||
else
|
||||
{
|
||||
CurrentChar = '\n';
|
||||
SliceIndex++;
|
||||
offset = -1;
|
||||
_offset = -1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
CurrentChar = '\0';
|
||||
Start = End + 1;
|
||||
SliceIndex = lines.Count;
|
||||
offset--;
|
||||
SliceIndex = _lines.Count;
|
||||
_offset--;
|
||||
}
|
||||
return CurrentChar;
|
||||
}
|
||||
|
||||
public char PeekChar()
|
||||
public char PeekChar(int offset = 1)
|
||||
{
|
||||
if (Start + 1 > End)
|
||||
if (offset < 0) throw new ArgumentOutOfRangeException("Negative offset are not supported for StringLineGroup", nameof(offset));
|
||||
|
||||
if (Start + offset > End)
|
||||
{
|
||||
return '\0';
|
||||
}
|
||||
|
||||
var slice = (StringSlice)lines.Lines[SliceIndex];
|
||||
if (offset + 1 >= slice.Length)
|
||||
var slice = (StringSlice)_lines.Lines[SliceIndex];
|
||||
if (_offset + offset >= slice.Length)
|
||||
{
|
||||
return '\n';
|
||||
}
|
||||
|
||||
return slice[slice.Start + offset + 1];
|
||||
return slice[slice.Start + _offset + offset];
|
||||
}
|
||||
|
||||
public bool TrimStart()
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -106,23 +106,12 @@ namespace Markdig.Helpers
|
||||
/// <param name="offset">The offset.</param>
|
||||
/// <returns>The character at offset, returns `\0` if none.</returns>
|
||||
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
|
||||
public char PeekChar(int offset)
|
||||
public char PeekChar(int offset = 1)
|
||||
{
|
||||
var index = Start + offset;
|
||||
return index >= Start && index <= End ? Text[index] : (char) 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Peeks the character immediately after the current <see cref="Start"/> position
|
||||
/// or returns `\0` if after the <see cref="End"/> position.
|
||||
/// </summary>
|
||||
/// <returns>The next character, returns `\0` if none.</returns>
|
||||
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
|
||||
public char PeekChar()
|
||||
{
|
||||
return PeekChar(1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Peeks a character at the specified offset from the current beginning of the string, without taking into account <see cref="Start"/> and <see cref="End"/>
|
||||
/// </summary>
|
||||
@@ -179,6 +168,28 @@ namespace Markdig.Helpers
|
||||
return i == text.Length;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Expect spaces until a end of line. Return <c>false</c> otherwise.
|
||||
/// </summary>
|
||||
/// <returns><c>true</c> if whitespaces where matched until a end of line</returns>
|
||||
public bool SkipSpacesToEndOfLineOrEndOfDocument()
|
||||
{
|
||||
for (int i = Start; i <= End; i++)
|
||||
{
|
||||
var c = Text[i];
|
||||
if (c.IsWhitespace())
|
||||
{
|
||||
if (c == '\0' || c == '\n' || (c == '\r' && i + 1 <= End && Text[i + 1] != '\n'))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Matches the specified text using lowercase comparison.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,37 +1,23 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<Description>A fast, powerfull, CommonMark compliant, extensible Markdown processor for .NET with 20+ builtin extensions (pipetables, footnotes, definition lists... etc.)</Description>
|
||||
<Description>A fast, powerful, CommonMark compliant, extensible Markdown processor for .NET with 20+ builtin extensions (pipetables, footnotes, definition lists... etc.)</Description>
|
||||
<Copyright>Alexandre Mutel</Copyright>
|
||||
<AssemblyTitle>Markdig</AssemblyTitle>
|
||||
<NeutralLanguage>en-US</NeutralLanguage>
|
||||
<VersionPrefix>0.12.3</VersionPrefix>
|
||||
<VersionPrefix>0.15.1</VersionPrefix>
|
||||
<Authors>Alexandre Mutel</Authors>
|
||||
<TargetFrameworks>net35;net40;portable40-net40+sl5+win8+wp8+wpa81;netstandard1.1</TargetFrameworks>
|
||||
<TargetFrameworks>net35;net40;portable40-net40+sl5+win8+wp8+wpa81;netstandard1.1;netstandard2.0;uap10.0</TargetFrameworks>
|
||||
<AssemblyName>Markdig</AssemblyName>
|
||||
<PackageId>Markdig</PackageId>
|
||||
<PackageId Condition="'$(SignAssembly)' == 'true'">Markdig.Signed</PackageId>
|
||||
<PackageTags>Markdown CommonMark md html md2html</PackageTags>
|
||||
<PackageReleaseNotes>
|
||||
> 0.12.3
|
||||
- Fix issue with HTML blocks for heading h2,h3,h4,h5,h6 that were not correctly identified as HTML blocks as per CommonMark spec
|
||||
> 0.12.2
|
||||
- Fix issue with generic attributes used just before a pipe table (issue #121)
|
||||
> 0.12.1
|
||||
- Fix issue with media links extension when a URL to video is used, an unexpected closing `</iframe>` was inserted (issue #119)
|
||||
> 0.12.0
|
||||
- Add new extension JiraLink support (thanks to @clarkd)
|
||||
- Fix issue in html attributes not parsing correctly properties (thanks to @meziantou)
|
||||
- Fix issues detected by an automatic static code analysis tool
|
||||
> 0.11.0
|
||||
- Fix issue with math extension and $$ block parsing not handling correctly beginning of a $$ as a inline math instead (issue #107)
|
||||
- Fix issue with custom attributes for emphasis
|
||||
- Add support for new special custom arrows emoji (-> <- <-> <= => and <==>)
|
||||
</PackageReleaseNotes>
|
||||
<PackageReleaseNotes>https://github.com/lunet-io/markdig/blob/master/changelog.md</PackageReleaseNotes>
|
||||
<PackageIconUrl>https://raw.githubusercontent.com/lunet-io/markdig/master/img/markdig.png</PackageIconUrl>
|
||||
<PackageProjectUrl>https://github.com/lunet-io/markdig</PackageProjectUrl>
|
||||
<PackageLicenseUrl>https://github.com/lunet-io/markdig/blob/master/license.txt</PackageLicenseUrl>
|
||||
<NetStandardImplicitPackageVersion Condition=" '$(TargetFramework)' == 'netstandard1.1' ">1.6.0</NetStandardImplicitPackageVersion>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup Condition=" '$(TargetFramework)' == 'net35' ">
|
||||
@@ -54,22 +40,51 @@
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(TargetFramework)' == 'net35' ">
|
||||
<DefineConstants>$(DefineConstants);SUPPORT_FIXED_STRING</DefineConstants>
|
||||
<DefineConstants>$(DefineConstants);SUPPORT_FIXED_STRING;SUPPORT_UNSAFE</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(TargetFramework)' == 'net40' ">
|
||||
<DefineConstants>$(DefineConstants);SUPPORT_FIXED_STRING;SUPPORT_UNSAFE</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(TargetFramework)' == 'netstandard1.1' ">
|
||||
<DefineConstants>$(DefineConstants);NETSTANDARD_11</DefineConstants>
|
||||
<DefineConstants>$(DefineConstants);NETSTANDARD_11;SUPPORT_UNSAFE</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
<PropertyGroup Condition=" '$(TargetFramework)' == 'netstandard2.0' ">
|
||||
<DefineConstants>$(DefineConstants);SUPPORT_FIXED_STRING;SUPPORT_UNSAFE</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(TargetFramework)' == 'uap10.0' ">
|
||||
<TargetPlatformIdentifier>UAP</TargetPlatformIdentifier>
|
||||
<TargetPlatformVersion Condition="'$(TargetPlatformVersion)' == ''">10.0.10240.0</TargetPlatformVersion>
|
||||
<TargetPlatformMinVersion Condition="'$(TargetPlatformMinVersion)' == ''">10.0.10240.0</TargetPlatformMinVersion>
|
||||
<DefineConstants>$(DefineConstants);NETSTANDARD_11;SUPPORT_UNSAFE</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(TargetFramework)' == 'portable40-net40+sl5+win8+wp8+wpa81'">
|
||||
<TargetFrameworkIdentifier>.NETPortable</TargetFrameworkIdentifier>
|
||||
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
|
||||
<TargetFrameworkProfile>Profile328</TargetFrameworkProfile>
|
||||
</PropertyGroup>
|
||||
<LanguageTargets>$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets</LanguageTargets>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||
<NoWarn>$(NoWarn);CS1591</NoWarn>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(SignAssembly)' == 'true' ">
|
||||
<AssemblyOriginatorKeyFile>key.snk</AssemblyOriginatorKeyFile>
|
||||
<PublicSign Condition=" '$(OS)' != 'Windows_NT' ">true</PublicSign>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Special packages and imports for UWP support -->
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MSBuild.Sdk.Extras" Version="1.0.9" PrivateAssets="all" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition=" '$(TargetFramework)' == 'uap10.0' ">
|
||||
<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform " Version="5.2.2" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildSDKExtrasTargets)" Condition="Exists('$(MSBuildSDKExtrasTargets)')" />
|
||||
</Project>
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -29,6 +29,7 @@ using Markdig.Extensions.SmartyPants;
|
||||
using Markdig.Extensions.NonAsciiNoEscape;
|
||||
using Markdig.Extensions.Tables;
|
||||
using Markdig.Extensions.TaskLists;
|
||||
using Markdig.Extensions.TextRenderer;
|
||||
using Markdig.Extensions.Yaml;
|
||||
using Markdig.Parsers;
|
||||
using Markdig.Parsers.Inlines;
|
||||
@@ -51,6 +52,19 @@ namespace Markdig
|
||||
return pipeline;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified extension instance to the extensions collection.
|
||||
/// </summary>
|
||||
/// <param name="pipeline">The pipeline.</param>
|
||||
/// <param name="extension">The instance of the extension to be added.</param>
|
||||
/// <typeparam name="TExtension">The type of the extension.</typeparam>
|
||||
/// <returns>The modified pipeline</returns>
|
||||
public static MarkdownPipelineBuilder Use<TExtension>(this MarkdownPipelineBuilder pipeline, TExtension extension) where TExtension : class, IMarkdownExtension
|
||||
{
|
||||
pipeline.Extensions.AddIfNotAlready(extension);
|
||||
return pipeline;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uses all extensions except the BootStrap, Emoji, SmartyPants and soft line as hard line breaks extensions.
|
||||
/// </summary>
|
||||
@@ -409,10 +423,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;
|
||||
}
|
||||
|
||||
@@ -571,5 +589,17 @@ namespace Markdig
|
||||
}
|
||||
return pipeline;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configures the string to be used for line-endings, when writing.
|
||||
/// </summary>
|
||||
/// <param name="pipeline">The pipeline.</param>
|
||||
/// <param name="newLine">The string to be used for line-endings.</param>
|
||||
/// <returns>The modified pipeline</returns>
|
||||
public static MarkdownPipelineBuilder ConfigureNewLine(this MarkdownPipelineBuilder pipeline, string newLine)
|
||||
{
|
||||
pipeline.Use(new ConfigureNewLineExtension(newLine));
|
||||
return pipeline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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--)
|
||||
@@ -274,20 +294,6 @@ namespace Markdig.Parsers.Inlines
|
||||
|
||||
var embracer = (ContainerInline)openDelimiter;
|
||||
|
||||
// Go down to the first emphasis with a lower level
|
||||
while (true)
|
||||
{
|
||||
var previousEmphasis = embracer.FirstChild as EmphasisInline;
|
||||
if (previousEmphasis != null && previousEmphasis.IsDouble && !isStrong && embracer.FirstChild == embracer.LastChild)
|
||||
{
|
||||
embracer = previousEmphasis;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Copy attributes attached to delimiter to the emphasis
|
||||
var attributes = closeDelimiter.TryGetAttributes();
|
||||
if (attributes != null)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// Copyright (c) Alexandre Mutel. All rights reserved.
|
||||
// This file is licensed under the BSD-Clause 2 license.
|
||||
// See the license.txt file in the project root for more information.
|
||||
|
||||
using System.Text;
|
||||
using Markdig.Helpers;
|
||||
using Markdig.Syntax;
|
||||
using Markdig.Syntax.Inlines;
|
||||
@@ -21,26 +23,40 @@ 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)
|
||||
{
|
||||
string entityName;
|
||||
literal = null;
|
||||
int entityNameStart;
|
||||
int entityNameLength;
|
||||
int entityValue;
|
||||
var startPosition = slice.Start;
|
||||
int match = HtmlHelper.ScanEntity(slice.Text, slice.Start, slice.Length, out entityName, out entityValue);
|
||||
match = HtmlHelper.ScanEntity(slice, out entityValue, out entityNameStart, out entityNameLength);
|
||||
if (match == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
string literal = null;
|
||||
if (entityName != null)
|
||||
if (entityNameLength > 0)
|
||||
{
|
||||
literal = EntityHelper.DecodeEntity(entityName);
|
||||
literal = EntityHelper.DecodeEntity(new StringSlice(slice.Text, entityNameStart, entityNameStart + entityNameLength - 1).ToString());
|
||||
}
|
||||
else if (entityValue >= 0)
|
||||
{
|
||||
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)
|
||||
{
|
||||
|
||||
@@ -86,7 +86,7 @@ namespace Markdig.Parsers.Inlines
|
||||
}
|
||||
}
|
||||
|
||||
// If we don’t find one, we return a literal slice node ].
|
||||
// If we don’t 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 it’s not active,
|
||||
// If we do find one, but it’s 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 it’s active,
|
||||
// If we find one and it’s 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();
|
||||
|
||||
@@ -86,7 +86,7 @@ namespace Markdig.Parsers
|
||||
// interpretations of a line, the thematic break takes precedence
|
||||
BlockState result;
|
||||
var thematicParser = ThematicBreakParser.Default;
|
||||
if (thematicParser.HasOpeningCharacter(processor.CurrentChar))
|
||||
if (!(processor.LastBlock is FencedCodeBlock) && thematicParser.HasOpeningCharacter(processor.CurrentChar))
|
||||
{
|
||||
result = thematicParser.TryOpen(processor);
|
||||
if (result.IsBreak())
|
||||
@@ -230,17 +230,16 @@ namespace Markdig.Parsers
|
||||
return BlockState.None;
|
||||
}
|
||||
|
||||
// We require at least one char
|
||||
state.NextColumn();
|
||||
|
||||
// Parse the following indent
|
||||
state.RestartIndent();
|
||||
var columnBeforeIndent = state.Column;
|
||||
state.ParseIndent();
|
||||
|
||||
if (state.IsCodeIndent)
|
||||
// We expect at most 4 columns after
|
||||
// If we have more, we reset the position
|
||||
if (state.Indent > 4)
|
||||
{
|
||||
state.GoToColumn(columnBeforeIndent);
|
||||
state.GoToColumn(columnBeforeIndent + 1);
|
||||
}
|
||||
|
||||
// Number of spaces required for the following content to be part of this list item
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(">");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,7 @@ namespace Markdig.Renderers.Html.Inlines
|
||||
renderer.Write("mailto:");
|
||||
}
|
||||
renderer.WriteEscapeUrl(obj.Url);
|
||||
renderer.Write('"');
|
||||
renderer.WriteAttributes(obj);
|
||||
|
||||
if (!obj.IsEmail && AutoRelNoFollow)
|
||||
@@ -33,7 +34,7 @@ namespace Markdig.Renderers.Html.Inlines
|
||||
renderer.Write(" rel=\"nofollow\"");
|
||||
}
|
||||
|
||||
renderer.Write("\">");
|
||||
renderer.Write(">");
|
||||
}
|
||||
|
||||
renderer.WriteEscape(obj.Url);
|
||||
|
||||
@@ -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>");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>");
|
||||
}
|
||||
|
||||
@@ -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>");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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(" />");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -66,6 +75,16 @@ namespace Markdig.Renderers
|
||||
|
||||
public bool UseNonAsciiNoEscape { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value to use as the base url for all relative links
|
||||
/// </summary>
|
||||
public Uri BaseUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Allows links to be rewritten
|
||||
/// </summary>
|
||||
public Func<string,string> LinkRewriter { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Writes the content escaped for HTML.
|
||||
/// </summary>
|
||||
@@ -183,6 +202,16 @@ namespace Markdig.Renderers
|
||||
if (content == null)
|
||||
return this;
|
||||
|
||||
if (BaseUrl != null && !Uri.TryCreate(content, UriKind.Absolute, out Uri _))
|
||||
{
|
||||
content = new Uri(BaseUrl, content).AbsoluteUri;
|
||||
}
|
||||
|
||||
if (LinkRewriter != null)
|
||||
{
|
||||
content = LinkRewriter(content);
|
||||
}
|
||||
|
||||
int previousPosition = 0;
|
||||
int length = content.Length;
|
||||
|
||||
|
||||
55
src/Markdig/Renderers/Normalize/CodeBlockRenderer.cs
Normal file
55
src/Markdig/Renderers/Normalize/CodeBlockRenderer.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
36
src/Markdig/Renderers/Normalize/HeadingRenderer.cs
Normal file
36
src/Markdig/Renderers/Normalize/HeadingRenderer.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
16
src/Markdig/Renderers/Normalize/HtmlBlockRenderer.cs
Normal file
16
src/Markdig/Renderers/Normalize/HtmlBlockRenderer.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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('>');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 (<br />)
|
||||
/// </summary>
|
||||
public bool RenderAsHardlineBreak { get; set; }
|
||||
|
||||
protected override void Write(NormalizeRenderer renderer, LineBreakInline obj)
|
||||
{
|
||||
if (obj.IsHard)
|
||||
{
|
||||
renderer.Write(obj.IsBackslash ? "\\" : " ");
|
||||
}
|
||||
renderer.WriteLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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(')');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
93
src/Markdig/Renderers/Normalize/ListRenderer.cs
Normal file
93
src/Markdig/Renderers/Normalize/ListRenderer.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
16
src/Markdig/Renderers/Normalize/NormalizeObjectRenderer.cs
Normal file
16
src/Markdig/Renderers/Normalize/NormalizeObjectRenderer.cs
Normal 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
|
||||
{
|
||||
}
|
||||
}
|
||||
54
src/Markdig/Renderers/Normalize/NormalizeOptions.cs
Normal file
54
src/Markdig/Renderers/Normalize/NormalizeOptions.cs
Normal file
@@ -0,0 +1,54 @@
|
||||
// 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;
|
||||
ExpandAutoLinks = true;
|
||||
ListItemCharacter = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a space after a QuoteBlock >. 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; }
|
||||
|
||||
/// <summary>
|
||||
/// Expands AutoLinks to the normal inline representation. Default is <c>true</c>
|
||||
/// </summary>
|
||||
public bool ExpandAutoLinks { get; set; }
|
||||
}
|
||||
}
|
||||
161
src/Markdig/Renderers/Normalize/NormalizeRenderer.cs
Normal file
161
src/Markdig/Renderers/Normalize/NormalizeRenderer.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
20
src/Markdig/Renderers/Normalize/ParagraphRenderer.cs
Normal file
20
src/Markdig/Renderers/Normalize/ParagraphRenderer.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
24
src/Markdig/Renderers/Normalize/QuoteBlockRenderer.cs
Normal file
24
src/Markdig/Renderers/Normalize/QuoteBlockRenderer.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
21
src/Markdig/Renderers/Normalize/ThematicBreakRenderer.cs
Normal file
21
src/Markdig/Renderers/Normalize/ThematicBreakRenderer.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user