目次 このページのソースコードを表示
公開日:
更新日:

OutlineTextのパーサ(Parser)について

はじめに

OutlineTextは,2018年4月から開発され,明確なパーサモデルがないまま,トライアル・アンド・エラーで構築,改良されていきました.本稿で挙げられているパーサモデルはその過程で作られたものです.特にモデルは理想や目標で語られることが多く[注 1],実際のスクリプトがモデルに追い付いていないことがあります(特にクラスの分離や,命名など).ご容赦ください.

文法内の要素について

OutlineTextの記法には,多くのほかの記法と同じように,大きく分けて二つの要素があります.一つめは,インライン要素で,二つ目は,ブロック要素です.

インライン要素

行内にある記法.行をまたがない記法

ex)

                **強調**, //アクセント//, __アンダーライン__, 
                [URL](url), など
ブロック要素

複数行にわたる記法.行をまたぐ記法.

ex)

                * リスト
                * リスト
                
                段落
                段落
                    
                    段落
                    段落
                    
                    上の段落を合わせて
                    一つのセクションというブロック要素
                    
                定義リスト:
                    定義用語
                    
要素の種類
要素の種類

イベント駆動型モジュラーパーサ(Event-driven Modular Parser)

OutlineTextのパーサは,一つのパーサがすべての文法を解釈するのではなく,複数のパーサが組み合わさって動作します.パーサの種類は大きく分けて三種類あり,メインパーサブロック要素パーサインライン要素パーサがあります.ブロック要素パーサとインライン要素パーサは,メインパーサが発火させるイベントを受けて動作します.

Event-driven Modular Parser
Event-driven Modular Parser

パーサの種類

メインパーサ

文書を読み込み,イベント(インデントが入った, 抜けた, 行の先頭にきたなど) を発火させる.

ブロック要素パーサ

ブロック要素を担当するパーサ.メインパーサが発火させるイベントに反応して処理を実行する.この種類のパーサは,単体ではなく,ブロック要素ごとに一つのパーサが担当する.

インライン要素パーサ

インライン要素を担当するパーサ.メインパーサとブロック要素から呼ばれる.インライン文法の変換テーブルを参照して,プレインテキストを変換する.複数のインライン文法があっても,このパーサは一つ.

イベントの種類

メインパーサは,読み込まれた文章を上から読み,行の変化やインデントの変化等があったときにイベントを発火します.

Event の種類
Event の種類

優先順位

イベント時,メインパーサが呼ぶブロック要素パーサには順番があります.そのイベントに対して優先度の高いパーサから順番に呼ばれていきます.呼ばれたパーサは,処理を行いそのあとのパーサに処理を回すか選択できます.処理を回すことを選択すると,メインパーサは,続けて次に優先度の高いパーサを呼び出します.逆に処理を回さないことを選択すると,メインパーサは,それ以降のパーサを呼び出しません.

例えば,行頭のイベントでは,まず見出しに関するパーサが呼ばれたあと,段落に関するパーサが呼ばれます.実際に,行頭が見出しの場合,見出しパーサがその行をデコードし,それより後のパーサには処理を回しません.

優先順位
優先順位

文脈(Context)

各パーサが独立しているため,あるパーサが持っている文書の情報をほかのパーサが参照できる仕組みが必要です.そこで,文脈(Context)というデータが登場します.パーサは理解した文書の情報を文脈に書き込みます.ほかのパーサは,文脈を見て,しかるべき動作をします.

文脈(Context)
文脈(Context)

処理の流れ

  1. メインパーサが文書読み込み
  2. デコード単位(チャンク)に分ける
  3. チャンクごとにデコード開始.
    1. メインパーサがイベント発火
    2. イベントを受けとったブロック要素パーサ,インライン要素パーサがデコードを行う.
  4. 終了

文法の追加

各パーサが独立して動作するイベント駆動型モジュラーパーサを採用しているため,文法の追加は,難しくありません.

ブロック文法

  1. ブロック要素パーサの基底クラスを継承してパーサの実装
                    /**
                    * 空行を線で区切る(誰得?)
                    */
                    class SeparateEmptyParser extends ElementParser {
                        public static function OnEmptyLine($context, &$output) {
                            $output = '<hr>';
                            return false;
                        }
                    }
    
    
  2. メインパーサにイベントの登録
                    private static $onEmptyLineParserList = [
                        'HeadingElementParser',
                        'ParagraphElementParser',
                        'TableElementParser',
                        'ReferenceListParser',
                        'SeparateEmptyParser', // <- 追加
                    ];
    

インライン文法

  1. インライン文法変換テーブルに追加
                    private static $spanElementPatternTable = [
                        ["/\[\[ *(.*?) *\]\]/", '<a name="{0}"></a>', null],
                        ["/\[(.*?)\]\((.*?)\)/", null, 'DecodeLinkElementCallback'],
                        ["/\*\*(.*?)\*\*/", '<strong>{0}</strong>', null],
                        ["/\/\/(.*?)\/\//", '<em>{0}</em>', null],
                        ["/__(.*?)__/", '<mark>{0}</mark>', null],
                        ["/~~(.*?)~~/", '<del>{0}</del>', null],
                        ["/\^\[(.*?)\]/", null, 'DecodeReferenceElementCallback'],
                        ["/<((http|https):\/\/[0-9a-z\-\._~%\:\/\?\#\[\]@\!\$&'\(\)\*\+,;\=]+)>/i", '<a href="{0}">{0}</a>', null],
                        ["/<(([a-zA-Z0-9])+([a-zA-Z0-9\?\*\[|\]%'=~^\{\}\/\+!#&\$\._-])*@([a-zA-Z0-9_-])+\.([a-zA-Z0-9\._-]+)+)>/", '<a href="mailto:{0}">{0}</a>', null],
                        ["/->/", '&#8594;', null],
                        ["/<-/", '&#8592;', null],
                        ["/=>/", '&#8658;', null],
                        ["/<=/", '&#8656;', null],
                        ["/\.\.\./", '&#8230;', null],
                        ["/--/", '&#8212;', null],
                        ["/\(TM\)/", '&#8482;', null],
                        ["/\(R\)/", '&#174;', null],
                        ["/\(C\)/", '&#169;', null],
                        ["/'/", '&#8217;', null],
                        ["/です/", 'ぽよ', null], // <- 追加. 'です'を'ぽよ'に変換する
                    ];
    

注釈

  1. ^ 筆者独自解釈
「https://contentsviewer.work/Master/OutlineText/HowItWorks/Parser」から取得