Модуль:Список серий

Материал из Википедии — свободной энциклопедии
Перейти к навигации Перейти к поиску
Документация

Документацию смотри на странице шаблонов {{Таблица эпизодов}} и {{Список серий}}

require('strict')

local p = {}

local tableEmptyCellModule = require('Модуль:Пустая ячейка таблицы')
local colorContrastModule = require('Модуль:Color contrast')
local htmlColor = mw.loadData('Модуль:Color contrast/colors')
local delinkModule = require('Модуль:Delink')
local yesNoModule = require('Модуль:Yesno')
local mathModule = require('Модуль:Math')

local lower = mw.ustring.lower
local match = mw.ustring.match
local find = mw.ustring.find
local gsub = mw.ustring.gsub
local sub = mw.ustring.sub
local len = mw.ustring.len


local row
local nonNilParams = 0
local cellValueTBA = false
local trackingCategories = '[[Категория:Википедия:Статьи, использующие шаблон Список серий]]'

local trackingCategoryList = {
	['air_dates']='[[Категория:Википедия:Списки эпизодов с неоформленной датой показа]]',
	['alt_air_dates']='[[Категория:Википедия:Списки эпизодов с неверно оформленной датой показа]]',
	['faulty_line_colors']='[[Категория:Википедия:Списки эпизодов с неверным цветом границ]]',
	['non_compliant_line_colors']='[[Категория:Википедия:Списки эпизодов с неверным цветом границ]]',
	['default_line_colors']='[[Категория:Википедия:Списки эпизодов с неверным цветом границ]]',
	['row_deviations']='[[Категория:Википедия:Списки эпизодов с отклонениями строк]]',
	['invalid_top_colors']='[[Категория:Википедия:Потенциально нечитаемые таблицы эпизодов]]',
	['tba_values']='[[Категория:Википедия:Списки эпизодов с незаполненными ячейками]]',
	['nonmatching_numbered_parameters']='[[Категория:Википедия:Списки эпизодов с неверными параметрами]]',
	['raw_unformatted_storyteleplay']='[[Категория:Википедия:Списки эпизодов с неоформленными сюжетом или телесценарием]]'
}

local cellNameList = {
	'Aux1', 'Столбец1',
	'DirectedBy', 'Режиссёр', 'Режиссер',
	'WrittenBy', 'Сценарист',
	'Aux2', 'Столбец2',
	'Aux3', 'Столбец3',
	'OriginalAirDate', 'ДатаПоказа',
	'AltDate', 'ДатаПоказа2',
	'Guests', 'Гости',
	'MusicalGuests', 'Персоны',
	'ProdCode', 'ПродКод',
	'Viewers', 'Зрителей',
	'Aux4', 'Столбец4'
}

-- Список взаимоисключающих параметров
local excludeList = {
	['Guests']='Aux1', ['Гости']='Столбец2',
	['MusicalGuests']='Aux2', ['Персоны']='Столбец3'
}

-- Список ячеек, имеющих группы параметров
local parameterGroupCells = {}
local firstParameterGroupCell
local parameterGroupCellsAny = false

-- Список параметров заголовка для списков с несколькими названиями.
local titleList = {
	'Title', 'Название',
	'RTitle', 'НазваниеПрим',
	'AltTitle', 'Название2',
	'RAltTitle', 'Название2Прим',
	'NativeTitle', 'НазваниеОригинал',
	'TranslitTitle', 'НазваниеТранслит'
}

-- Локальная функция для получения номера эпизода или производственного кода,
-- без какого-либо дополнительного текста.
local function idTrim(val, search)
	local valFind = find(val, search)
	if not valFind then
		return val
	end
	return sub(val, 0, valFind - 1)
end

-- Локальная функция для проверки того, что параметр имеет значение.
local function hasValue(param)
	if param and param:find('%S') then
		return true
	end
end

-- Локальная функция для создания транслитерации.
local function langSpan(translitNativeTitle, langCode)
	local line = mw.html.create('span')
	if hasValue(langCode) then
		line:attr('lang', langCode)
			:attr('xml:lang', langCode)
			:css('font-style', 'normal')
			:wikitext(translitNativeTitle)
	end
	return tostring(line)
end

-- Локальная функция для создания аббревиатуры
local function abbr(text, title)
	local abbr = mw.html.create('abbr')
	abbr:attr('title', title)
		:wikitext(text)
	return tostring(abbr)
end

