All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.pkl.thirdparty.commonmark.renderer.html.HtmlRenderer Maven / Gradle / Ivy

Go to download

Fat Jar containing pkl-cli, pkl-codegen-java, pkl-codegen-kotlin, pkl-config-java, pkl-core, pkl-doc, and their shaded third-party dependencies.

There is a newer version: 0.27.1
Show newest version
package org.pkl.thirdparty.commonmark.renderer.html;

import org.pkl.thirdparty.commonmark.Extension;
import org.pkl.thirdparty.commonmark.internal.renderer.NodeRendererMap;
import org.pkl.thirdparty.commonmark.internal.util.Escaping;
import org.pkl.thirdparty.commonmark.node.*;
import org.pkl.thirdparty.commonmark.renderer.NodeRenderer;
import org.pkl.thirdparty.commonmark.renderer.Renderer;

import java.util.*;

/**
 * Renders a tree of nodes to HTML.
 * 

* Start with the {@link #builder} method to configure the renderer. Example: *


 * HtmlRenderer renderer = HtmlRenderer.builder().escapeHtml(true).build();
 * renderer.render(node);
 * 
*/ public class HtmlRenderer implements Renderer { private final String softbreak; private final boolean escapeHtml; private final boolean percentEncodeUrls; private final boolean omitSingleParagraphP; private final boolean sanitizeUrls; private final UrlSanitizer urlSanitizer; private final List attributeProviderFactories; private final List nodeRendererFactories; private HtmlRenderer(Builder builder) { this.softbreak = builder.softbreak; this.escapeHtml = builder.escapeHtml; this.percentEncodeUrls = builder.percentEncodeUrls; this.omitSingleParagraphP = builder.omitSingleParagraphP; this.sanitizeUrls = builder.sanitizeUrls; this.urlSanitizer = builder.urlSanitizer; this.attributeProviderFactories = new ArrayList<>(builder.attributeProviderFactories); this.nodeRendererFactories = new ArrayList<>(builder.nodeRendererFactories.size() + 1); this.nodeRendererFactories.addAll(builder.nodeRendererFactories); // Add as last. This means clients can override the rendering of core nodes if they want. this.nodeRendererFactories.add(new HtmlNodeRendererFactory() { @Override public NodeRenderer create(HtmlNodeRendererContext context) { return new CoreHtmlNodeRenderer(context); } }); } /** * Create a new builder for configuring an {@link HtmlRenderer}. * * @return a builder */ public static Builder builder() { return new Builder(); } @Override public void render(Node node, Appendable output) { Objects.requireNonNull(node, "node must not be null"); RendererContext context = new RendererContext(new HtmlWriter(output)); context.beforeRoot(node); context.render(node); context.afterRoot(node); } @Override public String render(Node node) { Objects.requireNonNull(node, "node must not be null"); StringBuilder sb = new StringBuilder(); render(node, sb); return sb.toString(); } /** * Builder for configuring an {@link HtmlRenderer}. See methods for default configuration. */ public static class Builder { private String softbreak = "\n"; private boolean escapeHtml = false; private boolean sanitizeUrls = false; private UrlSanitizer urlSanitizer = new DefaultUrlSanitizer(); private boolean percentEncodeUrls = false; private boolean omitSingleParagraphP = false; private List attributeProviderFactories = new ArrayList<>(); private List nodeRendererFactories = new ArrayList<>(); /** * @return the configured {@link HtmlRenderer} */ public HtmlRenderer build() { return new HtmlRenderer(this); } /** * The HTML to use for rendering a softbreak, defaults to {@code "\n"} (meaning the rendered result doesn't have * a line break). *

* Set it to {@code "
"} (or {@code "
"} to make them hard breaks. *

* Set it to {@code " "} to ignore line wrapping in the source. * * @param softbreak HTML for softbreak * @return {@code this} */ public Builder softbreak(String softbreak) { this.softbreak = softbreak; return this; } /** * Whether {@link HtmlInline} and {@link HtmlBlock} should be escaped, defaults to {@code false}. *

* Note that {@link HtmlInline} is only a tag itself, not the text between an opening tag and a closing tag. So * markup in the text will be parsed as normal and is not affected by this option. * * @param escapeHtml true for escaping, false for preserving raw HTML * @return {@code this} */ public Builder escapeHtml(boolean escapeHtml) { this.escapeHtml = escapeHtml; return this; } /** * Whether {@link Image} src and {@link Link} href should be sanitized, defaults to {@code false}. * * @param sanitizeUrls true for sanitization, false for preserving raw attribute * @return {@code this} * @since 0.14.0 */ public Builder sanitizeUrls(boolean sanitizeUrls) { this.sanitizeUrls = sanitizeUrls; return this; } /** * {@link UrlSanitizer} used to filter URL's if {@link #sanitizeUrls} is true. * * @param urlSanitizer Filterer used to filter {@link Image} src and {@link Link}. * @return {@code this} * @since 0.14.0 */ public Builder urlSanitizer(UrlSanitizer urlSanitizer) { this.urlSanitizer = urlSanitizer; return this; } /** * Whether URLs of link or images should be percent-encoded, defaults to {@code 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 encodeURI in JS)
  • *
  • Unreserved characters such as "a" are preserved
  • *
  • Other characters such umlauts are percent-encoded
  • *
* * @param percentEncodeUrls true to percent-encode, false for leaving as-is * @return {@code this} */ public Builder percentEncodeUrls(boolean percentEncodeUrls) { this.percentEncodeUrls = percentEncodeUrls; return this; } /** * Whether documents that only contain a single paragraph should be rendered without the {@code

} tag. Set to * {@code true} to render without the tag; the default of {@code false} always renders the tag. * * @return {@code this} */ public Builder omitSingleParagraphP(boolean omitSingleParagraphP) { this.omitSingleParagraphP = omitSingleParagraphP; return this; } /** * Add a factory for an attribute provider for adding/changing HTML attributes to the rendered tags. * * @param attributeProviderFactory the attribute provider factory to add * @return {@code this} */ public Builder attributeProviderFactory(AttributeProviderFactory attributeProviderFactory) { Objects.requireNonNull(attributeProviderFactory, "attributeProviderFactory must not be null"); this.attributeProviderFactories.add(attributeProviderFactory); 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 the rendering for core node types can be overridden; the default rendering comes last.) * * @param nodeRendererFactory the factory for creating a node renderer * @return {@code this} */ public Builder nodeRendererFactory(HtmlNodeRendererFactory nodeRendererFactory) { Objects.requireNonNull(nodeRendererFactory, "nodeRendererFactory must not be null"); this.nodeRendererFactories.add(nodeRendererFactory); return this; } /** * @param extensions extensions to use on this HTML renderer * @return {@code this} */ public Builder extensions(Iterable extensions) { Objects.requireNonNull(extensions, "extensions must not be null"); for (Extension extension : extensions) { if (extension instanceof HtmlRendererExtension) { HtmlRendererExtension htmlRendererExtension = (HtmlRendererExtension) extension; htmlRendererExtension.extend(this); } } return this; } } /** * Extension for {@link HtmlRenderer}. */ public interface HtmlRendererExtension extends Extension { void extend(Builder rendererBuilder); } private class RendererContext implements HtmlNodeRendererContext, AttributeProviderContext { private final HtmlWriter htmlWriter; private final List attributeProviders; private final NodeRendererMap nodeRendererMap = new NodeRendererMap(); private RendererContext(HtmlWriter htmlWriter) { this.htmlWriter = htmlWriter; attributeProviders = new ArrayList<>(attributeProviderFactories.size()); for (var attributeProviderFactory : attributeProviderFactories) { attributeProviders.add(attributeProviderFactory.create(this)); } for (var factory : nodeRendererFactories) { var renderer = factory.create(this); nodeRendererMap.add(renderer); } } @Override public boolean shouldEscapeHtml() { return escapeHtml; } @Override public boolean shouldOmitSingleParagraphP() { return omitSingleParagraphP; } @Override public boolean shouldSanitizeUrls() { return sanitizeUrls; } @Override public UrlSanitizer urlSanitizer() { return urlSanitizer; } @Override public String encodeUrl(String url) { if (percentEncodeUrls) { return Escaping.percentEncodeUrl(url); } else { return url; } } @Override public Map extendAttributes(Node node, String tagName, Map attributes) { Map attrs = new LinkedHashMap<>(attributes); setCustomAttributes(node, tagName, attrs); return attrs; } @Override public HtmlWriter getWriter() { return htmlWriter; } @Override public String getSoftbreak() { return softbreak; } @Override public void render(Node node) { nodeRendererMap.render(node); } public void beforeRoot(Node node) { nodeRendererMap.beforeRoot(node); } public void afterRoot(Node node) { nodeRendererMap.afterRoot(node); } private void setCustomAttributes(Node node, String tagName, Map attrs) { for (AttributeProvider attributeProvider : attributeProviders) { attributeProvider.setAttributes(node, tagName, attrs); } } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy