<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="ru">
	<id>https://narofootball.ru/index.php?action=history&amp;feed=atom&amp;title=%D0%9C%D0%BE%D0%B4%D1%83%D0%BB%D1%8C%3A%D0%9F%D1%80%D0%B8%D0%B7%D1%91%D1%80%D1%8B</id>
	<title>Модуль:Призёры - История изменений</title>
	<link rel="self" type="application/atom+xml" href="https://narofootball.ru/index.php?action=history&amp;feed=atom&amp;title=%D0%9C%D0%BE%D0%B4%D1%83%D0%BB%D1%8C%3A%D0%9F%D1%80%D0%B8%D0%B7%D1%91%D1%80%D1%8B"/>
	<link rel="alternate" type="text/html" href="https://narofootball.ru/index.php?title=%D0%9C%D0%BE%D0%B4%D1%83%D0%BB%D1%8C:%D0%9F%D1%80%D0%B8%D0%B7%D1%91%D1%80%D1%8B&amp;action=history"/>
	<updated>2026-11-08T14:59:24Z</updated>
	<subtitle>История изменений этой страницы в вики</subtitle>
	<generator>MediaWiki 1.43.9</generator>
	<entry>
		<id>https://narofootball.ru/index.php?title=%D0%9C%D0%BE%D0%B4%D1%83%D0%BB%D1%8C:%D0%9F%D1%80%D0%B8%D0%B7%D1%91%D1%80%D1%8B&amp;diff=4846&amp;oldid=prev</id>
		<title>Andrew: 1 версия импортирована</title>
		<link rel="alternate" type="text/html" href="https://narofootball.ru/index.php?title=%D0%9C%D0%BE%D0%B4%D1%83%D0%BB%D1%8C:%D0%9F%D1%80%D0%B8%D0%B7%D1%91%D1%80%D1%8B&amp;diff=4846&amp;oldid=prev"/>
		<updated>2026-10-22T20:03:31Z</updated>

		<summary type="html">&lt;p&gt;1 версия импортирована&lt;/p&gt;