-- Локальная функция для создания ячейки Таблицы эпизодов.
local function createTableData(text, rowSpan, textAlign)
	if rowSpan and tonumber(rowSpan) > 1 then
		row:tag('td')
			:attr('rowspan', rowSpan)
			:wikitext(text)
	else
		row:tag('td')
			:css('text-align', textAlign)
			:wikitext(text)
	end
end

-- Локальная функция для добавления отслеживающей категории  на страницу.
local function addTrackingCategory(category)
	trackingCategories = trackingCategories .. category
end

-- Локальная функция для создания Краткого Содержания.
local function createShortSummaryRow(args, lineColor, linetop)
	local shortSummary = args.ShortSummary or args['КраткоеСодержание']

	-- исправление Краткого Содержания
	if hasValue(shortSummary) and (match(shortSummary, '^[*:;#]') or match(shortSummary, '^{|')) then
		shortSummary = '<span></span>\n' .. shortSummary
	end

	if hasValue(shortSummary) and match(shortSummary, '\n[*:;#]') then
		shortSummary = shortSummary .. '\n<span></span>'
	end

	if linetop then
		shortSummary = nil
	end

	local shortSummaryCell = mw.html.create('td')
	shortSummaryCell
		:addClass('description')
		:css('border-bottom', 'solid 3px ' .. lineColor)
		:attr('colspan', nonNilParams)
		:newline()
		:wikitext(shortSummary)

	local cell = mw.html.create('tr')
	cell:addClass('expand-child')
		:node(shortSummaryCell)

	if linetop then
		local lencell = len(cell)
		cell = sub(cell, 1, lencell - 10)
	end

	return tostring(cell)
end

-- Локальная функция для добавления отслеживающей категории при проблемах с цветом.
local function addTopColorTrackingCategories(args)
	local topColor = args.TopColor or args['ЦветСтроки']
	if hasValue(topColor) or '#eaecf0' then
		addTrackingCategory(trackingCategoryList['row_deviations'])

		-- Проверка контрастности в соответствии с [[ВП:ЦВЕТ]].
		local textContrastRatio = colorContrastModule._ratio{topColor, 'black', ['error']=0}
		local linkContrastRatio = colorContrastModule._ratio{'#0B0080', topColor, ['error']=0}
		local visitedLinkContrastRatio = colorContrastModule._ratio{topColor, '#0645AD', ['error']=0}

		if textContrastRatio < 4.5 or linkContrastRatio < 3 or visitedLinkContrastRatio < 3 then
			addTrackingCategory(trackingCategoryList['invalid_top_colors'])
		end
	end
end

-- Локальная функция для добавления отслеживающей категории при проблемах с цветом границы.
local function addLineColorTrackingCategories(args)
	local lineColor = args.LineColor or args['ЦветГраницы']  --  or '#a2a9b1'
	if hasValue(lineColor) then
		local blackContrastRatio = colorContrastModule._ratio{lineColor, 'black', ['error']=0}
		local whiteContrastRatio = colorContrastModule._ratio{'white', lineColor, ['error']=0}

		if lineColor == '' then
			addTrackingCategory(trackingCategoryList['faulty_line_colors'])
		elseif blackContrastRatio < 3 and whiteContrastRatio < 3 then
			addTrackingCategory(trackingCategoryList['non_compliant_line_colors'])
		end
	else
		addTrackingCategory(trackingCategoryList['default_line_colors'])
	end
end

-- Локальная функция для удаления вики-ссылок из повторяющейся информации в ячейках.
local function removeWikilinks(args, v)
	return delinkModule._delink({args[v]})
end

