View Javadoc
1   /*
2    * Portions of this software was developed by employees of the National Institute
3    * of Standards and Technology (NIST), an agency of the Federal Government and is
4    * being made available as a public service. Pursuant to title 17 United States
5    * Code Section 105, works of NIST employees are not subject to copyright
6    * protection in the United States. This software may be subject to foreign
7    * copyright. Permission in the United States and in foreign countries, to the
8    * extent that NIST may hold copyright, to use, copy, modify, create derivative
9    * works, and distribute this software and its documentation without fee is hereby
10   * granted on a non-exclusive basis, provided that this notice and disclaimer
11   * of warranty appears in all copies.
12   *
13   * THE SOFTWARE IS PROVIDED 'AS IS' WITHOUT ANY WARRANTY OF ANY KIND, EITHER
14   * EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY
15   * THAT THE SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF
16   * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND FREEDOM FROM
17   * INFRINGEMENT, AND ANY WARRANTY THAT THE DOCUMENTATION WILL CONFORM TO THE
18   * SOFTWARE, OR ANY WARRANTY THAT THE SOFTWARE WILL BE ERROR FREE.  IN NO EVENT
19   * SHALL NIST BE LIABLE FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT,
20   * INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES, ARISING OUT OF, RESULTING FROM,
21   * OR IN ANY WAY CONNECTED WITH THIS SOFTWARE, WHETHER OR NOT BASED UPON WARRANTY,
22   * CONTRACT, TORT, OR OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY PERSONS OR
23   * PROPERTY OR OTHERWISE, AND WHETHER OR NOT LOSS WAS SUSTAINED FROM, OR AROSE OUT
24   * OF THE RESULTS OF, OR USE OF, THE SOFTWARE OR SERVICES PROVIDED HEREUNDER.
25   */
26  
27  package gov.nist.secauto.metaschema.core.datatype.markup.flexmark; // NOPMD AST processor
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      // do nothing
83    }
84  
85    @Override
86    public void rendererOptions(MutableDataHolder options) {
87      // do nothing
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     // @Override
142     // public Set<NodeRenderingHandler<?>> getNodeRenderingHandlers() {
143     // HashSet<NodeRenderingHandler<?>> set = new
144     // HashSet<NodeRenderingHandler<?>>();
145     // set.add(new NodeRenderingHandler<Macro>(Macro.class, new
146     // CustomNodeRenderer<Macro>() {
147     // @Override
148     // public void render(Macro node, NodeRendererContext context, HtmlWriter html)
149     // {
150     // MacroNodeRenderer.this.render(node, context, html); }
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       // do nothing
168     }
169 
170     @Override
171     public void finalizeDocument(InlineParser inlineParser) {
172       // do nothing
173     }
174 
175     @Override
176     public void finalizeBlock(InlineParser inlineParser) {
177       // do nothing
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; // NOPMD - readability
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( // NOPMD used as lambda
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 }