This Lua-based module is meant to build match result tables. Note that this module is used extensively, so test potential changes rigorously in the sandbox and please ensure consensus exists before implementing major changes. Also note that the module is called from Module:Sports table and check for potential issues there as well after making changes. The rest of this documentation explains how to use this module in an article or template, check the table of contents for specific items.

Wikitext test cases at Module:Sports results/testcases wikitext.

The template can be used as follows:

What you type
{{#invoke:Sports results|main
|update=1 January 1900
|source=[https://en.wikipedia.org/wiki/Main_Page Wikipedia]
|showteam=CCC
|team1=FDT |team2=BBB |team3=CCC |team4=DDD |team5=EEE

|name_FDT=[[FIFA World Cup Dream Team]]  |short_FDT=[[FIFA World Cup Dream Team|FDT]]
|name_BBB=Team 2                         |short_BBB=T2
|name_CCC=Team 3                         |short_CCC=T3
|name_DDD=DDD F.C.                       |short_DDD=DDD
|name_EEE={{fb-rt|GIB}} |short_EEE={{flagicon|Gibraltar}}

|match_FDT_BBB=1–0
|match_FDT_CCC=0–1
|match_FDT_DDD=<small>16 Nov '14</small>
|match_FDT_EEE=<small>3 Sep '15</small>

|match_BBB_FDT=B–F
|match_BBB_CCC=<small>3 Sep '15</small>
|match_BBB_DDD=B–D
|match_BBB_EEE=<small>3 Sep '15</small>

|match_CCC_FDT=<small>16 Nov '14</small>
|match_CCC_BBB=C–B
|match_CCC_DDD=<small>16 Nov '14</small>
|match_CCC_EEE=C–E

|match_DDD_FDT=D–F
|match_DDD_BBB=<small>3 Sep '15</small>
|match_DDD_CCC=D–C
|match_DDD_EEE=<small>3 Sep '15</small>

|match_EEE_FDT=E–F
|match_EEE_BBB=9–5
|match_EEE_CCC=<small>16 Nov '14</small>
|match_EEE_DDD=E–D

|match_FDT_BBB_note=Match awarded to FIFA World Cup Dream Team with a 1–0 score.
}}
What it looks like
Mezbon \ Mehmon FDT T2 T3 DDD Gibraltar
FIFA World Cup Dream Team 1–0[lower-alpha 1] 0–1 16 Nov '14 3 Sep '15
Team 2 B–F 3 Sep '15 B–D 3 Sep '15
Team 3 16 Nov '14 C–B 16 Nov '14 C–E
DDD F.C. D–F 3 Sep '15 D–C 3 Sep '15
Gibraltar  E–F 9–5 16 Nov '14 E–D
Updated to match(es) played on 1 January 1900. Manba: Wikipedia
Notes:
  1. Match awarded to FIFA World Cup Dream Team with a 1–0 score.

The main command is {{#invoke:Sports results|main}}, which calls the module and sets the basic structure up. Then you list the order in which the teams are listed as |team1=, |team2=, etc for however many teams you need in the table. If you set |showteam= to a team, then that team's results are highlighted as in the example. The display of team names for team TTT is now governed by |name_TTT= and |short_TTT= for the row and column headers respectively. The results for the game TTT vs. SSS can now be input by |match_TTT_SSS=. The table is build this way, future games can be listed with their date or they can be left blank.

The source is controlled by |source=. The update date can be set by |update=date when the season is active; can be removed after the season with |update=complete and can be set for a future start date by using |update=future and setting the start date with |start_date=.

The width of the columns can be set by |match_col_width=.

A note can be added to a result using |match_TTT_SSS_note=.

Another example is:

What you type
{{#invoke:Sports results|main

|team1=CZE |team2=ISL |team3=KAZ |team4=LVA |team5=NED |team6=TUR

|name_CZE={{fb-rt|CZE}}	|short_CZE={{flagicon|CZE}}
|name_ISL={{fb-rt|ISL}}	|short_ISL={{flagicon|ISL}}
|name_KAZ={{fb-rt|KAZ}}	|short_KAZ={{flagicon|KAZ}}
|name_LVA={{fb-rt|LVA}}	|short_LVA={{flagicon|LVA}}
|name_NED={{fb-rt|NED}}	|short_NED={{flagicon|NED}}
|name_TUR={{fb-rt|TUR}}	|short_TUR={{flagicon|TUR}}

|match_CZE_ISL=[[UEFA Euro 2016 qualifying Group A#Czech Republic v Iceland|<small>16 Nov '14</small>]]
|match_CZE_KAZ=[[UEFA Euro 2016 qualifying Group A#Czech Republic v Kazakhstan|<small>3 Sep '15</small>]]
|match_CZE_LVA=[[UEFA Euro 2016 qualifying Group A#Czech Republic v Latvia|<small>28 Mar '15</small>]]
|match_CZE_NED=[[UEFA Euro 2016 qualifying Group A#Czech Republic v Netherlands|2–1]]
|match_CZE_TUR=[[UEFA Euro 2016 qualifying Group A#Czech Republic v Turkey|<small>10 Oct '15</small>]]

|match_ISL_CZE=[[UEFA Euro 2016 qualifying Group A#Iceland v Czech Republic|<small>12 Jun '15</small>]]
|match_ISL_KAZ=[[UEFA Euro 2016 qualifying Group A#Iceland v Kazakhstan|<small>6 Sep '15</small>]]
|match_ISL_LVA=[[UEFA Euro 2016 qualifying Group A#Iceland v Latvia|<small>10 Oct '15</small>]]
|match_ISL_NED=[[UEFA Euro 2016 qualifying Group A#Iceland v Netherlands|2–0]]
|match_ISL_TUR=[[UEFA Euro 2016 qualifying Group A#Iceland v Turkey|3–0]]

|match_KAZ_CZE=[[UEFA Euro 2016 qualifying Group A#Kazakhstan v Czech Republic|2–4]]
|match_KAZ_ISL=[[UEFA Euro 2016 qualifying Group A#Kazakhstan v Iceland|<small>28 Mar '15</small>]]
|match_KAZ_LVA=[[UEFA Euro 2016 qualifying Group A#Kazakhstan v Latvia|0–0]]
|match_KAZ_NED=[[UEFA Euro 2016 qualifying Group A#Kazakhstan v Netherlands|<small>10 Oct '15</small>]]
|match_KAZ_TUR=[[UEFA Euro 2016 qualifying Group A#Kazakhstan v Turkey|<small>12 Jun '15</small>]]

|match_LVA_CZE=[[UEFA Euro 2016 qualifying Group A#Latvia v Czech Republic|<small>6 Sep '15</small>]]
|match_LVA_ISL=[[UEFA Euro 2016 qualifying Group A#Latvia v Iceland|0–3]]
|match_LVA_KAZ=[[UEFA Euro 2016 qualifying Group A#Latvia v Kazakhstan|<small>13 Oct '15</small>]]
|match_LVA_NED=[[UEFA Euro 2016 qualifying Group A#Latvia v Netherlands|<small>12 Jun '15</small>]]
|match_LVA_TUR=[[UEFA Euro 2016 qualifying Group A#Latvia v Turkey|1–1]]

|match_NED_CZE=[[UEFA Euro 2016 qualifying Group A#Netherlands v Czech Republic|<small>13 Oct '15</small>]]
|match_NED_ISL=[[UEFA Euro 2016 qualifying Group A#Netherlands v Iceland|<small>3 Sep '15</small>]]
|match_NED_KAZ=[[UEFA Euro 2016 qualifying Group A#Netherlands v Kazakhstan|3–1]]
|match_NED_LVA=[[UEFA Euro 2016 qualifying Group A#Netherlands v Latvia|<small>16 Nov '14</small>]]
|match_NED_TUR=[[UEFA Euro 2016 qualifying Group A#Netherlands v Turkey|<small>28 Mar '15</small>]]

|match_TUR_CZE=[[UEFA Euro 2016 qualifying Group A#Turkey v Czech Republic|1–2]]
|match_TUR_ISL=[[UEFA Euro 2016 qualifying Group A#Turkey v Iceland|<small>13 Oct '15</small>]]
|match_TUR_KAZ=[[UEFA Euro 2016 qualifying Group A#Turkey v Kazakhstan|<small>16 Nov '14</small>]]
|match_TUR_LVA=[[UEFA Euro 2016 qualifying Group A#Turkey v Latvia|<small>3 Sep '15</small>]]
|match_TUR_NED=[[UEFA Euro 2016 qualifying Group A#Turkey v Netherlands|<small>6 Sep '15</small>]]
}}
What it looks like

Win/Draw/Loss colour

tahrir

The colour of win/draw/loss colour can be set by |matches_style=FBR.

What you type
{{#invoke:sports results|main
|source = 
|update=22 April 2018
|matches_style = FBR
|team1=CER |team2=DAV |team3=GLC |team4=JPV |team5=KAY |team6=STA

|name_CER=[[Ceres–Negros F.C.|Ceres–Negros]]
|name_DAV=[[Davao Aguilas F.C.|Davao Aguilas]]
|name_GLC=[[Global Cebu F.C.|Global Cebu]]
|name_JPV=[[JPV Marikina F.C.|JPV Marikina]]
|name_KAY=[[Kaya F.C.|Kaya–Iloilo]]
|name_STA={{nowrap|[[Stallion Laguna F.C.|Stallion Laguna]]}}

|match_CER_DAV={{small|{{small|23 May}}}}
|match_CER_GLC={{small|{{small|30 May}}}}
|match_CER_JPV={{small|{{small|20 Jun}}}}
|match_CER_KAY=2–1
|match_CER_STA={{small|{{small|2 Jun}}}}
|match_DAV_CER={{small|{{small|2 May}}}}
|match_DAV_GLC={{small|{{small|20 Jun}}}}
|match_DAV_JPV=3–2
|match_DAV_KAY=2–2
|match_DAV_STA={{small|{{small|6 May}}}}
|match_GLC_CER=0–2
|match_GLC_DAV=2–2
|match_GLC_JPV={{small|{{small|27 May}}}}
|match_GLC_KAY={{small|{{small|23 May}}}}
|match_GLC_STA=1–2
|match_JPV_CER=0–3
|match_JPV_DAV={{small|{{small|9 Jun}}}}
|match_JPV_GLC=2–1
|match_JPV_KAY={{small|{{small|5 May}}}}
|match_JPV_STA={{small|{{small|19 May}}}}
|match_KAY_CER={{small|{{small|12 May}}}}
|match_KAY_DAV={{small|{{small|20 May}}}}
|match_KAY_GLC={{small|{{small|2 May}}}}
|match_KAY_JPV=1–0
|match_KAY_STA=4–1
|match_STA_CER={{small|{{small|26 May}}}}
|match_STA_DAV=0–1
|match_STA_GLC={{small|{{small|12 May}}}}
|match_STA_JPV=1–2
|match_STA_KAY=null
}}
What it looks like
Mezbon \ Mehmon CER DAV GLC JPV KAY STA
Ceres–Negros 23 May 30 May 20 Jun 2–1 2 Jun
Davao Aguilas 2 May 20 Jun 3–2 2–2 6 May
Global Cebu 0–2 2–2 27 May 23 May 1–2
JPV Marikina 0–3 9 Jun 2–1 5 May 19 May
Kaya–Iloilo 12 May 20 May 2 May 1–0 4–1
Stallion Laguna 26 May 0–1 12 May 1–2
Updated to match(es) played on 22 April 2018. Manba: [manba kerak]
Tavsif: Blue = mezbon gʻalaba; Yellow = durang; Red = mehmon gʻalaba.

Team header

tahrir

To change the default Home \ Away team header, use |team_header=.

Two legs

tahrir

For two matches per team pair, use |legs=2 and |match1_AAA_BBB= / |match2_AAA_BBB= instead of |match_AAA_BBB=. For wide tables, use |multirowlegs=y which provides a more narrow format.

Blank and pivot cells

tahrir

Pivot cells defaulted to show (m-dash), but they can be changed to solidly colored cell using code |solid_cell= where color of the cell could be set by indicating with a phrase (i.e. grey). Also, each individual cell could be made inactive with a phrase null.

Generating a blank table

tahrir

To generate a blank table, use

{{subst:#invoke:Sports results/blank|main
| team1 = AAA | team2 = BBB | ...
}}

where |team1=, |team2=, ... are the abbreviations for the team names in the order you wish them to appear. You may include |name_...= and other parameters as well, and they should be included in the result.


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

require('Module:No globals')

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})

	-- Exit early if we are using section transclusion for a different section
	if (Args['transcludesection'] and Args['section'])
		and Args['transcludesection'] ~= Args['section'] then
		return ''
	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 'Mezbon \\ Mehmon'
	-- 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().text == 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'
		})

	-- 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..';"\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 = Args['note_'..team_code_ii]
		if team_note then
			notes_exist = true
			-- Only when it exist
			-- First check for existence of reference for note
			if not Args['note_'..team_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_'..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, Args['note_'..team_note]} }
				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 on '..start_date..'. ')
	else
		table.insert(t_footer,'Updated to match(es) played on '..update..'. ')
	end
	table.insert(t_footer,'Manba: '..source)
	if (Args['matches_style'] or '') == 'FBR' then
		table.insert(t_footer, Args['team_header']
			and '<br />Legend: Blue = left column team win; Yellow = draw; Red = top row team win.'
			or '<br />Tavsif: Blue = mezbon gʻalaba; Yellow = durang; Red = mehmon gʻalaba.')
	elseif (Args['matches_style'] or '') == 'BSR' then
		table.insert(t_footer, Args['team_header']
			and '<br />Legend: Blue = left column team win; Red = top row team win.'
			or '<br />Tavsif: Blue = mezbon gʻalaba; Red = mehmon gʻalaba.')
	end
	if Args['a_note'] then
		table.insert(t_footer, '<br />For upcoming matches, an "a" indicates there is an article about the rivalry between the two participants.')
	end
	if Args['ot_note'] then
		table.insert(t_footer, '<br />Matches with lighter background shading were decided after overtime.')
	end

	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 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
			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, '.*(<span class="flagicon">%s*%[%[[Ff][Ii][Ll][Ee]:[^%[%]]*%]%][^<>]*</span>).*', '%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)
	local s1, s2
	-- Define the colouring
	local wc, lc, tc
	if 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%s*style%s*=["\'%s]*background[%-colr]*%s*:([^\'";<>]*).-$') then
		local c = mw.ustring.gsub(s,'^%s*<span%s%s*style%s*=["\'%s]*background[%-colr]*%s*:([^\'";<>]*).-$', '%1')
		return 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%.][%d%.]*)%s*–%s*([%d%.][%d%.]*).*', '%1' ) or '') or ''
	s2 = tonumber(mw.ustring.gsub( s or '',
		'^%s*([%d%.][%d%.]*)%s*–%s*([%d%.][%d%.]*).*', '%2' ) or '') or ''

	-- return colouring if possible
	if s1 ~= '' and s2 ~= '' then
		return (s1 > s2) and wc or ((s2 > s1) and lc or tc)
	else
		return 'transparent'
	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

function p.header(tt,Args,p_sub,N_teams,team_list,legs,multirowlegs)
	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 ''

	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 ''
			result_extra = Args['result_'..team_code_ii..'_'..team_code_jj] or ''
			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 = 'background:transparent;'

				-- 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 = 'background:transparent;'

				-- Reformat dashes
				if result ~= '' then
					result = format_score(result)
				end
				-- Background coloring if enabled
				if matches_style == 'FBR' and result ~= '' then
					if result_extra == 'OT' then
						bg = 'background:' .. get_score_background(result,'level2') .. ';'
					elseif result_extra == 'PK' then
						bg = 'background:' .. get_score_background(result,'level3') .. ';'
					else
						bg = 'background:' .. get_score_background(result,'') .. ';'
					end
				elseif matches_style == 'BSR' and result ~= '' then
					if result_extra == 'OT' then
						bg = 'background:' .. get_score_background(result,'level3') .. ';'
					elseif result_extra == 'OTL' then
						bg = 'background:' .. get_score_background('0–1','level3') .. ';'
					elseif result_extra == 'OTW' then
						bg = 'background:' .. get_score_background('1–0','level3') .. ';'
					elseif result_extra == 'L' then
						bg = 'background:' .. get_score_background('0–1','') .. ';'
					elseif result_extra == 'W' then
						bg = 'background:' .. get_score_background('1–0','') .. ';'
					else
						bg = 'background:' .. get_score_background(result,'') .. ';'
					end
				end
				table.insert(tt,'| style="white-space:nowrap;'..fw..bl..bg..'" |'..result..'\n')
			end
		end
	end

	return tt
end

return p