Skip to content

Commit 29cdecd

Browse files
committed
Make HTML rendering for nodes extensible (#35)
We had `CustomHtmlRenderer` for custom nodes before. That was ok for that, but didn't allow clients to override rendering for core nodes. This introduces a new concept `NodeRenderer` (including factories, who doesn't like those?) that is also used for the core nodes. That allows overriding the rendering and allows to define the rendering for custom nodes at the same time. NodeRenderers have access to rendering configuration and functionality, such as extending attributes. That fixes the problem that the renderer for table nodes wasn't extensible via `AttributeProvider` (#31).
1 parent bdcf366 commit 29cdecd

18 files changed

Lines changed: 752 additions & 457 deletions

File tree

commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/StrikethroughExtension.java

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22

33
import org.commonmark.Extension;
44
import org.commonmark.ext.gfm.strikethrough.internal.StrikethroughDelimiterProcessor;
5-
import org.commonmark.ext.gfm.strikethrough.internal.StrikethroughHtmlRenderer;
5+
import org.commonmark.ext.gfm.strikethrough.internal.StrikethroughNodeRenderer;
6+
import org.commonmark.html.renderer.NodeRenderer;
7+
import org.commonmark.html.renderer.NodeRendererContext;
8+
import org.commonmark.html.renderer.NodeRendererFactory;
69
import org.commonmark.parser.Parser;
710
import org.commonmark.html.HtmlRenderer;
811

@@ -33,7 +36,11 @@ public void extend(Parser.Builder parserBuilder) {
3336

3437
@Override
3538
public void extend(HtmlRenderer.Builder rendererBuilder) {
36-
rendererBuilder.customHtmlRenderer(new StrikethroughHtmlRenderer());
39+
rendererBuilder.nodeRendererFactory(new NodeRendererFactory() {
40+
@Override
41+
public NodeRenderer create(NodeRendererContext context) {
42+
return new StrikethroughNodeRenderer(context);
43+
}
44+
});
3745
}
38-
3946
}

commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/internal/StrikethroughHtmlRenderer.java

Lines changed: 0 additions & 31 deletions
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package org.commonmark.ext.gfm.strikethrough.internal;
2+
3+
import org.commonmark.ext.gfm.strikethrough.Strikethrough;
4+
import org.commonmark.html.HtmlWriter;
5+
import org.commonmark.html.renderer.NodeRenderer;
6+
import org.commonmark.html.renderer.NodeRendererContext;
7+
import org.commonmark.node.Node;
8+
9+
import java.util.Collections;
10+
import java.util.Map;
11+
import java.util.Set;
12+
13+
public class StrikethroughNodeRenderer implements NodeRenderer {
14+
15+
private final NodeRendererContext context;
16+
private final HtmlWriter html;
17+
18+
public StrikethroughNodeRenderer(NodeRendererContext context) {
19+
this.context = context;
20+
this.html = context.getHtmlWriter();
21+
}
22+
23+
@Override
24+
public Set<Class<? extends Node>> getNodeTypes() {
25+
return Collections.<Class<? extends Node>>singleton(Strikethrough.class);
26+
}
27+
28+
@Override
29+
public void render(Node node) {
30+
Map<String, String> attributes = context.extendAttributes(node, Collections.<String, String>emptyMap());
31+
html.tag("del", attributes);
32+
renderChildren(node);
33+
html.tag("/del");
34+
}
35+
36+
private void renderChildren(Node parent) {
37+
Node node = parent.getFirstChild();
38+
while (node != null) {
39+
Node next = node.getNext();
40+
context.render(node);
41+
node = next;
42+
}
43+
}
44+
}

commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TablesExtension.java

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22

33
import org.commonmark.Extension;
44
import org.commonmark.ext.gfm.tables.internal.TableBlockParser;
5-
import org.commonmark.ext.gfm.tables.internal.TableHtmlRenderer;
5+
import org.commonmark.ext.gfm.tables.internal.TableNodeRenderer;
6+
import org.commonmark.html.renderer.NodeRenderer;
7+
import org.commonmark.html.renderer.NodeRendererContext;
8+
import org.commonmark.html.renderer.NodeRendererFactory;
69
import org.commonmark.parser.Parser;
710
import org.commonmark.html.HtmlRenderer;
811

@@ -33,7 +36,11 @@ public void extend(Parser.Builder parserBuilder) {
3336

3437
@Override
3538
public void extend(HtmlRenderer.Builder rendererBuilder) {
36-
rendererBuilder.customHtmlRenderer(new TableHtmlRenderer());
39+
rendererBuilder.nodeRendererFactory(new NodeRendererFactory() {
40+
@Override
41+
public NodeRenderer create(NodeRendererContext context) {
42+
return new TableNodeRenderer(context);
43+
}
44+
});
3745
}
38-
3946
}

commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableHtmlRenderer.java

Lines changed: 0 additions & 99 deletions
This file was deleted.
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
package org.commonmark.ext.gfm.tables.internal;
2+
3+
import org.commonmark.ext.gfm.tables.*;
4+
import org.commonmark.html.HtmlWriter;
5+
import org.commonmark.html.renderer.NodeRenderer;
6+
import org.commonmark.html.renderer.NodeRendererContext;
7+
import org.commonmark.node.Node;
8+
9+
import java.util.*;
10+
11+
public class TableNodeRenderer implements NodeRenderer {
12+
13+
private final HtmlWriter htmlWriter;
14+
private final NodeRendererContext context;
15+
16+
public TableNodeRenderer(NodeRendererContext context) {
17+
this.htmlWriter = context.getHtmlWriter();
18+
this.context = context;
19+
}
20+
21+
@Override
22+
public Set<Class<? extends Node>> getNodeTypes() {
23+
return new HashSet<>(Arrays.asList(
24+
TableBlock.class,
25+
TableHead.class,
26+
TableBody.class,
27+
TableRow.class,
28+
TableCell.class
29+
));
30+
}
31+
32+
@Override
33+
public void render(Node node) {
34+
if (node instanceof TableBlock) {
35+
renderBlock((TableBlock) node);
36+
} else if (node instanceof TableHead) {
37+
renderHead((TableHead) node);
38+
} else if (node instanceof TableBody) {
39+
renderBody((TableBody) node);
40+
} else if (node instanceof TableRow) {
41+
renderRow((TableRow) node);
42+
} else if (node instanceof TableCell) {
43+
renderCell((TableCell) node);
44+
}
45+
}
46+
47+
private void renderBlock(TableBlock tableBlock) {
48+
htmlWriter.line();
49+
htmlWriter.tag("table", getAttributes(tableBlock));
50+
renderChildren(tableBlock);
51+
htmlWriter.tag("/table");
52+
htmlWriter.line();
53+
}
54+
55+
private void renderHead(TableHead tableHead) {
56+
htmlWriter.line();
57+
htmlWriter.tag("thead", getAttributes(tableHead));
58+
renderChildren(tableHead);
59+
htmlWriter.tag("/thead");
60+
htmlWriter.line();
61+
}
62+
63+
private void renderBody(TableBody tableBody) {
64+
htmlWriter.line();
65+
htmlWriter.tag("tbody", getAttributes(tableBody));
66+
renderChildren(tableBody);
67+
htmlWriter.tag("/tbody");
68+
htmlWriter.line();
69+
}
70+
71+
private void renderRow(TableRow tableRow) {
72+
htmlWriter.line();
73+
htmlWriter.tag("tr", getAttributes(tableRow));
74+
renderChildren(tableRow);
75+
htmlWriter.tag("/tr");
76+
htmlWriter.line();
77+
}
78+
79+
private void renderCell(TableCell tableCell) {
80+
String tag = tableCell.isHeader() ? "th" : "td";
81+
htmlWriter.tag(tag, getCellAttributes(tableCell));
82+
renderChildren(tableCell);
83+
htmlWriter.tag("/" + tag);
84+
}
85+
86+
private Map<String, String> getAttributes(Node node) {
87+
return context.extendAttributes(node, Collections.<String, String>emptyMap());
88+
}
89+
90+
private Map<String, String> getCellAttributes(TableCell tableCell) {
91+
if (tableCell.getAlignment() != null) {
92+
return context.extendAttributes(tableCell, Collections.singletonMap("align", getAlignValue(tableCell.getAlignment())));
93+
} else {
94+
return context.extendAttributes(tableCell, Collections.<String, String>emptyMap());
95+
}
96+
}
97+
98+
private static String getAlignValue(TableCell.Alignment alignment) {
99+
switch (alignment) {
100+
case LEFT:
101+
return "left";
102+
case CENTER:
103+
return "center";
104+
case RIGHT:
105+
return "right";
106+
}
107+
throw new IllegalStateException("Unknown alignment: " + alignment);
108+
}
109+
110+
private void renderChildren(Node parent) {
111+
Node node = parent.getFirstChild();
112+
while (node != null) {
113+
Node next = node.getNext();
114+
context.render(node);
115+
node = next;
116+
}
117+
}
118+
}

0 commit comments

Comments
 (0)