Модуль:YearMetaCat2
Модуль используется для навигации и автокатегоризации категорий по годам (для категорий с заголовком, включающим «<число> год/года/годе»).
- Определяет десятилетие и эру (до н. э. / н. э.).
- Работает со странами
- Определяет название страны из заголовка в любом падеже.
- В категориях меняет падеж страны в нужный.
- Определяет, в какой части света (континенте) расположена страна и публикует её/их в нужном падеже.
- Позволяет проверить существование категории или опубликовать замену для неё.
- Добавляет {{автоиндекс}} (появляется от 200 статей, расширенный индекс от 1200 статей).
- Создаёт навигационную линейку по годам, с возможностями:
- Задавать min/max год.
- Указывать количество элементов в линейке.
- Автоматически отслеживает годы существования отдельных стран и выдаёт ошибку при выходе за ограничение.
- Добавляет категории.
Используемые списки:
- Страны и их падежные формы с предлогом: Find country/countries.json.
- Страны по частям света: CountryMetaCat/country-continents.json.
- Годы существования стран: YearMetaCat2/country-years.json.
Использование
{{#invoke:YearMetaCat2|main |Мир <век> века <в стране>!<ключ> |Мир <десятилетие>-х годов по странам!<ключ> |Мир <год> года <страны> |Мир по годам <в части света>!<ключ> }}
<год>
— год числом без слова «год»<десятилетие>
— десятилетие числом (без окончания -е/-х)<век>
— век римскими цифрами без слова «век»<ключ>
— ключ сортировки, н. э. — год, до н. э. — отрицательное число начиная с −10000 (-9999 == 1 год до н. э. −9998 == 2 год до н. э. и т. д.); нужен для корректной сортировки в категориях<страна>
,<страны>
,<в стране>
— страна в именительном, родительном или предложном падеже; для последнего автоматически выбирается и ставится предлог «в/во/на».<часть света>
,<части света>
,<в части света>
— часть света в именительном, родительном или предложном падеже; для последнего автоматически ставится предлог «в».
Вызов ключа части света обрабатывает помещение в соответствующую часть света, а также в некоторые надрегионы. Для отдельных стран работает механизм разделения на несколько частей света, когда страна, расположенная одновременно в двух частях света, будет автоматически получать две категории каждой части света. В случае, если один ключей <часть света> используется только в качестве ключа сортировки, а название категории при этом не меняется, то категория публикуется только один раз с первым ключом сортировки.
Механизм проверки существования категорий
?
— указывается первым символом перед названием категории, которую необходимо проверять на существование. Если категория существует, она публикуется. Если не существует, ищется замена для публикации на следующей строке, которая должна начинаться с символа~
. В случае неудачи категория не публикуется.~
— указывается первым символом перед названием категории, которая публикуется (без проверки на существование) только в том случае, если на предыдущей строке категория с?
не создана. Во всех остальных случаях пропускается.
Для проверок работает механизм раздваивания по частям света. Будет проверяться на существование категория с каждой частью света и подбираться замена для соответствующей (замена также должна содержать ключ <части света>).
Полная версия
{{#invoke:YearMetaCat2|main |Категория 1![ключ сортировки] |?Категория 2![ключ сортировки] |~Категория 3![ключ сортировки] ... |Категория N[...] |min = до какого года рисовать линейку слева, по умолчанию -40000 (0 — рисовать только года нашей эры) |max = до какого года рисовать линейку справа, по умолчанию 2100 |range = сколько десятилетий в линейке слева и справа, по умолчанию 5 |title = заголовок страницы, используемый вместо текущего (для тестов) }}
Категория состоит из 2-х полей, разделенных !
(восклицательным знаком):
- первое — название категории
- второе — ключ сортировки (необязательно)
Примеры:
|Мир по годам!<ключ>
— добавлять категорию «Мир по годам» с ключом сортировки <ключ>
Дополнительные параметры для тестов
|title = заголовок страницы, используемый вместо текущего |noindex = 1 (указывается, если необходимо отключить добавления шаблона индекса) |nonav = 1 (указывается, если необходимо отключить добавления навигационной линейки)
Дополнительные функции
Функция expand
- заменяет
<год>
на текущий, по необходимости добавив «до н. э.» - заменяет
<десятилетие>
на текущее, по необходимости добавив «до н. э.» - заменяет
<век>
на текущий, по необходимости добавив «до н. э.» - заменяет
<ключ>
на ключ сортировки
Страны и части света функция не обрабатывает.
Пример:
{{#invoke:YearMetaCat2|expand|Мир <год> года}}
на странице «К:Земля в 100 году до н. э.» вернёт:
Мир 100 года до н. э.
Категории отслеживания
- Википедия:Страницы с некорректным использованием модуля YearMetaCat2 (6) — отслеживание использований с несуществующими странами или частями света, а также с нарушениями диапазонов в навигационной линейке.
- Шаблоны, использующие модуль YearMetaCat2 (187) — в модуль встроено автодобавление в эту категорию страниц, на которых он используется, при условии что страница является шаблоном. Однако, так как проверка пространства страницы и размещение происходит через код модуля, то необходимо избегать помещения модуля в тег
<includeonly></includeonly>
на странице шаблона. Модуль нужно размещать вне любых подобных тэгов. - Категория:Шаблоны, использующие индекс категории (автоматический)
См. также
- Модуль:DecadeMetaCat — аналог для десятилетий
- Модуль:CenturyMetaCat — аналог для веков
- Модуль:CountryMetaCat — аналог для стран
- Модуль:YearMetaCat — аналог для годов и десятилетий
- Модуль:MetaCatDoc — для документирования шаблонов, использующих этот модуль
---*- mode: lua; coding: utf-8; -*-
local p = {}
-- Переменные
local year -- год, положительное число
local BC -- 0 == н.э. 1 == до н.э.
local templ -- строка-шаблон вида 'Мир в %s году%s'
local title = mw.title.getCurrentTitle().text
-- Опции
local year_min = -40000 -- 0 == только н.э.
local year_max = 2100 -- XXI
local range = 5
-- Импортируемые функции
local getArgs = require('Module:Arguments').getArgs
local sparseIpairs = require('Module:TableTools').sparseIpairs
local toroman = require('Module:Roman').convert
local getStyles = require('Модуль:Индекс категории').getStyles
local gsub = mw.ustring.gsub
local findCountry = require('Модуль:Find country')
-- Инициализация трекера для ошибок
local error_list = {}
local year_range_error = nil -- Переменная для хранения ошибки диапазонов
local country_error_flag = false
------------------ Ошибки ------------------
-- Сбор и обработка ошибок
local function add_error(error_code, additional_info)
local error_specific = {
[1] = 'Ошибка: год не найден.',
[2] = 'Минимальный год, ограниченный шаблоном: ' .. (additional_info or "") .. '.',
[3] = 'Максимальный год, ограниченный шаблоном: ' .. (additional_info or "") .. '.',
[4] = 'Минимальный год для ' .. (additional_info or "") .. '.',
[5] = 'Максимальный год для ' .. (additional_info or "") .. '.',
[6] = 'Ошибка: страна не найдена.',
[7] = 'Ошибка: часть света для страны не найдена.',
[8] = 'Ошибка: обнаружено два года.'
}
if error_code >= 2 and error_code <= 5 then
if not year_range_error then
year_range_error = {message = 'Ошибка: год не попадает в заданный диапазон.', details = {}}
table.insert(error_list, year_range_error)
end
table.insert(year_range_error.details, error_specific[error_code])
else
table.insert(error_list, {message = '<span class="error">' .. error_specific[error_code] .. '</span>'})
end
end
-- Публикация всех ошибок в едином блоке
local function publish_errors()
local error_category = '[[Категория:Википедия:Страницы с некорректным использованием модуля YearMetaCat2]]'
if #error_list == 0 then
return ''
end
local result = '<div class="error-list">'
for _, err in ipairs(error_list) do
if err.details then
result = result .. '<span class="error">' .. err.message
for _, detail in ipairs(err.details) do
result = result .. ' ' .. detail
end
result = result .. '</span>'
else
result = result .. err.message
end
end
result = result .. '</div>'
result = result .. error_category
return result
end
------------------ Считывание и обработка годов ------------------
-- Считывание года из строки
local function get_year(t)
local years = {}
for year in mw.ustring.gmatch(t, '([0-9]+)%s*год') do
table.insert(years, tonumber(year)) -- Преобразование строки в число
end
if #years == 0 then
add_error(1) -- Ошибка "не найден"
return nil
elseif #years > 1 then
add_error(8) -- Ошибка "обнаружено два"
return nil
end
return years[1] -- Возврат единственного найденнего значения
end
-- Замена плейсхолдеров (год, десятилетие, век, ключ) на реальные значения
local function do_expand(s)
-- <год> - год без слова "год"
-- <ключ> - ключ сортировки, н.э. - номер года,
-- до н.э. - отрицательное число начиная с -99 (-99 == 1 год до н.э. -98 == 2 год до н.э. и т.д.)
-- <десятилетие> - десятилетие числом (без окончания -е/-х)
-- <век> - век римскими цифрами
local d = math.floor(year/10)*10 -- Определение десятилетия
local c = toroman(math.floor((year-1)/100)+1) -- Преобразование века в римские цифры
-- Обработка для II века (в/во)
if c == 'II' then
s = gsub(s, ' в <век>', ' во <век>')
end
-- Обработка для 2 года (в/во)
if year == 2 then
s = gsub(s, 'в <год> году', 'во <год> году')
end
if BC == 1 then
s = gsub(s, '<год> (год[ау]?)', year..' %1 до н. э.')
s = gsub(s, '<ключ>', year - 10000) -- Преобразование ключа для до н.э.
s = gsub(s, '<десятилетие>(-[ех] год[ыоа][вх]?)', d..'%1 до н. э.') -- годы/годов/годах
s = gsub(s, '<век> (век[еа]?)', c..' %1 до н. э.')
else
s = gsub(s, '<год>', year)
s = gsub(s, '<ключ>', year)
s = gsub(s, '<десятилетие>', d)
s = gsub(s, '<век>', c)
end
return s
end
------------------ Обработка min/max ------------------
-- Поиск данных о стране в JSON-файле по названию или алиасу
local function find_country_in_json(country_name)
local country_data = mw.loadJsonData('Модуль:YearMetaCat2/country-years.json')
for _, country in ipairs(country_data.countries) do
if country.name == country_name then
return country
end
if country.aliases then
for _, alias in ipairs(country.aliases) do
if alias == country_name then
return country
end
end
end
end
return nil
end
-- Проверка, попадает ли год в диапазон страны или вручную заданные значения
local function check_year_in_bounds(args)
args = args or {}
local country_name = findCountry.findcountryinstring(title)
local country_data = find_country_in_json(country_name)
-- Корректировка для до н.э.
local year_adjusted = (BC == 1) and -year or year
-- Ручные ограничения min и max
local manual_min = tonumber(args['min'])
local manual_max = tonumber(args['max'])
-- Определение активных границ
local effective_min = manual_min or (country_data and country_data.min)
local effective_max = manual_max or (country_data and country_data.max)
-- Проверка минимального значения
if effective_min and year_adjusted < effective_min then
if manual_min then
-- Если задано вручную
add_error(2, tostring(manual_min))
elseif country_data then
-- Если данные из страны
add_error(4, string.format("%s: %d", country_name, country_data.min))
end
end
-- Проверка максимального значения
if effective_max and year_adjusted > effective_max then
if manual_max then
-- Если задано вручную
add_error(3, tostring(manual_max))
elseif country_data then
-- Если данные из страны
add_error(5, string.format("%s: %d", country_name, country_data.max))
end
end
end
------------------ Считывание и обработка стран ------------------
-- Проверка на наличие плейсхолдеров стран или частей света
local function has_country_placeholders(s)
local placeholders = {'<страна>', '<страны>', '<в стране>', '<часть света>', '<части света>', '<в части света>'}
for _, placeholder in ipairs(placeholders) do
if s:find(placeholder, 1, true) then
return true -- Возврат true, если найден хотя бы один
end
end
return false
end
-- Замена плейсхолдеров на страны и части света
local function process_country_placeholders(s, title)
if type(s) ~= 'string' then return {}, nil end
local lines = mw.text.split(s, '\n')
local result_lines = {}
local added_categories = {}
local c = require('Module:CountryMetaCat')
local function add_category(category, typ)
-- Проверка уникальности категории перед добавлением
if category ~= '' and not added_categories[category] then
table.insert(result_lines, {text = category, type = typ})
added_categories[category] = true
end
end
for _, line in ipairs(lines) do
if has_country_placeholders(line) then
local args = {line, title = title}
local country_result = c.resolve_country(args)
if country_result then
local main_category = country_result.result or '' -- основная категория или 1 часть света
local extra_category = country_result.extra_result or '' -- 2 часть света
local error_code = country_result.error or 0
-- Добавление основной категории
add_category(main_category, "main")
-- Удаление ключей сортировки у категорий для сравнения названий
local main_base_category = mw.ustring.match(main_category, "^(.-)!") or main_category
local extra_base_category = mw.ustring.match(extra_category, "^(.-)!") or extra_category
-- Добавление extra категории если отличаются
if extra_base_category ~= main_base_category then
add_category(extra_category, "extra")
end
-- Ошибка, если страна или часть света не найдены
if error_code > 0 and not country_error_flag then
add_error(error_code == 1 and 6 or 7)
country_error_flag = true
end
elseif not country_error_flag then
add_error(6)
country_error_flag = true
end
else
-- Если строка не содержит плейсхолдеров, добавляем её как основную
table.insert(result_lines, {text = line, type = "main"})
end
end
return result_lines, nil
end
------------------ Форматирование строк ------------------
-- Формирование шаблона строки для отображения года с учётом до н. э.
local function get_templ(s)
-- Формируем строку-шаблон вида: 'Мир в 99 году до н. э.' -> 'Мир в %s году%s'
local t
t, BC = gsub(s, '[0-9]+ (год[ау]?) до н%. э%.', '%%s %1%%s')
local n = BC
if BC ~= 1 then
t, n = gsub(s, '[0-9]+ (год[ау]?)', '%%s %1%%s')
end
if n ~= 1 then
add_error(1) -- Ошибка, если совпадений нет или их больше одного
end
-- Корректировка для "во втором году"
templ = gsub(t, 'во %%s году', 'в %%s году')
end
-- Форматирование года с учётом до н. э.
local function format(y, wiki)
local bcs, t
if y < 1 then
y = 1 - y
bcs = ' до н. э.'
t = '−'..y
else
bcs = ''
t = y
end
local s
if wiki then
local tt = templ
-- Корректировка для "во втором году"
if y == 2 then
tt = gsub(templ, 'в %%s году', 'во %%s году')
end
s = string.format(tt, y, bcs)
s = string.format('[[:К:%s|%s]]', s, t)
else
s = t
end
return s
end
------------------ Список категорий ------------------
-- Проверка на существование категории
local function category_exists(category_name)
if not category_name or category_name == '' then return false end
-- Удаление символов ? ~ вначале или ! с текстом вконце
category_name = mw.ustring.match(category_name, "^[%?~]*(.-)!") or category_name
local title = mw.title.new('Категория:' .. category_name)
return title and title.exists
end
-- Публикация категорией с логикой проверок ? и замен ~
local function cats(args)
local ret = ''
local added_categories = {}
local lines = {}
-- Считывание строк в массив
for i, arg in sparseIpairs(args) do
if type(arg) == "string" and arg ~= "" then
local result = process_country_placeholders(arg, title)
for _, line in ipairs(result) do
table.insert(lines, {text = do_expand(line.text), type = line.type})
end
end
end
local function process_single_category(category_string)
category_string = mw.ustring.gsub(category_string, "!", "|")
-- Разделение строки на основную категорию и ключ сортировки
local categories = mw.text.split(category_string, "|")
local base_category = categories[1]
local sort_key = categories[2] or "" -- Ключ сортировки (если есть)
if not added_categories[base_category] then
if sort_key ~= "" then
ret = ret .. string.format('[[Категория:%s|%s]]', base_category, sort_key)
else
ret = ret .. string.format('[[Категория:%s]]', base_category)
end
added_categories[base_category] = true
end
end
local i = 1
while i <= #lines do
local arg = lines[i]
local first_char = mw.ustring.sub(arg.text, 1, 1)
local rest_string = mw.ustring.sub(arg.text, 2):gsub("^%s+", "") -- Удаление пробелов после символов
local typ = arg.type -- Получаем тип строки ("main" или "extra")
if first_char == '?' then
local category_name = mw.ustring.match(rest_string, "^(.-)!") or rest_string
local sort_key = mw.ustring.match(rest_string, "!(.-)$") or ''
category_name = mw.ustring.match(category_name, "^[?]*(.*)") or category_name
if category_exists(category_name) then
-- Категория существует, публикуем её
process_single_category(category_name .. (sort_key ~= '' and '|' .. sort_key or ''))
else
-- Поиск строки с "~" с тем-же типом для замены, если категория не найдена
local found_replacement = false
for j = i + 1, #lines do
local next_arg = lines[j]
local next_first_char = mw.ustring.sub(next_arg.text, 1, 1)
local next_typ = next_arg.type
if next_first_char == '~' and next_typ == typ then
local replacement_rest_string = mw.ustring.sub(next_arg.text, 2):gsub("^%s+", "")
process_single_category(replacement_rest_string)
found_replacement = true
break -- Выход из цикла после нахождения замены
end
end
-- Если замена не найдена, ничего не делаем
end
i = i + 1
else
-- Обрабатываем остальные строки (не начинающиеся с "?")
if first_char ~= '~' then
process_single_category(arg.text)
end
i = i + 1
end
end
return ret
end
------------------ Навигационный блок ------------------
local function navbox()
-- Корректировка для до н. э.
local y = (BC == 1) and 1 - year or year
local wt = mw.html.create('div'):addClass('ts-module-Индекс_категории hlist')
local row = wt:tag('ul')
-- Корректировка min и max для до н. э.
local adjusted_min = year_min <= 0 and year_min + 1 or year_min
local adjusted_max = year_max <= 0 and year_max + 1 or year_max
local country_data = find_country_in_json(findCountry.findcountryinstring(title))
-- Определение минимального и максимального года для страны
local country_min_year = math.max(adjusted_min, country_data and country_data.min or adjusted_min)
local country_max_year = math.min(adjusted_max, country_data and country_data.max or adjusted_max)
-- Определение стартового и конечного года
local ystart = math.max(country_min_year, y - range)
local yend = math.min(country_max_year, y + range)
-- Если диапазон некорректный, возвращаем пустую строку
if yend < ystart then return "" end
-- Добавляем элементы в навигационную полоску
for i = ystart, yend do
row:tag('li'):wikitext(format(i, true))
end
return getStyles() .. tostring(wt)
end
------------------ Вывод ------------------
function p.main(frame)
local args = getArgs(frame)
title = args['title'] or title
range = tonumber(args['range'] or range)
if mw.title.getCurrentTitle().namespace == 10 then -- проверка пространства шаблонов
return "[[Категория:Шаблоны, использующие модуль YearMetaCat2]]" ..
"[[Категория:Шаблоны, использующие индекс категории (автоматический)]]"
end
-- Обработка вручную заданных min и max
year_min = tonumber(args['min'] or year_min)
year_max = tonumber(args['max'] or year_max)
-- Нахождение года по заголовку страницы
year = get_year(title)
if not year then
return publish_errors() -- Возврат ошибок и прекращаем выполнение, если год не найден
end
-- Создание шаблона-строки
get_templ(title)
-- Стандартная категоризация
local categories = cats(args)
-- Проверка, попадает ли год в допустимые границы
check_year_in_bounds(args)
local output = ""
-- Навигационная полоска с отключением
if args['nonav'] ~= "1" then
output = output .. navbox(title)
end
-- Автоиндекс с отключением
if args['noindex'] ~= "1" then
output = output .. mw.getCurrentFrame():preprocess('{{индекс категории (автоматический)}}\n')
end
-- Преобразование таблицы категорий в строку, если это таблица
if type(categories) == "table" then
local flat_categories = {}
for _, value in ipairs(categories) do
table.insert(flat_categories, value.text)
end
categories = table.concat(flat_categories, '')
end
output = output .. publish_errors()
return output .. (categories or "")
end
-- Вспомогательная функция для развёртывания
function p.expand(frame)
local args = getArgs(frame)
title = args['title'] or title
year = get_year(title)
if not year then
return publish_errors()
end
BC = mw.ustring.find(title, '[0-9]+ год[ау]? до н%. э%.')
if BC then
BC = 1
else
BC = 0
end
-- в/во
local tt = args[1]
if year == 2 then
tt = mw.ustring.gsub(args[1], 'в <год> году', 'во <год> году')
end
return do_expand(tt)
end
return p