Compare commits

...

63 Commits

Author SHA1 Message Date
Alexandre Mutel
5528023158 Merge pull request #844 from snnz/fix-gridtables
Prevent GridTableParser from looking beyond the end of a line.
2025-01-09 18:15:11 +01:00
Alexandre Mutel
f93b9d79d9 Merge branch 'master' into fix-gridtables 2025-01-06 08:43:45 +01:00
Alexandre Mutel
d53fd0e870 Merge pull request #843 from snnz/fix-deflists
Fixes exception in DefinitionListParser.GetCurrentDefinitionList()
2025-01-06 08:42:36 +01:00
Alexandre Mutel
c488aca96c Merge branch 'master' into fix-deflists 2025-01-05 21:12:33 +01:00
Alexandre Mutel
9b3f442765 Merge pull request #842 from snnz/fix-alerts
Check that the alert candidate is not already in an alert block or nested within other elements.
2025-01-05 21:11:11 +01:00
Sergey Nozhenko
7b6d659bbd A test has been added. 2025-01-03 07:03:28 +03:00
Sergey Nozhenko
bc8ba4fecb A test has been added. 2025-01-03 07:02:38 +03:00
Sergey Nozhenko
d87bb7292d A test has been added. 2025-01-03 07:01:29 +03:00
Sergey Nozhenko
118d28f886 Prevent GridTableParser from looking beyond the end of a line. 2025-01-03 04:29:24 +03:00
Sergey Nozhenko
3e0c72f043 Fixes exception in DefinitionListParser.GetCurrentDefinitionList() 2025-01-03 03:30:49 +03:00
Sergey Nozhenko
f2590e7b80 Check that the alert candidate is not already in an alert block or nested within other elements. 2025-01-03 01:27:11 +03:00
Alexandre Mutel
d1233ffe66 Merge pull request #837 from snnz/fix-links
Fix errors in LinkHelper and LinkInlineParser.
2024-12-27 09:49:04 +01:00
Sergey Nozhenko
ab8e85b06e Remove additional condition, since a carriage return constitute a line ending regardless of whether it is followed by a line feed or not. 2024-12-21 06:56:23 +03:00
snnz
90bc15c016 Update src/Markdig.Tests/TestPlayParser.cs
Co-authored-by: Miha Zupan <mihazupan.zupan1@gmail.com>
2024-12-21 06:14:16 +03:00
snnz
7f604bef30 Update src/Markdig/Parsers/Inlines/LinkInlineParser.cs
Co-authored-by: Miha Zupan <mihazupan.zupan1@gmail.com>
2024-12-21 06:14:07 +03:00
snnz
54783b8f65 Update src/Markdig/Parsers/Inlines/LinkInlineParser.cs
Co-authored-by: Miha Zupan <mihazupan.zupan1@gmail.com>
2024-12-21 06:13:56 +03:00
snnz
ad0770a594 Update src/Markdig/Parsers/Inlines/LinkInlineParser.cs
Co-authored-by: Miha Zupan <mihazupan.zupan1@gmail.com>
2024-12-21 06:13:22 +03:00
snnz
90365bfeee Update src/Markdig/Parsers/Inlines/LinkInlineParser.cs
Co-authored-by: Miha Zupan <mihazupan.zupan1@gmail.com>
2024-12-21 06:13:09 +03:00
Sergey Nozhenko
c35f7fff17 Fixed errors in LinkHelper and LinkInlineParser. 2024-12-21 03:29:31 +03:00
Alexandre Mutel
fdaef77474 Update ci badge 2024-12-19 05:48:36 +01:00
Alexandre Mutel
733c028311 Merge pull request #836 from snnz/fix-abbreviation
Fix an error in the AbbreviationParser.
2024-12-19 05:46:48 +01:00
Sergey Nozhenko
bc41b0c2a3 Existing test has been extended. 2024-12-19 00:44:29 +03:00
Alexandre Mutel
a8de2087d8 Merge pull request #835 from snnz/fix-pipetable-span
Include opening and closing pipes in the table span
2024-12-18 20:36:31 +01:00
Sergey Nozhenko
2cff6c5194 It's necessary to keep a copy of the original literal.Span.End, because otherwise it is just lost in some cases. 2024-12-18 13:09:00 +03:00
Sergey Nozhenko
5e4a917dbd Fixes an error in the AbbreviationParser. 2024-12-18 12:38:13 +03:00
Sergey Nozhenko
aff8a6823a A test has been added. 2024-12-18 10:21:29 +03:00
Alexandre Mutel
b8a3c270cc Merge pull request #834 from snnz/fix-gridtables
Fix an incorrect offset in GridTableParser.
2024-12-18 06:47:17 +01:00
Sergey Nozhenko
68659f4037 Include opening and closing pipes in the table span 2024-12-18 07:43:22 +03:00
Sergey Nozhenko
e92a8097d0 Fixes an incorrect offset in GridTableParser. 2024-12-18 02:36:56 +03:00
Alexandre Mutel
57fad6fc1a Merge pull request #828 from MihaZupan/net90
Add .NET 9 target, drop .NET 6
2024-12-17 08:43:00 +01:00
Alexandre Mutel
260f4d5acc Merge pull request #832 from snnz/footnote
Set the correct source location in Footnote and FootnoteLinkReferenceDefinition
2024-12-17 08:41:22 +01:00
Alexandre Mutel
102d02a6c1 Merge pull request #831 from snnz/custom-container
Set delimiter char and count in CustomContainerInline instances.
2024-12-17 08:40:55 +01:00
Alexandre Mutel
5ae8ab7a74 Merge pull request #829 from snnz/fix-emphasis-span
Fix incorrect emphasis span calculation.
2024-12-17 08:40:24 +01:00
Sergey Nozhenko
eb28f76588 Set the correct source location to the Footnote and FootnoteLinkReferenceDefinition. 2024-12-15 20:13:35 +03:00
Sergey Nozhenko
d0311b4cea Set delimiter in the CustomContainer instance. 2024-12-15 19:49:56 +03:00
Sergey Nozhenko
a11899a350 Fixes emphasis span calculation. A test is added. 2024-12-09 22:20:32 +03:00
Miha Zupan
40781737c3 Tweak SpecFileGen paths 2024-11-30 03:19:49 +01:00
Miha Zupan
455f8f333d Fix dotnet-versions format 2024-11-29 20:22:40 +01:00
Miha Zupan
98a060f2a3 Add .NET 9 target, drop .NET 6 2024-11-29 19:06:58 +01:00
snnz
49cf59b819 Fix extra line feeds in link title (#826)
* Fix extra line feeds in link title

* Test added.
2024-11-24 11:51:56 +01:00
Alexandre Mutel
310a55c724 Update readme.md 2024-10-30 19:08:35 +01:00
Alexandre Mutel
f734e91568 Merge pull request #823 from xoofx/fix-mermaid
Update DiagramExtension.cs
2024-10-25 22:01:40 +02:00
Alexandre Mutel
090e6d791a Update DiagramExtension.cs
Update tests
2024-10-25 21:58:28 +02:00
Alexandre Mutel
41bdb0f0ab Merge pull request #817 from ehsankalafchi/rename-variable
Rename a variable
2024-10-01 20:53:47 +02:00
Ehsan Kalafchi
b27ef11240 Rename a variable 2024-09-26 13:51:18 +02:00
Alexandre Mutel
dfa2c94b88 Merge pull request #808 from digvijayad/master
Fix mermaid link in readme.md
2024-07-18 06:50:19 +02:00
Digvijay Naruka
89330f3524 Update mermaid link in readme.md
Updated broken old GitHub pages link to the new domain https://mermaid.js.org/
2024-07-17 12:44:26 +05:30
Alexandre Mutel
1a1bbecc46 Merge pull request #786 from MartinZikmund/feature/youtube-short-support
Support for YouTube Shorts embedding
2024-04-09 21:11:30 +02:00
Martin Zikmund
68bd3074b2 Add support for YouTube Shorts embedding 2024-04-01 17:42:46 +02:00
Martin Zikmund
e486903687 Test support for YouTube Shorts embedding 2024-04-01 17:42:12 +02:00
Alexandre Mutel
8e22754db4 Merge pull request #785 from toothache/fix_issues
Fix issues for math span calculation
2024-03-29 21:23:31 +01:00
teethache
93d88ab994 Fix math span calculation. 2024-03-27 07:06:56 +08:00
Alexandre Mutel
000393f46a Fix invalid setext heading (#785) 2024-03-26 21:02:48 +01:00
Alexandre Mutel
a5796890e1 Merge pull request #784 from Abrynos/case-invariant-alerts
Make alert block headers case-invariant
2024-03-25 21:26:19 +01:00
Sebastian Göls
c19ba5b0eb Add fallback value in order to mark unknown alert kinds in some way as well 2024-03-25 12:29:22 +01:00
Sebastian Göls
03390e4f71 Misc. 2024-03-18 10:28:36 +01:00
Sebastian Göls
42bd65caaf Apply feedback 2024-03-18 09:18:39 +01:00
Sebastian Göls
b7ae04bdba Make alert block headers case-invariant 2024-03-18 09:04:47 +01:00
Alexandre Mutel
391f376fa2 Merge pull request #782 from Abrynos/bootstrap-alerts
Add bootstrap alert renderer
2024-03-18 08:11:19 +01:00
Sebastian Göls
f9e96bc9c9 Apply feedback 2024-03-18 07:48:22 +01:00
Alexandre Mutel
c75a11ec32 Update parsing-extensions.md 2024-03-17 15:01:14 +01:00
Alexandre Mutel
fd226d53e9 Update readme.md 2024-03-17 14:59:05 +01:00
Sebastian Göls
7132584996 Add bootstrap alert renderer 2024-03-15 11:28:48 +01:00
35 changed files with 422 additions and 200 deletions

View File

@@ -13,6 +13,9 @@ jobs:
build:
uses: xoofx/.github/.github/workflows/dotnet.yml@main
with:
dotnet-version: '6.0 8.0'
dotnet-version: |
6.0
8.0
9.0
secrets:
NUGET_TOKEN: ${{ secrets.NUGET_TOKEN }}

View File

@@ -1,6 +1,6 @@
# Extensions and Parsers
Markdig was [implemented in such a way](http://xoofx.com/blog/2016/06/13/implementing-a-markdown-processor-for-dotnet/) as to be extremely pluggable, with even basic behaviors being mutable and extendable.
Markdig was [implemented in such a way](http://xoofx.github.io/blog/2016/06/13/implementing-a-markdown-processor-for-dotnet/) as to be extremely pluggable, with even basic behaviors being mutable and extendable.
The basic mechanism for extension of Markdig is the `IMarkdownExtension` interface, which allows any implementing class to be registered with the pipeline builder and thus to directly modify the collections of `BlockParser` and `InlineParser` objects which end up in the pipeline.

View File

@@ -1,4 +1,4 @@
# Markdig [![Build Status](https://github.com/lunet-io/markdig/workflows/ci/badge.svg?branch=master)](https://github.com/lunet-io/markdig/actions) [![Coverage Status](https://coveralls.io/repos/github/xoofx/markdig/badge.svg?branch=master)](https://coveralls.io/github/xoofx/markdig?branch=master) [![NuGet](https://img.shields.io/nuget/v/Markdig.svg)](https://www.nuget.org/packages/Markdig/) [![Donate](https://www.paypalobjects.com/en_US/i/btn/btn_donate_SM.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=FRGHXBTP442JL)
# Markdig [![ci](https://github.com/xoofx/markdig/actions/workflows/ci.yml/badge.svg)](https://github.com/xoofx/markdig/actions/workflows/ci.yml) [![Coverage Status](https://coveralls.io/repos/github/xoofx/markdig/badge.svg?branch=master)](https://coveralls.io/github/xoofx/markdig?branch=master) [![NuGet](https://img.shields.io/nuget/v/Markdig.svg)](https://www.nuget.org/packages/Markdig/) [![Donate](https://www.paypalobjects.com/en_US/i/btn/btn_donate_SM.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=FRGHXBTP442JL)
<img align="right" width="160px" height="160px" src="img/markdig.png">
@@ -48,7 +48,7 @@ You can **try Markdig online** and compare it to other implementations on [babel
- [**Emoji**](src/Markdig.Tests/Specs/EmojiSpecs.md) support (inspired from [Markdown-it](https://markdown-it.github.io/))
- [**SmartyPants**](src/Markdig.Tests/Specs/SmartyPantsSpecs.md) (inspired from [Daring Fireball - SmartyPants](https://daringfireball.net/projects/smartypants/))
- [**Bootstrap**](src/Markdig.Tests/Specs/BootstrapSpecs.md) class (to output bootstrap class)
- [**Diagrams**](src/Markdig.Tests/Specs/DiagramsSpecs.md) extension whenever a fenced code block contains a special keyword, it will be converted to a div block with the content as-is (currently, supports [`mermaid`](https://knsv.github.io/mermaid/) and [`nomnoml`](https://github.com/skanaar/nomnoml) diagrams)
- [**Diagrams**](src/Markdig.Tests/Specs/DiagramsSpecs.md) extension whenever a fenced code block contains a special keyword, it will be converted to a div block with the content as-is (currently, supports [`mermaid`](https://mermaid.js.org) and [`nomnoml`](https://github.com/skanaar/nomnoml) diagrams)
- [**YAML Front Matter**](src/Markdig.Tests/Specs/YamlSpecs.md) to parse without evaluating the front matter and to discard it from the HTML output (typically used for previewing without the front matter in MarkdownEditor)
- [**JIRA links**](src/Markdig.Tests/Specs/JiraLinks.md) to automatically generate links for JIRA project references (Thanks to @clarkd: https://github.com/clarkd/MarkdigJiraLinker)
- Starting with Markdig version `0.20.0+`, Markdig is compatible only with `NETStandard 2.0`, `NETStandard 2.1`, `NETCoreApp 2.1` and `NETCoreApp 3.1`.
@@ -70,7 +70,7 @@ If you are looking for support for an old .NET Framework 3.5 or 4.0, you can dow
While there is not yet a dedicated documentation, you can find from the [specs documentation](src/Markdig.Tests/Specs/readme.md) how to use these extensions.
In the meantime, you can have a "behind the scene" article about Markdig in my blog post ["Implementing a Markdown Engine for .NET"](http://xoofx.com/blog/2016/06/13/implementing-a-markdown-processor-for-dotnet/)
In the meantime, you can have a "behind the scene" article about Markdig in my blog post ["Implementing a Markdown Engine for .NET"](http://xoofx.github.io/blog/2016/06/13/implementing-a-markdown-processor-for-dotnet/)
## Download
@@ -144,12 +144,12 @@ AMD Ryzen 9 5950X, 1 CPU, 32 logical and 16 physical cores
- Markdig is roughly **x100 times faster than MarkdownSharp**
- **20% faster than the reference cmark C implementation**
## Sponsors
## Donate
Supports this project with a monthly donation and help me continue improving it. \[[Become a sponsor](https://github.com/sponsors/xoofx)\]
If you are using this library and find it useful for your project, please consider a donation for it!
[![Donate](https://www.paypalobjects.com/en_US/i/btn/btn_donate_SM.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=FRGHXBTP442JL)
[<img src="https://github.com/lilith.png?size=200" width="64px;" style="border-radius: 50%" alt="lilith"/>](https://github.com/lilith) Lilith River, author of [Imageflow Server, an easy on-demand
image editing, optimization, and delivery server](https://github.com/imazen/imageflow-server)
## Credits
@@ -164,4 +164,4 @@ Some decoding part (e.g HTML [EntityHelper.cs](https://github.com/lunet-io/markd
Thanks to the work done by @clarkd on the JIRA Link extension (https://github.com/clarkd/MarkdigJiraLinker), now included with this project!
## Author
Alexandre MUTEL aka [xoofx](http://xoofx.com)
Alexandre MUTEL aka [xoofx](http://xoofx.github.io)

View File

@@ -1,13 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFrameworks>net6.0;net8.0;net9.0</TargetFrameworks>
<OutputType>Exe</OutputType>
<IsPackable>false</IsPackable>
<ImplicitUsings>enable</ImplicitUsings>
<LangVersion>13.0</LangVersion>
<StartupObject>Markdig.Tests.Program</StartupObject>
<SpecExecutable>$(MSBuildProjectDirectory)\..\SpecFileGen\bin\$(Configuration)\net8.0\SpecFileGen.dll</SpecExecutable>
<SpecTimestamp>$(MSBuildProjectDirectory)\..\SpecFileGen\bin\$(Configuration)\net8.0\SpecFileGen.timestamp</SpecTimestamp>
<SpecExecutable>$(MSBuildProjectDirectory)\..\SpecFileGen\bin\$(Configuration)\$(TargetFramework)\SpecFileGen.dll</SpecExecutable>
<SpecTimestamp>$(MSBuildProjectDirectory)\..\SpecFileGen\bin\$(Configuration)\$(TargetFramework)\SpecFileGen.timestamp</SpecTimestamp>
</PropertyGroup>
<ItemGroup>

View File

@@ -317,4 +317,73 @@ $$
Assert.That(paragraph.Inline.Span.Start == paragraph.Inline.FirstChild.Span.Start);
Assert.That(paragraph.Inline.Span.End == paragraph.Inline.LastChild.Span.End);
}
[Test]
public void TestGridTableShortLine()
{
var input = @"
+--+
| |
+-";
var expected = @"<table>
<col style=""width:100%"" />
<tbody>
<tr>
<td></td>
</tr>
</tbody>
</table>
";
TestParser.TestSpec(input, expected, new MarkdownPipelineBuilder().UseGridTables().Build());
}
[Test]
public void TestDefinitionListInListItemWithBlankLine()
{
var input = @"
-
term
: definition
";
var expected = @"<ul>
<li>
<dl>
<dt>term</dt>
<dd>definition</dd>
</dl>
</li>
</ul>
";
TestParser.TestSpec(input, expected, new MarkdownPipelineBuilder().UseDefinitionLists().Build());
}
[Test]
public void TestAlertWithinAlertOrNestedBlock()
{
var input = @"
>[!NOTE]
[!NOTE]
The second one is not a note.
>>[!NOTE]
Also not a note.
";
var expected = @"<div class=""markdown-alert markdown-alert-note"">
<p class=""markdown-alert-title""><svg viewBox=""0 0 16 16"" version=""1.1"" width=""16"" height=""16"" aria-hidden=""true""><path d=""M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8Zm8-6.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13ZM6.5 7.75A.75.75 0 0 1 7.25 7h1a.75.75 0 0 1 .75.75v2.75h.25a.75.75 0 0 1 0 1.5h-2a.75.75 0 0 1 0-1.5h.25v-2h-.25a.75.75 0 0 1-.75-.75ZM8 6a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z""></path></svg>Note</p>
<p>[!NOTE]
The second one is not a note.</p>
</div>
<blockquote>
<blockquote>
<p>[!NOTE]
Also not a note.</p>
</blockquote>
</blockquote>
";
TestParser.TestSpec(input, expected, new MarkdownPipelineBuilder().UseAlertBlocks().Build());
}
}

View File

@@ -17,7 +17,7 @@ namespace Markdig.Tests.Specs.Diagrams
//
// ## Mermaid diagrams
//
// Using a fenced code block with the `mermaid` language info will output a `<div class='mermaid'>` instead of a `pre/code` block:
// Using a fenced code block with the `mermaid` language info will output a `<pre class='mermaid'>` block (which is the default for other code block):
[Test]
public void ExtensionsMermaidDiagrams_Example001()
{
@@ -34,14 +34,14 @@ namespace Markdig.Tests.Specs.Diagrams
// ```
//
// Should be rendered as:
// <div class="mermaid">graph TD;
// <pre class="mermaid">graph TD;
// A-->B;
// A-->C;
// B-->D;
// C-->D;
// </div>
// </pre>
TestParser.TestSpec("```mermaid\ngraph TD;\n A-->B;\n A-->C;\n B-->D;\n C-->D;\n```", "<div class=\"mermaid\">graph TD;\n A-->B;\n A-->C;\n B-->D;\n C-->D;\n</div>", "diagrams|advanced", context: "Example 1\nSection Extensions / Mermaid diagrams\n");
TestParser.TestSpec("```mermaid\ngraph TD;\n A-->B;\n A-->C;\n B-->D;\n C-->D;\n```", "<pre class=\"mermaid\">graph TD;\n A-->B;\n A-->C;\n B-->D;\n C-->D;\n</pre>", "diagrams|advanced", context: "Example 1\nSection Extensions / Mermaid diagrams\n");
}
}

View File

@@ -4,7 +4,7 @@ Adds support for diagrams extension:
## Mermaid diagrams
Using a fenced code block with the `mermaid` language info will output a `<div class='mermaid'>` instead of a `pre/code` block:
Using a fenced code block with the `mermaid` language info will output a `<pre class='mermaid'>` block (which is the default for other code block):
```````````````````````````````` example
```mermaid
@@ -15,12 +15,12 @@ graph TD;
C-->D;
```
.
<div class="mermaid">graph TD;
<pre class="mermaid">graph TD;
A-->B;
A-->C;
B-->D;
C-->D;
</div>
</pre>
````````````````````````````````
## nomnoml diagrams

View File

@@ -81,6 +81,22 @@ public class TestLinkHelper
Assert.AreEqual(' ', text.CurrentChar);
}
[Test]
public void TestTitleMultiline()
{
var text = new StringSlice("'this\ris\r\na\ntitle'");
Assert.True(LinkHelper.TryParseTitle(ref text, out string title, out _));
Assert.AreEqual("this\ris\r\na\ntitle", title);
}
[Test]
public void TestTitleMultilineWithSpaceAndBackslash()
{
var text = new StringSlice("'a\n\\ \\\ntitle'");
Assert.True(LinkHelper.TryParseTitle(ref text, out string title, out _));
Assert.AreEqual("a\n\\ \\\ntitle", title);
}
[Test]
public void TestUrlAndTitle()
{
@@ -230,6 +246,13 @@ public class TestLinkHelper
}
[Test]
public void TestlLinkReferenceDefinitionInvalid()
{
var text = new StringSlice("[foo]: /url (title) x\n");
Assert.False(LinkHelper.TryParseLinkReferenceDefinition(ref text, out _, out _, out _, out _, out _, out _));
}
[Test]
public void TestAutoLinkUrlSimple()
{

View File

@@ -25,6 +25,7 @@ public class TestMediaLinks
[Test]
[TestCase("![static mp4](https://sample.com/video.mp4)", "<p><video width=\"500\" height=\"281\" controls=\"\"><source type=\"video/mp4\" src=\"https://sample.com/video.mp4\"></source></video></p>\n")]
[TestCase("![static mp4](//sample.com/video.mp4)", "<p><video width=\"500\" height=\"281\" controls=\"\"><source type=\"video/mp4\" src=\"//sample.com/video.mp4\"></source></video></p>\n")]
[TestCase(@"![youtube short](https://www.youtube.com/shorts/6BUptHVuvyI?feature=share)", "<p><iframe src=\"https://www.youtube.com/embed/6BUptHVuvyI\" class=\"youtubeshort\" width=\"500\" height=\"281\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n")]
[TestCase(@"![youtube.com](https://www.youtube.com/watch?v=mswPy5bt3TQ)", "<p><iframe src=\"https://www.youtube.com/embed/mswPy5bt3TQ\" class=\"youtube\" width=\"500\" height=\"281\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n")]
[TestCase("![yandex.ru](https://music.yandex.ru/album/411845/track/4402274)", "<p><iframe src=\"https://music.yandex.ru/iframe/#track/4402274/411845/\" class=\"yandex\" width=\"500\" height=\"281\" frameborder=\"0\"></iframe></p>\n")]
[TestCase("![vimeo](https://vimeo.com/8607834)", "<p><iframe src=\"https://player.vimeo.com/video/8607834\" class=\"vimeo\" width=\"500\" height=\"281\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n")]
@@ -33,7 +34,7 @@ public class TestMediaLinks
public void TestBuiltInHosts(string markdown, string expected)
{
string html = Markdown.ToHtml(markdown, GetPipeline());
Assert.AreEqual(html, expected);
Assert.AreEqual(expected, html);
}
[TestCase("![static video relative path](./video.mp4)",
@@ -43,7 +44,7 @@ public class TestMediaLinks
public void TestBuiltInHostsWithRelativePaths(string markdown, string expected)
{
string html = Markdown.ToHtml(markdown, GetPipeline());
Assert.AreEqual(html, expected);
Assert.AreEqual(expected, html);
}
private class TestHostProvider : IHostProvider

View File

@@ -9,6 +9,13 @@ namespace Markdig.Tests;
[TestFixture]
public class TestPlayParser
{
[Test]
public void TestInvalidSetext()
{
TestParser.TestSpec("test\n===n", "<p>test\n===n</p>", "advanced");
}
[Test]
public void TestBugWithEmphasisAndTable()
{
@@ -39,6 +46,14 @@ public class TestPlayParser
Assert.AreEqual("/yoyo", link?.Url);
}
[Test]
public void TestLinkWithMultipleBackslashesInTitle()
{
var doc = Markdown.Parse(@"[link](/uri '\\\\127.0.0.1')");
var link = doc.Descendants<LinkInline>().FirstOrDefault();
Assert.AreEqual(@"\\127.0.0.1", link?.Title);
}
[Test]
public void TestListBug2()
{

View File

@@ -160,6 +160,17 @@ literal ( 0, 8) 8-8
");
}
[Test]
public void TestEmphasis4()
{
Check("**foo*", @"
paragraph ( 0, 0) 0-5
literal ( 0, 0) 0-0
emphasis ( 0, 1) 1-5
literal ( 0, 2) 2-4
");
}
[Test]
public void TestEmphasisFalse()
{
@@ -522,13 +533,17 @@ literal ( 1, 6) 8-9
[Test]
public void TestAbbreviations()
{
Check("*[HTML]: Hypertext Markup Language\r\n\r\nLater in a text we are using HTML and it becomes an abbr tag HTML", @"
Check("*[HTML]: Hypertext Markup Language\r\n\r\nLater in a text we are using HTML and it becomes an abbr tag HTML\r\n\r\nHTML abbreviation at the beginning of a line", @"
paragraph ( 2, 0) 38-102
container ( 2, 0) 38-102
literal ( 2, 0) 38-66
abbreviation ( 2,29) 67-70
literal ( 2,33) 71-98
abbreviation ( 2,61) 99-102
paragraph ( 4, 0) 107-150
container ( 4, 0) 107-150
abbreviation ( 4, 0) 107-110
literal ( 4, 4) 111-150
", "abbreviations");
}
@@ -698,7 +713,7 @@ literal ( 0, 2) 2-3
[Test]
public void TestMathematicsInline()
{
// 01 23456789AB
// 01 23456789ABCDEF
Check("0\n012 $abcd$ 321", @"
paragraph ( 0, 0) 0-15
literal ( 0, 0) 0-0
@@ -707,6 +722,13 @@ literal ( 1, 0) 2-5
math ( 1, 4) 6-11
attributes ( 0, 0) 0--1
literal ( 1,10) 12-15
", "mathematics");
// 012345678
Check("$ abcd $", @"
paragraph ( 0, 0) 0-7
math ( 0, 0) 0-7
attributes ( 0, 0) 0--1
", "mathematics");
}
@@ -789,6 +811,29 @@ literal ( 4, 2) 13-13
", "pipetables");
}
[Test]
public void TestPipeTable3()
{
// 01234 5678 9ABCD
Check("|a|b\n-|-\n0|1|\n", @"
table ( 0, 0) 0-12
tablerow ( 0, 1) 1-3
tablecell ( 0, 1) 1-1
paragraph ( 0, 1) 1-1
literal ( 0, 1) 1-1
tablecell ( 0, 3) 3-3
paragraph ( 0, 3) 3-3
literal ( 0, 3) 3-3
tablerow ( 2, 0) 9-11
tablecell ( 2, 0) 9-9
paragraph ( 2, 0) 9-9
literal ( 2, 0) 9-9
tablecell ( 2, 2) 11-11
paragraph ( 2, 2) 11-11
literal ( 2, 2) 11-11
", "pipetables");
}
[Test]
public void TestIndentedCode()
{

View File

@@ -107,7 +107,7 @@ public class TestYamlFrontMatterExtension
}
}
Assert.Pass("No exception parsing and iterating through YAML front matter block lines");
// No exception parsing and iterating through YAML front matter block lines
}
}

View File

@@ -89,6 +89,7 @@ public class AbbreviationParser : BlockParser
{
var literal = (LiteralInline)processor.Inline!;
var originalLiteral = literal;
var originalSpanEnd = literal.Span.End;
ContainerInline? container = null;
@@ -171,7 +172,7 @@ public class AbbreviationParser : BlockParser
// Process the remaining literal
literal = new LiteralInline()
{
Span = new SourceSpan(abbrInline.Span.End + 1, literal.Span.End),
Span = new SourceSpan(abbrInline.Span.End + 1, originalSpanEnd),
Line = line,
Column = column + match.Length,
};

View File

@@ -50,6 +50,7 @@ public class AlertBlockRenderer : HtmlObjectRenderer<AlertBlock>
{
renderer.WriteLine("</div>");
}
renderer.EnsureLine();
}
@@ -61,7 +62,14 @@ public class AlertBlockRenderer : HtmlObjectRenderer<AlertBlock>
/// <param name="kind">The kind of the alert to render</param>
public static void DefaultRenderKind(HtmlRenderer renderer, StringSlice kind)
{
string? html = kind.AsSpan() switch
if (kind.Length >= 16)
{
return;
}
Span<char> upperKind = stackalloc char[kind.Length];
kind.AsSpan().ToUpperInvariant(upperKind);
string? html = upperKind switch
{
"NOTE" => "<p class=\"markdown-alert-title\"><svg viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path d=\"M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8Zm8-6.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13ZM6.5 7.75A.75.75 0 0 1 7.25 7h1a.75.75 0 0 1 .75.75v2.75h.25a.75.75 0 0 1 0 1.5h-2a.75.75 0 0 1 0-1.5h.25v-2h-.25a.75.75 0 0 1-.75-.75ZM8 6a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z\"></path></svg>Note</p>",
"TIP" => "<p class=\"markdown-alert-title\"><svg viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path d=\"M8 1.5c-2.363 0-4 1.69-4 3.75 0 .984.424 1.625.984 2.304l.214.253c.223.264.47.556.673.848.284.411.537.896.621 1.49a.75.75 0 0 1-1.484.211c-.04-.282-.163-.547-.37-.847a8.456 8.456 0 0 0-.542-.68c-.084-.1-.173-.205-.268-.32C3.201 7.75 2.5 6.766 2.5 5.25 2.5 2.31 4.863 0 8 0s5.5 2.31 5.5 5.25c0 1.516-.701 2.5-1.328 3.259-.095.115-.184.22-.268.319-.207.245-.383.453-.541.681-.208.3-.33.565-.37.847a.751.751 0 0 1-1.485-.212c.084-.593.337-1.078.621-1.489.203-.292.45-.584.673-.848.075-.088.147-.173.213-.253.561-.679.985-1.32.985-2.304 0-2.06-1.637-3.75-4-3.75ZM5.75 12h4.5a.75.75 0 0 1 0 1.5h-4.5a.75.75 0 0 1 0-1.5ZM6 15.25a.75.75 0 0 1 .75-.75h2.5a.75.75 0 0 1 0 1.5h-2.5a.75.75 0 0 1-.75-.75Z\"></path></svg>Tip</p>",

View File

@@ -29,7 +29,8 @@ public class AlertInlineParser : InlineParser
// We expect the alert to be the first child of a quote block. Example:
// > [!NOTE]
// > This is a note
if (processor.Block is not ParagraphBlock paragraphBlock || paragraphBlock.Parent is not QuoteBlock quoteBlock || paragraphBlock.Inline?.FirstChild != null)
if (processor.Block is not ParagraphBlock paragraphBlock || paragraphBlock.Parent is not QuoteBlock quoteBlock || paragraphBlock.Inline?.FirstChild != null
|| quoteBlock is AlertBlock || quoteBlock.Parent is not MarkdownDocument)
{
return false;
}
@@ -46,7 +47,7 @@ public class AlertInlineParser : InlineParser
var start = slice.Start;
var end = start;
while (c.IsAlphaUpper())
while (c.IsAlpha())
{
end = slice.Start;
c = slice.NextChar();
@@ -86,7 +87,7 @@ public class AlertInlineParser : InlineParser
}
break;
}
else if (!c.IsSpaceOrTab())
else if (!c.IsSpaceOrTab())
{
slice = saved;
return false;

View File

@@ -1,7 +1,9 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using System.Linq;
using Markdig.Extensions.Alerts;
using Markdig.Renderers;
using Markdig.Renderers.Html;
using Markdig.Syntax;
@@ -24,10 +26,22 @@ public class BootstrapExtension : IMarkdownExtension
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
{
if (renderer is HtmlRenderer htmlRenderer)
{
var alertRenderer = htmlRenderer.ObjectRenderers.OfType<AlertBlockRenderer>().FirstOrDefault();
if (alertRenderer == null)
{
alertRenderer = new AlertBlockRenderer();
renderer.ObjectRenderers.InsertBefore<QuoteBlockRenderer>(new AlertBlockRenderer());
}
alertRenderer.RenderKind = (_, _) => { };
}
}
private static void PipelineOnDocumentProcessed(MarkdownDocument document)
{
Span<char> upperKind = new char[16];
foreach (var node in document.Descendants())
{
if (node.IsInline)
@@ -43,6 +57,28 @@ public class BootstrapExtension : IMarkdownExtension
{
node.GetAttributes().AddClass("table");
}
else if (node is AlertBlock alertBlock) // Needs to be before QuoteBlock
{
var attributes = node.GetAttributes();
attributes.AddClass("alert");
attributes.AddProperty("role", "alert");
if (alertBlock.Kind.Length <= upperKind.Length)
{
alertBlock.Kind.AsSpan().ToUpperInvariant(upperKind);
attributes.AddClass(upperKind.Slice(0, alertBlock.Kind.Length) switch
{
"NOTE" => "alert-primary",
"TIP" => "alert-success",
"IMPORTANT" => "alert-info",
"WARNING" => "alert-warning",
"CAUTION" => "alert-danger",
_ => "alert-dark",
});
}
var lastParagraph = alertBlock.Descendants().OfType<ParagraphBlock>().LastOrDefault();
lastParagraph?.GetAttributes().AddClass("mb-0");
}
else if (node is QuoteBlock)
{
node.GetAttributes().AddClass("blockquote");

View File

@@ -30,7 +30,11 @@ public class CustomContainerExtension : IMarkdownExtension
{
if (delimiterCount == 2 && emphasisChar == ':')
{
return new CustomContainerInline();
return new CustomContainerInline
{
DelimiterChar = ':',
DelimiterCount = 2
};
}
return null;
});

View File

@@ -105,13 +105,20 @@ public class DefinitionListParser : BlockParser
{
var index = previousParent.IndexOf(paragraphBlock) - 1;
if (index < 0) return null;
var lastBlock = previousParent[index];
if (lastBlock is BlankLineBlock)
switch (previousParent[index])
{
lastBlock = previousParent[index - 1];
previousParent.RemoveAt(index);
case DefinitionList definitionList:
return definitionList;
case BlankLineBlock:
if (index > 0 && previousParent[index - 1] is DefinitionList definitionList2)
{
previousParent.RemoveAt(index);
return definitionList2;
}
break;
}
return lastBlock as DefinitionList;
return null;
}
public override BlockState TryContinue(BlockProcessor processor, Block block)

View File

@@ -1,5 +1,5 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// 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;
@@ -22,9 +22,8 @@ public class DiagramExtension : IMarkdownExtension
if (renderer is HtmlRenderer htmlRenderer)
{
var codeRenderer = htmlRenderer.ObjectRenderers.FindExact<CodeBlockRenderer>()!;
// TODO: Add other well known diagram languages
codeRenderer.BlocksAsDiv.Add("mermaid");
codeRenderer.BlockMapping["mermaid"] = "pre";
codeRenderer.BlocksAsDiv.Add("nomnoml");
}
}
}
}

View File

@@ -57,6 +57,8 @@ public class FootnoteParser : BlockParser
{
Label = label,
LabelSpan = labelSpan,
Column = processor.Column,
Span = new SourceSpan(processor.Start, processor.Line.End),
};
// Maintain a list of all footnotes at document level
@@ -74,6 +76,7 @@ public class FootnoteParser : BlockParser
{
CreateLinkInline = CreateLinkToFootnote,
Line = processor.LineIndex,
Column = saved,
Span = new SourceSpan(start, processor.Start - 2), // account for ]:
LabelSpan = labelSpan,
Label = label

View File

@@ -152,7 +152,7 @@ public class MathInlineParser : InlineParser
// Create a new MathInline
var inline = new MathInline()
{
Span = new SourceSpan(processor.GetSourcePosition(startPosition, out int line, out int column), processor.GetSourcePosition(end)),
Span = new SourceSpan(processor.GetSourcePosition(startPosition, out int line, out int column), processor.GetSourcePosition(slice.Start - 1)),
Line = line,
Column = column,
Delimiter = match,

View File

@@ -58,6 +58,7 @@ public class HostProviderBuilder
internal static Dictionary<string, IHostProvider> KnownHosts { get; }
= new Dictionary<string, IHostProvider>(StringComparer.OrdinalIgnoreCase)
{
["YouTubeShort"] = Create("www.youtube.com", YouTubeShort, iframeClass: "youtubeshort"),
["YouTube"] = Create("www.youtube.com", YouTube, iframeClass: "youtube"),
["YouTubeShortened"] = Create("youtu.be", YouTubeShortened, iframeClass: "youtube"),
["Vimeo"] = Create("vimeo.com", Vimeo, iframeClass: "vimeo"),
@@ -92,6 +93,19 @@ public class HostProviderBuilder
);
}
private static string? YouTubeShort(Uri uri)
{
string uriPath = uri.AbsolutePath;
bool isYouTubeShort = uriPath.StartsWith("/shorts/", StringComparison.OrdinalIgnoreCase);
if (!isYouTubeShort)
{
return null;
}
var shortId = uriPath.Substring("/shorts/".Length).Split('?').FirstOrDefault(); // the format might be "/shorts/6BUptHVuvyI?feature=share"
return BuildYouTubeIframeUrl(shortId, null);
}
private static string? YouTubeShortened(Uri uri)
{
return BuildYouTubeIframeUrl(

View File

@@ -135,6 +135,7 @@ public class GridTableParser : BlockParser
private static void SetRowSpanState(List<GridTableState.ColumnSlice> columns, StringSlice line, out bool isHeaderRow, out bool hasRowSpan)
{
var lineStart = line.Start;
var lineEnd = line.End;
isHeaderRow = line.PeekChar() == '=' || line.PeekChar(2) == '=';
hasRowSpan = false;
foreach (var columnSlice in columns)
@@ -142,7 +143,7 @@ public class GridTableParser : BlockParser
if (columnSlice.CurrentCell != null)
{
line.Start = lineStart + columnSlice.Start + 1;
line.End = lineStart + columnSlice.End - 1;
line.End = Math.Min(lineStart + columnSlice.End - 1, lineEnd);
line.Trim();
if (line.IsEmptyOrWhitespace() || !IsRowSeparator(line))
{
@@ -256,7 +257,7 @@ public class GridTableParser : BlockParser
{
sliceForCell.End = line.Start + columnEnd - 1;
}
else if (line.PeekCharExtra(line.End) == '|')
else if (line.PeekCharExtra(line.End - line.Start) == '|')
{
sliceForCell.End = line.End - 1;
}

View File

@@ -280,6 +280,8 @@ public class PipeTableParser : InlineParser, IPostInlineProcessor
tableState.EndOfLines.Add(endOfTable);
}
int lastPipePos = 0;
// Cell loop
// Reconstruct the table from the delimiters
TableRow? row = null;
@@ -302,6 +304,12 @@ public class PipeTableParser : InlineParser, IPostInlineProcessor
if (pipeSeparator != null && (delimiter.PreviousSibling is null || delimiter.PreviousSibling is LineBreakInline))
{
delimiter.Remove();
if (table.Span.IsEmpty)
{
table.Span = delimiter.Span;
table.Line = delimiter.Line;
table.Column = delimiter.Column;
}
continue;
}
}
@@ -354,6 +362,7 @@ public class PipeTableParser : InlineParser, IPostInlineProcessor
// If the delimiter is a pipe, we need to remove it from the tree
// so that previous loop looking for a parent will not go further on subsequent cells
delimiter.Remove();
lastPipePos = delimiter.Span.End;
}
// We trim whitespace at the beginning and ending of the cell
@@ -421,6 +430,11 @@ public class PipeTableParser : InlineParser, IPostInlineProcessor
}
}
if (lastPipePos > table.Span.End)
{
table.UpdateSpanEnd(lastPipePos);
}
// Once we are done with the cells, we can remove all end of lines in the table tree
foreach (var endOfLine in tableState.EndOfLines)
{

View File

@@ -30,7 +30,7 @@ internal sealed class FastStringWriter : TextWriter
public override string NewLine
{
get => _newLine;
set => _newLine = value ?? Environment.NewLine;
set => base.NewLine = _newLine = value ?? Environment.NewLine;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]

View File

@@ -545,87 +545,70 @@ public static class LinkHelper
enclosingCharacter = c;
var closingQuote = c == '(' ? ')' : c;
bool hasEscape = false;
// -1: undefined
// 0: has only spaces
// 1: has other characters
int hasOnlyWhiteSpacesSinceLastLine = -1;
while (true)
bool isLineBlank = false; // the first line is never blank
while ((c = text.NextChar()) != '\0')
{
c = text.NextChar();
if (c == '\r' || c == '\n')
{
if (hasOnlyWhiteSpacesSinceLastLine >= 0)
if (isLineBlank)
{
if (hasOnlyWhiteSpacesSinceLastLine == 1)
{
break;
}
hasOnlyWhiteSpacesSinceLastLine = -1;
break;
}
if (hasEscape)
{
hasEscape = false;
buffer.Append('\\');
}
buffer.Append(c);
if (c == '\r' && text.PeekChar() == '\n')
{
buffer.Append('\n');
text.SkipChar();
}
continue;
}
if (c == '\0')
{
break;
isLineBlank = true;
}
if (c == closingQuote)
else if (hasEscape)
{
if (hasEscape)
hasEscape = false;
if (!c.IsAsciiPunctuation())
{
buffer.Append(closingQuote);
hasEscape = false;
continue;
buffer.Append('\\');
}
buffer.Append(c);
}
else if (c == closingQuote)
{
// Skip last quote
text.SkipChar();
goto ReturnValid;
title = buffer.ToString();
return true;
}
if (hasEscape && !c.IsAsciiPunctuation())
{
buffer.Append('\\');
}
if (c == '\\')
else if (c == '\\')
{
hasEscape = true;
continue;
isLineBlank = false;
}
hasEscape = false;
if (c.IsSpaceOrTab())
else
{
if (hasOnlyWhiteSpacesSinceLastLine < 0)
if (isLineBlank && !c.IsSpaceOrTab())
{
hasOnlyWhiteSpacesSinceLastLine = 1;
isLineBlank = false;
}
}
else if (c != '\n' && c != '\r' && text.PeekChar() != '\n')
{
hasOnlyWhiteSpacesSinceLastLine = 0;
}
buffer.Append(c);
buffer.Append(c);
}
}
}
buffer.Dispose();
title = null;
return false;
ReturnValid:
title = buffer.ToString();
return true;
}
public static bool TryParseTitleTrivia<T>(ref T text, out string? title, out char enclosingCharacter) where T : ICharIterator
@@ -641,87 +624,70 @@ public static class LinkHelper
enclosingCharacter = c;
var closingQuote = c == '(' ? ')' : c;
bool hasEscape = false;
// -1: undefined
// 0: has only spaces
// 1: has other characters
int hasOnlyWhiteSpacesSinceLastLine = -1;
while (true)
bool isLineBlank = false; // the first line is never blank
while ((c = text.NextChar()) != '\0')
{
c = text.NextChar();
if (c == '\r' || c == '\n')
{
if (hasOnlyWhiteSpacesSinceLastLine >= 0)
if (isLineBlank)
{
if (hasOnlyWhiteSpacesSinceLastLine == 1)
{
break;
}
hasOnlyWhiteSpacesSinceLastLine = -1;
break;
}
if (hasEscape)
{
hasEscape = false;
buffer.Append('\\');
}
buffer.Append(c);
if (c == '\r' && text.PeekChar() == '\n')
{
buffer.Append('\n');
text.SkipChar();
}
continue;
}
if (c == '\0')
{
break;
isLineBlank = true;
}
if (c == closingQuote)
else if (hasEscape)
{
if (hasEscape)
hasEscape = false;
if (!c.IsAsciiPunctuation())
{
buffer.Append(closingQuote);
hasEscape = false;
continue;
buffer.Append('\\');
}
buffer.Append(c);
}
else if (c == closingQuote)
{
// Skip last quote
text.SkipChar();
goto ReturnValid;
title = buffer.ToString();
return true;
}
if (hasEscape && !c.IsAsciiPunctuation())
{
buffer.Append('\\');
}
if (c == '\\')
else if (c == '\\')
{
hasEscape = true;
continue;
isLineBlank = false;
}
hasEscape = false;
if (c.IsSpaceOrTab())
else
{
if (hasOnlyWhiteSpacesSinceLastLine < 0)
if (isLineBlank && !c.IsSpaceOrTab())
{
hasOnlyWhiteSpacesSinceLastLine = 1;
isLineBlank = false;
}
}
else if (c != '\n' && c != '\r' && text.PeekChar() != '\n')
{
hasOnlyWhiteSpacesSinceLastLine = 0;
}
buffer.Append(c);
buffer.Append(c);
}
}
}
buffer.Dispose();
title = null;
return false;
ReturnValid:
title = buffer.ToString();
return true;
}
public static bool TryParseUrl<T>(T text, [NotNullWhen(true)] out string? link) where T : ICharIterator
@@ -758,12 +724,15 @@ public static class LinkHelper
break;
}
if (hasEscape && !c.IsAsciiPunctuation())
if (hasEscape)
{
buffer.Append('\\');
hasEscape = false;
if (!c.IsAsciiPunctuation())
{
buffer.Append('\\');
}
}
if (c == '\\')
else if (c == '\\')
{
hasEscape = true;
continue;
@@ -774,8 +743,6 @@ public static class LinkHelper
break;
}
hasEscape = false;
buffer.Append(c);
} while (c != '\0');
@@ -814,20 +781,21 @@ public static class LinkHelper
if (!isAutoLink)
{
if (hasEscape && !c.IsAsciiPunctuation())
if (hasEscape)
{
buffer.Append('\\');
hasEscape = false;
if (!c.IsAsciiPunctuation())
{
buffer.Append('\\');
}
}
// If we have an escape
if (c == '\\')
else if (c == '\\')
{
hasEscape = true;
c = text.NextChar();
continue;
}
hasEscape = false;
}
if (IsEndOfUri(c, isAutoLink))
@@ -905,12 +873,15 @@ public static class LinkHelper
break;
}
if (hasEscape && !c.IsAsciiPunctuation())
if (hasEscape)
{
buffer.Append('\\');
hasEscape = false;
if (!c.IsAsciiPunctuation())
{
buffer.Append('\\');
}
}
if (c == '\\')
else if (c == '\\')
{
hasEscape = true;
continue;
@@ -921,8 +892,6 @@ public static class LinkHelper
break;
}
hasEscape = false;
buffer.Append(c);
} while (c != '\0');
@@ -961,20 +930,21 @@ public static class LinkHelper
if (!isAutoLink)
{
if (hasEscape && !c.IsAsciiPunctuation())
if (hasEscape)
{
buffer.Append('\\');
hasEscape = false;
if (!c.IsAsciiPunctuation())
{
buffer.Append('\\');
}
}
// If we have an escape
if (c == '\\')
else if (c == '\\')
{
hasEscape = true;
c = text.NextChar();
continue;
}
hasEscape = false;
}
if (IsEndOfUri(c, isAutoLink))
@@ -1159,7 +1129,7 @@ public static class LinkHelper
c = text.NextChar();
}
if (c != '\0' && c != '\n' && c != '\r' && text.PeekChar() != '\n')
if (c != '\0' && c != '\n' && c != '\r')
{
// If we were able to parse the url but the title doesn't end with space,
// we are still returning a valid definition
@@ -1299,7 +1269,7 @@ public static class LinkHelper
c = text.NextChar();
}
if (c != '\0' && c != '\n' && c != '\r' && text.PeekChar() != '\n')
if (c != '\0' && c != '\n' && c != '\r')
{
// If we were able to parse the url but the title doesn't end with space,
// we are still returning a valid definition

View File

@@ -5,7 +5,7 @@
<Copyright>Alexandre Mutel</Copyright>
<NeutralLanguage>en-US</NeutralLanguage>
<Authors>Alexandre Mutel</Authors>
<TargetFrameworks>net462;netstandard2.0;netstandard2.1;net6.0;net8.0</TargetFrameworks>
<TargetFrameworks>net462;netstandard2.0;netstandard2.1;net8.0;net9.0</TargetFrameworks>
<CheckEolTargetFramework>false</CheckEolTargetFramework>
<PackageTags>Markdown CommonMark md html md2html</PackageTags>
<PackageReleaseNotes>https://github.com/lunet-io/markdig/blob/master/changelog.md</PackageReleaseNotes>

View File

@@ -302,14 +302,13 @@ public class EmphasisInlineParser : InlineParser, IPostInlineProcessor
var openDelimitercount = openDelimiter.DelimiterCount;
var closeDelimitercount = closeDelimiter.DelimiterCount;
emphasis!.Span.Start = openDelimiter.Span.Start;
emphasis!.Span.Start = openDelimiter.Span.Start + openDelimitercount - delimiterDelta;
emphasis.Line = openDelimiter.Line;
emphasis.Column = openDelimiter.Column;
emphasis.Column = openDelimiter.Column + openDelimitercount - delimiterDelta;
emphasis.Span.End = closeDelimiter.Span.End - closeDelimitercount + delimiterDelta;
openDelimiter.Content.Start += delimiterDelta;
openDelimiter.Span.Start += delimiterDelta;
openDelimiter.Column += delimiterDelta;
openDelimiter.Span.End -= delimiterDelta;
openDelimiter.Content.End -= delimiterDelta;
closeDelimiter.Content.Start += delimiterDelta;
closeDelimiter.Span.Start += delimiterDelta;
closeDelimiter.Column += delimiterDelta;

View File

@@ -137,6 +137,9 @@ public class LinkInlineParser : InlineParser
if (linkRef.CreateLinkInline != null)
{
link = linkRef.CreateLinkInline(state, linkRef, parent.FirstChild);
link.Span = new SourceSpan(parent.Span.Start, endPosition);
link.Line = parent.Line;
link.Column = parent.Column;
}
// Create a default link if the callback was not found
@@ -145,8 +148,8 @@ public class LinkInlineParser : InlineParser
// Inline Link
var linkInline = new LinkInline()
{
Url = HtmlHelper.Unescape(linkRef.Url),
Title = HtmlHelper.Unescape(linkRef.Title),
Url = HtmlHelper.Unescape(linkRef.Url, removeBackSlash: false),
Title = HtmlHelper.Unescape(linkRef.Title, removeBackSlash: false),
Label = label,
LabelSpan = labelSpan,
UrlSpan = linkRef.UrlSpan,
@@ -256,8 +259,8 @@ public class LinkInlineParser : InlineParser
// Inline Link
link = new LinkInline()
{
Url = HtmlHelper.Unescape(url),
Title = HtmlHelper.Unescape(title),
Url = HtmlHelper.Unescape(url, removeBackSlash: false),
Title = HtmlHelper.Unescape(title, removeBackSlash: false),
IsImage = openParent.IsImage,
LabelSpan = openParent.LabelSpan,
UrlSpan = inlineState.GetSourcePositionFromLocalSpan(linkSpan),
@@ -382,11 +385,11 @@ public class LinkInlineParser : InlineParser
return new LinkInline()
{
TriviaBeforeUrl = wsBeforeLink,
Url = HtmlHelper.Unescape(url),
Url = HtmlHelper.Unescape(url, removeBackSlash: false),
UnescapedUrl = unescapedUrl,
UrlHasPointyBrackets = urlHasPointyBrackets,
TriviaAfterUrl = wsAfterLink,
Title = HtmlHelper.Unescape(title),
Title = HtmlHelper.Unescape(title, removeBackSlash: false),
UnescapedTitle = unescapedTitle,
TitleEnclosingCharacter = titleEnclosingCharacter,
TriviaAfterTitle = wsAfterTitle,

View File

@@ -171,13 +171,9 @@ public class ParagraphBlockParser : BlockParser
{
count = line.CountAndSkipChar(headingChar);
if (line.IsEmpty)
{
return headingChar;
}
while (line.NextChar().IsSpaceOrTab())
while (line.CurrentChar.IsSpaceOrTab())
{
line.NextChar();
}
if (line.IsEmpty)

View File

@@ -1,5 +1,5 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// 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;
@@ -13,8 +13,6 @@ namespace Markdig.Renderers.Html;
/// <seealso cref="HtmlObjectRenderer{CodeBlock}" />
public class CodeBlockRenderer : HtmlObjectRenderer<CodeBlock>
{
private HashSet<string>? _blocksAsDiv;
/// <summary>
/// Initializes a new instance of the <see cref="CodeBlockRenderer"/> class.
/// </summary>
@@ -25,23 +23,32 @@ public class CodeBlockRenderer : HtmlObjectRenderer<CodeBlock>
/// <summary>
/// Gets a map of fenced code block infos that should be rendered as div blocks instead of pre/code blocks.
/// </summary>
public HashSet<string> BlocksAsDiv => _blocksAsDiv ??= new HashSet<string>(StringComparer.OrdinalIgnoreCase);
public HashSet<string> BlocksAsDiv { get; } = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
/// <summary>
/// Gets a map of custom block mapping to render as custom blocks instead of pre/code blocks.
/// For example defining {"mermaid", "pre"} will render a block with info `mermaid` as a `pre` block but without the code HTML element.
/// </summary>
public Dictionary<string, string> BlockMapping { get; } = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
protected override void Write(HtmlRenderer renderer, CodeBlock obj)
{
renderer.EnsureLine();
if (_blocksAsDiv is not null && (obj as FencedCodeBlock)?.Info is string info && _blocksAsDiv.Contains(info))
if ((obj as FencedCodeBlock)?.Info is string info && (BlocksAsDiv.Contains(info) || BlockMapping.ContainsKey(info)))
{
var infoPrefix = (obj.Parser as FencedCodeBlockParser)?.InfoPrefix ??
FencedCodeBlockParser.DefaultInfoPrefix;
var htmlBlock = BlockMapping.TryGetValue(info, out var blockType) ? blockType : "div";
// We are replacing the HTML attribute `language-mylang` by `mylang` only for a div block
// NOTE that we are allocating a closure here
if (renderer.EnableHtmlForBlock)
{
renderer.Write("<div")
renderer.WriteRaw('<');
renderer.Write(htmlBlock)
.WriteAttributes(obj.TryGetAttributes(),
cls => cls.StartsWith(infoPrefix, StringComparison.Ordinal) ? cls.Substring(infoPrefix.Length) : cls)
.WriteRaw('>');
@@ -51,7 +58,7 @@ public class CodeBlockRenderer : HtmlObjectRenderer<CodeBlock>
if (renderer.EnableHtmlForBlock)
{
renderer.WriteLine("</div>");
renderer.Write("</").Write(htmlBlock).WriteLine(">");
}
}
else

View File

@@ -28,15 +28,15 @@ public abstract class MarkdownObjectRenderer<TRenderer, TObject> : IMarkdownObje
public virtual void Write(RendererBase renderer, MarkdownObject obj)
{
var htmlRenderer = (TRenderer)renderer;
var typedRenderer = (TRenderer)renderer;
var typedObj = (TObject)obj;
if (_tryWriters is not null && TryWrite(htmlRenderer, typedObj))
if (_tryWriters is not null && TryWrite(typedRenderer, typedObj))
{
return;
}
Write(htmlRenderer, typedObj);
Write(typedRenderer, typedObj);
}
private bool TryWrite(TRenderer renderer, TObject obj)

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<TargetFrameworks>net6.0;net8.0;net9.0</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<IsPackable>false</IsPackable>
</PropertyGroup>

View File

@@ -1,6 +1,6 @@
{
"sdk": {
"version": "8.0.100",
"version": "9.0.100",
"rollForward": "latestMajor",
"allowPrerelease": false
}

View File

@@ -3,7 +3,9 @@
This file is licensed under the BSD-Clause 2 license. &#xD;
See the license.txt file in the project root for more information.</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateInstanceFields/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=4a98fdf6_002D7d98_002D4f5a_002Dafeb_002Dea44ad98c70c/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Instance" AccessRightKinds="Private" Description="Instance fields (private)"&gt;&lt;ElementKinds&gt;&lt;Kind Name="FIELD" /&gt;&lt;Kind Name="READONLY_FIELD" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;&lt;/Policy&gt;</s:String>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EFeature_002EServices_002ECodeCleanup_002EFileHeader_002EFileHeaderSettingsMigrate/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EPredefinedNamingRulesToUserRulesUpgrade/@EntryIndexedValue">True</s:Boolean>
<s:String x:Key="/Default/Environment/UnitTesting/NUnitProvider/SetCurrentDirectoryTo/@EntryValue">TestFolder</s:String>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Autolink/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Inlines/@EntryIndexedValue">True</s:Boolean>