2021-09-07 04:56:25 +00:00
|
|
|
using System;
|
|
|
|
using System.Collections.Generic;
|
|
|
|
using System.IO;
|
2022-11-02 22:37:04 +00:00
|
|
|
|
2021-09-07 04:56:25 +00:00
|
|
|
using Markdig.Helpers;
|
|
|
|
using Markdig.Renderers;
|
|
|
|
using Markdig.Syntax;
|
2022-11-02 22:37:04 +00:00
|
|
|
|
2021-09-07 04:56:25 +00:00
|
|
|
using MfGames.Markdown.Gemtext.Renderers.Gemtext.Blocks;
|
|
|
|
using MfGames.Markdown.Gemtext.Renderers.Gemtext.Inlines;
|
|
|
|
|
|
|
|
namespace MfGames.Markdown.Gemtext.Renderers
|
|
|
|
{
|
|
|
|
public class GemtextRenderer : TextRendererBase<GemtextRenderer>
|
|
|
|
{
|
|
|
|
/// <inheritdoc />
|
|
|
|
public GemtextRenderer(TextWriter writer)
|
|
|
|
: base(writer)
|
|
|
|
{
|
|
|
|
// Set up our default values.
|
|
|
|
this.NextFootnoteNumber = 1;
|
|
|
|
this.GatheredLinks = new List<string>();
|
|
|
|
|
|
|
|
// Default block renderers.
|
|
|
|
this.ObjectRenderers.Add(new CodeBlockRenderer());
|
|
|
|
this.ObjectRenderers.Add(new HeadingRenderer());
|
|
|
|
this.ObjectRenderers.Add(new HtmlBlockRenderer());
|
|
|
|
this.ObjectRenderers.Add(new ListRenderer());
|
|
|
|
this.ObjectRenderers.Add(new MarkdownDocumentRenderer());
|
|
|
|
this.ObjectRenderers.Add(new ParagraphRenderer());
|
|
|
|
this.ObjectRenderers.Add(new QuoteBlockRenderer());
|
|
|
|
this.ObjectRenderers.Add(new ThematicBreakRenderer());
|
|
|
|
|
|
|
|
// Default inline renderers.
|
|
|
|
this.ObjectRenderers.Add(new CodeInlineRenderer());
|
|
|
|
this.ObjectRenderers.Add(new DelimiterInlineRenderer());
|
|
|
|
this.ObjectRenderers.Add(new EmphasisInlineRenderer());
|
|
|
|
this.ObjectRenderers.Add(new HtmlEntityInlineRenderer());
|
|
|
|
this.ObjectRenderers.Add(new LineBreakInlineRenderer());
|
|
|
|
this.ObjectRenderers.Add(new LinkInlineRenderer());
|
|
|
|
this.ObjectRenderers.Add(new LiteralInlineRenderer());
|
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets or sets how to handle links inside paragraphs and other blocks.
|
|
|
|
/// </summary>
|
|
|
|
public BlockLinkHandling BlockLinkHandling { get; set; }
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets or sets the optional formatting for code inlines (backticks).
|
|
|
|
/// If this is unset, then `InlineFormatting` will be used.
|
|
|
|
/// </summary>
|
|
|
|
public InlineFormatting? CodeFormatting { get; set; }
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets the actual formatting for code inlines (backticks) which
|
|
|
|
/// is either `CodeInlineFormatting` or `InlineFormatting` if that
|
|
|
|
/// is not set.
|
|
|
|
/// </summary>
|
|
|
|
public InlineFormatting CodeFormattingResolved =>
|
|
|
|
this.CodeFormatting ?? this.InlineFormatting;
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets or sets the optional formatting for emphasis (which includes
|
|
|
|
/// italics and bolds). If this is unset, then `InlineFormatting`
|
|
|
|
/// will be used.
|
|
|
|
/// </summary>
|
|
|
|
public InlineFormatting? EmphasisFormatting { get; set; }
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets the actual formatting for emphasis which is either
|
|
|
|
/// `EmphasisFormatting` or `InlineFormatting` if that isn't set.
|
|
|
|
/// </summary>
|
|
|
|
public InlineFormatting EmphasisFormattingResolved =>
|
|
|
|
this.EmphasisFormatting ?? this.InlineFormatting;
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets or sets the formatting for how links that are gathered at the
|
|
|
|
/// end of a paragraph or document are formatted inside the paragraph.
|
|
|
|
/// </summary>
|
|
|
|
public EndLinkInlineFormatting EndLinkInlineFormatting { get; set; }
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets the current list of formatted links that have been gathered
|
|
|
|
/// up to this point for rendering.
|
|
|
|
/// </summary>
|
|
|
|
public List<string> GatheredLinks { get; }
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets or sets the formatting rule for HTML blocks.
|
|
|
|
/// </summary>
|
|
|
|
public HtmlBlockFormatting HtmlBlockFormatting { get; set; }
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets or sets the default formatting for all inlines.
|
|
|
|
/// </summary>
|
|
|
|
public InlineFormatting InlineFormatting { get; set; }
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// An internal processing flag that determines if the rendered link
|
|
|
|
/// is inside a block or not to trigger extra handling.
|
|
|
|
/// </summary>
|
|
|
|
public bool LinkInsideBlock { get; set; }
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets or sets the next footnote while rendering links.
|
|
|
|
/// </summary>
|
|
|
|
public int NextFootnoteNumber { get; set; }
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Ensures there are two blank lines before an element.
|
|
|
|
/// </summary>
|
|
|
|
/// <returns></returns>
|
|
|
|
public GemtextRenderer EnsureTwoLines()
|
|
|
|
{
|
|
|
|
if (this.previousWasLine)
|
|
|
|
{
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.WriteLine();
|
|
|
|
this.WriteLine();
|
|
|
|
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// A wrapper method to push the state of LinkInsideBlock while
|
|
|
|
/// performing an action.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="action">The action to perform.</param>
|
|
|
|
public void WhileLinkInsideBlock(Action action)
|
|
|
|
{
|
|
|
|
bool oldState = this.LinkInsideBlock;
|
|
|
|
this.LinkInsideBlock = true;
|
|
|
|
action();
|
|
|
|
this.LinkInsideBlock = oldState;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Writes the lines of a <see cref="LeafBlock" />
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="leafBlock">The leaf block.</param>
|
|
|
|
/// <param name="writeEndOfLines">if set to <c>true</c> write end of lines.</param>
|
|
|
|
/// <returns>This instance</returns>
|
|
|
|
public GemtextRenderer WriteLeafRawLines(
|
|
|
|
LeafBlock leafBlock,
|
|
|
|
bool writeEndOfLines)
|
|
|
|
{
|
|
|
|
// Make sure we have sane input.
|
|
|
|
if (leafBlock == null)
|
|
|
|
{
|
|
|
|
throw new ArgumentNullException(nameof(leafBlock));
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we have nothing to write, then don't do anything. Even though
|
|
|
|
// Markdig says this can't be null, `leafBlock.Lines` may be null
|
|
|
|
// according to the comments.
|
|
|
|
// ReSharper disable once ConditionIsAlwaysTrueOrFalse
|
|
|
|
if (leafBlock.Lines.Lines == null)
|
|
|
|
{
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Go through the block and write out each of the lines.
|
|
|
|
StringLineGroup lines = leafBlock.Lines;
|
|
|
|
StringLine[] slices = lines.Lines;
|
|
|
|
|
|
|
|
for (int i = 0; i < lines.Count; i++)
|
|
|
|
{
|
|
|
|
if (!writeEndOfLines && i > 0)
|
|
|
|
{
|
|
|
|
this.WriteLine();
|
|
|
|
}
|
|
|
|
|
|
|
|
this.Write(ref slices[i].Slice);
|
|
|
|
|
|
|
|
if (writeEndOfLines)
|
|
|
|
{
|
|
|
|
this.WriteLine();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|