&lt;table style=&quot;background-color: #fff; color: #202122;&quot; data-mw=&quot;interface&quot;&gt;
				&lt;tr class=&quot;diff-title&quot; lang=&quot;ru&quot;&gt;
				&lt;td colspan=&quot;1&quot; style=&quot;background-color: #fff; color: #202122; text-align: center;&quot;&gt;← Предыдущая версия&lt;/td&gt;
				&lt;td colspan=&quot;1&quot; style=&quot;background-color: #fff; color: #202122; text-align: center;&quot;&gt;Версия от 20:03, 22 октября 2026&lt;/td&gt;
				&lt;/tr&gt;&lt;tr&gt;&lt;td colspan=&quot;2&quot; class=&quot;diff-notice&quot; lang=&quot;ru&quot;&gt;&lt;div class=&quot;mw-diff-empty&quot;&gt;(нет различий)&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;</summary>
		<author><name>Andrew</name></author>
	</entry>
	<entry>
		<id>https://narofootball.ru/index.php?title=%D0%9C%D0%BE%D0%B4%D1%83%D0%BB%D1%8C:%D0%9F%D1%80%D0%B8%D0%B7%D1%91%D1%80%D1%8B&amp;diff=4845&amp;oldid=prev</id>
		<title>imported&gt;Импорт: Модуль таблиц призёров: по сезонам и зачёт медалей/кубков</title>
		<link rel="alternate" type="text/html" href="https://narofootball.ru/index.php?title=%D0%9C%D0%BE%D0%B4%D1%83%D0%BB%D1%8C:%D0%9F%D1%80%D0%B8%D0%B7%D1%91%D1%80%D1%8B&amp;diff=4845&amp;oldid=prev"/>
		<updated>2026-10-22T17:15:49Z</updated>

		<summary type="html">&lt;p&gt;Модуль таблиц призёров: по сезонам и зачёт медалей/кубков&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Новая страница&lt;/b&gt;&lt;/p&gt;&lt;div&gt;--[[&lt;br /&gt;
	Модуль:Призёры&lt;br /&gt;
&lt;br /&gt;
	Из одного списка сезонов строит две таблицы:&lt;br /&gt;
	  * призёры по сезонам                     — вид = сезоны;&lt;br /&gt;
	  * сводная таблица медалей или кубков     — вид = зачёт (по умолчанию).&lt;br /&gt;
&lt;br /&gt;
	Данные: каждый сезон — с новой строки, ячейки через «|»:&lt;br /&gt;
	  | Сезон | 1-е место | 2-е место | 3-е место | доп. столбцы…&lt;br /&gt;
	Если строк с данными нет, модуль ищет таблицу призёров в тексте статьи&lt;br /&gt;
	(параметр «источник», по умолчанию — текущая страница).&lt;br /&gt;
&lt;br /&gt;
	Подробно — на странице «Шаблон:Призёры/doc».&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
local p = {}&lt;br /&gt;
&lt;br /&gt;
local CLUBS_MODULE = &amp;#039;Module:Призёры/Клубы&amp;#039;&lt;br /&gt;
&lt;br /&gt;
-- Столбцы для каждого типа турнира.&lt;br /&gt;
local PRESETS = {&lt;br /&gt;
	medals = {&lt;br /&gt;
		labels = { &amp;#039;Золото&amp;#039;, &amp;#039;Серебро&amp;#039;, &amp;#039;Бронза&amp;#039; },          -- сводная таблица&lt;br /&gt;
		places = { &amp;#039;1-е место&amp;#039;, &amp;#039;2-е место&amp;#039;, &amp;#039;3-е место&amp;#039; },  -- таблица по сезонам&lt;br /&gt;
		colors = { &amp;#039;gold&amp;#039;, &amp;#039;silver&amp;#039;, &amp;#039;#CC9966&amp;#039; },&lt;br /&gt;
		total  = &amp;#039;Всего медалей&amp;#039;,&lt;br /&gt;
	},&lt;br /&gt;
	cup = {&lt;br /&gt;
		labels = { &amp;#039;Победитель&amp;#039;, &amp;#039;Финалист&amp;#039; },&lt;br /&gt;
		places = { &amp;#039;Победитель&amp;#039;, &amp;#039;Финалист&amp;#039; },&lt;br /&gt;
		colors = { &amp;#039;gold&amp;#039;, &amp;#039;silver&amp;#039; },&lt;br /&gt;
		total  = &amp;#039;Всего кубков&amp;#039;,&lt;br /&gt;
	},&lt;br /&gt;
	supercup = {&lt;br /&gt;
		labels = { &amp;#039;Победитель&amp;#039;, &amp;#039;Финалист&amp;#039; },&lt;br /&gt;
		places = { &amp;#039;Победитель&amp;#039;, &amp;#039;Финалист&amp;#039; },&lt;br /&gt;
		colors = { &amp;#039;gold&amp;#039;, &amp;#039;silver&amp;#039; },&lt;br /&gt;
		total  = &amp;#039;Всего суперкубков&amp;#039;,&lt;br /&gt;
	},&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
-- Добавляется к кубку при «малый кубок = да».&lt;br /&gt;
local SMALL_CUP = {&lt;br /&gt;
	labels = { &amp;#039;Малый кубок&amp;#039;, &amp;#039;Финалист МК&amp;#039; },&lt;br /&gt;
	colors = { &amp;#039;gold&amp;#039;, &amp;#039;silver&amp;#039; },&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local TYPES = {&lt;br /&gt;
	[&amp;#039;медали&amp;#039;] = &amp;#039;medals&amp;#039;, [&amp;#039;чемпионат&amp;#039;] = &amp;#039;medals&amp;#039;, [&amp;#039;первенство&amp;#039;] = &amp;#039;medals&amp;#039;,&lt;br /&gt;
	[&amp;#039;кубок&amp;#039;] = &amp;#039;cup&amp;#039;, [&amp;#039;кубки&amp;#039;] = &amp;#039;cup&amp;#039;,&lt;br /&gt;
	[&amp;#039;суперкубок&amp;#039;] = &amp;#039;supercup&amp;#039;, [&amp;#039;суперкубки&amp;#039;] = &amp;#039;supercup&amp;#039;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
-- Ключи записаны так, как их возвращает norm(): строчные, «ё» → «е».&lt;br /&gt;
local VIEWS = {&lt;br /&gt;
	[&amp;#039;зачет&amp;#039;] = &amp;#039;standings&amp;#039;, [&amp;#039;итоги&amp;#039;] = &amp;#039;standings&amp;#039;, [&amp;#039;итог&amp;#039;] = &amp;#039;standings&amp;#039;,&lt;br /&gt;
	[&amp;#039;сезоны&amp;#039;] = &amp;#039;seasons&amp;#039;, [&amp;#039;сезон&amp;#039;] = &amp;#039;seasons&amp;#039;, [&amp;#039;по сезонам&amp;#039;] = &amp;#039;seasons&amp;#039;, [&amp;#039;призеры&amp;#039;] = &amp;#039;seasons&amp;#039;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local PARAMS = {&lt;br /&gt;
	&amp;#039;вид&amp;#039;, &amp;#039;тип&amp;#039;, &amp;#039;малый кубок&amp;#039;, &amp;#039;доп&amp;#039;, &amp;#039;названия&amp;#039;, &amp;#039;названия мест&amp;#039;, &amp;#039;цвета&amp;#039;,&lt;br /&gt;
	&amp;#039;всего&amp;#039;, &amp;#039;столбец сезона&amp;#039;, &amp;#039;столбец клуба&amp;#039;, &amp;#039;сортировка&amp;#039;, &amp;#039;равные&amp;#039;,&lt;br /&gt;
	&amp;#039;учтены&amp;#039;, &amp;#039;жирный&amp;#039;, &amp;#039;клубы&amp;#039;, &amp;#039;источник&amp;#039;, &amp;#039;номер таблицы&amp;#039;,&lt;br /&gt;
}&lt;br /&gt;
local KNOWN = {}&lt;br /&gt;
for _, name in ipairs(PARAMS) do&lt;br /&gt;
	KNOWN[name] = true&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Ячейка места с таким текстом — награда не вручалась или пока неизвестна.&lt;br /&gt;
local DASHES = { [&amp;#039;—&amp;#039;] = true, [&amp;#039;–&amp;#039;] = true, [&amp;#039;-&amp;#039;] = true, [&amp;#039;−&amp;#039;] = true, [&amp;#039;?&amp;#039;] = true }&lt;br /&gt;
&lt;br /&gt;
-- Ссылки в эти пространства имён — не клубы (флаги, категории).&lt;br /&gt;
local SKIP_NS = {&lt;br /&gt;
	[&amp;#039;файл&amp;#039;] = true, [&amp;#039;file&amp;#039;] = true, [&amp;#039;image&amp;#039;] = true, [&amp;#039;изображение&amp;#039;] = true,&lt;br /&gt;
	[&amp;#039;категория&amp;#039;] = true, [&amp;#039;category&amp;#039;] = true, [&amp;#039;media&amp;#039;] = true, [&amp;#039;медиа&amp;#039;] = true,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------&lt;br /&gt;
-- Строки&lt;br /&gt;
------------------------------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
local function trim(s)&lt;br /&gt;
	if s == nil then&lt;br /&gt;
		return &amp;#039;&amp;#039;&lt;br /&gt;
	end&lt;br /&gt;
	return mw.text.trim(s)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Для сравнения слов: без пробелов по краям, строчные, «ё» → «е».&lt;br /&gt;
local function norm(s)&lt;br /&gt;
	return (mw.ustring.lower(trim(s)):gsub(&amp;#039;ё&amp;#039;, &amp;#039;е&amp;#039;))&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function isYes(s)&lt;br /&gt;
	local v = norm(s)&lt;br /&gt;
	return v == &amp;#039;да&amp;#039; or v == &amp;#039;yes&amp;#039; or v == &amp;#039;1&amp;#039; or v == &amp;#039;true&amp;#039; or v == &amp;#039;вкл&amp;#039;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function isNo(s)&lt;br /&gt;
	local v = norm(s)&lt;br /&gt;
	return v == &amp;#039;нет&amp;#039; or v == &amp;#039;no&amp;#039; or v == &amp;#039;0&amp;#039; or v == &amp;#039;false&amp;#039; or v == &amp;#039;выкл&amp;#039;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- «a; b; c» → { &amp;#039;a&amp;#039;, &amp;#039;b&amp;#039;, &amp;#039;c&amp;#039; }; точку с запятой внутри &amp;amp;nbsp; и т. п. не трогает.&lt;br /&gt;
local function splitList(s)&lt;br /&gt;
	local list = {}&lt;br /&gt;
	if not s then&lt;br /&gt;
		return list&lt;br /&gt;
	end&lt;br /&gt;
	s = s:gsub(&amp;#039;&amp;amp;(#?%w+);&amp;#039;, &amp;#039;&amp;amp;%1\1&amp;#039;)&lt;br /&gt;
	for part in mw.text.gsplit(s, &amp;#039;;&amp;#039;, true) do&lt;br /&gt;
		table.insert(list, trim((part:gsub(&amp;#039;\1&amp;#039;, &amp;#039;;&amp;#039;))))&lt;br /&gt;
	end&lt;br /&gt;
	return list&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Все strip-маркеры строки (так в аргументы попадают &amp;lt;ref&amp;gt; и подобные теги).&lt;br /&gt;
local function markersOf(s)&lt;br /&gt;
	local found = {}&lt;br /&gt;
	for marker in s:gmatch(&amp;#039;\127[^\127]*\127&amp;#039;) do&lt;br /&gt;
		table.insert(found, marker)&lt;br /&gt;
	end&lt;br /&gt;
	return table.concat(found)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Текст без маркеров, вики-разметки выделения и HTML-тегов.&lt;br /&gt;
local function stripFormatting(s)&lt;br /&gt;
	s = mw.text.killMarkers(s or &amp;#039;&amp;#039;)&lt;br /&gt;
	s = s:gsub(&amp;quot;&amp;#039;&amp;#039;&amp;#039;?&amp;quot;, &amp;#039;&amp;#039;)&lt;br /&gt;
	s = s:gsub(&amp;#039;&amp;lt;[^&amp;gt;]*&amp;gt;&amp;#039;, &amp;#039;&amp;#039;)&lt;br /&gt;
	return trim(s)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Видимый текст: ссылки заменены их подписями.&lt;br /&gt;
local function plainText(s)&lt;br /&gt;
	s = mw.text.killMarkers(s or &amp;#039;&amp;#039;)&lt;br /&gt;
	s = s:gsub(&amp;#039;%[%[([^%]]-)%]%]&amp;#039;, function (inner)&lt;br /&gt;
		local label = inner:match(&amp;#039;^[^|]*|(.*)$&amp;#039;)&lt;br /&gt;
		return label or inner&lt;br /&gt;
	end)&lt;br /&gt;
	s = s:gsub(&amp;#039;%[%a+://[^%s%]]+%s+([^%]]+)%]&amp;#039;, &amp;#039;%1&amp;#039;)&lt;br /&gt;
	return stripFormatting(s)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Название страницы в каноническом виде: пробелы вместо «_», первая буква заглавная.&lt;br /&gt;
local function normTitle(t)&lt;br /&gt;
	t = t:gsub(&amp;#039;_&amp;#039;, &amp;#039; &amp;#039;)&lt;br /&gt;
	t = t:gsub(&amp;#039;#.*$&amp;#039;, &amp;#039;&amp;#039;)&lt;br /&gt;
	t = t:gsub(&amp;#039;%s+&amp;#039;, &amp;#039; &amp;#039;)&lt;br /&gt;
	t = trim(t)&lt;br /&gt;
	t = t:gsub(&amp;#039;^:%s*&amp;#039;, &amp;#039;&amp;#039;)&lt;br /&gt;
	if t == &amp;#039;&amp;#039; then&lt;br /&gt;
		return t&lt;br /&gt;
	end&lt;br /&gt;
	return mw.ustring.upper(mw.ustring.sub(t, 1, 1)) .. mw.ustring.sub(t, 2)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Первая ссылка на статью в тексте: цель и подпись.&lt;br /&gt;
local function firstLink(s)&lt;br /&gt;
	for inner in s:gmatch(&amp;#039;%[%[(.-)%]%]&amp;#039;) do&lt;br /&gt;
		local target, label = inner:match(&amp;#039;^([^|]*)|(.*)$&amp;#039;)&lt;br /&gt;
		target = target or inner&lt;br /&gt;
		local ns = target:match(&amp;#039;^%s*:?%s*([^:]+):&amp;#039;)&lt;br /&gt;
		if trim(target) ~= &amp;#039;&amp;#039; and not (ns and SKIP_NS[norm(ns)]) then&lt;br /&gt;
			if label == nil then&lt;br /&gt;
				label = (target:gsub(&amp;#039;#.*$&amp;#039;, &amp;#039;&amp;#039;):gsub(&amp;#039;^%s*:&amp;#039;, &amp;#039;&amp;#039;))&lt;br /&gt;
			elseif trim(label) == &amp;#039;&amp;#039; then&lt;br /&gt;
				label = (target:gsub(&amp;#039;%s*%(.-%)%s*$&amp;#039;, &amp;#039;&amp;#039;))   -- «трюк с вертикальной чертой»&lt;br /&gt;
			end&lt;br /&gt;
			return target, stripFormatting(label)&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function makeLink(target, label)&lt;br /&gt;
	if target == label then&lt;br /&gt;
		return &amp;#039;[[&amp;#039; .. target .. &amp;#039;]]&amp;#039;&lt;br /&gt;
	end&lt;br /&gt;
	return &amp;#039;[[&amp;#039; .. target .. &amp;#039;|&amp;#039; .. label .. &amp;#039;]]&amp;#039;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------&lt;br /&gt;
-- Список клубов&lt;br /&gt;
------------------------------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
local function loadClubs(name, warnings)&lt;br /&gt;
	name = name or CLUBS_MODULE&lt;br /&gt;
	if not name:find(&amp;#039;:&amp;#039;, 1, true) then&lt;br /&gt;
		name = &amp;#039;Module:&amp;#039; .. name&lt;br /&gt;
	end&lt;br /&gt;
	local ok, data = pcall(mw.loadData, name)&lt;br /&gt;
	if ok and type(data) == &amp;#039;table&amp;#039; then&lt;br /&gt;
		return data&lt;br /&gt;
	end&lt;br /&gt;
	if not ok and not tostring(data):find(&amp;#039;not found&amp;#039;, 1, true) then&lt;br /&gt;
		table.insert(warnings, &amp;#039;не удалось прочитать список клубов «&amp;#039; .. name .. &amp;#039;»: &amp;#039; .. tostring(data))&lt;br /&gt;
	end&lt;br /&gt;
	return {}&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------&lt;br /&gt;
-- Аргументы&lt;br /&gt;
------------------------------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
-- Ячейки по строкам: строка заканчивается там, где ячейка заканчивается переводом строки.&lt;br /&gt;
local function splitRows(positional)&lt;br /&gt;
	local rows, current = {}, {}&lt;br /&gt;
	for _, raw in ipairs(positional) do&lt;br /&gt;
		local endsLine = raw:find(&amp;#039;\n[ \t]*$&amp;#039;) ~= nil&lt;br /&gt;
		table.insert(current, trim((raw:gsub(&amp;#039;%s*\n%s*&amp;#039;, &amp;#039; &amp;#039;))))&lt;br /&gt;
		if endsLine then&lt;br /&gt;
			table.insert(rows, current)&lt;br /&gt;
			current = {}&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	if #current &amp;gt; 0 then&lt;br /&gt;
		table.insert(rows, current)&lt;br /&gt;
	end&lt;br /&gt;
	local filled = {}&lt;br /&gt;
	for _, row in ipairs(rows) do&lt;br /&gt;
		for _, cell in ipairs(row) do&lt;br /&gt;
			if cell ~= &amp;#039;&amp;#039; then&lt;br /&gt;
				table.insert(filled, row)&lt;br /&gt;
				break&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return filled&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function viewOf(s)&lt;br /&gt;
	if s == nil or trim(s) == &amp;#039;&amp;#039; then&lt;br /&gt;
		return nil&lt;br /&gt;
	end&lt;br /&gt;
	return VIEWS[norm(s)]&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function collectArgs(frame, forcedView, warnings)&lt;br /&gt;
	local fargs = frame.args&lt;br /&gt;
	local parent = frame:getParent()&lt;br /&gt;
	local pargs = parent and parent.args or {}&lt;br /&gt;
&lt;br /&gt;
	-- Параметр из вызова на странице важнее того, что записан в шаблоне.&lt;br /&gt;
	local function get(name)&lt;br /&gt;
		local v = pargs[name]&lt;br /&gt;
		if v == nil or trim(v) == &amp;#039;&amp;#039; then&lt;br /&gt;
			v = fargs[name]&lt;br /&gt;
		end&lt;br /&gt;
		v = trim(v)&lt;br /&gt;
		return v ~= &amp;#039;&amp;#039; and v or nil&lt;br /&gt;
	end&lt;br /&gt;
	local settings = {}&lt;br /&gt;
	for _, name in ipairs(PARAMS) do&lt;br /&gt;
		settings[name] = get(name)&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	local fromTemplate = fargs[1] ~= nil&lt;br /&gt;
	local source = fromTemplate and fargs or pargs&lt;br /&gt;
	local positional, i = {}, 1&lt;br /&gt;
	while source[i] ~= nil do&lt;br /&gt;
		positional[i] = source[i]&lt;br /&gt;
		i = i + 1&lt;br /&gt;
	end&lt;br /&gt;
	-- «сезоны» / «зачёт» самой первой ячейкой — это вид таблицы, а не данные. Убираем его&lt;br /&gt;
	-- до разбивки на строки: если следом на той же строке идёт именованный параметр&lt;br /&gt;
	-- ({{Призёры|сезоны|доп=…), перевод строки достаётся параметру, а не этому слову.&lt;br /&gt;
	local leadingView = viewOf(positional[1])&lt;br /&gt;
	if leadingView then&lt;br /&gt;
		table.remove(positional, 1)&lt;br /&gt;
	end&lt;br /&gt;
	local rows = splitRows(positional)&lt;br /&gt;
&lt;br /&gt;
	local wrongView = settings[&amp;#039;вид&amp;#039;]&lt;br /&gt;
	if not wrongView and fromTemplate and trim(pargs[1]) ~= &amp;#039;&amp;#039; then&lt;br /&gt;
		wrongView = trim(pargs[1])   -- {{Призёры/Чемпионат 7х7|…}}: безымянный параметр — только вид&lt;br /&gt;
	end&lt;br /&gt;
	if wrongView and not viewOf(wrongView) then&lt;br /&gt;
		table.insert(warnings, &amp;#039;неизвестный вид «&amp;#039; .. wrongView .. &amp;#039;»: ожидается «зачёт» или «сезоны».&amp;#039;)&lt;br /&gt;
	end&lt;br /&gt;
	local pageView = viewOf(pargs[&amp;#039;вид&amp;#039;])&lt;br /&gt;
	if fromTemplate then&lt;br /&gt;
		pageView = pageView or viewOf(pargs[1])&lt;br /&gt;
	else&lt;br /&gt;
		pageView = pageView or leadingView&lt;br /&gt;
	end&lt;br /&gt;
	local templateView = viewOf(fargs[&amp;#039;вид&amp;#039;]) or (fromTemplate and leadingView or nil)&lt;br /&gt;
	settings.view = forcedView or pageView or templateView or &amp;#039;standings&amp;#039;&lt;br /&gt;
&lt;br /&gt;
	local function scan(args)&lt;br /&gt;
		for name in pairs(args) do&lt;br /&gt;
			if type(name) == &amp;#039;string&amp;#039; and not KNOWN[name] then&lt;br /&gt;
				table.insert(warnings, &amp;#039;неизвестный параметр «&amp;#039; .. name .. &amp;#039;». Если это ячейка, &amp;#039;&lt;br /&gt;
					.. &amp;#039;в которой есть знак «=», запишите его как {{=}} или &amp;amp;amp;#61;.&amp;#039;)&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	scan(fargs)&lt;br /&gt;
	if parent then&lt;br /&gt;
		scan(pargs)&lt;br /&gt;
	end&lt;br /&gt;
	return rows, settings&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function makeConfig(settings, warnings)&lt;br /&gt;
	local typeName = &amp;#039;medals&amp;#039;&lt;br /&gt;
	if settings[&amp;#039;тип&amp;#039;] then&lt;br /&gt;
		typeName = TYPES[norm(settings[&amp;#039;тип&amp;#039;])]&lt;br /&gt;
		if not typeName then&lt;br /&gt;
			table.insert(warnings, &amp;#039;неизвестный тип «&amp;#039; .. settings[&amp;#039;тип&amp;#039;]&lt;br /&gt;
				.. &amp;#039;»: ожидается «медали», «кубок» или «суперкубок».&amp;#039;)&lt;br /&gt;
			typeName = &amp;#039;medals&amp;#039;&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	local preset = PRESETS[typeName]&lt;br /&gt;
	local cfg = { labels = {}, places = {}, colors = {} }&lt;br /&gt;
	for i = 1, #preset.labels do&lt;br /&gt;
		cfg.labels[i] = preset.labels[i]&lt;br /&gt;
		cfg.places[i] = preset.places[i]&lt;br /&gt;
		cfg.colors[i] = preset.colors[i]&lt;br /&gt;
	end&lt;br /&gt;
	if typeName ~= &amp;#039;medals&amp;#039; and settings[&amp;#039;малый кубок&amp;#039;] and isYes(settings[&amp;#039;малый кубок&amp;#039;]) then&lt;br /&gt;
		for i = 1, #SMALL_CUP.labels do&lt;br /&gt;
			table.insert(cfg.labels, SMALL_CUP.labels[i])&lt;br /&gt;
			table.insert(cfg.places, SMALL_CUP.labels[i])&lt;br /&gt;
			table.insert(cfg.colors, SMALL_CUP.colors[i])&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	cfg.n = #cfg.labels&lt;br /&gt;
&lt;br /&gt;
	local function override(list, param)&lt;br /&gt;
		for i, value in ipairs(splitList(settings[param])) do&lt;br /&gt;
			if i &amp;lt;= cfg.n and value ~= &amp;#039;&amp;#039; then&lt;br /&gt;
				list[i] = value&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	override(cfg.labels, &amp;#039;названия&amp;#039;)&lt;br /&gt;
	override(cfg.places, &amp;#039;названия мест&amp;#039;)&lt;br /&gt;
	override(cfg.colors, &amp;#039;цвета&amp;#039;)&lt;br /&gt;
&lt;br /&gt;
	cfg.total = settings[&amp;#039;всего&amp;#039;] or preset.total&lt;br /&gt;
	cfg.seasonHeader = settings[&amp;#039;столбец сезона&amp;#039;] or &amp;#039;Сезон&amp;#039;&lt;br /&gt;
	cfg.clubHeader = settings[&amp;#039;столбец клуба&amp;#039;] or &amp;#039;Клуб&amp;#039;&lt;br /&gt;
	cfg.extras = splitList(settings[&amp;#039;доп&amp;#039;])&lt;br /&gt;
	cfg.extrasGiven = settings[&amp;#039;доп&amp;#039;] ~= nil&lt;br /&gt;
	cfg.bold = not (settings[&amp;#039;жирный&amp;#039;] and isNo(settings[&amp;#039;жирный&amp;#039;]))&lt;br /&gt;
	cfg.note = settings[&amp;#039;учтены&amp;#039;] ~= nil and isYes(settings[&amp;#039;учтены&amp;#039;])&lt;br /&gt;
&lt;br /&gt;
	local order = norm(settings[&amp;#039;сортировка&amp;#039;] or &amp;#039;&amp;#039;)&lt;br /&gt;
	if order == &amp;#039;&amp;#039; or order:find(&amp;#039;сумм&amp;#039;) then&lt;br /&gt;
		cfg.olympic = false&lt;br /&gt;
	elseif order:find(&amp;#039;олимп&amp;#039;) or order:find(&amp;#039;золот&amp;#039;) or order:find(&amp;#039;мест&amp;#039;) or order:find(&amp;#039;побед&amp;#039;) then&lt;br /&gt;
		cfg.olympic = true&lt;br /&gt;
	else&lt;br /&gt;
		table.insert(warnings, &amp;#039;неизвестная сортировка «&amp;#039; .. settings[&amp;#039;сортировка&amp;#039;]&lt;br /&gt;
			.. &amp;#039;»: ожидается «по сумме» или «олимпийская».&amp;#039;)&lt;br /&gt;
		cfg.olympic = false&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	local equal = norm(settings[&amp;#039;равные&amp;#039;] or &amp;#039;&amp;#039;)&lt;br /&gt;
	cfg.alphabetical = equal:find(&amp;#039;алфав&amp;#039;) ~= nil&lt;br /&gt;
	if equal ~= &amp;#039;&amp;#039; and not cfg.alphabetical and not equal:find(&amp;#039;поряд&amp;#039;) then&lt;br /&gt;
		table.insert(warnings, &amp;#039;неизвестное значение «равные = &amp;#039; .. settings[&amp;#039;равные&amp;#039;]&lt;br /&gt;
			.. &amp;#039;»: ожидается «по алфавиту» или «по порядку».&amp;#039;)&lt;br /&gt;
	end&lt;br /&gt;
	return cfg&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------&lt;br /&gt;
-- Таблица из текста статьи&lt;br /&gt;
------------------------------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
-- Делит строку по разделителям, не заглядывая внутрь [[…]] и {{…}}.&lt;br /&gt;
local function splitTop(s, seps)&lt;br /&gt;
	local parts, start, i, n = {}, 1, 1, #s&lt;br /&gt;
	local links, templates = 0, 0&lt;br /&gt;
	while i &amp;lt;= n do&lt;br /&gt;
		local two = s:sub(i, i + 1)&lt;br /&gt;
		if two == &amp;#039;[[&amp;#039; then&lt;br /&gt;
			links = links + 1&lt;br /&gt;
			i = i + 2&lt;br /&gt;
		elseif two == &amp;#039;]]&amp;#039; and links &amp;gt; 0 then&lt;br /&gt;
			links = links - 1&lt;br /&gt;
			i = i + 2&lt;br /&gt;
		elseif two == &amp;#039;{{&amp;#039; then&lt;br /&gt;
			templates = templates + 1&lt;br /&gt;
			i = i + 2&lt;br /&gt;
		elseif two == &amp;#039;}}&amp;#039; and templates &amp;gt; 0 then&lt;br /&gt;
			templates = templates - 1&lt;br /&gt;
			i = i + 2&lt;br /&gt;
		else&lt;br /&gt;
			local sep&lt;br /&gt;
			if links == 0 and templates == 0 then&lt;br /&gt;
				for _, candidate in ipairs(seps) do&lt;br /&gt;
					if s:sub(i, i + #candidate - 1) == candidate then&lt;br /&gt;
						sep = candidate&lt;br /&gt;
						break&lt;br /&gt;
					end&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
			if sep then&lt;br /&gt;
				table.insert(parts, s:sub(start, i - 1))&lt;br /&gt;
				i = i + #sep&lt;br /&gt;
				start = i&lt;br /&gt;
			else&lt;br /&gt;
				i = i + 1&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	table.insert(parts, s:sub(start))&lt;br /&gt;
	return parts&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Вики-таблицы верхнего уровня: строки с сырыми ячейками.&lt;br /&gt;
local function parseTables(text)&lt;br /&gt;
	local tables, current, depth = {}, nil, 0&lt;br /&gt;
	for line in (text .. &amp;#039;\n&amp;#039;):gmatch(&amp;#039;([^\n]*)\n&amp;#039;) do&lt;br /&gt;
		local l = trim(line)&lt;br /&gt;
		if l:sub(1, 2) == &amp;#039;{|&amp;#039; then&lt;br /&gt;
			depth = depth + 1&lt;br /&gt;
			if depth == 1 then&lt;br /&gt;
				current = { rows = {}, newRow = true }&lt;br /&gt;
			end&lt;br /&gt;
		elseif depth &amp;gt; 0 and l:sub(1, 2) == &amp;#039;|}&amp;#039; then&lt;br /&gt;
			depth = depth - 1&lt;br /&gt;
			if depth == 0 then&lt;br /&gt;
				table.insert(tables, current)&lt;br /&gt;
				current = nil&lt;br /&gt;
			end&lt;br /&gt;
		elseif depth == 1 then&lt;br /&gt;
			local row = current.rows[#current.rows]&lt;br /&gt;
			local first = l:sub(1, 1)&lt;br /&gt;
			if l:sub(1, 2) == &amp;#039;|-&amp;#039; then&lt;br /&gt;
				current.newRow = true&lt;br /&gt;
			elseif l:sub(1, 2) == &amp;#039;|+&amp;#039; then&lt;br /&gt;
				-- подпись таблицы&lt;br /&gt;
			elseif first == &amp;#039;|&amp;#039; or first == &amp;#039;!&amp;#039; then&lt;br /&gt;
				if current.newRow or not row then&lt;br /&gt;
					row = { cells = {}, header = true }&lt;br /&gt;
					table.insert(current.rows, row)&lt;br /&gt;
					current.newRow = false&lt;br /&gt;
				end&lt;br /&gt;
				local isHeader = first == &amp;#039;!&amp;#039;&lt;br /&gt;
				for _, cell in ipairs(splitTop(l:sub(2), isHeader and { &amp;#039;!!&amp;#039;, &amp;#039;||&amp;#039; } or { &amp;#039;||&amp;#039; })) do&lt;br /&gt;
					table.insert(row.cells, cell)&lt;br /&gt;
					if not isHeader then&lt;br /&gt;
						row.header = false&lt;br /&gt;
					end&lt;br /&gt;
				end&lt;br /&gt;
			elseif row and #row.cells &amp;gt; 0 then&lt;br /&gt;
				row.cells[#row.cells] = row.cells[#row.cells] .. &amp;#039;\n&amp;#039; .. l&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return tables&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function attrNumber(attrs, name)&lt;br /&gt;
	return tonumber(attrs:lower():match(name .. &amp;#039;%s*=%s*[&amp;quot;\&amp;#039;]?%s*(%d+)&amp;#039;))&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Раскладывает ячейки по столбцам с учётом rowspan и colspan.&lt;br /&gt;
local function tableGrid(tbl)&lt;br /&gt;
	local pending = {}&lt;br /&gt;
	for _, row in ipairs(tbl.rows) do&lt;br /&gt;
		local out, col = {}, 1&lt;br /&gt;
		local function skipSpanned()&lt;br /&gt;
			while pending[col] do&lt;br /&gt;
				local span = pending[col]&lt;br /&gt;
				out[col] = span.text&lt;br /&gt;
				span.left = span.left - 1&lt;br /&gt;
				if span.left == 0 then&lt;br /&gt;
					pending[col] = nil&lt;br /&gt;
				end&lt;br /&gt;
				col = col + 1&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		for _, cellText in ipairs(row.cells) do&lt;br /&gt;
			skipSpanned()&lt;br /&gt;
			local parts = splitTop(cellText, { &amp;#039;|&amp;#039; })&lt;br /&gt;
			local attrs, content = &amp;#039;&amp;#039;, cellText&lt;br /&gt;
			if #parts &amp;gt; 1 then&lt;br /&gt;
				attrs = parts[1]&lt;br /&gt;
				content = table.concat(parts, &amp;#039;|&amp;#039;, 2)&lt;br /&gt;
			end&lt;br /&gt;
			content = trim(content)&lt;br /&gt;
			local rowspan = attrNumber(attrs, &amp;#039;rowspan&amp;#039;) or 1&lt;br /&gt;
			local colspan = attrNumber(attrs, &amp;#039;colspan&amp;#039;) or 1&lt;br /&gt;
			for k = 0, colspan - 1 do&lt;br /&gt;
				local text = content&lt;br /&gt;
				if colspan &amp;gt; 1 and not row.header then&lt;br /&gt;
					-- Объединённая по горизонтали ячейка («не проводился») — пояснение, а не клуб.&lt;br /&gt;
					text = (k == 0 and content ~= &amp;#039;&amp;#039;) and (&amp;#039;(&amp;#039; .. content .. &amp;#039;)&amp;#039;) or &amp;#039;&amp;#039;&lt;br /&gt;
				end&lt;br /&gt;
				out[col + k] = text&lt;br /&gt;
				if rowspan &amp;gt; 1 then&lt;br /&gt;
					pending[col + k] = { text = text, left = rowspan - 1 }&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
			col = col + colspan&lt;br /&gt;
		end&lt;br /&gt;
		skipSpanned()&lt;br /&gt;
		row.grid = out&lt;br /&gt;
		row.width = col - 1&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function readSourceTable(frame, settings, cfg, warnings)&lt;br /&gt;
	local name = settings[&amp;#039;источник&amp;#039;]&lt;br /&gt;
	local title = name and mw.title.new(name) or mw.title.getCurrentTitle()&lt;br /&gt;
	if not title then&lt;br /&gt;
		return nil, nil, &amp;#039;не удалось разобрать название страницы «&amp;#039; .. name .. &amp;#039;».&amp;#039;&lt;br /&gt;
	end&lt;br /&gt;
	local text = title:getContent()&lt;br /&gt;
	if not text then&lt;br /&gt;
		return nil, nil, &amp;#039;страница «&amp;#039; .. title.prefixedText .. &amp;#039;» не найдена.&amp;#039;&lt;br /&gt;
	end&lt;br /&gt;
	text = text:gsub(&amp;#039;&amp;lt;!%-%-.-%-%-&amp;gt;&amp;#039;, &amp;#039;&amp;#039;)&lt;br /&gt;
	text = text:gsub(&amp;#039;&amp;lt;[Rr][Ee][Ff][^&amp;gt;]-/&amp;gt;&amp;#039;, &amp;#039;&amp;#039;)&lt;br /&gt;
	text = text:gsub(&amp;#039;&amp;lt;[Rr][Ee][Ff][%s&amp;gt;].-&amp;lt;/[Rr][Ee][Ff]%s*&amp;gt;&amp;#039;, &amp;#039;&amp;#039;)&lt;br /&gt;
&lt;br /&gt;
	local wanted = tonumber(settings[&amp;#039;номер таблицы&amp;#039;] or &amp;#039;&amp;#039;)&lt;br /&gt;
	local chosen&lt;br /&gt;
	for i, tbl in ipairs(parseTables(text)) do&lt;br /&gt;
		tableGrid(tbl)&lt;br /&gt;
		if wanted then&lt;br /&gt;
			if i == wanted then&lt;br /&gt;
				chosen = tbl&lt;br /&gt;
			end&lt;br /&gt;
		else&lt;br /&gt;
			for _, row in ipairs(tbl.rows) do&lt;br /&gt;
				local second = row.header and row.grid[2]&lt;br /&gt;
				if second then&lt;br /&gt;
					second = norm(stripFormatting(second))&lt;br /&gt;
					if second == norm(cfg.places[1]) or second == norm(cfg.labels[1]) then&lt;br /&gt;
						chosen = tbl&lt;br /&gt;
						break&lt;br /&gt;
					end&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		if chosen then&lt;br /&gt;
			break&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	if not chosen then&lt;br /&gt;
		if wanted then&lt;br /&gt;
			return nil, nil, &amp;#039;на странице «&amp;#039; .. title.prefixedText .. &amp;#039;» нет таблицы № &amp;#039; .. wanted .. &amp;#039;.&amp;#039;&lt;br /&gt;
		end&lt;br /&gt;
		return nil, nil, &amp;#039;на странице «&amp;#039; .. title.prefixedText .. &amp;#039;» не найдена таблица призёров &amp;#039;&lt;br /&gt;
			.. &amp;#039;(во втором столбце заголовка ожидается «&amp;#039; .. cfg.places[1] .. &amp;#039;»). &amp;#039;&lt;br /&gt;
			.. &amp;#039;Укажите «номер таблицы» или впишите данные строками.&amp;#039;&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	local rows, header = {}, nil&lt;br /&gt;
	for _, row in ipairs(chosen.rows) do&lt;br /&gt;
		if row.header then&lt;br /&gt;
			header = row   -- при двухэтажной шапке нужен нижний этаж&lt;br /&gt;
		else&lt;br /&gt;
			local cells, filled = {}, false&lt;br /&gt;
			for c = 1, row.width do&lt;br /&gt;
				local value = row.grid[c] or &amp;#039;&amp;#039;&lt;br /&gt;
				if value:find(&amp;#039;{{&amp;#039;, 1, true) then&lt;br /&gt;
					value = frame:preprocess(value)&lt;br /&gt;
				end&lt;br /&gt;
				cells[c] = trim(value)&lt;br /&gt;
				filled = filled or cells[c] ~= &amp;#039;&amp;#039;&lt;br /&gt;
			end&lt;br /&gt;
			if filled then&lt;br /&gt;
				table.insert(rows, cells)&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	local extras = {}&lt;br /&gt;
	if header then&lt;br /&gt;
		-- Столбцы мест должны называться так, как ждёт «тип»: иначе, например, «Счёт»&lt;br /&gt;
		-- молча засчитался бы как «Малый кубок».&lt;br /&gt;
		local wrong = {}&lt;br /&gt;
		for j = 1, cfg.n do&lt;br /&gt;
			local h = stripFormatting(header.grid[1 + j] or &amp;#039;&amp;#039;)&lt;br /&gt;
			if h ~= &amp;#039;&amp;#039; and norm(h) ~= norm(cfg.places[j]) and norm(h) ~= norm(cfg.labels[j]) then&lt;br /&gt;
				table.insert(wrong, (1 + j) .. &amp;#039;-й столбец называется «&amp;#039; .. h .. &amp;#039;», а ожидается «&amp;#039;&lt;br /&gt;
					.. cfg.places[j] .. &amp;#039;»&amp;#039;)&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		if #wrong &amp;gt; 0 then&lt;br /&gt;
			table.insert(warnings, &amp;#039;в таблице на странице «&amp;#039; .. title.prefixedText .. &amp;#039;» &amp;#039;&lt;br /&gt;
				.. table.concat(wrong, &amp;#039;; &amp;#039;) .. &amp;#039;. Проверьте «тип» и «малый кубок» &amp;#039;&lt;br /&gt;
				.. &amp;#039;или задайте «названия мест» как в шапке таблицы.&amp;#039;)&lt;br /&gt;
		end&lt;br /&gt;
		for c = cfg.n + 2, header.width do&lt;br /&gt;
			table.insert(extras, header.grid[c] or &amp;#039;&amp;#039;)&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return rows, extras&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------&lt;br /&gt;
-- Сезоны и клубы&lt;br /&gt;
------------------------------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
local function parseTeam(part, clubs)&lt;br /&gt;
	local clean = trim(mw.text.killMarkers(part))&lt;br /&gt;
	if clean == &amp;#039;&amp;#039; or DASHES[clean] then&lt;br /&gt;
		return nil&lt;br /&gt;
	end&lt;br /&gt;
	local team = { raw = part, markers = markersOf(part) }&lt;br /&gt;
	local target, label = firstLink(clean)&lt;br /&gt;
	if target then&lt;br /&gt;
		team.target = normTitle(target)&lt;br /&gt;
		team.label = label ~= &amp;#039;&amp;#039; and label or team.target&lt;br /&gt;
		team.linked = true&lt;br /&gt;
	else&lt;br /&gt;
		team.label = stripFormatting(clean)&lt;br /&gt;
		if team.label == &amp;#039;&amp;#039; or DASHES[team.label] then&lt;br /&gt;
			return nil&lt;br /&gt;
		end&lt;br /&gt;
		local known = clubs[team.label]&lt;br /&gt;
		if type(known) == &amp;#039;string&amp;#039; and trim(known) ~= &amp;#039;&amp;#039; then&lt;br /&gt;
			team.target = normTitle(known)&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return team&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local BR = &amp;#039;&amp;lt;[Bb][Rr]%s*/?%s*&amp;gt;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
local function parseCell(raw, clubs)&lt;br /&gt;
	local cell = { raw = raw, parts = {} }&lt;br /&gt;
	local inner = stripFormatting(raw):match(&amp;#039;^%((.*)%)$&amp;#039;)&lt;br /&gt;
	if inner then&lt;br /&gt;
		cell.note = trim(inner) .. markersOf(raw)&lt;br /&gt;
		return cell&lt;br /&gt;
	end&lt;br /&gt;
	local pos = 1&lt;br /&gt;
	while true do&lt;br /&gt;
		local a, b = raw:find(BR, pos)&lt;br /&gt;
		local piece = raw:sub(pos, a and a - 1 or nil)&lt;br /&gt;
		table.insert(cell.parts, { raw = piece, team = parseTeam(piece, clubs) })&lt;br /&gt;
		if not a then&lt;br /&gt;
			break&lt;br /&gt;
		end&lt;br /&gt;
		pos = b + 1&lt;br /&gt;
	end&lt;br /&gt;
	return cell&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function buildSeasons(rows, cfg, clubs)&lt;br /&gt;
	local seasons = {}&lt;br /&gt;
	for _, cells in ipairs(rows) do&lt;br /&gt;
		local season = { label = cells[1] or &amp;#039;&amp;#039;, places = {}, extras = {} }&lt;br /&gt;
		for j = 1, cfg.n do&lt;br /&gt;
			season.places[j] = parseCell(cells[1 + j] or &amp;#039;&amp;#039;, clubs)&lt;br /&gt;
		end&lt;br /&gt;
		for e = 1, #cfg.extras do&lt;br /&gt;
			season.extras[e] = cells[1 + cfg.n + e] or &amp;#039;&amp;#039;&lt;br /&gt;
		end&lt;br /&gt;
		table.insert(seasons, season)&lt;br /&gt;
	end&lt;br /&gt;
	return seasons&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function eachTeam(seasons, n, fn)&lt;br /&gt;
	for _, season in ipairs(seasons) do&lt;br /&gt;
		for j = 1, n do&lt;br /&gt;
			for _, part in ipairs(season.places[j].parts) do&lt;br /&gt;
				if part.team then&lt;br /&gt;
					fn(part.team, season, j)&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Клуб, записанный текстом, получает ссылку, если в данных он уже встречался ссылкой&lt;br /&gt;
-- с той же подписью (и только с одной целью). Затем — ключи и вид ячеек.&lt;br /&gt;
local function resolveTeams(seasons, n)&lt;br /&gt;
	local byLabel = {}&lt;br /&gt;
	eachTeam(seasons, n, function (team)&lt;br /&gt;
		if team.linked then&lt;br /&gt;
			byLabel[team.label] = byLabel[team.label] or {}&lt;br /&gt;
			byLabel[team.label][team.target] = true&lt;br /&gt;
		end&lt;br /&gt;
	end)&lt;br /&gt;
	eachTeam(seasons, n, function (team)&lt;br /&gt;
		if not team.target and byLabel[team.label] then&lt;br /&gt;
			local only, count = nil, 0&lt;br /&gt;
			for target in pairs(byLabel[team.label]) do&lt;br /&gt;
				only = target&lt;br /&gt;
				count = count + 1&lt;br /&gt;
			end&lt;br /&gt;
			if count == 1 then&lt;br /&gt;
				team.target = only&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		team.key = team.target or (&amp;#039;\1&amp;#039; .. team.label)&lt;br /&gt;
		if team.linked or not team.target then&lt;br /&gt;
			team.display = team.raw&lt;br /&gt;
		else&lt;br /&gt;
			local link = makeLink(team.target, team.label)&lt;br /&gt;
			local a, b = team.raw:find(team.label, 1, true)&lt;br /&gt;
			if a then&lt;br /&gt;
				team.display = team.raw:sub(1, a - 1) .. link .. team.raw:sub(b + 1)&lt;br /&gt;
			else&lt;br /&gt;
				team.display = link .. team.markers&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function tally(seasons, n)&lt;br /&gt;
	local byKey, list = {}, {}&lt;br /&gt;
	eachTeam(seasons, n, function (team, season, j)&lt;br /&gt;
		local s = byKey[team.key]&lt;br /&gt;
		if not s then&lt;br /&gt;
			s = { counts = {}, total = 0, first = #list + 1 }&lt;br /&gt;
			for c = 1, n do&lt;br /&gt;
				s.counts[c] = 0&lt;br /&gt;
			end&lt;br /&gt;
			byKey[team.key] = s&lt;br /&gt;
			table.insert(list, s)&lt;br /&gt;
		end&lt;br /&gt;
		s.counts[j] = s.counts[j] + 1&lt;br /&gt;
		s.total = s.total + 1&lt;br /&gt;
		s.label = team.label            -- подпись — из последнего упоминания&lt;br /&gt;
		s.target = team.target or s.target&lt;br /&gt;
		season.counted = true&lt;br /&gt;
	end)&lt;br /&gt;
	return list&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function rank(list, cfg)&lt;br /&gt;
	table.sort(list, function (a, b)&lt;br /&gt;
		if not cfg.olympic and a.total ~= b.total then&lt;br /&gt;
			return a.total &amp;gt; b.total&lt;br /&gt;
		end&lt;br /&gt;
		for c = 1, cfg.n do&lt;br /&gt;
			if a.counts[c] ~= b.counts[c] then&lt;br /&gt;
				return a.counts[c] &amp;gt; b.counts[c]&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		if cfg.alphabetical then&lt;br /&gt;
			local x, y = norm(a.label), norm(b.label)&lt;br /&gt;
			if x ~= y then&lt;br /&gt;
				return x &amp;lt; y&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		return a.first &amp;lt; b.first&lt;br /&gt;
	end)&lt;br /&gt;
	local place = 0&lt;br /&gt;
	for i, s in ipairs(list) do&lt;br /&gt;
		local same = i &amp;gt; 1&lt;br /&gt;
		if same then&lt;br /&gt;
			for c = 1, cfg.n do&lt;br /&gt;
				if list[i - 1].counts[c] ~= s.counts[c] then&lt;br /&gt;
					same = false&lt;br /&gt;
					break&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		if not same then&lt;br /&gt;
			place = place + 1&lt;br /&gt;
		end&lt;br /&gt;
		s.place = place&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function seasonsNote(seasons)&lt;br /&gt;
	local first, last&lt;br /&gt;
	for _, season in ipairs(seasons) do&lt;br /&gt;
		if season.counted then&lt;br /&gt;
			local label = plainText(season.label)&lt;br /&gt;
			if label ~= &amp;#039;&amp;#039; then&lt;br /&gt;
				first = first or label&lt;br /&gt;
				last = label&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	if not first then&lt;br /&gt;
		return nil&lt;br /&gt;
	end&lt;br /&gt;
	local y1, y2 = tonumber(first:match(&amp;#039;%d%d%d%d&amp;#039;)), tonumber(last:match(&amp;#039;%d%d%d%d&amp;#039;))&lt;br /&gt;
	if y1 and y2 and y1 &amp;gt; y2 then&lt;br /&gt;
		first, last = last, first&lt;br /&gt;
	end&lt;br /&gt;
	if first == last then&lt;br /&gt;
		return &amp;#039;Учтён сезон &amp;#039; .. first&lt;br /&gt;
	end&lt;br /&gt;
	return &amp;#039;Учтены сезоны с &amp;#039; .. first .. &amp;#039; по &amp;#039; .. last&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------&lt;br /&gt;
-- Вывод&lt;br /&gt;
------------------------------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
local function newTable()&lt;br /&gt;
	return mw.html.create(&amp;#039;table&amp;#039;)&lt;br /&gt;
		:addClass(&amp;#039;wikitable&amp;#039;)&lt;br /&gt;
		:css(&amp;#039;text-align&amp;#039;, &amp;#039;center&amp;#039;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function colorHeader(row, text, color)&lt;br /&gt;
	local th = row:tag(&amp;#039;th&amp;#039;):wikitext(text)&lt;br /&gt;
	if color and color ~= &amp;#039;&amp;#039; then&lt;br /&gt;
		th:css(&amp;#039;background-color&amp;#039;, color)&lt;br /&gt;
	end&lt;br /&gt;
	return th&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function renderStandings(list, cfg)&lt;br /&gt;
	local root = newTable()&lt;br /&gt;
	local head = root:tag(&amp;#039;tr&amp;#039;)&lt;br /&gt;
	head:tag(&amp;#039;th&amp;#039;)&lt;br /&gt;
	head:tag(&amp;#039;th&amp;#039;):css(&amp;#039;width&amp;#039;, &amp;#039;18em&amp;#039;):css(&amp;#039;height&amp;#039;, &amp;#039;2em&amp;#039;):wikitext(cfg.clubHeader)&lt;br /&gt;
	for j = 1, cfg.n do&lt;br /&gt;
		colorHeader(head, cfg.labels[j], cfg.colors[j])&lt;br /&gt;
	end&lt;br /&gt;
	head:tag(&amp;#039;th&amp;#039;):css(&amp;#039;width&amp;#039;, &amp;#039;8em&amp;#039;):css(&amp;#039;height&amp;#039;, &amp;#039;2em&amp;#039;):wikitext(cfg.total)&lt;br /&gt;
&lt;br /&gt;
	local function number(v)&lt;br /&gt;
		return cfg.bold and (&amp;quot;&amp;#039;&amp;#039;&amp;#039;&amp;quot; .. v .. &amp;quot;&amp;#039;&amp;#039;&amp;#039;&amp;quot;) or tostring(v)&lt;br /&gt;
	end&lt;br /&gt;
	if #list == 0 then&lt;br /&gt;
		root:tag(&amp;#039;tr&amp;#039;):tag(&amp;#039;td&amp;#039;):attr(&amp;#039;colspan&amp;#039;, cfg.n + 3):wikitext(&amp;#039;Призёров пока нет&amp;#039;)&lt;br /&gt;
	end&lt;br /&gt;
	local i = 1&lt;br /&gt;
	while i &amp;lt;= #list do&lt;br /&gt;
		local j = i&lt;br /&gt;
		while j &amp;lt; #list and list[j + 1].place == list[i].place do&lt;br /&gt;
			j = j + 1&lt;br /&gt;
		end&lt;br /&gt;
		for k = i, j do&lt;br /&gt;
			local s = list[k]&lt;br /&gt;
			local tr = root:tag(&amp;#039;tr&amp;#039;)&lt;br /&gt;
			if k == i then&lt;br /&gt;
				local td = tr:tag(&amp;#039;td&amp;#039;):wikitext(s.place)&lt;br /&gt;
				if j &amp;gt; i then&lt;br /&gt;
					td:attr(&amp;#039;rowspan&amp;#039;, j - i + 1)&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
			local name = s.target and makeLink(s.target, s.label) or s.label&lt;br /&gt;
			tr:tag(&amp;#039;td&amp;#039;):css(&amp;#039;text-align&amp;#039;, &amp;#039;left&amp;#039;):wikitext(name)&lt;br /&gt;
			for c = 1, cfg.n do&lt;br /&gt;
				tr:tag(&amp;#039;td&amp;#039;):wikitext(number(s.counts[c]))&lt;br /&gt;
			end&lt;br /&gt;
			tr:tag(&amp;#039;td&amp;#039;):wikitext(number(s.total))&lt;br /&gt;
		end&lt;br /&gt;
		i = j + 1&lt;br /&gt;
	end&lt;br /&gt;
	return tostring(root)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function cellText(cell)&lt;br /&gt;
	if cell.note then&lt;br /&gt;
		return cell.note&lt;br /&gt;
	end&lt;br /&gt;
	local out = {}&lt;br /&gt;
	for _, part in ipairs(cell.parts) do&lt;br /&gt;
		table.insert(out, part.team and part.team.display or part.raw)&lt;br /&gt;
	end&lt;br /&gt;
	return table.concat(out, &amp;#039;&amp;lt;br&amp;gt;&amp;#039;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function renderSeasons(seasons, cfg)&lt;br /&gt;
	local root = newTable()&lt;br /&gt;
	local head = root:tag(&amp;#039;tr&amp;#039;)&lt;br /&gt;
	head:tag(&amp;#039;th&amp;#039;):wikitext(cfg.seasonHeader)&lt;br /&gt;
	for j = 1, cfg.n do&lt;br /&gt;
		colorHeader(head, cfg.places[j], cfg.colors[j])&lt;br /&gt;
	end&lt;br /&gt;
	for e = 1, #cfg.extras do&lt;br /&gt;
		head:tag(&amp;#039;th&amp;#039;):wikitext(cfg.extras[e])&lt;br /&gt;
	end&lt;br /&gt;
	for _, season in ipairs(seasons) do&lt;br /&gt;
		local tr = root:tag(&amp;#039;tr&amp;#039;)&lt;br /&gt;
		tr:tag(&amp;#039;td&amp;#039;):wikitext(season.label)&lt;br /&gt;
		local first = season.places[1]&lt;br /&gt;
		local merged = first and first.note ~= nil&lt;br /&gt;
		for j = 2, cfg.n do&lt;br /&gt;
			if season.places[j].raw ~= &amp;#039;&amp;#039; then&lt;br /&gt;
				merged = false&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		if merged then&lt;br /&gt;
			tr:tag(&amp;#039;td&amp;#039;):attr(&amp;#039;colspan&amp;#039;, cfg.n):wikitext(first.note)&lt;br /&gt;
		else&lt;br /&gt;
			for j = 1, cfg.n do&lt;br /&gt;
				tr:tag(&amp;#039;td&amp;#039;):wikitext(cellText(season.places[j]))&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		for e = 1, #cfg.extras do&lt;br /&gt;
			tr:tag(&amp;#039;td&amp;#039;):wikitext(season.extras[e])&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return tostring(root)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function messages(list)&lt;br /&gt;
	local out = {}&lt;br /&gt;
	for _, text in ipairs(list) do&lt;br /&gt;
		if mw.addWarning then&lt;br /&gt;
			mw.addWarning(&amp;#039;Призёры: &amp;#039; .. text)&lt;br /&gt;
		end&lt;br /&gt;
		table.insert(out, &amp;#039;&amp;lt;div class=&amp;quot;error&amp;quot;&amp;gt;Призёры: &amp;#039; .. text .. &amp;#039;&amp;lt;/div&amp;gt;&amp;#039;)&lt;br /&gt;
	end&lt;br /&gt;
	return #out &amp;gt; 0 and (table.concat(out, &amp;#039;\n&amp;#039;) .. &amp;#039;\n&amp;#039;) or &amp;#039;&amp;#039;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------&lt;br /&gt;
-- Точки входа&lt;br /&gt;
------------------------------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
local function main(frame, forcedView)&lt;br /&gt;
	local warnings = {}&lt;br /&gt;
	local rows, settings = collectArgs(frame, forcedView, warnings)&lt;br /&gt;
	local cfg = makeConfig(settings, warnings)&lt;br /&gt;
	local clubs = loadClubs(settings[&amp;#039;клубы&amp;#039;], warnings)&lt;br /&gt;
&lt;br /&gt;
	if #rows &amp;gt; 0 then&lt;br /&gt;
		for _, cells in ipairs(rows) do&lt;br /&gt;
			if #cells &amp;gt; 1 + cfg.n + #cfg.extras then&lt;br /&gt;
				table.insert(warnings, &amp;#039;в сезоне «&amp;#039; .. plainText(cells[1]) .. &amp;#039;» лишние ячейки (всего &amp;#039;&lt;br /&gt;
					.. #cells .. &amp;#039;, а столбцов &amp;#039; .. (1 + cfg.n + #cfg.extras) .. &amp;#039;). &amp;#039;&lt;br /&gt;
					.. &amp;#039;Каждый сезон пишите с новой строки, дополнительные столбцы объявите в «доп».&amp;#039;)&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	else&lt;br /&gt;
		local extras, err&lt;br /&gt;
		rows, extras, err = readSourceTable(frame, settings, cfg, warnings)&lt;br /&gt;
		if err then&lt;br /&gt;
			return messages(warnings) .. &amp;#039;&amp;lt;strong class=&amp;quot;error&amp;quot;&amp;gt;Призёры: &amp;#039; .. err .. &amp;#039;&amp;lt;/strong&amp;gt;&amp;#039;&lt;br /&gt;
		end&lt;br /&gt;
		if not cfg.extrasGiven then&lt;br /&gt;
			cfg.extras = extras&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	if #rows == 0 then&lt;br /&gt;
		return messages(warnings) .. &amp;#039;&amp;lt;strong class=&amp;quot;error&amp;quot;&amp;gt;Призёры: нет данных — &amp;#039;&lt;br /&gt;
			.. &amp;#039;впишите сезоны (каждый с новой строки) или укажите «источник».&amp;lt;/strong&amp;gt;&amp;#039;&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	local seasons = buildSeasons(rows, cfg, clubs)&lt;br /&gt;
	resolveTeams(seasons, cfg.n)&lt;br /&gt;
	local list = tally(seasons, cfg.n)&lt;br /&gt;
&lt;br /&gt;
	-- Один клуб, записанный ссылками на разные статьи, попал бы в зачёт двумя строками.&lt;br /&gt;
	local byName = {}&lt;br /&gt;
	for _, s in ipairs(list) do&lt;br /&gt;
		local name = norm(s.label)&lt;br /&gt;
		local other = byName[name]&lt;br /&gt;
		if other then&lt;br /&gt;
			table.insert(warnings, &amp;#039;в зачёте две строки «&amp;#039; .. s.label .. &amp;#039;»: ссылки ведут на «&amp;#039;&lt;br /&gt;
				.. (other.target or &amp;#039;без ссылки&amp;#039;) .. &amp;#039;» и «&amp;#039; .. (s.target or &amp;#039;без ссылки&amp;#039;)&lt;br /&gt;
				.. &amp;#039;». Если это один клуб, запишите его везде одинаково.&amp;#039;)&lt;br /&gt;
		else&lt;br /&gt;
			byName[name] = s&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	if settings.view == &amp;#039;seasons&amp;#039; then&lt;br /&gt;
		return messages(warnings) .. renderSeasons(seasons, cfg)&lt;br /&gt;
	end&lt;br /&gt;
	rank(list, cfg)&lt;br /&gt;
	local out = renderStandings(list, cfg)&lt;br /&gt;
	if cfg.note then&lt;br /&gt;
		local note = seasonsNote(seasons)&lt;br /&gt;
		if note then&lt;br /&gt;
			out = out .. &amp;#039;\n&amp;lt;small&amp;gt;&amp;#039; .. note .. &amp;#039;&amp;lt;/small&amp;gt;&amp;#039;&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return messages(warnings) .. out&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function p.main(frame)&lt;br /&gt;
	return main(frame)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function p.seasons(frame)&lt;br /&gt;
	return main(frame, &amp;#039;seasons&amp;#039;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function p.standings(frame)&lt;br /&gt;
	return main(frame, &amp;#039;standings&amp;#039;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- {{#invoke:Призёры|клуб|ДСК}} → [[ДСК (футбольный клуб, Наро-Фоминск)|ДСК]]&lt;br /&gt;
-- Второй параметр — другая подпись. Пригодится в доп. столбцах (бомбардир и т. п.).&lt;br /&gt;
function p.club(frame)&lt;br /&gt;
	local args = frame.args&lt;br /&gt;
	if args[1] == nil then&lt;br /&gt;
		local parent = frame:getParent()&lt;br /&gt;
		args = parent and parent.args or {}&lt;br /&gt;
	end&lt;br /&gt;
	local name = trim(args[1])&lt;br /&gt;
	local label = trim(args[2])&lt;br /&gt;
	if label == &amp;#039;&amp;#039; then&lt;br /&gt;
		label = name&lt;br /&gt;
	end&lt;br /&gt;
	local listName = trim(args[&amp;#039;клубы&amp;#039;])&lt;br /&gt;
	local target = loadClubs(listName ~= &amp;#039;&amp;#039; and listName or nil, {})[name]&lt;br /&gt;
	if type(target) == &amp;#039;string&amp;#039; and trim(target) ~= &amp;#039;&amp;#039; then&lt;br /&gt;
		return makeLink(normTitle(target), label)&lt;br /&gt;
	end&lt;br /&gt;
	return label&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
p[&amp;#039;таблица&amp;#039;] = p.main&lt;br /&gt;
p[&amp;#039;сезоны&amp;#039;] = p.seasons&lt;br /&gt;
p[&amp;#039;зачёт&amp;#039;] = p.standings&lt;br /&gt;
p[&amp;#039;зачет&amp;#039;] = p.standings&lt;br /&gt;
p[&amp;#039;клуб&amp;#039;] = p.club&lt;br /&gt;
&lt;br /&gt;
return p&lt;/div&gt;</summary>
		<author><name>imported&gt;Импорт</name></author>
	</entry>
</feed>