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

Материал из in.wiki
Перейти к навигации Перейти к поиску
(Новая страница: «/* * Викификатор. Подключается к панели инструментов «Традиции». * * '''ВНИМАНИЕ! Внося изм...»)
 
м (обновление)
 
(не показано 18 промежуточных версий этого же участника)
Строка 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Выделите своё сообщение — обработано будет только оно';
Строка 12: Строка 12:
 
// Функция викифицирует переданный текст и возвращает викифицированный:
 
// Функция викифицирует переданный текст и возвращает викифицированный:
 
function wikifyText (s) {
 
function wikifyText (s) {
    var txt='', hidden = [];
 
  
    var u = '\u00A0'; // -- unbreakable space.
+
if (isDiscussion) {
    if (isDiscussion) {
+
// -- это обсуждение.
    // -- это обсуждение.
+
// Несколько дат, вероятно, чужие подписи::
        u = ' ';
+
var sigs = s.match (/\d\d:\d\d, \d\d? \S{3,8} 20\d\d \(UTC\)/g);
        // Несколько дат, вероятно, чужие подписи::
+
if (sigs && sigs.length > 1) {
        var sigs = s.match (/\d\d:\d\d, \d\d? \S{3,8} 20\d\d \(UTC\)/g);
+
window.alert (wmTalkPage);
            if (sigs && sigs.length > 1) {
+
return s;
                window.alert (wmTalkPage);
+
}
                return s;
+
}
            }
 
    }
 
 
 
    // Скрытие преформатированных тегов и прочих тегов, не подлежащих викификации:
 
    s = hideTag (s, ['nowiki', 'pre', 'source', 'syntaxhighlight', 'code', 'tt', 'svgcode', 'graphviz', 'mscgen', 'score', 'math', 'gallery']);
 
 
      
 
      
     s = runReplacements (s, [[/(IS[BS]N):?\s*([\s\d‒−—-]{8,17}[X\d])/ig, '{{$1|$2}}']]); // -- ISBN/ISSN теперь в шаблон.
+
     // Собственно, викификация:
 +
    /*
 +
    Следует ограничиться использованием функций hide(), hideTags(), restore(), r(), collect_link(), resolve_links() и internalise_link().
 +
    Они будут также распознаны и выполнены в том же порядке серверным викификатором
 +
    Module:Wikifier.
 
      
 
      
     s = hide (s, /{\{[\s\S]+?}}/g); // — шаблоны.
+
     Вызовы функций re(), collect_link() и internalise_links() будут выполнены только серверным викификатором,
    s = hide (s, /^ .*/mg); // — преформатированный текст.
+
    а в JavaScript они не будут иметь эффекта.
   
+
    */
