Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
<PackageVersion Include="OneOf" Version="3.0.263" />
<PackageVersion Include="OneOf.SourceGenerator" Version="3.0.263" />
<PackageVersion Include="PdfPig" Version="0.1.9-alpha-20231119-4537e" />
<PackageVersion Include="PlantUml.Net" Version="1.4.80" />
<PackageVersion Include="Spectre.Console" Version="0.48.0" />
<PackageVersion Include="Spectre.Console.Cli" Version="0.48.0" />
<PackageVersion Include="Stubble.Core" Version="1.10.8" />
Expand All @@ -46,4 +47,4 @@
<PackageVersion Include="xunit.runner.visualstudio" Version="2.5.4" />
<PackageVersion Include="xunit" Version="2.6.2" />
</ItemGroup>
</Project>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
<ItemGroup>
<PackageReference Include="Markdig" />
<PackageReference Include="Newtonsoft.Json" />
<PackageReference Include="PlantUml.Net" />
</ItemGroup>

</Project>
16 changes: 15 additions & 1 deletion src/Docfx.MarkdigEngine.Extensions/MarkdownContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,13 @@ public class MarkdownContext
/// <returns>Image url bound to the path</returns>
public delegate string GetImageLinkDelegate(string path, MarkdownObject origin, string altText);

/// <summary>
/// Allows configuration of extensions
/// </summary>
/// <param name="extension">Name of the extension being configured</param>
/// <returns>Object representing the configuration for the extension</returns>
public delegate object GetExtensionConfigurationDelegate(string extension);

/// <summary>
/// Reads a file as text.
/// </summary>
Expand All @@ -51,6 +58,11 @@ public class MarkdownContext
/// </summary>
public GetImageLinkDelegate GetImageLink { get; }

/// <summary>
/// Get the configuration for a given extension
/// </summary>
public GetExtensionConfigurationDelegate GetExtensionConfiguration { get; }

