Permanently protected module
From Wikipedia, the free encyclopedia


-- This module implements {{Series overview}}.



require('strict')

local yesno = require('Module:Yesno')

local HTMLcolor = mw.loadData( 'Module:Color contrast/colors' )



--------------------------------------------------------------------------------

-- SeriesOverview class

-- The main class.

--------------------------------------------------------------------------------



local SeriesOverview = {}



function SeriesOverview.cellspan(SeasonEntries, SeasonEntries_ordered, key, cell, multipart, setspan)

	if setspan ~= nil then return setspan end

	

	local spanlength = 1

	

	local firstEntry = SeasonEntriesSeasonEntries_orderedcell]]

	if key == 'network' and firstEntry.networkA and not firstEntry.networkB then spanlength = 2 end

	

	for i = cell+1, #SeasonEntries_ordered do

		local entry = SeasonEntriesSeasonEntries_orderedi]]

		-- Split season, then regular season

		if entry.startA then

			if not entrykey..'A' then spanlength = spanlength + 1

			else break end

			if not entrykey..'B' then spanlength = spanlength + 1

			else break end

		else

			if not entrykey and (key == 'network' or ((string.sub(key,0,7) == 'postaux' or string.sub(key,0,3) == 'aux') and (not entry.special or entry.episodes)) or (string.sub(key,0,4) == 'info') and multipart) then

				spanlength = spanlength + 1

			else break end

		end

	end

	return spanlength

end



-- Sorting function

function SeriesOverview.series_sort(op1, op2)

	local n1,s1 = string.match(op1,"(%d+)(%a*)")

	local n2,s2 = string.match(op2,"(%d+)(%a*)")

	local n1N,n2N = tonumber(n1),tonumber(n2)



	if n1N == n2N then

		return s1 < s2

	else

		return n1N < n2N

	end

end



-- Function to add either text or {{N/a}} to cell

function SeriesOverview.season_cell(text, frame)

	local cell

	

	if string.find(text or '', 'table-na', 0, true) ~= nil then

		local findpipe = string.find(text, ' | ', 0, true)

		if findpipe ~= nil then

			cell = SeriesOverview.series_attributes( frame:expandTemplate{title='N/A',args={string.sub(text,findpipe+3)}} )

		else

			cell = SeriesOverview.series_attributes( frame:expandTemplate{title='N/A'} )

		end

	else

		cell = mw.html.create('td'):wikitext(text)

	end

	

	return cell

end



-- Allow usages of {{N/A}} cells

function SeriesOverview.series_attributes(infoParam)

	local entries = {}

	local infoCell = mw.html.create('td')

	local attrMatch = '([%a-]*)="([^"]*)"'

	

	while true do

		local a,b = string.match(infoParam,attrMatch)

		if a == nil or b == nil then break end

		infoCell:attr(a,b)

		infoParam = string.gsub(infoParam,attrMatch,'',1)

	end



	infoParam = string.gsub(infoParam,'%s*|%s*','',1)

	infoCell:wikitext(infoParam)

	

	return infoCell

end



function SeriesOverview.endtable()

	return "</table></div>"

end



