Java の flexmark-java で Markdown をいじる

Markdown processor というと Perl (本家) / Python / Ruby / JavaScript などでは割と著名なライブラリがそろっている気がしますが、では Java でと言うと、そんなに聞いたことがない (というかあまり標準的なものがない) 気がします。

現状 (2017-03-02) open-sourced で見つかった Java の Markdown processor を一通り眺め、今もアクティブに開発が行われていて機能的・速度的にも満足できそうな [flexmark-java](https://github.com/vsch/flexmark-java) を少し触ってみたので、軽い紹介です。

Java の Markdown ライブラリ概観
================================

* [flexmark-java](https://github.com/vsch/flexmark-java)
    * 後述の commonmark-java をベースに作られているが、かなり拡張されている。
    * 今もアクティブに開発が進められている。
* [commonmark-java](https://github.com/atlassian/commonmark-java)
    * [Atlassian](https://www.atlassian.com/) 製。
    * 方言だらけの Markdown の仕様をどうにか標準化しようとして生まれた [CommonMark](http://commonmark.org/) の実装、という形。
* [pegdown](https://github.com/sirthias/pegdown)
    * 少し前までよく使われていた雰囲気ではあるものの、公式に end of life が宣言された。
    * *"Instead I suggest you turn to [@vsch](https://github.com/vsch)'s [flexmark-java](https://github.com/vsch/flexmark-java), ..."* とも言われている。
* [markedj](https://github.com/gitbucket/markedj)
    * JavaScript で有名な [marked.js](https://github.com/chjj/marked) の Java 移植。
    * [GitBucket](https://gitbucket.github.io/gitbucket-news/) プロジェクト製。
* [Markdown4j](https://code.google.com/archive/p/markdown4j/)
    * 最終リリース (2.2) が 2013-01-12 で、かつ Google Code (shut down) に置かれていた。
    * もうメンテナンスされていない模様。
    * クローンがいくつか GitHub にある。
* [MarkdownJ](https://github.com/myabc/markdownj)
    * コード本体の最終更新が 2014-02-19 で、あまりメンテナンスされていない模様。
* [MarkdownPapers](https://github.com/lruiz/MarkdownPapers)
    * 比較的最近 (2016-03-17) までは更新されていたものの、止まっている模様。
* [Txtmark](https://github.com/rjeschke/txtmark)
    * 2015-07-23 最終更新。

flexmark-java facts
====================

* commonmark-java から 2016-06-02 に [fork](https://github.com/vsch/flexmark-java/commit/03e84e1bb417499d711d5a9584fd396c4d07fa2c) して始まった。
* [XWiki](https://jira.xwiki.org/browse/MARKDOWN-16), [Lucene](https://issues.apache.org/jira/browse/LUCENE-7727) あたりで使用 (が検討) されている。
* [Markdown Navigator](http://vladsch.com/product/markdown-navigator) という IntelliJ などのプラグインで使うために開発を始めた模様。　[要出典]

Markdown の憂鬱と flexmark-java 拡張
=====================================

Markdown といえば前述の通り方言がたくさんあって困ることでも有名ですが、だいたいは同じなんだし、できれば単一実装でカバーしたいところです。ということで flexmark-java は拡張型 (プラグイン型?) にすることで多くの方言をカバーしています。

```java
// https://github.com/vsch/flexmark-java/wiki/Usage の例を改変

import com.vladsch.flexmark.ast.Node;
import com.vladsch.flexmark.html.HtmlRenderer;
import com.vladsch.flexmark.parser.Parser;
import com.vladsch.flexmark.parser.ParserEmulationProfile;
import com.vladsch.flexmark.util.options.MutableDataHolder;
import com.vladsch.flexmark.util.options.MutableDataSet;

import com.vladsch.flexmark.ext.abbreviation.AbbreviationExtension;
import com.vladsch.flexmark.ext.definition.DefinitionExtension;
import com.vladsch.flexmark.ext.footnotes.FootnoteExtension;
import com.vladsch.flexmark.ext.tables.TablesExtension;
import com.vladsch.flexmark.ext.typographic.TypographicExtension;

public class RenderingExamples {

    // Markdown の方言ごとのオプションを設定
    private MutableHolder getOptions(String type) {
        MutableDataHolder options = new MutableDataSet();
        switch (type) {
            case "markdown":
                options.setFrom(ParserEmulationProfile.MARKDOWN);
                break;
            case "multimarkdown":
                options.setFrom(ParserEmulationProfile.MULTI_MARKDOWN);
                break;
            case "kramdown":
                options.setFrom(ParserEmulationProfile.KRAMDOWN);
                options.set(Parser.EXTENSIONS, Arrays.asList(
                    AbbreviationExtension.create(),
                    DefinitionExtension.create(),
                    FootnoteExtension.create(),
                    TablesExtension.create(),
                    TypographicExtension.create()
                    ));
                break;
         }
         return options;
    }

    // 方言ごとの Parser を生成
    private Parser getParser(String type) {
        switch (type) {
            case "commonmark":
                return Parser.builder().build();
            default:
                return Parser.builder(getOptions(type)).build();
        }
    }

    // 方言ごとの Renderer を生成
    private HtmlRenderer getHtmlRenderer(String type) {
        switch (type) {
            case "commonmark":
                return HtmlRenderer.builder().build();
            default:
                return HtmlRenderer.builder(getOptions(type)).build();
        }
    }

    // HTML をレンダリング!
    public String render(String markdownText, String type) {
        Node document = getParser(type).parse(markdownText);
        return getHtmlRenderer(type).render(document);
    }
}
```

自分で Markdown の中までいじりたい
===================================

という諸兄には parse 済みの Node を自分でいじる方法も用意されているようです。

```java
// https://github.com/vsch/flexmark-java/wiki/Usage の例を改変

import com.vladsch.flexmark.ast.Node;
import com.vladsch.flexmark.ast.NodeVisitor;
import com.vladsch.flexmark.ast.Text;
import com.vladsch.flexmark.ast.VisitHandler;

public class NodeVisitingExamples {
    public NodeVisitingExamples() {
        this.visitor = new NodeVisitor(
            new VisitHandler<>(Text.class, NodeVisitingExamples.this::visit)
        );
    }

    public void traverse(String markdownText) {
        Parser parser = Parser.builder().build();
        this.visitor.visit(parser.parse(markdownText));
    }

    private void visit(Text text) {
        System.out.println(text);
    }

    private final NodeVisitor visitor;
}
```

自分で拡張を書きたい
=====================

(これは気が向いたら追記)

自分 pegdown 使ってたんだけど!
===============================

移行方法の解説: https://github.com/vsch/flexmark-java/wiki/Pegdown-Migration

もっと詳しく
=============

公式: https://github.com/vsch/flexmark-java/wiki/Usage

サンプルコード
===============

https://gist.github.com/dmikurube/630b82f437977501a415155e3df3d68c