MarkdownをHTMLに変換する

前のセクションではコマンドライン引数で受け取ったファイルを読み込み、標準出力に表示しました。 次は読み込んだMarkdownファイルをHTMLに変換して、その結果を標準出力に表示してみましょう。

markedパッケージを使う

JavaScriptでMarkdownをHTMLへ変換するために、今回はmarkedというライブラリを使用します。 markedのパッケージはnpmで配布されているので、commanderと同様にnpm installコマンドでパッケージをインストールしましょう。

$ npm install --save marked@0.6

インストールが完了したら、Node.jsのスクリプトから読み込みます。 前のセクションの最後で書いたスクリプトに、markedパッケージの読み込み処理を追加します。

const program = require("commander");
const fs = require("fs");
const marked = require("marked"); // markedパッケージを読み込む

program.parse(process.argv);
const filePath = program.args[0];

fs.readFile(filePath, "utf8", (err, file) => {
    if (err) {
        console.error(err);
        process.exit(err.code);
        return;
    }
    console.log(file);
});

markedパッケージから取得したmarkedオブジェクトは、Markdown文字列を引数に取りHTML文字列を返す関数です。 次のようにreadFile関数で読み込んだファイルの文字列を引数として渡せば、HTMLに変換できます。

const program = require("commander");
const fs = require("fs");
const marked = require("marked");

program.parse(process.argv);
const filePath = program.args[0];

fs.readFile(filePath, "utf8", (err, file) => {
    if (err) {
        console.error(err);
        process.exit(err.code);
        return;
    }
    const html = marked(file); // HTML文字列に変換する
    console.log(html);
});

変換オプションを作成する

markedにはMarkdownの変換オプションがあり、オプションの設定によって変換後のHTMLが変化します。 いくつかのオプションについてアプリケーション中でのデフォルトの設定を決め、さらにコマンドライン引数から設定を切り替えられるようにしてみましょう。 今回扱うオプションは次の2つです。

  • gfm
  • sanitize

gfmオプション

gfmオプションは、GitHubにおけるMarkdownの仕様(GitHub Flavored Markdown, GFM)に合わせて変換するかを決めるオプションです。 markedのデフォルトではtrueになっています。GFMは標準的なMarkdownにいくつかの拡張を加えたもので、代表的な拡張がURLの自動リンク化です。 例として、次のようなMarkdownファイルを用意し、先ほどのスクリプトと、gfmオプションをfalseにしたスクリプトで結果の違いを見てみましょう。

# サンプルファイル

これはサンプルです。
https://jsprimer.net/

- サンプル1
- サンプル2

gfmオプションが有効のときは、URLの文字列が自動的に<a>タグのリンクに置き換わります。

<h1 id="-">サンプルファイル</h1>
<p>これはサンプルです。
<a href="https://jsprimer.net/">https://jsprimer.net/</a></p>
<ul>
<li>サンプル1</li>
<li>サンプル2</li>
</ul>

一方、次のようにgfmオプションをfalseにすると、単なる文字列として扱われ、リンクには置き換わりません。

const program = require("commander");
const fs = require("fs");
const marked = require("marked");

program.parse(process.argv);
const filePath = program.args[0];

fs.readFile(filePath, "utf8", (err, file) => {
    if (err) {
        console.error(err);
        process.exit(err.code);
        return;
    }
    const html = marked(file, {
        gfm: false
    });
    console.log(html);
});
<h1 id="-">サンプルファイル</h1>
<p>これはサンプルです。
https://jsprimer.net/</p>
<ul>
<li>サンプル1</li>
<li>サンプル2</li>
</ul>

自動リンクの他にもいくつかの拡張がありますが、詳しくはGitHub Flavored Markdownのドキュメンテーションを参照してください。

sanitizeオプション

