なぜwpautopは余計な段落タグを生成するのか

今回は、WordPressでせっかく書いたHTMLコードが破壊されてしまうことで悪名高い[wpautop] (https://developer.wordpress.org/reference/functions/wpautop/ "WordPressホームページ")関数の`<p>`追加関連の処理の動作を記述します。
なお、GutenBergのブロックエディタではこのような問題は発生しないようです。
wpautopをフィルタ解除すれば問題なくなるからか、余りこの問題の詳細を扱った記事が無いうえ、会社が古いバージョン（5年以上も前！）を使い続けており、フィルタすら触れない環境に苦しめられているので、似たような思いをしている方向けに、回避策等含めて書いていきます。

#WordPressで\</p>だけが生成されてしまう問題と、その回避策

特に厄介なものの発生条件を説明します。他にもあればコメント欄に書いていただけると嬉しいです。

1. インライン要素[^1]直下にブロック要素がある場合`</p>`が発生する。見た目上は、インライン要素の直下に空の段落が存在するように見える(横書きの場合)。<br>**解決策: インライン要素は必ず`<p>`で囲む。**
2. `<div>`、`<address>`、`<form>`を3層以上の入れ子構造にして、tabなどでインデントを行っている場合、内側から2番目以降の閉じタグ下に`</p>`が挿入される。<br>**解決策: コードをWordPressに貼り付ける際は閉じタグのものだけでもインデントを消去する。**

#wpautopの`<p>`生成関連処理手順
一旦、一連の処理をリストアップしてみます。

1. 連続する`<br>`を2重改行(区切りとして機能する)に変換
2. ブロック要素[^2]開始タブ直前に2重改行追加
3. ブロック要素終了タブ直後に2重改行追加
4. `<hr />`直後に2重改行追加
5. `<option>`、`<figcaption>`、`<source>`および`<track>`要素前後、`<object>`、`<audio>`および`<video>`要素内部、`<param>`および`<embed>`要素前の改行を削除
6. 改行の最大連続数を2に制限(要は区切りに変換する)
7. 2重改行による区分けごとに前後の改行を削除したうえで`<p>`で囲む
8. 中身が空の`<p>`を削除する
9. `<div>`、`<address>`および`<form>`内部の`<p>`に閉じタグが見当たらない場合、閉じる
10. ブロック要素の開始・終了タグのいずれか、もしくは`<li>`が`<p>`で囲まれている場合、その`<p>`を取り除く
11. `<blockquote>`が`<p>`で囲まれている場合、`<p>`を`<blockquote>`で囲むように修正
12. ブロック要素開始タグ、閉じタグの前に`<p>`がある場合削除
13. ブロック要素開始タグ、閉じタグの後ろに`</p>`がある場合削除

#実際の処理の流れを体験してみよう
Qiitaが`<ol>`の`value`プロパティに対応していないので、項番の見た目が悪いですがご容赦を。

元

```html:source.html
<div>
    <div>
        test1
        <div>
            test2
            <div>
                test3
            </div>
        </div>
    </div>
</div>
```

1.連続する`<br>`を2重改行に変換(該当無し)

2.ブロック要素[^2]開始タブ直前に2重改行追加

```html:process2.html
<div>


    <div>
        test1


        <div>
            test2


            <div>
                test3
            </div>
        </div>
    </div>
</div>
```

3.ブロック要素終了タブ直後に2重改行追加

```html:process3.html
<div>


    <div>
        test1


        <div>
            test2


            <div>
                test3
            </div>


        </div>


    </div>


</div>
```

4.`<hr>`直後に2重改行追加(該当無し)

5.`<option>`、`<figcaption>`、`<source>`および`<track>`要素前後、`<object>`、`<audio>`および`<video>`要素内部、`<param>`および`<embed>`要素前の改行を削除(該当無し)

6.改行の最大連続数を2に制限(要は区切りに変換する)(該当無し)

7.区分けごとに前後の改行を削除したうえで`<p>`で囲む

```html:process7.html
<p><div></p>
<p>    <div>
        test1</p>
<p>        <div>
            test2</p>
<p>            <div>
                test3
            </div></p>
<p>        </div></p>
<p>    </div></p>
<p></div></p>
```

8.中身が空の`<p>`を削除する(該当無し)

9.`<div>`、`<address>`および`<form>`内部の`<p>`に閉じタグが見当たらない場合、閉じる

```html:process9.html
<p><div></p>
<p>    <div>
        test1</p>
<p>        <div>
            test2</p>
<p>            <div>
                test3
            </div></p>
<p>        </p></div></p>
<p>    </p></div></p>
<p></div></p>
```

10.ブロック要素の開始・終了タグのいずれか、もしくは`<li>`が`<p>`で囲まれている場合、その`<p>`を取り除く

```html:process10.html
<div>
<p>    <div>
        test1</p>
<p>        <div>
            test2</p>
<p>            <div>
                test3
            </div></p>
<p>        </p></div></p>
<p>    </p></div></p>
</div>
```

11.`<blockquote>`が`<p>`で囲まれている場合、`<p>`を`<blockquote>`で囲むように修正(該当無し)

12.ブロック要素開始タグ、閉じタグの前に`<p>`がある場合削除

```html:process12.html
<div>
    <div>
        test1</p>
        <div>
            test2</p>
            <div>
                test3
            </div></p>
        </p></div></p>
    </p></div></p>
</div>
```

13.ブロック要素開始タグ、閉じタグの後ろに`</p>`がある場合削除

```html:process13.html
<div>
    <div>
        test1</p>
        <div>
            test2</p>
            <div>
                test3
            </div>
        </p></div>
    </p></div>
</div>
```

#感想
PHPのコメント文を参考にしながら読み解いて参りました(筆者はPHP経験ほぼゼロ。[PHP研究所] (https://www.php.co.jp/)の新書なら何冊か読んだはずです(笑)。何を読んだかは覚えていませんが…)が、問題と思われる手順が2つあります。
まず、8番と9番の処理は明らかに手順を逆にした方がよかったです。そうすれば`<div/>`末尾の`</p>`は発生しなかったでしょう。
また、9番の処理が`<p>`の内部に`<div>`、`<address>`もしくは`<form>`の開始タグがある場合、それぞれの開始タグを入れ替える処理にすれば(見てくれはぎこちないですが)[html文法的には妥当] (https://developer.mozilla.org/ja/docs/Web/HTML/Element/p#content)なコードになります。

[2020年8月1日時点で37.8%という圧倒的なシェアを誇る] (https://w3techs.com/technologies/history_overview/content_management/all)巨大CMSが、「終了タグしかない要素は存在しない、`<p>`要素が省略できるのは終了タグの方」という初歩的な文法の間違いを放置してきたというのが何ともやるせないですね。

##WordPressの名誉のために
昨年、WordPress開発部内にも[この問題を解決しようとする動き] (https://core.trac.wordpress.org/ticket/27350#comment:21)が存在しました。そこでの解決策は、8番と9番の処理順入れ替えと、閉じていない`<p>`タグ(開始終了どちらも)を正規表現で削除することでした。しかし、[PHP 7.2以前ではパフォーマンスに重大な問題があるということで元の木阿弥になってしまったようです] (https://core.trac.wordpress.org/ticket/27350#comment:30)。これがレガシーというものですね。システムを根本から刷新しようという動きは当然の流れだったのかもしれません。

[^1]: プレーンテキスト、`<span>`要素など。

[^2]: table|thead|tfoot|caption|col|colgroup|tbody|tr|td|th|div|dl|dd|dt|ul|ol|li|pre|form|map|area|blockquote|address|math|style|p|h[1-6]|hr|fieldset|legend|section|article|aside|hgroup|header|footer|nav|figure|figcaption|details|menu|summary