Edge Rewrite
// HTMLRewriter · presentation

This page was redesigned at the edge.

Cloudflare fetched the original article and streamed it through HTMLRewriter to apply an entirely new visual system without rebuilding the source page.

Jump to content

Module:Sports results

Permanently protected module
From Wikipedia, the free encyclopedia

-- Module to build results cross-tables for standings in Sports
-- See documentation for details

require('strict')

local yesno = require('Module:Yesno')

local p = {}

-- Main function
function p.main(frame)
	-- Get the args, stripping out blank values
	local getArgs = require('Module:Arguments').getArgs
	local Args = getArgs(frame, {parentFirst = true})
	return p._main(frame, Args, true)
end

-- Helpers shared with Module:Sports results/schedule
local util = {}

-- Main function, for callers that have already assembled the argument table
function p._main(frame, Args, standalone)
	-- Exit early if we are using section transclusion for a different section
	local tsection = frame:getParent().args['transcludesection'] or frame:getParent().args['section'] or ''
	local bsection = frame.args['section'] or ''
	if( tsection ~= '' and bsection ~= '' ) then
		if( tsection ~= bsection ) then
			return ''
		end
	end

	-- Hand over to the schedule format if asked for
	local mformat = string.lower(mw.text.trim(Args['matches_format'] or ''))
	if mformat == 'schedule' or mformat == 'series' or mformat == 'series_split' then
		return require('Module:Sports results/schedule')._main(frame, Args, util, standalone)
	end

	-- Declare locals
	local t = {}
	local t_footer = {}
	local t_return = {}
	local team_list = {}
	local notes_exist = false
	local ii, ii_fw, bg_col, team_name, team_code_ii, ii_start, ii_end
	-- Optional custom team header
	local team_header = Args['team_header'] or 'Home \\ Away'
	-- Number of legs
	local legs = tonumber(Args['legs']) or 1
	local multirowlegs = (Args['multirowlegs'] or 'no') ~= 'no'

	-- Edit links if requested
	local baselink = frame:getParent():getTitle()
	if mw.title.getCurrentTitle().fullText == baselink then	baselink = '' end
	local template_name = Args['template_name']
		or (baselink ~= '' and (':' .. baselink))
		or ''
	local edit_links = template_name == '' and ''
		or require('Module:Navbar')._navbar({
			template_name,
			mini=1,
			style='float:right'
		})

	-- Optional baselink override for the match-report anchor links
	-- (this does not impact the v·t·e edit links)
	local baselink_override = Args['baselink']
	if baselink_override and baselink_override ~= '' then
		baselink = baselink_override
		if mw.title.getCurrentTitle().fullText == baselink then baselink = '' end
	end

	-- Get the custom start point for the table (most will start by default at 1)
	local top_pos = tonumber(Args['highest_pos']) or 1
	-- Get the custom end point for the table (unrestricted if bottom_pos is < top_pos)
	local bottom_pos = tonumber(Args['lowest_pos']) or 0
	local N_teams = top_pos - 1 -- Default to 0 at start, but higher number needed to skip certain entries

	-- Load some other modules
	local p_sub = require('Module:Sports table/sub')

	-- Alternative syntax for team list
	if Args['team_order'] and Args['team_order'] ~= '' then
		local tlist = mw.text.split(Args['team_order'], '%s*[;,]%s*')
		for k, tname in ipairs(tlist) do
			if tname ~= '' then
				Args['team' .. k] = tname
			end
		end
	end

	if Args['team_header_note'] then
		notes_exist=true
		local note_string = frame:expandTemplate{ title = 'efn',
			args = { group='lower-alpha', Args['team_header_note']} }
		team_header = team_header .. note_string
	end

	-- Read in number of consecutive teams (ignore entries after skipping a spot)
	ii_start = N_teams
	while Args['team'..N_teams+1] ~= nil and (bottom_pos < top_pos or N_teams < bottom_pos) do
		N_teams = N_teams+1
		-- Sneakily add it twice to the team_list parameter, once for the actual
		-- ranking, the second for position lookup in sub-tables
		-- This is possible because Lua allows both numbers and strings as indices.
		team_list[N_teams] = Args['team'..N_teams] -- i^th entry is team X
		team_list[Args['team'..N_teams]] = N_teams -- team X entry is position i
	end
	ii_end = N_teams
	-- Get team to show
	local ii_show = team_list[Args['showteam']] -- nil if non-existant

	-- Set the font size
	local font_size=Args['font_size'] or '100%'

	-- Create header
	-- Open table
	table.insert(t,'{|class="wikitable plainrowheaders" style="text-align:center;font-size:'..font_size..';'
		..(yesno(Args['table_nowrap'] or 'no') and 'white-space:nowrap;' or '')..'"\n')
	-- Table title
	if Args['title'] then
		table.insert(t,'|+ ' .. Args['title'] .. '\n')
	end
	-- First column
	t_return.count = 0 			-- Dummy parameter, using subfunction call seems best at this point because both module are intertwined
	t_return.tab_text = t		-- Actual text
	t_return = p_sub.colhead(t_return,'auto', edit_links .. ' ' .. team_header)
	-- Other columns passed to subfunction
	t_return = p.header(t_return,Args,p_sub,N_teams,team_list,legs,multirowlegs)
	t = t_return.tab_text

	-- Random value used for uniqueness
	math.randomseed( os.clock() * 10^8 )
	local rand_val = math.random()

	local note_string, note_id
	local note_id_list = {}

	-- Now create individual rows
	ii_start = tonumber(Args['highest_row']) and (tonumber(Args['highest_row']) > top_pos) and tonumber(Args['highest_row']) or top_pos
	ii_end = tonumber(Args['lowest_row']) and (tonumber(Args['lowest_row']) < N_teams) and tonumber(Args['lowest_row']) or N_teams
	for ii=ii_start,ii_end do
		-- Get team info
		team_code_ii = team_list[ii]
		team_name = Args['name_'..team_code_ii] or team_code_ii
		local ii_style = 'text-align:' .. (Args['team_align'] or 'right') .. ';'
			.. (ii and ii == ii_show and 'font-weight:bold;' or '')
			.. (Args['team_nowrap'] and 'white-space:nowrap;' or '')
		local team_note = p.team_note(Args, team_code_ii, standalone)
		if team_note then
			notes_exist = true
			-- Only when it exist
			-- First check for existence of reference for note
			if not p.team_note(Args, team_note, standalone) then
				-- It's the entry
				-- Add random end for unique ID if more tables are present on article (which might otherwise share an ID)
				note_id = '"table_note_'..team_code_ii..rand_val..'"'
				note_id_list[team_code_ii] = note_id
				note_string = frame:expandTemplate{ title = 'efn',
					args = { group='lower-alpha', name=note_id,  team_note} }
			else
				-- Check for existence elsewhere
				local note_local_num = team_list[team_note] or ii_end + 1
				if note_id_list[team_note] or ((note_local_num >= ii_start) and (note_local_num <= ii_end)) then
					-- It exists
					note_id = '"table_note_'..team_note..rand_val..'"' -- Identifier
					note_string = frame:extensionTag{ name = 'ref',
						args = { group = 'lower-alpha', name = note_id} }
				else
					-- Now define the identifier for this
					-- Add random end for unique ID
					note_id = '"table_note_'..team_note..rand_val..'"'
					note_id_list[team_note] = note_id
					note_string = frame:expandTemplate{ title = 'efn',
						args = { group='lower-alpha', name=note_id, p.team_note(Args, team_note, standalone)} }
				end
			end
			-- Now append this to the team_name string
			team_name = team_name..note_string
		end
		-- Team names
		table.insert(t,'|- \n')  -- New row
		table.insert(t,'! scope="row"'.. (multirowlegs and ' rowspan=' .. legs or '') 
			.. 'style="'.. ii_style ..'"| '..team_name..'\n')  -- Position number

		-- Now include note to match results if needed
		for jj=top_pos,N_teams do
			local team_code_jj = team_list[jj]
			if ii == jj then
				-- Nothing
			else
				for l=1,legs do
					local m = (legs == 1) and 'match_' or 'match' .. l .. '_'
					local match_note = Args[m ..team_code_ii..'_'..team_code_jj..'_note']
					if match_note then
						notes_exist = true
						-- Only when it exist
						-- First check for existence of reference for note
						if not (Args['note_'..match_note] or Args[m ..match_note..'_note']) then
							-- It's the entry
							-- Add random end for unique ID if more tables are present on article (which might otherwise share an ID)
							note_id = '"table_note_'..l.."_"..team_code_ii..'_'..team_code_jj..rand_val..'"'
							note_id_list[team_code_ii..'_'..team_code_jj] = note_id
							note_string = frame:expandTemplate{ title = 'efn',
								args = { group='lower-alpha', name=note_id,  match_note} }
						else
							-- Check for existence elsewhere
							local note_local_num = team_list[match_note] or ii_end + 1
							if note_id_list[match_note] then
								-- Referencing an existing note
								note_id = note_id_list[match_note] -- Borrow the existing identifier
								note_string = frame:extensionTag{ name = 'ref',
									args = { group = 'lower-alpha', name = note_id} }
							elseif (note_local_num >= ii_start) and (note_local_num <= ii_end) then
								-- Referencing a player note from a match note. In this case, we remove the leg part
								note_id = '"table_note_'..match_note..rand_val..'"'
								note_string = frame:extensionTag{ name = 'ref',
									args = { group = 'lower-alpha', name = note_id} }
							else
								-- Referencing a different match note before its defined with its content
								note_id = '"table_note_'..l.."_"..match_note..rand_val..'"'
								note_id_list[match_note] = note_id
								note_string = frame:expandTemplate{ title = 'efn',
									args = { group='lower-alpha', name=note_id, Args['note_'..match_note]} }
							end
						end
						-- Now append this to the match result string
						Args[m..team_code_ii..'_'..team_code_jj] = (Args[m..team_code_ii..'_'..team_code_jj] or '–')..note_string
					end
				end
			end
		end
		-- Then individual results
		t = p.row(t,Args,N_teams,team_list,ii,ii_show,legs,multirowlegs)
	end

	-- Close table
	table.insert(t, '|}\n')

	-- Get info for footer
	local update = Args['update']
		or 'unknown'
	local start_date = Args['start_date']
		or 'unknown'
	local source = Args['source']
		or frame:expandTemplate{ title = 'citation needed',
			args = { reason='No source parameter defined', date=os.date('%B %Y') } }

	-- Create footer text
	-- Date updating
	if string.lower(update)=='complete' then
		-- Do nothing
	elseif update=='' then
		-- Empty parameter
		table.insert(t_footer,'Updated to match(es) played on unknown. ')
	elseif string.lower(update)=='future' then
		-- Future start date
		table.insert(t_footer,'First match(es) will be played: '..start_date..'. ')
	else
		table.insert(t_footer,'Updated to match(es) played on '..update..'. ')
	end
	table.insert(t_footer,'Source: '..source)
	table.insert(t_footer, p.footer_legend(Args))

	local templatestyles = frame:extensionTag{
		name = 'templatestyles', args = { src = 'Module:Sports results/styles.css' }
	}

	-- Add notes (if applicable)
	if notes_exist then
		table.insert(t_footer,'<br>Notes:')
		t_footer = templatestyles .. '<div class="sports-results-notes">'..table.concat(t_footer)..'</div>'
		t_footer = t_footer..frame:expandTemplate{ title = 'notelist', args = { group='lower-alpha'} }
	else
		t_footer = templatestyles .. '<div class="sports-results-notes">'..table.concat(t_footer)..'</div>'
	end

	-- Add footer to main text table
	table.insert(t,t_footer)

	-- Rewrite anchor links
	for k=1,#t do
		if t[k]:match('%[%[#[^%[%]]*%|') then
			t[k] = mw.ustring.gsub(t[k], '(%[%[)(#[^%[%]]*%|)', '%1' .. baselink .. '%2')
		end
	end

	return '<div style="overflow:hidden">'
		.. '<div class="noresize overflowbugx" style="overflow:auto">\n'
		.. table.concat(t) .. '</div></div>'