sanitizeオプションは出力されるHTMLを安全な形にサニタイズするためのオプションです。 sanitizeオプションが有効なとき、Markdownファイル中に書かれたHTMLタグはエスケープされ、単なる文字列として出力されます。 例として次のようなMarkdownファイルの変換がsanitizeオプションによってどう変わるかを見てみましょう。

# サンプルファイル

これはサンプルです。
https://jsprimer.net/

<p>これはHTMLです</p>

- サンプル1
- サンプル2

sanitizeオプションのデフォルト値はfalseなので、何も指定しなければMarkdownファイル中のHTMLはそのまま出力されるHTML中でもタグとして残ります。

<h1 id="-">サンプルファイル</h1>
<p>これはサンプルです。
https://jsprimer.net/</p>
<p>これはHTMLです</p>

<ul>
<li>サンプル1</li>
<li>サンプル2</li>
</ul>

ここで次のようにsanitizeオプションを有効にすると、<>がエスケープされてHTMLタグとして機能しなくなります。 自由なHTMLを書かれては困る場合に有用なオプションです。

const program = require("commander");
const fs = require("fs");
const marked = require("marked");

program.parse(process.argv);
const filePath = program.args[0];

fs.readFile(filePath, "utf8", (err, file) => {
    if (err) {
        console.error(err);
        process.exit(err.code);
        return;
    }
    const html = marked(file, {
        gfm: false,
        sanitize: true
    });
    console.log(html);
});
<h1 id="-">サンプルファイル</h1>
<p>これはサンプルです。
https://jsprimer.net/</p>
<p>&lt;p&gt;これはHTMLです&lt;/p&gt;

</p>
<ul>
<li>サンプル1</li>
<li>サンプル2</li>
</ul>

コマンドライン引数からオプションを受け取る

それぞれの変換オプションについて、コマンドライン引数で制御できるようにします。 gfmオプションは--gfmsanitizeオプションは--sanitize-Sでコマンドラインから設定できるようにします。

program
    .option("--gfm", "GFMを有効にする")
    .option("-S, --sanitize", "サニタイズを行う");

program.parse(process.argv);

デフォルト設定を定義する

毎回すべての設定を明示的に入力させるのは不便なので、それぞれの変換オプションのデフォルト設定を定義します。 今回はgfmオプションとsanitizeオプションをどちらもデフォルトでfalseにします。 アプリケーション側でデフォルト設定を持っておくことで、将来的にmarkedの挙動が変わったときにも影響を受けにくくなります。

markedのオプションはオブジェクトを渡す形式です。 オブジェクトのデフォルト値を明示的な値で上書きするときには...(spread構文)を使うと便利です。(オブジェクトのspread構文を参照) 次のようにデフォルトのオプションを表現したオブジェクトに対して、program.optsメソッドの戻り値で上書きします。

const markedOptions = {
    gfm: false,
    sanitize: false,
    // オプションのkey-valueオブジェクトをマージする
    ...program.opts()
};

あとはmarkedOptionsオブジェクトからmarkedにオプションを渡すだけです。 スクリプト全体は次のようになります。

const program = require("commander");
const fs = require("fs");
const marked = require("marked");

program
    .option("--gfm", "GFMを有効にする")
    .option("-S, --sanitize", "サニタイズを行う");

program.parse(process.argv);
const filePath = program.args[0];

const markedOptions = {
    gfm: false,
    sanitize: false,
    ...program.opts()
};

fs.readFile(filePath, "utf8", (err, file) => {
    if (err) {
        console.error(err);
        process.exit(err.code);
        return;
    }
    const html = marked(file, {
        gfm: markedOptions.gfm,
        sanitize: markedOptions.sanitize
    });
    console.log(html);
});

定義したコマンドライン引数を使って、Markdownファイルを変換してみましょう。

# gfmオプションを有効にする
$ node main.js --gfm sample.md
# sanitizeオプションを短縮形で有効にする
$ node main.js -S sample.md

これでMarkdown変換の設定をコマンドライン引数でオプションとして与えられるようになりました。