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("Code block")); + } + + [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;