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}