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;
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      // do nothing
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( // NOPMD actually used in lambda
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         // html.text(Escaping.collapseWhitespace(node.getText(), true));
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 }