Модуль:CountryMetaCat (Bk;rl,&CountryMetaCat)
Модуль используется для автокатегоризации категорий по странам (для категорий с заголовком, включающим ).
Возможности
[править код]- Обработка стран:
- Определяет страну из заголовка в любом падеже.
- Позволяет изменить падеж страны для категорий.
- Определяет, в каких частях света расположена страна и публикует их в выбранном падеже.
- Определяет, в какое государство входит (входила) страна, исходя из текущего века, позволяя опубликовать категории для любых государств в одном формате, либо указывать отдельные категории для выбранных государств.
- Позволяет проверить существование категории и опубликовать одну или несколько замен для неё.
- Добавляет {{автоиндекс}} (появляется от 200 статей, расширенный индекс от 1200 статей).
- Позволяет указывать количество элементов в линейке.
- Добавляет категории.
Используемые списки данных для стран:
- падежные формы стран с предлогом
- разделение стран по частям света
- вхождение стран в государства по годам
Использование
[править код]{{#invoke:CountryMetaCat|main |Появились <в стране> |События <части света>!текст для сортировки }}
Категория состоит из 4-х частей, разделенных !
(восклицательным знаком). Первая часть — название категории, вторая часть — ключ сортировки (необязателен).
Переменные
[править код]<страна>
,<страны>
,<в стране>
— страна в необходимом падеже<часть света>
,<части света>
,<в части света>
— часть света в необходимом падеже<государство>
,<государства>
,<в государстве>
— государство в необходимом падеже<государство:Название>
,<государства:Название>
,<в государстве:Название>
— дополнительная проверка, позволяющая публиковать категорию только для стран, входящих в конкретное государство. Использование символа^
перед названием государства, наоборот, исключает его из публикации среди всех остальных государств. Можно исключать сразу несколько государств, отделяя каждое из них символом^
. Для установки сложных условий отображения см. Модуль:CountryMetaCat/State.
Именительный, родительный и предложный падежи для стран, частей света и государств подставляются автоматически, соответственно указанным переменным. Вариант предложного падежа у стран и государств автоматически выводится с нужным предлогом «в/во/на». Для частей света в предложном падеже автоматически ставится предлог «в».
Следующие символы, указанные перед названием категории, осуществляют механизм проверки на существование категорий:
?
— категория публикуется только если она существует.~
— является заменой для несуществующей категории?
. Обязательно должна следовать сразу за ней на следующей строке, иначе игнорируется. Замены публикуются без проверок на существование. Для одной проверяемой категории может указываться несколько замен подряд.
Для отдельных стран, расположенных на двух частях света или входящих в два государства, выполняется механизм раздваивания категорий с соответствующими переменными. Если переменная указана лишь в качестве ключа сортировки, то категория публикуется только один раз. Проверка на существование категорий осуществляется для каждой из частей света или государства в названии. Если одна из категорий не существует, то будет опубликована замена для соответствующей переменной.
Полная версия
[править код]{{#invoke:CountryMetaCat|main |Категория 1![ключ сортировки] |?Категория 2![ключ сортировки] |~Категория 3![ключ сортировки] ... |Категория N[...] }}
Дополнительные параметры:
|title = заголовок страницы, используемый вместо текущего (для тестов) |noindex = 1 (указывается, если необходимо отключить добавления шаблона индекса)
Дополнительные функции
[править код]resolve_country
Функция используется для экспорта в другие модули. Принимает заголовок страницы (или принимает title) + строку. Обрабатывает строку, если в ней содержалась заготовка страны, части света или государства. Возвращает таблицу со значениями:
result
— основной вариант строки для единственного результата (или первой части света или государства),extra_result
— возможный второй вариант строки для второй части света или государства,error
— код ошибки (0 — ошибки нет, 1 — страна не найдена, 2 — часть света не найдена).
Категории отслеживания
[править код]- Википедия:Страницы с некорректным использованием модуля CountryMetaCat — отслеживание использований с несуществующими странами или частями света, а также с нарушениями диапазонов в навигационной линейке.
- Шаблоны, использующие модуль CountryMetaCat (10) — в модуль встроено автодобавление в эту категорию страниц, на которых он используется, при условии что страница является шаблоном. Однако, так как проверка пространства страницы и размещение происходит через код модуля, то необходимо избегать помещения модуля в тег
<includeonly></includeonly>
на странице шаблона. Модуль нужно размещать вне любых подобных тэгов. - Категория:Шаблоны, использующие индекс категории (автоматический)
См. также
[править код]- Модуль:YearMetaCat2 — аналог для годов
- Модуль:DecadeMetaCat — аналог для десятилетий
- Модуль:CenturyMetaCat — аналог для веков
- Модуль:YearMetaCat — аналог для годов и десятилетий
- Модуль:MetaCatDoc — для документирования шаблонов, использующих этот модуль
local p = {}
local getArgs = require('Модуль:Arguments').getArgs
local findCountry = require('Модуль:Find country')
local error_category = '[[Категория:Википедия:Страницы с некорректным использованием модуля CountryMetaCat]]'
local errors = {}
--------------- Отслеживание ошибок ---------------
-- Добавляет сообщение об ошибке в список с указанием кода ошибки
local function add_error(error_code, additional_info)
local error_specific = {
[1] = 'Ошибка: страна не найдена.',
[2] = 'Ошибка: часть света не найдена для страны ' .. (additional_info or '') .. '.'
}
local error_text = error_specific[error_code]
if error_text then
table.insert(errors, '<span class="error">' .. error_text .. '</span>')
end
end
-- Возвращает строку с ошибками и очищает список ошибок
local function get_errors()
local result = table.concat(errors)
if result ~= "" then
result = result .. error_category
end
errors = {} -- Очищаем ошибки после получения
return result
end
---------------- Обработка государств и частей света ----------------
-- Проверяет наличие указанных плейсхолдеров в аргументах
local function has_placeholders(args, placeholders)
for _, value in pairs(args) do
if type(value) == "string" then
for _, placeholder in ipairs(placeholders) do
if mw.ustring.find(value, placeholder, 1, true) then
return true
end
end
end
end
return false
end
-- Проверяет наличие плейсхолдеров для частей света
local function has_continent_placeholders(args)
return has_placeholders(args, {'<часть света>', '<части света>', '<в части света>'})
end
-- Проверяет наличие плейсхолдеров для государств
local function has_state_placeholders(args)
for _, value in pairs(args) do
if type(value) == "string" then
if mw.ustring.find(value, '<государство[^>]*>') or
mw.ustring.find(value, '<государства[^>]*>') or
mw.ustring.find(value, '<в государстве[^>]*>') then
return true
end
end
end
return false
end
-- Загрузка данных о государствах из JSON
local function load_states_data()
return mw.loadJsonData('Модуль:CountryMetaCat/state-data.json')
end
-- Проверяет, попадает ли заданный год в указанный период (век, десятилетие, год)
local function check_time_period(start_year, end_year, type, time)
local year = tonumber(time)
if not year then return false end
if type == "century" then
local century_start = (year - 1) * 100 + 1
local century_end = year * 100
return (not end_year and start_year <= century_end) or
(start_year <= century_end and end_year and end_year >= century_start)
elseif type == "decade" then
local decade_start = math.floor(year / 10) * 10
local decade_end = decade_start + 9
return (not end_year and start_year <= decade_end) or
(start_year <= decade_end and end_year and end_year >= decade_start)
else -- year
return (not end_year and start_year <= year) or
(start_year <= year and end_year and end_year >= year)
end
end
-- Безопасно возвращает название страны в нужном падеже
local function get_safe_case(country, case)
if not country then return "" end
local result = findCountry.findcountryinstring(country, case)
if not result or result:match("^Ошибка") then result = "" end
return result
end
-- Находит государства по временному периоду, и сортирует их
local function find_states_by_time(country, type, time)
local states = {}
local states_data = load_states_data()
local country_data = states_data.country[country]
if not country_data then return states end
-- Создаем массив с годами для сортировки
local states_with_years = {}
for state_name, years in pairs(country_data) do
local start_year = tonumber(years[1])
local end_year = years[2] and tonumber(years[2]) or nil
if check_time_period(start_year, end_year, type, time) then
table.insert(states_with_years, {
name = state_name,
start_year = start_year,
end_year = end_year,
cases = {
['именительный'] = get_safe_case(state_name, 'именительный'),
['родительный'] = get_safe_case(state_name, 'родительный'),
['предлог'] = get_safe_case(state_name, 'предлог')
}
})
end
end
-- Сортируем по начальному году (по возрастанию)
table.sort(states_with_years, function(a, b)
return a.start_year < b.start_year
end)
-- При запросе со временем возвращаем все подходящие государства для дальнейшей обработки
return states_with_years
end
-- Проверяет соответствие названия государства из плейсхолдера с указанным государством
local function check_state_match(state, placeholder)
if not state then return false end
local required_state_name = mw.ustring.match(placeholder, ":([^>]+)>")
if not required_state_name then return true end -- если нет конкретного требования, разрешаем любое государство
-- Нормализуем названия для корректного сравнения
required_state_name = mw.ustring.gsub(required_state_name, "%s+", " ")
local state_name = mw.ustring.gsub(state.name or "", "%s+", " ")
-- Проверяем на исключения (формат ^Страна1^Страна2^...)
if mw.ustring.match(required_state_name, "^%^") then
-- Разбиваем строку исключений на отдельные страны
local excluded_states = {}
for excluded_state in mw.ustring.gmatch(required_state_name, "%^([^%^]+)") do
excluded_states[mw.ustring.gsub(excluded_state, "%s+", " ")] = true
end
-- Возвращаем true, если государство НЕ в списке исключений
return not excluded_states[state_name]
else
-- Обычное сравнение для включения конкретной страны
return state_name == required_state_name
end
end
-- Заменяет плейсхолдеры в тексте на значения из таблицы
local function replace_placeholders(text, replacements)
for placeholder, value in pairs(replacements) do
text = mw.ustring.gsub(text, placeholder, value)
end
return text
end
-- Удаляет неиспользованные плейсхолдеры из текста
local function remove_unused_placeholders(text, placeholders)
for _, placeholder in ipairs(placeholders) do
text = mw.ustring.gsub(text, placeholder, '')
end
return text
end
-- Вспомогательная функция для замены плейсхолдера государства в нужном падеже
local function replace_state_placeholder(result, pattern, case, state, is_question)
return result:gsub(pattern, function(state_name)
if check_state_match(state, pattern:gsub(":([^>]+)>", ":" .. state_name .. ">")) then
return state.cases[case] or ""
end
return is_question and (pattern:gsub("([^>]+)>", state_name .. ">")) or ""
end)
end
--------------- Обработка всех плейсхолдеров ---------------
-- Основная обработка всех плейсхолдеров с заменой
local function process_placeholders(s, country, continent, state, is_question)
if not s then return "" end
local result = s
local has_exclamation_space = mw.ustring.find(s, "! ")
-- Проверка на пустой возврат для не "?"
if not is_question then
if not state and result:match('<[^>]*государств[^>]*>') then
return ""
end
if state and result:match('<[^>]+:[^>]+>') then
local found_match = false
for pattern in result:gmatch('<[^>]+:[^>]+>') do
if check_state_match(state, pattern) then
found_match = true
break
end
end
if not found_match then return "" end
end
end
-- Обработка плейсхолдеров государства с использованием вспомогательной функции
if state then
result = replace_placeholders(result, {
['<государство>'] = state.cases['именительный'],
['<государства>'] = state.cases['родительный'],
['<в государстве>'] = state.cases['предлог']
})
result = replace_state_placeholder(result, '<государство:([^>]+)>', 'именительный', state, is_question)
result = replace_state_placeholder(result, '<государства:([^>]+)>', 'родительный', state, is_question)
result = replace_state_placeholder(result, '<в государстве:([^>]+)>', 'предлог', state, is_question)
end
-- Обработка плейсхолдеров частей света (без изменений)
if result:match('<[^>]*част[^>]*света[^>]*>') then
if continent and continent.cases then
result = replace_placeholders(result, {
['<часть света>'] = continent.cases['именительный'] or '',
['<части света>'] = continent.cases['родительный'] or '',
['<в части света>'] = continent.cases['предложный'] and ('в ' .. continent.cases['предложный']) or ''
})
else
add_error(2, country)
result = remove_unused_placeholders(result, {
'<часть света>', '<части света>', '<в части света>'
})
end
end
-- Обработка плейсхолдеров страны (без изменений)
if country then
result = replace_placeholders(result, {
['<страна>'] = get_safe_case(country, 'именительный') or "",
['<страны>'] = get_safe_case(country, 'родительный') or "",
['<в стране>'] = get_safe_case(country, 'предлог') or ""
})
elseif not is_question then
result = remove_unused_placeholders(result, {
'<страна>', '<страны>', '<в стране>'
})
end
-- Финальная обработка (без изменений)
result = mw.ustring.gsub(result, " !", "!")
result = mw.ustring.gsub(result, "[%!%s]+$", "")
if has_exclamation_space and not mw.ustring.find(result, "!") then
result = result .. "! "
end
return result
end
--------------- Обработка и публикация категорий ---------------
-- Генерирует комбинации частей света и государств
local function combinations_categories(country, args)
local combinations = {}
local continents = {}
local states = {}
-- Загрузка данных о частях света
if has_continent_placeholders(args) then
local continents_data = mw.loadJsonData('Модуль:CountryMetaCat/country-continents.json')
for _, continent in ipairs(continents_data.continents) do
if continent.countries then
for _, c in ipairs(continent.countries) do
if c == country then
table.insert(continents, continent)
break
end
end
end
end
end
-- Получение государств
if has_state_placeholders(args) then
states = find_states_by_time(country, args.type or "year", args.time or os.date("%Y"))
end
-- Вспомогательная функция для добавления комбинаций
local function add_combinations(continents_list, states_list)
if #continents_list == 0 and #states_list == 0 then
table.insert(combinations, { continent = nil, state = nil })
else
for _, continent in ipairs(continents_list) do
for _, state in ipairs(states_list) do
table.insert(combinations, { continent = continent, state = state })
end
end
-- Добавляем оставшиеся комбинации, если один из списков пуст
if #continents_list > 0 and #states_list == 0 then
for _, continent in ipairs(continents_list) do
table.insert(combinations, { continent = continent, state = nil })
end
elseif #continents_list == 0 and #states_list > 0 then
for _, state in ipairs(states_list) do
table.insert(combinations, { continent = nil, state = state })
end
end
end
end
-- Добавляем комбинации частей света и государств
add_combinations(continents, states)
return combinations
end
-- Проверка существования категории
local function category_exists(category_name)
if not category_name or category_name == '' then return false end
local clean_category_name = mw.ustring.match(category_name, "^(.-)!")
clean_category_name = clean_category_name or category_name
local title = mw.title.new('Категория:' .. clean_category_name)
return title and title.exists
end
-- Обрабатывает и добавляет уникальные категории на основе комбинаций частей света и государств
local function process_and_add_categories(text, country, combinations, results, added_categories, check_exists)
for _, combination in ipairs(combinations) do
if not combination.state or check_state_match(combination.state, text) then
local processed = process_placeholders(text, country, combination.continent, combination.state, check_exists)
if processed ~= "" then
local category_name = mw.ustring.match(processed, "^(.-)!") or processed
if not added_categories[category_name] then
table.insert(results, string.format('[[Категория:%s]]',
mw.ustring.gsub(processed, "!", "|")))
added_categories[category_name] = true
end
end
end
end
end
-- Создание категорий на основе аргументов и комбинаций
local function create_categories(args, country, combinations)
local results = {}
local added_categories = {}
if #combinations == 0 and has_continent_placeholders(args) then
add_error(2, country)
end
local ordered_args = {}
for i, arg in pairs(args) do
if type(arg) == "string" and arg ~= "" and type(i) == "number" then
table.insert(ordered_args, {index = i, value = arg})
end
end
table.sort(ordered_args, function(a, b) return a.index < b.index end)
local i = 1
while i <= #ordered_args do
local arg_data = ordered_args[i]
local arg = arg_data.value
if type(arg) == "string" and arg ~= "" then
local first_char = mw.ustring.sub(arg, 1, 1)
local rest_string = mw.ustring.sub(arg, 2):gsub("^%s+", "")
if first_char == '?' then
local replacements = {}
local j = i + 1
while j <= #ordered_args and mw.ustring.sub(ordered_args[j].value, 1, 1) == '~' do
local replacement_text = mw.ustring.sub(ordered_args[j].value, 2):gsub("^%s+", "")
table.insert(replacements, replacement_text)
j = j + 1
end
i = j - 1
-- Обрабатываем каждую комбинацию отдельно
for _, combination in ipairs(combinations) do
local processed = process_placeholders(rest_string, country, combination.continent, combination.state, true)
if processed ~= "" then
local category_name = mw.ustring.match(processed, "^(.-)!") or processed
if category_exists(category_name) then
-- Если категория существует, добавляем её
if not added_categories[category_name] then
table.insert(results, string.format('[[Категория:%s]]',
mw.ustring.gsub(processed, "!", "|")))
added_categories[category_name] = true
end
else
-- Если категория не существует, обрабатываем замены для этой комбинации
for _, replacement in ipairs(replacements) do
local replacement_processed = process_placeholders(replacement, country,
combination.continent, combination.state, false)
if replacement_processed ~= "" then
local replacement_category = mw.ustring.match(replacement_processed, "^(.-)!") or replacement_processed
if not added_categories[replacement_category] then
table.insert(results, string.format('[[Категория:%s]]',
mw.ustring.gsub(replacement_processed, "!", "|")))
added_categories[replacement_category] = true
end
end
end
end
end
end
elseif first_char ~= '~' then
-- Обработка обычных категорий без проверки существования
process_and_add_categories(arg, country, combinations, results, added_categories, false)
end
end
i = i + 1
end
return table.concat(results)
end
-- Основная функция модуля
function p.main(frame)
local args = getArgs(frame)
local title = args.title or mw.title.getCurrentTitle().text
if mw.title.getCurrentTitle().namespace == 10 then
return "[[Категория:Шаблоны, использующие модуль CountryMetaCat]]" ..
"[[Категория:Шаблоны, использующие индекс категории (автоматический)]]"
end
local country = findCountry.findcountryinstring(title, 'именительный')
if not country or country == "" then
add_error(1)
return get_errors()
end
local combinations = combinations_categories(country, args)
local result = create_categories(args, country, combinations)
if args.noindex ~= "1" then
result = mw.getCurrentFrame():preprocess('{{индекс категории (автоматический)}}\n') .. result
end
return result .. get_errors()
end
-- Функция для внешней обработки стран
function p._resolve_country(args)
local title = args.title or mw.title.getCurrentTitle().text
local country = findCountry.findcountryinstring(title, 'именительный')
local text = args[1] or ""
local is_question = text:match("^%?")
local has_state = text:match('<[^>]*государств[^>]*>')
local has_specific_state = text:match('<[^>]+:[^>]+>')
local result = {
result = "",
extra_result = nil,
error = country and country ~= "" and 0 or 1
}
-- Получаем комбинации, если страна найдена
local combinations = country and country ~= "" and combinations_categories(country, args) or {}
-- Ранний выход, если это ? с названием государства, но комбинаций нет
if is_question and has_state and #combinations == 0 then
result.result = process_placeholders(text, country, nil, nil, true)
return result
end
-- Проверка на ошибку части света
if #combinations > 0 and has_continent_placeholders(args) and not combinations[1].continent then
result.error = 2
end
-- Сбор допустимых результатов
local valid_results = {}
for _, combo in ipairs(combinations) do
local should_process = is_question and
(not has_specific_state or (combo.state and check_state_match(combo.state, text))) or
(not has_state or (combo.state and check_state_match(combo.state, text)))
if should_process then
local processed = process_placeholders(text, country, combo.continent, combo.state, is_question)
if processed ~= "" then
table.insert(valid_results, processed)
if #valid_results >= 2 then break end -- We only need max 2 results
end
end
end
-- Результаты
if #valid_results > 0 then
result.result = valid_results[1]
result.extra_result = valid_results[2]
elseif is_question and has_state then
result.result = process_placeholders(text, country,
combinations[1] and combinations[1].continent, nil, true)
else
result.result = process_placeholders(text, country, nil, nil, is_question)
end
return result
end
function p.resolve_country(frame)
local args = getArgs(frame)
return p._resolve_country(args)
end
return {
main = p.main,
resolve_country = p.resolve_country
}