mirror of
https://github.com/xoofx/markdig.git
synced 2026-02-04 05:44:50 +00:00
Compare commits
88 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
22271db431 | ||
|
|
446764873d | ||
|
|
bdb5119806 | ||
|
|
f3bb06531e | ||
|
|
9a74f3708e | ||
|
|
57033311dc | ||
|
|
bda2f8c9ad | ||
|
|
0d5d3ddb2a | ||
|
|
3f8e50031b | ||
|
|
949d5c0c25 | ||
|
|
31e163f9f7 | ||
|
|
b1440ab788 | ||
|
|
6e55d84f01 | ||
|
|
595dbb9af4 | ||
|
|
d1576fd2ee | ||
|
|
1fe05639c9 | ||
|
|
8afff09b3e | ||
|
|
694747471a | ||
|
|
dfcd5cddfa | ||
|
|
a99b7f4572 | ||
|
|
7d7598db54 | ||
|
|
8767a1d8ed | ||
|
|
16c0829d9e | ||
|
|
fc5d832432 | ||
|
|
7292acc27a | ||
|
|
d05bc1572d | ||
|
|
5aa138425f | ||
|
|
c56041811b | ||
|
|
790bff3baf | ||
|
|
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 |
@@ -6,7 +6,6 @@ root = true
|
||||
# All Files
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = crlf
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
insert_final_newline = false
|
||||
|
||||
31
changelog.md
31
changelog.md
@@ -1,5 +1,36 @@
|
||||
# Changelog
|
||||
|
||||
## 0.15.3 (15 Sep 2018)
|
||||
- Add support for RTL ([(PR #239)](https://github.com/lunet-io/markdig/pull/239))
|
||||
- Add MarkdownDocument.LineCount ([(PR #241)](https://github.com/lunet-io/markdig/pull/241))
|
||||
- Fix source positions for link definitions ([(PR #243)](https://github.com/lunet-io/markdig/pull/243))
|
||||
- Add ListItemBlock.Order ([(PR #244)](https://github.com/lunet-io/markdig/pull/244))
|
||||
- Add MarkdownDocument.LineStartIndexes ([(PR #247)](https://github.com/lunet-io/markdig/pull/247))
|
||||
|
||||
## 0.15.2 (21 Aug 2018)
|
||||
- Fix footnotes parsing when they are defined after a container that has been closed in the meantime (#223)
|
||||
|
||||
## 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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -52,6 +52,12 @@ You can **try Markdig online** and compare it to other implementations on [babel
|
||||
- [**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!
|
||||
|
||||
@@ -57,6 +57,7 @@
|
||||
<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" />
|
||||
@@ -65,6 +66,8 @@
|
||||
<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" />
|
||||
@@ -74,6 +77,7 @@
|
||||
<ItemGroup>
|
||||
<None Include="App.config" />
|
||||
<None Include="project.json" />
|
||||
<None Include="Specs\GlobalizationSpecs.md" />
|
||||
<None Include="Specs\JiraLinks.md" />
|
||||
<None Include="Specs\AutoLinks.md" />
|
||||
<None Include="Specs\AutoIdentifierSpecs.md" />
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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>
|
||||
````````````````````````````````
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ This section describes the different extensions supported:
|
||||
|
||||
## Custom Container
|
||||
|
||||
A custom container is similar to a fenced code block, but it is using the character `:` to declare a block (with at least 3 characters), and instead of generating a `<pre><code>...</code></pre>` it will generate a `<div>...</dib>` block.
|
||||
A custom container is similar to a fenced code block, but it is using the character `:` to declare a block (with at least 3 characters), and instead of generating a `<pre><code>...</code></pre>` it will generate a `<div>...</div>` block.
|
||||
|
||||
```````````````````````````````` example
|
||||
:::spoiler
|
||||
@@ -109,4 +109,4 @@ Attributes can be attached to a inline custom container:
|
||||
This is a text ::with special emphasis::{#myId .myemphasis}
|
||||
.
|
||||
<p>This is a text <span id="myId" class="myemphasis">with special emphasis</span></p>
|
||||
````````````````````````````````
|
||||
````````````````````````````````
|
||||
|
||||
@@ -118,3 +118,24 @@ Here is a footnote[^1]. And another one[^2]. And a third one[^3]. And a fourth[^
|
||||
</ol>
|
||||
</div>
|
||||
````````````````````````````````
|
||||
|
||||
A footnote link inside a list should work as well:
|
||||
|
||||
```````````````````````````````` example
|
||||
- abc
|
||||
- def[^1]
|
||||
|
||||
[^1]: Here is the footnote.
|
||||
.
|
||||
<ul>
|
||||
<li>abc</li>
|
||||
<li>def<a id="fnref:1" href="#fn:1" class="footnote-ref"><sup>1</sup></a></li>
|
||||
</ul>
|
||||
<div class="footnotes">
|
||||
<hr />
|
||||
<ol>
|
||||
<li id="fn:1">
|
||||
<p>Here is the footnote.<a href="#fnref:1" class="footnote-back-ref">↩</a></p></li>
|
||||
</ol>
|
||||
</div>
|
||||
````````````````````````````````
|
||||
|
||||
189
src/Markdig.Tests/Specs/GlobalizationSpecs.md
Normal file
189
src/Markdig.Tests/Specs/GlobalizationSpecs.md
Normal file
@@ -0,0 +1,189 @@
|
||||
# Extensions
|
||||
|
||||
This section describes the different extensions supported:
|
||||
|
||||
## Globalization
|
||||
Adds support for RTL content by adding `dir="rtl"` and `align="right` attributes to the appropriate html elements. Left to right text is not affected by this extension.
|
||||
|
||||
Whether a markdown block is marked as RTL or not is determined by the [first strong character](https://en.wikipedia.org/wiki/Bi-directional_text#Strong_characters) of the block.
|
||||
|
||||
**Note**: You might need to add `<meta charset="UTF-8">` to the head of the html file to be able to see the result correctly.
|
||||
|
||||
Headings and block quotes:
|
||||
```````````````````````````````` example
|
||||
# Fruits
|
||||
In botany, a [fruit](https://en.wikipedia.org/wiki/Fruit) is the seed-bearing structure in flowering plants (also known as angiosperms) formed from the ovary after flowering.
|
||||
|
||||
> Fruits are good for health
|
||||
-- Anonymous
|
||||
|
||||
# میوە
|
||||
[میوە](https://ckb.wikipedia.org/wiki/%D9%85%DB%8C%D9%88%DB%95) یان مێوە بەروبوومی ڕوەکیە کە ڕوەکەکان ھەڵیان ئەگرن وەک بەرگێک بۆ تۆوەکانیان، بە زۆری جیادەکرێتەوە بە شیرینی یان ترشی لە تامدا و بە بوونی بڕێکی زۆر ئاو
|
||||
|
||||
> میوە بۆ تەندروستی باشە
|
||||
-- نەزانراو
|
||||
.
|
||||
<h1 id="fruits">Fruits</h1>
|
||||
<p>In botany, a <a href="https://en.wikipedia.org/wiki/Fruit">fruit</a> is the seed-bearing structure in flowering plants (also known as angiosperms) formed from the ovary after flowering.</p>
|
||||
<blockquote>
|
||||
<p>Fruits are good for health
|
||||
-- Anonymous</p>
|
||||
</blockquote>
|
||||
<h1 id="section" dir="rtl">میوە</h1>
|
||||
<p dir="rtl"><a href="https://ckb.wikipedia.org/wiki/%D9%85%DB%8C%D9%88%DB%95" dir="rtl">میوە</a> یان مێوە بەروبوومی ڕوەکیە کە ڕوەکەکان ھەڵیان ئەگرن وەک بەرگێک بۆ تۆوەکانیان، بە زۆری جیادەکرێتەوە بە شیرینی یان ترشی لە تامدا و بە بوونی بڕێکی زۆر ئاو</p>
|
||||
<blockquote dir="rtl">
|
||||
<p dir="rtl">میوە بۆ تەندروستی باشە
|
||||
-- نەزانراو</p>
|
||||
</blockquote>
|
||||
````````````````````````````````
|
||||
|
||||
Lists:
|
||||
```````````````````````````````` example
|
||||
## Types of fruits
|
||||
- Berries
|
||||
- Strawberry
|
||||
- kiwifruit
|
||||
- Citrus
|
||||
- Orange
|
||||
- Lemon
|
||||
|
||||
## Examples of fruits :yum:
|
||||
1. Apple
|
||||
2. Banana
|
||||
3. Orange
|
||||
|
||||
## Grocery List
|
||||
- [X] Watermelon
|
||||
- [X] Apricot
|
||||
- [ ] Fig
|
||||
|
||||
## نموونەی میوە :yum:
|
||||
1. ? سێو
|
||||
2. 5 مۆز
|
||||
3. پرتەقاڵ
|
||||
|
||||
## جۆرەکانی میوە
|
||||
- توو
|
||||
- فڕاولە
|
||||
- کیوی
|
||||
- مزرەمەنی
|
||||
- پڕتەقاڵ
|
||||
- لیمۆ
|
||||
|
||||
## لیستی کڕین
|
||||
- [X] شووتی
|
||||
- [X] قەیسی
|
||||
- [ ] هەنجیر
|
||||
.
|
||||
<h2 id="types-of-fruits">Types of fruits</h2>
|
||||
<ul>
|
||||
<li>Berries
|
||||
<ul>
|
||||
<li>Strawberry</li>
|
||||
<li>kiwifruit</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Citrus
|
||||
<ul>
|
||||
<li>Orange</li>
|
||||
<li>Lemon</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<h2 id="examples-of-fruits">Examples of fruits 😋</h2>
|
||||
<ol>
|
||||
<li>Apple</li>
|
||||
<li>Banana</li>
|
||||
<li>Orange</li>
|
||||
</ol>
|
||||
<h2 id="grocery-list">Grocery List</h2>
|
||||
<ul class="contains-task-list">
|
||||
<li class="task-list-item"><input disabled="disabled" type="checkbox" checked="checked" /> Watermelon</li>
|
||||
<li class="task-list-item"><input disabled="disabled" type="checkbox" checked="checked" /> Apricot</li>
|
||||
<li class="task-list-item"><input disabled="disabled" type="checkbox" /> Fig</li>
|
||||
</ul>
|
||||
<h2 id="section" dir="rtl">نموونەی میوە 😋</h2>
|
||||
<ol dir="rtl">
|
||||
<li>? سێو</li>
|
||||
<li>5 مۆز</li>
|
||||
<li> پرتەقاڵ</li>
|
||||
</ol>
|
||||
<h2 id="section-1" dir="rtl">جۆرەکانی میوە</h2>
|
||||
<ul dir="rtl">
|
||||
<li>توو
|
||||
<ul dir="rtl">
|
||||
<li>فڕاولە</li>
|
||||
<li>کیوی</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>مزرەمەنی
|
||||
<ul dir="rtl">
|
||||
<li>پڕتەقاڵ</li>
|
||||
<li>لیمۆ</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<h2 id="section-2" dir="rtl">لیستی کڕین</h2>
|
||||
<ul class="contains-task-list" dir="rtl">
|
||||
<li class="task-list-item"><input disabled="disabled" type="checkbox" checked="checked" /> شووتی</li>
|
||||
<li class="task-list-item"><input disabled="disabled" type="checkbox" checked="checked" /> قەیسی</li>
|
||||
<li class="task-list-item"><input disabled="disabled" type="checkbox" /> هەنجیر</li>
|
||||
</ul>
|
||||
````````````````````````````````
|
||||
|
||||
Tables:
|
||||
|
||||
```````````````````````````````` example
|
||||
Nuitrion |Apple | Oranges
|
||||
--|-- | --
|
||||
Calories|52|47
|
||||
Sugar|10g|9g
|
||||
|
||||
پێکهاتە |سێو | پڕتەقاڵ
|
||||
--|-- | --
|
||||
کالۆری|٥٢|٤٧
|
||||
شەکر| ١٠گ|٩گ
|
||||
.
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Nuitrion</th>
|
||||
<th>Apple</th>
|
||||
<th>Oranges</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Calories</td>
|
||||
<td>52</td>
|
||||
<td>47</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Sugar</td>
|
||||
<td>10g</td>
|
||||
<td>9g</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<table dir="rtl" align="right">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>پێکهاتە</th>
|
||||
<th>سێو</th>
|
||||
<th>پڕتەقاڵ</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>کالۆری</td>
|
||||
<td>٥٢</td>
|
||||
<td>٤٧</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>شەکر</td>
|
||||
<td>١٠گ</td>
|
||||
<td>٩گ</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
````````````````````````````````
|
||||
@@ -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>
|
||||
````````````````````````````````
|
||||
File diff suppressed because it is too large
Load Diff
@@ -63,10 +63,12 @@ SOFTWARE.
|
||||
new KeyValuePair<string, string>(Host.ResolvePath("YamlSpecs.md"), "yaml"),
|
||||
new KeyValuePair<string, string>(Host.ResolvePath("AutoLinks.md"), "autolinks|advanced"),
|
||||
new KeyValuePair<string, string>(Host.ResolvePath("JiraLinks.md"), "jiralinks"),
|
||||
new KeyValuePair<string, string>(Host.ResolvePath("GlobalizationSpecs.md"), "globalization+advanced+emojis"),
|
||||
};
|
||||
var emptyLines = false;
|
||||
var displayEmptyLines = false;
|
||||
#>
|
||||
// Generated the <#= DateTime.Now #>
|
||||
using System;
|
||||
using NUnit.Framework;
|
||||
|
||||
@@ -148,7 +150,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); }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -47,6 +47,7 @@ It can end with three dots `...`:
|
||||
```````````````````````````````` example
|
||||
---
|
||||
this: is a frontmatter
|
||||
|
||||
...
|
||||
This is a text
|
||||
.
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
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 + "\""));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -411,6 +411,28 @@ This is a last line";
|
||||
|
||||
|
||||
|
||||
[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();
|
||||
@@ -441,6 +463,8 @@ This is a last line";
|
||||
expected = NormText(expected, trim);
|
||||
|
||||
var pipeline = new MarkdownPipelineBuilder()
|
||||
.UseAutoLinks()
|
||||
.UseJiraLinks(new Extensions.JiraLinks.JiraLinkOptions("https://jira.example.com"))
|
||||
.UseTaskLists()
|
||||
.Build();
|
||||
|
||||
|
||||
@@ -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,14 @@ 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()
|
||||
{
|
||||
|
||||
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.
|
||||
|
||||
@@ -6,6 +6,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Markdig.Extensions.Footnotes;
|
||||
using Markdig.Helpers;
|
||||
using Markdig.Renderers.Html;
|
||||
using Markdig.Syntax;
|
||||
@@ -140,6 +141,48 @@ literal ( 0, 2) 2-3
|
||||
emphasis ( 0, 4) 4-9
|
||||
literal ( 0, 6) 6-7
|
||||
");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestFootnoteLinkReferenceDefinition()
|
||||
{
|
||||
// 01 2 345678
|
||||
var footnote = Markdown.Parse("0\n\n [^1]:", new MarkdownPipelineBuilder().UsePreciseSourceLocation().UseFootnotes().Build()).Descendants().OfType<FootnoteLinkReferenceDefinition>().FirstOrDefault();
|
||||
Assert.NotNull(footnote);
|
||||
|
||||
Assert.AreEqual(2, footnote.Line);
|
||||
Assert.AreEqual(new SourceSpan(4, 7), footnote.Span);
|
||||
Assert.AreEqual(new SourceSpan(5, 6), footnote.LabelSpan);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestLinkReferenceDefinition1()
|
||||
{
|
||||
// 0 1
|
||||
// 0123456789012345
|
||||
var link = Markdown.Parse("[234]: /56 'yo' ", new MarkdownPipelineBuilder().UsePreciseSourceLocation().Build()).Descendants().OfType<LinkReferenceDefinition>().FirstOrDefault();
|
||||
Assert.NotNull(link);
|
||||
|
||||
Assert.AreEqual(0, link.Line);
|
||||
Assert.AreEqual(new SourceSpan(0, 14), link.Span);
|
||||
Assert.AreEqual(new SourceSpan(1, 3), link.LabelSpan);
|
||||
Assert.AreEqual(new SourceSpan(7, 9), link.UrlSpan);
|
||||
Assert.AreEqual(new SourceSpan(11, 14), link.TitleSpan);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestLinkReferenceDefinition2()
|
||||
{
|
||||
// 0 1
|
||||
// 01 2 34567890123456789
|
||||
var link = Markdown.Parse("0\n\n [234]: /56 'yo' ", new MarkdownPipelineBuilder().UsePreciseSourceLocation().Build()).Descendants().OfType<LinkReferenceDefinition>().FirstOrDefault();
|
||||
Assert.NotNull(link);
|
||||
|
||||
Assert.AreEqual(2, link.Line);
|
||||
Assert.AreEqual(new SourceSpan(4, 18), link.Span);
|
||||
Assert.AreEqual(new SourceSpan(5, 7), link.LabelSpan);
|
||||
Assert.AreEqual(new SourceSpan(11, 13), link.UrlSpan);
|
||||
Assert.AreEqual(new SourceSpan(15, 18), link.TitleSpan);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -353,6 +396,42 @@ listitem ( 1, 0) 4-6
|
||||
paragraph ( 1, 2) 6-6
|
||||
literal ( 1, 2) 6-6
|
||||
");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestListBlock2()
|
||||
{
|
||||
string test = @"
|
||||
1. Foo
|
||||
9. Bar
|
||||
5. Foo
|
||||
6. Bar
|
||||
987123. FooBar";
|
||||
test = test.Replace("\r\n", "\n");
|
||||
var list = Markdown.Parse(test, new MarkdownPipelineBuilder().UsePreciseSourceLocation().Build()).Descendants().OfType<ListBlock>().FirstOrDefault();
|
||||
Assert.NotNull(list);
|
||||
|
||||
Assert.AreEqual(1, list.Line);
|
||||
Assert.True(list.IsOrdered);
|
||||
List<ListItemBlock> items = list.Cast<ListItemBlock>().ToList();
|
||||
Assert.AreEqual(5, items.Count);
|
||||
|
||||
// Test orders
|
||||
Assert.AreEqual(1, items[0].Order);
|
||||
Assert.AreEqual(9, items[1].Order);
|
||||
Assert.AreEqual(5, items[2].Order);
|
||||
Assert.AreEqual(6, items[3].Order);
|
||||
Assert.AreEqual(987123, items[4].Order);
|
||||
|
||||
// Test positions
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
Assert.AreEqual(i + 1, items[i].Line);
|
||||
Assert.AreEqual(1 + (i * 7), items[i].Span.Start);
|
||||
Assert.AreEqual(6, items[i].Span.Length);
|
||||
}
|
||||
Assert.AreEqual(5, items[4].Line);
|
||||
Assert.AreEqual(new SourceSpan(29, 42), items[4].Span);
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
||||
@@ -19,6 +19,6 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"NUnit": "3.2.0",
|
||||
"NUnit3TestAdapter": "3.2.0"
|
||||
"NUnit3TestAdapter": "3.9.0"
|
||||
}
|
||||
}
|
||||
9
src/Markdig/Directory.Build.props
Normal file
9
src/Markdig/Directory.Build.props
Normal file
@@ -0,0 +1,9 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
||||
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0-*" PrivateAssets="All"/>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -1,201 +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;
|
||||
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
|
||||
};
|
||||
|
||||
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 a HtmlRenderer with
|
||||
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;
|
||||
}
|
||||
}
|
||||
// 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,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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -158,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()
|
||||
@@ -166,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,8 +5,8 @@
|
||||
using Markdig.Helpers;
|
||||
using Markdig.Parsers;
|
||||
using Markdig.Syntax;
|
||||
using Markdig.Syntax.Inlines;
|
||||
|
||||
using Markdig.Syntax.Inlines;
|
||||
|
||||
namespace Markdig.Extensions.Footnotes
|
||||
{
|
||||
/// <summary>
|
||||
@@ -33,7 +33,8 @@ namespace Markdig.Extensions.Footnotes
|
||||
private BlockState TryOpen(BlockProcessor processor, bool isContinue)
|
||||
{
|
||||
// We expect footnote to appear only at document level and not indented more than a code indent block
|
||||
if (processor.IsCodeIndent || (!isContinue && processor.CurrentContainer.GetType() != typeof(MarkdownDocument)) || (isContinue && !(processor.CurrentContainer is Footnote)))
|
||||
var currentContainer = processor.GetCurrentContainerOpened();
|
||||
if (processor.IsCodeIndent || (!isContinue && currentContainer.GetType() != typeof(MarkdownDocument)) || (isContinue && !(currentContainer is FootnoteGroup)))
|
||||
{
|
||||
return BlockState.None;
|
||||
}
|
||||
@@ -74,7 +75,11 @@ namespace Markdig.Extensions.Footnotes
|
||||
var linkRef = new FootnoteLinkReferenceDefinition()
|
||||
{
|
||||
Footnote = footnote,
|
||||
CreateLinkInline = CreateLinkToFootnote
|
||||
CreateLinkInline = CreateLinkToFootnote,
|
||||
Line = processor.LineIndex,
|
||||
Span = new SourceSpan(start, processor.Start - 2), // account for ]:
|
||||
LabelSpan = labelSpan,
|
||||
Label = label
|
||||
};
|
||||
processor.Document.SetLinkReferenceDefinition(footnote.Label, linkRef);
|
||||
processor.NewBlocks.Push(footnote);
|
||||
|
||||
108
src/Markdig/Extensions/Globalization/GlobalizationExtension.cs
Normal file
108
src/Markdig/Extensions/Globalization/GlobalizationExtension.cs
Normal file
@@ -0,0 +1,108 @@
|
||||
// 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.Extensions.Tables;
|
||||
using Markdig.Extensions.TaskLists;
|
||||
using Markdig.Helpers;
|
||||
using Markdig.Renderers;
|
||||
using Markdig.Renderers.Html;
|
||||
using Markdig.Syntax;
|
||||
using Markdig.Syntax.Inlines;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Markdig.Extensions.Globalization
|
||||
{
|
||||
/// <summary>
|
||||
/// Extension to add support for RTL content.
|
||||
/// </summary>
|
||||
public class GlobalizationExtension : IMarkdownExtension
|
||||
{
|
||||
|
||||
public void Setup(MarkdownPipelineBuilder pipeline)
|
||||
{
|
||||
// Make sure we don't have a delegate twice
|
||||
pipeline.DocumentProcessed -= Pipeline_DocumentProcessed;
|
||||
pipeline.DocumentProcessed += Pipeline_DocumentProcessed;
|
||||
}
|
||||
|
||||
private void Pipeline_DocumentProcessed(MarkdownDocument document)
|
||||
{
|
||||
foreach (var node in document.Descendants())
|
||||
{
|
||||
if (node is TableRow || node is TableCell || node is ListItemBlock)
|
||||
continue;
|
||||
|
||||
if (ShouldBeRightToLeft(node))
|
||||
{
|
||||
var attributes = node.GetAttributes();
|
||||
attributes.AddPropertyIfNotExist("dir", "rtl");
|
||||
|
||||
if (node is Table table)
|
||||
{
|
||||
attributes.AddPropertyIfNotExist("align", "right");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private bool ShouldBeRightToLeft(MarkdownObject item)
|
||||
{
|
||||
if (item is IEnumerable<MarkdownObject> container)
|
||||
{
|
||||
foreach (var child in container)
|
||||
{
|
||||
// TaskList items contain an "X", which will cause
|
||||
// the function to always return false.
|
||||
if (child is TaskList)
|
||||
continue;
|
||||
|
||||
return ShouldBeRightToLeft(child);
|
||||
}
|
||||
}
|
||||
else if (item is LeafBlock leaf)
|
||||
{
|
||||
return ShouldBeRightToLeft(leaf.Inline);
|
||||
}
|
||||
else if (item is LiteralInline literal)
|
||||
{
|
||||
return StartsWithRtlCharacter(literal.ToString());
|
||||
}
|
||||
|
||||
foreach (var descendant in item.Descendants())
|
||||
{
|
||||
if (descendant is ParagraphBlock p)
|
||||
{
|
||||
foreach (var i in p.Inline)
|
||||
{
|
||||
if (i is LiteralInline l)
|
||||
{
|
||||
return StartsWithRtlCharacter(l.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool StartsWithRtlCharacter(string text)
|
||||
{
|
||||
foreach (var c in CharHelper.ToUtf32(text))
|
||||
{
|
||||
if (CharHelper.IsRightToLeft(c))
|
||||
return true;
|
||||
|
||||
else if (CharHelper.IsLeftToRight(c))
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -82,10 +82,10 @@ namespace Markdig.Extensions.Yaml
|
||||
{
|
||||
c = fullLine.NextChar();
|
||||
}
|
||||
nc = fullLine.NextChar(); // skip \n
|
||||
nc = fullLine.PeekChar();
|
||||
if (nc == '-')
|
||||
{
|
||||
if (fullLine.NextChar() == '-' && fullLine.NextChar() == '-' && (fullLine.NextChar() == '\0' || fullLine.SkipSpacesToEndOfLineOrEndOfDocument()))
|
||||
if (fullLine.NextChar() == '-' && fullLine.NextChar() == '-' && fullLine.NextChar() == '-' && (fullLine.NextChar() == '\0' || fullLine.SkipSpacesToEndOfLineOrEndOfDocument()))
|
||||
{
|
||||
hasFullYamlFrontMatter = true;
|
||||
break;
|
||||
@@ -93,7 +93,7 @@ namespace Markdig.Extensions.Yaml
|
||||
}
|
||||
else if (nc == '.')
|
||||
{
|
||||
if (fullLine.NextChar() == '.' && fullLine.NextChar() == '.' && (fullLine.NextChar() == '\0' || fullLine.SkipSpacesToEndOfLineOrEndOfDocument()))
|
||||
if (fullLine.NextChar() == '.' && fullLine.NextChar() == '.' && fullLine.NextChar() == '.' && (fullLine.NextChar() == '\0' || fullLine.SkipSpacesToEndOfLineOrEndOfDocument()))
|
||||
{
|
||||
hasFullYamlFrontMatter = true;
|
||||
break;
|
||||
|
||||
@@ -1,6 +1,37 @@
|
||||
// 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.
|
||||
|
||||
|
||||
// The IsHighSurrogate, IsLowSurrogate and ConvertToUtf32 methods are copied from
|
||||
// .Net Core source code which is under MIT license. They are copied here because
|
||||
// they don't exist in `portable40-net40+sl5+win8+wp8+wpa81`, We probably should remove them
|
||||
// once we dropped support for that target platform and use the official .Net methods.
|
||||
|
||||
//The MIT License(MIT)
|
||||
|
||||
//Copyright(c) .NET Foundation and Contributors
|
||||
|
||||
//All rights reserved.
|
||||
|
||||
//Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
//of this software and associated documentation files (the "Software"), to deal
|
||||
//in the Software without restriction, including without limitation the rights
|
||||
//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
//copies of the Software, and to permit persons to whom the Software is
|
||||
//furnished to do so, subject to the following conditions:
|
||||
|
||||
//The above copyright notice and this permission notice shall be included in all
|
||||
//copies or substantial portions of the Software.
|
||||
|
||||
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
//SOFTWARE.
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Runtime.CompilerServices;
|
||||
@@ -20,8 +51,16 @@ namespace Markdig.Helpers
|
||||
|
||||
public const string ZeroSafeString = "\uFFFD";
|
||||
|
||||
private const char HighSurrogateStart = '\ud800';
|
||||
private const char HighSurrogateEnd = '\udbff';
|
||||
private const char LowSurrogateStart = '\udc00';
|
||||
private const char LowSurrogateEnd = '\udfff';
|
||||
|
||||
// The starting codepoint for Unicode plane 1. Plane 1 contains 0x010000 ~ 0x01ffff.
|
||||
private const int UnicodePlane01Start = 0x10000;
|
||||
|
||||
// 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 = { '−', '-', '†', '‡' };
|
||||
|
||||
@@ -306,5 +345,419 @@ namespace Markdig.Helpers
|
||||
{
|
||||
return ".!#$%&'*+/=?^_`{|}~-+.~".IndexOf(c) >= 0;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
|
||||
public static bool IsHighSurrogate(char c)
|
||||
{
|
||||
return ((c >= HighSurrogateStart) && (c <= HighSurrogateEnd));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
|
||||
public static bool IsLowSurrogate(char c)
|
||||
{
|
||||
return ((c >= LowSurrogateStart) && (c <= LowSurrogateEnd));
|
||||
}
|
||||
|
||||
public static int ConvertToUtf32(char highSurrogate, char lowSurrogate)
|
||||
{
|
||||
if (!IsHighSurrogate(highSurrogate))
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(highSurrogate), "Invalid high surrogate");
|
||||
}
|
||||
if (!IsLowSurrogate(lowSurrogate))
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(lowSurrogate), "Invalid low surrogate");
|
||||
}
|
||||
return (((highSurrogate - HighSurrogateStart) * 0x400) + (lowSurrogate - LowSurrogateStart) + UnicodePlane01Start);
|
||||
}
|
||||
|
||||
public static IEnumerable<int> ToUtf32(string text)
|
||||
{
|
||||
for (int i = 0; i < text.Length; i++)
|
||||
{
|
||||
if (IsHighSurrogate(text[i]) && i < text.Length - 1 && IsLowSurrogate(text[i + 1]))
|
||||
{
|
||||
yield return ConvertToUtf32(text[i], text[i + 1]);
|
||||
}
|
||||
else
|
||||
{
|
||||
yield return text[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsRightToLeft(int c)
|
||||
{
|
||||
// Generated from Table D.1 of RFC3454
|
||||
// http://www.ietf.org/rfc/rfc3454.txt
|
||||
|
||||
// Probably should use a binary search approach
|
||||
|
||||
return c >= 0x0005D0 && c <= 0x0005EA ||
|
||||
c >= 0x0005F0 && c <= 0x0005F4 ||
|
||||
c >= 0x000621 && c <= 0x00063A ||
|
||||
c >= 0x000640 && c <= 0x00064A ||
|
||||
c >= 0x00066D && c <= 0x00066F ||
|
||||
c >= 0x000671 && c <= 0x0006D5 ||
|
||||
c >= 0x0006E5 && c <= 0x0006E6 ||
|
||||
c >= 0x0006FA && c <= 0x0006FE ||
|
||||
c >= 0x000700 && c <= 0x00070D ||
|
||||
c >= 0x000712 && c <= 0x00072C ||
|
||||
c >= 0x000780 && c <= 0x0007A5 ||
|
||||
c >= 0x00FB1F && c <= 0x00FB28 ||
|
||||
c >= 0x00FB2A && c <= 0x00FB36 ||
|
||||
c >= 0x00FB38 && c <= 0x00FB3C ||
|
||||
c >= 0x00FB40 && c <= 0x00FB41 ||
|
||||
c >= 0x00FB43 && c <= 0x00FB44 ||
|
||||
c >= 0x00FB46 && c <= 0x00FBB1 ||
|
||||
c >= 0x00FBD3 && c <= 0x00FD3D ||
|
||||
c >= 0x00FD50 && c <= 0x00FD8F ||
|
||||
c >= 0x00FD92 && c <= 0x00FDC7 ||
|
||||
c >= 0x00FDF0 && c <= 0x00FDFC ||
|
||||
c >= 0x00FE70 && c <= 0x00FE74 ||
|
||||
c >= 0x00FE76 && c <= 0x00FEFC ||
|
||||
c == 0x0005BE || c == 0x0005C0 ||
|
||||
c == 0x0005C3 || c == 0x00061B ||
|
||||
c == 0x00061F || c == 0x0006DD ||
|
||||
c == 0x000710 || c == 0x0007B1 ||
|
||||
c == 0x00200F || c == 0x00FB1D ||
|
||||
c == 0x00FB3E;
|
||||
}
|
||||
|
||||
public static bool IsLeftToRight(int c)
|
||||
{
|
||||
// Generated from Table D.2 of RFC3454
|
||||
// http://www.ietf.org/rfc/rfc3454.txt
|
||||
|
||||
// Probably should use a binary search approach
|
||||
|
||||
return c >= 0x000041 && c <= 0x00005A ||
|
||||
c >= 0x000061 && c <= 0x00007A ||
|
||||
c >= 0x0000C0 && c <= 0x0000D6 ||
|
||||
c >= 0x0000D8 && c <= 0x0000F6 ||
|
||||
c >= 0x0000F8 && c <= 0x000220 ||
|
||||
c >= 0x000222 && c <= 0x000233 ||
|
||||
c >= 0x000250 && c <= 0x0002AD ||
|
||||
c >= 0x0002B0 && c <= 0x0002B8 ||
|
||||
c >= 0x0002BB && c <= 0x0002C1 ||
|
||||
c >= 0x0002D0 && c <= 0x0002D1 ||
|
||||
c >= 0x0002E0 && c <= 0x0002E4 ||
|
||||
c >= 0x000388 && c <= 0x00038A ||
|
||||
c >= 0x00038E && c <= 0x0003A1 ||
|
||||
c >= 0x0003A3 && c <= 0x0003CE ||
|
||||
c >= 0x0003D0 && c <= 0x0003F5 ||
|
||||
c >= 0x000400 && c <= 0x000482 ||
|
||||
c >= 0x00048A && c <= 0x0004CE ||
|
||||
c >= 0x0004D0 && c <= 0x0004F5 ||
|
||||
c >= 0x0004F8 && c <= 0x0004F9 ||
|
||||
c >= 0x000500 && c <= 0x00050F ||
|
||||
c >= 0x000531 && c <= 0x000556 ||
|
||||
c >= 0x000559 && c <= 0x00055F ||
|
||||
c >= 0x000561 && c <= 0x000587 ||
|
||||
c >= 0x000905 && c <= 0x000939 ||
|
||||
c >= 0x00093D && c <= 0x000940 ||
|
||||
c >= 0x000949 && c <= 0x00094C ||
|
||||
c >= 0x000958 && c <= 0x000961 ||
|
||||
c >= 0x000964 && c <= 0x000970 ||
|
||||
c >= 0x000982 && c <= 0x000983 ||
|
||||
c >= 0x000985 && c <= 0x00098C ||
|
||||
c >= 0x00098F && c <= 0x000990 ||
|
||||
c >= 0x000993 && c <= 0x0009A8 ||
|
||||
c >= 0x0009AA && c <= 0x0009B0 ||
|
||||
c >= 0x0009B6 && c <= 0x0009B9 ||
|
||||
c >= 0x0009BE && c <= 0x0009C0 ||
|
||||
c >= 0x0009C7 && c <= 0x0009C8 ||
|
||||
c >= 0x0009CB && c <= 0x0009CC ||
|
||||
c >= 0x0009DC && c <= 0x0009DD ||
|
||||
c >= 0x0009DF && c <= 0x0009E1 ||
|
||||
c >= 0x0009E6 && c <= 0x0009F1 ||
|
||||
c >= 0x0009F4 && c <= 0x0009FA ||
|
||||
c >= 0x000A05 && c <= 0x000A0A ||
|
||||
c >= 0x000A0F && c <= 0x000A10 ||
|
||||
c >= 0x000A13 && c <= 0x000A28 ||
|
||||
c >= 0x000A2A && c <= 0x000A30 ||
|
||||
c >= 0x000A32 && c <= 0x000A33 ||
|
||||
c >= 0x000A35 && c <= 0x000A36 ||
|
||||
c >= 0x000A38 && c <= 0x000A39 ||
|
||||
c >= 0x000A3E && c <= 0x000A40 ||
|
||||
c >= 0x000A59 && c <= 0x000A5C ||
|
||||
c >= 0x000A66 && c <= 0x000A6F ||
|
||||
c >= 0x000A72 && c <= 0x000A74 ||
|
||||
c >= 0x000A85 && c <= 0x000A8B ||
|
||||
c >= 0x000A8F && c <= 0x000A91 ||
|
||||
c >= 0x000A93 && c <= 0x000AA8 ||
|
||||
c >= 0x000AAA && c <= 0x000AB0 ||
|
||||
c >= 0x000AB2 && c <= 0x000AB3 ||
|
||||
c >= 0x000AB5 && c <= 0x000AB9 ||
|
||||
c >= 0x000ABD && c <= 0x000AC0 ||
|
||||
c >= 0x000ACB && c <= 0x000ACC ||
|
||||
c >= 0x000AE6 && c <= 0x000AEF ||
|
||||
c >= 0x000B02 && c <= 0x000B03 ||
|
||||
c >= 0x000B05 && c <= 0x000B0C ||
|
||||
c >= 0x000B0F && c <= 0x000B10 ||
|
||||
c >= 0x000B13 && c <= 0x000B28 ||
|
||||
c >= 0x000B2A && c <= 0x000B30 ||
|
||||
c >= 0x000B32 && c <= 0x000B33 ||
|
||||
c >= 0x000B36 && c <= 0x000B39 ||
|
||||
c >= 0x000B3D && c <= 0x000B3E ||
|
||||
c >= 0x000B47 && c <= 0x000B48 ||
|
||||
c >= 0x000B4B && c <= 0x000B4C ||
|
||||
c >= 0x000B5C && c <= 0x000B5D ||
|
||||
c >= 0x000B5F && c <= 0x000B61 ||
|
||||
c >= 0x000B66 && c <= 0x000B70 ||
|
||||
c >= 0x000B85 && c <= 0x000B8A ||
|
||||
c >= 0x000B8E && c <= 0x000B90 ||
|
||||
c >= 0x000B92 && c <= 0x000B95 ||
|
||||
c >= 0x000B99 && c <= 0x000B9A ||
|
||||
c >= 0x000B9E && c <= 0x000B9F ||
|
||||
c >= 0x000BA3 && c <= 0x000BA4 ||
|
||||
c >= 0x000BA8 && c <= 0x000BAA ||
|
||||
c >= 0x000BAE && c <= 0x000BB5 ||
|
||||
c >= 0x000BB7 && c <= 0x000BB9 ||
|
||||
c >= 0x000BBE && c <= 0x000BBF ||
|
||||
c >= 0x000BC1 && c <= 0x000BC2 ||
|
||||
c >= 0x000BC6 && c <= 0x000BC8 ||
|
||||
c >= 0x000BCA && c <= 0x000BCC ||
|
||||
c >= 0x000BE7 && c <= 0x000BF2 ||
|
||||
c >= 0x000C01 && c <= 0x000C03 ||
|
||||
c >= 0x000C05 && c <= 0x000C0C ||
|
||||
c >= 0x000C0E && c <= 0x000C10 ||
|
||||
c >= 0x000C12 && c <= 0x000C28 ||
|
||||
c >= 0x000C2A && c <= 0x000C33 ||
|
||||
c >= 0x000C35 && c <= 0x000C39 ||
|
||||
c >= 0x000C41 && c <= 0x000C44 ||
|
||||
c >= 0x000C60 && c <= 0x000C61 ||
|
||||
c >= 0x000C66 && c <= 0x000C6F ||
|
||||
c >= 0x000C82 && c <= 0x000C83 ||
|
||||
c >= 0x000C85 && c <= 0x000C8C ||
|
||||
c >= 0x000C8E && c <= 0x000C90 ||
|
||||
c >= 0x000C92 && c <= 0x000CA8 ||
|
||||
c >= 0x000CAA && c <= 0x000CB3 ||
|
||||
c >= 0x000CB5 && c <= 0x000CB9 ||
|
||||
c >= 0x000CC0 && c <= 0x000CC4 ||
|
||||
c >= 0x000CC7 && c <= 0x000CC8 ||
|
||||
c >= 0x000CCA && c <= 0x000CCB ||
|
||||
c >= 0x000CD5 && c <= 0x000CD6 ||
|
||||
c >= 0x000CE0 && c <= 0x000CE1 ||
|
||||
c >= 0x000CE6 && c <= 0x000CEF ||
|
||||
c >= 0x000D02 && c <= 0x000D03 ||
|
||||
c >= 0x000D05 && c <= 0x000D0C ||
|
||||
c >= 0x000D0E && c <= 0x000D10 ||
|
||||
c >= 0x000D12 && c <= 0x000D28 ||
|
||||
c >= 0x000D2A && c <= 0x000D39 ||
|
||||
c >= 0x000D3E && c <= 0x000D40 ||
|
||||
c >= 0x000D46 && c <= 0x000D48 ||
|
||||
c >= 0x000D4A && c <= 0x000D4C ||
|
||||
c >= 0x000D60 && c <= 0x000D61 ||
|
||||
c >= 0x000D66 && c <= 0x000D6F ||
|
||||
c >= 0x000D82 && c <= 0x000D83 ||
|
||||
c >= 0x000D85 && c <= 0x000D96 ||
|
||||
c >= 0x000D9A && c <= 0x000DB1 ||
|
||||
c >= 0x000DB3 && c <= 0x000DBB ||
|
||||
c >= 0x000DC0 && c <= 0x000DC6 ||
|
||||
c >= 0x000DCF && c <= 0x000DD1 ||
|
||||
c >= 0x000DD8 && c <= 0x000DDF ||
|
||||
c >= 0x000DF2 && c <= 0x000DF4 ||
|
||||
c >= 0x000E01 && c <= 0x000E30 ||
|
||||
c >= 0x000E32 && c <= 0x000E33 ||
|
||||
c >= 0x000E40 && c <= 0x000E46 ||
|
||||
c >= 0x000E4F && c <= 0x000E5B ||
|
||||
c >= 0x000E81 && c <= 0x000E82 ||
|
||||
c >= 0x000E87 && c <= 0x000E88 ||
|
||||
c >= 0x000E94 && c <= 0x000E97 ||
|
||||
c >= 0x000E99 && c <= 0x000E9F ||
|
||||
c >= 0x000EA1 && c <= 0x000EA3 ||
|
||||
c >= 0x000EAA && c <= 0x000EAB ||
|
||||
c >= 0x000EAD && c <= 0x000EB0 ||
|
||||
c >= 0x000EB2 && c <= 0x000EB3 ||
|
||||
c >= 0x000EC0 && c <= 0x000EC4 ||
|
||||
c >= 0x000ED0 && c <= 0x000ED9 ||
|
||||
c >= 0x000EDC && c <= 0x000EDD ||
|
||||
c >= 0x000F00 && c <= 0x000F17 ||
|
||||
c >= 0x000F1A && c <= 0x000F34 ||
|
||||
c >= 0x000F3E && c <= 0x000F47 ||
|
||||
c >= 0x000F49 && c <= 0x000F6A ||
|
||||
c >= 0x000F88 && c <= 0x000F8B ||
|
||||
c >= 0x000FBE && c <= 0x000FC5 ||
|
||||
c >= 0x000FC7 && c <= 0x000FCC ||
|
||||
c >= 0x001000 && c <= 0x001021 ||
|
||||
c >= 0x001023 && c <= 0x001027 ||
|
||||
c >= 0x001029 && c <= 0x00102A ||
|
||||
c >= 0x001040 && c <= 0x001057 ||
|
||||
c >= 0x0010A0 && c <= 0x0010C5 ||
|
||||
c >= 0x0010D0 && c <= 0x0010F8 ||
|
||||
c >= 0x001100 && c <= 0x001159 ||
|
||||
c >= 0x00115F && c <= 0x0011A2 ||
|
||||
c >= 0x0011A8 && c <= 0x0011F9 ||
|
||||
c >= 0x001200 && c <= 0x001206 ||
|
||||
c >= 0x001208 && c <= 0x001246 ||
|
||||
c >= 0x00124A && c <= 0x00124D ||
|
||||
c >= 0x001250 && c <= 0x001256 ||
|
||||
c >= 0x00125A && c <= 0x00125D ||
|
||||
c >= 0x001260 && c <= 0x001286 ||
|
||||
c >= 0x00128A && c <= 0x00128D ||
|
||||
c >= 0x001290 && c <= 0x0012AE ||
|
||||
c >= 0x0012B2 && c <= 0x0012B5 ||
|
||||
c >= 0x0012B8 && c <= 0x0012BE ||
|
||||
c >= 0x0012C2 && c <= 0x0012C5 ||
|
||||
c >= 0x0012C8 && c <= 0x0012CE ||
|
||||
c >= 0x0012D0 && c <= 0x0012D6 ||
|
||||
c >= 0x0012D8 && c <= 0x0012EE ||
|
||||
c >= 0x0012F0 && c <= 0x00130E ||
|
||||
c >= 0x001312 && c <= 0x001315 ||
|
||||
c >= 0x001318 && c <= 0x00131E ||
|
||||
c >= 0x001320 && c <= 0x001346 ||
|
||||
c >= 0x001348 && c <= 0x00135A ||
|
||||
c >= 0x001361 && c <= 0x00137C ||
|
||||
c >= 0x0013A0 && c <= 0x0013F4 ||
|
||||
c >= 0x001401 && c <= 0x001676 ||
|
||||
c >= 0x001681 && c <= 0x00169A ||
|
||||
c >= 0x0016A0 && c <= 0x0016F0 ||
|
||||
c >= 0x001700 && c <= 0x00170C ||
|
||||
c >= 0x00170E && c <= 0x001711 ||
|
||||
c >= 0x001720 && c <= 0x001731 ||
|
||||
c >= 0x001735 && c <= 0x001736 ||
|
||||
c >= 0x001740 && c <= 0x001751 ||
|
||||
c >= 0x001760 && c <= 0x00176C ||
|
||||
c >= 0x00176E && c <= 0x001770 ||
|
||||
c >= 0x001780 && c <= 0x0017B6 ||
|
||||
c >= 0x0017BE && c <= 0x0017C5 ||
|
||||
c >= 0x0017C7 && c <= 0x0017C8 ||
|
||||
c >= 0x0017D4 && c <= 0x0017DA ||
|
||||
c >= 0x0017E0 && c <= 0x0017E9 ||
|
||||
c >= 0x001810 && c <= 0x001819 ||
|
||||
c >= 0x001820 && c <= 0x001877 ||
|
||||
c >= 0x001880 && c <= 0x0018A8 ||
|
||||
c >= 0x001E00 && c <= 0x001E9B ||
|
||||
c >= 0x001EA0 && c <= 0x001EF9 ||
|
||||
c >= 0x001F00 && c <= 0x001F15 ||
|
||||
c >= 0x001F18 && c <= 0x001F1D ||
|
||||
c >= 0x001F20 && c <= 0x001F45 ||
|
||||
c >= 0x001F48 && c <= 0x001F4D ||
|
||||
c >= 0x001F50 && c <= 0x001F57 ||
|
||||
c >= 0x001F5F && c <= 0x001F7D ||
|
||||
c >= 0x001F80 && c <= 0x001FB4 ||
|
||||
c >= 0x001FB6 && c <= 0x001FBC ||
|
||||
c >= 0x001FC2 && c <= 0x001FC4 ||
|
||||
c >= 0x001FC6 && c <= 0x001FCC ||
|
||||
c >= 0x001FD0 && c <= 0x001FD3 ||
|
||||
c >= 0x001FD6 && c <= 0x001FDB ||
|
||||
c >= 0x001FE0 && c <= 0x001FEC ||
|
||||
c >= 0x001FF2 && c <= 0x001FF4 ||
|
||||
c >= 0x001FF6 && c <= 0x001FFC ||
|
||||
c >= 0x00210A && c <= 0x002113 ||
|
||||
c >= 0x002119 && c <= 0x00211D ||
|
||||
c >= 0x00212A && c <= 0x00212D ||
|
||||
c >= 0x00212F && c <= 0x002131 ||
|
||||
c >= 0x002133 && c <= 0x002139 ||
|
||||
c >= 0x00213D && c <= 0x00213F ||
|
||||
c >= 0x002145 && c <= 0x002149 ||
|
||||
c >= 0x002160 && c <= 0x002183 ||
|
||||
c >= 0x002336 && c <= 0x00237A ||
|
||||
c >= 0x00249C && c <= 0x0024E9 ||
|
||||
c >= 0x003005 && c <= 0x003007 ||
|
||||
c >= 0x003021 && c <= 0x003029 ||
|
||||
c >= 0x003031 && c <= 0x003035 ||
|
||||
c >= 0x003038 && c <= 0x00303C ||
|
||||
c >= 0x003041 && c <= 0x003096 ||
|
||||
c >= 0x00309D && c <= 0x00309F ||
|
||||
c >= 0x0030A1 && c <= 0x0030FA ||
|
||||
c >= 0x0030FC && c <= 0x0030FF ||
|
||||
c >= 0x003105 && c <= 0x00312C ||
|
||||
c >= 0x003131 && c <= 0x00318E ||
|
||||
c >= 0x003190 && c <= 0x0031B7 ||
|
||||
c >= 0x0031F0 && c <= 0x00321C ||
|
||||
c >= 0x003220 && c <= 0x003243 ||
|
||||
c >= 0x003260 && c <= 0x00327B ||
|
||||
c >= 0x00327F && c <= 0x0032B0 ||
|
||||
c >= 0x0032C0 && c <= 0x0032CB ||
|
||||
c >= 0x0032D0 && c <= 0x0032FE ||
|
||||
c >= 0x003300 && c <= 0x003376 ||
|
||||
c >= 0x00337B && c <= 0x0033DD ||
|
||||
c >= 0x0033E0 && c <= 0x0033FE ||
|
||||
c >= 0x003400 && c <= 0x004DB5 ||
|
||||
c >= 0x004E00 && c <= 0x009FA5 ||
|
||||
c >= 0x00A000 && c <= 0x00A48C ||
|
||||
c >= 0x00AC00 && c <= 0x00D7A3 ||
|
||||
c >= 0x00D800 && c <= 0x00FA2D ||
|
||||
c >= 0x00FA30 && c <= 0x00FA6A ||
|
||||
c >= 0x00FB00 && c <= 0x00FB06 ||
|
||||
c >= 0x00FB13 && c <= 0x00FB17 ||
|
||||
c >= 0x00FF21 && c <= 0x00FF3A ||
|
||||
c >= 0x00FF41 && c <= 0x00FF5A ||
|
||||
c >= 0x00FF66 && c <= 0x00FFBE ||
|
||||
c >= 0x00FFC2 && c <= 0x00FFC7 ||
|
||||
c >= 0x00FFCA && c <= 0x00FFCF ||
|
||||
c >= 0x00FFD2 && c <= 0x00FFD7 ||
|
||||
c >= 0x00FFDA && c <= 0x00FFDC ||
|
||||
c >= 0x010300 && c <= 0x01031E ||
|
||||
c >= 0x010320 && c <= 0x010323 ||
|
||||
c >= 0x010330 && c <= 0x01034A ||
|
||||
c >= 0x010400 && c <= 0x010425 ||
|
||||
c >= 0x010428 && c <= 0x01044D ||
|
||||
c >= 0x01D000 && c <= 0x01D0F5 ||
|
||||
c >= 0x01D100 && c <= 0x01D126 ||
|
||||
c >= 0x01D12A && c <= 0x01D166 ||
|
||||
c >= 0x01D16A && c <= 0x01D172 ||
|
||||
c >= 0x01D183 && c <= 0x01D184 ||
|
||||
c >= 0x01D18C && c <= 0x01D1A9 ||
|
||||
c >= 0x01D1AE && c <= 0x01D1DD ||
|
||||
c >= 0x01D400 && c <= 0x01D454 ||
|
||||
c >= 0x01D456 && c <= 0x01D49C ||
|
||||
c >= 0x01D49E && c <= 0x01D49F ||
|
||||
c >= 0x01D4A5 && c <= 0x01D4A6 ||
|
||||
c >= 0x01D4A9 && c <= 0x01D4AC ||
|
||||
c >= 0x01D4AE && c <= 0x01D4B9 ||
|
||||
c >= 0x01D4BD && c <= 0x01D4C0 ||
|
||||
c >= 0x01D4C2 && c <= 0x01D4C3 ||
|
||||
c >= 0x01D4C5 && c <= 0x01D505 ||
|
||||
c >= 0x01D507 && c <= 0x01D50A ||
|
||||
c >= 0x01D50D && c <= 0x01D514 ||
|
||||
c >= 0x01D516 && c <= 0x01D51C ||
|
||||
c >= 0x01D51E && c <= 0x01D539 ||
|
||||
c >= 0x01D53B && c <= 0x01D53E ||
|
||||
c >= 0x01D540 && c <= 0x01D544 ||
|
||||
c >= 0x01D54A && c <= 0x01D550 ||
|
||||
c >= 0x01D552 && c <= 0x01D6A3 ||
|
||||
c >= 0x01D6A8 && c <= 0x01D7C9 ||
|
||||
c >= 0x020000 && c <= 0x02A6D6 ||
|
||||
c >= 0x02F800 && c <= 0x02FA1D ||
|
||||
c >= 0x0F0000 && c <= 0x0FFFFD ||
|
||||
c >= 0x100000 && c <= 0x10FFFD ||
|
||||
c == 0x0000AA || c == 0x0000B5 ||
|
||||
c == 0x0000BA || c == 0x0002EE ||
|
||||
c == 0x00037A || c == 0x000386 ||
|
||||
c == 0x00038C || c == 0x000589 ||
|
||||
c == 0x000903 || c == 0x000950 ||
|
||||
c == 0x0009B2 || c == 0x0009D7 ||
|
||||
c == 0x000A5E || c == 0x000A83 ||
|
||||
c == 0x000A8D || c == 0x000AC9 ||
|
||||
c == 0x000AD0 || c == 0x000AE0 ||
|
||||
c == 0x000B40 || c == 0x000B57 ||
|
||||
c == 0x000B83 || c == 0x000B9C ||
|
||||
c == 0x000BD7 || c == 0x000CBE ||
|
||||
c == 0x000CDE || c == 0x000D57 ||
|
||||
c == 0x000DBD || c == 0x000E84 ||
|
||||
c == 0x000E8A || c == 0x000E8D ||
|
||||
c == 0x000EA5 || c == 0x000EA7 ||
|
||||
c == 0x000EBD || c == 0x000EC6 ||
|
||||
c == 0x000F36 || c == 0x000F38 ||
|
||||
c == 0x000F7F || c == 0x000F85 ||
|
||||
c == 0x000FCF || c == 0x00102C ||
|
||||
c == 0x001031 || c == 0x001038 ||
|
||||
c == 0x0010FB || c == 0x001248 ||
|
||||
c == 0x001258 || c == 0x001288 ||
|
||||
c == 0x0012B0 || c == 0x0012C0 ||
|
||||
c == 0x001310 || c == 0x0017DC ||
|
||||
c == 0x001F59 || c == 0x001F5B ||
|
||||
c == 0x001F5D || c == 0x001FBE ||
|
||||
c == 0x00200E || c == 0x002071 ||
|
||||
c == 0x00207F || c == 0x002102 ||
|
||||
c == 0x002107 || c == 0x002115 ||
|
||||
c == 0x002124 || c == 0x002126 ||
|
||||
c == 0x002128 || c == 0x002395 ||
|
||||
c == 0x01D4A2 || c == 0x01D4BB ||
|
||||
c == 0x01D546;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
<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.14.5</VersionPrefix>
|
||||
<VersionPrefix>0.15.3</VersionPrefix>
|
||||
<Authors>Alexandre Mutel</Authors>
|
||||
<TargetFrameworks>net35;net40;portable40-net40+sl5+win8+wp8+wpa81;netstandard1.1;uap10.0</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>
|
||||
@@ -51,6 +51,10 @@
|
||||
<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>
|
||||
|
||||
@@ -29,9 +29,11 @@ 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;
|
||||
using Markdig.Extensions.Globalization;
|
||||
|
||||
namespace Markdig
|
||||
{
|
||||
@@ -51,6 +53,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>
|
||||
@@ -446,6 +461,17 @@ namespace Markdig
|
||||
return pipeline;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds support for right-to-left content by adding appropriate html attribtues.
|
||||
/// </summary>
|
||||
/// <param name="pipeline">The pipeline</param>
|
||||
/// <returns>The modified pipeline</returns>
|
||||
public static MarkdownPipelineBuilder UseGlobalization(this MarkdownPipelineBuilder pipeline)
|
||||
{
|
||||
pipeline.Extensions.AddIfNotAlready<GlobalizationExtension>();
|
||||
return pipeline;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This will disable the HTML support in the markdown processor (for constraint/safe parsing).
|
||||
/// </summary>
|
||||
@@ -569,11 +595,26 @@ namespace Markdig
|
||||
case "autolinks":
|
||||
pipeline.UseAutoLinks();
|
||||
break;
|
||||
case "globalization":
|
||||
pipeline.UseGlobalization();
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException($"Invalid extension `{extension}` from `{extensions}`", nameof(extensions));
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -116,9 +116,16 @@ namespace Markdig
|
||||
extension.Setup(this);
|
||||
}
|
||||
|
||||
pipeline = new MarkdownPipeline(new OrderedList<IMarkdownExtension>(Extensions),
|
||||
new BlockParserList(BlockParsers), new InlineParserList(InlineParsers), StringBuilderCache, DebugLog,
|
||||
GetDocumentProcessed) {PreciseSourceLocation = PreciseSourceLocation};
|
||||
pipeline = new MarkdownPipeline(
|
||||
new OrderedList<IMarkdownExtension>(Extensions),
|
||||
new BlockParserList(BlockParsers),
|
||||
new InlineParserList(InlineParsers),
|
||||
StringBuilderCache,
|
||||
DebugLog,
|
||||
GetDocumentProcessed)
|
||||
{
|
||||
PreciseSourceLocation = PreciseSourceLocation
|
||||
};
|
||||
return pipeline;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -162,6 +162,21 @@ namespace Markdig.Parsers
|
||||
/// </summary>
|
||||
private bool ContinueProcessingLine { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Get the current Container that is currently opened
|
||||
/// </summary>
|
||||
/// <returns>The current Container that is currently opened</returns>
|
||||
public ContainerBlock GetCurrentContainerOpened()
|
||||
{
|
||||
var container = CurrentContainer;
|
||||
while (container != null && !container.IsOpen)
|
||||
{
|
||||
container = container.Parent;
|
||||
}
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the next character in the line being processed. Update <see cref="Start"/> and <see cref="Column"/>.
|
||||
/// </summary>
|
||||
@@ -429,6 +444,8 @@ namespace Markdig.Parsers
|
||||
{
|
||||
CurrentLineStartPosition = newLine.Start;
|
||||
|
||||
Document.LineStartIndexes?.Add(CurrentLineStartPosition);
|
||||
|
||||
ContinueProcessingLine = true;
|
||||
|
||||
ResetLine(newLine);
|
||||
|
||||
@@ -262,10 +262,12 @@ namespace Markdig.Parsers
|
||||
}
|
||||
}
|
||||
|
||||
int.TryParse(listInfo.OrderedStart, out int order);
|
||||
var newListItem = new ListItemBlock(this)
|
||||
{
|
||||
Column = initColumn,
|
||||
ColumnWidth = columnWidth,
|
||||
Order = order,
|
||||
Span = new SourceSpan(sourcePosition, sourceEndPosition)
|
||||
};
|
||||
state.NewBlocks.Push(newListItem);
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -85,8 +85,14 @@ namespace Markdig.Parsers
|
||||
/// <returns>A document instance</returns>
|
||||
private MarkdownDocument Parse()
|
||||
{
|
||||
if (preciseSourceLocation)
|
||||
document.LineStartIndexes = new List<int>();
|
||||
|
||||
ProcessBlocks();
|
||||
ProcessInlines();
|
||||
|
||||
// At this point the LineIndex is the same as the number of lines in the document
|
||||
document.LineCount = blockProcessor.LineIndex;
|
||||
|
||||
// Allow to call a hook after processing a document
|
||||
documentProcessed?.Invoke(document);
|
||||
@@ -146,8 +152,7 @@ namespace Markdig.Parsers
|
||||
for (; item.Index < container.Count; item.Index++)
|
||||
{
|
||||
var block = container[item.Index];
|
||||
var leafBlock = block as LeafBlock;
|
||||
if (leafBlock != null)
|
||||
if (block is LeafBlock leafBlock)
|
||||
{
|
||||
leafBlock.OnProcessInlinesBegin(inlineProcessor);
|
||||
if (leafBlock.ProcessInlines)
|
||||
|
||||
@@ -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 Markdig.Helpers;
|
||||
@@ -9,7 +9,7 @@ namespace Markdig.Parsers
|
||||
/// <summary>
|
||||
/// Block parser for a <see cref="ParagraphBlock"/>.
|
||||
/// </summary>
|
||||
/// <seealso cref="Markdig.Parsers.BlockParser" />
|
||||
/// <seealso cref="BlockParser" />
|
||||
public class ParagraphBlockParser : BlockParser
|
||||
{
|
||||
public override BlockState TryOpen(BlockProcessor processor)
|
||||
@@ -152,15 +152,20 @@ namespace Markdig.Parsers
|
||||
{
|
||||
// If we have found a LinkReferenceDefinition, we can discard the previous paragraph
|
||||
var iterator = lines.ToCharIterator();
|
||||
LinkReferenceDefinition linkReferenceDefinition;
|
||||
if (LinkReferenceDefinition.TryParse(ref iterator, out linkReferenceDefinition))
|
||||
{
|
||||
if (!state.Document.ContainsLinkReferenceDefinition(linkReferenceDefinition.Label))
|
||||
{
|
||||
state.Document.SetLinkReferenceDefinition(linkReferenceDefinition.Label, linkReferenceDefinition);
|
||||
}
|
||||
if (LinkReferenceDefinition.TryParse(ref iterator, out LinkReferenceDefinition linkReferenceDefinition))
|
||||
{
|
||||
state.Document.SetLinkReferenceDefinition(linkReferenceDefinition.Label, linkReferenceDefinition);
|
||||
atLeastOneFound = true;
|
||||
|
||||
// Correct the locations of each field
|
||||
linkReferenceDefinition.Line = lines.Lines[0].Line;
|
||||
int startPosition = lines.Lines[0].Slice.Start;
|
||||
|
||||
linkReferenceDefinition.Span = linkReferenceDefinition.Span .MoveForward(startPosition);
|
||||
linkReferenceDefinition.LabelSpan = linkReferenceDefinition.LabelSpan .MoveForward(startPosition);
|
||||
linkReferenceDefinition.UrlSpan = linkReferenceDefinition.UrlSpan .MoveForward(startPosition);
|
||||
linkReferenceDefinition.TitleSpan = linkReferenceDefinition.TitleSpan .MoveForward(startPosition);
|
||||
|
||||
// Remove lines that have been matched
|
||||
if (iterator.Start > iterator.End)
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -75,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>
|
||||
@@ -192,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;
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ namespace Markdig.Renderers.Normalize
|
||||
EmptyLineAfterCodeBlock = true;
|
||||
EmptyLineAfterHeading = true;
|
||||
EmptyLineAfterThematicBreak = true;
|
||||
ExpandAutoLinks = true;
|
||||
ListItemCharacter = null;
|
||||
}
|
||||
|
||||
@@ -44,5 +45,10 @@ namespace Markdig.Renderers.Normalize
|
||||
/// 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; }
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -192,17 +192,16 @@ namespace Markdig.Syntax.Inlines
|
||||
/// <returns><c>true</c> if this instance contains a parent of the specified type; <c>false</c> otherwise</returns>
|
||||
public bool ContainsParentOfType<T>() where T : Inline
|
||||
{
|
||||
var delimiter = this as T;
|
||||
if (delimiter != null)
|
||||
var inline = this;
|
||||
while (inline != null)
|
||||
{
|
||||
return true;
|
||||
var delimiter = inline as T;
|
||||
if (delimiter != null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
inline = inline.Parent;
|
||||
}
|
||||
|
||||
if (Parent != null)
|
||||
{
|
||||
return Parent.ContainsParentOfType<T>();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -213,19 +212,15 @@ namespace Markdig.Syntax.Inlines
|
||||
/// <returns>An enumeration on the parents of the specified type</returns>
|
||||
public IEnumerable<T> FindParentOfType<T>() where T : Inline
|
||||
{
|
||||
var parent = Parent;
|
||||
var delimiter = this as T;
|
||||
if (delimiter != null)
|
||||
var inline = this;
|
||||
while (inline != null)
|
||||
{
|
||||
yield return delimiter;
|
||||
}
|
||||
|
||||
if (parent != null)
|
||||
{
|
||||
foreach (var previous in parent.FindParentOfType<T>())
|
||||
var delimiter = inline as T;
|
||||
if (delimiter != null)
|
||||
{
|
||||
yield return previous;
|
||||
yield return delimiter;
|
||||
}
|
||||
inline = inline.Parent;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -69,6 +69,11 @@ namespace Markdig.Syntax.Inlines
|
||||
/// </summary>
|
||||
public bool IsShortcut { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a boolean indicating whether the inline link was parsed using markdown syntax or was automatic recognized.
|
||||
/// </summary>
|
||||
public bool IsAutoLink { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the reference this link is attached to. May be null.
|
||||
/// </summary>
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -28,11 +28,14 @@ namespace Markdig.Syntax
|
||||
|
||||
public void Set(string label, LinkReferenceDefinition link)
|
||||
{
|
||||
if (link == null) throw new ArgumentNullException(nameof(link));
|
||||
Links[label] = link;
|
||||
if (!Contains(link))
|
||||
{
|
||||
Add(link);
|
||||
if (link == null) throw new ArgumentNullException(nameof(link));
|
||||
if (!Contains(link))
|
||||
{
|
||||
Add(link);
|
||||
if (!Links.ContainsKey(label))
|
||||
{
|
||||
Links[label] = link;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 Markdig.Parsers;
|
||||
@@ -20,5 +20,10 @@ namespace Markdig.Syntax
|
||||
}
|
||||
|
||||
internal int ColumnWidth { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The number defined for this <see cref="ListItemBlock"/> in an ordered list
|
||||
/// </summary>
|
||||
public int Order { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
// 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.Collections.Generic;
|
||||
|
||||
namespace Markdig.Syntax
|
||||
{
|
||||
/// <summary>
|
||||
@@ -15,5 +17,16 @@ namespace Markdig.Syntax
|
||||
public MarkdownDocument() : base(null)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of lines in this <see cref="MarkdownDocument"/>
|
||||
/// </summary>
|
||||
public int LineCount;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of zero-based indexes of line beginnings in the source span
|
||||
/// <para>Available if <see cref="MarkdownPipelineBuilder.PreciseSourceLocation"/> is used, otherwise null</para>
|
||||
/// </summary>
|
||||
public List<int> LineStartIndexes;
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -43,6 +43,11 @@ namespace Markdig.Syntax
|
||||
|
||||
public bool IsEmpty => Start > End;
|
||||
|
||||
public SourceSpan MoveForward(int count)
|
||||
{
|
||||
return new SourceSpan(Start + count, End + count);
|
||||
}
|
||||
|
||||
public bool Equals(SourceSpan other)
|
||||
{
|
||||
return Start == other.Start && End == other.End;
|
||||
|
||||
@@ -6,6 +6,7 @@ MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{061866E2-005C-4D13-A338-EA464BBEC60F}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
..\appveyor.yml = ..\appveyor.yml
|
||||
..\changelog.md = ..\changelog.md
|
||||
..\license.txt = ..\license.txt
|
||||
..\readme.md = ..\readme.md
|
||||
EndProjectSection
|
||||
|
||||
Reference in New Issue
Block a user