001/* 002 * Portions of this software was developed by employees of the National Institute 003 * of Standards and Technology (NIST), an agency of the Federal Government and is 004 * being made available as a public service. Pursuant to title 17 United States 005 * Code Section 105, works of NIST employees are not subject to copyright 006 * protection in the United States. This software may be subject to foreign 007 * copyright. Permission in the United States and in foreign countries, to the 008 * extent that NIST may hold copyright, to use, copy, modify, create derivative 009 * works, and distribute this software and its documentation without fee is hereby 010 * granted on a non-exclusive basis, provided that this notice and disclaimer 011 * of warranty appears in all copies. 012 * 013 * THE SOFTWARE IS PROVIDED 'AS IS' WITHOUT ANY WARRANTY OF ANY KIND, EITHER 014 * EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY 015 * THAT THE SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF 016 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND FREEDOM FROM 017 * INFRINGEMENT, AND ANY WARRANTY THAT THE DOCUMENTATION WILL CONFORM TO THE 018 * SOFTWARE, OR ANY WARRANTY THAT THE SOFTWARE WILL BE ERROR FREE. IN NO EVENT 019 * SHALL NIST BE LIABLE FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT, 020 * INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES, ARISING OUT OF, RESULTING FROM, 021 * OR IN ANY WAY CONNECTED WITH THIS SOFTWARE, WHETHER OR NOT BASED UPON WARRANTY, 022 * CONTRACT, TORT, OR OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY PERSONS OR 023 * PROPERTY OR OTHERWISE, AND WHETHER OR NOT LOSS WAS SUSTAINED FROM, OR AROSE OUT 024 * OF THE RESULTS OF, OR USE OF, THE SOFTWARE OR SERVICES PROVIDED HEREUNDER. 025 */ 026 027package gov.nist.secauto.metaschema.core.datatype.markup.flexmark; // NOPMD AST processor 028 029import com.vladsch.flexmark.formatter.Formatter; 030import com.vladsch.flexmark.formatter.MarkdownWriter; 031import com.vladsch.flexmark.formatter.NodeFormatter; 032import com.vladsch.flexmark.formatter.NodeFormatterContext; 033import com.vladsch.flexmark.formatter.NodeFormatterFactory; 034import com.vladsch.flexmark.formatter.NodeFormattingHandler; 035import com.vladsch.flexmark.html.HtmlRenderer; 036import com.vladsch.flexmark.html.HtmlWriter; 037import com.vladsch.flexmark.html.renderer.NodeRenderer; 038import com.vladsch.flexmark.html.renderer.NodeRendererContext; 039import com.vladsch.flexmark.html.renderer.NodeRendererFactory; 040import com.vladsch.flexmark.html.renderer.NodeRenderingHandler; 041import com.vladsch.flexmark.html2md.converter.FlexmarkHtmlConverter; 042import com.vladsch.flexmark.html2md.converter.HtmlMarkdownWriter; 043import com.vladsch.flexmark.html2md.converter.HtmlNodeConverterContext; 044import com.vladsch.flexmark.html2md.converter.HtmlNodeRenderer; 045import com.vladsch.flexmark.html2md.converter.HtmlNodeRendererFactory; 046import com.vladsch.flexmark.html2md.converter.HtmlNodeRendererHandler; 047import com.vladsch.flexmark.parser.InlineParser; 048import com.vladsch.flexmark.parser.InlineParserExtension; 049import com.vladsch.flexmark.parser.InlineParserExtensionFactory; 050import com.vladsch.flexmark.parser.LightInlineParser; 051import com.vladsch.flexmark.parser.Parser; 052import com.vladsch.flexmark.util.ast.Node; 053import com.vladsch.flexmark.util.data.DataHolder; 054import com.vladsch.flexmark.util.data.DataKey; 055import com.vladsch.flexmark.util.data.MutableDataHolder; 056import com.vladsch.flexmark.util.misc.Extension; 057import com.vladsch.flexmark.util.sequence.BasedSequence; 058import com.vladsch.flexmark.util.sequence.CharSubSequence; 059 060import org.jsoup.nodes.Element; 061 062import java.util.Collections; 063import java.util.Set; 064import java.util.regex.Matcher; 065import java.util.regex.Pattern; 066 067import edu.umd.cs.findbugs.annotations.NonNull; 068 069public class InsertAnchorExtension 070 implements Parser.ParserExtension, HtmlRenderer.HtmlRendererExtension, 071 Formatter.FormatterExtension, FlexmarkHtmlConverter.HtmlConverterExtension { 072 public static final DataKey<Boolean> ENABLE_INLINE_INSERT_ANCHORS 073 = new DataKey<>("ENABLE_INLINE_INSERT_ANCHORS", true); 074 public static final DataKey<Boolean> ENABLE_RENDERING = new DataKey<>("ENABLE_RENDERING", true); 075 076 public static Extension create() { 077 return new InsertAnchorExtension(); 078 } 079 080 @Override 081 public void parserOptions(MutableDataHolder options) { 082 // do nothing 083 } 084 085 @Override 086 public void rendererOptions(MutableDataHolder options) { 087 // do nothing 088 } 089 090 @Override 091 public void extend(HtmlRenderer.Builder rendererBuilder, String rendererType) { 092 rendererBuilder.nodeRendererFactory(new InsertAnchorNodeRenderer.Factory()); 093 } 094 095 @Override 096 public void extend(Parser.Builder parserBuilder) { 097 if (ENABLE_INLINE_INSERT_ANCHORS.get(parserBuilder)) { 098 parserBuilder.customInlineParserExtensionFactory(new InsertAnchorInlineParser.Factory()); 099 } 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}