Wikiの代わりにMarkdownで全文検索を実装した話。

2020年01月23日報告会

NetTech 3年 中川佑人

目次

  • 背景
  • PukiWikiについて
  • ないならつくればいいじゃない。
  • MD Search と ライブラリ解説
  • ライブラリ関係
  • ファイル関係
  • 認証関連, BracketNameの実装
  • とりあえず完成
  • 反省点 (バグと既知の問題点), TODO
  • 考察とまとめ
  • 参考文献
  • PlusAlpha

背景

  • 些細なこと〜覚書をどうまとめるか?
  • PukiWiki を使って書いていました。
* たいとる
** サブタイトル
- ↓ Table

,0,1,2,3,4,5
,1,X,X,X,X,X
,2,X,X,X,X,X
,3,X,X,X,X,X
  • 記法が独自だしMarkdownに変えたい -> つくればいいじゃない。
  • PHPはまだまだ全然使える!
  • PHPとJavascriptの補完関係。ライブラリがあるから使うか、開発しやすさか?

PukiWikiについて

  • PHPベースで実装されているWikiシステム。本家はPHPのみで動かすことが出来る。
  • 派生Wikiも多数ある。(e.g. Plus!, Adv.)
    • Adv.の問題をLalavelでスクラッチで書いた LukiWiki がある。(Markdown風)
  • PukiWiki独自の記法。移行の際にコンバートが必要。
  • コミュニティ(2人 or 1人のコミッタ)
  • 認証がついていても全文検索の対象となる。
  • 移行手段が限られる。
  • CLIからの操作を考慮していない。
  • APIを実装していない。(質問箱/5335)

ないなら、つくればいいじゃない。

  • 汎用性のあるMarkdownでの実装
    • PukiWikiのBracketName(Wiki内名前指定)と同様のやつを実装。
  • ファイルベースの階層的な管理
  • シンプルな実装
  • 認証をつける。
    • 認証付ファイルを全文検索から外す。
  • リアルタイム反映
  • 数式も入れたい。(LaTeXライクな)
  • CLIからのアクセス

MD Search (実際に実装したもの)

  • PHP + javascript + SQLite3(検索のみ)
  • TNTSearch (全文検索エンジン)
  • BootStrap + JQuery (レスポンシブ)
  • Marked JS (Markdown Parser/Compiler to HTML) + highlight JS
  • MathJax(数式)
  • 機能面
    • PHP_SESSION認証
    • リアルタイムプレビュー
  • ホスト: Mixhost - レンタルサーバー
      - PHP 7.2.26, Linux (CloudLinux), LiteSpeed 5.x.x(Apache 2.4.41), x86_64

TNT Search

PHPで書かれた機能面に優れた全文検査エンジン
機能面
あいまい検索・関数的な実装・緯度経度検索・テキスト学習・語幹解釈・カスタムトークン作成・BM25(Okapi BM25)ランキングアルゴリズム・真偽値検索・結果のハイライト

  • DBの構築は、SQLiteかMySQLか選択可能。
  • Elasticsearch(Node)に比べて実装が楽。

Marked JS

  • https://github.com/markedjs/marked

  • 業界スタンダードなGFMと標準化をすすめるCommonMarkの拡張Markdownの仕様でParseしてくれる。

    • 汎用性UP!
Flavor Version
The original markdown.pl --
CommonMark 0.29
GitHub Flavored Markdown 0.29

MathJax

MathJaxはすでに有力な標準仕様(抜粋)

世界最大の数学論文データベース MathSciNet (Examples: 1, 2, 3) は MathJax を採用。
少なくとも数学研究の世界ではウェブでの数式表示に関して MathJax が標準になった
Project EuclidMathJax を採用 (Project Euclid、オンライン上の"美しい数学"を後押し)。
有力な団体がインターネット上で標準的に使うために MathJax の開発にお金を出している。
MathOverflow (MathJaxを使った専門的な数学の質問掲示板) でも MathJax を使っている。
  • $$ \sqrt{ 2 } = 1.4142 \ldots $$
    • 2=1.4142\sqrt{ 2 } = 1.4142 \ldots

ライブラリ関係

(検索スクリプト)
search_core.php -+
(検索DB作成:認証)  +--- TNT_SEARCH ---> SQLite3
create.php ------+
(MDファイル編集:認証)
editor.php ----+
(閲覧用)        +------+--[MathJax]-- Marked
viewer.php ----+
 (条件付き認証)
  • 数式を出せるようにした。
    • IncludeされたMarkdownテキストをMarkedに入れてHTMLを出す。
    • $$ ~ $$タグはエスケープして、MathJaxに投げる。