s = runReplacements (s, [
+
[/<\s*a\s+href\s*=\s*(["'])\s*https?:\/\/traditio(?:\.ru|-ru\.org|\.wiki)(?:\/wiki)?\/(\S+?)\s*\1\s*>(.+?)<\s*\/a\s*>/gi, function (_, __, p2, p3) {
+
var hidden = [];
        return '[[' + decodeURIComponent (p2) + '|' + p3 + ']]';
+
var external_links = new Set ();
        }] // -- внутренние <a> → [[]]
+
var resolved = {};
  , [/<\s*a\s+href\s*=\s*(["'])(\S+?)\1\s*>(.+?)<\s*\/a\s*>/gi, '[$2 $3]'] // -- <a> → []
+
 
]);
+
// Скрытие преформатированных тегов и прочих тегов, не подлежащих викификации:
   
+
s = hideTags (s, 'nowiki', 'templatedata',
    s = hide (s, /(https?|ftp|news|nntp|telnet|irc|gopher):\/\/[^\s\[\]<>"]+ ?/gi); // — гиперссылки.
+
'pre', 'source', 'syntaxhighlight', 'code', 'kbd', 'tt', 'gallery',
    s = hide (s, /^#(redirect|перенапр(авление)?)/i); // — перенаправления.
+
'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); // -- скрытие викиссылок.
 +
 
 +
// Все теги в <> надо обработать здесь:
  
    s = s.replace (/ +(\n|\r)/g, '$1'); // -- пробелы в конце строки.
+
// HTML -> викитекст:
    s = '\n' + s + '\n';
+
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 = runReplacements (s, [
+
// Создание сносок:
        // Русификация пространств имён:
+
s = r (s, /(?!\n)([^\[])\[(\d+)\](?!\s*с\.)/g, '$1<ref name="ref$2" />'); // -- сноска вида [1].
        [/(\[\[:?)(category|категория):( *)/ig, '$1Категория:']
+
s = r (s, /\n\[(\d+)\]\s*([\s\S]+?)(?=\n\[\d+\]|$)/g, '\n<ref name="ref$1">$2</ref>');// -- текст сноски вида [1].
      , [/(\[\[:?)(image|изображение|file):( *)/ig, '$1Файл:']
 
        // Оформление дат:
 
      , [/(\(|\s)(\[\[[12]?\d{3}\]\])[\u00A0 ]?(-{1,3}|–|—) ?(\[\[[12]?\d{3}\]\])(\W)/g, '$1$2—$4$5']
 
      , [/(\[\[[12]?\d{3}\]\]) ?(гг?\.)/g, '$1' + u + '$2']
 
      , [/(\(|\s)(\[\[[IVX]{1,5}\]\])[\u00A0 ]?(-{1,3}|–|—) ?(\[\[[IVX]{1,5}\]\])(\W)/g, '$1$2—$4$5']
 
      , [/(\[\[[IVX]{1,5}\]\]) ?(вв?\.)/g, '$1' + u + '$2']
 
      , [/\[\[(\d+)\]\][\u00A0 ]год/g, '[[$1' + u + 'год]]']
 
      , [/\[\[((\d+)(?: (?:год )?в [\wa-яёА-ЯЁ ]+\|\2)?)\]\][\u00A0 ](год[а-яё]*)/g, '[[$1' + u + '$3]]']
 
      , [/\[\[([XVI]+)\]\][\u00A0 ]век/g, '[[$1' + u + 'век]]']
 
      , [/\[\[(([XVI]+) век\|\2)\]\][\u00A0 ]век/g, '[[$2' + u + 'век]]']
 
      // Удаление недопустимых символов из викиссылок:
 
      , [/(\[\[[^|\[\]]*)[\u00AD\u200E\u200F]+([^\[\]]*\]\])/g, '$1$2'] // -- Soft Hyphen & DirMark.
 
      , [/\[\[ *([a-zA-Zа-яёА-ЯЁ\u00A0-\u00FF %!\"$&'()*,\-.\/0-9:;=?\\@\^_`’~]+) *\| *(\1)([a-zа-яё]*) *\]\]/g, '[[$2]]$3'] // -- ".
 
      , [/\[\[ *([a-zA-Zа-яёА-ЯЁ\u00A0-\u00FF %!\"$&'()*,\-.\/0-9:;=?\\@\^_`’~]+) *\| *([^|[\]]+) *\]\]([a-zа-яё]+)/g, '[[$1|$2$3]]'] // -- ".
 
    ]);
 
    s = hide (s, /\[\[[^\]|]+/g); // -- скрытие викиссылок.
 
  
    // Все теги в <> надо обработать здесь:
+
// .,… до сносок -- сдвиг сносок:
s = runReplacements (s, [
+
s = r (s, /\s*,\s*((<ref[^<\/>]*?(>[^<]+?<\/ref|\/)>)+)/gi , '{{,}}$1');  // -- запятая до;
        // HTML -> викитекст:
+
s = r (s, /\s*()\s*((<ref[^<\/>]*?(>[^<]+?<\/ref|\/)>)+)/gi , '{{,|$1}}$2'); // -- др. знак до;
[/<<(\S.+\S)>>/g, '"$1"'] // -- угловые кавычки в обычные. Или лучше в «»?
+
s = r (s, /\s*\.\s*((<ref[^<\/>]*?(>[^<]+?<\/ref|\/)>)+)/gi, '{{тчк}}$1'); // -- точка до;
      , [/(sup>|sub>|\s)-(\d)/g, '$1−$2'] // -- minus в индексах.
 
      , [/(<sup>2<\/sup>|&sup2;)/gi, '²'] // -- символы квадрата
 
      , [/(<sup>3<\/sup>|&sup3;)/gi, '³'] //    и куба.
 
      , [/<(b|strong)>([\s\S]+?)<\/(b|strong)>/gi,"'''$2'''"] // -- вики-полужирный.
 
      , [/<(i|em)>([\s\S]+?)<\/(i|em)>/gi,"''$2''"] // -- вики-курсив.
 
      , [/^<hr ?\/?>/gim, '----'] // -- вики-гор. разделитель.
 
      , [/<\/?(hr|br)( [^\/>]+?)? ?\/?>/gi, '<$1$2 />'] // оформление hr/br по стандарту XML.
 
      , [/\n*<p(?:\s+align\s*=\s*(["']?)(?:left|justify)\1\s*)?>([\s\S]*?)<\/p>/gi, '\n\n$2']
 
    // -- remove <p>.
 
      , [/(\n==\s*Примечания\s*==\n)<references *\/>/,'$1{{примечания}}']
 
  
// Создание сносок:
+
// .,… после сносок -- перенос до и сдвиг сносок:
  , [/(?!^)([^\[])\[(\d+)\](?!\s*с\.)/gm, '$1<ref name="ref$2" />'] // -- сноска.
+
s = r (s, /\s*((<ref[^<\/>]*?(>[^<]+?<\/ref|\/)>)+)\s*(?:,|\{\{(?:,|зпт)\}\})/gi , '{{,}}$1'); // -- запятая после;
  , [/\n\[(\d+)\]\s*([\s\S]+?)(?=\n\[\d+\]|$)/g, '\n<ref name="ref$1">$2</ref>']// -- текст сноски.
+
s = r (s, /\s*((?:<ref[^<\/>]*?(?:>[^<]+?<\/ref|\/)>)+)\s*([…])/gi , '{{,|$2}}$1'); // -- др. знак после;
 +
s = r (s, /\s*((<ref[^<\/>]*?(>[^<]+?<\/ref|\/)>)+)\s*(?:\.|\{\{(?:,\|\.|тчк)\}\})/gi, '{{тчк}}$1'); // -- точка после;
  
        // .,… до сносок -- сдвиг сносок:
+
// !? после сносок -- перенос до:
      , [/\s*,\s*((<ref[^<\/>]*?(>[^<]+?<\/ref|\/)>)+)/gi , '{{,}}$1']  // -- запятая до;
+
s = r (s, /\s*((?:<ref[^<\/>]*?(?:>[^<]+?<\/ref|\/)>)+)([!?;:“])/gi , '$2$1');  // -- !? после;
      , [/\s*(…)\s*((<ref[^<\/>]*?(>[^<]+?<\/ref|\/)>)+)/gi , '{{,|$1}}$2'] // -- др. знак до;
 
      , [/\s*\.\s*((<ref[^<\/>]*?(>[^<]+?<\/ref|\/)>)+)/gi, '{{тчк}}$1'] // -- точка до;
 
  
        // .,… после сносок -- перенос до и сдвиг сносок:
+
// !?;:“» до сносок -- удаление лишних пробелов:
      , [/\s*((<ref[^<\/>]*?(>[^<]+?<\/ref|\/)>)+)\s*(?:,|\{\{(?:,|зпт)\}\})/gi , '{{,}}$1'] // -- запятая после;
+
s = r (s, /\s*([!?;:“»])\s+((<ref[^<\/>]*?(>[^<]+?<\/ref|\/)>)+)/gi , '$1$2');  // -- др. знак до;
      , [/\s*((?:<ref[^<\/>]*?(?:>[^<]+?<\/ref|\/)>)+)\s*([…])/gi , '{{,|$2}}$1'] // -- др. знак после;
+
 
      , [/\s*((<ref[^<\/>]*?(>[^<]+?<\/ref|\/)>)+)\s*(?:\.|\{\{(?:,\|\.|тчк)\}\})/gi, '{{тчк}}$1'] // -- точка после;
+
  // Удаление лишних пробелов и переводов строки внутри сносок:
 +
s = r (s, /(<ref[^\/>]*>)\s+/gi, '$1');
 +
s = r (s, /\s+<\/ref>/gi, '</ref>');  
  
        // !? после сносок -- перенос до:
+
// Создание раздела сносок:
      , [/\s*((?:<ref[^<\/>]*?(?:>[^<]+?<\/ref|\/)>)+)([!?;:“])/gi , '$2$1'// -- !? после;
+
s = r (s, /((?:<ref\sname=".+?">[\s\S]+?<\/ref>)+)\s*$/, '<references>$1</references>\n'); // -- раздел сносок.
  
        // !?;:“» до сносок -- удаление лишних пробелов:
+
s = hide(s, /<[a-z][^>]*?>/gi); // -- hide all HTML tags.
      , [/\s*([!?;:“»])\s+((<ref[^<\/>]*?(>[^<]+?<\/ref|\/)>)+)/gi , '$1$2']  // -- др. знак до;
 
  
    ]);
+
s = hide(s, /^(?:{\||\|-).*/mg); // -- таблицы и ряды.
   
+
s = hide(s, /(?:^\||^!|!!|\|\|) *[a-z]+=[^|]+\|(?!\|)/mgi); // -- стили ячеек.
    // Создание раздела сносок:
+
s = hide(s, /\| +/g); // -- форматированные ячейки.
    if (s.indexOf ('<references>') === -1) {
 
    s = runReplacements (s, [
 
[/((?:<ref\sname=".+?">[\s\S]+?<\/ref>)+)$/, '<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 = r (s, /[ \t]+/g, ' ');
    s = hide(s, /\| +/g); // -- форматированные ячейки.
 
  
    s = s.replace (/[ \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 = runReplacements (s, [
+
// Любые кавычки → в простые, которые потом будут перерасставлены:
        // Заголовки:
+
s = r (s, /&((la|ra|bd|ld)quo|quot);/g,'"');
        [/^(=+)[ \t\f\v]*(.*?)[ \t\f\v]*=+$/gm, '$1 $2 $1'] // -- окружить пробелами.
+
s = r (s, /«|»|“|”|„/g, '"'); // -- временное скрытие нормальных кавычек. Нужно ли?
      , [/([^\r\n])(\r?\n==.*==\r?\n)/g, '$1\n$2'] // -- пустую строку впереди.
 
      , [/^== см(\.?|отрите) ?так\s*же ==$/gmi, '== См. также =='] // -- «См. также».
 
      , [/^== сноски ==$/gmi, '== Примечания ==']
 
      , [/^== L ==$/gmi, '== Ссылки ==']     
 
      , [/^== (.*[^.])[.:] ==$/gm, '== $1 =='] // -- точка или двоеточие в конце.
 
  
      , [/«|»||”|„/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'); // -- отбитый - перед цифрой → −
  
// Программыный код как в github:
+
// Мнемоники HTML -> символы:
// ``` … ``` → indented … → <pre>…</pre>:
+
s = r (s, /&#x([0-9a-f]{1,4});/gi, char);  //&#x301;
  , [/\n```((.|\n)+?)\n```/g, function (_, s) {return s.replace (/^/gm, ' '); }]
+
s = r (s, /&copy;/gi,'©');
  , [/`(.+?)`/g, '<code>$1</code>'] // `…` → <code>…</code>
+
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,'');
 
   
 
   
        // Тире и дефисы:
+
// Символы, которых нет на клавиатуре:
      , [//g, '-']                    // -- &ndash; → -
+
s = r (s, /\.\.\.|&hellip;/g,'…');
      , [/&(#151|[nm]dash);/g, '']     // -- мнемоника тире
+
s = r (s, /\+-|&plusmn;/g,'±');
      , [/(&nbsp;|\s)-{1,3} /g, '$1— '] // -- отбитые - → —
+
s = r (s, /~=/g,'≈');
      , [/(\d)-{1,2}(\d)/g, '$1‒$2']    // -- -/-- между цифрами → ‒ (&#2012;)
+
s = r (s, /\^2(\D)/g,'²$1');
      , [/([IVXLCDM]+)-{1,2}([IVXLCDM]+)/g, '$1‒$2']    // -- -/-- между римскими цифрами → ‒ (&#2012;)
+
s = r (s, /\^3(\D)/g,'³$1');
      , [/(\s)-(\d)/g, '$1−$2']        // -- отбитый - перед цифрой → −
+
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'); // -- кубические единицы измерения.
  
        // Мнемоники HTML -> символы:
+
// Пробелы:
      , [/&#x([0-9a-f]{1,4});/gi, function (n, a) {return String.fromCharCode (eval ('0x' + a.substr (-4)))}]  //&#x301;
+
s = r (s, /^([#*:]+)[ \t\f\v]*([^ \t\f\v*#:;])/gm, '$1 $2'); // -- в списках.
      , [/&copy;/gi,'©']
+
s = r (s, /(\S) (-{1,3}|—) (\S)/g, '$1\xa0— $3'); // тире.
      , [/&reg;/gi,'®']
+
s = r (s, /([А-Я]\.) ?([А-Я]\.) ?([А-Я][а-я])/g, '$1\xa0$2\xa0$3'); // -- инициалы.
      , [/&sect;/gi,'§']
+
s = r (s, /([А-Я]\.)([А-Я]\.)/g, '$1 $2'); // инициалы.
      , [/&euro;/gi,'€']
+
s = r (s, /([а-я]\.)([А-ЯA-Z])/g, '$1 $2'); // -- после точки.
      , [/&yen;/gi,'¥']
+
s = r (s, /([)"а-яa-z\]])\s*,([\[("а-яa-z])/g, '$1, $2'); // -- после точки.
      , [/&pound;/gi,'£']
+
s = r (s, /([)"а-яa-z\]])\s([,;])\s([\[("а-яa-z])/g, '$1$2 $3'); // -- после запятой.
      , [/&deg;/g,'°']
+
s = r (s, /([^%\/\w]\d+?(?:[.,]\d+?)?) ?([%‰])(?!-[А-Яа-яЁё])/g, '$1\xa0$2'); // -- проценты.
      , [/\(tm\)|\(тм\)|&trade;/gi,'']
+
s = r (s, /(\d) ([%‰])(?=-[А-Яа-яЁё])/g, '$1$2'); // -- 5%-й
     
+
s = r (s, /([№§])(\s*)(\d)/g, '$1\xa0$3'); // -- номер и параграф.
      // Символы, которых нет на клавиатуре:
+
s = r (s, /\( +/g, '(');
      , [/\.\.\.|&hellip;/g,'']
+
s = r (s, / +\)/g, ')'); // -- убрать пробелы у внутренних сторон скобок.
      , [/\+-|&plusmn;/g,'±']
 
      , [/~=/g,'≈']
 
      , [/\^2(\D)/g,'²$1']
 
      , [/\^3(\D)/g,'³$1']
 
      , [/\([]\)/gi,'©']
 
     
 
      // Любые кавычки → в простые, которые потом будут перерасставлены:
 
      , [/&((la|ra|bd|ld)quo|quot);/g,'"']
 
     
 
      , [/([\wа-яА-ЯёЁ])'([\wа-яА-ЯёЁ])/g,'$1’$2'] //' → типографский апостроф.
 
     
 
      , [/№№/g,''] // знаки номера.
 
  
        // Годы и века
+
// Температура:
      , [/(\(|\s)([12]?\d{3})[\u00A0 ]?(-{1,3}|—) ?([12]?\d{3})(?![\w-°])/g, '$1$2—$4']
+
s = r (s, /([\s\d=≈≠≤≥<>("'|])([+±−-]?\d+?(?:[.,]\d+?)?)(([ °^*]| ^*])[])(?=[\s"').,;!?|])/gm, '$1$2\xa0°C');
      , [/([12]?\d{3}) ?(гг?\.)/g, '$1' + u + '$2']
+
s = r (s, /([\s\d=≈≠≤≥<>("'|])([+±−-]?\d+?(?:[.,]\d+?)?)(([ °^*]| ^*])F)(?=[\s"').,;|!?])/gm, '$1$2\xa0°F');
      , [/(\(|\s)([IVX]{1,5})[\u00A0 ]?(-{1,3}|—) ?([IVX]{1,5})(?![\w-°])/g, '$1$2—$4']
 
      , [/([IVX]{1,5}) ?(вв?\.)/g, '$1' + u + '$2']
 
  
        // Сокращения:
+
// Десятичная точка → запятая:
      , [/(Т|т)\.\s?е\./g, '$1о есть']
+
s = r (s, /(\s\d+)\.(\d+[\u00A0 ]*[%‰°])/gi, '$1,$2');
      , [/(Т|т)\.\s?к\./g, '$1ак как']
 
      , [/(В|в)\sт\. ?ч\./g, '$1 том числе']
 
      , [/и\sт\.\s\./g, 'и' + u + 'т\.' + u + 'д\.']
 
      , [/и\sт\.\s?п\./g, 'и' + u + 'т\.' + u + 'п\.']
 
      , [/(Т|т)\.\s?н\./g, '$1\.' + u + 'н\.']
 
      , [/н\.\s?э\./g, 'н\.' + u + 'э\.']
 
      , [/(Д|д)(о|\.)\sн\.\s?э\./g, '$1о' + u + 'н\.' + u + 'э\.']
 
      , [/(\d)[\u00A0 ]?(тыс\.|млн|млрд|трлн|(?:м|с|д|к)?м|[км]г)\.?(?=[,;.]| "?[а-яё-])/g, '$1' + u + '$2']
 
      , [/(\d)[\u00A0 ](тыс)([^\.А-Яа-яЁё])/g, '$1' + u + '$2.$3']
 
  
        // Пробелы:
+
// Союзы, предлоги, частицы:
      , [/^([#*:]+)[ \t\f\v]*([^ \t\f\v*#:;])/gm, '$1 $2'] // -- в списках.
+
s = r (s, /(^|\s|\()(а|в|во|да|для|до|за|и|ибо|из|из-за|из-под|или|к|ко|на|над|не|ни|но|о|об|обо|от|перед|по|под|при|с|со|у)\s+/gi, '$1$2\xA0'); // -- проклитики
      , [/(\S) (-{1,3}|) (\S)/g, '$1' + u + '— $3'] // тире.
+
s = r (s, /\s+(б|бы|ж|же|ли|ль)(?=$|\s|[,;:.!?)])/gi, '\xA0$1'); // -- энклитики.
      , [/([А-Я]\.) ?([А-Я]\.) ?([А-Я][а-я])/g, '$1' + u + '$2' + u + '$3'] // -- инициалы.
 
      , [/([А-Я]\.)([А-Я]\.)/g, '$1 $2'] //  инициалы.
 
      , [/([а-я]\.)([А-ЯA-Z])/g, '$1 $2'] // -- после точки.
 
      , [/([)"а-яa-z\]])\s*,([\[("а-яa-z])/g, '$1, $2'] // -- после точки.
 
      , [/([)"а-яa-z\]])\s([,;])\s([\[("а-яa-z])/g, '$1$2 $3'] // -- после запятой.
 
      , [/([^%\/\w]\d+?(?:[.,]\d+?)?) ?([%‰])(?!-[А-Яа-яЁё])/g, '$1' + u + '$2'] // -- проценты.
 
      , [/(\d) ([%‰])(?=-[А-Яа-яЁё])/g, '$1$2'] // -- 5%-й
 
      , [/([№§])(\s*)(\d)/g, '$1' + u + '$3'] // -- номер и параграф.
 
      , [/\( +/g, '(']
 
      , [/ +\)/g, ')'] // -- убрать пробелы у внутренних сторон скобок.
 
  
        // Температура:
+
// Интерфейс для дополнений к викификатору:
      , [/([\s\d=≈≠≤≥<>("'|])([+±−-]?\d+?(?:[.,]\d+?)?)(([ °^*]| [°^*])[CС])(?=[\s"').,;!?|])/gm, '$1$2' + u + '°C']
+
var pairs = window.wfPlugins;
      , [/([\s\d=≈≠≤≥<>("'|])([+±−-]?\d+?(?:[.,]\d+?)?)(([ °^*]| [°^*])F)(?=[\s"').,;|!?])/gm, '$1$2' + u + '°F']
+
if (pairs && pairs.length) {
 +
pairs.forEach (function (pair) {
 +
s = r (s, pair [0], pair [1]);
 +
    });
 +
}
  
        // Десятичная точка запятая:
+
// Восстановление кавычек: "" «»:
      , [/(\s\d+)\.(\d+[\u00A0 ]*[%‰°])/gi, '$1,$2']
+
// 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|\()(а|в|во|да|для|до|за|и|ибо|из|из-за|из-под|или|к|ко|на|не|ни|но|о|об|обо|от|перед|при|с|со|у)\s+/gi, '$1$2' + u] // -- проклитики
+
s = r (s, /^\n/, '');
      , [/\s+(б|бы|ж|же|ли|ль)(?=$|\s|[,;:.!?)])/gi, u + '$1'] // -- энклитики.
+
s = r (s, /\n$/, '');
    ]);
+
 
 +
// Восстановление скрытого:
 +
s = restore (s);
 
      
 
      
    // Интерфейс для дополнений к викификатору:
+
return s; 
    s = runReplacements (s, window.wfPlugins);
+
 
 +
// Вспомогательные функции:
 +
 
 +
// Одна замена:
 +
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;
 +
}
  
    // Подумать о простых <q></q>:
+
function collect_link (_, url, alias) {
    // Восстановление кавычек: "" → «»:
+
console.log ('_ = ' + _ + ', url = ' + url + ', alias = ' + alias);
    for (var i=1; i<=2; i++) {
+
external_links.add (url);
        s = s.replace (/([\s\u00A0·\x02!|#'"\/(;+-])"([^"]*)([^\s"(|])"([^a-zа-яё])/ig, '$1«$2$3»$4'); //"
+
return '[' + url + ' ' + alias + ']';
    }
+
}
    while (/«[^»]*«/.test(s)) {
+
function resolve_links (/* String */ s) {
        s = s.replace (/«([^»]*([^»]*)»/g, '«$1„$2“');
+
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 + ']';
 +
}
  
    s = s.substr (1, s.length - 2); // -- удаление начального и конечного пробелов.
+
// Превращение ссылок HTML на «Традицию» в викиссылки:
    if ('0'.replace('0', '$$') === '$') { // -- $ в регэксах, как всегда, IE особенный.
+
function wikifyInternalLinks (_, __, page, alias) {
        for (i = 0; i < hidden.length; i++) {
+
return '[[' + decodeURIComponent (page) + '|' + alias + ']]';
            hidden [i] = hidden [i].replace (/\$/g, '$$$$');
+
}
        }
+
 
    }
+
// Создание символа из кода:
    // Раскрытие скрытого в hide ():
+
function char (_, a) {
    while (hidden.length > 0) {
+
return String.fromCharCode (eval ('0x' + a.substr (-4)))
        s = s.replace ('\x01' + hidden.length + '\x02', hidden.pop ());
+
}
    }
+
   
 
    return s;
 
   
 
    // Вспомогательные функции:
 
    // Выполнение массива замен:
 
    function runReplacements (s, pairs, log) {
 
        if (pairs && pairs.length) {
 
        pairs.forEach (function (pair) {
 
            if (log) console.log (pair [0] + ': Before: ' + s);
 
            s = s.replace (pair [0], pair [1]);
 
            if (log) console.log ('After: ' + s + '\n');
 
            });
 
        }
 
        return s;
 
    }
 
    // Скрытие фрагментов путём окружения \x01 и \x02:
 
    function hide (txt, re) {
 
        return txt.replace (re, function (s) {return '\x01' + hidden.push (s) + '\x02'});
 
    }
 
    function hideTag (txt, tag) {
 
    if (tag.join) {
 
    tag = tag.join ('|');
 
    }
 
        return hide (txt, RegExp ('<(' + tag + ')( [^>]+)?>[\\s\\S]+?<\\/\\1>', 'gi'));
 
    }
 
 
} // -- function wikifyText (s)
 
} // -- function wikifyText (s)
  
Строка 269: Строка 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: '(справка о викификаторе)'}
 
    );
 
    );
 
});
 
});
}
 
 
if (module) {
 
module.exports = wikifyText;
 
 
}
 
}

Текущая версия от 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: '(справка о викификаторе)'}
	    );
	});
}