3. 9 shortcodeのpタグラップを解除して保存

更新・追加前のコンテンツ整形

 更新等保存前に投稿本文を整形します。
 コードは、更新等保存前の投稿本文の整形全体です。htmlモードで保存した場合には基本的には整形を行いません。ビジュアルモード保存時、更新・追加前のコンテンツ整形として、次のことを実施しています。
   ・先頭にタグがない場合、pタグでラップ
   ・ショートコードpタグラップ解除
   ・table 内 <p> 削除
   ・<p> 字下げ
   ・ソースコードに改行の挿入
 これらにより、次の効果があります。
   ・文頭の字下げができる。
   ・ショートコードのpタグラップを解除できる。
   ・WORDからのコピペ後のtable 内 <p> 削除
   ・WORDからのコピペ後の字下げ
   ・HTMLコードが見やすくなる。

 空白文字のコードを統一しています。00A0 は JacaScript で使用されるユニコードの空白コードの一つです。
 関数 wp_default_editor ですが、名前が“デフォルト”で紛らわしいのですが、実際はクッキーを使い保存時のエディターの種類(ビジュアル、テキスト)を返します。
 投稿本文にどのようなショートコードがあるか判定しています。
 “疑似ショートコード”とは、Escaped shortcodes のことで適当な日本語訳がないのでとりあえずこのように記述しています。ショートコードを文字列として記述したい場合に“[[ショートコード]]”のようにします。ショートコードではありません。一般の人は使用しないと思います。この“疑似ショートコード”は、囲み型ショートコードの場合に、ビジュアルモードで正しく表示されません。これは、カスタマイズの範囲外です。
 先頭にタグがない場合、その部分をpタグでラップしています。“字下げ用全角スペースを保持(2)”で説明したように、文頭の字下げを保持するため新規作成時に初期値を設定していますが、これが BackSpace で消えることがあります。このための処理で、字下げを行うことができます。
 ショートコードのpタグラップを解除します。
 WORDコピペ対策とhtmlコードの整形は別途説明します。
 使用しているフックは、“投稿作成のカスタマイズ(3)”で作成した独自フックです。
 なお、保存前に整形したい場合は、JavaScript のカスタマイズが必要です。
 関数 hk_escape_pre と hk_restore_pre は、“共通関数”に記載します。
 変数 $hk_editor_allblocks は、“字下げ用全角スペースを保持(2)”に記載した変数です。

コード

// --------------------------------
// 更新・追加前のコンテンツ整形
// --------------------------------
function hk_editor_insert_post_data( $data, $post_id, $old_post ) {
    // 本文取得
    $content = $data['post_content'];
    $content = trim( $content );
    // 本文整形
    if ( $content != '' ) {
        // タブ削除
        $content = preg_replace( '/\t/', '', $content );
        // 文字を統一
        $content = preg_replace( '/\x{00A0}/u', ' ',  $content );
        $content = preg_replace( '/\r\n|\r/',   "\n", $content );
        // エディターモードが'tinymce'の場合に整形
        if ( wp_default_editor() == 'tinymce' )
            $content = hk_editor_content_reform_tmce( $content );
    }
    // 整形完了
    $data['post_content'] = $content;
    return $data;
}
add_filter( 'hk_func_insert_post_data', 'hk_editor_insert_post_data', 10, 3 );

