Compare commits

...

41 Commits

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

* Test added.
2024-11-24 11:51:56 +01:00
Alexandre Mutel
310a55c724 Update readme.md 2024-10-30 19:08:35 +01:00
22 changed files with 306 additions and 161 deletions

View File

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

View File

@@ -1,4 +1,4 @@
# Markdig [![Build Status](https://github.com/lunet-io/markdig/workflows/ci/badge.svg?branch=master)](https://github.com/lunet-io/markdig/actions) [![Coverage Status](https://coveralls.io/repos/github/xoofx/markdig/badge.svg?branch=master)](https://coveralls.io/github/xoofx/markdig?branch=master) [![NuGet](https://img.shields.io/nuget/v/Markdig.svg)](https://www.nuget.org/packages/Markdig/) [![Donate](https://www.paypalobjects.com/en_US/i/btn/btn_donate_SM.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=FRGHXBTP442JL)
# Markdig [![ci](https://github.com/xoofx/markdig/actions/workflows/ci.yml/badge.svg)](https://github.com/xoofx/markdig/actions/workflows/ci.yml) [![Coverage Status](https://coveralls.io/repos/github/xoofx/markdig/badge.svg?branch=master)](https://coveralls.io/github/xoofx/markdig?branch=master) [![NuGet](https://img.shields.io/nuget/v/Markdig.svg)](https://www.nuget.org/packages/Markdig/) [![Donate](https://www.paypalobjects.com/en_US/i/btn/btn_donate_SM.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=FRGHXBTP442JL)
<img align="right" width="160px" height="160px" src="img/markdig.png">
@@ -144,12 +144,12 @@ AMD Ryzen 9 5950X, 1 CPU, 32 logical and 16 physical cores
- Markdig is roughly **x100 times faster than MarkdownSharp**
- **20% faster than the reference cmark C implementation**
## Sponsors
## Donate
Supports this project with a monthly donation and help me continue improving it. \[[Become a sponsor](https://github.com/sponsors/xoofx)\]
If you are using this library and find it useful for your project, please consider a donation for it!
[![Donate](https://www.paypalobjects.com/en_US/i/btn/btn_donate_SM.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=FRGHXBTP442JL)
[<img src="https://github.com/lilith.png?size=200" width="64px;" style="border-radius: 50%" alt="lilith"/>](https://github.com/lilith) Lilith River, author of [Imageflow Server, an easy on-demand
image editing, optimization, and delivery server](https://github.com/imazen/imageflow-server)
## Credits

View File

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

View File

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

View File

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

View File

@@ -46,6 +46,14 @@ public class TestPlayParser
Assert.AreEqual("/yoyo", link?.Url);
}
[Test]
public void TestLinkWithMultipleBackslashesInTitle()
{
var doc = Markdown.Parse(@"[link](/uri '\\\\127.0.0.1')");
var link = doc.Descendants<LinkInline>().FirstOrDefault();
Assert.AreEqual(@"\\127.0.0.1", link?.Title);
}
[Test]
public void TestListBug2()
{

View File

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

View File

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

View File

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

View File

@@ -29,7 +29,8 @@ public class AlertInlineParser : InlineParser
// We expect the alert to be the first child of a quote block. Example:
// > [!NOTE]
// > This is a note
if (processor.Block is not ParagraphBlock paragraphBlock || paragraphBlock.Parent is not QuoteBlock quoteBlock || paragraphBlock.Inline?.FirstChild != null)
if (processor.Block is not ParagraphBlock paragraphBlock || paragraphBlock.Parent is not QuoteBlock quoteBlock || paragraphBlock.Inline?.FirstChild != null
|| quoteBlock is AlertBlock || quoteBlock.Parent is not MarkdownDocument)
{
return false;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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