Compare commits

...

25 Commits

Author SHA1 Message Date
Miha Zupan
8c01cf0549 Add another test for pipe tables (#907) 2025-10-21 08:37:43 +02:00
Miha Zupan
bcbd8e47ac Lazily allocate ProcessInlinesBegin/End delegates on Blocks (#906) 2025-10-21 08:37:02 +02:00
Miha Zupan
d6e88f16f7 Fix pipe table parsing with a leading paragraph (#905)
* Fix pipe table parsing with a leading paragraph

* Use the alternative approach
2025-10-20 21:43:25 +02:00
Miha Zupan
03bdf60086 Add a basic fuzzing project (#903)
* Add basic fuzzing project

* Mark the project as non-packable
2025-10-17 08:09:28 +02:00
Miha Zupan
5c78932f55 Fix edge cases in EmphasisInlineParser (#902) 2025-10-17 08:07:15 +02:00
Miha Zupan
191e33ab32 Fix build warnings (#899) 2025-10-16 17:25:47 +02:00
Miha Zupan
800235ba7a Fix IndexOutOfRangeException in CodeInlineParser (#900) 2025-10-16 17:25:30 +02:00
Miha Zupan
d5f8a809a0 Move sln to slnx (#901) 2025-10-16 17:24:33 +02:00
Asttear
781d9b5365 Remove leading newline in block attributes (#896)
* Remove leading newline in block attributes

fix #895

* Add handling logic for `\r\n`
2025-10-05 11:21:12 +02:00
Phillip Haydon
543570224e 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 <mihazupan.zupan1@gmail.com>

* Apply suggestion from @MihaZupan

Co-authored-by: Miha Zupan <mihazupan.zupan1@gmail.com>

* Update src/Markdig.Tests/TestPipeTable.cs

Co-authored-by: Miha Zupan <mihazupan.zupan1@gmail.com>

* fix broken test

* removed unreachable code and added more tests

* Update src/Markdig.Tests/TestPipeTable.cs

Co-authored-by: Miha Zupan <mihazupan.zupan1@gmail.com>

* Update src/Markdig.Tests/TestPipeTable.cs

Co-authored-by: Miha Zupan <mihazupan.zupan1@gmail.com>

* removed uncessary inline code check

* Update src/Markdig/Parsers/Inlines/CodeInlineParser.cs

Co-authored-by: Miha Zupan <mihazupan.zupan1@gmail.com>

---------

Co-authored-by: Miha Zupan <mihazupan.zupan1@gmail.com>
Co-authored-by: Alexandre Mutel <alexandre_mutel@live.com>
2025-10-03 09:34:24 +02:00
Daniel Klecha
4dc0be88b4 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
2025-10-03 09:22:51 +02:00
Phillip Haydon
0e9e80e1cd Fix for table depth error when cell contains backticks (#891)
* failing test

* fixed bug with table containing back tick which causes depth error
2025-09-21 16:26:02 +02:00
Alexandre Mutel
1b04599c44 Merge pull request #888 from prozolic/pullreq
Fixes issue #845
2025-09-11 07:55:51 +02:00
prozolic
5e6fb2d1c5 Add test for issue #845 list item blank line 2025-09-08 22:36:09 +09:00
prozolic
14406bc60d Fixes issue #845 2025-09-06 21:10:51 +09:00
Alexandre Mutel
2aa6780a30 Merge pull request #883 from messani/master
Add source position tracking for grid tables
2025-08-28 09:04:44 +02:00
Alexandre Mutel
c43646586c Merge pull request #885 from dannyp32/supportTableWithoutExtraLine
Add support for a table without an extra new line before it
2025-08-28 09:02:29 +02:00
Daniel Pino
d548b82bcd Add support for a table without an extra new line before it 2025-08-09 08:50:49 +00:00
Tibor Peluch
aab5543cb5 Code cleanup 2025-07-14 20:17:50 +02:00
Tibor Peluch
2e1d741aaf Cleaned up code, added tests for source position 2025-07-14 10:23:15 +02:00
Tibor Peluch
80c50e31e2 Attempt to fix tracking of tree node positions (line, column) inside GridTable 2025-07-11 13:25:03 +02:00
Alexandre Mutel
7ff8db9016 Merge pull request #877 from Mertsch/Mertsch-patch-1
Update readme.md
2025-06-19 08:41:54 +02:00
Alexandre Mutel
c69fb9ae73 Merge pull request #879 from stylefish/issue878
Fixes #878: RoundtripRenderer: render indent and 0 blocks for ordered lists
2025-06-19 08:41:10 +02:00
stylefish
5a3c206076 Fixes #878: render indent and 0 blocks 2025-06-16 11:26:23 +02:00
Mertsch
b92890094c Update readme.md 2025-06-12 14:26:00 +02:00
51 changed files with 1136 additions and 186 deletions

View File

@@ -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.

2
.gitattributes vendored
View File

@@ -1,3 +1,3 @@
* text=auto
*.cs text=auto diff=csharp
*.sln text=auto eol=crlf
*.slnx text=auto eol=crlf

View File

@@ -2,7 +2,7 @@
<img align="right" width="160px" height="160px" src="img/markdig.png">
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/)

4
src/Markdig.Fuzzing/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
corpus
libfuzzer-dotnet-windows.exe
crash-*
timeout-*

View File

@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="SharpFuzz" Version="2.2.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Markdig\Markdig.csproj" />
</ItemGroup>
</Project>

View File

@@ -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<byte> 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();
}
}

View File

@@ -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

View File

@@ -9,6 +9,7 @@
<StartupObject>Markdig.Tests.Program</StartupObject>
<SpecExecutable>$(MSBuildProjectDirectory)\..\SpecFileGen\bin\$(Configuration)\$(TargetFramework)\SpecFileGen.dll</SpecExecutable>
<SpecTimestamp>$(MSBuildProjectDirectory)\..\SpecFileGen\bin\$(Configuration)\$(TargetFramework)\SpecFileGen.timestamp</SpecTimestamp>
<NoWarn>$(NoWarn);NETSDK1138</NoWarn>
</PropertyGroup>
<ItemGroup>

View File

@@ -386,4 +386,27 @@ Also not a note.</p>
";
TestParser.TestSpec(input, expected, new MarkdownPipelineBuilder().UseAlertBlocks().Build());
}
[Test]
public void TestIssue845ListItemBlankLine()
{
TestParser.TestSpec("-\n\n foo",@"
<ul>
<li></li>
</ul>
<p>foo</p>");
TestParser.TestSpec("-\n-\n\n foo",@"
<ul>
<li></li>
<li></li>
</ul>
<p>foo</p>");
TestParser.TestSpec("-\n\n-\n\n foo",@"
<ul>
<li></li>
<li></li>
</ul>
<p>foo</p>");
}
}

View File

@@ -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);

View File

@@ -98,5 +98,22 @@ namespace Markdig.Tests.Specs.GenericAttributes
TestParser.TestSpec("[Foo](url){data-x=1}\n\n[Foo](url){data-x='1'}\n\n[Foo](url){data-x=11}", "<p><a href=\"url\" data-x=\"1\">Foo</a></p>\n<p><a href=\"url\" data-x=\"1\">Foo</a></p>\n<p><a href=\"url\" data-x=\"11\">Foo</a></p>", "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:
// <p class="center">A paragraph</p>
TestParser.TestSpec("{.center}\nA paragraph", "<p class=\"center\">A paragraph</p>", "attributes|advanced", context: "Example 4\nSection Extensions / Generic Attributes\n");
}
}
}

View File

@@ -61,3 +61,12 @@ Attribute values can be one character long
<p><a href="url" data-x="1">Foo</a></p>
<p><a href="url" data-x="11">Foo</a></p>
````````````````````````````````
Attributes that occur immediately before a block element, on a line by themselves, affect that element
```````````````````````````````` example
{.center}
A paragraph
.
<p class="center">A paragraph</p>
````````````````````````````````

View File

@@ -386,5 +386,34 @@ namespace Markdig.Tests.Specs.GridTables
TestParser.TestSpec("+", "<ul>\n<li></li>\n</ul>", "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:
// <p>Some
// <strong>text</strong>.</p>
// <table>
// <col style="width:100%" />
// <tbody>
// <tr>
// <td>A</td>
// </tr>
// </tbody>
// </table>
TestParser.TestSpec("Some\n**text**.\n+---+\n| A |\n+---+", "<p>Some\n<strong>text</strong>.</p>\n<table>\n<col style=\"width:100%\" />\n<tbody>\n<tr>\n<td>A</td>\n</tr>\n</tbody>\n</table>", "gridtables|advanced", context: "Example 12\nSection Extensions / Grid Table\n");
}
}
}

View File

@@ -285,3 +285,24 @@ An empty `+` on a line should result in a simple empty list output:
<li></li>
</ul>
````````````````````````````````
A table may begin right after a paragraph without an empty line in between:
```````````````````````````````` example
Some
**text**.
+---+
| A |
+---+
.
<p>Some
<strong>text</strong>.</p>
<table>
<col style="width:100%" />
<tbody>
<tr>
<td>A</td>
</tr>
</tbody>
</table>
````````````````````````````````

View File

@@ -825,5 +825,190 @@ namespace Markdig.Tests.Specs.PipeTables
TestParser.TestSpec("a | b\n-- | - \n0 | 1 | 2", "<table>\n<thead>\n<tr>\n<th>a</th>\n<th>b</th>\n<th></th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>0</td>\n<td>1</td>\n<td>2</td>\n</tr>\n</tbody>\n</table>", "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:
// <p>Some
// <strong>text</strong>.</p>
// <table>
// <thead>
// <tr>
// <th>A</th>
// </tr>
// </thead>
// <tbody>
// <tr>
// <td>B</td>
// </tr>
// </tbody>
// </table>
TestParser.TestSpec("Some\n**text**.\n| A |\n|---|\n| B |", "<p>Some\n<strong>text</strong>.</p>\n<table>\n<thead>\n<tr>\n<th>A</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>B</td>\n</tr>\n</tbody>\n</table>", "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:
// <p>Bullet list</p>
// <ul>
// <li><p>Table 1</p>
// <table>
// <thead>
// <tr>
// <th>Header 1</th>
// <th>Header 2</th>
// </tr>
// </thead>
// <tbody>
// <tr>
// <td>Row 1 Column 1</td>
// <td>Row 1 Column 2</td>
// </tr>
// </tbody>
// </table></li>
// <li><p>Table 2</p>
// <table>
// <thead>
// <tr>
// <th>Header 1</th>
// <th>Header 2</th>
// </tr>
// </thead>
// <tbody>
// <tr>
// <td>Row 1 Column 1</td>
// <td>Row 1 Column 2</td>
// </tr>
// </tbody>
// </table></li>
// <li><p>Table 3
// Lorem ipsum ...
// Lorem ipsum ...</p>
// <table>
// <thead>
// <tr>
// <th>Header 1</th>
// <th>Header 2</th>
// </tr>
// </thead>
// <tbody>
// <tr>
// <td>Row 1 Column 1</td>
// <td>Row 1 Column 2</td>
// </tr>
// </tbody>
// </table></li>
// </ul>
// <p>Ordered list</p>
// <ol>
// <li><p>Table 1</p>
// <table>
// <thead>
// <tr>
// <th>Header 1</th>
// <th>Header 2</th>
// </tr>
// </thead>
// <tbody>
// <tr>
// <td>Row 1 Column 1</td>
// <td>Row 1 Column 2</td>
// </tr>
// </tbody>
// </table></li>
// <li><p>Table 2</p>
// <table>
// <thead>
// <tr>
// <th>Header 1</th>
// <th>Header 2</th>
// </tr>
// </thead>
// <tbody>
// <tr>
// <td>Row 1 Column 1</td>
// <td>Row 1 Column 2</td>
// </tr>
// </tbody>
// </table></li>
// <li><p>Table 3
// Lorem ipsum ...
// Lorem ipsum ...</p>
// <table>
// <thead>
// <tr>
// <th>Header 1</th>
// <th>Header 2</th>
// </tr>
// </thead>
// <tbody>
// <tr>
// <td>Row 1 Column 1</td>
// <td>Row 1 Column 2</td>
// </tr>
// </tbody>
// </table></li>
// </ol>
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 |", "<p>Bullet list</p>\n<ul>\n<li><p>Table 1</p>\n<table>\n<thead>\n<tr>\n<th>Header 1</th>\n<th>Header 2</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Row 1 Column 1</td>\n<td>Row 1 Column 2</td>\n</tr>\n</tbody>\n</table></li>\n<li><p>Table 2</p>\n<table>\n<thead>\n<tr>\n<th>Header 1</th>\n<th>Header 2</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Row 1 Column 1</td>\n<td>Row 1 Column 2</td>\n</tr>\n</tbody>\n</table></li>\n<li><p>Table 3\nLorem ipsum ...\nLorem ipsum ...</p>\n<table>\n<thead>\n<tr>\n<th>Header 1</th>\n<th>Header 2</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Row 1 Column 1</td>\n<td>Row 1 Column 2</td>\n</tr>\n</tbody>\n</table></li>\n</ul>\n<p>Ordered list</p>\n<ol>\n<li><p>Table 1</p>\n<table>\n<thead>\n<tr>\n<th>Header 1</th>\n<th>Header 2</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Row 1 Column 1</td>\n<td>Row 1 Column 2</td>\n</tr>\n</tbody>\n</table></li>\n<li><p>Table 2</p>\n<table>\n<thead>\n<tr>\n<th>Header 1</th>\n<th>Header 2</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Row 1 Column 1</td>\n<td>Row 1 Column 2</td>\n</tr>\n</tbody>\n</table></li>\n<li><p>Table 3\nLorem ipsum ...\nLorem ipsum ...</p>\n<table>\n<thead>\n<tr>\n<th>Header 1</th>\n<th>Header 2</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Row 1 Column 1</td>\n<td>Row 1 Column 2</td>\n</tr>\n</tbody>\n</table></li>\n</ol>", "pipetables|advanced", context: "Example 27\nSection Extensions / Pipe Table\n");
}
}
}

View File

@@ -612,4 +612,173 @@ a | b
</tr>
</tbody>
</table>
````````````````````````````````
A table may begin right after a paragraph without an empty line in between:
```````````````````````````````` example
Some
**text**.
| A |
|---|
| B |
.
<p>Some
<strong>text</strong>.</p>
<table>
<thead>
<tr>
<th>A</th>
</tr>
</thead>
<tbody>
<tr>
<td>B</td>
</tr>
</tbody>
</table>
````````````````````````````````
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 |
.
<p>Bullet list</p>
<ul>
<li><p>Table 1</p>
<table>
<thead>
<tr>
<th>Header 1</th>
<th>Header 2</th>
</tr>
</thead>
<tbody>
<tr>
<td>Row 1 Column 1</td>
<td>Row 1 Column 2</td>
</tr>
</tbody>
</table></li>
<li><p>Table 2</p>
<table>
<thead>
<tr>
<th>Header 1</th>
<th>Header 2</th>
</tr>
</thead>
<tbody>
<tr>
<td>Row 1 Column 1</td>
<td>Row 1 Column 2</td>
</tr>
</tbody>
</table></li>
<li><p>Table 3
Lorem ipsum ...
Lorem ipsum ...</p>
<table>
<thead>
<tr>
<th>Header 1</th>
<th>Header 2</th>
</tr>
</thead>
<tbody>
<tr>
<td>Row 1 Column 1</td>
<td>Row 1 Column 2</td>
</tr>
</tbody>
</table></li>
</ul>
<p>Ordered list</p>
<ol>
<li><p>Table 1</p>
<table>
<thead>
<tr>
<th>Header 1</th>
<th>Header 2</th>
</tr>
</thead>
<tbody>
<tr>
<td>Row 1 Column 1</td>
<td>Row 1 Column 2</td>
</tr>
</tbody>
</table></li>
<li><p>Table 2</p>
<table>
<thead>
<tr>
<th>Header 1</th>
<th>Header 2</th>
</tr>
</thead>
<tbody>
<tr>
<td>Row 1 Column 1</td>
<td>Row 1 Column 2</td>
</tr>
</tbody>
</table></li>
<li><p>Table 3
Lorem ipsum ...
Lorem ipsum ...</p>
<table>
<thead>
<tr>
<th>Header 1</th>
<th>Header 2</th>
</tr>
</thead>
<tbody>
<tr>
<td>Row 1 Column 1</td>
<td>Row 1 Column 2</td>
</tr>
</tbody>
</table></li>
</ol>
````````````````````````````````

View File

@@ -0,0 +1,10 @@
namespace Markdig.Tests;
public class TestCodeInline
{
[Test]
public void UnpairedCodeInlineWithTrailingChars()
{
TestParser.TestSpec("*`\n\f", "<p>*`</p>");
}
}

View File

@@ -148,6 +148,9 @@ public class TestEmphasisExtended
[TestCase("1Foo1", "<one-only>Foo</one-only>")]
[TestCase("1121", "1<one-only>2</one-only>")]
[TestCase("22322", "<two-only>3</two-only>")]
[TestCase("2223222", "2<two-only>32</two-only>")]
[TestCase("22223222", "22<two-only>32</two-only>")]
[TestCase("22223223222", "22223<two-only>3</two-only>2")]
[TestCase("2232", "2232")]
[TestCase("333", "333")]
[TestCase("3334333", "<three-only>4</three-only>")]

View File

@@ -1,5 +1,7 @@
using Markdig;
using Markdig.Extensions.Tables;
using Markdig.Syntax;
using Markdig.Syntax.Inlines;
namespace Markdig.Tests;
@@ -10,6 +12,9 @@ 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|---|---|")]
[TestCase("a\r\n| S | T |\r|---|---|")]
public void TestTableBug(string markdown, int tableCount = 1)
{
MarkdownDocument document =
@@ -55,4 +60,147 @@ public sealed class TestPipeTable
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<Table>().ToArray();
Assert.That(tables, Has.Length.EqualTo(1));
string html = string.Empty;
Assert.DoesNotThrow(() => html = Markdown.ToHtml(markdown, pipeline));
Assert.That(html, Does.Contain("<table"));
Assert.That(html, Does.Contain("<td>`C</td>"));
}
[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<CodeInline>().SingleOrDefault();
Assert.IsNotNull(codeInline);
Assert.That(codeInline!.Content, Is.EqualTo("|| hidden text ||"));
Assert.That(document.ToHtml(), Is.EqualTo("<p><code>|| hidden text ||</code></p>\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("<p><code>|| hidden text ||</code></p>\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("<td><code>Code block</code></td>"));
}
[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<CodeInline>().Single();
Assert.That(codeInline.Content, Is.EqualTo("foo"));
Assert.That(Markdown.ToHtml(markdown, pipeline), Is.EqualTo("<p><code>foo</code></p>\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("<p><code>|| hidden text ||</code></p>"));
Assert.That(html, Does.Contain("<table"));
Assert.That(html, Does.Contain("<td>B</td>"));
}
}

View File

@@ -834,6 +834,29 @@ literal ( 2, 2) 11-11
", "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()
{

View File

@@ -73,8 +73,6 @@ public class AbbreviationParser : BlockParser
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)

View File

@@ -101,7 +101,6 @@ public class AutoIdentifierExtension : IMarkdownExtension
private void DocumentOnProcessInlinesBegin(InlineProcessor processor, Inline? inline)
{
var doc = processor.Document;
doc.ProcessInlinesBegin -= _processInlinesBegin;
var dictionary = (Dictionary<string, HeadingLinkReferenceDefinition>)doc.GetData(this)!;
foreach (var keyPair in dictionary)
{

View File

@@ -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 class AutoLinkOptions
public string ValidPreviousCharacters { get; set; }
/// <summary>
/// Should the link open in a new window when clicked (false by default)
/// </summary>
public bool OpenInNewWindow { get; set; }
/// <summary>
/// Should a www link be prefixed with https:// instead of http:// (false by default)
/// </summary>

View File

@@ -134,9 +134,6 @@ public class FootnoteParser : BlockParser
/// <param name="inline">The inline.</param>
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);

View File

@@ -109,6 +109,15 @@ public class GenericAttributesParser : InlineParser
{
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;
}

View File

@@ -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;
/// <summary>
/// Available options for replacing JIRA links
/// </summary>
public class JiraLinkOptions
public class JiraLinkOptions : LinkOptions
{
/// <summary>
/// The base Url (e.g. `https://mycompany.atlassian.net`)
@@ -21,11 +22,6 @@ public class JiraLinkOptions
/// </summary>
public string BasePath { get; set; }
/// <summary>
/// Should the link open in a new window when clicked
/// </summary>
public bool OpenInNewWindow { get; set; }
public JiraLinkOptions(string baseUrl)
{
OpenInNewWindow = true; //default

View File

@@ -204,8 +204,6 @@ public class SmartyPantsInlineParser : InlineParser, IPostInlineProcessor
private void BlockOnProcessInlinesEnd(InlineProcessor processor, Inline? inline)
{
processor.Block!.ProcessInlinesEnd -= BlockOnProcessInlinesEnd;
var pants = (ListSmartyPants) processor.ParserStates[Index];
var openers = new Stack<Opener>(4);

View File

@@ -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 class GridTableParser : BlockParser
}
// 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 class GridTableParser : BlockParser
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 @@ public class GridTableParser : BlockParser
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 @@ public class GridTableParser : BlockParser
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 @@ public class GridTableParser : BlockParser
}
// Process the content of the cell
columnSlice.BlockProcessor!.LineIndex = processor.LineIndex;
columnSlice.BlockProcessor.ProcessLine(sliceForCell);
columnSlice.BlockProcessor.ProcessLinePart(sliceForCell, sliceForCell.Start - line.Start);
}
// Go to next column

View File

@@ -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;
@@ -60,13 +59,12 @@ public class PipeTableParser : InlineParser, IPostInlineProcessor
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 && (c == '\n' || c == '\r'))
{
return false;
}
@@ -75,6 +73,7 @@ public class PipeTableParser : InlineParser, IPostInlineProcessor
{
isFirstLineEmpty = true;
}
// Else setup a table processor
tableState = new TableState();
processor.ParserStates[Index] = tableState;
@@ -88,7 +87,6 @@ public class PipeTableParser : InlineParser, IPostInlineProcessor
}
tableState.LineHasPipe = false;
lineBreakParser.Match(processor, ref slice);
tableState.LineIndex++;
if (!isFirstLineEmpty)
{
tableState.ColumnAndLineDelimiters.Add(processor.Inline!);
@@ -104,13 +102,8 @@ public class PipeTableParser : InlineParser, IPostInlineProcessor
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);
@@ -196,6 +189,16 @@ public class PipeTableParser : InlineParser, IPostInlineProcessor
// 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;
}
@@ -218,7 +221,6 @@ public class PipeTableParser : InlineParser, IPostInlineProcessor
attributes.CopyTo(table.GetAttributes());
}
state.BlockNew = table;
var cells = tableState.Cells;
cells.Clear();
@@ -477,6 +479,35 @@ public class PipeTableParser : InlineParser, IPostInlineProcessor
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!;
parent.ProcessInlinesEnd += (_, _) =>
{
parent.Insert(parent.IndexOf(leadingParagraph) + 1, table);
};
}
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;
}
@@ -672,8 +703,6 @@ public class PipeTableParser : InlineParser, IPostInlineProcessor
public bool LineHasPipe { get; set; }
public int LineIndex { get; set; }
public List<Inline> ColumnAndLineDelimiters { get; } = [];
public List<TableCell> Cells { get; } = [];

