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}