再帰的にMarkdownを処理

このウェブログではHTMLを直接ぺちぺち書くことが多いが、たまにiPhoneで書く時にはMarkdownで書いている。Markdownの処理系にはここ数年はmarkedを使っており(宗教上の理由でCommonMark系は使うことができない)、あまり困ってはいないのだが、sectionfigure要素を使うとその中のMarkdown文字列がうまく処理できないという問題がある。

例えばfigure要素を使ってtable要素を図としてマークアップしたいとする。その場合、markedでは表の記法が拡張されているので、以下のように書くことになる。

<figure>
|Name|Num|
|-|-|
|foo|128|
|bar|256|

<figcaption>表1</figcaption>
</figure>

このMarkdown文字列をmarkedで処理しても何も起きず、そのまま出てくる。つまり何らかのHTMLタグで括られた間はインライン系の記法しか処理されない。


markedには処理をフックする機構が用意されているので、それを使うとうまく処理することできる。こういったHTML文字列の場合、Renderer.htmlでそういった文字列を処理する関数を指定してやれば良い。

// node this.js < test.md
var marked = require("marked");
var renderer = new marked.Renderer();

function processHTML(html) {
  var attributes;
  var contents;
  var tag;
  var tags = ["aside", "figure", "section"];
  var tokens = html.trim().match(/^<(\w+)(.*?)>([\s\S]*)<\/\1>/);

  if (!tokens) {
    return html;
  }

  tag = tokens[1];

  if (tags.indexOf(tag) === -1) {
    return html;
  }

  attributes = tokens[2];
  contents = marked(tokens[3].replace(/&gt;/g, ">"), {
    renderer: renderer
  }).trim();

  return "<" + tag + attributes + ">\n" + contents + "\n</" + tag + ">\n";
}

renderer.html = processHTML;
console.log(marked(fs.readFileSync(process.stdin.fd, "utf8"), {
  renderer: renderer
}));

空白文字を削った一番外側のHTMLタグを拾い、その中身を更にmarkedで処理するようにしているわけだ。インライン記法は処理できるので、markedがサポートしていないfiguresectionaside要素のみを処理するようにしている。>の文字参照だけを解除しているのは、引用記法がうまく処理できなくなるからで、そもそもの正規表現も含め、この辺りはもうちょっとちゃんとした方が良さそうだ。

この関数を利用すると、最初に挙げたMarkdown文字列の例は以下のように期待通りに処理される(インデントは読みやすいように追加した)。

<figure>
  <table>
    <thead>
      <tr>
        <th>Name</th>
        <th>Num</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>foo</td>
        <td>128</td>
      </tr>
      <tr>
        <td>bar</td>
        <td>256</td>
      </tr>
    </tbody>
  </table>

  <figcaption>表1</figcaption>
</figure>

本当はこういったアドホックなプラグイン志向でMarkdownを拡張するのは好きではない。Markdownは拡張性なしの安定志向のもの(それこそオリジナルで良い)を使い、プリプロセッサーでtabledl要素の独自記法を処理して渡す方が良いと考えている。そうしてプリプロセッサーで処理済みのものをデータとして保存しておけば、ほぼあらゆるMarkdown処理系で正常に処理できることが期待できるからだ。