mirror of
https://github.com/xoofx/markdig.git
synced 2026-02-12 13:54:45 +00:00
Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dd6418d108 | ||
|
|
7875e4bce9 | ||
|
|
2e1b1a1fdc | ||
|
|
fa2b157c1a | ||
|
|
e4e6406546 | ||
|
|
1cff10270a | ||
|
|
87023184cb | ||
|
|
aecdf2192e | ||
|
|
0a36382126 | ||
|
|
0057b368ec | ||
|
|
3033284096 | ||
|
|
9b1b791b18 | ||
|
|
2b7d205701 | ||
|
|
89b28659b1 | ||
|
|
4d172bf905 | ||
|
|
890b2cda2a | ||
|
|
8886b48634 | ||
|
|
64cd8ec262 | ||
|
|
a1e19912a9 | ||
|
|
ca51967fb1 | ||
|
|
bb3a4f372c | ||
|
|
1d6a464c5d | ||
|
|
0fe5c17a93 |
@@ -1,5 +1,10 @@
|
||||
# Changelog
|
||||
|
||||
## 0.18.1 (21 Jan 2020)
|
||||
- Re-allow emojis and smileys customization, that was broken in [PR #308](https://github.com/lunet-io/markdig/pull/308) ([PR #386](https://github.com/lunet-io/markdig/pull/386))
|
||||
- Add `IHostProvider` for medialink customization (#337), support protocol-less url (#135) ([(PR #341)](https://github.com/lunet-io/markdig/pull/341))
|
||||
- Add missing Descendants<T> overload ([(PR #387)](https://github.com/lunet-io/markdig/pull/387))
|
||||
|
||||
## 0.18.0 (24 Oct 2019)
|
||||
- Ignore backslashes in GFM AutoLinks ([(PR #357)](https://github.com/lunet-io/markdig/pull/357))
|
||||
- Fix SmartyPants quote matching ([(PR #360)](https://github.com/lunet-io/markdig/pull/360))
|
||||
@@ -7,6 +12,7 @@
|
||||
- Fix link text balanced bracket matching ([(PR #375)](https://github.com/lunet-io/markdig/pull/375))
|
||||
- Improve overall performance and substantially reduce allocations ([(PR #377)](https://github.com/lunet-io/markdig/pull/377))
|
||||
|
||||
|
||||
## 0.17.1 (04 July 2019)
|
||||
- Fix regression when escaping HTML characters ([(PR #340)](https://github.com/lunet-io/markdig/pull/340))
|
||||
- Update Emoji Dictionary ([(PR #346)](https://github.com/lunet-io/markdig/pull/346))
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Generated: 2019-04-05 16:06:14
|
||||
// Generated: 2019-05-15 02:46:55
|
||||
|
||||
// --------------------------------
|
||||
// Abbreviations
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Generated: 2019-04-15 05:20:50
|
||||
// Generated: 2020-01-13 21:08:58
|
||||
|
||||
// --------------------------------
|
||||
// Emoji
|
||||
@@ -18,7 +18,7 @@ namespace Markdig.Tests.Specs.Emoji
|
||||
//
|
||||
// ## Emoji
|
||||
//
|
||||
// Emoji and smiley can be converted to their respective unicode characters:
|
||||
// Emoji shortcodes and smileys can be converted to their respective unicode characters:
|
||||
[Test]
|
||||
public void ExtensionsEmoji_Example001()
|
||||
{
|
||||
@@ -52,7 +52,7 @@ namespace Markdig.Tests.Specs.Emoji
|
||||
TestParser.TestSpec("These are not:) an emoji with a:) x:angry:x", "<p>These are not:) an emoji with a:) x:angry:x</p>", "emojis|advanced+emojis");
|
||||
}
|
||||
|
||||
// Emoji can be followed by close punctuation (or any other characters):
|
||||
// Emojis can be followed by close punctuation (or any other characters):
|
||||
[Test]
|
||||
public void ExtensionsEmoji_Example003()
|
||||
{
|
||||
@@ -69,7 +69,7 @@ namespace Markdig.Tests.Specs.Emoji
|
||||
TestParser.TestSpec("We all need :), it makes us :muscle:. (and :ok_hand:).", "<p>We all need 😃, it makes us 💪. (and 👌).</p>", "emojis|advanced+emojis");
|
||||
}
|
||||
|
||||
// Sentences can end with Emoji:
|
||||
// Sentences can end with emojis:
|
||||
[Test]
|
||||
public void ExtensionsEmoji_Example004()
|
||||
{
|
||||
|
||||
@@ -4,7 +4,7 @@ This section describes the different extensions supported:
|
||||
|
||||
## Emoji
|
||||
|
||||
Emoji and smiley can be converted to their respective unicode characters:
|
||||
Emoji shortcodes and smileys can be converted to their respective unicode characters:
|
||||
|
||||
```````````````````````````````` example
|
||||
This is a test with a :) and a :angry: smiley
|
||||
@@ -20,7 +20,7 @@ These are not:) an emoji with a:) x:angry:x
|
||||
<p>These are not:) an emoji with a:) x:angry:x</p>
|
||||
````````````````````````````````
|
||||
|
||||
Emoji can be followed by close punctuation (or any other characters):
|
||||
Emojis can be followed by close punctuation (or any other characters):
|
||||
|
||||
```````````````````````````````` example
|
||||
We all need :), it makes us :muscle:. (and :ok_hand:).
|
||||
@@ -28,7 +28,7 @@ We all need :), it makes us :muscle:. (and :ok_hand:).
|
||||
<p>We all need 😃, it makes us 💪. (and 👌).</p>
|
||||
````````````````````````````````
|
||||
|
||||
Sentences can end with Emoji:
|
||||
Sentences can end with emojis:
|
||||
|
||||
```````````````````````````````` example
|
||||
This is a sentence :ok_hand:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Generated: 2019-04-29 18:40:06
|
||||
// Generated: 2019-05-15 02:46:20
|
||||
|
||||
// --------------------------------
|
||||
// Media
|
||||
@@ -47,19 +47,19 @@ namespace Markdig.Tests.Specs.Media
|
||||
// 
|
||||
//
|
||||
// Should be rendered as:
|
||||
// <p><iframe src="https://www.youtube.com/embed/mswPy5bt3TQ" width="500" height="281" frameborder="0" allowfullscreen=""></iframe></p>
|
||||
// <p><iframe src="https://www.youtube.com/embed/mswPy5bt3TQ?start=100" width="500" height="281" frameborder="0" allowfullscreen=""></iframe></p>
|
||||
// <p><iframe src="https://www.youtube.com/embed/mswPy5bt3TQ" width="500" height="281" frameborder="0" allowfullscreen=""></iframe></p>
|
||||
// <p><iframe src="https://www.youtube.com/embed/mswPy5bt3TQ?start=100" width="500" height="281" frameborder="0" allowfullscreen=""></iframe></p>
|
||||
// <p><iframe src="https://www.youtube.com/embed/mswPy5bt3TQ?start=100&rel=0" width="500" height="281" frameborder="0" allowfullscreen=""></iframe></p>
|
||||
// <p><iframe src="https://www.youtube.com/embed?listType=playlist&list=PLC77007E23FF423C6" 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><iframe src="https://www.youtube.com/embed/mswPy5bt3TQ" width="500" height="281" class="youtube" frameborder="0" allowfullscreen=""></iframe></p>
|
||||
// <p><iframe src="https://www.youtube.com/embed/mswPy5bt3TQ?start=100" width="500" height="281" class="youtube" frameborder="0" allowfullscreen=""></iframe></p>
|
||||
// <p><iframe src="https://www.youtube.com/embed/mswPy5bt3TQ" width="500" height="281" class="youtube" frameborder="0" allowfullscreen=""></iframe></p>
|
||||
// <p><iframe src="https://www.youtube.com/embed/mswPy5bt3TQ?start=100" width="500" height="281" class="youtube" frameborder="0" allowfullscreen=""></iframe></p>
|
||||
// <p><iframe src="https://www.youtube.com/embed/mswPy5bt3TQ?start=100&rel=0" width="500" height="281" class="youtube" frameborder="0" allowfullscreen=""></iframe></p>
|
||||
// <p><iframe src="https://www.youtube.com/embed?listType=playlist&list=PLC77007E23FF423C6" width="500" height="281" class="youtube" frameborder="0" allowfullscreen=""></iframe></p>
|
||||
// <p><iframe src="https://player.vimeo.com/video/8607834" width="500" height="281" class="vimeo" 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>
|
||||
// <p><iframe src="https://music.yandex.ru/iframe/#track/4402274/411845/" width="500" height="281" class="yandex" frameborder="0"></iframe></p>
|
||||
// <p><iframe src="https://ok.ru/videoembed/26870090463" width="500" height="281" class="odnoklassniki" frameborder="0" allowfullscreen=""></iframe></p>
|
||||
|
||||
Console.WriteLine("Example 1\nSection Extensions / Media links\n");
|
||||
TestParser.TestSpec("\n\n\n\n\n\n\n\n\n \n\n\n\n\n\n\n\n\n", "<p><iframe src=\"https://www.youtube.com/embed/mswPy5bt3TQ\" width=\"500\" height=\"281\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n<p><iframe src=\"https://www.youtube.com/embed/mswPy5bt3TQ?start=100\" width=\"500\" height=\"281\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n<p><iframe src=\"https://www.youtube.com/embed/mswPy5bt3TQ\" width=\"500\" height=\"281\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n<p><iframe src=\"https://www.youtube.com/embed/mswPy5bt3TQ?start=100\" width=\"500\" height=\"281\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n<p><iframe src=\"https://www.youtube.com/embed/mswPy5bt3TQ?start=100&rel=0\" width=\"500\" height=\"281\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n<p><iframe src=\"https://www.youtube.com/embed?listType=playlist&list=PLC77007E23FF423C6\" width=\"500\" height=\"281\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n<p><iframe src=\"https://player.vimeo.com/video/8607834\" width=\"500\" height=\"281\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n<p><video width=\"500\" height=\"281\" controls=\"\"><source type=\"video/mp4\" src=\"https://sample.com/video.mp4\"></source></video></p>\n<p><iframe src=\"https://music.yandex.ru/iframe/#track/4402274/411845/\" width=\"500\" height=\"281\" frameborder=\"0\"></iframe></p>\n<p><iframe src=\"https://ok.ru/videoembed/26870090463\" width=\"500\" height=\"281\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>", "medialinks|advanced+medialinks");
|
||||
TestParser.TestSpec("\n\n\n\n\n\n\n\n\n \n\n\n\n\n\n\n\n\n", "<p><iframe src=\"https://www.youtube.com/embed/mswPy5bt3TQ\" width=\"500\" height=\"281\" class=\"youtube\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n<p><iframe src=\"https://www.youtube.com/embed/mswPy5bt3TQ?start=100\" width=\"500\" height=\"281\" class=\"youtube\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n<p><iframe src=\"https://www.youtube.com/embed/mswPy5bt3TQ\" width=\"500\" height=\"281\" class=\"youtube\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n<p><iframe src=\"https://www.youtube.com/embed/mswPy5bt3TQ?start=100\" width=\"500\" height=\"281\" class=\"youtube\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n<p><iframe src=\"https://www.youtube.com/embed/mswPy5bt3TQ?start=100&rel=0\" width=\"500\" height=\"281\" class=\"youtube\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n<p><iframe src=\"https://www.youtube.com/embed?listType=playlist&list=PLC77007E23FF423C6\" width=\"500\" height=\"281\" class=\"youtube\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n<p><iframe src=\"https://player.vimeo.com/video/8607834\" width=\"500\" height=\"281\" class=\"vimeo\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n<p><video width=\"500\" height=\"281\" controls=\"\"><source type=\"video/mp4\" src=\"https://sample.com/video.mp4\"></source></video></p>\n<p><iframe src=\"https://music.yandex.ru/iframe/#track/4402274/411845/\" width=\"500\" height=\"281\" class=\"yandex\" frameborder=\"0\"></iframe></p>\n<p><iframe src=\"https://ok.ru/videoembed/26870090463\" width=\"500\" height=\"281\" class=\"odnoklassniki\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>", "medialinks|advanced+medialinks");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,14 +27,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://www.youtube.com/embed/mswPy5bt3TQ?start=100" width="500" height="281" frameborder="0" allowfullscreen=""></iframe></p>
|
||||
<p><iframe src="https://www.youtube.com/embed/mswPy5bt3TQ" width="500" height="281" frameborder="0" allowfullscreen=""></iframe></p>
|
||||
<p><iframe src="https://www.youtube.com/embed/mswPy5bt3TQ?start=100" width="500" height="281" frameborder="0" allowfullscreen=""></iframe></p>
|
||||
<p><iframe src="https://www.youtube.com/embed/mswPy5bt3TQ?start=100&rel=0" width="500" height="281" frameborder="0" allowfullscreen=""></iframe></p>
|
||||
<p><iframe src="https://www.youtube.com/embed?listType=playlist&list=PLC77007E23FF423C6" 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><iframe src="https://www.youtube.com/embed/mswPy5bt3TQ" width="500" height="281" class="youtube" frameborder="0" allowfullscreen=""></iframe></p>
|
||||
<p><iframe src="https://www.youtube.com/embed/mswPy5bt3TQ?start=100" width="500" height="281" class="youtube" frameborder="0" allowfullscreen=""></iframe></p>
|
||||
<p><iframe src="https://www.youtube.com/embed/mswPy5bt3TQ" width="500" height="281" class="youtube" frameborder="0" allowfullscreen=""></iframe></p>
|
||||
<p><iframe src="https://www.youtube.com/embed/mswPy5bt3TQ?start=100" width="500" height="281" class="youtube" frameborder="0" allowfullscreen=""></iframe></p>
|
||||
<p><iframe src="https://www.youtube.com/embed/mswPy5bt3TQ?start=100&rel=0" width="500" height="281" class="youtube" frameborder="0" allowfullscreen=""></iframe></p>
|
||||
<p><iframe src="https://www.youtube.com/embed?listType=playlist&list=PLC77007E23FF423C6" width="500" height="281" class="youtube" frameborder="0" allowfullscreen=""></iframe></p>
|
||||
<p><iframe src="https://player.vimeo.com/video/8607834" width="500" height="281" class="vimeo" 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>
|
||||
<p><iframe src="https://music.yandex.ru/iframe/#track/4402274/411845/" width="500" height="281" class="yandex" frameborder="0"></iframe></p>
|
||||
<p><iframe src="https://ok.ru/videoembed/26870090463" width="500" height="281" class="odnoklassniki" frameborder="0" allowfullscreen=""></iframe></p>
|
||||
````````````````````````````````
|
||||
99
src/Markdig.Tests/TestCustomEmojis.cs
Normal file
99
src/Markdig.Tests/TestCustomEmojis.cs
Normal file
@@ -0,0 +1,99 @@
|
||||
using System.Collections.Generic;
|
||||
using Markdig.Extensions.Emoji;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace Markdig.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
public class TestCustomEmojis
|
||||
{
|
||||
[Test]
|
||||
[TestCase(":smiley:", "<p>♥</p>\n")]
|
||||
[TestCase(":confused:", "<p>:confused:</p>\n")] // default emoji does not work
|
||||
[TestCase(":/", "<p>:/</p>\n")] // default smiley does not work
|
||||
public void TestCustomEmoji(string input, string expected)
|
||||
{
|
||||
var emojiToUnicode = new Dictionary<string, string>();
|
||||
var smileyToEmoji = new Dictionary<string, string>();
|
||||
|
||||
emojiToUnicode[":smiley:"] = "♥";
|
||||
|
||||
var customMapping = new EmojiMapping(emojiToUnicode, smileyToEmoji);
|
||||
|
||||
var pipeline = new MarkdownPipelineBuilder()
|
||||
.UseEmojiAndSmiley(customEmojiMapping: customMapping)
|
||||
.Build();
|
||||
|
||||
var actual = Markdown.ToHtml(input, pipeline);
|
||||
Assert.AreEqual(expected, actual);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase(":testheart:", "<p>♥</p>\n")]
|
||||
[TestCase("hello", "<p>♥</p>\n")]
|
||||
[TestCase(":confused:", "<p>:confused:</p>\n")] // default emoji does not work
|
||||
[TestCase(":/", "<p>:/</p>\n")] // default smiley does not work
|
||||
public void TestCustomSmiley(string input, string expected)
|
||||
{
|
||||
var emojiToUnicode = new Dictionary<string, string>();
|
||||
var smileyToEmoji = new Dictionary<string, string>();
|
||||
|
||||
emojiToUnicode[":testheart:"] = "♥";
|
||||
smileyToEmoji["hello"] = ":testheart:";
|
||||
|
||||
var customMapping = new EmojiMapping(emojiToUnicode, smileyToEmoji);
|
||||
|
||||
var pipeline = new MarkdownPipelineBuilder()
|
||||
.UseEmojiAndSmiley(customEmojiMapping: customMapping)
|
||||
.Build();
|
||||
|
||||
var actual = Markdown.ToHtml(input, pipeline);
|
||||
Assert.AreEqual(expected, actual);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase(":smiley:", "<p>♥</p>\n")]
|
||||
[TestCase(":)", "<p>♥</p>\n")]
|
||||
[TestCase(":confused:", "<p>😕</p>\n")] // default emoji still works
|
||||
[TestCase(":/", "<p>😕</p>\n")] // default smiley still works
|
||||
public void TestOverrideDefaultWithCustomEmoji(string input, string expected)
|
||||
{
|
||||
var emojiToUnicode = EmojiMapping.GetDefaultEmojiShortcodeToUnicode();
|
||||
var smileyToEmoji = EmojiMapping.GetDefaultSmileyToEmojiShortcode();
|
||||
|
||||
emojiToUnicode[":smiley:"] = "♥";
|
||||
|
||||
var customMapping = new EmojiMapping(emojiToUnicode, smileyToEmoji);
|
||||
|
||||
var pipeline = new MarkdownPipelineBuilder()
|
||||
.UseEmojiAndSmiley(customEmojiMapping: customMapping)
|
||||
.Build();
|
||||
|
||||
var actual = Markdown.ToHtml(input, pipeline);
|
||||
Assert.AreEqual(expected, actual);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase(":testheart:", "<p>♥</p>\n")]
|
||||
[TestCase("hello", "<p>♥</p>\n")]
|
||||
[TestCase(":confused:", "<p>😕</p>\n")] // default emoji still works
|
||||
[TestCase(":/", "<p>😕</p>\n")] // default smiley still works
|
||||
public void TestOverrideDefaultWithCustomSmiley(string input, string expected)
|
||||
{
|
||||
var emojiToUnicode = EmojiMapping.GetDefaultEmojiShortcodeToUnicode();
|
||||
var smileyToEmoji = EmojiMapping.GetDefaultSmileyToEmojiShortcode();
|
||||
|
||||
emojiToUnicode[":testheart:"] = "♥";
|
||||
smileyToEmoji["hello"] = ":testheart:";
|
||||
|
||||
var customMapping = new EmojiMapping(emojiToUnicode, smileyToEmoji);
|
||||
|
||||
var pipeline = new MarkdownPipelineBuilder()
|
||||
.UseEmojiAndSmiley(customEmojiMapping: customMapping)
|
||||
.Build();
|
||||
|
||||
var actual = Markdown.ToHtml(input, pipeline);
|
||||
Assert.AreEqual(expected, actual);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ using Markdig.Syntax;
|
||||
using Markdig.Syntax.Inlines;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using Markdig.Helpers;
|
||||
|
||||
namespace Markdig.Tests
|
||||
{
|
||||
@@ -12,24 +13,91 @@ namespace Markdig.Tests
|
||||
[Test]
|
||||
public void TestSchemas()
|
||||
{
|
||||
foreach (var markdown in TestParser.SpecsMarkdown)
|
||||
foreach (var syntaxTree in TestParser.SpecsSyntaxTrees)
|
||||
{
|
||||
AssertSameDescendantsOrder(markdown);
|
||||
AssertIEnumerablesAreEqual(
|
||||
Descendants_Legacy(syntaxTree),
|
||||
syntaxTree.Descendants());
|
||||
|
||||
AssertIEnumerablesAreEqual(
|
||||
syntaxTree.Descendants().OfType<ParagraphBlock>(),
|
||||
syntaxTree.Descendants<ParagraphBlock>());
|
||||
|
||||
AssertIEnumerablesAreEqual(
|
||||
syntaxTree.Descendants().OfType<ParagraphBlock>(),
|
||||
(syntaxTree as ContainerBlock).Descendants<ParagraphBlock>());
|
||||
|
||||
AssertIEnumerablesAreEqual(
|
||||
syntaxTree.Descendants().OfType<LiteralInline>(),
|
||||
syntaxTree.Descendants<LiteralInline>());
|
||||
|
||||
foreach (LiteralInline literalInline in syntaxTree.Descendants<LiteralInline>())
|
||||
{
|
||||
Assert.AreSame(ArrayHelper<ListBlock>.Empty, literalInline.Descendants<ListBlock>());
|
||||
Assert.AreSame(ArrayHelper<ParagraphBlock>.Empty, literalInline.Descendants<ParagraphBlock>());
|
||||
Assert.AreSame(ArrayHelper<ContainerInline>.Empty, literalInline.Descendants<ContainerInline>());
|
||||
}
|
||||
|
||||
foreach (ContainerInline containerInline in syntaxTree.Descendants<ContainerInline>())
|
||||
{
|
||||
AssertIEnumerablesAreEqual(
|
||||
containerInline.FindDescendants<LiteralInline>(),
|
||||
containerInline.Descendants<LiteralInline>());
|
||||
|
||||
AssertIEnumerablesAreEqual(
|
||||
containerInline.FindDescendants<LiteralInline>(),
|
||||
(containerInline as MarkdownObject).Descendants<LiteralInline>());
|
||||
|
||||
if (containerInline.FirstChild is null)
|
||||
{
|
||||
Assert.AreSame(ArrayHelper<LiteralInline>.Empty, containerInline.Descendants<LiteralInline>());
|
||||
Assert.AreSame(ArrayHelper<LiteralInline>.Empty, containerInline.FindDescendants<LiteralInline>());
|
||||
Assert.AreSame(ArrayHelper<LiteralInline>.Empty, (containerInline as MarkdownObject).Descendants<LiteralInline>());
|
||||
}
|
||||
|
||||
Assert.AreSame(ArrayHelper<ListBlock>.Empty, containerInline.Descendants<ListBlock>());
|
||||
Assert.AreSame(ArrayHelper<ParagraphBlock>.Empty, containerInline.Descendants<ParagraphBlock>());
|
||||
}
|
||||
|
||||
foreach (ParagraphBlock paragraphBlock in syntaxTree.Descendants<ParagraphBlock>())
|
||||
{
|
||||
AssertIEnumerablesAreEqual(
|
||||
(paragraphBlock as MarkdownObject).Descendants<LiteralInline>(),
|
||||
paragraphBlock.Descendants<LiteralInline>());
|
||||
|
||||
Assert.AreSame(ArrayHelper<ParagraphBlock>.Empty, paragraphBlock.Descendants<ParagraphBlock>());
|
||||
}
|
||||
|
||||
foreach (ContainerBlock containerBlock in syntaxTree.Descendants<ContainerBlock>())
|
||||
{
|
||||
AssertIEnumerablesAreEqual(
|
||||
containerBlock.Descendants<LiteralInline>(),
|
||||
(containerBlock as MarkdownObject).Descendants<LiteralInline>());
|
||||
|
||||
AssertIEnumerablesAreEqual(
|
||||
containerBlock.Descendants<ParagraphBlock>(),
|
||||
(containerBlock as MarkdownObject).Descendants<ParagraphBlock>());
|
||||
|
||||
if (containerBlock.Count == 0)
|
||||
{
|
||||
Assert.AreSame(ArrayHelper<LiteralInline>.Empty, containerBlock.Descendants<LiteralInline>());
|
||||
Assert.AreSame(ArrayHelper<LiteralInline>.Empty, (containerBlock as Block).Descendants<LiteralInline>());
|
||||
Assert.AreSame(ArrayHelper<LiteralInline>.Empty, (containerBlock as MarkdownObject).Descendants<LiteralInline>());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void AssertSameDescendantsOrder(string markdown)
|
||||
private static void AssertIEnumerablesAreEqual<T>(IEnumerable<T> first, IEnumerable<T> second)
|
||||
{
|
||||
var syntaxTree = Markdown.Parse(markdown, new MarkdownPipelineBuilder().UseAdvancedExtensions().Build());
|
||||
var firstList = new List<T>(first);
|
||||
var secondList = new List<T>(second);
|
||||
|
||||
var descendants_legacy = Descendants_Legacy(syntaxTree).ToList();
|
||||
var descendants_new = syntaxTree.Descendants().ToList();
|
||||
Assert.AreEqual(firstList.Count, secondList.Count);
|
||||
|
||||
Assert.AreEqual(descendants_legacy.Count, descendants_new.Count);
|
||||
|
||||
for (int i = 0; i < descendants_legacy.Count; i++)
|
||||
for (int i = 0; i < firstList.Count; i++)
|
||||
{
|
||||
Assert.AreSame(descendants_legacy[i], descendants_new[i]);
|
||||
Assert.AreSame(firstList[i], secondList[i]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
87
src/Markdig.Tests/TestMediaLinks.cs
Normal file
87
src/Markdig.Tests/TestMediaLinks.cs
Normal file
@@ -0,0 +1,87 @@
|
||||
using Markdig.Extensions.MediaLinks;
|
||||
using NUnit.Framework;
|
||||
using System;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Markdig.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
public class TestMediaLinks
|
||||
{
|
||||
private MarkdownPipeline GetPipeline(MediaOptions options = null)
|
||||
{
|
||||
return new MarkdownPipelineBuilder()
|
||||
.UseMediaLinks(options)
|
||||
.Build();
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase("", "<p><video width=\"500\" height=\"281\" controls=\"\"><source type=\"video/mp4\" src=\"https://sample.com/video.mp4\"></source></video></p>\n")]
|
||||
[TestCase("", "<p><video width=\"500\" height=\"281\" controls=\"\"><source type=\"video/mp4\" src=\"//sample.com/video.mp4\"></source></video></p>\n")]
|
||||
[TestCase(@"", "<p><iframe src=\"https://www.youtube.com/embed/mswPy5bt3TQ\" width=\"500\" height=\"281\" class=\"youtube\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n")]
|
||||
[TestCase("", "<p><iframe src=\"https://music.yandex.ru/iframe/#track/4402274/411845/\" width=\"500\" height=\"281\" class=\"yandex\" frameborder=\"0\"></iframe></p>\n")]
|
||||
[TestCase("", "<p><iframe src=\"https://player.vimeo.com/video/8607834\" width=\"500\" height=\"281\" class=\"vimeo\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n")]
|
||||
[TestCase("", "<p><iframe src=\"https://ok.ru/videoembed/26870090463\" width=\"500\" height=\"281\" class=\"odnoklassniki\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n")]
|
||||
[TestCase("", "<p><iframe src=\"https://ok.ru/videoembed/26870090463\" width=\"500\" height=\"281\" class=\"odnoklassniki\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n")]
|
||||
public void TestBuiltInHosts(string markdown, string expected)
|
||||
{
|
||||
string html = Markdown.ToHtml(markdown, GetPipeline());
|
||||
Assert.AreEqual(html, expected);
|
||||
}
|
||||
|
||||
private class TestHostProvider : IHostProvider
|
||||
{
|
||||
public string Class { get; } = "regex";
|
||||
public bool AllowFullScreen { get; }
|
||||
|
||||
public bool TryHandle(Uri mediaUri, bool isSchemaRelative, out string iframeUrl)
|
||||
{
|
||||
iframeUrl = null;
|
||||
var uri = isSchemaRelative ? "//" + mediaUri.GetComponents(UriComponents.AbsoluteUri & ~UriComponents.Scheme, UriFormat.UriEscaped) : mediaUri.ToString();
|
||||
if (!matcher.IsMatch(uri))
|
||||
return false;
|
||||
iframeUrl = matcher.Replace(uri, replacement);
|
||||
return true;
|
||||
}
|
||||
|
||||
private Regex matcher;
|
||||
private string replacement;
|
||||
|
||||
public TestHostProvider(string provider, string replace)
|
||||
{
|
||||
matcher = new Regex(provider);
|
||||
replacement = replace;
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase("", "<p><iframe src=\"https://example.com/video.mp4\" width=\"500\" height=\"281\" class=\"regex\" frameborder=\"0\"></iframe></p>\n", @"^https?://sample.com/(.+)$", @"https://example.com/$1")]
|
||||
[TestCase("", "<p><iframe src=\"https://example.com/video.mp4\" width=\"500\" height=\"281\" class=\"regex\" frameborder=\"0\"></iframe></p>\n", @"^//sample.com/(.+)$", @"https://example.com/$1")]
|
||||
[TestCase("", "<p><iframe src=\"https://example.com/video.mp4?token=aaabbb\" width=\"500\" height=\"281\" class=\"regex\" frameborder=\"0\"></iframe></p>\n", @"^https?://sample.com/(.+)$", @"https://example.com/$1?token=aaabbb")]
|
||||
public void TestCustomHostProvider(string markdown, string expected, string provider, string replace)
|
||||
{
|
||||
string html = Markdown.ToHtml(markdown, GetPipeline(new MediaOptions
|
||||
{
|
||||
Hosts =
|
||||
{
|
||||
new TestHostProvider(provider, replace),
|
||||
}
|
||||
}));
|
||||
Assert.AreEqual(html, expected);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase("", "<p><video width=\"500\" height=\"281\" controls=\"\"><source type=\"video/mp4\" src=\"//sample.com/video.mp4\"></source></video></p>\n", "")]
|
||||
[TestCase(@"", "<p><iframe src=\"https://www.youtube.com/embed/mswPy5bt3TQ\" width=\"500\" height=\"281\" class=\"youtube\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n", "")]
|
||||
[TestCase("", "<p><video width=\"500\" height=\"281\" controls=\"\" class=\"k\"><source type=\"video/mp4\" src=\"//sample.com/video.mp4\"></source></video></p>\n", "k")]
|
||||
[TestCase(@"", "<p><iframe src=\"https://www.youtube.com/embed/mswPy5bt3TQ\" width=\"500\" height=\"281\" class=\"k youtube\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n", "k")]
|
||||
public void TestCustomClass(string markdown, string expected, string klass)
|
||||
{
|
||||
string html = Markdown.ToHtml(markdown, GetPipeline(new MediaOptions
|
||||
{
|
||||
Class = klass,
|
||||
}));
|
||||
Assert.AreEqual(html, expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using Markdig.Extensions.JiraLinks;
|
||||
using Markdig.Syntax;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace Markdig.Tests
|
||||
@@ -151,6 +152,11 @@ namespace Markdig.Tests
|
||||
/// Contains the markdown source for specification files (order is the same as in <see cref="SpecsFilePaths"/>)
|
||||
/// </summary>
|
||||
public static readonly string[] SpecsMarkdown;
|
||||
/// <summary>
|
||||
/// Contains the markdown syntax tree for specification files (order is the same as in <see cref="SpecsFilePaths"/>)
|
||||
/// </summary>
|
||||
public static readonly MarkdownDocument[] SpecsSyntaxTrees;
|
||||
|
||||
static TestParser()
|
||||
{
|
||||
SpecsFilePaths = Directory.GetDirectories(TestsDirectory)
|
||||
@@ -161,10 +167,16 @@ namespace Markdig.Tests
|
||||
.ToArray();
|
||||
|
||||
SpecsMarkdown = new string[SpecsFilePaths.Length];
|
||||
SpecsSyntaxTrees = new MarkdownDocument[SpecsFilePaths.Length];
|
||||
|
||||
var pipeline = new MarkdownPipelineBuilder()
|
||||
.UseAdvancedExtensions()
|
||||
.Build();
|
||||
|
||||
for (int i = 0; i < SpecsFilePaths.Length; i++)
|
||||
{
|
||||
SpecsMarkdown[i] = File.ReadAllText(SpecsFilePaths[i]);
|
||||
string markdown = SpecsMarkdown[i] = File.ReadAllText(SpecsFilePaths[i]);
|
||||
SpecsSyntaxTrees[i] = Markdown.Parse(markdown, pipeline);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ Later in a text we are using HTML and it becomes an abbr tag HTML
|
||||
> some other text";
|
||||
var doc = Markdown.Parse(text);
|
||||
|
||||
Assert.True(doc.Descendants().OfType<LiteralInline>().All(x => !x.Content.IsEmpty),
|
||||
Assert.True(doc.Descendants<LiteralInline>().All(x => !x.Content.IsEmpty),
|
||||
"There should not have any empty literals");
|
||||
}
|
||||
|
||||
|
||||
@@ -6,8 +6,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Markdig.Extensions.Footnotes;
|
||||
using Markdig.Helpers;
|
||||
using Markdig.Extensions.Footnotes;
|
||||
using Markdig.Renderers.Html;
|
||||
using Markdig.Syntax;
|
||||
using Markdig.Syntax.Inlines;
|
||||
@@ -147,7 +146,7 @@ literal ( 0, 6) 6-7
|
||||
public void TestFootnoteLinkReferenceDefinition()
|
||||
{
|
||||
// 01 2 345678
|
||||
var footnote = Markdown.Parse("0\n\n [^1]:", new MarkdownPipelineBuilder().UsePreciseSourceLocation().UseFootnotes().Build()).Descendants().OfType<FootnoteLinkReferenceDefinition>().FirstOrDefault();
|
||||
var footnote = Markdown.Parse("0\n\n [^1]:", new MarkdownPipelineBuilder().UsePreciseSourceLocation().UseFootnotes().Build()).Descendants<FootnoteLinkReferenceDefinition>().FirstOrDefault();
|
||||
Assert.NotNull(footnote);
|
||||
|
||||
Assert.AreEqual(2, footnote.Line);
|
||||
@@ -160,7 +159,7 @@ literal ( 0, 6) 6-7
|
||||
{
|
||||
// 0 1
|
||||
// 0123456789012345
|
||||
var link = Markdown.Parse("[234]: /56 'yo' ", new MarkdownPipelineBuilder().UsePreciseSourceLocation().Build()).Descendants().OfType<LinkReferenceDefinition>().FirstOrDefault();
|
||||
var link = Markdown.Parse("[234]: /56 'yo' ", new MarkdownPipelineBuilder().UsePreciseSourceLocation().Build()).Descendants<LinkReferenceDefinition>().FirstOrDefault();
|
||||
Assert.NotNull(link);
|
||||
|
||||
Assert.AreEqual(0, link.Line);
|
||||
@@ -175,7 +174,7 @@ literal ( 0, 6) 6-7
|
||||
{
|
||||
// 0 1
|
||||
// 01 2 34567890123456789
|
||||
var link = Markdown.Parse("0\n\n [234]: /56 'yo' ", new MarkdownPipelineBuilder().UsePreciseSourceLocation().Build()).Descendants().OfType<LinkReferenceDefinition>().FirstOrDefault();
|
||||
var link = Markdown.Parse("0\n\n [234]: /56 'yo' ", new MarkdownPipelineBuilder().UsePreciseSourceLocation().Build()).Descendants<LinkReferenceDefinition>().FirstOrDefault();
|
||||
Assert.NotNull(link);
|
||||
|
||||
Assert.AreEqual(2, link.Line);
|
||||
@@ -213,7 +212,7 @@ literal ( 0, 4) 4-5
|
||||
{
|
||||
// 0 1
|
||||
// 01 2 3456789012345
|
||||
var link = Markdown.Parse("0\n\n01 [234](/56)", new MarkdownPipelineBuilder().UsePreciseSourceLocation().Build()).Descendants().OfType<LinkInline>().FirstOrDefault();
|
||||
var link = Markdown.Parse("0\n\n01 [234](/56)", new MarkdownPipelineBuilder().UsePreciseSourceLocation().Build()).Descendants<LinkInline>().FirstOrDefault();
|
||||
Assert.NotNull(link);
|
||||
|
||||
Assert.AreEqual(new SourceSpan(7, 9), link.LabelSpan);
|
||||
@@ -226,7 +225,7 @@ literal ( 0, 4) 4-5
|
||||
{
|
||||
// 0 1
|
||||
// 01 2 34567890123456789
|
||||
var link = Markdown.Parse("0\n\n01 [234](/56 'yo')", new MarkdownPipelineBuilder().UsePreciseSourceLocation().Build()).Descendants().OfType<LinkInline>().FirstOrDefault();
|
||||
var link = Markdown.Parse("0\n\n01 [234](/56 'yo')", new MarkdownPipelineBuilder().UsePreciseSourceLocation().Build()).Descendants<LinkInline>().FirstOrDefault();
|
||||
Assert.NotNull(link);
|
||||
|
||||
Assert.AreEqual(new SourceSpan(7, 9), link.LabelSpan);
|
||||
@@ -240,7 +239,7 @@ literal ( 0, 4) 4-5
|
||||
{
|
||||
// 0 1
|
||||
// 01 2 3456789012345
|
||||
var link = Markdown.Parse("0\n\n01", new MarkdownPipelineBuilder().UsePreciseSourceLocation().Build()).Descendants().OfType<LinkInline>().FirstOrDefault();
|
||||
var link = Markdown.Parse("0\n\n01", new MarkdownPipelineBuilder().UsePreciseSourceLocation().Build()).Descendants<LinkInline>().FirstOrDefault();
|
||||
Assert.NotNull(link);
|
||||
|
||||
Assert.AreEqual(new SourceSpan(5, 15), link.Span);
|
||||
@@ -408,7 +407,7 @@ literal ( 1, 2) 6-6
|
||||
6. Bar
|
||||
987123. FooBar";
|
||||
test = test.Replace("\r\n", "\n");
|
||||
var list = Markdown.Parse(test, new MarkdownPipelineBuilder().UsePreciseSourceLocation().Build()).Descendants().OfType<ListBlock>().FirstOrDefault();
|
||||
var list = Markdown.Parse(test, new MarkdownPipelineBuilder().UsePreciseSourceLocation().Build()).Descendants<ListBlock>().FirstOrDefault();
|
||||
Assert.NotNull(list);
|
||||
|
||||
Assert.AreEqual(1, list.Line);
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -51,8 +51,7 @@ namespace Markdig.Extensions.Bootstrap
|
||||
}
|
||||
else if (node is Inline)
|
||||
{
|
||||
var link = node as LinkInline;
|
||||
if (link != null && link.IsImage)
|
||||
if (node is LinkInline link && link.IsImage)
|
||||
{
|
||||
link.GetAttributes().AddClass("img-fluid");
|
||||
}
|
||||
|
||||
@@ -7,24 +7,24 @@ using Markdig.Renderers;
|
||||
namespace Markdig.Extensions.Emoji
|
||||
{
|
||||
/// <summary>
|
||||
/// Extension to allow emoji and smiley replacement.
|
||||
/// Extension to allow emoji shortcodes and smileys replacement.
|
||||
/// </summary>
|
||||
/// <seealso cref="Markdig.IMarkdownExtension" />
|
||||
public class EmojiExtension : IMarkdownExtension
|
||||
{
|
||||
public EmojiExtension(bool enableSmiley = true)
|
||||
{
|
||||
EnableSmiley = enableSmiley;
|
||||
public EmojiExtension(EmojiMapping emojiMapping)
|
||||
{
|
||||
EmojiMapping = emojiMapping;
|
||||
}
|
||||
|
||||
public bool EnableSmiley { get; set; }
|
||||
|
||||
|
||||
public EmojiMapping EmojiMapping { get; }
|
||||
|
||||
public void Setup(MarkdownPipelineBuilder pipeline)
|
||||
{
|
||||
if (!pipeline.InlineParsers.Contains<EmojiParser>())
|
||||
{
|
||||
// Insert the parser before any other parsers
|
||||
pipeline.InlineParsers.Insert(0, new EmojiParser(EnableSmiley));
|
||||
pipeline.InlineParsers.Insert(0, new EmojiParser(EmojiMapping));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -8,7 +8,7 @@ using Markdig.Syntax.Inlines;
|
||||
namespace Markdig.Extensions.Emoji
|
||||
{
|
||||
/// <summary>
|
||||
/// An emoji inline
|
||||
/// An emoji inline.
|
||||
/// </summary>
|
||||
/// <seealso cref="Markdig.Syntax.Inlines.Inline" />
|
||||
public class EmojiInline : LiteralInline
|
||||
@@ -32,7 +32,7 @@ namespace Markdig.Extensions.Emoji
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the original match string (either an emoji or a text smiley)
|
||||
/// Gets or sets the original match string (either an emoji shortcode or a text smiley)
|
||||
/// </summary>
|
||||
public string Match { get; set; }
|
||||
}
|
||||
|
||||
1794
src/Markdig/Extensions/Emoji/EmojiMapping.cs
Normal file
1794
src/Markdig/Extensions/Emoji/EmojiMapping.cs
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -38,7 +38,7 @@ namespace Markdig.Extensions.Globalization
|
||||
var attributes = node.GetAttributes();
|
||||
attributes.AddPropertyIfNotExist("dir", "rtl");
|
||||
|
||||
if (node is Table table)
|
||||
if (node is Table)
|
||||
{
|
||||
attributes.AddPropertyIfNotExist("align", "right");
|
||||
}
|
||||
@@ -71,19 +71,16 @@ namespace Markdig.Extensions.Globalization
|
||||
}
|
||||
else if (item is LiteralInline literal)
|
||||
{
|
||||
return StartsWithRtlCharacter(literal.ToString());
|
||||
return StartsWithRtlCharacter(literal.Content);
|
||||
}
|
||||
|
||||
foreach (var descendant in item.Descendants())
|
||||
foreach (var paragraph in item.Descendants<ParagraphBlock>())
|
||||
{
|
||||
if (descendant is ParagraphBlock p)
|
||||
foreach (var inline in paragraph.Inline)
|
||||
{
|
||||
foreach (var i in p.Inline)
|
||||
if (inline is LiteralInline literal)
|
||||
{
|
||||
if (i is LiteralInline l)
|
||||
{
|
||||
return StartsWithRtlCharacter(l.ToString());
|
||||
}
|
||||
return StartsWithRtlCharacter(literal.Content);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -91,9 +88,9 @@ namespace Markdig.Extensions.Globalization
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool StartsWithRtlCharacter(string text)
|
||||
private bool StartsWithRtlCharacter(StringSlice slice)
|
||||
{
|
||||
foreach (var c in CharHelper.ToUtf32(text))
|
||||
foreach (var c in CharHelper.ToUtf32(slice))
|
||||
{
|
||||
if (CharHelper.IsRightToLeft(c))
|
||||
return true;
|
||||
|
||||
137
src/Markdig/Extensions/MediaLinks/HostProviderBuilder.cs
Normal file
137
src/Markdig/Extensions/MediaLinks/HostProviderBuilder.cs
Normal file
@@ -0,0 +1,137 @@
|
||||
// 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;
|
||||
|
||||
namespace Markdig.Extensions.MediaLinks
|
||||
{
|
||||
public class HostProviderBuilder
|
||||
{
|
||||
private sealed class DelegateProvider : IHostProvider
|
||||
{
|
||||
public string HostPrefix { get; set; }
|
||||
public Func<Uri, string> Delegate { get; set; }
|
||||
public bool AllowFullScreen { get; set; } = true;
|
||||
public string Class { get; set; }
|
||||
|
||||
public bool TryHandle(Uri mediaUri, bool isSchemaRelative, out string iframeUrl)
|
||||
{
|
||||
if (!mediaUri.Host.StartsWith(HostPrefix, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
iframeUrl = null;
|
||||
return false;
|
||||
}
|
||||
iframeUrl = Delegate(mediaUri);
|
||||
return !string.IsNullOrEmpty(iframeUrl);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a <see cref="IHostProvider"/> with delegate handler.
|
||||
/// </summary>
|
||||
/// <param name="hostPrefix">Prefix of host that can be handled.</param>
|
||||
/// <param name="handler">Handler that generate iframe url, if uri cannot be handled, it can return <see langword="null"/>.</param>
|
||||
/// <param name="allowFullScreen">Should the generated iframe has allowfullscreen attribute.</param>
|
||||
/// <param name="iframeClass">"class" attribute of generated iframe.</param>
|
||||
/// <returns>A <see cref="IHostProvider"/> with delegate handler.</returns>
|
||||
public static IHostProvider Create(string hostPrefix, Func<Uri, string> handler, bool allowFullScreen = true, string iframeClass = null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(hostPrefix))
|
||||
throw new ArgumentException("hostPrefix is null or empty.", nameof(hostPrefix));
|
||||
if (handler == null)
|
||||
throw new ArgumentNullException(nameof(handler));
|
||||
return new DelegateProvider { HostPrefix = hostPrefix, Delegate = handler, AllowFullScreen = allowFullScreen, Class = iframeClass };
|
||||
}
|
||||
|
||||
internal static Dictionary<string, IHostProvider> KnownHosts { get; }
|
||||
= new Dictionary<string, IHostProvider>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
["YouTube"] = Create("www.youtube.com", YouTube, iframeClass: "youtube"),
|
||||
["YouTubeShortened"] = Create("youtu.be", YouTubeShortened, iframeClass: "youtube"),
|
||||
["Vimeo"] = Create("vimeo.com", Vimeo, iframeClass: "vimeo"),
|
||||
["Yandex"] = Create("music.yandex.ru", Yandex, allowFullScreen: false, iframeClass: "yandex"),
|
||||
["Odnoklassniki"] = Create("ok.ru", Odnoklassniki, iframeClass: "odnoklassniki"),
|
||||
};
|
||||
|
||||
#region Known providers
|
||||
|
||||
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)
|
||||
{
|
||||
string uriPath = uri.AbsolutePath;
|
||||
if (string.Equals(uriPath, "/embed", StringComparison.OrdinalIgnoreCase) || uriPath.StartsWith("/embed/", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return uri.ToString();
|
||||
}
|
||||
if (!string.Equals(uriPath, "/watch", StringComparison.OrdinalIgnoreCase) && !uriPath.StartsWith("/watch/", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var queryParams = SplitQuery(uri);
|
||||
return BuildYouTubeIframeUrl(
|
||||
queryParams.FirstOrDefault(p => p.StartsWith("v="))?.Substring(2),
|
||||
queryParams.FirstOrDefault(p => p.StartsWith("t="))?.Substring(2)
|
||||
);
|
||||
}
|
||||
|
||||
private static string YouTubeShortened(Uri uri)
|
||||
{
|
||||
return BuildYouTubeIframeUrl(
|
||||
uri.AbsolutePath.Substring(1),
|
||||
SplitQuery(uri).FirstOrDefault(p => p.StartsWith("t="))?.Substring(2)
|
||||
);
|
||||
}
|
||||
|
||||
private static string BuildYouTubeIframeUrl(string videoId, string startTime)
|
||||
{
|
||||
if (string.IsNullOrEmpty(videoId))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
string url = $"https://www.youtube.com/embed/{videoId}";
|
||||
return string.IsNullOrEmpty(startTime) ? url : $"{url}?start={startTime}";
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
36
src/Markdig/Extensions/MediaLinks/IHostProvider.cs
Normal file
36
src/Markdig/Extensions/MediaLinks/IHostProvider.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
// Copyright (c) Alexandre Mutel. All rights reserved.
|
||||
// This file is licensed under the BSD-Clause 2 license.
|
||||
// See the license.txt file in the project root for more information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Markdig.Extensions.MediaLinks
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides url for media links.
|
||||
/// </summary>
|
||||
public interface IHostProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// "class" attribute of generated iframe.
|
||||
/// </summary>
|
||||
string Class { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Generate url for iframe.
|
||||
/// </summary>
|
||||
/// <param name="mediaUri">Input media uri.</param>
|
||||
/// <param name="isSchemaRelative"><see langword="true"/> if <paramref name="mediaUri"/> is a schema relative uri, i.e. uri starts with "//".</param>
|
||||
/// <param name="iframeUrl">Generated url for iframe.</param>
|
||||
/// <seealso href="https://tools.ietf.org/html/rfc3986#section-4.2"/>
|
||||
bool TryHandle(Uri mediaUri, bool isSchemaRelative, out string iframeUrl);
|
||||
|
||||
/// <summary>
|
||||
/// Should the generated iframe has allowfullscreen attribute.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Should be false for audio embedding.
|
||||
/// </remarks>
|
||||
bool AllowFullScreen { get; }
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,6 @@
|
||||
// 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;
|
||||
@@ -55,18 +54,28 @@ namespace Markdig.Extensions.MediaLinks
|
||||
}
|
||||
|
||||
Uri uri;
|
||||
bool isSchemaRelative = false;
|
||||
// Only process absolute Uri
|
||||
if (!Uri.TryCreate(linkInline.Url, UriKind.RelativeOrAbsolute, out uri) || !uri.IsAbsoluteUri)
|
||||
{
|
||||
return false;
|
||||
// see https://tools.ietf.org/html/rfc3986#section-4.2
|
||||
// since relative uri doesn't support many properties, "http" is used as a placeholder here.
|
||||
if (linkInline.Url.StartsWith("//") && Uri.TryCreate("http:" + linkInline.Url, UriKind.Absolute, out uri))
|
||||
{
|
||||
isSchemaRelative = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (TryRenderIframeFromKnownProviders(uri, renderer, linkInline))
|
||||
if (TryRenderIframeFromKnownProviders(uri, isSchemaRelative, renderer, linkInline))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (TryGuessAudioVideoFile(uri, renderer, linkInline))
|
||||
if (TryGuessAudioVideoFile(uri, isSchemaRelative, renderer, linkInline))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -86,7 +95,7 @@ namespace Markdig.Extensions.MediaLinks
|
||||
return htmlAttributes;
|
||||
}
|
||||
|
||||
private bool TryGuessAudioVideoFile(Uri uri, HtmlRenderer renderer, LinkInline linkInline)
|
||||
private bool TryGuessAudioVideoFile(Uri uri, bool isSchemaRelative, 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
|
||||
@@ -106,6 +115,10 @@ namespace Markdig.Extensions.MediaLinks
|
||||
htmlAttributes.AddPropertyIfNotExist("height", Options.Height);
|
||||
}
|
||||
htmlAttributes.AddPropertyIfNotExist("controls", null);
|
||||
|
||||
if (!string.IsNullOrEmpty(Options.Class))
|
||||
htmlAttributes.AddPropertyIfNotExist("class", Options.Class);
|
||||
|
||||
renderer.WriteAttributes(htmlAttributes);
|
||||
|
||||
renderer.Write($"><source type=\"{mimeType}\" src=\"{linkInline.Url}\"></source></{tagType}>");
|
||||
@@ -115,38 +128,18 @@ namespace Markdig.Extensions.MediaLinks
|
||||
return false;
|
||||
}
|
||||
|
||||
#region Known providers
|
||||
|
||||
private class KnownProvider
|
||||
private bool TryRenderIframeFromKnownProviders(Uri uri, bool isSchemaRelative, HtmlRenderer renderer, LinkInline linkInline)
|
||||
{
|
||||
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 = "youtu.be", Delegate = YouTubeShortened},
|
||||
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
|
||||
IHostProvider foundProvider = null;
|
||||
string iframeUrl = null;
|
||||
foreach (var provider in Options.Hosts)
|
||||
{
|
||||
if (!provider.TryHandle(uri, isSchemaRelative, out iframeUrl))
|
||||
continue;
|
||||
foundProvider = provider;
|
||||
break;
|
||||
}
|
||||
|
||||
if (foundProvider == null)
|
||||
{
|
||||
@@ -155,17 +148,20 @@ namespace Markdig.Extensions.MediaLinks
|
||||
|
||||
var htmlAttributes = GetHtmlAttributes(linkInline);
|
||||
renderer.Write("<iframe src=\"");
|
||||
renderer.WriteEscapeUrl(foundProvider.Result);
|
||||
renderer.WriteEscapeUrl(iframeUrl);
|
||||
renderer.Write("\"");
|
||||
|
||||
if(!string.IsNullOrEmpty(Options.Width))
|
||||
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);
|
||||
if (!string.IsNullOrEmpty(Options.Class) || !string.IsNullOrEmpty(foundProvider.Class))
|
||||
htmlAttributes.AddPropertyIfNotExist("class",
|
||||
(!string.IsNullOrEmpty(Options.Class) && !string.IsNullOrEmpty(foundProvider.Class))
|
||||
? Options.Class + " " + foundProvider.Class
|
||||
: Options.Class + foundProvider.Class);
|
||||
|
||||
htmlAttributes.AddPropertyIfNotExist("frameborder", "0");
|
||||
if (foundProvider.AllowFullScreen)
|
||||
@@ -177,81 +173,5 @@ namespace Markdig.Extensions.MediaLinks
|
||||
|
||||
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)
|
||||
{
|
||||
string uriPath = uri.AbsolutePath;
|
||||
if (string.Equals(uriPath, "/embed", StringComparison.OrdinalIgnoreCase) || uriPath.StartsWith("/embed/", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return uri.ToString();
|
||||
}
|
||||
if (!string.Equals(uriPath, "/watch", StringComparison.OrdinalIgnoreCase) && !uriPath.StartsWith("/watch/", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var queryParams = SplitQuery(uri);
|
||||
return BuildYouTubeIframeUrl(
|
||||
queryParams.FirstOrDefault(p => p.StartsWith("v="))?.Substring(2),
|
||||
queryParams.FirstOrDefault(p => p.StartsWith("t="))?.Substring(2)
|
||||
);
|
||||
}
|
||||
|
||||
private static string YouTubeShortened(Uri uri)
|
||||
{
|
||||
return BuildYouTubeIframeUrl(
|
||||
uri.AbsolutePath.Substring(1),
|
||||
SplitQuery(uri).FirstOrDefault(p => p.StartsWith("t="))?.Substring(2)
|
||||
);
|
||||
}
|
||||
|
||||
private static string BuildYouTubeIframeUrl(string videoId, string startTime)
|
||||
{
|
||||
if (string.IsNullOrEmpty(videoId))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
string url = $"https://www.youtube.com/embed/{videoId}";
|
||||
return string.IsNullOrEmpty(startTime) ? url : $"{url}?start={startTime}";
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,12 +76,14 @@ namespace Markdig.Extensions.MediaLinks
|
||||
{".ecelp7470", "audio/vnd.nuera.ecelp7470"},
|
||||
{".ecelp9600", "audio/vnd.nuera.ecelp9600"},
|
||||
{".oga", "audio/ogg"},
|
||||
{".ogg", "audio/ogg"},
|
||||
{".weba", "audio/webm"},
|
||||
{".ram", "audio/x-pn-realaudio"},
|
||||
{".rmp", "audio/x-pn-realaudio-plugin"},
|
||||
{".au", "audio/basic"},
|
||||
{".wav", "audio/x-wav"},
|
||||
};
|
||||
Hosts = new List<IHostProvider>(HostProviderBuilder.KnownHosts.Values);
|
||||
}
|
||||
|
||||
public string Width { get; set; }
|
||||
@@ -91,5 +93,7 @@ namespace Markdig.Extensions.MediaLinks
|
||||
public string Class { get; set; }
|
||||
|
||||
public Dictionary<string, string> ExtensionToMimeType { get; }
|
||||
|
||||
public List<IHostProvider> Hosts { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,7 +177,7 @@ namespace Markdig.Helpers
|
||||
{
|
||||
// 2.1 Characters and lines
|
||||
// A whitespace character is a space(U + 0020), tab(U + 0009), newline(U + 000A), line tabulation (U + 000B), form feed (U + 000C), or carriage return (U + 000D).
|
||||
return c == ' ' || c == '\t' || c == '\n' || c == '\v' || c == '\f' || c == '\r';
|
||||
return c <= ' ' && (c == ' ' || c == '\t' || c == '\n' || c == '\v' || c == '\f' || c == '\r');
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
|
||||
@@ -303,8 +303,8 @@ namespace Markdig.Helpers
|
||||
|
||||
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
|
||||
public static bool IsAlpha(this char c)
|
||||
{
|
||||
return ((uint)(c - 'a') <= ('z' - 'a')) || ((uint)(c - 'A') <= ('Z' - 'A'));
|
||||
{
|
||||
return (uint)((c - 'A') & ~0x20) <= ('Z' - 'A');
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
|
||||
@@ -370,16 +370,20 @@ namespace Markdig.Helpers
|
||||
|
||||
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
|
||||
public static bool IsHighSurrogate(char c)
|
||||
{
|
||||
return ((c >= HighSurrogateStart) && (c <= HighSurrogateEnd));
|
||||
{
|
||||
return IsInInclusiveRange(c, HighSurrogateStart, HighSurrogateEnd);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
|
||||
public static bool IsLowSurrogate(char c)
|
||||
{
|
||||
return ((c >= LowSurrogateStart) && (c <= LowSurrogateEnd));
|
||||
return IsInInclusiveRange(c, LowSurrogateStart, LowSurrogateEnd);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
|
||||
private static bool IsInInclusiveRange(char c, char min, char max)
|
||||
=> (uint) (c - min) <= (uint) (max - min);
|
||||
|
||||
public static int ConvertToUtf32(char highSurrogate, char lowSurrogate)
|
||||
{
|
||||
if (!IsHighSurrogate(highSurrogate))
|
||||
@@ -393,13 +397,14 @@ namespace Markdig.Helpers
|
||||
return (((highSurrogate - HighSurrogateStart) * 0x400) + (lowSurrogate - LowSurrogateStart) + UnicodePlane01Start);
|
||||
}
|
||||
|
||||
public static IEnumerable<int> ToUtf32(string text)
|
||||
public static IEnumerable<int> ToUtf32(StringSlice text)
|
||||
{
|
||||
for (int i = 0; i < text.Length; i++)
|
||||
for (int i = text.Start; i <= text.End; i++)
|
||||
{
|
||||
if (IsHighSurrogate(text[i]) && i < text.Length - 1 && IsLowSurrogate(text[i + 1]))
|
||||
{
|
||||
yield return ConvertToUtf32(text[i], text[i + 1]);
|
||||
if (IsHighSurrogate(text[i]) && i < text.End && IsLowSurrogate(text[i + 1]))
|
||||
{
|
||||
Debug.Assert(char.IsSurrogatePair(text[i], text[i + 1]));
|
||||
yield return char.ConvertToUtf32(text[i], text[i + 1]);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<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>
|
||||
<NeutralLanguage>en-US</NeutralLanguage>
|
||||
<VersionPrefix>0.18.0</VersionPrefix>
|
||||
<VersionPrefix>0.18.1</VersionPrefix>
|
||||
<Authors>Alexandre Mutel</Authors>
|
||||
<TargetFrameworks>net35;net40;netstandard2.0;uap10.0;netcoreapp2.1</TargetFrameworks>
|
||||
<PackageTags>Markdown CommonMark md html md2html</PackageTags>
|
||||
|
||||
@@ -92,8 +92,8 @@ namespace Markdig
|
||||
.UseDiagrams()
|
||||
.UseAutoLinks()
|
||||
.UseGenericAttributes(); // Must be last as it is one parser that is modifying other parsers
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uses this extension to enable autolinks from text `http://`, `https://`, `ftp://`, `mailto:`, `www.xxx.yyy`
|
||||
/// </summary>
|
||||
@@ -421,20 +421,35 @@ namespace Markdig
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uses the emoji and smiley extension.
|
||||
/// Uses the emojis and smileys extension.
|
||||
/// </summary>
|
||||
/// <param name="pipeline">The pipeline.</param>
|
||||
/// <param name="enableSmiley">Enable smiley in addition to Emoji, <c>true</c> by default.</param>
|
||||
/// <param name="enableSmileys">Enable smileys in addition to emoji shortcodes, <c>true</c> by default.</param>
|
||||
/// <returns>The modified pipeline</returns>
|
||||
public static MarkdownPipelineBuilder UseEmojiAndSmiley(this MarkdownPipelineBuilder pipeline, bool enableSmiley = true)
|
||||
public static MarkdownPipelineBuilder UseEmojiAndSmiley(this MarkdownPipelineBuilder pipeline, bool enableSmileys = true)
|
||||
{
|
||||
if (!pipeline.Extensions.Contains<EmojiExtension>())
|
||||
{
|
||||
pipeline.Extensions.Add(new EmojiExtension(enableSmiley));
|
||||
var emojiMapping = enableSmileys ? EmojiMapping.DefaultEmojisAndSmileysMapping : EmojiMapping.DefaultEmojisOnlyMapping;
|
||||
pipeline.Extensions.Add(new EmojiExtension(emojiMapping));
|
||||
}
|
||||
return pipeline;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uses the emojis and smileys extension.
|
||||
/// </summary>
|
||||
/// <param name="pipeline">The pipeline.</param>
|
||||
/// <param name="customEmojiMapping">Enable customization of the emojis and smileys mapping.</param>
|
||||
/// <returns>The modified pipeline</returns>
|
||||
public static MarkdownPipelineBuilder UseEmojiAndSmiley(this MarkdownPipelineBuilder pipeline, EmojiMapping customEmojiMapping)
|
||||
{
|
||||
if (!pipeline.Extensions.Contains<EmojiExtension>())
|
||||
{
|
||||
pipeline.Extensions.Add(new EmojiExtension(customEmojiMapping));
|
||||
}
|
||||
return pipeline;
|
||||
}
|
||||
/// <summary>
|
||||
/// Add rel=nofollow to all links rendered to HTML.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
// Copyright (c) Alexandre Mutel. All rights reserved.
|
||||
// This file is licensed under the BSD-Clause 2 license.
|
||||
// See the license.txt file in the project root for more information.
|
||||
using Markdig.Helpers;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
|
||||
namespace Markdig.Syntax.Inlines
|
||||
@@ -94,8 +96,20 @@ namespace Markdig.Syntax.Inlines
|
||||
/// <returns>An enumeration of T</returns>
|
||||
public IEnumerable<T> FindDescendants<T>() where T : Inline
|
||||
{
|
||||
// Fast-path an empty container to avoid allocating a Stack
|
||||
if (LastChild == null) yield break;
|
||||
if (FirstChild is null)
|
||||
{
|
||||
return ArrayHelper<T>.Empty;
|
||||
}
|
||||
else
|
||||
{
|
||||
return FindDescendantsInternal<T>();
|
||||
}
|
||||
}
|
||||
internal IEnumerable<T> FindDescendantsInternal<T>() where T : MarkdownObject
|
||||
{
|
||||
#if !UAP
|
||||
Debug.Assert(typeof(T).IsSubclassOf(typeof(Inline)));
|
||||
#endif
|
||||
|
||||
Stack<Inline> stack = new Stack<Inline>();
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
// See the license.txt file in the project root for more information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using Markdig.Helpers;
|
||||
using Markdig.Syntax.Inlines;
|
||||
|
||||
namespace Markdig.Syntax
|
||||
@@ -20,10 +22,6 @@ namespace Markdig.Syntax
|
||||
/// <returns>An iteration over the descendant elements</returns>
|
||||
public static IEnumerable<MarkdownObject> Descendants(this MarkdownObject markdownObject)
|
||||
{
|
||||
// Fast-path an object with no children to avoid allocating Stack objects
|
||||
if (!(markdownObject is ContainerBlock) && !(markdownObject is ContainerInline)) yield break;
|
||||
|
||||
// TODO: A single Stack<(MarkdownObject block, bool push)> when ValueTuples are available
|
||||
Stack<MarkdownObject> stack = new Stack<MarkdownObject>();
|
||||
Stack<bool> pushStack = new Stack<bool>();
|
||||
|
||||
@@ -66,7 +64,51 @@ namespace Markdig.Syntax
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Iterates over the descendant elements for the specified markdown <see cref="Inline" /> element and filters by the type {T}.
|
||||
/// Iterates over the descendant elements for the specified markdown element, including <see cref="Block"/> and <see cref="Inline"/> and filters by the type <typeparamref name="T"/>.
|
||||
/// <para>The descendant elements are returned in DFS-like order.</para>
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type to use for filtering the descendants</typeparam>
|
||||
/// <param name="markdownObject">The markdown object.</param>
|
||||
/// <returns>An iteration over the descendant elements</returns>
|
||||
public static IEnumerable<T> Descendants<T>(this MarkdownObject markdownObject) where T : MarkdownObject
|
||||
{
|
||||
#if UAP
|
||||
foreach (MarkdownObject descendant in markdownObject.Descendants())
|
||||
{
|
||||
if (descendant is T descendantT)
|
||||
{
|
||||
yield return descendantT;
|
||||
}
|
||||
}
|
||||
#else
|
||||
if (typeof(T).IsSubclassOf(typeof(Block)))
|
||||
{
|
||||
if (markdownObject is ContainerBlock containerBlock && containerBlock.Count > 0)
|
||||
{
|
||||
return BlockDescendantsInternal<T>(containerBlock);
|
||||
}
|
||||
}
|
||||
else // typeof(T).IsSubclassOf(typeof(Inline)))
|
||||
{
|
||||
if (markdownObject is ContainerBlock containerBlock)
|
||||
{
|
||||
if (containerBlock.Count > 0)
|
||||
{
|
||||
return InlineDescendantsInternal<T>(containerBlock);
|
||||
}
|
||||
}
|
||||
else if (markdownObject is ContainerInline containerInline && containerInline.FirstChild != null)
|
||||
{
|
||||
return containerInline.FindDescendantsInternal<T>();
|
||||
}
|
||||
}
|
||||
|
||||
return ArrayHelper<T>.Empty;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Iterates over the descendant elements for the specified markdown <see cref="Inline" /> element and filters by the type <typeparamref name="T"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type to use for filtering the descendants</typeparam>
|
||||
/// <param name="inline">The inline markdown object.</param>
|
||||
@@ -77,7 +119,7 @@ namespace Markdig.Syntax
|
||||
=> inline.FindDescendants<T>();
|
||||
|
||||
/// <summary>
|
||||
/// Iterates over the descendant elements for the specified markdown <see cref="Block" /> element and filters by the type {T}.
|
||||
/// Iterates over the descendant elements for the specified markdown <see cref="Block" /> element and filters by the type <typeparamref name="T"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type to use for filtering the descendants</typeparam>
|
||||
/// <param name="block">The markdown object.</param>
|
||||
@@ -86,8 +128,21 @@ namespace Markdig.Syntax
|
||||
/// </returns>
|
||||
public static IEnumerable<T> Descendants<T>(this ContainerBlock block) where T : Block
|
||||
{
|
||||
// Fast-path an empty container to avoid allocating a Stack
|
||||
if (block.Count == 0) yield break;
|
||||
if (block != null && block.Count > 0)
|
||||
{
|
||||
return BlockDescendantsInternal<T>(block);
|
||||
}
|
||||
else
|
||||
{
|
||||
return ArrayHelper<T>.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<T> BlockDescendantsInternal<T>(ContainerBlock block) where T : MarkdownObject
|
||||
{
|
||||
#if !UAP
|
||||
Debug.Assert(typeof(T).IsSubclassOf(typeof(Block)));
|
||||
#endif
|
||||
|
||||
Stack<Block> stack = new Stack<Block>();
|
||||
|
||||
@@ -115,5 +170,20 @@ namespace Markdig.Syntax
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<T> InlineDescendantsInternal<T>(ContainerBlock block) where T : MarkdownObject
|
||||
{
|
||||
#if !UAP
|
||||
Debug.Assert(typeof(T).IsSubclassOf(typeof(Inline)));
|
||||
#endif
|
||||
|
||||
foreach (MarkdownObject descendant in block.Descendants())
|
||||
{
|
||||
if (descendant is T descendantT)
|
||||
{
|
||||
yield return descendantT;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user