001/*
002 * Stallion Core: A Modern Web Framework
003 *
004 * Copyright (C) 2015 - 2016 Stallion Software LLC.
005 *
006 * This program is free software: you can redistribute it and/or modify it under the terms of the
007 * GNU General Public License as published by the Free Software Foundation, either version 2 of
008 * the License, or (at your option) any later version. This program is distributed in the hope that
009 * it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
010 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
011 * License for more details. You should have received a copy of the GNU General Public License
012 * along with this program.  If not, see <http://www.gnu.org/licenses/gpl-2.0.html>.
013 *
014 *
015 *
016 */
017
018package io.stallion.utils;
019
020import io.stallion.services.Log;
021import org.pegdown.Extensions;
022import org.pegdown.PegDownProcessor;
023
024import java.util.List;
025import java.util.regex.Matcher;
026import java.util.regex.Pattern;
027
028import static io.stallion.utils.Literals.*;
029import static io.stallion.Context.*;
030
031/**
032 * Default markdown processor for most stallion uses.
033 * Includes support for fenced code blocks, footnotes, smart quotes, tables, and rawHtml tags.
034 */
035public class Markdown {
036    private static Markdown _instance = null;
037
038    public static Markdown instance() {
039        if (_instance == null) {
040            _instance = new Markdown();
041        }
042        return _instance;
043    }
044
045    protected Markdown() {
046
047    }
048
049    public String process(String itemContent) {
050        itemContent = new MarkdownFootnotesProcessor(itemContent).process();
051        ParsedContent parsed = parseOutRawHtml(itemContent);
052        PegDownProcessor pegdownProcessor = new PegDownProcessor(
053                Extensions.FENCED_CODE_BLOCKS |
054                        Extensions.AUTOLINKS |
055                        Extensions.STRIKETHROUGH |
056                        Extensions.TABLES |
057                        Extensions.QUOTES |
058                        Extensions.SMARTS
059        );
060        itemContent = pegdownProcessor.markdownToHtml(parsed.content);
061        itemContent = swapInRawHtml(itemContent, parsed);
062        return itemContent;
063    }
064
065    private static class ParsedContent {
066        private String content = "";
067        private List<String> rawHtmls = list();
068    }
069
070    private static Pattern rawHtmlPattern = Pattern.compile("<rawHtml>([\\s|\\S]*?)</rawHtml>");
071    private static String RAW_HTML_HOLDER = "@!RaWhTmL=%s!@";
072
073    protected ParsedContent parseOutRawHtml(String content) {
074        ParsedContent parsed = new ParsedContent();
075        Matcher matcher = rawHtmlPattern.matcher(content);
076        StringBuffer sb = new StringBuffer();
077        for (Integer index: safeLoop(100)) {
078            boolean found = matcher.find();
079            if (!found) {
080                break;
081            }
082            parsed.rawHtmls.add(matcher.group(1));
083            matcher.appendReplacement(sb, String.format(RAW_HTML_HOLDER, index));
084        }
085        matcher.appendTail(sb);
086        parsed.content = sb.toString();
087        return parsed;
088    }
089
090    protected static Pattern rawHtmlHolderPattern = Pattern.compile("@!RaWhTmL=(\\d+)!@");
091    protected String swapInRawHtml(String content, ParsedContent parsed) {
092        if (parsed.rawHtmls.size() == 0) {
093            return content;
094        }
095        Matcher matcher = rawHtmlHolderPattern.matcher(content);
096        StringBuffer sb = new StringBuffer();
097        Log.info("Swapping in RawHtml to {0}", content);
098        for (Integer index: safeLoop(100)) {
099            boolean found = matcher.find();
100            if (!found) {
101                break;
102            }
103            Log.info("Match was: {0} {1} {2}", matcher.toString(), matcher.groupCount(), matcher.group(0));
104            int pIndex = Integer.parseInt(matcher.group(1));
105            matcher.appendReplacement(sb, Matcher.quoteReplacement(parsed.rawHtmls.get(pIndex)));
106            //matcher.appendReplacement(sb, parsed.rawHtmls.get(pIndex));
107        }
108        matcher.appendTail(sb);
109        return sb.toString();
110    }
111
112}