Модуль:Sports table/argcheck
Материал из вики «Вече»
Дополнительные действия
Для документации этого модуля может быть создана страница Модуль:Sports table/argcheck/doc
-- Проверка параметров турнирной таблицы и самостоятельной шахматки.
-- Ищет неизвестные и недействующие параметры, ссылки на необъявленные команды, названия,
-- примечания и результаты без владельца, а также неканонические логические значения,
-- нестандартную запись счёта, цвета вне палитры, ручное оформление,
-- непереведённые сокращения, ручные ширины столбцов и незавершённый турнир.
-- Сам модуль ничего не выводит: a.check возвращает два списка ссылок на категории.
-- Первый — предупреждения, их вызывающий модуль в предпросмотре показывает текстом;
-- второй — только отслеживающие категории. Вызывают его [[Модуль:Sports table]] — для таблицы
-- и перед передачей вызова шахматке при show_matches=only — и [[Модуль:Sports results]]
-- для самостоятельной шахматки. Ставить ли категории на странице, решает вызывающий модуль.
-- Перечень категорий и условия попадания в них:
-- [[Модуль:Sports table/doc#Отслеживающие категории]].
local a = {}
-- Из [[Модуль:Sports table/sub]] берутся разбор цвета палитры, поиск ручного оформления,
-- проверка логических значений и код строки итогов.
local p_sub = require('Модуль:Sports table/sub')
-- Категории -----------------------------------------------------------------------------
-- Категории по родам находок; несколько родов могут делить одну категорию.
local categories = {
ignored_parameters = 'Категория:Страницы, использующие спортивные таблицы с недействующими параметрами',
missing_results = 'Категория:Страницы, использующие спортивные таблицы с ошибками в описании квалификационного раунда',
missing_teams = 'Категория:Страницы, использующие спортивные таблицы с отсутствующими командами',
orphaned_match_notes = 'Категория:Страницы, использующие спортивные таблицы с некорректными параметрами',
orphaned_names = 'Категория:Страницы, использующие спортивные таблицы с некорректными параметрами',
orphaned_notes = 'Категория:Страницы, использующие спортивные таблицы с некорректными параметрами',
orphaned_results = 'Категория:Страницы, использующие спортивные таблицы с ошибками в описании квалификационного раунда',
possible_incorrect_winpoints = 'Категория:Страницы, использующие спортивные таблицы с возможно неверным количеством очков',
possibly_ignored_parameters = 'Категория:Страницы, использующие спортивные таблицы с возможно игнорируемыми параметрами',
unknown_parameters = 'Категория:Страницы, использующие спортивные таблицы с неизвестными параметрами',
bad_boolean = 'Категория:Страницы, использующие спортивные таблицы с нестандартными логическими значениями',
bad_score = 'Категория:Страницы, использующие спортивные таблицы с нестандартной записью счёта',
useless_params = 'Категория:Страницы, использующие спортивные таблицы с параметрами без эффекта',
bad_color = 'Категория:Страницы, использующие спортивные таблицы с цветами вне палитры',
manual_format = 'Категория:Страницы с ручным оформлением в спортивных шаблонах',
unrecognised_style = 'Категория:Страницы, использующие спортивные таблицы с нераспознанным стилем',
untranslated_short = 'Категория:Страницы, использующие спортивные таблицы с непереведёнными сокращениями',
manual_teamwidth = 'Категория:Страницы, использующие спортивные таблицы с ручной шириной столбцов',
unfinished_update = 'Категория:Страницы, использующие спортивные таблицы с незавершённым турниром',
adjust_and_start = 'Категория:Страницы, использующие спортивные таблицы с настройками и начальными точками'
}
-- Допустимые параметры ------------------------------------------------------------------
-- Допустимые параметры турнирной таблицы, общие для всех стилей, вместе с параметрами
-- соседних модулей, которые приходят в тот же вызов. a.check накладывает на копию набора
-- надстройку стиля и параметры шахматки; сам набор не меняется.
local good_args = {
['away_goals_header'] = 1,
['baselink'] = 1,
['bonus_header'] = 1,
['class_rules'] = 1,
['digitpad'] = 1,
['draw_header'] = 1,
['drawpoints'] = 1,
['for_against_style'] = 1,
['GB_team'] = 1,
['generate_links'] = 1,
['goalpoints'] = 1,
['group_header'] = 1,
['keep_english_status_labels'] = 1,
['notracking'] = 1,
['show_box'] = 1,
['collapsible'] = 1,
['team_reports'] = 1,
['below_results_header'] = 1,
['matches_source'] = 1,
['matches_team_header'] = 1,
['matches_team_notes'] = 1,
['matches_title'] = 1,
['auto_generate_standings'] = 1,
['default_order'] = 1,
['external_teams'] = 1,
['h2h_after_complete'] = 1,
['matches_format'] = 1,
['ranking_criteria'] = 1,
['show_tied_positions'] = 1,
['hide_footer'] = 1,
['hide_for_against_columns'] = 1,
['hide_played'] = 1,
['hide_results'] = 1,
['highest_pos'] = 1,
['legs'] = 1,
['loss_before_draw'] = 1,
['loss_before_tie'] = 1,
['losspoints'] = 1,
['lowest_pos'] = 1,
['matches_style'] = 1,
['matches_text'] = 1,
['match_col_width'] = 1,
['note_header_res'] = 1,
['only_pld_pts'] = 1,
['only_totals'] = 1,
['only_fa_totals'] = 1,
['param1_valid'] = 1,
['param2_valid'] = 1,
['param3_valid'] = 1,
['pct_style'] = 1,
['pld_header'] = 1,
['postitle'] = 1,
['perc_first'] = 1,
['pts_first'] = 1,
['ranking_style'] = 1,
['res_col_header'] = 1,
['result_prefix'] = 1,
['rwpoints'] = 1,
['rounds_won'] = 1,
['rw_header'] = 1,
['section'] = 1,
['short_style'] = 1,
['show_GB'] = 1,
['show_away_goals'] = 1,
['show_bonus_points'] = 1,
['show_class_rules'] = 1,
['show_draw'] = 1,
['show_groups'] = 1,
['show_hth'] = 1,
['show_limit'] = 1,
['show_loss'] = 1,
['show_matches'] = 1,
['show_positions'] = 1,
['show_rw'] = 1,
['show_status'] = 1,
['no_totals_check'] = 1,
['show_totals'] = 1,
['show_win'] = 1,
['sortable_table'] = 1,
['source'] = 1,
['start_date'] = 1,
['stack_footer'] = 1,
['status_order'] = 1,
['status_pos'] = 1,
['style'] = 1,
['table_header'] = 1,
['table_header_source'] = 1,
['team_header'] = 1,
['team_order'] = 1,
['team_order_start'] = 1,
['teamwidth'] = 1, ['pointswidth'] = 1,
['template_name'] = 1,
['title'] = 1,
['title_source'] = 1,
['transcludesection'] = 1,
['update'] = 1,
['use_against_percentage'] = 1,
['use_goal_average'] = 1,
['use_goal_diff'] = 1,
['use_goal_percentage'] = 1,
['use_goal_ratio'] = 1,
['use_goals_scored'] = 1,
['use_point_percentage'] = 1,
['use_goals_per_match'] = 1,
['use_points_per_match'] = 1,
['use_tie'] = 1,
['winpoints'] = 1,
['win_header'] = 1,
['loss_header'] = 1,
}
-- Надстройка для стиля WDLHA — таблицы с разделением на дом и выезд.
local good_args_HA = {
['ha_side'] = 1,
['hwinpoints'] = 1,
['awinpoints'] = 1
}
-- Надстройка для стилей WDL OT и WL OT:
-- очки, столбцы и заголовки для исходов в овертайме, по буллитам и в серии пенальти.
local good_args_PKOT = {
['showOTloss'] = 1,
['showOTwin'] = 1,
['showPKloss'] = 1,
['showSOloss'] = 1,
['showPKwin'] = 1,
['showSOwin'] = 1,
['show_tiebr'] = 1,
['PKlosspoints'] = 1,
['SOlosspoints'] = 1,
['OTlosspoints'] = 1,
['PKwinpoints'] = 1,
['SOwinpoints'] = 1,
['OTwinpoints'] = 1,
['PKloss_after_loss'] = 1,
['SOloss_after_loss'] = 1,
['OTloss_after_loss'] = 1,
['tiebr_header'] = 1,
['OTwin_header'] = 1,
['OTloss_header'] = 1,
['PKwin_header'] = 1,
['PKloss_header'] = 1,
['SOwin_header'] = 1,
['SOloss_header'] = 1,
['percentage_format'] = 1,
}
-- Параметры шахматки [[Модуль:Sports results]], которые a.check добавляет к набору таблицы при любом стиле,
-- а не только при заданном show_matches. Шаблон обычно держит и данные таблицы, и данные шахматки и вызывается дважды,
-- причём show_matches=only задаётся только во втором вызове: привязка к этому параметру пометила бы верную разметку первого вызова.
local good_args_matches = {
['a_note'] = 1,
['font_size'] = 1,
['highest_row'] = 1,
['lowest_row'] = 1,
['multirowlegs'] = 1,
['ot_note'] = 1,
['ot_sup'] = 1,
['pk_note'] = 1,
['team_align'] = 1,
['team_header_note'] = 1,
['team_nowrap'] = 1
}
-- Набор для самостоятельной шахматки (a.check с mode = 'results'): применяется вместо набора таблицы,
-- префиксов поколоночных параметров у него нет. Параметры таблицы (ranking_style, win_ХХХ и прочие)
-- в самостоятельной шахматке ничего не делают, и предупредить о них полезнее, чем молча принять.
-- team_order_start здесь нет намеренно:
-- Sports results его не читает и раскладывает team_order всегда с team1.
local good_args_results = {
['a_note'] = 1,
['baselink'] = 1,
['teamwidth'] = 1,
['font_size'] = 1,
['generate_links'] = 1,
['highest_pos'] = 1,
['highest_row'] = 1,
['legs'] = 1,
['loss_before_draw'] = 1,
['loss_before_tie'] = 1,
['lowest_pos'] = 1,
['lowest_row'] = 1,
['match_col_width'] = 1,
['matches_style'] = 1,
['multirowlegs'] = 1,
['ot_note'] = 1,
['ot_sup'] = 1,
['pk_note'] = 1,
['section'] = 1,
['short_style'] = 1,
['source'] = 1,
['start_date'] = 1,
['team_align'] = 1,
['team_header'] = 1,
['team_header_note'] = 1,
['team_nowrap'] = 1,
['team_order'] = 1,
['title'] = 1,
['transcludesection'] = 1,
['update'] = 1
}
-- Набор для расписания ([[Модуль:Sports results/schedule]], matches_format=schedule, series,
-- series_split): при самостоятельном вызове (a.check с mode = 'schedule') применяется вместо
-- набора таблицы, а в вызове турнирной таблицы с таким matches_format добавляется к её набору.
-- Параметры по номеру тура и блока (roundN_matches, roundN_header, roundN_date, blockN,
-- blockN_title) узнаются по образцу — см. schedule_patterns. legs здесь есть: о том, что он
-- в расписании не действует, сообщает само расписание, и второе сообщение не нужно.
local good_args_schedule = {
['matches_format'] = 1, ['all_neutral'] = 1, ['external_teams'] = 1,
['external_teams_label'] = 1, ['default_order'] = 1, ['team_order'] = 1,
['highest_pos'] = 1, ['lowest_pos'] = 1, ['legs'] = 1,
['title'] = 1, ['block_columns'] = 1,
['team_header'] = 1, ['team_header_note'] = 1, ['team_align'] = 1, ['team_nowrap'] = 1,
['teamwidth'] = 1, ['match_col_width'] = 1, ['font_size'] = 1,
['round_type'] = 1, ['round_abbreviation'] = 1, ['round_name'] = 1, ['round_span_header'] = 1,
['display_codes'] = 1, ['opponent_inline_codes'] = 1, ['short_style'] = 1,
['legend_divider'] = 1, ['abbreviation_after'] = 1, ['header_legend'] = 1,
['legend_teams_per_line'] = 1, ['venue_display'] = 1, ['home_first'] = 1,
['solid_cell'] = 1, ['null_text'] = 1, ['row_dividers'] = 1,
['matches_style'] = 1, ['loss_before_draw'] = 1, ['loss_before_tie'] = 1, ['use_tie'] = 1,
['generate_links'] = 1, ['baselink'] = 1,
['update'] = 1, ['source'] = 1, ['start_date'] = 1,
['a_note'] = 1, ['ot_note'] = 1, ['pk_note'] = 1, ['ot_sup'] = 1,
['series_matches_style'] = 1, ['series_team1'] = 1, ['series_team2'] = 1,
['series_columns'] = 1, ['bold_winner'] = 1,
['section'] = 1, ['transcludesection'] = 1, ['notracking'] = 1,
}
-- Образцы имён параметров расписания с номером тура или блока.
local schedule_patterns = {
'^round%d+_matches$', '^round%d+_header$', '^round%d+_date$', '^block%d+$', '^block%d+_title$',
}
-- Виды matches_format, которые выводит расписание.
local schedule_formats = {schedule = true, series = true, series_split = true}
-- Префиксы поколоночных параметров вида win_КОД, общие для всех стилей; надстройка стиля
-- накладывается так же, как на good_args. a.scan узнаёт префикс по части имени до первого
-- подчёркивания, а составной (adjust_points, matches_note) — до второго.
local col_names = {
['adjust_points'] = 1,
['away_goals'] = 1,
['schedule'] = 1,
['matches_note'] = 1,
['draw'] = 1,
['loss'] = 1,
['gf'] = 1,
['ga'] = 1,
['group'] = 1,
['pf'] = 1,
['pa'] = 1,
['matches'] = 1,
['pos'] = 1,
['rw'] = 1,
['short'] = 1,
['startpoints'] = 1,
['status'] = 1,
['win'] = 1,
}
-- Надстройка префиксов для стиля WDLHA: 0 убирает префикс из набора, 1 добавляет. Общие
-- draw, loss, gf, ga, pf, pa и rw заменяются парами «дома»/«на выезде» с приставками h и a;
-- win остаётся, к нему добавляются hwin и awin.
local col_names_HA = {
['draw'] = 0,
['loss'] = 0,
['gf'] = 0,
['ga'] = 0,
['pf'] = 0,
['pa'] = 0,
['rw'] = 0,
['hdraw'] = 1,
['adraw'] = 1,
['hloss'] = 1,
['aloss'] = 1,
['hwin'] = 1,
['awin'] = 1,
['hga'] = 1,
['aga'] = 1,
['hgf'] = 1,
['agf'] = 1,
['hpa'] = 1,
['apa'] = 1,
['hpf'] = 1,
['apf'] = 1,
['hrw'] = 1,
['arw'] = 1,
}
-- Надстройка префиксов для стилей WDL OT и WL OT.
local col_names_PKOT = {
['PKloss'] = 1,
['SOloss'] = 1,
['OTloss'] = 1,
['PKwin'] = 1,
['SOwin'] = 1,
['OTwin'] = 1,
['tiebr'] = 1
}
-- Проверяемые стили и надстройка, которую a.check применяет к каждому ('WDL' — без надстройки).
-- style сравнивается с учётом регистра, после замены пробелов и подчёркиваний одним пробелом.
-- Незаданный style и стиль не из списка проверки не получают: a.check возвращает пустые списки,
-- а для заданного, но нераспознанного стиля — одну категорию unrecognised_style.
local tracked_styles = {
['WDL'] = 'WDL',
['WL'] = 'WDL',
['WDLHA'] = 'HA',
['WDL OT'] = 'PKOT',
['WL OT'] = 'PKOT'
}
-- Параметры с особыми правилами ---------------------------------------------------------
-- Параметры, которые распознаются, но ни на что не влияют: страница с ними попадает
-- в категорию недействующих параметров, как и с параметрами на att_ (их a.scan узнаёт
-- по приставке). hdrawpoints/adrawpoints и hlosspoints/alosspoints выглядят парой
-- к действующим hwinpoints/awinpoints, но стиль WDLHA их не читает: очки за ничью
-- и поражение он берёт только из общих drawpoints и losspoints.
local ignored_args = {
['date'] = 1,
['ptsfirst'] = 1,
['solid_cell'] = 1,
['hdrawpoints'] = 1,
['adrawpoints'] = 1,
['hlosspoints'] = 1,
['alosspoints'] = 1
}
-- Логические параметры, запись которых сверяется с каноническими значениями
-- (ppp.is_canonical_boolean, набор общий с карточками матча). Список один для таблицы и самостоятельной шахматки.
--
-- show_matches сюда не входит и входить не должен: кроме логических значений он принимает
-- only, below и below_levelN, и каждое из них отмечалось бы как неканоническое.
-- По той же причине здесь нет ot_note (принимает ещё ot, pk, so) и ot_sup (принимает подписи).
local boolean_args = {
['generate_links'] = 1, ['ha_side'] = 1, ['hide_footer'] = 1,
['hide_for_against_columns'] = 1, ['hide_played'] = 1, ['hide_results'] = 1,
['loss_before_draw'] = 1, ['loss_before_tie'] = 1, ['multirowlegs'] = 1,
['auto_generate_standings'] = 1, ['collapsible'] = 1, ['matches_team_notes'] = 1, ['team_reports'] = 1,
['show_tied_positions'] = 1,
['no_totals_check'] = 1, ['notracking'] = 1,
['only_fa_totals'] = 1, ['only_totals'] = 1,
['a_note'] = 1, ['pk_note'] = 1, ['OTloss_after_loss'] = 1,
['all_neutral'] = 1, ['header_legend'] = 1, ['home_first'] = 1, ['row_dividers'] = 1,
['abbreviation_after'] = 1, ['bold_winner'] = 1,
['PKloss_after_loss'] = 1, ['SOloss_after_loss'] = 1, ['perc_first'] = 1,
['pts_first'] = 1, ['rounds_won'] = 1, ['showOTloss'] = 1, ['showOTwin'] = 1,
['showPKloss'] = 1, ['showPKwin'] = 1, ['showSOloss'] = 1, ['showSOwin'] = 1,
['show_GB'] = 1, ['show_away_goals'] = 1, ['show_bonus_points'] = 1,
['show_class_rules'] = 1, ['show_draw'] = 1, ['show_groups'] = 1,
['show_hth'] = 1, ['show_loss'] = 1, ['show_positions'] = 1, ['show_rw'] = 1,
['show_status'] = 1, ['show_tiebr'] = 1, ['show_totals'] = 1, ['show_win'] = 1,
['sortable_table'] = 1, ['stack_footer'] = 1, ['use_against_percentage'] = 1,
['use_goal_average'] = 1, ['use_goal_diff'] = 1, ['use_goal_percentage'] = 1,
['use_goal_ratio'] = 1, ['use_goals_per_match'] = 1, ['use_goals_scored'] = 1,
['use_point_percentage'] = 1, ['use_points_per_match'] = 1, ['use_tie'] = 1
}
-- Параметры, которые действуют только вместе с другим. Заданные в одиночку они ничего не меняют:
-- разметка выглядит осмысленной, а результат не тот, которого ждал редактор.
-- Ключ — зависимый параметр, значение — тот, без которого он бесполезен.
local requires_param = {
['GB_team'] = 'show_GB',
['team_order_start'] = 'team_order'
}
-- Вспомогательные функции ---------------------------------------------------------------
-- Возвращает ссылку на категорию с value в роли ключа сортировки:
-- по нему в категории видно, что именно вызвало отметку.
local function get_tracking_category(category, value)
return '[[' .. category .. '|' .. value .. ']]'
end
-- Возвращает поверхностную копию таблицы. a.check копирует ею аргументы вызова и базовые наборы:
-- a.scan меняет аргументы, а наборы — данные модуля, общие для всех вызовов a.check,
-- и надстройка одного вызова не должна в них оставаться.
local function shallow_copy(source)
local copy = {}
for k, v in pairs(source) do
copy[k] = v
end
return copy
end
-- Добавляет по ссылке на категорию для каждого значения из args: в warn (предупреждения)
-- либо, при is_track, в track (только категории). Знаки из набора bad_char
-- в значении заменяются на «?», пустое значение — на пробел.
local function validate_table_values(warn, track, args, category, bad_char, is_track)
for _, v in ipairs(args) do
v = mw.ustring.gsub(v, bad_char, '?')
if v == '' then
v = ' '
end
if is_track then
table.insert(track, get_tracking_category(category, v))
else
table.insert(warn, get_tracking_category(category, v))
end
end
end
-- Проверка вызова -----------------------------------------------------------------------
-- Помечает разобранными (обнуляет в targs) параметры расписания с номером тура или блока —
-- их набор допустимых параметров перечислить не может.
local function clear_schedule_patterns(targs)
for k in pairs(targs) do
if type(k) == 'string' then
for _, pattern in ipairs(schedule_patterns) do
if k:find(pattern) then
targs[k] = ''
break
end
end
end
end
end
-- Возвращает true, если вызов выводит расписание (matches_format=schedule, series, series_split).
local function is_schedule(targs)
local f = targs['matches_format']
return type(f) == 'string' and schedule_formats[mw.ustring.lower(mw.text.trim(f))] == true
end
-- Проверяет параметры вызова. targs — аргументы модуля, pargs — аргументы вызова шаблона,
-- mode — nil для турнирной таблицы, 'results' для самостоятельной шахматки
-- или 'schedule' для самостоятельного расписания. Выбирает набор допустимых параметров
-- по режиму и стилю и передаёт разбор в a.scan. Возвращает два массива ссылок на категории:
-- предупреждения и отслеживающие категории.
-- Разбор идёт по копии: a.scan обнуляет разобранные параметры и дописывает позиции teamN,
-- а вызывающий модуль продолжает работать с теми же аргументами — при show_matches=only и below
-- по ним после проверки строится шахматка.
function a.check(targs, pargs, mode)
targs = shallow_copy(targs)
local warn, track = {}, {}
-- Самостоятельная шахматка: свой набор параметров и пустой набор префиксов — name_,
-- short_, note_, hth_, match_ и result_ a.scan узнаёт по образцам имён.
if mode == 'results' then
local good_args_local = shallow_copy(good_args_results)
return a.scan(targs, pargs, good_args_local, {}, warn, track)
end
-- Самостоятельное расписание: свой набор, из префиксов — только schedule_КОД. Соперникам вне турнира
-- (external_teams) строки и названия не нужны: матчи с ними не отмечаются как матчи отсутствующих команд.
if mode == 'schedule' then
clear_schedule_patterns(targs)
local known = {}
for _, c in ipairs(mw.text.split(targs['external_teams'] or '', '%s*[;,]%s*')) do
c = mw.text.trim(c)
if c ~= '' then known[c] = 1 end
end
return a.scan(targs, pargs, shallow_copy(good_args_schedule), {schedule = 1}, warn, track, known)
end
-- Незаданный style — стиль по умолчанию, как и в самой турнирной таблице.
local raw_style = mw.ustring.gsub(targs['style'] or '', '[%s_][%s_]*', ' ')
if raw_style == '' then
raw_style = 'WDL'
end
local mstyle = tracked_styles[raw_style]
if mstyle == nil then
if raw_style ~= '' then
table.insert(track, get_tracking_category(categories.unrecognised_style, raw_style))
end
return warn, track
end
-- Надстройка стиля накладывается на копии наборов. При заданном only_fa_totals стиль
-- WDLHA читает и общие gf, ga, pf, pa, поэтому они возвращаются в набор префиксов.
local good_args_local = shallow_copy(good_args)
local col_names_local = shallow_copy(col_names)
if mstyle == 'HA' then
for k, v in pairs(good_args_HA) do
good_args_local[k] = v ~= 0 and v or nil
end
for k, v in pairs(col_names_HA) do
col_names_local[k] = v ~= 0 and v or nil
end
if targs['only_fa_totals'] then
col_names_local['pf'] = 1
col_names_local['gf'] = 1
col_names_local['pa'] = 1
col_names_local['ga'] = 1
end
end
if mstyle == 'PKOT' then
for k, v in pairs(good_args_PKOT) do
good_args_local[k] = v ~= 0 and v or nil
end
for k, v in pairs(col_names_PKOT) do
col_names_local[k] = v ~= 0 and v or nil
end
end
-- Параметры шахматки допустимы при любом стиле — см. good_args_matches.
for k, v in pairs(good_args_matches) do
good_args_local[k] = v
end
-- При matches_format=schedule и подобных шаблон держит и параметры расписания:
-- его выводит та же таблица при show_matches=only или below.
if is_schedule(targs) then
for k, v in pairs(good_args_schedule) do
good_args_local[k] = v
end
clear_schedule_patterns(targs)
end
-- Команды, у которых заданы и поправка очков, и начальные очки. Стили складывают их
-- по-разному: WDL и WDLHA — суммой, стили с овертаймом берут только поправку, — и такую
-- запись стоит проверить. Проверяется до a.scan: тот стирает в копии аргументов teamN.
-- Строка итогов не в счёт: у неё обе величины — суммы по командам.
local both = {}
for k, v in pairs(targs) do
if type(k) == 'string' and k:find('^team%d+$') and v ~= p_sub.total_row_code
and tonumber(targs['adjust_points_' .. v]) and tonumber(targs['startpoints_' .. v]) then
both[#both + 1] = v
end
end
table.sort(both)
if #both > 0 then
table.insert(track, get_tracking_category(categories.adjust_and_start, both[1]))
end
return a.scan(targs, pargs, good_args_local, col_names_local, warn, track)
end
-- Разбирает аргументы targs по набору допустимых параметров good_args_local и префиксов
-- col_names_local, дописывает ссылки на категории в warn и track и возвращает их же.
-- Общая для турнирной таблицы и самостоятельной шахматки: наборы выбирает a.check. pargs —
-- аргументы вызова шаблона: нераспознанный параметр, который задан и там, считается возможно
-- игнорируемым (только категория), остальные — неизвестными (предупреждение). known_teams —
-- коды, которые считаются командами без teamN и name_КОД (соперники вне турнира в расписании).
-- Разбор меняет targs — обнуляет разобранные параметры и дописывает позиции teamN, —
-- поэтому передаётся копия. Сначала идут проверки, которым нужны нетронутые значения,
-- затем два прохода: первый строит списки команд и кодов результатов и помечает связанные
-- с ними параметры, второй разбирает всё оставшееся; в конце — логические значения,
-- парные параметры и запись счёта.
function a.scan(targs, pargs, good_args_local, col_names_local, warn, track, known_teams)
local teams = shallow_copy(known_teams or {})
local missing_teams = {}
local missing_results = {}
local orphaned_match_notes = {}
local orphaned_results = {}
local orphaned_names = {}
local orphaned_notes = {}
local ignored = {}
local possibly_ignored = {}
local unknown = {}
-- Турнир, не помеченный завершённым: update не равен complete (регистр не важен).
-- Ключ сортировки — род случая, а не само значение: цифра впереди задаёт порядок групп:
-- 0 not set — параметр не заполняли вовсе;
-- 1 date — дата стоит, но её надо либо обновлять, либо заменить на complete;
-- 2 future — турнир ещё не начался.
local unfinished_update = {}
do
local upd = targs['update']
local low = type(upd) == 'string' and mw.ustring.lower(mw.text.trim(upd)) or ''
if low ~= 'complete' then
table.insert(unfinished_update,
(low == '' and '0 not set') or (low == 'future' and '2 future') or '1 date')
end
end
-- Ручные ширины столбцов: teamwidth (столбец команд), pointswidth (столбец очков)
-- и match_col_width (клетки шахматки). Ширины подбираются сами; вручную их задают,
-- чтобы несколько таблиц на странице совпали по ширине, и категория показывает где.
local manual_teamwidth = {}
for _, key in ipairs({'teamwidth', 'pointswidth', 'match_col_width'}) do
if type(targs[key]) == 'string' and targs[key] ~= '' then
table.insert(manual_teamwidth, key)
end
end
-- Сокращения, оставшиеся на латинице при названии команды на кириллице.
local untranslated = {}
for k, v in pairs(targs) do
local code = type(k) == 'string' and type(v) == 'string' and k:match('^name_(.+)$')
-- Строка итогов — не команда: её код служебный и в заголовок столбца не попадает.
if code and code ~= p_sub.total_row_code and mw.ustring.find(v, '[А-Яа-яЁё]') then
local short = targs['short_' .. code]
local shown, param
if type(short) == 'string' and short ~= '' then
-- Берётся видимое: подпись ссылки, если она есть, иначе цель; шаблоны и теги снимаются.
shown = mw.ustring.gsub(short, '%[%[[^%[%]|]*|([^%[%]]*)%]%]', '%1')
shown = mw.ustring.gsub(shown, '%[%[([^%[%]]*)%]%]', '%1')
shown = mw.ustring.gsub(shown, '%b{}', '')
shown = mw.ustring.gsub(shown, '<[^<>]*>', '')
shown = mw.text.trim(shown)
param = 'short_' .. code
else
-- Сокращение не задано: в заголовок столбца шахматки попадает сам код команды,
-- и латинский код оставляет столбец непереведённым так же.
shown = code
param = 'name_' .. code
end
-- Отмечается только сокращение с буквами, среди которых нет ни одной кириллической:
-- сокращение из одних цифр или знаков ошибкой не считается.
if mw.ustring.find(shown, '%a') and not mw.ustring.find(shown, '[А-Яа-яЁё]')
and mw.ustring.find(shown, '^[%w%s%p]*$') then
table.insert(untranslated, param)
end
end
end
-- Параметры col_ХХХ, заданные не утверждённым именем палитры (ppp.palette_color
-- возвращает происхождение, отличное от 'named'): шестнадцатеричным кодом, названием
-- цвета CSS, именем оттенка без номера уровня, именем без назначенного смысла или
-- с опечаткой. Всё, что похоже на цвет, при этом применяется — категория нужна, чтобы
-- привести разметку к именам или дополнить палитру.
-- Проверка идёт до обоих проходов: первый обнуляет разобранные параметры,
-- включая col_ХХХ, и после него сверять уже нечего.
local bad_color = {}
for k, v in pairs(targs) do
if type(k) == 'string' and type(v) == 'string' and v ~= '' and k:find('^col_') then
local _, origin = p_sub.palette_color(v)
if origin ~= 'named' then
table.insert(bad_color, k)
end
end
end
-- Заливка отдельной клетки шахматки задаётся не параметром, а тегом прямо в значении матча:
-- Проверка по col_ХХХ такую запись не видит, а имя палитры там столь же уместно.
-- Находки идут в ту же категорию: случай тот же — цвет вне утверждённых имён.
for k, v in pairs(targs) do
if type(k) == 'string' and type(v) == 'string' and v ~= ''
and k:find('^match[%d]*_.*_.*$') and not k:find('_note$') and k ~= 'match_col_width'
and v:find('<', 1, true) then
local inline = mw.ustring.match(v,
'^%s*<span%s+style%s*=["\'%s]*background[%-colr]*%s*:([^\'";<>]*)')
if inline then
local _, origin = p_sub.palette_color(inline)
if origin ~= 'named' then
table.insert(bad_color, k)
end
end
end
end
-- Ручное оформление в значениях (ppp.manual_format): запрет переноса,
-- принудительный перенос строки, размер шрифта, ширина, отступы, выравнивание.
-- Оформление семейства рассчитано на узкие экраны, и такие вставки ему мешают:
-- запрет переноса выталкивает длинную строку за край,
-- а уменьшение шрифта поверх уже уменьшенного нарушает [[ВП:РТ]].
-- Проверка идёт по всем именованным параметрам и до обоих проходов —
-- первый обнуляет разобранные параметры. Вид приёма идёт в ключ сортировки:
-- в предпросмотре он виден рядом с именем параметра.
-- Источник (source, matches_source) не проверяется: это ссылка на литературу, и размер шрифта в ней
-- задают шаблоны цитирования — {{книга}}, пометка языка «(англ.)», — а не редактор таблицы.
local manual_format = {}
local citation_args = {source = true, matches_source = true}
for k, v in pairs(targs) do
local kind = type(k) == 'string' and not citation_args[k] and p_sub.manual_format(v)
if kind then
table.insert(manual_format, k .. ' (' .. kind .. ')')
end
end
-- team_order раскладывается по teamN с тем же смещением team_order_start,
-- что и в Sports table: иначе проверка видела бы команды не на тех позициях.
if targs['team_order'] and targs['team_order'] ~= '' then
local team_order_offset = (tonumber(targs['team_order_start']) or 1) - 1
local tlist = mw.text.split(targs['team_order'], '%s*[;,]%s*')
for k, tname in ipairs(tlist) do
if tname ~= '' then
targs['team' .. (k + team_order_offset)] = tname
end
end
end
-- Вызов с выделенной командой (showteam или team) не проверяется, если эта команда стоит
-- на одной из показываемых позиций — от highest_pos до lowest_pos, а без lowest_pos до
-- последней команды: функция возвращает warn и track нетронутыми, и собранные выше списки
-- тоже не выводятся. showteam обычно передают из статьи о клубе, а данные таблицы
-- проверяет вызов без него. Команду, которой на этих позициях нет, второй проход
-- отмечает как отсутствующую.
if targs['showteam'] or targs['team'] then
local top_pos = tonumber(targs['highest_pos']) or 1
local bottom_pos = tonumber(targs['lowest_pos']) or 0
local N = top_pos - 1
while targs['team' .. N + 1] ~= nil and (bottom_pos < top_pos or N < bottom_pos) do
N = N + 1
teams[targs['team' .. N]] = 1
if teams[targs['showteam'] or targs['team']] then
return warn, track
end
end
end
-- Позиции команд, не заданные вручную (нет team1). Автосборка записывает вычисленный
-- порядок в teamN, но при show_matches=only Sports table проверяет аргументы до неё
-- и передаёт вызов шахматке, так и не дойдя до автосборки.
-- Без позиций первый проход не распознаёт ни одного поимённого параметра — ни примечания,
-- ни названия, ни сокращения, — и все они попадают в некорректные. Поэтому список
-- достраивается из самих названий: для разбора важен набор кодов, а не их порядок.
-- Проверка осиротевших названий от этого не притупляется: она ловит name_КОД у команды,
-- которой нет в таблице, а когда позиций не задано вовсе, таблицу и составляют сами названия.
-- Если team1 задан, достройка не выполняется.
-- Коды сначала собираются, а позиции записываются потом: добавлять новые ключи в таблицу
-- во время её обхода pairs() нельзя — обход после этого может пропустить часть ключей,
-- и команды молча терялись бы.
if targs['team1'] == nil then
local codes = {}
for k, _ in pairs(targs) do
local code = type(k) == 'string' and k:match('^name_(.+)$')
if code then
codes[#codes + 1] = code
end
end
table.sort(codes)
for i, code in ipairs(codes) do
if targs['team' .. i] == nil then
targs['team' .. i] = code
end
end
end
-- Первый проход: собирает команды по teamN и коды результатов по resultN и помечает
-- связанные с ними параметры — обнуляет их в targs, чтобы второй проход не счёл их
-- неизвестными. Здесь же отмечаются команды без name_КОД, коды результатов без
-- оформления и примечания к матчам, которых нет.
for k, v in pairs(targs) do
if type(k) == 'string' then
if k:find('^team%d%d*$') then
teams[v] = 1
targs[k] = ''
if targs['name_' .. v] then
targs['name_' .. v] = ''
else
table.insert(missing_teams, v)
end
-- short_КОД — сокращение для заголовка столбца шахматки. Помечается здесь,
-- наравне с названием и примечанием: в самостоятельной шахматке префиксов
-- нет, и распознать его иначе нечем.
if targs['short_' .. v] then
targs['short_' .. v] = ''
end
if targs['note_' .. v] then
targs['note_' .. v] = ''
end
-- hth_КОД — примечание о личных встречах: собственный текст либо отсылка
-- к hth другой команды — одним кодом или списком кодов через запятую.
-- Список засчитывается, только если есть примечания всех его кодов;
-- примечания, на которые идёт отсылка, помечаются разобранными.
if targs['hth_' .. v] then
local multiref = 1
local hth_local_table = mw.text.split(targs['hth_' .. v], '%s*,%s*')
if (#hth_local_table > 1) then
for _, hth_loc in ipairs(hth_local_table) do
multiref = multiref * (targs['hth_' .. hth_loc] and 1 or 0)
end
else
multiref = 0
end
if multiref > 0 then
for _, hth_loc in ipairs(hth_local_table) do
targs['hth_' .. hth_loc] = ''
end
elseif targs['hth_' .. targs['hth_' .. v]] then
targs['hth_' .. targs['hth_' .. v]] = ''
end
targs['hth_' .. v] = ''
end
elseif k:find('^result%d%d*$') or k:find('^.*_result%d%d*$') then
-- resultN (или ПРЕФИКС_resultN) — код результата команды. Коду нужен хотя бы один
-- из col_КОД, text_КОД, note_res_КОД с тем же префиксом; note_res_КОД
-- отсылает к другим примечаниям так же, как hth_КОД.
local pre = mw.ustring.gsub(k, '^(.*)result%d%d*$', '%1')
targs[k] = ''
if targs[pre .. 'col_' .. v] or targs[pre .. 'text_' .. v] or targs[pre .. 'note_res_' .. v] then
if targs[pre .. 'col_' .. v] then
targs[pre .. 'col_' .. v] = ''
end
if targs[pre .. 'text_' .. v] then
targs[pre .. 'text_' .. v] = ''
end
if targs[pre .. 'note_res_' .. v] then
local multiref = 1
local note_res_local_table = mw.text.split(targs[pre .. 'note_res_' .. v], '%s*,%s*')
if (#note_res_local_table > 1) then
for _, note_res_loc in ipairs(note_res_local_table) do
multiref = multiref * (targs[pre .. 'note_res_' .. note_res_loc] and 1 or 0)
end
else
multiref = 0
end
if multiref > 0 then
for _, note_res_loc in ipairs(note_res_local_table) do
targs[pre .. 'note_res_' .. note_res_loc] = ''
end
elseif targs[pre .. 'note_res_' .. targs[pre .. 'note_res_' .. v]] then
targs[pre .. 'note_res_' .. targs[pre .. 'note_res_' .. v]] = ''
end
targs[pre .. 'note_res_' .. v] = ''
end
if targs[pre .. 'res_col_header'] then
targs[pre .. 'res_col_header'] = ''
end
else
table.insert(missing_results, v)
end
elseif k:find('^matchn[%d]*_.*_.*$') or k:find('^resultn[%d]*_.*_.*$') then
-- matchn_ААА_БББ — матч на нейтральном поле, resultn_ААА_БББ — код его исхода.
-- Их читает только автосборка: в шахматке у клетки есть хозяева и гости,
-- и такой матч она не показывает. Здесь они лишь помечаются разобранными,
-- вместе с примечанием к матчу.
targs[k] = ''
elseif k:find('^match[%d]*_.*_.*_note$') then
-- Примечание к матчу требует самого матча. Значение примечания может быть
-- отсылкой к note_КОД — тогда и это примечание помечается разобранным.
local m = mw.ustring.gsub(k, '^(match[%d]*_.*_.*)_note$', '%1')
targs[k] = ''
if targs[m] then
if targs['note_' .. v] then
targs['note_' .. v] = ''
end
else
table.insert(orphaned_match_notes, m)
end
end
end
end
-- Второй проход: каждый оставшийся непустой параметр сверяется с известными видами
-- в порядке проверок ниже. Не подошедший ни под один вид считается возможно игнорируемым,
-- если он задан и в вызове шаблона (pargs), иначе — неизвестным.
local possible_missing_winpoints = 0
for k, v in pairs(targs) do
if v and v ~= '' then
local found_arg = 0
if type(k) == 'string' then
-- Счётчик для категории possible_incorrect_winpoints: ненулевая поправка
-- adjust_points_КОД, в точности гасящая число побед win_КОД, прибавляет единицу,
-- любая другая ненулевая — вычитает. Сам параметр распознаётся ниже, по префиксу.
if k:find('^adjust_points_') then
local p = mw.ustring.gsub(k, '^adjust_points_', '')
if (tonumber(v) or 0) ~= 0 then
local pdiff = (tonumber(targs['win_' .. p] or 0) or 0) + tonumber(v)
if pdiff == 0 then
possible_missing_winpoints = possible_missing_winpoints + 1
else
possible_missing_winpoints = possible_missing_winpoints - 1
end
end
end
if found_arg == 0 and (good_args_local[k] or k:find('^split[%d][%d]*$')) then
found_arg = 1
end
if found_arg == 0 and (k == 'showteam' or k == 'team') then
found_arg = 1
if teams[v] ~= 1 then
table.insert(missing_teams, v)
end
end
if found_arg == 0 and (ignored_args[k] or k:find('^att_')) then
found_arg = 1
table.insert(ignored, k)
end
if found_arg == 0 and k:find('^[A-Za-z3-5]*_.*$') then
local p = mw.ustring.gsub(k, '^([A-Za-z3-5]*)_.*$', '%1')
if col_names_local[p] then
found_arg = 1
end
end
if found_arg == 0 and k:find('^[A-Za-z]*_[A-Za-z]*_.*$') then
local p = mw.ustring.gsub(k, '^([A-Za-z]*_[A-Za-z]*)_.*$', '%1')
if col_names_local[p] then
found_arg = 1
end
end
-- Имя матча или кода исхода делится на коды команд по известным кодам
-- (ppp.split_pair): подчёркивание бывает и внутри кода, и деление по последнему
-- отметило бы «B_A» и «1» вместо команд «B» и «A_1».
if found_arg == 0 and k:find('^match[%d]*_.*_.*$') then
local t1, t2 = p_sub.split_pair(k:match('^match%d*_(.+)$'), teams)
found_arg = 1
if teams[t1] == nil or teams[t2] == nil then
if teams[t1] == nil then
table.insert(missing_teams, t1)
end
if teams[t2] == nil then
table.insert(missing_teams, t2)
end
end
end
if found_arg == 0 and k:find('^result[%d]*_.*_.*$') then
local t1, t2 = p_sub.split_pair(k:match('^result%d*_(.+)$'), teams)
found_arg = 1
if teams[t1] == nil or teams[t2] == nil then
if teams[t1] == nil then
table.insert(missing_teams, t1)
end
if teams[t2] == nil then
table.insert(missing_teams, t2)
end
end
end
if found_arg == 0 and (k:find('^name_') or k:find('^short_')) then
found_arg = 1
table.insert(orphaned_names, k)
end
if found_arg == 0 and (k:find('^note_') or k:find('^hth_')) then
found_arg = 1
-- Нетронутые образцы из документации шаблона (hth_ABC=H2H note и т. п.)
-- идут в недействующие параметры, а не в некорректные.
if (k == 'hth_ABC' and v == 'H2H note') then
table.insert(warn, get_tracking_category(categories.ignored_parameters, 'hth_ABC'))
elseif (k == 'note_ABC' and v == 'Team note') then
table.insert(warn, get_tracking_category(categories.ignored_parameters, 'note_ABC'))
elseif (k == 'note_res_AAA' and v == 'Result note') then
table.insert(warn, get_tracking_category(categories.ignored_parameters, 'note_res_AAA'))
else
table.insert(orphaned_notes, k)
end
end
if found_arg == 0 and (k:find('^col_') or k:find('^text_')) then
found_arg = 1
table.insert(orphaned_results, k)
end
end
-- Позиционные аргументы шаблона-переключателя: первый обычно выбирает ветку
-- #switch, второй — что она выводит (таблицу или отчёты о матчах), третий —
-- команду, матчами которой ограничен вывод. Модулю они не предназначены,
-- но попадают в его аргументы через parentFirst; param1_valid–param3_valid
-- разрешают их, и без этих признаков они отмечались бы как лишние.
if found_arg == 0 and tonumber(k) == 1 and targs['param1_valid'] then
found_arg = 1
end
if found_arg == 0 and tonumber(k) == 2 and targs['param2_valid'] then
found_arg = 1
end
if found_arg == 0 and tonumber(k) == 3 and targs['param3_valid'] then
found_arg = 1
end
if found_arg == 0 then
if pargs[k] and pargs[k] ~= '' then
if tonumber(k) then
k = k .. ' = ' .. v
end
table.insert(possibly_ignored, k)
else
if tonumber(k) then
k = k .. ' = ' .. v
end
table.insert(unknown, k)
end
end
end
end
-- Логические параметры, записанные не в каноническом виде: и заведомо сломанные
-- значения, и рабочие, но разнобойные. Разбираются они по наполнению категории,
-- поэтому в один список попадают и те и другие.
local bad_boolean = {}
for k in pairs(boolean_args) do
local v = targs[k]
if v ~= nil and v ~= '' and not p_sub.is_canonical_boolean(v) then
table.insert(bad_boolean, k)
end
end
-- Параметры, заданные без того, с которым они работают в паре (requires_param).
local useless_params = {}
for k, needed in pairs(requires_param) do
if targs[k] ~= nil and targs[k] ~= '' and (targs[needed] == nil or targs[needed] == '') then
table.insert(useless_params, k)
end
end
-- Результаты матчей, записанные не так, как принято. Собираются три рода случаев.
-- Счёт через дефис или тире вместо двоеточия: модуль такую запись понимает
-- и приводит к общему виду, это разнобой, а не поломка.
-- Счёт с припиской вроде «3:0<br>возд.». Оформление снимается перед проверкой, поэтому
-- «[[Отчёт|2:1]]» и «'''2:1'''» проходят, а приписка — нет: после снятия там остаётся
-- не только счёт. Пояснению место в match_ААА_БББ_note, а не рядом со счётом.
-- Значение, не распознанное ни как счёт, ни как дата будущего матча или пояснение.
-- Так выглядят опечатки вроде «-:0» или «2;1»: счётом они не считаются, в таблицу
-- не попадают, и заметить их можно только по пустой клетке в шахматке.
-- Что не отмечается:
-- «2:1» и «10:0» — счёт, в том числе обёрнутый ссылкой или шаблоном;
-- «+:−» и «−:+» — техническая победа и поражение;
-- «null» — матч намеренно не показывается;
-- пустое значение после снятия оформления — счёт целиком внутри шаблона;
-- текст, где есть две буквы подряд, — дата будущего матча, «перенесён», «отменён».
local bad_score = {}
for k, v in pairs(targs) do
if type(k) == 'string' and type(v) == 'string' and v ~= ''
and k:find('^match[%d]*_.*_.*$') and not k:find('_note$') and k ~= 'match_col_width'
and not v:find('^%d+:%d+$') then
-- Обычный счёт вида «2:1» пропускается сразу, остальное разбирается.
-- Оформление снимается: счёт часто обёрнут ссылкой на отчёт, шаблоном
-- уменьшенного шрифта или полужирным. Сноска через ref убирается вместе
-- с содержимым — она к счёту не относится. Так же убирается служебная метка сноски:
-- её оставляет и ref, развёрнутый до вызова модуля, и сноска к матчу (match_ХХХ_УУУ_note),
-- которую шахматка дописывает к счёту в Args раньше, чем идёт эта проверка.
local s = mw.ustring.gsub(v, '\127[^\127]*\127', '')
s = mw.ustring.gsub(s, '<ref[^<>]*>.-</ref>', '')
s = mw.ustring.gsub(s, '<ref[^<>]*/>', '')
s = mw.ustring.gsub(s, '%[%[[^%[%]|]*|([^%[%]]*)%]%]', '%1')
s = mw.ustring.gsub(s, '%[%[([^%[%]]*)%]%]', '%1')
s = mw.ustring.gsub(s, '%b{}', '')
s = mw.ustring.gsub(s, '</?%w+[^<>]*>', '')
-- Скобки вокруг gsub обязательны: без них вторым аргументом trim получил бы
-- число замен и принял бы его за набор обрезаемых знаков — «2:0» теряло бы «0».
s = mw.text.trim((mw.ustring.gsub(s, "'+", '')))
if mw.ustring.match(s, '^%s*%d+%s*[%-–—−]%s*%d+%s*$') then
table.insert(bad_score, k)
elseif mw.ustring.match(s, '^%s*%d+%s*:%s*%d+') then
if not mw.ustring.match(s, '^%s*%d+%s*:%s*%d+%s*$') then
table.insert(bad_score, k)
end
elseif not mw.ustring.match(s, '^%s*[%+%-−]%s*:%s*[%+%-−]%s*$')
and mw.ustring.lower(s) ~= 'null'
and s ~= ''
and not mw.ustring.find(s, '%a%a') then
table.insert(bad_score, k)
end
end
end
-- Списки превращаются в ссылки на категории. Значение идёт в ключ сортировки,
-- и опасны там только знаки разметки: квадратная скобка закрывает ссылку посреди ключа,
-- вертикальная черта делит её на части, фигурные открывают шаблон, перевод строки разрывает ссылку.
-- Список запрещённых знаков, а не разрешённых: разрешающий список из латиницы и цифр
-- превратил бы кириллицу в вопросительные знаки, ключи вида match_БАВ_МЮ стали бы
-- нечитаемыми, и записи в категории перестали бы различаться.
local bad_char = '[%[%]|{}\n\r]'
validate_table_values(warn, track, unknown, categories.unknown_parameters, bad_char, false)
validate_table_values(warn, track, bad_boolean, categories.bad_boolean, bad_char, false)
validate_table_values(warn, track, bad_score, categories.bad_score, bad_char, true)
validate_table_values(warn, track, useless_params, categories.useless_params, bad_char, false)
validate_table_values(warn, track, bad_color, categories.bad_color, bad_char, false)
validate_table_values(warn, track, manual_format, categories.manual_format, bad_char, false)
validate_table_values(warn, track, untranslated, categories.untranslated_short, bad_char, true)
validate_table_values(warn, track, manual_teamwidth, categories.manual_teamwidth, bad_char, true)
validate_table_values(warn, track, unfinished_update, categories.unfinished_update, bad_char, true)
validate_table_values(warn, track, missing_teams, categories.missing_teams, bad_char, false)
validate_table_values(warn, track, missing_results, categories.missing_results, bad_char, false)
validate_table_values(warn, track, orphaned_results, categories.orphaned_results, bad_char, false)
validate_table_values(warn, track, orphaned_names, categories.orphaned_names, bad_char, false)
validate_table_values(warn, track, orphaned_notes, categories.orphaned_notes, bad_char, false)
validate_table_values(warn, track, orphaned_match_notes, categories.orphaned_match_notes, bad_char, false)
validate_table_values(warn, track, ignored, categories.ignored_parameters, bad_char, false)
validate_table_values(warn, track, possibly_ignored, categories.possibly_ignored_parameters, bad_char, true)
if possible_missing_winpoints > 1 then
table.insert(track, get_tracking_category(categories.possible_incorrect_winpoints, possible_missing_winpoints))
end
return warn, track
end
return a