自動整形のどこに問題点があるか
例えば、DWなどで作成したHTMLソースをHTMLエディタにそのまま貼り付けて記事を作成することがある(僕はないけど)。その場合、一度ビジュアルエディタに切り替えてまたすぐHTMLエディタに戻すと<p>と<br />が消去されている。つってもthe_content()で返っくるソースにはちゃんと<p>も<br />も挿入されてんだし、むしろHTMLエディタで編集中の場合にもっともよく使う<p>と<br />を入力しなくても済むんだから楽だなくらいに思ってました。
ところがちゃんと検証してみたら大きな欠陥があったんです。
まず、HTMLエディタに以下のソースを入れたとします。
<p>それには及びません。<br /> 私は罪な男です。</p>
そしてビジュアルエディタに切り替えると、
それには及びません。
私は罪な男です。
「ん??」
これはおかしい。HTMLエディタに戻してみると、
<p>それには及びません。</p> <p>私は罪な男です。</p>
なぜか2行とも<p>で括られてしまっている。
これは直さねばっつーわけで、その方法を記録しておく。
それと今回は<p>と<br />の除去も止めさせます。やっぱり始めてWordPressを使う人を混乱させるだけなんで。
editor.jsをハックする
wp-admin/js/editor.jsがエディタ切り替え時の自動整形を行なっているんで、これを改良するわけですが、コアファイルを直接弄るわけにはいかないので、このeditor.jsを停止させ、改良したコピーを読み込んでもらいます。コピーしたeditor.jsはテーマディレクトリに置きました。
wp_deregister_script( 'editor' ); wp_enqueue_script( 'editor', get_bloginfo( 'template_directory' ) . '/editor.js' );
これをfunctions.phpに書きます。
※たぶん他のハンドルも同じ要領でいけます。
http://codex.wordpress.org/Function_Reference/wp_enqueue_script#Default_scripts_included_with_WordPress
以下、改良後のソースです。(わかりやすく改行しています)
var switchEditors = { switchto:function(b) { var c = b.id, a = c.length, e = c.substr(0, a - 5), d = c.substr(a - 4); this.go(e, d); }, go:function(g, f) { g = g || "content"; f = f || "toggle"; var c = this, b = tinyMCE.get(g), a, d, e = tinymce.DOM; a = "wp-" + g + "-wrap"; d = e.get(g); if ("toggle" == f) { if (b&&!b.isHidden()) { f = "html"; } else { f = "tmce"; } } if ("tmce" == f || "tinymce" == f) { if (b&&!b.isHidden()) { return false; } if (typeof(QTags) != "undefined") { QTags.closeAllTags(g); } if (tinyMCEPreInit.mceInit[g] && tinyMCEPreInit.mceInit[g].wpautop) { d.value = c.wpautop(d.value); } if (b) { b.show(); } else { b = new tinymce.Editor(g, tinyMCEPreInit.mceInit[g]); b.render(); } e.removeClass(a, "html-active"); e.addClass(a, "tmce-active"); setUserSetting("editor", "tinymce"); } else { if ("html" == f) { if (b&&b.isHidden()) { return false; } if (b) { d.style.height = b.getContentAreaContainer().offsetHeight + 20 + "px"; b.hide(); } e.removeClass(a, "tmce-active"); e.addClass(a, "html-active"); setUserSetting("editor", "html"); } } return false; }, _wp_Nop:function(b) { var c, a; if (b.indexOf("<pre") != -1 || b.indexOf("<script") != -1) { b = b.replace(/<(pre|script)[^>]*>[\s\S]+?<\/\1>/g, function(d) { d = d.replace(/<br ?\/?>(\r\n|\n)?/g, "<wp_temp>"); return d.replace(/<\/?p( [^>]*)?>(\r\n|\n)?/g, "<wp_temp>"); }); } c = "blockquote|ul|ol|li|table|thead|tbody|tfoot|tr|th|td|div|h[1-6]|p|fieldset"; b = b.replace(new RegExp("\\s*</("+c+")>\\s*", "g"), "</$1>\n"); b = b.replace(new RegExp("\\s*<((?:"+c+")(?: [^>]*)?)>", "g"), "\n<$1>"); b = b.replace(/(<p [^>]+>.*?)<\/p>/g, "$1</p#>"); /*<p>は消したくないのでコメントアウト*/ //b = b.replace(/<div( [^>]*)?>\s*<p>/gi, "<div$1>\n\n"); //b = b.replace(/\s*<p>/gi, ""); //b = b.replace(/\s*<\/p>\s*/gi, "\n\n"); b = b.replace(/\n[\s\u00a0]+\n/g, "\n\n"); /*<br />も消したくないのでコメントアウト*/ //b = b.replace(/\s*<br ?\/?>\s*/gi, "\n"); /*<br />の後に改行を入れる*/ b = b.replace(/\s*<br ?\/?>\s*/gi, "<br />\n"); /*<iframe>を括る<p>と<br />は除去*/ b = b.replace(/<p>\s*<iframe( [^>]*)?>/gi, "<iframe$1>"); b = b.replace(/<\/iframe>\s*(<\/p>|<br \/>)/gi, "</iframe>"); b = b.replace(/\s*<div/g, "\n<div"); b = b.replace(/<\/div>\s*/g, "</div>\n"); /* この行は省略します。 */ /* この行は省略します。 */ a = "blockquote|ul|ol|li|table|thead|tbody|tfoot|tr|th|td|h[1-6]|pre|fieldset"; b = b.replace(new RegExp("\\s*<((?:"+a+")(?: [^>]*)?)\\s*>", "g"), "\n<$1>"); b = b.replace(new RegExp("\\s*</("+a+")>\\s*", "g"), "</$1>\n"); b = b.replace(/<li([^>]*)>/g, "\t<li$1>"); if (b.indexOf("<hr") != -1) { b = b.replace(/\s*<hr( [^>]*)?>\s*/g, "\n\n<hr$1>\n\n"); } if (b.indexOf("<object") != -1) { b = b.replace(/<object[\s\S]+?<\/object>/g, function(d) { return d.replace(/[\r\n]+/g, ""); }); } b = b.replace(/<\/p#>/g, "</p>\n"); b = b.replace(/\s*(<p [^>]+>[\s\S]*?<\/p>)/g, "\n$1"); b = b.replace(/^\s+/, ""); b = b.replace(/[\s\u00a0]+$/, ""); b = b.replace(/<wp_temp>/g, "\n"); return b; }, _wp_Autop:function(a) { var b = "table|thead|tfoot|tbody|tr|td|th|caption|col|colgroup|div|dl|dd|dt|ul|ol|li|pre|select|form|blockquote|address|math|p|h[1-6]|fieldset|legend|hr|noscript|menu|samp|header|footer|article|section|hgroup|nav|aside|details|summary"; if (a.indexOf("<object") != -1) { a = a.replace(/<object[\s\S]+?<\/object>/g, function(c){return c.replace(/[\r\n]+/g, "")}); } a = a.replace(/<[^<>]+>/g, function(c){return c.replace(/[\r\n]+/g, " ")}); if (a.indexOf("<pre") != -1||a.indexOf("<script") != -1) { a = a.replace(/<(pre|script)[^>]*>[\s\S]+?<\/\1>/g, function(c) { return c.replace(/(\r\n|\n)/g, "<wp_temp_br>"); }); } a = a + "\n\n"; a = a.replace(/<br \/>\s*<br \/>/gi, "\n\n"); a = a.replace(new RegExp("(<(?:"+b+")(?: [^>]*)?>)", "gi"), "\n$1"); a = a.replace(new RegExp("(</(?:"+b+")>)", "gi"), "$1\n\n"); a = a.replace(/<hr( [^>]*)?>/gi, "<hr$1>\n\n"); a = a.replace(/\r\n|\r/g, "\n"); a = a.replace(/\n\s*\n+/g, "\n\n"); a = a.replace(/([\s\S]+?)\n\n/g, "<p>$1</p>\n"); a = a.replace(/<p>\s*?<\/p>/gi, ""); a = a.replace(new RegExp("<p>\\s*(</?(?:"+b+")(?: [^>]*)?>)\\s*</p>", "gi"), "$1"); a = a.replace(/<p>(<li.+?)<\/p>/gi, "$1"); a = a.replace(/<p>\s*<blockquote([^>]*)>/gi, "<blockquote$1><p>"); a = a.replace(/<\/blockquote>\s*<\/p>/gi, "</p></blockquote>"); a = a.replace(new RegExp("<p>\\s*(</?(?:"+b+")(?: [^>]*)?>)", "gi"), "$1"); a = a.replace(new RegExp("(</?(?:"+b+")(?: [^>]*)?>)\\s*</p>", "gi"), "$1"); a = a.replace(/\s*\n/gi, "<br />\n"); /*↑で余計についた<br />を削除*/ a = a.replace(/<br ?\/?><br ?\/?>\n/gi, "<br />\n"); a = a.replace(new RegExp("(</?(?:"+b+")[^>]*>)\\s*<br />", "gi"), "$1"); a = a.replace(/<br \/>(\s*<\/?(?:p|li|div|dl|dd|dt|th|pre|td|ul|ol)>)/gi, "$1"); /* この行は省略します。 */ a = a.replace(/(<(?:div|th|td|form|fieldset|dd)[^>]*>)(.*?)<\/p>/g, function(e, d,f){if (f.match(/<p( [^>]*)?>/)){return e}return d+"<p>"+f+"</p>"}); a = a.replace(/<wp_temp_br>/g, "\n"); return a; }, pre_wpautop:function(b) { var a = this, d = {o:a, data:b, unfiltered:b}, c = typeof(jQuery) != "undefined"; if (c) { jQuery("body").trigger("beforePreWpautop", [d]); } d.data = a._wp_Nop(d.data); if (c) { jQuery("body").trigger("afterPreWpautop", [d]); } return d.data }, wpautop:function(b) { var a = this, d = {o:a, data:b, unfiltered:b}, c = typeof(jQuery) != "undefined"; if (c) { jQuery("body").trigger("beforeWpautop", [d]); } d.data = a._wp_Autop(d.data); if (c) { jQuery("body").trigger("afterWpautop", [d]); } return d.data; } };
_wp_Autopの方はHTML→ビジュアル切り替え時に。
_wp_Nopの方はビジュアル→HTML及び、保存時のスクリプトです。
まず、HTMLに切り替えた時に消されてしまう<p>と<br />を保護します。以下4文をコメントアウトします。(71~73、78行目)
//b = b.replace(/<div( [^>]*)?>\s*<p>/gi, "<div$1>\n\n"); //b = b.replace(/\s*<p>/gi, ""); //b = b.replace(/\s*<\/p>\s*/gi, "\n\n"); //b = b.replace(/\s*<br ?\/?>\s*/gi, "\n");
コードを見やすくするために<br />の後に改行が欲しいので以下を追記します。(81行目)
b = b.replace(/\s*<br ?\/?>\s*/gi, "<br />\n");
先のコードで<p>と<br />を救ってしまったがために、<iframe></iframe>を括った<p>や<br />が残ってしまうので、これを除去します。
b = b.replace(/<p>\s*<iframe( [^>]*)?>/gi, "<iframe$1>"); b = b.replace(/<\/iframe>\s*(<\/p>|<br \/>)/gi, "</iframe>");
次に、ビジュアルエディタに切り替えた時にすべての改行コードが<br />に置き換えられてしまうところをカバーしてあげます。(147行目)
a = a.replace(/<br ?\/?><br ?\/?>\n/gi, "<br />\n");
以上です。
「この行は省略します」が3箇所ありますが、これらにはcaptionタグに関するコードが書かれています。エディタが見事に置き換えてしまうので省略しています。
故に、コードをそのままコピペしても動作しません。
そしてこれはあくまで僕の場合なので参考程度にしてください。
※バージョン3.4.1で確認しています。
2012/08/05 加筆。