Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ConfigureHtmlRenderer to MarkdownPipelineBuilder #802

Draft
wants to merge 12 commits into
base: master
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,15 @@ public void Test(string value, string expected)
// do not unintentionally use the expected parameter
private static void RoundTrip(string markdown, string expected)
{
var pipelineBuilder = new MarkdownPipelineBuilder();
pipelineBuilder.EnableTrackTrivia();
MarkdownPipeline pipeline = pipelineBuilder.Build();
MarkdownDocument markdownDocument = Markdown.Parse(markdown, pipeline);
var sw = new StringWriter();
var rr = new RoundtripRenderer(sw);
var pipeline = new MarkdownPipelineBuilder()
.EnableTrackTrivia()
.ConfigureRoundtripRenderer()
.Build();

rr.Write(markdownDocument);
MarkdownDocument markdownDocument = Markdown.Parse(markdown, pipeline);
var result = Markdown.ToHtml(markdownDocument, pipeline);

Assert.AreEqual(expected, sw.ToString());
Assert.AreEqual(expected, result);
}
}
}
14 changes: 5 additions & 9 deletions src/Markdig.Tests/TestLinkRewriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,13 @@ public void ReplacesRelativeImageSources()

public static void TestSpec(Func<string,string> linkRewriter, string markdown, string expectedLink)
{
var pipeline = new MarkdownPipelineBuilder().Build();

var writer = new StringWriter();
var renderer = new HtmlRenderer(writer);
renderer.LinkRewriter = linkRewriter;
pipeline.Setup(renderer);
var pipeline = new MarkdownPipelineBuilder()
.ConfigureHtmlRenderer(r => r.UseLinkRewriter(linkRewriter))
.Build();

var document = MarkdownParser.Parse(markdown, pipeline);
renderer.Render(document);
writer.Flush();
var html = Markdown.ToHtml(document, pipeline);

Assert.That(writer.ToString(), Contains.Substring("=\"" + expectedLink + "\""));
Assert.That(html, Contains.Substring("=\"" + expectedLink + "\""));
}
}
15 changes: 5 additions & 10 deletions src/Markdig.Tests/TestRelativeUrlReplacement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,13 @@ public void ReplacesRelativeImageSources()

