MediaWiki:Gadget-wikifier.js: различия между версиями

Материал из in.wiki
Перейти к навигации Перейти к поиску
(traditio.wiki -> in.wiki)
м (обновление)
 
(не показано 15 промежуточных версий этого же участника)
Строка 1: Строка 1:
 
/*
 
/*
  * Викификатор. Подключается к панели инструментов «Традиции».
+
  * Викификатор. Работает в браузере (когда подключат) и на сервере.
 
  *
 
  *
  * '''ВНИМАНИЕ! Внося изменения в код, не забывайте обновлять справку на странице [[Традиция:Викификатор]]'''
+
  * '''ВНИМАНИЕ! Внося изменения в код, не забывайте обновлять справку на странице [[Проект:Викификатор]]'''
 
  */
 
  */
var wmVersion = '2020-06-28';
+
var wmVersion = '2023-09-08';
 
var wmCantWork = 'Викификатор не может работать в вашем браузере\n\nWikifier cannot work in your browser';
 
var wmCantWork = 'Викификатор не может работать в вашем браузере\n\nWikifier cannot work in your browser';
 
var wmTalkPage = 'Викификатор не обрабатывает страницы обсуждения целиком.\n\nВыделите своё сообщение — обработано будет только оно';
 
var wmTalkPage = 'Викификатор не обрабатывает страницы обсуждения целиком.\n\nВыделите своё сообщение — обработано будет только оно';
Строка 24: Строка 24:
 
      
 
      
 
     // Собственно, викификация:
 
     // Собственно, викификация:
 +
    /*
 +
    Следует ограничиться использованием функций hide(), hideTags(), restore(), r(), collect_link(), resolve_links() и internalise_link().
 +
    Они будут также распознаны и выполнены в том же порядке серверным викификатором
 +
    Module:Wikifier.
 +
   
 +
    Вызовы функций re(), collect_link() и internalise_links() будут выполнены только серверным викификатором,
 +
    а в JavaScript они не будут иметь эффекта.
 +
    */
 +
 
var hidden = [];
 
var hidden = [];
 +
var external_links = new Set ();
 +
var resolved = {};
  
 
// Скрытие преформатированных тегов и прочих тегов, не подлежащих викификации:
 