/// <summary>
/// Log info
/// </summary>
Expand Down Expand Up @@ -86,12 +98,14 @@ public MarkdownContext(
LogActionDelegate logError = null,
ReadFileDelegate readFile = null,
GetLinkDelegate getLink = null,
GetImageLinkDelegate getImageLink = null)
GetImageLinkDelegate getImageLink = null,
GetExtensionConfigurationDelegate getConfig = null)
{
_getToken = getToken ?? (_ => null);
ReadFile = readFile ?? ((a, b) => (a, a));
GetLink = getLink ?? ((a, b) => a);
GetImageLink = getImageLink ?? ((a, b, c) => a);
GetExtensionConfiguration = getConfig ?? (_ => null);
LogInfo = logInfo ?? ((a, b, c, d) => { });
LogSuggestion = logSuggestion ?? ((a, b, c, d) => { });
LogWarning = logWarning ?? ((a, b, c, d) => { });
Expand Down
15 changes: 11 additions & 4 deletions src/Docfx.MarkdigEngine.Extensions/MarkdownExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ namespace Docfx.MarkdigEngine.Extensions;

public static class MarkdownExtensions
{
public static MarkdownPipelineBuilder UseDocfxExtensions(this MarkdownPipelineBuilder pipeline, MarkdownContext context, Dictionary<string, string> notes = null)
public static MarkdownPipelineBuilder UseDocfxExtensions(this MarkdownPipelineBuilder pipeline, MarkdownContext context)
{
return pipeline
.UseMathematics()
Expand All @@ -24,7 +24,7 @@ public static MarkdownPipelineBuilder UseDocfxExtensions(this MarkdownPipelineBu
.UseIncludeFile(context)
.UseCodeSnippet(context)
.UseDFMCodeInfoPrefix()
.UseQuoteSectionNote(context, notes)
.UseQuoteSectionNote(context)
.UseXref()
.UseEmojiAndSmiley(false)
.UseTabGroup(context)
Expand All @@ -35,6 +35,7 @@ public static MarkdownPipelineBuilder UseDocfxExtensions(this MarkdownPipelineBu
.UseTripleColon(context)
.UseNoloc()
.UseResolveLink(context)
.UsePlantUml(context)
.RemoveUnusedExtensions();
}

Expand Down Expand Up @@ -99,9 +100,9 @@ public static MarkdownPipelineBuilder UseDFMCodeInfoPrefix(this MarkdownPipeline
return pipeline;
}

public static MarkdownPipelineBuilder UseQuoteSectionNote(this MarkdownPipelineBuilder pipeline, MarkdownContext context, Dictionary<string, string> notes = null)
public static MarkdownPipelineBuilder UseQuoteSectionNote(this MarkdownPipelineBuilder pipeline, MarkdownContext context)
{
pipeline.Extensions.AddIfNotAlready(new QuoteSectionNoteExtension(context, notes));
pipeline.Extensions.AddIfNotAlready(new QuoteSectionNoteExtension(context));
return pipeline;
}

Expand All @@ -111,6 +112,12 @@ public static MarkdownPipelineBuilder UseLineNumber(this MarkdownPipelineBuilder
return pipeline;
}

public static MarkdownPipelineBuilder UsePlantUml(this MarkdownPipelineBuilder pipeline, MarkdownContext context)
{
pipeline.Extensions.AddIfNotAlready(new PlantUmlExtension(context));
return pipeline;
}

public static MarkdownPipelineBuilder UseResolveLink(this MarkdownPipelineBuilder pipeline, MarkdownContext context)
{
pipeline.Extensions.AddIfNotAlready(new ResolveLinkExtension(context));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
using static System.Text.Encoding;

using Markdig.Renderers;
using Markdig.Syntax;
using Markdig.Renderers.Html;
using PlantUml.Net;

namespace Docfx.MarkdigEngine.Extensions;

/// <summary>
/// An HTML renderer for a <see cref="CodeBlock"/> and <see cref="FencedCodeBlock"/>.
/// </summary>
/// <seealso cref="HtmlObjectRenderer{CodeBlock}" />
public class CustomCodeBlockRenderer : CodeBlockRenderer
{
private readonly MarkdownContext _context;
private readonly DocfxPlantUmlSettings _settings;
private readonly RendererFactory rendererFactory;

/// <summary>
/// Initializes a new instance of the <see cref="CodeBlockRenderer"/> class.
/// </summary>
/// <param name="context"></param>
/// <param name="settings"></param>
public CustomCodeBlockRenderer(MarkdownContext context, DocfxPlantUmlSettings settings)
{
_context = context;
_settings = settings;

rendererFactory = new RendererFactory();
}

protected override void Write(HtmlRenderer renderer, CodeBlock obj)
{
if (obj is FencedCodeBlock fencedCodeBlock
&& fencedCodeBlock.Info is string info
&& info.Equals("plantuml", StringComparison.OrdinalIgnoreCase))
{
IPlantUmlRenderer plantUmlRenderer = rendererFactory.CreateRenderer(_settings);

// Get PlantUML code.
var plantUmlCode = fencedCodeBlock.Lines.ToString();

try
{
byte[] output = plantUmlRenderer.Render(plantUmlCode, _settings.OutputFormat);

renderer.EnsureLine();
renderer.Write(FormatOutput(_settings.OutputFormat, output));
renderer.EnsureLine();
}
catch (RenderingException ex)
{
_context.LogWarning(nameof(PlantUmlExtension), ex.Message, null);
}
catch (Exception ex)
{
_context.LogError(nameof(PlantUmlExtension), ex.Message, null);

// If the error is not related to rendering a specific diagram, re-throw to abort
throw;
}

return;
}

// Fallback to default CodeBlockRenderer
base.Write(renderer, obj);
}

private static string FormatOutput(OutputFormat format, byte[] output)
{
switch (format)
{
case OutputFormat.Svg:
string svg = UTF8.GetString(output);
return $"<div class=\"lang-plantUml\">{svg}</div>";

case OutputFormat.Ascii:
string ascii = ASCII.GetString(output);
return $"<div class=\"lang-plantUml\"><pre>{ascii}</pre></div>";

case OutputFormat.Ascii_Unicode:
string asciiUnicode = UTF8.GetString(output);
return $"<div class=\"lang-plantUml\"><pre>{asciiUnicode}</pre></div>";

case OutputFormat.Png:
case OutputFormat.Eps:
case OutputFormat.Pdf:
case OutputFormat.Vdx:
case OutputFormat.Xmi:
case OutputFormat.Scxml:
case OutputFormat.Html:
case OutputFormat.LaTeX:
default:
throw new NotSupportedException($"Output format {format} is currently not supported");
}
}
}
68 changes: 68 additions & 0 deletions src/Docfx.MarkdigEngine.Extensions/PlantUml/PlantUmlExtension.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
using Markdig;
using Markdig.Renderers;
using Markdig.Renderers.Html;
using PlantUml.Net;

namespace Docfx.MarkdigEngine.Extensions;

public class DocfxPlantUmlSettings : PlantUmlSettings
{
public DocfxPlantUmlSettings() : base()
{
}

public DocfxPlantUmlSettings(IReadOnlyDictionary<string, string> config) : this()
{
if (config.TryGetValue("remoteUrl", out var url))
RemoteUrl = url;
if (config.TryGetValue("outputFormat", out var format))
OutputFormat = Enum.Parse<OutputFormat>(format, true);
if (config.TryGetValue("javaPath", out var path))
JavaPath = path;
if (config.TryGetValue("localPlantUmlPath", out path))
LocalPlantUmlPath = path;
if (config.TryGetValue("localGraphvizDotPath", out path))
LocalGraphvizDotPath = path;
if (config.TryGetValue("renderingMode", out var renderMode))
RenderingMode = Enum.Parse<RenderingMode>(renderMode, true);
}

public OutputFormat OutputFormat { get; set; } = OutputFormat.Svg;
}

internal class PlantUmlExtension : IMarkdownExtension
{
private readonly MarkdownContext _context;
private readonly DocfxPlantUmlSettings _settings;

public PlantUmlExtension(MarkdownContext context)
{
_context = context;
_settings = new();

if (_context.GetExtensionConfiguration("PlantUml") is Dictionary<string, string> config)
_settings = new DocfxPlantUmlSettings(config);
}

public void Setup(MarkdownPipelineBuilder pipeline)
{
}

public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
{
if (renderer is HtmlRenderer { ObjectRenderers: not null } htmlRenderer)
{
var customRenderer = new CustomCodeBlockRenderer(_context, _settings);
var renderers = htmlRenderer.ObjectRenderers;

if (renderers.Contains<CodeBlockRenderer>())
{
renderers.InsertBefore<CodeBlockRenderer>(customRenderer);
}
else
{
renderers.AddIfNotAlready(customRenderer);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Text.Json.Serialization;
using Markdig;
using Markdig.Parsers;
using Markdig.Renderers;
using Markdig.Renderers.Html;
using Newtonsoft.Json;

namespace Docfx.MarkdigEngine.Extensions;

Expand All @@ -20,13 +22,13 @@ public class QuoteSectionNoteExtension : IMarkdownExtension
["CAUTION"] = "CAUTION",
};

public QuoteSectionNoteExtension(MarkdownContext context, Dictionary<string, string> notes = null)
public QuoteSectionNoteExtension(MarkdownContext context)
{
_context = context;

if (notes != null)
if (_context.GetExtensionConfiguration("Alerts") is Dictionary<string, string> config)
{
foreach (var (key, value) in notes)
foreach (var (key, value) in config)
_notes[key] = value;
}
}
Expand Down
8 changes: 6 additions & 2 deletions src/Docfx.MarkdigEngine/MarkdigMarkdownService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Immutable;
using System.Text;
using Docfx.Common;
using Docfx.MarkdigEngine.Extensions;
using Docfx.Plugins;
Expand Down Expand Up @@ -33,7 +34,8 @@ public MarkdigMarkdownService(
(code, message, origin, line) => Logger.LogError(message, null, InclusionContext.File.ToString(), line?.ToString(), code),
ReadFile,
GetLink,
GetImageLink);
GetImageLink,
GetExtensionConfiguration);
}

public MarkupResult Markup(string content, string filePath)
Expand Down Expand Up @@ -127,7 +129,7 @@ private MarkdownPipeline CreateMarkdownPipeline(bool isInline, bool multipleYaml

var builder = new MarkdownPipelineBuilder();

builder.UseDocfxExtensions(_context, _parameters.Extensions?.Alerts);
builder.UseDocfxExtensions(_context);
builder.Extensions.Insert(0, new YamlHeaderExtension(_context) { AllowInMiddleOfDocument = multipleYamlHeader });

if (enableSourceInfo)
Expand Down Expand Up @@ -185,6 +187,8 @@ private static string GetLink(string path, MarkdownObject origin)
return path;
}

private object GetExtensionConfiguration(string extension) => _parameters.GetExtensionConfiguration(extension);

private static string GetImageLink(string href, MarkdownObject origin, string altText) => GetLink(href, origin);

private static void ReportDependency(RelativePath filePathToDocset, string parentFileDirectoryToDocset)
Expand Down
19 changes: 19 additions & 0 deletions src/Docfx.Plugins/MarkdownServiceParameters.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,13 @@ public class MarkdownServiceProperties
[JsonProperty("alerts")]
[JsonPropertyName("alerts")]
public Dictionary<string, string> Alerts { get; set; }

/// <summary>
/// PlantUml extension configuration parameters
/// </summary>
[JsonProperty("plantUml")]
[JsonPropertyName("plantUml")]
public Dictionary<string, string> PlantUml { get; set; }
}

public class MarkdownServiceParameters
Expand All @@ -43,4 +50,16 @@ public class MarkdownServiceParameters
public string TemplateDir { get; set; }
public MarkdownServiceProperties Extensions { get; set; } = new();
public ImmutableDictionary<string, string> Tokens { get; set; } = ImmutableDictionary<string, string>.Empty;

public object GetExtensionConfiguration(string extension)
{
if (!string.IsNullOrEmpty(extension) && Extensions != null)
{
var property = typeof(MarkdownServiceProperties).GetProperty(extension);
if (property != null)
return property.GetValue(Extensions);
}

return null;
}
}
Loading