public static void TestSpec(string baseUrl, string markdown, string expectedLink)
{
var pipeline = new MarkdownPipelineBuilder().Build();

var writer = new StringWriter();
var renderer = new HtmlRenderer(writer);
if (baseUrl != null)
renderer.BaseUrl = new Uri(baseUrl);
pipeline.Setup(renderer);
var pipeline = new MarkdownPipelineBuilder()
.ConfigureHtmlRenderer(b => b.UseBaseUrl(baseUrl))
.Build();

var document = MarkdownParser.Parse(markdown, pipeline);
renderer.Render(document);
writer.Flush();
var html = Markdown.ToHtml(document, pipeline);

Assert.That(writer.ToString(), Contains.Substring("=\"" + expectedLink + "\""));
Assert.That(html, Contains.Substring("=\"" + expectedLink + "\""));
}
}
16 changes: 6 additions & 10 deletions src/Markdig.Tests/TestRoundtrip.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,14 @@ internal static void TestSpec(string markdownText, string expected, string exten

internal static void RoundTrip(string markdown, string context = null)
{
var pipelineBuilder = new MarkdownPipelineBuilder();
pipelineBuilder.EnableTrackTrivia();
pipelineBuilder.UseYamlFrontMatter();
MarkdownPipeline pipeline = pipelineBuilder.Build();
var pipeline = new MarkdownPipelineBuilder()
.EnableTrackTrivia()
.UseYamlFrontMatter()
.ConfigureRoundtripRenderer()
.Build();
MarkdownDocument markdownDocument = Markdown.Parse(markdown, pipeline);
var sw = new StringWriter();
var nr = new RoundtripRenderer(sw);
pipeline.Setup(nr);

nr.Write(markdownDocument);

var result = sw.ToString();
var result = Markdown.ToHtml(markdownDocument, pipeline);
TestParser.PrintAssertExpected("", result, markdown, context);
}
}
6 changes: 3 additions & 3 deletions src/Markdig/Markdown.cs
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ public static string ToHtml(this MarkdownDocument document, MarkdownPipeline? pi
pipeline ??= DefaultPipeline;

using var rentedRenderer = pipeline.RentHtmlRenderer();
HtmlRenderer renderer = rentedRenderer.Instance;
var renderer = rentedRenderer.Instance;

renderer.Render(document);
renderer.Writer.Flush();
Expand All @@ -141,7 +141,7 @@ public static void ToHtml(this MarkdownDocument document, TextWriter writer, Mar
pipeline ??= DefaultPipeline;

using var rentedRenderer = pipeline.RentHtmlRenderer(writer);
HtmlRenderer renderer = rentedRenderer.Instance;
var renderer = rentedRenderer.Instance;

renderer.Render(document);
renderer.Writer.Flush();
Expand All @@ -166,7 +166,7 @@ public static MarkdownDocument ToHtml(string markdown, TextWriter writer, Markdo
var document = MarkdownParser.Parse(markdown, pipeline, context);

using var rentedRenderer = pipeline.RentHtmlRenderer(writer);
HtmlRenderer renderer = rentedRenderer.Instance;
var renderer = rentedRenderer.Instance;

renderer.Render(document);
writer.Flush();
Expand Down
44 changes: 43 additions & 1 deletion src/Markdig/MarkdownExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@
using Markdig.Parsers;
using Markdig.Parsers.Inlines;
using Markdig.Renderers;
using Markdig.Renderers.Normalize;
using Markdig.Renderers.Roundtrip;

namespace Markdig;

Expand Down Expand Up @@ -108,7 +110,7 @@ public static MarkdownPipelineBuilder UseAlertBlocks(this MarkdownPipelineBuilde
pipeline.Extensions.ReplaceOrAdd<AlertExtension>(new AlertExtension() { RenderKind = renderKind });
return pipeline;
}

/// <summary>
/// Uses this extension to enable autolinks from text `http://`, `https://`, `ftp://`, `mailto:`, `www.xxx.yyy`
/// </summary>
Expand Down Expand Up @@ -708,4 +710,44 @@ public static MarkdownPipelineBuilder EnableTrackTrivia(this MarkdownPipelineBui
}
return pipeline;
}

/// <summary>
/// Configure the pipeline with a <see cref="HtmlRenderer"/>.
/// </summary>
/// <param name="pipeline">The pipeline.</param>
/// <param name="configureRenderer">An action which configures the <c>HtmlRenderer</c>.</param>
public static MarkdownPipelineBuilder ConfigureHtmlRenderer(
this MarkdownPipelineBuilder pipeline,
Func<HtmlRendererBuilder, HtmlRendererBuilder> configureRenderer)
=> pipeline.UseRendererBuilder(configureRenderer(new HtmlRendererBuilder()));

/// <summary>
/// Configure the pipeline with a <see cref="NormalizeRenderer"/>.
/// </summary>
/// <param name="pipeline">The pipeline.</param>
/// <param name="configureRenderer">An action which configures the <c>NormalizeRenderer</c>.</param>
public static MarkdownPipelineBuilder ConfigureNormalizeRenderer(
this MarkdownPipelineBuilder pipeline,
Func<NormalizeRendererBuilder, NormalizeRendererBuilder> configureRenderer)
=> pipeline.UseRendererBuilder(configureRenderer(new NormalizeRendererBuilder()));

/// <summary>
/// Configure the pipeline with a <see cref="RoundtripRenderer"/>.
/// </summary>
/// <param name="pipeline">The pipeline.</param>
public static MarkdownPipelineBuilder ConfigureRoundtripRenderer(this MarkdownPipelineBuilder pipeline)
=> pipeline.UseRendererBuilder(new RoundtripRendererBuilder());

/// <summary>
/// Configure the pipeline to use a <see cref="IMarkdownRendererBuilder"/> to construct the renderer.
/// </summary>
/// <param name="pipeline">The pipeline.</param>
/// <param name="rendererBuilder">The builder for the renderer.</param>
public static MarkdownPipelineBuilder UseRendererBuilder(
this MarkdownPipelineBuilder pipeline,
IMarkdownRendererBuilder rendererBuilder)
{
pipeline.RendererBuilder = rendererBuilder;
return pipeline;
}
}
30 changes: 19 additions & 11 deletions src/Markdig/MarkdownPipeline.cs
Original file line number Diff line number Diff line change
@@ -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.IO;
Expand All @@ -25,7 +25,8 @@ public sealed class MarkdownPipeline
BlockParserList blockParsers,
InlineParserList inlineParsers,
TextWriter? debugLog,
ProcessDocumentDelegate? documentProcessed)
ProcessDocumentDelegate? documentProcessed,
IMarkdownRendererBuilder? rendererBuilder)
{
if (blockParsers is null) ThrowHelper.ArgumentNullException(nameof(blockParsers));
if (inlineParsers is null) ThrowHelper.ArgumentNullException(nameof(inlineParsers));
Expand All @@ -35,6 +36,7 @@ public sealed class MarkdownPipeline
InlineParsers = inlineParsers;
DebugLog = debugLog;
DocumentProcessed = documentProcessed;
RendererBuilder = rendererBuilder;

SelfPipeline = Extensions.Find<SelfPipelineExtension>();
}
Expand All @@ -57,6 +59,8 @@ public sealed class MarkdownPipeline

internal SelfPipelineExtension? SelfPipeline;

internal IMarkdownRendererBuilder? RendererBuilder;