end

-- Other functions
local function split_icon(str)
	if not str or str == '' then return '', str or '' end
	local rest = str
	local taken = 0
	while true do
		local before = rest
		-- a span containing an image
		rest = mw.ustring.gsub(rest, '^(<span[^<>]*>%s*%[%[[Ff][Ii][Ll][Ee]:[^%[%]]*%]%]%s*</span>)', '')
		rest = mw.ustring.gsub(rest, '^(<span[^<>]*>%s*%[%[[Ii][Mm][Aa][Gg][Ee]:[^%[%]]*%]%]%s*</span>)', '')
		-- a bare image
		rest = mw.ustring.gsub(rest, '^%[%[[Ff][Ii][Ll][Ee]:[^%[%]]*%]%]', '')
		rest = mw.ustring.gsub(rest, '^%[%[[Ii][Mm][Aa][Gg][Ee]:[^%[%]]*%]%]', '')
		-- the space between an icon and the name
		rest = mw.ustring.gsub(rest, '^%s*&nbsp;%s*', '')
		rest = mw.ustring.gsub(rest, '^%s+', '')
		if rest == before then break end
		taken = #str - #rest
	end
	if taken == 0 then return '', str end
	return mw.ustring.sub(str, 1, mw.ustring.len(str) - mw.ustring.len(rest)), rest