View File

@@ -47,6 +47,7 @@ public static class TableHelper
/// <param name="slice">The text slice.</param>
/// <param name="delimiterChar">The delimiter character (either `-` or `=`). If `\0`, it will detect the character (either `-` or `=`)</param>
/// <param name="align">The alignment of the column.</param>
/// <param name="delimiterCount">The number of times <paramref name="delimiterChar"/> appeared in the column header.</param>
/// <returns>
/// <c>true</c> if parsing was successful
/// </returns>

View File

@@ -1,5 +1,5 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using System.Diagnostics;
@@ -430,7 +430,7 @@ public static class HtmlHelper
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 @@ public static class HtmlHelper
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 class HtmlHelper
public static int ScanEntity<T>(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 class HtmlHelper
var start = slice.Start;
char c = slice.NextChar();
int counter = 0;
if (c == '#')
{
c = slice.PeekChar();

View File

@@ -22,7 +22,7 @@ internal static class UnicodeUtility
[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);

View File

@@ -538,7 +538,7 @@ public static class MarkdownExtensions
var inlineParser = pipeline.InlineParsers.Find<AutolinkInlineParser>();
if (inlineParser != null)
{
inlineParser.EnableHtmlParsing = false;
inlineParser.Options.EnableHtmlParsing = false;
}
return pipeline;
}

View File

@@ -493,8 +493,34 @@ public class BlockProcessor
ContinueProcessingLine = true;
ResetLine(newLine);
ResetLine(newLine, 0);
Process();
LineIndex++;
}
/// <summary>
/// Processes part of a line.
/// </summary>
/// <param name="line">The line.</param>
/// <param name="column">The column.</param>
public void ProcessLinePart(StringSlice line, int column)
{
CurrentLineStartPosition = line.Start - column;
ContinueProcessingLine = true;
ResetLine(line, column);
Process();
}
/// <summary>
/// Process current string slice.
/// </summary>
private void Process()
{
TryContinueBlocks();
// If the line was not entirely processed by pending blocks, try to process it with any new block
@@ -502,8 +528,6 @@ public class BlockProcessor
// Close blocks that are no longer opened
CloseAll(false);
LineIndex++;
}
internal bool IsOpen(Block block)
@@ -956,18 +980,17 @@ public class BlockProcessor
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)
{

View File

@@ -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;
/// <seealso cref="InlineParser" />
public class AutolinkInlineParser : InlineParser
{
/// <summary>
/// Initializes a new instance of the <see cref="AutolinkInlineParser"/> class.
/// </summary>
public AutolinkInlineParser()
public AutolinkInlineParser() : this(new AutolinkOptions())
{
OpeningCharacters = ['<'];
EnableHtmlParsing = true;
}
/// <summary>
/// Gets or sets a value indicating whether to enable HTML parsing. Default is <c>true</c>
/// Initializes a new instance of the <see cref="AutolinkInlineParser"/> class.
/// </summary>
public bool EnableHtmlParsing { get; set; }
public AutolinkInlineParser(AutolinkOptions options)
{
Options = options ?? throw new ArgumentNullException(nameof(options));
OpeningCharacters = ['<'];
}
public readonly AutolinkOptions Options;
public override bool Match(InlineProcessor processor, ref StringSlice slice)
{
@@ -42,8 +45,12 @@ public class AutolinkInlineParser : InlineParser
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 class AutolinkInlineParser : InlineParser
Line = line,
Column = column
};
if (Options.OpenInNewWindow)
{
processor.Inline.GetAttributes().AddPropertyIfNotExist("target", "_blank");
}
}
else
{

View File

@@ -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
{
/// <summary>
/// Gets or sets a value indicating whether to enable HTML parsing. Default is <c>true</c>
/// </summary>
public bool EnableHtmlParsing { get; set; } = true;
}

View File

@@ -4,6 +4,7 @@
using System.Diagnostics;
using Markdig.Extensions.Tables;
using Markdig.Helpers;
using Markdig.Syntax;
using Markdig.Syntax.Inlines;
@@ -35,6 +36,7 @@ public class CodeInlineParser : InlineParser
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 +77,21 @@ public class CodeInlineParser : InlineParser
{
break;
}
else if (closeSticks == 0)
if (closeSticks == 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.
// Treat it as regular text to avoid breaking the overall table shape.
if (processor.Inline != null && processor.Inline.ContainsParentOfType<PipeTableDelimiterInline>())
{
slice.Start = openingStart;
return false;
}
}
containsNewLines = true;
span = span.Slice(1);
}

View File

@@ -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;
/// <summary>
/// Descriptor for an emphasis.
/// </summary>
[DebuggerDisplay("Emphasis Char={Character}, Min={MinimumCount}, Max={MaximumCount}, EnableWithinWord={EnableWithinWord}")]
public sealed class EmphasisDescriptor
{
/// <summary>

View File

@@ -233,9 +233,9 @@ public class EmphasisInlineParser : InlineParser, IPostInlineProcessor
continue;
}
if ((closeDelimiter.Type & DelimiterType.Close) != 0 && closeDelimiter.DelimiterCount >= 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 @@ public class EmphasisInlineParser : InlineParser, IPostInlineProcessor
{
var previousOpenDelimiter = delimiters[j];
var isOddMatch = ((closeDelimiter.Type & DelimiterType.Open) != 0 ||
(previousOpenDelimiter.Type & DelimiterType.Close) != 0) &&
var isOddMatch = ((closeDelimiter.Type & DelimiterType.Open) != 0 || (previousOpenDelimiter.Type & DelimiterType.Close) != 0) &&
previousOpenDelimiter.DelimiterCount != closeDelimiter.DelimiterCount &&
(previousOpenDelimiter.DelimiterCount + closeDelimiter.DelimiterCount) % 3 == 0 &&
(previousOpenDelimiter.DelimiterCount % 3 != 0 || closeDelimiter.DelimiterCount % 3 != 0);
@@ -357,7 +356,8 @@ public class EmphasisInlineParser : InlineParser, IPostInlineProcessor
}
// The current delimiters are matching
if (openDelimiter.DelimiterCount >= emphasisDesc.MinimumCount)
if (openDelimiter.DelimiterCount >= emphasisDesc.MinimumCount &&
closeDelimiter.DelimiterCount >= emphasisDesc.MinimumCount)
{
goto process_delims;
}

View File

@@ -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
/// <summary>
/// Initializes a new instance of the <see cref="LinkInlineParser"/> class.
/// </summary>
public LinkInlineParser()
public LinkInlineParser() : this(new LinkOptions())
{
}
/// <summary>
/// Initializes a new instance of the <see cref="LinkInlineParser"/> class.
/// </summary>
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 @@ public class LinkInlineParser : InlineParser
linkInline.LocalLabel = localLabel;
}
if (Options.OpenInNewWindow)
{
linkInline.GetAttributes().AddPropertyIfNotExist("target", "_blank");
}
link = linkInline;
}