-- Локальная функция для установки текста пустой ячейки либо 'TBD' - to be announced, либо  'н/д' - нет данных.
local function setTBDStatus(args, awaitingText, expiredText)
	local year, day, month, seconds
	local originalAirDate = args.OriginalAirDate or args['ДатаПоказа']


	if not hasValue(originalAirDate) then
		return tableEmptyCellModule._main({alt_text=awaitingText, title_text=awaitingText})
	end

	originalAirDate = gsub(originalAirDate, '&nbsp;', ' ')
	originalAirDate = gsub(originalAirDate, '[%[%]]', '')

	if match(originalAirDate, '{{[Ss]tart date') or match(originalAirDate, '-') then
		originalAirDate = gsub(originalAirDate, '[-%s|]+', ' ')
		year, month, day = match(originalAirDate, '(%d+) (%d+) (%d+)')
	elseif match(originalAirDate, ',') then
		month, day, year = match(originalAirDate, '([%a%A]+) (%d+), (%d+)')
	elseif match(originalAirDate, '[./]') then
		originalAirDate = gsub(originalAirDate, '[%s./|]+', ' ')
		day, month, year = match(originalAirDate, '(%d+) (%d+) (%d+)')
	else
		originalAirDate = gsub(originalAirDate, '[-%s.,/|]+', ' ')
		day, month, year = match(originalAirDate, '(%d+) ([%a%A]+) (%d+)')
	end

	if not day then
		return tableEmptyCellModule._main({alt_text='TBD'})
	end

	-- Список месяцев.
	local monthList = {
		['января']=1, ['январь']=1, ['january']=1, ['1']=1, ['01']=1, 1,
		['февраля']=2, ['февраль']=2, ['february']=2, ['2']=2, ['02']=2, 2,
		['марта']=3, ['март']=3, ['march']=3, ['3']=3, ['03']=3, 3,
		['апреля']=4, ['апрель']=4, ['april']=4, ['4']=4, ['04']=4, 4,
		['мая']=5, ['май']=5, ['may']=5, ['5']=5, ['05']=5, 5,
		['июня']=6, ['июнь']=6, ['june']=6, ['6']=6, ['06']=6, 6,
		['июля']=7, ['июль']=7, ['july']=7, ['7']=7, ['07']=7, 7,
		['августа']=8, ['август']=8, ['august']=8, ['8']=8, ['08']=8, 8,
		['сентября']=9, ['сентябрь']=9, ['september']=9, ['9']=9, ['09']=9, 9,
		['октября']=10, ['октябрь']=10, ['october']=10, ['10']=10, 10,
		['ноября']=11, ['ноябрь']=11, ['november']=11, ['11']=11, 11,
		['декабря']=12, ['декабрь']=12, ['december']=12, ['12']=12, 12
	}

	local monthnumber = monthList[lower(month)]
	if not monthnumber then
		error('Неверный месяц ' .. month)
	end

	seconds = os.time() - os.time({year=year, month=monthnumber, day=day, hour=0, min=0, sec=0})

	-- 60 * 60 * 24 * 7 * 4 = 2419200 секунд в 4-х неделях
	if seconds >= 2419200 then
		return tableEmptyCellModule._main({alt_text=expiredText, title_text=expiredText})
	end

	return tableEmptyCellModule._main({alt_text=awaitingText, title_text=awaitingText})
end

-- Local function which is used to create an empty cell.
local function createEmptyCell(args, v, unsetParameterGroup)
	local originalAirDate = args.OriginalAirDate or args['ДатаПоказа']
	local directedByAll = 'DirectedBy Режиссёр Режиссер'
	local writtenByAll = 'WrittenBy Сценарист'
	local viewersAll = 'Viewers Зрителей'
	if unsetParameterGroup then
		args[v] = tableEmptyCellModule._main({alt_text='н/д'})
	elseif find(viewersAll, v) and hasValue(originalAirDate) then
		args[v] = setTBDStatus(args, 'TBD', 'н/д')
	elseif find(directedByAll, v) or find(writtenByAll, v) then
		args[v] = setTBDStatus(args, 'TBA', 'Неизвестно')
	else
		args[v] = tableEmptyCellModule._main({})
	end
end

-- Air dates that don't use {{Start date}}
local function checkUsageOfDateTemplates(args, v, onInitialPage, title)
	local originalAirDateAll = 'OriginalAirDate ДатаПоказа'
	local altDateAll = 'AltDate ДатаПоказа2'

	if find(originalAirDateAll, v)
		and hasValue(args[v])
		and match(args[v], '%d%d%d%d')
		and not match(args[v], '2C2C2C')
		and not find(args[v], 'dtstart')
		and onInitialPage
		and title.namespace == 0
	then
		addTrackingCategory(trackingCategoryList['air_dates'])
	end

	-- Alternate air dates that do use {{Start date}}
	if find(altDateAll, v)
			and hasValue(args[v])
			and find(args[v], 'dtstart')
			and onInitialPage
			and title.namespace == 0
	then
		addTrackingCategory(trackingCategoryList['alt_air_dates'])
	end
end

