【WordPress】Prism.js(シンタックスハイライト)をショートコードで使う

HTMLやPHPなどのソースコードを見やすく表示するため、プラグイン:SyntaxHighlighter Evolvedを使用していますが、複数コードを載せた場合の処理が重たく感じます。そこで、軽快に動作するPrism.jsへと変更しました。結果、SyntaxHighlighter Evolvedと比較してかなり軽くなりました。

WordPressでPrism.jsをショートコードで表示するためにハマったポイントをまとめておきます。なお、Prism.jsについてはWordPressでプラグインを使わずにシンタックスハイライトがとても参考になりました。

環境
PHP
7.0
WordPress
4.2.2
Prism.js
1.6.0

Prism.js 読み込み設定

ソースコードは記事ページでのみ載せる予定ですので、ダウンロードしたprism.cssprism.jssingle(記事)ページでのみ読み込むように設定します。

functions.php
function set_prism_js()
{
    if (is_single()) {
        wp_enqueue_style('prism-style', get_template_directory_uri(). '/css/prism.css', false, '1.6.0');
        wp_enqueue_script('prism-script', get_template_directory_uri(). '/js/prism.js', false, '1.6.0', true);
    }
}
add_action('wp_enqueue_scripts', 'set_prism_js');

Prism.jsの使い方

例としてPrism.jsとPrism.jsプラグイン(Line Highlight、Line Numbers、Show Language)を使用した場合のWordPressテキストエディタは以下のようになります。

WordPress テキストエディタ
<pre class="line-numbers" data-line="3" data-language="functions.php">
<code class="language-php">
<?php

echo &apos;WordPressでPrism.js&apos;;

?>
</code>
</pre>

と表示されます。

Webブラウザでの表示
<?php

echo 'WordPressでPrism.js';

?>

ただ、毎回precodeの入力。そして、&lt;&gt;などを特殊文字(文字実体参照)へ変換するのは面倒なので、ショートコード化します。

Prism.jsをショートコード化する

先ほどのコードを以下のショートコードで表示できるようにします。

WordPress テキストエディタ
[prism language="php" data_language="functions.php" data_line="3"]
<?php

echo 'WordPressでPrism.js';

?>
[/prism]

ショートコードの定義

ショートコードの使い方は以前に記事でまとめていますので、今回は完成したコードのみを載せておきます。

functions.php
function shortcode_prism_js($atts, $content = null)
{
    extract(shortcode_atts([
        'language'      => '',
        'data_language' => '',
        'data_line'     => ''
    ], $atts));

    if ($data_language) {
        $data_language = ' data-language="'. esc_attr($data_language). '"';
    }

    if ($data_line) {
        $data_line = ' data-line="'. esc_attr($data_line). '"';
    }

    $format = '<pre class="line-numbers"%s%s><code class="language-%s">%s</code></pre>';
    return sprintf(
                $format,
                $data_language,
                $data_line,
                esc_attr($language),
                htmlspecialchars(trim($content), ENT_QUOTES, 'UTF-8')
            );
}
add_shortcode('prism', 'shortcode_prism_js');

ショートコード内のソースコードに余計なタグが入る

先ほどのコードでは、[prism][/prism]内のソースコードに余計な<p></p><br />が入り、意図するように表示されません。

WordPressには<p></p><br />を付加するwpautopという関数があり、デフォルトで記事本文に適用されているようです。ですが、本来であればwpautop<pre>...</pre>タグをスルーするはずです。

調べてみたところ、原因は記事本文を取得表示する関数the_contentに登録されているフィルターの優先順位にありました。ショートコードを展開するdo_shortcodeよりも先にwpautopを実行するように優先順位が設定されてあります。

wp-includes/default-filters.php
// 優先順位が省略されているので、10となる
// 小さい数字の関数から実行される

add_filter( 'the_content', 'wptexturize'                       );
add_filter( 'the_content', 'convert_smilies'                   );
add_filter( 'the_content', 'wpautop'                           );
add_filter( 'the_content', 'shortcode_unautop'                 );
add_filter( 'the_content', 'prepend_attachment'                );
add_filter( 'the_content', 'wp_make_content_images_responsive' );

add_filter( 'the_content', 'do_shortcode', 11 ); // AFTER wpautop()

ですので、wpautop実行の時点では、[prism][/prism]は単なる文字列として扱われます。当然、<pre>では無いので、[prism][/prism]内の文字列(ソースコード)にwpautopが適用されます。

原因がわかったのでwpautopよりも先に[prism][/prism]を展開させます。

指定のショートコードをwpautopよりも先に展開させる

the_contentをフックして実現させます。the_contentの第3引数の数値(優先順位)にwpautopよりも少ない数値(10未満)を設定し、指定のショートコードを先に展開させます。

functions.php
// コメントアウト
// add_shortcode('prism', 'shortcode_prism_js');

add_filter('the_content', function ($content) {

    // 登録されている全ショートコード
    global $shortcode_tags;

    // 登録されているショートコードを退避してから消去
    $orig_shortcode_tags = $shortcode_tags;
    remove_all_shortcodes();

    // wpautop関数実行前に処理したいショートコードをここで登録
    add_shortcode('prism', 'shortcode_prism_js');

    // [prism]ショートコードを実行
    $content = do_shortcode($content);

    // 退避したショートコードを元に戻す
    $shortcode_tags = $orig_shortcode_tags;

    // 実行済みのショートコードを削除
    remove_shortcode('prism');

    return $content;
}, 9, 1);

以上で意図した動作と表示になりました。

エディタのクイックボタンにprismを追加する

さて、ショートコード化したことでタグ入力よりも簡素になったとは言え、長いショートコードを自分で打つのも同じように面倒です。そこで、prism ショートコードの挿入をエディタのクイックボタンに追加します。

functions.php
function add_my_quick_btn()
	if ( !wp_script_is( 'quicktags' ) ) {
		return;
	}
?>
<script type="text/javascript">
    // QTags.addButton('ID', 'ボタンのラベル', '開始タグ', '終了タグ');
    QTags.addButton('prism', 'prism', '[prism language="" data_language="" data_line=""]', '[/prism]');
</script>
<?php
}
add_action('admin_print_footer_scripts',  'add_my_quick_btn');

prismボタンが追加されました。クイックボタンをクリックすると…

WordPress テキストエディタ
[prism language="" data_language="" data_line=""]
[/prism]

が挿入されます。以上で完成です。

追記: ショートコードが展開されない時がある

ショートコードがうまく展開されないときがあり、閉じタグが文字列のまま出力されてしまうときがありましたが、ショートコードの展開よりも先にエスケープ処理をすることで対処できました。

functions.php
function shortcode_prism_js($atts, $content = null)
{
    // ここまで省略

    $format = '<pre class="line-numbers"%s%s><code class="language-%s">%s</code></pre>';
    return sprintf(
                $format,
                $data_language,
                $data_line,
                esc_attr($language),
                // htmlspecialchars(trim($content), ENT_QUOTES, 'UTF-8') 消去
                $content
            );
}

add_filter('the_content', function ($content) {

    // ここまで省略

    add_shortcode('prism', 'shortcode_prism_js');

    $content = preg_replace_callback(
        '/\[prism(.+?)\](.+?)\[\/prism\]/su',
        function ($matches) {
            return '[prism'. $matches[1]. ']'. htmlspecialchars(trim($matches[2]), ENT_QUOTES, 'UTF-8'). '[/prism]';
        },
        $content
    );

    $content = do_shortcode($content);

    // ここから省略
}, 9, 1);