Список старейших котов в мире
- 1 year ago
- 0
- 0
Документацию смотри на странице шаблона {{ Список старейших людей в мире }}
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'):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 = '#99FF99' 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'):wikitext(person.name) row:attr('bgcolor', color) 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'):wikitext(sexCell) end row:tag('td'):wikitext(formatDateSortable(person.dateBirth)) if person.dateDeath ~= nil then -- Если мертв или мертва row:tag('td'):wikitext(formatDateSortable(person.dateDeath)) else -- Если жив или жива if string.match(sexCell, 'жен.') == 'жен.' then row:tag('td'):wikitext('Жива') elseif string.match(sexCell, 'муж.') == 'муж.' then row:tag('td'):wikitext('Жив') else row:tag('td'):wikitext('Жив(а)') end end row:tag('td'):wikitext(ageInYearsAndDaysFormat(person.age)) if person.dateAssignment ~= nil then row:tag('td'):wikitext(formatDateSortable(person.dateAssignment)) end if person.placeOfBirth ~= nil or person.placeOfDeath ~= nil or person.placeOfResidence ~= nil then row:tag('td'):wikitext(person.placeOfBirth or '') if person.placeOfResidence ~= nil then row:tag('td'):wikitext(person.placeOfResidence or '') else row:tag('td'):wikitext(person.placeOfDeath or '') end else row:tag('td'):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('#99FF99', 'Ныне живущие:', numberOfLiving) end if keyLast - numberOfLiving > 0 then legend = legend .. p.legendTable('#F9F9F9', 'Умершие:', keyLast - numberOfLiving) end legend = legend .. p.legendTable('#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