From 310a55c724c44a9302a7aa06b0770222817989a9 Mon Sep 17 00:00:00 2001 From: Alexandre Mutel Date: Wed, 30 Oct 2024 19:08:35 +0100 Subject: [PATCH 01/88] Update readme.md --- readme.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/readme.md b/readme.md index 1ae79f16..713d9240 100644 --- a/readme.md +++ b/readme.md @@ -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) +[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 From 49cf59b819a53d3a1d356ce0dfdce014a48fd91f Mon Sep 17 00:00:00 2001 From: snnz Date: Sun, 24 Nov 2024 13:51:56 +0300 Subject: [PATCH 02/88] Fix extra line feeds in link title (#826) * Fix extra line feeds in link title * Test added. --- src/Markdig.Tests/TestLinkHelper.cs | 8 ++++++++ src/Markdig/Helpers/LinkHelper.cs | 2 ++ 2 files changed, 10 insertions(+) diff --git a/src/Markdig.Tests/TestLinkHelper.cs b/src/Markdig.Tests/TestLinkHelper.cs index 33edb465..75acbb19 100644 --- a/src/Markdig.Tests/TestLinkHelper.cs +++ b/src/Markdig.Tests/TestLinkHelper.cs @@ -81,6 +81,14 @@ public void TestTitleSimpleAlternate() 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 TestUrlAndTitle() { diff --git a/src/Markdig/Helpers/LinkHelper.cs b/src/Markdig/Helpers/LinkHelper.cs index a7d2ae55..48581e1e 100644 --- a/src/Markdig/Helpers/LinkHelper.cs +++ b/src/Markdig/Helpers/LinkHelper.cs @@ -567,6 +567,7 @@ public static bool TryParseTitle(ref T text, out string? title, out char encl if (c == '\r' && text.PeekChar() == '\n') { buffer.Append('\n'); + text.SkipChar(); } continue; } @@ -663,6 +664,7 @@ public static bool TryParseTitleTrivia(ref T text, out string? title, out cha if (c == '\r' && text.PeekChar() == '\n') { buffer.Append('\n'); + text.SkipChar(); } continue; } From 98a060f2a340bc7a6e2dd9e0f32eac00ffefe170 Mon Sep 17 00:00:00 2001 From: Miha Zupan Date: Fri, 29 Nov 2024 19:06:58 +0100 Subject: [PATCH 03/88] Add .NET 9 target, drop .NET 6 --- .github/workflows/ci.yml | 2 +- src/Markdig.Tests/Markdig.Tests.csproj | 9 +++++---- src/Markdig.Tests/TestContainerBlocks.cs | 2 +- src/Markdig.Tests/TestFastStringWriter.cs | 4 +++- src/Markdig.Tests/TestYamlFrontMatterExtension.cs | 2 +- src/Markdig/Helpers/FastStringWriter.cs | 2 +- src/Markdig/Markdig.targets | 2 +- src/global.json | 2 +- 8 files changed, 14 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9738b508..da15d160 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,6 +13,6 @@ 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 }} \ No newline at end of file diff --git a/src/Markdig.Tests/Markdig.Tests.csproj b/src/Markdig.Tests/Markdig.Tests.csproj index 49c875fc..5d33dc8d 100644 --- a/src/Markdig.Tests/Markdig.Tests.csproj +++ b/src/Markdig.Tests/Markdig.Tests.csproj @@ -1,10 +1,11 @@ - net8.0 + net462;net6.0;net8.0;net9.0 Exe false enable + 13.0 Markdig.Tests.Program $(MSBuildProjectDirectory)\..\SpecFileGen\bin\$(Configuration)\net8.0\SpecFileGen.dll $(MSBuildProjectDirectory)\..\SpecFileGen\bin\$(Configuration)\net8.0\SpecFileGen.timestamp @@ -18,14 +19,14 @@ - - + + @@ -37,7 +38,7 @@ - + diff --git a/src/Markdig.Tests/TestContainerBlocks.cs b/src/Markdig.Tests/TestContainerBlocks.cs index abffe84f..c5e0440b 100644 --- a/src/Markdig.Tests/TestContainerBlocks.cs +++ b/src/Markdig.Tests/TestContainerBlocks.cs @@ -172,7 +172,7 @@ public void CopyTo() Assert.NotNull(destination[2]); Assert.Null(destination[3]); - Array.Clear(destination); + Array.Clear(destination, 0, destination.Length); container.Add(new ParagraphBlock()); container.CopyTo(destination, 1); diff --git a/src/Markdig.Tests/TestFastStringWriter.cs b/src/Markdig.Tests/TestFastStringWriter.cs index e28b0a60..b5cbf663 100644 --- a/src/Markdig.Tests/TestFastStringWriter.cs +++ b/src/Markdig.Tests/TestFastStringWriter.cs @@ -1,7 +1,8 @@ using System.Text; - using Markdig.Helpers; +#if NET6_0_OR_GREATER + namespace Markdig.Tests; [TestFixture] @@ -184,3 +185,4 @@ public async Task Write_StringBuilder() AssertToString("foobar\nbazfoo\nfoo" + new string('a', 1050)); } } +#endif \ No newline at end of file diff --git a/src/Markdig.Tests/TestYamlFrontMatterExtension.cs b/src/Markdig.Tests/TestYamlFrontMatterExtension.cs index 3b38e4f0..a98b5793 100644 --- a/src/Markdig.Tests/TestYamlFrontMatterExtension.cs +++ b/src/Markdig.Tests/TestYamlFrontMatterExtension.cs @@ -107,7 +107,7 @@ public void FrontMatterBlockLinesCharIterator(string value) } } - Assert.Pass("No exception parsing and iterating through YAML front matter block lines"); + // No exception parsing and iterating through YAML front matter block lines } } diff --git a/src/Markdig/Helpers/FastStringWriter.cs b/src/Markdig/Helpers/FastStringWriter.cs index e0995f74..213134d7 100644 --- a/src/Markdig/Helpers/FastStringWriter.cs +++ b/src/Markdig/Helpers/FastStringWriter.cs @@ -30,7 +30,7 @@ public FastStringWriter() public override string NewLine { get => _newLine; - set => _newLine = value ?? Environment.NewLine; + set => base.NewLine = _newLine = value ?? Environment.NewLine; } [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/Markdig/Markdig.targets b/src/Markdig/Markdig.targets index 43babbd0..a6abba36 100644 --- a/src/Markdig/Markdig.targets +++ b/src/Markdig/Markdig.targets @@ -5,7 +5,7 @@ Alexandre Mutel en-US Alexandre Mutel - net462;netstandard2.0;netstandard2.1;net6.0;net8.0 + net462;netstandard2.0;netstandard2.1;net8.0;net9.0 false Markdown CommonMark md html md2html https://github.com/lunet-io/markdig/blob/master/changelog.md diff --git a/src/global.json b/src/global.json index 72d38cd2..30094e81 100644 --- a/src/global.json +++ b/src/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "8.0.100", + "version": "9.0.100", "rollForward": "latestMajor", "allowPrerelease": false } From 455f8f333d995eeb8267678a92754e4104204d18 Mon Sep 17 00:00:00 2001 From: Miha Zupan Date: Fri, 29 Nov 2024 20:22:40 +0100 Subject: [PATCH 04/88] Fix dotnet-versions format --- .github/workflows/ci.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index da15d160..bc547cc4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,6 +13,9 @@ jobs: build: uses: xoofx/.github/.github/workflows/dotnet.yml@main with: - dotnet-version: '6.0 8.0 9.0' + dotnet-version: | + 6.0 + 8.0 + 9.0 secrets: NUGET_TOKEN: ${{ secrets.NUGET_TOKEN }} \ No newline at end of file From 40781737c35c6c12c02969a4771376ce334c4691 Mon Sep 17 00:00:00 2001 From: Miha Zupan Date: Sat, 30 Nov 2024 03:03:26 +0100 Subject: [PATCH 05/88] Tweak SpecFileGen paths --- src/Markdig.Tests/Markdig.Tests.csproj | 14 +++++++------- src/Markdig.Tests/TestContainerBlocks.cs | 2 +- src/Markdig.Tests/TestFastStringWriter.cs | 4 +--- src/SpecFileGen/SpecFileGen.csproj | 2 +- 4 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/Markdig.Tests/Markdig.Tests.csproj b/src/Markdig.Tests/Markdig.Tests.csproj index 5d33dc8d..1726b8f4 100644 --- a/src/Markdig.Tests/Markdig.Tests.csproj +++ b/src/Markdig.Tests/Markdig.Tests.csproj @@ -1,14 +1,14 @@ - + - net462;net6.0;net8.0;net9.0 + net6.0;net8.0;net9.0 Exe false enable 13.0 Markdig.Tests.Program - $(MSBuildProjectDirectory)\..\SpecFileGen\bin\$(Configuration)\net8.0\SpecFileGen.dll - $(MSBuildProjectDirectory)\..\SpecFileGen\bin\$(Configuration)\net8.0\SpecFileGen.timestamp + $(MSBuildProjectDirectory)\..\SpecFileGen\bin\$(Configuration)\$(TargetFramework)\SpecFileGen.dll + $(MSBuildProjectDirectory)\..\SpecFileGen\bin\$(Configuration)\$(TargetFramework)\SpecFileGen.timestamp @@ -19,14 +19,14 @@ + - - + @@ -38,7 +38,7 @@ - + diff --git a/src/Markdig.Tests/TestContainerBlocks.cs b/src/Markdig.Tests/TestContainerBlocks.cs index c5e0440b..abffe84f 100644 --- a/src/Markdig.Tests/TestContainerBlocks.cs +++ b/src/Markdig.Tests/TestContainerBlocks.cs @@ -172,7 +172,7 @@ public void CopyTo() Assert.NotNull(destination[2]); Assert.Null(destination[3]); - Array.Clear(destination, 0, destination.Length); + Array.Clear(destination); container.Add(new ParagraphBlock()); container.CopyTo(destination, 1); diff --git a/src/Markdig.Tests/TestFastStringWriter.cs b/src/Markdig.Tests/TestFastStringWriter.cs index b5cbf663..e28b0a60 100644 --- a/src/Markdig.Tests/TestFastStringWriter.cs +++ b/src/Markdig.Tests/TestFastStringWriter.cs @@ -1,7 +1,6 @@ using System.Text; -using Markdig.Helpers; -#if NET6_0_OR_GREATER +using Markdig.Helpers; namespace Markdig.Tests; @@ -185,4 +184,3 @@ public async Task Write_StringBuilder() AssertToString("foobar\nbazfoo\nfoo" + new string('a', 1050)); } } -#endif \ No newline at end of file diff --git a/src/SpecFileGen/SpecFileGen.csproj b/src/SpecFileGen/SpecFileGen.csproj index 0ad72b08..0c28b463 100644 --- a/src/SpecFileGen/SpecFileGen.csproj +++ b/src/SpecFileGen/SpecFileGen.csproj @@ -2,7 +2,7 @@ Exe - net8.0 + net6.0;net8.0;net9.0 enable false From a11899a350d8cdccc746361183342ae5c56f7148 Mon Sep 17 00:00:00 2001 From: Sergey Nozhenko Date: Mon, 9 Dec 2024 22:20:32 +0300 Subject: [PATCH 06/88] Fixes emphasis span calculation. A test is added. --- src/Markdig.Tests/TestSourcePosition.cs | 11 +++++++++++ src/Markdig/Parsers/Inlines/EmphasisInlineParser.cs | 9 ++++----- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/Markdig.Tests/TestSourcePosition.cs b/src/Markdig.Tests/TestSourcePosition.cs index 3cd0e97e..746694eb 100644 --- a/src/Markdig.Tests/TestSourcePosition.cs +++ b/src/Markdig.Tests/TestSourcePosition.cs @@ -160,6 +160,17 @@ public void TestEmphasis3() "); } + [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() { diff --git a/src/Markdig/Parsers/Inlines/EmphasisInlineParser.cs b/src/Markdig/Parsers/Inlines/EmphasisInlineParser.cs index 4fc6268c..440a878b 100644 --- a/src/Markdig/Parsers/Inlines/EmphasisInlineParser.cs +++ b/src/Markdig/Parsers/Inlines/EmphasisInlineParser.cs @@ -302,14 +302,13 @@ private void ProcessEmphasis(InlineProcessor processor, List Date: Sun, 15 Dec 2024 19:49:56 +0300 Subject: [PATCH 07/88] Set delimiter in the CustomContainer instance. --- .../Extensions/CustomContainers/CustomContainerExtension.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Markdig/Extensions/CustomContainers/CustomContainerExtension.cs b/src/Markdig/Extensions/CustomContainers/CustomContainerExtension.cs index d07d4a4c..1a38013e 100644 --- a/src/Markdig/Extensions/CustomContainers/CustomContainerExtension.cs +++ b/src/Markdig/Extensions/CustomContainers/CustomContainerExtension.cs @@ -30,7 +30,11 @@ public void Setup(MarkdownPipelineBuilder pipeline) { if (delimiterCount == 2 && emphasisChar == ':') { - return new CustomContainerInline(); + return new CustomContainerInline + { + DelimiterChar = ':', + DelimiterCount = 2 + }; } return null; }); From eb28f76588b9a6fd2f25720e2975e81e9ea56c2a Mon Sep 17 00:00:00 2001 From: Sergey Nozhenko Date: Sun, 15 Dec 2024 20:13:35 +0300 Subject: [PATCH 08/88] Set the correct source location to the Footnote and FootnoteLinkReferenceDefinition. --- src/Markdig/Extensions/Footnotes/FootnoteParser.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Markdig/Extensions/Footnotes/FootnoteParser.cs b/src/Markdig/Extensions/Footnotes/FootnoteParser.cs index 6d184bde..52c8c2c7 100644 --- a/src/Markdig/Extensions/Footnotes/FootnoteParser.cs +++ b/src/Markdig/Extensions/Footnotes/FootnoteParser.cs @@ -57,6 +57,8 @@ private BlockState TryOpen(BlockProcessor processor, bool isContinue) { 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 @@ private BlockState TryOpen(BlockProcessor processor, bool isContinue) { CreateLinkInline = CreateLinkToFootnote, Line = processor.LineIndex, + Column = saved, Span = new SourceSpan(start, processor.Start - 2), // account for ]: LabelSpan = labelSpan, Label = label From e92a8097d0d0dd1f0c2afb8355905f059f55e3c2 Mon Sep 17 00:00:00 2001 From: Sergey Nozhenko Date: Wed, 18 Dec 2024 02:36:56 +0300 Subject: [PATCH 09/88] Fixes an incorrect offset in GridTableParser. --- src/Markdig/Extensions/Tables/GridTableParser.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Markdig/Extensions/Tables/GridTableParser.cs b/src/Markdig/Extensions/Tables/GridTableParser.cs index 173dc014..0dc268fa 100644 --- a/src/Markdig/Extensions/Tables/GridTableParser.cs +++ b/src/Markdig/Extensions/Tables/GridTableParser.cs @@ -256,7 +256,7 @@ private BlockState HandleContents(BlockProcessor processor, GridTableState table { 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; } From 68659f4037d1b2942818f7a9261cf02ecc38bfd3 Mon Sep 17 00:00:00 2001 From: Sergey Nozhenko Date: Wed, 18 Dec 2024 07:43:22 +0300 Subject: [PATCH 10/88] Include opening and closing pipes in the table span --- src/Markdig/Extensions/Tables/PipeTableParser.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/Markdig/Extensions/Tables/PipeTableParser.cs b/src/Markdig/Extensions/Tables/PipeTableParser.cs index 87bd4249..71c14538 100644 --- a/src/Markdig/Extensions/Tables/PipeTableParser.cs +++ b/src/Markdig/Extensions/Tables/PipeTableParser.cs @@ -280,6 +280,8 @@ public bool PostProcess(InlineProcessor state, Inline? root, Inline? lastChild, tableState.EndOfLines.Add(endOfTable); } + int lastPipePos = 0; + // Cell loop // Reconstruct the table from the delimiters TableRow? row = null; @@ -302,6 +304,12 @@ public bool PostProcess(InlineProcessor state, Inline? root, Inline? lastChild, 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 bool PostProcess(InlineProcessor state, Inline? root, Inline? lastChild, // 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 bool PostProcess(InlineProcessor state, Inline? root, Inline? lastChild, } } + 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) { From aff8a6823aa64af88620194e3ece2354349789c1 Mon Sep 17 00:00:00 2001 From: Sergey Nozhenko Date: Wed, 18 Dec 2024 10:21:29 +0300 Subject: [PATCH 11/88] A test has been added. --- src/Markdig.Tests/TestSourcePosition.cs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/Markdig.Tests/TestSourcePosition.cs b/src/Markdig.Tests/TestSourcePosition.cs index 746694eb..2cfa61e1 100644 --- a/src/Markdig.Tests/TestSourcePosition.cs +++ b/src/Markdig.Tests/TestSourcePosition.cs @@ -807,6 +807,29 @@ public void TestPipeTable2() ", "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() { From 5e4a917dbd25c6c40e2d184cfaca908c2908aca5 Mon Sep 17 00:00:00 2001 From: Sergey Nozhenko Date: Wed, 18 Dec 2024 12:38:13 +0300 Subject: [PATCH 12/88] Fixes an error in the AbbreviationParser. --- src/Markdig/Extensions/Abbreviations/AbbreviationParser.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Markdig/Extensions/Abbreviations/AbbreviationParser.cs b/src/Markdig/Extensions/Abbreviations/AbbreviationParser.cs index 863e882f..0b9548b1 100644 --- a/src/Markdig/Extensions/Abbreviations/AbbreviationParser.cs +++ b/src/Markdig/Extensions/Abbreviations/AbbreviationParser.cs @@ -171,7 +171,7 @@ private void DocumentOnProcessInlinesBegin(InlineProcessor inlineProcessor, Inli // Process the remaining literal literal = new LiteralInline() { - Span = new SourceSpan(abbrInline.Span.End + 1, literal.Span.End), + Span = new SourceSpan(abbrInline.Span.End + 1, container.Span.End), Line = line, Column = column + match.Length, }; From 2cff6c51949324ace802cf0a5880160fa2627470 Mon Sep 17 00:00:00 2001 From: Sergey Nozhenko Date: Wed, 18 Dec 2024 13:09:00 +0300 Subject: [PATCH 13/88] It's necessary to keep a copy of the original literal.Span.End, because otherwise it is just lost in some cases. --- src/Markdig/Extensions/Abbreviations/AbbreviationParser.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Markdig/Extensions/Abbreviations/AbbreviationParser.cs b/src/Markdig/Extensions/Abbreviations/AbbreviationParser.cs index 0b9548b1..9cfe5b10 100644 --- a/src/Markdig/Extensions/Abbreviations/AbbreviationParser.cs +++ b/src/Markdig/Extensions/Abbreviations/AbbreviationParser.cs @@ -89,6 +89,7 @@ private void DocumentOnProcessInlinesBegin(InlineProcessor inlineProcessor, Inli { var literal = (LiteralInline)processor.Inline!; var originalLiteral = literal; + var originalSpanEnd = literal.Span.End; ContainerInline? container = null; @@ -171,7 +172,7 @@ private void DocumentOnProcessInlinesBegin(InlineProcessor inlineProcessor, Inli // Process the remaining literal literal = new LiteralInline() { - Span = new SourceSpan(abbrInline.Span.End + 1, container.Span.End), + Span = new SourceSpan(abbrInline.Span.End + 1, originalSpanEnd), Line = line, Column = column + match.Length, }; From bc41b0c2a33278489319ff9fa0c0c4c9ca1f7ba1 Mon Sep 17 00:00:00 2001 From: Sergey Nozhenko Date: Thu, 19 Dec 2024 00:44:29 +0300 Subject: [PATCH 14/88] Existing test has been extended. --- src/Markdig.Tests/TestSourcePosition.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Markdig.Tests/TestSourcePosition.cs b/src/Markdig.Tests/TestSourcePosition.cs index 746694eb..2a7b8acd 100644 --- a/src/Markdig.Tests/TestSourcePosition.cs +++ b/src/Markdig.Tests/TestSourcePosition.cs @@ -533,13 +533,17 @@ public void TestHtmlEntityInline() [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"); } From fdaef77474dff931d25a443b8f9c038202d81942 Mon Sep 17 00:00:00 2001 From: Alexandre Mutel Date: Thu, 19 Dec 2024 05:48:36 +0100 Subject: [PATCH 15/88] Update ci badge --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 713d9240..e5f5f7ab 100644 --- a/readme.md +++ b/readme.md @@ -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) From c35f7fff178bddcb4d8165e9f67c97a37d0b912f Mon Sep 17 00:00:00 2001 From: Sergey Nozhenko Date: Sat, 21 Dec 2024 03:29:31 +0300 Subject: [PATCH 16/88] Fixed errors in LinkHelper and LinkInlineParser. --- src/Markdig.Tests/TestLinkHelper.cs | 15 ++ src/Markdig.Tests/TestPlayParser.cs | 8 + src/Markdig/Helpers/LinkHelper.cs | 216 ++++++++---------- .../Parsers/Inlines/LinkInlineParser.cs | 15 +- 4 files changed, 124 insertions(+), 130 deletions(-) diff --git a/src/Markdig.Tests/TestLinkHelper.cs b/src/Markdig.Tests/TestLinkHelper.cs index 75acbb19..d46c50ab 100644 --- a/src/Markdig.Tests/TestLinkHelper.cs +++ b/src/Markdig.Tests/TestLinkHelper.cs @@ -89,6 +89,14 @@ public void TestTitleMultiline() 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() { @@ -238,6 +246,13 @@ public void TestlLinkReferenceDefinitionSimple() } + [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() { diff --git a/src/Markdig.Tests/TestPlayParser.cs b/src/Markdig.Tests/TestPlayParser.cs index 53420b1d..54af3927 100644 --- a/src/Markdig.Tests/TestPlayParser.cs +++ b/src/Markdig.Tests/TestPlayParser.cs @@ -46,6 +46,14 @@ public void TestLink() Assert.AreEqual("/yoyo", link?.Url); } + [Test] + public void TestLinkWithMultipleBackslashesInTitle() + { + var doc = Markdown.Parse(@"[link](/uri '\\\\127.0.0.1')"); + var link = doc.Descendants().SelectMany(x => x.Inline.Descendants()).FirstOrDefault(); + Assert.AreEqual(@"\\127.0.0.1", link?.Title); + } + [Test] public void TestListBug2() { diff --git a/src/Markdig/Helpers/LinkHelper.cs b/src/Markdig/Helpers/LinkHelper.cs index 48581e1e..0356a275 100644 --- a/src/Markdig/Helpers/LinkHelper.cs +++ b/src/Markdig/Helpers/LinkHelper.cs @@ -545,88 +545,70 @@ public static bool TryParseTitle(ref T text, out string? title, out char encl 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('\\'); } - // Skip last quote - text.SkipChar(); - goto ReturnValid; + buffer.Append(c); } - - if (hasEscape && !c.IsAsciiPunctuation()) + else if (c == closingQuote) { - buffer.Append('\\'); + // Skip last quote + text.SkipChar(); + title = buffer.ToString(); + return true; } - - 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(ref T text, out string? title, out char enclosingCharacter) where T : ICharIterator @@ -642,88 +624,70 @@ public static bool TryParseTitleTrivia(ref T text, out string? title, out cha 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('\\'); } - // Skip last quote - text.SkipChar(); - goto ReturnValid; + buffer.Append(c); } - - if (hasEscape && !c.IsAsciiPunctuation()) + else if (c == closingQuote) { - buffer.Append('\\'); + // Skip last quote + text.SkipChar(); + title = buffer.ToString(); + return true; } - - 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 text, [NotNullWhen(true)] out string? link) where T : ICharIterator @@ -760,12 +724,15 @@ public static bool TryParseUrl(ref T text, [NotNullWhen(true)] out string? li break; } - if (hasEscape && !c.IsAsciiPunctuation()) + if (hasEscape) { - buffer.Append('\\'); + hasEscape = false; + if (!c.IsAsciiPunctuation()) + { + buffer.Append('\\'); + } } - - if (c == '\\') + else if (c == '\\') { hasEscape = true; continue; @@ -776,8 +743,6 @@ public static bool TryParseUrl(ref T text, [NotNullWhen(true)] out string? li break; } - hasEscape = false; - buffer.Append(c); } while (c != '\0'); @@ -816,20 +781,21 @@ public static bool TryParseUrl(ref T text, [NotNullWhen(true)] out string? li 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)) @@ -907,12 +873,15 @@ public static bool TryParseUrlTrivia(ref T text, out string? link, out bool h break; } - if (hasEscape && !c.IsAsciiPunctuation()) + if (hasEscape) { - buffer.Append('\\'); + hasEscape = false; + if (!c.IsAsciiPunctuation()) + { + buffer.Append('\\'); + } } - - if (c == '\\') + else if (c == '\\') { hasEscape = true; continue; @@ -923,8 +892,6 @@ public static bool TryParseUrlTrivia(ref T text, out string? link, out bool h break; } - hasEscape = false; - buffer.Append(c); } while (c != '\0'); @@ -963,20 +930,21 @@ public static bool TryParseUrlTrivia(ref T text, out string? link, out bool h 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)) @@ -1161,7 +1129,7 @@ public static bool TryParseLinkReferenceDefinition(ref T text, c = text.NextChar(); } - if (c != '\0' && c != '\n' && c != '\r' && text.PeekChar() != '\n') + if (c != '\0' && c != '\n' && (c != '\r' || text.PeekChar() != '\n')) { // If we were able to parse the url but the title doesn't end with space, // we are still returning a valid definition @@ -1301,7 +1269,7 @@ public static bool TryParseLinkReferenceDefinitionTrivia( c = text.NextChar(); } - if (c != '\0' && c != '\n' && c != '\r' && text.PeekChar() != '\n') + if (c != '\0' && c != '\n' && (c != '\r' || text.PeekChar() != '\n')) { // If we were able to parse the url but the title doesn't end with space, // we are still returning a valid definition diff --git a/src/Markdig/Parsers/Inlines/LinkInlineParser.cs b/src/Markdig/Parsers/Inlines/LinkInlineParser.cs index 0caa1ef4..97431d5a 100644 --- a/src/Markdig/Parsers/Inlines/LinkInlineParser.cs +++ b/src/Markdig/Parsers/Inlines/LinkInlineParser.cs @@ -137,6 +137,9 @@ private bool ProcessLinkReference( 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 @@ private bool ProcessLinkReference( // Inline Link var linkInline = new LinkInline() { - Url = HtmlHelper.Unescape(linkRef.Url), - Title = HtmlHelper.Unescape(linkRef.Title), + Url = HtmlHelper.Unescape(linkRef.Url, false), + Title = HtmlHelper.Unescape(linkRef.Title, false), Label = label, LabelSpan = labelSpan, UrlSpan = linkRef.UrlSpan, @@ -256,8 +259,8 @@ private bool TryProcessLinkOrImage(InlineProcessor inlineState, ref StringSlice // Inline Link link = new LinkInline() { - Url = HtmlHelper.Unescape(url), - Title = HtmlHelper.Unescape(title), + Url = HtmlHelper.Unescape(url, false), + Title = HtmlHelper.Unescape(title, false), IsImage = openParent.IsImage, LabelSpan = openParent.LabelSpan, UrlSpan = inlineState.GetSourcePositionFromLocalSpan(linkSpan), @@ -382,11 +385,11 @@ private bool TryProcessLinkOrImage(InlineProcessor inlineState, ref StringSlice return new LinkInline() { TriviaBeforeUrl = wsBeforeLink, - Url = HtmlHelper.Unescape(url), + Url = HtmlHelper.Unescape(url, false), UnescapedUrl = unescapedUrl, UrlHasPointyBrackets = urlHasPointyBrackets, TriviaAfterUrl = wsAfterLink, - Title = HtmlHelper.Unescape(title), + Title = HtmlHelper.Unescape(title, false), UnescapedTitle = unescapedTitle, TitleEnclosingCharacter = titleEnclosingCharacter, TriviaAfterTitle = wsAfterTitle, From 90365bfeee0e9051992354aacfe35f4350a76c16 Mon Sep 17 00:00:00 2001 From: snnz Date: Sat, 21 Dec 2024 06:13:09 +0300 Subject: [PATCH 17/88] Update src/Markdig/Parsers/Inlines/LinkInlineParser.cs Co-authored-by: Miha Zupan --- src/Markdig/Parsers/Inlines/LinkInlineParser.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Markdig/Parsers/Inlines/LinkInlineParser.cs b/src/Markdig/Parsers/Inlines/LinkInlineParser.cs index 97431d5a..d7428147 100644 --- a/src/Markdig/Parsers/Inlines/LinkInlineParser.cs +++ b/src/Markdig/Parsers/Inlines/LinkInlineParser.cs @@ -148,8 +148,8 @@ private bool ProcessLinkReference( // Inline Link var linkInline = new LinkInline() { - Url = HtmlHelper.Unescape(linkRef.Url, false), - Title = HtmlHelper.Unescape(linkRef.Title, false), + Url = HtmlHelper.Unescape(linkRef.Url, removeBackSlash: false), + Title = HtmlHelper.Unescape(linkRef.Title, removeBackSlash: false), Label = label, LabelSpan = labelSpan, UrlSpan = linkRef.UrlSpan, From ad0770a59406f432d3de402a93289c6d983d5428 Mon Sep 17 00:00:00 2001 From: snnz Date: Sat, 21 Dec 2024 06:13:22 +0300 Subject: [PATCH 18/88] Update src/Markdig/Parsers/Inlines/LinkInlineParser.cs Co-authored-by: Miha Zupan --- src/Markdig/Parsers/Inlines/LinkInlineParser.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Markdig/Parsers/Inlines/LinkInlineParser.cs b/src/Markdig/Parsers/Inlines/LinkInlineParser.cs index d7428147..cade0e3f 100644 --- a/src/Markdig/Parsers/Inlines/LinkInlineParser.cs +++ b/src/Markdig/Parsers/Inlines/LinkInlineParser.cs @@ -259,8 +259,8 @@ private bool TryProcessLinkOrImage(InlineProcessor inlineState, ref StringSlice // Inline Link link = new LinkInline() { - Url = HtmlHelper.Unescape(url, false), - Title = HtmlHelper.Unescape(title, false), + Url = HtmlHelper.Unescape(url, removeBackSlash: false), + Title = HtmlHelper.Unescape(title, removeBackSlash: false), IsImage = openParent.IsImage, LabelSpan = openParent.LabelSpan, UrlSpan = inlineState.GetSourcePositionFromLocalSpan(linkSpan), From 54783b8f65db62a56f6d7b8982566d8922117cd6 Mon Sep 17 00:00:00 2001 From: snnz Date: Sat, 21 Dec 2024 06:13:56 +0300 Subject: [PATCH 19/88] Update src/Markdig/Parsers/Inlines/LinkInlineParser.cs Co-authored-by: Miha Zupan --- src/Markdig/Parsers/Inlines/LinkInlineParser.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Markdig/Parsers/Inlines/LinkInlineParser.cs b/src/Markdig/Parsers/Inlines/LinkInlineParser.cs index cade0e3f..85b6c9bf 100644 --- a/src/Markdig/Parsers/Inlines/LinkInlineParser.cs +++ b/src/Markdig/Parsers/Inlines/LinkInlineParser.cs @@ -385,7 +385,7 @@ private bool TryProcessLinkOrImage(InlineProcessor inlineState, ref StringSlice return new LinkInline() { TriviaBeforeUrl = wsBeforeLink, - Url = HtmlHelper.Unescape(url, false), + Url = HtmlHelper.Unescape(url, removeBackSlash: false), UnescapedUrl = unescapedUrl, UrlHasPointyBrackets = urlHasPointyBrackets, TriviaAfterUrl = wsAfterLink, From 7f604bef30746ee35d74d2e65498dcaf2cac384f Mon Sep 17 00:00:00 2001 From: snnz Date: Sat, 21 Dec 2024 06:14:07 +0300 Subject: [PATCH 20/88] Update src/Markdig/Parsers/Inlines/LinkInlineParser.cs Co-authored-by: Miha Zupan --- src/Markdig/Parsers/Inlines/LinkInlineParser.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Markdig/Parsers/Inlines/LinkInlineParser.cs b/src/Markdig/Parsers/Inlines/LinkInlineParser.cs index 85b6c9bf..eee94ec7 100644 --- a/src/Markdig/Parsers/Inlines/LinkInlineParser.cs +++ b/src/Markdig/Parsers/Inlines/LinkInlineParser.cs @@ -389,7 +389,7 @@ private bool TryProcessLinkOrImage(InlineProcessor inlineState, ref StringSlice UnescapedUrl = unescapedUrl, UrlHasPointyBrackets = urlHasPointyBrackets, TriviaAfterUrl = wsAfterLink, - Title = HtmlHelper.Unescape(title, false), + Title = HtmlHelper.Unescape(title, removeBackSlash: false), UnescapedTitle = unescapedTitle, TitleEnclosingCharacter = titleEnclosingCharacter, TriviaAfterTitle = wsAfterTitle, From 90bc15c016a84c3758374337516c83e5733435b9 Mon Sep 17 00:00:00 2001 From: snnz Date: Sat, 21 Dec 2024 06:14:16 +0300 Subject: [PATCH 21/88] Update src/Markdig.Tests/TestPlayParser.cs Co-authored-by: Miha Zupan --- src/Markdig.Tests/TestPlayParser.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Markdig.Tests/TestPlayParser.cs b/src/Markdig.Tests/TestPlayParser.cs index 54af3927..18942411 100644 --- a/src/Markdig.Tests/TestPlayParser.cs +++ b/src/Markdig.Tests/TestPlayParser.cs @@ -50,7 +50,7 @@ public void TestLink() public void TestLinkWithMultipleBackslashesInTitle() { var doc = Markdown.Parse(@"[link](/uri '\\\\127.0.0.1')"); - var link = doc.Descendants().SelectMany(x => x.Inline.Descendants()).FirstOrDefault(); + var link = doc.Descendants().FirstOrDefault(); Assert.AreEqual(@"\\127.0.0.1", link?.Title); } From ab8e85b06e87e87c30a7530d0c37c0561921bd95 Mon Sep 17 00:00:00 2001 From: Sergey Nozhenko Date: Sat, 21 Dec 2024 06:56:23 +0300 Subject: [PATCH 22/88] Remove additional condition, since a carriage return constitute a line ending regardless of whether it is followed by a line feed or not. --- src/Markdig/Helpers/LinkHelper.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Markdig/Helpers/LinkHelper.cs b/src/Markdig/Helpers/LinkHelper.cs index 0356a275..ac47b294 100644 --- a/src/Markdig/Helpers/LinkHelper.cs +++ b/src/Markdig/Helpers/LinkHelper.cs @@ -1129,7 +1129,7 @@ public static bool TryParseLinkReferenceDefinition(ref T text, 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 @@ -1269,7 +1269,7 @@ public static bool TryParseLinkReferenceDefinitionTrivia( 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 From 88c5b5cb410f7c8507e61dca4731e6e558bfa78b Mon Sep 17 00:00:00 2001 From: Melodi <62381002+Melodi17@users.noreply.github.com> Date: Tue, 31 Dec 2024 22:57:02 +1000 Subject: [PATCH 23/88] Added method for clearing indents in TextRendererBase as well as added case handling to PopIndent() --- src/Markdig/Renderers/TextRendererBase.cs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/Markdig/Renderers/TextRendererBase.cs b/src/Markdig/Renderers/TextRendererBase.cs index 4d428a77..ef4dd525 100644 --- a/src/Markdig/Renderers/TextRendererBase.cs +++ b/src/Markdig/Renderers/TextRendererBase.cs @@ -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 System.Diagnostics.CodeAnalysis; @@ -174,10 +174,12 @@ public void PushIndent(string[] lineSpecific) public void PopIndent() { - // TODO: Check - indents.RemoveAt(indents.Count - 1); + if (this.indents.Count > 0) + indents.RemoveAt(indents.Count - 1); } + public void ClearIndent() => indents.Clear(); + [MethodImpl(MethodImplOptions.AggressiveInlining)] private protected void WriteIndent() { @@ -220,12 +222,12 @@ public T Write(string? content) [MethodImpl(MethodImplOptions.AggressiveInlining)] internal T Write(char c, int count) { - WriteIndent(); - + WriteIndent(); + for (int i = 0; i < count; i++) { Writer.Write(c); - } + } return (T)this; } From f2590e7b809c9b36dfbd2afcfdab185ae1fdcfce Mon Sep 17 00:00:00 2001 From: Sergey Nozhenko Date: Fri, 3 Jan 2025 01:27:11 +0300 Subject: [PATCH 24/88] Check that the alert candidate is not already in an alert block or nested within other elements. --- src/Markdig/Extensions/Alerts/AlertInlineParser.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Markdig/Extensions/Alerts/AlertInlineParser.cs b/src/Markdig/Extensions/Alerts/AlertInlineParser.cs index 6f95c5b6..faf56859 100644 --- a/src/Markdig/Extensions/Alerts/AlertInlineParser.cs +++ b/src/Markdig/Extensions/Alerts/AlertInlineParser.cs @@ -29,7 +29,8 @@ public override bool Match(InlineProcessor processor, ref StringSlice slice) // 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; } From 3e0c72f0430cb58d7aa9caf325bd780325229e17 Mon Sep 17 00:00:00 2001 From: Sergey Nozhenko Date: Fri, 3 Jan 2025 03:30:49 +0300 Subject: [PATCH 25/88] Fixes exception in DefinitionListParser.GetCurrentDefinitionList() --- .../DefinitionLists/DefinitionListParser.cs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/Markdig/Extensions/DefinitionLists/DefinitionListParser.cs b/src/Markdig/Extensions/DefinitionLists/DefinitionListParser.cs index 0985ed8d..bb3af8cf 100644 --- a/src/Markdig/Extensions/DefinitionLists/DefinitionListParser.cs +++ b/src/Markdig/Extensions/DefinitionLists/DefinitionListParser.cs @@ -105,13 +105,20 @@ public override BlockState TryOpen(BlockProcessor processor) { 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) From 118d28f886f0af1b1c7d7430d2f7ee8d5e4eca1c Mon Sep 17 00:00:00 2001 From: Sergey Nozhenko Date: Fri, 3 Jan 2025 04:29:24 +0300 Subject: [PATCH 26/88] Prevent GridTableParser from looking beyond the end of a line. --- src/Markdig/Extensions/Tables/GridTableParser.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Markdig/Extensions/Tables/GridTableParser.cs b/src/Markdig/Extensions/Tables/GridTableParser.cs index 0dc268fa..6e6d562a 100644 --- a/src/Markdig/Extensions/Tables/GridTableParser.cs +++ b/src/Markdig/Extensions/Tables/GridTableParser.cs @@ -135,6 +135,7 @@ private BlockState HandleNewRow(BlockProcessor processor, GridTableState tableSt private static void SetRowSpanState(List 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 @@ private static void SetRowSpanState(List columns, St 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)) { From d87bb7292d550f95c0088454b96fc97e5de6e052 Mon Sep 17 00:00:00 2001 From: Sergey Nozhenko Date: Fri, 3 Jan 2025 07:01:29 +0300 Subject: [PATCH 27/88] A test has been added. --- src/Markdig.Tests/MiscTests.cs | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/Markdig.Tests/MiscTests.cs b/src/Markdig.Tests/MiscTests.cs index 85847988..0d3d14dd 100644 --- a/src/Markdig.Tests/MiscTests.cs +++ b/src/Markdig.Tests/MiscTests.cs @@ -317,4 +317,31 @@ public void RootInlineInTableCellHasCorrectSourceSpan() 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 TestAlertWithinAlertOrNestedBlock() + { + var input = @" +>[!NOTE] +[!NOTE] +The second one is not a note. + +>>[!NOTE] +Also not a note. +"; + + var expected = @"
+

Note

+

[!NOTE] +The second one is not a note.

+
+
+
+

[!NOTE] +Also not a note.

+
+
+"; + TestParser.TestSpec(input, expected, new MarkdownPipelineBuilder().UseAlertBlocks().Build()); + } } From bc8ba4fecb9fd6ce849a601bc7031bb42210de76 Mon Sep 17 00:00:00 2001 From: Sergey Nozhenko Date: Fri, 3 Jan 2025 07:02:38 +0300 Subject: [PATCH 28/88] A test has been added. --- src/Markdig.Tests/MiscTests.cs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/Markdig.Tests/MiscTests.cs b/src/Markdig.Tests/MiscTests.cs index 85847988..0f7ad4d1 100644 --- a/src/Markdig.Tests/MiscTests.cs +++ b/src/Markdig.Tests/MiscTests.cs @@ -317,4 +317,26 @@ public void RootInlineInTableCellHasCorrectSourceSpan() 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 TestDefinitionListInListItemWithBlankLine() + { + var input = @" +- + + term + : definition +"; + + var expected = @"
    +
  • +
    +
    term
    +
    definition
    +
    +
  • +
+"; + TestParser.TestSpec(input, expected, new MarkdownPipelineBuilder().UseDefinitionLists().Build()); + } } From 7b6d659bbd803e401a1d5ca705cb1f69267a981b Mon Sep 17 00:00:00 2001 From: Sergey Nozhenko Date: Fri, 3 Jan 2025 07:03:28 +0300 Subject: [PATCH 29/88] A test has been added. --- src/Markdig.Tests/MiscTests.cs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/Markdig.Tests/MiscTests.cs b/src/Markdig.Tests/MiscTests.cs index 85847988..77b95059 100644 --- a/src/Markdig.Tests/MiscTests.cs +++ b/src/Markdig.Tests/MiscTests.cs @@ -317,4 +317,24 @@ public void RootInlineInTableCellHasCorrectSourceSpan() 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 = @" ++ + + + + +
+"; + TestParser.TestSpec(input, expected, new MarkdownPipelineBuilder().UseGridTables().Build()); + } } From 5b32391348835b4e5f451d15871bad490e001639 Mon Sep 17 00:00:00 2001 From: Alexandre Mutel Date: Fri, 10 Jan 2025 08:56:38 +0100 Subject: [PATCH 30/88] Update dependencies NuGet --- src/Markdig.Benchmarks/Markdig.Benchmarks.csproj | 6 +++--- src/Markdig.Tests/Markdig.Tests.csproj | 6 +++--- src/global.json | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Markdig.Benchmarks/Markdig.Benchmarks.csproj b/src/Markdig.Benchmarks/Markdig.Benchmarks.csproj index 0fdb48c4..fb4c989a 100644 --- a/src/Markdig.Benchmarks/Markdig.Benchmarks.csproj +++ b/src/Markdig.Benchmarks/Markdig.Benchmarks.csproj @@ -19,12 +19,12 @@
- - + + - + diff --git a/src/Markdig.Tests/Markdig.Tests.csproj b/src/Markdig.Tests/Markdig.Tests.csproj index 1726b8f4..eb019e28 100644 --- a/src/Markdig.Tests/Markdig.Tests.csproj +++ b/src/Markdig.Tests/Markdig.Tests.csproj @@ -12,9 +12,9 @@
- - - + + + diff --git a/src/global.json b/src/global.json index 30094e81..4f85ebc6 100644 --- a/src/global.json +++ b/src/global.json @@ -1,7 +1,7 @@ { "sdk": { "version": "9.0.100", - "rollForward": "latestMajor", + "rollForward": "latestMinor", "allowPrerelease": false } } \ No newline at end of file From 148278417f5aeee2e523ee9ad2fea4500320fa23 Mon Sep 17 00:00:00 2001 From: Melodi <62381002+Melodi17@users.noreply.github.com> Date: Tue, 14 Jan 2025 14:25:20 +1000 Subject: [PATCH 31/88] Added error throwing when stack is empty and PopIndent() is called --- src/Markdig/Renderers/TextRendererBase.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Markdig/Renderers/TextRendererBase.cs b/src/Markdig/Renderers/TextRendererBase.cs index ef4dd525..cb2b0da1 100644 --- a/src/Markdig/Renderers/TextRendererBase.cs +++ b/src/Markdig/Renderers/TextRendererBase.cs @@ -176,6 +176,8 @@ public void PopIndent() { if (this.indents.Count > 0) indents.RemoveAt(indents.Count - 1); + else + throw new InvalidOperationException("No indent to pop"); } public void ClearIndent() => indents.Clear(); From 9dac60df7358e2b4fa3108a00bf459b64d3cbaf3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fa=E9=B8=BD?= <43724908+Akarinnnnn@users.noreply.github.com> Date: Mon, 24 Feb 2025 10:49:59 +0800 Subject: [PATCH 32/88] Replace encoding polyfill with NET5+ one. netstandard2.1 is a special TFM that .NET5+ doesn't mark themselves compitable, even if they mostly are. --- src/Markdig/Polyfills/EncodingExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Markdig/Polyfills/EncodingExtensions.cs b/src/Markdig/Polyfills/EncodingExtensions.cs index 14d3845a..17467a07 100644 --- a/src/Markdig/Polyfills/EncodingExtensions.cs +++ b/src/Markdig/Polyfills/EncodingExtensions.cs @@ -2,7 +2,7 @@ // This file is licensed under the BSD-Clause 2 license. // See the license.txt file in the project root for more information. -#if !NETSTANDARD2_1_OR_GREATER +#if !NETCOREAPP2_1_OR_GREATER && !NETSTANDARD2_1_OR_GREATER using System.Runtime.InteropServices; From 39b07d6bc5d7b0fb20a3cedc8216cd87cdf1c9ac Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Mon, 17 Mar 2025 07:46:23 +0800 Subject: [PATCH 33/88] Add AutoLinkOptions.AllowDomainWithoutPeriod --- src/Markdig.Tests/TestAutoLinks.cs | 22 +++++++++++++++++++ .../Extensions/AutoLinks/AutoLinkOptions.cs | 5 +++++ .../Extensions/AutoLinks/AutoLinkParser.cs | 2 +- src/Markdig/Helpers/LinkHelper.cs | 4 ++-- 4 files changed, 30 insertions(+), 3 deletions(-) create mode 100644 src/Markdig.Tests/TestAutoLinks.cs diff --git a/src/Markdig.Tests/TestAutoLinks.cs b/src/Markdig.Tests/TestAutoLinks.cs new file mode 100644 index 00000000..dd5e455b --- /dev/null +++ b/src/Markdig.Tests/TestAutoLinks.cs @@ -0,0 +1,22 @@ +using Markdig.Extensions.AutoLinks; + +namespace Markdig.Tests; + +[TestFixture] +public class TestAutoLinks +{ + [Test] + [TestCase("https://localhost", "

https://localhost

")] + [TestCase("http://localhost", "

http://localhost

")] + [TestCase("https://l", "

https://l

")] + [TestCase("www.l", "

www.l

")] + public void TestLinksWithAllowDomainWithoutPeriod(string markdown, string expected) + { + var pipeline = new MarkdownPipelineBuilder() + .UseAutoLinks(new AutoLinkOptions { AllowDomainWithoutPeriod = true }) + .Build(); + var html = Markdown.ToHtml(markdown, pipeline); + + Assert.That(html, Is.EqualTo(expected).IgnoreWhiteSpace); + } +} diff --git a/src/Markdig/Extensions/AutoLinks/AutoLinkOptions.cs b/src/Markdig/Extensions/AutoLinks/AutoLinkOptions.cs index 307b216b..189a6411 100644 --- a/src/Markdig/Extensions/AutoLinks/AutoLinkOptions.cs +++ b/src/Markdig/Extensions/AutoLinks/AutoLinkOptions.cs @@ -22,4 +22,9 @@ public AutoLinkOptions() /// Should a www link be prefixed with https:// instead of http:// (false by default) /// public bool UseHttpsForWWWLinks { get; set; } + + /// + /// Should a link host name with no period, e.g. https://localhost, be allowed (false by default) + /// + public bool AllowDomainWithoutPeriod { get; set; } } diff --git a/src/Markdig/Extensions/AutoLinks/AutoLinkParser.cs b/src/Markdig/Extensions/AutoLinks/AutoLinkParser.cs index 515c9882..6ee6b9f0 100644 --- a/src/Markdig/Extensions/AutoLinks/AutoLinkParser.cs +++ b/src/Markdig/Extensions/AutoLinks/AutoLinkParser.cs @@ -164,7 +164,7 @@ public override bool Match(InlineProcessor processor, ref StringSlice slice) } // Do not need to check if a telephone number is a valid domain - if (c != 't' && !LinkHelper.IsValidDomain(link, domainOffset)) + if (c != 't' && !LinkHelper.IsValidDomain(link, domainOffset, Options.AllowDomainWithoutPeriod)) { return false; } diff --git a/src/Markdig/Helpers/LinkHelper.cs b/src/Markdig/Helpers/LinkHelper.cs index ac47b294..a994b3f2 100644 --- a/src/Markdig/Helpers/LinkHelper.cs +++ b/src/Markdig/Helpers/LinkHelper.cs @@ -1006,7 +1006,7 @@ private static bool IsEndOfUri(char c, bool isAutoLink) return c == '\0' || c.IsSpaceOrTab() || c.IsControl() || (isAutoLink && c == '<'); // TODO: specs unclear. space is strict or relaxed? (includes tabs?) } - public static bool IsValidDomain(string link, int prefixLength) + public static bool IsValidDomain(string link, int prefixLength, bool allowDomainWithoutPeriod) { // https://github.github.com/gfm/#extended-www-autolink // A valid domain consists of alphanumeric characters, underscores (_), hyphens (-) and periods (.). @@ -1052,7 +1052,7 @@ public static bool IsValidDomain(string link, int prefixLength) segmentHasCharacters = true; } - return segmentCount != 1 && // At least one dot was present + return (segmentCount != 1 || allowDomainWithoutPeriod) && // At least one dot was present segmentHasCharacters && // Last segment has valid characters segmentCount - lastUnderscoreSegment >= 2; // No underscores are present in the last two segments of the domain } From 8b403918b9539dd7bc47a3d9321627d62753e068 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Mon, 17 Mar 2025 07:47:40 +0800 Subject: [PATCH 34/88] Update XML doc --- src/Markdig/Extensions/AutoLinks/AutoLinkOptions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Markdig/Extensions/AutoLinks/AutoLinkOptions.cs b/src/Markdig/Extensions/AutoLinks/AutoLinkOptions.cs index 189a6411..1dd03670 100644 --- a/src/Markdig/Extensions/AutoLinks/AutoLinkOptions.cs +++ b/src/Markdig/Extensions/AutoLinks/AutoLinkOptions.cs @@ -24,7 +24,7 @@ public AutoLinkOptions() public bool UseHttpsForWWWLinks { get; set; } /// - /// Should a link host name with no period, e.g. https://localhost, be allowed (false by default) + /// Should auto-linking allow a domain with no period, e.g. https://localhost (false by default) /// public bool AllowDomainWithoutPeriod { get; set; } } From ee403ce28f1a18a1216e43fdfb3344c45b3f813f Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Mon, 17 Mar 2025 08:26:51 +0800 Subject: [PATCH 35/88] Port tests --- src/Markdig.Tests/TestAutoLinks.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Markdig.Tests/TestAutoLinks.cs b/src/Markdig.Tests/TestAutoLinks.cs index dd5e455b..005c3d13 100644 --- a/src/Markdig.Tests/TestAutoLinks.cs +++ b/src/Markdig.Tests/TestAutoLinks.cs @@ -10,6 +10,8 @@ public class TestAutoLinks [TestCase("http://localhost", "

http://localhost

")] [TestCase("https://l", "

https://l

")] [TestCase("www.l", "

www.l

")] + [TestCase("https://localhost:5000", "

https://localhost:5000

")] + [TestCase("www.l:5000", "

www.l:5000

")] public void TestLinksWithAllowDomainWithoutPeriod(string markdown, string expected) { var pipeline = new MarkdownPipelineBuilder() From 90c73b775453734fbfb28cde78c19b5c9f6139e9 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Tue, 18 Mar 2025 14:54:20 +0800 Subject: [PATCH 36/88] Update src/Markdig/Helpers/LinkHelper.cs Co-authored-by: Miha Zupan --- src/Markdig/Helpers/LinkHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Markdig/Helpers/LinkHelper.cs b/src/Markdig/Helpers/LinkHelper.cs index a994b3f2..82b3ff84 100644 --- a/src/Markdig/Helpers/LinkHelper.cs +++ b/src/Markdig/Helpers/LinkHelper.cs @@ -1006,7 +1006,7 @@ private static bool IsEndOfUri(char c, bool isAutoLink) return c == '\0' || c.IsSpaceOrTab() || c.IsControl() || (isAutoLink && c == '<'); // TODO: specs unclear. space is strict or relaxed? (includes tabs?) } - public static bool IsValidDomain(string link, int prefixLength, bool allowDomainWithoutPeriod) + public static bool IsValidDomain(string link, int prefixLength, bool allowDomainWithoutPeriod = false) { // https://github.github.com/gfm/#extended-www-autolink // A valid domain consists of alphanumeric characters, underscores (_), hyphens (-) and periods (.). From 086440bcd3d14b838c2658f13e2a3bd77691d2f5 Mon Sep 17 00:00:00 2001 From: Meir Blachman Date: Thu, 20 Mar 2025 16:59:50 +0200 Subject: [PATCH 37/88] update repository's github path --- src/Markdig/Markdig.targets | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Markdig/Markdig.targets b/src/Markdig/Markdig.targets index a6abba36..ec928761 100644 --- a/src/Markdig/Markdig.targets +++ b/src/Markdig/Markdig.targets @@ -8,11 +8,11 @@ net462;netstandard2.0;netstandard2.1;net8.0;net9.0 false Markdown CommonMark md html md2html - https://github.com/lunet-io/markdig/blob/master/changelog.md + https://github.com/xoofx/markdig/blob/master/changelog.md BSD-2-Clause readme.md markdig.png - https://github.com/lunet-io/markdig + https://github.com/xoofx/markdig true 12 enable From 55f770cc0724aff99b1de593ad4fba080645b500 Mon Sep 17 00:00:00 2001 From: Manuel Amstutz Date: Wed, 9 Apr 2025 20:55:54 +0200 Subject: [PATCH 38/88] feat: infer pipe table column widths from separator row Adds support for calculating column widths in pipe tables based on the number of dashes in the header separator row. Enabled via the InferColumnWidthsFromSeparator option in PipeTableOptions. --- src/Markdig.Tests/TestPipeTable.cs | 39 +++++++++++++++- .../Extensions/Tables/GridTableParser.cs | 2 +- .../Extensions/Tables/PipeTableOptions.cs | 7 +++ .../Extensions/Tables/PipeTableParser.cs | 45 ++++++++++++++----- src/Markdig/Extensions/Tables/TableHelper.cs | 14 +++--- 5 files changed, 88 insertions(+), 19 deletions(-) diff --git a/src/Markdig.Tests/TestPipeTable.cs b/src/Markdig.Tests/TestPipeTable.cs index cffd479c..b891c722 100644 --- a/src/Markdig.Tests/TestPipeTable.cs +++ b/src/Markdig.Tests/TestPipeTable.cs @@ -12,10 +12,47 @@ public sealed class TestPipeTable [TestCase("| S | \r\n|---|\r\n| G |\r\n\r\n| D | D |\r\n| ---| ---| \r\n| V | V |", 2)] public void TestTableBug(string markdown, int tableCount = 1) { - MarkdownDocument document = Markdown.Parse(markdown, new MarkdownPipelineBuilder().UseAdvancedExtensions().Build()); + MarkdownDocument document = + Markdown.Parse(markdown, new MarkdownPipelineBuilder().UseAdvancedExtensions().Build()); Table[] tables = document.Descendants().OfType().ToArray(); Assert.AreEqual(tableCount, tables.Length); } + + [TestCase("A | B\r\n---|---", new[] {50.0f, 50.0f})] + [TestCase("A | B\r\n-|---", new[] {25.0f, 75.0f})] + [TestCase("A | B\r\n-|---\r\nA | B\r\n---|---", new[] {25.0f, 75.0f})] + [TestCase("A | B\r\n---|---|---", new[] {33.33f, 33.33f, 33.33f})] + [TestCase("A | B\r\n---|---|---|", new[] {33.33f, 33.33f, 33.33f})] + public void TestColumnWidthByHeaderLines(string markdown, float[] expectedWidth) + { + var pipeline = new MarkdownPipelineBuilder() + .UsePipeTables(new PipeTableOptions() {InferColumnWidthsFromSeparator = true}) + .Build(); + var document = Markdown.Parse(markdown, pipeline); + var table = document.Descendants().OfType
().FirstOrDefault(); + Assert.IsNotNull(table); + var actualWidths = table.ColumnDefinitions.Select(x => x.Width).ToList(); + Assert.AreEqual(actualWidths.Count, expectedWidth.Length); + for (int i = 0; i < expectedWidth.Length; i++) + { + Assert.AreEqual(actualWidths[i], expectedWidth[i], 0.01); + } + } + + [Test] + public void TestColumnWidthIsNotSetWithoutConfigurationFlag() + { + var pipeline = new MarkdownPipelineBuilder() + .UsePipeTables(new PipeTableOptions() {InferColumnWidthsFromSeparator = false}) + .Build(); + var document = Markdown.Parse("| A | B | C |\r\n|---|---|---|", pipeline); + var table = document.Descendants().OfType
().FirstOrDefault(); + Assert.IsNotNull(table); + foreach (var column in table.ColumnDefinitions) + { + Assert.AreEqual(0, column.Width); + } + } } diff --git a/src/Markdig/Extensions/Tables/GridTableParser.cs b/src/Markdig/Extensions/Tables/GridTableParser.cs index 6e6d562a..847bd94c 100644 --- a/src/Markdig/Extensions/Tables/GridTableParser.cs +++ b/src/Markdig/Extensions/Tables/GridTableParser.cs @@ -43,7 +43,7 @@ public override BlockState TryOpen(BlockProcessor processor) } // Parse a column alignment - if (!TableHelper.ParseColumnHeader(ref line, '-', out TableColumnAlign? columnAlign)) + if (!TableHelper.ParseColumnHeader(ref line, '-', out TableColumnAlign? columnAlign, out _)) { return BlockState.None; } diff --git a/src/Markdig/Extensions/Tables/PipeTableOptions.cs b/src/Markdig/Extensions/Tables/PipeTableOptions.cs index 05181ec2..bbeafac9 100644 --- a/src/Markdig/Extensions/Tables/PipeTableOptions.cs +++ b/src/Markdig/Extensions/Tables/PipeTableOptions.cs @@ -33,4 +33,11 @@ public PipeTableOptions() /// in all other rows (default behavior). /// public bool UseHeaderForColumnCount { get; set; } + + + /// + /// Gets or sets a value indicating whether column widths should be inferred based on the number of dashes + /// in the header separator row. Each column's width will be proportional to the dash count in its respective column. + /// + public bool InferColumnWidthsFromSeparator { get; set; } } \ No newline at end of file diff --git a/src/Markdig/Extensions/Tables/PipeTableParser.cs b/src/Markdig/Extensions/Tables/PipeTableParser.cs index 71c14538..f4d17c41 100644 --- a/src/Markdig/Extensions/Tables/PipeTableParser.cs +++ b/src/Markdig/Extensions/Tables/PipeTableParser.cs @@ -481,9 +481,10 @@ public bool PostProcess(InlineProcessor state, Inline? root, Inline? lastChild, return false; } - private static bool ParseHeaderString(Inline? inline, out TableColumnAlign? align) + private static bool ParseHeaderString(Inline? inline, out TableColumnAlign? align, out int delimiterCount) { align = 0; + delimiterCount = 0; var literal = inline as LiteralInline; if (literal is null) { @@ -492,7 +493,7 @@ private static bool ParseHeaderString(Inline? inline, out TableColumnAlign? alig // Work on a copy of the slice var line = literal.Content; - if (TableHelper.ParseColumnHeader(ref line, '-', out align)) + if (TableHelper.ParseColumnHeader(ref line, '-', out align, out delimiterCount)) { if (line.CurrentChar != '\0') { @@ -507,7 +508,8 @@ private static bool ParseHeaderString(Inline? inline, out TableColumnAlign? alig private List? FindHeaderRow(List delimiters) { bool isValidRow = false; - List? aligns = null; + int totalDelimiterCount = 0; + List? columnDefinitions = null; for (int i = 0; i < delimiters.Count; i++) { if (!IsLine(delimiters[i])) @@ -529,18 +531,19 @@ private static bool ParseHeaderString(Inline? inline, out TableColumnAlign? alig // Check the left side of a `|` delimiter TableColumnAlign? align = null; + int delimiterCount = 0; if (delimiter.PreviousSibling != null && !(delimiter.PreviousSibling is LiteralInline li && li.Content.IsEmptyOrWhitespace()) && // ignore parsed whitespace - !ParseHeaderString(delimiter.PreviousSibling, out align)) + !ParseHeaderString(delimiter.PreviousSibling, out align, out delimiterCount)) { break; } // Create aligns until we may have a header row - aligns ??= new List(); - - aligns.Add(new TableColumnDefinition() { Alignment = align }); + columnDefinitions ??= new List(); + totalDelimiterCount += delimiterCount; + columnDefinitions.Add(new TableColumnDefinition() { Alignment = align, Width = delimiterCount}); // If this is the last delimiter, we need to check the right side of the `|` delimiter if (nextDelimiter is null) @@ -556,13 +559,13 @@ private static bool ParseHeaderString(Inline? inline, out TableColumnAlign? alig break; } - if (!ParseHeaderString(nextSibling, out align)) + if (!ParseHeaderString(nextSibling, out align, out delimiterCount)) { break; } - + totalDelimiterCount += delimiterCount; isValidRow = true; - aligns.Add(new TableColumnDefinition() { Alignment = align }); + columnDefinitions.Add(new TableColumnDefinition() { Alignment = align, Width = delimiterCount}); break; } @@ -576,7 +579,27 @@ private static bool ParseHeaderString(Inline? inline, out TableColumnAlign? alig break; } - return isValidRow ? aligns : null; + // calculate the width of the columns in percent based on the delimiter count + if (!isValidRow || columnDefinitions == null) + { + return null; + } + + if (Options.InferColumnWidthsFromSeparator) + { + foreach (var columnDefinition in columnDefinitions) + { + columnDefinition.Width = (columnDefinition.Width * 100) / totalDelimiterCount; + } + } + else + { + foreach (var columnDefinition in columnDefinitions) + { + columnDefinition.Width = 0; + } + } + return columnDefinitions; } private static bool IsLine(Inline inline) diff --git a/src/Markdig/Extensions/Tables/TableHelper.cs b/src/Markdig/Extensions/Tables/TableHelper.cs index bbbda511..f75090a5 100644 --- a/src/Markdig/Extensions/Tables/TableHelper.cs +++ b/src/Markdig/Extensions/Tables/TableHelper.cs @@ -17,12 +17,13 @@ public static class TableHelper /// The text slice. /// The delimiter character (either `-` or `=`). /// The alignment of the column. + /// The number of delimiters. /// /// true if parsing was successful /// - public static bool ParseColumnHeader(ref StringSlice slice, char delimiterChar, out TableColumnAlign? align) + public static bool ParseColumnHeader(ref StringSlice slice, char delimiterChar, out TableColumnAlign? align, out int delimiterCount) { - return ParseColumnHeaderDetect(ref slice, ref delimiterChar, out align); + return ParseColumnHeaderDetect(ref slice, ref delimiterChar, out align, out delimiterCount); } /// @@ -37,7 +38,7 @@ public static bool ParseColumnHeader(ref StringSlice slice, char delimiterChar, public static bool ParseColumnHeaderAuto(ref StringSlice slice, out char delimiterChar, out TableColumnAlign? align) { delimiterChar = '\0'; - return ParseColumnHeaderDetect(ref slice, ref delimiterChar, out align); + return ParseColumnHeaderDetect(ref slice, ref delimiterChar, out align, out _); } /// @@ -49,10 +50,10 @@ public static bool ParseColumnHeaderAuto(ref StringSlice slice, out char delimit /// /// true if parsing was successful /// - public static bool ParseColumnHeaderDetect(ref StringSlice slice, ref char delimiterChar, out TableColumnAlign? align) + public static bool ParseColumnHeaderDetect(ref StringSlice slice, ref char delimiterChar, out TableColumnAlign? align, out int delimiterCount) { align = null; - + delimiterCount = 0; slice.TrimStart(); var c = slice.CurrentChar; bool hasLeft = false; @@ -80,7 +81,8 @@ public static bool ParseColumnHeaderDetect(ref StringSlice slice, ref char delim } // We expect at least one `-` delimiter char - if (slice.CountAndSkipChar(delimiterChar) == 0) + delimiterCount = slice.CountAndSkipChar(delimiterChar); + if (delimiterCount == 0) { return false; } From dab1ca54837338f6d168f130c534d24c5b902500 Mon Sep 17 00:00:00 2001 From: Miha Zupan Date: Sun, 16 Mar 2025 15:03:06 +0100 Subject: [PATCH 39/88] Avoid unnecessary null check when reading trivia info --- src/Markdig/Polyfills/Unsafe.cs | 10 +++++++--- src/Markdig/Syntax/MarkdownObject.cs | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/Markdig/Polyfills/Unsafe.cs b/src/Markdig/Polyfills/Unsafe.cs index 1ee72edc..f4c13c50 100644 --- a/src/Markdig/Polyfills/Unsafe.cs +++ b/src/Markdig/Polyfills/Unsafe.cs @@ -2,15 +2,19 @@ // This file is licensed under the BSD-Clause 2 license. // See the license.txt file in the project root for more information. +#if NETSTANDARD2_1 + +using System.Diagnostics.CodeAnalysis; + namespace System.Runtime.CompilerServices; -#if NETSTANDARD2_1 internal static class Unsafe { [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T As(object o) where T : class + [return: NotNullIfNotNull(nameof(o))] + public static T? As(object? o) where T : class { - return (T)o; + return (T?)o; } } #endif \ No newline at end of file diff --git a/src/Markdig/Syntax/MarkdownObject.cs b/src/Markdig/Syntax/MarkdownObject.cs index 5b8a6bd5..5235fc9c 100644 --- a/src/Markdig/Syntax/MarkdownObject.cs +++ b/src/Markdig/Syntax/MarkdownObject.cs @@ -143,7 +143,7 @@ public string ToPositionText() private protected T? GetTrivia() where T : class { object? trivia = _attachedDatas?.Trivia; - return trivia is null ? null : Unsafe.As(trivia); + return Unsafe.As(trivia); } private protected T GetOrSetTrivia() where T : class, new() From adfcf42529649e1e4bf5e95e152411577e9f7eec Mon Sep 17 00:00:00 2001 From: Miha Zupan Date: Sun, 16 Mar 2025 15:20:30 +0100 Subject: [PATCH 40/88] Use FrozenDictionary in a couple places --- .../MediaLinks/HostProviderBuilder.cs | 19 +++++++-------- .../Extensions/MediaLinks/MediaOptions.cs | 2 +- src/Markdig/Globals.cs | 1 + src/Markdig/Helpers/CharHelper.cs | 24 ++++++++++++------- src/Markdig/Helpers/CharNormalizer.cs | 4 ++-- src/Markdig/Helpers/CharacterMap.cs | 15 ++++++++---- src/Markdig/Markdig.targets | 6 ++--- src/UnicodeNormDApp/Program.cs | 4 ++-- 8 files changed, 44 insertions(+), 31 deletions(-) diff --git a/src/Markdig/Extensions/MediaLinks/HostProviderBuilder.cs b/src/Markdig/Extensions/MediaLinks/HostProviderBuilder.cs index 38377ac8..9b2e455c 100644 --- a/src/Markdig/Extensions/MediaLinks/HostProviderBuilder.cs +++ b/src/Markdig/Extensions/MediaLinks/HostProviderBuilder.cs @@ -55,16 +55,15 @@ public static IHostProvider Create(string hostPrefix, Func handler return new DelegateProvider(hostPrefix, handler, allowFullScreen, iframeClass); } - internal static Dictionary KnownHosts { get; } - = new Dictionary(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"), - ["Yandex"] = Create("music.yandex.ru", Yandex, allowFullScreen: false, iframeClass: "yandex"), - ["Odnoklassniki"] = Create("ok.ru", Odnoklassniki, iframeClass: "odnoklassniki"), - }; + internal static readonly IHostProvider[] KnownHosts = + [ + Create("www.youtube.com", YouTubeShort, iframeClass: "youtubeshort"), + Create("www.youtube.com", YouTube, iframeClass: "youtube"), + Create("youtu.be", YouTubeShortened, iframeClass: "youtube"), + Create("vimeo.com", Vimeo, iframeClass: "vimeo"), + Create("music.yandex.ru", Yandex, allowFullScreen: false, iframeClass: "yandex"), + Create("ok.ru", Odnoklassniki, iframeClass: "odnoklassniki"), + ]; #region Known providers diff --git a/src/Markdig/Extensions/MediaLinks/MediaOptions.cs b/src/Markdig/Extensions/MediaLinks/MediaOptions.cs index de682e69..2a22fd21 100644 --- a/src/Markdig/Extensions/MediaLinks/MediaOptions.cs +++ b/src/Markdig/Extensions/MediaLinks/MediaOptions.cs @@ -81,7 +81,7 @@ public MediaOptions() {".au", "audio/basic"}, {".wav", "audio/x-wav"}, }; - Hosts = new List(HostProviderBuilder.KnownHosts.Values); + Hosts = new List(HostProviderBuilder.KnownHosts); } public string Width { get; set; } diff --git a/src/Markdig/Globals.cs b/src/Markdig/Globals.cs index 1a8b91ba..ed1c0bd4 100644 --- a/src/Markdig/Globals.cs +++ b/src/Markdig/Globals.cs @@ -1,2 +1,3 @@ global using System; +global using System.Collections.Frozen; global using System.Collections.Generic; \ No newline at end of file diff --git a/src/Markdig/Helpers/CharHelper.cs b/src/Markdig/Helpers/CharHelper.cs index 7e80bedb..3a035ff7 100644 --- a/src/Markdig/Helpers/CharHelper.cs +++ b/src/Markdig/Helpers/CharHelper.cs @@ -24,12 +24,6 @@ public static class CharHelper private const char LowSurrogateStart = '\udc00'; private const char LowSurrogateEnd = '\udfff'; - // We don't support LCDM - private static readonly Dictionary romanMap = new Dictionary(6) { - { 'i', 1 }, { 'v', 5 }, { 'x', 10 }, - { 'I', 1 }, { 'V', 5 }, { 'X', 10 } - }; - [MethodImpl(MethodImplOptions.AggressiveInlining)] private static bool IsPunctuationException(char c) => c is '−' or '-' or '†' or '‡'; @@ -101,8 +95,8 @@ public static int RomanToArabic(ReadOnlySpan text) int result = 0; for (int i = 0; i < text.Length; i++) { - var candidate = romanMap[text[i]]; - if ((uint)(i + 1) < text.Length && candidate < romanMap[text[i + 1]]) + int candidate = RomanToArabic(text[i]); + if ((uint)(i + 1) < text.Length && candidate < RomanToArabic(text[i + 1])) { result -= candidate; } @@ -112,6 +106,20 @@ public static int RomanToArabic(ReadOnlySpan text) } } return result; + + // We don't support LCDM + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static int RomanToArabic(char c) + { + Debug.Assert(IsRomanLetterPartial(c)); + + return (c | 0x20) switch + { + 'i' => 1, + 'v' => 5, + _ => 10 + }; + } } [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/Markdig/Helpers/CharNormalizer.cs b/src/Markdig/Helpers/CharNormalizer.cs index fc6ecffa..fddae10c 100644 --- a/src/Markdig/Helpers/CharNormalizer.cs +++ b/src/Markdig/Helpers/CharNormalizer.cs @@ -20,7 +20,7 @@ public static class CharNormalizer } // This table was generated by the app UnicodeNormDApp - private static readonly Dictionary CodeToAscii = new(1269) + private static readonly FrozenDictionary CodeToAscii = new Dictionary(1269) { {'Ḋ', "D"}, {'Ḍ', "D"}, @@ -1291,5 +1291,5 @@ public static class CharNormalizer {'|', "|"}, {'}', "}"}, {'~', "~"}, - }; + }.ToFrozenDictionary(); } \ No newline at end of file diff --git a/src/Markdig/Helpers/CharacterMap.cs b/src/Markdig/Helpers/CharacterMap.cs index 7980c973..73ed3322 100644 --- a/src/Markdig/Helpers/CharacterMap.cs +++ b/src/Markdig/Helpers/CharacterMap.cs @@ -4,7 +4,6 @@ using System.Buffers; using System.Diagnostics; -using System.Linq; using System.Runtime.CompilerServices; namespace Markdig.Helpers; @@ -17,7 +16,7 @@ public sealed class CharacterMap where T : class { private readonly SearchValues _values; private readonly T[] _asciiMap; - private readonly Dictionary? _nonAsciiMap; + private readonly FrozenDictionary? _nonAsciiMap; /// /// Initializes a new instance of the class. @@ -39,6 +38,7 @@ public CharacterMap(IEnumerable> maps) Array.Sort(OpeningCharacters); _asciiMap = new T[128]; + Dictionary? nonAsciiMap = null; foreach (var state in maps) { @@ -49,16 +49,21 @@ public CharacterMap(IEnumerable> maps) } else { - _nonAsciiMap ??= new Dictionary(); + nonAsciiMap ??= []; - if (!_nonAsciiMap.ContainsKey(openingChar)) + if (!nonAsciiMap.ContainsKey(openingChar)) { - _nonAsciiMap[openingChar] = state.Value; + nonAsciiMap[openingChar] = state.Value; } } } _values = SearchValues.Create(OpeningCharacters); + + if (nonAsciiMap is not null) + { + _nonAsciiMap = nonAsciiMap.ToFrozenDictionary(); + } } /// diff --git a/src/Markdig/Markdig.targets b/src/Markdig/Markdig.targets index ec928761..9f4d17ca 100644 --- a/src/Markdig/Markdig.targets +++ b/src/Markdig/Markdig.targets @@ -14,7 +14,7 @@ markdig.png https://github.com/xoofx/markdig true - 12 + 13 enable $(NoWarn);CS1591 true @@ -24,8 +24,8 @@ snupkg - - + + diff --git a/src/UnicodeNormDApp/Program.cs b/src/UnicodeNormDApp/Program.cs index 9c96b419..f839e031 100644 --- a/src/UnicodeNormDApp/Program.cs +++ b/src/UnicodeNormDApp/Program.cs @@ -78,7 +78,7 @@ static async Task Main(string[] args) //var newValues = new Dictionary(values.Count) //{ // {15, 'a'} - //} + //}.ToFrozenDictionary(); Trace.WriteLine($"CodeToAscii = new Dictionary({values.Count})"); Trace.WriteLine("{"); foreach (var pair in values) @@ -86,7 +86,7 @@ static async Task Main(string[] args) var escape = pair.Value.Replace("\\", @"\\").Replace("\"", "\\\""); Trace.WriteLine($" {{'{pair.Key}',\"{escape}\"}},"); } - Trace.WriteLine("};"); + Trace.WriteLine("}.ToFrozenDictionary();"); //Trace.WriteLine("count: " + count); //Trace.WriteLine("max: " + max); From d8f69218db17ee86399f21915ab72d87138c4dc8 Mon Sep 17 00:00:00 2001 From: Miha Zupan Date: Sun, 16 Mar 2025 16:02:34 +0100 Subject: [PATCH 41/88] Commit FrozenDictionary polyfill --- src/Markdig/Polyfills/FrozenCollections.cs | 25 ++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 src/Markdig/Polyfills/FrozenCollections.cs diff --git a/src/Markdig/Polyfills/FrozenCollections.cs b/src/Markdig/Polyfills/FrozenCollections.cs new file mode 100644 index 00000000..ab07f756 --- /dev/null +++ b/src/Markdig/Polyfills/FrozenCollections.cs @@ -0,0 +1,25 @@ +// Copyright (c) Alexandre Mutel. All rights reserved. +// This file is licensed under the BSD-Clause 2 license. +// See the license.txt file in the project root for more information. + +#if !NET8_0_OR_GREATER + +namespace System.Collections.Frozen; + +// We're using a polyfill instead of conditionally referencing the package as the package is untested on older TFMs, and +// brings in a reference to System.Runtime.CompilerServices.Unsafe, which conflicts with our polyfills of that type. + +internal sealed class FrozenDictionary : Dictionary +{ + public FrozenDictionary(Dictionary dictionary) : base(dictionary) { } +} + +internal static class FrozenDictionaryExtensions +{ + public static FrozenDictionary ToFrozenDictionary(this Dictionary dictionary) + { + return new FrozenDictionary(dictionary); + } +} + +#endif \ No newline at end of file From ecfda373b9f9185f74bcf6e901a9d6901a841761 Mon Sep 17 00:00:00 2001 From: Miha Zupan Date: Sun, 16 Mar 2025 16:01:20 +0100 Subject: [PATCH 42/88] Avoid warnings in Markdig.WebApp --- src/Markdig.WebApp/ApiController.cs | 2 +- src/Markdig.WebApp/Startup.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Markdig.WebApp/ApiController.cs b/src/Markdig.WebApp/ApiController.cs index 2018c936..a11740b3 100644 --- a/src/Markdig.WebApp/ApiController.cs +++ b/src/Markdig.WebApp/ApiController.cs @@ -8,7 +8,7 @@ public class ApiController : Controller { [HttpGet()] [Route("")] - public string Empty() + public new string Empty() { return string.Empty; } diff --git a/src/Markdig.WebApp/Startup.cs b/src/Markdig.WebApp/Startup.cs index ce84a977..28073d1a 100644 --- a/src/Markdig.WebApp/Startup.cs +++ b/src/Markdig.WebApp/Startup.cs @@ -12,7 +12,7 @@ public Startup(IWebHostEnvironment env) if (env.IsEnvironment("Development")) { // This will push telemetry data through Application Insights pipeline faster, allowing you to view results immediately. - builder.AddApplicationInsightsSettings(developerMode: true); + builder.AddApplicationInsightsSettings(connectionString: null, developerMode: true); } builder.AddEnvironmentVariables(); From a82c3bd705e03d67c168a5a773247130f3954115 Mon Sep 17 00:00:00 2001 From: Miha Zupan Date: Sun, 13 Apr 2025 16:59:29 +0200 Subject: [PATCH 43/88] Improve some character tests --- src/Markdig.Tests/TestCharHelper.cs | 144 ++++++++++++++++-- .../Abbreviations/AbbreviationParser.cs | 11 +- .../GenericAttributesParser.cs | 2 +- .../Globalization/GlobalizationExtension.cs | 2 +- .../Extensions/Yaml/YamlFrontMatterParser.cs | 4 +- src/Markdig/Helpers/CharHelper.cs | 12 ++ src/Markdig/Helpers/LinkHelper.cs | 18 +-- src/Markdig/Helpers/StringSlice.cs | 2 +- src/Markdig/Parsers/FencedBlockParserBase.cs | 2 +- src/Markdig/Parsers/HtmlBlockParser.cs | 3 +- src/Markdig/Polyfills/SearchValues.cs | 6 + src/Markdig/Renderers/HtmlRenderer.cs | 2 +- src/Markdig/Syntax/CharIteratorHelper.cs | 2 +- 13 files changed, 173 insertions(+), 37 deletions(-) diff --git a/src/Markdig.Tests/TestCharHelper.cs b/src/Markdig.Tests/TestCharHelper.cs index ce6099bd..823b6071 100644 --- a/src/Markdig.Tests/TestCharHelper.cs +++ b/src/Markdig.Tests/TestCharHelper.cs @@ -43,19 +43,73 @@ private static bool ExpectedIsWhitespace(char c) { // A Unicode whitespace character is any code point in the Unicode Zs general category, // or a tab (U+0009), line feed (U+000A), form feed (U+000C), or carriage return (U+000D). - return c == '\t' || c == '\n' || c == '\u000C' || c == '\r' || + return c == '\t' || c == '\n' || c == '\f' || c == '\r' || CharUnicodeInfo.GetUnicodeCategory(c) == UnicodeCategory.SpaceSeparator; } + [Test] + public void IsAcrossTab() + { + Assert.False(CharHelper.IsAcrossTab(0)); + Assert.True(CharHelper.IsAcrossTab(1)); + Assert.True(CharHelper.IsAcrossTab(2)); + Assert.True(CharHelper.IsAcrossTab(3)); + Assert.False(CharHelper.IsAcrossTab(4)); + } + + [Test] + public void AddTab() + { + Assert.AreEqual(4, CharHelper.AddTab(0)); + Assert.AreEqual(4, CharHelper.AddTab(1)); + Assert.AreEqual(4, CharHelper.AddTab(2)); + Assert.AreEqual(4, CharHelper.AddTab(3)); + Assert.AreEqual(8, CharHelper.AddTab(4)); + Assert.AreEqual(8, CharHelper.AddTab(5)); + } + [Test] public void IsWhitespace() { - for (int i = char.MinValue; i <= char.MaxValue; i++) - { - char c = (char)i; + Test( + ExpectedIsWhitespace, + CharHelper.IsWhitespace); - Assert.AreEqual(ExpectedIsWhitespace(c), CharHelper.IsWhitespace(c)); - } + Test( + ExpectedIsWhitespace, + CharHelper.WhitespaceChars.Contains); + } + + [Test] + public void IsWhiteSpaceOrZero() + { + Test( + c => ExpectedIsWhitespace(c) || c == 0, + CharHelper.IsWhiteSpaceOrZero); + } + + [Test] + public void IsAsciiPunctuation() + { + Test( + c => char.IsAscii(c) && ExpectedIsPunctuation(c), + CharHelper.IsAsciiPunctuation); + } + + [Test] + public void IsAsciiPunctuationOrZero() + { + Test( + c => char.IsAscii(c) && (ExpectedIsPunctuation(c) || c == 0), + CharHelper.IsAsciiPunctuationOrZero); + } + + [Test] + public void IsSpaceOrPunctuation() + { + Test( + c => c == 0 || ExpectedIsWhitespace(c) || ExpectedIsPunctuation(c), + CharHelper.IsSpaceOrPunctuation); } [Test] @@ -76,15 +130,83 @@ public void CheckUnicodeCategory() } [Test] - public void IsSpaceOrPunctuation() + public void IsControl() + { + Test( + char.IsControl, + CharHelper.IsControl); + } + + [Test] + public void IsAlpha() + { + Test( + c => (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'), + CharHelper.IsAlpha); + } + + [Test] + public void IsAlphaUpper() + { + Test( + c => c >= 'A' && c <= 'Z', + CharHelper.IsAlphaUpper); + } + + [Test] + public void IsAlphaNumeric() + { + Test( + c => (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9'), + CharHelper.IsAlphaNumeric); + } + + [Test] + public void IsDigit() + { + Test( + c => c >= '0' && c <= '9', + CharHelper.IsDigit); + } + + [Test] + public void IsNewLineOrLineFeed() + { + Test( + c => c is '\r' or '\n', + CharHelper.IsNewLineOrLineFeed); + } + + [Test] + public void IsSpaceOrTab() + { + Test( + c => c is ' ' or '\t', + CharHelper.IsSpaceOrTab); + } + + [Test] + public void IsEscapableSymbol() + { + Test( + "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~•".Contains, + CharHelper.IsEscapableSymbol); + } + + [Test] + public void IsEmailUsernameSpecialChar() + { + Test( + ".!#$%&'*+/=?^_`{|}~-+.~".Contains, + CharHelper.IsEmailUsernameSpecialChar); + } + + private static void Test(Func expected, Func actual) { for (int i = char.MinValue; i <= char.MaxValue; i++) { char c = (char)i; - - bool expected = c == 0 || ExpectedIsWhitespace(c) || ExpectedIsPunctuation(c); - - Assert.AreEqual(expected, CharHelper.IsSpaceOrPunctuation(c)); + Assert.AreEqual(expected(c), actual(c)); } } } diff --git a/src/Markdig/Extensions/Abbreviations/AbbreviationParser.cs b/src/Markdig/Extensions/Abbreviations/AbbreviationParser.cs index 9cfe5b10..5af5a036 100644 --- a/src/Markdig/Extensions/Abbreviations/AbbreviationParser.cs +++ b/src/Markdig/Extensions/Abbreviations/AbbreviationParser.cs @@ -203,20 +203,17 @@ private static bool IsValidAbbreviationEnding(string match, StringSlice content, while (index <= contentNew.End) { var c = contentNew.PeekCharAbsolute(index); - if (!(c == '\0' || c.IsWhitespace() || c.IsAsciiPunctuation())) + + if (c.IsWhitespace()) { - return false; + break; } - if (c.IsAlphaNumeric()) + if (!c.IsAsciiPunctuationOrZero()) { return false; } - if (c.IsWhitespace()) - { - break; - } index++; } return true; diff --git a/src/Markdig/Extensions/GenericAttributes/GenericAttributesParser.cs b/src/Markdig/Extensions/GenericAttributes/GenericAttributesParser.cs index ae34e21d..781a4bfb 100644 --- a/src/Markdig/Extensions/GenericAttributes/GenericAttributesParser.cs +++ b/src/Markdig/Extensions/GenericAttributes/GenericAttributesParser.cs @@ -124,7 +124,7 @@ public static bool TryParse(ref StringSlice slice, [NotNullWhen(true)] out HtmlA var start = line.Start; // Get all non-whitespace characters following a # // But stop if we found a } or \0 - while (c != '}' && c != '\0' && !c.IsWhitespace()) + while (c != '}' && !c.IsWhiteSpaceOrZero()) { c = line.NextChar(); } diff --git a/src/Markdig/Extensions/Globalization/GlobalizationExtension.cs b/src/Markdig/Extensions/Globalization/GlobalizationExtension.cs index 7f7817b5..7c66786c 100644 --- a/src/Markdig/Extensions/Globalization/GlobalizationExtension.cs +++ b/src/Markdig/Extensions/Globalization/GlobalizationExtension.cs @@ -105,7 +105,7 @@ private static bool StartsWithRtlCharacter(StringSlice slice) } int rune = c; - if (CharHelper.IsHighSurrogate(c) && i < slice.End && CharHelper.IsLowSurrogate(slice[i + 1])) + if (char.IsHighSurrogate(c) && i < slice.End && char.IsLowSurrogate(slice[i + 1])) { Debug.Assert(char.IsSurrogatePair(c, slice[i + 1])); rune = char.ConvertToUtf32(c, slice[i + 1]); diff --git a/src/Markdig/Extensions/Yaml/YamlFrontMatterParser.cs b/src/Markdig/Extensions/Yaml/YamlFrontMatterParser.cs index f93635f8..dddb8e4d 100644 --- a/src/Markdig/Extensions/Yaml/YamlFrontMatterParser.cs +++ b/src/Markdig/Extensions/Yaml/YamlFrontMatterParser.cs @@ -71,7 +71,7 @@ public override BlockState TryOpen(BlockProcessor processor) // If three dashes (optionally followed by whitespace) // this is a YAML front matter block - if (count == 3 && (c == '\0' || c.IsWhitespace()) && line.TrimEnd()) + if (count == 3 && c.IsWhiteSpaceOrZero() && line.TrimEnd()) { bool hasFullYamlFrontMatter = false; // We make sure that there is a closing frontmatter somewhere in the document @@ -146,7 +146,7 @@ public override BlockState TryContinue(BlockProcessor processor, Block block) // If we have a closing fence, close it and discard the current line // The line must contain only fence characters and optional following whitespace. - if (count == 3 && !processor.IsCodeIndent && (c == '\0' || c.IsWhitespace()) && line.TrimEnd()) + if (count == 3 && !processor.IsCodeIndent && c.IsWhiteSpaceOrZero() && line.TrimEnd()) { block.UpdateSpanEnd(line.Start - 1); diff --git a/src/Markdig/Helpers/CharHelper.cs b/src/Markdig/Helpers/CharHelper.cs index 3a035ff7..032987b7 100644 --- a/src/Markdig/Helpers/CharHelper.cs +++ b/src/Markdig/Helpers/CharHelper.cs @@ -24,6 +24,13 @@ public static class CharHelper private const char LowSurrogateStart = '\udc00'; private const char LowSurrogateEnd = '\udfff'; + // 2.1 Characters and lines + // A Unicode whitespace character is any code point in the Unicode Zs general category, + // or a tab (U+0009), line feed (U+000A), form feed (U+000C), or carriage return (U+000D). + // CharUnicodeInfo.GetUnicodeCategory(c) == UnicodeCategory.SpaceSeparator; + private const string AsciiWhitespaceChars = "\t\n\f\r "; + internal const string WhitespaceChars = AsciiWhitespaceChars + "\u00A0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000"; + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static bool IsPunctuationException(char c) => c is '−' or '-' or '†' or '‡'; @@ -314,6 +321,11 @@ public static bool IsDigit(this char c) return (uint)(c - '0') <= ('9' - '0'); } + internal static bool IsAsciiPunctuationOrZero(this char c) + { + return c == '\0' || IsAsciiPunctuation(c); + } + public static bool IsAsciiPunctuation(this char c) { // 2.1 Characters and lines diff --git a/src/Markdig/Helpers/LinkHelper.cs b/src/Markdig/Helpers/LinkHelper.cs index 82b3ff84..3f57bbe6 100644 --- a/src/Markdig/Helpers/LinkHelper.cs +++ b/src/Markdig/Helpers/LinkHelper.cs @@ -1023,18 +1023,18 @@ public static bool IsValidDomain(string link, int prefixLength, bool allowDomain { char c = link[i]; - if (c == '.') // New segment + if (!c.IsAlphaNumeric()) { - if (!segmentHasCharacters) - return false; + if (c == '.') // New segment + { + if (!segmentHasCharacters) + return false; - segmentCount++; - segmentHasCharacters = false; - continue; - } + segmentCount++; + segmentHasCharacters = false; + continue; + } - if (!c.IsAlphaNumeric()) - { if (c == '/' || c == '?' || c == '#' || c == ':') // End of domain name break; diff --git a/src/Markdig/Helpers/StringSlice.cs b/src/Markdig/Helpers/StringSlice.cs index dd27e400..95ddad99 100644 --- a/src/Markdig/Helpers/StringSlice.cs +++ b/src/Markdig/Helpers/StringSlice.cs @@ -291,7 +291,7 @@ public bool SkipSpacesToEndOfLineOrEndOfDocument() var c = Text[i]; if (c.IsWhitespace()) { - if (c == '\0' || c == '\n' || (c == '\r' && i + 1 <= End && Text[i + 1] != '\n')) + if (c == '\n' || (c == '\r' && i + 1 <= End && Text[i + 1] != '\n')) { return true; } diff --git a/src/Markdig/Parsers/FencedBlockParserBase.cs b/src/Markdig/Parsers/FencedBlockParserBase.cs index 07232a0a..ed8b573b 100644 --- a/src/Markdig/Parsers/FencedBlockParserBase.cs +++ b/src/Markdig/Parsers/FencedBlockParserBase.cs @@ -322,7 +322,7 @@ public override BlockState TryContinue(BlockProcessor processor, Block block) if (fence.OpeningFencedCharCount <= closingCount && !processor.IsCodeIndent && - (c == '\0' || c.IsWhitespace()) && + c.IsWhiteSpaceOrZero() && line.TrimEnd()) { block.UpdateSpanEnd(startBeforeTrim - 1); diff --git a/src/Markdig/Parsers/HtmlBlockParser.cs b/src/Markdig/Parsers/HtmlBlockParser.cs index 0afe8648..a5b89096 100644 --- a/src/Markdig/Parsers/HtmlBlockParser.cs +++ b/src/Markdig/Parsers/HtmlBlockParser.cs @@ -140,8 +140,7 @@ private BlockState TryParseTagType16(BlockProcessor state, StringSlice line, int } if ( - !(c == '>' || (!hasLeadingClose && c == '/' && line.PeekChar() == '>') || c.IsWhitespace() || - c == '\0')) + !(c == '>' || (!hasLeadingClose && c == '/' && line.PeekChar() == '>') || c.IsWhiteSpaceOrZero())) { return BlockState.None; } diff --git a/src/Markdig/Polyfills/SearchValues.cs b/src/Markdig/Polyfills/SearchValues.cs index 5d19c025..b1cb6798 100644 --- a/src/Markdig/Polyfills/SearchValues.cs +++ b/src/Markdig/Polyfills/SearchValues.cs @@ -26,6 +26,8 @@ public static int IndexOfAnyExcept(this ReadOnlySpan span, SearchValues { + public abstract bool Contains(T value); + public abstract int IndexOfAny(ReadOnlySpan span); public abstract int IndexOfAnyExcept(ReadOnlySpan span); @@ -52,6 +54,10 @@ public PreNet8CompatSearchValues(ReadOnlySpan values) } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override bool Contains(char value) => + value < 128 ? _ascii[value] : (_nonAscii is { } nonAscii && nonAscii.Contains(value)); + public override int IndexOfAny(ReadOnlySpan span) { if (_nonAscii is null) diff --git a/src/Markdig/Renderers/HtmlRenderer.cs b/src/Markdig/Renderers/HtmlRenderer.cs index 085a4e97..90a6e733 100644 --- a/src/Markdig/Renderers/HtmlRenderer.cs +++ b/src/Markdig/Renderers/HtmlRenderer.cs @@ -286,7 +286,7 @@ static int WriteEscapedUtf8Bytes(HtmlRenderer renderer, ReadOnlySpan conte { scoped ReadOnlySpan chars; - if (CharHelper.IsHighSurrogate(c) && (uint)(i + 1) < (uint)content.Length) + if (char.IsHighSurrogate(c) && (uint)(i + 1) < (uint)content.Length) { chars = stackalloc char[] { c, content[i + 1] }; i++; diff --git a/src/Markdig/Syntax/CharIteratorHelper.cs b/src/Markdig/Syntax/CharIteratorHelper.cs index ea3f28ee..06935eca 100644 --- a/src/Markdig/Syntax/CharIteratorHelper.cs +++ b/src/Markdig/Syntax/CharIteratorHelper.cs @@ -22,7 +22,7 @@ public static bool TrimStartAndCountNewLines(ref T iterator, out int countNew var c = iterator.CurrentChar; bool hasWhitespaces = false; lastLine = NewLine.None; - while (c != '\0' && c.IsWhitespace()) + while (c.IsWhitespace()) { if (c == '\n' || c == '\r') { From 8cfa0cf0ae55fcff1aee61e402f727b2dd191420 Mon Sep 17 00:00:00 2001 From: Miha Zupan Date: Sun, 16 Mar 2025 16:02:07 +0100 Subject: [PATCH 44/88] Improve more character tests with SearchValues --- src/Markdig.Tests/TestCharHelper.cs | 8 +++ src/Markdig/Helpers/CharHelper.cs | 89 +++++++++++++---------------- src/Markdig/Helpers/LinkHelper.cs | 4 +- 3 files changed, 51 insertions(+), 50 deletions(-) diff --git a/src/Markdig.Tests/TestCharHelper.cs b/src/Markdig.Tests/TestCharHelper.cs index 823b6071..ac201518 100644 --- a/src/Markdig.Tests/TestCharHelper.cs +++ b/src/Markdig.Tests/TestCharHelper.cs @@ -201,6 +201,14 @@ public void IsEmailUsernameSpecialChar() CharHelper.IsEmailUsernameSpecialChar); } + [Test] + public void IsEmailUsernameSpecialCharOrDigit() + { + Test( + c => CharHelper.IsDigit(c) || ".!#$%&'*+/=?^_`{|}~-+.~".Contains(c), + CharHelper.IsEmailUsernameSpecialCharOrDigit); + } + private static void Test(Func expected, Func actual) { for (int i = char.MinValue; i <= char.MaxValue; i++) diff --git a/src/Markdig/Helpers/CharHelper.cs b/src/Markdig/Helpers/CharHelper.cs index 032987b7..fbf6e470 100644 --- a/src/Markdig/Helpers/CharHelper.cs +++ b/src/Markdig/Helpers/CharHelper.cs @@ -2,6 +2,7 @@ // This file is licensed under the BSD-Clause 2 license. // See the license.txt file in the project root for more information. +using System.Buffers; using System.Diagnostics; using System.Globalization; using System.Runtime.CompilerServices; @@ -19,10 +20,7 @@ public static class CharHelper public const string ReplacementCharString = "\uFFFD"; - private const char HighSurrogateStart = '\ud800'; - private const char HighSurrogateEnd = '\udbff'; - private const char LowSurrogateStart = '\udc00'; - private const char LowSurrogateEnd = '\udfff'; + private const string EmailUsernameSpecialChars = ".!#$%&'*+/=?^_`{|}~-+.~"; // 2.1 Characters and lines // A Unicode whitespace character is any code point in the Unicode Zs general category, @@ -31,6 +29,21 @@ public static class CharHelper private const string AsciiWhitespaceChars = "\t\n\f\r "; internal const string WhitespaceChars = AsciiWhitespaceChars + "\u00A0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000"; + // 2.1 Characters and lines + // An ASCII punctuation character is + // !, ", #, $, %, &, ', (, ), *, +, ,, -, ., / (U+0021–2F), + // :, ;, <, =, >, ?, @ (U+003A–0040), + // [, \, ], ^, _, ` (U+005B–0060), + // {, |, }, or ~ (U+007B–007E). + private const string AsciiPunctuationChars = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"; + + private static readonly SearchValues s_emailUsernameSpecialChar = SearchValues.Create(EmailUsernameSpecialChars); + private static readonly SearchValues s_emailUsernameSpecialCharOrDigit = SearchValues.Create(EmailUsernameSpecialChars + "0123456789"); + private static readonly SearchValues s_asciiPunctuationChars = SearchValues.Create(AsciiPunctuationChars); + private static readonly SearchValues s_asciiPunctuationCharsOrZero = SearchValues.Create(AsciiPunctuationChars + '\0'); + private static readonly SearchValues s_asciiPunctuationOrWhitespaceCharsOrZero = SearchValues.Create(AsciiPunctuationChars + AsciiWhitespaceChars + '\0'); + private static readonly SearchValues s_escapableSymbolChars = SearchValues.Create("!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~•"); + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static bool IsPunctuationException(char c) => c is '−' or '-' or '†' or '‡'; @@ -189,7 +202,7 @@ public static bool IsControl(this char c) public static bool IsEscapableSymbol(this char c) { // char.IsSymbol also works with Unicode symbols that cannot be escaped based on the specification. - return (c > ' ' && c < '0') || (c > '9' && c < 'A') || (c > 'Z' && c < 'a') || (c > 'z' && c < 127) || c == '•'; + return s_escapableSymbolChars.Contains(c); } //[MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -209,7 +222,7 @@ public static void CheckUnicodeCategory(this char c, out bool space, out bool pu else if (c <= 127) { space = c == '\0'; - punctuation = c == '\0' || IsAsciiPunctuation(c); + punctuation = IsAsciiPunctuationOrZero(c); } else { @@ -230,15 +243,12 @@ public static void CheckUnicodeCategory(this char c, out bool space, out bool pu } // Same as CheckUnicodeCategory + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static bool IsSpaceOrPunctuation(this char c) { - if (IsWhitespace(c)) + if (c <= 127) { - return true; - } - else if (c <= 127) - { - return c == '\0' || IsAsciiPunctuation(c); + return s_asciiPunctuationOrWhitespaceCharsOrZero.Contains(c); } else { @@ -249,7 +259,8 @@ internal static bool IsSpaceOrPunctuation(this char c) 1 << (int)UnicodeCategory.ClosePunctuation | 1 << (int)UnicodeCategory.InitialQuotePunctuation | 1 << (int)UnicodeCategory.FinalQuotePunctuation | - 1 << (int)UnicodeCategory.OtherPunctuation; + 1 << (int)UnicodeCategory.OtherPunctuation | + 1 << (int)UnicodeCategory.SpaceSeparator; return (PunctuationCategoryMask & (1 << (int)CharUnicodeInfo.GetUnicodeCategory(c))) != 0; } @@ -294,7 +305,7 @@ public static char EscapeInsecure(this char c) { // 2.3 Insecure characters // For security reasons, the Unicode character U+0000 must be replaced with the REPLACEMENT CHARACTER (U+FFFD). - return c == '\0' ? '\ufffd' : c; + return c == '\0' ? ReplacementChar : c; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -321,51 +332,33 @@ public static bool IsDigit(this char c) return (uint)(c - '0') <= ('9' - '0'); } - internal static bool IsAsciiPunctuationOrZero(this char c) - { - return c == '\0' || IsAsciiPunctuation(c); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static bool IsAsciiPunctuationOrZero(this char c) => + s_asciiPunctuationCharsOrZero.Contains(c); - public static bool IsAsciiPunctuation(this char c) - { - // 2.1 Characters and lines - // An ASCII punctuation character is - // !, ", #, $, %, &, ', (, ), *, +, ,, -, ., / (U+0021–2F), - // :, ;, <, =, >, ?, @ (U+003A–0040), - // [, \, ], ^, _, ` (U+005B–0060), - // {, |, }, or ~ (U+007B–007E). - return c <= 127 && ( - IsInInclusiveRange(c, 33, 47) || - IsInInclusiveRange(c, 58, 64) || - IsInInclusiveRange(c, 91, 96) || - IsInInclusiveRange(c, 123, 126)); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsAsciiPunctuation(this char c) => + s_asciiPunctuationChars.Contains(c); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsEmailUsernameSpecialChar(char c) - { - return ".!#$%&'*+/=?^_`{|}~-+.~".IndexOf(c) >= 0; - } + public static bool IsEmailUsernameSpecialChar(char c) => + s_emailUsernameSpecialChar.Contains(c); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsHighSurrogate(char c) - { - return IsInInclusiveRange(c, HighSurrogateStart, HighSurrogateEnd); - } + internal static bool IsEmailUsernameSpecialCharOrDigit(char c) => + s_emailUsernameSpecialCharOrDigit.Contains(c); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsLowSurrogate(char c) - { - return IsInInclusiveRange(c, LowSurrogateStart, LowSurrogateEnd); - } + public static bool IsHighSurrogate(char c) => + char.IsHighSurrogate(c); [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool IsInInclusiveRange(char c, char min, char max) - => (uint)(c - min) <= (uint)(max - min); + public static bool IsLowSurrogate(char c) => + char.IsLowSurrogate(c); [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static bool IsInInclusiveRange(int value, uint min, uint max) - => ((uint)value - min) <= (max - min); + internal static bool IsInInclusiveRange(int value, uint min, uint max) => + ((uint)value - min) <= (max - min); public static bool IsRightToLeft(int c) { diff --git a/src/Markdig/Helpers/LinkHelper.cs b/src/Markdig/Helpers/LinkHelper.cs index 3f57bbe6..15c5089d 100644 --- a/src/Markdig/Helpers/LinkHelper.cs +++ b/src/Markdig/Helpers/LinkHelper.cs @@ -166,7 +166,7 @@ public static bool TryParseAutolink(ref StringSlice text, [NotNullWhen(true)] ou if (!c.IsAlpha()) { // We may have an email char? - if (c.IsDigit() || CharHelper.IsEmailUsernameSpecialChar(c)) + if (CharHelper.IsEmailUsernameSpecialCharOrDigit(c)) { state = -1; } @@ -1019,7 +1019,7 @@ public static bool IsValidDomain(string link, int prefixLength, bool allowDomain bool segmentHasCharacters = false; int lastUnderscoreSegment = -1; - for (int i = prefixLength; i < link.Length; i++) + for (int i = prefixLength; (uint)i < (uint)link.Length; i++) { char c = link[i]; From 7a9c192d7dcad181cdcc58fe720351dc684d35b7 Mon Sep 17 00:00:00 2001 From: Miha Zupan Date: Sun, 16 Mar 2025 18:02:56 +0100 Subject: [PATCH 45/88] Speed up FencedCodeBlock rendering --- src/Markdig/Polyfills/FrozenCollections.cs | 13 +++++++++++++ .../Renderers/Html/CodeBlockRenderer.cs | 18 +++++++++++++++++- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/src/Markdig/Polyfills/FrozenCollections.cs b/src/Markdig/Polyfills/FrozenCollections.cs index ab07f756..0f0dfbe9 100644 --- a/src/Markdig/Polyfills/FrozenCollections.cs +++ b/src/Markdig/Polyfills/FrozenCollections.cs @@ -22,4 +22,17 @@ public static FrozenDictionary ToFrozenDictionary(th } } +internal sealed class FrozenSet : HashSet +{ + public FrozenSet(HashSet set, IEqualityComparer comparer) : base(set, comparer) { } +} + +internal static class FrozenSetExtensions +{ + public static FrozenSet ToFrozenSet(this HashSet set, IEqualityComparer comparer) + { + return new FrozenSet(set, comparer); + } +} + #endif \ No newline at end of file diff --git a/src/Markdig/Renderers/Html/CodeBlockRenderer.cs b/src/Markdig/Renderers/Html/CodeBlockRenderer.cs index b4fb0cae..be268003 100644 --- a/src/Markdig/Renderers/Html/CodeBlockRenderer.cs +++ b/src/Markdig/Renderers/Html/CodeBlockRenderer.cs @@ -31,11 +31,27 @@ public CodeBlockRenderer() { } /// public Dictionary BlockMapping { get; } = new Dictionary(StringComparer.OrdinalIgnoreCase); + private FrozenSet? _specialBlockMapping; + + private FrozenSet SpecialBlockMapping + { + get + { + return _specialBlockMapping ?? CreateNew(); + + FrozenSet CreateNew() + { + HashSet set = [.. BlocksAsDiv, .. BlockMapping.Keys]; + return _specialBlockMapping = set.ToFrozenSet(StringComparer.OrdinalIgnoreCase); + } + } + } + protected override void Write(HtmlRenderer renderer, CodeBlock obj) { renderer.EnsureLine(); - if ((obj as FencedCodeBlock)?.Info is string info && (BlocksAsDiv.Contains(info) || BlockMapping.ContainsKey(info))) + if ((obj as FencedCodeBlock)?.Info is string info && SpecialBlockMapping.Contains(info)) { var infoPrefix = (obj.Parser as FencedCodeBlockParser)?.InfoPrefix ?? FencedCodeBlockParser.DefaultInfoPrefix; From eceb70c16a19bb37fe71b93cb1159224e637f955 Mon Sep 17 00:00:00 2001 From: Miha Zupan Date: Sun, 16 Mar 2025 18:17:10 +0100 Subject: [PATCH 46/88] Avoid delegate allocations in AutoIdentifierExtension --- .../AutoIdentifiers/AutoIdentifierExtension.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/Markdig/Extensions/AutoIdentifiers/AutoIdentifierExtension.cs b/src/Markdig/Extensions/AutoIdentifiers/AutoIdentifierExtension.cs index 00e81bbd..f7877b4b 100644 --- a/src/Markdig/Extensions/AutoIdentifiers/AutoIdentifierExtension.cs +++ b/src/Markdig/Extensions/AutoIdentifiers/AutoIdentifierExtension.cs @@ -22,6 +22,8 @@ public class AutoIdentifierExtension : IMarkdownExtension private static readonly StripRendererCache _rendererCache = new(); private readonly AutoIdentifierOptions _options; + private readonly ProcessInlineDelegate _processInlinesBegin; + private readonly ProcessInlineDelegate _processInlinesEnd; /// /// Initializes a new instance of the class. @@ -30,6 +32,8 @@ public class AutoIdentifierExtension : IMarkdownExtension public AutoIdentifierExtension(AutoIdentifierOptions options) { _options = options; + _processInlinesBegin = DocumentOnProcessInlinesBegin; + _processInlinesEnd = HeadingBlock_ProcessInlinesEnd; } public void Setup(MarkdownPipelineBuilder pipeline) @@ -85,19 +89,19 @@ private void HeadingBlockParser_Closed(BlockProcessor processor, Block block) { dictionary = new Dictionary(); doc.SetData(this, dictionary); - doc.ProcessInlinesBegin += DocumentOnProcessInlinesBegin; + doc.ProcessInlinesBegin += _processInlinesBegin; } dictionary[text] = linkRef; } // Then we register after inline have been processed to actually generate the proper #id - headingBlock.ProcessInlinesEnd += HeadingBlock_ProcessInlinesEnd; + headingBlock.ProcessInlinesEnd += _processInlinesEnd; } private void DocumentOnProcessInlinesBegin(InlineProcessor processor, Inline? inline) { var doc = processor.Document; - doc.ProcessInlinesBegin -= DocumentOnProcessInlinesBegin; + doc.ProcessInlinesBegin -= _processInlinesBegin; var dictionary = (Dictionary)doc.GetData(this)!; foreach (var keyPair in dictionary) { @@ -117,7 +121,7 @@ private void DocumentOnProcessInlinesBegin(InlineProcessor processor, Inline? in /// Callback when there is a reference to found to a heading. /// Note that reference are only working if they are declared after. /// - private Inline CreateLinkInlineForHeading(InlineProcessor inlineState, LinkReferenceDefinition linkRef, Inline? child) + private static Inline CreateLinkInlineForHeading(InlineProcessor inlineState, LinkReferenceDefinition linkRef, Inline? child) { var headingRef = (HeadingLinkReferenceDefinition) linkRef; return new LinkInline() From da3d7f4f3a43b912dad373970963561dc612c84f Mon Sep 17 00:00:00 2001 From: Miha Zupan Date: Sun, 6 Apr 2025 11:32:01 +0200 Subject: [PATCH 47/88] Improve some descriptions --- .../Extensions/Alerts/AlertBlockRenderer.cs | 1 - .../Extensions/Alerts/AlertInlineParser.cs | 1 - src/Markdig/Markdown.cs | 19 +++++++------------ 3 files changed, 7 insertions(+), 14 deletions(-) diff --git a/src/Markdig/Extensions/Alerts/AlertBlockRenderer.cs b/src/Markdig/Extensions/Alerts/AlertBlockRenderer.cs index 6ac4aed7..2ca7c347 100644 --- a/src/Markdig/Extensions/Alerts/AlertBlockRenderer.cs +++ b/src/Markdig/Extensions/Alerts/AlertBlockRenderer.cs @@ -5,7 +5,6 @@ using Markdig.Helpers; using Markdig.Renderers; using Markdig.Renderers.Html; -using Markdig.Syntax; namespace Markdig.Extensions.Alerts; diff --git a/src/Markdig/Extensions/Alerts/AlertInlineParser.cs b/src/Markdig/Extensions/Alerts/AlertInlineParser.cs index faf56859..04fd97a6 100644 --- a/src/Markdig/Extensions/Alerts/AlertInlineParser.cs +++ b/src/Markdig/Extensions/Alerts/AlertInlineParser.cs @@ -6,7 +6,6 @@ using Markdig.Parsers; using Markdig.Renderers.Html; using Markdig.Syntax; -using Markdig.Syntax.Inlines; namespace Markdig.Extensions.Alerts; diff --git a/src/Markdig/Markdown.cs b/src/Markdig/Markdown.cs index 53dfdb78..637725b1 100644 --- a/src/Markdig/Markdown.cs +++ b/src/Markdig/Markdown.cs @@ -3,7 +3,6 @@ // See the license.txt file in the project root for more information. using System.IO; -using System.Linq; using System.Reflection; using Markdig.Helpers; @@ -90,8 +89,8 @@ public static MarkdownDocument Normalize(string markdown, TextWriter writer, Nor /// A Markdown text. /// The pipeline used for the conversion. /// A parser context used for the parsing. - /// The result of the conversion - /// if markdown variable is null + /// The HTML string. + /// If is null. public static string ToHtml(string markdown, MarkdownPipeline? pipeline = null, MarkdownParserContext? context = null) { if (markdown is null) ThrowHelper.ArgumentNullException_markdown(); @@ -108,8 +107,8 @@ public static string ToHtml(string markdown, MarkdownPipeline? pipeline = null, /// /// A Markdown document. /// The pipeline used for the conversion. - /// The result of the conversion - /// if markdown document variable is null + /// The HTML string. + /// If is null. public static string ToHtml(this MarkdownDocument document, MarkdownPipeline? pipeline = null) { if (document is null) ThrowHelper.ArgumentNullException(nameof(document)); @@ -131,8 +130,8 @@ public static string ToHtml(this MarkdownDocument document, MarkdownPipeline? pi /// A Markdown document. /// The destination that will receive the result of the conversion. /// The pipeline used for the conversion. - /// The result of the conversion - /// if markdown document variable is null + /// The HTML string. + /// If is null. public static void ToHtml(this MarkdownDocument document, TextWriter writer, MarkdownPipeline? pipeline = null) { if (document is null) ThrowHelper.ArgumentNullException(nameof(document)); @@ -165,11 +164,7 @@ public static MarkdownDocument ToHtml(string markdown, TextWriter writer, Markdo var document = MarkdownParser.Parse(markdown, pipeline, context); - using var rentedRenderer = pipeline.RentHtmlRenderer(writer); - HtmlRenderer renderer = rentedRenderer.Instance; - - renderer.Render(document); - writer.Flush(); + ToHtml(document, writer, pipeline); return document; } From c82a36884d34f90ad03ac946cd81eddd455767c3 Mon Sep 17 00:00:00 2001 From: Miha Zupan Date: Sun, 6 Apr 2025 13:08:32 +0200 Subject: [PATCH 48/88] Use the field keyword in a few places --- src/Markdig/Markdig.targets | 2 +- src/Markdig/Markdown.cs | 13 +++++++------ src/Markdig/MarkdownPipelineBuilder.cs | 10 +++++----- src/Markdig/Polyfills/NullableAttributes.cs | 3 +++ src/Markdig/Renderers/Html/CodeBlockRenderer.cs | 12 +++++++----- 5 files changed, 23 insertions(+), 17 deletions(-) diff --git a/src/Markdig/Markdig.targets b/src/Markdig/Markdig.targets index 9f4d17ca..a0c27b17 100644 --- a/src/Markdig/Markdig.targets +++ b/src/Markdig/Markdig.targets @@ -14,7 +14,7 @@ markdig.png https://github.com/xoofx/markdig true - 13 + preview enable $(NoWarn);CS1591 true diff --git a/src/Markdig/Markdown.cs b/src/Markdig/Markdown.cs index 637725b1..f60195f9 100644 --- a/src/Markdig/Markdown.cs +++ b/src/Markdig/Markdown.cs @@ -2,6 +2,7 @@ // This file is licensed under the BSD-Clause 2 license. // See the license.txt file in the project root for more information. +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Reflection; @@ -18,13 +19,13 @@ namespace Markdig; /// public static class Markdown { - public static string Version => - s_version ??= typeof(Markdown).Assembly.GetCustomAttribute()?.Version ?? "Unknown"; - - private static string? s_version; + [field: MaybeNull] + public static string Version => field ??= typeof(Markdown).Assembly.GetCustomAttribute()?.Version ?? "Unknown"; internal static readonly MarkdownPipeline DefaultPipeline = new MarkdownPipelineBuilder().Build(); - private static readonly MarkdownPipeline _defaultTrackTriviaPipeline = new MarkdownPipelineBuilder().EnableTrackTrivia().Build(); + + [field: MaybeNull] + private static MarkdownPipeline DefaultTrackTriviaPipeline => field ??= new MarkdownPipelineBuilder().EnableTrackTrivia().Build(); private static MarkdownPipeline GetPipeline(MarkdownPipeline? pipeline, string markdown) { @@ -201,7 +202,7 @@ public static MarkdownDocument Parse(string markdown, bool trackTrivia = false) { if (markdown is null) ThrowHelper.ArgumentNullException_markdown(); - MarkdownPipeline? pipeline = trackTrivia ? _defaultTrackTriviaPipeline : null; + MarkdownPipeline? pipeline = trackTrivia ? DefaultTrackTriviaPipeline : null; return Parse(markdown, pipeline); } diff --git a/src/Markdig/MarkdownPipelineBuilder.cs b/src/Markdig/MarkdownPipelineBuilder.cs index 165365d8..94ee9c06 100644 --- a/src/Markdig/MarkdownPipelineBuilder.cs +++ b/src/Markdig/MarkdownPipelineBuilder.cs @@ -16,7 +16,7 @@ namespace Markdig; /// NOTE: A pipeline is not thread-safe. public class MarkdownPipelineBuilder { - private MarkdownPipeline? pipeline; + private MarkdownPipeline? _pipeline; /// /// Initializes a new instance of the class. @@ -95,9 +95,9 @@ public MarkdownPipelineBuilder() /// An extension cannot be null public MarkdownPipeline Build() { - if (pipeline != null) + if (_pipeline != null) { - return pipeline; + return _pipeline; } // TODO: Review the whole initialization process for extensions @@ -115,7 +115,7 @@ public MarkdownPipeline Build() extension.Setup(this); } - pipeline = new MarkdownPipeline( + _pipeline = new MarkdownPipeline( new OrderedList(Extensions), new BlockParserList(BlockParsers), new InlineParserList(InlineParsers), @@ -125,6 +125,6 @@ public MarkdownPipeline Build() PreciseSourceLocation = PreciseSourceLocation, TrackTrivia = TrackTrivia, }; - return pipeline; + return _pipeline; } } \ No newline at end of file diff --git a/src/Markdig/Polyfills/NullableAttributes.cs b/src/Markdig/Polyfills/NullableAttributes.cs index c6510033..2ee4767f 100644 --- a/src/Markdig/Polyfills/NullableAttributes.cs +++ b/src/Markdig/Polyfills/NullableAttributes.cs @@ -18,6 +18,9 @@ internal sealed class NotNullWhenAttribute : Attribute [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, Inherited = false)] internal sealed class AllowNullAttribute : Attribute { } + +[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)] +internal sealed class MaybeNullAttribute : Attribute { } #endif #if !NET5_0_OR_GREATER diff --git a/src/Markdig/Renderers/Html/CodeBlockRenderer.cs b/src/Markdig/Renderers/Html/CodeBlockRenderer.cs index be268003..c09a8a23 100644 --- a/src/Markdig/Renderers/Html/CodeBlockRenderer.cs +++ b/src/Markdig/Renderers/Html/CodeBlockRenderer.cs @@ -4,6 +4,8 @@ using Markdig.Parsers; using Markdig.Syntax; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; namespace Markdig.Renderers.Html; @@ -31,18 +33,18 @@ public CodeBlockRenderer() { } /// public Dictionary BlockMapping { get; } = new Dictionary(StringComparer.OrdinalIgnoreCase); - private FrozenSet? _specialBlockMapping; - + [field: MaybeNull] private FrozenSet SpecialBlockMapping { get { - return _specialBlockMapping ?? CreateNew(); + return field ?? CreateNew(); + [MethodImpl(MethodImplOptions.NoInlining)] FrozenSet CreateNew() { HashSet set = [.. BlocksAsDiv, .. BlockMapping.Keys]; - return _specialBlockMapping = set.ToFrozenSet(StringComparer.OrdinalIgnoreCase); + return field = set.ToFrozenSet(StringComparer.OrdinalIgnoreCase); } } } @@ -51,7 +53,7 @@ protected override void Write(HtmlRenderer renderer, CodeBlock obj) { renderer.EnsureLine(); - if ((obj as FencedCodeBlock)?.Info is string info && SpecialBlockMapping.Contains(info)) + if (obj is FencedCodeBlock { Info: string info } && SpecialBlockMapping.Contains(info)) { var infoPrefix = (obj.Parser as FencedCodeBlockParser)?.InfoPrefix ?? FencedCodeBlockParser.DefaultInfoPrefix; From 8484420b7288473eb76172846c64f7d43a8b269c Mon Sep 17 00:00:00 2001 From: Miha Zupan Date: Sun, 6 Apr 2025 13:20:41 +0200 Subject: [PATCH 49/88] Remove some branches from IsWhiteSpace and IsWhiteSpaceOrZero --- src/Markdig/Helpers/CharHelper.cs | 63 ++++++++++++++++++------------- 1 file changed, 36 insertions(+), 27 deletions(-) diff --git a/src/Markdig/Helpers/CharHelper.cs b/src/Markdig/Helpers/CharHelper.cs index fbf6e470..5d6180a6 100644 --- a/src/Markdig/Helpers/CharHelper.cs +++ b/src/Markdig/Helpers/CharHelper.cs @@ -37,6 +37,7 @@ public static class CharHelper // {, |, }, or ~ (U+007B–007E). private const string AsciiPunctuationChars = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"; + // We're not currently using these SearchValues instances for vectorized IndexOfAny-like searches, but for their efficient single Contains(char) checks. private static readonly SearchValues s_emailUsernameSpecialChar = SearchValues.Create(EmailUsernameSpecialChars); private static readonly SearchValues s_emailUsernameSpecialCharOrDigit = SearchValues.Create(EmailUsernameSpecialChars + "0123456789"); private static readonly SearchValues s_asciiPunctuationChars = SearchValues.Create(AsciiPunctuationChars); @@ -162,33 +163,47 @@ public static bool IsWhitespace(this char c) // 2.1 Characters and lines // A Unicode whitespace character is any code point in the Unicode Zs general category, // or a tab (U+0009), line feed (U+000A), form feed (U+000C), or carriage return (U+000D). - if (c <= ' ') + if (c < '\u00A0') { - const long Mask = - (1L << ' ') | - (1L << '\t') | - (1L << '\n') | - (1L << '\f') | - (1L << '\r'); - - return (Mask & (1L << c)) != 0; + // Matches any of "\t\n\f\r ". See comments in HexConverter.IsHexChar for how these checks work: + // https://github.com/dotnet/runtime/blob/a2e1d21bb4faf914363968b812c990329ba92d8e/src/libraries/Common/src/System/HexConverter.cs#L392-L415 + // https://gist.github.com/MihaZupan/b93ba180c2b5fbaaed993db2ade76b49 + ulong shift = 30399299632234496UL << c; + ulong mask = (ulong)c - 64; + return (long)(shift & mask) < 0; } - return c >= '\u00A0' && IsWhitespaceRare(c); + return IsWhitespaceRare(c); + } - static bool IsWhitespaceRare(char c) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsWhiteSpaceOrZero(this char c) + { + if (c < '\u00A0') { - // return CharUnicodeInfo.GetUnicodeCategory(c) == UnicodeCategory.SpaceSeparator; + // Matches any of "\0\t\n\f\r ". + ulong shift = 9253771336487010304UL << c; + ulong mask = (ulong)c - 64; + return (long)(shift & mask) < 0; + } - if (c < 5760) - { - return c == '\u00A0'; - } - else - { - return c <= 12288 && - (c == 5760 || IsInInclusiveRange(c, 8192, 8202) || c == 8239 || c == 8287 || c == 12288); - } + return IsWhitespaceRare(c); + } + + private static bool IsWhitespaceRare(char c) + { + Debug.Assert(c >= '\u00A0'); + + // return CharUnicodeInfo.GetUnicodeCategory(c) == UnicodeCategory.SpaceSeparator; + + if (c < 5760) + { + return c == '\u00A0'; + } + else + { + return c <= 12288 && + (c == 5760 || IsInInclusiveRange(c, 8192, 8202) || c == 8239 || c == 8287 || c == 12288); } } @@ -205,12 +220,6 @@ public static bool IsEscapableSymbol(this char c) return s_escapableSymbolChars.Contains(c); } - //[MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsWhiteSpaceOrZero(this char c) - { - return IsZero(c) || IsWhitespace(c); - } - // Check if a char is a space or a punctuation public static void CheckUnicodeCategory(this char c, out bool space, out bool punctuation) { From 0e6d0f4cb24cbf32a2f1f26cc43aeed7a7573edf Mon Sep 17 00:00:00 2001 From: Miha Zupan Date: Sun, 6 Apr 2025 14:30:22 +0200 Subject: [PATCH 50/88] Fix style --- src/Markdig/Parsers/HtmlBlockParser.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Markdig/Parsers/HtmlBlockParser.cs b/src/Markdig/Parsers/HtmlBlockParser.cs index a5b89096..bcc6ca73 100644 --- a/src/Markdig/Parsers/HtmlBlockParser.cs +++ b/src/Markdig/Parsers/HtmlBlockParser.cs @@ -139,8 +139,7 @@ private BlockState TryParseTagType16(BlockProcessor state, StringSlice line, int c = line.NextChar(); } - if ( - !(c == '>' || (!hasLeadingClose && c == '/' && line.PeekChar() == '>') || c.IsWhiteSpaceOrZero())) + if (!(c == '>' || (!hasLeadingClose && c == '/' && line.PeekChar() == '>') || c.IsWhiteSpaceOrZero())) { return BlockState.None; } From 8269ff1af54456c97e58b53c5f6d3f4eaa24875b Mon Sep 17 00:00:00 2001 From: Miha Zupan Date: Thu, 3 Apr 2025 15:00:23 +0200 Subject: [PATCH 51/88] Improve AutoLinkParser overhead for false-positive opening chars --- .../Extensions/AutoLinks/AutoLinkParser.cs | 261 ++++++++---------- src/Markdig/Helpers/StringSlice.cs | 2 +- src/Markdig/Polyfills/SpanExtensions.cs | 23 ++ 3 files changed, 144 insertions(+), 142 deletions(-) create mode 100644 src/Markdig/Polyfills/SpanExtensions.cs diff --git a/src/Markdig/Extensions/AutoLinks/AutoLinkParser.cs b/src/Markdig/Extensions/AutoLinks/AutoLinkParser.cs index 6ee6b9f0..0b0fba26 100644 --- a/src/Markdig/Extensions/AutoLinks/AutoLinkParser.cs +++ b/src/Markdig/Extensions/AutoLinks/AutoLinkParser.cs @@ -6,6 +6,8 @@ using Markdig.Parsers; using Markdig.Renderers.Html; using Markdig.Syntax.Inlines; +using System.Buffers; +using System.Diagnostics; namespace Markdig.Extensions.AutoLinks; @@ -31,186 +33,171 @@ public AutoLinkParser(AutoLinkOptions options) 'w', // for www. ]; - _listOfCharCache = new ListOfCharCache(); + _validPreviousCharacters = SearchValues.Create(options.ValidPreviousCharacters); } public readonly AutoLinkOptions Options; - private readonly ListOfCharCache _listOfCharCache; + private readonly SearchValues _validPreviousCharacters; + // This is a particularly expensive parser as it gets called for many common letters. public override bool Match(InlineProcessor processor, ref StringSlice slice) { // Previous char must be a whitespace or a punctuation var previousChar = slice.PeekCharExtra(-1); - if (!previousChar.IsWhiteSpaceOrZero() && Options.ValidPreviousCharacters.IndexOf(previousChar) == -1) + if (!previousChar.IsWhiteSpaceOrZero() && !_validPreviousCharacters.Contains(previousChar)) { return false; } - var startPosition = slice.Start; - int domainOffset = 0; + ReadOnlySpan span = slice.AsSpan(); + + Debug.Assert(span[0] is 'h' or 'f' or 'm' or 't' or 'w'); - var c = slice.CurrentChar; // Precheck URL - switch (c) + bool mayBeValid = span.Length >= 4 && span[0] switch { - case 'h': - if (slice.MatchLowercase("ttp://", 1)) + 'h' => span.StartsWith("https://", StringComparison.Ordinal) || span.StartsWith("http://", StringComparison.Ordinal), + 'w' => span.StartsWith("www.", StringComparison.Ordinal), // We won't match http:/www. or /www.xxx + 'f' => span.StartsWith("ftp://", StringComparison.Ordinal), + 'm' => span.StartsWith("mailto:", StringComparison.Ordinal), + _ => span.StartsWith("tel:", StringComparison.Ordinal), + }; + + return mayBeValid && MatchCore(processor, ref slice); + } + + private bool MatchCore(InlineProcessor processor, ref StringSlice slice) + { + char c = slice.CurrentChar; + var startPosition = slice.Start; + + // We don't bother disposing the builder as it'll realistically never grow beyond the initial stack size. + var pendingEmphasis = new ValueStringBuilder(stackalloc char[32]); + + // Check that an autolink is possible in the current context + if (!IsAutoLinkValidInCurrentContext(processor, ref pendingEmphasis)) + { + return false; + } + + // Parse URL + if (!LinkHelper.TryParseUrl(ref slice, out string? link, out _, true)) + { + return false; + } + + // If we have any pending emphasis, remove any pending emphasis characters from the end of the link + if (pendingEmphasis.Length > 0) + { + for (int i = link.Length - 1; i >= 0; i--) + { + if (pendingEmphasis.AsSpan().Contains(link[i])) { - domainOffset = 7; // http:// + slice.Start--; } - else if (slice.MatchLowercase("ttps://", 1)) + else { - domainOffset = 8; // https:// + if (i < link.Length - 1) + { + link = link.Substring(0, i + 1); + } + break; } - else return false; - break; - case 'f': - if (!slice.MatchLowercase("tp://", 1)) + } + } + + int domainOffset = 0; + + // Post-check URL + switch (c) + { + case 'h': + if (string.Equals(link, "http://", StringComparison.Ordinal) || + string.Equals(link, "https://", StringComparison.Ordinal)) { return false; } - domainOffset = 6; // ftp:// + domainOffset = link[4] == 's' ? 8 : 7; // https:// or http:// break; - case 'm': - if (!slice.MatchLowercase("ailto:", 1)) + + case 'w': + domainOffset = 4; // www. + break; + + case 'f': + if (string.Equals(link, "ftp://", StringComparison.Ordinal)) { return false; } + domainOffset = 6; // ftp:// break; + case 't': - if (!slice.MatchLowercase("el:", 1)) + if (string.Equals(link, "tel", StringComparison.Ordinal)) { return false; } - domainOffset = 4; break; - case 'w': - if (!slice.MatchLowercase("ww.", 1)) // We won't match http:/www. or /www.xxx + + case 'm': + int atIndex = link.IndexOf('@'); + if (atIndex == -1 || + atIndex == 7) // mailto:@ - no email part { return false; } - domainOffset = 4; // www. + domainOffset = atIndex + 1; break; } - List pendingEmphasis = _listOfCharCache.Get(); - try + // Do not need to check if a telephone number is a valid domain + if (c != 't' && !LinkHelper.IsValidDomain(link, domainOffset, Options.AllowDomainWithoutPeriod)) { - // Check that an autolink is possible in the current context - if (!IsAutoLinkValidInCurrentContext(processor, pendingEmphasis)) - { - return false; - } - - // Parse URL - if (!LinkHelper.TryParseUrl(ref slice, out string? link, out _, true)) - { - return false; - } - - - // If we have any pending emphasis, remove any pending emphasis characters from the end of the link - if (pendingEmphasis.Count > 0) - { - for (int i = link.Length - 1; i >= 0; i--) - { - if (pendingEmphasis.Contains(link[i])) - { - slice.Start--; - } - else - { - if (i < link.Length - 1) - { - link = link.Substring(0, i + 1); - } - break; - } - } - } + return false; + } - // Post-check URL - switch (c) + var inline = new LinkInline() + { + Span = { - case 'h': - if (string.Equals(link, "http://", StringComparison.OrdinalIgnoreCase) || - string.Equals(link, "https://", StringComparison.OrdinalIgnoreCase)) - { - return false; - } - break; - case 'f': - if (string.Equals(link, "ftp://", StringComparison.OrdinalIgnoreCase)) - { - return false; - } - break; - case 't': - if (string.Equals(link, "tel", StringComparison.OrdinalIgnoreCase)) - { - return false; - } - break; - case 'm': - int atIndex = link.IndexOf('@'); - if (atIndex == -1 || - atIndex == 7) // mailto:@ - no email part - { - return false; - } - domainOffset = atIndex + 1; - break; - } + Start = processor.GetSourcePosition(startPosition, out int line, out int column), + }, + Line = line, + Column = column, + Url = c == 'w' ? ((Options.UseHttpsForWWWLinks ? "https://" : "http://") + link) : link, + IsClosed = true, + IsAutoLink = true, + }; - // Do not need to check if a telephone number is a valid domain - if (c != 't' && !LinkHelper.IsValidDomain(link, domainOffset, Options.AllowDomainWithoutPeriod)) - { - return false; - } + int skipFromBeginning = c switch + { + 'm' => 7, // For mailto: skip "mailto:" for content + 't' => 4, // Same but for tel: + _ => 0 + }; - var inline = new LinkInline() - { - Span = - { - Start = processor.GetSourcePosition(startPosition, out int line, out int column), - }, - Line = line, - Column = column, - Url = c == 'w' ? ((Options.UseHttpsForWWWLinks ? "https://" : "http://") + link) : link, - IsClosed = true, - IsAutoLink = true, - }; - - var skipFromBeginning = c == 'm' ? 7 : 0; // For mailto: skip "mailto:" for content - skipFromBeginning = c == 't' ? 4 : skipFromBeginning; // See above but for tel: - - inline.Span.End = inline.Span.Start + link.Length - 1; - inline.UrlSpan = inline.Span; - inline.AppendChild(new LiteralInline() - { - Span = inline.Span, - Line = line, - Column = column, - Content = new StringSlice(slice.Text, startPosition + skipFromBeginning, startPosition + link.Length - 1), - IsClosed = true - }); - processor.Inline = inline; - - if (Options.OpenInNewWindow) - { - inline.GetAttributes().AddPropertyIfNotExist("target", "_blank"); - } + inline.Span.End = inline.Span.Start + link.Length - 1; + inline.UrlSpan = inline.Span; + inline.AppendChild(new LiteralInline() + { + Span = inline.Span, + Line = line, + Column = column, + Content = new StringSlice(slice.Text, startPosition + skipFromBeginning, startPosition + link.Length - 1), + IsClosed = true + }); + processor.Inline = inline; - return true; - } - finally + if (Options.OpenInNewWindow) { - _listOfCharCache.Release(pendingEmphasis); + inline.GetAttributes().AddPropertyIfNotExist("target", "_blank"); } + + return true; } - private bool IsAutoLinkValidInCurrentContext(InlineProcessor processor, List pendingEmphasis) + private static bool IsAutoLinkValidInCurrentContext(InlineProcessor processor, ref ValueStringBuilder pendingEmphasis) { // Case where there is a pending HtmlInline var currentInline = processor.Inline; @@ -257,9 +244,9 @@ private bool IsAutoLinkValidInCurrentContext(InlineProcessor processor, List> - { - protected override void Reset(List instance) - { - instance.Clear(); - } - } } \ No newline at end of file diff --git a/src/Markdig/Helpers/StringSlice.cs b/src/Markdig/Helpers/StringSlice.cs index 95ddad99..ed1bea66 100644 --- a/src/Markdig/Helpers/StringSlice.cs +++ b/src/Markdig/Helpers/StringSlice.cs @@ -231,7 +231,7 @@ public readonly char PeekCharAbsolute(int index) } /// - /// Peeks a character at the specified offset from the current begining of the slice + /// Peeks a character at the specified offset from the current beginning of the slice /// without using the range or , returns `\0` if outside the . /// /// The offset. diff --git a/src/Markdig/Polyfills/SpanExtensions.cs b/src/Markdig/Polyfills/SpanExtensions.cs new file mode 100644 index 00000000..caf61cac --- /dev/null +++ b/src/Markdig/Polyfills/SpanExtensions.cs @@ -0,0 +1,23 @@ +// Copyright (c) Alexandre Mutel. All rights reserved. +// This file is licensed under the BSD-Clause 2 license. +// See the license.txt file in the project root for more information. + +#if NET462 || NETSTANDARD2_0 + +using System.Diagnostics; + +namespace System; + +internal static class SpanExtensions +{ + public static bool StartsWith(this ReadOnlySpan span, string prefix, StringComparison comparisonType) + { + Debug.Assert(comparisonType is StringComparison.Ordinal or StringComparison.OrdinalIgnoreCase); + + return + span.Length >= prefix.Length && + span.Slice(0, prefix.Length).Equals(prefix.AsSpan(), comparisonType); + } +} + +#endif \ No newline at end of file From f4effc25c00516799a92881520f2889e6ecc82da Mon Sep 17 00:00:00 2001 From: "Ram.Type-0" <39725073+RamType0@users.noreply.github.com> Date: Tue, 15 Apr 2025 00:57:16 +0900 Subject: [PATCH 52/88] Fix `MathInline` is called "math block" --- src/Markdig.Tests/Specs/MathSpecs.md | 62 ++++++++++++++-------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/src/Markdig.Tests/Specs/MathSpecs.md b/src/Markdig.Tests/Specs/MathSpecs.md index 60b80dbc..d583796a 100644 --- a/src/Markdig.Tests/Specs/MathSpecs.md +++ b/src/Markdig.Tests/Specs/MathSpecs.md @@ -4,79 +4,79 @@ Adds support for mathematics spans: ## Math Inline -Allows to define a mathematic block embraced by `$...$` +Allows to define a mathematic inline block embraced by `$...$` ```````````````````````````````` example -This is a $math block$ +This is a $math inline$ . -

This is a \(math block\)

+

This is a \(math inline\)

```````````````````````````````` Or by `$$...$$` embracing it by: ```````````````````````````````` example -This is a $$math block$$ +This is a $$math inline$$ . -

This is a \(math block\)

+

This is a \(math inline\)

```````````````````````````````` Newlines inside an inline math are not allowed: ```````````````````````````````` example This is not a $$math -block$$ and? this is a $$math block$$ +inline$$ and? this is a $$math inline$$ .

This is not a $$math -block$$ and? this is a \(math block\)

+inline$$ and? this is a \(math inline\)

```````````````````````````````` ```````````````````````````````` example This is not a $math -block$ and? this is a $math block$ +inline$ and? this is a $math inline$ .

This is not a $math -block$ and? this is a \(math block\)

+inline$ and? this is a \(math inline\)

```````````````````````````````` An opening `$` can be followed by a space if the closing is also preceded by a space `$`: ```````````````````````````````` example -This is a $ math block $ +This is a $ math inline $ . -

This is a \(math block\)

+

This is a \(math inline\)

```````````````````````````````` ```````````````````````````````` example -This is a $ math block $ after +This is a $ math inline $ after . -

This is a \(math block\) after

+

This is a \(math inline\) after

```````````````````````````````` ```````````````````````````````` example -This is a $$ math block $$ after +This is a $$ math inline $$ after . -

This is a \(math block\) after

+

This is a \(math inline\) after

```````````````````````````````` ```````````````````````````````` example -This is a not $ math block$ because there is not a whitespace before the closing +This is a not $ math inline$ because there is not a whitespace before the closing . -

This is a not $ math block$ because there is not a whitespace before the closing

+

This is a not $ math inline$ because there is not a whitespace before the closing

```````````````````````````````` For the opening `$` it requires a space or a punctuation before (but cannot be used within a word): ```````````````````````````````` example -This is not a m$ath block$ +This is not a m$ath inline$ . -

This is not a m$ath block$

+

This is not a m$ath inline$

```````````````````````````````` For the closing `$` it requires a space after or a punctuation (but cannot be preceded by a space and cannot be used within a word): ```````````````````````````````` example -This is not a $math bloc$k +This is not a $math inlin$e . -

This is not a $math bloc$k

+

This is not a $math inlin$e

```````````````````````````````` For the closing `$` it requires a space after or a punctuation (but cannot be preceded by a space and cannot be used within a word): @@ -90,34 +90,34 @@ This is should not match a 16$ or a $15 A `$` can be escaped between a math inline block by using the escape `\\` ```````````````````````````````` example -This is a $math \$ block$ +This is a $math \$ inline$ . -

This is a \(math \$ block\)

+

This is a \(math \$ inline\)

```````````````````````````````` At most, two `$` will be matched for the opening and closing: ```````````````````````````````` example -This is a $$$math block$$$ +This is a $$$math inline$$$ . -

This is a \($math block$\)

+

This is a \($math inline$\)

```````````````````````````````` Regular text can come both before and after the math inline ```````````````````````````````` example -This is a $math block$ with text on both sides. +This is a $math inline$ with text on both sides. . -

This is a \(math block\) with text on both sides.

+

This is a \(math inline\) with text on both sides.

```````````````````````````````` -A mathematic block takes precedence over standard emphasis `*` `_`: +A mathematic inline block takes precedence over standard emphasis `*` `_`: ```````````````````````````````` example -This is *a $math* block$ +This is *a $math* inline$ . -

This is *a \(math* block\)

+

This is *a \(math* inline\)

```````````````````````````````` -An opening $$ at the beginning of a line should not be interpreted as a Math block: +An opening $$ at the beginning of a line should not be interpreted as a Math inline: ```````````````````````````````` example $$ math $$ starting at a line From 0d6343b421734d15d71fc4b48c7e780fc085d3d7 Mon Sep 17 00:00:00 2001 From: Miha Zupan Date: Mon, 14 Apr 2025 22:02:21 +0200 Subject: [PATCH 53/88] Make AlertBlock parsing a bit cheaper --- src/Markdig/Extensions/Alerts/AlertBlock.cs | 2 +- .../Extensions/Alerts/AlertInlineParser.cs | 39 +++++++++++-------- 2 files changed, 24 insertions(+), 17 deletions(-) diff --git a/src/Markdig/Extensions/Alerts/AlertBlock.cs b/src/Markdig/Extensions/Alerts/AlertBlock.cs index 4e5ff529..43869c05 100644 --- a/src/Markdig/Extensions/Alerts/AlertBlock.cs +++ b/src/Markdig/Extensions/Alerts/AlertBlock.cs @@ -22,7 +22,7 @@ public AlertBlock(StringSlice kind) : base(null) } /// - /// Gets or sets the kind of the alert block (e.g `NOTE`, `TIP`, `IMPORTANT`, `WARNING`, `CAUTION`) + /// Gets or sets the kind of the alert block (e.g `NOTE`, `TIP`, `IMPORTANT`, `WARNING`, `CAUTION`). /// public StringSlice Kind { get; set; } diff --git a/src/Markdig/Extensions/Alerts/AlertInlineParser.cs b/src/Markdig/Extensions/Alerts/AlertInlineParser.cs index 04fd97a6..7d609e5b 100644 --- a/src/Markdig/Extensions/Alerts/AlertInlineParser.cs +++ b/src/Markdig/Extensions/Alerts/AlertInlineParser.cs @@ -15,6 +15,9 @@ namespace Markdig.Extensions.Alerts; /// public class AlertInlineParser : InlineParser { + private static readonly TransformedStringCache s_alertTypeClassCache = new( + type => $"markdown-alert-{type.ToLowerInvariant()}"); + /// /// Initializes a new instance of the class. /// @@ -25,27 +28,30 @@ public AlertInlineParser() public override bool Match(InlineProcessor processor, ref StringSlice slice) { - // 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 - || quoteBlock is AlertBlock || quoteBlock.Parent is not MarkdownDocument) + if (slice.PeekChar() != '!') { return false; } - var saved = slice; - var c = slice.NextChar(); - if (c != '!') + // 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 || + quoteBlock is AlertBlock || + quoteBlock.Parent is not MarkdownDocument) { - slice = saved; return false; } - c = slice.NextChar(); // Skip ! + StringSlice saved = slice; + + slice.SkipChar(); // Skip [ + char c = slice.NextChar(); // Skip ! - var start = slice.Start; - var end = start; + int start = slice.Start; + int end = start; while (c.IsAlpha()) { end = slice.Start; @@ -76,13 +82,13 @@ public override bool Match(InlineProcessor processor, ref StringSlice slice) end = slice.Start; if (c == '\n') { - slice.NextChar(); // Skip \n + slice.SkipChar(); // Skip \n } } } else if (c == '\n') { - slice.NextChar(); // Skip \n + slice.SkipChar(); // Skip \n } break; } @@ -103,8 +109,9 @@ public override bool Match(InlineProcessor processor, ref StringSlice slice) Column = quoteBlock.Column, }; - alertBlock.GetAttributes().AddClass("markdown-alert"); - alertBlock.GetAttributes().AddClass($"markdown-alert-{alertType.ToString().ToLowerInvariant()}"); + HtmlAttributes attributes = alertBlock.GetAttributes(); + attributes.AddClass("markdown-alert"); + attributes.AddClass(s_alertTypeClassCache.Get(alertType.AsSpan())); // Replace the quote block with the alert block var parentQuoteBlock = quoteBlock.Parent!; From bbefce3b1f2d38346fa6d6888137330de129f9a3 Mon Sep 17 00:00:00 2001 From: Miha Zupan Date: Mon, 14 Apr 2025 22:11:53 +0200 Subject: [PATCH 54/88] Sealed + ref struct --- src/Markdig/MarkdownPipeline.cs | 2 +- src/Markdig/Syntax/MarkdownObject.cs | 2 +- src/Markdig/Syntax/QuoteBlock.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Markdig/MarkdownPipeline.cs b/src/Markdig/MarkdownPipeline.cs index d9667500..94c75ddc 100644 --- a/src/Markdig/MarkdownPipeline.cs +++ b/src/Markdig/MarkdownPipeline.cs @@ -127,7 +127,7 @@ protected override void Reset(HtmlRenderer instance) } } - internal readonly struct RentedHtmlRenderer : IDisposable + internal readonly ref struct RentedHtmlRenderer : IDisposable { private readonly HtmlRendererCache _cache; public readonly HtmlRenderer Instance; diff --git a/src/Markdig/Syntax/MarkdownObject.cs b/src/Markdig/Syntax/MarkdownObject.cs index 5235fc9c..444a5b87 100644 --- a/src/Markdig/Syntax/MarkdownObject.cs +++ b/src/Markdig/Syntax/MarkdownObject.cs @@ -153,7 +153,7 @@ public string ToPositionText() return Unsafe.As(storage.Trivia); } - private class DataEntriesAndTrivia + private sealed class DataEntriesAndTrivia { private struct DataEntry(object key, object value) { diff --git a/src/Markdig/Syntax/QuoteBlock.cs b/src/Markdig/Syntax/QuoteBlock.cs index 251e4273..433fb580 100644 --- a/src/Markdig/Syntax/QuoteBlock.cs +++ b/src/Markdig/Syntax/QuoteBlock.cs @@ -25,7 +25,7 @@ public QuoteBlock(BlockParser? parser) : base(parser) /// /// Gets or sets the trivia per line of this QuoteBlock. - /// Trivia: only parsed when is enabled, otherwise null. + /// Trivia: only parsed when is enabled. /// public List QuoteLines => Trivia; From 023d93c091070e4e215c81158c22a2ec12ae1ce2 Mon Sep 17 00:00:00 2001 From: Miha Zupan Date: Mon, 14 Apr 2025 23:32:22 +0200 Subject: [PATCH 55/88] Update CommonMark spec to 0.31.2 --- .../Specs/CommonMark.generated.cs | 1794 +++++++++-------- src/Markdig.Tests/Specs/CommonMark.md | 153 +- 2 files changed, 977 insertions(+), 970 deletions(-) diff --git a/src/Markdig.Tests/Specs/CommonMark.generated.cs b/src/Markdig.Tests/Specs/CommonMark.generated.cs index 3644a5b6..9011894c 100644 --- a/src/Markdig.Tests/Specs/CommonMark.generated.cs +++ b/src/Markdig.Tests/Specs/CommonMark.generated.cs @@ -14,9 +14,9 @@ public class TestPreliminariesTabs // --- // title: CommonMark Spec // author: John MacFarlane - // version: '0.30' - // date: '2021-06-19' - // license: '[CC-BY-SA 4.0](http://creativecommons.org/licenses/by-sa/4.0/)' + // version: '0.31.2' + // date: '2024-01-28' + // license: '[CC-BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/)' // ... // // # Introduction @@ -27,7 +27,7 @@ public class TestPreliminariesTabs // based on conventions for indicating formatting in email // and usenet posts. It was developed by John Gruber (with // help from Aaron Swartz) and released in 2004 in the form of a - // [syntax description](http://daringfireball.net/projects/markdown/syntax) + // [syntax description](https://daringfireball.net/projects/markdown/syntax) // and a Perl script (`Markdown.pl`) for converting Markdown to // HTML. In the next decade, dozens of implementations were // developed in many languages. Some extended the original @@ -47,10 +47,10 @@ public class TestPreliminariesTabs // > Markdown-formatted document should be publishable as-is, as // > plain text, without looking like it's been marked up with tags // > or formatting instructions. - // > () + // > () // // The point can be illustrated by comparing a sample of - // [AsciiDoc](http://www.methods.co.nz/asciidoc/) with + // [AsciiDoc](https://asciidoc.org/) with // an equivalent sample of Markdown. Here is a sample of // AsciiDoc from the AsciiDoc manual: // @@ -116,7 +116,7 @@ public class TestPreliminariesTabs // ## Why is a spec needed? // // John Gruber's [canonical description of Markdown's - // syntax](http://daringfireball.net/projects/markdown/syntax) + // syntax](https://daringfireball.net/projects/markdown/syntax) // does not specify the syntax unambiguously. Here are some examples of // questions it does not answer: // @@ -329,9 +329,9 @@ public class TestPreliminariesTabs // // The following definitions of character classes will be used in this spec: // - // A [Unicode whitespace character](@) is - // any code point in the Unicode `Zs` general category, or a tab (`U+0009`), - // line feed (`U+000A`), form feed (`U+000C`), or carriage return (`U+000D`). + // A [Unicode whitespace character](@) is a character in the Unicode `Zs` general + // category, or a tab (`U+0009`), line feed (`U+000A`), form feed (`U+000C`), or + // carriage return (`U+000D`). // // [Unicode whitespace](@) is a sequence of one or more // [Unicode whitespace characters]. @@ -350,9 +350,8 @@ public class TestPreliminariesTabs // `[`, `\`, `]`, `^`, `_`, `` ` `` (U+005B–0060), // `{`, `|`, `}`, or `~` (U+007B–007E). // - // A [Unicode punctuation character](@) is an [ASCII - // punctuation character] or anything in - // the general Unicode categories `Pc`, `Pd`, `Pe`, `Pf`, `Pi`, `Po`, or `Ps`. + // A [Unicode punctuation character](@) is a character in the Unicode `P` + // (puncuation) or `S` (symbol) general categories. // // ## Tabs // @@ -754,12 +753,12 @@ public void PreliminariesBackslashEscapes_Example020() // Section: Preliminaries / Backslash escapes // // The following Markdown: - // + // // // Should be rendered as: - //

http://example.com?find=\*

+ //

https://example.com?find=\*

- TestParser.TestSpec("", "

http://example.com?find=\\*

", "", context: "Example 20\nSection Preliminaries / Backslash escapes\n"); + TestParser.TestSpec("", "

https://example.com?find=\\*

", "", context: "Example 20\nSection Preliminaries / Backslash escapes\n"); } [Test] @@ -2879,7 +2878,7 @@ public class TestLeafBlocksFencedCodeBlocks // opening code fence until the end of the containing block (or // document). (An alternative spec would require backtracking in the // event that a closing code fence is not found. But this makes parsing - // much less efficient, and there seems to be no real down side to the + // much less efficient, and there seems to be no real downside to the // behavior described here.) // // A fenced code block may interrupt a paragraph, and does not require @@ -3535,7 +3534,7 @@ public class TestLeafBlocksHTMLBlocks // `h1`, `h2`, `h3`, `h4`, `h5`, `h6`, `head`, `header`, `hr`, // `html`, `iframe`, `legend`, `li`, `link`, `main`, `menu`, `menuitem`, // `nav`, `noframes`, `ol`, `optgroup`, `option`, `p`, `param`, - // `section`, `source`, `summary`, `table`, `tbody`, `td`, + // `search`, `section`, `summary`, `table`, `tbody`, `td`, // `tfoot`, `th`, `thead`, `title`, `tr`, `track`, `ul`, followed // by a space, a tab, the end of the line, the string `>`, or // the string `/>`.\ @@ -6028,7 +6027,7 @@ public class TestContainerBlocksListItems // blocks *Bs* starting with a character other than a space or tab, and *M* is // a list marker of width *W* followed by 1 ≤ *N* ≤ 4 spaces of indentation, // then the result of prepending *M* and the following spaces to the first line - // of Ls*, and indenting subsequent lines of *Ls* by *W + N* spaces, is a + // of *Ls*, and indenting subsequent lines of *Ls* by *W + N* spaces, is a // list item with *Bs* as its contents. The type of the list item // (bullet or ordered) is determined by the type of its list marker. // If the list item is ordered, then it is also assigned a start @@ -7635,11 +7634,11 @@ public void ContainerBlocksLists_Example303() // Since it is well established Markdown practice to allow lists to // interrupt paragraphs inside list items, the [principle of // uniformity] requires us to allow this outside list items as - // well. ([reStructuredText](http://docutils.sourceforge.net/rst.html) + // well. ([reStructuredText](https://docutils.sourceforge.net/rst.html) // takes a different approach, requiring blank lines before lists // even inside other list items.) // - // In order to solve of unwanted lists in paragraphs with + // In order to solve the problem of unwanted lists in paragraphs with // hard-wrapped numerals, we allow only lists starting with `1` to // interrupt paragraphs. Thus, [Test] @@ -8661,12 +8660,12 @@ public void InlinesCodeSpans_Example345() // Section: Inlines / Code spans // // The following Markdown: - // `` + // `` // // Should be rendered as: - //

<http://foo.bar.baz>`

+ //

<https://foo.bar.baz>`

- TestParser.TestSpec("``", "

<http://foo.bar.baz>`

", "", context: "Example 345\nSection Inlines / Code spans\n"); + TestParser.TestSpec("``", "

<https://foo.bar.baz>`

", "", context: "Example 345\nSection Inlines / Code spans\n"); } // But this is an autolink: @@ -8677,12 +8676,12 @@ public void InlinesCodeSpans_Example346() // Section: Inlines / Code spans // // The following Markdown: - // ` + // ` // // Should be rendered as: - //

http://foo.bar.`baz`

+ //

https://foo.bar.`baz`

- TestParser.TestSpec("`", "

http://foo.bar.`baz`

", "", context: "Example 346\nSection Inlines / Code spans\n"); + TestParser.TestSpec("`", "

https://foo.bar.`baz`

", "", context: "Example 346\nSection Inlines / Code spans\n"); } // When a backtick string is not closed by a matching backtick string, @@ -8741,7 +8740,7 @@ public class TestInlinesEmphasisAndStrongEmphasis // ## Emphasis and strong emphasis // // John Gruber's original [Markdown syntax - // description](http://daringfireball.net/projects/markdown/syntax#em) says: + // description](https://daringfireball.net/projects/markdown/syntax#em) says: // // > Markdown treats asterisks (`*`) and underscores (`_`) as indicators of // > emphasis. Text wrapped with one `*` or `_` will be wrapped with an HTML @@ -8843,7 +8842,7 @@ public class TestInlinesEmphasisAndStrongEmphasis // (The idea of distinguishing left-flanking and right-flanking // delimiter runs based on the character before and the character // after comes from Roopesh Chander's - // [vfmd](http://www.vfmd.org/vfmd-spec/specification/#procedure-for-identifying-emphasis-tags). + // [vfmd](https://web.archive.org/web/20220608143320/http://www.vfmd.org/vfmd-spec/specification/#procedure-for-identifying-emphasis-tags). // vfmd uses the terminology "emphasis indicator string" instead of "delimiter // run," and its rules for distinguishing left- and right-flanking runs // are a bit more complex than the ones given here.) @@ -9013,7 +9012,7 @@ public void InlinesEmphasisAndStrongEmphasis_Example353() TestParser.TestSpec("* a *", "

* a *

", "", context: "Example 353\nSection Inlines / Emphasis and strong emphasis\n"); } - // Intraword emphasis with `*` is permitted: + // Unicode symbols count as punctuation, too: [Test] public void InlinesEmphasisAndStrongEmphasis_Example354() { @@ -9021,14 +9020,21 @@ public void InlinesEmphasisAndStrongEmphasis_Example354() // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: - // foo*bar* + // *$*alpha. + // + // *£*bravo. + // + // *€*charlie. // // Should be rendered as: - //

foobar

+ //

*$*alpha.

+ //

*£*bravo.

+ //

*€*charlie.

- TestParser.TestSpec("foo*bar*", "

foobar

", "", context: "Example 354\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("*$*alpha.\n\n*£*bravo.\n\n*€*charlie.", "

*$*alpha.

\n

*£*bravo.

\n

*€*charlie.

", "", context: "Example 354\nSection Inlines / Emphasis and strong emphasis\n"); } + // Intraword emphasis with `*` is permitted: [Test] public void InlinesEmphasisAndStrongEmphasis_Example355() { @@ -9036,19 +9042,34 @@ public void InlinesEmphasisAndStrongEmphasis_Example355() // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: + // foo*bar* + // + // Should be rendered as: + //

foobar

+ + TestParser.TestSpec("foo*bar*", "

foobar

", "", context: "Example 355\nSection Inlines / Emphasis and strong emphasis\n"); + } + + [Test] + public void InlinesEmphasisAndStrongEmphasis_Example356() + { + // Example 356 + // Section: Inlines / Emphasis and strong emphasis + // + // The following Markdown: // 5*6*78 // // Should be rendered as: //

5678

- TestParser.TestSpec("5*6*78", "

5678

", "", context: "Example 355\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("5*6*78", "

5678

", "", context: "Example 356\nSection Inlines / Emphasis and strong emphasis\n"); } // Rule 2: [Test] - public void InlinesEmphasisAndStrongEmphasis_Example356() + public void InlinesEmphasisAndStrongEmphasis_Example357() { - // Example 356 + // Example 357 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -9057,15 +9078,15 @@ public void InlinesEmphasisAndStrongEmphasis_Example356() // Should be rendered as: //

foo bar

- TestParser.TestSpec("_foo bar_", "

foo bar

", "", context: "Example 356\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("_foo bar_", "

foo bar

", "", context: "Example 357\nSection Inlines / Emphasis and strong emphasis\n"); } // This is not emphasis, because the opening `_` is followed by // whitespace: [Test] - public void InlinesEmphasisAndStrongEmphasis_Example357() + public void InlinesEmphasisAndStrongEmphasis_Example358() { - // Example 357 + // Example 358 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -9074,15 +9095,15 @@ public void InlinesEmphasisAndStrongEmphasis_Example357() // Should be rendered as: //

_ foo bar_

- TestParser.TestSpec("_ foo bar_", "

_ foo bar_

", "", context: "Example 357\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("_ foo bar_", "

_ foo bar_

", "", context: "Example 358\nSection Inlines / Emphasis and strong emphasis\n"); } // This is not emphasis, because the opening `_` is preceded // by an alphanumeric and followed by punctuation: [Test] - public void InlinesEmphasisAndStrongEmphasis_Example358() + public void InlinesEmphasisAndStrongEmphasis_Example359() { - // Example 358 + // Example 359 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -9091,14 +9112,14 @@ public void InlinesEmphasisAndStrongEmphasis_Example358() // Should be rendered as: //

a_"foo"_

- TestParser.TestSpec("a_\"foo\"_", "

a_"foo"_

", "", context: "Example 358\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("a_\"foo\"_", "

a_"foo"_

", "", context: "Example 359\nSection Inlines / Emphasis and strong emphasis\n"); } // Emphasis with `_` is not allowed inside words: [Test] - public void InlinesEmphasisAndStrongEmphasis_Example359() + public void InlinesEmphasisAndStrongEmphasis_Example360() { - // Example 359 + // Example 360 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -9107,13 +9128,13 @@ public void InlinesEmphasisAndStrongEmphasis_Example359() // Should be rendered as: //

foo_bar_

- TestParser.TestSpec("foo_bar_", "

foo_bar_

", "", context: "Example 359\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("foo_bar_", "

foo_bar_

", "", context: "Example 360\nSection Inlines / Emphasis and strong emphasis\n"); } [Test] - public void InlinesEmphasisAndStrongEmphasis_Example360() + public void InlinesEmphasisAndStrongEmphasis_Example361() { - // Example 360 + // Example 361 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -9122,13 +9143,13 @@ public void InlinesEmphasisAndStrongEmphasis_Example360() // Should be rendered as: //

5_6_78

- TestParser.TestSpec("5_6_78", "

5_6_78

", "", context: "Example 360\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("5_6_78", "

5_6_78

", "", context: "Example 361\nSection Inlines / Emphasis and strong emphasis\n"); } [Test] - public void InlinesEmphasisAndStrongEmphasis_Example361() + public void InlinesEmphasisAndStrongEmphasis_Example362() { - // Example 361 + // Example 362 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -9137,15 +9158,15 @@ public void InlinesEmphasisAndStrongEmphasis_Example361() // Should be rendered as: //

пристаням_стремятся_

- TestParser.TestSpec("пристаням_стремятся_", "

пристаням_стремятся_

", "", context: "Example 361\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("пристаням_стремятся_", "

пристаням_стремятся_

", "", context: "Example 362\nSection Inlines / Emphasis and strong emphasis\n"); } // Here `_` does not generate emphasis, because the first delimiter run // is right-flanking and the second left-flanking: [Test] - public void InlinesEmphasisAndStrongEmphasis_Example362() + public void InlinesEmphasisAndStrongEmphasis_Example363() { - // Example 362 + // Example 363 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -9154,16 +9175,16 @@ public void InlinesEmphasisAndStrongEmphasis_Example362() // Should be rendered as: //

aa_"bb"_cc

- TestParser.TestSpec("aa_\"bb\"_cc", "

aa_"bb"_cc

", "", context: "Example 362\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("aa_\"bb\"_cc", "

aa_"bb"_cc

", "", context: "Example 363\nSection Inlines / Emphasis and strong emphasis\n"); } // This is emphasis, even though the opening delimiter is // both left- and right-flanking, because it is preceded by // punctuation: [Test] - public void InlinesEmphasisAndStrongEmphasis_Example363() + public void InlinesEmphasisAndStrongEmphasis_Example364() { - // Example 363 + // Example 364 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -9172,7 +9193,7 @@ public void InlinesEmphasisAndStrongEmphasis_Example363() // Should be rendered as: //

foo-(bar)

- TestParser.TestSpec("foo-_(bar)_", "

foo-(bar)

", "", context: "Example 363\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("foo-_(bar)_", "

foo-(bar)

", "", context: "Example 364\nSection Inlines / Emphasis and strong emphasis\n"); } // Rule 3: @@ -9180,9 +9201,9 @@ public void InlinesEmphasisAndStrongEmphasis_Example363() // This is not emphasis, because the closing delimiter does // not match the opening delimiter: [Test] - public void InlinesEmphasisAndStrongEmphasis_Example364() + public void InlinesEmphasisAndStrongEmphasis_Example365() { - // Example 364 + // Example 365 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -9191,15 +9212,15 @@ public void InlinesEmphasisAndStrongEmphasis_Example364() // Should be rendered as: //

_foo*

- TestParser.TestSpec("_foo*", "

_foo*

", "", context: "Example 364\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("_foo*", "

_foo*

", "", context: "Example 365\nSection Inlines / Emphasis and strong emphasis\n"); } // This is not emphasis, because the closing `*` is preceded by // whitespace: [Test] - public void InlinesEmphasisAndStrongEmphasis_Example365() + public void InlinesEmphasisAndStrongEmphasis_Example366() { - // Example 365 + // Example 366 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -9208,14 +9229,14 @@ public void InlinesEmphasisAndStrongEmphasis_Example365() // Should be rendered as: //

*foo bar *

- TestParser.TestSpec("*foo bar *", "

*foo bar *

", "", context: "Example 365\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("*foo bar *", "

*foo bar *

", "", context: "Example 366\nSection Inlines / Emphasis and strong emphasis\n"); } // A line ending also counts as whitespace: [Test] - public void InlinesEmphasisAndStrongEmphasis_Example366() + public void InlinesEmphasisAndStrongEmphasis_Example367() { - // Example 366 + // Example 367 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -9226,16 +9247,16 @@ public void InlinesEmphasisAndStrongEmphasis_Example366() //

*foo bar // *

- TestParser.TestSpec("*foo bar\n*", "

*foo bar\n*

", "", context: "Example 366\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("*foo bar\n*", "

*foo bar\n*

", "", context: "Example 367\nSection Inlines / Emphasis and strong emphasis\n"); } // This is not emphasis, because the second `*` is // preceded by punctuation and followed by an alphanumeric // (hence it is not part of a [right-flanking delimiter run]: [Test] - public void InlinesEmphasisAndStrongEmphasis_Example367() + public void InlinesEmphasisAndStrongEmphasis_Example368() { - // Example 367 + // Example 368 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -9244,15 +9265,15 @@ public void InlinesEmphasisAndStrongEmphasis_Example367() // Should be rendered as: //

*(*foo)

- TestParser.TestSpec("*(*foo)", "

*(*foo)

", "", context: "Example 367\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("*(*foo)", "

*(*foo)

", "", context: "Example 368\nSection Inlines / Emphasis and strong emphasis\n"); } // The point of this restriction is more easily appreciated // with this example: [Test] - public void InlinesEmphasisAndStrongEmphasis_Example368() + public void InlinesEmphasisAndStrongEmphasis_Example369() { - // Example 368 + // Example 369 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -9261,14 +9282,14 @@ public void InlinesEmphasisAndStrongEmphasis_Example368() // Should be rendered as: //

(foo)

- TestParser.TestSpec("*(*foo*)*", "

(foo)

", "", context: "Example 368\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("*(*foo*)*", "

(foo)

", "", context: "Example 369\nSection Inlines / Emphasis and strong emphasis\n"); } // Intraword emphasis with `*` is allowed: [Test] - public void InlinesEmphasisAndStrongEmphasis_Example369() + public void InlinesEmphasisAndStrongEmphasis_Example370() { - // Example 369 + // Example 370 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -9277,7 +9298,7 @@ public void InlinesEmphasisAndStrongEmphasis_Example369() // Should be rendered as: //

foobar

- TestParser.TestSpec("*foo*bar", "

foobar

", "", context: "Example 369\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("*foo*bar", "

foobar

", "", context: "Example 370\nSection Inlines / Emphasis and strong emphasis\n"); } // Rule 4: @@ -9285,9 +9306,9 @@ public void InlinesEmphasisAndStrongEmphasis_Example369() // This is not emphasis, because the closing `_` is preceded by // whitespace: [Test] - public void InlinesEmphasisAndStrongEmphasis_Example370() + public void InlinesEmphasisAndStrongEmphasis_Example371() { - // Example 370 + // Example 371 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -9296,15 +9317,15 @@ public void InlinesEmphasisAndStrongEmphasis_Example370() // Should be rendered as: //

_foo bar _

- TestParser.TestSpec("_foo bar _", "

_foo bar _

", "", context: "Example 370\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("_foo bar _", "

_foo bar _

", "", context: "Example 371\nSection Inlines / Emphasis and strong emphasis\n"); } // This is not emphasis, because the second `_` is // preceded by punctuation and followed by an alphanumeric: [Test] - public void InlinesEmphasisAndStrongEmphasis_Example371() + public void InlinesEmphasisAndStrongEmphasis_Example372() { - // Example 371 + // Example 372 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -9313,14 +9334,14 @@ public void InlinesEmphasisAndStrongEmphasis_Example371() // Should be rendered as: //

_(_foo)

- TestParser.TestSpec("_(_foo)", "

_(_foo)

", "", context: "Example 371\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("_(_foo)", "

_(_foo)

", "", context: "Example 372\nSection Inlines / Emphasis and strong emphasis\n"); } // This is emphasis within emphasis: [Test] - public void InlinesEmphasisAndStrongEmphasis_Example372() + public void InlinesEmphasisAndStrongEmphasis_Example373() { - // Example 372 + // Example 373 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -9329,14 +9350,14 @@ public void InlinesEmphasisAndStrongEmphasis_Example372() // Should be rendered as: //

(foo)

- TestParser.TestSpec("_(_foo_)_", "

(foo)

", "", context: "Example 372\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("_(_foo_)_", "

(foo)

", "", context: "Example 373\nSection Inlines / Emphasis and strong emphasis\n"); } // Intraword emphasis is disallowed for `_`: [Test] - public void InlinesEmphasisAndStrongEmphasis_Example373() + public void InlinesEmphasisAndStrongEmphasis_Example374() { - // Example 373 + // Example 374 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -9345,13 +9366,13 @@ public void InlinesEmphasisAndStrongEmphasis_Example373() // Should be rendered as: //

_foo_bar

- TestParser.TestSpec("_foo_bar", "

_foo_bar

", "", context: "Example 373\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("_foo_bar", "

_foo_bar

", "", context: "Example 374\nSection Inlines / Emphasis and strong emphasis\n"); } [Test] - public void InlinesEmphasisAndStrongEmphasis_Example374() + public void InlinesEmphasisAndStrongEmphasis_Example375() { - // Example 374 + // Example 375 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -9360,13 +9381,13 @@ public void InlinesEmphasisAndStrongEmphasis_Example374() // Should be rendered as: //

_пристаням_стремятся

- TestParser.TestSpec("_пристаням_стремятся", "

_пристаням_стремятся

", "", context: "Example 374\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("_пристаням_стремятся", "

_пристаням_стремятся

", "", context: "Example 375\nSection Inlines / Emphasis and strong emphasis\n"); } [Test] - public void InlinesEmphasisAndStrongEmphasis_Example375() + public void InlinesEmphasisAndStrongEmphasis_Example376() { - // Example 375 + // Example 376 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -9375,16 +9396,16 @@ public void InlinesEmphasisAndStrongEmphasis_Example375() // Should be rendered as: //

foo_bar_baz

- TestParser.TestSpec("_foo_bar_baz_", "

foo_bar_baz

", "", context: "Example 375\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("_foo_bar_baz_", "

foo_bar_baz

", "", context: "Example 376\nSection Inlines / Emphasis and strong emphasis\n"); } // This is emphasis, even though the closing delimiter is // both left- and right-flanking, because it is followed by // punctuation: [Test] - public void InlinesEmphasisAndStrongEmphasis_Example376() + public void InlinesEmphasisAndStrongEmphasis_Example377() { - // Example 376 + // Example 377 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -9393,14 +9414,14 @@ public void InlinesEmphasisAndStrongEmphasis_Example376() // Should be rendered as: //

(bar).

- TestParser.TestSpec("_(bar)_.", "

(bar).

", "", context: "Example 376\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("_(bar)_.", "

(bar).

", "", context: "Example 377\nSection Inlines / Emphasis and strong emphasis\n"); } // Rule 5: [Test] - public void InlinesEmphasisAndStrongEmphasis_Example377() + public void InlinesEmphasisAndStrongEmphasis_Example378() { - // Example 377 + // Example 378 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -9409,15 +9430,15 @@ public void InlinesEmphasisAndStrongEmphasis_Example377() // Should be rendered as: //

foo bar

- TestParser.TestSpec("**foo bar**", "

foo bar

", "", context: "Example 377\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("**foo bar**", "

foo bar

", "", context: "Example 378\nSection Inlines / Emphasis and strong emphasis\n"); } // This is not strong emphasis, because the opening delimiter is // followed by whitespace: [Test] - public void InlinesEmphasisAndStrongEmphasis_Example378() + public void InlinesEmphasisAndStrongEmphasis_Example379() { - // Example 378 + // Example 379 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -9426,16 +9447,16 @@ public void InlinesEmphasisAndStrongEmphasis_Example378() // Should be rendered as: //

** foo bar**

- TestParser.TestSpec("** foo bar**", "

** foo bar**

", "", context: "Example 378\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("** foo bar**", "

** foo bar**

", "", context: "Example 379\nSection Inlines / Emphasis and strong emphasis\n"); } // This is not strong emphasis, because the opening `**` is preceded // by an alphanumeric and followed by punctuation, and hence // not part of a [left-flanking delimiter run]: [Test] - public void InlinesEmphasisAndStrongEmphasis_Example379() + public void InlinesEmphasisAndStrongEmphasis_Example380() { - // Example 379 + // Example 380 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -9444,14 +9465,14 @@ public void InlinesEmphasisAndStrongEmphasis_Example379() // Should be rendered as: //

a**"foo"**

- TestParser.TestSpec("a**\"foo\"**", "

a**"foo"**

", "", context: "Example 379\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("a**\"foo\"**", "

a**"foo"**

", "", context: "Example 380\nSection Inlines / Emphasis and strong emphasis\n"); } // Intraword strong emphasis with `**` is permitted: [Test] - public void InlinesEmphasisAndStrongEmphasis_Example380() + public void InlinesEmphasisAndStrongEmphasis_Example381() { - // Example 380 + // Example 381 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -9460,14 +9481,14 @@ public void InlinesEmphasisAndStrongEmphasis_Example380() // Should be rendered as: //

foobar

- TestParser.TestSpec("foo**bar**", "

foobar

", "", context: "Example 380\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("foo**bar**", "

foobar

", "", context: "Example 381\nSection Inlines / Emphasis and strong emphasis\n"); } // Rule 6: [Test] - public void InlinesEmphasisAndStrongEmphasis_Example381() + public void InlinesEmphasisAndStrongEmphasis_Example382() { - // Example 381 + // Example 382 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -9476,15 +9497,15 @@ public void InlinesEmphasisAndStrongEmphasis_Example381() // Should be rendered as: //

foo bar

- TestParser.TestSpec("__foo bar__", "

foo bar

", "", context: "Example 381\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("__foo bar__", "

foo bar

", "", context: "Example 382\nSection Inlines / Emphasis and strong emphasis\n"); } // This is not strong emphasis, because the opening delimiter is // followed by whitespace: [Test] - public void InlinesEmphasisAndStrongEmphasis_Example382() + public void InlinesEmphasisAndStrongEmphasis_Example383() { - // Example 382 + // Example 383 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -9493,14 +9514,14 @@ public void InlinesEmphasisAndStrongEmphasis_Example382() // Should be rendered as: //

__ foo bar__

- TestParser.TestSpec("__ foo bar__", "

__ foo bar__

", "", context: "Example 382\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("__ foo bar__", "

__ foo bar__

", "", context: "Example 383\nSection Inlines / Emphasis and strong emphasis\n"); } // A line ending counts as whitespace: [Test] - public void InlinesEmphasisAndStrongEmphasis_Example383() + public void InlinesEmphasisAndStrongEmphasis_Example384() { - // Example 383 + // Example 384 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -9511,15 +9532,15 @@ public void InlinesEmphasisAndStrongEmphasis_Example383() //

__ // foo bar__

- TestParser.TestSpec("__\nfoo bar__", "

__\nfoo bar__

", "", context: "Example 383\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("__\nfoo bar__", "

__\nfoo bar__

", "", context: "Example 384\nSection Inlines / Emphasis and strong emphasis\n"); } // This is not strong emphasis, because the opening `__` is preceded // by an alphanumeric and followed by punctuation: [Test] - public void InlinesEmphasisAndStrongEmphasis_Example384() + public void InlinesEmphasisAndStrongEmphasis_Example385() { - // Example 384 + // Example 385 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -9528,14 +9549,14 @@ public void InlinesEmphasisAndStrongEmphasis_Example384() // Should be rendered as: //

a__"foo"__

- TestParser.TestSpec("a__\"foo\"__", "

a__"foo"__

", "", context: "Example 384\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("a__\"foo\"__", "

a__"foo"__

", "", context: "Example 385\nSection Inlines / Emphasis and strong emphasis\n"); } // Intraword strong emphasis is forbidden with `__`: [Test] - public void InlinesEmphasisAndStrongEmphasis_Example385() + public void InlinesEmphasisAndStrongEmphasis_Example386() { - // Example 385 + // Example 386 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -9544,13 +9565,13 @@ public void InlinesEmphasisAndStrongEmphasis_Example385() // Should be rendered as: //

foo__bar__

- TestParser.TestSpec("foo__bar__", "

foo__bar__

", "", context: "Example 385\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("foo__bar__", "

foo__bar__

", "", context: "Example 386\nSection Inlines / Emphasis and strong emphasis\n"); } [Test] - public void InlinesEmphasisAndStrongEmphasis_Example386() + public void InlinesEmphasisAndStrongEmphasis_Example387() { - // Example 386 + // Example 387 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -9559,13 +9580,13 @@ public void InlinesEmphasisAndStrongEmphasis_Example386() // Should be rendered as: //

5__6__78

- TestParser.TestSpec("5__6__78", "

5__6__78

", "", context: "Example 386\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("5__6__78", "

5__6__78

", "", context: "Example 387\nSection Inlines / Emphasis and strong emphasis\n"); } [Test] - public void InlinesEmphasisAndStrongEmphasis_Example387() + public void InlinesEmphasisAndStrongEmphasis_Example388() { - // Example 387 + // Example 388 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -9574,13 +9595,13 @@ public void InlinesEmphasisAndStrongEmphasis_Example387() // Should be rendered as: //

пристаням__стремятся__

- TestParser.TestSpec("пристаням__стремятся__", "

пристаням__стремятся__

", "", context: "Example 387\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("пристаням__стремятся__", "

пристаням__стремятся__

", "", context: "Example 388\nSection Inlines / Emphasis and strong emphasis\n"); } [Test] - public void InlinesEmphasisAndStrongEmphasis_Example388() + public void InlinesEmphasisAndStrongEmphasis_Example389() { - // Example 388 + // Example 389 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -9589,16 +9610,16 @@ public void InlinesEmphasisAndStrongEmphasis_Example388() // Should be rendered as: //

foo, bar, baz

- TestParser.TestSpec("__foo, __bar__, baz__", "

foo, bar, baz

", "", context: "Example 388\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("__foo, __bar__, baz__", "

foo, bar, baz

", "", context: "Example 389\nSection Inlines / Emphasis and strong emphasis\n"); } // This is strong emphasis, even though the opening delimiter is // both left- and right-flanking, because it is preceded by // punctuation: [Test] - public void InlinesEmphasisAndStrongEmphasis_Example389() + public void InlinesEmphasisAndStrongEmphasis_Example390() { - // Example 389 + // Example 390 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -9607,7 +9628,7 @@ public void InlinesEmphasisAndStrongEmphasis_Example389() // Should be rendered as: //

foo-(bar)

- TestParser.TestSpec("foo-__(bar)__", "

foo-(bar)

", "", context: "Example 389\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("foo-__(bar)__", "

foo-(bar)

", "", context: "Example 390\nSection Inlines / Emphasis and strong emphasis\n"); } // Rule 7: @@ -9615,9 +9636,9 @@ public void InlinesEmphasisAndStrongEmphasis_Example389() // This is not strong emphasis, because the closing delimiter is preceded // by whitespace: [Test] - public void InlinesEmphasisAndStrongEmphasis_Example390() + public void InlinesEmphasisAndStrongEmphasis_Example391() { - // Example 390 + // Example 391 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -9626,7 +9647,7 @@ public void InlinesEmphasisAndStrongEmphasis_Example390() // Should be rendered as: //

**foo bar **

- TestParser.TestSpec("**foo bar **", "

**foo bar **

", "", context: "Example 390\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("**foo bar **", "

**foo bar **

", "", context: "Example 391\nSection Inlines / Emphasis and strong emphasis\n"); } // (Nor can it be interpreted as an emphasized `*foo bar *`, because of @@ -9635,9 +9656,9 @@ public void InlinesEmphasisAndStrongEmphasis_Example390() // This is not strong emphasis, because the second `**` is // preceded by punctuation and followed by an alphanumeric: [Test] - public void InlinesEmphasisAndStrongEmphasis_Example391() + public void InlinesEmphasisAndStrongEmphasis_Example392() { - // Example 391 + // Example 392 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -9646,15 +9667,15 @@ public void InlinesEmphasisAndStrongEmphasis_Example391() // Should be rendered as: //

**(**foo)

- TestParser.TestSpec("**(**foo)", "

**(**foo)

", "", context: "Example 391\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("**(**foo)", "

**(**foo)

", "", context: "Example 392\nSection Inlines / Emphasis and strong emphasis\n"); } // The point of this restriction is more easily appreciated // with these examples: [Test] - public void InlinesEmphasisAndStrongEmphasis_Example392() + public void InlinesEmphasisAndStrongEmphasis_Example393() { - // Example 392 + // Example 393 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -9663,13 +9684,13 @@ public void InlinesEmphasisAndStrongEmphasis_Example392() // Should be rendered as: //

(foo)

- TestParser.TestSpec("*(**foo**)*", "

(foo)

", "", context: "Example 392\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("*(**foo**)*", "

(foo)

", "", context: "Example 393\nSection Inlines / Emphasis and strong emphasis\n"); } [Test] - public void InlinesEmphasisAndStrongEmphasis_Example393() + public void InlinesEmphasisAndStrongEmphasis_Example394() { - // Example 393 + // Example 394 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -9680,13 +9701,13 @@ public void InlinesEmphasisAndStrongEmphasis_Example393() //

Gomphocarpus (Gomphocarpus physocarpus, syn. // Asclepias physocarpa)

- TestParser.TestSpec("**Gomphocarpus (*Gomphocarpus physocarpus*, syn.\n*Asclepias physocarpa*)**", "

Gomphocarpus (Gomphocarpus physocarpus, syn.\nAsclepias physocarpa)

", "", context: "Example 393\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("**Gomphocarpus (*Gomphocarpus physocarpus*, syn.\n*Asclepias physocarpa*)**", "

Gomphocarpus (Gomphocarpus physocarpus, syn.\nAsclepias physocarpa)

", "", context: "Example 394\nSection Inlines / Emphasis and strong emphasis\n"); } [Test] - public void InlinesEmphasisAndStrongEmphasis_Example394() + public void InlinesEmphasisAndStrongEmphasis_Example395() { - // Example 394 + // Example 395 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -9695,14 +9716,14 @@ public void InlinesEmphasisAndStrongEmphasis_Example394() // Should be rendered as: //

foo "bar" foo

- TestParser.TestSpec("**foo \"*bar*\" foo**", "

foo "bar" foo

", "", context: "Example 394\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("**foo \"*bar*\" foo**", "

foo "bar" foo

", "", context: "Example 395\nSection Inlines / Emphasis and strong emphasis\n"); } // Intraword emphasis: [Test] - public void InlinesEmphasisAndStrongEmphasis_Example395() + public void InlinesEmphasisAndStrongEmphasis_Example396() { - // Example 395 + // Example 396 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -9711,7 +9732,7 @@ public void InlinesEmphasisAndStrongEmphasis_Example395() // Should be rendered as: //

foobar

- TestParser.TestSpec("**foo**bar", "

foobar

", "", context: "Example 395\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("**foo**bar", "

foobar

", "", context: "Example 396\nSection Inlines / Emphasis and strong emphasis\n"); } // Rule 8: @@ -9719,9 +9740,9 @@ public void InlinesEmphasisAndStrongEmphasis_Example395() // This is not strong emphasis, because the closing delimiter is // preceded by whitespace: [Test] - public void InlinesEmphasisAndStrongEmphasis_Example396() + public void InlinesEmphasisAndStrongEmphasis_Example397() { - // Example 396 + // Example 397 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -9730,15 +9751,15 @@ public void InlinesEmphasisAndStrongEmphasis_Example396() // Should be rendered as: //

__foo bar __

- TestParser.TestSpec("__foo bar __", "

__foo bar __

", "", context: "Example 396\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("__foo bar __", "

__foo bar __

", "", context: "Example 397\nSection Inlines / Emphasis and strong emphasis\n"); } // This is not strong emphasis, because the second `__` is // preceded by punctuation and followed by an alphanumeric: [Test] - public void InlinesEmphasisAndStrongEmphasis_Example397() + public void InlinesEmphasisAndStrongEmphasis_Example398() { - // Example 397 + // Example 398 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -9747,15 +9768,15 @@ public void InlinesEmphasisAndStrongEmphasis_Example397() // Should be rendered as: //

__(__foo)

- TestParser.TestSpec("__(__foo)", "

__(__foo)

", "", context: "Example 397\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("__(__foo)", "

__(__foo)

", "", context: "Example 398\nSection Inlines / Emphasis and strong emphasis\n"); } // The point of this restriction is more easily appreciated // with this example: [Test] - public void InlinesEmphasisAndStrongEmphasis_Example398() + public void InlinesEmphasisAndStrongEmphasis_Example399() { - // Example 398 + // Example 399 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -9764,14 +9785,14 @@ public void InlinesEmphasisAndStrongEmphasis_Example398() // Should be rendered as: //

(foo)

- TestParser.TestSpec("_(__foo__)_", "

(foo)

", "", context: "Example 398\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("_(__foo__)_", "

(foo)

", "", context: "Example 399\nSection Inlines / Emphasis and strong emphasis\n"); } // Intraword strong emphasis is forbidden with `__`: [Test] - public void InlinesEmphasisAndStrongEmphasis_Example399() + public void InlinesEmphasisAndStrongEmphasis_Example400() { - // Example 399 + // Example 400 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -9780,13 +9801,13 @@ public void InlinesEmphasisAndStrongEmphasis_Example399() // Should be rendered as: //

__foo__bar

- TestParser.TestSpec("__foo__bar", "

__foo__bar

", "", context: "Example 399\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("__foo__bar", "

__foo__bar

", "", context: "Example 400\nSection Inlines / Emphasis and strong emphasis\n"); } [Test] - public void InlinesEmphasisAndStrongEmphasis_Example400() + public void InlinesEmphasisAndStrongEmphasis_Example401() { - // Example 400 + // Example 401 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -9795,13 +9816,13 @@ public void InlinesEmphasisAndStrongEmphasis_Example400() // Should be rendered as: //

__пристаням__стремятся

- TestParser.TestSpec("__пристаням__стремятся", "

__пристаням__стремятся

", "", context: "Example 400\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("__пристаням__стремятся", "

__пристаням__стремятся

", "", context: "Example 401\nSection Inlines / Emphasis and strong emphasis\n"); } [Test] - public void InlinesEmphasisAndStrongEmphasis_Example401() + public void InlinesEmphasisAndStrongEmphasis_Example402() { - // Example 401 + // Example 402 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -9810,16 +9831,16 @@ public void InlinesEmphasisAndStrongEmphasis_Example401() // Should be rendered as: //

foo__bar__baz

- TestParser.TestSpec("__foo__bar__baz__", "

foo__bar__baz

", "", context: "Example 401\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("__foo__bar__baz__", "

foo__bar__baz

", "", context: "Example 402\nSection Inlines / Emphasis and strong emphasis\n"); } // This is strong emphasis, even though the closing delimiter is // both left- and right-flanking, because it is followed by // punctuation: [Test] - public void InlinesEmphasisAndStrongEmphasis_Example402() + public void InlinesEmphasisAndStrongEmphasis_Example403() { - // Example 402 + // Example 403 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -9828,7 +9849,7 @@ public void InlinesEmphasisAndStrongEmphasis_Example402() // Should be rendered as: //

(bar).

- TestParser.TestSpec("__(bar)__.", "

(bar).

", "", context: "Example 402\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("__(bar)__.", "

(bar).

", "", context: "Example 403\nSection Inlines / Emphasis and strong emphasis\n"); } // Rule 9: @@ -9836,9 +9857,9 @@ public void InlinesEmphasisAndStrongEmphasis_Example402() // Any nonempty sequence of inline elements can be the contents of an // emphasized span. [Test] - public void InlinesEmphasisAndStrongEmphasis_Example403() + public void InlinesEmphasisAndStrongEmphasis_Example404() { - // Example 403 + // Example 404 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -9847,13 +9868,13 @@ public void InlinesEmphasisAndStrongEmphasis_Example403() // Should be rendered as: //

foo bar

- TestParser.TestSpec("*foo [bar](/url)*", "

foo bar

", "", context: "Example 403\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("*foo [bar](/url)*", "

foo bar

", "", context: "Example 404\nSection Inlines / Emphasis and strong emphasis\n"); } [Test] - public void InlinesEmphasisAndStrongEmphasis_Example404() + public void InlinesEmphasisAndStrongEmphasis_Example405() { - // Example 404 + // Example 405 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -9864,15 +9885,15 @@ public void InlinesEmphasisAndStrongEmphasis_Example404() //

foo // bar

- TestParser.TestSpec("*foo\nbar*", "

foo\nbar

", "", context: "Example 404\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("*foo\nbar*", "

foo\nbar

", "", context: "Example 405\nSection Inlines / Emphasis and strong emphasis\n"); } // In particular, emphasis and strong emphasis can be nested // inside emphasis: [Test] - public void InlinesEmphasisAndStrongEmphasis_Example405() + public void InlinesEmphasisAndStrongEmphasis_Example406() { - // Example 405 + // Example 406 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -9881,13 +9902,13 @@ public void InlinesEmphasisAndStrongEmphasis_Example405() // Should be rendered as: //

foo bar baz

- TestParser.TestSpec("_foo __bar__ baz_", "

foo bar baz

", "", context: "Example 405\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("_foo __bar__ baz_", "

foo bar baz

", "", context: "Example 406\nSection Inlines / Emphasis and strong emphasis\n"); } [Test] - public void InlinesEmphasisAndStrongEmphasis_Example406() + public void InlinesEmphasisAndStrongEmphasis_Example407() { - // Example 406 + // Example 407 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -9896,13 +9917,13 @@ public void InlinesEmphasisAndStrongEmphasis_Example406() // Should be rendered as: //

foo bar baz

- TestParser.TestSpec("_foo _bar_ baz_", "

foo bar baz

", "", context: "Example 406\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("_foo _bar_ baz_", "

foo bar baz

", "", context: "Example 407\nSection Inlines / Emphasis and strong emphasis\n"); } [Test] - public void InlinesEmphasisAndStrongEmphasis_Example407() + public void InlinesEmphasisAndStrongEmphasis_Example408() { - // Example 407 + // Example 408 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -9911,13 +9932,13 @@ public void InlinesEmphasisAndStrongEmphasis_Example407() // Should be rendered as: //

foo bar

- TestParser.TestSpec("__foo_ bar_", "

foo bar

", "", context: "Example 407\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("__foo_ bar_", "

foo bar

", "", context: "Example 408\nSection Inlines / Emphasis and strong emphasis\n"); } [Test] - public void InlinesEmphasisAndStrongEmphasis_Example408() + public void InlinesEmphasisAndStrongEmphasis_Example409() { - // Example 408 + // Example 409 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -9926,13 +9947,13 @@ public void InlinesEmphasisAndStrongEmphasis_Example408() // Should be rendered as: //

foo bar

- TestParser.TestSpec("*foo *bar**", "

foo bar

", "", context: "Example 408\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("*foo *bar**", "

foo bar

", "", context: "Example 409\nSection Inlines / Emphasis and strong emphasis\n"); } [Test] - public void InlinesEmphasisAndStrongEmphasis_Example409() + public void InlinesEmphasisAndStrongEmphasis_Example410() { - // Example 409 + // Example 410 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -9941,13 +9962,13 @@ public void InlinesEmphasisAndStrongEmphasis_Example409() // Should be rendered as: //

foo bar baz

- TestParser.TestSpec("*foo **bar** baz*", "

foo bar baz

", "", context: "Example 409\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("*foo **bar** baz*", "

foo bar baz

", "", context: "Example 410\nSection Inlines / Emphasis and strong emphasis\n"); } [Test] - public void InlinesEmphasisAndStrongEmphasis_Example410() + public void InlinesEmphasisAndStrongEmphasis_Example411() { - // Example 410 + // Example 411 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -9956,7 +9977,7 @@ public void InlinesEmphasisAndStrongEmphasis_Example410() // Should be rendered as: //

foobarbaz

- TestParser.TestSpec("*foo**bar**baz*", "

foobarbaz

", "", context: "Example 410\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("*foo**bar**baz*", "

foobarbaz

", "", context: "Example 411\nSection Inlines / Emphasis and strong emphasis\n"); } // Note that in the preceding case, the interpretation @@ -9977,9 +9998,9 @@ public void InlinesEmphasisAndStrongEmphasis_Example410() // For the same reason, we don't get two consecutive // emphasis sections in this example: [Test] - public void InlinesEmphasisAndStrongEmphasis_Example411() + public void InlinesEmphasisAndStrongEmphasis_Example412() { - // Example 411 + // Example 412 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -9988,7 +10009,7 @@ public void InlinesEmphasisAndStrongEmphasis_Example411() // Should be rendered as: //

foo**bar

- TestParser.TestSpec("*foo**bar*", "

foo**bar

", "", context: "Example 411\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("*foo**bar*", "

foo**bar

", "", context: "Example 412\nSection Inlines / Emphasis and strong emphasis\n"); } // The same condition ensures that the following @@ -9996,9 +10017,9 @@ public void InlinesEmphasisAndStrongEmphasis_Example411() // emphasis, even when the interior whitespace is // omitted: [Test] - public void InlinesEmphasisAndStrongEmphasis_Example412() + public void InlinesEmphasisAndStrongEmphasis_Example413() { - // Example 412 + // Example 413 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -10007,13 +10028,13 @@ public void InlinesEmphasisAndStrongEmphasis_Example412() // Should be rendered as: //

foo bar

- TestParser.TestSpec("***foo** bar*", "

foo bar

", "", context: "Example 412\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("***foo** bar*", "

foo bar

", "", context: "Example 413\nSection Inlines / Emphasis and strong emphasis\n"); } [Test] - public void InlinesEmphasisAndStrongEmphasis_Example413() + public void InlinesEmphasisAndStrongEmphasis_Example414() { - // Example 413 + // Example 414 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -10022,13 +10043,13 @@ public void InlinesEmphasisAndStrongEmphasis_Example413() // Should be rendered as: //

foo bar

- TestParser.TestSpec("*foo **bar***", "

foo bar

", "", context: "Example 413\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("*foo **bar***", "

foo bar

", "", context: "Example 414\nSection Inlines / Emphasis and strong emphasis\n"); } [Test] - public void InlinesEmphasisAndStrongEmphasis_Example414() + public void InlinesEmphasisAndStrongEmphasis_Example415() { - // Example 414 + // Example 415 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -10037,16 +10058,16 @@ public void InlinesEmphasisAndStrongEmphasis_Example414() // Should be rendered as: //

foobar

- TestParser.TestSpec("*foo**bar***", "

foobar

", "", context: "Example 414\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("*foo**bar***", "

foobar

", "", context: "Example 415\nSection Inlines / Emphasis and strong emphasis\n"); } // When the lengths of the interior closing and opening // delimiter runs are *both* multiples of 3, though, // they can match to create emphasis: [Test] - public void InlinesEmphasisAndStrongEmphasis_Example415() + public void InlinesEmphasisAndStrongEmphasis_Example416() { - // Example 415 + // Example 416 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -10055,13 +10076,13 @@ public void InlinesEmphasisAndStrongEmphasis_Example415() // Should be rendered as: //

foobarbaz

- TestParser.TestSpec("foo***bar***baz", "

foobarbaz

", "", context: "Example 415\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("foo***bar***baz", "

foobarbaz

", "", context: "Example 416\nSection Inlines / Emphasis and strong emphasis\n"); } [Test] - public void InlinesEmphasisAndStrongEmphasis_Example416() + public void InlinesEmphasisAndStrongEmphasis_Example417() { - // Example 416 + // Example 417 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -10070,14 +10091,14 @@ public void InlinesEmphasisAndStrongEmphasis_Example416() // Should be rendered as: //

foobar***baz

- TestParser.TestSpec("foo******bar*********baz", "

foobar***baz

", "", context: "Example 416\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("foo******bar*********baz", "

foobar***baz

", "", context: "Example 417\nSection Inlines / Emphasis and strong emphasis\n"); } // Indefinite levels of nesting are possible: [Test] - public void InlinesEmphasisAndStrongEmphasis_Example417() + public void InlinesEmphasisAndStrongEmphasis_Example418() { - // Example 417 + // Example 418 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -10086,13 +10107,13 @@ public void InlinesEmphasisAndStrongEmphasis_Example417() // Should be rendered as: //

foo bar baz bim bop

- TestParser.TestSpec("*foo **bar *baz* bim** bop*", "

foo bar baz bim bop

", "", context: "Example 417\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("*foo **bar *baz* bim** bop*", "

foo bar baz bim bop

", "", context: "Example 418\nSection Inlines / Emphasis and strong emphasis\n"); } [Test] - public void InlinesEmphasisAndStrongEmphasis_Example418() + public void InlinesEmphasisAndStrongEmphasis_Example419() { - // Example 418 + // Example 419 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -10101,14 +10122,14 @@ public void InlinesEmphasisAndStrongEmphasis_Example418() // Should be rendered as: //

foo bar

- TestParser.TestSpec("*foo [*bar*](/url)*", "

foo bar

", "", context: "Example 418\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("*foo [*bar*](/url)*", "

foo bar

", "", context: "Example 419\nSection Inlines / Emphasis and strong emphasis\n"); } // There can be no empty emphasis or strong emphasis: [Test] - public void InlinesEmphasisAndStrongEmphasis_Example419() + public void InlinesEmphasisAndStrongEmphasis_Example420() { - // Example 419 + // Example 420 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -10117,13 +10138,13 @@ public void InlinesEmphasisAndStrongEmphasis_Example419() // Should be rendered as: //

** is not an empty emphasis

- TestParser.TestSpec("** is not an empty emphasis", "

** is not an empty emphasis

", "", context: "Example 419\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("** is not an empty emphasis", "

** is not an empty emphasis

", "", context: "Example 420\nSection Inlines / Emphasis and strong emphasis\n"); } [Test] - public void InlinesEmphasisAndStrongEmphasis_Example420() + public void InlinesEmphasisAndStrongEmphasis_Example421() { - // Example 420 + // Example 421 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -10132,7 +10153,7 @@ public void InlinesEmphasisAndStrongEmphasis_Example420() // Should be rendered as: //

**** is not an empty strong emphasis

- TestParser.TestSpec("**** is not an empty strong emphasis", "

**** is not an empty strong emphasis

", "", context: "Example 420\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("**** is not an empty strong emphasis", "

**** is not an empty strong emphasis

", "", context: "Example 421\nSection Inlines / Emphasis and strong emphasis\n"); } // Rule 10: @@ -10140,9 +10161,9 @@ public void InlinesEmphasisAndStrongEmphasis_Example420() // Any nonempty sequence of inline elements can be the contents of an // strongly emphasized span. [Test] - public void InlinesEmphasisAndStrongEmphasis_Example421() + public void InlinesEmphasisAndStrongEmphasis_Example422() { - // Example 421 + // Example 422 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -10151,13 +10172,13 @@ public void InlinesEmphasisAndStrongEmphasis_Example421() // Should be rendered as: //

foo bar

- TestParser.TestSpec("**foo [bar](/url)**", "

foo bar

", "", context: "Example 421\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("**foo [bar](/url)**", "

foo bar

", "", context: "Example 422\nSection Inlines / Emphasis and strong emphasis\n"); } [Test] - public void InlinesEmphasisAndStrongEmphasis_Example422() + public void InlinesEmphasisAndStrongEmphasis_Example423() { - // Example 422 + // Example 423 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -10168,15 +10189,15 @@ public void InlinesEmphasisAndStrongEmphasis_Example422() //

foo // bar

- TestParser.TestSpec("**foo\nbar**", "

foo\nbar

", "", context: "Example 422\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("**foo\nbar**", "

foo\nbar

", "", context: "Example 423\nSection Inlines / Emphasis and strong emphasis\n"); } // In particular, emphasis and strong emphasis can be nested // inside strong emphasis: [Test] - public void InlinesEmphasisAndStrongEmphasis_Example423() + public void InlinesEmphasisAndStrongEmphasis_Example424() { - // Example 423 + // Example 424 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -10185,13 +10206,13 @@ public void InlinesEmphasisAndStrongEmphasis_Example423() // Should be rendered as: //

foo bar baz

- TestParser.TestSpec("__foo _bar_ baz__", "

foo bar baz

", "", context: "Example 423\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("__foo _bar_ baz__", "

foo bar baz

", "", context: "Example 424\nSection Inlines / Emphasis and strong emphasis\n"); } [Test] - public void InlinesEmphasisAndStrongEmphasis_Example424() + public void InlinesEmphasisAndStrongEmphasis_Example425() { - // Example 424 + // Example 425 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -10200,13 +10221,13 @@ public void InlinesEmphasisAndStrongEmphasis_Example424() // Should be rendered as: //

foo bar baz

- TestParser.TestSpec("__foo __bar__ baz__", "

foo bar baz

", "", context: "Example 424\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("__foo __bar__ baz__", "

foo bar baz

", "", context: "Example 425\nSection Inlines / Emphasis and strong emphasis\n"); } [Test] - public void InlinesEmphasisAndStrongEmphasis_Example425() + public void InlinesEmphasisAndStrongEmphasis_Example426() { - // Example 425 + // Example 426 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -10215,13 +10236,13 @@ public void InlinesEmphasisAndStrongEmphasis_Example425() // Should be rendered as: //

foo bar

- TestParser.TestSpec("____foo__ bar__", "

foo bar

", "", context: "Example 425\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("____foo__ bar__", "

foo bar

", "", context: "Example 426\nSection Inlines / Emphasis and strong emphasis\n"); } [Test] - public void InlinesEmphasisAndStrongEmphasis_Example426() + public void InlinesEmphasisAndStrongEmphasis_Example427() { - // Example 426 + // Example 427 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -10230,13 +10251,13 @@ public void InlinesEmphasisAndStrongEmphasis_Example426() // Should be rendered as: //

foo bar

- TestParser.TestSpec("**foo **bar****", "

foo bar

", "", context: "Example 426\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("**foo **bar****", "

foo bar

", "", context: "Example 427\nSection Inlines / Emphasis and strong emphasis\n"); } [Test] - public void InlinesEmphasisAndStrongEmphasis_Example427() + public void InlinesEmphasisAndStrongEmphasis_Example428() { - // Example 427 + // Example 428 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -10245,13 +10266,13 @@ public void InlinesEmphasisAndStrongEmphasis_Example427() // Should be rendered as: //

foo bar baz

- TestParser.TestSpec("**foo *bar* baz**", "

foo bar baz

", "", context: "Example 427\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("**foo *bar* baz**", "

foo bar baz

", "", context: "Example 428\nSection Inlines / Emphasis and strong emphasis\n"); } [Test] - public void InlinesEmphasisAndStrongEmphasis_Example428() + public void InlinesEmphasisAndStrongEmphasis_Example429() { - // Example 428 + // Example 429 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -10260,13 +10281,13 @@ public void InlinesEmphasisAndStrongEmphasis_Example428() // Should be rendered as: //

foobarbaz

- TestParser.TestSpec("**foo*bar*baz**", "

foobarbaz

", "", context: "Example 428\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("**foo*bar*baz**", "

foobarbaz

", "", context: "Example 429\nSection Inlines / Emphasis and strong emphasis\n"); } [Test] - public void InlinesEmphasisAndStrongEmphasis_Example429() + public void InlinesEmphasisAndStrongEmphasis_Example430() { - // Example 429 + // Example 430 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -10275,13 +10296,13 @@ public void InlinesEmphasisAndStrongEmphasis_Example429() // Should be rendered as: //

foo bar

- TestParser.TestSpec("***foo* bar**", "

foo bar

", "", context: "Example 429\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("***foo* bar**", "

foo bar

", "", context: "Example 430\nSection Inlines / Emphasis and strong emphasis\n"); } [Test] - public void InlinesEmphasisAndStrongEmphasis_Example430() + public void InlinesEmphasisAndStrongEmphasis_Example431() { - // Example 430 + // Example 431 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -10290,14 +10311,14 @@ public void InlinesEmphasisAndStrongEmphasis_Example430() // Should be rendered as: //

foo bar

- TestParser.TestSpec("**foo *bar***", "

foo bar

", "", context: "Example 430\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("**foo *bar***", "

foo bar

", "", context: "Example 431\nSection Inlines / Emphasis and strong emphasis\n"); } // Indefinite levels of nesting are possible: [Test] - public void InlinesEmphasisAndStrongEmphasis_Example431() + public void InlinesEmphasisAndStrongEmphasis_Example432() { - // Example 431 + // Example 432 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -10308,13 +10329,13 @@ public void InlinesEmphasisAndStrongEmphasis_Example431() //

foo bar baz // bim bop

- TestParser.TestSpec("**foo *bar **baz**\nbim* bop**", "

foo bar baz\nbim bop

", "", context: "Example 431\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("**foo *bar **baz**\nbim* bop**", "

foo bar baz\nbim bop

", "", context: "Example 432\nSection Inlines / Emphasis and strong emphasis\n"); } [Test] - public void InlinesEmphasisAndStrongEmphasis_Example432() + public void InlinesEmphasisAndStrongEmphasis_Example433() { - // Example 432 + // Example 433 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -10323,14 +10344,14 @@ public void InlinesEmphasisAndStrongEmphasis_Example432() // Should be rendered as: //

foo bar

- TestParser.TestSpec("**foo [*bar*](/url)**", "

foo bar

", "", context: "Example 432\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("**foo [*bar*](/url)**", "

foo bar

", "", context: "Example 433\nSection Inlines / Emphasis and strong emphasis\n"); } // There can be no empty emphasis or strong emphasis: [Test] - public void InlinesEmphasisAndStrongEmphasis_Example433() + public void InlinesEmphasisAndStrongEmphasis_Example434() { - // Example 433 + // Example 434 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -10339,13 +10360,13 @@ public void InlinesEmphasisAndStrongEmphasis_Example433() // Should be rendered as: //

__ is not an empty emphasis

- TestParser.TestSpec("__ is not an empty emphasis", "

__ is not an empty emphasis

", "", context: "Example 433\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("__ is not an empty emphasis", "

__ is not an empty emphasis

", "", context: "Example 434\nSection Inlines / Emphasis and strong emphasis\n"); } [Test] - public void InlinesEmphasisAndStrongEmphasis_Example434() + public void InlinesEmphasisAndStrongEmphasis_Example435() { - // Example 434 + // Example 435 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -10354,14 +10375,14 @@ public void InlinesEmphasisAndStrongEmphasis_Example434() // Should be rendered as: //

____ is not an empty strong emphasis

- TestParser.TestSpec("____ is not an empty strong emphasis", "

____ is not an empty strong emphasis

", "", context: "Example 434\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("____ is not an empty strong emphasis", "

____ is not an empty strong emphasis

", "", context: "Example 435\nSection Inlines / Emphasis and strong emphasis\n"); } // Rule 11: [Test] - public void InlinesEmphasisAndStrongEmphasis_Example435() + public void InlinesEmphasisAndStrongEmphasis_Example436() { - // Example 435 + // Example 436 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -10370,13 +10391,13 @@ public void InlinesEmphasisAndStrongEmphasis_Example435() // Should be rendered as: //

foo ***

- TestParser.TestSpec("foo ***", "

foo ***

", "", context: "Example 435\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("foo ***", "

foo ***

", "", context: "Example 436\nSection Inlines / Emphasis and strong emphasis\n"); } [Test] - public void InlinesEmphasisAndStrongEmphasis_Example436() + public void InlinesEmphasisAndStrongEmphasis_Example437() { - // Example 436 + // Example 437 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -10385,13 +10406,13 @@ public void InlinesEmphasisAndStrongEmphasis_Example436() // Should be rendered as: //

foo *

- TestParser.TestSpec("foo *\\**", "

foo *

", "", context: "Example 436\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("foo *\\**", "

foo *

", "", context: "Example 437\nSection Inlines / Emphasis and strong emphasis\n"); } [Test] - public void InlinesEmphasisAndStrongEmphasis_Example437() + public void InlinesEmphasisAndStrongEmphasis_Example438() { - // Example 437 + // Example 438 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -10400,13 +10421,13 @@ public void InlinesEmphasisAndStrongEmphasis_Example437() // Should be rendered as: //

foo _

- TestParser.TestSpec("foo *_*", "

foo _

", "", context: "Example 437\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("foo *_*", "

foo _

", "", context: "Example 438\nSection Inlines / Emphasis and strong emphasis\n"); } [Test] - public void InlinesEmphasisAndStrongEmphasis_Example438() + public void InlinesEmphasisAndStrongEmphasis_Example439() { - // Example 438 + // Example 439 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -10415,13 +10436,13 @@ public void InlinesEmphasisAndStrongEmphasis_Example438() // Should be rendered as: //

foo *****

- TestParser.TestSpec("foo *****", "

foo *****

", "", context: "Example 438\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("foo *****", "

foo *****

", "", context: "Example 439\nSection Inlines / Emphasis and strong emphasis\n"); } [Test] - public void InlinesEmphasisAndStrongEmphasis_Example439() + public void InlinesEmphasisAndStrongEmphasis_Example440() { - // Example 439 + // Example 440 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -10430,13 +10451,13 @@ public void InlinesEmphasisAndStrongEmphasis_Example439() // Should be rendered as: //

foo *

- TestParser.TestSpec("foo **\\***", "

foo *

", "", context: "Example 439\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("foo **\\***", "

foo *

", "", context: "Example 440\nSection Inlines / Emphasis and strong emphasis\n"); } [Test] - public void InlinesEmphasisAndStrongEmphasis_Example440() + public void InlinesEmphasisAndStrongEmphasis_Example441() { - // Example 440 + // Example 441 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -10445,16 +10466,16 @@ public void InlinesEmphasisAndStrongEmphasis_Example440() // Should be rendered as: //

foo _

- TestParser.TestSpec("foo **_**", "

foo _

", "", context: "Example 440\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("foo **_**", "

foo _

", "", context: "Example 441\nSection Inlines / Emphasis and strong emphasis\n"); } // Note that when delimiters do not match evenly, Rule 11 determines // that the excess literal `*` characters will appear outside of the // emphasis, rather than inside it: [Test] - public void InlinesEmphasisAndStrongEmphasis_Example441() + public void InlinesEmphasisAndStrongEmphasis_Example442() { - // Example 441 + // Example 442 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -10463,13 +10484,13 @@ public void InlinesEmphasisAndStrongEmphasis_Example441() // Should be rendered as: //

*foo

- TestParser.TestSpec("**foo*", "

*foo

", "", context: "Example 441\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("**foo*", "

*foo

", "", context: "Example 442\nSection Inlines / Emphasis and strong emphasis\n"); } [Test] - public void InlinesEmphasisAndStrongEmphasis_Example442() + public void InlinesEmphasisAndStrongEmphasis_Example443() { - // Example 442 + // Example 443 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -10478,13 +10499,13 @@ public void InlinesEmphasisAndStrongEmphasis_Example442() // Should be rendered as: //

foo*

- TestParser.TestSpec("*foo**", "

foo*

", "", context: "Example 442\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("*foo**", "

foo*

", "", context: "Example 443\nSection Inlines / Emphasis and strong emphasis\n"); } [Test] - public void InlinesEmphasisAndStrongEmphasis_Example443() + public void InlinesEmphasisAndStrongEmphasis_Example444() { - // Example 443 + // Example 444 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -10493,13 +10514,13 @@ public void InlinesEmphasisAndStrongEmphasis_Example443() // Should be rendered as: //

*foo

- TestParser.TestSpec("***foo**", "

*foo

", "", context: "Example 443\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("***foo**", "

*foo

", "", context: "Example 444\nSection Inlines / Emphasis and strong emphasis\n"); } [Test] - public void InlinesEmphasisAndStrongEmphasis_Example444() + public void InlinesEmphasisAndStrongEmphasis_Example445() { - // Example 444 + // Example 445 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -10508,13 +10529,13 @@ public void InlinesEmphasisAndStrongEmphasis_Example444() // Should be rendered as: //

***foo

- TestParser.TestSpec("****foo*", "

***foo

", "", context: "Example 444\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("****foo*", "

***foo

", "", context: "Example 445\nSection Inlines / Emphasis and strong emphasis\n"); } [Test] - public void InlinesEmphasisAndStrongEmphasis_Example445() + public void InlinesEmphasisAndStrongEmphasis_Example446() { - // Example 445 + // Example 446 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -10523,13 +10544,13 @@ public void InlinesEmphasisAndStrongEmphasis_Example445() // Should be rendered as: //

foo*

- TestParser.TestSpec("**foo***", "

foo*

", "", context: "Example 445\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("**foo***", "

foo*

", "", context: "Example 446\nSection Inlines / Emphasis and strong emphasis\n"); } [Test] - public void InlinesEmphasisAndStrongEmphasis_Example446() + public void InlinesEmphasisAndStrongEmphasis_Example447() { - // Example 446 + // Example 447 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -10538,14 +10559,14 @@ public void InlinesEmphasisAndStrongEmphasis_Example446() // Should be rendered as: //

foo***

- TestParser.TestSpec("*foo****", "

foo***

", "", context: "Example 446\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("*foo****", "

foo***

", "", context: "Example 447\nSection Inlines / Emphasis and strong emphasis\n"); } // Rule 12: [Test] - public void InlinesEmphasisAndStrongEmphasis_Example447() + public void InlinesEmphasisAndStrongEmphasis_Example448() { - // Example 447 + // Example 448 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -10554,13 +10575,13 @@ public void InlinesEmphasisAndStrongEmphasis_Example447() // Should be rendered as: //

foo ___

- TestParser.TestSpec("foo ___", "

foo ___

", "", context: "Example 447\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("foo ___", "

foo ___

", "", context: "Example 448\nSection Inlines / Emphasis and strong emphasis\n"); } [Test] - public void InlinesEmphasisAndStrongEmphasis_Example448() + public void InlinesEmphasisAndStrongEmphasis_Example449() { - // Example 448 + // Example 449 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -10569,13 +10590,13 @@ public void InlinesEmphasisAndStrongEmphasis_Example448() // Should be rendered as: //

foo _

- TestParser.TestSpec("foo _\\__", "

foo _

", "", context: "Example 448\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("foo _\\__", "

foo _

", "", context: "Example 449\nSection Inlines / Emphasis and strong emphasis\n"); } [Test] - public void InlinesEmphasisAndStrongEmphasis_Example449() + public void InlinesEmphasisAndStrongEmphasis_Example450() { - // Example 449 + // Example 450 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -10584,13 +10605,13 @@ public void InlinesEmphasisAndStrongEmphasis_Example449() // Should be rendered as: //

foo *

- TestParser.TestSpec("foo _*_", "

foo *

", "", context: "Example 449\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("foo _*_", "

foo *

", "", context: "Example 450\nSection Inlines / Emphasis and strong emphasis\n"); } [Test] - public void InlinesEmphasisAndStrongEmphasis_Example450() + public void InlinesEmphasisAndStrongEmphasis_Example451() { - // Example 450 + // Example 451 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -10599,13 +10620,13 @@ public void InlinesEmphasisAndStrongEmphasis_Example450() // Should be rendered as: //

foo _____

- TestParser.TestSpec("foo _____", "

foo _____

", "", context: "Example 450\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("foo _____", "

foo _____

", "", context: "Example 451\nSection Inlines / Emphasis and strong emphasis\n"); } [Test] - public void InlinesEmphasisAndStrongEmphasis_Example451() + public void InlinesEmphasisAndStrongEmphasis_Example452() { - // Example 451 + // Example 452 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -10614,13 +10635,13 @@ public void InlinesEmphasisAndStrongEmphasis_Example451() // Should be rendered as: //

foo _

- TestParser.TestSpec("foo __\\___", "

foo _

", "", context: "Example 451\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("foo __\\___", "

foo _

", "", context: "Example 452\nSection Inlines / Emphasis and strong emphasis\n"); } [Test] - public void InlinesEmphasisAndStrongEmphasis_Example452() + public void InlinesEmphasisAndStrongEmphasis_Example453() { - // Example 452 + // Example 453 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -10629,13 +10650,13 @@ public void InlinesEmphasisAndStrongEmphasis_Example452() // Should be rendered as: //

foo *

- TestParser.TestSpec("foo __*__", "

foo *

", "", context: "Example 452\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("foo __*__", "

foo *

", "", context: "Example 453\nSection Inlines / Emphasis and strong emphasis\n"); } [Test] - public void InlinesEmphasisAndStrongEmphasis_Example453() + public void InlinesEmphasisAndStrongEmphasis_Example454() { - // Example 453 + // Example 454 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -10644,16 +10665,16 @@ public void InlinesEmphasisAndStrongEmphasis_Example453() // Should be rendered as: //

_foo

- TestParser.TestSpec("__foo_", "

_foo

", "", context: "Example 453\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("__foo_", "

_foo

", "", context: "Example 454\nSection Inlines / Emphasis and strong emphasis\n"); } // Note that when delimiters do not match evenly, Rule 12 determines // that the excess literal `_` characters will appear outside of the // emphasis, rather than inside it: [Test] - public void InlinesEmphasisAndStrongEmphasis_Example454() + public void InlinesEmphasisAndStrongEmphasis_Example455() { - // Example 454 + // Example 455 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -10662,13 +10683,13 @@ public void InlinesEmphasisAndStrongEmphasis_Example454() // Should be rendered as: //

foo_

- TestParser.TestSpec("_foo__", "

foo_

", "", context: "Example 454\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("_foo__", "

foo_

", "", context: "Example 455\nSection Inlines / Emphasis and strong emphasis\n"); } [Test] - public void InlinesEmphasisAndStrongEmphasis_Example455() + public void InlinesEmphasisAndStrongEmphasis_Example456() { - // Example 455 + // Example 456 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -10677,13 +10698,13 @@ public void InlinesEmphasisAndStrongEmphasis_Example455() // Should be rendered as: //

_foo

- TestParser.TestSpec("___foo__", "

_foo

", "", context: "Example 455\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("___foo__", "

_foo

", "", context: "Example 456\nSection Inlines / Emphasis and strong emphasis\n"); } [Test] - public void InlinesEmphasisAndStrongEmphasis_Example456() + public void InlinesEmphasisAndStrongEmphasis_Example457() { - // Example 456 + // Example 457 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -10692,13 +10713,13 @@ public void InlinesEmphasisAndStrongEmphasis_Example456() // Should be rendered as: //

___foo

- TestParser.TestSpec("____foo_", "

___foo

", "", context: "Example 456\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("____foo_", "

___foo

", "", context: "Example 457\nSection Inlines / Emphasis and strong emphasis\n"); } [Test] - public void InlinesEmphasisAndStrongEmphasis_Example457() + public void InlinesEmphasisAndStrongEmphasis_Example458() { - // Example 457 + // Example 458 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -10707,13 +10728,13 @@ public void InlinesEmphasisAndStrongEmphasis_Example457() // Should be rendered as: //

foo_

- TestParser.TestSpec("__foo___", "

foo_

", "", context: "Example 457\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("__foo___", "

foo_

", "", context: "Example 458\nSection Inlines / Emphasis and strong emphasis\n"); } [Test] - public void InlinesEmphasisAndStrongEmphasis_Example458() + public void InlinesEmphasisAndStrongEmphasis_Example459() { - // Example 458 + // Example 459 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -10722,15 +10743,15 @@ public void InlinesEmphasisAndStrongEmphasis_Example458() // Should be rendered as: //

foo___

- TestParser.TestSpec("_foo____", "

foo___

", "", context: "Example 458\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("_foo____", "

foo___

", "", context: "Example 459\nSection Inlines / Emphasis and strong emphasis\n"); } // Rule 13 implies that if you want emphasis nested directly inside // emphasis, you must use different delimiters: [Test] - public void InlinesEmphasisAndStrongEmphasis_Example459() + public void InlinesEmphasisAndStrongEmphasis_Example460() { - // Example 459 + // Example 460 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -10739,13 +10760,13 @@ public void InlinesEmphasisAndStrongEmphasis_Example459() // Should be rendered as: //

foo

- TestParser.TestSpec("**foo**", "

foo

", "", context: "Example 459\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("**foo**", "

foo

", "", context: "Example 460\nSection Inlines / Emphasis and strong emphasis\n"); } [Test] - public void InlinesEmphasisAndStrongEmphasis_Example460() + public void InlinesEmphasisAndStrongEmphasis_Example461() { - // Example 460 + // Example 461 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -10754,13 +10775,13 @@ public void InlinesEmphasisAndStrongEmphasis_Example460() // Should be rendered as: //

foo

- TestParser.TestSpec("*_foo_*", "

foo

", "", context: "Example 460\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("*_foo_*", "

foo

", "", context: "Example 461\nSection Inlines / Emphasis and strong emphasis\n"); } [Test] - public void InlinesEmphasisAndStrongEmphasis_Example461() + public void InlinesEmphasisAndStrongEmphasis_Example462() { - // Example 461 + // Example 462 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -10769,13 +10790,13 @@ public void InlinesEmphasisAndStrongEmphasis_Example461() // Should be rendered as: //

foo

- TestParser.TestSpec("__foo__", "

foo

", "", context: "Example 461\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("__foo__", "

foo

", "", context: "Example 462\nSection Inlines / Emphasis and strong emphasis\n"); } [Test] - public void InlinesEmphasisAndStrongEmphasis_Example462() + public void InlinesEmphasisAndStrongEmphasis_Example463() { - // Example 462 + // Example 463 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -10784,15 +10805,15 @@ public void InlinesEmphasisAndStrongEmphasis_Example462() // Should be rendered as: //

foo

- TestParser.TestSpec("_*foo*_", "

foo

", "", context: "Example 462\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("_*foo*_", "

foo

", "", context: "Example 463\nSection Inlines / Emphasis and strong emphasis\n"); } // However, strong emphasis within strong emphasis is possible without // switching delimiters: [Test] - public void InlinesEmphasisAndStrongEmphasis_Example463() + public void InlinesEmphasisAndStrongEmphasis_Example464() { - // Example 463 + // Example 464 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -10801,13 +10822,13 @@ public void InlinesEmphasisAndStrongEmphasis_Example463() // Should be rendered as: //

foo

- TestParser.TestSpec("****foo****", "

foo

", "", context: "Example 463\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("****foo****", "

foo

", "", context: "Example 464\nSection Inlines / Emphasis and strong emphasis\n"); } [Test] - public void InlinesEmphasisAndStrongEmphasis_Example464() + public void InlinesEmphasisAndStrongEmphasis_Example465() { - // Example 464 + // Example 465 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -10816,15 +10837,15 @@ public void InlinesEmphasisAndStrongEmphasis_Example464() // Should be rendered as: //

foo

- TestParser.TestSpec("____foo____", "

foo

", "", context: "Example 464\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("____foo____", "

foo

", "", context: "Example 465\nSection Inlines / Emphasis and strong emphasis\n"); } // Rule 13 can be applied to arbitrarily long sequences of // delimiters: [Test] - public void InlinesEmphasisAndStrongEmphasis_Example465() + public void InlinesEmphasisAndStrongEmphasis_Example466() { - // Example 465 + // Example 466 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -10833,14 +10854,14 @@ public void InlinesEmphasisAndStrongEmphasis_Example465() // Should be rendered as: //

foo

- TestParser.TestSpec("******foo******", "

foo

", "", context: "Example 465\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("******foo******", "

foo

", "", context: "Example 466\nSection Inlines / Emphasis and strong emphasis\n"); } // Rule 14: [Test] - public void InlinesEmphasisAndStrongEmphasis_Example466() + public void InlinesEmphasisAndStrongEmphasis_Example467() { - // Example 466 + // Example 467 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -10849,13 +10870,13 @@ public void InlinesEmphasisAndStrongEmphasis_Example466() // Should be rendered as: //

foo

- TestParser.TestSpec("***foo***", "

foo

", "", context: "Example 466\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("***foo***", "

foo

", "", context: "Example 467\nSection Inlines / Emphasis and strong emphasis\n"); } [Test] - public void InlinesEmphasisAndStrongEmphasis_Example467() + public void InlinesEmphasisAndStrongEmphasis_Example468() { - // Example 467 + // Example 468 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -10864,14 +10885,14 @@ public void InlinesEmphasisAndStrongEmphasis_Example467() // Should be rendered as: //

foo

- TestParser.TestSpec("_____foo_____", "

foo

", "", context: "Example 467\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("_____foo_____", "

foo

", "", context: "Example 468\nSection Inlines / Emphasis and strong emphasis\n"); } // Rule 15: [Test] - public void InlinesEmphasisAndStrongEmphasis_Example468() + public void InlinesEmphasisAndStrongEmphasis_Example469() { - // Example 468 + // Example 469 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -10880,13 +10901,13 @@ public void InlinesEmphasisAndStrongEmphasis_Example468() // Should be rendered as: //

foo _bar baz_

- TestParser.TestSpec("*foo _bar* baz_", "

foo _bar baz_

", "", context: "Example 468\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("*foo _bar* baz_", "

foo _bar baz_

", "", context: "Example 469\nSection Inlines / Emphasis and strong emphasis\n"); } [Test] - public void InlinesEmphasisAndStrongEmphasis_Example469() + public void InlinesEmphasisAndStrongEmphasis_Example470() { - // Example 469 + // Example 470 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -10895,14 +10916,14 @@ public void InlinesEmphasisAndStrongEmphasis_Example469() // Should be rendered as: //

foo bar *baz bim bam

- TestParser.TestSpec("*foo __bar *baz bim__ bam*", "

foo bar *baz bim bam

", "", context: "Example 469\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("*foo __bar *baz bim__ bam*", "

foo bar *baz bim bam

", "", context: "Example 470\nSection Inlines / Emphasis and strong emphasis\n"); } // Rule 16: [Test] - public void InlinesEmphasisAndStrongEmphasis_Example470() + public void InlinesEmphasisAndStrongEmphasis_Example471() { - // Example 470 + // Example 471 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -10911,13 +10932,13 @@ public void InlinesEmphasisAndStrongEmphasis_Example470() // Should be rendered as: //

**foo bar baz

- TestParser.TestSpec("**foo **bar baz**", "

**foo bar baz

", "", context: "Example 470\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("**foo **bar baz**", "

**foo bar baz

", "", context: "Example 471\nSection Inlines / Emphasis and strong emphasis\n"); } [Test] - public void InlinesEmphasisAndStrongEmphasis_Example471() + public void InlinesEmphasisAndStrongEmphasis_Example472() { - // Example 471 + // Example 472 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -10926,14 +10947,14 @@ public void InlinesEmphasisAndStrongEmphasis_Example471() // Should be rendered as: //

*foo bar baz

- TestParser.TestSpec("*foo *bar baz*", "

*foo bar baz

", "", context: "Example 471\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("*foo *bar baz*", "

*foo bar baz

", "", context: "Example 472\nSection Inlines / Emphasis and strong emphasis\n"); } // Rule 17: [Test] - public void InlinesEmphasisAndStrongEmphasis_Example472() + public void InlinesEmphasisAndStrongEmphasis_Example473() { - // Example 472 + // Example 473 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -10942,13 +10963,13 @@ public void InlinesEmphasisAndStrongEmphasis_Example472() // Should be rendered as: //

*bar*

- TestParser.TestSpec("*[bar*](/url)", "

*bar*

", "", context: "Example 472\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("*[bar*](/url)", "

*bar*

", "", context: "Example 473\nSection Inlines / Emphasis and strong emphasis\n"); } [Test] - public void InlinesEmphasisAndStrongEmphasis_Example473() + public void InlinesEmphasisAndStrongEmphasis_Example474() { - // Example 473 + // Example 474 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -10957,13 +10978,13 @@ public void InlinesEmphasisAndStrongEmphasis_Example473() // Should be rendered as: //

_foo bar_

- TestParser.TestSpec("_foo [bar_](/url)", "

_foo bar_

", "", context: "Example 473\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("_foo [bar_](/url)", "

_foo bar_

", "", context: "Example 474\nSection Inlines / Emphasis and strong emphasis\n"); } [Test] - public void InlinesEmphasisAndStrongEmphasis_Example474() + public void InlinesEmphasisAndStrongEmphasis_Example475() { - // Example 474 + // Example 475 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -10972,13 +10993,13 @@ public void InlinesEmphasisAndStrongEmphasis_Example474() // Should be rendered as: //

*

- TestParser.TestSpec("*", "

*

", "", context: "Example 474\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("*", "

*

", "", context: "Example 475\nSection Inlines / Emphasis and strong emphasis\n"); } [Test] - public void InlinesEmphasisAndStrongEmphasis_Example475() + public void InlinesEmphasisAndStrongEmphasis_Example476() { - // Example 475 + // Example 476 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -10987,13 +11008,13 @@ public void InlinesEmphasisAndStrongEmphasis_Example475() // Should be rendered as: //

**

- TestParser.TestSpec("**", "

**

", "", context: "Example 475\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("**", "

**

", "", context: "Example 476\nSection Inlines / Emphasis and strong emphasis\n"); } [Test] - public void InlinesEmphasisAndStrongEmphasis_Example476() + public void InlinesEmphasisAndStrongEmphasis_Example477() { - // Example 476 + // Example 477 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -11002,13 +11023,13 @@ public void InlinesEmphasisAndStrongEmphasis_Example476() // Should be rendered as: //

__

- TestParser.TestSpec("__", "

__

", "", context: "Example 476\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("__", "

__

", "", context: "Example 477\nSection Inlines / Emphasis and strong emphasis\n"); } [Test] - public void InlinesEmphasisAndStrongEmphasis_Example477() + public void InlinesEmphasisAndStrongEmphasis_Example478() { - // Example 477 + // Example 478 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -11017,13 +11038,13 @@ public void InlinesEmphasisAndStrongEmphasis_Example477() // Should be rendered as: //

a *

- TestParser.TestSpec("*a `*`*", "

a *

", "", context: "Example 477\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("*a `*`*", "

a *

", "", context: "Example 478\nSection Inlines / Emphasis and strong emphasis\n"); } [Test] - public void InlinesEmphasisAndStrongEmphasis_Example478() + public void InlinesEmphasisAndStrongEmphasis_Example479() { - // Example 478 + // Example 479 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: @@ -11032,37 +11053,37 @@ public void InlinesEmphasisAndStrongEmphasis_Example478() // Should be rendered as: //

a _

- TestParser.TestSpec("_a `_`_", "

a _

", "", context: "Example 478\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("_a `_`_", "

a _

", "", context: "Example 479\nSection Inlines / Emphasis and strong emphasis\n"); } [Test] - public void InlinesEmphasisAndStrongEmphasis_Example479() + public void InlinesEmphasisAndStrongEmphasis_Example480() { - // Example 479 + // Example 480 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: - // **a + // **a // // Should be rendered as: - //

**ahttp://foo.bar/?q=**

+ //

**ahttps://foo.bar/?q=**

- TestParser.TestSpec("**a", "

**ahttp://foo.bar/?q=**

", "", context: "Example 479\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("**a", "

**ahttps://foo.bar/?q=**

", "", context: "Example 480\nSection Inlines / Emphasis and strong emphasis\n"); } [Test] - public void InlinesEmphasisAndStrongEmphasis_Example480() + public void InlinesEmphasisAndStrongEmphasis_Example481() { - // Example 480 + // Example 481 // Section: Inlines / Emphasis and strong emphasis // // The following Markdown: - // __a + // __a // // Should be rendered as: - //

__ahttp://foo.bar/?q=__

+ //

__ahttps://foo.bar/?q=__

- TestParser.TestSpec("__a", "

__ahttp://foo.bar/?q=__

", "", context: "Example 480\nSection Inlines / Emphasis and strong emphasis\n"); + TestParser.TestSpec("__a", "

__ahttps://foo.bar/?q=__

", "", context: "Example 481\nSection Inlines / Emphasis and strong emphasis\n"); } } @@ -11149,9 +11170,9 @@ public class TestInlinesLinks // // Here is a simple inline link: [Test] - public void InlinesLinks_Example481() + public void InlinesLinks_Example482() { - // Example 481 + // Example 482 // Section: Inlines / Links // // The following Markdown: @@ -11160,15 +11181,15 @@ public void InlinesLinks_Example481() // Should be rendered as: //

link

- TestParser.TestSpec("[link](/uri \"title\")", "

link

", "", context: "Example 481\nSection Inlines / Links\n"); + TestParser.TestSpec("[link](/uri \"title\")", "

link

", "", context: "Example 482\nSection Inlines / Links\n"); } // The title, the link text and even // the destination may be omitted: [Test] - public void InlinesLinks_Example482() + public void InlinesLinks_Example483() { - // Example 482 + // Example 483 // Section: Inlines / Links // // The following Markdown: @@ -11177,13 +11198,13 @@ public void InlinesLinks_Example482() // Should be rendered as: //

link

- TestParser.TestSpec("[link](/uri)", "

link

", "", context: "Example 482\nSection Inlines / Links\n"); + TestParser.TestSpec("[link](/uri)", "

link

", "", context: "Example 483\nSection Inlines / Links\n"); } [Test] - public void InlinesLinks_Example483() + public void InlinesLinks_Example484() { - // Example 483 + // Example 484 // Section: Inlines / Links // // The following Markdown: @@ -11192,13 +11213,13 @@ public void InlinesLinks_Example483() // Should be rendered as: //

- TestParser.TestSpec("[](./target.md)", "

", "", context: "Example 483\nSection Inlines / Links\n"); + TestParser.TestSpec("[](./target.md)", "

", "", context: "Example 484\nSection Inlines / Links\n"); } [Test] - public void InlinesLinks_Example484() + public void InlinesLinks_Example485() { - // Example 484 + // Example 485 // Section: Inlines / Links // // The following Markdown: @@ -11207,13 +11228,13 @@ public void InlinesLinks_Example484() // Should be rendered as: //

link

- TestParser.TestSpec("[link]()", "

link

", "", context: "Example 484\nSection Inlines / Links\n"); + TestParser.TestSpec("[link]()", "

link

", "", context: "Example 485\nSection Inlines / Links\n"); } [Test] - public void InlinesLinks_Example485() + public void InlinesLinks_Example486() { - // Example 485 + // Example 486 // Section: Inlines / Links // // The following Markdown: @@ -11222,13 +11243,13 @@ public void InlinesLinks_Example485() // Should be rendered as: //

link

- TestParser.TestSpec("[link](<>)", "

link

", "", context: "Example 485\nSection Inlines / Links\n"); + TestParser.TestSpec("[link](<>)", "

link

", "", context: "Example 486\nSection Inlines / Links\n"); } [Test] - public void InlinesLinks_Example486() + public void InlinesLinks_Example487() { - // Example 486 + // Example 487 // Section: Inlines / Links // // The following Markdown: @@ -11237,15 +11258,15 @@ public void InlinesLinks_Example486() // Should be rendered as: //

- TestParser.TestSpec("[]()", "

", "", context: "Example 486\nSection Inlines / Links\n"); + TestParser.TestSpec("[]()", "

", "", context: "Example 487\nSection Inlines / Links\n"); } // The destination can only contain spaces if it is // enclosed in pointy brackets: [Test] - public void InlinesLinks_Example487() + public void InlinesLinks_Example488() { - // Example 487 + // Example 488 // Section: Inlines / Links // // The following Markdown: @@ -11254,13 +11275,13 @@ public void InlinesLinks_Example487() // Should be rendered as: //

[link](/my uri)

- TestParser.TestSpec("[link](/my uri)", "

[link](/my uri)

", "", context: "Example 487\nSection Inlines / Links\n"); + TestParser.TestSpec("[link](/my uri)", "

[link](/my uri)

", "", context: "Example 488\nSection Inlines / Links\n"); } [Test] - public void InlinesLinks_Example488() + public void InlinesLinks_Example489() { - // Example 488 + // Example 489 // Section: Inlines / Links // // The following Markdown: @@ -11269,15 +11290,15 @@ public void InlinesLinks_Example488() // Should be rendered as: //

link

- TestParser.TestSpec("[link]()", "

link

", "", context: "Example 488\nSection Inlines / Links\n"); + TestParser.TestSpec("[link]()", "

link

", "", context: "Example 489\nSection Inlines / Links\n"); } // The destination cannot contain line endings, // even if enclosed in pointy brackets: [Test] - public void InlinesLinks_Example489() + public void InlinesLinks_Example490() { - // Example 489 + // Example 490 // Section: Inlines / Links // // The following Markdown: @@ -11288,13 +11309,13 @@ public void InlinesLinks_Example489() //

[link](foo // bar)

- TestParser.TestSpec("[link](foo\nbar)", "

[link](foo\nbar)

", "", context: "Example 489\nSection Inlines / Links\n"); + TestParser.TestSpec("[link](foo\nbar)", "

[link](foo\nbar)

", "", context: "Example 490\nSection Inlines / Links\n"); } [Test] - public void InlinesLinks_Example490() + public void InlinesLinks_Example491() { - // Example 490 + // Example 491 // Section: Inlines / Links // // The following Markdown: @@ -11305,15 +11326,15 @@ public void InlinesLinks_Example490() //

[link]()

- TestParser.TestSpec("[link]()", "

[link]()

", "", context: "Example 490\nSection Inlines / Links\n"); + TestParser.TestSpec("[link]()", "

[link]()

", "", context: "Example 491\nSection Inlines / Links\n"); } // The destination can contain `)` if it is enclosed // in pointy brackets: [Test] - public void InlinesLinks_Example491() + public void InlinesLinks_Example492() { - // Example 491 + // Example 492 // Section: Inlines / Links // // The following Markdown: @@ -11322,14 +11343,14 @@ public void InlinesLinks_Example491() // Should be rendered as: //

a

- TestParser.TestSpec("[a]()", "

a

", "", context: "Example 491\nSection Inlines / Links\n"); + TestParser.TestSpec("[a]()", "

a

", "", context: "Example 492\nSection Inlines / Links\n"); } // Pointy brackets that enclose links must be unescaped: [Test] - public void InlinesLinks_Example492() + public void InlinesLinks_Example493() { - // Example 492 + // Example 493 // Section: Inlines / Links // // The following Markdown: @@ -11338,15 +11359,15 @@ public void InlinesLinks_Example492() // Should be rendered as: //

[link](<foo>)

- TestParser.TestSpec("[link]()", "

[link](<foo>)

", "", context: "Example 492\nSection Inlines / Links\n"); + TestParser.TestSpec("[link]()", "

[link](<foo>)

", "", context: "Example 493\nSection Inlines / Links\n"); } // These are not links, because the opening pointy bracket // is not matched properly: [Test] - public void InlinesLinks_Example493() + public void InlinesLinks_Example494() { - // Example 493 + // Example 494 // Section: Inlines / Links // // The following Markdown: @@ -11359,14 +11380,14 @@ public void InlinesLinks_Example493() // [a](<b)c> // [a](c)

- TestParser.TestSpec("[a](\n[a](c)", "

[a](<b)c\n[a](<b)c>\n[a](c)

", "", context: "Example 493\nSection Inlines / Links\n"); + TestParser.TestSpec("[a](\n[a](c)", "

[a](<b)c\n[a](<b)c>\n[a](c)

", "", context: "Example 494\nSection Inlines / Links\n"); } // Parentheses inside the link destination may be escaped: [Test] - public void InlinesLinks_Example494() + public void InlinesLinks_Example495() { - // Example 494 + // Example 495 // Section: Inlines / Links // // The following Markdown: @@ -11375,15 +11396,15 @@ public void InlinesLinks_Example494() // Should be rendered as: //

link

- TestParser.TestSpec("[link](\\(foo\\))", "

link

", "", context: "Example 494\nSection Inlines / Links\n"); + TestParser.TestSpec("[link](\\(foo\\))", "

link

", "", context: "Example 495\nSection Inlines / Links\n"); } // Any number of parentheses are allowed without escaping, as long as they are // balanced: [Test] - public void InlinesLinks_Example495() + public void InlinesLinks_Example496() { - // Example 495 + // Example 496 // Section: Inlines / Links // // The following Markdown: @@ -11392,15 +11413,15 @@ public void InlinesLinks_Example495() // Should be rendered as: //

link

- TestParser.TestSpec("[link](foo(and(bar)))", "

link

", "", context: "Example 495\nSection Inlines / Links\n"); + TestParser.TestSpec("[link](foo(and(bar)))", "

link

", "", context: "Example 496\nSection Inlines / Links\n"); } // However, if you have unbalanced parentheses, you need to escape or use the // `<...>` form: [Test] - public void InlinesLinks_Example496() + public void InlinesLinks_Example497() { - // Example 496 + // Example 497 // Section: Inlines / Links // // The following Markdown: @@ -11409,13 +11430,13 @@ public void InlinesLinks_Example496() // Should be rendered as: //

[link](foo(and(bar))

- TestParser.TestSpec("[link](foo(and(bar))", "

[link](foo(and(bar))

", "", context: "Example 496\nSection Inlines / Links\n"); + TestParser.TestSpec("[link](foo(and(bar))", "

[link](foo(and(bar))

", "", context: "Example 497\nSection Inlines / Links\n"); } [Test] - public void InlinesLinks_Example497() + public void InlinesLinks_Example498() { - // Example 497 + // Example 498 // Section: Inlines / Links // // The following Markdown: @@ -11424,13 +11445,13 @@ public void InlinesLinks_Example497() // Should be rendered as: //

link

- TestParser.TestSpec("[link](foo\\(and\\(bar\\))", "

link

", "", context: "Example 497\nSection Inlines / Links\n"); + TestParser.TestSpec("[link](foo\\(and\\(bar\\))", "

link

", "", context: "Example 498\nSection Inlines / Links\n"); } [Test] - public void InlinesLinks_Example498() + public void InlinesLinks_Example499() { - // Example 498 + // Example 499 // Section: Inlines / Links // // The following Markdown: @@ -11439,15 +11460,15 @@ public void InlinesLinks_Example498() // Should be rendered as: //

link

- TestParser.TestSpec("[link]()", "

link

", "", context: "Example 498\nSection Inlines / Links\n"); + TestParser.TestSpec("[link]()", "

link

", "", context: "Example 499\nSection Inlines / Links\n"); } // Parentheses and other symbols can also be escaped, as usual // in Markdown: [Test] - public void InlinesLinks_Example499() + public void InlinesLinks_Example500() { - // Example 499 + // Example 500 // Section: Inlines / Links // // The following Markdown: @@ -11456,37 +11477,37 @@ public void InlinesLinks_Example499() // Should be rendered as: //

link

- TestParser.TestSpec("[link](foo\\)\\:)", "

link

", "", context: "Example 499\nSection Inlines / Links\n"); + TestParser.TestSpec("[link](foo\\)\\:)", "

link

", "", context: "Example 500\nSection Inlines / Links\n"); } // A link can contain fragment identifiers and queries: [Test] - public void InlinesLinks_Example500() + public void InlinesLinks_Example501() { - // Example 500 + // Example 501 // Section: Inlines / Links // // The following Markdown: // [link](#fragment) // - // [link](http://example.com#fragment) + // [link](https://example.com#fragment) // - // [link](http://example.com?foo=3#frag) + // [link](https://example.com?foo=3#frag) // // Should be rendered as: //

link

- //

link

- //

link

+ //

link

+ //

link

- TestParser.TestSpec("[link](#fragment)\n\n[link](http://example.com#fragment)\n\n[link](http://example.com?foo=3#frag)", "

link

\n

link

\n

link

", "", context: "Example 500\nSection Inlines / Links\n"); + TestParser.TestSpec("[link](#fragment)\n\n[link](https://example.com#fragment)\n\n[link](https://example.com?foo=3#frag)", "

link

\n

link

\n

link

", "", context: "Example 501\nSection Inlines / Links\n"); } // Note that a backslash before a non-escapable character is // just a backslash: [Test] - public void InlinesLinks_Example501() + public void InlinesLinks_Example502() { - // Example 501 + // Example 502 // Section: Inlines / Links // // The following Markdown: @@ -11495,7 +11516,7 @@ public void InlinesLinks_Example501() // Should be rendered as: //

link

- TestParser.TestSpec("[link](foo\\bar)", "

link

", "", context: "Example 501\nSection Inlines / Links\n"); + TestParser.TestSpec("[link](foo\\bar)", "

link

", "", context: "Example 502\nSection Inlines / Links\n"); } // URL-escaping should be left alone inside the destination, as all @@ -11507,9 +11528,9 @@ public void InlinesLinks_Example501() // HTML or other formats. Renderers may make different decisions // about how to escape or normalize URLs in the output. [Test] - public void InlinesLinks_Example502() + public void InlinesLinks_Example503() { - // Example 502 + // Example 503 // Section: Inlines / Links // // The following Markdown: @@ -11518,16 +11539,16 @@ public void InlinesLinks_Example502() // Should be rendered as: //

link

- TestParser.TestSpec("[link](foo%20bä)", "

link

", "", context: "Example 502\nSection Inlines / Links\n"); + TestParser.TestSpec("[link](foo%20bä)", "

link

", "", context: "Example 503\nSection Inlines / Links\n"); } // Note that, because titles can often be parsed as destinations, // if you try to omit the destination and keep the title, you'll // get unexpected results: [Test] - public void InlinesLinks_Example503() + public void InlinesLinks_Example504() { - // Example 503 + // Example 504 // Section: Inlines / Links // // The following Markdown: @@ -11536,14 +11557,14 @@ public void InlinesLinks_Example503() // Should be rendered as: //

link

- TestParser.TestSpec("[link](\"title\")", "

link

", "", context: "Example 503\nSection Inlines / Links\n"); + TestParser.TestSpec("[link](\"title\")", "

link

", "", context: "Example 504\nSection Inlines / Links\n"); } // Titles may be in single quotes, double quotes, or parentheses: [Test] - public void InlinesLinks_Example504() + public void InlinesLinks_Example505() { - // Example 504 + // Example 505 // Section: Inlines / Links // // The following Markdown: @@ -11556,15 +11577,15 @@ public void InlinesLinks_Example504() // link // link

- TestParser.TestSpec("[link](/url \"title\")\n[link](/url 'title')\n[link](/url (title))", "

link\nlink\nlink

", "", context: "Example 504\nSection Inlines / Links\n"); + TestParser.TestSpec("[link](/url \"title\")\n[link](/url 'title')\n[link](/url (title))", "

link\nlink\nlink

", "", context: "Example 505\nSection Inlines / Links\n"); } // Backslash escapes and entity and numeric character references // may be used in titles: [Test] - public void InlinesLinks_Example505() + public void InlinesLinks_Example506() { - // Example 505 + // Example 506 // Section: Inlines / Links // // The following Markdown: @@ -11573,16 +11594,16 @@ public void InlinesLinks_Example505() // Should be rendered as: //

link

- TestParser.TestSpec("[link](/url \"title \\\""\")", "

link

", "", context: "Example 505\nSection Inlines / Links\n"); + TestParser.TestSpec("[link](/url \"title \\\""\")", "

link

", "", context: "Example 506\nSection Inlines / Links\n"); } // Titles must be separated from the link using spaces, tabs, and up to one line // ending. // Other [Unicode whitespace] like non-breaking space doesn't work. [Test] - public void InlinesLinks_Example506() + public void InlinesLinks_Example507() { - // Example 506 + // Example 507 // Section: Inlines / Links // // The following Markdown: @@ -11591,14 +11612,14 @@ public void InlinesLinks_Example506() // Should be rendered as: //

link

- TestParser.TestSpec("[link](/url \"title\")", "

link

", "", context: "Example 506\nSection Inlines / Links\n"); + TestParser.TestSpec("[link](/url \"title\")", "

link

", "", context: "Example 507\nSection Inlines / Links\n"); } // Nested balanced quotes are not allowed without escaping: [Test] - public void InlinesLinks_Example507() + public void InlinesLinks_Example508() { - // Example 507 + // Example 508 // Section: Inlines / Links // // The following Markdown: @@ -11607,14 +11628,14 @@ public void InlinesLinks_Example507() // Should be rendered as: //

[link](/url "title "and" title")

- TestParser.TestSpec("[link](/url \"title \"and\" title\")", "

[link](/url "title "and" title")

", "", context: "Example 507\nSection Inlines / Links\n"); + TestParser.TestSpec("[link](/url \"title \"and\" title\")", "

[link](/url "title "and" title")

", "", context: "Example 508\nSection Inlines / Links\n"); } // But it is easy to work around this by using a different quote type: [Test] - public void InlinesLinks_Example508() + public void InlinesLinks_Example509() { - // Example 508 + // Example 509 // Section: Inlines / Links // // The following Markdown: @@ -11623,7 +11644,7 @@ public void InlinesLinks_Example508() // Should be rendered as: //

link

- TestParser.TestSpec("[link](/url 'title \"and\" title')", "

link

", "", context: "Example 508\nSection Inlines / Links\n"); + TestParser.TestSpec("[link](/url 'title \"and\" title')", "

link

", "", context: "Example 509\nSection Inlines / Links\n"); } // (Note: `Markdown.pl` did allow double quotes inside a double-quoted @@ -11644,9 +11665,9 @@ public void InlinesLinks_Example508() // Spaces, tabs, and up to one line ending is allowed around the destination and // title: [Test] - public void InlinesLinks_Example509() + public void InlinesLinks_Example510() { - // Example 509 + // Example 510 // Section: Inlines / Links // // The following Markdown: @@ -11656,15 +11677,15 @@ public void InlinesLinks_Example509() // Should be rendered as: //

link

- TestParser.TestSpec("[link]( /uri\n \"title\" )", "

link

", "", context: "Example 509\nSection Inlines / Links\n"); + TestParser.TestSpec("[link]( /uri\n \"title\" )", "

link

", "", context: "Example 510\nSection Inlines / Links\n"); } // But it is not allowed between the link text and the // following parenthesis: [Test] - public void InlinesLinks_Example510() + public void InlinesLinks_Example511() { - // Example 510 + // Example 511 // Section: Inlines / Links // // The following Markdown: @@ -11673,15 +11694,15 @@ public void InlinesLinks_Example510() // Should be rendered as: //

[link] (/uri)

- TestParser.TestSpec("[link] (/uri)", "

[link] (/uri)

", "", context: "Example 510\nSection Inlines / Links\n"); + TestParser.TestSpec("[link] (/uri)", "

[link] (/uri)

", "", context: "Example 511\nSection Inlines / Links\n"); } // The link text may contain balanced brackets, but not unbalanced ones, // unless they are escaped: [Test] - public void InlinesLinks_Example511() + public void InlinesLinks_Example512() { - // Example 511 + // Example 512 // Section: Inlines / Links // // The following Markdown: @@ -11690,13 +11711,13 @@ public void InlinesLinks_Example511() // Should be rendered as: //

link [foo [bar]]

- TestParser.TestSpec("[link [foo [bar]]](/uri)", "

link [foo [bar]]

", "", context: "Example 511\nSection Inlines / Links\n"); + TestParser.TestSpec("[link [foo [bar]]](/uri)", "

link [foo [bar]]

", "", context: "Example 512\nSection Inlines / Links\n"); } [Test] - public void InlinesLinks_Example512() + public void InlinesLinks_Example513() { - // Example 512 + // Example 513 // Section: Inlines / Links // // The following Markdown: @@ -11705,13 +11726,13 @@ public void InlinesLinks_Example512() // Should be rendered as: //

[link] bar](/uri)

- TestParser.TestSpec("[link] bar](/uri)", "

[link] bar](/uri)

", "", context: "Example 512\nSection Inlines / Links\n"); + TestParser.TestSpec("[link] bar](/uri)", "

[link] bar](/uri)

", "", context: "Example 513\nSection Inlines / Links\n"); } [Test] - public void InlinesLinks_Example513() + public void InlinesLinks_Example514() { - // Example 513 + // Example 514 // Section: Inlines / Links // // The following Markdown: @@ -11720,13 +11741,13 @@ public void InlinesLinks_Example513() // Should be rendered as: //

[link bar

- TestParser.TestSpec("[link [bar](/uri)", "

[link bar

", "", context: "Example 513\nSection Inlines / Links\n"); + TestParser.TestSpec("[link [bar](/uri)", "

[link bar

", "", context: "Example 514\nSection Inlines / Links\n"); } [Test] - public void InlinesLinks_Example514() + public void InlinesLinks_Example515() { - // Example 514 + // Example 515 // Section: Inlines / Links // // The following Markdown: @@ -11735,14 +11756,14 @@ public void InlinesLinks_Example514() // Should be rendered as: //

link [bar

- TestParser.TestSpec("[link \\[bar](/uri)", "

link [bar

", "", context: "Example 514\nSection Inlines / Links\n"); + TestParser.TestSpec("[link \\[bar](/uri)", "

link [bar

", "", context: "Example 515\nSection Inlines / Links\n"); } // The link text may contain inline content: [Test] - public void InlinesLinks_Example515() + public void InlinesLinks_Example516() { - // Example 515 + // Example 516 // Section: Inlines / Links // // The following Markdown: @@ -11751,13 +11772,13 @@ public void InlinesLinks_Example515() // Should be rendered as: //

link foo bar #

- TestParser.TestSpec("[link *foo **bar** `#`*](/uri)", "

link foo bar #

", "", context: "Example 515\nSection Inlines / Links\n"); + TestParser.TestSpec("[link *foo **bar** `#`*](/uri)", "

link foo bar #

", "", context: "Example 516\nSection Inlines / Links\n"); } [Test] - public void InlinesLinks_Example516() + public void InlinesLinks_Example517() { - // Example 516 + // Example 517 // Section: Inlines / Links // // The following Markdown: @@ -11766,14 +11787,14 @@ public void InlinesLinks_Example516() // Should be rendered as: //

moon

- TestParser.TestSpec("[![moon](moon.jpg)](/uri)", "

\"moon\"

", "", context: "Example 516\nSection Inlines / Links\n"); + TestParser.TestSpec("[![moon](moon.jpg)](/uri)", "

\"moon\"

", "", context: "Example 517\nSection Inlines / Links\n"); } // However, links may not contain other links, at any level of nesting. [Test] - public void InlinesLinks_Example517() + public void InlinesLinks_Example518() { - // Example 517 + // Example 518 // Section: Inlines / Links // // The following Markdown: @@ -11782,13 +11803,13 @@ public void InlinesLinks_Example517() // Should be rendered as: //

[foo bar](/uri)

- TestParser.TestSpec("[foo [bar](/uri)](/uri)", "

[foo bar](/uri)

", "", context: "Example 517\nSection Inlines / Links\n"); + TestParser.TestSpec("[foo [bar](/uri)](/uri)", "

[foo bar](/uri)

", "", context: "Example 518\nSection Inlines / Links\n"); } [Test] - public void InlinesLinks_Example518() + public void InlinesLinks_Example519() { - // Example 518 + // Example 519 // Section: Inlines / Links // // The following Markdown: @@ -11797,13 +11818,13 @@ public void InlinesLinks_Example518() // Should be rendered as: //

[foo [bar baz](/uri)](/uri)

- TestParser.TestSpec("[foo *[bar [baz](/uri)](/uri)*](/uri)", "

[foo [bar baz](/uri)](/uri)

", "", context: "Example 518\nSection Inlines / Links\n"); + TestParser.TestSpec("[foo *[bar [baz](/uri)](/uri)*](/uri)", "

[foo [bar baz](/uri)](/uri)

", "", context: "Example 519\nSection Inlines / Links\n"); } [Test] - public void InlinesLinks_Example519() + public void InlinesLinks_Example520() { - // Example 519 + // Example 520 // Section: Inlines / Links // // The following Markdown: @@ -11812,15 +11833,15 @@ public void InlinesLinks_Example519() // Should be rendered as: //

[foo](uri2)

- TestParser.TestSpec("![[[foo](uri1)](uri2)](uri3)", "

\"[foo](uri2)\"

", "", context: "Example 519\nSection Inlines / Links\n"); + TestParser.TestSpec("![[[foo](uri1)](uri2)](uri3)", "

\"[foo](uri2)\"

", "", context: "Example 520\nSection Inlines / Links\n"); } // These cases illustrate the precedence of link text grouping over // emphasis grouping: [Test] - public void InlinesLinks_Example520() + public void InlinesLinks_Example521() { - // Example 520 + // Example 521 // Section: Inlines / Links // // The following Markdown: @@ -11829,13 +11850,13 @@ public void InlinesLinks_Example520() // Should be rendered as: //

*foo*

- TestParser.TestSpec("*[foo*](/uri)", "

*foo*

", "", context: "Example 520\nSection Inlines / Links\n"); + TestParser.TestSpec("*[foo*](/uri)", "

*foo*

", "", context: "Example 521\nSection Inlines / Links\n"); } [Test] - public void InlinesLinks_Example521() + public void InlinesLinks_Example522() { - // Example 521 + // Example 522 // Section: Inlines / Links // // The following Markdown: @@ -11844,15 +11865,15 @@ public void InlinesLinks_Example521() // Should be rendered as: //

foo *bar

- TestParser.TestSpec("[foo *bar](baz*)", "

foo *bar

", "", context: "Example 521\nSection Inlines / Links\n"); + TestParser.TestSpec("[foo *bar](baz*)", "

foo *bar

", "", context: "Example 522\nSection Inlines / Links\n"); } // Note that brackets that *aren't* part of links do not take // precedence: [Test] - public void InlinesLinks_Example522() + public void InlinesLinks_Example523() { - // Example 522 + // Example 523 // Section: Inlines / Links // // The following Markdown: @@ -11861,15 +11882,15 @@ public void InlinesLinks_Example522() // Should be rendered as: //

foo [bar baz]

- TestParser.TestSpec("*foo [bar* baz]", "

foo [bar baz]

", "", context: "Example 522\nSection Inlines / Links\n"); + TestParser.TestSpec("*foo [bar* baz]", "

foo [bar baz]

", "", context: "Example 523\nSection Inlines / Links\n"); } // These cases illustrate the precedence of HTML tags, code spans, // and autolinks over link grouping: [Test] - public void InlinesLinks_Example523() + public void InlinesLinks_Example524() { - // Example 523 + // Example 524 // Section: Inlines / Links // // The following Markdown: @@ -11878,13 +11899,13 @@ public void InlinesLinks_Example523() // Should be rendered as: //

[foo

- TestParser.TestSpec("[foo ", "

[foo

", "", context: "Example 523\nSection Inlines / Links\n"); + TestParser.TestSpec("[foo ", "

[foo

", "", context: "Example 524\nSection Inlines / Links\n"); } [Test] - public void InlinesLinks_Example524() + public void InlinesLinks_Example525() { - // Example 524 + // Example 525 // Section: Inlines / Links // // The following Markdown: @@ -11893,22 +11914,22 @@ public void InlinesLinks_Example524() // Should be rendered as: //

[foo](/uri)

- TestParser.TestSpec("[foo`](/uri)`", "

[foo](/uri)

", "", context: "Example 524\nSection Inlines / Links\n"); + TestParser.TestSpec("[foo`](/uri)`", "

[foo](/uri)

", "", context: "Example 525\nSection Inlines / Links\n"); } [Test] - public void InlinesLinks_Example525() + public void InlinesLinks_Example526() { - // Example 525 + // Example 526 // Section: Inlines / Links // // The following Markdown: - // [foo + // [foo // // Should be rendered as: - //

[foohttp://example.com/?search=](uri)

+ //

[foohttps://example.com/?search=](uri)

- TestParser.TestSpec("[foo", "

[foohttp://example.com/?search=](uri)

", "", context: "Example 525\nSection Inlines / Links\n"); + TestParser.TestSpec("[foo", "

[foohttps://example.com/?search=](uri)

", "", context: "Example 526\nSection Inlines / Links\n"); } // There are three kinds of [reference link](@)s: @@ -11942,9 +11963,9 @@ public void InlinesLinks_Example525() // // Here is a simple example: [Test] - public void InlinesLinks_Example526() + public void InlinesLinks_Example527() { - // Example 526 + // Example 527 // Section: Inlines / Links // // The following Markdown: @@ -11955,7 +11976,7 @@ public void InlinesLinks_Example526() // Should be rendered as: //

foo

- TestParser.TestSpec("[foo][bar]\n\n[bar]: /url \"title\"", "

foo

", "", context: "Example 526\nSection Inlines / Links\n"); + TestParser.TestSpec("[foo][bar]\n\n[bar]: /url \"title\"", "

foo

", "", context: "Example 527\nSection Inlines / Links\n"); } // The rules for the [link text] are the same as with @@ -11964,9 +11985,9 @@ public void InlinesLinks_Example526() // The link text may contain balanced brackets, but not unbalanced ones, // unless they are escaped: [Test] - public void InlinesLinks_Example527() + public void InlinesLinks_Example528() { - // Example 527 + // Example 528 // Section: Inlines / Links // // The following Markdown: @@ -11977,13 +11998,13 @@ public void InlinesLinks_Example527() // Should be rendered as: //

link [foo [bar]]

- TestParser.TestSpec("[link [foo [bar]]][ref]\n\n[ref]: /uri", "

link [foo [bar]]

", "", context: "Example 527\nSection Inlines / Links\n"); + TestParser.TestSpec("[link [foo [bar]]][ref]\n\n[ref]: /uri", "

link [foo [bar]]

", "", context: "Example 528\nSection Inlines / Links\n"); } [Test] - public void InlinesLinks_Example528() + public void InlinesLinks_Example529() { - // Example 528 + // Example 529 // Section: Inlines / Links // // The following Markdown: @@ -11994,14 +12015,14 @@ public void InlinesLinks_Example528() // Should be rendered as: //

link [bar

- TestParser.TestSpec("[link \\[bar][ref]\n\n[ref]: /uri", "

link [bar

", "", context: "Example 528\nSection Inlines / Links\n"); + TestParser.TestSpec("[link \\[bar][ref]\n\n[ref]: /uri", "

link [bar

", "", context: "Example 529\nSection Inlines / Links\n"); } // The link text may contain inline content: [Test] - public void InlinesLinks_Example529() + public void InlinesLinks_Example530() { - // Example 529 + // Example 530 // Section: Inlines / Links // // The following Markdown: @@ -12012,13 +12033,13 @@ public void InlinesLinks_Example529() // Should be rendered as: //

link foo bar #

- TestParser.TestSpec("[link *foo **bar** `#`*][ref]\n\n[ref]: /uri", "

link foo bar #

", "", context: "Example 529\nSection Inlines / Links\n"); + TestParser.TestSpec("[link *foo **bar** `#`*][ref]\n\n[ref]: /uri", "

link foo bar #

", "", context: "Example 530\nSection Inlines / Links\n"); } [Test] - public void InlinesLinks_Example530() + public void InlinesLinks_Example531() { - // Example 530 + // Example 531 // Section: Inlines / Links // // The following Markdown: @@ -12029,14 +12050,14 @@ public void InlinesLinks_Example530() // Should be rendered as: //

moon

- TestParser.TestSpec("[![moon](moon.jpg)][ref]\n\n[ref]: /uri", "

\"moon\"

", "", context: "Example 530\nSection Inlines / Links\n"); + TestParser.TestSpec("[![moon](moon.jpg)][ref]\n\n[ref]: /uri", "

\"moon\"

", "", context: "Example 531\nSection Inlines / Links\n"); } // However, links may not contain other links, at any level of nesting. [Test] - public void InlinesLinks_Example531() + public void InlinesLinks_Example532() { - // Example 531 + // Example 532 // Section: Inlines / Links // // The following Markdown: @@ -12047,13 +12068,13 @@ public void InlinesLinks_Example531() // Should be rendered as: //

[foo bar]ref

- TestParser.TestSpec("[foo [bar](/uri)][ref]\n\n[ref]: /uri", "

[foo bar]ref

", "", context: "Example 531\nSection Inlines / Links\n"); + TestParser.TestSpec("[foo [bar](/uri)][ref]\n\n[ref]: /uri", "

[foo bar]ref

", "", context: "Example 532\nSection Inlines / Links\n"); } [Test] - public void InlinesLinks_Example532() + public void InlinesLinks_Example533() { - // Example 532 + // Example 533 // Section: Inlines / Links // // The following Markdown: @@ -12064,7 +12085,7 @@ public void InlinesLinks_Example532() // Should be rendered as: //

[foo bar baz]ref

- TestParser.TestSpec("[foo *bar [baz][ref]*][ref]\n\n[ref]: /uri", "

[foo bar baz]ref

", "", context: "Example 532\nSection Inlines / Links\n"); + TestParser.TestSpec("[foo *bar [baz][ref]*][ref]\n\n[ref]: /uri", "

[foo bar baz]ref

", "", context: "Example 533\nSection Inlines / Links\n"); } // (In the examples above, we have two [shortcut reference links] @@ -12073,9 +12094,9 @@ public void InlinesLinks_Example532() // The following cases illustrate the precedence of link text grouping over // emphasis grouping: [Test] - public void InlinesLinks_Example533() + public void InlinesLinks_Example534() { - // Example 533 + // Example 534 // Section: Inlines / Links // // The following Markdown: @@ -12086,13 +12107,13 @@ public void InlinesLinks_Example533() // Should be rendered as: //

*foo*

- TestParser.TestSpec("*[foo*][ref]\n\n[ref]: /uri", "

*foo*

", "", context: "Example 533\nSection Inlines / Links\n"); + TestParser.TestSpec("*[foo*][ref]\n\n[ref]: /uri", "

*foo*

", "", context: "Example 534\nSection Inlines / Links\n"); } [Test] - public void InlinesLinks_Example534() + public void InlinesLinks_Example535() { - // Example 534 + // Example 535 // Section: Inlines / Links // // The following Markdown: @@ -12103,15 +12124,15 @@ public void InlinesLinks_Example534() // Should be rendered as: //

foo *bar*

- TestParser.TestSpec("[foo *bar][ref]*\n\n[ref]: /uri", "

foo *bar*

", "", context: "Example 534\nSection Inlines / Links\n"); + TestParser.TestSpec("[foo *bar][ref]*\n\n[ref]: /uri", "

foo *bar*

", "", context: "Example 535\nSection Inlines / Links\n"); } // These cases illustrate the precedence of HTML tags, code spans, // and autolinks over link grouping: [Test] - public void InlinesLinks_Example535() + public void InlinesLinks_Example536() { - // Example 535 + // Example 536 // Section: Inlines / Links // // The following Markdown: @@ -12122,13 +12143,13 @@ public void InlinesLinks_Example535() // Should be rendered as: //

[foo

- TestParser.TestSpec("[foo \n\n[ref]: /uri", "

[foo

", "", context: "Example 535\nSection Inlines / Links\n"); + TestParser.TestSpec("[foo \n\n[ref]: /uri", "

[foo

", "", context: "Example 536\nSection Inlines / Links\n"); } [Test] - public void InlinesLinks_Example536() + public void InlinesLinks_Example537() { - // Example 536 + // Example 537 // Section: Inlines / Links // // The following Markdown: @@ -12139,31 +12160,31 @@ public void InlinesLinks_Example536() // Should be rendered as: //

[foo][ref]

- TestParser.TestSpec("[foo`][ref]`\n\n[ref]: /uri", "

[foo][ref]

", "", context: "Example 536\nSection Inlines / Links\n"); + TestParser.TestSpec("[foo`][ref]`\n\n[ref]: /uri", "

[foo][ref]

", "", context: "Example 537\nSection Inlines / Links\n"); } [Test] - public void InlinesLinks_Example537() + public void InlinesLinks_Example538() { - // Example 537 + // Example 538 // Section: Inlines / Links // // The following Markdown: - // [foo + // [foo // // [ref]: /uri // // Should be rendered as: - //

[foohttp://example.com/?search=][ref]

+ //

[foohttps://example.com/?search=][ref]

- TestParser.TestSpec("[foo\n\n[ref]: /uri", "

[foohttp://example.com/?search=][ref]

", "", context: "Example 537\nSection Inlines / Links\n"); + TestParser.TestSpec("[foo\n\n[ref]: /uri", "

[foohttps://example.com/?search=][ref]

", "", context: "Example 538\nSection Inlines / Links\n"); } // Matching is case-insensitive: [Test] - public void InlinesLinks_Example538() + public void InlinesLinks_Example539() { - // Example 538 + // Example 539 // Section: Inlines / Links // // The following Markdown: @@ -12174,14 +12195,14 @@ public void InlinesLinks_Example538() // Should be rendered as: //

foo

- TestParser.TestSpec("[foo][BaR]\n\n[bar]: /url \"title\"", "

foo

", "", context: "Example 538\nSection Inlines / Links\n"); + TestParser.TestSpec("[foo][BaR]\n\n[bar]: /url \"title\"", "

foo

", "", context: "Example 539\nSection Inlines / Links\n"); } // Unicode case fold is used: [Test] - public void InlinesLinks_Example539() + public void InlinesLinks_Example540() { - // Example 539 + // Example 540 // Section: Inlines / Links // // The following Markdown: @@ -12192,15 +12213,15 @@ public void InlinesLinks_Example539() // Should be rendered as: //

- TestParser.TestSpec("[ẞ]\n\n[SS]: /url", "

", "", context: "Example 539\nSection Inlines / Links\n"); + TestParser.TestSpec("[ẞ]\n\n[SS]: /url", "

", "", context: "Example 540\nSection Inlines / Links\n"); } // Consecutive internal spaces, tabs, and line endings are treated as one space for // purposes of determining matching: [Test] - public void InlinesLinks_Example540() + public void InlinesLinks_Example541() { - // Example 540 + // Example 541 // Section: Inlines / Links // // The following Markdown: @@ -12212,15 +12233,15 @@ public void InlinesLinks_Example540() // Should be rendered as: //

Baz

- TestParser.TestSpec("[Foo\n bar]: /url\n\n[Baz][Foo bar]", "

Baz

", "", context: "Example 540\nSection Inlines / Links\n"); + TestParser.TestSpec("[Foo\n bar]: /url\n\n[Baz][Foo bar]", "

Baz

", "", context: "Example 541\nSection Inlines / Links\n"); } // No spaces, tabs, or line endings are allowed between the [link text] and the // [link label]: [Test] - public void InlinesLinks_Example541() + public void InlinesLinks_Example542() { - // Example 541 + // Example 542 // Section: Inlines / Links // // The following Markdown: @@ -12231,13 +12252,13 @@ public void InlinesLinks_Example541() // Should be rendered as: //

[foo] bar

- TestParser.TestSpec("[foo] [bar]\n\n[bar]: /url \"title\"", "

[foo] bar

", "", context: "Example 541\nSection Inlines / Links\n"); + TestParser.TestSpec("[foo] [bar]\n\n[bar]: /url \"title\"", "

[foo] bar

", "", context: "Example 542\nSection Inlines / Links\n"); } [Test] - public void InlinesLinks_Example542() + public void InlinesLinks_Example543() { - // Example 542 + // Example 543 // Section: Inlines / Links // // The following Markdown: @@ -12250,7 +12271,7 @@ public void InlinesLinks_Example542() //

[foo] // bar

- TestParser.TestSpec("[foo]\n[bar]\n\n[bar]: /url \"title\"", "

[foo]\nbar

", "", context: "Example 542\nSection Inlines / Links\n"); + TestParser.TestSpec("[foo]\n[bar]\n\n[bar]: /url \"title\"", "

[foo]\nbar

", "", context: "Example 543\nSection Inlines / Links\n"); } // This is a departure from John Gruber's original Markdown syntax @@ -12283,9 +12304,9 @@ public void InlinesLinks_Example542() // When there are multiple matching [link reference definitions], // the first is used: [Test] - public void InlinesLinks_Example543() + public void InlinesLinks_Example544() { - // Example 543 + // Example 544 // Section: Inlines / Links // // The following Markdown: @@ -12298,16 +12319,16 @@ public void InlinesLinks_Example543() // Should be rendered as: //

bar

- TestParser.TestSpec("[foo]: /url1\n\n[foo]: /url2\n\n[bar][foo]", "

bar

", "", context: "Example 543\nSection Inlines / Links\n"); + TestParser.TestSpec("[foo]: /url1\n\n[foo]: /url2\n\n[bar][foo]", "

bar

", "", context: "Example 544\nSection Inlines / Links\n"); } // Note that matching is performed on normalized strings, not parsed // inline content. So the following does not match, even though the // labels define equivalent inline content: [Test] - public void InlinesLinks_Example544() + public void InlinesLinks_Example545() { - // Example 544 + // Example 545 // Section: Inlines / Links // // The following Markdown: @@ -12318,15 +12339,15 @@ public void InlinesLinks_Example544() // Should be rendered as: //

[bar][foo!]

- TestParser.TestSpec("[bar][foo\\!]\n\n[foo!]: /url", "

[bar][foo!]

", "", context: "Example 544\nSection Inlines / Links\n"); + TestParser.TestSpec("[bar][foo\\!]\n\n[foo!]: /url", "

[bar][foo!]

", "", context: "Example 545\nSection Inlines / Links\n"); } // [Link labels] cannot contain brackets, unless they are // backslash-escaped: [Test] - public void InlinesLinks_Example545() + public void InlinesLinks_Example546() { - // Example 545 + // Example 546 // Section: Inlines / Links // // The following Markdown: @@ -12338,13 +12359,13 @@ public void InlinesLinks_Example545() //

[foo][ref[]

//

[ref[]: /uri

- TestParser.TestSpec("[foo][ref[]\n\n[ref[]: /uri", "

[foo][ref[]

\n

[ref[]: /uri

", "", context: "Example 545\nSection Inlines / Links\n"); + TestParser.TestSpec("[foo][ref[]\n\n[ref[]: /uri", "

[foo][ref[]

\n

[ref[]: /uri

", "", context: "Example 546\nSection Inlines / Links\n"); } [Test] - public void InlinesLinks_Example546() + public void InlinesLinks_Example547() { - // Example 546 + // Example 547 // Section: Inlines / Links // // The following Markdown: @@ -12356,13 +12377,13 @@ public void InlinesLinks_Example546() //

[foo][ref[bar]]

//

[ref[bar]]: /uri

- TestParser.TestSpec("[foo][ref[bar]]\n\n[ref[bar]]: /uri", "

[foo][ref[bar]]

\n

[ref[bar]]: /uri

", "", context: "Example 546\nSection Inlines / Links\n"); + TestParser.TestSpec("[foo][ref[bar]]\n\n[ref[bar]]: /uri", "

[foo][ref[bar]]

\n

[ref[bar]]: /uri

", "", context: "Example 547\nSection Inlines / Links\n"); } [Test] - public void InlinesLinks_Example547() + public void InlinesLinks_Example548() { - // Example 547 + // Example 548 // Section: Inlines / Links // // The following Markdown: @@ -12374,13 +12395,13 @@ public void InlinesLinks_Example547() //

[[[foo]]]

//

[[[foo]]]: /url

- TestParser.TestSpec("[[[foo]]]\n\n[[[foo]]]: /url", "

[[[foo]]]

\n

[[[foo]]]: /url

", "", context: "Example 547\nSection Inlines / Links\n"); + TestParser.TestSpec("[[[foo]]]\n\n[[[foo]]]: /url", "

[[[foo]]]

\n

[[[foo]]]: /url

", "", context: "Example 548\nSection Inlines / Links\n"); } [Test] - public void InlinesLinks_Example548() + public void InlinesLinks_Example549() { - // Example 548 + // Example 549 // Section: Inlines / Links // // The following Markdown: @@ -12391,14 +12412,14 @@ public void InlinesLinks_Example548() // Should be rendered as: //

foo

- TestParser.TestSpec("[foo][ref\\[]\n\n[ref\\[]: /uri", "

foo

", "", context: "Example 548\nSection Inlines / Links\n"); + TestParser.TestSpec("[foo][ref\\[]\n\n[ref\\[]: /uri", "

foo

", "", context: "Example 549\nSection Inlines / Links\n"); } // Note that in this example `]` is not backslash-escaped: [Test] - public void InlinesLinks_Example549() + public void InlinesLinks_Example550() { - // Example 549 + // Example 550 // Section: Inlines / Links // // The following Markdown: @@ -12409,15 +12430,15 @@ public void InlinesLinks_Example549() // Should be rendered as: //

bar\

- TestParser.TestSpec("[bar\\\\]: /uri\n\n[bar\\\\]", "

bar\\

", "", context: "Example 549\nSection Inlines / Links\n"); + TestParser.TestSpec("[bar\\\\]: /uri\n\n[bar\\\\]", "

bar\\

", "", context: "Example 550\nSection Inlines / Links\n"); } // A [link label] must contain at least one character that is not a space, tab, or // line ending: [Test] - public void InlinesLinks_Example550() + public void InlinesLinks_Example551() { - // Example 550 + // Example 551 // Section: Inlines / Links // // The following Markdown: @@ -12429,13 +12450,13 @@ public void InlinesLinks_Example550() //

[]

//

[]: /uri

- TestParser.TestSpec("[]\n\n[]: /uri", "

[]

\n

[]: /uri

", "", context: "Example 550\nSection Inlines / Links\n"); + TestParser.TestSpec("[]\n\n[]: /uri", "

[]

\n

[]: /uri

", "", context: "Example 551\nSection Inlines / Links\n"); } [Test] - public void InlinesLinks_Example551() + public void InlinesLinks_Example552() { - // Example 551 + // Example 552 // Section: Inlines / Links // // The following Markdown: @@ -12451,21 +12472,21 @@ public void InlinesLinks_Example551() //

[ // ]: /uri

- TestParser.TestSpec("[\n ]\n\n[\n ]: /uri", "

[\n]

\n

[\n]: /uri

", "", context: "Example 551\nSection Inlines / Links\n"); + TestParser.TestSpec("[\n ]\n\n[\n ]: /uri", "

[\n]

\n

[\n]: /uri

", "", context: "Example 552\nSection Inlines / Links\n"); } // A [collapsed reference link](@) // consists of a [link label] that [matches] a // [link reference definition] elsewhere in the // document, followed by the string `[]`. - // The contents of the first link label are parsed as inlines, + // The contents of the link label are parsed as inlines, // which are used as the link's text. The link's URI and title are // provided by the matching reference link definition. Thus, // `[foo][]` is equivalent to `[foo][foo]`. [Test] - public void InlinesLinks_Example552() + public void InlinesLinks_Example553() { - // Example 552 + // Example 553 // Section: Inlines / Links // // The following Markdown: @@ -12476,13 +12497,13 @@ public void InlinesLinks_Example552() // Should be rendered as: //

foo

- TestParser.TestSpec("[foo][]\n\n[foo]: /url \"title\"", "

foo

", "", context: "Example 552\nSection Inlines / Links\n"); + TestParser.TestSpec("[foo][]\n\n[foo]: /url \"title\"", "

foo

", "", context: "Example 553\nSection Inlines / Links\n"); } [Test] - public void InlinesLinks_Example553() + public void InlinesLinks_Example554() { - // Example 553 + // Example 554 // Section: Inlines / Links // // The following Markdown: @@ -12493,14 +12514,14 @@ public void InlinesLinks_Example553() // Should be rendered as: //

foo bar

- TestParser.TestSpec("[*foo* bar][]\n\n[*foo* bar]: /url \"title\"", "

foo bar

", "", context: "Example 553\nSection Inlines / Links\n"); + TestParser.TestSpec("[*foo* bar][]\n\n[*foo* bar]: /url \"title\"", "

foo bar

", "", context: "Example 554\nSection Inlines / Links\n"); } // The link labels are case-insensitive: [Test] - public void InlinesLinks_Example554() + public void InlinesLinks_Example555() { - // Example 554 + // Example 555 // Section: Inlines / Links // // The following Markdown: @@ -12511,15 +12532,15 @@ public void InlinesLinks_Example554() // Should be rendered as: //

Foo

- TestParser.TestSpec("[Foo][]\n\n[foo]: /url \"title\"", "

Foo

", "", context: "Example 554\nSection Inlines / Links\n"); + TestParser.TestSpec("[Foo][]\n\n[foo]: /url \"title\"", "

Foo

", "", context: "Example 555\nSection Inlines / Links\n"); } // As with full reference links, spaces, tabs, or line endings are not // allowed between the two sets of brackets: [Test] - public void InlinesLinks_Example555() + public void InlinesLinks_Example556() { - // Example 555 + // Example 556 // Section: Inlines / Links // // The following Markdown: @@ -12532,21 +12553,21 @@ public void InlinesLinks_Example555() //

foo // []

- TestParser.TestSpec("[foo] \n[]\n\n[foo]: /url \"title\"", "

foo\n[]

", "", context: "Example 555\nSection Inlines / Links\n"); + TestParser.TestSpec("[foo] \n[]\n\n[foo]: /url \"title\"", "

foo\n[]

", "", context: "Example 556\nSection Inlines / Links\n"); } // A [shortcut reference link](@) // consists of a [link label] that [matches] a // [link reference definition] elsewhere in the // document and is not followed by `[]` or a link label. - // The contents of the first link label are parsed as inlines, + // The contents of the link label are parsed as inlines, // which are used as the link's text. The link's URI and title // are provided by the matching link reference definition. // Thus, `[foo]` is equivalent to `[foo][]`. [Test] - public void InlinesLinks_Example556() + public void InlinesLinks_Example557() { - // Example 556 + // Example 557 // Section: Inlines / Links // // The following Markdown: @@ -12557,13 +12578,13 @@ public void InlinesLinks_Example556() // Should be rendered as: //

foo

- TestParser.TestSpec("[foo]\n\n[foo]: /url \"title\"", "

foo

", "", context: "Example 556\nSection Inlines / Links\n"); + TestParser.TestSpec("[foo]\n\n[foo]: /url \"title\"", "

foo

", "", context: "Example 557\nSection Inlines / Links\n"); } [Test] - public void InlinesLinks_Example557() + public void InlinesLinks_Example558() { - // Example 557 + // Example 558 // Section: Inlines / Links // // The following Markdown: @@ -12574,13 +12595,13 @@ public void InlinesLinks_Example557() // Should be rendered as: //

foo bar

- TestParser.TestSpec("[*foo* bar]\n\n[*foo* bar]: /url \"title\"", "

foo bar

", "", context: "Example 557\nSection Inlines / Links\n"); + TestParser.TestSpec("[*foo* bar]\n\n[*foo* bar]: /url \"title\"", "

foo bar

", "", context: "Example 558\nSection Inlines / Links\n"); } [Test] - public void InlinesLinks_Example558() + public void InlinesLinks_Example559() { - // Example 558 + // Example 559 // Section: Inlines / Links // // The following Markdown: @@ -12591,13 +12612,13 @@ public void InlinesLinks_Example558() // Should be rendered as: //

[foo bar]

- TestParser.TestSpec("[[*foo* bar]]\n\n[*foo* bar]: /url \"title\"", "

[foo bar]

", "", context: "Example 558\nSection Inlines / Links\n"); + TestParser.TestSpec("[[*foo* bar]]\n\n[*foo* bar]: /url \"title\"", "

[foo bar]

", "", context: "Example 559\nSection Inlines / Links\n"); } [Test] - public void InlinesLinks_Example559() + public void InlinesLinks_Example560() { - // Example 559 + // Example 560 // Section: Inlines / Links // // The following Markdown: @@ -12608,14 +12629,14 @@ public void InlinesLinks_Example559() // Should be rendered as: //

[[bar foo

- TestParser.TestSpec("[[bar [foo]\n\n[foo]: /url", "

[[bar foo

", "", context: "Example 559\nSection Inlines / Links\n"); + TestParser.TestSpec("[[bar [foo]\n\n[foo]: /url", "

[[bar foo

", "", context: "Example 560\nSection Inlines / Links\n"); } // The link labels are case-insensitive: [Test] - public void InlinesLinks_Example560() + public void InlinesLinks_Example561() { - // Example 560 + // Example 561 // Section: Inlines / Links // // The following Markdown: @@ -12626,14 +12647,14 @@ public void InlinesLinks_Example560() // Should be rendered as: //

Foo

- TestParser.TestSpec("[Foo]\n\n[foo]: /url \"title\"", "

Foo

", "", context: "Example 560\nSection Inlines / Links\n"); + TestParser.TestSpec("[Foo]\n\n[foo]: /url \"title\"", "

Foo

", "", context: "Example 561\nSection Inlines / Links\n"); } // A space after the link text should be preserved: [Test] - public void InlinesLinks_Example561() + public void InlinesLinks_Example562() { - // Example 561 + // Example 562 // Section: Inlines / Links // // The following Markdown: @@ -12644,15 +12665,15 @@ public void InlinesLinks_Example561() // Should be rendered as: //

foo bar

- TestParser.TestSpec("[foo] bar\n\n[foo]: /url", "

foo bar

", "", context: "Example 561\nSection Inlines / Links\n"); + TestParser.TestSpec("[foo] bar\n\n[foo]: /url", "

foo bar

", "", context: "Example 562\nSection Inlines / Links\n"); } // If you just want bracketed text, you can backslash-escape the // opening bracket to avoid links: [Test] - public void InlinesLinks_Example562() + public void InlinesLinks_Example563() { - // Example 562 + // Example 563 // Section: Inlines / Links // // The following Markdown: @@ -12663,15 +12684,15 @@ public void InlinesLinks_Example562() // Should be rendered as: //

[foo]

- TestParser.TestSpec("\\[foo]\n\n[foo]: /url \"title\"", "

[foo]

", "", context: "Example 562\nSection Inlines / Links\n"); + TestParser.TestSpec("\\[foo]\n\n[foo]: /url \"title\"", "

[foo]

", "", context: "Example 563\nSection Inlines / Links\n"); } // Note that this is a link, because a link label ends with the first // following closing bracket: [Test] - public void InlinesLinks_Example563() + public void InlinesLinks_Example564() { - // Example 563 + // Example 564 // Section: Inlines / Links // // The following Markdown: @@ -12682,15 +12703,15 @@ public void InlinesLinks_Example563() // Should be rendered as: //

*foo*

- TestParser.TestSpec("[foo*]: /url\n\n*[foo*]", "

*foo*

", "", context: "Example 563\nSection Inlines / Links\n"); + TestParser.TestSpec("[foo*]: /url\n\n*[foo*]", "

*foo*

", "", context: "Example 564\nSection Inlines / Links\n"); } - // Full and compact references take precedence over shortcut + // Full and collapsed references take precedence over shortcut // references: [Test] - public void InlinesLinks_Example564() + public void InlinesLinks_Example565() { - // Example 564 + // Example 565 // Section: Inlines / Links // // The following Markdown: @@ -12702,13 +12723,13 @@ public void InlinesLinks_Example564() // Should be rendered as: //

foo

- TestParser.TestSpec("[foo][bar]\n\n[foo]: /url1\n[bar]: /url2", "

foo

", "", context: "Example 564\nSection Inlines / Links\n"); + TestParser.TestSpec("[foo][bar]\n\n[foo]: /url1\n[bar]: /url2", "

foo

", "", context: "Example 565\nSection Inlines / Links\n"); } [Test] - public void InlinesLinks_Example565() + public void InlinesLinks_Example566() { - // Example 565 + // Example 566 // Section: Inlines / Links // // The following Markdown: @@ -12719,14 +12740,14 @@ public void InlinesLinks_Example565() // Should be rendered as: //

foo

- TestParser.TestSpec("[foo][]\n\n[foo]: /url1", "

foo

", "", context: "Example 565\nSection Inlines / Links\n"); + TestParser.TestSpec("[foo][]\n\n[foo]: /url1", "

foo

", "", context: "Example 566\nSection Inlines / Links\n"); } // Inline links also take precedence: [Test] - public void InlinesLinks_Example566() + public void InlinesLinks_Example567() { - // Example 566 + // Example 567 // Section: Inlines / Links // // The following Markdown: @@ -12737,13 +12758,13 @@ public void InlinesLinks_Example566() // Should be rendered as: //

foo

- TestParser.TestSpec("[foo]()\n\n[foo]: /url1", "

foo

", "", context: "Example 566\nSection Inlines / Links\n"); + TestParser.TestSpec("[foo]()\n\n[foo]: /url1", "

foo

", "", context: "Example 567\nSection Inlines / Links\n"); } [Test] - public void InlinesLinks_Example567() + public void InlinesLinks_Example568() { - // Example 567 + // Example 568 // Section: Inlines / Links // // The following Markdown: @@ -12754,15 +12775,15 @@ public void InlinesLinks_Example567() // Should be rendered as: //

foo(not a link)

- TestParser.TestSpec("[foo](not a link)\n\n[foo]: /url1", "

foo(not a link)

", "", context: "Example 567\nSection Inlines / Links\n"); + TestParser.TestSpec("[foo](not a link)\n\n[foo]: /url1", "

foo(not a link)

", "", context: "Example 568\nSection Inlines / Links\n"); } // In the following case `[bar][baz]` is parsed as a reference, // `[foo]` as normal text: [Test] - public void InlinesLinks_Example568() + public void InlinesLinks_Example569() { - // Example 568 + // Example 569 // Section: Inlines / Links // // The following Markdown: @@ -12773,15 +12794,15 @@ public void InlinesLinks_Example568() // Should be rendered as: //

[foo]bar

- TestParser.TestSpec("[foo][bar][baz]\n\n[baz]: /url", "

[foo]bar

", "", context: "Example 568\nSection Inlines / Links\n"); + TestParser.TestSpec("[foo][bar][baz]\n\n[baz]: /url", "

[foo]bar

", "", context: "Example 569\nSection Inlines / Links\n"); } // Here, though, `[foo][bar]` is parsed as a reference, since // `[bar]` is defined: [Test] - public void InlinesLinks_Example569() + public void InlinesLinks_Example570() { - // Example 569 + // Example 570 // Section: Inlines / Links // // The following Markdown: @@ -12793,15 +12814,15 @@ public void InlinesLinks_Example569() // Should be rendered as: //

foobaz

- TestParser.TestSpec("[foo][bar][baz]\n\n[baz]: /url1\n[bar]: /url2", "

foobaz

", "", context: "Example 569\nSection Inlines / Links\n"); + TestParser.TestSpec("[foo][bar][baz]\n\n[baz]: /url1\n[bar]: /url2", "

foobaz

", "", context: "Example 570\nSection Inlines / Links\n"); } // Here `[foo]` is not parsed as a shortcut reference, because it // is followed by a link label (even though `[bar]` is not defined): [Test] - public void InlinesLinks_Example570() + public void InlinesLinks_Example571() { - // Example 570 + // Example 571 // Section: Inlines / Links // // The following Markdown: @@ -12813,7 +12834,7 @@ public void InlinesLinks_Example570() // Should be rendered as: //

[foo]bar

- TestParser.TestSpec("[foo][bar][baz]\n\n[baz]: /url1\n[foo]: /url2", "

[foo]bar

", "", context: "Example 570\nSection Inlines / Links\n"); + TestParser.TestSpec("[foo][bar][baz]\n\n[baz]: /url1\n[foo]: /url2", "

[foo]bar

", "", context: "Example 571\nSection Inlines / Links\n"); } } @@ -12832,9 +12853,9 @@ public class TestInlinesImages // as its contents. When an image is rendered to HTML, // this is standardly used as the image's `alt` attribute. [Test] - public void InlinesImages_Example571() + public void InlinesImages_Example572() { - // Example 571 + // Example 572 // Section: Inlines / Images // // The following Markdown: @@ -12843,13 +12864,13 @@ public void InlinesImages_Example571() // Should be rendered as: //

foo

- TestParser.TestSpec("![foo](/url \"title\")", "

\"foo\"

", "", context: "Example 571\nSection Inlines / Images\n"); + TestParser.TestSpec("![foo](/url \"title\")", "

\"foo\"

", "", context: "Example 572\nSection Inlines / Images\n"); } [Test] - public void InlinesImages_Example572() + public void InlinesImages_Example573() { - // Example 572 + // Example 573 // Section: Inlines / Images // // The following Markdown: @@ -12860,13 +12881,13 @@ public void InlinesImages_Example572() // Should be rendered as: //

foo bar

- TestParser.TestSpec("![foo *bar*]\n\n[foo *bar*]: train.jpg \"train & tracks\"", "

\"foo

", "", context: "Example 572\nSection Inlines / Images\n"); + TestParser.TestSpec("![foo *bar*]\n\n[foo *bar*]: train.jpg \"train & tracks\"", "

\"foo

", "", context: "Example 573\nSection Inlines / Images\n"); } [Test] - public void InlinesImages_Example573() + public void InlinesImages_Example574() { - // Example 573 + // Example 574 // Section: Inlines / Images // // The following Markdown: @@ -12875,13 +12896,13 @@ public void InlinesImages_Example573() // Should be rendered as: //

foo bar

- TestParser.TestSpec("![foo ![bar](/url)](/url2)", "

\"foo

", "", context: "Example 573\nSection Inlines / Images\n"); + TestParser.TestSpec("![foo ![bar](/url)](/url2)", "

\"foo

", "", context: "Example 574\nSection Inlines / Images\n"); } [Test] - public void InlinesImages_Example574() + public void InlinesImages_Example575() { - // Example 574 + // Example 575 // Section: Inlines / Images // // The following Markdown: @@ -12890,7 +12911,7 @@ public void InlinesImages_Example574() // Should be rendered as: //

foo bar

- TestParser.TestSpec("![foo [bar](/url)](/url2)", "

\"foo

", "", context: "Example 574\nSection Inlines / Images\n"); + TestParser.TestSpec("![foo [bar](/url)](/url2)", "

\"foo

", "", context: "Example 575\nSection Inlines / Images\n"); } // Though this spec is concerned with parsing, not rendering, it is @@ -12900,9 +12921,9 @@ public void InlinesImages_Example574() // [bar](/url)` or `foo bar`. Only the plain string // content is rendered, without formatting. [Test] - public void InlinesImages_Example575() + public void InlinesImages_Example576() { - // Example 575 + // Example 576 // Section: Inlines / Images // // The following Markdown: @@ -12913,13 +12934,13 @@ public void InlinesImages_Example575() // Should be rendered as: //

foo bar

- TestParser.TestSpec("![foo *bar*][]\n\n[foo *bar*]: train.jpg \"train & tracks\"", "

\"foo

", "", context: "Example 575\nSection Inlines / Images\n"); + TestParser.TestSpec("![foo *bar*][]\n\n[foo *bar*]: train.jpg \"train & tracks\"", "

\"foo

", "", context: "Example 576\nSection Inlines / Images\n"); } [Test] - public void InlinesImages_Example576() + public void InlinesImages_Example577() { - // Example 576 + // Example 577 // Section: Inlines / Images // // The following Markdown: @@ -12930,13 +12951,13 @@ public void InlinesImages_Example576() // Should be rendered as: //

foo bar

- TestParser.TestSpec("![foo *bar*][foobar]\n\n[FOOBAR]: train.jpg \"train & tracks\"", "

\"foo

", "", context: "Example 576\nSection Inlines / Images\n"); + TestParser.TestSpec("![foo *bar*][foobar]\n\n[FOOBAR]: train.jpg \"train & tracks\"", "

\"foo

", "", context: "Example 577\nSection Inlines / Images\n"); } [Test] - public void InlinesImages_Example577() + public void InlinesImages_Example578() { - // Example 577 + // Example 578 // Section: Inlines / Images // // The following Markdown: @@ -12945,13 +12966,13 @@ public void InlinesImages_Example577() // Should be rendered as: //

foo

- TestParser.TestSpec("![foo](train.jpg)", "

\"foo\"

", "", context: "Example 577\nSection Inlines / Images\n"); + TestParser.TestSpec("![foo](train.jpg)", "

\"foo\"

", "", context: "Example 578\nSection Inlines / Images\n"); } [Test] - public void InlinesImages_Example578() + public void InlinesImages_Example579() { - // Example 578 + // Example 579 // Section: Inlines / Images // // The following Markdown: @@ -12960,13 +12981,13 @@ public void InlinesImages_Example578() // Should be rendered as: //

My foo bar

- TestParser.TestSpec("My ![foo bar](/path/to/train.jpg \"title\" )", "

My \"foo

", "", context: "Example 578\nSection Inlines / Images\n"); + TestParser.TestSpec("My ![foo bar](/path/to/train.jpg \"title\" )", "

My \"foo

", "", context: "Example 579\nSection Inlines / Images\n"); } [Test] - public void InlinesImages_Example579() + public void InlinesImages_Example580() { - // Example 579 + // Example 580 // Section: Inlines / Images // // The following Markdown: @@ -12975,13 +12996,13 @@ public void InlinesImages_Example579() // Should be rendered as: //

foo

- TestParser.TestSpec("![foo]()", "

\"foo\"

", "", context: "Example 579\nSection Inlines / Images\n"); + TestParser.TestSpec("![foo]()", "

\"foo\"

", "", context: "Example 580\nSection Inlines / Images\n"); } [Test] - public void InlinesImages_Example580() + public void InlinesImages_Example581() { - // Example 580 + // Example 581 // Section: Inlines / Images // // The following Markdown: @@ -12990,14 +13011,14 @@ public void InlinesImages_Example580() // Should be rendered as: //

- TestParser.TestSpec("![](/url)", "

\"\"

", "", context: "Example 580\nSection Inlines / Images\n"); + TestParser.TestSpec("![](/url)", "

\"\"

", "", context: "Example 581\nSection Inlines / Images\n"); } // Reference-style: [Test] - public void InlinesImages_Example581() + public void InlinesImages_Example582() { - // Example 581 + // Example 582 // Section: Inlines / Images // // The following Markdown: @@ -13008,13 +13029,13 @@ public void InlinesImages_Example581() // Should be rendered as: //

foo

- TestParser.TestSpec("![foo][bar]\n\n[bar]: /url", "

\"foo\"

", "", context: "Example 581\nSection Inlines / Images\n"); + TestParser.TestSpec("![foo][bar]\n\n[bar]: /url", "

\"foo\"

", "", context: "Example 582\nSection Inlines / Images\n"); } [Test] - public void InlinesImages_Example582() + public void InlinesImages_Example583() { - // Example 582 + // Example 583 // Section: Inlines / Images // // The following Markdown: @@ -13025,14 +13046,14 @@ public void InlinesImages_Example582() // Should be rendered as: //

foo

- TestParser.TestSpec("![foo][bar]\n\n[BAR]: /url", "

\"foo\"

", "", context: "Example 582\nSection Inlines / Images\n"); + TestParser.TestSpec("![foo][bar]\n\n[BAR]: /url", "

\"foo\"

", "", context: "Example 583\nSection Inlines / Images\n"); } // Collapsed: [Test] - public void InlinesImages_Example583() + public void InlinesImages_Example584() { - // Example 583 + // Example 584 // Section: Inlines / Images // // The following Markdown: @@ -13043,13 +13064,13 @@ public void InlinesImages_Example583() // Should be rendered as: //

foo

- TestParser.TestSpec("![foo][]\n\n[foo]: /url \"title\"", "

\"foo\"

", "", context: "Example 583\nSection Inlines / Images\n"); + TestParser.TestSpec("![foo][]\n\n[foo]: /url \"title\"", "

\"foo\"

", "", context: "Example 584\nSection Inlines / Images\n"); } [Test] - public void InlinesImages_Example584() + public void InlinesImages_Example585() { - // Example 584 + // Example 585 // Section: Inlines / Images // // The following Markdown: @@ -13060,14 +13081,14 @@ public void InlinesImages_Example584() // Should be rendered as: //

foo bar

- TestParser.TestSpec("![*foo* bar][]\n\n[*foo* bar]: /url \"title\"", "

\"foo

", "", context: "Example 584\nSection Inlines / Images\n"); + TestParser.TestSpec("![*foo* bar][]\n\n[*foo* bar]: /url \"title\"", "

\"foo

", "", context: "Example 585\nSection Inlines / Images\n"); } // The labels are case-insensitive: [Test] - public void InlinesImages_Example585() + public void InlinesImages_Example586() { - // Example 585 + // Example 586 // Section: Inlines / Images // // The following Markdown: @@ -13078,15 +13099,15 @@ public void InlinesImages_Example585() // Should be rendered as: //

Foo

- TestParser.TestSpec("![Foo][]\n\n[foo]: /url \"title\"", "

\"Foo\"

", "", context: "Example 585\nSection Inlines / Images\n"); + TestParser.TestSpec("![Foo][]\n\n[foo]: /url \"title\"", "

\"Foo\"

", "", context: "Example 586\nSection Inlines / Images\n"); } // As with reference links, spaces, tabs, and line endings, are not allowed // between the two sets of brackets: [Test] - public void InlinesImages_Example586() + public void InlinesImages_Example587() { - // Example 586 + // Example 587 // Section: Inlines / Images // // The following Markdown: @@ -13099,14 +13120,14 @@ public void InlinesImages_Example586() //

foo // []

- TestParser.TestSpec("![foo] \n[]\n\n[foo]: /url \"title\"", "

\"foo\"\n[]

", "", context: "Example 586\nSection Inlines / Images\n"); + TestParser.TestSpec("![foo] \n[]\n\n[foo]: /url \"title\"", "

\"foo\"\n[]

", "", context: "Example 587\nSection Inlines / Images\n"); } // Shortcut: [Test] - public void InlinesImages_Example587() + public void InlinesImages_Example588() { - // Example 587 + // Example 588 // Section: Inlines / Images // // The following Markdown: @@ -13117,13 +13138,13 @@ public void InlinesImages_Example587() // Should be rendered as: //

foo

- TestParser.TestSpec("![foo]\n\n[foo]: /url \"title\"", "

\"foo\"

", "", context: "Example 587\nSection Inlines / Images\n"); + TestParser.TestSpec("![foo]\n\n[foo]: /url \"title\"", "

\"foo\"

", "", context: "Example 588\nSection Inlines / Images\n"); } [Test] - public void InlinesImages_Example588() + public void InlinesImages_Example589() { - // Example 588 + // Example 589 // Section: Inlines / Images // // The following Markdown: @@ -13134,14 +13155,14 @@ public void InlinesImages_Example588() // Should be rendered as: //

foo bar

- TestParser.TestSpec("![*foo* bar]\n\n[*foo* bar]: /url \"title\"", "

\"foo

", "", context: "Example 588\nSection Inlines / Images\n"); + TestParser.TestSpec("![*foo* bar]\n\n[*foo* bar]: /url \"title\"", "

\"foo

", "", context: "Example 589\nSection Inlines / Images\n"); } // Note that link labels cannot contain unescaped brackets: [Test] - public void InlinesImages_Example589() + public void InlinesImages_Example590() { - // Example 589 + // Example 590 // Section: Inlines / Images // // The following Markdown: @@ -13153,14 +13174,14 @@ public void InlinesImages_Example589() //

![[foo]]

//

[[foo]]: /url "title"

- TestParser.TestSpec("![[foo]]\n\n[[foo]]: /url \"title\"", "

![[foo]]

\n

[[foo]]: /url "title"

", "", context: "Example 589\nSection Inlines / Images\n"); + TestParser.TestSpec("![[foo]]\n\n[[foo]]: /url \"title\"", "

![[foo]]

\n

[[foo]]: /url "title"

", "", context: "Example 590\nSection Inlines / Images\n"); } // The link labels are case-insensitive: [Test] - public void InlinesImages_Example590() + public void InlinesImages_Example591() { - // Example 590 + // Example 591 // Section: Inlines / Images // // The following Markdown: @@ -13171,15 +13192,15 @@ public void InlinesImages_Example590() // Should be rendered as: //

Foo

- TestParser.TestSpec("![Foo]\n\n[foo]: /url \"title\"", "

\"Foo\"

", "", context: "Example 590\nSection Inlines / Images\n"); + TestParser.TestSpec("![Foo]\n\n[foo]: /url \"title\"", "

\"Foo\"

", "", context: "Example 591\nSection Inlines / Images\n"); } // If you just want a literal `!` followed by bracketed text, you can // backslash-escape the opening `[`: [Test] - public void InlinesImages_Example591() + public void InlinesImages_Example592() { - // Example 591 + // Example 592 // Section: Inlines / Images // // The following Markdown: @@ -13190,15 +13211,15 @@ public void InlinesImages_Example591() // Should be rendered as: //

![foo]

- TestParser.TestSpec("!\\[foo]\n\n[foo]: /url \"title\"", "

![foo]

", "", context: "Example 591\nSection Inlines / Images\n"); + TestParser.TestSpec("!\\[foo]\n\n[foo]: /url \"title\"", "

![foo]

", "", context: "Example 592\nSection Inlines / Images\n"); } // If you want a link after a literal `!`, backslash-escape the // `!`: [Test] - public void InlinesImages_Example592() + public void InlinesImages_Example593() { - // Example 592 + // Example 593 // Section: Inlines / Images // // The following Markdown: @@ -13209,7 +13230,7 @@ public void InlinesImages_Example592() // Should be rendered as: //

!foo

- TestParser.TestSpec("\\![foo]\n\n[foo]: /url \"title\"", "

!foo

", "", context: "Example 592\nSection Inlines / Images\n"); + TestParser.TestSpec("\\![foo]\n\n[foo]: /url \"title\"", "

!foo

", "", context: "Example 593\nSection Inlines / Images\n"); } } @@ -13240,9 +13261,9 @@ public class TestInlinesAutolinks // // Here are some valid autolinks: [Test] - public void InlinesAutolinks_Example593() + public void InlinesAutolinks_Example594() { - // Example 593 + // Example 594 // Section: Inlines / Autolinks // // The following Markdown: @@ -13251,28 +13272,28 @@ public void InlinesAutolinks_Example593() // Should be rendered as: //

http://foo.bar.baz

- TestParser.TestSpec("", "

http://foo.bar.baz

", "", context: "Example 593\nSection Inlines / Autolinks\n"); + TestParser.TestSpec("", "

http://foo.bar.baz

", "", context: "Example 594\nSection Inlines / Autolinks\n"); } [Test] - public void InlinesAutolinks_Example594() + public void InlinesAutolinks_Example595() { - // Example 594 + // Example 595 // Section: Inlines / Autolinks // // The following Markdown: - // + // // // Should be rendered as: - //

http://foo.bar.baz/test?q=hello&id=22&boolean

+ //

https://foo.bar.baz/test?q=hello&id=22&boolean

- TestParser.TestSpec("", "

http://foo.bar.baz/test?q=hello&id=22&boolean

", "", context: "Example 594\nSection Inlines / Autolinks\n"); + TestParser.TestSpec("", "

https://foo.bar.baz/test?q=hello&id=22&boolean

", "", context: "Example 595\nSection Inlines / Autolinks\n"); } [Test] - public void InlinesAutolinks_Example595() + public void InlinesAutolinks_Example596() { - // Example 595 + // Example 596 // Section: Inlines / Autolinks // // The following Markdown: @@ -13281,14 +13302,14 @@ public void InlinesAutolinks_Example595() // Should be rendered as: //

irc://foo.bar:2233/baz

- TestParser.TestSpec("", "

irc://foo.bar:2233/baz

", "", context: "Example 595\nSection Inlines / Autolinks\n"); + TestParser.TestSpec("", "

irc://foo.bar:2233/baz

", "", context: "Example 596\nSection Inlines / Autolinks\n"); } // Uppercase is also fine: [Test] - public void InlinesAutolinks_Example596() + public void InlinesAutolinks_Example597() { - // Example 596 + // Example 597 // Section: Inlines / Autolinks // // The following Markdown: @@ -13297,7 +13318,7 @@ public void InlinesAutolinks_Example596() // Should be rendered as: //

MAILTO:FOO@BAR.BAZ

- TestParser.TestSpec("", "

MAILTO:FOO@BAR.BAZ

", "", context: "Example 596\nSection Inlines / Autolinks\n"); + TestParser.TestSpec("", "

MAILTO:FOO@BAR.BAZ

", "", context: "Example 597\nSection Inlines / Autolinks\n"); } // Note that many strings that count as [absolute URIs] for @@ -13305,9 +13326,9 @@ public void InlinesAutolinks_Example596() // schemes are not registered or because of other problems // with their syntax: [Test] - public void InlinesAutolinks_Example597() + public void InlinesAutolinks_Example598() { - // Example 597 + // Example 598 // Section: Inlines / Autolinks // // The following Markdown: @@ -13316,13 +13337,13 @@ public void InlinesAutolinks_Example597() // Should be rendered as: //

a+b+c:d

- TestParser.TestSpec("", "

a+b+c:d

", "", context: "Example 597\nSection Inlines / Autolinks\n"); + TestParser.TestSpec("", "

a+b+c:d

", "", context: "Example 598\nSection Inlines / Autolinks\n"); } [Test] - public void InlinesAutolinks_Example598() + public void InlinesAutolinks_Example599() { - // Example 598 + // Example 599 // Section: Inlines / Autolinks // // The following Markdown: @@ -13331,28 +13352,28 @@ public void InlinesAutolinks_Example598() // Should be rendered as: //

made-up-scheme://foo,bar

- TestParser.TestSpec("", "

made-up-scheme://foo,bar

", "", context: "Example 598\nSection Inlines / Autolinks\n"); + TestParser.TestSpec("", "

made-up-scheme://foo,bar

", "", context: "Example 599\nSection Inlines / Autolinks\n"); } [Test] - public void InlinesAutolinks_Example599() + public void InlinesAutolinks_Example600() { - // Example 599 + // Example 600 // Section: Inlines / Autolinks // // The following Markdown: - // + // // // Should be rendered as: - //

http://../

+ //

https://../

- TestParser.TestSpec("", "

http://../

", "", context: "Example 599\nSection Inlines / Autolinks\n"); + TestParser.TestSpec("", "

https://../

", "", context: "Example 600\nSection Inlines / Autolinks\n"); } [Test] - public void InlinesAutolinks_Example600() + public void InlinesAutolinks_Example601() { - // Example 600 + // Example 601 // Section: Inlines / Autolinks // // The following Markdown: @@ -13361,39 +13382,39 @@ public void InlinesAutolinks_Example600() // Should be rendered as: //

localhost:5001/foo

- TestParser.TestSpec("", "

localhost:5001/foo

", "", context: "Example 600\nSection Inlines / Autolinks\n"); + TestParser.TestSpec("", "

localhost:5001/foo

", "", context: "Example 601\nSection Inlines / Autolinks\n"); } // Spaces are not allowed in autolinks: [Test] - public void InlinesAutolinks_Example601() + public void InlinesAutolinks_Example602() { - // Example 601 + // Example 602 // Section: Inlines / Autolinks // // The following Markdown: - // + // // // Should be rendered as: - //

<http://foo.bar/baz bim>

+ //

<https://foo.bar/baz bim>

- TestParser.TestSpec("", "

<http://foo.bar/baz bim>

", "", context: "Example 601\nSection Inlines / Autolinks\n"); + TestParser.TestSpec("", "

<https://foo.bar/baz bim>

", "", context: "Example 602\nSection Inlines / Autolinks\n"); } // Backslash-escapes do not work inside autolinks: [Test] - public void InlinesAutolinks_Example602() + public void InlinesAutolinks_Example603() { - // Example 602 + // Example 603 // Section: Inlines / Autolinks // // The following Markdown: - // + // // // Should be rendered as: - //

http://example.com/\[\

+ //

https://example.com/\[\

- TestParser.TestSpec("", "

http://example.com/\\[\\

", "", context: "Example 602\nSection Inlines / Autolinks\n"); + TestParser.TestSpec("", "

https://example.com/\\[\\

", "", context: "Example 603\nSection Inlines / Autolinks\n"); } // An [email autolink](@) @@ -13411,9 +13432,9 @@ public void InlinesAutolinks_Example602() // // Examples of email autolinks: [Test] - public void InlinesAutolinks_Example603() + public void InlinesAutolinks_Example604() { - // Example 603 + // Example 604 // Section: Inlines / Autolinks // // The following Markdown: @@ -13422,13 +13443,13 @@ public void InlinesAutolinks_Example603() // Should be rendered as: //

foo@bar.example.com

- TestParser.TestSpec("", "

foo@bar.example.com

", "", context: "Example 603\nSection Inlines / Autolinks\n"); + TestParser.TestSpec("", "

foo@bar.example.com

", "", context: "Example 604\nSection Inlines / Autolinks\n"); } [Test] - public void InlinesAutolinks_Example604() + public void InlinesAutolinks_Example605() { - // Example 604 + // Example 605 // Section: Inlines / Autolinks // // The following Markdown: @@ -13437,14 +13458,14 @@ public void InlinesAutolinks_Example604() // Should be rendered as: //

foo+special@Bar.baz-bar0.com

- TestParser.TestSpec("", "

foo+special@Bar.baz-bar0.com

", "", context: "Example 604\nSection Inlines / Autolinks\n"); + TestParser.TestSpec("", "

foo+special@Bar.baz-bar0.com

", "", context: "Example 605\nSection Inlines / Autolinks\n"); } // Backslash-escapes do not work inside email autolinks: [Test] - public void InlinesAutolinks_Example605() + public void InlinesAutolinks_Example606() { - // Example 605 + // Example 606 // Section: Inlines / Autolinks // // The following Markdown: @@ -13453,14 +13474,14 @@ public void InlinesAutolinks_Example605() // Should be rendered as: //

<foo+@bar.example.com>

- TestParser.TestSpec("", "

<foo+@bar.example.com>

", "", context: "Example 605\nSection Inlines / Autolinks\n"); + TestParser.TestSpec("", "

<foo+@bar.example.com>

", "", context: "Example 606\nSection Inlines / Autolinks\n"); } // These are not autolinks: [Test] - public void InlinesAutolinks_Example606() + public void InlinesAutolinks_Example607() { - // Example 606 + // Example 607 // Section: Inlines / Autolinks // // The following Markdown: @@ -13469,28 +13490,28 @@ public void InlinesAutolinks_Example606() // Should be rendered as: //

<>

- TestParser.TestSpec("<>", "

<>

", "", context: "Example 606\nSection Inlines / Autolinks\n"); + TestParser.TestSpec("<>", "

<>

", "", context: "Example 607\nSection Inlines / Autolinks\n"); } [Test] - public void InlinesAutolinks_Example607() + public void InlinesAutolinks_Example608() { - // Example 607 + // Example 608 // Section: Inlines / Autolinks // // The following Markdown: - // < http://foo.bar > + // < https://foo.bar > // // Should be rendered as: - //

< http://foo.bar >

+ //

< https://foo.bar >

- TestParser.TestSpec("< http://foo.bar >", "

< http://foo.bar >

", "", context: "Example 607\nSection Inlines / Autolinks\n"); + TestParser.TestSpec("< https://foo.bar >", "

< https://foo.bar >

", "", context: "Example 608\nSection Inlines / Autolinks\n"); } [Test] - public void InlinesAutolinks_Example608() + public void InlinesAutolinks_Example609() { - // Example 608 + // Example 609 // Section: Inlines / Autolinks // // The following Markdown: @@ -13499,13 +13520,13 @@ public void InlinesAutolinks_Example608() // Should be rendered as: //

<m:abc>

- TestParser.TestSpec("", "

<m:abc>

", "", context: "Example 608\nSection Inlines / Autolinks\n"); + TestParser.TestSpec("", "

<m:abc>

", "", context: "Example 609\nSection Inlines / Autolinks\n"); } [Test] - public void InlinesAutolinks_Example609() + public void InlinesAutolinks_Example610() { - // Example 609 + // Example 610 // Section: Inlines / Autolinks // // The following Markdown: @@ -13514,28 +13535,28 @@ public void InlinesAutolinks_Example609() // Should be rendered as: //

<foo.bar.baz>

- TestParser.TestSpec("", "

<foo.bar.baz>

", "", context: "Example 609\nSection Inlines / Autolinks\n"); + TestParser.TestSpec("", "

<foo.bar.baz>

", "", context: "Example 610\nSection Inlines / Autolinks\n"); } [Test] - public void InlinesAutolinks_Example610() + public void InlinesAutolinks_Example611() { - // Example 610 + // Example 611 // Section: Inlines / Autolinks // // The following Markdown: - // http://example.com + // https://example.com // // Should be rendered as: - //

http://example.com

+ //

https://example.com

- TestParser.TestSpec("http://example.com", "

http://example.com

", "", context: "Example 610\nSection Inlines / Autolinks\n"); + TestParser.TestSpec("https://example.com", "

https://example.com

", "", context: "Example 611\nSection Inlines / Autolinks\n"); } [Test] - public void InlinesAutolinks_Example611() + public void InlinesAutolinks_Example612() { - // Example 611 + // Example 612 // Section: Inlines / Autolinks // // The following Markdown: @@ -13544,7 +13565,7 @@ public void InlinesAutolinks_Example611() // Should be rendered as: //

foo@bar.example.com

- TestParser.TestSpec("foo@bar.example.com", "

foo@bar.example.com

", "", context: "Example 611\nSection Inlines / Autolinks\n"); + TestParser.TestSpec("foo@bar.example.com", "

foo@bar.example.com

", "", context: "Example 612\nSection Inlines / Autolinks\n"); } } @@ -13602,10 +13623,9 @@ public class TestInlinesRawHTML // [tag name], optional spaces, tabs, and up to one line ending, and the character // `>`. // - // An [HTML comment](@) consists of ``, - // where *text* does not start with `>` or `->`, does not end with `-`, - // and does not contain `--`. (See the - // [HTML5 spec](http://www.w3.org/TR/html5/syntax.html#comments).) + // An [HTML comment](@) consists of ``, ``, or ``, and `-->` (see the + // [HTML spec](https://html.spec.whatwg.org/multipage/parsing.html#markup-declaration-open-state)). // // A [processing instruction](@) // consists of the string `

- TestParser.TestSpec("
", "

", "", context: "Example 612\nSection Inlines / Raw HTML\n"); + TestParser.TestSpec("", "

", "", context: "Example 613\nSection Inlines / Raw HTML\n"); } // Empty elements: [Test] - public void InlinesRawHTML_Example613() + public void InlinesRawHTML_Example614() { - // Example 613 + // Example 614 // Section: Inlines / Raw HTML // // The following Markdown: @@ -13652,14 +13672,14 @@ public void InlinesRawHTML_Example613() // Should be rendered as: //

- TestParser.TestSpec("", "

", "", context: "Example 613\nSection Inlines / Raw HTML\n"); + TestParser.TestSpec("", "

", "", context: "Example 614\nSection Inlines / Raw HTML\n"); } // Whitespace is allowed: [Test] - public void InlinesRawHTML_Example614() + public void InlinesRawHTML_Example615() { - // Example 614 + // Example 615 // Section: Inlines / Raw HTML // // The following Markdown: @@ -13670,14 +13690,14 @@ public void InlinesRawHTML_Example614() //

- TestParser.TestSpec("", "

", "", context: "Example 614\nSection Inlines / Raw HTML\n"); + TestParser.TestSpec("", "

", "", context: "Example 615\nSection Inlines / Raw HTML\n"); } // With attributes: [Test] - public void InlinesRawHTML_Example615() + public void InlinesRawHTML_Example616() { - // Example 615 + // Example 616 // Section: Inlines / Raw HTML // // The following Markdown: @@ -13688,14 +13708,14 @@ public void InlinesRawHTML_Example615() //

- TestParser.TestSpec("", "

", "", context: "Example 615\nSection Inlines / Raw HTML\n"); + TestParser.TestSpec("", "

", "", context: "Example 616\nSection Inlines / Raw HTML\n"); } // Custom tag names can be used: [Test] - public void InlinesRawHTML_Example616() + public void InlinesRawHTML_Example617() { - // Example 616 + // Example 617 // Section: Inlines / Raw HTML // // The following Markdown: @@ -13704,14 +13724,14 @@ public void InlinesRawHTML_Example616() // Should be rendered as: //

Foo

- TestParser.TestSpec("Foo ", "

Foo

", "", context: "Example 616\nSection Inlines / Raw HTML\n"); + TestParser.TestSpec("Foo ", "

Foo

", "", context: "Example 617\nSection Inlines / Raw HTML\n"); } // Illegal tag names, not parsed as HTML: [Test] - public void InlinesRawHTML_Example617() + public void InlinesRawHTML_Example618() { - // Example 617 + // Example 618 // Section: Inlines / Raw HTML // // The following Markdown: @@ -13720,14 +13740,14 @@ public void InlinesRawHTML_Example617() // Should be rendered as: //

<33> <__>

- TestParser.TestSpec("<33> <__>", "

<33> <__>

", "", context: "Example 617\nSection Inlines / Raw HTML\n"); + TestParser.TestSpec("<33> <__>", "

<33> <__>

", "", context: "Example 618\nSection Inlines / Raw HTML\n"); } // Illegal attribute names: [Test] - public void InlinesRawHTML_Example618() + public void InlinesRawHTML_Example619() { - // Example 618 + // Example 619 // Section: Inlines / Raw HTML // // The following Markdown: @@ -13736,14 +13756,14 @@ public void InlinesRawHTML_Example618() // Should be rendered as: //

<a h*#ref="hi">

- TestParser.TestSpec("
", "

<a h*#ref="hi">

", "", context: "Example 618\nSection Inlines / Raw HTML\n"); + TestParser.TestSpec("
", "

<a h*#ref="hi">

", "", context: "Example 619\nSection Inlines / Raw HTML\n"); } // Illegal attribute values: [Test] - public void InlinesRawHTML_Example619() + public void InlinesRawHTML_Example620() { - // Example 619 + // Example 620 // Section: Inlines / Raw HTML // // The following Markdown: @@ -13752,14 +13772,14 @@ public void InlinesRawHTML_Example619() // Should be rendered as: //

<a href="hi'> <a href=hi'>

- TestParser.TestSpec("
", "

<a href="hi'> <a href=hi'>

", "", context: "Example 619\nSection Inlines / Raw HTML\n"); + TestParser.TestSpec("
", "

<a href="hi'> <a href=hi'>

", "", context: "Example 620\nSection Inlines / Raw HTML\n"); } // Illegal whitespace: [Test] - public void InlinesRawHTML_Example620() + public void InlinesRawHTML_Example621() { - // Example 620 + // Example 621 // Section: Inlines / Raw HTML // // The following Markdown: @@ -13774,14 +13794,14 @@ public void InlinesRawHTML_Example620() // <foo bar=baz // bim!bop />

- TestParser.TestSpec("< a><\nfoo>\n", "

< a><\nfoo><bar/ >\n<foo bar=baz\nbim!bop />

", "", context: "Example 620\nSection Inlines / Raw HTML\n"); + TestParser.TestSpec("< a><\nfoo>\n", "

< a><\nfoo><bar/ >\n<foo bar=baz\nbim!bop />

", "", context: "Example 621\nSection Inlines / Raw HTML\n"); } // Missing whitespace: [Test] - public void InlinesRawHTML_Example621() + public void InlinesRawHTML_Example622() { - // Example 621 + // Example 622 // Section: Inlines / Raw HTML // // The following Markdown: @@ -13790,14 +13810,14 @@ public void InlinesRawHTML_Example621() // Should be rendered as: //

<a href='bar'title=title>

- TestParser.TestSpec("
", "

<a href='bar'title=title>

", "", context: "Example 621\nSection Inlines / Raw HTML\n"); + TestParser.TestSpec("
", "

<a href='bar'title=title>

", "", context: "Example 622\nSection Inlines / Raw HTML\n"); } // Closing tags: [Test] - public void InlinesRawHTML_Example622() + public void InlinesRawHTML_Example623() { - // Example 622 + // Example 623 // Section: Inlines / Raw HTML // // The following Markdown: @@ -13806,14 +13826,14 @@ public void InlinesRawHTML_Example622() // Should be rendered as: //

- TestParser.TestSpec("", "

", "", context: "Example 622\nSection Inlines / Raw HTML\n"); + TestParser.TestSpec("", "

", "", context: "Example 623\nSection Inlines / Raw HTML\n"); } // Illegal attributes in closing tag: [Test] - public void InlinesRawHTML_Example623() + public void InlinesRawHTML_Example624() { - // Example 623 + // Example 624 // Section: Inlines / Raw HTML // // The following Markdown: @@ -13822,27 +13842,10 @@ public void InlinesRawHTML_Example623() // Should be rendered as: //

</a href="foo">

- TestParser.TestSpec("", "

</a href="foo">

", "", context: "Example 623\nSection Inlines / Raw HTML\n"); + TestParser.TestSpec("", "

</a href="foo">

", "", context: "Example 624\nSection Inlines / Raw HTML\n"); } // Comments: - [Test] - public void InlinesRawHTML_Example624() - { - // Example 624 - // Section: Inlines / Raw HTML - // - // The following Markdown: - // foo - // - // Should be rendered as: - //

foo

- - TestParser.TestSpec("foo ", "

foo

", "", context: "Example 624\nSection Inlines / Raw HTML\n"); - } - [Test] public void InlinesRawHTML_Example625() { @@ -13850,15 +13853,16 @@ public void InlinesRawHTML_Example625() // Section: Inlines / Raw HTML // // The following Markdown: - // foo + // foo // // Should be rendered as: - //

foo <!-- not a comment -- two hyphens -->

+ //

foo

- TestParser.TestSpec("foo ", "

foo <!-- not a comment -- two hyphens -->

", "", context: "Example 625\nSection Inlines / Raw HTML\n"); + TestParser.TestSpec("foo ", "

foo

", "", context: "Example 625\nSection Inlines / Raw HTML\n"); } - // Not comments: [Test] public void InlinesRawHTML_Example626() { @@ -13868,13 +13872,13 @@ public void InlinesRawHTML_Example626() // The following Markdown: // foo foo --> // - // foo + // foo foo --> // // Should be rendered as: - //

foo <!--> foo -->

- //

foo <!-- foo--->

+ //

foo foo -->

+ //

foo foo -->

- TestParser.TestSpec("foo foo -->\n\nfoo ", "

foo <!--> foo -->

\n

foo <!-- foo--->

", "", context: "Example 626\nSection Inlines / Raw HTML\n"); + TestParser.TestSpec("foo foo -->\n\nfoo foo -->", "

foo foo -->

\n

foo foo -->

", "", context: "Example 626\nSection Inlines / Raw HTML\n"); } // Processing instructions: @@ -14604,7 +14608,7 @@ public void InlinesTextualContent_Example652() // delimiter from the stack, and return a literal text node `]`. // // - If we find one and it's active, then we parse ahead to see if - // we have an inline link/image, reference link/image, compact reference + // we have an inline link/image, reference link/image, collapsed reference // link/image, or shortcut reference link/image. // // + If we don't, then we remove the opening delimiter from the diff --git a/src/Markdig.Tests/Specs/CommonMark.md b/src/Markdig.Tests/Specs/CommonMark.md index 23f7ee04..f1fab281 100644 --- a/src/Markdig.Tests/Specs/CommonMark.md +++ b/src/Markdig.Tests/Specs/CommonMark.md @@ -1,9 +1,9 @@ --- title: CommonMark Spec author: John MacFarlane -version: '0.30' -date: '2021-06-19' -license: '[CC-BY-SA 4.0](http://creativecommons.org/licenses/by-sa/4.0/)' +version: '0.31.2' +date: '2024-01-28' +license: '[CC-BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/)' ... # Introduction @@ -14,7 +14,7 @@ Markdown is a plain text format for writing structured documents, based on conventions for indicating formatting in email and usenet posts. It was developed by John Gruber (with help from Aaron Swartz) and released in 2004 in the form of a -[syntax description](http://daringfireball.net/projects/markdown/syntax) +[syntax description](https://daringfireball.net/projects/markdown/syntax) and a Perl script (`Markdown.pl`) for converting Markdown to HTML. In the next decade, dozens of implementations were developed in many languages. Some extended the original @@ -34,10 +34,10 @@ As Gruber writes: > Markdown-formatted document should be publishable as-is, as > plain text, without looking like it's been marked up with tags > or formatting instructions. -> () +> () The point can be illustrated by comparing a sample of -[AsciiDoc](http://www.methods.co.nz/asciidoc/) with +[AsciiDoc](https://asciidoc.org/) with an equivalent sample of Markdown. Here is a sample of AsciiDoc from the AsciiDoc manual: @@ -103,7 +103,7 @@ source, not just in the processed document. ## Why is a spec needed? John Gruber's [canonical description of Markdown's -syntax](http://daringfireball.net/projects/markdown/syntax) +syntax](https://daringfireball.net/projects/markdown/syntax) does not specify the syntax unambiguously. Here are some examples of questions it does not answer: @@ -316,9 +316,9 @@ A line containing no characters, or a line containing only spaces The following definitions of character classes will be used in this spec: -A [Unicode whitespace character](@) is -any code point in the Unicode `Zs` general category, or a tab (`U+0009`), -line feed (`U+000A`), form feed (`U+000C`), or carriage return (`U+000D`). +A [Unicode whitespace character](@) is a character in the Unicode `Zs` general +category, or a tab (`U+0009`), line feed (`U+000A`), form feed (`U+000C`), or +carriage return (`U+000D`). [Unicode whitespace](@) is a sequence of one or more [Unicode whitespace characters]. @@ -337,9 +337,8 @@ is `!`, `"`, `#`, `$`, `%`, `&`, `'`, `(`, `)`, `[`, `\`, `]`, `^`, `_`, `` ` `` (U+005B–0060), `{`, `|`, `}`, or `~` (U+007B–007E). -A [Unicode punctuation character](@) is an [ASCII -punctuation character] or anything in -the general Unicode categories `Pc`, `Pd`, `Pe`, `Pf`, `Pi`, `Po`, or `Ps`. +A [Unicode punctuation character](@) is a character in the Unicode `P` +(puncuation) or `S` (symbol) general categories. ## Tabs @@ -579,9 +578,9 @@ raw HTML: ```````````````````````````````` example - + . -

http://example.com?find=\*

+

https://example.com?find=\*

```````````````````````````````` @@ -1964,7 +1963,7 @@ has been found, the code block contains all of the lines after the opening code fence until the end of the containing block (or document). (An alternative spec would require backtracking in the event that a closing code fence is not found. But this makes parsing -much less efficient, and there seems to be no real down side to the +much less efficient, and there seems to be no real downside to the behavior described here.) A fenced code block may interrupt a paragraph, and does not require @@ -2403,7 +2402,7 @@ followed by one of the strings (case-insensitive) `address`, `h1`, `h2`, `h3`, `h4`, `h5`, `h6`, `head`, `header`, `hr`, `html`, `iframe`, `legend`, `li`, `link`, `main`, `menu`, `menuitem`, `nav`, `noframes`, `ol`, `optgroup`, `option`, `p`, `param`, -`section`, `source`, `summary`, `table`, `tbody`, `td`, +`search`, `section`, `summary`, `table`, `tbody`, `td`, `tfoot`, `th`, `thead`, `title`, `tr`, `track`, `ul`, followed by a space, a tab, the end of the line, the string `>`, or the string `/>`.\ @@ -4115,7 +4114,7 @@ The following rules define [list items]: blocks *Bs* starting with a character other than a space or tab, and *M* is a list marker of width *W* followed by 1 ≤ *N* ≤ 4 spaces of indentation, then the result of prepending *M* and the following spaces to the first line - of Ls*, and indenting subsequent lines of *Ls* by *W + N* spaces, is a + of *Ls*, and indenting subsequent lines of *Ls* by *W + N* spaces, is a list item with *Bs* as its contents. The type of the list item (bullet or ordered) is determined by the type of its list marker. If the list item is ordered, then it is also assigned a start @@ -5350,11 +5349,11 @@ by itself should be a paragraph followed by a nested sublist. Since it is well established Markdown practice to allow lists to interrupt paragraphs inside list items, the [principle of uniformity] requires us to allow this outside list items as -well. ([reStructuredText](http://docutils.sourceforge.net/rst.html) +well. ([reStructuredText](https://docutils.sourceforge.net/rst.html) takes a different approach, requiring blank lines before lists even inside other list items.) -In order to solve of unwanted lists in paragraphs with +In order to solve the problem of unwanted lists in paragraphs with hard-wrapped numerals, we allow only lists starting with `1` to interrupt paragraphs. Thus, @@ -6055,18 +6054,18 @@ But this is an HTML tag: And this is code: ```````````````````````````````` example -`` +`` . -

<http://foo.bar.baz>`

+

<https://foo.bar.baz>`

```````````````````````````````` But this is an autolink: ```````````````````````````````` example -` +` . -

http://foo.bar.`baz`

+

https://foo.bar.`baz`

```````````````````````````````` @@ -6099,7 +6098,7 @@ closing backtick strings to be equal in length: ## Emphasis and strong emphasis John Gruber's original [Markdown syntax -description](http://daringfireball.net/projects/markdown/syntax#em) says: +description](https://daringfireball.net/projects/markdown/syntax#em) says: > Markdown treats asterisks (`*`) and underscores (`_`) as indicators of > emphasis. Text wrapped with one `*` or `_` will be wrapped with an HTML @@ -6201,7 +6200,7 @@ Here are some examples of delimiter runs. (The idea of distinguishing left-flanking and right-flanking delimiter runs based on the character before and the character after comes from Roopesh Chander's -[vfmd](http://www.vfmd.org/vfmd-spec/specification/#procedure-for-identifying-emphasis-tags). +[vfmd](https://web.archive.org/web/20220608143320/http://www.vfmd.org/vfmd-spec/specification/#procedure-for-identifying-emphasis-tags). vfmd uses the terminology "emphasis indicator string" instead of "delimiter run," and its rules for distinguishing left- and right-flanking runs are a bit more complex than the ones given here.) @@ -6343,6 +6342,21 @@ Unicode nonbreaking spaces count as whitespace, too: ```````````````````````````````` +Unicode symbols count as punctuation, too: + +```````````````````````````````` example +*$*alpha. + +*£*bravo. + +*€*charlie. +. +

*$*alpha.

+

*£*bravo.

+

*€*charlie.

+```````````````````````````````` + + Intraword emphasis with `*` is permitted: ```````````````````````````````` example @@ -7428,16 +7442,16 @@ _a `_`_ ```````````````````````````````` example -**a +**a . -

**ahttp://foo.bar/?q=**

+

**ahttps://foo.bar/?q=**

```````````````````````````````` ```````````````````````````````` example -__a +__a . -

__ahttp://foo.bar/?q=__

+

__ahttps://foo.bar/?q=__

```````````````````````````````` @@ -7685,13 +7699,13 @@ A link can contain fragment identifiers and queries: ```````````````````````````````` example [link](#fragment) -[link](http://example.com#fragment) +[link](https://example.com#fragment) -[link](http://example.com?foo=3#frag) +[link](https://example.com?foo=3#frag) .

link

-

link

-

link

+

link

+

link

```````````````````````````````` @@ -7935,9 +7949,9 @@ and autolinks over link grouping: ```````````````````````````````` example -[foo +[foo . -

[foohttp://example.com/?search=](uri)

+

[foohttps://example.com/?search=](uri)

```````````````````````````````` @@ -8091,11 +8105,11 @@ and autolinks over link grouping: ```````````````````````````````` example -[foo +[foo [ref]: /uri . -

[foohttp://example.com/?search=][ref]

+

[foohttps://example.com/?search=][ref]

```````````````````````````````` @@ -8295,7 +8309,7 @@ A [collapsed reference link](@) consists of a [link label] that [matches] a [link reference definition] elsewhere in the document, followed by the string `[]`. -The contents of the first link label are parsed as inlines, +The contents of the link label are parsed as inlines, which are used as the link's text. The link's URI and title are provided by the matching reference link definition. Thus, `[foo][]` is equivalent to `[foo][foo]`. @@ -8348,7 +8362,7 @@ A [shortcut reference link](@) consists of a [link label] that [matches] a [link reference definition] elsewhere in the document and is not followed by `[]` or a link label. -The contents of the first link label are parsed as inlines, +The contents of the link label are parsed as inlines, which are used as the link's text. The link's URI and title are provided by the matching link reference definition. Thus, `[foo]` is equivalent to `[foo][]`. @@ -8435,7 +8449,7 @@ following closing bracket: ```````````````````````````````` -Full and compact references take precedence over shortcut +Full and collapsed references take precedence over shortcut references: ```````````````````````````````` example @@ -8771,9 +8785,9 @@ Here are some valid autolinks: ```````````````````````````````` example - + . -

http://foo.bar.baz/test?q=hello&id=22&boolean

+

https://foo.bar.baz/test?q=hello&id=22&boolean

```````````````````````````````` @@ -8813,9 +8827,9 @@ with their syntax: ```````````````````````````````` example - + . -

http://../

+

https://../

```````````````````````````````` @@ -8829,18 +8843,18 @@ with their syntax: Spaces are not allowed in autolinks: ```````````````````````````````` example - + . -

<http://foo.bar/baz bim>

+

<https://foo.bar/baz bim>

```````````````````````````````` Backslash-escapes do not work inside autolinks: ```````````````````````````````` example - + . -

http://example.com/\[\

+

https://example.com/\[\

```````````````````````````````` @@ -8892,9 +8906,9 @@ These are not autolinks: ```````````````````````````````` example -< http://foo.bar > +< https://foo.bar > . -

< http://foo.bar >

+

< https://foo.bar >

```````````````````````````````` @@ -8913,9 +8927,9 @@ These are not autolinks: ```````````````````````````````` example -http://example.com +https://example.com . -

http://example.com

+

https://example.com

```````````````````````````````` @@ -8977,10 +8991,9 @@ A [closing tag](@) consists of the string ``. -An [HTML comment](@) consists of ``, -where *text* does not start with `>` or `->`, does not end with `-`, -and does not contain `--`. (See the -[HTML5 spec](http://www.w3.org/TR/html5/syntax.html#comments).) +An [HTML comment](@) consists of ``, ``, or ``, and `-->` (see the +[HTML spec](https://html.spec.whatwg.org/multipage/parsing.html#markup-declaration-open-state)). A [processing instruction](@) consists of the string ` +foo . -

foo

+

foo

```````````````````````````````` - -```````````````````````````````` example -foo -. -

foo <!-- not a comment -- two hyphens -->

-```````````````````````````````` - - -Not comments: - ```````````````````````````````` example foo foo --> -foo +foo foo --> . -

foo <!--> foo -->

-

foo <!-- foo--->

+

foo foo -->

+

foo foo -->

```````````````````````````````` @@ -9671,7 +9674,7 @@ through the stack for an opening `[` or `![` delimiter. delimiter from the stack, and return a literal text node `]`. - If we find one and it's active, then we parse ahead to see if - we have an inline link/image, reference link/image, compact reference + we have an inline link/image, reference link/image, collapsed reference link/image, or shortcut reference link/image. + If we don't, then we remove the opening delimiter from the From a9ce0eb438e48706d123cde703f85fb5627134ed Mon Sep 17 00:00:00 2001 From: Miha Zupan Date: Tue, 15 Apr 2025 03:09:59 +0200 Subject: [PATCH 56/88] Update definition of punctuation to include symbols --- .../Specs/AutoLinks.generated.cs | 23 +++++++ src/Markdig.Tests/Specs/AutoLinks.md | 15 ++++ .../Specs/EmphasisExtraSpecs.generated.cs | 10 +-- src/Markdig.Tests/Specs/EmphasisExtraSpecs.md | 9 +-- src/Markdig.Tests/TestCharHelper.cs | 62 ++++++++++++++--- src/Markdig/Helpers/CharHelper.cs | 69 ++++++++++++------- src/Markdig/Helpers/LinkHelper.cs | 58 ++++++++-------- 7 files changed, 172 insertions(+), 74 deletions(-) diff --git a/src/Markdig.Tests/Specs/AutoLinks.generated.cs b/src/Markdig.Tests/Specs/AutoLinks.generated.cs index 9828d56d..adeef9ae 100644 --- a/src/Markdig.Tests/Specs/AutoLinks.generated.cs +++ b/src/Markdig.Tests/Specs/AutoLinks.generated.cs @@ -533,5 +533,28 @@ public void ExtensionsAutoLinksUnicodeSupport_Example025() TestParser.TestSpec("`", "

http://foö.bar.`baz`

", "autolinks|advanced", context: "Example 25\nSection Extensions / AutoLinks / Unicode support\n"); } + + // Unicode punctuation characters are not allowed, but symbols are. + // Note that this does _not_ exactly match CommonMark's "Unicode punctuation character" definition. + [Test] + public void ExtensionsAutoLinksUnicodeSupport_Example026() + { + // Example 26 + // Section: Extensions / AutoLinks / Unicode support + // + // The following Markdown: + // http://☃.net?☃ // OtherSymbol + // + // http://🍉.net?🍉 // A UTF-16 surrogate pair, but code point is OtherSymbol + // + // http://‰.net?‰ // OtherPunctuation + // + // Should be rendered as: + //

http://☃.net?☃ // OtherSymbol

+ //

http://🍉.net?🍉 // A UTF-16 surrogate pair, but code point is OtherSymbol

+ //

http://‰.net?‰ // OtherPunctuation

+ + TestParser.TestSpec("http://☃.net?☃ // OtherSymbol\n\nhttp://🍉.net?🍉 // A UTF-16 surrogate pair, but code point is OtherSymbol\n\nhttp://‰.net?‰ // OtherPunctuation", "

http://☃.net?☃ // OtherSymbol

\n

http://🍉.net?🍉 // A UTF-16 surrogate pair, but code point is OtherSymbol

\n

http://‰.net?‰ // OtherPunctuation

", "autolinks|advanced", context: "Example 26\nSection Extensions / AutoLinks / Unicode support\n"); + } } } diff --git a/src/Markdig.Tests/Specs/AutoLinks.md b/src/Markdig.Tests/Specs/AutoLinks.md index f0a148c6..1727e94d 100644 --- a/src/Markdig.Tests/Specs/AutoLinks.md +++ b/src/Markdig.Tests/Specs/AutoLinks.md @@ -303,4 +303,19 @@ This will therefore be seen as an autolink and not as code inline. ` .

http://foö.bar.`baz`

+```````````````````````````````` + +Unicode punctuation characters are not allowed, but symbols are. +Note that this does _not_ exactly match CommonMark's "Unicode punctuation character" definition. + +```````````````````````````````` example +http://☃.net?☃ // OtherSymbol + +http://🍉.net?🍉 // A UTF-16 surrogate pair, but code point is OtherSymbol + +http://‰.net?‰ // OtherPunctuation +. +

http://☃.net?☃ // OtherSymbol

+

http://🍉.net?🍉 // A UTF-16 surrogate pair, but code point is OtherSymbol

+

http://‰.net?‰ // OtherPunctuation

```````````````````````````````` \ No newline at end of file diff --git a/src/Markdig.Tests/Specs/EmphasisExtraSpecs.generated.cs b/src/Markdig.Tests/Specs/EmphasisExtraSpecs.generated.cs index e501c409..89d04042 100644 --- a/src/Markdig.Tests/Specs/EmphasisExtraSpecs.generated.cs +++ b/src/Markdig.Tests/Specs/EmphasisExtraSpecs.generated.cs @@ -123,6 +123,8 @@ public void ExtensionsMarked_Example005() public class TestExtensionsEmphasisOnHtmlEntities { // ## Emphasis on Html Entities + // + // Note that Unicode symbols are treated as punctuation, which are not allowed to open the emphasis unless they are preceded by a space. [Test] public void ExtensionsEmphasisOnHtmlEntities_Example006() { @@ -132,14 +134,14 @@ public void ExtensionsEmphasisOnHtmlEntities_Example006() // The following Markdown: // This is text MyBrand ^®^ and MyTrademark ^™^ // This is text MyBrand^®^ and MyTrademark^™^ - // This is text MyBrand~®~ and MyCopyright^©^ + // This is text MyBrand ~®~ and MyCopyright ^©^ // // Should be rendered as: //

This is text MyBrand ® and MyTrademark TM - // This is text MyBrand® and MyTrademarkTM - // This is text MyBrand® and MyCopyright©

+ // This is text MyBrand^®^ and MyTrademark^TM^ + // This is text MyBrand ® and MyCopyright ©

- TestParser.TestSpec("This is text MyBrand ^®^ and MyTrademark ^™^\nThis is text MyBrand^®^ and MyTrademark^™^\nThis is text MyBrand~®~ and MyCopyright^©^", "

This is text MyBrand ® and MyTrademark TM\nThis is text MyBrand® and MyTrademarkTM\nThis is text MyBrand® and MyCopyright©

", "emphasisextras|advanced", context: "Example 6\nSection Extensions / Emphasis on Html Entities\n"); + TestParser.TestSpec("This is text MyBrand ^®^ and MyTrademark ^™^\nThis is text MyBrand^®^ and MyTrademark^™^\nThis is text MyBrand ~®~ and MyCopyright ^©^", "

This is text MyBrand ® and MyTrademark TM\nThis is text MyBrand^®^ and MyTrademark^TM^\nThis is text MyBrand ® and MyCopyright ©

", "emphasisextras|advanced", context: "Example 6\nSection Extensions / Emphasis on Html Entities\n"); } } } diff --git a/src/Markdig.Tests/Specs/EmphasisExtraSpecs.md b/src/Markdig.Tests/Specs/EmphasisExtraSpecs.md index 50f6e14e..8facd4ff 100644 --- a/src/Markdig.Tests/Specs/EmphasisExtraSpecs.md +++ b/src/Markdig.Tests/Specs/EmphasisExtraSpecs.md @@ -52,16 +52,17 @@ Marked text can be used to specify that a text has been marked in a document. T .

Marked text

```````````````````````````````` + ## Emphasis on Html Entities +Note that Unicode symbols are treated as punctuation, which are not allowed to open the emphasis unless they are preceded by a space. ```````````````````````````````` example This is text MyBrand ^®^ and MyTrademark ^™^ This is text MyBrand^®^ and MyTrademark^™^ -This is text MyBrand~®~ and MyCopyright^©^ +This is text MyBrand ~®~ and MyCopyright ^©^ .

This is text MyBrand ® and MyTrademark TM -This is text MyBrand® and MyTrademarkTM -This is text MyBrand® and MyCopyright©

+This is text MyBrand^®^ and MyTrademark^TM^ +This is text MyBrand ® and MyCopyright ©

```````````````````````````````` - diff --git a/src/Markdig.Tests/TestCharHelper.cs b/src/Markdig.Tests/TestCharHelper.cs index ac201518..81c2c327 100644 --- a/src/Markdig.Tests/TestCharHelper.cs +++ b/src/Markdig.Tests/TestCharHelper.cs @@ -19,18 +19,32 @@ public class TestCharHelper '{', '|', '}', '~' }; - // A Unicode punctuation character is an ASCII punctuation character or anything in the general Unicode categories - // Pc, Pd, Pe, Pf, Pi, Po, or Ps. - private static readonly HashSet s_punctuationCategories = new() - { + // A Unicode punctuation character is a character in the Unicode P (punctuation) or S (symbol) general categories. + private static readonly HashSet s_punctuationCategories = + [ UnicodeCategory.ConnectorPunctuation, UnicodeCategory.DashPunctuation, + UnicodeCategory.OpenPunctuation, UnicodeCategory.ClosePunctuation, + UnicodeCategory.InitialQuotePunctuation, UnicodeCategory.FinalQuotePunctuation, + UnicodeCategory.OtherPunctuation, + UnicodeCategory.MathSymbol, + UnicodeCategory.CurrencySymbol, + UnicodeCategory.ModifierSymbol, + UnicodeCategory.OtherSymbol, + ]; + + private static readonly HashSet s_punctuationWithoutSymbolsCategories = + [ + UnicodeCategory.ConnectorPunctuation, + UnicodeCategory.DashPunctuation, + UnicodeCategory.OpenPunctuation, + UnicodeCategory.ClosePunctuation, UnicodeCategory.InitialQuotePunctuation, + UnicodeCategory.FinalQuotePunctuation, UnicodeCategory.OtherPunctuation, - UnicodeCategory.OpenPunctuation - }; + ]; private static bool ExpectedIsPunctuation(char c) { @@ -39,6 +53,13 @@ private static bool ExpectedIsPunctuation(char c) : s_punctuationCategories.Contains(CharUnicodeInfo.GetUnicodeCategory(c)); } + private static bool ExpectedIsPunctuationWithoutSymbols(char c) + { + return c <= 127 + ? s_asciiPunctuation.Contains(c) + : s_punctuationWithoutSymbolsCategories.Contains(CharUnicodeInfo.GetUnicodeCategory(c)); + } + private static bool ExpectedIsWhitespace(char c) { // A Unicode whitespace character is any code point in the Unicode Zs general category, @@ -105,11 +126,25 @@ public void IsAsciiPunctuationOrZero() } [Test] - public void IsSpaceOrPunctuation() + public void IsSpaceOrPunctuationForGFMAutoLink() + { + Test( + c => c == 0 || ExpectedIsWhitespace(c) || ExpectedIsPunctuationWithoutSymbols(c), + CharHelper.IsSpaceOrPunctuationForGFMAutoLink); + } + + [Test] + public void InvalidAutoLinkCharacters() { + // 6.5 Autolinks - https://spec.commonmark.org/0.31.2/#autolinks + // An absolute URI, for these purposes, consists of a scheme followed by a colon (:) followed by + // zero or more characters other than ASCII control characters, space, <, and >. + // + // 2.1 Characters and lines + // An ASCII control character is a character between U+0000–1F (both including) or U+007F. Test( - c => c == 0 || ExpectedIsWhitespace(c) || ExpectedIsPunctuation(c), - CharHelper.IsSpaceOrPunctuation); + c => c != 0 && c is < (char)0x20 or ' ' or '<' or '>' or '\u007F', + CharHelper.InvalidAutoLinkCharacters.Contains); } [Test] @@ -214,7 +249,14 @@ private static void Test(Func expected, Func actual) for (int i = char.MinValue; i <= char.MaxValue; i++) { char c = (char)i; - Assert.AreEqual(expected(c), actual(c)); + + bool expectedResult = expected(c); + bool actualResult = actual(c); + + if (expectedResult != actualResult) + { + Assert.AreEqual(expectedResult, actualResult, $"Char: '{c}' ({i})"); + } } } } diff --git a/src/Markdig/Helpers/CharHelper.cs b/src/Markdig/Helpers/CharHelper.cs index 5d6180a6..8844434f 100644 --- a/src/Markdig/Helpers/CharHelper.cs +++ b/src/Markdig/Helpers/CharHelper.cs @@ -37,6 +37,29 @@ public static class CharHelper // {, |, }, or ~ (U+007B–007E). private const string AsciiPunctuationChars = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"; + // Unicode P (punctuation) categories. + private const int UnicodePunctuationCategoryMask = + 1 << (int)UnicodeCategory.ConnectorPunctuation | + 1 << (int)UnicodeCategory.DashPunctuation | + 1 << (int)UnicodeCategory.OpenPunctuation | + 1 << (int)UnicodeCategory.ClosePunctuation | + 1 << (int)UnicodeCategory.InitialQuotePunctuation | + 1 << (int)UnicodeCategory.FinalQuotePunctuation | + 1 << (int)UnicodeCategory.OtherPunctuation; + + private const int UnicodePunctuationOrSpaceCategoryMask = + UnicodePunctuationCategoryMask | + 1 << (int)UnicodeCategory.SpaceSeparator; + + // 2.1 Characters and lines + // A Unicode punctuation character is a character in the Unicode P (punctuation) or S (symbol) general categories. + private const int CommonMarkPunctuationCategoryMask = + UnicodePunctuationCategoryMask | + 1 << (int)UnicodeCategory.MathSymbol | + 1 << (int)UnicodeCategory.CurrencySymbol | + 1 << (int)UnicodeCategory.ModifierSymbol | + 1 << (int)UnicodeCategory.OtherSymbol; + // We're not currently using these SearchValues instances for vectorized IndexOfAny-like searches, but for their efficient single Contains(char) checks. private static readonly SearchValues s_emailUsernameSpecialChar = SearchValues.Create(EmailUsernameSpecialChars); private static readonly SearchValues s_emailUsernameSpecialCharOrDigit = SearchValues.Create(EmailUsernameSpecialChars + "0123456789"); @@ -235,46 +258,42 @@ public static void CheckUnicodeCategory(this char c, out bool space, out bool pu } else { - // A Unicode punctuation character is an ASCII punctuation character - // or anything in the general Unicode categories Pc, Pd, Pe, Pf, Pi, Po, or Ps. - const int PunctuationCategoryMask = - 1 << (int)UnicodeCategory.ConnectorPunctuation | - 1 << (int)UnicodeCategory.DashPunctuation | - 1 << (int)UnicodeCategory.OpenPunctuation | - 1 << (int)UnicodeCategory.ClosePunctuation | - 1 << (int)UnicodeCategory.InitialQuotePunctuation | - 1 << (int)UnicodeCategory.FinalQuotePunctuation | - 1 << (int)UnicodeCategory.OtherPunctuation; - space = false; - punctuation = (PunctuationCategoryMask & (1 << (int)CharUnicodeInfo.GetUnicodeCategory(c))) != 0; + punctuation = (CommonMarkPunctuationCategoryMask & (1 << (int)CharUnicodeInfo.GetUnicodeCategory(c))) != 0; } } - // Same as CheckUnicodeCategory [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static bool IsSpaceOrPunctuation(this char c) + internal static bool IsSpaceOrPunctuationForGFMAutoLink(char c) { + // Github Flavored Markdown's allowed set of domain characters differs from CommonMark's "punctuation" definition. + // CommonMark also counts symbols as punctuation, but GitHub will render e.g. http://☃.net as an autolink, despite + // the snowman emoji falling under the OtherSymbol (So) category. if (c <= 127) { return s_asciiPunctuationOrWhitespaceCharsOrZero.Contains(c); } else { - const int PunctuationCategoryMask = - 1 << (int)UnicodeCategory.ConnectorPunctuation | - 1 << (int)UnicodeCategory.DashPunctuation | - 1 << (int)UnicodeCategory.OpenPunctuation | - 1 << (int)UnicodeCategory.ClosePunctuation | - 1 << (int)UnicodeCategory.InitialQuotePunctuation | - 1 << (int)UnicodeCategory.FinalQuotePunctuation | - 1 << (int)UnicodeCategory.OtherPunctuation | - 1 << (int)UnicodeCategory.SpaceSeparator; - - return (PunctuationCategoryMask & (1 << (int)CharUnicodeInfo.GetUnicodeCategory(c))) != 0; + return NonAscii(c); + + static bool NonAscii(char c) => + (UnicodePunctuationOrSpaceCategoryMask & (1 << (int)CharUnicodeInfo.GetUnicodeCategory(c))) != 0; } } + // 6.5 Autolinks - https://spec.commonmark.org/0.31.2/#autolinks + // An absolute URI, for these purposes, consists of a scheme followed by a colon (:) followed by + // zero or more characters other than ASCII control characters, space, <, and >. + // + // 2.1 Characters and lines + // An ASCII control character is a character between U+0000–1F (both including) or U+007F. + internal static readonly SearchValues InvalidAutoLinkCharacters = SearchValues.Create( + // 0 is excluded because it can be slightly more expensive for SearchValues to handle, and we've already removed it from the input text. + "\u0001\u0002\u0003\u0004\u0005\u0006\u0007\u0008\u0009\u000A\u000B\u000C\u000D\u000E\u000F" + + "\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017\u0018\u0019\u001A\u001B\u001C\u001D\u001E\u001F" + + " <>\u007F"); + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsNewLineOrLineFeed(this char c) { diff --git a/src/Markdig/Helpers/LinkHelper.cs b/src/Markdig/Helpers/LinkHelper.cs index 15c5089d..b76cddca 100644 --- a/src/Markdig/Helpers/LinkHelper.cs +++ b/src/Markdig/Helpers/LinkHelper.cs @@ -2,6 +2,8 @@ // This file is licensed under the BSD-Clause 2 license. // See the license.txt file in the project root for more information. +using System.Buffers; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using Markdig.Syntax; @@ -286,40 +288,34 @@ public static bool TryParseAutolink(ref StringSlice text, [NotNullWhen(true)] ou } else { - // scan an uri - // An absolute URI, for these purposes, consists of a scheme followed by a colon (:) - // followed by zero or more characters other than ASCII whitespace and control characters, <, and >. + // 6.5 Autolinks - https://spec.commonmark.org/0.31.2/#autolinks + // An absolute URI, for these purposes, consists of a scheme followed by a colon (:) followed by + // zero or more characters other than ASCII control characters, space, <, and >. // If the URI includes these characters, they must be percent-encoded (e.g. %20 for a space). + // + // 2.1 Characters and lines + // An ASCII control character is a character between U+0000–1F (both including) or U+007F. - while (true) - { - c = text.NextChar(); - if (c == '\0') - { - break; - } + text.SkipChar(); + ReadOnlySpan slice = text.AsSpan(); - if (c == '>') - { - text.SkipChar(); - link = builder.ToString(); - return true; - } + Debug.Assert(!slice.Contains('\0')); - // Chars valid for both scheme and email - if (c <= 127) - { - if (c > ' ' && c != '>') - { - builder.Append(c); - } - else break; - } - else if (!c.IsSpaceOrPunctuation()) - { - builder.Append(c); - } - else break; + // This set of invalid characters includes '>'. + int end = slice.IndexOfAny(CharHelper.InvalidAutoLinkCharacters); + + if ((uint)end < (uint)slice.Length && slice[end] == '>') + { + // We've found '>' and all characters before it are valid. +#if NET + link = string.Concat(builder.AsSpan(), slice.Slice(0, end)); + builder.Dispose(); +#else + builder.Append(slice.Slice(0, end)); + link = builder.ToString(); +#endif + text.Start += end + 1; // +1 to skip '>' + return true; } } @@ -1042,7 +1038,7 @@ public static bool IsValidDomain(string link, int prefixLength, bool allowDomain { lastUnderscoreSegment = segmentCount; } - else if (c != '-' && c.IsSpaceOrPunctuation()) + else if (c != '-' && CharHelper.IsSpaceOrPunctuationForGFMAutoLink(c)) { // An invalid character has been found return false; From 61e9be290b1dad611e8137251d249a3aae9664d3 Mon Sep 17 00:00:00 2001 From: Miha Zupan Date: Tue, 15 Apr 2025 04:02:22 +0200 Subject: [PATCH 57/88] Allow empty HTML comments, double hyphens in text --- src/Markdig/Helpers/HtmlHelper.cs | 59 +++++++++++++++++++------------ 1 file changed, 36 insertions(+), 23 deletions(-) diff --git a/src/Markdig/Helpers/HtmlHelper.cs b/src/Markdig/Helpers/HtmlHelper.cs index e3722df1..4887653f 100644 --- a/src/Markdig/Helpers/HtmlHelper.cs +++ b/src/Markdig/Helpers/HtmlHelper.cs @@ -2,6 +2,7 @@ // This file is licensed under the BSD-Clause 2 license. // See the license.txt file in the project root for more information. +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; @@ -393,40 +394,52 @@ internal static bool TryParseHtmlCloseTag(ref StringSlice text, ref ValueStringB private static bool TryParseHtmlTagHtmlComment(ref StringSlice text, ref ValueStringBuilder builder) { + // https://spec.commonmark.org/0.31.2/#raw-html + // An HTML comment consists of , , or + // , and -->. + + // The caller already checked ') + + c = text.NextChar(); + + if (c == '>') { - return false; + // is considered valid. + builder.Append("-->"); + text.SkipChar(); + return true; } - var countHyphen = 0; - while (true) + if (c == '-' && text.PeekChar() == '>') { - c = text.NextChar(); - if (c == '\0') - { - return false; - } + // is also considered valid. + builder.Append("--->"); + text.SkipChar(); + text.SkipChar(); + return true; + } - if (countHyphen == 2) - { - if (c == '>') - { - builder.Append('>'); - text.SkipChar(); - return true; - } - return false; - } - countHyphen = c == '-' ? countHyphen + 1 : 0; - builder.Append(c); + ReadOnlySpan slice = text.AsSpan(); + + const string EndOfComment = "-->"; + + int endOfComment = slice.IndexOf(EndOfComment, StringComparison.Ordinal); + if (endOfComment < 0) + { + return false; } + + builder.Append("--"); + builder.Append(slice.Slice(0, endOfComment + EndOfComment.Length)); + text.Start += endOfComment + EndOfComment.Length; + return true; } private static bool TryParseHtmlTagProcessingInstruction(ref StringSlice text, ref ValueStringBuilder builder) From b15cf582a538e0c0e016e70cb8a0f03d4f2d55d8 Mon Sep 17 00:00:00 2001 From: Miha Zupan Date: Tue, 15 Apr 2025 04:31:13 +0200 Subject: [PATCH 58/88] Add 'search' HTML tag support --- src/Markdig.Tests/TestHtmlCodeBlocks.cs | 35 +++++++++++++++++++++++++ src/Markdig/Parsers/HtmlBlockParser.cs | 5 ++-- 2 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 src/Markdig.Tests/TestHtmlCodeBlocks.cs diff --git a/src/Markdig.Tests/TestHtmlCodeBlocks.cs b/src/Markdig.Tests/TestHtmlCodeBlocks.cs new file mode 100644 index 00000000..5935d6ef --- /dev/null +++ b/src/Markdig.Tests/TestHtmlCodeBlocks.cs @@ -0,0 +1,35 @@ +using Markdig.Syntax; + +namespace Markdig.Tests; + +public class TestHtmlCodeBlocks +{ + // Start condition: line begins with the string < or , or the string />. + public static string[] KnownSimpleHtmlTags => + [ + "address", "article", "aside", "base", "basefont", "blockquote", "body", "caption", "center", "col", "colgroup", "dd", "details", + "dialog", "dir", "div", "dl", "dt", "fieldset", "figcaption", "figure", "footer", "form", "frame", "frameset", + "h1", "h2", "h3", "h4", "h5", "h6", "head", "header", "hr", "html", "iframe", "legend", "li", "link", + "main", "menu", "menuitem", "nav", "noframes", "ol", "optgroup", "option", "p", "param", + "search", "section", "summary", "table", "tbody", "td", "tfoot", "th", "thead", "title", "tr", "track", "ul", + ]; + + [Theory] + [TestCaseSource(nameof(KnownSimpleHtmlTags))] + public void TestKnownTags(string tag) + { + MarkdownDocument document = Markdown.Parse( + $""" + Hello + <{tag} /> + World + """.ReplaceLineEndings("\n")); + + HtmlBlock[] htmlBlocks = document.Descendants().ToArray(); + + Assert.AreEqual(1, htmlBlocks.Length); + Assert.AreEqual(7, htmlBlocks[0].Span.Start); + Assert.AreEqual(10 + tag.Length, htmlBlocks[0].Span.Length); + } +} \ No newline at end of file diff --git a/src/Markdig/Parsers/HtmlBlockParser.cs b/src/Markdig/Parsers/HtmlBlockParser.cs index bcc6ca73..5c85e228 100644 --- a/src/Markdig/Parsers/HtmlBlockParser.cs +++ b/src/Markdig/Parsers/HtmlBlockParser.cs @@ -295,7 +295,7 @@ private BlockState CreateHtmlBlock(BlockProcessor state, HtmlBlockType type, int return BlockState.Continue; } - private static readonly CompactPrefixTree HtmlTags = new(66, 94, 83) + private static readonly CompactPrefixTree HtmlTags = new(67, 96, 86) { { "address", 0 }, { "article", 1 }, @@ -362,6 +362,7 @@ private BlockState CreateHtmlBlock(BlockProcessor state, HtmlBlockType type, int { "title", 62 }, { "tr", 63 }, { "track", 64 }, - { "ul", 65 } + { "ul", 65 }, + { "search", 66 }, }; } \ No newline at end of file From 42ab98968d7efcc76a18d5bdabd1b2e48166539b Mon Sep 17 00:00:00 2001 From: Miha Zupan Date: Tue, 15 Apr 2025 04:32:52 +0200 Subject: [PATCH 59/88] Update readme --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index e5f5f7ab..b9b8f9a8 100644 --- a/readme.md +++ b/readme.md @@ -14,7 +14,7 @@ You can **try Markdig online** and compare it to other implementations on [babel - **Abstract Syntax Tree** with precise source code location for syntax tree, useful when building a Markdown editor. - Checkout [Markdown Editor v2 for Visual Studio 2022](https://marketplace.visualstudio.com/items?itemName=MadsKristensen.MarkdownEditor2) powered by Markdig! - Converter to **HTML** -- Passing more than **600+ tests** from the latest [CommonMark specs (0.30)](http://spec.commonmark.org/) +- Passing more than **600+ tests** from the latest [CommonMark specs (0.31.2)](http://spec.commonmark.org/) - Includes all the core elements of CommonMark: - including **GFM fenced code blocks**. - **Extensible** architecture From 09a4b81a6ed04baaf1f890a2e8665c01f9494ab6 Mon Sep 17 00:00:00 2001 From: Alexandre Mutel Date: Tue, 15 Apr 2025 11:35:54 +0200 Subject: [PATCH 60/88] Update tests --- .../Specs/MathSpecs.generated.cs | 90 +++++++++---------- 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/src/Markdig.Tests/Specs/MathSpecs.generated.cs b/src/Markdig.Tests/Specs/MathSpecs.generated.cs index ee0a291c..0077dab1 100644 --- a/src/Markdig.Tests/Specs/MathSpecs.generated.cs +++ b/src/Markdig.Tests/Specs/MathSpecs.generated.cs @@ -17,7 +17,7 @@ public class TestExtensionsMathInline // // ## Math Inline // - // Allows to define a mathematic block embraced by `$...$` + // Allows to define a mathematic inline block embraced by `$...$` [Test] public void ExtensionsMathInline_Example001() { @@ -25,12 +25,12 @@ public void ExtensionsMathInline_Example001() // Section: Extensions / Math Inline // // The following Markdown: - // This is a $math block$ + // This is a $math inline$ // // Should be rendered as: - //

This is a \(math block\)

+ //

This is a \(math inline\)

- TestParser.TestSpec("This is a $math block$", "

This is a \\(math block\\)

", "mathematics|advanced", context: "Example 1\nSection Extensions / Math Inline\n"); + TestParser.TestSpec("This is a $math inline$", "

This is a \\(math inline\\)

", "mathematics|advanced", context: "Example 1\nSection Extensions / Math Inline\n"); } // Or by `$$...$$` embracing it by: @@ -41,12 +41,12 @@ public void ExtensionsMathInline_Example002() // Section: Extensions / Math Inline // // The following Markdown: - // This is a $$math block$$ + // This is a $$math inline$$ // // Should be rendered as: - //

This is a \(math block\)

+ //

This is a \(math inline\)

- TestParser.TestSpec("This is a $$math block$$", "

This is a \\(math block\\)

", "mathematics|advanced", context: "Example 2\nSection Extensions / Math Inline\n"); + TestParser.TestSpec("This is a $$math inline$$", "

This is a \\(math inline\\)

", "mathematics|advanced", context: "Example 2\nSection Extensions / Math Inline\n"); } // Newlines inside an inline math are not allowed: @@ -58,13 +58,13 @@ public void ExtensionsMathInline_Example003() // // The following Markdown: // This is not a $$math - // block$$ and? this is a $$math block$$ + // inline$$ and? this is a $$math inline$$ // // Should be rendered as: //

This is not a $$math - // block$$ and? this is a \(math block\)

+ // inline$$ and? this is a \(math inline\)

- TestParser.TestSpec("This is not a $$math \nblock$$ and? this is a $$math block$$", "

This is not a $$math\nblock$$ and? this is a \\(math block\\)

", "mathematics|advanced", context: "Example 3\nSection Extensions / Math Inline\n"); + TestParser.TestSpec("This is not a $$math \ninline$$ and? this is a $$math inline$$", "

This is not a $$math\ninline$$ and? this is a \\(math inline\\)

", "mathematics|advanced", context: "Example 3\nSection Extensions / Math Inline\n"); } [Test] @@ -75,13 +75,13 @@ public void ExtensionsMathInline_Example004() // // The following Markdown: // This is not a $math - // block$ and? this is a $math block$ + // inline$ and? this is a $math inline$ // // Should be rendered as: //

This is not a $math - // block$ and? this is a \(math block\)

+ // inline$ and? this is a \(math inline\)

- TestParser.TestSpec("This is not a $math \nblock$ and? this is a $math block$", "

This is not a $math\nblock$ and? this is a \\(math block\\)

", "mathematics|advanced", context: "Example 4\nSection Extensions / Math Inline\n"); + TestParser.TestSpec("This is not a $math \ninline$ and? this is a $math inline$", "

This is not a $math\ninline$ and? this is a \\(math inline\\)

", "mathematics|advanced", context: "Example 4\nSection Extensions / Math Inline\n"); } // An opening `$` can be followed by a space if the closing is also preceded by a space `$`: @@ -92,12 +92,12 @@ public void ExtensionsMathInline_Example005() // Section: Extensions / Math Inline // // The following Markdown: - // This is a $ math block $ + // This is a $ math inline $ // // Should be rendered as: - //

This is a \(math block\)

+ //

This is a \(math inline\)

- TestParser.TestSpec("This is a $ math block $", "

This is a \\(math block\\)

", "mathematics|advanced", context: "Example 5\nSection Extensions / Math Inline\n"); + TestParser.TestSpec("This is a $ math inline $", "

This is a \\(math inline\\)

", "mathematics|advanced", context: "Example 5\nSection Extensions / Math Inline\n"); } [Test] @@ -107,12 +107,12 @@ public void ExtensionsMathInline_Example006() // Section: Extensions / Math Inline // // The following Markdown: - // This is a $ math block $ after + // This is a $ math inline $ after // // Should be rendered as: - //

This is a \(math block\) after

+ //

This is a \(math inline\) after

- TestParser.TestSpec("This is a $ math block $ after", "

This is a \\(math block\\) after

", "mathematics|advanced", context: "Example 6\nSection Extensions / Math Inline\n"); + TestParser.TestSpec("This is a $ math inline $ after", "

This is a \\(math inline\\) after

", "mathematics|advanced", context: "Example 6\nSection Extensions / Math Inline\n"); } [Test] @@ -122,12 +122,12 @@ public void ExtensionsMathInline_Example007() // Section: Extensions / Math Inline // // The following Markdown: - // This is a $$ math block $$ after + // This is a $$ math inline $$ after // // Should be rendered as: - //

This is a \(math block\) after

+ //

This is a \(math inline\) after

- TestParser.TestSpec("This is a $$ math block $$ after", "

This is a \\(math block\\) after

", "mathematics|advanced", context: "Example 7\nSection Extensions / Math Inline\n"); + TestParser.TestSpec("This is a $$ math inline $$ after", "

This is a \\(math inline\\) after

", "mathematics|advanced", context: "Example 7\nSection Extensions / Math Inline\n"); } [Test] @@ -137,12 +137,12 @@ public void ExtensionsMathInline_Example008() // Section: Extensions / Math Inline // // The following Markdown: - // This is a not $ math block$ because there is not a whitespace before the closing + // This is a not $ math inline$ because there is not a whitespace before the closing // // Should be rendered as: - //

This is a not $ math block$ because there is not a whitespace before the closing

+ //

This is a not $ math inline$ because there is not a whitespace before the closing

- TestParser.TestSpec("This is a not $ math block$ because there is not a whitespace before the closing", "

This is a not $ math block$ because there is not a whitespace before the closing

", "mathematics|advanced", context: "Example 8\nSection Extensions / Math Inline\n"); + TestParser.TestSpec("This is a not $ math inline$ because there is not a whitespace before the closing", "

This is a not $ math inline$ because there is not a whitespace before the closing

", "mathematics|advanced", context: "Example 8\nSection Extensions / Math Inline\n"); } // For the opening `$` it requires a space or a punctuation before (but cannot be used within a word): @@ -153,12 +153,12 @@ public void ExtensionsMathInline_Example009() // Section: Extensions / Math Inline // // The following Markdown: - // This is not a m$ath block$ + // This is not a m$ath inline$ // // Should be rendered as: - //

This is not a m$ath block$

+ //

This is not a m$ath inline$

- TestParser.TestSpec("This is not a m$ath block$", "

This is not a m$ath block$

", "mathematics|advanced", context: "Example 9\nSection Extensions / Math Inline\n"); + TestParser.TestSpec("This is not a m$ath inline$", "

This is not a m$ath inline$

", "mathematics|advanced", context: "Example 9\nSection Extensions / Math Inline\n"); } // For the closing `$` it requires a space after or a punctuation (but cannot be preceded by a space and cannot be used within a word): @@ -169,12 +169,12 @@ public void ExtensionsMathInline_Example010() // Section: Extensions / Math Inline // // The following Markdown: - // This is not a $math bloc$k + // This is not a $math inlin$e // // Should be rendered as: - //

This is not a $math bloc$k

+ //

This is not a $math inlin$e

- TestParser.TestSpec("This is not a $math bloc$k", "

This is not a $math bloc$k

", "mathematics|advanced", context: "Example 10\nSection Extensions / Math Inline\n"); + TestParser.TestSpec("This is not a $math inlin$e", "

This is not a $math inlin$e

", "mathematics|advanced", context: "Example 10\nSection Extensions / Math Inline\n"); } // For the closing `$` it requires a space after or a punctuation (but cannot be preceded by a space and cannot be used within a word): @@ -201,12 +201,12 @@ public void ExtensionsMathInline_Example012() // Section: Extensions / Math Inline // // The following Markdown: - // This is a $math \$ block$ + // This is a $math \$ inline$ // // Should be rendered as: - //

This is a \(math \$ block\)

+ //

This is a \(math \$ inline\)

- TestParser.TestSpec("This is a $math \\$ block$", "

This is a \\(math \\$ block\\)

", "mathematics|advanced", context: "Example 12\nSection Extensions / Math Inline\n"); + TestParser.TestSpec("This is a $math \\$ inline$", "

This is a \\(math \\$ inline\\)

", "mathematics|advanced", context: "Example 12\nSection Extensions / Math Inline\n"); } // At most, two `$` will be matched for the opening and closing: @@ -217,12 +217,12 @@ public void ExtensionsMathInline_Example013() // Section: Extensions / Math Inline // // The following Markdown: - // This is a $$$math block$$$ + // This is a $$$math inline$$$ // // Should be rendered as: - //

This is a \($math block$\)

+ //

This is a \($math inline$\)

- TestParser.TestSpec("This is a $$$math block$$$", "

This is a \\($math block$\\)

", "mathematics|advanced", context: "Example 13\nSection Extensions / Math Inline\n"); + TestParser.TestSpec("This is a $$$math inline$$$", "

This is a \\($math inline$\\)

", "mathematics|advanced", context: "Example 13\nSection Extensions / Math Inline\n"); } // Regular text can come both before and after the math inline @@ -233,15 +233,15 @@ public void ExtensionsMathInline_Example014() // Section: Extensions / Math Inline // // The following Markdown: - // This is a $math block$ with text on both sides. + // This is a $math inline$ with text on both sides. // // Should be rendered as: - //

This is a \(math block\) with text on both sides.

+ //

This is a \(math inline\) with text on both sides.

- TestParser.TestSpec("This is a $math block$ with text on both sides.", "

This is a \\(math block\\) with text on both sides.

", "mathematics|advanced", context: "Example 14\nSection Extensions / Math Inline\n"); + TestParser.TestSpec("This is a $math inline$ with text on both sides.", "

This is a \\(math inline\\) with text on both sides.

", "mathematics|advanced", context: "Example 14\nSection Extensions / Math Inline\n"); } - // A mathematic block takes precedence over standard emphasis `*` `_`: + // A mathematic inline block takes precedence over standard emphasis `*` `_`: [Test] public void ExtensionsMathInline_Example015() { @@ -249,15 +249,15 @@ public void ExtensionsMathInline_Example015() // Section: Extensions / Math Inline // // The following Markdown: - // This is *a $math* block$ + // This is *a $math* inline$ // // Should be rendered as: - //

This is *a \(math* block\)

+ //

This is *a \(math* inline\)

- TestParser.TestSpec("This is *a $math* block$", "

This is *a \\(math* block\\)

", "mathematics|advanced", context: "Example 15\nSection Extensions / Math Inline\n"); + TestParser.TestSpec("This is *a $math* inline$", "

This is *a \\(math* inline\\)

", "mathematics|advanced", context: "Example 15\nSection Extensions / Math Inline\n"); } - // An opening $$ at the beginning of a line should not be interpreted as a Math block: + // An opening $$ at the beginning of a line should not be interpreted as a Math inline: [Test] public void ExtensionsMathInline_Example016() { From c41b38905302be286c0aa14e8e36ceb76cc4c437 Mon Sep 17 00:00:00 2001 From: prozolic <42107886+prozolic@users.noreply.github.com> Date: Sun, 27 Apr 2025 16:49:05 +0900 Subject: [PATCH 61/88] Fix CodeBlockRenderer.Write --- src/Markdig.Tests/TestPlainText.cs | 8 ++++++++ src/Markdig/Renderers/Html/CodeBlockRenderer.cs | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Markdig.Tests/TestPlainText.cs b/src/Markdig.Tests/TestPlainText.cs index 3aa78299..c7139026 100644 --- a/src/Markdig.Tests/TestPlainText.cs +++ b/src/Markdig.Tests/TestPlainText.cs @@ -26,6 +26,14 @@ public void TestPlainEnsureNewLine(string markdownText, string expected) Assert.AreEqual(expected, actual); } + [Test] + [TestCase(/* markdownText: */ "```\nConsole.WriteLine(\"Hello, World!\");\n```", /* expected: */ "Console.WriteLine(\"Hello, World!\");\n")] + public void TestPlainCodeBlock(string markdownText, string expected) + { + var actual = Markdown.ToPlainText(markdownText); + Assert.AreEqual(expected, actual); + } + [Test] [TestCase(/* markdownText: */ ":::\nfoo\n:::", /* expected: */ "foo\n", /*extensions*/ "customcontainers|advanced")] [TestCase(/* markdownText: */ ":::bar\nfoo\n:::", /* expected: */ "foo\n", /*extensions*/ "customcontainers+attributes|advanced")] diff --git a/src/Markdig/Renderers/Html/CodeBlockRenderer.cs b/src/Markdig/Renderers/Html/CodeBlockRenderer.cs index c09a8a23..b7aff4d6 100644 --- a/src/Markdig/Renderers/Html/CodeBlockRenderer.cs +++ b/src/Markdig/Renderers/Html/CodeBlockRenderer.cs @@ -100,7 +100,7 @@ protected override void Write(HtmlRenderer renderer, CodeBlock obj) renderer.WriteRaw('>'); } - renderer.WriteLeafRawLines(obj, true, true); + renderer.WriteLeafRawLines(obj, true, renderer.EnableHtmlEscape); if (renderer.EnableHtmlForBlock) { From 47c4e9b1e2c6cd6eea018326a7053505dd904017 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fa=E9=B8=BD?= <43724908+Akarinnnnn@users.noreply.github.com> Date: Sat, 31 May 2025 16:01:42 +0800 Subject: [PATCH 62/88] Fix #872 by reserve null title string. --- src/Markdig/Helpers/HtmlHelper.cs | 18 ++++++++++++++++++ src/Markdig/Helpers/LinkHelper.cs | 2 +- .../Parsers/Inlines/LinkInlineParser.cs | 2 +- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/Markdig/Helpers/HtmlHelper.cs b/src/Markdig/Helpers/HtmlHelper.cs index 4887653f..83f18bcb 100644 --- a/src/Markdig/Helpers/HtmlHelper.cs +++ b/src/Markdig/Helpers/HtmlHelper.cs @@ -465,6 +465,24 @@ private static bool TryParseHtmlTagProcessingInstruction(ref StringSlice text, r } } + /// + /// Destructively unescape a string: remove backslashes before punctuation or symbol characters. + /// + /// The string data that will be changed by unescaping any punctuation or symbol characters. + /// if set to true [remove back slash]. + /// Unescaped text, or null if is null. + public static string? UnescapeNullable(string? text, bool removeBackSlash = true) + { + if (text == null) + { + return null; + } + else + { + return Unescape(text, removeBackSlash); + } + } + /// /// Destructively unescape a string: remove backslashes before punctuation or symbol characters. /// diff --git a/src/Markdig/Helpers/LinkHelper.cs b/src/Markdig/Helpers/LinkHelper.cs index b76cddca..3a9f9bb6 100644 --- a/src/Markdig/Helpers/LinkHelper.cs +++ b/src/Markdig/Helpers/LinkHelper.cs @@ -411,7 +411,7 @@ public static bool TryParseInlineLink(ref StringSlice text, out string? link, ou { // Skip ')' text.SkipChar(); - title ??= string.Empty; + // not to normalize nulls } return isValid; diff --git a/src/Markdig/Parsers/Inlines/LinkInlineParser.cs b/src/Markdig/Parsers/Inlines/LinkInlineParser.cs index eee94ec7..a982eb37 100644 --- a/src/Markdig/Parsers/Inlines/LinkInlineParser.cs +++ b/src/Markdig/Parsers/Inlines/LinkInlineParser.cs @@ -260,7 +260,7 @@ private bool TryProcessLinkOrImage(InlineProcessor inlineState, ref StringSlice link = new LinkInline() { Url = HtmlHelper.Unescape(url, removeBackSlash: false), - Title = HtmlHelper.Unescape(title, removeBackSlash: false), + Title = HtmlHelper.UnescapeNullable(title, removeBackSlash: false), IsImage = openParent.IsImage, LabelSpan = openParent.LabelSpan, UrlSpan = inlineState.GetSourcePositionFromLocalSpan(linkSpan), From 6d1fa96389c8cf04f235a9de06f1fb271610d95f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fa=E9=B8=BD?= <43724908+Akarinnnnn@users.noreply.github.com> Date: Sat, 31 May 2025 16:33:29 +0800 Subject: [PATCH 63/88] Changed link parsing tests for #872 --- src/Markdig.Tests/TestLinkHelper.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Markdig.Tests/TestLinkHelper.cs b/src/Markdig.Tests/TestLinkHelper.cs index d46c50ab..b9a15292 100644 --- a/src/Markdig.Tests/TestLinkHelper.cs +++ b/src/Markdig.Tests/TestLinkHelper.cs @@ -112,26 +112,26 @@ public void TestUrlAndTitle() } [Test] - public void TestUrlAndTitleEmpty() + public void TestUrlEmptyAndTitleNull() { // 01234 var text = new StringSlice(@"(<>)A"); Assert.True(LinkHelper.TryParseInlineLink(ref text, out string link, out string title, out SourceSpan linkSpan, out SourceSpan titleSpan)); Assert.AreEqual(string.Empty, link); - Assert.AreEqual(string.Empty, title); + Assert.AreEqual(null, title); Assert.AreEqual(new SourceSpan(1, 2), linkSpan); Assert.AreEqual(SourceSpan.Empty, titleSpan); Assert.AreEqual('A', text.CurrentChar); } [Test] - public void TestUrlAndTitleEmpty2() + public void TestUrlEmptyAndTitleNull2() { // 012345 var text = new StringSlice(@"( <> )A"); Assert.True(LinkHelper.TryParseInlineLink(ref text, out string link, out string title, out SourceSpan linkSpan, out SourceSpan titleSpan)); Assert.AreEqual(string.Empty, link); - Assert.AreEqual(string.Empty, title); + Assert.AreEqual(null, title); Assert.AreEqual(new SourceSpan(2, 3), linkSpan); Assert.AreEqual(SourceSpan.Empty, titleSpan); Assert.AreEqual('A', text.CurrentChar); @@ -158,7 +158,7 @@ public void TestUrlEmpty() var text = new StringSlice(@"()A"); Assert.True(LinkHelper.TryParseInlineLink(ref text, out string link, out string title, out SourceSpan linkSpan, out SourceSpan titleSpan)); Assert.AreEqual(string.Empty, link); - Assert.AreEqual(string.Empty, title); + Assert.AreEqual(null, title); Assert.AreEqual(SourceSpan.Empty, linkSpan); Assert.AreEqual(SourceSpan.Empty, titleSpan); Assert.AreEqual('A', text.CurrentChar); From 6261660d377e28430c0b01a0efc1f55476f86a45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fa=E9=B8=BD?= <43724908+Akarinnnnn@users.noreply.github.com> Date: Sat, 31 May 2025 22:26:33 +0800 Subject: [PATCH 64/88] Explain why not to normalize link title into empty strings --- src/Markdig/Helpers/LinkHelper.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Markdig/Helpers/LinkHelper.cs b/src/Markdig/Helpers/LinkHelper.cs index 3a9f9bb6..c5004594 100644 --- a/src/Markdig/Helpers/LinkHelper.cs +++ b/src/Markdig/Helpers/LinkHelper.cs @@ -411,7 +411,7 @@ public static bool TryParseInlineLink(ref StringSlice text, out string? link, ou { // Skip ')' text.SkipChar(); - // not to normalize nulls + // not to normalize nulls into empty strings, since LinkInline.Title property is nullable. } return isValid; @@ -1565,4 +1565,4 @@ public static bool TryParseLabelTrivia(ref T lines, bool allowEmpty, out stri label = buffer.ToString(); return true; } -} \ No newline at end of file +} From ec2eef25b2a70dfab54e2636f50d14f2a0b8ca08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fa=E9=B8=BD?= <43724908+Akarinnnnn@users.noreply.github.com> Date: Wed, 4 Jun 2025 19:23:18 +0800 Subject: [PATCH 65/88] Remove HtmlHelper.UnescapeNullable --- src/Markdig/Helpers/HtmlHelper.cs | 18 ------------------ .../Parsers/Inlines/LinkInlineParser.cs | 2 +- 2 files changed, 1 insertion(+), 19 deletions(-) diff --git a/src/Markdig/Helpers/HtmlHelper.cs b/src/Markdig/Helpers/HtmlHelper.cs index 83f18bcb..4887653f 100644 --- a/src/Markdig/Helpers/HtmlHelper.cs +++ b/src/Markdig/Helpers/HtmlHelper.cs @@ -465,24 +465,6 @@ private static bool TryParseHtmlTagProcessingInstruction(ref StringSlice text, r } } - /// - /// Destructively unescape a string: remove backslashes before punctuation or symbol characters. - /// - /// The string data that will be changed by unescaping any punctuation or symbol characters. - /// if set to true [remove back slash]. - /// Unescaped text, or null if is null. - public static string? UnescapeNullable(string? text, bool removeBackSlash = true) - { - if (text == null) - { - return null; - } - else - { - return Unescape(text, removeBackSlash); - } - } - /// /// Destructively unescape a string: remove backslashes before punctuation or symbol characters. /// diff --git a/src/Markdig/Parsers/Inlines/LinkInlineParser.cs b/src/Markdig/Parsers/Inlines/LinkInlineParser.cs index a982eb37..a9687a31 100644 --- a/src/Markdig/Parsers/Inlines/LinkInlineParser.cs +++ b/src/Markdig/Parsers/Inlines/LinkInlineParser.cs @@ -260,7 +260,7 @@ private bool TryProcessLinkOrImage(InlineProcessor inlineState, ref StringSlice link = new LinkInline() { Url = HtmlHelper.Unescape(url, removeBackSlash: false), - Title = HtmlHelper.UnescapeNullable(title, removeBackSlash: false), + Title = title is null ? null : HtmlHelper.Unescape(title, removeBackSlash: false), IsImage = openParent.IsImage, LabelSpan = openParent.LabelSpan, UrlSpan = inlineState.GetSourcePositionFromLocalSpan(linkSpan), From b92890094c28b86bf99beadb9ae897603755b212 Mon Sep 17 00:00:00 2001 From: Mertsch Date: Thu, 12 Jun 2025 14:26:00 +0200 Subject: [PATCH 66/88] Update readme.md --- readme.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/readme.md b/readme.md index b9b8f9a8..60644a15 100644 --- a/readme.md +++ b/readme.md @@ -2,7 +2,7 @@ -Markdig is a fast, powerful, [CommonMark](http://commonmark.org/) compliant, extensible Markdown processor for .NET. +Markdig is a fast, powerful, [CommonMark](https://commonmark.org/) compliant, extensible Markdown processor for .NET. > NOTE: The repository is under construction. There will be a dedicated website and proper documentation at some point! @@ -14,7 +14,7 @@ You can **try Markdig online** and compare it to other implementations on [babel - **Abstract Syntax Tree** with precise source code location for syntax tree, useful when building a Markdown editor. - Checkout [Markdown Editor v2 for Visual Studio 2022](https://marketplace.visualstudio.com/items?itemName=MadsKristensen.MarkdownEditor2) powered by Markdig! - Converter to **HTML** -- Passing more than **600+ tests** from the latest [CommonMark specs (0.31.2)](http://spec.commonmark.org/) +- Passing more than **600+ tests** from the latest [CommonMark specs (0.31.2)](https://spec.commonmark.org/) - Includes all the core elements of CommonMark: - including **GFM fenced code blocks**. - **Extensible** architecture @@ -22,9 +22,9 @@ You can **try Markdig online** and compare it to other implementations on [babel - [**Roundtrip support**](./src/Markdig/Roundtrip.md): Parses trivia (whitespace, newlines and other characters) to support lossless parse ⭢ render roundtrip. This enables changing markdown documents without introducing undesired trivia changes. - Built-in with **20+ extensions**, including: - 2 kind of tables: - - [**Pipe tables**](src/Markdig.Tests/Specs/PipeTableSpecs.md) (inspired from GitHub tables and [PanDoc - Pipe Tables](http://pandoc.org/README.html#extension-pipe_tables)) - - [**Grid tables**](src/Markdig.Tests/Specs/GridTableSpecs.md) (inspired from [Pandoc - Grid Tables](http://pandoc.org/README.html#extension-grid_tables)) - - [**Extra emphasis**](src/Markdig.Tests/Specs/EmphasisExtraSpecs.md) (inspired from [Pandoc - Emphasis](http://pandoc.org/README.html#strikeout) and [Markdown-it](https://markdown-it.github.io/)) + - [**Pipe tables**](src/Markdig.Tests/Specs/PipeTableSpecs.md) (inspired from GitHub tables and [PanDoc - Pipe Tables](https://pandoc.org/MANUAL.html#extension-pipe_tables)) + - [**Grid tables**](src/Markdig.Tests/Specs/GridTableSpecs.md) (inspired from [Pandoc - Grid Tables](https://pandoc.org/MANUAL.html#extension-grid_tables)) + - [**Extra emphasis**](src/Markdig.Tests/Specs/EmphasisExtraSpecs.md) (inspired from [Pandoc - Emphasis](https://pandoc.org/MANUAL.html#strikeout) and [Markdown-it](https://markdown-it.github.io/)) - strike through `~~`, - Subscript `~` - Superscript `^` @@ -33,7 +33,7 @@ You can **try Markdig online** and compare it to other implementations on [babel - [**Special attributes**](src/Markdig.Tests/Specs/GenericAttributesSpecs.md) or attached HTML attributes (inspired from [PHP Markdown Extra - Special Attributes](https://michelf.ca/projects/php-markdown/extra/#spe-attr)) - [**Definition lists**](src/Markdig.Tests/Specs/DefinitionListSpecs.md) (inspired from [PHP Markdown Extra - Definitions Lists](https://michelf.ca/projects/php-markdown/extra/#def-list)) - [**Footnotes**](src/Markdig.Tests/Specs/FootnotesSpecs.md) (inspired from [PHP Markdown Extra - Footnotes](https://michelf.ca/projects/php-markdown/extra/#footnotes)) - - [**Auto-identifiers**](src/Markdig.Tests/Specs/AutoIdentifierSpecs.md) for headings (similar to [Pandoc - Auto Identifiers](http://pandoc.org/README.html#extension-auto_identifiers)) + - [**Auto-identifiers**](src/Markdig.Tests/Specs/AutoIdentifierSpecs.md) for headings (similar to [Pandoc - Auto Identifiers](https://pandoc.org/MANUAL.html#extension-auto_identifiers)) - [**Auto-links**](src/Markdig.Tests/Specs/AutoLinks.md) generates links if a text starts with `http://` or `https://` or `ftp://` or `mailto:` or `www.xxx.yyy` - [**Task Lists**](src/Markdig.Tests/Specs/TaskListSpecs.md) inspired from [Github Task lists](https://github.com/blog/1375-task-lists-in-gfm-issues-pulls-comments). - [**Extra bullet lists**](src/Markdig.Tests/Specs/ListExtraSpecs.md), supporting alpha bullet `a.` `b.` and roman bullet (`i`, `ii`...etc.) @@ -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.github.io/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"](https://xoofx.github.io/blog/2016/06/13/implementing-a-markdown-processor-for-dotnet/) ## Download @@ -153,7 +153,7 @@ image editing, optimization, and delivery server](https://github.com/imazen/imag ## Credits -Thanks to the fantastic work done by [John Mac Farlane](http://johnmacfarlane.net/) for the CommonMark specs and all the people involved in making Markdown a better standard! +Thanks to the fantastic work done by [John Mac Farlane](https://johnmacfarlane.net/) for the CommonMark specs and all the people involved in making Markdown a better standard! This project would not have been possible without this huge foundation. @@ -161,7 +161,7 @@ Thanks also to the project [BenchmarkDotNet](https://github.com/PerfDotNet/Bench Some decoding part (e.g HTML [EntityHelper.cs](https://github.com/lunet-io/markdig/blob/master/src/Markdig/Helpers/EntityHelper.cs)) have been re-used from [CommonMark.NET](https://github.com/Knagis/CommonMark.NET) -Thanks to the work done by @clarkd on the JIRA Link extension (https://github.com/clarkd/MarkdigJiraLinker), now included with this project! +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.github.io) +Alexandre MUTEL aka [xoofx](https://xoofx.github.io/) From 5a3c206076621dd75c6f0ec36236f031ebbadea2 Mon Sep 17 00:00:00 2001 From: stylefish Date: Mon, 16 Jun 2025 11:26:23 +0200 Subject: [PATCH 67/88] Fixes #878: render indent and 0 blocks --- .../RoundtripSpecs/TestUnorderedList.cs | 1 + src/Markdig/Renderers/Roundtrip/ListRenderer.cs | 12 ++++++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/Markdig.Tests/RoundtripSpecs/TestUnorderedList.cs b/src/Markdig.Tests/RoundtripSpecs/TestUnorderedList.cs index f35a2e8b..a65c3e71 100644 --- a/src/Markdig.Tests/RoundtripSpecs/TestUnorderedList.cs +++ b/src/Markdig.Tests/RoundtripSpecs/TestUnorderedList.cs @@ -25,6 +25,7 @@ public class TestUnorderedList [TestCase("-\ti1")] [TestCase("-\ti1\n-\ti2")] [TestCase("-\ti1\n- i2\n-\ti3")] + [TestCase("- 1.\n- 2.")] public void Test(string value) { RoundTrip(value); diff --git a/src/Markdig/Renderers/Roundtrip/ListRenderer.cs b/src/Markdig/Renderers/Roundtrip/ListRenderer.cs index 41a94128..6b474652 100644 --- a/src/Markdig/Renderers/Roundtrip/ListRenderer.cs +++ b/src/Markdig/Renderers/Roundtrip/ListRenderer.cs @@ -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.Helpers; @@ -28,7 +28,15 @@ protected override void Write(RoundtripRenderer renderer, ListBlock listBlock) var bullet = listItem.SourceBullet.ToString(); var delimiter = listBlock.OrderedDelimiter; renderer.PushIndent(new string[] { $"{bws}{bullet}{delimiter}" }); - renderer.WriteChildren(listItem); + if (listItem.Count == 0) + { + renderer.Write(""); // trigger writing of indent + } + else + { + renderer.WriteChildren(listItem); + } + renderer.PopIndent(); renderer.RenderLinesAfter(listItem); } } From 80c50e31e25e92d2999e69f027f4b5080a8f7b75 Mon Sep 17 00:00:00 2001 From: Tibor Peluch Date: Fri, 11 Jul 2025 13:25:03 +0200 Subject: [PATCH 68/88] Attempt to fix tracking of tree node positions (line, column) inside GridTable --- .../Extensions/Tables/GridTableParser.cs | 34 +++++++++++++++---- src/Markdig/Parsers/BlockProcessor.cs | 10 ++++-- 2 files changed, 36 insertions(+), 8 deletions(-) diff --git a/src/Markdig/Extensions/Tables/GridTableParser.cs b/src/Markdig/Extensions/Tables/GridTableParser.cs index 847bd94c..aff92bd5 100644 --- a/src/Markdig/Extensions/Tables/GridTableParser.cs +++ b/src/Markdig/Extensions/Tables/GridTableParser.cs @@ -1,10 +1,11 @@ // 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.Helpers; using Markdig.Parsers; using Markdig.Syntax; +using System.Linq; namespace Markdig.Extensions.Tables; @@ -60,7 +61,12 @@ public override BlockState TryOpen(BlockProcessor processor) } // Store the line (if we need later to build a ParagraphBlock because the GridTable was in fact invalid) tableState.AddLine(ref processor.Line); - var table = new Table(this); + var table = new Table(this) + { + Line = processor.LineIndex, + Column = processor.Column, + Span = { Start = lineStart } + }; table.SetData(typeof(GridTableState), tableState); // Calculate the total width of all columns @@ -94,10 +100,12 @@ public override BlockState TryContinue(BlockProcessor processor, Block block) tableState.AddLine(ref processor.Line); if (processor.CurrentChar == '+') { + gridTable.UpdateSpanEnd(processor.Line.End); return HandleNewRow(processor, tableState, gridTable); } if (processor.CurrentChar == '|') { + gridTable.UpdateSpanEnd(processor.Line.End); return HandleContents(processor, tableState, gridTable); } TerminateCurrentRow(processor, tableState, gridTable, true); @@ -182,8 +190,18 @@ private static void TerminateCurrentRow(BlockProcessor processor, GridTableState var columnSlice = columns[i]; if (columnSlice.CurrentCell != null) { - currentRow ??= new TableRow(); - + if (currentRow == null) + { + TableCell firstCell = columns.First(c => c.CurrentCell != null).CurrentCell!; + TableCell lastCell = columns.Last(c => c.CurrentCell != null).CurrentCell!; + + currentRow ??= new TableRow() + { + Span = new SourceSpan(firstCell.Span.Start, lastCell.Span.End), + Line = firstCell.Line + }; + } + // If this cell does not already belong to a row if (columnSlice.CurrentCell.Parent is null) { @@ -271,7 +289,10 @@ private BlockState HandleContents(BlockProcessor processor, GridTableState table columnSlice.CurrentCell = new TableCell(this) { ColumnSpan = columnSlice.CurrentColumnSpan, - ColumnIndex = i + ColumnIndex = i, + Column = columnSlice.Start, + Line = processor.LineIndex, + Span = new SourceSpan(line.Start + columnSlice.Start, line.Start + columnSlice.End) }; columnSlice.BlockProcessor ??= processor.CreateChild(); @@ -281,7 +302,8 @@ private BlockState HandleContents(BlockProcessor processor, GridTableState table } // Process the content of the cell columnSlice.BlockProcessor!.LineIndex = processor.LineIndex; - columnSlice.BlockProcessor.ProcessLine(sliceForCell); + + columnSlice.BlockProcessor.ProcessLine(sliceForCell, sliceForCell.Start - line.Start); } // Go to next column diff --git a/src/Markdig/Parsers/BlockProcessor.cs b/src/Markdig/Parsers/BlockProcessor.cs index 225ed971..1019bbc2 100644 --- a/src/Markdig/Parsers/BlockProcessor.cs +++ b/src/Markdig/Parsers/BlockProcessor.cs @@ -485,15 +485,21 @@ public void Discard(Block block) /// Processes a new line. ///
/// The new line. - public void ProcessLine(StringSlice newLine) + /// The offset. + public void ProcessLine(StringSlice newLine, int column = 0) { CurrentLineStartPosition = newLine.Start; - Document.LineStartIndexes?.Add(CurrentLineStartPosition); + if (column == 0) + { + Document.LineStartIndexes?.Add(CurrentLineStartPosition); + } ContinueProcessingLine = true; ResetLine(newLine); + Column = column; + originalLineStart -= column; TryContinueBlocks(); From 2e1d741aaf15ad1084334d397cc7abf63af323a4 Mon Sep 17 00:00:00 2001 From: Tibor Peluch Date: Mon, 14 Jul 2025 10:23:15 +0200 Subject: [PATCH 69/88] Cleaned up code, added tests for source position --- src/Markdig.Tests/TestSourcePosition.cs | 23 +++++++++++ .../Extensions/Tables/GridTableParser.cs | 2 +- src/Markdig/Parsers/BlockProcessor.cs | 39 ++++++++++++++----- 3 files changed, 54 insertions(+), 10 deletions(-) diff --git a/src/Markdig.Tests/TestSourcePosition.cs b/src/Markdig.Tests/TestSourcePosition.cs index 27212653..eed7139a 100644 --- a/src/Markdig.Tests/TestSourcePosition.cs +++ b/src/Markdig.Tests/TestSourcePosition.cs @@ -834,6 +834,29 @@ public void TestPipeTable3() ", "pipetables"); } + [Test] + public void TestGridTable() + { + Check("0\n\n+-+-+\n|A|B|\n+=+=+\n|C|D|\n+-+-+", @" +paragraph ( 0, 0) 0-0 +literal ( 0, 0) 0-0 +table ( 2, 0) 3-31 +tablerow ( 3, 0) 9-13 +tablecell ( 3, 0) 9-11 +paragraph ( 3, 1) 10-10 +literal ( 3, 1) 10-10 +tablecell ( 3, 2) 11-13 +paragraph ( 3, 3) 12-12 +literal ( 3, 3) 12-12 +tablerow ( 5, 0) 21-25 +tablecell ( 5, 0) 21-23 +paragraph ( 5, 1) 22-22 +literal ( 5, 1) 22-22 +tablecell ( 5, 2) 23-25 +paragraph ( 5, 3) 24-24 +literal ( 5, 3) 24-24", "gridtables"); + } + [Test] public void TestIndentedCode() { diff --git a/src/Markdig/Extensions/Tables/GridTableParser.cs b/src/Markdig/Extensions/Tables/GridTableParser.cs index aff92bd5..30ce2c3d 100644 --- a/src/Markdig/Extensions/Tables/GridTableParser.cs +++ b/src/Markdig/Extensions/Tables/GridTableParser.cs @@ -303,7 +303,7 @@ private BlockState HandleContents(BlockProcessor processor, GridTableState table // Process the content of the cell columnSlice.BlockProcessor!.LineIndex = processor.LineIndex; - columnSlice.BlockProcessor.ProcessLine(sliceForCell, sliceForCell.Start - line.Start); + columnSlice.BlockProcessor.ProcessLinePart(sliceForCell, sliceForCell.Start - line.Start); } // Go to next column diff --git a/src/Markdig/Parsers/BlockProcessor.cs b/src/Markdig/Parsers/BlockProcessor.cs index 1019bbc2..236bac7a 100644 --- a/src/Markdig/Parsers/BlockProcessor.cs +++ b/src/Markdig/Parsers/BlockProcessor.cs @@ -497,10 +497,34 @@ public void ProcessLine(StringSlice newLine, int column = 0) ContinueProcessingLine = true; - ResetLine(newLine); - Column = column; - originalLineStart -= column; + ResetLine(newLine, 0); + + Process(); + + LineIndex++; + } + + /// + /// Processes part of a line. + /// + /// The line. + /// The column. + public void ProcessLinePart(StringSlice line, int column) + { + CurrentLineStartPosition = line.Start - column; + + ContinueProcessingLine = true; + ResetLine(line, column); + + Process(); + } + + /// + /// Process current string slice. + /// + private void Process() + { TryContinueBlocks(); // If the line was not entirely processed by pending blocks, try to process it with any new block @@ -508,8 +532,6 @@ public void ProcessLine(StringSlice newLine, int column = 0) // Close blocks that are no longer opened CloseAll(false); - - LineIndex++; } internal bool IsOpen(Block block) @@ -962,18 +984,17 @@ private void ProcessNewBlocks(BlockState result, bool allowClosing) ContinueProcessingLine = !result.IsDiscard(); } - private void ResetLine(StringSlice newLine) + private void ResetLine(StringSlice newLine, int column) { Line = newLine; - Column = 0; + Column = column; ColumnBeforeIndent = 0; StartBeforeIndent = Start; - originalLineStart = newLine.Start; + originalLineStart = newLine.Start - column; TriviaStart = newLine.Start; } - [MemberNotNull(nameof(Document), nameof(Parsers))] internal void Setup(MarkdownDocument document, BlockParserList parsers, MarkdownParserContext? context, bool trackTrivia) { From aab5543cb5b3bd78cf5222144e411f0f52672504 Mon Sep 17 00:00:00 2001 From: Tibor Peluch Date: Mon, 14 Jul 2025 20:17:50 +0200 Subject: [PATCH 70/88] Code cleanup --- src/Markdig/Parsers/BlockProcessor.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/Markdig/Parsers/BlockProcessor.cs b/src/Markdig/Parsers/BlockProcessor.cs index 236bac7a..39f03d3f 100644 --- a/src/Markdig/Parsers/BlockProcessor.cs +++ b/src/Markdig/Parsers/BlockProcessor.cs @@ -485,15 +485,11 @@ public void Discard(Block block) /// Processes a new line. /// /// The new line. - /// The offset. - public void ProcessLine(StringSlice newLine, int column = 0) + public void ProcessLine(StringSlice newLine) { CurrentLineStartPosition = newLine.Start; - if (column == 0) - { - Document.LineStartIndexes?.Add(CurrentLineStartPosition); - } + Document.LineStartIndexes?.Add(CurrentLineStartPosition); ContinueProcessingLine = true; From d548b82bcd269c34221d06a007b06f3ea33ed236 Mon Sep 17 00:00:00 2001 From: Daniel Pino Date: Sat, 9 Aug 2025 08:50:49 +0000 Subject: [PATCH 71/88] Add support for a table without an extra new line before it --- src/Markdig.Tests/TestPipeTable.cs | 2 ++ src/Markdig/Extensions/Tables/PipeTableParser.cs | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Markdig.Tests/TestPipeTable.cs b/src/Markdig.Tests/TestPipeTable.cs index b891c722..f6f60a13 100644 --- a/src/Markdig.Tests/TestPipeTable.cs +++ b/src/Markdig.Tests/TestPipeTable.cs @@ -10,6 +10,8 @@ public sealed class TestPipeTable [TestCase("| S | T |\r\n|---|---|\t\r\n| G | H |")] [TestCase("| S | T |\r\n|---|---|\f\r\n| G | H |")] [TestCase("| S | \r\n|---|\r\n| G |\r\n\r\n| D | D |\r\n| ---| ---| \r\n| V | V |", 2)] + [TestCase("a\r| S | T |\r|---|---|")] + [TestCase("a\n| S | T |\r|---|---|")] public void TestTableBug(string markdown, int tableCount = 1) { MarkdownDocument document = diff --git a/src/Markdig/Extensions/Tables/PipeTableParser.cs b/src/Markdig/Extensions/Tables/PipeTableParser.cs index f4d17c41..b9ef8199 100644 --- a/src/Markdig/Extensions/Tables/PipeTableParser.cs +++ b/src/Markdig/Extensions/Tables/PipeTableParser.cs @@ -48,6 +48,7 @@ public override bool Match(InlineProcessor processor, ref StringSlice slice) } var c = slice.CurrentChar; + var isNewLineFollowedByPipe = (c == '\n' || c == '\r') && slice.PeekChar() == '|'; // If we have not a delimiter on the first line of a paragraph, don't bother to continue // tracking other delimiters on following lines @@ -60,18 +61,17 @@ public override bool Match(InlineProcessor processor, ref StringSlice slice) if (tableState is null) { - // A table could be preceded by an empty line or a line containing an inline // that has not been added to the stack, so we consider this as a valid // start for a table. Typically, with this, we can have an attributes {...} // starting on the first line of a pipe table, even if the first line // doesn't have a pipe - if (processor.Inline != null && (localLineIndex > 0 || c == '\n' || c == '\r')) + if (processor.Inline != null && (localLineIndex > 0 || c == '\n' || c == '\r') && !isNewLineFollowedByPipe) { return false; } - if (processor.Inline is null) + if (processor.Inline is null || isNewLineFollowedByPipe) { isFirstLineEmpty = true; } From 14406bc60d515bfe3bc96b017560d22fb5b9805b Mon Sep 17 00:00:00 2001 From: prozolic <42107886+prozolic@users.noreply.github.com> Date: Sat, 6 Sep 2025 21:10:51 +0900 Subject: [PATCH 72/88] Fixes issue #845 --- src/Markdig/Parsers/ListBlockParser.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Markdig/Parsers/ListBlockParser.cs b/src/Markdig/Parsers/ListBlockParser.cs index a17a48c7..e9dcf323 100644 --- a/src/Markdig/Parsers/ListBlockParser.cs +++ b/src/Markdig/Parsers/ListBlockParser.cs @@ -145,6 +145,7 @@ private BlockState TryContinueListItem(BlockProcessor state, ListItemBlock listI if (list.CountBlankLinesReset == 1 && listItem.ColumnWidth < 0) { state.Close(listItem); + list.CountBlankLinesReset = 0; // Leave the list open list.IsOpen = true; From 5e6fb2d1c538ada61f10e21d747ab1b5c122c894 Mon Sep 17 00:00:00 2001 From: prozolic <42107886+prozolic@users.noreply.github.com> Date: Mon, 8 Sep 2025 22:36:09 +0900 Subject: [PATCH 73/88] Add test for issue #845 list item blank line --- src/Markdig.Tests/MiscTests.cs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/Markdig.Tests/MiscTests.cs b/src/Markdig.Tests/MiscTests.cs index e9c5a70e..ef667f8b 100644 --- a/src/Markdig.Tests/MiscTests.cs +++ b/src/Markdig.Tests/MiscTests.cs @@ -386,4 +386,27 @@ Also not a note.

"; TestParser.TestSpec(input, expected, new MarkdownPipelineBuilder().UseAlertBlocks().Build()); } + + + [Test] + public void TestIssue845ListItemBlankLine() + { + TestParser.TestSpec("-\n\n foo",@" +
    +
  • +
+

foo

"); + TestParser.TestSpec("-\n-\n\n foo",@" +
    +
  • +
  • +
+

foo

"); + TestParser.TestSpec("-\n\n-\n\n foo",@" +
    +
  • +
  • +
+

foo

"); + } } From 0e9e80e1cd9c544d77eae59d62efc333bfc0cc3e Mon Sep 17 00:00:00 2001 From: Phillip Haydon Date: Mon, 22 Sep 2025 02:26:02 +1200 Subject: [PATCH 74/88] Fix for table depth error when cell contains backticks (#891) * failing test * fixed bug with table containing back tick which causes depth error --- src/Markdig.Tests/TestPipeTable.cs | 44 +++++++++++++++++++ .../Extensions/Tables/PipeTableParser.cs | 10 +++++ .../Parsers/Inlines/CodeInlineParser.cs | 20 ++++++++- 3 files changed, 73 insertions(+), 1 deletion(-) diff --git a/src/Markdig.Tests/TestPipeTable.cs b/src/Markdig.Tests/TestPipeTable.cs index f6f60a13..f36f9fb0 100644 --- a/src/Markdig.Tests/TestPipeTable.cs +++ b/src/Markdig.Tests/TestPipeTable.cs @@ -57,4 +57,48 @@ public void TestColumnWidthIsNotSetWithoutConfigurationFlag() Assert.AreEqual(0, column.Width); } } + + [Test] + public void TableWithUnbalancedCodeSpanParsesWithoutDepthLimitError() + { + const string markdown = """ +| Count | A | B | C | D | E | +|-------|---|---|---|---|---| +| 0 | B | C | D | E | F | +| 1 | B | `C | D | E | F | +| 2 | B | `C | D | E | F | +| 3 | B | C | D | E | F | +| 4 | B | C | D | E | F | +| 5 | B | C | D | E | F | +| 6 | B | C | D | E | F | +| 7 | B | C | D | E | F | +| 8 | B | C | D | E | F | +| 9 | B | C | D | E | F | +| 10 | B | C | D | E | F | +| 11 | B | C | D | E | F | +| 12 | B | C | D | E | F | +| 13 | B | C | D | E | F | +| 14 | B | C | D | E | F | +| 15 | B | C | D | E | F | +| 16 | B | C | D | E | F | +| 17 | B | C | D | E | F | +| 18 | B | C | D | E | F | +| 19 | B | C | D | E | F | +"""; + + var pipeline = new MarkdownPipelineBuilder() + .UseAdvancedExtensions() + .Build(); + + MarkdownDocument document = null!; + Assert.DoesNotThrow(() => document = Markdown.Parse(markdown, pipeline)); + + var tables = document.Descendants().OfType
().ToArray(); + Assert.That(tables, Has.Length.EqualTo(1)); + + string html = string.Empty; + Assert.DoesNotThrow(() => html = Markdown.ToHtml(markdown, pipeline)); + Assert.That(html, Does.Contain("`C")); + } } diff --git a/src/Markdig/Extensions/Tables/PipeTableParser.cs b/src/Markdig/Extensions/Tables/PipeTableParser.cs index b9ef8199..8453d134 100644 --- a/src/Markdig/Extensions/Tables/PipeTableParser.cs +++ b/src/Markdig/Extensions/Tables/PipeTableParser.cs @@ -196,6 +196,16 @@ public bool PostProcess(InlineProcessor state, Inline? root, Inline? lastChild, // Continue if (tableState is null || container is null || tableState.IsInvalidTable || !tableState.LineHasPipe ) //|| tableState.LineIndex != state.LocalLineIndex) { + if (tableState is not null) + { + foreach (var inline in tableState.ColumnAndLineDelimiters) + { + if (inline is PipeTableDelimiterInline pipeDelimiter) + { + pipeDelimiter.ReplaceByLiteral(); + } + } + } return true; } diff --git a/src/Markdig/Parsers/Inlines/CodeInlineParser.cs b/src/Markdig/Parsers/Inlines/CodeInlineParser.cs index 25cbad2d..34a2a1fc 100644 --- a/src/Markdig/Parsers/Inlines/CodeInlineParser.cs +++ b/src/Markdig/Parsers/Inlines/CodeInlineParser.cs @@ -35,6 +35,7 @@ public override bool Match(InlineProcessor processor, ref StringSlice slice) Debug.Assert(match is not ('\r' or '\n')); // Match the opened sticks + int openingStart = slice.Start; int openSticks = slice.CountAndSkipChar(match); // A backtick string is a string of one or more backtick characters (`) that is neither preceded nor followed by a backtick. @@ -75,8 +76,25 @@ public override bool Match(InlineProcessor processor, ref StringSlice slice) { break; } - else if (closeSticks == 0) + + if (closeSticks == 0) { + ReadOnlySpan lookAhead = span.Length > 1 ? span.Slice(1) : ReadOnlySpan.Empty; + while (!lookAhead.IsEmpty && (lookAhead[0] == '\r' || lookAhead[0] == '\n')) + { + lookAhead = lookAhead.Slice(1); + } + int whitespace = 0; + while (whitespace < lookAhead.Length && (lookAhead[whitespace] == ' ' || lookAhead[whitespace] == '\t')) + { + whitespace++; + } + if (whitespace < lookAhead.Length && lookAhead[whitespace] == '|') + { + slice.Start = openingStart; + return false; + } + containsNewLines = true; span = span.Slice(1); } From 4dc0be88b4538a66284f612b5d56a40a9adb80b1 Mon Sep 17 00:00:00 2001 From: Daniel Klecha Date: Fri, 3 Oct 2025 09:22:51 +0200 Subject: [PATCH 75/88] add options for link inline (#894) * add options for link inline * create LinkOptions and associate it with all four parsers * set EnableHtmlParsing to true by default --- .../Extensions/AutoLinks/AutoLinkOptions.cs | 9 +++---- .../Extensions/JiraLinks/JiraLinkOptions.cs | 8 ++---- src/Markdig/MarkdownExtensions.cs | 2 +- .../Parsers/Inlines/AutolinkInlineParser.cs | 25 +++++++++++++------ .../Parsers/Inlines/AutolinkOptions.cs | 13 ++++++++++ .../Parsers/Inlines/LinkInlineParser.cs | 19 +++++++++++++- src/Markdig/Parsers/LinkOptions.cs | 19 ++++++++++++++ 7 files changed, 74 insertions(+), 21 deletions(-) create mode 100644 src/Markdig/Parsers/Inlines/AutolinkOptions.cs create mode 100644 src/Markdig/Parsers/LinkOptions.cs diff --git a/src/Markdig/Extensions/AutoLinks/AutoLinkOptions.cs b/src/Markdig/Extensions/AutoLinks/AutoLinkOptions.cs index 1dd03670..7821b95b 100644 --- a/src/Markdig/Extensions/AutoLinks/AutoLinkOptions.cs +++ b/src/Markdig/Extensions/AutoLinks/AutoLinkOptions.cs @@ -2,9 +2,11 @@ // 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; + namespace Markdig.Extensions.AutoLinks; -public class AutoLinkOptions +public class AutoLinkOptions : LinkOptions { public AutoLinkOptions() { @@ -13,11 +15,6 @@ public AutoLinkOptions() public string ValidPreviousCharacters { get; set; } - /// - /// Should the link open in a new window when clicked (false by default) - /// - public bool OpenInNewWindow { get; set; } - /// /// Should a www link be prefixed with https:// instead of http:// (false by default) /// diff --git a/src/Markdig/Extensions/JiraLinks/JiraLinkOptions.cs b/src/Markdig/Extensions/JiraLinks/JiraLinkOptions.cs index 1a9e5ce1..3d9d75e6 100644 --- a/src/Markdig/Extensions/JiraLinks/JiraLinkOptions.cs +++ b/src/Markdig/Extensions/JiraLinks/JiraLinkOptions.cs @@ -3,13 +3,14 @@ // See the license.txt file in the project root for more information. using Markdig.Helpers; +using Markdig.Parsers; namespace Markdig.Extensions.JiraLinks; /// /// Available options for replacing JIRA links /// -public class JiraLinkOptions +public class JiraLinkOptions : LinkOptions { /// /// The base Url (e.g. `https://mycompany.atlassian.net`) @@ -21,11 +22,6 @@ public class JiraLinkOptions /// public string BasePath { get; set; } - /// - /// Should the link open in a new window when clicked - /// - public bool OpenInNewWindow { get; set; } - public JiraLinkOptions(string baseUrl) { OpenInNewWindow = true; //default diff --git a/src/Markdig/MarkdownExtensions.cs b/src/Markdig/MarkdownExtensions.cs index 185cb3b5..8aa064de 100644 --- a/src/Markdig/MarkdownExtensions.cs +++ b/src/Markdig/MarkdownExtensions.cs @@ -538,7 +538,7 @@ public static MarkdownPipelineBuilder DisableHtml(this MarkdownPipelineBuilder p var inlineParser = pipeline.InlineParsers.Find(); if (inlineParser != null) { - inlineParser.EnableHtmlParsing = false; + inlineParser.Options.EnableHtmlParsing = false; } return pipeline; } diff --git a/src/Markdig/Parsers/Inlines/AutolinkInlineParser.cs b/src/Markdig/Parsers/Inlines/AutolinkInlineParser.cs index 0fd369c2..06d01d37 100644 --- a/src/Markdig/Parsers/Inlines/AutolinkInlineParser.cs +++ b/src/Markdig/Parsers/Inlines/AutolinkInlineParser.cs @@ -3,6 +3,7 @@ // See the license.txt file in the project root for more information. using Markdig.Helpers; +using Markdig.Renderers.Html; using Markdig.Syntax; using Markdig.Syntax.Inlines; @@ -14,19 +15,21 @@ namespace Markdig.Parsers.Inlines; /// public class AutolinkInlineParser : InlineParser { + public AutolinkInlineParser() : this(new AutolinkOptions()) + { + + } + /// /// Initializes a new instance of the class. /// - public AutolinkInlineParser() + public AutolinkInlineParser(AutolinkOptions options) { + Options = options ?? throw new ArgumentNullException(nameof(options)); OpeningCharacters = ['<']; - EnableHtmlParsing = true; } - /// - /// Gets or sets a value indicating whether to enable HTML parsing. Default is true - /// - public bool EnableHtmlParsing { get; set; } + public readonly AutolinkOptions Options; public override bool Match(InlineProcessor processor, ref StringSlice slice) { @@ -42,8 +45,12 @@ public override bool Match(InlineProcessor processor, ref StringSlice slice) Line = line, Column = column }; + if (Options.OpenInNewWindow) + { + processor.Inline.GetAttributes().AddPropertyIfNotExist("target", "_blank"); + } } - else if (EnableHtmlParsing) + else if (Options.EnableHtmlParsing) { slice = saved; if (!HtmlHelper.TryParseHtmlTag(ref slice, out string? htmlTag)) @@ -57,6 +64,10 @@ public override bool Match(InlineProcessor processor, ref StringSlice slice) Line = line, Column = column }; + if (Options.OpenInNewWindow) + { + processor.Inline.GetAttributes().AddPropertyIfNotExist("target", "_blank"); + } } else { diff --git a/src/Markdig/Parsers/Inlines/AutolinkOptions.cs b/src/Markdig/Parsers/Inlines/AutolinkOptions.cs new file mode 100644 index 00000000..153c9cae --- /dev/null +++ b/src/Markdig/Parsers/Inlines/AutolinkOptions.cs @@ -0,0 +1,13 @@ +// Copyright (c) Alexandre Mutel. All rights reserved. +// This file is licensed under the BSD-Clause 2 license. +// See the license.txt file in the project root for more information. + +namespace Markdig.Parsers.Inlines; + +public class AutolinkOptions : LinkOptions +{ + /// + /// Gets or sets a value indicating whether to enable HTML parsing. Default is true + /// + public bool EnableHtmlParsing { get; set; } = true; +} diff --git a/src/Markdig/Parsers/Inlines/LinkInlineParser.cs b/src/Markdig/Parsers/Inlines/LinkInlineParser.cs index a9687a31..2a34e461 100644 --- a/src/Markdig/Parsers/Inlines/LinkInlineParser.cs +++ b/src/Markdig/Parsers/Inlines/LinkInlineParser.cs @@ -3,6 +3,7 @@ // See the license.txt file in the project root for more information. using Markdig.Helpers; +using Markdig.Renderers.Html; using Markdig.Syntax; using Markdig.Syntax.Inlines; @@ -17,11 +18,22 @@ public class LinkInlineParser : InlineParser /// /// Initializes a new instance of the class. /// - public LinkInlineParser() + public LinkInlineParser() : this(new LinkOptions()) { + + } + + /// + /// Initializes a new instance of the class. + /// + public LinkInlineParser(LinkOptions options) + { + Options = options ?? throw new ArgumentNullException(nameof(options)); OpeningCharacters = ['[', ']', '!']; } + public readonly LinkOptions Options; + public override bool Match(InlineProcessor processor, ref StringSlice slice) { // The following methods are inspired by the "An algorithm for parsing nested emphasis and links" @@ -169,6 +181,11 @@ private bool ProcessLinkReference( linkInline.LocalLabel = localLabel; } + if (Options.OpenInNewWindow) + { + linkInline.GetAttributes().AddPropertyIfNotExist("target", "_blank"); + } + link = linkInline; } diff --git a/src/Markdig/Parsers/LinkOptions.cs b/src/Markdig/Parsers/LinkOptions.cs new file mode 100644 index 00000000..25d17ddf --- /dev/null +++ b/src/Markdig/Parsers/LinkOptions.cs @@ -0,0 +1,19 @@ +// Copyright (c) Alexandre Mutel. All rights reserved. +// This file is licensed under the BSD-Clause 2 license. +// See the license.txt file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Markdig.Parsers; + +public class LinkOptions +{ + /// + /// Should the link open in a new window when clicked (false by default) + /// + public bool OpenInNewWindow { get; set; } +} From 543570224e940826c38187f8eda013328d3e70d4 Mon Sep 17 00:00:00 2001 From: Phillip Haydon Date: Fri, 3 Oct 2025 20:34:24 +1300 Subject: [PATCH 76/88] Fix issue where an inline code block that spans multiple lines doesn't parse correctly (#893) * fixes issue where an inline code block that spans multiple lines doesn't get treated as code * Update src/Markdig.Tests/TestPipeTable.cs Co-authored-by: Miha Zupan * Apply suggestion from @MihaZupan Co-authored-by: Miha Zupan * Update src/Markdig.Tests/TestPipeTable.cs Co-authored-by: Miha Zupan * fix broken test * removed unreachable code and added more tests * Update src/Markdig.Tests/TestPipeTable.cs Co-authored-by: Miha Zupan * Update src/Markdig.Tests/TestPipeTable.cs Co-authored-by: Miha Zupan * removed uncessary inline code check * Update src/Markdig/Parsers/Inlines/CodeInlineParser.cs Co-authored-by: Miha Zupan --------- Co-authored-by: Miha Zupan Co-authored-by: Alexandre Mutel --- src/Markdig.Tests/TestPipeTable.cs | 101 ++++++++++++++++++ src/Markdig/Helpers/HtmlHelper.cs | 10 +- .../Parsers/Inlines/CodeInlineParser.cs | 18 ++-- 3 files changed, 116 insertions(+), 13 deletions(-) diff --git a/src/Markdig.Tests/TestPipeTable.cs b/src/Markdig.Tests/TestPipeTable.cs index f36f9fb0..06bf3849 100644 --- a/src/Markdig.Tests/TestPipeTable.cs +++ b/src/Markdig.Tests/TestPipeTable.cs @@ -1,5 +1,7 @@ +using Markdig; using Markdig.Extensions.Tables; using Markdig.Syntax; +using Markdig.Syntax.Inlines; namespace Markdig.Tests; @@ -101,4 +103,103 @@ public void TableWithUnbalancedCodeSpanParsesWithoutDepthLimitError() Assert.That(html, Does.Contain("`C")); } + + [Test] + public void CodeInlineWithPipeDelimitersRemainsCodeInline() + { + const string markdown = "`|| hidden text ||`"; + + var pipeline = new MarkdownPipelineBuilder() + .UseAdvancedExtensions() + .Build(); + + var document = Markdown.Parse(markdown, pipeline); + + var codeInline = document.Descendants().OfType().SingleOrDefault(); + Assert.IsNotNull(codeInline); + Assert.That(codeInline!.Content, Is.EqualTo("|| hidden text ||")); + Assert.That(document.ToHtml(), Is.EqualTo("

|| hidden text ||

\n")); + } + + [Test] + public void MultiLineCodeInlineWithPipeDelimitersRendersAsCode() + { + string markdown = + """ + ` + || hidden text || + ` + """.ReplaceLineEndings("\n"); + + var pipeline = new MarkdownPipelineBuilder() + .UseAdvancedExtensions() + .Build(); + + var html = Markdown.ToHtml(markdown, pipeline); + + Assert.That(html, Is.EqualTo("

|| hidden text ||

\n")); + } + + [Test] + public void TableCellWithCodeInlineRendersCorrectly() + { + const string markdown = + """ + | Count | A | B | C | D | E | + |-------|---|---|---|---|---| + | 0 | B | C | D | E | F | + | 1 | B | `Code block` | D | E | F | + | 2 | B | C | D | E | F | + """; + + var pipeline = new MarkdownPipelineBuilder() + .UseAdvancedExtensions() + .Build(); + + var html = Markdown.ToHtml(markdown, pipeline); + + Assert.That(html, Does.Contain("
")); + } + + [Test] + public void CodeInlineWithIndentedContentPreservesWhitespace() + { + const string markdown = "`\n foo\n`"; + + var pipeline = new MarkdownPipelineBuilder() + .UseAdvancedExtensions() + .Build(); + + var document = Markdown.Parse(markdown, pipeline); + var codeInline = document.Descendants().OfType().Single(); + + Assert.That(codeInline.Content, Is.EqualTo("foo")); + Assert.That(Markdown.ToHtml(markdown, pipeline), Is.EqualTo("

foo

\n")); + } + + [Test] + public void TableWithIndentedPipeAfterCodeInlineParsesCorrectly() + { + var markdown = + """ + ` + || hidden text || + ` + + | Count | Value | + |-------|-------| + | 0 | B | + + """.ReplaceLineEndings("\n"); + + var pipeline = new MarkdownPipelineBuilder() + .UseAdvancedExtensions() + .Build(); + + var html = Markdown.ToHtml(markdown, pipeline); + + Assert.That(html, Does.Contain("

|| hidden text ||

")); + Assert.That(html, Does.Contain("B")); + } } diff --git a/src/Markdig/Helpers/HtmlHelper.cs b/src/Markdig/Helpers/HtmlHelper.cs index 4887653f..317c6dde 100644 --- a/src/Markdig/Helpers/HtmlHelper.cs +++ b/src/Markdig/Helpers/HtmlHelper.cs @@ -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 System.Diagnostics; @@ -430,7 +430,7 @@ private static bool TryParseHtmlTagHtmlComment(ref StringSlice text, ref ValueSt const string EndOfComment = "-->"; - int endOfComment = slice.IndexOf(EndOfComment, StringComparison.Ordinal); + int endOfComment = slice.IndexOf(EndOfComment.AsSpan(), StringComparison.Ordinal); if (endOfComment < 0) { return false; @@ -474,7 +474,7 @@ private static bool TryParseHtmlTagProcessingInstruction(ref StringSlice text, r public static string Unescape(string? text, bool removeBackSlash = true) { // Credits: code from CommonMark.NET - // Copyright (c) 2014, Kārlis Gaņģis All rights reserved. + // Copyright (c) 2014, Kārlis Gaņģis All rights reserved. // See license for details: https://github.com/Knagis/CommonMark.NET/blob/master/LICENSE.md if (string.IsNullOrEmpty(text)) { @@ -553,7 +553,7 @@ public static string Unescape(string? text, bool removeBackSlash = true) public static int ScanEntity(T slice, out int numericEntity, out int namedEntityStart, out int namedEntityLength) where T : ICharIterator { // Credits: code from CommonMark.NET - // Copyright (c) 2014, Kārlis Gaņģis All rights reserved. + // Copyright (c) 2014, Kārlis Gaņģis All rights reserved. // See license for details: https://github.com/Knagis/CommonMark.NET/blob/master/LICENSE.md numericEntity = 0; @@ -568,7 +568,7 @@ public static int ScanEntity(T slice, out int numericEntity, out int namedEnt var start = slice.Start; char c = slice.NextChar(); int counter = 0; - + if (c == '#') { c = slice.PeekChar(); diff --git a/src/Markdig/Parsers/Inlines/CodeInlineParser.cs b/src/Markdig/Parsers/Inlines/CodeInlineParser.cs index 34a2a1fc..e71029f9 100644 --- a/src/Markdig/Parsers/Inlines/CodeInlineParser.cs +++ b/src/Markdig/Parsers/Inlines/CodeInlineParser.cs @@ -4,6 +4,7 @@ using System.Diagnostics; +using Markdig.Extensions.Tables; using Markdig.Helpers; using Markdig.Syntax; using Markdig.Syntax.Inlines; @@ -84,15 +85,16 @@ public override bool Match(InlineProcessor processor, ref StringSlice slice) { lookAhead = lookAhead.Slice(1); } - int whitespace = 0; - while (whitespace < lookAhead.Length && (lookAhead[whitespace] == ' ' || lookAhead[whitespace] == '\t')) + if (lookAhead[0] == '|') { - whitespace++; - } - if (whitespace < lookAhead.Length && lookAhead[whitespace] == '|') - { - slice.Start = openingStart; - return false; + // We saw the start of a code inline, but the close sticks are not present on the same line. + // If the next line starts with a pipe character, this is likely an incomplete CodeInline within a table. + // Treat it as regular text to avoid breaking the overall table shape. + if (processor.Inline != null && processor.Inline.ContainsParentOfType()) + { + slice.Start = openingStart; + return false; + } } containsNewLines = true; From 781d9b536598e0b770c1eabbe58d2ac76b285409 Mon Sep 17 00:00:00 2001 From: Asttear Date: Sun, 5 Oct 2025 17:21:12 +0800 Subject: [PATCH 77/88] Remove leading newline in block attributes (#896) * Remove leading newline in block attributes fix #895 * Add handling logic for `\r\n` --- .../Specs/GenericAttributesSpecs.generated.cs | 17 +++++++++++++++++ .../Specs/GenericAttributesSpecs.md | 9 +++++++++ .../GenericAttributesParser.cs | 9 +++++++++ 3 files changed, 35 insertions(+) diff --git a/src/Markdig.Tests/Specs/GenericAttributesSpecs.generated.cs b/src/Markdig.Tests/Specs/GenericAttributesSpecs.generated.cs index d7eea2d3..ff8a3909 100644 --- a/src/Markdig.Tests/Specs/GenericAttributesSpecs.generated.cs +++ b/src/Markdig.Tests/Specs/GenericAttributesSpecs.generated.cs @@ -98,5 +98,22 @@ public void ExtensionsGenericAttributes_Example003() TestParser.TestSpec("[Foo](url){data-x=1}\n\n[Foo](url){data-x='1'}\n\n[Foo](url){data-x=11}", "

Foo

\n

Foo

\n

Foo

", "attributes|advanced", context: "Example 3\nSection Extensions / Generic Attributes\n"); } + + // Attributes that occur immediately before a block element, on a line by themselves, affect that element + [Test] + public void ExtensionsGenericAttributes_Example004() + { + // Example 4 + // Section: Extensions / Generic Attributes + // + // The following Markdown: + // {.center} + // A paragraph + // + // Should be rendered as: + //

A paragraph

+ + TestParser.TestSpec("{.center}\nA paragraph", "

A paragraph

", "attributes|advanced", context: "Example 4\nSection Extensions / Generic Attributes\n"); + } } } diff --git a/src/Markdig.Tests/Specs/GenericAttributesSpecs.md b/src/Markdig.Tests/Specs/GenericAttributesSpecs.md index a0c46e40..3fee4340 100644 --- a/src/Markdig.Tests/Specs/GenericAttributesSpecs.md +++ b/src/Markdig.Tests/Specs/GenericAttributesSpecs.md @@ -61,3 +61,12 @@ Attribute values can be one character long

Foo

Foo

```````````````````````````````` + +Attributes that occur immediately before a block element, on a line by themselves, affect that element + +```````````````````````````````` example +{.center} +A paragraph +. +

A paragraph

+```````````````````````````````` diff --git a/src/Markdig/Extensions/GenericAttributes/GenericAttributesParser.cs b/src/Markdig/Extensions/GenericAttributes/GenericAttributesParser.cs index 781a4bfb..9c329e45 100644 --- a/src/Markdig/Extensions/GenericAttributes/GenericAttributesParser.cs +++ b/src/Markdig/Extensions/GenericAttributes/GenericAttributesParser.cs @@ -109,6 +109,15 @@ public static bool TryParse(ref StringSlice slice, [NotNullWhen(true)] out HtmlA { isValid = true; line.SkipChar(); // skip } + // skip line breaks + if (line.CurrentChar == '\n') + { + line.SkipChar(); + } + else if (line.CurrentChar == '\r' && line.PeekChar() == '\n') + { + line.Start += 2; + } break; } From d5f8a809a00dd32975af3cf1c26609e5b7a7f7a3 Mon Sep 17 00:00:00 2001 From: Miha Zupan Date: Thu, 16 Oct 2025 17:24:33 +0200 Subject: [PATCH 78/88] Move sln to slnx (#901) --- .editorconfig | 10 ++++- .gitattributes | 2 +- src/dotnet-releaser.toml | 2 +- src/markdig.sln | 79 ------------------------------------- src/markdig.sln.DotSettings | 12 ------ src/markdig.slnx | 23 +++++++++++ 6 files changed, 33 insertions(+), 95 deletions(-) delete mode 100644 src/markdig.sln delete mode 100644 src/markdig.sln.DotSettings create mode 100644 src/markdig.slnx diff --git a/.editorconfig b/.editorconfig index cbde0b15..a7cbdcb8 100644 --- a/.editorconfig +++ b/.editorconfig @@ -12,8 +12,9 @@ insert_final_newline = false trim_trailing_whitespace = true # Solution Files -[*.sln] -indent_style = tab +[*.slnx] +indent_size = 2 +insert_final_newline = true # XML Project Files [*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}] @@ -35,3 +36,8 @@ insert_final_newline = true # Bash Files [*.sh] end_of_line = lf + +# C# files +[*.cs] +# License header +file_header_template = Copyright (c) Alexandre Mutel. All rights reserved.\nThis file is licensed under the BSD-Clause 2 license.\nSee the license.txt file in the project root for more information. \ No newline at end of file diff --git a/.gitattributes b/.gitattributes index 142bc04f..ceb4244c 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,3 +1,3 @@ * text=auto *.cs text=auto diff=csharp -*.sln text=auto eol=crlf \ No newline at end of file +*.slnx text=auto eol=crlf \ No newline at end of file diff --git a/src/dotnet-releaser.toml b/src/dotnet-releaser.toml index 27e25732..540a5967 100644 --- a/src/dotnet-releaser.toml +++ b/src/dotnet-releaser.toml @@ -1,5 +1,5 @@ [msbuild] -project = ["markdig.sln", "./Markdig.Signed/Markdig.Signed.csproj"] +project = ["markdig.slnx", "./Markdig.Signed/Markdig.Signed.csproj"] build_debug = true [github] user = "xoofx" diff --git a/src/markdig.sln b/src/markdig.sln deleted file mode 100644 index 03a6b2f7..00000000 --- a/src/markdig.sln +++ /dev/null @@ -1,79 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.0.32112.339 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{061866E2-005C-4D13-A338-EA464BBEC60F}" - ProjectSection(SolutionItems) = preProject - ..\.editorconfig = ..\.editorconfig - ..\.gitattributes = ..\.gitattributes - ..\.gitignore = ..\.gitignore - ..\changelog.md = ..\changelog.md - ..\.github\workflows\ci.yml = ..\.github\workflows\ci.yml - global.json = global.json - ..\license.txt = ..\license.txt - ..\readme.md = ..\readme.md - EndProjectSection -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Markdig", "Markdig\Markdig.csproj", "{8A58A7E2-627C-4F41-933F-5AC92ADFAB48}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Markdig.Tests", "Markdig.Tests\Markdig.Tests.csproj", "{A0C5CB5F-5568-40AB-B945-D6D2664D51B0}" - ProjectSection(ProjectDependencies) = postProject - {8A58A7E2-627C-4F41-933F-5AC92ADFAB48} = {8A58A7E2-627C-4F41-933F-5AC92ADFAB48} - EndProjectSection -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Markdig.Benchmarks", "Markdig.Benchmarks\Markdig.Benchmarks.csproj", "{6A19F040-BC7C-4283-873A-177B5324F1ED}" - ProjectSection(ProjectDependencies) = postProject - {8A58A7E2-627C-4F41-933F-5AC92ADFAB48} = {8A58A7E2-627C-4F41-933F-5AC92ADFAB48} - EndProjectSection -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Markdig.WebApp", "Markdig.WebApp\Markdig.WebApp.csproj", "{3CAD9801-9976-46BE-BACA-F6D0D21FDC00}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnicodeNormDApp", "UnicodeNormDApp\UnicodeNormDApp.csproj", "{33FFC0B9-0187-44F9-9424-BB5AF5B4FB84}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "mdtoc", "mdtoc\mdtoc.csproj", "{E3CDFF0F-5BFC-42E9-BDBA-2797651900A2}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SpecFileGen", "SpecFileGen\SpecFileGen.csproj", "{DB6E2ED5-7884-4E97-84AF-35E2480CF685}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {8A58A7E2-627C-4F41-933F-5AC92ADFAB48}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8A58A7E2-627C-4F41-933F-5AC92ADFAB48}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8A58A7E2-627C-4F41-933F-5AC92ADFAB48}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8A58A7E2-627C-4F41-933F-5AC92ADFAB48}.Release|Any CPU.Build.0 = Release|Any CPU - {A0C5CB5F-5568-40AB-B945-D6D2664D51B0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A0C5CB5F-5568-40AB-B945-D6D2664D51B0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A0C5CB5F-5568-40AB-B945-D6D2664D51B0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A0C5CB5F-5568-40AB-B945-D6D2664D51B0}.Release|Any CPU.Build.0 = Release|Any CPU - {6A19F040-BC7C-4283-873A-177B5324F1ED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6A19F040-BC7C-4283-873A-177B5324F1ED}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6A19F040-BC7C-4283-873A-177B5324F1ED}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6A19F040-BC7C-4283-873A-177B5324F1ED}.Release|Any CPU.Build.0 = Release|Any CPU - {3CAD9801-9976-46BE-BACA-F6D0D21FDC00}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3CAD9801-9976-46BE-BACA-F6D0D21FDC00}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3CAD9801-9976-46BE-BACA-F6D0D21FDC00}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3CAD9801-9976-46BE-BACA-F6D0D21FDC00}.Release|Any CPU.Build.0 = Release|Any CPU - {33FFC0B9-0187-44F9-9424-BB5AF5B4FB84}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {33FFC0B9-0187-44F9-9424-BB5AF5B4FB84}.Debug|Any CPU.Build.0 = Debug|Any CPU - {33FFC0B9-0187-44F9-9424-BB5AF5B4FB84}.Release|Any CPU.ActiveCfg = Release|Any CPU - {33FFC0B9-0187-44F9-9424-BB5AF5B4FB84}.Release|Any CPU.Build.0 = Release|Any CPU - {E3CDFF0F-5BFC-42E9-BDBA-2797651900A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E3CDFF0F-5BFC-42E9-BDBA-2797651900A2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E3CDFF0F-5BFC-42E9-BDBA-2797651900A2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E3CDFF0F-5BFC-42E9-BDBA-2797651900A2}.Release|Any CPU.Build.0 = Release|Any CPU - {DB6E2ED5-7884-4E97-84AF-35E2480CF685}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {DB6E2ED5-7884-4E97-84AF-35E2480CF685}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DB6E2ED5-7884-4E97-84AF-35E2480CF685}.Release|Any CPU.ActiveCfg = Release|Any CPU - {DB6E2ED5-7884-4E97-84AF-35E2480CF685}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {D068F7B6-6ACC-456C-A2E1-10EA746D956D} - EndGlobalSection -EndGlobal diff --git a/src/markdig.sln.DotSettings b/src/markdig.sln.DotSettings deleted file mode 100644 index baf9a14b..00000000 --- a/src/markdig.sln.DotSettings +++ /dev/null @@ -1,12 +0,0 @@ - - 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. - <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> - <Policy><Descriptor Staticness="Instance" AccessRightKinds="Private" Description="Instance fields (private)"><ElementKinds><Kind Name="FIELD" /><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></Policy> - True - True - TestFolder - True - True - True \ No newline at end of file diff --git a/src/markdig.slnx b/src/markdig.slnx new file mode 100644 index 00000000..512d4a2c --- /dev/null +++ b/src/markdig.slnx @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + From 800235ba7ab773ac3ea691d16e4bb17a1f9b0200 Mon Sep 17 00:00:00 2001 From: Miha Zupan Date: Thu, 16 Oct 2025 17:25:30 +0200 Subject: [PATCH 79/88] Fix IndexOutOfRangeException in CodeInlineParser (#900) --- src/Markdig.Tests/TestCodeInline.cs | 10 ++++++++++ src/Markdig/Parsers/Inlines/CodeInlineParser.cs | 7 +------ src/Markdig/Polyfills/SpanExtensions.cs | 17 +++++++++++++---- 3 files changed, 24 insertions(+), 10 deletions(-) create mode 100644 src/Markdig.Tests/TestCodeInline.cs diff --git a/src/Markdig.Tests/TestCodeInline.cs b/src/Markdig.Tests/TestCodeInline.cs new file mode 100644 index 00000000..b5427647 --- /dev/null +++ b/src/Markdig.Tests/TestCodeInline.cs @@ -0,0 +1,10 @@ +namespace Markdig.Tests; + +public class TestCodeInline +{ + [Test] + public void UnpairedCodeInlineWithTrailingChars() + { + TestParser.TestSpec("*`\n\f", "

*`

"); + } +} diff --git a/src/Markdig/Parsers/Inlines/CodeInlineParser.cs b/src/Markdig/Parsers/Inlines/CodeInlineParser.cs index e71029f9..216e68f8 100644 --- a/src/Markdig/Parsers/Inlines/CodeInlineParser.cs +++ b/src/Markdig/Parsers/Inlines/CodeInlineParser.cs @@ -80,12 +80,7 @@ public override bool Match(InlineProcessor processor, ref StringSlice slice) if (closeSticks == 0) { - ReadOnlySpan lookAhead = span.Length > 1 ? span.Slice(1) : ReadOnlySpan.Empty; - while (!lookAhead.IsEmpty && (lookAhead[0] == '\r' || lookAhead[0] == '\n')) - { - lookAhead = lookAhead.Slice(1); - } - if (lookAhead[0] == '|') + if (span.TrimStart(['\r', '\n']).StartsWith('|')) { // We saw the start of a code inline, but the close sticks are not present on the same line. // If the next line starts with a pipe character, this is likely an incomplete CodeInline within a table. diff --git a/src/Markdig/Polyfills/SpanExtensions.cs b/src/Markdig/Polyfills/SpanExtensions.cs index caf61cac..e314f2a5 100644 --- a/src/Markdig/Polyfills/SpanExtensions.cs +++ b/src/Markdig/Polyfills/SpanExtensions.cs @@ -2,14 +2,14 @@ // This file is licensed under the BSD-Clause 2 license. // See the license.txt file in the project root for more information. -#if NET462 || NETSTANDARD2_0 - using System.Diagnostics; +using System.Runtime.CompilerServices; namespace System; internal static class SpanExtensions { +#if NET462 || NETSTANDARD2_0 public static bool StartsWith(this ReadOnlySpan span, string prefix, StringComparison comparisonType) { Debug.Assert(comparisonType is StringComparison.Ordinal or StringComparison.OrdinalIgnoreCase); @@ -18,6 +18,15 @@ public static bool StartsWith(this ReadOnlySpan span, string prefix, Strin span.Length >= prefix.Length && span.Slice(0, prefix.Length).Equals(prefix.AsSpan(), comparisonType); } -} -#endif \ No newline at end of file +#endif + +#if !NET9_0_OR_GREATER + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool StartsWith(this ReadOnlySpan span, char c) + { + return span.Length > 0 && span[0] == c; + } + +#endif +} \ No newline at end of file From 191e33ab324d4dc2fd3c3466b1b997e83ebe462d Mon Sep 17 00:00:00 2001 From: Miha Zupan Date: Thu, 16 Oct 2025 17:25:47 +0200 Subject: [PATCH 80/88] Fix build warnings (#899) --- src/Markdig.Tests/Markdig.Tests.csproj | 1 + src/Markdig/Extensions/Tables/TableHelper.cs | 1 + src/SpecFileGen/SpecFileGen.csproj | 1 + 3 files changed, 3 insertions(+) diff --git a/src/Markdig.Tests/Markdig.Tests.csproj b/src/Markdig.Tests/Markdig.Tests.csproj index eb019e28..43afb2d4 100644 --- a/src/Markdig.Tests/Markdig.Tests.csproj +++ b/src/Markdig.Tests/Markdig.Tests.csproj @@ -9,6 +9,7 @@ Markdig.Tests.Program $(MSBuildProjectDirectory)\..\SpecFileGen\bin\$(Configuration)\$(TargetFramework)\SpecFileGen.dll $(MSBuildProjectDirectory)\..\SpecFileGen\bin\$(Configuration)\$(TargetFramework)\SpecFileGen.timestamp + $(NoWarn);NETSDK1138 diff --git a/src/Markdig/Extensions/Tables/TableHelper.cs b/src/Markdig/Extensions/Tables/TableHelper.cs index f75090a5..421535d3 100644 --- a/src/Markdig/Extensions/Tables/TableHelper.cs +++ b/src/Markdig/Extensions/Tables/TableHelper.cs @@ -47,6 +47,7 @@ public static bool ParseColumnHeaderAuto(ref StringSlice slice, out char delimit /// The text slice. /// The delimiter character (either `-` or `=`). If `\0`, it will detect the character (either `-` or `=`) /// The alignment of the column. + /// The number of times appeared in the column header. /// /// true if parsing was successful /// diff --git a/src/SpecFileGen/SpecFileGen.csproj b/src/SpecFileGen/SpecFileGen.csproj index 0c28b463..98716bc8 100644 --- a/src/SpecFileGen/SpecFileGen.csproj +++ b/src/SpecFileGen/SpecFileGen.csproj @@ -5,6 +5,7 @@ net6.0;net8.0;net9.0 enable false + $(NoWarn);NETSDK1138 From 5c78932f552f25035521411885296eae2558222a Mon Sep 17 00:00:00 2001 From: Miha Zupan Date: Fri, 17 Oct 2025 08:07:15 +0200 Subject: [PATCH 81/88] Fix edge cases in EmphasisInlineParser (#902) --- src/Markdig.Tests/TestEmphasisExtended.cs | 3 +++ src/Markdig/Helpers/UnicodeUtility.cs | 2 +- src/Markdig/Parsers/Inlines/EmphasisDescriptor.cs | 2 ++ src/Markdig/Parsers/Inlines/EmphasisInlineParser.cs | 10 +++++----- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/Markdig.Tests/TestEmphasisExtended.cs b/src/Markdig.Tests/TestEmphasisExtended.cs index ce6c8973..c95fc5e3 100644 --- a/src/Markdig.Tests/TestEmphasisExtended.cs +++ b/src/Markdig.Tests/TestEmphasisExtended.cs @@ -148,6 +148,9 @@ class CustomEmphasisInline : EmphasisInline { } [TestCase("1Foo1", "Foo")] [TestCase("1121", "12")] [TestCase("22322", "3")] + [TestCase("2223222", "232")] + [TestCase("22223222", "2232")] + [TestCase("22223223222", "2222332")] [TestCase("2232", "2232")] [TestCase("333", "333")] [TestCase("3334333", "4")] diff --git a/src/Markdig/Helpers/UnicodeUtility.cs b/src/Markdig/Helpers/UnicodeUtility.cs index 4364c953..2c059afc 100644 --- a/src/Markdig/Helpers/UnicodeUtility.cs +++ b/src/Markdig/Helpers/UnicodeUtility.cs @@ -22,7 +22,7 @@ public static bool IsValidUnicodeScalar(uint value) [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void GetUtf16SurrogatesFromSupplementaryPlaneScalar(uint value, out char highSurrogateCodePoint, out char lowSurrogateCodePoint) { - Debug.Assert(IsValidUnicodeScalar(value) && IsBmpCodePoint(value)); + Debug.Assert(IsValidUnicodeScalar(value) && !IsBmpCodePoint(value)); highSurrogateCodePoint = (char)((value + ((0xD800u - 0x40u) << 10)) >> 10); lowSurrogateCodePoint = (char)((value & 0x3FFu) + 0xDC00u); diff --git a/src/Markdig/Parsers/Inlines/EmphasisDescriptor.cs b/src/Markdig/Parsers/Inlines/EmphasisDescriptor.cs index c2e8259e..429a24b1 100644 --- a/src/Markdig/Parsers/Inlines/EmphasisDescriptor.cs +++ b/src/Markdig/Parsers/Inlines/EmphasisDescriptor.cs @@ -3,12 +3,14 @@ // See the license.txt file in the project root for more information. using Markdig.Helpers; +using System.Diagnostics; namespace Markdig.Parsers.Inlines; /// /// Descriptor for an emphasis. /// +[DebuggerDisplay("Emphasis Char={Character}, Min={MinimumCount}, Max={MaximumCount}, EnableWithinWord={EnableWithinWord}")] public sealed class EmphasisDescriptor { /// diff --git a/src/Markdig/Parsers/Inlines/EmphasisInlineParser.cs b/src/Markdig/Parsers/Inlines/EmphasisInlineParser.cs index 440a878b..af2921c8 100644 --- a/src/Markdig/Parsers/Inlines/EmphasisInlineParser.cs +++ b/src/Markdig/Parsers/Inlines/EmphasisInlineParser.cs @@ -233,9 +233,9 @@ private void ProcessEmphasis(InlineProcessor processor, List= emphasisDesc.MinimumCount) + if ((closeDelimiter.Type & DelimiterType.Close) != 0) { - while (true) + while (closeDelimiter.DelimiterCount >= emphasisDesc.MinimumCount) { // Now, look back in the stack (staying above stack_bottom and the openers_bottom for this delimiter type) // for the first matching potential opener (“matching” means same delimiter). @@ -245,8 +245,7 @@ private void ProcessEmphasis(InlineProcessor processor, List= emphasisDesc.MinimumCount) + if (openDelimiter.DelimiterCount >= emphasisDesc.MinimumCount && + closeDelimiter.DelimiterCount >= emphasisDesc.MinimumCount) { goto process_delims; } From 03bdf6008631166ea8d288a647e2d1a2da0c389b Mon Sep 17 00:00:00 2001 From: Miha Zupan Date: Fri, 17 Oct 2025 08:09:28 +0200 Subject: [PATCH 82/88] Add a basic fuzzing project (#903) * Add basic fuzzing project * Mark the project as non-packable --- src/Markdig.Fuzzing/.gitignore | 4 + src/Markdig.Fuzzing/Markdig.Fuzzing.csproj | 19 +++++ src/Markdig.Fuzzing/Program.cs | 71 ++++++++++++++++++ src/Markdig.Fuzzing/run-fuzzer.ps1 | 86 ++++++++++++++++++++++ src/markdig.slnx | 3 + 5 files changed, 183 insertions(+) create mode 100644 src/Markdig.Fuzzing/.gitignore create mode 100644 src/Markdig.Fuzzing/Markdig.Fuzzing.csproj create mode 100644 src/Markdig.Fuzzing/Program.cs create mode 100644 src/Markdig.Fuzzing/run-fuzzer.ps1 diff --git a/src/Markdig.Fuzzing/.gitignore b/src/Markdig.Fuzzing/.gitignore new file mode 100644 index 00000000..60aca1b0 --- /dev/null +++ b/src/Markdig.Fuzzing/.gitignore @@ -0,0 +1,4 @@ +corpus +libfuzzer-dotnet-windows.exe +crash-* +timeout-* \ No newline at end of file diff --git a/src/Markdig.Fuzzing/Markdig.Fuzzing.csproj b/src/Markdig.Fuzzing/Markdig.Fuzzing.csproj new file mode 100644 index 00000000..b9774246 --- /dev/null +++ b/src/Markdig.Fuzzing/Markdig.Fuzzing.csproj @@ -0,0 +1,19 @@ + + + + Exe + net9.0 + enable + enable + false + + + + + + + + + + + diff --git a/src/Markdig.Fuzzing/Program.cs b/src/Markdig.Fuzzing/Program.cs new file mode 100644 index 00000000..9f63b611 --- /dev/null +++ b/src/Markdig.Fuzzing/Program.cs @@ -0,0 +1,71 @@ +using Markdig; +using Markdig.Renderers.Roundtrip; +using Markdig.Syntax; +using SharpFuzz; +using System.Diagnostics; +using System.Text; + +ReadOnlySpanAction fuzzTarget = ParseRenderFuzzer.FuzzTarget; + +if (args.Length > 0) +{ + // Run the target on existing inputs + string[] files = Directory.Exists(args[0]) + ? Directory.GetFiles(args[0]) + : [args[0]]; + + Debugger.Launch(); + + foreach (string inputFile in files) + { + fuzzTarget(File.ReadAllBytes(inputFile)); + } +} +else +{ + Fuzzer.LibFuzzer.Run(fuzzTarget); +} + +sealed class ParseRenderFuzzer +{ + private static readonly MarkdownPipeline s_advancedPipeline = new MarkdownPipelineBuilder() + .UseAdvancedExtensions() + .Build(); + + private static readonly ResettableRoundtripRenderer _roundtripRenderer = new(); + + public static void FuzzTarget(ReadOnlySpan bytes) + { + string text = Encoding.UTF8.GetString(bytes); + + try + { + MarkdownDocument document = Markdown.Parse(text); + _ = document.ToHtml(); + + document = Markdown.Parse(text, s_advancedPipeline); + _ = document.ToHtml(s_advancedPipeline); + + document = Markdown.Parse(text, trackTrivia: true); + _ = document.ToHtml(); + _roundtripRenderer.Reset(); + _roundtripRenderer.Render(document); + + _ = Markdown.Normalize(text); + _ = Markdown.ToPlainText(text); + } + catch (Exception ex) when (IsIgnorableException(ex)) { } + } + + private static bool IsIgnorableException(Exception exception) + { + return exception.Message.Contains("Markdown elements in the input are too deeply nested", StringComparison.Ordinal); + } + + private sealed class ResettableRoundtripRenderer : RoundtripRenderer + { + public ResettableRoundtripRenderer() : base(new StringWriter(new StringBuilder(1024 * 1024))) { } + + public new void Reset() => base.Reset(); + } +} \ No newline at end of file diff --git a/src/Markdig.Fuzzing/run-fuzzer.ps1 b/src/Markdig.Fuzzing/run-fuzzer.ps1 new file mode 100644 index 00000000..133acdf8 --- /dev/null +++ b/src/Markdig.Fuzzing/run-fuzzer.ps1 @@ -0,0 +1,86 @@ +param ( + [string]$configuration = $null +) + +Set-StrictMode -Version Latest + +$libFuzzer = "libfuzzer-dotnet-windows.exe" +$outputDir = "bin" + +function Get-LibFuzzer { + param ( + [string]$Path + ) + + $libFuzzerUrl = "https://github.com/Metalnem/libfuzzer-dotnet/releases/download/v2025.05.02.0904/libfuzzer-dotnet-windows.exe" + $expectedHash = "17af5b3f6ff4d2c57b44b9a35c13051b570eb66f0557d00015df3832709050bf" + + Write-Output "Downloading libFuzzer from $libFuzzerUrl..." + + try { + $tempFile = "$Path.tmp" + Invoke-WebRequest -Uri $libFuzzerUrl -OutFile $tempFile -UseBasicParsing + + $downloadedHash = (Get-FileHash -Path $tempFile -Algorithm SHA256).Hash + + if ($downloadedHash -eq $ExpectedHash) { + Move-Item -Path $tempFile -Destination $Path -Force + Write-Output "libFuzzer downloaded successfully to $Path" + } + else { + Write-Error "Hash validation failed." + Remove-Item -Path $tempFile -Force -ErrorAction SilentlyContinue + exit 1 + } + } + catch { + Write-Error "Failed to download libFuzzer: $($_.Exception.Message)" + Remove-Item -Path $tempFile -Force -ErrorAction SilentlyContinue + exit 1 + } +} + +# Check if libFuzzer exists, download if not +if (-not (Test-Path $libFuzzer)) { + Get-LibFuzzer -Path $libFuzzer +} + +$toolListOutput = dotnet tool list --global sharpFuzz.CommandLine 2>$null +if (-not ($toolListOutput -match "sharpfuzz")) { + Write-Output "Installing sharpfuzz CLI" + dotnet tool install --global sharpFuzz.CommandLine +} + +if (Test-Path $outputDir) { + Remove-Item -Recurse -Force $outputDir +} + +if ($configuration -eq $null) { + $configuration = "Debug" +} + +dotnet publish -c $configuration -o $outputDir + +$project = Join-Path $outputDir "Markdig.Fuzzing.dll" + +$fuzzingTarget = Join-Path $outputDir "Markdig.dll" + +Write-Output "Instrumenting $fuzzingTarget" +& sharpfuzz $fuzzingTarget + +if ($LastExitCode -ne 0) { + Write-Error "An error occurred while instrumenting $fuzzingTarget" + exit 1 +} + +New-Item -ItemType Directory -Force -Path corpus | Out-Null + +$libFuzzerArgs = @("--target_path=dotnet", "--target_arg=$project", "-timeout=10", "corpus") + +# Add any additional arguments passed to the script +if ($args) { + $libFuzzerArgs += $args +} + +Write-Output "Starting libFuzzer with arguments: $libFuzzerArgs" +& ./$libFuzzer @libFuzzerArgs \ No newline at end of file diff --git a/src/markdig.slnx b/src/markdig.slnx index 512d4a2c..a6f41692 100644 --- a/src/markdig.slnx +++ b/src/markdig.slnx @@ -12,6 +12,9 @@ + + + From d6e88f16f7d2d86a096a552250f89415513d09dc Mon Sep 17 00:00:00 2001 From: Miha Zupan Date: Mon, 20 Oct 2025 21:43:25 +0200 Subject: [PATCH 83/88] Fix pipe table parsing with a leading paragraph (#905) * Fix pipe table parsing with a leading paragraph * Use the alternative approach --- .../Specs/GridTableSpecs.generated.cs | 29 +++++++++++ src/Markdig.Tests/Specs/GridTableSpecs.md | 21 ++++++++ .../Specs/PipeTableSpecs.generated.cs | 33 ++++++++++++ src/Markdig.Tests/Specs/PipeTableSpecs.md | 25 ++++++++++ src/Markdig.Tests/TestPipeTable.cs | 1 + .../Extensions/Tables/PipeTableParser.cs | 50 +++++++++++++------ 6 files changed, 145 insertions(+), 14 deletions(-) diff --git a/src/Markdig.Tests/Specs/GridTableSpecs.generated.cs b/src/Markdig.Tests/Specs/GridTableSpecs.generated.cs index e636d299..0149ac07 100644 --- a/src/Markdig.Tests/Specs/GridTableSpecs.generated.cs +++ b/src/Markdig.Tests/Specs/GridTableSpecs.generated.cs @@ -386,5 +386,34 @@ public void ExtensionsGridTable_Example011() TestParser.TestSpec("+", "
    \n
  • \n
", "gridtables|advanced", context: "Example 11\nSection Extensions / Grid Table\n"); } + + // A table may begin right after a paragraph without an empty line in between: + [Test] + public void ExtensionsGridTable_Example012() + { + // Example 12 + // Section: Extensions / Grid Table + // + // The following Markdown: + // Some + // **text**. + // +---+ + // | A | + // +---+ + // + // Should be rendered as: + //

Some + // text.

+ //
Code block
+ // + // + // + // + // + // + //
A
+ + TestParser.TestSpec("Some\n**text**.\n+---+\n| A |\n+---+", "

Some\ntext.

\n\n\n\n\n\n\n\n
A
", "gridtables|advanced", context: "Example 12\nSection Extensions / Grid Table\n"); + } } } diff --git a/src/Markdig.Tests/Specs/GridTableSpecs.md b/src/Markdig.Tests/Specs/GridTableSpecs.md index de4618cf..ed68767e 100644 --- a/src/Markdig.Tests/Specs/GridTableSpecs.md +++ b/src/Markdig.Tests/Specs/GridTableSpecs.md @@ -285,3 +285,24 @@ An empty `+` on a line should result in a simple empty list output:
  • ```````````````````````````````` + +A table may begin right after a paragraph without an empty line in between: + +```````````````````````````````` example +Some +**text**. ++---+ +| A | ++---+ +. +

    Some +text.

    + ++ + + + + +
    A
    +```````````````````````````````` \ No newline at end of file diff --git a/src/Markdig.Tests/Specs/PipeTableSpecs.generated.cs b/src/Markdig.Tests/Specs/PipeTableSpecs.generated.cs index 108a3c7c..8de5891c 100644 --- a/src/Markdig.Tests/Specs/PipeTableSpecs.generated.cs +++ b/src/Markdig.Tests/Specs/PipeTableSpecs.generated.cs @@ -825,5 +825,38 @@ public void ExtensionsPipeTable_Example025() TestParser.TestSpec("a | b\n-- | - \n0 | 1 | 2", "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
    ab
    012
    ", "pipetables|advanced", context: "Example 25\nSection Extensions / Pipe Table\n"); } + + // A table may begin right after a paragraph without an empty line in between: + [Test] + public void ExtensionsPipeTable_Example026() + { + // Example 26 + // Section: Extensions / Pipe Table + // + // The following Markdown: + // Some + // **text**. + // | A | + // |---| + // | B | + // + // Should be rendered as: + //

    Some + // text.

    + // + // + // + // + // + // + // + // + // + // + // + //
    A
    B
    + + TestParser.TestSpec("Some\n**text**.\n| A |\n|---|\n| B |", "

    Some\ntext.

    \n\n\n\n\n\n\n\n\n\n\n\n
    A
    B
    ", "pipetables|advanced", context: "Example 26\nSection Extensions / Pipe Table\n"); + } } } diff --git a/src/Markdig.Tests/Specs/PipeTableSpecs.md b/src/Markdig.Tests/Specs/PipeTableSpecs.md index c96811c3..8ea5fe99 100644 --- a/src/Markdig.Tests/Specs/PipeTableSpecs.md +++ b/src/Markdig.Tests/Specs/PipeTableSpecs.md @@ -612,4 +612,29 @@ a | b +```````````````````````````````` + +A table may begin right after a paragraph without an empty line in between: + +```````````````````````````````` example +Some +**text**. +| A | +|---| +| B | +. +

    Some +text.

    + + + + + + + + + + + +
    A
    B
    ```````````````````````````````` \ No newline at end of file diff --git a/src/Markdig.Tests/TestPipeTable.cs b/src/Markdig.Tests/TestPipeTable.cs index 06bf3849..6486329e 100644 --- a/src/Markdig.Tests/TestPipeTable.cs +++ b/src/Markdig.Tests/TestPipeTable.cs @@ -14,6 +14,7 @@ public sealed class TestPipeTable [TestCase("| S | \r\n|---|\r\n| G |\r\n\r\n| D | D |\r\n| ---| ---| \r\n| V | V |", 2)] [TestCase("a\r| S | T |\r|---|---|")] [TestCase("a\n| S | T |\r|---|---|")] + [TestCase("a\r\n| S | T |\r|---|---|")] public void TestTableBug(string markdown, int tableCount = 1) { MarkdownDocument document = diff --git a/src/Markdig/Extensions/Tables/PipeTableParser.cs b/src/Markdig/Extensions/Tables/PipeTableParser.cs index 8453d134..6c366981 100644 --- a/src/Markdig/Extensions/Tables/PipeTableParser.cs +++ b/src/Markdig/Extensions/Tables/PipeTableParser.cs @@ -3,7 +3,6 @@ // See the license.txt file in the project root for more information. using System.Diagnostics; - using Markdig.Helpers; using Markdig.Parsers; using Markdig.Parsers.Inlines; @@ -48,7 +47,6 @@ public override bool Match(InlineProcessor processor, ref StringSlice slice) } var c = slice.CurrentChar; - var isNewLineFollowedByPipe = (c == '\n' || c == '\r') && slice.PeekChar() == '|'; // If we have not a delimiter on the first line of a paragraph, don't bother to continue // tracking other delimiters on following lines @@ -66,15 +64,16 @@ public override bool Match(InlineProcessor processor, ref StringSlice slice) // start for a table. Typically, with this, we can have an attributes {...} // starting on the first line of a pipe table, even if the first line // doesn't have a pipe - if (processor.Inline != null && (localLineIndex > 0 || c == '\n' || c == '\r') && !isNewLineFollowedByPipe) + if (processor.Inline != null && (c == '\n' || c == '\r')) { return false; } - if (processor.Inline is null || isNewLineFollowedByPipe) + if (processor.Inline is null) { isFirstLineEmpty = true; } + // Else setup a table processor tableState = new TableState(); processor.ParserStates[Index] = tableState; @@ -88,7 +87,6 @@ public override bool Match(InlineProcessor processor, ref StringSlice slice) } tableState.LineHasPipe = false; lineBreakParser.Match(processor, ref slice); - tableState.LineIndex++; if (!isFirstLineEmpty) { tableState.ColumnAndLineDelimiters.Add(processor.Inline!); @@ -104,13 +102,8 @@ public override bool Match(InlineProcessor processor, ref StringSlice slice) Column = column, LocalLineIndex = localLineIndex }; - var deltaLine = localLineIndex - tableState.LineIndex; - if (deltaLine > 0) - { - tableState.IsInvalidTable = true; - } + tableState.LineHasPipe = true; - tableState.LineIndex = localLineIndex; slice.SkipChar(); // Skip the `|` character tableState.ColumnAndLineDelimiters.Add(processor.Inline); @@ -228,7 +221,6 @@ public bool PostProcess(InlineProcessor state, Inline? root, Inline? lastChild, attributes.CopyTo(table.GetAttributes()); } - state.BlockNew = table; var cells = tableState.Cells; cells.Clear(); @@ -487,6 +479,38 @@ public bool PostProcess(InlineProcessor state, Inline? root, Inline? lastChild, table.NormalizeUsingMaxWidth(); } + if (state.Block is ParagraphBlock { Inline.FirstChild: not null } leadingParagraph) + { + // The table was preceded by a non-empty paragraph, e.g. + // ```md + // Some text + // | Header | + // ``` + // + // Keep the paragraph as-is and insert the table after it. + // Since we've already processed all the inlines in this table block, + // we can't insert it while the parent is still being processed. + // Hook up a callback that inserts the table after we're done with ProcessInlines for the parent block. + + // We've processed inlines in the table, but not the leading paragraph itself yet. + state.PostProcessInlines(0, leadingParagraph.Inline, null, isFinalProcessing: true); + + ContainerBlock parent = leadingParagraph.Parent!; + + ProcessInlineDelegate insertTableDelegate = null!; + insertTableDelegate = (processor, _) => + { + parent.ProcessInlinesEnd -= insertTableDelegate; + parent.Insert(parent.IndexOf(leadingParagraph) + 1, table); + }; + parent.ProcessInlinesEnd += insertTableDelegate; + } + else + { + // Nothing interesting in the existing block, just replace it. + state.BlockNew = table; + } + // We don't want to continue procesing delimiters, as we are already processing them here return false; } @@ -682,8 +706,6 @@ private sealed class TableState public bool LineHasPipe { get; set; } - public int LineIndex { get; set; } - public List ColumnAndLineDelimiters { get; } = []; public List Cells { get; } = []; From bcbd8e47ac388acdac0d3d98b8850eac460777cc Mon Sep 17 00:00:00 2001 From: Miha Zupan Date: Tue, 21 Oct 2025 08:37:02 +0200 Subject: [PATCH 84/88] Lazily allocate ProcessInlinesBegin/End delegates on Blocks (#906) --- .../Abbreviations/AbbreviationParser.cs | 2 -- .../AutoIdentifierExtension.cs | 1 - .../Extensions/Footnotes/FootnoteParser.cs | 3 -- .../SmartyPants/SmartyPantsInlineParser.cs | 2 -- .../Extensions/Tables/PipeTableParser.cs | 5 +-- src/Markdig/Renderers/RendererBase.cs | 4 +-- src/Markdig/Syntax/Block.cs | 33 ++++++++++++++++--- 7 files changed, 32 insertions(+), 18 deletions(-) diff --git a/src/Markdig/Extensions/Abbreviations/AbbreviationParser.cs b/src/Markdig/Extensions/Abbreviations/AbbreviationParser.cs index 5af5a036..df8f95e3 100644 --- a/src/Markdig/Extensions/Abbreviations/AbbreviationParser.cs +++ b/src/Markdig/Extensions/Abbreviations/AbbreviationParser.cs @@ -73,8 +73,6 @@ public override BlockState TryOpen(BlockProcessor processor) private void DocumentOnProcessInlinesBegin(InlineProcessor inlineProcessor, Inline? inline) { - inlineProcessor.Document.ProcessInlinesBegin -= DocumentOnProcessInlinesBegin; - var abbreviations = inlineProcessor.Document.GetAbbreviations(); // Should not happen, but another extension could decide to remove them, so... if (abbreviations is null) diff --git a/src/Markdig/Extensions/AutoIdentifiers/AutoIdentifierExtension.cs b/src/Markdig/Extensions/AutoIdentifiers/AutoIdentifierExtension.cs index f7877b4b..f2c1e220 100644 --- a/src/Markdig/Extensions/AutoIdentifiers/AutoIdentifierExtension.cs +++ b/src/Markdig/Extensions/AutoIdentifiers/AutoIdentifierExtension.cs @@ -101,7 +101,6 @@ private void HeadingBlockParser_Closed(BlockProcessor processor, Block block) private void DocumentOnProcessInlinesBegin(InlineProcessor processor, Inline? inline) { var doc = processor.Document; - doc.ProcessInlinesBegin -= _processInlinesBegin; var dictionary = (Dictionary)doc.GetData(this)!; foreach (var keyPair in dictionary) { diff --git a/src/Markdig/Extensions/Footnotes/FootnoteParser.cs b/src/Markdig/Extensions/Footnotes/FootnoteParser.cs index 52c8c2c7..c74fbeb7 100644 --- a/src/Markdig/Extensions/Footnotes/FootnoteParser.cs +++ b/src/Markdig/Extensions/Footnotes/FootnoteParser.cs @@ -134,9 +134,6 @@ public override BlockState TryContinue(BlockProcessor processor, Block block) /// The inline. private void Document_ProcessInlinesEnd(InlineProcessor state, Inline? inline) { - // Unregister - state.Document.ProcessInlinesEnd -= Document_ProcessInlinesEnd; - var footnotes = (FootnoteGroup)state.Document.GetData(DocumentKey)!; // Remove the footnotes from the document and readd them at the end state.Document.Remove(footnotes); diff --git a/src/Markdig/Extensions/SmartyPants/SmartyPantsInlineParser.cs b/src/Markdig/Extensions/SmartyPants/SmartyPantsInlineParser.cs index 97a71668..c701ff21 100644 --- a/src/Markdig/Extensions/SmartyPants/SmartyPantsInlineParser.cs +++ b/src/Markdig/Extensions/SmartyPants/SmartyPantsInlineParser.cs @@ -204,8 +204,6 @@ public Opener(int type, int index) private void BlockOnProcessInlinesEnd(InlineProcessor processor, Inline? inline) { - processor.Block!.ProcessInlinesEnd -= BlockOnProcessInlinesEnd; - var pants = (ListSmartyPants) processor.ParserStates[Index]; var openers = new Stack(4); diff --git a/src/Markdig/Extensions/Tables/PipeTableParser.cs b/src/Markdig/Extensions/Tables/PipeTableParser.cs index 6c366981..601241fd 100644 --- a/src/Markdig/Extensions/Tables/PipeTableParser.cs +++ b/src/Markdig/Extensions/Tables/PipeTableParser.cs @@ -497,13 +497,10 @@ public bool PostProcess(InlineProcessor state, Inline? root, Inline? lastChild, ContainerBlock parent = leadingParagraph.Parent!; - ProcessInlineDelegate insertTableDelegate = null!; - insertTableDelegate = (processor, _) => + parent.ProcessInlinesEnd += (_, _) => { - parent.ProcessInlinesEnd -= insertTableDelegate; parent.Insert(parent.IndexOf(leadingParagraph) + 1, table); }; - parent.ProcessInlinesEnd += insertTableDelegate; } else { diff --git a/src/Markdig/Renderers/RendererBase.cs b/src/Markdig/Renderers/RendererBase.cs index fde4b5d3..afed6fc5 100644 --- a/src/Markdig/Renderers/RendererBase.cs +++ b/src/Markdig/Renderers/RendererBase.cs @@ -85,12 +85,12 @@ protected RendererBase() public bool IsLastInContainer { get; private set; } /// - /// Occurs when before writing an object. + /// Occurs before writing an object. /// public event Action? ObjectWriteBefore; /// - /// Occurs when after writing an object. + /// Occurs after writing an object. /// public event Action? ObjectWriteAfter; diff --git a/src/Markdig/Syntax/Block.cs b/src/Markdig/Syntax/Block.cs index 89bea2be..5c5ae66a 100644 --- a/src/Markdig/Syntax/Block.cs +++ b/src/Markdig/Syntax/Block.cs @@ -92,12 +92,20 @@ protected Block(BlockParser? parser) /// /// Occurs when the process of inlines begin. /// - public event ProcessInlineDelegate? ProcessInlinesBegin; + public event ProcessInlineDelegate? ProcessInlinesBegin + { + add => Trivia.ProcessInlinesBegin += value; + remove => _trivia?.ProcessInlinesBegin -= value; + } /// /// Occurs when the process of inlines ends for this instance. /// - public event ProcessInlineDelegate? ProcessInlinesEnd; + public event ProcessInlineDelegate? ProcessInlinesEnd + { + add => Trivia.ProcessInlinesEnd += value; + remove => _trivia?.ProcessInlinesEnd -= value; + } /// /// Called when the process of inlines begin. @@ -105,7 +113,13 @@ protected Block(BlockParser? parser) /// The inline parser state. internal void OnProcessInlinesBegin(InlineProcessor state) { - ProcessInlinesBegin?.Invoke(state, null); + if (_trivia is BlockTriviaProperties trivia) + { + trivia.ProcessInlinesBegin?.Invoke(state, null); + + // Not exactly standard 'event' behavior, but these aren't expected to be called more than once. + _trivia.ProcessInlinesBegin = null; + } } /// @@ -114,7 +128,13 @@ internal void OnProcessInlinesBegin(InlineProcessor state) /// The inline parser state. internal void OnProcessInlinesEnd(InlineProcessor state) { - ProcessInlinesEnd?.Invoke(state, null); + if (_trivia is BlockTriviaProperties trivia) + { + trivia.ProcessInlinesEnd?.Invoke(state, null); + + // Not exactly standard 'event' behavior, but these aren't expected to be called more than once. + _trivia.ProcessInlinesEnd = null; + } } public void UpdateSpanEnd(int spanEnd) @@ -156,6 +176,11 @@ private sealed class BlockTriviaProperties // Used by derived types to store their own TriviaProperties public object? DerivedTriviaSlot; + // These callbacks are set on a tiny subset of blocks (usually only the main MarkdownDocument), + // so we store them in a lazily-allocated container to save memory for the majority of blocks. + public ProcessInlineDelegate? ProcessInlinesBegin; + public ProcessInlineDelegate? ProcessInlinesEnd; + public StringSlice TriviaBefore; public StringSlice TriviaAfter; public List? LinesBefore; From 8c01cf054971a1ec4cd663edaca6e2d035236133 Mon Sep 17 00:00:00 2001 From: Miha Zupan Date: Tue, 21 Oct 2025 08:37:43 +0200 Subject: [PATCH 85/88] Add another test for pipe tables (#907) --- .../Specs/PipeTableSpecs.generated.cs | 152 ++++++++++++++++++ src/Markdig.Tests/Specs/PipeTableSpecs.md | 144 +++++++++++++++++ 2 files changed, 296 insertions(+) diff --git a/src/Markdig.Tests/Specs/PipeTableSpecs.generated.cs b/src/Markdig.Tests/Specs/PipeTableSpecs.generated.cs index 8de5891c..c05b3d78 100644 --- a/src/Markdig.Tests/Specs/PipeTableSpecs.generated.cs +++ b/src/Markdig.Tests/Specs/PipeTableSpecs.generated.cs @@ -858,5 +858,157 @@ public void ExtensionsPipeTable_Example026() TestParser.TestSpec("Some\n**text**.\n| A |\n|---|\n| B |", "

    Some\ntext.

    \n\n\n\n\n\n\n\n\n\n\n\n
    A
    B
    ", "pipetables|advanced", context: "Example 26\nSection Extensions / Pipe Table\n"); } + + // Tables can be nested inside other blocks, like lists: + [Test] + public void ExtensionsPipeTable_Example027() + { + // Example 27 + // Section: Extensions / Pipe Table + // + // The following Markdown: + // Bullet list + // * Table 1 + // + // | Header 1 | Header 2 | + // |----------------|----------------| + // | Row 1 Column 1 | Row 1 Column 2 | + // + // * Table 2 + // | Header 1 | Header 2 | + // |----------------|----------------| + // | Row 1 Column 1 | Row 1 Column 2 | + // + // * Table 3 + // Lorem ipsum ... + // Lorem ipsum ... + // | Header 1 | Header 2 | + // |----------------|----------------| + // | Row 1 Column 1 | Row 1 Column 2 | + // + // + // Ordered list + // 1. Table 1 + // + // | Header 1 | Header 2 | + // |----------------|----------------| + // | Row 1 Column 1 | Row 1 Column 2 | + // + // 2. Table 2 + // | Header 1 | Header 2 | + // |----------------|----------------| + // | Row 1 Column 1 | Row 1 Column 2 | + // + // 3. Table 3 + // Lorem ipsum ... + // Lorem ipsum ... + // | Header 1 | Header 2 | + // |----------------|----------------| + // | Row 1 Column 1 | Row 1 Column 2 | + // + // Should be rendered as: + //

    Bullet list

    + //
      + //
    • Table 1

      + // + // + // + // + // + // + // + // + // + // + // + // + // + //
      Header 1Header 2
      Row 1 Column 1Row 1 Column 2
    • + //
    • Table 2

      + // + // + // + // + // + // + // + // + // + // + // + // + // + //
      Header 1Header 2
      Row 1 Column 1Row 1 Column 2
    • + //
    • Table 3 + // Lorem ipsum ... + // Lorem ipsum ...

      + // + // + // + // + // + // + // + // + // + // + // + // + // + //
      Header 1Header 2
      Row 1 Column 1Row 1 Column 2
    • + //
    + //

    Ordered list

    + //
      + //
    1. Table 1

      + // + // + // + // + // + // + // + // + // + // + // + // + // + //
      Header 1Header 2
      Row 1 Column 1Row 1 Column 2
    2. + //
    3. Table 2

      + // + // + // + // + // + // + // + // + // + // + // + // + // + //
      Header 1Header 2
      Row 1 Column 1Row 1 Column 2
    4. + //
    5. Table 3 + // Lorem ipsum ... + // Lorem ipsum ...

      + // + // + // + // + // + // + // + // + // + // + // + // + // + //
      Header 1Header 2
      Row 1 Column 1Row 1 Column 2
    6. + //
    + + TestParser.TestSpec("Bullet list\n* Table 1\n\n | Header 1 | Header 2 |\n |----------------|----------------|\n | Row 1 Column 1 | Row 1 Column 2 |\n\n* Table 2\n | Header 1 | Header 2 |\n |----------------|----------------|\n | Row 1 Column 1 | Row 1 Column 2 |\n\n* Table 3\n Lorem ipsum ...\n Lorem ipsum ...\n | Header 1 | Header 2 |\n |----------------|----------------|\n | Row 1 Column 1 | Row 1 Column 2 |\n\n\nOrdered list\n1. Table 1\n\n | Header 1 | Header 2 |\n |----------------|----------------|\n | Row 1 Column 1 | Row 1 Column 2 |\n\n2. Table 2\n | Header 1 | Header 2 |\n |----------------|----------------|\n | Row 1 Column 1 | Row 1 Column 2 |\n\n3. Table 3\n Lorem ipsum ...\n Lorem ipsum ...\n | Header 1 | Header 2 |\n |----------------|----------------|\n | Row 1 Column 1 | Row 1 Column 2 |", "

    Bullet list

    \n
      \n
    • Table 1

      \n\n\n\n\n\n\n\n\n\n\n\n\n\n
      Header 1Header 2
      Row 1 Column 1Row 1 Column 2
    • \n
    • Table 2

      \n\n\n\n\n\n\n\n\n\n\n\n\n\n
      Header 1Header 2
      Row 1 Column 1Row 1 Column 2
    • \n
    • Table 3\nLorem ipsum ...\nLorem ipsum ...

      \n\n\n\n\n\n\n\n\n\n\n\n\n\n
      Header 1Header 2
      Row 1 Column 1Row 1 Column 2
    • \n
    \n

    Ordered list

    \n
      \n
    1. Table 1

      \n\n\n\n\n\n\n\n\n\n\n\n\n\n
      Header 1Header 2
      Row 1 Column 1Row 1 Column 2
    2. \n
    3. Table 2

      \n\n\n\n\n\n\n\n\n\n\n\n\n\n
      Header 1Header 2
      Row 1 Column 1Row 1 Column 2
    4. \n
    5. Table 3\nLorem ipsum ...\nLorem ipsum ...

      \n\n\n\n\n\n\n\n\n\n\n\n\n\n
      Header 1Header 2
      Row 1 Column 1Row 1 Column 2
    6. \n
    ", "pipetables|advanced", context: "Example 27\nSection Extensions / Pipe Table\n"); + } } } diff --git a/src/Markdig.Tests/Specs/PipeTableSpecs.md b/src/Markdig.Tests/Specs/PipeTableSpecs.md index 8ea5fe99..195f6fe7 100644 --- a/src/Markdig.Tests/Specs/PipeTableSpecs.md +++ b/src/Markdig.Tests/Specs/PipeTableSpecs.md @@ -637,4 +637,148 @@ Some +```````````````````````````````` + +Tables can be nested inside other blocks, like lists: + +```````````````````````````````` example +Bullet list +* Table 1 + + | Header 1 | Header 2 | + |----------------|----------------| + | Row 1 Column 1 | Row 1 Column 2 | + +* Table 2 + | Header 1 | Header 2 | + |----------------|----------------| + | Row 1 Column 1 | Row 1 Column 2 | + +* Table 3 + Lorem ipsum ... + Lorem ipsum ... + | Header 1 | Header 2 | + |----------------|----------------| + | Row 1 Column 1 | Row 1 Column 2 | + + +Ordered list +1. Table 1 + + | Header 1 | Header 2 | + |----------------|----------------| + | Row 1 Column 1 | Row 1 Column 2 | + +2. Table 2 + | Header 1 | Header 2 | + |----------------|----------------| + | Row 1 Column 1 | Row 1 Column 2 | + +3. Table 3 + Lorem ipsum ... + Lorem ipsum ... + | Header 1 | Header 2 | + |----------------|----------------| + | Row 1 Column 1 | Row 1 Column 2 | +. +

    Bullet list

    +
      +
    • Table 1

      + + + + + + + + + + + + + +
      Header 1Header 2
      Row 1 Column 1Row 1 Column 2
    • +
    • Table 2

      + + + + + + + + + + + + + +
      Header 1Header 2
      Row 1 Column 1Row 1 Column 2
    • +
    • Table 3 +Lorem ipsum ... +Lorem ipsum ...

      + + + + + + + + + + + + + +
      Header 1Header 2
      Row 1 Column 1Row 1 Column 2
    • +
    +

    Ordered list

    +
      +
    1. Table 1

      + + + + + + + + + + + + + +
      Header 1Header 2
      Row 1 Column 1Row 1 Column 2
    2. +
    3. Table 2

      + + + + + + + + + + + + + +
      Header 1Header 2
      Row 1 Column 1Row 1 Column 2
    4. +
    5. Table 3 +Lorem ipsum ... +Lorem ipsum ...

      + + + + + + + + + + + + + +
      Header 1Header 2
      Row 1 Column 1Row 1 Column 2
    6. +
    ```````````````````````````````` \ No newline at end of file From 12590e5fbe1730984e0783452666a67df8e0b806 Mon Sep 17 00:00:00 2001 From: mos379 <45105519+mos379@users.noreply.github.com> Date: Mon, 10 Nov 2025 22:01:35 +0100 Subject: [PATCH 86/88] feat(link-helper): improve ASCII normalization handling (#911) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(link-helper): improve ASCII normalization handling Enhanced the `Urilize` method to better handle ASCII normalization and special characters. Added support for decomposing characters when `allowOnlyAscii` is true and skipping diacritical marks. Introduced handling for special German, Scandinavian, and Icelandic characters via new helper methods: `IsSpecialScandinavianOrGermanChar` and `NormalizeScandinavianOrGermanChar`. Reorganized `using` directives for better clarity. Updated the processing loop in `Urilize` to handle normalized spans and ASCII equivalents more effectively. These changes improve link generation compatibility across various languages. * Add tests for Scandinavian and German character normalization Added tests for NormalizeScandinavianOrGermanChar method to validate character normalization for various special characters in both ASCII and non-ASCII contexts. * test(link-helper): update ASCII transliteration tests Updated test cases in `TestUrilizeOnlyAscii_Simple` to reflect changes in `LinkHelper.Urilize` behavior. Non-ASCII characters like `æ` and `ø` are now transliterated to their ASCII equivalents (`ae` and `oe`) instead of being removed. --- src/Markdig.Tests/TestLinkHelper.cs | 85 +++++++++++++++++++++++++--- src/Markdig/Helpers/LinkHelper.cs | 88 ++++++++++++++++++++++++++--- 2 files changed, 158 insertions(+), 15 deletions(-) diff --git a/src/Markdig.Tests/TestLinkHelper.cs b/src/Markdig.Tests/TestLinkHelper.cs index b9a15292..ecfa5210 100644 --- a/src/Markdig.Tests/TestLinkHelper.cs +++ b/src/Markdig.Tests/TestLinkHelper.cs @@ -327,8 +327,8 @@ public void TestUrilizeOnlyAscii_Simple(string input, string expectedResult) Assert.AreEqual(expectedResult, LinkHelper.Urilize(input, true)); } - [TestCase("bær", "br")] - [TestCase("bør", "br")] + [TestCase("bær", "baer")] + [TestCase("bør", "boer")] [TestCase("bΘr", "br")] [TestCase("四五", "")] public void TestUrilizeOnlyAscii_NonAscii(string input, string expectedResult) @@ -343,6 +343,75 @@ public void TestUrilizeOnlyAscii_Normalization(string input, string expectedResu Assert.AreEqual(expectedResult, LinkHelper.Urilize(input, true)); } + // Tests for NormalizeScandinavianOrGermanChar method mappings + // These special characters are always normalized (both allowOnlyAscii=true and false) + // + // Note: When allowOnlyAscii=true, NFD (Canonical Decomposition) is applied first: + // - German umlauts ä,ö,ü decompose to base letter + combining mark (ü -> u + ¨) + // The combining mark is then stripped, leaving just the base letter (ü -> u) + // - å decomposes similarly (å -> a + ˚ -> a) + // - But ø, æ, ß, þ, ð do NOT decompose, so they use NormalizeScandinavianOrGermanChar + // + // When allowOnlyAscii=false, NormalizeScandinavianOrGermanChar is used for ALL special chars + + // German ß (Eszett/sharp s) - does NOT decompose with NFD + [TestCase("Straße", "strasse")] // ß -> ss (both allowOnlyAscii=true and false) + + // Scandinavian æ, ø - do NOT decompose with NFD + [TestCase("æble", "aeble")] // æ -> ae (both modes) + [TestCase("Ærø", "aeroe")] // Æ -> Ae, ø -> oe (both modes, then lowercase) + [TestCase("København", "koebenhavn")] // ø -> oe (both modes) + [TestCase("Øresund", "oeresund")] // Ø -> Oe (both modes, then lowercase) + + // Icelandic þ, ð - do NOT decompose with NFD + [TestCase("þing", "thing")] // þ (thorn) -> th (both modes) + [TestCase("bað", "bad")] // ð (eth) -> d (both modes) + + // Mixed special characters (only chars that behave same in both modes) + [TestCase("øst-æble", "oest-aeble")] // ø->oe, æ->ae (both modes) + public void TestUrilizeScandinavianGermanChars(string input, string expectedResult) + { + // These transformations apply regardless of allowOnlyAscii flag + Assert.AreEqual(expectedResult, LinkHelper.Urilize(input, true)); + Assert.AreEqual(expectedResult, LinkHelper.Urilize(input, false)); + } + + // Tests specific to allowOnlyAscii=true behavior + // German umlauts (ä, ö, ü) and å decompose with NFD, so they become base letter only + [TestCase("schön", "schon")] // ö decomposes to o (NFD strips combining mark) + [TestCase("Mädchen", "madchen")] // ä decomposes to a + [TestCase("Übung", "ubung")] // Ü decomposes to U (then lowercase to u) + [TestCase("Düsseldorf", "dusseldorf")] // ü decomposes to u + [TestCase("Käse", "kase")] // ä decomposes to a + [TestCase("gå", "ga")] // å decomposes to a + [TestCase("Ålesund", "alesund")] // Å decomposes to A (then lowercase) + [TestCase("grüßen", "grussen")] // ü decomposes to u, ß -> ss + [TestCase("Þór", "thor")] // Þ -> Th, ó decomposes to o (then lowercase) + [TestCase("Íslandsbanki", "islandsbanki")] // Í decomposes to I (then lowercase) + public void TestUrilizeOnlyAscii_GermanUmlautsDecompose(string input, string expectedResult) + { + // With allowOnlyAscii=true, these characters decompose via NFD and lose their diacritics + Assert.AreEqual(expectedResult, LinkHelper.Urilize(input, true)); + } + + // Tests specific to allowOnlyAscii=false behavior + // All special chars use NormalizeScandinavianOrGermanChar (including ä, ö, ü, å) + [TestCase("schön", "schoen")] // ö -> oe (NormalizeScandinavianOrGermanChar) + [TestCase("Mädchen", "maedchen")] // ä -> ae + [TestCase("Übung", "uebung")] // Ü -> Ue (then lowercase) + [TestCase("Düsseldorf", "duesseldorf")] // ü -> ue + [TestCase("Käse", "kaese")] // ä -> ae + [TestCase("gå", "gaa")] // å -> aa + [TestCase("Ålesund", "aalesund")] // Å -> Aa (then lowercase) + [TestCase("grüßen", "gruessen")] // ü -> ue, ß -> ss + [TestCase("Þór", "thór")] // Þ -> Th (then lowercase 'th'), ó is kept as-is + [TestCase("Íslandsbanki", "íslandsbanki")] // í is kept as-is when allowOnlyAscii=false + public void TestUrilizeNonAscii_GermanUmlautsExpanded(string input, string expectedResult) + { + // With allowOnlyAscii=false, these characters use NormalizeScandinavianOrGermanChar + Assert.AreEqual(expectedResult, LinkHelper.Urilize(input, false)); + } + [TestCase("123", "")] [TestCase("1,-b", "b")] [TestCase("b1,-", "b1")] // Not Pandoc equivalent: b1- @@ -360,11 +429,11 @@ public void TestUrilizeNonAscii_NonAsciiNumeric(string input, string expectedRes Assert.AreEqual(expectedResult, LinkHelper.Urilize(input, false)); } - [TestCase("bær", "bær")] - [TestCase("æ5el", "æ5el")] - [TestCase("-æ5el", "æ5el")] - [TestCase("-frø-", "frø")] - [TestCase("-fr-ø", "fr-ø")] + [TestCase("bær", "baer")] + [TestCase("æ5el", "ae5el")] + [TestCase("-æ5el", "ae5el")] + [TestCase("-frø-", "froe")] + [TestCase("-fr-ø", "fr-oe")] public void TestUrilizeNonAscii_Simple(string input, string expectedResult) { Assert.AreEqual(expectedResult, LinkHelper.Urilize(input, false)); @@ -393,4 +462,4 @@ public void TestUnicodeInDomainNameOfLinkReferenceDefinition() { TestParser.TestSpec("[Foo]\n\n[Foo]: http://ünicode.com", "

    Foo

    "); } -} \ No newline at end of file +} diff --git a/src/Markdig/Helpers/LinkHelper.cs b/src/Markdig/Helpers/LinkHelper.cs index c5004594..fd7e6ba2 100644 --- a/src/Markdig/Helpers/LinkHelper.cs +++ b/src/Markdig/Helpers/LinkHelper.cs @@ -2,11 +2,13 @@ // This file is licensed under the BSD-Clause 2 license. // See the license.txt file in the project root for more information. +using Markdig.Syntax; using System.Buffers; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.Globalization; using System.Runtime.CompilerServices; -using Markdig.Syntax; +using System.Text; namespace Markdig.Helpers; @@ -30,11 +32,38 @@ public static string Urilize(ReadOnlySpan headingText, bool allowOnlyAscii var headingBuffer = new ValueStringBuilder(stackalloc char[ValueStringBuilder.StackallocThreshold]); bool hasLetter = keepOpeningDigits && headingText.Length > 0 && char.IsLetterOrDigit(headingText[0]); bool previousIsSpace = false; - for (int i = 0; i < headingText.Length; i++) + + // First normalize the string to decompose characters if allowOnlyAscii is true + string normalizedString = string.Empty; + if (allowOnlyAscii) { - var c = headingText[i]; - var normalized = allowOnlyAscii ? CharNormalizer.ConvertToAscii(c) : null; - for (int j = 0; j < (normalized?.Length ?? 1); j++) + normalizedString = headingText.ToString().Normalize(NormalizationForm.FormD); + } + + var textToProcess = string.IsNullOrEmpty(normalizedString) ? headingText : normalizedString.AsSpan(); + + for (int i = 0; i < textToProcess.Length; i++) + { + var c = textToProcess[i]; + + // Skip combining diacritical marks when normalized + if (allowOnlyAscii && CharUnicodeInfo.GetUnicodeCategory(c) == UnicodeCategory.NonSpacingMark) + { + continue; + } + + // Handle German umlauts and Norwegian/Danish characters explicitly (they don't decompose properly) + ReadOnlySpan normalized; + if (IsSpecialScandinavianOrGermanChar(c)) + { + normalized = NormalizeScandinavianOrGermanChar(c); + } + else + { + normalized = allowOnlyAscii ? CharNormalizer.ConvertToAscii(c) : null; + } + + for (int j = 0; j < (normalized.Length < 1 ? 1 : normalized.Length); j++) { if (normalized != null) { @@ -101,6 +130,50 @@ public static string Urilize(ReadOnlySpan headingText, bool allowOnlyAscii return headingBuffer.ToString(); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsSpecialScandinavianOrGermanChar(char c) + { + // German umlauts and ß + // Norwegian/Danish/Swedish æ, ø, å + // Icelandic þ (thorn), ð (eth) + return c == 'ä' || c == 'ö' || c == 'ü' || + c == 'Ä' || c == 'Ö' || c == 'Ü' || + c == 'ß' || + c == 'æ' || c == 'ø' || c == 'å' || + c == 'Æ' || c == 'Ø' || c == 'Å' || + c == 'þ' || c == 'ð' || + c == 'Þ' || c == 'Ð'; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static ReadOnlySpan NormalizeScandinavianOrGermanChar(char c) + { + return c switch + { + // German + 'ä' => "ae", + 'ö' => "oe", + 'ü' => "ue", + 'Ä' => "Ae", + 'Ö' => "Oe", + 'Ü' => "Ue", + 'ß' => "ss", + // Norwegian/Danish/Swedish + 'æ' => "ae", + 'ø' => "oe", + 'å' => "aa", + 'Æ' => "Ae", + 'Ø' => "Oe", + 'Å' => "Aa", + // Icelandic + 'þ' => "th", + 'Þ' => "Th", + 'ð' => "d", + 'Ð' => "D", + _ => ReadOnlySpan.Empty + }; + } + public static string UrilizeAsGfm(string headingText) { return UrilizeAsGfm(headingText.AsSpan()); @@ -218,7 +291,8 @@ public static bool TryParseAutolink(ref StringSlice text, [NotNullWhen(true)] ou } state = 1; break; - } else if (c == '@') + } + else if (c == '@') { if (state > 0) { @@ -234,7 +308,7 @@ public static bool TryParseAutolink(ref StringSlice text, [NotNullWhen(true)] ou } // append ':' or '@' - builder.Append(c); + builder.Append(c); if (state < 0) { From fb698598e4ce273dbdba7ae371e34c93b0091356 Mon Sep 17 00:00:00 2001 From: Alexandre Mutel Date: Mon, 17 Nov 2025 08:19:42 +0100 Subject: [PATCH 87/88] Use central package management --- src/Directory.Packages.props | 23 +++++++++++++++++++ .../Markdig.Benchmarks.csproj | 12 +++++----- src/Markdig.Fuzzing/Markdig.Fuzzing.csproj | 4 ++-- src/Markdig.Tests/Markdig.Tests.csproj | 12 +++++----- src/Markdig.WebApp/Markdig.WebApp.csproj | 2 +- src/Markdig/Markdig.targets | 7 +++--- src/markdig.slnx | 1 + 7 files changed, 42 insertions(+), 19 deletions(-) create mode 100644 src/Directory.Packages.props diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props new file mode 100644 index 00000000..00b22dda --- /dev/null +++ b/src/Directory.Packages.props @@ -0,0 +1,23 @@ + + + true + false + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Markdig.Benchmarks/Markdig.Benchmarks.csproj b/src/Markdig.Benchmarks/Markdig.Benchmarks.csproj index fb4c989a..b2c0c406 100644 --- a/src/Markdig.Benchmarks/Markdig.Benchmarks.csproj +++ b/src/Markdig.Benchmarks/Markdig.Benchmarks.csproj @@ -19,12 +19,12 @@ - - - - - - + + + + + + diff --git a/src/Markdig.Fuzzing/Markdig.Fuzzing.csproj b/src/Markdig.Fuzzing/Markdig.Fuzzing.csproj index b9774246..26942fbf 100644 --- a/src/Markdig.Fuzzing/Markdig.Fuzzing.csproj +++ b/src/Markdig.Fuzzing/Markdig.Fuzzing.csproj @@ -1,4 +1,4 @@ - + Exe @@ -9,7 +9,7 @@ - + diff --git a/src/Markdig.Tests/Markdig.Tests.csproj b/src/Markdig.Tests/Markdig.Tests.csproj index 43afb2d4..ed4bbc60 100644 --- a/src/Markdig.Tests/Markdig.Tests.csproj +++ b/src/Markdig.Tests/Markdig.Tests.csproj @@ -1,7 +1,7 @@  - net6.0;net8.0;net9.0 + net8.0;net9.0 Exe false enable @@ -13,9 +13,9 @@ - - - + + + @@ -36,10 +36,10 @@ - + - + diff --git a/src/Markdig.WebApp/Markdig.WebApp.csproj b/src/Markdig.WebApp/Markdig.WebApp.csproj index 59519368..587a7564 100644 --- a/src/Markdig.WebApp/Markdig.WebApp.csproj +++ b/src/Markdig.WebApp/Markdig.WebApp.csproj @@ -14,7 +14,7 @@ - + diff --git a/src/Markdig/Markdig.targets b/src/Markdig/Markdig.targets index a0c27b17..904fa0d6 100644 --- a/src/Markdig/Markdig.targets +++ b/src/Markdig/Markdig.targets @@ -25,17 +25,16 @@ - + - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - diff --git a/src/markdig.slnx b/src/markdig.slnx index a6f41692..633ca5d2 100644 --- a/src/markdig.slnx +++ b/src/markdig.slnx @@ -7,6 +7,7 @@ + From cd7b9ca0ef66cb582232cbceaefbfe4195cf575b Mon Sep 17 00:00:00 2001 From: Miha Zupan Date: Mon, 17 Nov 2025 18:46:26 +0100 Subject: [PATCH 88/88] Test netstandard (#915) * Add GH Action to test netstandard 2.0 and 2.1 * Account for TFM changes in tests project --- .github/workflows/test-netstandard.yml | 44 ++++++++++++++++++++++++++ src/markdig.slnx | 5 ++- 2 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/test-netstandard.yml diff --git a/.github/workflows/test-netstandard.yml b/.github/workflows/test-netstandard.yml new file mode 100644 index 00000000..f9bb91b7 --- /dev/null +++ b/.github/workflows/test-netstandard.yml @@ -0,0 +1,44 @@ +name: Test netstandard + +on: pull_request + +jobs: + test-netstandard: + runs-on: ubuntu-latest + + strategy: + matrix: + netstandard-version: ['netstandard2.0', 'netstandard2.1'] + + steps: + - uses: actions/checkout@v4 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: | + 8.0.x + 9.0.x + + - name: Patch build to test ${{ matrix.netstandard-version }} + run: | + cd src + sed -i 's/.*<\/TargetFrameworks>/${{ matrix.netstandard-version }}<\/TargetFrameworks>/' Markdig/Markdig.targets + sed -i 's/.*<\/TargetFrameworks>/net8.0;net9.0<\/TargetFrameworks>/' Markdig.Tests/Markdig.Tests.csproj + echo "Markdig.targets TFMs:" + grep "TargetFrameworks" Markdig/Markdig.targets + echo "Markdig.Tests.csproj TFMs:" + grep "TargetFrameworks" Markdig.Tests/Markdig.Tests.csproj + + - name: Restore dependencies + run: dotnet restore src/Markdig.Tests/Markdig.Tests.csproj + + - name: Test Debug + run: | + dotnet build src/Markdig.Tests/Markdig.Tests.csproj -c Debug --no-restore + dotnet test src/Markdig.Tests/Markdig.Tests.csproj -c Debug --no-build + + - name: Test Release + run: | + dotnet build src/Markdig.Tests/Markdig.Tests.csproj -c Release --no-restore + dotnet test src/Markdig.Tests/Markdig.Tests.csproj -c Release --no-build diff --git a/src/markdig.slnx b/src/markdig.slnx index 633ca5d2..fae87cce 100644 --- a/src/markdig.slnx +++ b/src/markdig.slnx @@ -2,7 +2,6 @@ - @@ -10,6 +9,10 @@ + + + +