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万円|

目次(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ではスタイルをあてて、このように表示されます。

最後に

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