Home About
Markdown , Node.js , JavaScript

markdown テキストをパースしてあれこれしたい (commonmark.js 編)

先日 markdown-to-ast を使って markdown をパースするというエントリーを書いたのですが、 markdown-to-astcommonmark をラップしたライブラリだとの情報を得た。

それならば、markdown-to-ast を使うのではなく、直接 commonmark を使ってみることにする。

プロジェクト作成

$ mkdir m-to-ast
$ cd m-to-ast
$ npm init -y

依存するライブラリをインストール:

$ npm install commonmark
$ npm install underscore

index.js を書く:

const commonmark = require("./node_modules/commonmark/dist/commonmark.js");

const reader = new commonmark.Parser();
const ast = reader.parse("Hello, *World*!");
console.log(ast);

実行する:

$ node index.js

標準出力(長いため先頭のみ):

<ref *1> Node {
  _type: 'document',
  _parent: null,
  _firstChild: <ref *2> Node {
    _type: 'paragraph',
    _parent: [Circular *1],
    _firstChild: Node {
      _type: 'text',
      _parent: [Circular *2],
      _firstChild: null,

markdown-to-ast と似たもの(というか本来は、markdown-to-ast が commonmark の出力に似せたのであろう)が出ます。 ただ、markdown-to-ast では、サブノード(s) を children で取得できたのですが、 commonmark では、children という属性はありません。 その代わり、 _firstChild があり _next があります。 この二つの属性を使うことで、markdown-to-ast における children を作り出すことができます。

このように:

const children = (node) => {
    const recur = (acc, child) => {
        if( child==null ){
            return acc;
        }
        else {
            acc.push(child);
            return recur(acc, child._next);
        }
    };

    return recur([], node._firstChild);
};

要するに、_firstChild で先頭のサブノードを取得し、その後はそのサブノードの _next を見て、(兄弟のノードが)存在していれば、処理を続ける。以下同様に _next を見て兄弟を探し続ける。という再帰処理です。

あとはだいたい 前回(markdow-to-ast編) と同じです。

完成したコードはこちら:

const commonmark = require("./node_modules/commonmark/dist/commonmark.js");
const _ = require('underscore');

const children = (node) => {
    const recur = (acc, child) => {
        if( child==null ){
            return acc;
        }
        else {
            acc.push(child);
            return recur(acc, child._next);
        }
    };

    return recur([], node._firstChild);
};

const parseNode = (node) => {
    // _.foldl で使う関数:
    const f = (acc, subNode) => {
        return acc + parseNode(subNode);
    };

    if(node._type == 'document' ){
        return '<doc>' + _.foldl(children(node), f, '') + '</doc>';
    }
    else if(node._type == 'paragraph' ){
        return '<p>' + _.foldl(children(node), f, '') + '</p>';
    }
    else if(node._type == 'emph' ){
        return '<emp>' + _.foldl(children(node), f, '') + '</emp>';
    }
    else if(node._type == 'text' ){
        return '<str>' + node._literal + '</str>';
    }
};


const reader = new commonmark.Parser();
const ast = reader.parse("Hello, *World*!");

const xml = parseNode(ast);
console.log(xml);

実行と結果:

$ node index.js
<doc><p><str>Hello, </str><emp><str>World</str></emp><str>!</str></p></doc>