mirror of
https://github.com/xoofx/markdig.git
synced 2026-02-04 05:44:50 +00:00
Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1b04599c44 | ||
|
|
5e6fb2d1c5 | ||
|
|
14406bc60d | ||
|
|
2aa6780a30 | ||
|
|
c43646586c | ||
|
|
d548b82bcd | ||
|
|
aab5543cb5 | ||
|
|
2e1d741aaf | ||
|
|
80c50e31e2 | ||
|
|
7ff8db9016 | ||
|
|
c69fb9ae73 | ||
|
|
5a3c206076 | ||
|
|
b92890094c |
20
readme.md
20
readme.md
@@ -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/)
|
||||
|
||||
@@ -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>");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -10,6 +10,8 @@ public sealed class TestPipeTable
|
||||
[TestCase("| S | T |\r\n|---|---|\t\r\n| G | H |")]
|
||||
[TestCase("| S | T |\r\n|---|---|\f\r\n| G | H |")]
|
||||
[TestCase("| S | \r\n|---|\r\n| G |\r\n\r\n| D | D |\r\n| ---| ---| \r\n| V | V |", 2)]
|
||||
[TestCase("a\r| S | T |\r|---|---|")]
|
||||
[TestCase("a\n| S | T |\r|---|---|")]
|
||||
public void TestTableBug(string markdown, int tableCount = 1)
|
||||
{
|
||||
MarkdownDocument document =
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -48,6 +48,7 @@ public class PipeTableParser : InlineParser, IPostInlineProcessor
|
||||
}
|
||||
|
||||
var c = slice.CurrentChar;
|
||||
var isNewLineFollowedByPipe = (c == '\n' || c == '\r') && slice.PeekChar() == '|';
|
||||
|
||||
// If we have not a delimiter on the first line of a paragraph, don't bother to continue
|
||||
// tracking other delimiters on following lines
|
||||
@@ -60,18 +61,17 @@ public 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 && (localLineIndex > 0 || c == '\n' || c == '\r') && !isNewLineFollowedByPipe)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (processor.Inline is null)
|
||||
if (processor.Inline is null || isNewLineFollowedByPipe)
|
||||
{
|
||||
isFirstLineEmpty = true;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user