mirror of
https://github.com/xoofx/markdig.git
synced 2026-02-15 05:55:41 +00:00
Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f9bfcaab7b | ||
|
|
afa0182f02 | ||
|
|
b83de5934d | ||
|
|
c294d3bfb4 | ||
|
|
a7cdb2351a | ||
|
|
46ef21a3ed | ||
|
|
18c8d0178c | ||
|
|
ebc79dafbd | ||
|
|
a1d2467643 | ||
|
|
8220f0fa56 | ||
|
|
ec385acc7f | ||
|
|
04c1cc62d4 | ||
|
|
abdbd65f60 | ||
|
|
1f32a060da | ||
|
|
699d80c150 |
@@ -10,12 +10,13 @@ You can **try Markdig online** and compare it to other implementations on [babel
|
||||
|
||||
## Features
|
||||
|
||||
- **Very fast parser** (no-regexp), very lightweight in terms of GC pressure. See benchmarks
|
||||
- **Abstract Syntax Tree**
|
||||
- **Very fast parser and html renderer** (no-regexp), very lightweight in terms of GC pressure. See benchmarks
|
||||
- **Abstract Syntax Tree** with precise source code location for syntax tree, useful when building a Markdown editor.
|
||||
- Checkout [MarkdownEditor for Visual Studio](https://visualstudiogallery.msdn.microsoft.com/eaab33c3-437b-4918-8354-872dfe5d1bfe) powered by Markdig!
|
||||
- Converter to **HTML**
|
||||
- Passing more than **600+ tests** from the latest [CommonMark specs](http://spec.commonmark.org/)
|
||||
- Includes all the core elements of CommonMark:
|
||||
- including GFM fenced code blocks.
|
||||
- including **GFM fenced code blocks**.
|
||||
- **Extensible** architecture
|
||||
- Even the core Markdown/CommonMark parsing is pluggable, so it allows to disable builtin Markdown/Commonmark parsing (e.g [Disable HTML parsing](https://github.com/lunet-io/markdig/blob/7964bd0160d4c18e4155127a4c863d61ebd8944a/src/Markdig/MarkdownExtensions.cs#L306)) or change behaviour (e.g change matching `#` of a headers with `@`)
|
||||
- Built-in with **20+ extensions**, including:
|
||||
|
||||
@@ -59,9 +59,11 @@
|
||||
<DesignTime>True</DesignTime>
|
||||
<DependentUpon>Specs.tt</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="TestHtmlAttributes.cs" />
|
||||
<Compile Include="TestHtmlHelper.cs" />
|
||||
<Compile Include="TestLineReader.cs" />
|
||||
<Compile Include="TestLinkHelper.cs" />
|
||||
<Compile Include="TestPragmaLines.cs" />
|
||||
<Compile Include="TestSourcePosition.cs" />
|
||||
<Compile Include="TestStringSliceList.cs" />
|
||||
<Compile Include="TestPlayParser.cs" />
|
||||
|
||||
96
src/Markdig.Tests/TestHtmlAttributes.cs
Normal file
96
src/Markdig.Tests/TestHtmlAttributes.cs
Normal file
@@ -0,0 +1,96 @@
|
||||
// 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 Markdig.Renderers.Html;
|
||||
using NUnit.Framework;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Markdig.Tests
|
||||
{
|
||||
[TestFixture()]
|
||||
public class TestHtmlAttributes
|
||||
{
|
||||
[Test]
|
||||
public void TestAddClass()
|
||||
{
|
||||
var attributes = new HtmlAttributes();
|
||||
attributes.AddClass("test");
|
||||
Assert.NotNull(attributes.Classes);
|
||||
Assert.AreEqual(new List<string>() { "test" }, attributes.Classes);
|
||||
|
||||
attributes.AddClass("test");
|
||||
Assert.AreEqual(1, attributes.Classes.Count);
|
||||
|
||||
attributes.AddClass("test1");
|
||||
Assert.AreEqual(new List<string>() { "test", "test1" }, attributes.Classes);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAddProperty()
|
||||
{
|
||||
var attributes = new HtmlAttributes();
|
||||
attributes.AddProperty("key1", "1");
|
||||
Assert.NotNull(attributes.Properties);
|
||||
Assert.AreEqual(new List<KeyValuePair<string, string>>() { new KeyValuePair<string, string>("key1", "1") }, attributes.Properties);
|
||||
|
||||
attributes.AddPropertyIfNotExist("key1", "1");
|
||||
Assert.NotNull(attributes.Properties);
|
||||
Assert.AreEqual(new List<KeyValuePair<string, string>>() { new KeyValuePair<string, string>("key1", "1") }, attributes.Properties);
|
||||
|
||||
attributes.AddPropertyIfNotExist("key2", "2");
|
||||
Assert.AreEqual(new List<KeyValuePair<string, string>>() { new KeyValuePair<string, string>("key1", "1"), new KeyValuePair<string, string>("key2", "2") }, attributes.Properties);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCopyTo()
|
||||
{
|
||||
var from = new HtmlAttributes();
|
||||
from.AddClass("test");
|
||||
from.AddProperty("key1", "1");
|
||||
|
||||
var to = new HtmlAttributes();
|
||||
from.CopyTo(to);
|
||||
|
||||
Assert.True(ReferenceEquals(from.Classes, to.Classes));
|
||||
Assert.True(ReferenceEquals(from.Properties, to.Properties));
|
||||
|
||||
// From: Classes From: Properties To: Classes To: Properties
|
||||
// test1: null null null null
|
||||
from = new HtmlAttributes();
|
||||
to = new HtmlAttributes();
|
||||
from.CopyTo(to, false, false);
|
||||
Assert.Null(to.Classes);
|
||||
Assert.Null(to.Properties);
|
||||
|
||||
// test2: ["test"] ["key1", "1"] null null
|
||||
from = new HtmlAttributes();
|
||||
to = new HtmlAttributes();
|
||||
from.AddClass("test");
|
||||
from.AddProperty("key1", "1");
|
||||
from.CopyTo(to, false, false);
|
||||
Assert.AreEqual(new List<string>() { "test" }, to.Classes);
|
||||
Assert.AreEqual(new List<KeyValuePair<string, string>>() { new KeyValuePair<string, string>("key1", "1")}, to.Properties);
|
||||
|
||||
// test3: null null ["test"] ["key1", "1"]
|
||||
from = new HtmlAttributes();
|
||||
to = new HtmlAttributes();
|
||||
to.AddClass("test");
|
||||
to.AddProperty("key1", "1");
|
||||
from.CopyTo(to, false, false);
|
||||
Assert.AreEqual(new List<string>() { "test" }, to.Classes);
|
||||
Assert.AreEqual(new List<KeyValuePair<string, string>>() { new KeyValuePair<string, string>("key1", "1") }, to.Properties);
|
||||
|
||||
// test4: ["test1"] ["key2", "2"] ["test"] ["key1", "1"]
|
||||
from = new HtmlAttributes();
|
||||
to = new HtmlAttributes();
|
||||
from.AddClass("test1");
|
||||
from.AddProperty("key2", "2");
|
||||
to.AddClass("test");
|
||||
to.AddProperty("key1", "1");
|
||||
from.CopyTo(to, false, false);
|
||||
Assert.AreEqual(new List<string>() { "test", "test1" }, to.Classes);
|
||||
Assert.AreEqual(new List<KeyValuePair<string, string>>() { new KeyValuePair<string, string>("key1", "1"), new KeyValuePair<string, string>("key2", "2") }, to.Properties);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -78,7 +78,7 @@ namespace Markdig.Tests
|
||||
private static string Compact(string html)
|
||||
{
|
||||
// Normalize the output to make it compatible with CommonMark specs
|
||||
html = html.Replace("\r", "").Trim();
|
||||
html = html.Replace("\r\n", "\n").Replace(@"\r", @"\n").Trim();
|
||||
html = Regex.Replace(html, @"\s+</li>", "</li>");
|
||||
html = Regex.Replace(html, @"<li>\s+", "<li>");
|
||||
html = html.Normalize(NormalizationForm.FormKD);
|
||||
|
||||
@@ -27,6 +27,58 @@ Later in a text we are using HTML and it becomes an abbr tag HTML
|
||||
Console.WriteLine(result);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestListBug()
|
||||
{
|
||||
// TODO: Add this test back to the CommonMark specs
|
||||
var text = @"- item1
|
||||
- item2
|
||||
- item3
|
||||
- item4";
|
||||
TestParser.TestSpec(text, @"<ul>
|
||||
<li>item1
|
||||
<ul>
|
||||
<li>item2
|
||||
<ul>
|
||||
<li>item3
|
||||
<ul>
|
||||
<li>item4</li>
|
||||
</ul></li>
|
||||
</ul></li>
|
||||
</ul></li>
|
||||
</ul>
|
||||
");
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public void TestHtmlBug()
|
||||
{
|
||||
TestParser.TestSpec(@" # header1
|
||||
|
||||
<pre class='copy'>
|
||||
blabla
|
||||
</pre>
|
||||
|
||||
# header2
|
||||
", @"<h1>header1</h1>
|
||||
<pre class='copy'>
|
||||
blabla
|
||||
</pre>
|
||||
<h1>header2</h1>");
|
||||
}
|
||||
|
||||
|
||||
|
||||
[Test]
|
||||
public void TestBugAdvancaed()
|
||||
{
|
||||
TestParser.TestSpec(@"`https://{domain}/callbacks`
|
||||
#### HEADING
|
||||
Paragraph
|
||||
", "<p><code>https://{domain}/callbacks</code></p>\n<h4 id=\"heading\">HEADING</h4>\n<p>Paragraph</p>", "advanced");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSamePipelineAllExtensions()
|
||||
{
|
||||
|
||||
81
src/Markdig.Tests/TestPragmaLines.cs
Normal file
81
src/Markdig.Tests/TestPragmaLines.cs
Normal file
@@ -0,0 +1,81 @@
|
||||
// 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 Markdig.Renderers;
|
||||
using Markdig.Syntax;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace Markdig.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
public class TestPragmaLines
|
||||
{
|
||||
[Test]
|
||||
public void TestFindClosest()
|
||||
{
|
||||
var doc = Markdown.Parse(
|
||||
"test1\n" + // 0
|
||||
"\n" + // 1
|
||||
"test2\n" + // 2
|
||||
"\n" + // 3
|
||||
"test3\n" + // 4
|
||||
"\n" + // 5
|
||||
"test4\n" + // 6
|
||||
"\n" + // 7
|
||||
"# Heading\n" + // 8
|
||||
"\n" + // 9
|
||||
"Long para\n" + // 10
|
||||
"on multiple\n" + // 11
|
||||
"lines\n" + // 12
|
||||
"to check that\n" + // 13
|
||||
"lines are\n" + // 14
|
||||
"correctly \n" + // 15
|
||||
"found\n" + // 16
|
||||
"\n" + // 17
|
||||
"- item1\n" + // 18
|
||||
"- item2\n" + // 19
|
||||
"- item3\n" + // 20
|
||||
"\n" + // 21
|
||||
"This is a last paragraph\n" // 22
|
||||
, new MarkdownPipelineBuilder().UsePragmaLines().Build());
|
||||
|
||||
foreach (var exact in new int[] {0, 2, 4, 6, 8, 10, 18, 19, 20, 22})
|
||||
{
|
||||
Assert.AreEqual(exact, doc.FindClosestLine(exact));
|
||||
}
|
||||
|
||||
Assert.AreEqual(22, doc.FindClosestLine(23));
|
||||
|
||||
Assert.AreEqual(10, doc.FindClosestLine(11));
|
||||
Assert.AreEqual(10, doc.FindClosestLine(12));
|
||||
Assert.AreEqual(10, doc.FindClosestLine(13));
|
||||
Assert.AreEqual(18, doc.FindClosestLine(14)); // > 50% of the paragraph, we switch to next
|
||||
Assert.AreEqual(18, doc.FindClosestLine(15));
|
||||
Assert.AreEqual(18, doc.FindClosestLine(16));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestFindClosest1()
|
||||
{
|
||||
var text =
|
||||
"- item1\n" + // 0
|
||||
" - item11\n" + // 1
|
||||
" - item12\n" + // 2
|
||||
" - item121\n" + // 3
|
||||
" - item13\n" + // 4
|
||||
" - item131\n" + // 5
|
||||
" - item1311\n"; // 6
|
||||
|
||||
var pipeline = new MarkdownPipelineBuilder().UsePragmaLines().Build();
|
||||
var doc = Markdown.Parse(text, pipeline);
|
||||
|
||||
for (int exact = 0; exact < 7; exact++)
|
||||
{
|
||||
Assert.AreEqual(exact, doc.FindClosestLine(exact));
|
||||
}
|
||||
|
||||
Assert.AreEqual(6, doc.FindClosestLine(50));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -98,7 +98,7 @@ namespace Markdig.Extensions.DefinitionLists
|
||||
processor.Open(definitionItem);
|
||||
|
||||
// Update the end position
|
||||
currentDefinitionList.Span.End = processor.Line.End;
|
||||
currentDefinitionList.UpdateSpanEnd(processor.Line.End);
|
||||
|
||||
return BlockState.Continue;
|
||||
}
|
||||
|
||||
@@ -114,7 +114,7 @@ namespace Markdig.Extensions.Figures
|
||||
figure.Add(caption);
|
||||
}
|
||||
|
||||
figure.Span.End = line.End;
|
||||
figure.UpdateSpanEnd(line.End);
|
||||
|
||||
// Don't keep the last line
|
||||
return BlockState.BreakDiscard;
|
||||
@@ -123,7 +123,7 @@ namespace Markdig.Extensions.Figures
|
||||
// Reset the indentation to the column before the indent
|
||||
processor.GoToColumn(processor.ColumnBeforeIndent);
|
||||
|
||||
figure.Span.End = line.End;
|
||||
figure.UpdateSpanEnd(line.End);
|
||||
|
||||
return BlockState.Continue;
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ namespace Markdig.Extensions.Footers
|
||||
{
|
||||
processor.NextChar(); // Skip following space
|
||||
}
|
||||
block.Span.End = processor.Line.End;
|
||||
block.UpdateSpanEnd(processor.Line.End);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -249,7 +249,7 @@ namespace Markdig.Helpers
|
||||
endOfIndex = 0;
|
||||
for (int i = Start; i <= end; i++)
|
||||
{
|
||||
if (MatchLowercase(text, End, i))
|
||||
if (MatchLowercase(text, End, i - Start))
|
||||
{
|
||||
endOfIndex = i + text.Length;
|
||||
return true;
|
||||
|
||||
@@ -155,7 +155,7 @@ namespace Markdig
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uses the boostrap extension.
|
||||
/// Uses the bootstrap extension.
|
||||
/// </summary>
|
||||
/// <param name="pipeline">The pipeline.</param>
|
||||
/// <returns>The modified pipeline</returns>
|
||||
@@ -442,4 +442,4 @@ namespace Markdig
|
||||
return pipeline;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -216,7 +216,7 @@ namespace Markdig.Parsers
|
||||
// The line must contain only fence opening character followed only by whitespaces.
|
||||
if (count <=0 && !processor.IsCodeIndent && (c == '\0' || c.IsWhitespace()) && line.TrimEnd())
|
||||
{
|
||||
block.Span.End = line.Start - 1;
|
||||
block.UpdateSpanEnd(line.Start - 1);
|
||||
|
||||
// Don't keep the last line
|
||||
return BlockState.BreakDiscard;
|
||||
|
||||
@@ -185,35 +185,35 @@ namespace Markdig.Parsers
|
||||
case HtmlBlockType.Comment:
|
||||
if (line.Search("-->", out endof))
|
||||
{
|
||||
htmlBlock.Span.End = endof - 1;
|
||||
htmlBlock.UpdateSpanEnd(endof - 1);
|
||||
result = BlockState.Break;
|
||||
}
|
||||
break;
|
||||
case HtmlBlockType.CData:
|
||||
if (line.Search("]]>", out endof))
|
||||
{
|
||||
htmlBlock.Span.End = endof - 1;
|
||||
htmlBlock.UpdateSpanEnd(endof - 1);
|
||||
result = BlockState.Break;
|
||||
}
|
||||
break;
|
||||
case HtmlBlockType.ProcessingInstruction:
|
||||
if (line.Search("?>", out endof))
|
||||
{
|
||||
htmlBlock.Span.End = endof - 1;
|
||||
htmlBlock.UpdateSpanEnd(endof - 1);
|
||||
result = BlockState.Break;
|
||||
}
|
||||
break;
|
||||
case HtmlBlockType.DocumentType:
|
||||
if (line.Search(">", out endof))
|
||||
{
|
||||
htmlBlock.Span.End = endof - 1;
|
||||
htmlBlock.UpdateSpanEnd(endof - 1);
|
||||
result = BlockState.Break;
|
||||
}
|
||||
break;
|
||||
case HtmlBlockType.ScriptPreOrStyle:
|
||||
if (line.SearchLowercase("</script>", out endof) || line.SearchLowercase("</pre>", out endof) || line.SearchLowercase("</style>", out endof))
|
||||
{
|
||||
htmlBlock.Span.End = endof - 1;
|
||||
htmlBlock.UpdateSpanEnd(endof - 1);
|
||||
result = BlockState.Break;
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -48,7 +48,7 @@ namespace Markdig.Parsers
|
||||
}
|
||||
if (block != null)
|
||||
{
|
||||
block.Span.End = processor.Line.End;
|
||||
block.UpdateSpanEnd(processor.Line.End);
|
||||
}
|
||||
return BlockState.Continue;
|
||||
}
|
||||
|
||||
@@ -153,7 +153,7 @@ namespace Markdig.Parsers
|
||||
}
|
||||
|
||||
// Update list-item source end position
|
||||
listItem.Span.End = state.Line.End;
|
||||
listItem.UpdateSpanEnd(state.Line.End);
|
||||
|
||||
return BlockState.Continue;
|
||||
}
|
||||
@@ -170,11 +170,11 @@ namespace Markdig.Parsers
|
||||
{
|
||||
if (state.Indent > columWidth && state.IsCodeIndent)
|
||||
{
|
||||
state.GoToColumn(columWidth);
|
||||
state.GoToColumn(state.ColumnBeforeIndent + columWidth);
|
||||
}
|
||||
|
||||
// Update list-item source end position
|
||||
listItem.Span.End = state.Line.End;
|
||||
listItem.UpdateSpanEnd(state.Line.End);
|
||||
|
||||
return BlockState.Continue;
|
||||
}
|
||||
@@ -352,11 +352,11 @@ namespace Markdig.Parsers
|
||||
isLastListItem = false;
|
||||
}
|
||||
|
||||
// Update end-position for the list
|
||||
if (listBlock.Count > 0)
|
||||
{
|
||||
listBlock.Span.End = listBlock[listBlock.Count - 1].Span.End;
|
||||
}
|
||||
//// Update end-position for the list
|
||||
//if (listBlock.Count > 0)
|
||||
//{
|
||||
// listBlock.Span.End = listBlock[listBlock.Count - 1].Span.End;
|
||||
//}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ namespace Markdig.Parsers
|
||||
return TryParseSetexHeading(processor, block);
|
||||
}
|
||||
|
||||
block.Span.End = processor.Line.End;
|
||||
block.UpdateSpanEnd(processor.Line.End);
|
||||
return BlockState.Continue;
|
||||
}
|
||||
|
||||
@@ -139,7 +139,7 @@ namespace Markdig.Parsers
|
||||
return BlockState.BreakDiscard;
|
||||
}
|
||||
|
||||
block.Span.End = state.Line.End;
|
||||
block.UpdateSpanEnd(state.Line.End);
|
||||
|
||||
return BlockState.Continue;
|
||||
}
|
||||
|
||||
@@ -61,7 +61,6 @@ namespace Markdig.Parsers
|
||||
var c = processor.CurrentChar;
|
||||
if (c != quote.QuoteChar)
|
||||
{
|
||||
block.Span.End = processor.Start - 1;
|
||||
return processor.IsBlankLine ? BlockState.BreakDiscard : BlockState.None;
|
||||
}
|
||||
|
||||
@@ -71,18 +70,8 @@ namespace Markdig.Parsers
|
||||
processor.NextChar(); // Skip following space
|
||||
}
|
||||
|
||||
block.Span.End = processor.Line.End;
|
||||
block.UpdateSpanEnd(processor.Line.End);
|
||||
return BlockState.Continue;
|
||||
}
|
||||
|
||||
public override bool Close(BlockProcessor processor, Block block)
|
||||
{
|
||||
var quoteBlock = block as QuoteBlock;
|
||||
if (quoteBlock?.LastChild != null)
|
||||
{
|
||||
quoteBlock.Span.End = quoteBlock.LastChild.Span.End;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,6 @@ namespace Markdig
|
||||
{
|
||||
public static partial class Markdown
|
||||
{
|
||||
public const string Version = "0.5.7";
|
||||
public const string Version = "0.5.11";
|
||||
}
|
||||
}
|
||||
@@ -118,7 +118,7 @@ namespace Markdig.Renderers.Html
|
||||
}
|
||||
if (htmlAttributes.Classes == null)
|
||||
{
|
||||
htmlAttributes.Classes = shared ? Classes : new List<string>(Classes);
|
||||
htmlAttributes.Classes = shared ? Classes : Classes != null ? new List<string>(Classes) : null;
|
||||
}
|
||||
else if (Classes != null)
|
||||
{
|
||||
@@ -127,7 +127,7 @@ namespace Markdig.Renderers.Html
|
||||
|
||||
if (htmlAttributes.Properties == null)
|
||||
{
|
||||
htmlAttributes.Properties = shared ? Properties : new List<KeyValuePair<string, string>>(Properties);
|
||||
htmlAttributes.Properties = shared ? Properties : Properties != null ? new List<KeyValuePair<string, string>>(Properties) : null;
|
||||
}
|
||||
else if (Properties != null)
|
||||
{
|
||||
@@ -140,6 +140,10 @@ namespace Markdig.Renderers.Html
|
||||
}
|
||||
else
|
||||
{
|
||||
if (htmlAttributes.Properties == null)
|
||||
{
|
||||
htmlAttributes.Properties = new List<KeyValuePair<string, string>>();
|
||||
}
|
||||
htmlAttributes.Properties.AddRange(Properties);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,5 +75,19 @@ namespace Markdig.Syntax
|
||||
{
|
||||
ProcessInlinesEnd?.Invoke(state, null);
|
||||
}
|
||||
|
||||
public void UpdateSpanEnd(int spanEnd)
|
||||
{
|
||||
// Update parent spans
|
||||
var parent = this;
|
||||
while (parent != null)
|
||||
{
|
||||
if (spanEnd > parent.Span.End)
|
||||
{
|
||||
parent.Span.End = spanEnd;
|
||||
}
|
||||
parent = parent.Parent;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
138
src/Markdig/Syntax/BlockExtensions.cs
Normal file
138
src/Markdig/Syntax/BlockExtensions.cs
Normal file
@@ -0,0 +1,138 @@
|
||||
// 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.Syntax
|
||||
{
|
||||
/// <summary>
|
||||
/// Extensions for <see cref="Block"/>
|
||||
/// </summary>
|
||||
public static class BlockExtensions
|
||||
{
|
||||
// TODO: Add test for this code
|
||||
|
||||
public static Block FindBlockAtPosition(this Block rootBlock, int position)
|
||||
{
|
||||
var contains = rootBlock.CompareToPosition(position) == 0;
|
||||
var blocks = rootBlock as ContainerBlock;
|
||||
if (blocks == null || blocks.Count == 0 || !contains)
|
||||
{
|
||||
return contains ? rootBlock : null;
|
||||
}
|
||||
|
||||
var lowerIndex = 0;
|
||||
var upperIndex = blocks.Count - 1;
|
||||
|
||||
// binary search on lines
|
||||
Block block = null;
|
||||
while (lowerIndex <= upperIndex)
|
||||
{
|
||||
int midIndex = (upperIndex - lowerIndex) / 2 + lowerIndex;
|
||||
block = blocks[midIndex];
|
||||
int comparison = block.CompareToPosition(position);
|
||||
if (comparison == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
block = null;
|
||||
if (comparison < 0)
|
||||
lowerIndex = midIndex + 1;
|
||||
else
|
||||
upperIndex = midIndex - 1;
|
||||
}
|
||||
|
||||
if (block == null)
|
||||
{
|
||||
return rootBlock;
|
||||
}
|
||||
|
||||
// Recursively go deep into the block
|
||||
return FindBlockAtPosition(block, position);
|
||||
}
|
||||
|
||||
|
||||
public static int FindClosestLine(this MarkdownDocument root, int line)
|
||||
{
|
||||
var closestBlock = root.FindClosestBlock(line);
|
||||
return closestBlock?.Line ?? 0;
|
||||
}
|
||||
|
||||
public static Block FindClosestBlock(this Block rootBlock, int line)
|
||||
{
|
||||
var blocks = rootBlock as ContainerBlock;
|
||||
if (blocks == null || blocks.Count == 0)
|
||||
{
|
||||
return rootBlock.Line == line ? rootBlock : null;
|
||||
}
|
||||
|
||||
var lowerIndex = 0;
|
||||
var upperIndex = blocks.Count - 1;
|
||||
|
||||
// binary search on lines
|
||||
while (lowerIndex <= upperIndex)
|
||||
{
|
||||
int midIndex = (upperIndex - lowerIndex) / 2 + lowerIndex;
|
||||
var block = blocks[midIndex];
|
||||
int comparison = block.Line.CompareTo(line);
|
||||
if (comparison == 0)
|
||||
{
|
||||
return block;
|
||||
}
|
||||
if (comparison < 0)
|
||||
lowerIndex = midIndex + 1;
|
||||
else
|
||||
upperIndex = midIndex - 1;
|
||||
}
|
||||
|
||||
// If we are between two lines, try to find the best spot
|
||||
if (lowerIndex > 0 && lowerIndex < blocks.Count)
|
||||
{
|
||||
var prevBlock = blocks[lowerIndex - 1].FindClosestBlock(line) ?? blocks[lowerIndex - 1];
|
||||
var nextBlock = blocks[lowerIndex].FindClosestBlock(line) ?? blocks[lowerIndex];
|
||||
|
||||
if (prevBlock.Line == line)
|
||||
{
|
||||
return prevBlock;
|
||||
}
|
||||
|
||||
if (nextBlock.Line == line)
|
||||
{
|
||||
return nextBlock;
|
||||
}
|
||||
|
||||
// we calculate the position of the current line relative to the line found and previous line
|
||||
var prevLine = prevBlock.Line;
|
||||
var nextLine = nextBlock.Line;
|
||||
|
||||
var middle = (line - prevLine) * 1.0 / (nextLine - prevLine);
|
||||
// If relative position < 0.5, we select the previous line, otherwise we select the line found
|
||||
return middle < 0.5 ? prevBlock : nextBlock;
|
||||
}
|
||||
|
||||
if (lowerIndex == 0)
|
||||
{
|
||||
var prevBlock = blocks[lowerIndex].FindClosestBlock(line) ?? blocks[lowerIndex];
|
||||
return prevBlock;
|
||||
}
|
||||
|
||||
if (lowerIndex == blocks.Count)
|
||||
{
|
||||
var prevBlock = blocks[lowerIndex - 1].FindClosestBlock(line) ?? blocks[lowerIndex - 1];
|
||||
return prevBlock;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public static bool ContainsPosition(this Block block, int position)
|
||||
{
|
||||
return CompareToPosition(block, position) == 0;
|
||||
}
|
||||
|
||||
public static int CompareToPosition(this Block block, int position)
|
||||
{
|
||||
return position < block.Span.Start ? 1 : position > block.Span.End + 1 ? -1 : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -69,10 +69,7 @@ namespace Markdig.Syntax
|
||||
children[Count++] = item;
|
||||
item.Parent = this;
|
||||
|
||||
if (item.Span.End > Span.End)
|
||||
{
|
||||
Span.End = item.Span.End;
|
||||
}
|
||||
UpdateSpanEnd(item.Span.End);
|
||||
}
|
||||
|
||||
private void EnsureCapacity(int min)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"title": "Markdig",
|
||||
"version": "0.5.7",
|
||||
"version": "0.5.11",
|
||||
"authors": [ "Alexandre Mutel" ],
|
||||
"description": "A fast, powerfull, CommonMark compliant, extensible Markdown processor for .NET",
|
||||
"copyright": "Alexandre Mutel",
|
||||
@@ -11,7 +11,7 @@
|
||||
"projectUrl": "https://github.com/lunet-io/markdig",
|
||||
"iconUrl": "https://raw.githubusercontent.com/lunet-io/markdig/master/img/markdig.png",
|
||||
"requireLicenseAcceptance": false,
|
||||
"releaseNotes": "> 0.5.7\n- Fix issue with generic attributes extension not working properly and breaking parsing\n> 0.5.6\n- Improve pragma line output\n- Make MarkdownPipeline.Setup(renderer) public.\n> 0.5.5\n- Add same github class for task lists\n- Add pragma lines extension\n> 0.5.4:\nFix bug in HTML block parsing which could break parsing of remaining document",
|
||||
"releaseNotes": "- Fix ArgumentNullException with MediaLinks extension in HtmlAttributes.CopyTo method\n",
|
||||
"tags": [ "Markdown CommonMark md html md2html" ]
|
||||
},
|
||||
"configurations": {
|
||||
|
||||
Reference in New Issue
Block a user