end

local function get_short_name(s, t, n, ss)
	-- return short name if defined
	if s and s ~= '' then
		return s
	end
	-- deflag if necessary
	if ss and n then
		if ss == 'noflag' then
			local _, without = split_icon(n)
			n = without
			n = mw.ustring.gsub(n, '%[%[[Ff][Ii][Ll][Ee]:[^%[%]]*%]%]', '')
		elseif ss == 'flag' then
			n = mw.ustring.gsub(n, '(<span class="flagicon">%s*%[%[[Ff][Ii][Ll][Ee]:[^%[%]]*link=)[^%|%[%]]*(%]%][^<>]*</span>)%s*%[%[([^%[%]%|]*)%|[^%[%]]*%]%]', '%1%3%2')
			n = mw.ustring.gsub(n, '(%[%[[Ff][Ii][Ll][Ee]:[^%[%]]*link=)[^%|%[%]]*(%]%])%s*&nbsp;%s*%[%[([^%[%]%|]*)%|[^%[%]]*%]%]', '%1%3%2')
			n = mw.ustring.gsub(n, '(%[%[[Ff][Ii][Ll][Ee]:[^%[%]]*link=)[^%|%[%]]*(%]%])%s*%[%[([^%[%]%|]*)%|[^%[%]]*%]%]', '%1%3%2')
			n = mw.ustring.gsub(n, '.*(<span class="flagicon">%s*%[%[[Ff][Ii][Ll][Ee]:[^%[%]]*%]%][^<>]*</span>).*', '%1')
			n = mw.ustring.gsub(n, '.*(%[%[[Ff][Ii][Ll][Ee]:[^%[%]]*%]%]).*', '%1')
			n = mw.ustring.gsub(n, '&nbsp;(</span>)', '%1')
		end
	end

	-- replace link text in name with team abbr if possible
	if n and t and n:match('(%[%[[^%[%]]*%]%])') then
		n = mw.ustring.gsub(n, '(%[%[[^%|%]]*%|)[^%|%]]*(%]%])', '%1' .. t .. '%2')
		n = mw.ustring.gsub(n, '(%[%[[^%|%]]*)(%]%])', '%1|' .. t .. '%2')
		n = mw.ustring.gsub(n, '(%[%[[^%|%]]*%|)([A-Z][A-Z][A-Z])(%]%])&nbsp;<span[^<>]*>%([A-Z][A-Z][A-Z]%)</span>', '%1%2%3')
		return n
	end
	-- nothing worked, so just return the unlinked team abbr
	return t or ''
