User:Khanson/check tags.js
Note: After saving, you have to bypass your browser's cache to see the changes. Google Chrome, Firefox, Microsoft Edge and Safari: Hold down the ⇧ Shift key and click the Reload toolbar button. For details and instructions about other browsers, see Wikipedia:Bypass your cache.
//Скрипт проверяет правильность закрытия тегов.
//Автор: X-romix
if (wgAction == 'edit' || wgAction == 'submit')
$(function(){
var wpSave = document.getElementById('wpSave');
if (!wpSave) return;
addHandler(wpSave, 'click', XRomix_CheckTagsHandler);
});
function XRomix_CheckTagsHandler(e){
var xSelectionStart=0;
var xSelectionEnd=0;
var wpTextbox1 = document.editform.wpTextbox1;
var isCancel=false;
var text=document.editform.wpTextbox1.value;
var isCancel=!XRomix_CheckTags(text);
if(isCancel){
setSelectionRange(wpTextbox1, xSelectionStart, xSelectionEnd);
isCancel= !window.confirm("Имеются ошибки форматирования. Всё равно сохранить?");
}else{
setRedWindow(""); //Очищаем красное окно, если оно было
}
if (isCancel){ //отменить нажатие кнопки
e = e || window.event //из-за различий в IE и стандартных браузерах
if (e.preventDefault) e.preventDefault(); else e.returnValue = false //остановить действие, снова по-разному
return false //на всякий случай
}
//Выводит красное окно предупреждения
function setRedWindow(res){
var w = document.getElementById('XRomix_editpage_CheckTags');
if (res!=""){
var wpSummary = document.getElementById('wpSummary')
if (!wpSummary) return
var w = document.getElementById('XRomix_editpage_CheckTags');
if(!w){
w = document.createElement('span')
w.id = 'XRomix_editpage_CheckTags'
wpSummary.parentNode.insertBefore(w, wpSummary.nextSibling);
}
w.innerHTML = '<div style="padding:10px; margin:5px; background:#FF8080; border:1px solid red;">'+
'Имеются незакрытые (или неправильно закрытые) элементы HTML или вики-разметки. ' + res +' '+
' (<a href="' + wgArticlePath.replace(/\$1/, 'Википедия:Как править статьи') +
'" title="(ссылка откроется в новом окне)" target=_blank>подробнее ↗</a>)</div>';
}else{
if(w) w.innerHTML=''; //Если нет ошибки, то очищаем
}
}
//////////////////////////////////////
//генерирует строку из пробелов указанной длины
function generateSpaces(len){
var s1=" ";
while(s1.length<len){
s1+=s1;
}
return s1.substr(0,len);
}
//////////////////////////////////////
//Проверяет теги
function XRomix_CheckTags(text){
//////////////////////////////////////
//Заменяет текст между открывающим и закрывающим тегами на пробелы
function replaceTags(tag1, tag2){
while(1==1){
var p=txt.search(tag1);
if(p==-1) break;
var p1=txt.indexOf(tag2,p);
if(p1==-1) { //ошибка, закрывающий тег не найден
setRedWindow('Не закрыт элемент '+htmlEncode(tag1)+'.');
xSelectionStart=p;
xSelectionEnd=p+tag2.length+1;
return false;
}
var w1=generateSpaces(p1-p+tag2.length);
var left=txt.substr(0, p);
var right=txt.substr(p1+tag2.length);
txt=left+w1+right;
}
return true;
}
//////////////////////////////////////
//Заменяет тег на пробелы
function replace1Tag(tag1){
var p=txt.indexOf(tag1);
if(p==-1) return;
var w1=generateSpaces(tag1.length);
var left=txt.substr(0, p);
var right=txt.substr(p+tag1.length);
txt=left+w1+right;
}
var ok=0;
if (document.URL.match(/\.js&action=(edit|submit)/)){
return true; //JavaScript не проверяем
}else if (document.URL.match(/\.css&action=(edit|submit)/)){
return true; //CSS не проверяем
}else if (wgTitle.match (/(Шаблон|Template)\:/)){
return true; //Шаблоны не проверяем
}else if ('code'.replace(/d/g, 'r') != 'core'){ //Проверяем, поддерживает ли браузер регулярные выражения (RegExp)
return true;
}
var wpTextbox1 = document.editform.wpTextbox1;
if(!wpTextbox1) return true;
var txt = wpTextbox1.value;
var ok=replaceTags(/<nowiki>/, "</nowiki>");
if(!ok) return false;
var ok=replaceTags(/<\!\-\-/, "-->");
if(!ok) return false;
var ok=replaceTags(/<math>/, "</math>");
if(!ok) return false;
var ok=replaceTags(/<source[\s>]/, "</source>");
if(!ok) return false;
var ok=replaceTags(/<pre[\s>]/, "</pre>");
if(!ok) return false;
var arrPos = new Array();
var arrTags= new Array();
var arr = txt.match(/(<[\/]?[A-Za-z][^>]*>|\[\[|\]\]|\{\{|\}\})/g);
if(arr==null) return true; //Нет тегов для проверки - возврат
if(arr){
for(var i=0; i<arr.length; i++){
var w=arr[i];
var pos=txt.indexOf(w);
//Запоминаем позицию и текст тега
arrPos.push(pos);
arrTags.push(w);
//Заменяем тег на пробелы
replace1Tag(w);
}
}
var arrStackTags = new Array();
var arrStackPos = new Array();
for(var i=0; i<arrTags.length; i++){
var TagName=arrTags[i];
var TagPos=arrPos[i];
if (TagName.search(/(\/[\s]*>|<hr>|<br>)/) >=0 ){ // <тег/>
continue; //пропускаем такие теги, поскольку они не требуют закрытия
}else if(TagName.search(/<[a-zA-Z][^>]*>/) >=0){
//открывающий <тег ...>
//помещаем тег и его позицию в стек
arrStackTags.push(TagName);
arrStackPos.push(TagPos);
}else if(TagName.search(/<\/[^>]+>/) >=0){
//Закрывающий </тег>
var TagNameN=TagName.replace(/[<>\/]/g, "");
TagNameN=TagNameN.toLowerCase(); //Нормализованное имя тега - без угловых скобок и /
if (arrStackTags.length==0){
setRedWindow('Неожиданный закрывающий тег '+htmlEncode(TagName)+'.')
xSelectionStart=TagPos;
xSelectionEnd=TagPos+TagName.length;
return false;
}else{
var TagName2=arrStackTags.pop();
var TagPos2=arrStackPos.pop();
var TagName2N=TagName2.replace(/[\s][^>]*/, ""); //убираем все после первого пробела
TagName2N=TagName2N.replace(/[<>\/]/g, "");//убираем </>
TagName2N=TagName2N.toLowerCase();
if(TagNameN!=TagName2N){
setRedWindow('Неожиданный закрывающий тег '+htmlEncode(TagName)+' после открывающего тега '+htmlEncode(TagName2)+'.');
xSelectionStart=TagPos;
xSelectionEnd=TagPos+TagName.length;
return false;
}
}
}else if(TagName=="[["){
arrStackTags.push(TagName);
arrStackPos.push(TagPos);
}else if(TagName=="{{"){
arrStackTags.push(TagName);
arrStackPos.push(TagPos);
}else if(TagName=="]]"){
if (arrStackTags.length==0){
setRedWindow('Неожиданный закрывающий элемент '+htmlEncode(TagName)+'.');
xSelectionStart=TagPos;
xSelectionEnd=TagPos+TagName.length;
return false;
}else{
var TagName2=arrStackTags.pop();
var TagPos2=arrStackPos.pop();
if(TagName2!="[["){
setRedWindow('Неожиданный закрывающий тег '+htmlEncode(TagName)+' после открывающего тега '+htmlEncode(TagName2)+'.');
xSelectionStart=TagPos;
xSelectionEnd=TagPos+TagName.length;
return false;
}
}
}else if(TagName=="}}"){
if (arrStackTags.length==0){
setRedWindow('Неожиданный закрывающий элемент '+htmlEncode(TagName)+'.');
xSelectionStart=TagPos;
xSelectionEnd=TagPos+TagName.length;
return false;
}else{
var TagName2=arrStackTags.pop();
var TagPos2=arrStackPos.pop();
if(TagName2!="{{"){
setRedWindow('Неожиданный закрывающий тег '+htmlEncode(TagName)+' после открывающего тега '+htmlEncode(TagName2)+'.');
xSelectionStart=TagPos;
xSelectionEnd=TagPos+TagName.length;
return false;
}
}
}
}//for
if(arrStackTags.length>0){
var TagName=arrStackTags.pop()
var TagPos=arrStackPos.pop();
setRedWindow('Имеется незакрытый элемент '+htmlEncode(TagName)+'.');
xSelectionStart=TagPos;
xSelectionEnd=TagPos+TagName.length;
return false;
}//if
return true; //нет ошибок
}//function
//подсчитывает концы строк в фрагменте текста
function countCrlf(str){
str=""+str;
var arr=str.match(/\n/g);
//Теперь массив содержит концы строк - возвращаем его длину
if (arr) return arr.length;
return 0;
}
//Извлекает первую строку (до \n или \r) из строки
function get1string(text){
var p=text.indexOf("\n");
var p1=text.indexOf("\r");
if(p1!=-1 && p!=-1){
if(p>p1) p=p1;
}
if(p!=-1){
text=text.substr(0, p);
}
return text;
}
//Браузеро-независимый setSelectionRange - изменяет начало и конец
//выделенного фрагмента в поле ввода input
function setSelectionRange(input, start, end){
if(!input) return;
if(!input.value) return;
var text=input.value.substring(start, end);
text=get1string(text);
if (input.setSelectionRange) {//Mozilla/Opera
//Попытаемся спозиционировать курсор на нужный текст
if (self.find) {//Mozilla, в Опере не работает
input.focus();
input.setSelectionRange(start, start);
var caseSensitive = false
var backwards = false
var wrapAround = false
self.find(text, caseSensitive, backwards, wrapAround);
}
//Выделим диапазон
input.focus();
input.setSelectionRange(start, end);
}else if (document.selection && document.selection.createRange) { //Internet Explorer
//IE проглючивает с началом и концом выделенного фрагмента - вносим исправление
var cnt1=countCrlf(input.value.substring(0, start));
var cnt2=countCrlf(input.value.substring(start, end));
var range = input.createTextRange();
if(range.findText){
input.focus();
range.collapse(true);
range.moveStart("character", start - cnt1);
range.moveEnd("character", start - cnt1);
range.select();
range.findText(text);
range.collapse(true);
}
var range = input.createTextRange();
//Выделяем диапазон в IE
input.focus();
range.collapse(true);
range.moveStart("character", start - cnt1);
range.moveEnd("character", end - start - cnt2);
range.select();
}
}
//Кодирование спецсимволов HTML
function htmlEncode(s){
s=s.replace(/[\&]/g, "&");
s=s.replace(/[<]/g, "<");
s=s.replace(/[>]/g, ">");
return s;
}
}