// --- サブルーチン ---------------
// コンテンツ整形 サブ
//   更新・追加前のコンテンツ整形用
// --------------------------------
function hk_editor_content_reform_tmce( $content ) {
    global $hk_editor_allblocks;
    // タグの前後の不要な空白の削除
    $content = preg_replace( '/ *(<[^>]*>) */i', "$1", $content );
    // 不要なpタグの除去
    $content = preg_replace( '/<p>&nbsp;<\/p>/i', '', $content );
    $content = preg_replace( '/<p><\/p>/i',   '', $content );
    $content = preg_replace( '/<p>\n<\/p>/i', '', $content );
    $content = trim( $content );
    if ( $content == '' )        return '';
    // PREタグ退避
    $pre_tags1 = array();
    $pre_tags2 = array();
    $line_break = true;
    $content = hk_escape_pre( $content, $pre_tags1, $pre_tags2, $line_break );
    // \n 削除(念のため)
    $content = preg_replace( '/\n/', '', $content );
    // short code データ
    global $shortcode_tags;
    $tagnames = array_keys( $shortcode_tags );
    $code_names = array();
    $code_types = array();
    // short code 有無チェック
    foreach ( $tagnames as $tagname ) {
        $code_name = $tagname;
        $code_name = preg_quote( $code_name );
        $self = hk_editor_short_code_pattern( $code_name, 'self' );
        $re_str1 = '/'.$self.'/i';
        if ( ! preg_match( $re_str1, $content ) )    continue;
        // ショートコード有り
        $encl = hk_editor_short_code_pattern( $code_name, 'encl' );
        $re_str2 = '/'.$encl.'/i';
        // 囲み型ショートコード
        if ( preg_match( $re_str2, $content ) ) {
            $type = 'encl';
            $pattern00 = $encl;
        }
        // 自己完結型ショートコード
        else {
            $type = 'self';
            $pattern00 = $self;
        }
        // 疑似ショートコード修正
        $pattern01 = '(\['.$pattern00.')(?!\])';
        $re_str01  = '/'.$pattern01.'/i';
        $content = preg_replace( $re_str01, "$1\]", $content );
        $pattern02 = '(?<!\[)('.$pattern00.'\])';
        $re_str02  = '/'.$pattern02.'/i';
        $content = preg_replace( $re_str02, "\[$1", $content );
        // ショートコード登録
        $pattern04 = '(?:[^\[]|^)'.$pattern00.'(?:[^\]]|$)';
        $re_str04  = '/'.$pattern04.'/i';
        if ( preg_match( $re_str04, $content ) ) {
            $code_names[] = $code_name;
            $code_types[ $code_name ] = $type;
        }
    }
    unset( $tagname );
    // 先頭にタグがない場合
    if ( $content{0} != '<' ) {
        // ブロックタグで分割
        $pattern_b = '/(<'.$hk_editor_allblocks.'(?: [^>]*>|>))/i';
        $blocks = preg_split( $pattern_b, $content, -1, PREG_SPLIT_DELIM_CAPTURE );
        // pラップ
        $blocks[0] = '<p>'.$blocks[0].'</p>';
        $content = implode( '', $blocks );
    }
    // ショートコードpラップ解除
    if ( ! empty( $code_names ) ) {
        foreach ( $code_names as $code_name ) {
            $code_type = $code_types[ $code_name ];
            $pattern00 = hk_editor_short_code_pattern( $code_name, $code_type );
            // ショートコードで分割
            $pattern3 = '(?<!\[)('.$pattern00.')(?!\])';
            $re_str3  = '/'.$pattern3.'/i';
            $blocks = preg_split( $re_str3, $content, -1, PREG_SPLIT_DELIM_CAPTURE );
            $i_max = count( $blocks ) - 2;
            for( $i=0; $i<$i_max; $i+=2 ) {
                // $blocks[$i] [shortcode] $blocks[$i+2]
                // $blocks[$i] をブロックタグで分割
                $pattern_b = '/(<'.$hk_editor_allblocks.'(?: [^>]*>|>))/i';
                $sub_blocks = preg_split( $pattern_b, $blocks[ $i ], -1, PREG_SPLIT_DELIM_CAPTURE );
                $sub_max = count( $sub_blocks );
                // ブロックタグが無い場合
                if ( $sub_max == 1 )        continue;
                // <block-tag> $sub_blocks[$sub_max-1] [shortcode]
                // 直近のブロックタグがpタグか?
                if ( ! preg_match( '/<p( [^>]*>|>)/i', $sub_blocks[ $sub_max-2 ] ) )        continue;
                // 直近のブロックに</p>があるか?
                $pos_pc = strripos( $sub_blocks[ $sub_max-1 ], '</p>' );
                if ( $pos_pc !== false )    continue;
                // <p> xxxxx [shortcode] yyyyy </p>
                // </p> と <p> の挿入
                // → <p> xxxxx </p> [shortcode] <p> yyyyy </p>
                $blocks[ $i ]  .= '</p>';
                $blocks[ $i+2 ] = '<p>'.$blocks[ $i+2 ];
            }
            $content = implode( '', $blocks );
            // <p></p> 削除
            $content = preg_replace( '/<p(?: [^>]*>|>)((?:<br [^>]*>)+)<\/p>/i', "$1", $content );
            $content = preg_replace( '/<p( [^>]*>|>)<\/p>/i', '', $content );
        }
        unset( $code_name );
    }
    // table 内 <p> 削除 WORDコピペ対策
    if ( strpos( $content, '</td>' ) !== false ) {
        // </td> で分割
        $content_parts = explode( '</td>', $content );
        // 整形
        $content = '';
        foreach ( $content_parts as $content_part ) {
            // <td で三分割
            $blocks = preg_split( '/(<td(?: [^>]*>|>))/i', $content_part, -1, PREG_SPLIT_DELIM_CAPTURE );
            // <td が無い場合(最終part)
            if ( count( $blocks ) == 1 ) {
                $content .= $content_part;
                continue;
            }
            // tdタグより後の文字列整形
            // <p> 除去
            $blocks[ 2 ] = preg_replace( '/<p(?: [^>]*>|>)/i', '', $blocks[ 2 ] );
            // 文字列末尾の</p> 除去
            $blocks[ 2 ] = preg_replace( '/<\/p>$/i', '',  $blocks[ 2 ] );
            // 残りの </p> を <br />
            $blocks[ 2 ] = preg_replace( '/<\/p>/i', '<br />', $blocks[ 2 ] );
            // 完了
            $content .= $blocks[ 0 ].$blocks[ 1 ].$blocks[ 2 ].'</td>';
        }
        unset( $content_part );
    }
    // <p> 字下げ WORDコピペ対策
    // スタイルが設定している場合は尊重
    $content = preg_replace( '/<p> /i',  '<p>',   $content );
    $content = preg_replace( '/<p>/i',    '<p> ', $content );
    $content = preg_replace( '/<p> </i', '<p><',  $content );
    $content = preg_replace( '/<p><\/p>/i', '',    $content );
    // ソースコードを見やすくする
    // タグの前に改行の挿入
    $tag1 = 'p|div|\/div|table|\/tbody|tr|\/tr|h1|h2|h3|h4|h5|h6|ul|\/ul|ol|\/ol|pre';
    $str1 = '(?:'.$tag1.')(?: |>)';
    $content = preg_replace( '/(<'.$str1.')/i', "\n$1", $content );
    // タグの前に改行と字下げの挿入
    $tag1 = 'li|td|th';
    $str1 = '(?:'.$tag1.')(?: |>)';
    $content = preg_replace( '/(<'.$str1.')/i', "\n    $1", $content );
    // 終了タグの後ろに改行の挿入:開始タグでなく文章が続く場合がある
    $tag1 = 'p|div|table|h1|h2|h3|h4|h5|h6|ul|ol|pre';
    $str1 = '(?:'.$tag1.')';
    $content = preg_replace( '/(<\/'.$str1.'>)/i', "$1\n", $content );
    // DIVタグの後ろに改行の挿入
    $content = preg_replace( '/<div( [^>]*>|>)/i', "<div$1\n", $content );
    // brタグの後ろに改行の挿入
    $content = preg_replace( '/(<br [^>]*>)/i', "$1\n", $content );
    // ショートコードの前後に改行の挿入
    if ( ! empty( $code_names ) ) {
        foreach ( $code_names as $code_name ) {
            $code_type = $code_types[ $code_name ];
            $pattern00 = hk_editor_short_code_pattern( $code_name, $code_type );
            $pattern4 = '(?<!\[)('.$pattern00.')(?!\])';
            $re_str4  = '/'.$pattern4.'/i';
            $content = preg_replace( $re_str4, "\n$1\n", $content );
        }
        unset( $code_name );
    }
    // 重複した改行の削除
    $content = preg_replace( '/\n+/', "\n", $content );
    // PREタグ復元
    $content = hk_restore_pre( $content, $pre_tags1, $pre_tags2 );
    // 整形完了
    $content = trim( $content );
    return $content;
}

// --- サブルーチン ---------------
// ショートコード パターン
// --------------------------------
function hk_editor_short_code_pattern( $code_name, $code_type ) {
    $self = '\['.$code_name.'[^\]]*\]';
    $encl = $self.'[^\[]*[\[]+\/'.$code_name.'\]';
    if ( $code_type == 'self' )
        return $self;
    return $encl;
}

 このプログラムをお使いになる場合は、お使いになる方の自己責任でお願いします。

関連

更新日:2016/03/24
掲載日:2015/11/16