001/* 002 * JDrupes MDoclet 003 * Copyright (C) 2017 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 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 General Public License 013 * for more details. 014 * 015 * You should have received a copy of the GNU General Public License along 016 * with this program; if not, see <http://www.gnu.org/licenses/>. 017 */ 018package org.jdrupes.mdoclet.processors; 019 020import java.lang.reflect.InvocationTargetException; 021import java.util.ArrayList; 022import java.util.HashSet; 023import java.util.List; 024import java.util.Set; 025 026import org.jdrupes.mdoclet.MarkdownProcessor; 027 028import com.vladsch.flexmark.Extension; 029import com.vladsch.flexmark.ast.Node; 030import com.vladsch.flexmark.ext.abbreviation.AbbreviationExtension; 031import com.vladsch.flexmark.ext.definition.DefinitionExtension; 032import com.vladsch.flexmark.ext.footnotes.FootnoteExtension; 033import com.vladsch.flexmark.ext.tables.TablesExtension; 034import com.vladsch.flexmark.ext.typographic.TypographicExtension; 035import com.vladsch.flexmark.ext.wikilink.WikiLinkExtension; 036import com.vladsch.flexmark.html.HtmlRenderer; 037import com.vladsch.flexmark.parser.Parser; 038import com.vladsch.flexmark.parser.ParserEmulationProfile; 039import com.vladsch.flexmark.util.options.MutableDataSet; 040 041/** 042 * This class provides an adapter for the 043 * [flexmark-java](https://github.com/vsch/flexmark-java) Markdown 044 * processor. 045 * 046 * The adapter supports the following flags: 047 * 048 * `-parser-profile` 049 * : Sets one of the profiles defined in 050 * `com.vladsch.flexmark.parser.ParserEmulationProfile`. The name of the 051 * profle is the lower case name of the enum value. At the time of this 052 * writing supported names are: 053 * - commonmark (default) 054 * - fixed_indent 055 * - kramdown 056 * - markdown 057 * - github_doc 058 * - multi_markdown 059 * - pegdown 060 * 061 * `-clear-extensions` 062 * : Clears the list of extensions. The following extensions are predefined: 063 * - [Abbreviation](https://github.com/vsch/flexmark-java/wiki/Extensions#abbreviation) 064 * - [Definition](https://github.com/vsch/flexmark-java/wiki/Extensions#definition-lists) 065 * (Definition Lists)[^DefLists] 066 * - [Footnote](https://github.com/vsch/flexmark-java/wiki/Extensions#footnotes) 067 * - [Tables](https://github.com/vsch/flexmark-java/wiki/Extensions#tables) 068 * 069 * `-extension <name>` 070 * : Adds the flexmark extension with the given name to the list of extensions. 071 * If the name contains a dot, it is assumed to be a fully qualified class 072 * name. Else, it is expanded using the naming pattern used by flexmark. 073 * 074 * The parser also supports disabling the automatic highlight feature. 075 * 076 * [^DefLists]: If you use this extension, you'll most likely want to supply a 077 * modified style sheet because the standard stylesheet assumes all definition 078 * lists to be parameter defintion lists and formats them accordingly. 079 * 080 * Here are the changes made for this documentation: 081 * ```css 082 * /* [MOD] {@literal *}/ 083 * /* .contentContainer .description dl dd, {@literal *}/ .contentContainer .details dl dt, .serializedFormContainer dl dt { 084 * font-size:12px; 085 * font-weight:bold; 086 * margin:10px 0 0 0; 087 * color:#4E4E4E; 088 * } 089 * /* [MOD] Added {@literal *}/ 090 * dl dt { 091 * margin:10px 0 0 0; 092 * } 093 * 094 * /* [MOD] {@literal *}/ 095 * /* .contentContainer .description dl dd, {@literal *}/ .contentContainer .details dl dd, .serializedFormContainer dl dd { 096 * margin:5px 0 10px 0px; 097 * font-size:14px; 098 * font-family:'DejaVu Sans Mono',monospace; 099 * } 100 * ``` 101 * 102 */ 103public class FlexmarkProcessor implements MarkdownProcessor { 104 105 final private static String OPT_PROFILE = "-parser-profile"; 106 final private static String OPT_CLEAR_EXTENSIONS = "-clear-extensions"; 107 final private static String OPT_EXTENSION = "-extension"; 108 109 private Parser parser; 110 private HtmlRenderer renderer; 111 112 @Override 113 public int isSupportedOption(String option) { 114 switch (option) { 115 case OPT_CLEAR_EXTENSIONS: 116 case INTERNAL_OPT_DISABLE_AUTO_HIGHLIGHT: 117 return 0; 118 119 case OPT_PROFILE: 120 case OPT_EXTENSION: 121 return 1; 122 default: 123 return -1; 124 } 125 } 126 127 128 @Override 129 public void start(String[][] options) { 130 MutableDataSet flexmarkOpts = new MutableDataSet(); 131 Set<Class<? extends Extension>> extensions = new HashSet<>(); 132 extensions.add(AbbreviationExtension.class); 133 extensions.add(DefinitionExtension.class); 134 extensions.add(FootnoteExtension.class); 135 extensions.add(TablesExtension.class); 136 extensions.add(TypographicExtension.class); 137 extensions.add(WikiLinkExtension.class); 138 139 for (String[] opt: options) { 140 switch (opt[0]) { 141 case OPT_PROFILE: 142 setFromProfile(flexmarkOpts, opt[1]); 143 continue; 144 145 case OPT_CLEAR_EXTENSIONS: 146 extensions.clear(); 147 continue; 148 149 case OPT_EXTENSION: 150 try { 151 String clsName = opt[1]; 152 if (!clsName.contains(".")) { 153 clsName = "com.vladsch.flexmark.ext." 154 + opt[1].toLowerCase() + "." + opt[1] + "Extension"; 155 } 156 @SuppressWarnings("unchecked") 157 Class<? extends Extension> cls = (Class<? extends Extension>) 158 getClass().getClassLoader().loadClass(clsName); 159 extensions.add(cls); 160 continue; 161 } catch (ClassNotFoundException | ClassCastException e) { 162 throw new IllegalArgumentException 163 ("Cannot find extension " + opt[1] 164 + " (check spelling and classpath)."); 165 } 166 167 case INTERNAL_OPT_DISABLE_AUTO_HIGHLIGHT: 168 flexmarkOpts.set 169 (HtmlRenderer.FENCED_CODE_NO_LANGUAGE_CLASS, "nohighlight"); 170 continue; 171 172 default: 173 throw new IllegalArgumentException("Unknown option: " + opt[0]); 174 } 175 } 176 177 List<Extension> extObjs = new ArrayList<>(); 178 for (Class<? extends Extension> cls: extensions) { 179 try { 180 extObjs.add((Extension)cls.getMethod("create").invoke(null)); 181 } catch (IllegalAccessException | IllegalArgumentException 182 | InvocationTargetException | NoSuchMethodException 183 | SecurityException e) { 184 throw new IllegalArgumentException("Cannot create extension of type " 185 + cls + "."); 186 } 187 } 188 if (!extObjs.isEmpty()) { 189 flexmarkOpts.set(Parser.EXTENSIONS, extObjs); 190 } 191 parser = Parser.builder(flexmarkOpts).build(); 192 renderer = HtmlRenderer.builder(flexmarkOpts).build(); 193 } 194 195 private void setFromProfile(MutableDataSet fmOpts, String profileName) { 196 for (ParserEmulationProfile p: ParserEmulationProfile.values()) { 197 if (p.toString().equalsIgnoreCase(profileName)) { 198 fmOpts.setFrom(p); 199 return; 200 } 201 } 202 throw new IllegalArgumentException("Unknown profile: " + profileName); 203 } 204 205 206 /* (non-Javadoc) 207 * @see org.jdrupes.mdoclet.MarkdownProcessor#toHtml(java.lang.String) 208 */ 209 @Override 210 public String toHtml(String markdown) { 211 Node document = parser.parse(markdown); 212 return renderer.render(document); 213 } 214 215}