View File

@@ -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
{
/// <summary>
/// Should the link open in a new window when clicked (false by default)
/// </summary>
public bool OpenInNewWindow { get; set; }
}

View File

@@ -145,6 +145,7 @@ public class ListBlockParser : BlockParser
if (list.CountBlankLinesReset == 1 && listItem.ColumnWidth < 0)
{
state.Close(listItem);
list.CountBlankLinesReset = 0;
// Leave the list open
list.IsOpen = true;

View File

@@ -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<char> span, string prefix, StringComparison comparisonType)
{
Debug.Assert(comparisonType is StringComparison.Ordinal or StringComparison.OrdinalIgnoreCase);
@@ -18,6 +18,15 @@ internal static class SpanExtensions
span.Length >= prefix.Length &&
span.Slice(0, prefix.Length).Equals(prefix.AsSpan(), comparisonType);
}
}
#endif
#endif
#if !NET9_0_OR_GREATER
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool StartsWith(this ReadOnlySpan<char> span, char c)
{
return span.Length > 0 && span[0] == c;
}
#endif
}

View File

@@ -85,12 +85,12 @@ public abstract class RendererBase : IMarkdownRenderer
public bool IsLastInContainer { get; private set; }
/// <summary>
/// Occurs when before writing an object.
/// Occurs before writing an object.
/// </summary>
public event Action<IMarkdownRenderer, MarkdownObject>? ObjectWriteBefore;
/// <summary>
/// Occurs when after writing an object.
/// Occurs after writing an object.
/// </summary>
public event Action<IMarkdownRenderer, MarkdownObject>? ObjectWriteAfter;

