PukiwikiにMarkdownをlibディレクトリに触れずに実装した

ぼくが運営しているPukiWikiサイトでは、以前からMarkdown記法でページを書けるように改造を加えてきました。その成果はpukiwiki154_mdとしてGitHubで公開しています。ただ、この方式はPukiWikiのlibフォルダ内のファイルを直接書き換えるものだったため、本体のバージョンが上がるたびに改造をやり直さなければならない欠点がありました。今回、pluginフォルダへのファイル追加・差し替えだけでMarkdown対応を完結させる方式に作り直し、pukiwiki154_md2としてv0.1を公開しました。

- なぜ作り直すことにしたのか
- pluginフォルダだけで実現できた仕組み
- 実装で詰まったポイント
- Markdownの中で使えるもの
- 設定(任意)
- 後方互換性を検証した
- 制限と留意点
- 開発の進め方について
なぜ作り直すことにしたのか
従来のpukiwiki154_mdでは、lib/convert_html.phpをはじめとして4ファイルに手を入れていました。動くものはできましたし、実際に運用もできています。しかしPukiWiki 1.5.5が出たら、改造箇所を一つずつ思い出しながら新しいlibに同じ手術を施す作業が待っています。さらに、lib改造版で書きためたページデータは素のPukiWikiへ持っていくと表示が崩れ、「データが改造版に縛られている」状態も気になっていました。Wikiというのは何年も使い続けるものなので、特定の改造版に依存した状態はいつか精算したいと思っていました。
pluginフォルダだけで実現できた仕組み
新しい方式の土台になっているのは、PukiWikiのページ表示がplugin/read.inc.phpという標準プラグインで実装されているという構造です。lib/pukiwiki.phpには次のような分岐があります。
if (isset($retvars['body']) && $retvars['body'] != '') {
$body = & $retvars['body']; // プラグインが返したHTMLをそのまま採用
} else {
$body = convert_html(get_source($base)); // 通常のPukiWiki変換
}
標準のread.inc.phpは空のbodyを返すため通常はconvert_html()による変換に進みますが、空でないHTMLを返せば本体はそれをそのまま表示します。ページのソースに#mdがあればMarkdown変換したHTMLを返し、なければ空を返す——read.inc.phpへの12行の追加だけで、libに一切触れずにページ描画を切り替えられます。編集画面のプレビューも同様に、edit.inc.phpが内部でconvert_html()を呼んでいるためpluginフォルダ内で対応が完結します。Markdown変換エンジン本体は新規のmd.inc.phpに集約し、パーサーのleague/commonmarkはplugin/markdown_parser/として同梱しました。
実装で詰まったポイント
保存時の罠が一番大きな問題でした。PukiWikiはページを保存するとき、make_str_rules()という関数で本文を自動整形します。代表的なのが*で始まる行の末尾へのアンカー自動付与で、PukiWiki記法では*は見出しなので便利な機能です。しかしMarkdownでは* 項目はリストです。何も対策しないと、Markdownのリストを保存するたびに行末へアンカーが付いて本文が壊れていきます。#mdのあるページを保存するときだけ$str_rulesと$fixed_heading_anchorを一時的に無効化することで回避しました。PukiWiki記法ページの保存は従来通り動作することも確認しています。
改行の扱いも調整しました。CommonMark仕様では行末にスペースを2つ置かないと改行が反映されませんが、Wikiのようにさっと書く場面では行末スペースはまず忘れます。league/commonmarkの設定で、単純な改行がそのまま<br>になるようにしています。
Markdownの中で使えるもの
GitHub Flavored Markdown(テーブル、打ち消し線、タスクリスト)に加え、次のものがMarkdownページ内で使えます。
- PukiWikiプラグイン: Markdownでは
#は見出し記法と衝突するため、ブロックプラグインは!commentのように!で書きます。46のようなインラインプラグインはそのまま使えます。なお、設定で#をブロックプラグインの記号として使用可能にできます(その場合は見出しとブロックプラグインを区別するために、見だしは#の後ろに半角スペースを1文字入れる必要があります) - ページ内目次:
#contentsと書いた行がMarkdown見出しから生成された目次に置き換わります(league/commonmarkのTableOfContents拡張)。 - 脚注3記法: Markdown参照式[^1]、Pandocインライン式^[脚注]、PukiWiki式((脚注))のいずれでも書けます。PukiWiki式は変換前にPandoc式へ書き換えることで対応しています。
- リンク両記法: [テキスト](URL)でも[[ページ名]]・[[テキスト>URL]]でも書けます。
設定(任意)
pukiwiki.ini.phpに設定を追記することで挙動を変更できます(追記しなくてもデフォルトで動作します)。主なものは$use_markdown_cache(変換結果キャッシュ、デフォルト有効)、$default_md(新規ページ作成時に#mdを自動挿入、デフォルト有効)、$markdown_support_hash_plugin(#plugin記法を許可、デフォルト無効)です。キャッシュが有効なとき、!pcommentや46のようにページ本文を変えずに表示だけが変わるプラグインは、キャッシュ期間中は更新が反映されません。そういったプラグインを多用するページでは$use_markdown_cache = 0を検討してください。詳しくはGitHubのREADME.mdをご覧ください。
後方互換性を検証した
今回の方式で一番確認したかったのは後方互換性です。プラグインのない素のPukiWiki 1.5.4を別ポートに立てて、#mdのページデータをそのまま持ち込んで表示させてみました。結果はHTTP 200でエラーは一切出ません。#mdの行が文字としてそのまま表示され、本文はPukiWiki記法として解釈された状態になります。書式は失われますが内容は読めます。逆に言えば、このプラグインを撤去してもWikiは壊れず、データを別の素のPukiWikiへ引っ越してもエラーは発生しません。lib/・skin/・pukiwiki.ini.phpはPukiWiki 1.5.4公式とdiff差分ゼロなので、改造版への依存から抜け出せた感覚があります。
制限と留意点
現在のv0.1には注意点が2点あります。まず、!commentなどのコメント系プラグイン経由で保存が行われると、edit.inc.phpの整形抑止が適用されないため、Markdownの*始まり行の末尾にアンカーが書き込まれる場合があります。通常は表示時に除去されるので見た目には現れませんが、フェンスコードブロック内の行は除去対象外です。コメント系プラグインを多用するページでは気になる場合があるので、その際は該当プラグインにも同様の抑止処理を入れる必要があります。次に、#contentsの引数(depth指定等)は現在無視され、常に全階層の目次が出力されます。生成されるアンカーはPukiWiki標準の[#xxxx]ではなくcommonmarkのパーマリンクID(例: #content-概要)になるため、他ページから特定見出しへリンクする際はIDを確認してください。
開発の進め方について
今回の開発はAnthropic社のClaude Code Fable 5と一緒に進めました。最初にぼくが提案したのはページ全体を#md{{ }}で囲う方式で、Fable 5はその実現可能性を検証したうえで、read.inc.phpの差し替えで済ませる方が筋がよいという代案を出してきました。保存時のアンカー付与でMarkdownが壊れる問題も、実装前の調査段階で指摘されています。仕様の判断(フラグ名の変更、既存データとの互換性など)はやはり運用してきた人間にしかできませんが、設計検討にAIを使うのはコードを書かせるのと同じくらい価値があると感じました。半日ほどでv0.1の公開まで辿り着けたのは、その恩恵が大きいと思っています。
インストールはpluginフォルダの中身をコピーするだけです。プラグイン一式とインストール手順はpukiwiki154_md2に置いてあります。まずはぼく自身のWikiで運用して、v0.1の粗を洗い出すところからです。

この記事に対するコメント
このページには、まだコメントはありません。
更新日:2026-06-11 閲覧数:46 views.