/// <summary>
/// True to parse trivia such as whitespace, extra heading characters and unescaped
/// string values.
Expand All @@ -82,10 +86,10 @@ public void Setup(IMarkdownRenderer renderer)
internal RentedHtmlRenderer RentHtmlRenderer(TextWriter? writer = null)
{
HtmlRendererCache cache = writer is null
? _rendererCache ??= new HtmlRendererCache(this, customWriter: false)
: _rendererCacheForCustomWriter ??= new HtmlRendererCache(this, customWriter: true);
? _rendererCache ??= new HtmlRendererCache(this, customWriter: false, RendererBuilder)
: _rendererCacheForCustomWriter ??= new HtmlRendererCache(this, customWriter: true, RendererBuilder);

HtmlRenderer renderer = cache.Get();
TextRendererBase renderer = cache.Get();

if (writer is not null)
{
Expand All @@ -97,22 +101,26 @@ internal RentedHtmlRenderer RentHtmlRenderer(TextWriter? writer = null)

internal sealed class HtmlRendererCache(
MarkdownPipeline pipeline,
bool customWriter = false) : ObjectCache<HtmlRenderer>
bool customWriter = false,
IMarkdownRendererBuilder? rendererBuilder = null) : ObjectCache<TextRendererBase>
{
private static readonly FastStringWriter s_dummyWriter = new();

private readonly MarkdownPipeline _pipeline = pipeline;
private readonly bool _customWriter = customWriter;

protected override HtmlRenderer NewInstance()
private readonly IMarkdownRendererBuilder RendererBuilder =
rendererBuilder ?? new HtmlRendererBuilder();

protected override TextRendererBase NewInstance()
{
TextWriter writer = _customWriter ? s_dummyWriter : new FastStringWriter();
var renderer = new HtmlRenderer(writer);
var renderer = RendererBuilder.Build(writer);
_pipeline.Setup(renderer);
return renderer;
}

protected override void Reset(HtmlRenderer instance)
protected override void Reset(TextRendererBase instance)
{
instance.ResetInternal();

Expand All @@ -130,9 +138,9 @@ protected override void Reset(HtmlRenderer instance)
internal readonly struct RentedHtmlRenderer : IDisposable
{
private readonly HtmlRendererCache _cache;
public readonly HtmlRenderer Instance;
public readonly TextRendererBase Instance;

internal RentedHtmlRenderer(HtmlRendererCache cache, HtmlRenderer renderer)
internal RentedHtmlRenderer(HtmlRendererCache cache, TextRendererBase renderer)
{
_cache = cache;
Instance = renderer;
Expand Down
10 changes: 7 additions & 3 deletions src/Markdig/MarkdownPipelineBuilder.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
// 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.IO;

using Markdig.Helpers;
using Markdig.Parsers;
using Markdig.Parsers.Inlines;
using Markdig.Renderers;

namespace Markdig;

Expand Down Expand Up @@ -89,6 +90,8 @@ public MarkdownPipelineBuilder()

internal ProcessDocumentDelegate? GetDocumentProcessed => DocumentProcessed;

internal IMarkdownRendererBuilder? RendererBuilder;

/// <summary>
/// Builds a pipeline from this instance. Once the pipeline is build, it cannot be modified.
/// </summary>
Expand Down Expand Up @@ -120,11 +123,12 @@ public MarkdownPipeline Build()
new BlockParserList(BlockParsers),
new InlineParserList(InlineParsers),
DebugLog,
GetDocumentProcessed)
GetDocumentProcessed,
RendererBuilder)
{
PreciseSourceLocation = PreciseSourceLocation,
TrackTrivia = TrackTrivia,
};
return pipeline;
}
}
}
13 changes: 11 additions & 2 deletions src/Markdig/Renderers/HtmlRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public HtmlRenderer(TextWriter writer) : base(writer)
ObjectRenderers.Add(new EmphasisInlineRenderer());
ObjectRenderers.Add(new LineBreakInlineRenderer());
ObjectRenderers.Add(new HtmlInlineRenderer());
ObjectRenderers.Add(new HtmlEntityInlineRenderer());
ObjectRenderers.Add(new HtmlEntityInlineRenderer());
ObjectRenderers.Add(new LinkInlineRenderer());
ObjectRenderers.Add(new LiteralInlineRenderer());

Expand Down Expand Up @@ -202,7 +202,16 @@ public HtmlRenderer WriteEscapeUrl(string? content)
// this is the proper cross-platform way to check whether a uri is absolute or not:
&& Uri.TryCreate(content, UriKind.RelativeOrAbsolute, out var contentUri) && !contentUri.IsAbsoluteUri)
{
content = new Uri(BaseUrl, contentUri).AbsoluteUri;
if (BaseUrl.IsAbsoluteUri)
content = new Uri(BaseUrl, contentUri).AbsoluteUri;
else
{
var baseUrl = BaseUrl.ToString();
content = baseUrl;
if (!baseUrl.EndsWith("/"))
content += "/";
content += contentUri.ToString();
}
}

if (LinkRewriter != null)
Expand Down
Loading