Movable Type の「改行を変換」を自作する方法

以前に「MTの「改行を変換」をHTML5に対応させる」という記事を書いたんですが、これは「改行を変換」フォーマットで書かれたウェブページやブログ記事から余計なp要素・br要素を削除するというアプローチを取っていました。

この方法では、pre要素などの「改行はするけどp要素・br要素を追加して欲しくない」場合にはスマートに対処出来ませんでした。

例えば、Google Maps などのスクリプトをscript要素で本文内に記述した場合、大量にp要素・br要素が追加されてしまい、正常に動作しません。

そこで、視点を変えて、「改行を変換」フォーマットを使用せず、p要素・br要素を自動挿入する方法を紹介します。

※意図したとおり出力されない部分があったため修正版を追記しました。

MTEntryBodyテンプレートタグを例に説明します。見やすいようにモディファイア部分を改行しています。

<MTEntryBody
 convert_breaks="0"
 regex_replace="/[\t ]*(<style.+?\/style>|<script.+?\/script>|<pre.+?\/pre>|<iframe.+?\/iframe>|<object.+?\/object>|<video.+?\/video>|<audio.+?\/audio>|<canvas.+?\/canvas>|<math.+?\/math>|<svg.+?\/svg>|<select.+?\/select>|<datalist.+?\/datalist>|<textarea.+?\/textarea>|<output.+?\/output>|<progress.+?\/progress>|<meter.+?\/meter>|<!--.*?-->|<\?.*?\?>|[^\r\n]+)/sg","<p>$1</p>"
 regex_replace="/(<p>)?(<\/?(style|script|noscript|article|section|nav|aside|h[1-6]|hgroup|header|footer|address|p|hr|pre|blockquote|ol|ul|li|dl|dt|dd|figure|figcaption|div|map|table|caption|colgroup|col|tbody|thead|tfoot|tr|td|th|form|fieldset|legend|details|summary|command|menu|dialog|!--.*?--|\?.*?\?).*?>)(<\/p>)?/sg","$2"
 regex_replace="/(<br \/>)?<\/p>(\r\n|\r|\n)<p>/sg","<br />\n"
>

まずconvert_breaksモディファイアで「改行を変換」を無効化しています。

convert_breaks="0"

次に1つ目のregex_replaceグローバルモディファイアで、全ての行にp要素を追加します。

regex_replace="/[\t ]*(<style.+?\/style>|<script.+?\/script>|<pre.+?\/pre>|<iframe.+?\/iframe>|<object.+?\/object>|<video.+?\/video>|<audio.+?\/audio>|<canvas.+?\/canvas>|<math.+?\/math>|<svg.+?\/svg>|<select.+?\/select>|<datalist.+?\/datalist>|<textarea.+?\/textarea>|<output.+?\/output>|<progress.+?\/progress>|<meter.+?\/meter>|<!--.*?-->|<\?.*?\?>|[^\r\n]+)/sg","<p>$1</p>"

この時、pre要素などの「改行はするけどp要素・br要素を追加したくない要素」を <pre.+?\/pre> といった記述で除外しています。これにより、

Paragraph
 
Paragraph
Paragraph
 
<p>Paragraph</p>
 
<p>Paragraph<br />
Paragraph</p>
 
<pre>Preformatted text
 
Preformatted text
 
Preformatted text</pre>

といったコードが、

<p>Paragraph</p>
 
<p>Paragraph</p>
<p>Paragraph</p>
 
<p><p>Paragraph</p></p>
 
<p><p>Paragraph<br /></p>
<p>Paragraph</p></p>
 
<p><pre>Preformatted text
 
Preformatted text
 
Preformatted text</pre></p>

のようになります。

次に2つ目のregex_replaceグローバルモディファイアで、指定するタグ(style|script|noscript| ~ |dialog|!--.*?--|\?.*?\?)の前後に追加されたp要素を削除します。

regex_replace="/(<p>)?(<\/?(style|script|noscript|article|section|nav|aside|h[1-6]|hgroup|header|footer|address|p|hr|pre|blockquote|ol|ul|li|dl|dt|dd|figure|figcaption|div|map|table|caption|colgroup|col|tbody|thead|tfoot|tr|td|th|form|fieldset|legend|details|summary|command|menu|dialog|!--.*?--|\?.*?\?).*?>)(<\/p>)?/sg","$2"

先ほどのコードは、

<p>Paragraph</p>
 
<p>Paragraph</p>
<p>Paragraph</p>
 
<p>Paragraph</p>
 
<p>Paragraph<br /></p>
<p>Paragraph</p>
 
<pre>Preformatted text
 
Preformatted text
 
Preformatted text</pre>

のようになります。

そして、最後のregex_replaceグローバルモディファイアで、隣接しているp要素をbr要素を含む1つのp要素にします。

regex_replace="/(<br \/>)?<\/p>(\r\n|\r|\n)<p>/sg","<br />\n">

先ほどのコードが、

<p>Paragraph</p>
 
<p>Paragraph<br />
Paragraph</p>
 
<p>Paragraph</p>
 
