Module:Sports results
| This Lua module is used on approximately 19,000 pages and changes may be widely noticed. Test changes in the module's /sandbox or /testcases subpages, or in your own module sandbox. Consider discussing changes on the talk page before implementing them. |
| This module is rated as beta. It is considered ready for widespread use, but as it is still relatively new, it should be applied with some caution to ensure results are as expected. |
| This module is currently protected from editing. See the protection policy and protection log for more details. Please discuss any changes on the talk page; you may submit an edit request to ask an administrator to make an edit if it is uncontroversial or supported by consensus. You may also request that this page be unprotected. |
| This module depends on the following other modules: |
| This module uses TemplateStyles: |
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.
Usage
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 |
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
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 |
Team header
To change the default Home \ Away team header, use |team_header=.
Two legs
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
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
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.
See also
-- 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* %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* %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, ' (</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])(%]%]) <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* %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..'" | —\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