//
// Copyright (c) 2024, Brian Frank and Andy Frank
// Licensed under the Academic Free License version 3.0
//
// History:
// 23 Oct 2024 Matthew Giannini Creation
//
**
** Renders a tree of nodes to HTML
**
@Js
const class HtmlRenderer : Renderer
{
//////////////////////////////////////////////////////////////////////////
// Constructor
//////////////////////////////////////////////////////////////////////////
** Obtain a builder for configuring the renderer
static HtmlRendererBuilder builder() { HtmlRendererBuilder() }
** Get a renderer with all the default configuration
static new make() { builder.build }
internal new makeBuilder(HtmlRendererBuilder builder)
{
this.softbreak = builder.softbreak
this.escapeHtml = builder.escapeHtml
this.percentEncodeUrls = builder.percentEncodeUrls
this.omitSingleParagraphP = builder.omitSingleParagraphP
this.sanitizeUrls = builder.sanitizeUrls
this.attrProviderFactories = builder.attrProviderFactories
factories := builder.nodeRendererFactories.dup
factories.add(|cx->NodeRenderer| { CoreHtmlNodeRenderer(cx) })
this.nodeRendererFactories = factories
}
internal const Str softbreak
internal const Bool escapeHtml
internal const Bool percentEncodeUrls
internal const Bool omitSingleParagraphP
internal const Bool sanitizeUrls
internal const |HtmlContext->NodeRenderer|[] nodeRendererFactories
internal const |HtmlContext->AttrProvider|[] attrProviderFactories
internal const UrlSanitizer urlSanitizer := DefaultUrlSanitizer()
//////////////////////////////////////////////////////////////////////////
// Render
//////////////////////////////////////////////////////////////////////////
override Void renderTo(OutStream out, Node node)
{
cx := HtmlContext(this, HtmlWriter(out))
cx.beforeRoot(node)
cx.render(node)
cx.afterRoot(node)
}
}
**************************************************************************
** HtmlRendererBuilder
**************************************************************************
**
** Builder for configuring an `HtmlRenderer`.
**
@Js
final class HtmlRendererBuilder
{
internal new make() { }
internal |HtmlContext->NodeRenderer|[] nodeRendererFactories := [,]
internal |HtmlContext->AttrProvider|[] attrProviderFactories := [,]
internal Str softbreak := "\n"
internal Bool escapeHtml := false
internal Bool percentEncodeUrls := false
internal Bool omitSingleParagraphP := false
internal Bool sanitizeUrls := false
** Get the configured `HtmlRenderer`
HtmlRenderer build() { HtmlRenderer(this) }
** The HTML to use for rendering a softbreak, default to '\n' (meaning the
** rendered result doesn't have a line break).
**
** Set it to '<br>' or '<br />' to make the hard breaks.
**
** Set it to ' ' (space) to ingore line wrapping in the source.
This withSoftBreak(Str s)
{
this.softbreak = s
return this
}
** Whether `HtmlInline` and `HtmlBlock` should be escaped, defaults to 'false'.
**
** Note that `HtmlInline` is only a tag itself, not the text between an opening tag
** and closing tag. So markup in the text will be parsed as normal and is not affected
** by this option.
This withEscapeHtml(Bool val := true)
{
this.escapeHtml = val
return this
}
** Whether URLs of link or images should be percent-encoded, defaults to 'false'.
**
** If enabled, the following is done:
** - Existing percent-encoded parts are preserved (e.g. "%20" is kept as "%20")
** - Reserved characters such as "/" are preserved, except for "[" and "]"
** (see encodeURL in JS).
** - Other characters such as umlauts are percent-encoded
This withPercentEncodeUrls(Bool val := true)
{
this.percentEncodeUrls = val
return this
}
** Whether documents that only contain a single paragraph shoudl be rendered without
** the '<p>' tag. Set to 'true' to render without the tag; the default of 'false'
** always renders the tag.
This withOmitSingleParagraphP(Bool val := true)
{
this.omitSingleParagraphP = val
return this
}
** Whether `Image` src and `Link` href should be sanitized, defaults to 'false'.
This withSanitizeUrls(Bool val := true)
{
this.sanitizeUrls = val
return this
}
** Add a factory for instantiating a node renderer (done when rendering). This allows
** to override the rendering of node types or define rendering for custom node types.
**
** If multiple node renderers for the same node type are created, the one from the
** factory that was added first "wins". (This is how rendering for core node types
** can be overriden; the default rendering comes last).
This nodeRendererFactory(|HtmlContext->NodeRenderer| factory)
{
nodeRendererFactories.add(factory)
return this
}
** Add a factory for an attribute provider for adding/changing HTML attributes to the
** rendered tags.
This attrProviderFactory(|HtmlContext->AttrProvider| factory)
{
attrProviderFactories.add(factory)
return this
}
** Configure the given extensions on this this renderer
This extensions(MarkdownExt[] exts)
{
exts.each |ext| { ext.extendHtml(this) }
return this
}
}