end

local function get_score_background(s, c, palette)
	local s1, s2
	-- Define the colouring
	local wc, lc, tc
	if palette == 'green' then
		if c == 'level2' then
		wc, lc, tc = '#CCF9CC', '#FCC', '#FFC' -- green2, red2, yellow2
		elseif c == 'level3' then
		wc, lc, tc = '#DDFCDD', '#FDD', '#FFD' -- green3, red3, yellow3
		elseif c == 'level4' then
		wc, lc, tc = '#EEFFEE', '#FEE', '#FFE' -- green4, red4, yellow4
		else
		wc, lc, tc = '#BBF3BB', '#FBB', '#FFB' -- green1, red1, yellow1
		end
	elseif c == 'level2' then
	wc, lc, tc = '#CCF9FF', '#FCC', '#FFC' -- blue2, red2, yellow2
	elseif c == 'level3' then
	wc, lc, tc = '#DDFCFF', '#FDD', '#FFD' -- blue3, red3, yellow3
	elseif c == 'level4' then
	wc, lc, tc = '#EEFFFF', '#FEE', '#FFE' -- blue4, red4, yellow4
	else
	wc, lc, tc = '#BBF3FF', '#FBB', '#FFB' -- blue1, red1, yellow1
	end

	-- check for override
	if s:match('^%s*<span%s+style%s*=["\'%s]*background[%-colr]*%s*:([^\'";<>]*).-$') then
		local c = mw.ustring.gsub(s,'^%s*<span%s+style%s*=["\'%s]*background[%-colr]*%s*:([^\'";<>]*).-$', '%1')
		return 'background: ' .. c ..';'
	end

	-- delink if necessary
	if s:match('^%s*%[%[[^%[%]]*%|([^%[%]]*)%]%]') then
		s = s:match('^%s*%[%[[^%[%]]*%|([^%[%]]*)%]%]')
	end
	if s:match('^%s*%[[^%[%]%s]*%s([^%[%]]*)%]') then
		s = s:match('^%s*%[[^%[%]%s]*%s([^%[%]]*)%]')
	end
	if s:match('<span[^<>]*>(.-)</span>') then
		s = s:match('<span[^<>]*>(.-)</span>')
	end

	-- get the scores
	s1 = tonumber(mw.ustring.gsub( s or '',
		'^%s*([%d%.]+)%s*–%s*([%d%.]+).*', '%1' ) or '') or ''
	s2 = tonumber(mw.ustring.gsub( s or '',
		'^%s*([%d%.]+)%s*–%s*([%d%.]+).*', '%2' ) or '') or ''

	-- return colouring if possible
	if s1 ~= '' and s2 ~= '' then
		return (s1 > s2) and ('background: ' .. wc .. ';') 
			or ((s2 > s1) and ('background: ' .. lc .. ';')
				or ('background: ' .. tc .. ';'))
	else
		return ''
	end