--[[
Local function which is used to extract data
from the numbered serial parameters (Title1, Aux1, etc.), and then convert them to
use the non-numbered parameter names (Title, Aux).

The function returns the args as non-numbered prameter names.
]]
local function extractDataFromNumberedSerialArgs(args, i, numberOfParameterGroups, title)
	local prodCodeAll = 'ProdCode ПродКод'

	for _, v in ipairs(cellNameList) do
		local parameter = v
		local numberedParameter = v .. '_' .. i
		local excludeParameter = excludeList[parameter] or 'NULL' .. parameter
		local excludeNumberParameter = (excludeList[numberedParameter] or 'NULL' .. parameter) .. '_' .. i
		if not hasValue(args[numberedParameter]) and not hasValue(args[excludeNumberParameter])
			and hasValue(parameterGroupCells[parameter]) and not hasValue(args[excludeParameter])
		then
			if find(prodCodeAll, v) then
				createEmptyCell(args, parameter, true)
			else
				args[parameter] = ''
			end
			if title.namespace == 0 then
				addTrackingCategory(trackingCategoryList['nonmatching_numbered_parameters'])
			end
		elseif hasValue(args[numberedParameter]) and not hasValue(args[excludeNumberParameter]) then
			args[parameter] = args[numberedParameter]
		end
	end

	return args
end

--[[
Local function which is used to create column cells.

EpisodeNumber, EpisodeNumber2 and Title are created in different functions
as they require some various if checks.

See:
	-- createEpisodeNumberCell()
	-- createEpisodeNumberCellSecondary()
	-- createTitleCell()
]]
local function createCells(args, isSerial, currentRow, onInitialPage, title, numberOfParameterGroups)
	local prodCodeAll = 'ProdCode ПродКод'
	local writtenByAll = 'WrittenBy Сценарист'
	local aux1All = 'Aux1 Столбец1'

	for k, v in ipairs(cellNameList) do
		if args[v] then
			-- Set empty cells to TBA/TBD
			if args[v] == '' then
				createEmptyCell(args, v, false)
			elseif find(writtenByAll, v) and title.namespace == 0 then
				if (find(args[v], "''Сюжет")
						or find(args[v], "''Сценарий")
						or find(args[v], "''Телесценарий")
						or find(args[v], "''Телепередача")
						or find(args[v], "''Передача"))
						and not find(args[v], '8202')
				then
					-- &#8202; is the hairspace added through {{StoryTeleplay}}
					addTrackingCategory(trackingCategoryList['raw_unformatted_storyteleplay'])
				end
			end

			-- If serial titles need to be centered and not left, then this should be removed.
			local textAlign = 'center'
			if find(aux1All, v) and isSerial then
				textAlign = 'left'
			end

			local thisRowspan
			if firstParameterGroupCell and k < firstParameterGroupCell then
				thisRowspan = numberOfParameterGroups
			else
				thisRowspan = 1
			end

			if currentRow == 1 or (currentRow > 1 and k >= (firstParameterGroupCell or 0)) then
				createTableData(args[v], thisRowspan, textAlign)
			end
			nonNilParams = nonNilParams + 1
			checkUsageOfDateTemplates(args, v, onInitialPage, title)
		end

		if args[v] == 'TBA' then
			cellValueTBA = true
		end
	end
end

