Здравствуйте!
При написании своего WYSIWYG редактора возникла проблема копирования текста из Ворда. Собственно проблем три:
- Ворд вставляет много мусорного html кода, который необходимо чистить
- Для представления списков Ворд почему-то использует параграфы вместо тегов UL и LI
- Собственно как определить, что вставленный текст является вставленным из Ворда.
В общем, для решения этих проблем, был написан jquery-плагин, полный исходный код которого доступен в конце статьи. Пример использования:
$(‘#editor’). msword_html_filter();
Плагин вешается на событие keyup и проверяет, является ли исходный код внутри редактора вставленным из Ворда, если да, то запускается функция очистки. В результирующем html прибивается все что только можно – неразрывные пробелы, атрибуты style и align, теги span, все Mso-классы, пустые параграфы.
Детали реализации под катом.
Большинство используемых регулярок были подсмотрены у TinyMCE.
Как определить, есть ли в строке html-код вставленный из Ворда:
if (/class="?Mso|style="[^"]*bmso-|style='[^'']*bmso-|w:WordDocument/i.test( content )) {
...
}
Функция чистки кода (в функцию передается jquery объект редактора):
function word_filter(editor){
var content = editor.html();
// Word comments like conditional comments etc
content = content.replace(/<!--[sS]+?-->/gi, '');
// Remove comments, scripts (e.g., msoShowComment), XML tag, VML content,
// MS Office namespaced tags, and a few other tags
content = content.replace(/<(!|script[^>]*>.*?</script(?=[>s])|/?(?xml(:w+)?|img|meta|link|style|w:w+)(?=[s/>]))[^>]*>/gi, '');
// Convert <s> into <strike> for line-though
content = content.replace(/<(/?)s>/gi, "<$1strike>");
// Replace nbsp entites to char since it's easier to handle
//content = content.replace(/ /gi, "u00a0");
content = content.replace(/ /gi, ' ');
// Convert <span style="mso-spacerun:yes">___</span> to string of alternating
// breaking/non-breaking spaces of same length
content = content.replace(/<spans+styles*=s*"s*mso-spaceruns*:s*yess*;?s*"s*>([su00a0]*)</span>/gi, function(str, spaces) {
return (spaces.length > 0) ? spaces.replace(/./, " ").slice(Math.floor(spaces.length/2)).split("").join("u00a0") : '';
});
editor.html(content);
// Parse out list indent level for lists
$('p', editor).each(function(){
var str = $(this).attr('style');
var matches = /mso-list:w+ w+([0-9]+)/.exec(str);
if (matches) {
$(this).data('_listLevel', parseInt(matches[1], 10));
}
});
// Parse Lists
var last_level=0;
var pnt = null;
$('p', editor).each(function(){
var cur_level = $(this).data('_listLevel');
if(cur_level != undefined){
var txt = $(this).text();
var list_tag = '<ul></ul>';
if (/^s*w+./.test(txt)) {
var matches = /([0-9])./.exec(txt);
if (matches) {
var start = parseInt(matches[1], 10);
list_tag = start>1 ? '<ol start="' + start + '"></ol>' : '<ol></ol>';
}else{
list_tag = '<ol></ol>';
}
}
if(cur_level>last_level){
if(last_level==0){
$(this).before(list_tag);
pnt = $(this).prev();
}else{
pnt = $(list_tag).appendTo(pnt);
}
}
if(cur_level<last_level){
for(var i=0; i<last_level-cur_level; i++){
pnt = pnt.parent();
}
}
$('span:first', this).remove();
pnt.append('<li>' + $(this).html() + '</li>')
$(this).remove();
last_level = cur_level;
}else{
last_level = 0;
}
})
$('[style]', editor).removeAttr('style');
$('[align]', editor).removeAttr('align');
$('span', editor).replaceWith(function() {return $(this).contents();});
$('span:empty', editor).remove();
$("[class^='Mso']", editor).removeAttr('class');
$('p:empty', editor).remove();
}
Полный исходный текст плагина под спойлером, сохранять в файл jquery.msword_html_filter.js
(function($) {
$.fn.msword_html_filter = function(options) {
var settings = $.extend( {}, options);
function word_filter(editor){
var content = editor.html();
// Word comments like conditional comments etc
content = content.replace(/<!--[sS]+?-->/gi, '');
// Remove comments, scripts (e.g., msoShowComment), XML tag, VML content,
// MS Office namespaced tags, and a few other tags
content = content.replace(/<(!|script[^>]*>.*?</script(?=[>s])|/?(?xml(:w+)?|img|meta|link|style|w:w+)(?=[s/>]))[^>]*>/gi, '');
// Convert <s> into <strike> for line-though
content = content.replace(/<(/?)s>/gi, "<$1strike>");
// Replace nbsp entites to char since it's easier to handle
//content = content.replace(/ /gi, "u00a0");
content = content.replace(/ /gi, ' ');
// Convert <span style="mso-spacerun:yes">___</span> to string of alternating
// breaking/non-breaking spaces of same length
content = content.replace(/<spans+styles*=s*"s*mso-spaceruns*:s*yess*;?s*"s*>([su00a0]*)</span>/gi, function(str, spaces) {
return (spaces.length > 0) ? spaces.replace(/./, " ").slice(Math.floor(spaces.length/2)).split("").join("u00a0") : '';
});
editor.html(content);
// Parse out list indent level for lists
$('p', editor).each(function(){
var str = $(this).attr('style');
var matches = /mso-list:w+ w+([0-9]+)/.exec(str);
if (matches) {
$(this).data('_listLevel', parseInt(matches[1], 10));
}
});
// Parse Lists
var last_level=0;
var pnt = null;
$('p', editor).each(function(){
var cur_level = $(this).data('_listLevel');
if(cur_level != undefined){
var txt = $(this).text();
var list_tag = '<ul></ul>';
if (/^s*w+./.test(txt)) {
var matches = /([0-9])./.exec(txt);
if (matches) {
var start = parseInt(matches[1], 10);
list_tag = start>1 ? '<ol start="' + start + '"></ol>' : '<ol></ol>';
}else{
list_tag = '<ol></ol>';
}
}
if(cur_level>last_level){
if(last_level==0){
$(this).before(list_tag);
pnt = $(this).prev();
}else{
pnt = $(list_tag).appendTo(pnt);
}
}
if(cur_level<last_level){
for(var i=0; i<last_level-cur_level; i++){
pnt = pnt.parent();
}
}
$('span:first', this).remove();
pnt.append('<li>' + $(this).html() + '</li>')
$(this).remove();
last_level = cur_level;
}else{
last_level = 0;
}
})
$('[style]', editor).removeAttr('style');
$('[align]', editor).removeAttr('align');
$('span', editor).replaceWith(function() {return $(this).contents();});
$('span:empty', editor).remove();
$("[class^='Mso']", editor).removeAttr('class');
$('p:empty', editor).remove();
}
return this.each(function() {
$(this).on('keyup', function(){
var content = $(this).html();
if (/class="?Mso|style="[^"]*bmso-|style='[^'']*bmso-|w:WordDocument/i.test( content )) {
word_filter( $(this) );
}
});
});
};
})( jQuery )
Работоспособность проверялась только в последнем Фаерфоксе.
Автор: PyroRed