mirror of
https://github.com/xoofx/markdig.git
synced 2026-02-04 05:44:50 +00:00
Merge pull request #521 from MihaZupan/random-perf-2
Random perf improvements
This commit is contained in:
@@ -11,11 +11,14 @@ namespace Markdig.Tests
|
||||
[Test]
|
||||
public void TestToHtml()
|
||||
{
|
||||
string html = Markdown.ToHtml("This is a text with some *emphasis*");
|
||||
Assert.AreEqual("<p>This is a text with some <em>emphasis</em></p>\n", html);
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
string html = Markdown.ToHtml("This is a text with some *emphasis*");
|
||||
Assert.AreEqual("<p>This is a text with some <em>emphasis</em></p>\n", html);
|
||||
|
||||
html = Markdown.ToHtml("This is a text with a https://link.tld/");
|
||||
Assert.AreNotEqual("<p>This is a text with a <a href=\"https://link.tld/\">https://link.tld/</a></p>\n", html);
|
||||
html = Markdown.ToHtml("This is a text with a https://link.tld/");
|
||||
Assert.AreNotEqual("<p>This is a text with a <a href=\"https://link.tld/\">https://link.tld/</a></p>\n", html);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -24,49 +27,66 @@ namespace Markdig.Tests
|
||||
var pipeline = new MarkdownPipelineBuilder()
|
||||
.Build();
|
||||
|
||||
string html = Markdown.ToHtml("This is a text with some *emphasis*", pipeline);
|
||||
Assert.AreEqual("<p>This is a text with some <em>emphasis</em></p>\n", html);
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
string html = Markdown.ToHtml("This is a text with some *emphasis*", pipeline);
|
||||
Assert.AreEqual("<p>This is a text with some <em>emphasis</em></p>\n", html);
|
||||
|
||||
html = Markdown.ToHtml("This is a text with a https://link.tld/", pipeline);
|
||||
Assert.AreNotEqual("<p>This is a text with a <a href=\"https://link.tld/\">https://link.tld/</a></p>\n", html);
|
||||
html = Markdown.ToHtml("This is a text with a https://link.tld/", pipeline);
|
||||
Assert.AreNotEqual("<p>This is a text with a <a href=\"https://link.tld/\">https://link.tld/</a></p>\n", html);
|
||||
}
|
||||
|
||||
pipeline = new MarkdownPipelineBuilder()
|
||||
.UseAdvancedExtensions()
|
||||
.Build();
|
||||
|
||||
html = Markdown.ToHtml("This is a text with a https://link.tld/", pipeline);
|
||||
Assert.AreEqual("<p>This is a text with a <a href=\"https://link.tld/\">https://link.tld/</a></p>\n", html);
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
string html = Markdown.ToHtml("This is a text with a https://link.tld/", pipeline);
|
||||
Assert.AreEqual("<p>This is a text with a <a href=\"https://link.tld/\">https://link.tld/</a></p>\n", html);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestToHtmlWithWriter()
|
||||
{
|
||||
StringWriter writer = new StringWriter();
|
||||
var writer = new StringWriter();
|
||||
|
||||
_ = Markdown.ToHtml("This is a text with some *emphasis*", writer);
|
||||
string html = writer.ToString();
|
||||
Assert.AreEqual("<p>This is a text with some <em>emphasis</em></p>\n", html);
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
_ = Markdown.ToHtml("This is a text with some *emphasis*", writer);
|
||||
string html = writer.ToString();
|
||||
Assert.AreEqual("<p>This is a text with some <em>emphasis</em></p>\n", html);
|
||||
writer.GetStringBuilder().Length = 0;
|
||||
}
|
||||
|
||||
writer = new StringWriter();
|
||||
var pipeline = new MarkdownPipelineBuilder()
|
||||
.UseAdvancedExtensions()
|
||||
.Build();
|
||||
|
||||
_ = Markdown.ToHtml("This is a text with a https://link.tld/", writer, pipeline);
|
||||
html = writer.ToString();
|
||||
Assert.AreEqual("<p>This is a text with a <a href=\"https://link.tld/\">https://link.tld/</a></p>\n", html);
|
||||
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
_ = Markdown.ToHtml("This is a text with a https://link.tld/", writer, pipeline);
|
||||
string html = writer.ToString();
|
||||
Assert.AreEqual("<p>This is a text with a <a href=\"https://link.tld/\">https://link.tld/</a></p>\n", html);
|
||||
writer.GetStringBuilder().Length = 0;
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestConvert()
|
||||
{
|
||||
StringWriter writer = new StringWriter();
|
||||
HtmlRenderer renderer = new HtmlRenderer(writer);
|
||||
var writer = new StringWriter();
|
||||
var renderer = new HtmlRenderer(writer);
|
||||
|
||||
_ = Markdown.Convert("This is a text with some *emphasis*", renderer);
|
||||
string html = writer.ToString();
|
||||
Assert.AreEqual("<p>This is a text with some <em>emphasis</em></p>\n", html);
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
_ = Markdown.Convert("This is a text with some *emphasis*", renderer);
|
||||
string html = writer.ToString();
|
||||
Assert.AreEqual("<p>This is a text with some <em>emphasis</em></p>\n", html);
|
||||
writer.GetStringBuilder().Length = 0;
|
||||
}
|
||||
|
||||
writer = new StringWriter();
|
||||
renderer = new HtmlRenderer(writer);
|
||||
@@ -74,9 +94,13 @@ namespace Markdig.Tests
|
||||
.UseAdvancedExtensions()
|
||||
.Build();
|
||||
|
||||
_ = Markdown.Convert("This is a text with a https://link.tld/", renderer, pipeline);
|
||||
html = writer.ToString();
|
||||
Assert.AreEqual("<p>This is a text with a <a href=\"https://link.tld/\">https://link.tld/</a></p>\n", html);
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
_ = Markdown.Convert("This is a text with a https://link.tld/", renderer, pipeline);
|
||||
string html = writer.ToString();
|
||||
Assert.AreEqual("<p>This is a text with a <a href=\"https://link.tld/\">https://link.tld/</a></p>\n", html);
|
||||
writer.GetStringBuilder().Length = 0;
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -88,62 +112,77 @@ namespace Markdig.Tests
|
||||
.UsePreciseSourceLocation()
|
||||
.Build();
|
||||
|
||||
MarkdownDocument document = Markdown.Parse(markdown, pipeline);
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
MarkdownDocument document = Markdown.Parse(markdown, pipeline);
|
||||
|
||||
Assert.AreEqual(1, document.LineCount);
|
||||
Assert.AreEqual(markdown.Length, document.Span.Length);
|
||||
Assert.AreEqual(1, document.LineStartIndexes.Count);
|
||||
Assert.AreEqual(0, document.LineStartIndexes[0]);
|
||||
Assert.AreEqual(1, document.LineCount);
|
||||
Assert.AreEqual(markdown.Length, document.Span.Length);
|
||||
Assert.AreEqual(1, document.LineStartIndexes.Count);
|
||||
Assert.AreEqual(0, document.LineStartIndexes[0]);
|
||||
|
||||
Assert.AreEqual(1, document.Count);
|
||||
ParagraphBlock paragraph = document[0] as ParagraphBlock;
|
||||
Assert.NotNull(paragraph);
|
||||
Assert.AreEqual(markdown.Length, paragraph.Span.Length);
|
||||
LiteralInline literal = paragraph.Inline.FirstChild as LiteralInline;
|
||||
Assert.NotNull(literal);
|
||||
Assert.AreEqual("This is a text with some ", literal.ToString());
|
||||
EmphasisInline emphasis = literal.NextSibling as EmphasisInline;
|
||||
Assert.NotNull(emphasis);
|
||||
Assert.AreEqual("*emphasis*".Length, emphasis.Span.Length);
|
||||
LiteralInline emphasisLiteral = emphasis.FirstChild as LiteralInline;
|
||||
Assert.NotNull(emphasisLiteral);
|
||||
Assert.AreEqual("emphasis", emphasisLiteral.ToString());
|
||||
Assert.Null(emphasisLiteral.NextSibling);
|
||||
Assert.Null(emphasis.NextSibling);
|
||||
Assert.AreEqual(1, document.Count);
|
||||
ParagraphBlock paragraph = document[0] as ParagraphBlock;
|
||||
Assert.NotNull(paragraph);
|
||||
Assert.AreEqual(markdown.Length, paragraph.Span.Length);
|
||||
LiteralInline literal = paragraph.Inline.FirstChild as LiteralInline;
|
||||
Assert.NotNull(literal);
|
||||
Assert.AreEqual("This is a text with some ", literal.ToString());
|
||||
EmphasisInline emphasis = literal.NextSibling as EmphasisInline;
|
||||
Assert.NotNull(emphasis);
|
||||
Assert.AreEqual("*emphasis*".Length, emphasis.Span.Length);
|
||||
LiteralInline emphasisLiteral = emphasis.FirstChild as LiteralInline;
|
||||
Assert.NotNull(emphasisLiteral);
|
||||
Assert.AreEqual("emphasis", emphasisLiteral.ToString());
|
||||
Assert.Null(emphasisLiteral.NextSibling);
|
||||
Assert.Null(emphasis.NextSibling);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNormalize()
|
||||
{
|
||||
string normalized = Markdown.Normalize("Heading\n=======");
|
||||
Assert.AreEqual("# Heading", normalized);
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
string normalized = Markdown.Normalize("Heading\n=======");
|
||||
Assert.AreEqual("# Heading", normalized);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNormalizeWithWriter()
|
||||
{
|
||||
StringWriter writer = new StringWriter();
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
var writer = new StringWriter();
|
||||
|
||||
_ = Markdown.Normalize("Heading\n=======", writer);
|
||||
string normalized = writer.ToString();
|
||||
Assert.AreEqual("# Heading", normalized);
|
||||
_ = Markdown.Normalize("Heading\n=======", writer);
|
||||
string normalized = writer.ToString();
|
||||
Assert.AreEqual("# Heading", normalized);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestToPlainText()
|
||||
{
|
||||
string plainText = Markdown.ToPlainText("*Hello*, [world](http://example.com)!");
|
||||
Assert.AreEqual("Hello, world!\n", plainText);
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
string plainText = Markdown.ToPlainText("*Hello*, [world](http://example.com)!");
|
||||
Assert.AreEqual("Hello, world!\n", plainText);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestToPlainTextWithWriter()
|
||||
{
|
||||
StringWriter writer = new StringWriter();
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
var writer = new StringWriter();
|
||||
|
||||
_ = Markdown.ToPlainText("*Hello*, [world](http://example.com)!", writer);
|
||||
string plainText = writer.ToString();
|
||||
Assert.AreEqual("Hello, world!\n", plainText);
|
||||
_ = Markdown.ToPlainText("*Hello*, [world](http://example.com)!", writer);
|
||||
string plainText = writer.ToString();
|
||||
Assert.AreEqual("Hello, world!\n", plainText);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,25 @@ namespace Markdig
|
||||
{
|
||||
public static readonly string Version = ((AssemblyFileVersionAttribute) typeof(Markdown).Assembly.GetCustomAttributes(typeof(AssemblyFileVersionAttribute), false)[0]).Version;
|
||||
|
||||
private static readonly MarkdownPipeline _defaultPipeline = new MarkdownPipelineBuilder().Build();
|
||||
private static readonly MarkdownPipeline _defaultTrackTriviaPipeline = new MarkdownPipelineBuilder().EnableTrackTrivia().Build();
|
||||
|
||||
private static MarkdownPipeline GetPipeline(MarkdownPipeline? pipeline, string markdown)
|
||||
{
|
||||
if (pipeline is null)
|
||||
{
|
||||
return _defaultPipeline;
|
||||
}
|
||||
|
||||
var selfPipeline = pipeline.Extensions.Find<SelfPipelineExtension>();
|
||||
if (selfPipeline is not null)
|
||||
{
|
||||
return selfPipeline.CreatePipelineFromInput(markdown);
|
||||
}
|
||||
return pipeline;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Normalizes the specified markdown to a normalized markdown text.
|
||||
/// </summary>
|
||||
@@ -49,14 +68,15 @@ namespace Markdig
|
||||
/// <returns>A normalized markdown text.</returns>
|
||||
public static MarkdownDocument Normalize(string markdown, TextWriter writer, NormalizeOptions? options = null, MarkdownPipeline? pipeline = null, MarkdownParserContext? context = null)
|
||||
{
|
||||
pipeline ??= new MarkdownPipelineBuilder().Build();
|
||||
pipeline = CheckForSelfPipeline(pipeline, markdown);
|
||||
if (markdown is null) ThrowHelper.ArgumentNullException_markdown();
|
||||
|
||||
pipeline = GetPipeline(pipeline, markdown);
|
||||
|
||||
var document = MarkdownParser.Parse(markdown, pipeline, context);
|
||||
|
||||
// We override the renderer with our own writer
|
||||
var renderer = new NormalizeRenderer(writer, options);
|
||||
pipeline.Setup(renderer);
|
||||
|
||||
var document = Parse(markdown, pipeline, context);
|
||||
renderer.Render(document);
|
||||
writer.Flush();
|
||||
|
||||
@@ -74,18 +94,12 @@ namespace Markdig
|
||||
public static string ToHtml(string markdown, MarkdownPipeline? pipeline = null, MarkdownParserContext? context = null)
|
||||
{
|
||||
if (markdown is null) ThrowHelper.ArgumentNullException_markdown();
|
||||
pipeline ??= new MarkdownPipelineBuilder().Build();
|
||||
pipeline = CheckForSelfPipeline(pipeline, markdown);
|
||||
|
||||
var renderer = pipeline.GetCacheableHtmlRenderer();
|
||||
pipeline = GetPipeline(pipeline, markdown);
|
||||
|
||||
var document = Parse(markdown, pipeline, context);
|
||||
renderer.Render(document);
|
||||
renderer.Writer.Flush();
|
||||
var document = MarkdownParser.Parse(markdown, pipeline, context);
|
||||
|
||||
string html = renderer.Writer.ToString()!;
|
||||
pipeline.ReleaseCacheableHtmlRenderer(renderer);
|
||||
return html;
|
||||
return ToHtml(document, pipeline);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -95,19 +109,19 @@ namespace Markdig
|
||||
/// <param name="pipeline">The pipeline used for the conversion.</param>
|
||||
/// <returns>The result of the conversion</returns>
|
||||
/// <exception cref="ArgumentNullException">if markdown document variable is null</exception>
|
||||
public static string ToHtml(this MarkdownDocument document, MarkdownPipeline pipeline = null)
|
||||
public static string ToHtml(this MarkdownDocument document, MarkdownPipeline? pipeline = null)
|
||||
{
|
||||
if (document == null) ThrowHelper.ArgumentNullException(nameof(document));
|
||||
pipeline ??= new MarkdownPipelineBuilder().Build();
|
||||
if (document is null) ThrowHelper.ArgumentNullException(nameof(document));
|
||||
|
||||
var renderer = pipeline.GetCacheableHtmlRenderer();
|
||||
pipeline ??= _defaultPipeline;
|
||||
|
||||
using var rentedRenderer = pipeline.RentHtmlRenderer();
|
||||
HtmlRenderer renderer = rentedRenderer.Instance;
|
||||
|
||||
renderer.Render(document);
|
||||
renderer.Writer.Flush();
|
||||
|
||||
string html = renderer.Writer.ToString();
|
||||
pipeline.ReleaseCacheableHtmlRenderer(renderer);
|
||||
return html;
|
||||
return renderer.Writer.ToString() ?? string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -123,14 +137,14 @@ namespace Markdig
|
||||
{
|
||||
if (markdown is null) ThrowHelper.ArgumentNullException_markdown();
|
||||
if (writer is null) ThrowHelper.ArgumentNullException_writer();
|
||||
pipeline ??= new MarkdownPipelineBuilder().Build();
|
||||
pipeline = CheckForSelfPipeline(pipeline, markdown);
|
||||
|
||||
// We override the renderer with our own writer
|
||||
var renderer = new HtmlRenderer(writer);
|
||||
pipeline.Setup(renderer);
|
||||
pipeline = GetPipeline(pipeline, markdown);
|
||||
|
||||
var document = MarkdownParser.Parse(markdown, pipeline, context);
|
||||
|
||||
using var rentedRenderer = pipeline.RentHtmlRenderer(writer);
|
||||
HtmlRenderer renderer = rentedRenderer.Instance;
|
||||
|
||||
var document = Parse(markdown, pipeline, context);
|
||||
renderer.Render(document);
|
||||
writer.Flush();
|
||||
|
||||
@@ -149,10 +163,11 @@ namespace Markdig
|
||||
{
|
||||
if (markdown is null) ThrowHelper.ArgumentNullException_markdown();
|
||||
if (renderer is null) ThrowHelper.ArgumentNullException(nameof(renderer));
|
||||
pipeline ??= new MarkdownPipelineBuilder().Build();
|
||||
|
||||
pipeline = CheckForSelfPipeline(pipeline, markdown);
|
||||
var document = Parse(markdown, pipeline, context);
|
||||
pipeline = GetPipeline(pipeline, markdown);
|
||||
|
||||
var document = MarkdownParser.Parse(markdown, pipeline, context);
|
||||
|
||||
pipeline.Setup(renderer);
|
||||
return renderer.Render(document);
|
||||
}
|
||||
@@ -168,9 +183,7 @@ namespace Markdig
|
||||
{
|
||||
if (markdown is null) ThrowHelper.ArgumentNullException_markdown();
|
||||
|
||||
MarkdownPipeline? pipeline = trackTrivia ? new MarkdownPipelineBuilder()
|
||||
.EnableTrackTrivia()
|
||||
.Build() : null;
|
||||
MarkdownPipeline? pipeline = trackTrivia ? _defaultTrackTriviaPipeline : null;
|
||||
|
||||
return Parse(markdown, pipeline);
|
||||
}
|
||||
@@ -186,22 +199,12 @@ namespace Markdig
|
||||
public static MarkdownDocument Parse(string markdown, MarkdownPipeline? pipeline, MarkdownParserContext? context = null)
|
||||
{
|
||||
if (markdown is null) ThrowHelper.ArgumentNullException_markdown();
|
||||
pipeline ??= new MarkdownPipelineBuilder().Build();
|
||||
|
||||
pipeline = CheckForSelfPipeline(pipeline, markdown);
|
||||
pipeline = GetPipeline(pipeline, markdown);
|
||||
|
||||
return MarkdownParser.Parse(markdown, pipeline, context);
|
||||
}
|
||||
|
||||
private static MarkdownPipeline CheckForSelfPipeline(MarkdownPipeline pipeline, string markdown)
|
||||
{
|
||||
var selfPipeline = pipeline.Extensions.Find<SelfPipelineExtension>();
|
||||
if (selfPipeline != null)
|
||||
{
|
||||
return selfPipeline.CreatePipelineFromInput(markdown);
|
||||
}
|
||||
return pipeline;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a Markdown string to Plain text and output to the specified writer.
|
||||
/// </summary>
|
||||
@@ -213,10 +216,12 @@ namespace Markdig
|
||||
/// <exception cref="ArgumentNullException">if reader or writer variable are null</exception>
|
||||
public static MarkdownDocument ToPlainText(string markdown, TextWriter writer, MarkdownPipeline? pipeline = null, MarkdownParserContext? context = null)
|
||||
{
|
||||
if (markdown == null) ThrowHelper.ArgumentNullException_markdown();
|
||||
if (writer == null) ThrowHelper.ArgumentNullException_writer();
|
||||
pipeline ??= new MarkdownPipelineBuilder().Build();
|
||||
pipeline = CheckForSelfPipeline(pipeline, markdown);
|
||||
if (markdown is null) ThrowHelper.ArgumentNullException_markdown();
|
||||
if (writer is null) ThrowHelper.ArgumentNullException_writer();
|
||||
|
||||
pipeline = GetPipeline(pipeline, markdown);
|
||||
|
||||
var document = MarkdownParser.Parse(markdown, pipeline, context);
|
||||
|
||||
// We override the renderer with our own writer
|
||||
var renderer = new HtmlRenderer(writer)
|
||||
@@ -227,7 +232,6 @@ namespace Markdig
|
||||
};
|
||||
pipeline.Setup(renderer);
|
||||
|
||||
var document = Parse(markdown, pipeline, context);
|
||||
renderer.Render(document);
|
||||
writer.Flush();
|
||||
|
||||
@@ -244,7 +248,7 @@ namespace Markdig
|
||||
/// <exception cref="ArgumentNullException">if markdown variable is null</exception>
|
||||
public static string ToPlainText(string markdown, MarkdownPipeline? pipeline = null, MarkdownParserContext? context = null)
|
||||
{
|
||||
if (markdown == null) ThrowHelper.ArgumentNullException_markdown();
|
||||
if (markdown is null) ThrowHelper.ArgumentNullException_markdown();
|
||||
var writer = new StringWriter();
|
||||
ToPlainText(markdown, writer, pipeline, context);
|
||||
return writer.ToString();
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using Markdig.Helpers;
|
||||
using Markdig.Parsers;
|
||||
using Markdig.Renderers;
|
||||
@@ -68,40 +69,76 @@ namespace Markdig
|
||||
}
|
||||
|
||||
|
||||
private HtmlRendererCache _rendererCache = null;
|
||||
private HtmlRendererCache _rendererCache, _rendererCacheForCustomWriter;
|
||||
|
||||
internal HtmlRenderer GetCacheableHtmlRenderer()
|
||||
internal RentedHtmlRenderer RentHtmlRenderer(TextWriter writer = null)
|
||||
{
|
||||
if (_rendererCache is null)
|
||||
HtmlRendererCache cache = writer is null
|
||||
? _rendererCache ??= new HtmlRendererCache(this, customWriter: false)
|
||||
: _rendererCacheForCustomWriter ??= new HtmlRendererCache(this, customWriter: true);
|
||||
|
||||
HtmlRenderer renderer = cache.Get();
|
||||
|
||||
if (writer is not null)
|
||||
{
|
||||
_rendererCache = new HtmlRendererCache
|
||||
{
|
||||
OnNewInstanceCreated = Setup
|
||||
};
|
||||
renderer.Writer = writer;
|
||||
}
|
||||
return _rendererCache.Get();
|
||||
}
|
||||
internal void ReleaseCacheableHtmlRenderer(HtmlRenderer renderer)
|
||||
{
|
||||
_rendererCache.Release(renderer);
|
||||
|
||||
return new RentedHtmlRenderer(cache, renderer);
|
||||
}
|
||||
|
||||
private sealed class HtmlRendererCache : ObjectCache<HtmlRenderer>
|
||||
internal sealed class HtmlRendererCache : ObjectCache<HtmlRenderer>
|
||||
{
|
||||
public Action<HtmlRenderer> OnNewInstanceCreated;
|
||||
private const int InitialCapacity = 1024;
|
||||
|
||||
private static readonly StringWriter _dummyWriter = new();
|
||||
|
||||
private readonly MarkdownPipeline _pipeline;
|
||||
private readonly bool _customWriter;
|
||||
|
||||
public HtmlRendererCache(MarkdownPipeline pipeline, bool customWriter = false)
|
||||
{
|
||||
_pipeline = pipeline;
|
||||
_customWriter = customWriter;
|
||||
}
|
||||
|
||||
protected override HtmlRenderer NewInstance()
|
||||
{
|
||||
var writer = new StringWriter();
|
||||
var writer = _customWriter ? _dummyWriter : new StringWriter(new StringBuilder(InitialCapacity));
|
||||
var renderer = new HtmlRenderer(writer);
|
||||
OnNewInstanceCreated(renderer);
|
||||
_pipeline.Setup(renderer);
|
||||
return renderer;
|
||||
}
|
||||
|
||||
protected override void Reset(HtmlRenderer instance)
|
||||
{
|
||||
instance.Reset();
|
||||
instance.ResetInternal();
|
||||
|
||||
if (_customWriter)
|
||||
{
|
||||
instance.Writer = _dummyWriter;
|
||||
}
|
||||
else
|
||||
{
|
||||
((StringWriter)instance.Writer).GetStringBuilder().Length = 0;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
internal readonly struct RentedHtmlRenderer : IDisposable
|
||||
{
|
||||
private readonly HtmlRendererCache _cache;
|
||||
public readonly HtmlRenderer Instance;
|
||||
|
||||
internal RentedHtmlRenderer(HtmlRendererCache cache, HtmlRenderer renderer)
|
||||
{
|
||||
_cache = cache;
|
||||
Instance = renderer;
|
||||
}
|
||||
|
||||
public void Dispose() => _cache.Release(Instance);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -155,28 +155,19 @@ namespace Markdig.Parsers
|
||||
return text.Replace('\0', CharHelper.ReplacementChar);
|
||||
}
|
||||
|
||||
private sealed class ContainerItemCache : DefaultObjectCache<ContainerItem>
|
||||
{
|
||||
protected override void Reset(ContainerItem instance)
|
||||
{
|
||||
instance.Container = null;
|
||||
instance.Index = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessInlines()
|
||||
{
|
||||
// "stackless" processor
|
||||
var cache = new ContainerItemCache();
|
||||
var blocks = new Stack<ContainerItem>();
|
||||
int blockCount = 1;
|
||||
var blocks = new ContainerItem[4];
|
||||
|
||||
// TODO: Use an ObjectCache for ContainerItem
|
||||
blocks.Push(new ContainerItem(document));
|
||||
blocks[0] = new ContainerItem(document);
|
||||
document.OnProcessInlinesBegin(inlineProcessor);
|
||||
while (blocks.Count > 0)
|
||||
|
||||
while (blockCount != 0)
|
||||
{
|
||||
process_new_block:
|
||||
var item = blocks.Peek();
|
||||
ref ContainerItem item = ref blocks[blockCount - 1];
|
||||
var container = item.Container;
|
||||
|
||||
for (; item.Index < container.Count; item.Index++)
|
||||
@@ -212,35 +203,31 @@ namespace Markdig.Parsers
|
||||
// Else we have processed it
|
||||
item.Index++;
|
||||
}
|
||||
var newItem = cache.Get();
|
||||
newItem.Container = (ContainerBlock)block;
|
||||
block.OnProcessInlinesBegin(inlineProcessor);
|
||||
newItem.Index = 0;
|
||||
ThrowHelper.CheckDepthLimit(blocks.Count);
|
||||
blocks.Push(newItem);
|
||||
|
||||
if (blockCount == blocks.Length)
|
||||
{
|
||||
Array.Resize(ref blocks, blockCount * 2);
|
||||
ThrowHelper.CheckDepthLimit(blocks.Length);
|
||||
}
|
||||
blocks[blockCount++] = new ContainerItem(newContainer);
|
||||
newContainer.OnProcessInlinesBegin(inlineProcessor);
|
||||
goto process_new_block;
|
||||
}
|
||||
}
|
||||
item = blocks.Pop();
|
||||
container = item.Container;
|
||||
container.OnProcessInlinesEnd(inlineProcessor);
|
||||
|
||||
cache.Release(item);
|
||||
blocks[--blockCount] = default;
|
||||
}
|
||||
}
|
||||
|
||||
private class ContainerItem
|
||||
private struct ContainerItem
|
||||
{
|
||||
public ContainerItem()
|
||||
{
|
||||
}
|
||||
|
||||
public ContainerItem(ContainerBlock container)
|
||||
{
|
||||
Container = container;
|
||||
Index = 0;
|
||||
}
|
||||
|
||||
public ContainerBlock Container;
|
||||
public readonly ContainerBlock Container;
|
||||
|
||||
public int Index;
|
||||
}
|
||||
|
||||
@@ -28,9 +28,7 @@ namespace Markdig.Renderers
|
||||
protected TextRendererBase(TextWriter writer)
|
||||
{
|
||||
if (writer == null) ThrowHelper.ArgumentNullException_writer();
|
||||
this.Writer = writer;
|
||||
// By default we output a newline with '\n' only even on Windows platforms
|
||||
Writer.NewLine = "\n";
|
||||
Writer = writer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -47,6 +45,8 @@ namespace Markdig.Renderers
|
||||
ThrowHelper.ArgumentNullException(nameof(value));
|
||||
}
|
||||
|
||||
// By default we output a newline with '\n' only even on Windows platforms
|
||||
value.NewLine = "\n";
|
||||
writer = value;
|
||||
}
|
||||
}
|
||||
@@ -128,6 +128,11 @@ namespace Markdig.Renderers
|
||||
ThrowHelper.InvalidOperationException("Cannot reset this TextWriter instance");
|
||||
}
|
||||
|
||||
ResetInternal();
|
||||
}
|
||||
|
||||
internal void ResetInternal()
|
||||
{
|
||||
childrenDepth = 0;
|
||||
previousWasLine = true;
|
||||
indents.Clear();
|
||||
|
||||
@@ -22,8 +22,7 @@ namespace Markdig.Syntax
|
||||
/// as we expect less than 5~10 entries, usually typically 1 (HtmlAttributes)
|
||||
/// so it will gives faster access than a Dictionary, and lower memory occupation
|
||||
/// </summary>
|
||||
private DataEntry[] attachedDatas;
|
||||
private int count;
|
||||
private DataEntries _attachedDatas;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the text column this instance was declared (zero-based).
|
||||
@@ -55,33 +54,7 @@ namespace Markdig.Syntax
|
||||
/// <param name="key">The key.</param>
|
||||
/// <param name="value">The value.</param>
|
||||
/// <exception cref="ArgumentNullException">if key is null</exception>
|
||||
public void SetData(object key, object value)
|
||||
{
|
||||
if (key == null) ThrowHelper.ArgumentNullException_key();
|
||||
if (attachedDatas == null)
|
||||
{
|
||||
attachedDatas = new DataEntry[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
if (attachedDatas[i].Key == key)
|
||||
{
|
||||
attachedDatas[i].Value = value;
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (count == attachedDatas.Length)
|
||||
{
|
||||
var temp = new DataEntry[attachedDatas.Length + 1];
|
||||
Array.Copy(attachedDatas, 0, temp, 0, count);
|
||||
attachedDatas = temp;
|
||||
}
|
||||
}
|
||||
attachedDatas[count] = new DataEntry(key, value);
|
||||
count++;
|
||||
}
|
||||
public void SetData(object key, object value) => (_attachedDatas ??= new DataEntries()).SetData(key, value);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether this instance contains the specified key data.
|
||||
@@ -89,23 +62,7 @@ namespace Markdig.Syntax
|
||||
/// <param name="key">The key.</param>
|
||||
/// <returns><c>true</c> if a data with the key is stored</returns>
|
||||
/// <exception cref="ArgumentNullException">if key is null</exception>
|
||||
public bool ContainsData(object key)
|
||||
{
|
||||
if (key == null) ThrowHelper.ArgumentNullException_key();
|
||||
if (attachedDatas == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
if (attachedDatas[i].Key == key)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
public bool ContainsData(object key) => _attachedDatas?.ContainsData(key) ?? false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the associated data for the specified key.
|
||||
@@ -113,22 +70,7 @@ namespace Markdig.Syntax
|
||||
/// <param name="key">The key.</param>
|
||||
/// <returns>The associated data or null if none</returns>
|
||||
/// <exception cref="ArgumentNullException">if key is null</exception>
|
||||
public object GetData(object key)
|
||||
{
|
||||
if (key == null) ThrowHelper.ArgumentNullException_key();
|
||||
if (attachedDatas == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
if (attachedDatas[i].Key == key)
|
||||
{
|
||||
return attachedDatas[i].Value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public object GetData(object key) => _attachedDatas?.GetData(key);
|
||||
|
||||
/// <summary>
|
||||
/// Removes the associated data for the specified key.
|
||||
@@ -136,44 +78,117 @@ namespace Markdig.Syntax
|
||||
/// <param name="key">The key.</param>
|
||||
/// <returns><c>true</c> if the data was removed; <c>false</c> otherwise</returns>
|
||||
/// <exception cref="ArgumentNullException"></exception>
|
||||
public bool RemoveData(object key)
|
||||
{
|
||||
if (key == null) ThrowHelper.ArgumentNullException_key();
|
||||
if (attachedDatas == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
public bool RemoveData(object key) => _attachedDatas?.RemoveData(key) ?? false;
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
private class DataEntries
|
||||
{
|
||||
private struct DataEntry
|
||||
{
|
||||
if (attachedDatas[i].Key == key)
|
||||
public readonly object Key;
|
||||
public object Value;
|
||||
|
||||
public DataEntry(object key, object value)
|
||||
{
|
||||
if (i < count - 1)
|
||||
{
|
||||
Array.Copy(attachedDatas, i + 1, attachedDatas, i, count - i - 1);
|
||||
}
|
||||
count--;
|
||||
attachedDatas[count] = new DataEntry();
|
||||
return true;
|
||||
Key = key;
|
||||
Value = value;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Store a Key/Value pair.
|
||||
/// </summary>
|
||||
private struct DataEntry
|
||||
{
|
||||
public DataEntry(object key, object value)
|
||||
private DataEntry[] _entries;
|
||||
private int _count;
|
||||
|
||||
public DataEntries()
|
||||
{
|
||||
Key = key;
|
||||
Value = value;
|
||||
_entries = new DataEntry[2];
|
||||
}
|
||||
|
||||
public readonly object Key;
|
||||
public void SetData(object key, object value)
|
||||
{
|
||||
if (key == null) ThrowHelper.ArgumentNullException_key();
|
||||
|
||||
public object Value;
|
||||
DataEntry[] entries = _entries;
|
||||
int count = _count;
|
||||
|
||||
for (int i = 0; i < entries.Length && i < count; i++)
|
||||
{
|
||||
ref DataEntry entry = ref entries[i];
|
||||
if (entry.Key == key)
|
||||
{
|
||||
entry.Value = value;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (count == entries.Length)
|
||||
{
|
||||
Array.Resize(ref _entries, count + 2);
|
||||
}
|
||||
|
||||
_entries[count] = new DataEntry(key, value);
|
||||
_count++;
|
||||
}
|
||||
|
||||
public object GetData(object key)
|
||||
{
|
||||
if (key == null) ThrowHelper.ArgumentNullException_key();
|
||||
|
||||
DataEntry[] entries = _entries;
|
||||
int count = _count;
|
||||
|
||||
for (int i = 0; i < entries.Length && i < count; i++)
|
||||
{
|
||||
ref DataEntry entry = ref entries[i];
|
||||
if (entry.Key == key)
|
||||
{
|
||||
return entry.Value;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public bool ContainsData(object key)
|
||||
{
|
||||
if (key == null) ThrowHelper.ArgumentNullException_key();
|
||||
|
||||
DataEntry[] entries = _entries;
|
||||
int count = _count;
|
||||
|
||||
for (int i = 0; i < entries.Length && i < count; i++)
|
||||
{
|
||||
if (entries[i].Key == key)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool RemoveData(object key)
|
||||
{
|
||||
if (key == null) ThrowHelper.ArgumentNullException_key();
|
||||
|
||||
DataEntry[] entries = _entries;
|
||||
int count = _count;
|
||||
|
||||
for (int i = 0; i < entries.Length && i < count; i++)
|
||||
{
|
||||
if (entries[i].Key == key)
|
||||
{
|
||||
if (i < count - 1)
|
||||
{
|
||||
Array.Copy(entries, i + 1, entries, i, count - i - 1);
|
||||
}
|
||||
count--;
|
||||
entries[count] = default;
|
||||
_count = count;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user