PHPのMarkdownパーサーであるPHP Markdownを導入して、自分好みに拡張してみました。
検証環境
検証に使った環境はPHP 8.0.1です。
$ php -v
PHP 8.0.1 (cli) (built: Jan 12 2021 01:57:17) ( NTS )
Copyright (c) The PHP Group
Zend Engine v4.0.1, Copyright (c) Zend Technologies
インストール
composerを使ってインストールします。
$ composer require michelf/php-markdown
試してみる
インストールできたら動作確認してみます。実行用のPHPファイル test.php
を用意します。
<?php
require 'vendor/autoload.php';
use Michelf\MarkdownExtra;
$my_text = <<<MYTEXT
# This is h1
hogehoge
MYTEXT;
$markdown = new MarkdownExtra();
echo $markdown->transform($my_text);
test.php
を実行すると、HTMLに変換されて出力されます。
$ php test.php
<h1>This is h1</h1>
<p>hogehoge</p>
なお、利用できるパーサーには Michelf\Markdown
と Michelf\MarkdownExtra
の2つがありますが、特別な理由がなければ拡張されたmarkdownのパーサーであるMichelf\MarkdownExtra
の方が使いやすいと思います(詳細はこちら)。
拡張してみる
markdownパーサーとして普通に使う分には、 Michelf\MarkdownExtra
をそのまま使えば十分です。が、自分の使い方的には、FencedCodeブロックでファイル丸ごと書く場合も多いので、簡単な記述でファイル名を表示できるように拡張しようと思いました。
目標は、以下のような表示を簡単に記述できるようにすることです。

アイデア
FencedCodeブロックでファイル名を指定できるようにしたいので、Special Attributesを利用し、filename
属性が指定された場合、 属性値を取り出してファイル名のHTML要素として出力するようにします。例えば以下の場合、hoge.txt
がファイル名の要素として出力されるようにします。
``` [filename=hoge.txt]
fuga
```
ソース
アイデアを基に実装したものが以下になります。_doFencedCodeBlocks_callback
メソッドがFencedCodeブロックの変換処理を行う部分なので、これをオーバーライドする形になっています。
<?php
namespace Src;
use Michelf\MarkdownExtra;
class Markdown extends MarkdownExtra
{
/**
* Callback to process fenced code blocks
*
* @param array $matches
* @return string
*/
protected function _doFencedCodeBlocks_callback($matches)
{
$classname =& $matches[2];
$attrs =& $matches[3];
$codeblock = $matches[4];
$file_name = $this->pullFileNameFromAttrs($attrs);
if ($this->code_block_content_func) {
$codeblock = call_user_func($this->code_block_content_func, $codeblock, $classname);
} else {
$codeblock = htmlspecialchars($codeblock, ENT_NOQUOTES);
}
$codeblock = preg_replace_callback('/^\n+/',
array($this, '_doFencedCodeBlocks_newlines'), $codeblock);
$classes = array();
if ($classname !== "") {
if ($classname[0] === '.') {
$classname = substr($classname, 1);
}
$classes[] = $this->code_class_prefix . $classname;
}
$attr_str = $this->doExtraAttributes($this->code_attr_on_pre ? "pre" : "code", $attrs, null, $classes);
$pre_attr_str = $this->code_attr_on_pre ? $attr_str : '';
$code_attr_str = $this->code_attr_on_pre ? '' : $attr_str;
$file_name_element = $this->buildFileNameElement($file_name);
$codeblock = "<div class=\"code-block\">$file_name_element<pre$pre_attr_str><code$code_attr_str>$codeblock</code></pre></div>";
return "\n\n".$this->hashBlock($codeblock)."\n\n";
}
/**
* Pull file name from attributes
*
* @param string $attrs
* @return string
*/
private function pullFileNameFromAttrs(string &$attrs): string
{
$matches = [];
if (preg_match('/(^| +)filename=([^ ]+)/', $attrs, $matches) !== 1) {
return '';
}
// remove filename attribute
$attrs = trim(str_replace($matches[0], '', $attrs));
return $matches[2];
}
/**
* Build DOM element for file name
*
* @param string $file_name
* @return string
*/
private function buildFileNameElement(string $file_name): string
{
if ($file_name === '') {
return '';
}
return "<span class=\"filename\">{$file_name}</span>";
}
}
実行結果
拡張したパーサーを試してみます。確認用に以下のファイルを用意しました。
<?php
require 'vendor/autoload.php';
use Src\Markdown;
$my_text = <<<MYTEXT
# Code
This is `hello.php`.
``` {filename=hello.php}
<?php
echo 'Hello!'
```
MYTEXT;
$markdown = new Markdown();
?>
<html>
<head>
<link rel="stylesheet" href="style.css">
</head>
<body>
<?= $markdown->transform($my_text); ?>
</body>
</html>
実行してみると、filename
属性に指定したファイル名が、 <span class="filename">hello.php</span>
という要素として出力できていることがわかります。
$ php test-extended.php
<html>
<head>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h1>Code</h1>
<p>This is <code>hello.php</code>.</p>
<div class="code-block"><span class="filename">hello.php</span><pre><code><?php
echo 'Hello!'
</code></pre></div>
</body>
</html>
そして適当なcss(style.css
)を用意して、ブラウザで表示すれば以下のようにファイル名がそれっぽく見えます!

.code-block {
position: relative;
padding: 1rem;
color: #efefef;
background: #1e1e1e;
}
.filename {
position: absolute;
top: 0;
color: #efefef;
background: #6f6f6f;
font-weight: 700;
}
まとめ
PHPのMarkdownパーサーであるPHP Markdownを自分好みに拡張してみました。PHP Markdownの拡張は自分で使うのに便利なのはもちろん、markdownの独自拡張の記法を簡単に取り扱えるので、特定のテーマに特化した情報共有サービスを作る場合にも利用できそうです。