Модуль:Список старейших людей в мире (Bk;rl,&Vhnvkt vmgjywon] lZ;yw f bnjy)
Перейти к навигации
Перейти к поиску
Документацию смотри на странице шаблона {{Список старейших людей в мире}}
require("strict")
local getArgs = require('Модуль:Arguments').getArgs
local displayMax = 500 -- Показать максимальное количество записей
local language = mw.getContentLanguage()
local root = ''
local legend = ''
local legendSpan = ''
local reference = ''
--------------------------------------------------------------------------------
-- Локальные вспомогательные функции
--------------------------------------------------------------------------------
-- Возвращает true, если год является или был високосным
local function isLeapYear(year)
return year % 4 == 0 and (year % 100 ~= 0 or year % 400 == 0)
end
local function gsd(year, month, day)
-- Возвращает день по григорианскому календарю (целое число >= 1) для заданной даты,
-- или возвращает nil, если дата недействительна (проверяется только этот год >= 1).
-- Это количество дней с 1 года нашей эры (нулевого года нет).
if year < 1 then
return nil
end
local floor = math.floor
local days_this_year = (month - 1) * 30.5 + day
if month > 2 then
if isLeapYear(year) then
days_this_year = days_this_year - 1
else
days_this_year = days_this_year - 2
end
if month > 8 then
days_this_year = days_this_year + 0.9
end
end
days_this_year = floor(days_this_year + 0.5)
year = year - 1
local days_from_past_years = year * 365
+ floor(year / 4)
- floor(year / 100)
+ floor(year / 400)
return days_from_past_years + days_this_year
end
-- Возвращает величину числа, определяемого как целая часть log10 абсолютного значения числа.
local function magnitude(number)
if number == 0 then return nil end
return math.floor(math.log10(math.abs(number)))
end
-- Возвращает ключ сортировки для числа
local function sortableNumber(number)
local result = ''
if number > 0 then
result = result .. '3&' .. tostring(500 + magnitude(number))
elseif number < 0 then
result = result .. '1&' .. tostring(500 - magnitude(number))
else -- 0
result = result .. '2'
end
if number >= 0 then
result = result .. '&' .. tostring(number)
else -- negative
result = result .. '&' .. tostring(100000 * (10 + number / 10 ^ magnitude(number)))
end
return '<span data-sort-value="' .. result .. '&">' .. tostring(number) .. '</span>';
end
--------------------------------------------------------------------------------
-- Функции для расчета возраста
--------------------------------------------------------------------------------
-- Возвращает возраст в годах и днях. Входные данные — это две даты в числовой форме, возвращенные из os.time.
local function ageInYearsAndDays(date2N, date1N)
local date1 = os.date('!*t', date1N)
local date2 = os.date('!*t', date2N)
local age = {}
age.years = date2.year - date1.year;
if date2.month < date1.month or date2.month == date1.month and date2.day < date1.day then
age.years = age.years - 1
end
local year = date2.year
if date2.month < date1.month or date2.month == date1.month and date2.day < date1.day then
year = year - 1
end
age.days = gsd(date2.year, date2.month, date2.day) - gsd(year, date1.month, date1.day)
return age
end
--------------------------------------------------------------------------------
-- Функции для сравнения возрастов
--------------------------------------------------------------------------------
-- Возвращает < 0 если ageA < ageB, 0 если ageA == ageB, > 0 если ageA > ageB
local function compareAges(ageA, ageB)
if ageA.years ~= ageB.years then
if ageA.years < ageB.years then return -1 end
return 1
end
if ageA.days ~= ageB.days then
if ageA.days < ageB.days then return -1 end
return 1
end
return 0 -- equal
end
-- Возвращает true, если ageA == ageB
local function equalAges(ageA, ageB)
return compareAges(ageA, ageB) == 0
end
--------------------------------------------------------------------------------
-- Функции для отображения возраста
--------------------------------------------------------------------------------
-- Возвращает дату в виде сортируемой строки с возрастом в годах и днях.
local function ageInYearsAndDaysFormat(age)
local result = sortableNumber(age.years) .. ' ' .. mw.getContentLanguage():plural(age.years, 'год', 'года', 'лет')
return result .. ' ' .. sortableNumber(age.days) .. ' ' .. mw.getContentLanguage():plural(age.days, 'день', 'дня', 'дней')
end
--------------------------------------------------------------------------------
-- ОСНОВНАЯ ЧАСТЬ
--------------------------------------------------------------------------------
-- Возвращает true, если personA старше, чем personB
local function comparePersons(personA, personB)
local diffAge = compareAges(personA.age, personB.age)
if diffAge == 0 then -- Ровесники, пусть "выиграет" тот, кто родился первым
if personA.dateBirth ~= personB.dateBirth then return personA.dateBirth < personB.dateBirth end
end
return diffAge > 0
end
local function formatDateIso8061(year, month, day)
return tostring(year) .. '-' .. string.format('%02d', month) .. '-' .. string.format('%02d', day)
end
local function formatDateSortable(dateN)
local date = os.date('!*t', dateN)
local dateIso = formatDateIso8061(date.year, date.month, date.day)
local result = '<span style="display:none">' .. dateIso .. '</span>'
result = result .. tostring(date.day) .. ' ' .. language:formatDate('xg', dateIso) .. ' ' .. tostring(date.year)
return result
end
local p = {}
-- Ожидает таблицу персон в качестве входных данных.
-- Возвращает строку с отсортированным и отформатированным выводом (в виде сортируемой таблицы)
function p.displaySortedTable(persons, args)
if #persons == 0 then return '' end
table.sort(persons, comparePersons) -- Сортировка по возрасту и дате рождения
local dateNow = os.date('!*t')
local dateIsoNow = formatDateIso8061(dateNow.year, dateNow.month, dateNow.day)
local root = p.createTable()
p.captionTable()
root:node('<tr>') --начало tr
p.cellTh('№')
p.cellTh('Имя')
if args['Безпола'] == nil then
p.cellTh('Пол') end
p.cellTh('Дата рождения')
p.cellTh('Дата смерти')
p.cellTh('Достигнутый возраст<br><small>на ' .. language:formatDate('j xg Y', dateIsoNow) .. '</small>')
if persons[1]['dateAssignment'] ~= nil then
p.cellTh('Дата присвоения<br>статуса') end
if persons[1]['placeOfBirth'] ~= nil or persons[1]['placeOfDeath'] ~= nil or persons[1]['placeOfResidence'] ~= nil then
p.cellTh('Место рождения')
p.cellTh('Место смерти<br>или<br>проживания')
else
p.cellTh('Страна')
end
root:node('</tr>') --конец tr
local lastAge = nil
local rank, keyLast = 0, 0
local numberOfVerifiedGRG, numberOfVerifiedESO, numberOfVerifiedGWR = 0, 0, 0
local numberOfContested, numberOfLiving = 0, 0
local row, rankCell = nil, nil
for key, person in ipairs(persons) do
if lastAge == nil or not equalAges(lastAge, person.age) then -- Новый век: повышение ранга
if rankCell ~= nil and key > rank then
rankCell:attr('rowspan', tostring(key - rank))
end
rankCell = nil
if key > displayMax then break end -- Показать максимальное количество записей
rank = key
lastAge = person.age
row = root:tag('tr')
rankCell = row:tag('th'):attr('scope', 'row'):wikitext(tostring(rank))
else
row = root:tag('tr')
end
keyLast = key
row:attr('vertical-align', 'top')
local color = nil
if not person.dateDeath then -- Not dead
numberOfLiving = numberOfLiving + 1
if args['Безцвета'] == nil then color = 'var(--background-color-success-subtle, #d5fdf4); color:inherit' end -- Цвет живых
end
if person.contested then
numberOfContested = numberOfContested + 1
color = '#e9b06b'
end
if person.verifiedGRG then
numberOfVerifiedGRG = numberOfVerifiedGRG + 1
color = '#f9f9f9'
end
if person.verifiedESO then
numberOfVerifiedESO = numberOfVerifiedESO + 1
color = '#ccff00'
end
if person.verifiedGWR then
numberOfVerifiedGWR = numberOfVerifiedGWR + 1
color = '#f0dc82'
end
row:tag('td'):css('background', color):wikitext(person.name)
local sexFix = { ['м']='муж.', ['ж']='жен.' }
local sexCell = mw.ustring.sub(person.sex, 1, 1)
local sexCell = sexFix[mw.ustring.lower(sexCell)] or ''
if args['Безпола'] == nil then
row:tag('td'):css('background', color):wikitext(sexCell)
end
row:tag('td'):css('background', color):wikitext(formatDateSortable(person.dateBirth))
if person.dateDeath ~= nil then -- Если мертв или мертва
row:tag('td'):css('background', color):wikitext(formatDateSortable(person.dateDeath))
else
-- Если жив или жива
if string.match(sexCell, 'жен.') == 'жен.' then
row:tag('td'):css('background', color):wikitext('Жива')
elseif string.match(sexCell, 'муж.') == 'муж.' then
row:tag('td'):css('background', color):wikitext('Жив')
else
row:tag('td'):css('background', color):wikitext('Жив(а)')
end
end
row:tag('td'):css('background', color):wikitext(ageInYearsAndDaysFormat(person.age))
if person.dateAssignment ~= nil then
row:tag('td'):css('background', color):wikitext(formatDateSortable(person.dateAssignment))
end
if person.placeOfBirth ~= nil or person.placeOfDeath ~= nil or person.placeOfResidence ~= nil then
row:tag('td'):css('background', color):wikitext(person.placeOfBirth or '')
if person.placeOfResidence ~= nil then
row:tag('td'):css('background', color):wikitext(person.placeOfResidence or '')
else
row:tag('td'):css('background', color):wikitext(person.placeOfDeath or '')
end
else
row:tag('td'):css('background', color):wikitext(person.nation or '')
end
end
if rankCell ~= nil and keyLast > rank then
rankCell:attr('rowspan', tostring(keyLast - rank + 1))
end
root:allDone()
-- Легенда
if numberOfVerifiedGRG > 0 then
legend = legend .. p.legendTable('#f9f9f9', 'Верифицированы GRG:', numberOfVerifiedGRG)
end
if numberOfVerifiedESO > 0 then
legend = legend .. p.legendTable('#ccff00', 'Верифицированы только ESO:', numberOfVerifiedESO)
end
if numberOfVerifiedGWR > 0 then
legend = legend .. p.legendTable('#f0dc82', 'Верифицированы Книгой рекордов Гиннесса:', numberOfVerifiedGWR)
end
if numberOfContested > 0 then
legend = legend .. p.legendTable('#E9B06B', 'Оспариваемые:', numberOfContested)
end
if numberOfLiving > 0 then
legend = legend .. p.legendTable('var(--background-color-success-subtle, #d5fdf4)', 'Ныне живущие:', numberOfLiving)
end
if keyLast - numberOfLiving > 0 then
legend = legend .. p.legendTable('var(--background-color-interactive-subtle, #f8f9fa)', 'Умершие:', keyLast - numberOfLiving)
end
legend = legend .. p.legendTable('var(--background-color-interactive, #eaecf0)', 'Всего:', keyLast)
return tostring(legend) .. tostring(root)
end
--------------------------------------------------------------------------------
-- Ячейка заголовка
function p.cellTh(text)
-- Ячейка
root
:tag('th') --начало th
:attr('scope', 'col')
:wikitext(text)
-- Сноска на примечание
if reference ~= nil and reference ~= '' then
root:wikitext(" " .. p.reference(reference))
end
root:done() --конец th
return tostring(root)
end
--Ячейка ряда строки
function p.cellTd(text)
-- Ячейка
root
:tag('td') --начало td
:wikitext(text)
-- Сноска на примечание
if reference ~= nil and reference ~= '' then
root:wikitext(" " .. p.reference(reference))
end
root:done() --конец td
return tostring(root)
end
--Создание ссылки
function p.reference(reference)
local refspan = mw.html.create('span')
:wikitext(reference)
:css('background-color', 'transparent')
:css('color', 'black')
:css('padding','1px')
:css('display','inline-block')
:css('line-height','50%')
return tostring(refspan)
end
--Легенда таблицы
function p.legendTable(color, text, number)
legendSpan = mw.html.create('span') --начало span 1
legendSpan
:css('font-size', '90%')
:css('margin', '0px 0px 1px 0px')
:css('display', 'block')
:tag('span') --начало span 2
:css('vertical-align', 'top')
:css('border', '1px solid #a2a9b1')
:css('background', color or '')
:css('text-align', 'center')
:attr('title', color or '')
:wikitext(' ')
:done() --конец span 2
:wikitext(' ' .. text or '')
:tag('span') --начало span 3
:css('font-weight', 'bold')
:wikitext(' ' .. number or '')
:done() --конец span 3
:done() --конец span 1
return tostring(legendSpan)
end
--Создание таблица
function p.createTable()
root = mw.html.create('table')
root
:addClass('wikitable sortable')
return root
end
--Заголовок таблицы
function p.captionTable()
local title = mw.title.getCurrentTitle()
local pageTitle = title.text
root
:tag('caption') --начало caption
:tag('span') --начало span
:addClass('noprint purgelink')
:attr('data-pagename', pageTitle)
:css('float', 'right')
:css('clear', 'right')
:css('font-style', 'italic')
:css('font-size', 'xx-small')
:wikitext('[[Special:Purge/' .. pageTitle .. '|Очистить кеш сервера]]')
:done() --конец span
:done() --конец caption
return tostring(root)
end
--Создание флага
function p.createFlag(args)
local strFlag = ''
local argsFlag = mw.text.trim(args) or ''
strFlag = '<span class="nowrap">'
.. mw.getCurrentFrame():expandTemplate{
title = 'Флаг',
args = { argsFlag }
}
.. ' [[' .. argsFlag .. ']]</span>'
return tostring(strFlag)
end
--------------------------------------------------------------------------------
-- Входная строка dateStr ожидается в формате «ГГГГ-ММ-ДД».
-- Возвращает числовое представление даты или nil
local function decodeDate(dateStr)
if dateStr == nil or (#dateStr < 8 and #dateStr > 10) then return nil end
local strings = mw.text.split(dateStr, '-', true)
if strings == nil or #strings ~= 3 then return nil end
local date = {}
date.year = tonumber(strings[1]) or 0
if tonumber(strings[2]) < 1 then
date.month = 1
elseif tonumber(strings[2]) > 12 then
date.month = 12
else
date.month = tonumber(strings[2]) or 1
end
date.day = tonumber(strings[3]) or 1
return os.time(date);
end
--[[ Ввод ожидается в виде таблицы аргументов.
Ожидается запись, разделенная точкой с запятой, для каждого человека в формате:
«Имя и т. д.; Пол; Дата рождения; Дата смерти; Ссылки и т. д.»
Имя и Дата рождения являются обязательными, остальное не являются обязательными.
Даты должны быть в формате «ГГГГ-ММ-ДД». ]]
-- Возвращает таблицу с персонами
local function decodeArgs(args)
local dateNow = os.time()
local persons = {}
for name, value in pairs(args) do
local strings = mw.text.split(value, ';', true)
if strings ~= nil and #strings >= 2 then -- Нужно хотя бы имя и дату рождения
local person = {}
person.name = mw.text.trim(strings[1]) or ''
person.sex = mw.text.trim(strings[2]) or ''
person.dateBirth = decodeDate(strings[3]) -- Дата рождения
if person.dateBirth ~= nil then
person.dateDeath = decodeDate(strings[4]) -- Дата смерти
if person.dateDeath ~= nil or person.dateDeath ~= '' then -- Мертвый
person.age = ageInYearsAndDays(person.dateDeath, person.dateBirth)
else -- Still alive
person.age = ageInYearsAndDays(dateNow, person.dateBirth)
end
if #strings == 6 then strings[5] = mw.text.trim(strings[5]) end
if strings[5] ~= nil and strings[5] ~= '' then
if string.match(strings[5], ',',1) ~= nil then
local country = mw.text.split(strings[5], ',', true)
person.nation = ''
for k=1, #country do
if country[k] == nil then break end
if country[k]:find('МестоРождения:') then
person.placeOfBirth = '[[' .. country[k]:gsub('МестоРождения:','',1) .. ']]'
elseif country[k]:find('МестоСмерти:') then
person.placeOfDeath = '[[' .. country[k]:gsub('МестоСмерти:','',1) .. ']]'
elseif country[k]:find('МестоПроживания:') then
person.placeOfResidence = '[[' .. country[k]:gsub('МестоПроживания:','',1) .. ']]'
else
person.nation = person.nation .. (k >= 2 and ' — ' or '') .. p.createFlag(country[k])
end
end
else
if strings[5]:find('МестоРождения:') then
person.placeOfBirth = '[[' .. strings[5]:gsub('МестоРождения:','',1) .. ']]'
elseif strings[5]:find('МестоСмерти:') then
person.placeOfDeath = '[[' .. strings[5]:gsub('МестоСмерти:','',1) .. ']]'
elseif strings[5]:find('МестоПроживания:') then
person.placeOfResidence = '[[' .. strings[5]:gsub('МестоПроживания:','',1) .. ']]'
else
person.nation = p.createFlag(strings[5]) or ''
end
end
end
if #strings == 6 and strings[6] ~= nil and strings[6] ~= '' then
if strings[6]:find('GRG') then
person.verifiedGRG = true
strings[6] = strings[6]:gsub('GRG','',1)
elseif strings[6]:find('ESO') then
person.verifiedESO = true
strings[6] = strings[6]:gsub('ESO','',1)
elseif strings[6]:find('GWR') then
person.verifiedGWR = true
strings[6] = strings[6]:gsub('GWR','',1)
elseif strings[6]:find('спорный') then
person.contested = true
strings[6] = strings[6]:gsub('спорный','',1)
end
strings[6] = mw.text.trim(strings[6]:gsub(',',''))
if strings[6] ~= '' then
person.dateAssignment = decodeDate(strings[6]) or '' -- Дата присвоения статуса
end
end
table.insert(persons, person)
end
end
end
return persons
end
-- Декодирует аргументы из фрейма и отображает список
local function displaySorted_(args)
local args = getArgs(args)
local persons = decodeArgs(args)
return p.displaySortedTable(persons, args) -- Отображать в виде сортируемой таблицы
end
-- Для использования через шаблон. Использует аргументы от родителя к шаблону
function p.displaySorted(frame)
local args = getArgs(frame, {
--trim = false,
--removeBlanks = false,
parentOnly = true,
wrappers = 'Шаблон:Список старейших людей в мире',
})
return displaySorted_(args)
end
-- Для использования непосредственно с #invoke
function p.displaySorted0(frame)
return displaySorted_(frame.args)
end
return p