具体的に

  1. $$ xxx $$は、コードブロックのlang-mathクラスになる。
  2. $$ xxx $$ でsplitして配列に投げたものを
    reduceで関数に組み込み文字列連結させながら回す。
    添字が偶数のモノがxxxにあたるので、
    対象文字列を<code class=lang-math></code>の間に組み込みながら回し、
    予めHTMLとして 保持しておく。
  3. Markedに入れてHTMLを出す。
  4. 今度は<code class=lang-math> xxx </code>を正規表現でマッチさせて、マッチしたところをsplitして$$ xxx $$ joinで戻す。

ファイル関係

        | (アクセス)
[/]     V
 |--- *.php <-------------------+
 |      |                       |
 |      V                       |
 |----/libs/*{.js|.css}         | file_get_contents();でInclude
 |                              |      
 |----/md/ ---------------------+      ^
    (Markdownファイル群)               |
    '/md/' . (?file=)IT/Linux/ssh.md --+
  • /md/ 配下にファイルを配置。
  • file_get_contents() でIncludeしてviewer.phpに反映。

認証関連

auth.php (PHP:token == COOKIE: token )
 |- login.php  -> COOKIE: PHPSESSID -> SHA256 -> COOKIE: token
 |- logout.php  -> session_destroy()
  • クッキーにユーザー名を保持。

認証付ファイルを全文検索から外す。

  • DBファイルへの追加は変えていない。検索過程を修正。
    • PATHを検索するfilemapテーブルを検索する際のSQL命令を改変
  > TNTSearch.php
  L411 - $query = "SELECT * FROM filemap WHERE id IN (".$docs->implode(', ').");";
  L411 + $query = "SELECT * FROM filemap WHERE path NOT LIKE '%AUTH%' AND id IN (".$docs->implode(', ').");";
  • PATH値が空の結果が返る。
    • !empty(path)だけ結果表示 & array_filterで配列化したものをcount()

PukiWikiのBracketName(Wiki内名前指定)と同様のやつを実装。


renderer.link = function (href, title, text) {
  /* [Memo.md]() 記法 */
  if (href == '') { 
      return  '<a target=_blank'
      + ' href=https://md.eric-lightning.info/viewer.php?file='
      + text +'&prv=1>' + text + '</a>';
  }
  /* [メモ](::Memo.md) 記法 */  
  else if(href.startsWith('::')){
      return  '<a targe=_blank`
      + href=https://md.eric-lightning.info/viewer.php?file='
      + href.replace('::','') +'&prv=1>' + text + '</a>';
  }
  /* デフォルトで _blank pukiwikiをターゲットにする */
  return `<a target="_blank" href="${href}">${text}` + '</a>';
}

とりあえず完成

https://md.eric-lightning.info

おまけ

反省点

  • 実装当初、ファイルエディタにディレクトリ・トラバーサルがあった。。。。
    /editor.php?file=../index.html
    • 認証付エディタにしておいてよかった... 修正済.
    $dirchk = preg_match('/(\.\.\/|\.\.\\\\)/', $_GET['file']) ? "true" : "false";
    if($dirchk == 'true') die();
    

バグ

  • 現状なし。Digest認証のときは認証が通らなかったが、セッション認証に変えてから安定。

既知の問題点

  • 画面遷移の流れをどうするか。

TODO:

  • ServiceWorkerの有効活用. PWA化難航。
  • 独自記法 -> 汎用記法へのコンバート
  • RESTful_API化
    • 検索結果をJsonにしてクライアントでParseしてDOMを最小限にする。
    • CLI化
      • 閲覧はNode製のmdlessにするか、htmlを出させてからGETしてw3m
%:curl "https://md.eric-lightning.info/local-search.php?q=ssh"

"IT/Linux/ssh-keygen.md" "IT/Linux/disk_mount.md" "IT/Linux/ssh.md" %                  

%:curl "https://md.eric-lightning.info/download.php?file=IT/Linux/ssh.md" -> Makdown
...
  • Server Side Include の必要性。(NodeでmarkedとMathJaxを入れ直す??)

考察・まとめ

  • 何故PHPで書いたのか? -> 実装しやすさ。
  • どの時点でリファクタリングするか?(予めモジュール単位を決める)
    • 個人開発でも開発工程を意識すべき。
      • (結構無視して書いたので、あまり見せられるコードじゃない)
    • バージョン管理すると、自分の進捗具合が見えてくる.(Privateだけど)
    • 将来的には、Publicにしたい。もちろん綺麗なコードにしてから。
  • ライブラリは多用するべきなのか?
  • MathJaxは専門外。でもMarkdownで自作パーサーはできた?

参考文献

参考文献2

参考文献3

Plus Alpha

  • PPTを書くに当たって、Linuxで書けないか試行錯誤。Pandocで変換する方法が多くあった。
  • Pandocではなく、Marpというアプリケーションが見つかった。
    • https://marp.app/
    • LivePreview・デザイン性・VSCodeに実装可・CLI操作可・CSSでデザイン操作。
    • Markdown to PDF,HTML,PPTX,PNG,JPG
  • 素晴らしいプレゼンテーションエディタの作者に感謝。