001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.juneau.console.views.markdown; 018 019import java.util.*; 020 021/** 022 * A pluggable markdown-to-HTML renderer: hand it a markdown string, get back an HTML fragment. 023 * 024 * <p> 025 * This is the extension point of the optional markdown module — the toolkit's answer to "the way 026 * {@code ViewDef} turns a list of beans into a themed table, hand me a markdown string and give me back HTML". 027 * The interface is deliberately library-neutral: a single {@link #toHtml(String)} method, so any parser can sit 028 * behind it. The shipped default is {@link CommonmarkMarkdownRenderer} (commonmark-java with the GFM tables 029 * extension enabled), but a consumer can swap in their own parser without the toolkit knowing which library won. 030 * 031 * <h5 class='section'>Selecting an implementation:</h5> 032 * <p> 033 * {@link #resolve()} returns the active renderer via the standard {@link ServiceLoader} SPI mechanism: if a 034 * consumer registers their own provider (a {@code META-INF/services/} 035 * {@code org.apache.juneau.console.views.markdown.MarkdownRenderer} file naming their implementation class), 036 * that provider wins; otherwise the built-in commonmark-java default is used. The default is intentionally 037 * <b>not</b> registered as a service, so a single consumer-supplied provider is always unambiguous. 038 * 039 * <h5 class='section'>Example:</h5> 040 * <p class='bjava'> 041 * <jc>// Render markdown to an HTML fragment using whichever renderer is active.</jc> 042 * MarkdownRenderer <jv>renderer</jv> = MarkdownRenderer.<jsm>resolve</jsm>(); 043 * String <jv>html</jv> = <jv>renderer</jv>.toHtml(<js>"# Runbook\n\nStep one."</js>); 044 * </p> 045 * 046 * <p> 047 * The rendered HTML is intended to be dropped inside a <c>.jc-prose</c> container so it picks up the console's 048 * prose typography. Raw HTML in the source is escaped; {@code javascript:} / {@code data:} URLs are stripped. 049 * The row-detail runtime still allowlist-copies the fragment (never {@code innerHTML}) when 050 * {@code FieldFormat.MARKDOWN} is set. 051 * 052 * <h5 class='section'>See Also:</h5> 053 * <ul> 054 * <li class='jc'>{@link CommonmarkMarkdownRenderer} 055 * </ul> 056 * 057 * @since 10.0.0 058 */ 059public interface MarkdownRenderer { 060 061 /** 062 * Renders the specified markdown source to an HTML fragment. 063 * 064 * @param markdown The markdown source. Must not be <jk>null</jk> (an empty string renders to an empty fragment). 065 * @return The rendered HTML fragment. 066 * @throws IllegalArgumentException If <jv>markdown</jv> is <jk>null</jk>. 067 */ 068 String toHtml(String markdown); 069 070 /** 071 * Resolves the active renderer: the first {@link ServiceLoader}-registered provider if one exists, otherwise the 072 * built-in commonmark-java default. 073 * 074 * @return The active renderer. Never <jk>null</jk>. 075 */ 076 static MarkdownRenderer resolve() { 077 return resolve(ServiceLoader.load(MarkdownRenderer.class)); 078 } 079 080 /** 081 * Resolution seam used by {@link #resolve()}, exposed package-private so both the provider-found and 082 * default-fallback branches can be exercised without touching the JVM-wide {@link ServiceLoader} registry. 083 * 084 * @param providers The candidate providers (a live {@link ServiceLoader} in production). 085 * @return The first provider if any, otherwise a new {@link CommonmarkMarkdownRenderer}. 086 */ 087 static MarkdownRenderer resolve(Iterable<MarkdownRenderer> providers) { 088 var i = providers.iterator(); 089 return i.hasNext() ? i.next() : new CommonmarkMarkdownRenderer(); 090 } 091}