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.formatter.Formatter;
30 import com.vladsch.flexmark.formatter.MarkdownWriter;
31 import com.vladsch.flexmark.formatter.NodeFormatter;
32 import com.vladsch.flexmark.formatter.NodeFormatterContext;
33 import com.vladsch.flexmark.formatter.NodeFormatterFactory;
34 import com.vladsch.flexmark.formatter.NodeFormattingHandler;
35 import com.vladsch.flexmark.html.HtmlRenderer;
36 import com.vladsch.flexmark.html.HtmlWriter;
37 import com.vladsch.flexmark.html.renderer.NodeRenderer;
38 import com.vladsch.flexmark.html.renderer.NodeRendererContext;
39 import com.vladsch.flexmark.html.renderer.NodeRendererFactory;
40 import com.vladsch.flexmark.html.renderer.NodeRenderingHandler;
41 import com.vladsch.flexmark.html2md.converter.FlexmarkHtmlConverter;
42 import com.vladsch.flexmark.html2md.converter.HtmlMarkdownWriter;
43 import com.vladsch.flexmark.html2md.converter.HtmlNodeConverterContext;
44 import com.vladsch.flexmark.html2md.converter.HtmlNodeRenderer;
45 import com.vladsch.flexmark.html2md.converter.HtmlNodeRendererFactory;
46 import com.vladsch.flexmark.html2md.converter.HtmlNodeRendererHandler;
47 import com.vladsch.flexmark.parser.InlineParser;
48 import com.vladsch.flexmark.parser.InlineParserExtension;
49 import com.vladsch.flexmark.parser.InlineParserExtensionFactory;
50 import com.vladsch.flexmark.parser.LightInlineParser;
51 import com.vladsch.flexmark.parser.Parser;
52 import com.vladsch.flexmark.util.ast.Node;
53 import com.vladsch.flexmark.util.data.DataHolder;
54 import com.vladsch.flexmark.util.data.DataKey;
55 import com.vladsch.flexmark.util.data.MutableDataHolder;
56 import com.vladsch.flexmark.util.misc.Extension;
57 import com.vladsch.flexmark.util.sequence.BasedSequence;
58 import com.vladsch.flexmark.util.sequence.CharSubSequence;
59
60 import org.jsoup.nodes.Element;
61
62 import java.util.Collections;
63 import java.util.Set;
64 import java.util.regex.Matcher;
65 import java.util.regex.Pattern;
66
67 import edu.umd.cs.findbugs.annotations.NonNull;
68
69 public class InsertAnchorExtension
70 implements Parser.ParserExtension, HtmlRenderer.HtmlRendererExtension,
71 Formatter.FormatterExtension, FlexmarkHtmlConverter.HtmlConverterExtension {
72 public static final DataKey<Boolean> ENABLE_INLINE_INSERT_ANCHORS
73 = new DataKey<>("ENABLE_INLINE_INSERT_ANCHORS", true);
74 public static final DataKey<Boolean> ENABLE_RENDERING = new DataKey<>("ENABLE_RENDERING", true);
75
76 public static Extension create() {
77 return new InsertAnchorExtension();
78 }
79
80 @Override
81 public void parserOptions(MutableDataHolder options) {
82
83 }
84
85 @Override
86 public void rendererOptions(MutableDataHolder options) {
87
88 }
89
90 @Override
91 public void extend(HtmlRenderer.Builder rendererBuilder, String rendererType) {
92 rendererBuilder.nodeRendererFactory(new InsertAnchorNodeRenderer.Factory());
93 }
94
95 @Override
96 public void extend(Parser.Builder parserBuilder) {
97 if (ENABLE_INLINE_INSERT_ANCHORS.get(parserBuilder)) {
98 parserBuilder.customInlineParserExtensionFactory(new InsertAnchorInlineParser.Factory());
99 }
100 }
101
102 @Override
103 public void extend(Formatter.Builder builder) {
104 builder.nodeFormatterFactory(new InsertAnchorFormatter.Factory());
105 }
106
107 @Override
108 public void extend(FlexmarkHtmlConverter.Builder builder) {
109 builder.htmlNodeRendererFactory(new InsertAnchorHtmlNodeRenderer.Factory());
110 }
111
112 private static class InsertAnchorOptions {
113 public final boolean enableInlineInsertAnchors;
114 public final boolean enableRendering;
115
116 public InsertAnchorOptions(DataHolder options) {
117 enableInlineInsertAnchors = ENABLE_INLINE_INSERT_ANCHORS.get(options);
118 enableRendering = ENABLE_RENDERING.get(options);
119 }
120 }
121
122 private static class InsertAnchorNodeRenderer implements NodeRenderer {
123 private final InsertAnchorOptions options;
124
125 public InsertAnchorNodeRenderer(DataHolder options) {
126 this.options = new InsertAnchorOptions(options);
127 }
128
129 @Override
130 public Set<NodeRenderingHandler<?>> getNodeRenderingHandlers() {
131 return Collections.singleton(new NodeRenderingHandler<>(InsertAnchorNode.class, this::render));
132 }
133
134 @SuppressWarnings("unused")
135 protected void render(InsertAnchorNode node, NodeRendererContext context, HtmlWriter html) {
136 if (options.enableRendering) {
137 html.attr("type", node.getType()).attr("id-ref", node.getIdReference()).withAttr().tagVoid("insert");
138 }
139 }
140
141
142
143
144
145
146
147
148
149
150
151
152 public static class Factory implements NodeRendererFactory {
153
154 @Override
155 public NodeRenderer apply(DataHolder options) {
156 return new InsertAnchorNodeRenderer(options);
157 }
158
159 }
160
161 }
162
163 private static class InsertAnchorInlineParser implements InlineParserExtension {
164 private static final Pattern PATTERN = Pattern.compile("\\{\\{\\s*insert:\\s*([^\\s]+),\\s*([^\\s]+)\\s*\\}\\}");
165
166 public InsertAnchorInlineParser(@SuppressWarnings("unused") LightInlineParser inlineParser) {
167
168 }
169
170 @Override
171 public void finalizeDocument(InlineParser inlineParser) {
172
173 }
174
175 @Override
176 public void finalizeBlock(InlineParser inlineParser) {
177
178 }
179
180 @Override
181 public boolean parse(LightInlineParser inlineParser) {
182 if (inlineParser.peek() == '{') {
183 BasedSequence input = inlineParser.getInput();
184 Matcher matcher = inlineParser.matcher(PATTERN);
185 if (matcher != null) {
186 BasedSequence type = input.subSequence(matcher.start(1), matcher.end(1));
187 BasedSequence idReference = input.subSequence(matcher.start(2), matcher.end(2));
188 assert type != null;
189 assert idReference != null;
190 inlineParser.appendNode(new InsertAnchorNode(type, idReference));
191 return true;
192 }
193 }
194 return false;
195 }
196
197 public static class Factory implements InlineParserExtensionFactory {
198 @Override
199 public Set<Class<?>> getAfterDependents() {
200 return Collections.emptySet();
201 }
202
203 @Override
204 public CharSequence getCharacters() {
205 return "{";
206 }
207
208 @Override
209 public Set<Class<?>> getBeforeDependents() {
210 return Collections.emptySet();
211 }
212
213 @Override
214 public InlineParserExtension apply(LightInlineParser lightInlineParser) {
215 return new InsertAnchorInlineParser(lightInlineParser);
216 }
217
218 @Override
219 public boolean affectsGlobalScope() {
220 return false;
221 }
222 }
223 }
224
225 private static class InsertAnchorFormatter implements NodeFormatter {
226 private final InsertAnchorOptions options;
227
228 public InsertAnchorFormatter(DataHolder options) {
229 this.options = new InsertAnchorOptions(options);
230 }
231
232 @Override
233 public Set<NodeFormattingHandler<?>> getNodeFormattingHandlers() {
234 return options.enableInlineInsertAnchors
235 ? Collections.singleton(new NodeFormattingHandler<>(InsertAnchorNode.class, this::render))
236 : Collections.emptySet();
237 }
238
239 @SuppressWarnings("unused")
240 protected void render(InsertAnchorNode node, NodeFormatterContext context, MarkdownWriter markdown) {
241 if (options.enableRendering) {
242 markdown.append("{{ insert: ");
243 markdown.append(node.getType());
244 markdown.append(", ");
245 markdown.append(node.getIdReference());
246 markdown.append(" }}");
247 }
248 }
249
250 @Override
251 public Set<Class<?>> getNodeClasses() {
252 return Collections.singleton(InsertAnchorNode.class);
253 }
254
255 public static class Factory implements NodeFormatterFactory {
256
257 @Override
258 public NodeFormatter create(DataHolder options) {
259 return new InsertAnchorFormatter(options);
260 }
261
262 }
263 }
264
265 private static class InsertAnchorHtmlNodeRenderer implements HtmlNodeRenderer {
266 private final InsertAnchorOptions options;
267
268 public InsertAnchorHtmlNodeRenderer(DataHolder options) {
269 this.options = new InsertAnchorOptions(options);
270 }
271
272 @Override
273 public Set<HtmlNodeRendererHandler<?>> getHtmlNodeRendererHandlers() {
274 return options.enableInlineInsertAnchors
275 ? Collections.singleton(new HtmlNodeRendererHandler<>("insert", Element.class, this::processInsert))
276 : Collections.emptySet();
277 }
278
279 private void processInsert(
280 Element node,
281 @SuppressWarnings("unused") HtmlNodeConverterContext context,
282 HtmlMarkdownWriter out) {
283
284 String type = node.attr("type");
285 String idRef = node.attr("id-ref");
286
287 out.append("{{ insert: ");
288 out.append(type);
289 out.append(", ");
290 out.append(idRef);
291 out.append(" }}");
292 }
293
294 public static class Factory implements HtmlNodeRendererFactory {
295
296 @Override
297 public HtmlNodeRenderer apply(DataHolder options) {
298 return new InsertAnchorHtmlNodeRenderer(options);
299 }
300 }
301 }
302
303 public static class InsertAnchorNode
304 extends Node {
305
306 @NonNull
307 private BasedSequence type;
308 @NonNull
309 private BasedSequence idReference;
310
311 @SuppressWarnings("null")
312 public InsertAnchorNode(@NonNull String type, @NonNull String idReference) {
313 this(CharSubSequence.of(type), CharSubSequence.of(idReference));
314 }
315
316 public InsertAnchorNode(@NonNull BasedSequence type, @NonNull BasedSequence idReference) {
317 this.type = type;
318 this.idReference = idReference;
319 }
320
321 @NonNull
322 public BasedSequence getType() {
323 return type;
324 }
325
326 @NonNull
327 public BasedSequence getIdReference() {
328 return idReference;
329 }
330
331 public void setIdReference(@NonNull BasedSequence value) {
332 this.idReference = value;
333 }
334
335 @Override
336 @NonNull
337 public BasedSequence[] getSegments() {
338 @NonNull BasedSequence[] retval = { getType(), getIdReference() };
339 return retval;
340 }
341
342 @Override
343 public void getAstExtra(StringBuilder out) {
344 segmentSpanChars(out, getType(), "type");
345 segmentSpanChars(out, getIdReference(), "id-ref");
346 }
347 }
348 }