--[[
Local function which is used to create the Title cell text.

The title text will be handled in the following way:
	Line 1: <Title><RTitle> (with no space between)
	Line 2: <AltTitle><RAltTitle> (with no space between) OR
	Line 2: Transcription: <TranslitTitle> (<Language>: <NativeTitle>)<RAltTitle> (with space between first two parameters)

	If <Title> or <RTitle> are empty,
	then the values of line 2 will be placed on line 1 instead.

--]]
local function createTitleText(args)
	local title = args.Title or args['Название']
	local rTitle = args.RTitle or args['НазваниеПрим']
	local altTitle = args.AltTitle or args['Название2']
	local rAltTitle = args.RAltTitle or args['Название2Прим']
	local nativeTitle = args.NativeTitle or args['НазваниеОригинал']
	local translitTitle = args.TranslitTitle or args['НазваниеТранслит']
	local nativeTitleLangCode = args.NativeTitleLangCode or args['КодЯзыка']
	local titleString = ''
	local isCellPresent = false
	local useSecondLine = false
	local lineBreakUsed = false

	-- «Заголовок в кавычках». Если пустой, то без кавычек.
	if title then
		isCellPresent = true
		if hasValue(title) then
			-- Название
			titleString = "&#171;'''" .. title .. "'''&#187;"
			useSecondLine = true
		end
	end

	if rTitle then
		isCellPresent = true
		if hasValue(rTitle) then
			titleString = titleString .. rTitle
			useSecondLine = true
		end
	end

	-- Surround the AltTitle/TranslitTitle with quotes; No quotes if empty.
	if altTitle or translitTitle then
		isCellPresent = true
		if useSecondLine then
			titleString = titleString .. '<br>'
			lineBreakUsed = true
		end

		if hasValue(altTitle) then
			-- Название2
			titleString = titleString .. "&#171;''" .. altTitle .. "''&#187;"
		elseif hasValue(translitTitle) then
			titleString = titleString .. abbr('транслит.', 'транслитерация названия') .. '.: &#171;'
			if hasValue(nativeTitleLangCode) then
				titleString = titleString .. langSpan(translitTitle, nativeTitleLangCode) .. '&#187;'
			else
				titleString = titleString .. translitTitle .. '&#187;'
			end
		end
	end

	if nativeTitle then
		isCellPresent = true
		if hasValue(nativeTitle) then
			if useSecondLine and not lineBreakUsed then
				titleString = titleString .. '<br>'
			end

			titleString = titleString .. ' (' .. abbr('ориг.', 'оригинальное название') .. '.: '
			if hasValue(nativeTitleLangCode) then
				titleString = titleString .. langSpan(nativeTitle, nativeTitleLangCode) .. ')'
			else
				titleString = titleString .. nativeTitle .. ')'
			end
		end
	end

	if rAltTitle then
		isCellPresent = true
		if hasValue(rAltTitle) then
			if useSecondLine and not lineBreakUsed then
				titleString = titleString .. '<br>'
			end
			titleString = titleString .. rAltTitle
		end
	end

	return titleString, isCellPresent
end

--[[
Local function which is used to extract data
from the numbered title parameters (Title1, RTitle2, etc.), and then convert them to
use the non-numbered prameter names (Title, RTitle).

The function returns two results:
	-- The args parameter table.
	-- A boolean indicating if the title group has data.
]] --
local function extractDataFromNumberedTitleArgs(args, i)
	local nextGroupValid = false

	for _, v in ipairs(titleList) do
		local parameter = v
		local numberedParameter = v .. '_' .. i
		args[parameter] = args[numberedParameter]

		if not nextGroupValid and hasValue(args[numberedParameter]) then
			nextGroupValid = true
		end
	end

	return args, nextGroupValid
end

