WordPress: 記事中の文字を絵文字や画像に変換する

ほぼ実用性はないと思うけれど、WordPressで入力した文章をブラウザに表示するときに、一部の単語を他の文字やリンクに置き換える方法をお伝えします。

やりたいこと

イメージをつかむために、実例でやりたいことをお伝えします。

次のような文章を、投稿画面で入力したとします。

こんにちは けーちゃん です(笑)
僕はいつも Yahoo! で検索しています
それしか使ったことないです (涙)

この記事をブラウザに表示するときに、(笑)を🙂に、(涙)を😥に、そしてYahoo!をリンクに置き換えます。
さらに、けーちゃんの前に画像を挿入します。

次のような結果になります。

こんにちは けーちゃんけーちゃん です 🙂
僕はいつも Yahoo! で検索しています
それしか使ったことないです 😥

単語を他の文字に置き換えることはあまりないかもしれませんね。

一方リンク置き換えは、ニュース記事などを見ていると記事中の人物名や重要なキーワードなどがリンクになっていることがよくあるので、需要がありそうな気がします。

方法は3つ

記事中の文字置き換え方法には様々な手段がありますが、今回は次の3つを挙げます。

  1. WordPressの英語圏顔文字変換機能を利用
  2. 変換機能を自作
  3. JavaScriptで置き換え

英語圏顔文字変換機能を利用

WordPressには :) などの英語圏で使用されている顔文字を 🙂 などの絵文字に変換する機能があります。
この機能を使用すると、任意の文字を絵文字や画像に変換することができます。

ただし、行の途中で任意の文字を使用する場合、前後にスペースを置く必要があります。
記事を入力するときに注意が必要なので、少し使いにくいかもしれません。

対象となっている英語圏顔文字や、この機能を停止させる方法については次の記事を読んでみてください。

WordPress: 英語圏顔文字の絵文字変換機能の削除

任意の文字を絵文字や画像に変換するには、smiliesフィルターで英語圏顔文字変換機能に変換したい文字を追加します。

smiliesフィルターで呼ばれる関数は、対象文字の連想配列を引数として受け取ります。
その配列を操作することで、対象文字を変更できます。

  • function my_smile( $smiles ){
  • $add_smiles = [
  • '(笑)'=>'🙂',
  • '(涙)'=>'😥',
  • 'Yahoo!'=>'<a href="https://www.yahoo.co.jp/">Yahoo!</a>',
  • 'けーちゃん'=>'<img src="https://xxxx.xxx/kchan.png" alt="けーちゃん">けーちゃん'
  • ];
  • return array_merge( $smiles , $add_smiles );
  • }
  • add_filter( 'smilies' , 'my_smile');
AFFS Simple Code Viewer
Copy

引数の$smilesには英語圏顔文字のデータが入っています。
このデータに追加してあげれば、後はWordPress側で処理してくれます。

データは置き換えたい文字をキーとして、置き換え後の文字を値とします。
置き換え後の文字にaタグやimgタグを入れておくと、そのまま使ってくれます。

英語圏顔文字の変換がいらないときは、array_merge()関数で配列を結合しないで$add_smilesをリターンすればOKです。

ちなみに、次のように置き換え後の文字が.pngや.jpgなどで終わるとimgタグを生成してくれます。

'けーちゃん'=>'kchan.png'

便利な気がしますが、おススメできません。

理由は画像のパス指定を他のフィルターでおこなう必要があることです。
ムダに処理を増やすのは避けた方がいいです。

それよりも問題なのが、次のようなタグが生成される点です。
<img src="" alt="" class="wp-smiley" style="height: 1em; max-height: 1em;" />

インラインのスタイル属性が付与されています。
これはAMP化したとき、問題になります。
AMPはインライン属性を許可していないのでエラーになってしまうのです。

変換機能を自作

前項のWordPressの英語圏顔文字変換機能を利用するケースは、記事入力時に対象文字の前後にスペースを挿入する必要があるため、とてもめんどうです。

そこで既存の機能に頼らずに、自分で置き換え機能を構築してみます。
今回は既存の英語圏顔文字変換機能のコードを参考にしました。