View File

@@ -1,5 +1,5 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using Markdig.Helpers;
@@ -28,7 +28,15 @@ public class ListRenderer : RoundtripObjectRenderer<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);
}
}

View File

@@ -92,12 +92,20 @@ public abstract class Block : MarkdownObject, IBlock
/// <summary>
/// Occurs when the process of inlines begin.
/// </summary>
public event ProcessInlineDelegate? ProcessInlinesBegin;
public event ProcessInlineDelegate? ProcessInlinesBegin
{
add => Trivia.ProcessInlinesBegin += value;
remove => _trivia?.ProcessInlinesBegin -= value;
}
/// <summary>
/// Occurs when the process of inlines ends for this instance.
/// </summary>
public event ProcessInlineDelegate? ProcessInlinesEnd;
public event ProcessInlineDelegate? ProcessInlinesEnd
{
add => Trivia.ProcessInlinesEnd += value;
remove => _trivia?.ProcessInlinesEnd -= value;
}
/// <summary>
/// Called when the process of inlines begin.
@@ -105,7 +113,13 @@ public abstract class Block : MarkdownObject, IBlock
/// <param name="state">The inline parser state.</param>
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;
}
}
/// <summary>
@@ -114,7 +128,13 @@ public abstract class Block : MarkdownObject, IBlock
/// <param name="state">The inline parser state.</param>
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 @@ public abstract class Block : MarkdownObject, IBlock
// 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<StringSlice>? LinesBefore;

