001/* 002 * JDrupes MDoclet 003 * Copyright (C) 2017, 2021 Michael N. Lipp 004 * 005 * This program is free software; you can redistribute it and/or modify it 006 * under the terms of the GNU Affero General Public License as published by 007 * the Free Software Foundation; either version 3 of the License, or 008 * (at your option) any later version. 009 * 010 * This program is distributed in the hope that it will be useful, but 011 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 012 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License 013 * for more details. 014 * 015 * You should have received a copy of the GNU Affero General Public License along 016 * with this program; if not, see <http://www.gnu.org/licenses/>. 017 */ 018 019package org.jdrupes.mdoclet.processors; 020 021import com.vladsch.flexmark.ext.abbreviation.AbbreviationExtension; 022import com.vladsch.flexmark.ext.anchorlink.AnchorLinkExtension; 023import com.vladsch.flexmark.ext.definition.DefinitionExtension; 024import com.vladsch.flexmark.ext.footnotes.FootnoteExtension; 025import com.vladsch.flexmark.ext.tables.TablesExtension; 026import com.vladsch.flexmark.ext.toc.TocExtension; 027import com.vladsch.flexmark.ext.typographic.TypographicExtension; 028import com.vladsch.flexmark.ext.wikilink.WikiLinkExtension; 029import com.vladsch.flexmark.html.HtmlRenderer; 030import com.vladsch.flexmark.parser.Parser; 031import com.vladsch.flexmark.parser.ParserEmulationProfile; 032import com.vladsch.flexmark.util.ast.Node; 033import com.vladsch.flexmark.util.data.MutableDataSet; 034import com.vladsch.flexmark.util.misc.Extension; 035 036import java.lang.reflect.InvocationTargetException; 037import java.util.ArrayList; 038import java.util.HashSet; 039import java.util.List; 040import java.util.Set; 041 042import org.jdrupes.mdoclet.MarkdownProcessor; 043import org.jdrupes.mdoclet.processors.flexmark.TopAnchorLinkExtension; 044 045/** 046 * This class provides an adapter for the 047 * [flexmark-java](https://github.com/vsch/flexmark-java) Markdown 048 * processor. 049 * 050 * The adapter supports the following flags: 051 * 052 * `--parser-profile` 053 * : Sets one of the profiles defined in 054 * `com.vladsch.flexmark.parser.ParserEmulationProfile`. The name of the 055 * profle is the lower case name of the enum value. At the time of this 056 * writing supported names are: 057 * - commonmark (default) 058 * - fixed_indent 059 * - kramdown 060 * - markdown 061 * - github_doc 062 * - multi_markdown 063 * - pegdown 064 * 065 * `--clear-extensions` 066 * : Clears the list of extensions. The following extensions are predefined: 067 * - [Abbreviation](https://github.com/vsch/flexmark-java/wiki/Extensions#abbreviation) 068 * - [AnchorLink](https://github.com/vsch/flexmark-java/wiki/Extensions#anchorlink) 069 * - [Definition](https://github.com/vsch/flexmark-java/wiki/Extensions#definition-lists) 070 * (Definition Lists)[^DefLists] 071 * - [Footnote](https://github.com/vsch/flexmark-java/wiki/Extensions#footnotes) 072 * - [Tables](https://github.com/vsch/flexmark-java/wiki/Extensions#tables) 073 * - [Table of Content](https://github.com/vsch/flexmark-java/wiki/Extensions#table-of-contents-1) 074 * - {@link TopAnchorLinkExtension TopAnchorLink} (provided by MDoclet) 075 * 076 * `--extension <name>` 077 * : Adds the flexmark extension with the given name to the list of extensions. 078 * If the name contains a dot, it is assumed to be a fully qualified class 079 * name. Else, it is expanded using the naming pattern used by flexmark. 080 * 081 * The parser also supports disabling the automatic highlight feature. 082 * 083 * [^DefLists]: If you use this extension, you'll most likely want to supply a 084 * modified style sheet because the standard stylesheet assumes all definition 085 * lists to be parameter defintion lists and formats them accordingly. 086 * 087 * Here are the changes made for this documentation: 088 * ```css 089 * /* [MOD] {@literal *}/ 090 * /* .contentContainer .description dl dd, {@literal *}/ .contentContainer .details dl dt, ... 091 * font-size:12px; 092 * font-weight:bold; 093 * margin:10px 0 0 0; 094 * color:#4E4E4E; 095 * } 096 * /* [MOD] Added {@literal *}/ 097 * dl dt { 098 * margin:10px 0 0 0; 099 * } 100 * 101 * /* [MOD] {@literal *}/ 102 * /* .contentContainer .description dl dd, {@literal *}/ .contentContainer .details dl dd, ... 103 * margin:5px 0 10px 0px; 104 * font-size:14px; 105 * font-family:'DejaVu Sans Mono',monospace; 106 * } 107 * ``` 108 * 109 */ 110public class FlexmarkProcessor implements MarkdownProcessor { 111 112 private static final String OPT_PROFILE = "--parser-profile"; 113 private static final String OPT_CLEAR_EXTENSIONS = "--clear-extensions"; 114 private static final String OPT_EXTENSION = "--extension"; 115 116 private Parser parser; 117 private HtmlRenderer renderer; 118 119 @Override 120 public int isSupportedOption(String option) { 121 switch (option) { 122 case OPT_CLEAR_EXTENSIONS: 123 case INTERNAL_OPT_DISABLE_AUTO_HIGHLIGHT: 124 return 0; 125 126 case OPT_PROFILE: 127 case OPT_EXTENSION: 128 return 1; 129 default: 130 return -1; 131 } 132 } 133 134 @Override 135 public void start(String[] options) { 136 Set<Class<? extends Extension>> extensions = new HashSet<>(); 137 extensions.add(AbbreviationExtension.class); 138 extensions.add(AnchorLinkExtension.class); 139 extensions.add(DefinitionExtension.class); 140 extensions.add(FootnoteExtension.class); 141 extensions.add(TablesExtension.class); 142 extensions.add(TypographicExtension.class); 143 extensions.add(TocExtension.class); 144 extensions.add(WikiLinkExtension.class); 145 extensions.add(TopAnchorLinkExtension.class); 146 147 MutableDataSet flexmarkOpts = new MutableDataSet(); 148 flexmarkOpts.set(HtmlRenderer.GENERATE_HEADER_ID, true); 149 150 for (String opt : options) { 151 String[] optAndArgs = opt.split("[= ]"); 152 switch (optAndArgs[0]) { 153 case OPT_PROFILE: 154 setFromProfile(flexmarkOpts, optAndArgs[1]); 155 continue; 156 157 case OPT_CLEAR_EXTENSIONS: 158 extensions.clear(); 159 continue; 160 161 case OPT_EXTENSION: 162 try { 163 String clsName = optAndArgs[1]; 164 if (!clsName.contains(".")) { 165 clsName = "com.vladsch.flexmark.ext." 166 + optAndArgs[1].toLowerCase() + "." + optAndArgs[1] 167 + "Extension"; 168 } 169 @SuppressWarnings("unchecked") 170 Class<? extends Extension> cls 171 = (Class<? extends Extension>) getClass() 172 .getClassLoader().loadClass(clsName); 173 extensions.add(cls); 174 continue; 175 } catch (ClassNotFoundException | ClassCastException e) { 176 throw new IllegalArgumentException("Cannot find extension " 177 + optAndArgs[1] + " (check spelling and classpath)."); 178 } 179 180 case INTERNAL_OPT_DISABLE_AUTO_HIGHLIGHT: 181 flexmarkOpts.set( 182 HtmlRenderer.FENCED_CODE_NO_LANGUAGE_CLASS, "nohighlight"); 183 continue; 184 185 default: 186 throw new IllegalArgumentException( 187 "Unknown option: " + optAndArgs[0]); 188 } 189 } 190 191 List<Extension> extObjs = new ArrayList<>(); 192 for (Class<? extends Extension> cls : extensions) { 193 try { 194 extObjs.add((Extension) cls.getMethod("create").invoke(null)); 195 } catch (IllegalAccessException | IllegalArgumentException 196 | InvocationTargetException | NoSuchMethodException 197 | SecurityException e) { 198 throw new IllegalArgumentException( 199 "Cannot create extension of type " + cls + "."); 200 } 201 } 202 if (!extObjs.isEmpty()) { 203 flexmarkOpts.set(Parser.EXTENSIONS, extObjs); 204 } 205 parser = Parser.builder(flexmarkOpts).build(); 206 renderer = HtmlRenderer.builder(flexmarkOpts).build(); 207 } 208 209 private void setFromProfile(MutableDataSet fmOpts, String profileName) { 210 for (ParserEmulationProfile p : ParserEmulationProfile.values()) { 211 if (p.toString().equalsIgnoreCase(profileName)) { 212 fmOpts.setFrom(p); 213 return; 214 } 215 } 216 throw new IllegalArgumentException("Unknown profile: " + profileName); 217 } 218 219 /* 220 * (non-Javadoc) 221 * 222 * @see org.jdrupes.mdoclet.MarkdownProcessor#toHtml(java.lang.String) 223 */ 224 @Override 225 public String toHtml(String markdown) { 226 Node document = parser.parse(markdown); 227 return renderer.render(document); 228 } 229 230 /* 231 * (non-Javadoc) 232 * 233 * @see 234 * org.jdrupes.mdoclet.MarkdownProcessor#toHtmlFragment(java.lang.String) 235 */ 236 @Override 237 public String toHtmlFragment(String markdown) { 238 markdown = markdown.trim(); 239 String result = toHtml(markdown).trim(); 240 if (markdown.startsWith("<")) { 241 // We should check if a surrounding tag was added. But until 242 // this pops up as an issue, let's keep this simple. 243 return result; 244 } 245 if (result.toUpperCase().startsWith("<P>") 246 && result.toUpperCase().endsWith("</P>")) { 247 return result.substring(3, result.length() - 4); 248 } 249 return result; 250 } 251 252}