Edited at
CeresDay 24

flexmark-javaでmarkdownで記事管理をする(classとか属性も付与できる)

セレスアドベントカレンダー24日目です。

セレスでWebエンジニアをやっている@ko-sasakiです。不動産や注文住宅系のメディアのサービス開発を行っています。当メディアはよくあるWordpressではなく、独自で記事管理システムを作っています。記述はHTMLでもマークダウンでもいいのですが、マークダウンを推奨しています。これは記事の可読性や編集等をしやすく狙いがあります。マークダウンで入力されたものをHTML等に出力するために、システム内でflexmark-javaを結構使っているので、その紹介をさせていただきます。

持ち家計画

不動産投資Oh!Ya


Javaのマークダウンパーサたち

Javaもいくつかマークダウンパーサの実装があって検討しました。


  • commonmark-java(https://github.com/atlassian/commonmark-java)

    アトラシアンがメンテしているマークダウンパーサです。CommonMarkというマークダウン仕様を実装しているものになります。CommonMarkは他にもPerlやRubyなどの複数の言語で実装されています。拡張ポイントがあるので、本体をさわることなく機能拡張はできそう。


  • markedj(https://github.com/gitbucket/markedj)

    gitbucket製のマークダウンパーサ。あんまり活発ではなさそうで、拡張機能とかもなさそうでした。


  • flexmark-java(https://github.com/vsch/flexmark-java)

    今回使用したマークダウンパーサで、pagdown,kramdown,commonMarkなどにも対応しているもようです。開発も結構活発で、最近Java9のmodule対応とかもしていました。またPDFやDOCXなどにも出力とかできるので、簡単な帳票出力のテンプレートとしても使えそうでした。拡張機能もあり、本体のソースコードをさわることなく、拡張が可能です。また性能もそこそこよく、10万文字くらいまでは普通に変換されます。(一部パーサでは2万文字くらいで落ちたりしていました)


調べたときは色々あったんですけど、結構Deprecatedになっていたので、割愛しております。


設定と出力

マークダウンパーサはとても簡単でオプション設定して、マークダウンをパーサにかけます。

出力後はcom.vladsch.flexmark.ast.Document型になります。HTMLやPDFの変換はその後に行います。

べた書きするとこんな感じのコードになります。


String source = "## title"

MutableDataSet option = new MutableDataSet();
option.setFrom(ParserEmulationProfile.MULTI_MARKDOWN)
.set(Parser.EXTENSIONS, Arrays.asList(
AbbreviationExtension.create() // エイリアスまわりの拡張
, DefinitionExtension.create() // 定義まわりの拡張
, TablesExtension.create() // テーブルまわりの拡張
, TocExtension.create() // 目次まわりの拡張
, TypographicExtension.create()
, AttributesExtension.create() // タグ内の属性追加の拡張
));

Parser parser = Parser.builder(option).build(); // パーサーにオプションを付与する
Document doc = parser.parse(source); // markdownをパースしてDocument型に変換する
HtmlRenderer renderer = HtmlRenderer.builder(option).build(); // HTMLに変換するオプションを指定する
String html = renderer.render(doc) // Document型のデータをHTMLに変換する


サンプル

H2〜H4タグや、リストタグ、テーブルタグ、aタグなど普通のマークダウンと同じ書き方でちゃんと出力できます。

変換前

## H2

リスト
- 1
- 2
- 3

### H3
リスト
+ 1
+ 2
+ 3

#### H4
オーダーリスト
1.
1.
1.

##### リンク
[google](https://google.com)

##### テーブル
|head1|head2|
|----|----|
|contents1|contents2|

変換後

<h2 id="h2">H2</h2>

<p>リスト</p>
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
<h3 id="h3">H3</h3>
<p>リスト</p>
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
<h4 id="h4">H4</h4>
<p>オーダーリスト</p>
<ol>
<li></li>
<li></li>
<li></li>
</ol>
<h5 id="リンク">リンク</h5>
<p><a href="https://google.com">google</a></p>
<h5 id="テーブル">テーブル</h5>
<table>
<thead>
<tr>
<th>head1</th>
<th>head2</th>
</tr>
</thead>
<tbody>
<tr>
<td>contents1</td>
<td>contents2</td>
</tr>
</tbody>
</table>

と簡単な紹介だとこんな感じになります。


markdownに属性を付与する

マークダウンは一般的にはclassとかstyleとか付与できません。とはいえ、Webデザインではclass指定とかここだけstyleしたいとかそういうのは、日常茶飯事かと思います。flexmark-javaだと、AttributesExtensionがあり、属性情報を追加で付与することができます。


記法

普通にマークダウンを書き、末尾に{属性=値}を記述します。

[通常]

[google](https://google.com)

<p><a href="https://google.com">google</a></p>

[属性付与]
[google](https://google.com){target="_blank"}

<p><a href="https://google.com" target="_blank">google</a></p>

上記のとおり、通常だと、普通のaタグですが、{}で属性を付与することができます。これは基本どこでも使用でき、不動産投資Oh!Ya、持ち家計画のサイト内でもいたるところに使われています。また、テーブル等で一部のセルだけ強調表示したいときにも使います。

持ち家計画の例です。

下記のコードを書くと、そのセルにだけclassがあたります。これでライターの要望にも応えています。なお、行に対しては当てられません。

|都道府県名|建築費+土地代|

|---|---|
|北海道|3,226万円|
|青森県|3,156万円|
|{class="ptc-Table_Highlight_Blue"}岩手県|{class="ptc-Table_Highlight_Blue"}3,154万円|

image.png


目次(TOC,table of contens)を自動生成する

サーバ側で目次の自動生成まで行います。Javascriptで実装してもよかったんですが、生成までに時間がかかるのと、記事によって目次をつけたい、つけたくないみたい要望に応えるためにサーバ側で実装することにしました。flexmark-javaのTocExtensionを使用します。

option.set(TocExtension.DIV_CLASS, "pst-Table_Contents"); // class指定

option.set(TocExtension.TITLE_LEVEL, 32); // 見出しの大きさ
option.set(TocExtension.TITLE, "目次");    // 見出しの文言
option.set(TocExtension.LEVELS, 4 | 8 | 16 ); // H2〜H4まで目次生成する

これをセットします。

これでマークダウンのテキストに[TOC]と書けば、その場所に目次が生成されます。

どこでもいいので、[TOC]と書くだけで、H2〜H4のタグの目次が自動生成されます

[TOC]

## H2-1
### H2-1-H3
## H2-2
### H2-2-H3

変換後は下記になります。



<div class="pst-Table_Contents">

<h6>目次</h6>

<ul>

<li>

<a href="#h21">H2-1</a>

<ul>

<li>

<a href="#h21h3">H2-1-H3</a>

<ul>

<li><a href="#h21h3">H2-1-H3</a></li>

</ul>

</li>

</ul>

</li>

<li>

<a href="#h22">H2-2</a>

<ul>

<li>

<a href="#h22h3">H2-2-H3</a>

<ul>

<li><a href="#h22h3">H2-2-H3</a></li>

</ul>

</li>

</ul>

</li>

</ul>

</div>



これで目次が生成されます。不動産投資Oh!Yaではスタイルをあてて、このように表示されます。

image.png


最後に

ライターにマークダウンで記事を書いてもらいつつも、目次を自動生成したりスタイルあてたりをflexmark-javaで実現しています。また必要になれば、拡張用のAPIがあるので、それを元に独自拡張を作る予定でいます。では、良い年末を!