end

local function format_score(s)
	s = mw.ustring.gsub(s or '', '^%s*([%d%.]+)%s*[–−—%-]%s*([%d%.]+)', '%1–%2')
	s = mw.ustring.gsub(s, '^%s*([%d%.]+)%s*&[MmNn][Dd][Aa][Ss][Hh];%s*([%d%.]+)', '%1–%2')
	s = mw.ustring.gsub(s, '^%s*(%[%[[^%[%]]*%|[%d%.]+)%s*%-%s*([%d%.]+)', '%1–%2')
	s = mw.ustring.gsub(s, '^%s*(%[[^%[%]%s]*%s+[%d%.]+)%s*%-%s*([%d%.]+)', '%1–%2')
	s = mw.ustring.gsub(s, '^%s*(%[%[[^%[%]]*%|[%d%.]+)%s*&[MmNn][Dd][Aa][Ss][Hh];%s*([%d%.]+)', '%1–%2')
	s = mw.ustring.gsub(s, '^%s*(%[[^%[%]%s]*%s+[%d%.]+)%s*&[MmNn][Dd][Aa][Ss][Hh];%s*([%d%.]+)', '%1–%2')
	return s
end

-- Replace a wikilink with its display text
local function replace_link(m)
	local pipe_pos = m:find('|')
	if pipe_pos then
		return m:sub(pipe_pos + 1, -3) -- text after the '|'
	else
		return m:sub(3, -3) -- text between the brackets
	end
end

