AsciiDocToHtml.java
package pro.verron.asciidoc.converters;
import pro.verron.asciidoc.core.*;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import static java.util.stream.Collectors.joining;
/// Converts an [AsciiDocModel] to an HTML document string.
///
/// Implements [Function]<[AsciiDocModel], String> and renders headings,
/// paragraphs, lists, tables, blockquotes, code blocks, images, and inline
/// elements (bold, italic, links) into their HTML counterparts.
///
/// @see AsciiDocToText
/// @see AsciiDocToSvg
public final class AsciiDocToHtml
implements Function<AsciiDocModel, String> {
/// Constructs a new [AsciiDocToHtml] converter.
public AsciiDocToHtml() {
}
private static String renderBlock(Block block) {
switch (block) {
case Heading(_, int level, List<Inline> inlines) -> {
return String.format("<h%d>%s</h%d>\n",
level,
renderInlines(inlines),
level);
}
case Paragraph(List<String> header, List<Inline> inlines) -> {
if (header.isEmpty())
return String.format("<p>%s</p>\n", renderInlines(inlines));
else return String.format("<p class=\"%s\">%s</p>\n",
header.getFirst(),
renderInlines(inlines));
}
case UnorderedList(List<ListItem> items) -> {
return "<ul>\n" + items.stream()
.map(item -> " <li>" + renderInlines(
item.inlines()) + "</li>\n")
.collect(joining()) + "</ul>\n";
}
case OrderedList(List<ListItem> items) -> {
return "<ol>\n" + items.stream()
.map(item -> " <li>" + renderInlines(
item.inlines()) + "</li>\n")
.collect(joining()) + "</ol>\n";
}
case Table(List<Row> rows) -> {
var sb = new StringBuilder("<table>\n");
for (var row : rows) {
sb.append(" <tr>\n");
for (var cell : row.cells()) {
sb.append(" <td>")
.append(cell.blocks()
.stream()
.map(AsciiDocToHtml::renderBlock)
.collect(joining()))
.append("</td>\n");
}
sb.append(" </tr>\n");
}
sb.append("</table>\n");
return sb.toString();
}
case QuoteBlock(List<Inline> inlines) -> {
return "<blockquote>" + renderInlines(inlines)
+ "</blockquote>\n";
}
case CodeBlock(String language, String content) -> {
return String.format(
"<pre><code class=\"language-%s\">%s</code></pre>\n",
language,
content);
}
case ImageBlock(String url, String altText) -> {
return String.format("<img src=\"%s\" alt=\"%s\">\n",
url,
altText);
}
default -> { /* DO NOTHING */ }
}
return "";
}
private static String renderInlines(List<Inline> inlines) {
var sb = new StringBuilder();
for (var inline : inlines) {
switch (inline) {
case Text(String str) -> sb.append(str);
case Bold(List<Inline> children) -> sb.append("<b>")
.append(renderInlines(
children))
.append("</b>");
case Italic(List<Inline> children) -> sb.append("<i>")
.append(renderInlines(
children))
.append("</i>");
case Tab _ -> sb.append(" ");
case Link(String url, String str) -> sb.append(String.format(
"<a href=\"%s\">%s</a>",
url,
str));
case ImageInline(String url, Map<String, String> map) ->
sb.append(String.format("<img src=\"%s\" alt=\"%s\">",
url,
map.get("title")));
default -> { /* DO NOTHING */ }
}
}
return sb.toString();
}
/// Converts the given AsciiDoc model into an HTML document string.
///
/// @param model the parsed AsciiDoc model
///
/// @return the HTML document as a string
public String apply(AsciiDocModel model) {
var html = new StringBuilder("""
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
""");
for (var block : model.getBlocks()) html.append(renderBlock(block));
html.append("""
</body>
</html>""");
return html.toString();
}
}