View File

@@ -5,6 +5,7 @@
<TargetFrameworks>net6.0;net8.0;net9.0</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<IsPackable>false</IsPackable>
<NoWarn>$(NoWarn);NETSDK1138</NoWarn>
</PropertyGroup>
</Project>

View File

@@ -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"

View File

@@ -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

View File

@@ -1,12 +0,0 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeStyle/FileHeader/FileHeaderText/@EntryValue">Copyright (c) Alexandre Mutel. All rights reserved.&#xD;
This file is licensed under the BSD-Clause 2 license. &#xD;
See the license.txt file in the project root for more information.</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateInstanceFields/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=4a98fdf6_002D7d98_002D4f5a_002Dafeb_002Dea44ad98c70c/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Instance" AccessRightKinds="Private" Description="Instance fields (private)"&gt;&lt;ElementKinds&gt;&lt;Kind Name="FIELD" /&gt;&lt;Kind Name="READONLY_FIELD" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;&lt;/Policy&gt;</s:String>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EFeature_002EServices_002ECodeCleanup_002EFileHeader_002EFileHeaderSettingsMigrate/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EPredefinedNamingRulesToUserRulesUpgrade/@EntryIndexedValue">True</s:Boolean>
<s:String x:Key="/Default/Environment/UnitTesting/NUnitProvider/SetCurrentDirectoryTo/@EntryValue">TestFolder</s:String>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Autolink/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Inlines/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Markdig/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