-- Clean team names to plain text for the match-report links
local function clean_team_name(str, default)
	if str and str ~= '' then
		str = str:gsub('<sup.->.-</sup>', '')
		str = str:gsub('</?%w+[^>]*>', '')
		str = str:gsub('\127%\'"`UNIQ.-QINU`"%\'\127', '')
		str = str:gsub('%[%[[Ff]ile:[^%]]+%]%]', '')
		str = str:gsub('%[%[[Ii]mage:[^%]]+%]%]', '')
		str = str:gsub('%[%[.-%]%]', replace_link)
		str = str:gsub('%s*&nbsp;%s*', '')
		str = str:gsub("'''+", '')
		str = str:gsub("''", '')
		str = str:match('^%s*(.-)%s*$') -- trim
		return (str ~= '' and str) or default
	end
	return default
end

-- Find the first '(' that is not inside a wikilink
local function find_paren_outside_wikilinks(s)
	local pos = 1
	while true do
		pos = s:find('%(', pos)
		if not pos then return nil end
		local before = s:sub(1, pos - 1)
		local open = select(2, before:gsub('%[%[', '')) - select(2, before:gsub('%]%]', ''))
		if open == 0 then return pos end
		pos = pos + 1
	end
end

-- Position where the trailing text (notes, tags, parentheses) of a score starts
local function tail_start(score)
	local positions = {}
	local sup = score:find('<sup')
	local marker = score:find('\127')                  -- start of a strip marker
	local paren = find_paren_outside_wikilinks(score)
	if sup then positions[#positions + 1] = sup end
	if marker then positions[#positions + 1] = marker end
	if paren then positions[#positions + 1] = paren end
	if #positions == 0 then return nil end
	return math.min(unpack(positions))
end

-- Link the score of a result to the match-report section anchor "#Home v Away"
local function generate_match_link(home_name, away_name, score)
	if not score or not score:match('%S') then
		return score
	end

	-- Split the linkable score/date from the trailing text (notes, tags, parentheses)
	local body, tail = score, ''
	local cut = tail_start(score)
	if cut then
		local trimmed = score:sub(1, cut - 1):match('^(.-)%s*$')  -- trim before tail
		body = trimmed
		tail = score:sub(#trimmed + 1)
	end

	if not body:match('%S') then
		return score
	end

	-- Never link byes
	if body:match('^%s*[Bb][Yy][Ee]%s*$') or body:match('|[Bb][Yy][Ee]%]%]') then
		return score
	end

	if body:find('%[') then
		-- Body already contains a wikilink: only wrap a plain leading scoreline,
		-- otherwise leave it untouched to avoid double-linking.
		local link_score = body:match('^([%d%.]+–[%d%.]+)')
		if not link_score then
			return score
		end
		local rest = body:sub(#link_score + 1)
		return '[[#' .. home_name .. ' v ' .. away_name .. '|' .. link_score .. ']]' .. rest .. tail
	end

	-- No wikilink: link the whole value
	return '[[#' .. home_name .. ' v ' .. away_name .. '|' .. body .. ']]' .. tail
end

-- |result_AAA_BBB= values that mark an overtime result. SO (shootout) is an alias
-- of OT for colouring; the value is also the text of the |ot_sup= superscript.
local OT_LABELS = {OT = 'OT', SO = 'SO'}

-- Add <sup>OT</sup> (or SO) right after the score, before any note or tag,
-- unless the score already carries one.
local function add_ot_sup(score, label)
	local low = score:lower()
	if low:find('<sup[^>]*>%s*ot%s*</sup>') or low:find('<sup[^>]*>%s*so%s*</sup>') then
		return score
	end
	local sup = ' <sup>' .. label .. '</sup>'
	local cut = tail_start(score)
	if not cut then return score .. sup end
	local trimmed = score:sub(1, cut - 1):match('^(.-)%s*$')
	return trimmed .. sup .. score:sub(#trimmed + 1)
end

-- Whether the |ot_note= legend covers overtime, shootout or both: whichever
-- |ot_note= names (e.g. "ot", "so", "ot, so"), otherwise whichever the
-- |result_AAA_BBB= values use, otherwise overtime.
local function ot_kinds(Args)
	local val = string.lower(tostring(Args['ot_note'] or ''))
	local has_ot = val:find('%f[%w]ot%f[%W]') ~= nil
	local has_so = val:find('%f[%w]so%f[%W]') ~= nil
	if not has_ot and not has_so then
		for k, v in pairs(Args) do
			if type(k) == 'string' and k:match('^resultn?%d*_') then
				if v == 'OT' then has_ot = true elseif v == 'SO' then has_so = true end
			end
		end
	end
	if not has_ot and not has_so then has_ot = true end
	return has_ot, has_so
end

-- The |ot_note= sentence
local function ot_note_text(Args, games)
	local has_ot, has_so = ot_kinds(Args)
	local both = has_ot and has_so
	local shading = 'lighter background shading'
	if yesno(Args['ot_sup'] or 'no') then
		shading = shading .. ' and a superscript ' .. (both and '"OT" or "SO"' or (has_so and '"SO"' or '"OT"'))
	end
	local how = both and 'after overtime or in a shootout' or (has_so and 'in a shootout' or 'after overtime')
	if both and yesno(Args['ot_sup'] or 'no') then how = how .. ', respectively' end
	return (games and 'Games' or 'Matches') .. ' with ' .. shading .. ' were decided ' .. how .. '.'
end

-- The legend in the footer, shared with Module:Sports table when |show_matches=yes
function p.footer_legend(Args)
	local out = {}
	local style = Args['matches_style'] or ''
	local tie = yesno(Args['use_tie'] or 'no')
	local loss_first = Args['loss_before_draw'] or Args['loss_before_tie'] or false
	local word = tie and 'game' or 'match'
	local drawn = tie and 'tie' or 'draw'
	local side1 = Args['team_header'] and 'left column team win' or 'home team win'
	local side2 = Args['team_header'] and 'top row team win' or 'away team win'

	if style == 'FBR' then
		if loss_first then
			out[#out + 1] = '<br />Legend: Blue = ' .. side1 .. '; Red = ' .. side2
				.. '; Yellow = ' .. drawn .. '.'
		else
			out[#out + 1] = '<br />Legend: Blue = ' .. side1 .. '; Yellow = ' .. drawn
				.. '; Red = ' .. side2 .. '.'
		end
	elseif style == 'BSR' then
		out[#out + 1] = '<br />Legend: Blue = ' .. side1 .. '; Red = ' .. side2 .. '.'
	end
	if Args['a_note'] then
		out[#out + 1] = '<br />For upcoming ' .. word .. 'es, an "a" indicates there is an article '
			.. 'about the rivalry between the two participants.'
	end
	if Args['ot_note'] then
		out[#out + 1] = '<br />' .. ot_note_text(Args, tie)
	end
	if Args['pk_note'] then
		out[#out + 1] = '<br />' .. (tie and 'Games' or 'Matches')
			.. ' with lighter background shading were decided by a penalty shoot-out.'
	end
	return table.concat(out)
end

-- Fill in the shared helper table
util.get_short_name = get_short_name
util.split_icon = split_icon
util.get_score_background = get_score_background
util.format_score = format_score
util.replace_link = replace_link
util.clean_team_name = clean_team_name
util.generate_match_link = generate_match_link
util.OT_LABELS = OT_LABELS
util.add_ot_sup = add_ot_sup
util.ot_note_text = ot_note_text

-- standalone: read |matches_note_TTT= instead of |note_TTT=, so a note meant
-- for the standings does not also appear in a results table.
-- |matches_note_AAA=note_AAA asks for the standings note.
function p.team_note(Args, code, standalone)
	if not standalone then return Args['note_' .. code] end
	local v = Args['matches_note_' .. code]
	if v == nil then return nil end
	if v == ('note_' .. code) then return Args['note_' .. code] end
	return v
end

util.team_note = p.team_note

function p.header(tt,Args,p_sub,N_teams,team_list,legs,multirowlegs,standalone)
	local ii, team_code_ii, short_name
	legs = legs or 1

	-- Set match column width
	local col_width = Args['match_col_width'] or '28'

	-- Get some default values in case it doesn't start at 1
	local top_pos = tonumber(Args['highest_pos']) or 1

	for l=1,legs do
		if multirowlegs and l > 1 then
			break
		end
		for ii=top_pos,N_teams do
			team_code_ii = team_list[ii]
			short_name = get_short_name(Args['short_'..team_code_ii],
				team_code_ii, Args['name_'..team_code_ii], Args['short_style'] or '')
			local bl = legs > 1 and ii == top_pos and ' style="border-left:2px solid #aaa;"' or ''
			tt = p_sub.colhead(tt,col_width .. bl,short_name)
		end
	end
	return tt
end

function p.row(tt,Args,N_teams,team_list,ii,ii_show,legs,multirowlegs)
	-- Note ii is the row number being shown
	local jj, fw, bg, result, result_extra, team_code_ii, team_code_jj
	legs = legs or 1

	-- Set score cell style
	local matches_style = Args['matches_style'] or ''

	-- Whether to link the scores to the match reports
	local generate_links = require('Module:Yesno')(Args['generate_links'])

	-- Whether to add a superscript OT/SO after overtime and shootout scores
	local ot_sup = yesno(Args['ot_sup'] or 'no')

	team_code_ii = team_list[ii]

	-- Get some default values in case it doesn't start at 1
	local top_pos = tonumber(Args['highest_pos']) or 1
	for l=1,legs do
		if multirowlegs and l > 1 then
			table.insert(tt,'|- \n')  -- New row
		end
		for jj=top_pos,N_teams do
			team_code_jj = team_list[jj]
			local m = (legs == 1) and 'match_' or 'match' .. l .. '_'
			result = Args[m..team_code_ii..'_'..team_code_jj] or ''
			local r = (legs == 1) and 'result_' or 'result' .. l .. '_'
			result_extra = Args[r..team_code_ii..'_'..team_code_jj] or ''
			-- SO (shootout) is coloured like OT; keep its own label for |ot_sup=
			local ot_label = OT_LABELS[result_extra]
			if ot_label then result_extra = 'OT' end
			local bl = legs > 1 and jj == top_pos and 'border-left:2px solid #aaa;' or ''

			if ii == jj or result == 'null' then
				-- Solid cell
				fw = 'font-weight:' .. (ii==ii_show and 'bold' or 'normal') .. ';'
				bg = ''

				-- Grey background color for solid cell
				if Args['solid_cell'] == 'grey' then
					table.insert(tt,'| style="'..fw..bl..'background:#bbb;" |\n')
				else
					table.insert(tt,'| style="'..fw..bl..bg..'" | &mdash;\n')
				end
			else
				-- Content cell
				-- Set bolding and background
				fw = 'font-weight:' .. ((ii==ii_show or jj == ii_show) and 'bold' or 'normal') .. ';'
				bg = ''

				-- Reformat dashes
				if result ~= '' then
					result = format_score(result)
				end
				-- Superscript OT/SO after the score if enabled
				if ot_sup and ot_label and result ~= '' then
					result = add_ot_sup(result, ot_label)
				end
				-- Background coloring if enabled
				if matches_style == 'FBR' and result ~= '' then
					if result_extra == 'OT' then
						bg = get_score_background(result,'level2')
					elseif result_extra == 'PK' then
						bg = get_score_background(result,'level3')
					else
						bg = get_score_background(result,'')
					end
				elseif matches_style == 'BSR' and result ~= '' then
					if result_extra == 'OT' then
						bg = get_score_background(result,'level3')
					elseif result_extra == 'OTL' then
						bg = get_score_background('0–1','level3')
					elseif result_extra == 'OTW' then
						bg = get_score_background('1–0','level3')
					elseif result_extra == 'L' then
						bg = get_score_background('0–1','')
					elseif result_extra == 'W' then
						bg = get_score_background('1–0','')
					else
						bg = get_score_background(result,'')
					end
				end
				-- Link the score to its match-report anchor "#Home v Away"
				if generate_links and result ~= '' and l == 1 then
					local home_name = clean_team_name(Args['name_'..team_code_ii], team_code_ii)
					local away_name = clean_team_name(Args['name_'..team_code_jj], team_code_jj)
					result = generate_match_link(home_name, away_name, result)
				end
				table.insert(tt,'| style="white-space:nowrap;'..fw..bl..bg..'" |'..result..'\n')
			end
		end
	end

	return tt
end

return p