-- Local function which is used to process the multi title list.
local function processMultiTitleList(args, numberOfParameterGroups)
	local nativeTitleLangCode = args.NativeTitleLangCode or args['КодЯзыка']
	local titleText = ''
	local isCellPresent = false
	local isFirstTitleGroup = true -- Ячейка заголовка создана хотя бы один раз и не создается повторно, если другие заголовки пусты.

	for i=1, numberOfParameterGroups do
		local args, nextGroupValid = extractDataFromNumberedTitleArgs(args, i)
		if nextGroupValid then
			if not isFirstTitleGroup then
				titleText = titleText .. '<hr>'
			end

			local titleTextRow = createTitleText(args)
			titleText = titleText .. titleTextRow
			isFirstTitleGroup = false
		else
			if isFirstTitleGroup then
				titleText, isCellPresent = createTitleText(args)
			end

			-- Valid titles have to be in succession (#1, #2, #3 and not #1, #4 #5), so exit for loop if next group is empty.
			return titleText, isCellPresent
		end
	end

	return titleText
end

-- Local function which is used to create a Title cell.
local function createTitleCell(args, numberOfParameterGroups, multiTitleListEnabled, isSerial)
	local titleText, isCellPresent

	if multiTitleListEnabled then
		titleText, isCellPresent = processMultiTitleList(args, numberOfParameterGroups)
	else
		titleText, isCellPresent = createTitleText(args)
	end

	if not isCellPresent then
		return nil
	end

	local textAlign = 'left'

	-- If Title is blank, then set Raw Title to TBA
	if not hasValue(titleText) then
		titleText = tableEmptyCellModule._main({})
		textAlign = 'left'
	end

	-- If title is the first cell, create it with a !scope='row'
	if nonNilParams == 0 then
		if isSerial then
			row:tag('th')
				:addClass('summary')
				:attr('scope', 'row')
				:attr('rowspan', numberOfParameterGroups)
				:css('text-align', textAlign)
				:wikitext(titleText)
		else
			row:tag('th')
				:addClass('summary')
				:attr('scope', 'row')
				:css('text-align', textAlign)
				:wikitext(titleText)
		end
	else
		if isSerial then
			row:tag('td')
				:addClass('summary')
				:attr('rowspan', numberOfParameterGroups)
				:css('text-align', textAlign)
				:wikitext(titleText)
		else
			row:tag('td')
				:addClass('summary')
				:css('text-align', textAlign)
				:wikitext(titleText)
		end
	end

	nonNilParams = nonNilParams + 1
end

-- Local function which is used to create a table row header for either the
-- EpisodeNumber or EpisodeNumber2 column cells.
local function createTableRowEpisodeNumberHeader(episodeNumber, numberOfParameterGroups, episodeText)
	local epID = match(episodeNumber, '^%w+')
	row:tag('th')
		:attr('scope', 'row')
		:attr('rowspan', numberOfParameterGroups)
		:attr('id', epID and 'ep' .. epID or '')
		:css('text-align', 'center')
		:wikitext(episodeText)
end

--[[
Local function which is used to extract the text from the EpisodeNumber or EpisodeNumber2
parameters and format them into a correct MoS compliant version.

Styles supported:
	-- A number range of two numbers, indicating the start and end of the range,
	seperated by an en-dash (–) with no spaces in between.
		Example: '1 - 2' -> '1–2'; '1-2-3' -> '1–3'.
	-- An alphanumeric or letter range, similar to the above.
		Example: 'A - B' -> 'A–B'; 'A-B-C' -> 'A–C'.
		Example: 'A1 - B1' -> 'A1–B1'; 'A1-B1-C1' -> 'A1–C1'.
	-- A number range of two numbers, indicating the start and end of the range,
	seperated by a visual <hr> (divider line).
	-- An alphanumeric or letter range, similar to the above.
]] --
local function getEpisodeText(episodeNumber)
	if episodeNumber == '' then
		return tableEmptyCellModule._main({})
	else
		local episodeNumber1, episodeNumber2

		-- Used for double episodes that need a visual '–' or '<hr>' added.
		local divider = '<hr>'

		episodeNumber = episodeNumber:gsub('%s*<br%s*/?%s*>%s*', '<hr>')

		if episodeNumber:match('^(%w+)%s*<hr */%s*>%s*(%w+)$') then
			episodeNumber1, episodeNumber2 = episodeNumber:match('^(%w+)%s*<hr */%s*>%s*(%w+)$')
			--divider = '<hr>'
		elseif episodeNumber:match('^(%w+)%s*<hr */%s*>.-<hr */%s*>%s*(%w+)$') then -- 3 or more elements
			episodeNumber1, episodeNumber2 = episodeNumber:match('^(%w+)%s*<hr */%s*>.-<hr */%s*>%s*(%w+)$')
			--divider = '<hr>'
		elseif match(episodeNumber, '^(%w+)%s*[%s%-–/&]%s*(%w+)$') then
			episodeNumber1, episodeNumber2 = match(episodeNumber, '^(%w+)%s*[%s%-–/&]%s*(%w+)$')
			--divider = '–'
		else
			episodeNumber1, episodeNumber2 = match(episodeNumber, '^(%w+)%s*[%s%-–/&].-[%s%-–/&]%s*(%w+)$') -- 3 or more elements
			--divider = '–'
		end

		if not episodeNumber1 then
			return episodeNumber
		elseif not episodeNumber2 then
			return match(episodeNumber, '%w+')
		else
			return episodeNumber1 .. divider .. episodeNumber2
		end
	end
end

-- Local function which is used to create EpisodeNumber2 and EpisodeNumber3 cells.
local function _createEpisodeNumberCellSecondary(episodeValue, numberOfParameterGroups)
	if episodeValue then
		local episodeText = getEpisodeText(episodeValue)

		if nonNilParams == 0 then
			createTableRowEpisodeNumberHeader(episodeValue, numberOfParameterGroups, episodeText)
		else
			createTableData(episodeText, numberOfParameterGroups, 'center')
		end

		nonNilParams = nonNilParams + 1

	end
end

-- Local function which is used to create seconday episode number cells.
local function createEpisodeNumberCellSecondary(args, numberOfParameterGroups)
	local episodeNumber2 = args.EpisodeNumber2 or args['НомерЭпизода2']
	local episodeNumber3 = args.EpisodeNumber3 or args['НомерЭпизода3']
	_createEpisodeNumberCellSecondary(episodeNumber2, numberOfParameterGroups)
	_createEpisodeNumberCellSecondary(episodeNumber3, numberOfParameterGroups)
end

-- Local function which is used to create an EpisodeNumber cell.
local function createEpisodeNumberCell(args, numberOfParameterGroups)
	local episodeNumber = args.EpisodeNumber or args['НомерЭпизода']
	if episodeNumber then
		local episodeText = getEpisodeText(episodeNumber)
		createTableRowEpisodeNumberHeader(episodeNumber, numberOfParameterGroups, episodeText)
		nonNilParams = nonNilParams + 1
	end
end

-- Local function which is used to create a single row of cells.
-- This is the standard function called.
local function createSingleRowCells(args, numberOfParameterGroups, multiTitleListEnabled, onInitialPage, title)
	createEpisodeNumberCell(args, 1)
	createEpisodeNumberCellSecondary(args, 1)
	createTitleCell(args, numberOfParameterGroups, multiTitleListEnabled, false)
	createCells(args, false, 1, onInitialPage, title, numberOfParameterGroups)
end

-- Local function which is used to create a multiple row of cells.
-- This function is called when part of the row is rowspaned.
-- Current use is for Doctor Who serials.
local function createMultiRowCells(args, numberOfParameterGroups, onInitialPage, title, topColor)
	createEpisodeNumberCell(args, numberOfParameterGroups)
	createEpisodeNumberCellSecondary(args, numberOfParameterGroups)
	createTitleCell(args, numberOfParameterGroups, false, true)

	for i=1, numberOfParameterGroups do
		args = extractDataFromNumberedSerialArgs(args, i, numberOfParameterGroups, title)
		createCells(args, true, i, onInitialPage, title, numberOfParameterGroups)
		if i ~= numberOfParameterGroups then
			row = row:done()-- Use done() to close the 'tr' tag in rowspaned rows.
				:tag('tr')
				:css('background', topColor)
		end
	end
end

-- Local function which is used to retrieve the NumParts value.
local function getnumberOfParameterGroups(args)
	local numParts = args.NumParts or args['КоличествоЧастей']
	for k, v in ipairs(cellNameList) do
		local numberedParameter = v .. '_' .. 1
		if args[numberedParameter] then
			parameterGroupCells[v] = true
			if not firstParameterGroupCell then
				firstParameterGroupCell = k
			end
		end
	end

	if hasValue(numParts) then
		return numParts, true
	else
		return 1, false
	end
end

-- Local function which is used to retrieve the remainder value after x has been divided by y
local function mathMod(x, y)
	local ret = x % y
	if not (0 <= ret and ret < y) then
		ret = 0
	end
	return ret
end

-- Local function which is used to retrieve the Top Color value.
local function getTopColor(args, rowColorEnabled, onInitialPage)
	local episodeNumber = args.EpisodeNumber or args['НомерЭпизода']
	local topColor = args.TopColor or args['ЦветСтроки']
	local shortSummary = args.ShortSummary or args['КраткоеСодержание']
	local episodeNumber = mathModule._cleanNumber(episodeNumber) or 1
	if topColor then
		if find(topColor, '#') then
			return topColor
		else
			return '#' .. topColor
		end
	elseif (rowColorEnabled and onInitialPage and mathMod(episodeNumber, 2) == 0) then
		return 'var(--background-color-neutral, #eaecf0)'
	elseif onInitialPage and shortSummary then
		return 'var(--background-color-neutral-subtle, #f8f9fa)'
	else
		return 'inherit'
	end
end

-- Локальная функция, которая используется для включения чередования цвета строк.
local function isRowColorEnabled(args)
	local rowColor = args.RowColor or args['ЧередованиеЦвета']
	return yesNoModule(rowColor, nil)
end

-- Local function which is used to retrieve the Line Color value.
local function getLineColor(args)
	-- Default color to light blue
	local lineColor = args.LineColor or args['ЦветГраницы'] or 'CCCCFF'

	-- Add # to color if necessary, and set to default color if invalid
	if not htmlColor[lineColor] then
		lineColor = '#' .. (match(lineColor, '^[%s#]*([a-fA-F0-9]*)[%s]*$') or '')
		if lineColor == '#' then
			lineColor = '#CCCCFF'
		end
	end

	return lineColor
end

-- Local function which is used to check if the table is located on the page
-- currently viewed, or on a transcluded page instead.
-- If it is on a transcluded page, the episode summary should not be shown.
local function isOnInitialPage(args, sublist, pageTitle, initiallistTitle)
	-- This should be the only check needed, however, it was previously implemented with two templates
	-- with one of them not requiring an article name, so for backward compatability, the whole sequence is kept.
	local onInitialPage = false

	-- Only sublist had anything about hiding, so only it needs to even check
	if sublist then
		local pageTitleMatch = match(pageTitle, 'Список эпизодов', 1) -- Результат: Список эпизодов или nil

		if not initiallistTitle and pageTitleMatch == 'Список эпизодов' then -- Автоопределение страницы 'Список эпизодов ...'
			onInitialPage = true
		elseif hasValue(initiallistTitle) then
			onInitialPage = mw.uri.anchorEncode(pageTitle) == mw.uri.anchorEncode(initiallistTitle)
		end
	end

	return onInitialPage
end

-- Local function which does the actual main process.
local function _main(args, sublist, linetop)
	local title = mw.title.getCurrentTitle()
	local title2 = args.Title_2 or args['Название_2']
	local pageTitle = title.text
	local initiallistTitle = args['1'] or ''
	local shortSummary = args.ShortSummary or args['КраткоеСодержание']

	-- Is this list on the same page as the page directly calling the template?
	local onInitialPage = isOnInitialPage(args, sublist, pageTitle, initiallistTitle)

	-- Need just this parameter removed if blank, no others
	if not hasValue(shortSummary) then
		shortSummary = nil
	end

	local lineColor = getLineColor(args)
	local rowColorEnabled = isRowColorEnabled(args)
	local topColor = getTopColor(args, rowColorEnabled, onInitialPage)

	local root = mw.html.create() -- Create the root mw.html object to return
	row = root:tag('tr')-- Create the table row and store it globally
			:addClass('vevent')
			:css('text-align', 'center')
			:css('background', topColor)
			:css('color', 'inherit')

	local numberOfParameterGroups, multiTitleListEnabled = getnumberOfParameterGroups(args)

	if multiTitleListEnabled and not title2 then
		createMultiRowCells(args, numberOfParameterGroups, onInitialPage, title, topColor)
	else
		createSingleRowCells(args, numberOfParameterGroups, multiTitleListEnabled, onInitialPage, title)
	end

	-- add these categories only in the mainspace and only if they are on the page where the template is used
	if onInitialPage and title.namespace == 0 then
		addLineColorTrackingCategories(args)
		addTopColorTrackingCategories(args)
	end

	if cellValueTBA and title.namespace == 0 then
		addTrackingCategory(trackingCategoryList['tba_values'])
	end

	local pageTitleMatch = match(pageTitle, 'Список эпизодов', 1)
	if pageTitleMatch == 'Список эпизодов' or title.namespace ~= 0 then
		trackingCategories = ''
	end

	-- Do not show the summary if this is being transcluded on the initial list page
	-- Do include it on all other lists
	if not onInitialPage and shortSummary then
		local bottomWrapper = createShortSummaryRow(args, lineColor)
		return tostring(root) .. tostring(bottomWrapper) .. trackingCategories
	elseif linetop then
		local bottomWrapper = createShortSummaryRow(args, lineColor)
		local bottomWrapperLen = len(bottomWrapper)
		local bottomWrapperEnd = sub(bottomWrapper, 1, bottomWrapperLen - 10)
		return tostring(root) .. tostring(bottomWrapperEnd) .. trackingCategories
	else
		return tostring(root) .. trackingCategories
	end
end

-- Local function which handles both module entry points.
local function main(frame, sublist, linetop)
	local getArgs = require('Модуль:Arguments').getArgs
	local args

	-- Most parameters should still display when blank, so don't remove blanks
	if sublist then
		args = getArgs(frame, {removeBlanks=false, wrappers='Шаблон:Список серий/sublist'})
	elseif linetop then
		args = getArgs(frame, {removeBlanks=false, wrappers='Шаблон:Список серий/шапка'})
	else
		args = getArgs(frame, {removeBlanks=false, wrappers='Шаблон:Список серий'})
	end

	return _main(args, sublist, linetop)
end

--------------------------------------------------------------------------------
-- Экспорт
--------------------------------------------------------------------------------

function p.sublist(frame)
	return main(frame, true, false)
end

function p.linetop(frame)
	return main(frame, false, true)
end

function p.list(frame)
	return main(frame, false, false)
end

return p