function SeriesOverview.new(frame, args)

	args = args or {}

	

	local initialArticle = args'1' or ''

	local categories = ''

	local title = mw.title.getCurrentTitle()

	local allReleased = yesno(args.allreleased)



	-- Create series overview table

	local root = mw.html.create((args.multiseries or not args.series) and 'table' or '')

	local cellPadding = '0 8px'

	local basePadding = '0.2em 0.4em'



	root

		:addClass('wikitable')

		:addClass('plainrowheaders')

		:css('text-align', 'center')

		:css('height', '1px')

		:css('display', 'table')

	

	-- Sortable

	if args.sortable then

		root:addClass('sortable');

	end

	

	-- "Series overview" ID

	if args.id then

		root:attr('id', 'Series overview')

	end

	

	-- Width

	if args.width then

		root:css('width', args.width)

	end



	-- Caption

	if args.caption then

		root:tag('caption'):wikitext(frame:expandTemplate{title='Screen reader-only',args={args.caption}})

	end



	-- Extract seasons info and place into a 3D array

	local SeasonEntries = {}

	for k,v in pairs(args) do

		local str, num, str2 = string.match(k, '([^%d]*)(%d*)(%a*)')

		if tonumber(k) ~= 1 and num ~= '' then 

			-- Special

			local special = false

			if string.sub(str2,1,1) == 'S' then

				special = true

				num = num .. str2

				str2 = ''

			end

			-- Add to entries, create if necessary

			if not SeasonEntriesnum then

				SeasonEntriesnum = {}

			end

			SeasonEntriesnum][str .. str2 = v

			if special then

				SeasonEntriesnum]['special' = 'y'

			end

		end

	end



	-- Order table by season number

	local SeasonEntries_ordered = {}

	for k in pairs(SeasonEntries) do

		table.insert(SeasonEntries_ordered, k)

	end

	table.sort(SeasonEntries_ordered,SeriesOverview.series_sort)

	

	local firstRow = args.multiseries and {} or SeasonEntriesSeasonEntries_ordered1]]

	

	-- Colspan calculation for information cells (0 = no info set)

	local numAuxCells = 0

	local numInfoCells = 0

	for i = string.byte('A'), string.byte('Z') do

		local param = 'info' .. string.char(i)

		if argsparam then numInfoCells = numInfoCells + 1 end

	end

	

	-- Use of colors and network

	local noColors = true

	local setNetwork = false

	if (args.multiseries and args.network) then setNetwork = true end

	for i = 1, #SeasonEntries_ordered do

		local season, entry = SeasonEntries_orderedi], SeasonEntriesSeasonEntries_orderedi]]

		for j0 = string.byte('A')-1, string.byte('Z') do

			local j = string.char(j0)

			if j0 == string.byte('A')-1 then j = '' end

			if entry'color' .. j then noColors = false end

			if entry'network' .. j then setNetwork = true end

		end

	end

	

	-- Top info cell

	-- @ = string.char(64), A = string.char(65)

	local topInfoCell = numInfoCells > 0 and string.char(numInfoCells + (string.byte('A') - 1)) or '@'

	

	-- Networks are included if the very first entry sets the first network

	local networkTransclude = args.network_transclude

	if (networkTransclude == 'onlyinclude' and title.fullText == initialArticle) or (networkTransclude == 'noinclude' and title.fullText ~= initialArticle) then

		setNetwork = false

	end



	-- Headers

	do

		if args.multiseries or not args.series then

			local headerRow = root:tag('tr')

			headerRow

				:css('text-align', 'center')

			

			local releasedBlurb = args.released and 'released' or 'aired'

			

			-- Base series/season content on the format of the first date; Series = D M Y, Season = M D, Y

			local matchDMY = false

			local thisStart = firstRow.start or firstRow.startA

			if thisStart then

				if string.match(thisStart:gsub("&nbsp;"," "), '(%d+)%s(%a+)%s(%d+)') then

					matchDMY = true

				end

			end

			

			-- Multiple series header

			if args.multiseries then

				headerRow:tag('th')

					:attr('scope', 'col')

					:css('padding', cellPadding)

					:attr('rowspan', allReleased and 1 or 2)

					:wikitext('Series')

			end

			

			-- Season header

			headerRow:tag('th')

				:attr('scope', 'col')

				:attr('rowspan', allReleased and 1 or 2)

				:css('min-width', '50px')

				:css('padding', cellPadding)

				:wikitext(args.seriesT or args.seasonT or (matchDMY and 'Series') or 'Season')

			

			for _a = 1, 3 do

				if _a == 1 or _a == 3 then

					-- Aux headers

					local auxtype = (_a == 3 and 'post' or '') .. 'aux'

					for i = string.byte('A'), string.byte('Z') do

						local param = auxtype .. string.char(i)

						if argsparam then

							numAuxCells = numAuxCells + 1

							headerRow:tag('th')

								:attr('scope', 'col')

								:css('padding', cellPadding)

								:attr('rowspan', allReleased and 1 or 2)

								:wikitext(argsparam])

						end

					end

				end

				

				if _a == 2 then

					-- Episodes header

					headerRow:tag('th')

						:attr('scope', 'col')

						:attr('rowspan', allReleased and 1 or 2)

						:attr('colspan', 2)

						:css('padding', cellPadding)

						:wikitext(args.episodesT or 'Episodes')

				end

			end



			-- Originally aired header

			local OriginallyColspan = (not allReleased and setNetwork) and 3 or 2

			local countryBlurb = ''

			if args.country then

				countryBlurb = ' (' .. args.country .. ')'

			end

			headerRow:tag('th')

				:attr('scope', (setNetwork and allReleased) and 'col' or 'colgroup')

				:attr('colspan', OriginallyColspan)

				:wikitext('Originally ' .. releasedBlurb .. countryBlurb)

			

			-- Network subheader for released series

			if setNetwork and allReleased then

				headerRow:tag('th')

					:attr('scope', 'col')

					:attr('rowspan', allReleased and 1 or 2)

					:css('padding', cellPadding)

					:wikitext('Network')

			end

			

			-- Information headers

			if topInfoCell ~= '@' then

				for i = string.byte('A'), string.byte(topInfoCell) do

					local param = 'info' .. string.char(i)

					local infoTransclude = argsparam .. '_transclude'

					if (infoTransclude == 'onlyinclude' and title.fullText == initialArticle) or (infoTransclude == 'noinclude' and title.fullText ~= initialArticle) then else

						headerRow:tag('th')

							:attr('scope', 'col')

							:attr('rowspan', allReleased and 1 or 2)

							:css('padding', cellPadding)

							:wikitext(argsparam])

					end

				end

			end

			

			-- Subheader row

			local subheaderRow = mw.html.create('tr')



			if not allReleased then

				-- First aired subheader

				subheaderRow:tag('th')

					:attr('scope', 'col')

					:wikitext('First ' .. releasedBlurb)



				-- Last aired subheader

				subheaderRow:tag('th')

					:attr('scope', 'col')

					:wikitext('Last ' .. releasedBlurb)

				

				-- Network subheader for aired series

				if setNetwork then

					subheaderRow:tag('th')

						:attr('scope', 'col')

						:css('padding', cellPadding)

						:wikitext('Network')

				end

			end

		

			-- Check for scenarios with an empty subheaderRow

			if not allReleased or numInfoCells > 0 then

				root:node(subheaderRow)

			end

		end

	end



	-- Season rows

	do

		if args.multiseries then

			-- Multi series individual entries

			if args.multiseries ~= "y" then

				root:node(args.multiseries)

			end

		else

			-- One row entries, only categorized in the mainspace

			if title.namespace == 0 and #SeasonEntries == 1 then

				categories = categories .. '[[Category:Articles using Template:Series overview with only one row]]'

			end

		

			-- Determine number of rows in the whole overview

			local SeasonEntriesRows = 0

			for X = 1, #SeasonEntries_ordered do

				local season, entry = SeasonEntries_orderedX], SeasonEntriesSeasonEntries_orderedX]]

				local splits = 0

				for i = string.byte('A'), string.byte('Z') do

					local param = 'start' .. string.char(i)

					if entryparam then splits = splits + 1 end

				end

				if splits == 0 then splits = 1 end

				SeasonEntriesRows = SeasonEntriesRows + splits

			end

			

			for X = 1, #SeasonEntries_ordered do

				local season, entry = SeasonEntries_orderedX], SeasonEntriesSeasonEntries_orderedX]]

				

				-- Determine number of splits in a season

				local splits = 0

				for i = string.byte('A'), string.byte('Z') do

					local param = 'start' .. string.char(i)

					if entryparam then splits = splits + 1 end

				end

				local splitSeason = (splits > 1)

				

				-- Season rows for each season

				for k0 = string.byte('A')-1, string.byte('Z') do

					local k = string.char(k0)

					if k0 == string.byte('A')-1 then k = '' end

					

					-- Part header

					if entry.part and k == '' then

						root:node(entry.part)

					end

					

					-- New season row

					-- local seasonRow = (entry['color' .. k] or entry['episodes' .. k] or entry['start' .. k] or entry['end' .. k]) and root:tag('tr') or mw.html.create('tr')

					local seasonRow = entry'start' .. k and root:tag('tr') or mw.html.create('tr')

					seasonRow:css('height', '100%')

					

					local borderBottom = '2px solid #8D939A'

					

					-- Series name for group overviews

					if X == 1 and (k == '' or k == 'A') and args.series then

						seasonRow:tag('th')

							:attr('scope', 'row')

							:attr('rowspan', SeasonEntriesRows)

							:wikitext(args.series)

							:css('border-bottom', borderBottom)

					end

					if X == #SeasonEntries_ordered and args.series then

						seasonRow:css('border-bottom', borderBottom)

					end

					

					-- Season number link, included only in the first row

					local cellColor

					if not noColors then

						if entry'color' .. k ~= nil and HTMLcolorentry'color' .. k]] == nil then 

							entry'color' .. k = '#'..(mw.ustring.match(entry'color' .. k], '^[%s#]*([a-fA-F0-9]*)[%s]*$') or '')

						end

						if splitSeason then

							if entry.color then

								cellColor = entry.color

							else

								cellColor = "linear-gradient(to bottom"

								for i = 0, splits-1 do

									local _color = 'color' .. string.upper(string.char(i+97))

									cellColor = cellColor .. ", " .. (entry_color or 'rgba(0,0,0,0)') .. " " .. (100/splits *i) .. "%"

														  .. ", " .. (entry_color or 'rgba(0,0,0,0)') .. " " .. (100/splits *(i+1)) .. "%"

								end

								cellColor = cellColor .. ")"

							end

						else

							cellColor = entry'color' .. k

						end

					end

					

					if k == '' or k == 'A' then

						local colorWidth = '14px'

						

						-- Overall table cell

						local cellRow = mw.html.create(args.series and 'td' or 'th')

							:attr('scope', 'row')

							:attr('rowspan', splitSeason and splits or nil)

							:attr('colspan', entry.special and not entry.episodes and 3+numAuxCells or 1)

							:css('height', 'inherit')

							:css('padding', '0')

							

						-- Overall inner span

						local spanRow = mw.html.create('span')

						spanRow

							:css('width: 100%')

							:css('text-align', 'center')

							:css('float', 'left')

							:css('width', '100%')

							:css('height', '100%')

						

						-- Coloured nested span

						local spanRow2 = mw.html.create('span')

						spanRow2

							:css('width', colorWidth)

							:css('background', cellColor)

							:css('color', '#202122')

							:css('height', '100%')

							:css('float', 'left')

							:css('box-shadow', 'inset -1px 0 #A2A9B1')

						

						-- Link nested span

						local spanRow3 = mw.html.create('span')

						spanRow3

							:css('height', '100%')

							:css('width', not noColors and 'calc(100% - ' .. colorWidth .. ' - 8px)' or '100%')

							:css('display', 'flex')

							:css('vertical-align', 'middle')

							:css('align-items', 'center')

							:css('justify-content', 'center')

							:css('padding', not noColors and '0 4px' or '')

							

						local spanRow4 = mw.html.create('span')

						spanRow4

							:addClass('nowrap')

						

						-- Coloured span first into the overall span

						if not noColors then

							spanRow:node(spanRow2)

						end

						-- Link into the blank span

						spanRow4:wikitext((entry.link and '[[' .. entry.link .. '|' .. (entry.linkT or season) .. ']]' or (entry.linkT or season)) .. (entry.linkR or ''))

						-- Blank span into the Link nested span

						spanRow3:node(spanRow4)

						-- Link span second into the overall span

						spanRow:node(spanRow3)

						-- Overall span into the actual cell

						cellRow:node(spanRow)

						-- The actual cell into the season row

						seasonRow:node(cellRow)

					end

					

					for _a = 1, 3 do

						if _a == 1 or _a == 3 then

							-- Aux headers

							local auxtype = (_a == 3 and 'post' or '') .. 'aux'

							-- Aux cells

							for i = string.byte('A'), string.byte('Z') do

								local param = auxtype .. string.char(i)

								if entryparam .. k then

									local thisCell = SeriesOverview.season_cell(entryparam .. k], frame)

										:attr('scope', 'col')

										:attr('rowspan', SeriesOverview.cellspan(SeasonEntries, SeasonEntries_ordered, param, X, (args.series and true or false), entryparam .. k .. 'span' or nil))

										:css('padding', cellPadding)

									seasonRow:node(thisCell)

								end

							end

						end

						

						if _a == 2 then

							-- Episodes counts

							if ((splitSeason and k == 'A' and entry.episodes ~= 'hide') or not splitSeason) then

								if entry.episodes then

									local thisCell = SeriesOverview.season_cell(entry.episodes, frame)

										:attr('colspan', (splitSeason and entry.episodesA ~= 'hide') and 1 or 2)

										:attr('rowspan', splitSeason and splits or nil)

									seasonRow:node(thisCell)

								elseif not entry.special then

									local infoCell = SeriesOverview.series_attributes( frame:expandTemplate{title='N/A',args={'TBA'}} )

									infoCell

										:attr('colspan', (splitSeason and entry.episodesA ~= 'hide') and 1 or 2)

										:attr('rowspan', splitSeason and splits or nil)

									seasonRow:node(infoCell)

								end

							end

							if splitSeason and entry.episodesA ~= 'hide' then

								if entry'episodes' .. k then

									local thisCell = SeriesOverview.season_cell(entry'episodes' .. k], frame)

										:attr('colspan', (entry.episodes ~= 'hide') and 1 or 2)

									seasonRow:node(thisCell)

								else

									local infoCell = SeriesOverview.series_attributes( frame:expandTemplate{title='N/A',args={'TBA'}} )

										:attr('colspan', (entry.episodes ~= 'hide') and 1 or 2)

									seasonRow:node(infoCell)

								end

							end

						end

					end

				

					-- Start date

					if entry'start' .. k then

						local thisCell = SeriesOverview.season_cell(entry'start' .. k], frame)

							:attr('colspan',((not entry.special and entry'end' .. k == 'start') or (entry.special and not entry'end' .. k]) or allReleased) and 2 or 1)

							:css('padding',basePadding)

						seasonRow:node(thisCell)

					else

						local infoCell = SeriesOverview.series_attributes( frame:expandTemplate{title='N/A',args={'TBA'}} )

						infoCell:css('padding',basePadding)

						seasonRow:node(infoCell)

					end

					

					-- End date

					if not allReleased and entry'end' .. k ~= 'start' and ((entry.special and entry'end' .. k]) or not entry.special) then

						if entry'end' .. k then

							local thisCell = SeriesOverview.season_cell(entry'end' .. k], frame)

								:css('padding',cellPadding)

							seasonRow:node(thisCell)

						else

							local infoCell = SeriesOverview.series_attributes( frame:expandTemplate{title='N/A',args={'TBA'}} )

							infoCell:css('padding',cellPadding)

							seasonRow:node(infoCell)

						end

					end

					

					-- Network

					if entry'network' .. k and setNetwork then

						local thisCell = SeriesOverview.season_cell(entry'network' .. k], frame)

							:attr('rowspan', SeriesOverview.cellspan(SeasonEntries, SeasonEntries_ordered, 'network', X, (args.series and true or false), entry'network' .. k .. 'span' or nil))

						seasonRow:node(thisCell)

					end

					

					-- Information

					for i = string.byte('A'), string.byte(topInfoCell) do

						local param0 = 'info' .. string.char(i)

						local param = 'info' .. string.char(i) .. k

						

						local infoTransclude = argsparam .. '_transclude'

						if (infoTransclude == 'onlyinclude' and title.fullText == initialArticle) or (infoTransclude == 'noinclude' and title.fullText ~= initialArticle) then else

							local infoParam = entryparam

							

							if infoParam and splitSeason and k == '' and not entryparam .. 'A' then

								entryparam .. 'A' = entryparam

								entryparam .. 'spanning' = 'y'

							end

							

							local rowspan = (entryparam0 .. 'spanning' and splits) or

											(args.series and SeriesOverview.cellspan(SeasonEntries, SeasonEntries_ordered, param0, X, (args.series and true or false), entryparam0 .. 'span' or nil))

											or nil

							

							if k == 'A' or (k ~= 'A' and not entryparam0 .. 'spanning']) then

								-- Cells with {{N/A|...}} already expanded

								if infoParam then

									if string.sub(infoParam,1,5) == 'style' then

										local infoCell = SeriesOverview.series_attributes(infoParam)

										infoCell:attr('rowspan', rowspan)

										seasonRow:node(infoCell)

									else

										-- Unstyled content info cell

										local thisCell = SeriesOverview.season_cell(infoParam, frame)

											:attr('rowspan', rowspan)

										seasonRow:node(thisCell)

									end

								else

									if not args.series then

										local infoCell = SeriesOverview.series_attributes( frame:expandTemplate{title='N/A',args={'TBA'}} )

										infoCell:attr('rowspan', rowspan)

										seasonRow:node(infoCell)

									end

								end

							elseif not entryparam0 .. 'spanning' then

								if not args.series then

									local infoCell = SeriesOverview.series_attributes( frame:expandTemplate{title='N/A',args={'TBA'}} )

									infoCell:attr('rowspan', rowspan)

									seasonRow:node(infoCell)

								end

							end

						end

					end

				

				end -- End k0 string.byte

			end -- End 'for' SeasonEntries_ordered

		end -- End 'if' multiseries

	end -- End 'do' season rows

	

	local rootdiv

	if args.multiseries or not args.series then

		rootdiv = mw.html.create('div')

		rootdiv

			:css('display', 'block')

			:css('overflow-x', 'auto')

		rootdiv:node(root)

		rootdiv = tostring(rootdiv)

	else

		rootdiv = tostring(root)

	end

	

	if args.dontclose then 

		rootdiv = mw.ustring.gsub(rootdiv, "</div>", "")

		rootdiv = mw.ustring.gsub(rootdiv, "</table>", "")

	end



	return rootdiv .. categories

end



--------------------------------------------------------------------------------

-- Exports

--------------------------------------------------------------------------------



local p = {}



function p.main(frame)

	local args = require('Module:Arguments').getArgs(frame, {

		wrappers = 'Template:Series overview'

	})

	return SeriesOverview.new(frame, args)

end



function p._end(frame)

	return SeriesOverview.endtable()

end



return p