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;
028
029import com.vladsch.flexmark.ext.typographic.TypographicQuotes;
030import com.vladsch.flexmark.html.HtmlRenderer;
031import com.vladsch.flexmark.html.HtmlWriter;
032import com.vladsch.flexmark.html.renderer.NodeRenderer;
033import com.vladsch.flexmark.html.renderer.NodeRendererContext;
034import com.vladsch.flexmark.html.renderer.NodeRendererFactory;
035import com.vladsch.flexmark.html.renderer.NodeRenderingHandler;
036import com.vladsch.flexmark.html2md.converter.FlexmarkHtmlConverter;
037import com.vladsch.flexmark.html2md.converter.HtmlMarkdownWriter;
038import com.vladsch.flexmark.html2md.converter.HtmlNodeConverterContext;
039import com.vladsch.flexmark.html2md.converter.HtmlNodeRenderer;
040import com.vladsch.flexmark.html2md.converter.HtmlNodeRendererFactory;
041import com.vladsch.flexmark.html2md.converter.HtmlNodeRendererHandler;
042import com.vladsch.flexmark.parser.Parser;
043import com.vladsch.flexmark.parser.block.NodePostProcessor;
044import com.vladsch.flexmark.parser.block.NodePostProcessorFactory;
045import com.vladsch.flexmark.util.ast.DoNotDecorate;
046import com.vladsch.flexmark.util.ast.Document;
047import com.vladsch.flexmark.util.ast.Node;
048import com.vladsch.flexmark.util.ast.NodeTracker;
049import com.vladsch.flexmark.util.data.DataHolder;
050import com.vladsch.flexmark.util.data.MutableDataHolder;
051
052import org.jsoup.nodes.Element;
053
054import java.util.Collections;
055import java.util.Set;
056
057import edu.umd.cs.findbugs.annotations.NonNull;
058import edu.umd.cs.findbugs.annotations.Nullable;
059
060public class HtmlQuoteTagExtension
061    implements Parser.ParserExtension, HtmlRenderer.HtmlRendererExtension,
062    FlexmarkHtmlConverter.HtmlConverterExtension {
063
064  public static HtmlQuoteTagExtension create() {
065    return new HtmlQuoteTagExtension();
066  }
067
068  @Override
069  public void rendererOptions(MutableDataHolder options) {
070    // do nothing
071  }
072
073  @Override
074  public void parserOptions(MutableDataHolder options) {
075    // do nothing
076  }
077
078  @Override
079  public void extend(HtmlRenderer.Builder rendererBuilder, String rendererType) {
080    rendererBuilder.nodeRendererFactory(new QTagNodeRenderer.Factory());
081  }
082
083  @Override
084  public void extend(Parser.Builder parserBuilder) {
085    parserBuilder.postProcessorFactory(new QuoteReplacingPostProcessor.Factory());
086  }
087
088  @Override
089  public void extend(FlexmarkHtmlConverter.Builder builder) {
090    builder.htmlNodeRendererFactory(new QTagHtmlNodeRenderer.Factory());
091  }
092
093  static class QTagNodeRenderer implements NodeRenderer {
094
095    @Override
096    public @Nullable Set<NodeRenderingHandler<?>> getNodeRenderingHandlers() {
097      return Collections.singleton(
098          new NodeRenderingHandler<>(DoubleQuoteNode.class, this::render));
099    }
100
101    protected void render(@NonNull DoubleQuoteNode node, @NonNull NodeRendererContext context,
102        @NonNull HtmlWriter html) {
103      html.withAttr().tag("q");
104      context.renderChildren(node);
105      html.tag("/q");
106    }
107
108    public static class Factory implements NodeRendererFactory {
109
110      @Override
111      public NodeRenderer apply(DataHolder options) {
112        return new QTagNodeRenderer();
113      }
114
115    }
116  }
117
118  static class QuoteReplacingPostProcessor
119      extends NodePostProcessor {
120
121    @Override
122    public void process(NodeTracker state, Node node) {
123      if (node instanceof TypographicQuotes) {
124        TypographicQuotes typographicQuotes = (TypographicQuotes) node;
125        if (typographicQuotes.getOpeningMarker().matchChars("\"")) {
126          DoubleQuoteNode quoteNode = new DoubleQuoteNode(typographicQuotes);
127          node.insertAfter(quoteNode);
128          state.nodeAdded(quoteNode);
129          node.unlink();
130          state.nodeRemoved(node);
131        }
132      }
133    }
134
135    public static class Factory
136        extends NodePostProcessorFactory {
137      public Factory() {
138        super(false);
139        addNodeWithExclusions(TypographicQuotes.class, DoNotDecorate.class);
140      }
141
142      @NonNull
143      @Override
144      public NodePostProcessor apply(Document document) {
145        return new QuoteReplacingPostProcessor();
146      }
147    }
148  }
149
150  static class QTagHtmlNodeRenderer implements HtmlNodeRenderer {
151
152    @Override
153    public Set<HtmlNodeRendererHandler<?>> getHtmlNodeRendererHandlers() {
154      return Collections.singleton(new HtmlNodeRendererHandler<>("q", Element.class, this::renderMarkdown));
155    }
156
157    protected void renderMarkdown(Element element, HtmlNodeConverterContext context,
158        @SuppressWarnings("unused") HtmlMarkdownWriter out) {
159      context.wrapTextNodes(element, "\"", element.nextElementSibling() != null);
160    }
161
162    public static class Factory implements HtmlNodeRendererFactory {
163
164      @Override
165      public HtmlNodeRenderer apply(DataHolder options) {
166        return new QTagHtmlNodeRenderer();
167      }
168    }
169
170  }
171
172  public static class DoubleQuoteNode
173      extends TypographicQuotes {
174
175    public DoubleQuoteNode(TypographicQuotes node) {
176      super(node.getOpeningMarker(), node.getText(), node.getClosingMarker());
177      setTypographicOpening(node.getTypographicOpening());
178      setTypographicClosing(node.getTypographicClosing());
179      for (Node child : node.getChildren()) {
180        appendChild(child);
181      }
182    }
183  }
184}