26
src/markdig.slnx Normal file
View File

@@ -0,0 +1,26 @@
<Solution>
<Folder Name="/Build/">
<File Path="../.editorconfig" />
<File Path="../.gitattributes" />
<File Path="../.github/workflows/ci.yml" />
<File Path="../.gitignore" />
<File Path="../changelog.md" />
<File Path="../license.txt" />
<File Path="../readme.md" />
<File Path="global.json" />
</Folder>
<Project Path="Markdig.Benchmarks/Markdig.Benchmarks.csproj">
<BuildDependency Project="Markdig/Markdig.csproj" />
</Project>
<Project Path="Markdig.Fuzzing/Markdig.Fuzzing.csproj">
<BuildDependency Project="Markdig/Markdig.csproj" />
</Project>
<Project Path="Markdig.Tests/Markdig.Tests.csproj">
<BuildDependency Project="Markdig/Markdig.csproj" />
</Project>
<Project Path="Markdig.WebApp/Markdig.WebApp.csproj" />
<Project Path="Markdig/Markdig.csproj" />
<Project Path="mdtoc/mdtoc.csproj" />
<Project Path="SpecFileGen/SpecFileGen.csproj" />
<Project Path="UnicodeNormDApp/UnicodeNormDApp.csproj" />
</Solution>