自動整形のどこに問題点があるか
例えば、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 加筆。


