1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 package gov.nist.secauto.metaschema.core.datatype.markup.flexmark;
28
29 import com.vladsch.flexmark.ast.Code;
30 import com.vladsch.flexmark.ast.Text;
31 import com.vladsch.flexmark.html.HtmlRenderer;
32 import com.vladsch.flexmark.html.HtmlRendererOptions;
33 import com.vladsch.flexmark.html.HtmlWriter;
34 import com.vladsch.flexmark.html.renderer.NodeRenderer;
35 import com.vladsch.flexmark.html.renderer.NodeRendererContext;
36 import com.vladsch.flexmark.html.renderer.NodeRendererFactory;
37 import com.vladsch.flexmark.html.renderer.NodeRenderingHandler;
38 import com.vladsch.flexmark.parser.Parser;
39 import com.vladsch.flexmark.util.ast.Node;
40 import com.vladsch.flexmark.util.data.DataHolder;
41 import com.vladsch.flexmark.util.data.MutableDataHolder;
42 import com.vladsch.flexmark.util.sequence.Escaping;
43 import com.vladsch.flexmark.util.sequence.Range;
44 import com.vladsch.flexmark.util.sequence.SequenceUtils;
45
46 import gov.nist.secauto.metaschema.core.util.ObjectUtils;
47
48 import java.util.Collections;
49 import java.util.Set;
50 import java.util.regex.Pattern;
51
52 import edu.umd.cs.findbugs.annotations.NonNull;
53 import edu.umd.cs.findbugs.annotations.Nullable;
54 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
55
56 public class HtmlCodeRenderExtension
57 implements HtmlRenderer.HtmlRendererExtension {
58 private static final Pattern EOL_PATTERN = Pattern.compile("\r\n|\r|\n");
59
60 public static HtmlCodeRenderExtension create() {
61 return new HtmlCodeRenderExtension();
62 }
63
64 @Override
65 public void rendererOptions(MutableDataHolder options) {
66
67 }
68
69 @Override
70 public void extend(HtmlRenderer.Builder rendererBuilder, String rendererType) {
71 rendererBuilder.nodeRendererFactory(new CodeNodeHtmlRenderer.Factory());
72 }
73
74 static final class CodeNodeHtmlRenderer implements NodeRenderer {
75 private final boolean codeSoftLineBreaks;
76
77 private CodeNodeHtmlRenderer(DataHolder options) {
78 codeSoftLineBreaks = Parser.CODE_SOFT_LINE_BREAKS.get(options);
79 }
80
81 @Override
82 public @Nullable Set<NodeRenderingHandler<?>> getNodeRenderingHandlers() {
83 return Collections.singleton(
84 new NodeRenderingHandler<>(Code.class, this::render));
85 }
86
87 @SuppressFBWarnings(value = "RV_RETURN_VALUE_IGNORED_NO_SIDE_EFFECT", justification = "false positive")
88 private void render(
89 @NonNull Code node,
90 @NonNull NodeRendererContext context,
91 @NonNull HtmlWriter html) {
92 HtmlRendererOptions htmlOptions = context.getHtmlOptions();
93
94 boolean customTag = htmlOptions.codeStyleHtmlOpen != null || htmlOptions.codeStyleHtmlClose != null;
95 if (customTag) {
96 html.raw(ObjectUtils.notNull(htmlOptions.codeStyleHtmlOpen));
97 } else {
98 if (context.getHtmlOptions().sourcePositionParagraphLines) {
99 html.withAttr().tag("code");
100 } else {
101 html.srcPos(node.getText()).withAttr().tag("code");
102 }
103 }
104
105 if (codeSoftLineBreaks && !htmlOptions.isSoftBreakAllSpaces) {
106 for (Node child : node.getChildren()) {
107 if (child instanceof Text) {
108 html.text(Escaping.collapseWhitespace(child.getChars(), false));
109 } else {
110 context.render(child);
111 }
112 }
113 } else {
114 String text = EOL_PATTERN.matcher(node.getText()).replaceAll(" ");
115 if (!text.isBlank() && SequenceUtils.startsWithWhitespace(text) && SequenceUtils.endsWithWhitespace(text)) {
116 html.text(SequenceUtils.subSequence(text, Range.of(1, text.length() - 1)));
117 } else {
118 html.raw(Escaping.escapeHtml(text, false));
119 }
120
121 }
122
123 if (customTag) {
124 html.raw(ObjectUtils.notNull(htmlOptions.codeStyleHtmlClose));
125 } else {
126 html.tag("/code");
127 }
128 }
129
130 public static class Factory implements NodeRendererFactory {
131
132 @Override
133 public NodeRenderer apply(DataHolder options) {
134 return new CodeNodeHtmlRenderer(options);
135 }
136
137 }
138 }
139 }