// Скрытие преформатированных тегов и прочих тегов, не подлежащих викификации:
s = hideTags (s, hidden, 'nowiki', 'templatedata',
+
s = hideTags (s, 'nowiki', 'templatedata',
 
'pre', 'source', 'syntaxhighlight', 'code', 'kbd', 'tt', 'gallery',
 
'pre', 'source', 'syntaxhighlight', 'code', 'kbd', 'tt', 'gallery',
 
'graph', 'svgcode', 'graphviz', 'mscgen', 'score', 'math', 'hiero', 'timeline',
 
'graph', 'svgcode', 'graphviz', 'mscgen', 'score', 'math', 'hiero', 'timeline',
Строка 35: Строка 46:
 
s = r (s, /(IS[BS]N):?\s*([\s\d‒−—-]{8,17}[X\d])/ig, '{{$1|$2}}'); // -- ISBN/ISSN теперь в шаблон.
 
s = r (s, /(IS[BS]N):?\s*([\s\d‒−—-]{8,17}[X\d])/ig, '{{$1|$2}}'); // -- ISBN/ISSN теперь в шаблон.
  
s = hide (s, /\{\{[\s\S]+?}}/g, hidden);// — шаблоны.
+
s = hide (s, /\{\{[\s\S]+?}}/g);// — шаблоны.
s = hide (s, /^ .*/mg, hidden); // — преформатированный текст.
+
s = hide (s, /^ .*/mg); // — преформатированный текст.
  
s = r (s, /<\s*a\s+href\s*=\s*(["'])\s*https?:\/\/in(formatorium)?\.wiki(?:\/wiki)?\/(\S+?)\s*\1\s*>(.+?)<\s*\/a\s*>/gi, wikifyInternalLinks);
+
s = r (s, /<\s*a\s+href\s*=\s*(["'])\s*https?:\/\/in\.wiki?\/(\S+?)\s*\1\s*>(.+?)<\s*\/a\s*>/gi, wikifyInternalLinks);
 
// -- внутренние <a> → [[]);
 
// -- внутренние <a> → [[]);
s = r (s, /<\s*a\s+href\s*=\s*(["'])(\S+?)\1\s*>(.+?)<\s*\/a\s*>/gi, '[$2 $3]'); // -- <a> → [);
+
s = r (s, /<\s*a\s+href\s*=\s*(["'])(\S+?)\1\s*>(.+?)<\s*\/a\s*>/gi, '[$2 $3]'); // <a> → [].
 +
s = r (s, /\[\s*(https?:\/\/[^\]\s]+)\s*([^\]]*)\]/gi, collect_link); // — сбор внешних ссылок для последующей замены внутренними.
 +
s = resolve_links (s); // — массовая интернализация ссылок. Выполняется только в Lua.
 +
s = r (s, /\[\s*(https?:\/\/[^\]\s]+)\s*([^\]]*)\]/gi, internalise_link); // — замена внешних ссылок на загруженные документы.
 
 
 
 
s = hide (s, /(?:https?|ftp|news|nntp|telnet|irc|gopher|magnet):\/\/[^\s\[\]<>"]+ ?/gi, hidden); // — гиперссылки.
+
s = hide (s, /(?:https?|ftp|news|nntp|telnet|irc|gopher|magnet):\/\/[^\s\[\]<>"]+ ?/gi); // — гиперссылки.
s = hide (s, /^#(?:redirect|перенапр(авление)?)/i, hidden); // — перенаправления.
+
s = hide (s, /^#(?:redirect|перенапр(авление)?)/i); // — перенаправления.
  
 
s = r (s, / +(\n|\r)/g, '$1'); // -- пробелы в конце строки.
 
s = r (s, / +(\n|\r)/g, '$1'); // -- пробелы в конце строки.
Строка 71: Строка 85:
 
s = r (s, /\[\[ *([a-zA-Zа-яёА-ЯЁ\u00A0-\u00FF %!\"$&'()*,\-.\/0-9:;=?\\@\^_`’~]+) *\| *([^|[\]]+) *\]\]([a-zа-яё]+)/g, '[[$1|$2$3]]'); // -- ".
 
s = r (s, /\[\[ *([a-zA-Zа-яёА-ЯЁ\u00A0-\u00FF %!\"$&'()*,\-.\/0-9:;=?\\@\^_`’~]+) *\| *([^|[\]]+) *\]\]([a-zа-яё]+)/g, '[[$1|$2$3]]'); // -- ".
 
   
 
   
s = hide (s, /\[\[[^\]|]+/g, hidden); // -- скрытие викиссылок.
+
s = hide (s, /\[\[[^\]|]+/g); // -- скрытие викиссылок.
  
 
// Все теги в <> надо обработать здесь:
 
// Все теги в <> надо обработать здесь:
Строка 122: Строка 136:
 
s = r (s, /((?:<ref\sname=".+?">[\s\S]+?<\/ref>)+)\s*$/, '<references>$1</references>\n'); // -- раздел сносок.
 
s = r (s, /((?:<ref\sname=".+?">[\s\S]+?<\/ref>)+)\s*$/, '<references>$1</references>\n'); // -- раздел сносок.
  
s = hide(s, /<[a-z][^>]*?>/gi, hidden); // -- hide all HTML tags.
+
s = hide(s, /<[a-z][^>]*?>/gi); // -- hide all HTML tags.
  
s = hide(s, /^(?:{\||\|-).*/mg, hidden); // -- таблицы и ряды.
+
s = hide(s, /^(?:{\||\|-).*/mg); // -- таблицы и ряды.
s = hide(s, /(?:^\||^!|!!|\|\|) *[a-z]+=[^|]+\|(?!\|)/mgi, hidden); // -- стили ячеек.
+
s = hide(s, /(?:^\||^!|!!|\|\|) *[a-z]+=[^|]+\|(?!\|)/mgi); // -- стили ячеек.
s = hide(s, /\| +/g, hidden); // -- форматированные ячейки.
+
s = hide(s, /\| +/g); // -- форматированные ячейки.
  
 
   // Двойные пробелы:
 
   // Двойные пробелы:
Строка 240: Строка 254:
  
 
// Восстановление скрытого:
 
// Восстановление скрытого:
s = restore (s, hidden);
+
s = restore (s);
 
      
 
      
 
return s;   
 
return s;   
Строка 251: Строка 265:
 
return string.replace (pattern, replacement);
 
return string.replace (pattern, replacement);
 
}
 
}
+
 
 
// Скрытие фрагментов путём окружения \x01 и \x02:
 
// Скрытие фрагментов путём окружения \x01 и \x02:
function hide (txt, re, hidden) {
+
function hide (txt, re) {
 
console.log ('Hiding ' + re);
 
console.log ('Hiding ' + re);
 
    return txt.replace (re, function (s) {return '\x01' + hidden.push (s) + '\x02'});
 
    return txt.replace (re, function (s) {return '\x01' + hidden.push (s) + '\x02'});
Строка 262: Строка 276:
 
var args = Array.prototype.slice.call (arguments);
 
var args = Array.prototype.slice.call (arguments);
 
var txt = args.shift ()
 
var txt = args.shift ()
var hidden = args.shift ()
 
 
var tags = args.join ('|');
 
var tags = args.join ('|');
 
    return hide (txt, RegExp ('<(' + tags + ')( [^>]+)?>[\\s\\S]+?<\\/\\1>', 'gi'));
 
    return hide (txt, RegExp ('<(' + tags + ')( [^>]+)?>[\\s\\S]+?<\\/\\1>', 'gi'));
} // -- function hideTags (txt, tags, hidden)
+
} // -- function hideTags (txt, tags)
 
 
 
// Восстановление скрытого:
 
// Восстановление скрытого:
function restore (/* String */ s, /* Array */ hidden) {
+
function restore (/* String */ s) {
 
if ('0'.replace('0', '$$') === '$') { // -- $ в регэксах, как всегда, IE особенный.
 
if ('0'.replace('0', '$$') === '$') { // -- $ в регэксах, как всегда, IE особенный.
 
for (i = 0; i < hidden.length; i++) {
 
for (i = 0; i < hidden.length; i++) {
Строка 279: Строка 292:
 
}
 
}
 
return s;
 
return s;
} // -- function restore (/* String */ s, /* Array */ hidden)
+
} // -- function restore (/* String */ s)
 
 
 +
// Эти функции ничего не делают в браузере, но реализованы в серверном викификаторе:
 +
function re (/* String */ s, /* String */ re) {
 +
return s;
 +
}
 +
 +
function collect_link (_, url, alias) {
 +
console.log ('_ = ' + _ + ', url = ' + url + ', alias = ' + alias);
 +
external_links.add (url);
 +
return '[' + url + ' ' + alias + ']';
 +
}
 +
function resolve_links (/* String */ s) {
 +
var text = s;
 +
if ( external_links.size > 0 ) {
 +
var property = 'URL источника';
 +
var list = Array.from (external_links).join ('||');
 +
var api = new mw.Api ();
 +
api.get ({
 +
action: 'askargs',
 +
conditions: property + '::' + list,
 +
printouts: property,
 +
format: 'json',
 +
parameters: 'limit=' + external_links.size,
 +
api_version: 3
 +
}).done (function (result) {
 +
if ( !result.query || !result.query.results ) return false;
 +
result.query.results.forEach (function (row) {
 +
Object.keys (row).forEach (function (page) {
 +
row [page].printouts [property].forEach (function (url) {
 +
resolved [url] = page;
 +
});
 +
})
 +
});
 +
Object.keys (resolved).forEach (function (url) {
 +
if ( resolved [url] ) {
 +
text = r (text, /\[\s*(https?:\/\/[^\]\s]+)\s*([^\]]*)\]/gi, '[[' + resolved [url] + '|\\2]]');
 +
}
 +
});
 +
}).fail (function (code, result) {
 +
return false;
 +
});
 +
}
 +
return text;
 +
}
 +
function internalise_link (_, url, alias) {
 +
return '[' + url + ' ' + alias + ']';
 +
}
 +
 
// Превращение ссылок HTML на «Традицию» в викиссылки:
 
// Превращение ссылок HTML на «Традицию» в викиссылки:
 
function wikifyInternalLinks (_, __, page, alias) {
 
function wikifyInternalLinks (_, __, page, alias) {
Строка 300: Строка 360:
 
mw.edit_gadget_extensions.push (function () {
 
mw.edit_gadget_extensions.push (function () {
 
    mw.tools_above [0].splice (1, 0
 
    mw.tools_above [0].splice (1, 0
  , {w: wikifyText, b: '<img src="/images/1/1f/Etool_wikify.png" width="20" height="16" alt="W"/>', t: 'Викификация', all: true}
+
  , {w: wikifyText, b: '<img src="/files/1/1f/Etool_wikify.png" width="20" height="16" alt="W"/>', t: 'Викификация', all: true}
      , {url: mw.util.getUrl ('Справка:Викификатор'), b: '<img src="/images/d/d5/Etool_help.png" height="16" width="16" alt="?">', t: '(справка о викификаторе)'}
+
      , {url: mw.util.getUrl ('Справка:Викификатор'), b: '<img src="/files/d/d5/Etool_help.png" height="16" width="16" alt="?">', t: '(справка о викификаторе)'}
 
    );
 
    );
 
});
 
});
 
}
 
}

Текущая версия от 11:43, 8 сентября 2023

/*
 * Викификатор. Работает в браузере (когда подключат) и на сервере.
 *
 * '''ВНИМАНИЕ! Внося изменения в код, не забывайте обновлять справку на странице [[Проект:Викификатор]]'''
 */
var wmVersion = '2023-09-08';
var wmCantWork = 'Викификатор не может работать в вашем браузере\n\nWikifier cannot work in your browser';
var wmTalkPage = 'Викификатор не обрабатывает страницы обсуждения целиком.\n\nВыделите своё сообщение — обработано будет только оно';

var isDiscussion = mw && (mw.config.get ('wgNamespaceNumber') % 2 === 1 || mw.config.get ('wgNamespaceNumber') === 4);

// Функция викифицирует переданный текст и возвращает викифицированный:
function wikifyText (s) {

	if (isDiscussion) {
		// -- это обсуждение.
		// Несколько дат, вероятно, чужие подписи::
		var sigs = s.match (/\d\d:\d\d, \d\d? \S{3,8} 20\d\d \(UTC\)/g);
		if (sigs && sigs.length > 1) {
			window.alert (wmTalkPage);
			return s;
		}
	}
    
    // Собственно, викификация:
    /*
    Следует ограничиться использованием функций hide(), hideTags(), restore(), r(), collect_link(), resolve_links() и internalise_link().
    Они будут также распознаны и выполнены в том же порядке серверным викификатором
    Module:Wikifier.
    
    Вызовы функций re(), collect_link() и internalise_links() будут выполнены только серверным викификатором,
    а в JavaScript они не будут иметь эффекта.
    */
	
	var hidden = [];
	var external_links = new Set ();
	var resolved = {};

	// Скрытие преформатированных тегов и прочих тегов, не подлежащих викификации:
	s = hideTags (s, 'nowiki', 'templatedata',
		'pre', 'source', 'syntaxhighlight', 'code', 'kbd', 'tt', 'gallery',
		'graph', 'svgcode', 'graphviz', 'mscgen', 'score', 'math', 'hiero', 'timeline',
		'chem', 'mapframe', 'maplink'
	);

	s = r (s, /(IS[BS]N):?\s*([\s\d‒−—-]{8,17}[X\d])/ig, '{{$1|$2}}'); // -- ISBN/ISSN теперь в шаблон.

	s = hide (s, /\{\{[\s\S]+?}}/g);// — шаблоны.
	s = hide (s, /^ .*/mg);			// — преформатированный текст.

	s = r (s, /<\s*a\s+href\s*=\s*(["'])\s*https?:\/\/in\.wiki?\/(\S+?)\s*\1\s*>(.+?)<\s*\/a\s*>/gi, wikifyInternalLinks);
	// -- внутренние <a> → [[]);
	s = r (s, /<\s*a\s+href\s*=\s*(["'])(\S+?)\1\s*>(.+?)<\s*\/a\s*>/gi, '[$2 $3]');	// — <a> → [].
	s = r (s, /\[\s*(https?:\/\/[^\]\s]+)\s*([^\]]*)\]/gi, collect_link);				// — сбор внешних ссылок для последующей замены внутренними.
	s = resolve_links (s);																// — массовая интернализация ссылок. Выполняется только в Lua.
	s = r (s, /\[\s*(https?:\/\/[^\]\s]+)\s*([^\]]*)\]/gi, internalise_link);			// — замена внешних ссылок на загруженные документы.	
		 
	s = hide (s, /(?:https?|ftp|news|nntp|telnet|irc|gopher|magnet):\/\/[^\s\[\]<>"]+ ?/gi);	// — гиперссылки.
	s = hide (s, /^#(?:redirect|перенапр(авление)?)/i);								// — перенаправления.

	s = r (s, / +(\n|\r)/g, '$1'); // -- пробелы в конце строки.

	// Окружить текст переводами строки, чтобы гарантировать правильную работу
	//	регулярных выражений в первой и последней строке:
	// s = r (s, /^/, '\n');
	// s = r (s, /$/, '\n');

	// Русификация пространств имён:
	s = r (s, /(\[\[:?)(category|категория):( *)/ig, '$1Категория:');
	s = r (s, /(\[\[:?)(image|изображение|file):( *)/ig, '$1Файл:');
	  
	// Оформление дат:
	s = r (s, /(\(|\s)(\[\[[12]?\d{3}\]\])[\u00A0 ]?(-{1,3}|–|—) ?(\[\[[12]?\d{3}\]\])(\W)/g, '$1$2—$4$5');
	s = r (s, /(\[\[[12]?\d{3}\]\]) ?(гг?\.)/g, '$1\xa0$2');
	s = r (s, /(\(|\s)(\[\[[IVX]{1,5}\]\])[\u00A0 ]?(-{1,3}|–|—) ?(\[\[[IVX]{1,5}\]\])(\W)/g, '$1$2—$4$5');
	s = r (s, /(\[\[[IVX]{1,5}\]\]) ?(вв?\.)/g, '$1\xa0$2');
	s = r (s, /\[\[(\d+)\]\][\u00A0 ]год/g, '[[$1\xa0год]]');
	s = r (s, /\[\[((\d+)(?: (?:год )?в [\wa-яёА-ЯЁ ]+\|\2)?)\]\][\u00A0 ](год[а-яё]*)/g, '[[$1\xa0$3]]');
	s = r (s, /\[\[([XVI]+)\]\][\u00A0 ]век/g, '[[$1\xa0век]]');
	s = r (s, /\[\[(([XVI]+) век\|\2)\]\][\u00A0 ]век/g, '[[$2\xa0век]]');

	// Удаление недопустимых символов из викиссылок:
	s = r (s, /(\[\[[^|\[\]]*)[\u00AD\u200E\u200F]+([^\[\]]*\]\])/g, '$1$2'); // -- Soft Hyphen & DirMark.
	s = r (s, /\[\[ *([a-zA-Zа-яёА-ЯЁ\u00A0-\u00FF %!\"$&'()*,\-.\/0-9:;=?\\@\^_`’~]+) *\| *(\1)([a-zа-яё]*) *\]\]/g, '[[$2]]$3'); // -- ".
	s = r (s, /\[\[ *([a-zA-Zа-яёА-ЯЁ\u00A0-\u00FF %!\"$&'()*,\-.\/0-9:;=?\\@\^_`’~]+) *\| *([^|[\]]+) *\]\]([a-zа-яё]+)/g, '[[$1|$2$3]]'); // -- ".
 
	s = hide (s, /\[\[[^\]|]+/g); // -- скрытие викиссылок.

	// Все теги в <> надо обработать здесь:

	// HTML -> викитекст:
	s = r (s, /<<(\S.+\S)>>/g, '"$1"');			// -- угловые псевдокавычки в обычные. Или лучше в «»?
	s = r (s, /(sup>|sub>|\s)-(\d)/g, '$1−$2');	// -- minus в индексах.
	s = r (s, /(<sup>2<\/sup>|&sup2;)/gi, '²');	// -- символы квадрата
	s = r (s, /(<sup>3<\/sup>|&sup3;)/gi, '³');	//	и куба.
	s = r (s, /<(b|strong)>([\s\S]+?)<\/(b|strong)>/gi,"'''$2'''");	// -- вики-полужирный.
	s = r (s, /<(i|em)>([\s\S]+?)<\/(i|em)>/gi,"''$2''");				// -- вики-курсив.
	s = r (s, /^<hr ?\/?>/gim, '----');			// -- вики-гор. разделитель.
	s = r (s, /<\/?(hr|br)( [^\/>]+?)? ?\/?>/gi, '<$1$2 />');		// оформление hr/br по стандарту XML.
	s = r (s, /\n*<p(?:\s+align\s*=\s*(["']?)(?:left|justify)\1\s*)?>([\s\S]*?)(<\/p>|(?=\n*<(p|h|pre)))/gi, '\n\n$2');
											// -- remove <p>.
  	// Заголовки:
	s = r (s, /\n*<h1(?:[^>]*)>(.+?)<\/h1>\n*/gi, '\n\n= $1 =\n');
	s = r (s, /\n*<h2(?:[^>]*)>(.+?)<\/h2>\n*/gi, '\n\n== $1 ==\n');
	s = r (s, /\n*<h3(?:[^>]*)>(.+?)<\/h3>\n*/gi, '\n\n=== $1 ===\n');
	s = r (s, /\n*<h4(?:[^>]*)>(.+?)<\/h4>\n*/gi, '\n\n==== $1 ====\n');
	s = r (s, /\n*<h5(?:[^>]*)>(.+?)<\/h5>\n*/gi, '\n\n===== $1 =====\n');
	s = r (s, /\n*<h6(?:[^>]*)>(.+?)<\/h6>\n*/gi, '\n\n====== $1 ======\n');
	s = r (s, /(\n==\s*Примечания\s*==\n)<references *\/>/,'$1{{примечания}}');

	// Создание сносок:
	s = r (s, /(?!\n)([^\[])\[(\d+)\](?!\s*с\.)/g, '$1<ref name="ref$2" />');	// -- сноска вида [1].
	s = r (s, /\n\[(\d+)\]\s*([\s\S]+?)(?=\n\[\d+\]|$)/g, '\n<ref name="ref$1">$2</ref>');// -- текст сноски вида [1].

	// .,… до сносок -- сдвиг сносок:
	s = r (s, /\s*,\s*((<ref[^<\/>]*?(>[^<]+?<\/ref|\/)>)+)/gi , '{{,}}$1');   	// -- запятая до;
	s = r (s, /\s*(…)\s*((<ref[^<\/>]*?(>[^<]+?<\/ref|\/)>)+)/gi , '{{,|$1}}$2');	// -- др. знак до;
	s = r (s, /\s*\.\s*((<ref[^<\/>]*?(>[^<]+?<\/ref|\/)>)+)/gi, '{{тчк}}$1');	// -- точка до;

	// .,… после сносок -- перенос до и сдвиг сносок:
	s = r (s, /\s*((<ref[^<\/>]*?(>[^<]+?<\/ref|\/)>)+)\s*(?:,|\{\{(?:,|зпт)\}\})/gi , '{{,}}$1');		// -- запятая после;
	s = r (s, /\s*((?:<ref[^<\/>]*?(?:>[^<]+?<\/ref|\/)>)+)\s*([…])/gi , '{{,|$2}}$1');					// -- др. знак после;
	s = r (s, /\s*((<ref[^<\/>]*?(>[^<]+?<\/ref|\/)>)+)\s*(?:\.|\{\{(?:,\|\.|тчк)\}\})/gi, '{{тчк}}$1');	// -- точка после;

	// !? после сносок -- перенос до:
	s = r (s, /\s*((?:<ref[^<\/>]*?(?:>[^<]+?<\/ref|\/)>)+)([!?;:“])/gi , '$2$1');   // -- !? после;

	// !?;:“» до сносок -- удаление лишних пробелов:
	s = r (s, /\s*([!?;:“»])\s+((<ref[^<\/>]*?(>[^<]+?<\/ref|\/)>)+)/gi , '$1$2');   // -- др. знак до;
	  
  	// Удаление лишних пробелов и переводов строки внутри сносок:
	s = r (s, /(<ref[^\/>]*>)\s+/gi, '$1');
	s = r (s, /\s+<\/ref>/gi, '</ref>');	  

	// Создание раздела сносок:
	s = r (s, /((?:<ref\sname=".+?">[\s\S]+?<\/ref>)+)\s*$/, '<references>$1</references>\n');	// -- раздел сносок.

	s = hide(s, /<[a-z][^>]*?>/gi);	// -- hide all HTML tags.

	s = hide(s, /^(?:{\||\|-).*/mg);	// -- таблицы и ряды.
	s = hide(s, /(?:^\||^!|!!|\|\|) *[a-z]+=[^|]+\|(?!\|)/mgi);	// -- стили ячеек.
	s = hide(s, /\| +/g);			// -- форматированные ячейки.

   	// Двойные пробелы:
	s = r (s, /[ \t]+/g, ' ');

	// Заголовки:
	s = r (s, /^(=+)[ \t\f\v]*(.*?)[ \t\f\v]*=+$/gm, '$1 $2 $1');			// -- окружить пробелами.
	s = r (s, /([^\r\n])(\r?\n==.*==\r?\n)/g, '$1\n$2');					// -- пустую строку впереди.
	s = r (s, /^== см(\.?|отрите) ?так\s*же ==$/gmi, '== См. также ==');	// -- «См. также».
	s = r (s, /^== сноски ==$/gmi, '== Примечания ==');
	s = r (s, /^== L ==$/gmi, '== Ссылки ==');	  
	s = r (s, /^== (.*[^.])[.:] ==$/gm, '== $1 ==');						// -- точка или двоеточие в конце.

	// Любые кавычки → в простые, которые потом будут перерасставлены:
	s = r (s, /&((la|ra|bd|ld)quo|quot);/g,'"');
	s = r (s, /«|»|“|”|„/g, '"');											// -- временное скрытие нормальных кавычек. Нужно ли?

	// Программный код как в github:
	// ``` … ``` → <pre>…</pre>:
	s = r (s, /\n```((.|\n)+?)\n```/g, '\n<pre>$1</pre>\n');
	s = r (s, /`(.+?)`/g, '<code>$1</code>');	// `…` → <code>…</code>
	  
	// Тире и дефисы:
	s = r (s, /–/g, '-');						// -- &ndash; → -
	s = r (s, /&(#151|[nm]dash);/g, '—');	 // -- мнемоника тире → —
	s = r (s, /(&nbsp;|\s)-{1,3} /g, '$1— '); // -- отбитые - → —
	s = r (s, /(\d)-{1,2}(\d)/g, '$1‒$2');	// -- -/-- между цифрами → ‒ (&#2012;)
	s = r (s, /([IVXLCDM]+)-{1,2}([IVXLCDM]+)/g, '$1‒$2');	// -- -/-- между римскими цифрами → ‒ (&#2012;)
	s = r (s, /(\s)-(\d)/g, '$1−$2');		 // -- отбитый - перед цифрой → −

	// Мнемоники HTML -> символы:
	s = r (s, /&#x([0-9a-f]{1,4});/gi, char);  //&#x301;
	s = r (s, /&copy;/gi,'©');
	s = r (s, /&reg;/gi,'®');
	s = r (s, /&sect;/gi,'§');
	s = r (s, /&euro;/gi,'€');
	s = r (s, /&yen;/gi,'¥');
	s = r (s, /&pound;/gi,'£');
	s = r (s, /&deg;/g,'°');
	s = r (s, /\(tm\)|\(тм\)|&trade;/gi,'™');
	  
	// Символы, которых нет на клавиатуре:
	s = r (s, /\.\.\.|&hellip;/g,'…');
	s = r (s, /\+-|&plusmn;/g,'±');
	s = r (s, /~=/g,'≈');
	s = r (s, /\^2(\D)/g,'²$1');
	s = r (s, /\^3(\D)/g,'³$1');
	s = r (s, /\([cс]\)/gi,'©');
	s = r (s, /([\wа-яА-ЯёЁ])'([\wа-яА-ЯёЁ])/g,'$1’$2'); //' → типографский апостроф.
	s = r (s, /№№/g,'№'); // знаки номера.

	// Годы и века
	s = r (s, /(\(|\s)([12]?\d{3})[\u00A0 ]?(-{1,3}|—) ?([12]?\d{3})(?![\w-°])/g, '$1$2—$4');
	s = r (s, /([12]?\d{3}) ?(гг?\.)/g, '$1\xa0$2');
	s = r (s, /(\(|\s)([IVX]{1,5})[\u00A0 ]?(-{1,3}|—) ?([IVX]{1,5})(?![\w-°])/g, '$1$2—$4');
	s = r (s, /([IVX]{1,5}) ?(вв?\.)/g, '$1\xa0$2');

	// Сокращения:
	s = r (s, /(Т|т)\.\s?е\./g, '$1о есть');
	s = r (s, /(Т|т)\.\s?к\./g, '$1ак как');
	s = r (s, /(В|в)\sт\. ?ч\./g, '$1 том числе');
	s = r (s, /и\sт\.\s?д\./g, 'и\xa0т\.\xa0д\.');
	s = r (s, /и\sт\.\s?п\./g, 'и\xa0т\.\xa0п\.');
	s = r (s, /(Т|т)\.\s?н\./g, '$1\.\xa0н\.');
	s = r (s, /н\.\s?э\./g, 'н\.\xa0э\.');
	s = r (s, /(Д|д)(о|\.)\sн\.\s?э\./g, '$1о\xa0н\.\xa0э\.');
	s = r (s, /(\d)[\u00A0 ]?(тыс\.|млн|млрд|трлн|(?:м|с|д|к)?м|[км]г)\.?(?=[,;.]| "?[а-яё-])/g, '$1\xa0$2');
	s = r (s, /(\d)[\u00A0 ](тыс)([^\.А-Яа-яЁё])/g, '$1\xa0$2.$3');
	s = r (s, /(\s)кв\.\s*(дм|см|мм|мкм|нм|км|м)(\s)/g, '$1\xA0$2²$3'); // -- квадратные единицы измерения.
	s = r (s, /(\s)куб\.\s*(дм|см|мм|мкм|нм|км|м)(\s)/g, '$1\xA0$2³$3'); // -- кубические единицы измерения.

	// Пробелы:
	s = r (s, /^([#*:]+)[ \t\f\v]*([^ \t\f\v*#:;])/gm, '$1 $2'); // -- в списках.
	s = r (s, /(\S) (-{1,3}|—) (\S)/g, '$1\xa0— $3'); //  тире.
	s = r (s, /([А-Я]\.) ?([А-Я]\.) ?([А-Я][а-я])/g, '$1\xa0$2\xa0$3'); // -- инициалы.
	s = r (s, /([А-Я]\.)([А-Я]\.)/g, '$1 $2'); //  инициалы.
	s = r (s, /([а-я]\.)([А-ЯA-Z])/g, '$1 $2'); // -- после точки.
	s = r (s, /([)"а-яa-z\]])\s*,([\[("а-яa-z])/g, '$1, $2'); // -- после точки.
	s = r (s, /([)"а-яa-z\]])\s([,;])\s([\[("а-яa-z])/g, '$1$2 $3'); // -- после запятой.
	s = r (s, /([^%\/\w]\d+?(?:[.,]\d+?)?) ?([%‰])(?!-[А-Яа-яЁё])/g, '$1\xa0$2'); // -- проценты.
	s = r (s, /(\d) ([%‰])(?=-[А-Яа-яЁё])/g, '$1$2'); // -- 5%-й
	s = r (s, /([№§])(\s*)(\d)/g, '$1\xa0$3'); // -- номер и параграф.
	s = r (s, /\( +/g, '(');
	s = r (s, / +\)/g, ')'); // -- убрать пробелы у внутренних сторон скобок.

	// Температура:
	s = r (s, /([\s\d=≈≠≤≥<>("'|])([+±−-]?\d+?(?:[.,]\d+?)?)(([ °^*]| [°^*])[CС])(?=[\s"').,;!?|])/gm, '$1$2\xa0°C');
	s = r (s, /([\s\d=≈≠≤≥<>("'|])([+±−-]?\d+?(?:[.,]\d+?)?)(([ °^*]| [°^*])F)(?=[\s"').,;|!?])/gm, '$1$2\xa0°F');

	// Десятичная точка → запятая:
	s = r (s, /(\s\d+)\.(\d+[\u00A0 ]*[%‰°])/gi, '$1,$2');

	// Союзы, предлоги, частицы:
	s = r (s, /(^|\s|\()(а|в|во|да|для|до|за|и|ибо|из|из-за|из-под|или|к|ко|на|над|не|ни|но|о|об|обо|от|перед|по|под|при|с|со|у)\s+/gi, '$1$2\xA0'); // -- проклитики
	s = r (s, /\s+(б|бы|ж|же|ли|ль)(?=$|\s|[,;:.!?)])/gi, '\xA0$1'); // -- энклитики.

	// Интерфейс для дополнений к викификатору:
	var pairs = window.wfPlugins;
	if (pairs && pairs.length) {
		pairs.forEach (function (pair) {
			s = r (s, pair [0], pair [1]);
	    });
	}

	// Восстановление кавычек: "" → «»:
	// s = restoreQuotes (s);
	s = r (s, /([\s\u00A0·\x02!|#'"\/(;+-])"([^"]*)([^\s"(|])"([^a-zа-яё])/ig, '$1<q>$2$3</q>$4'); // -- кавычки, внутри которых нет кавычек.
	s = r (s, /([\s\u00A0·\x02!|#'"\/(;+-])"([^"]*)([^\s"(|])"([^a-zа-яё])/ig, '$1<q>$2$3</q>$4'); // -- и ещё раз.

	// Удаление начального и конечного пробелов:
	s = r (s, /^\n/, '');
	s = r (s, /\n$/, '');

	// Восстановление скрытого:
	s = restore (s);
    
	return s;  

	// Вспомогательные функции:

	// Одна замена:
	function r (string, pattern, replacement) {
		console.log ('Replacing ' + pattern + ' with ' + replacement);
		return string.replace (pattern, replacement);
	}

	// Скрытие фрагментов путём окружения \x01 и \x02:
	function hide (txt, re) {
		console.log ('Hiding ' + re);
	    return txt.replace (re, function (s) {return '\x01' + hidden.push (s) + '\x02'});
	}
	
	// Скрытие тегов:
	function hideTags (/* variadic; no ES6 spread syntax txt, hidden, ... */) {
		var args = Array.prototype.slice.call (arguments);
		var txt = args.shift ()
		var tags = args.join ('|');
	    return hide (txt, RegExp ('<(' + tags + ')( [^>]+)?>[\\s\\S]+?<\\/\\1>', 'gi'));
	}	// -- function hideTags (txt, tags)
	
	// Восстановление скрытого:
	function restore (/* String */ s) {
		if ('0'.replace('0', '$$') === '$') { // -- $ в регэксах, как всегда, IE особенный.
			for (i = 0; i < hidden.length; i++) {
				hidden [i] = hidden [i].replace (/\$/g, '$$$$');
			}
		}
		// Раскрытие скрытого в hide ():
		while (hidden.length > 0) {
			s = s.replace ('\x01' + hidden.length + '\x02', hidden.pop ());
		}
		return s;
	}	// -- function restore (/* String */ s)
	
	// Эти функции ничего не делают в браузере, но реализованы в серверном викификаторе:
	function re (/* String */ s, /* String */ re) {
		return s;
	}

	function collect_link (_, url, alias) {
		console.log ('_ = ' + _ + ', url = ' + url + ', alias = ' + alias);
		external_links.add (url);
		return '[' + url + ' ' + alias + ']';
	}
	function resolve_links (/* String */ s) {
		var text = s;
		if ( external_links.size > 0 ) {
			var property = 'URL источника';
			var list = Array.from (external_links).join ('||');
			var api = new mw.Api ();
			api.get ({
				action: 'askargs',
				conditions: property + '::' + list,
				printouts: property,
				format: 'json',
				parameters: 'limit=' + external_links.size,
				api_version: 3
			}).done (function (result) {
				if ( !result.query || !result.query.results ) return false;
				result.query.results.forEach (function (row) {
					Object.keys (row).forEach (function (page) {
						row [page].printouts [property].forEach (function (url) {
							resolved [url] = page;
						});	
					})
				});
				Object.keys (resolved).forEach (function (url) {
					if ( resolved [url] ) {
						text = r (text, /\[\s*(https?:\/\/[^\]\s]+)\s*([^\]]*)\]/gi, '[[' + resolved [url] + '|\\2]]');
					}
				});
			}).fail (function (code, result) {
				return false;
			});
		}
		return text;
	}
	function internalise_link (_, url, alias) {
		return '[' + url + ' ' + alias + ']';
	}

	// Превращение ссылок HTML на «Традицию» в викиссылки:
	function wikifyInternalLinks (_, __, page, alias) {
		return '[[' + decodeURIComponent (page) + '|' + alias + ']]';
	}

	// Создание символа из кода:
	function char (_, a) {
		return String.fromCharCode (eval ('0x' + a.substr (-4)))
	}
	
}	// -- function wikifyText (s)

// Подключение викификатора к панели инструментов «Традиции»:
if (mw) {
	if (!mw.edit_gadget_extensions) {
		mw.edit_gadget_extensions = [];
	}
	mw.edit_gadget_extensions.push (function () {
	    mw.tools_above [0].splice (1, 0
		  , {w: wikifyText, b: '<img src="/files/1/1f/Etool_wikify.png" width="20" height="16" alt="W"/>', t: 'Викификация', all: true}
	      , {url: mw.util.getUrl ('Справка:Викификатор'), b: '<img src="/files/d/d5/Etool_help.png" height="16" width="16" alt="?">', t: '(справка о викификаторе)'}
	    );
	});
}