<p>Paragraph<br />
Paragraph</p>
 
<pre>Preformatted text
 
Preformatted text
 
Preformatted text</pre>

のようになって完成です。

コードが長すぎるのでMTSetVarTemplateタグを使って、擬似テンプレートタグ化してMTGetVarタグで呼び出せるようにすれば、使いやすくなります。

<MTSetVarTemplate name="ConvertEntryBody"><MTEntryBody convert_breaks="0" regex_replace="/[\t ]*(<style.+?\/style>|<script.+?\/script>|<pre.+?\/pre>|<iframe.+?\/iframe>|<object.+?\/object>|<video.+?\/video>|<audio.+?\/audio>|<canvas.+?\/canvas>|<math.+?\/math>|<svg.+?\/svg>|<select.+?\/select>|<datalist.+?\/datalist>|<textarea.+?\/textarea>|<output.+?\/output>|<progress.+?\/progress>|<meter.+?\/meter>|<!--.*?-->|<\?.*?\?>|[^\r\n]+)/sg","<p>$1</p>" regex_replace="/(<p>)?(<\/?(style|script|noscript|article|section|nav|aside|h[1-6]|hgroup|header|footer|address|p|hr|pre|blockquote|ol|ul|li|dl|dt|dd|figure|figcaption|div|map|table|caption|colgroup|col|tbody|thead|tfoot|tr|td|th|form|fieldset|legend|details|summary|command|menu|dialog|!--.*?--|\?.*?\?).*?>)(<\/p>)?/sg","$2" regex_replace="/(<br \/>)?<\/p>(\r\n|\r|\n)<p>/sg","<br />\n"></MTSetVarTemplate>
 
<MTGetVar name="ConvertEntryBody">

これで、HTML Validator でエラーを起こすようなソースとはおさらばできます。ただし、regex_replaceグローバルモディファイアによる正規表現での置換を3度も行っているので、再構築のパフォーマンスは悪くなると思います。

本来なら、様々なマークアップを想定して「改行を変換」フォーマットのロジックを作って欲しいんですけどね。

修正版

regex_replaceグローバルモディファイアの順序とその内容を若干変更しました。これにより、素のテキスト・インラインレベル要素・ブロックレベル要素が混在する要素の開始タグ・終了タグの前後にあるp要素・br要素が削除されるようになりました。

<MTEntryBody
 convert_breaks="0"
 regex_replace="/[\t ]*(<style.+?\/style>|<script.+?\/script>|<pre.+?\/pre>|<iframe.+?\/iframe>|<object.+?\/object>|<video.+?\/video>|<audio.+?\/audio>|<canvas.+?\/canvas>|<math.+?\/math>|<svg.+?\/svg>|<select.+?\/select>|<datalist.+?\/datalist>|<textarea.+?\/textarea>|<output.+?\/output>|<progress.+?\/progress>|<meter.+?\/meter>|<!--.*?-->|<\?.*?\?>|[^\r\n]+)/sg","<p>$1</p>"
 regex_replace="/(<br \/>)?<\/p>(\r\n|\r|\n)<p>/sg","<br />\n"
 regex_replace="/(<p>|<br \/>(\r\n|\r|\n))?(<\/?(style|script|noscript|article|section|nav|aside|h[1-6]|hgroup|header|footer|address|p|hr|pre|blockquote|ol|ul|li|dl|dt|dd|figure|figcaption|div|map|table|caption|colgroup|col|tbody|thead|tfoot|tr|td|th|form|fieldset|legend|details|summary|command|menu|dialog|!--.*?--|\?.*?\?)\s?.*?>)(<\/p>|<br \/>)?/sg","$2$3"
>
 
<MTSetVarTemplate name="ConvertEntryBody"><MTEntryBody convert_breaks="0" regex_replace="/[\t ]*(<style.+?\/style>|<script.+?\/script>|<pre.+?\/pre>|<iframe.+?\/iframe>|<object.+?\/object>|<video.+?\/video>|<audio.+?\/audio>|<canvas.+?\/canvas>|<math.+?\/math>|<svg.+?\/svg>|<select.+?\/select>|<datalist.+?\/datalist>|<textarea.+?\/textarea>|<output.+?\/output>|<progress.+?\/progress>|<meter.+?\/meter>|<!--.*?-->|<\?.*?\?>|[^\r\n]+)/sg","<p>$1</p>" regex_replace="/(<br \/>)?<\/p>(\r\n|\r|\n)<p>/sg","<br />\n" regex_replace="/(<p>|<br \/>(\r\n|\r|\n))?(<\/?(style|script|noscript|article|section|nav|aside|h[1-6]|hgroup|header|footer|address|p|hr|pre|blockquote|ol|ul|li|dl|dt|dd|figure|figcaption|div|map|table|caption|colgroup|col|tbody|thead|tfoot|tr|td|th|form|fieldset|legend|details|summary|command|menu|dialog|!--.*?--|\?.*?\?)\s?.*?>)(<\/p>|<br \/>)?/sg","$2$3"></MTSetVarTemplate>
 
<MTGetVar name="ConvertEntryBody">