3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

CeresAdvent Calendar 2018

Day 24

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

Last updated at Posted at 2018-12-28

セレスアドベントカレンダー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があるので、それを元に独自拡張を作る予定でいます。では、良い年末を!

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?