Участник:X-romix/check tags.js

Материал из Википедии — свободной энциклопедии
Перейти к навигации Перейти к поиску
//Скрипт проверяет правильность закрытия тегов.
//Автор: X-romix

if (mw.config.get('wgAction') == 'edit' || mw.config.get('wgAction') == 'submit')
$(function(){
	var wpSave = document.getElementById('wpSave');
	if (!wpSave) return;
	wpSave.addEventListener('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="' + mw.config.get('wgArticlePath').replace(/\$1/, 'Википедия:Как править статьи') +
		    '" title="(ссылка откроется в новом окне)" target=_blank>подробнее&nbsp;↗</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 (mw.config.get('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, "&amp;");
	  s=s.replace(/[<]/g, "&lt;");
	  s=s.replace(/[>]/g, "&gt;");
	  return s;
	}
}