完成したのが次のコードです。

  • // データの初期化
  • function my_replace_data_init(){
  • global $my_replace_data;
  • $my_replace_data = [
  • // 対象データ
  • 'data' =>[
  • '(笑)'=>'🙂',
  • '(涙)'=>'😥',
  • 'Yahoo!'=>'<a href="https://www.yahoo.co.jp/">Yahoo!</a>',
  • 'けーちゃん'=>'<img src="https://xxxx.xxx/kchan.png" alt="けーちゃん">けーちゃん'
  • ],
  • // 除外タグ
  • 'ignore_tag' => 'code|pre|style|script|textarea'
  • ];
  • $my_replace_data['search'] = (function($data) {
  • $keys = array_map(function($n){return preg_quote($n,'/');},array_keys( $data ));
  • rsort( $keys );
  • return '/(' . implode( '|' , $keys ) . ')/';
  • })($my_replace_data['data']);
  • $my_replace_data['ignore_start'] = '/^<(' . $my_replace_data['ignore_tag'] . ')/';
  • $my_replace_data['ignore_end'] = '/^<\/(' . $my_replace_data['ignore_tag'] . ')/';
  • }
  • // 文字の置き換え処理
  • function my_string_replace( $content ){
  • global $my_replace_data;
  • if( !isset( $my_replace_data ) ){
  • my_replace_data_init();
  • }
  • list('ignore_start' => $ignore_start, 'ignore_end' => $ignore_end , 'search' => $search , 'data' => $data) = $my_replace_data;
  • $split = preg_split( '/(<.*?>)/', $content, -1, PREG_SPLIT_DELIM_CAPTURE ); // Capture the tags as well as in between.
  • $length = count( $split );
  • $ignore_count = 0;
  • $replace_all_count = 0;
  • for ( $i = 0; $i < $length; $i++ ) {
  • $text = $split[ $i ];
  • if( preg_match( $ignore_start, $text ) ){
  • $ignore_count ++;continue;
  • }
  • if( preg_match( $ignore_end, $text ) ){
  • $ignore_count --;continue;
  • }
  • if( $ignore_count > 0 ) { continue; }
  • $replace_count=0;
  • $split[ $i ] = preg_replace_callback( $search ,function ($matches) use ( $data ){
  • return $data[ $matches[0] ];
  • },$text,-1,$replace_count);
  • $replace_all_count += $replace_count;
  • }
  • return $replace_all_count === 0 ? $content : implode("",$split);
  • }
  • add_filter('the_content','my_string_replace',30);
AFFS Simple Code Viewer
Copy

参考にしたコードがcode、pre、style、script、textareaタグ内での置き換えをおこなっていないので、上のコードも同様な処理をおこなっています。

基本的には受け取った文字列をタグで分割して、一行ずつ内容を確認しているだけです。

JavaScriptで置き換え

3つ目の方法は、JavaScriptでの置き換えです。
前項までの二つの方法はサーバー側の処理でしたが、こちらはスマホやパソコン側での処理になります。

次のコードを、jsファイルとして保存します。

  • (()=>{
  • // 対象データ
  • const replaceData = {
  • "(笑)" :"🙂",
  • "(涙)" :"😥",
  • "Yahoo!" : '<a href="https://www.yahoo.co.jp/">Yahoo!</a>',
  • "けーちゃん" : '<img src="https://xxxx.com/kchan.png" alt="けーちゃん">けーちゃん'
  • };
  • // 除外タグ
  • const ignoreTag = ["code","pre","style","script","textarea","ins"]
  • .map( e=>e.toUpperCase() );
  • const replaceRegx = new RegExp( "("
  • + Object.keys(replaceData)
  • .sort( (a,b)=>a<b ? 1 : -1 )
  • .map( e=>e.replace(/[\\^$.*+?()[\]{}|]/g,"\\$&") )
  • .join("|")
  • + ")");
  • const replaceText = text =>{
  • const r = text.replace(replaceRegx , m=>replaceData[m] );
  • return r === text ? null : r;
  • }
  • const replaceTextNode = startElement=>{
  • if( startElement === null || startElement.childNodes === null) return;
  • const firstChild = node =>{
  • return node.childNodes ? node.childNodes[0] : nextElement(node);
  • };
  • const nextElement = node =>{
  • if( node.nextSibling ) return node.nextSibling;
  • return node.parentNode === startElement ? null : nextElement(node.parentNode);
  • };
  • let currentElement = firstChild(startElement);
  • while( currentElement){
  • if( currentElement.nodeType === Node.ELEMENT_NODE
  • && ignoreTag.indexOf( currentElement.tagName ) < 0 ){
  • currentElement = firstChild(currentElement);
  • continue;
  • }
  • if( currentElement.nodeType === Node.TEXT_NODE ){
  • const r = replaceText(currentElement.nodeValue);
  • if( r !== null ){
  • const addElement = document.createElement("span");
  • addElement.innerHTML = r;
  • currentElement.replaceWith(addElement);
  • currentElement = addElement;
  • }
  • }
  • currentElement = nextElement( currentElement );
  • }
  • };
  • window.addEventListener( "DOMContentLoaded" , ()=> {
  • replaceTextNode( document.body );
  • });
  • })();
AFFS Simple Code Viewer
Copy

上のコードは除外するタグを変数ignoreTag で指定しています。
この中に ins がありますが、これはグーグルアドセンスが挿入した広告で使用されているので、影響がないように除外しています。

次にjsファイルを読み込むために、wp_enqueue_script()関数をfunction.phpなどで実行します。

  • wp_enqueue_script( 'myreplace' ,'jsファイルのパス' );
AFFS Simple Code Viewer
Copy

どの方法がいいのか

今回挙げた3つの方法のうち、どれを選択すべきかは状況次第です。
ただ、少々使いにくいので一つ目のWordPressの英語圏顔文字変換機能を利用は、候補から外れることが多いと思います。

二つ目はサーバー側の処理時間が増えるのがデメリットです。
とはいえ、極端に遅くなるというほどではありません。

三つ目はサーバー側の負担が減るので、PV数が非常に多いサイトなら三つ目の方法がよいケースもあります。
しかしブラウザ側での読み込み終了までの時間が増えるので、SEO的に不利になる可能性があります。
あくまで可能性です。必ず不利になるということではありません。

PV数が非常に多くなることはほとんどないので、どちらかというと二つ目がおススメです。