Module:Sports table
![]() | This Lua module is used on approximately 24,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 ready for general use. It has reached a mature form and is thought to be relatively bug-free and ready for use wherever appropriate. It is ready to mention on help pages and other Wikipedia resources as an option for new users to learn. To reduce server load and bad output, it should be improved by sandbox testing rather than repeated trial-and-error editing. |
![]() | This module is subject to page protection. It is a highly visible module in use by a very large number of pages, or is substituted very frequently. Because vandalism or mistakes would affect many pages, and even trivial editing might cause substantial load on the servers, it is protected from editing. |
![]() | This module depends on the following other modules: |
![]() | This module uses TemplateStyles: |
Module:Sports table is meant to build group and league tables for sports as well. Note that this module is used extensively, so test potential changes rigorously in the sandbox and please ensure consensus exists before implementing major changes. The rest of this documentation explains how the set-up of the module. Refer to individual style pages for detailed usage instructution
Wikitext test cases are at Module:Sports table/testcases wikitext.
Usage
The basic command is {{#invoke:Sports table|main|style=XXX}}
Style
The XXX
in style=XXX
is to be replaced by one of the following available styles:
General styles
- WDL – For tables with a win-draw-loss system
- WL – For tables with a win-loss system
- WL PK – For tables with a win-loss system that gives different weights to wins and losses from a penalty kick shootout
- WL OT – For tables with a win-loss system that gives different weights to overtime wins
- WL OTL tiebreak – For tables with a win-loss-OT loss system and a separate tiebreak column
- WL goal points – For tables with a win-loss system that gives additional points for goals
- WDLHA – For tables with separate home and away win-draw-loss accounting
- WLHA – For tables with separate home and away win-loss accounting
- WDL OT – For tables with a win-loss system which allow draws or overtime wins in certain situations
- PP – For simple tables which only show games played and number of points
- Round robin
- Custom – For tables in which the number of columns and all column headers are specified as inputs, for greater customization
Sport or league specific styles
- Volleyball – For tables with volleyball system, including sets, set-points and different points for different types of wins
- Darts
- Badminton individual
- Badminton team
- Chess
- CricketRR – Cricket table with Net Run Rate
- CricketCC – Cricket table for County Championship with batting and bowling bonus points
- Fencing
- Football (soccer) was the first creation. This style is renamed to WDL
More styles
The module uses Lua to create the tables. Most functionality is obtained from the main module, but specific column formatting comes from the style sub-modules. The existing styles can handle a lot of different options. In case you need additional options it could be useful to create a new style. Note that some tweaking of an existing style could give you the functionality you need, rather than creating a completely new style. In case you do create a new style, you might want to refer to the Lua reference manual.
Other options
Other options can be used, for example: {{#invoke:Sports table|main|style=WL OT
|sortable_table = yes
|source=ICC
|update=July 28, 2018
- sortable_table
"yes
" makes the table columns sortable
- source
Adds a link which should point to a web resource with the information for verifying or updating the table
- update
Gives the date when the table info was last updated. This is to be set manually - it's not automatically changed when someone changes the table.
- float
Set to left or right to float the table to the left or right on the page
- title_source
To append a reference to the title and suppress the need for a source at the foot of the table
-- Module to build tables for standings in Sports
-- See documentation for details
require('Module:No globals')
local p = {}
-- Main function
function p.main(frame)
-- Declare locals
local Args = frame.args
local N_teams, ii_start, ii_end, N_rows_res = 0
local text_field_result
local notes_exist = false
local t = {}
local t_footer = {}
local team_list = {}
local jjj
-- Random value used for uniqueness
math.randomseed( os.clock() * 10^8 )
local rand_val = math.random()
-- Declare colour scheme
local result_col = {}
result_col = {green1='#CCFFCC', green2='#E6FFE6', green3='#F3FFF3', green4='#F9FFF9',
blue1='#CCCCFF', blue2='#E6E6FF', blue3='#F3F3FF', blue4='#F9F9FF',
yellow1='#FFFFCC', yellow2='#FFFFE6', yellow3='#FFFFF3', yellow4='#FFFFF9',
red1='#FFCCCC', red2='#FFE6E6', red3='#FFF3F3', red4='#FFF9F9',
black1='#CCCCCC', black2='#E6E6E6', black3='#F3F3F3', black4='#F9F9F9'}
-- Declare results column header
local results_header = {}
results_header = {Q='Qualification', QR='Qualification or relegation',
P='Promotion', PQR='Promotion, qualification or relegation',
PR='Promotion or relegation', R='Relegation'}
local results_defined = false -- Check whether this would be needed
-- Now define line for column header (either option or custom)
local local_res_header = results_header[Args['res_col_header']] or Args['res_col_header'] or ''
-- Check whether it includes a note
local res_head_note = Args['note_header_res']
local res_head_note_text = ''
if res_head_note then
notes_exist = true
res_head_note_text = frame:expandTemplate{ title = 'efn', args = { group='Table_notes', res_head_note} }
end
local results_header_txt = '! scope="col" |'..local_res_header..res_head_note_text..'\n'
-- Declare status options
local status_code, status_called = {}
-- ------------------------------------------------------------
-- NOTE: If you add to status_code, also add to status_called and status_letters!!
-- Or functionality will be compromised
-- ------------------------------------------------------------
status_code = { A='Advances to a further round', C='Champion', D='Disqualified',
E='Eliminated', H='Group host', O='Play-off winner', P='Promoted', Q='Qualified to the phase indicated',
R='Relegated', T='Qualified, but not yet to the particular phase indicated'}
status_called = { A=false, C=false, D=false, E=false, H=false, O=false, P=false,
Q=false, R=false, T=false}
local status_letters = 'ACDEHOPQRT'
-- Read in number of consecutive teams (ignore entries after skipping a spot)
while Args['team'..N_teams+1] ~= nil 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
-- Show all stats in table or just games played and points
local full_table = not Args['only_pld_pts'] -- True if par doesn't exist, false otherwise
-- Custom position column label or note
local pos_label = Args['postitle'] or ''
if pos_label == '' then
-- If empty (or undeclared) then default
pos_label = '<abbr title="Position">Pos</abbr>'
end
-- Get VTE button text
local template_name = Args['template_name']
local VTE_text = ''
if template_name then
VTE_text = frame:expandTemplate{ title = 'navbar', args = { mini=1, style='float:right', template_name} }
end
-- Write column headers
local team_width = Args['teamwidth'] or '190'
table.insert(t,'{|class="wikitable" style="text-align:center;"\n') -- Open table
table.insert(t,'! scope="col" width=28|'..pos_label..'\n') -- Position col
table.insert(t,'! scope="col" width='..team_width..'|Team'..VTE_text..'\n') -- Team col
table.insert(t,'! scope="col" width=28|<abbr title="Played">Pld</abbr>\n') -- Games played col
if full_table then
table.insert(t,'! scope="col" width=28|<abbr title="Won">W</abbr>\n') -- Win col
table.insert(t,'! scope="col" width=28|<abbr title="Drawn">D</abbr>\n') -- Draw col
table.insert(t,'! scope="col" width=28|<abbr title="Lost">L</abbr>\n') -- Loss col
table.insert(t,'! scope="col" width=28|<abbr title="Goals for">GF</abbr>\n') -- Goal for col
table.insert(t,'! scope="col" width=28|<abbr title="Goals against">GA</abbr>\n') -- Goal against col
table.insert(t,'! scope="col" width=28|<abbr title="Goal difference">GD</abbr>\n') -- Goal difference col
end
table.insert(t,'! scope="col" width=28|<abbr title="Points">Pts</abbr>\n') -- Points col
if full_table then
table.insert(t,results_header_txt) -- Result col
end
-- Determine what entries go into table
-- Find out which team to show (if any)
local ii_show = team_list[Args['showteam']] -- nil if non-existant
-- Start and end positions to show
local n_to_show = tonumber(Args['show_limit']) or N_teams
-- Check for "legal value", if not legal (or non declared), then show all
local check_n = ((n_to_show>=N_teams) or (n_to_show<=1) or (n_to_show~=math.floor(n_to_show)))
-- Also check whether there is a valid ii_show
if check_n or (not ii_show) then
ii_start = 1
ii_end = N_teams
else
-- It's a proper integer between 2 and N_teams-1
-- If it is in the middle show the same number above and below
-- If it is in the top or bottom, show the exact number
-- How many to show on the side
local n_show_side = math.floor(n_to_show/2)
if ii_show<=n_show_side then
-- Top team
ii_start = 1
ii_end = n_to_show
elseif ii_show>=(N_teams+1-n_show_side) then
-- Bottom team
ii_start = N_teams+1-n_to_show
ii_end = N_teams
else
-- Normal case
ii_start = ii_show-n_show_side
ii_end = ii_show+n_show_side
end
end
-- Get custom options for in table
local win_points = tonumber(Args['winpoints']) or 3
local draw_points = tonumber(Args['drawpoints']) or 1
local loss_points = tonumber(Args['losspoints']) or 0
-- For results column
local new_res_ii = ii_start
-- Pre-check for existence of column
for ii = ii_start, ii_end do
if Args['result'..ii] then results_defined = true end
end
-- Write rows
local team_name, team_code_ii
local note_string, note_local, note_local_num, note_id
local hth_string, hth_local, hth_local_num, hth_id
local note_id_list = {}
local hth_id_list = {}
for ii = ii_start, ii_end do
-- First get code
team_code_ii = team_list[ii]
-- Now read values
local pos_num = Args['pos_'..team_code_ii] or ii
local team_name = Args['name_'..team_code_ii] or team_code_ii
local wins = tonumber(Args['win_'..team_code_ii]) or 0
local draws = tonumber(Args['draw_'..team_code_ii]) or 0
local losses = tonumber(Args['loss_'..team_code_ii]) or 0
local gfor = tonumber(Args['gf_'..team_code_ii]) or 0
local gaig = tonumber(Args['ga_'..team_code_ii]) or 0
local s_pts = tonumber(Args['startpoints_'..team_code_ii]) or 0
local note_local = Args['note_'..team_code_ii] or nil
local hth_local = Args['hth_'..team_code_ii] or nil
-- Then calculate some values
local games = wins + draws + losses
local gdiff = gfor - gaig
local points = win_points*wins + draw_points*draws + loss_points*losses + s_pts
-- Does it need a promotion/qualification/relegation tag
local result_local = Args['result'..ii] or nil
local bg_col = nil
-- Get local background colour
if result_local then
bg_col = result_col[Args['col_'..result_local]] or Args['col_'..result_local] or 'white'
bg_col = 'background-color:'..bg_col..';' -- Full style tag
end
if not bg_col then bg_col = 'background-color:transparent;' end -- Becomes default if undefined
-- Bold this line or not
local ii_fw = ii == ii_show and 'font-weight: bold;' or 'font-weight: normal;'
-- Check whether there is a note or not, if so get text ready for it
if note_local and full_table then
-- Set global check for notes to true
notes_exist = true
-- There are now 3 options for notes
-- 1) It is a full note
-- 2) It is a referal to another note (i.e. it's just a team code; e.g. note_AAA=Text, note_BBB=AAA) in which the note for BBB should link to the same footnote as AAA, with
-- 2a) The other linked note exist in the part of the table shown
-- 2b) The part of the note does not exist in the part of the table shown
if not Args['note_'..note_local] then
-- Option 1
-- Now define the identifier for this
note_id = '"table_note_'..team_code_ii..rand_val..'"' -- Add random end for unique ID if more tables are present on article (which might otherwise share an ID)
note_id_list[team_code_ii] = note_id
-- Call refn template
note_string = frame:expandTemplate{ title = 'efn', args = { group='Table_notes', name=note_id, note_local} }
else
-- Option 2
-- It is option 2a in either one if either the main note is inside the sub-table
-- or another ref to that note is inside the sub-table
-- Basically when it either has been defined, or the main link will be in the table
note_local_num = team_list[note_local]
if note_id_list[note_local] or ((note_local_num >= ii_start) and (note_local_num <= ii_end)) then
-- Option 2a
note_id = '"table_note_'..note_local..rand_val..'"'
note_string = frame:extensionTag{ name = 'ref', args = { group = 'lower-alpha', name = note_id} }
else
-- Option 2b
-- Now define the identifier for this
note_id = '"table_note_'..note_local..rand_val..'"' -- Add random end for unique ID
note_id_list[note_local] = note_id
-- Call refn template
note_string = frame:expandTemplate{ title = 'efn', args = { group='Table_notes', name=note_id, Args['note_'..note_local]} }
end
end
else
note_string = '';
end
-- Check whether there is a head-to-head note or not, if so get text ready for it the same way as for the notes
if hth_local and full_table then
-- Set global check for notes to true
notes_exist = true
if not Args['hth_'..hth_local] then
-- Option 1
-- Now define the identifier for this
hth_id = '"table_hth_'..team_code_ii..rand_val..'"' -- Add random end for unique ID if more tables are present on article (which might otherwise share an ID)
hth_id_list[team_code_ii] = hth_id
-- Call refn template
hth_string = frame:expandTemplate{ title = 'efn', args = { group='Table_notes', name=hth_id, hth_local} }
else
-- Option 2
hth_local_num = team_list[hth_local]
if hth_id_list[hth_local] or ((hth_local_num >= ii_start) and (hth_local_num <= ii_end)) then
-- Option 2a
hth_id = '"table_hth_'..hth_local..rand_val..'"'
hth_string = frame:extensionTag{ name = 'ref', args = { group = 'lower-alpha', name = hth_id} }
else
-- Option 2b
hth_id = '"table_hth_'..hth_local..rand_val..'"' -- Add random end for unique ID
hth_id_list[hth_local] = hth_id
-- Call refn template
hth_string = frame:expandTemplate{ title = 'efn', args = { group='Table_notes', name=hth_id, Args['hth_'..hth_local]} }
end
end
else
hth_string = '';
end
-- Insert status when needed
local status_string = ''
local status_local = Args['status_'..team_code_ii]
local status_let_first = true
local curr_letter
-- Only if it is defined
if status_local then
-- Take it letter by letter
for jjj = 1,mw.ustring.len(status_local) do
curr_letter = mw.ustring.upper(mw.ustring.sub(status_local,jjj,jjj))
-- See whether it exist
if status_code[curr_letter] then
-- Depending on whether it is the first letter of not
if status_let_first then
status_string = curr_letter
status_called[curr_letter] = true
status_let_first = false
else
status_string = status_string..', '..curr_letter
status_called[curr_letter] = true
end
end
end
-- Only add brackets and bolding if it exist
if not status_let_first then status_string = ' <span style="font-weight:bold">('..status_string..')</span>' end
end
-- Now build the rows
table.insert(t,'|- \n') -- New row
table.insert(t,'! scope="row" style="text-align: center;'..ii_fw..bg_col..'"| '..pos_num..'\n') -- Position number
table.insert(t,'| style="text-align: left; white-space:nowrap;'..ii_fw..bg_col..'"| '..team_name..note_string..status_string..'\n')-- Team (with possible note)
table.insert(t,'| style="'..ii_fw..bg_col..'" |'..games..'\n') -- Played
if full_table then
table.insert(t,'| style="'..ii_fw..bg_col..'" |'..wins..'\n') -- Won
table.insert(t,'| style="'..ii_fw..bg_col..'" |'..draws..'\n') -- Drawn
table.insert(t,'| style="'..ii_fw..bg_col..'" |'..losses..'\n') -- Lost
table.insert(t,'| style="'..ii_fw..bg_col..'" |'..gfor..'\n') -- GF
table.insert(t,'| style="'..ii_fw..bg_col..'" |'..gaig..'\n') -- GA
-- For goal difference insert + or − as needed
table.insert(t,'| style="'..ii_fw..bg_col..'" |')
if gdiff>0 then
table.insert(t,'+'..gdiff)
elseif gdiff == 0 then
table.insert(t,0)
else
table.insert(t,'−'..-gdiff)
end
table.insert(t,'\n')
end
-- Add − for negative point totals
table.insert(t,'| style="font-weight: bold;'..bg_col..'" | ')
if points<0 then
table.insert(t,'−'..-points..hth_string)
else
table.insert(t,points..hth_string)
end
table.insert(t,'\n')
-- Now check what needs to be added inside the results column
if full_table then
local res_jjj
if ii == new_res_ii then
-- First check how many rows you need for this
N_rows_res = 1
jjj = ii+1
result_local = Args['result'..ii] or ''
local cont_loop = true
while (jjj<=ii_end) and cont_loop do
if Args['split'..tostring(jjj-1)] then
cont_loop = false
new_res_ii = jjj
else
res_jjj = Args['result'..jjj] or ''
if result_local == res_jjj then
N_rows_res = N_rows_res+1
else
cont_loop = false
new_res_ii = jjj
end
end
jjj = jjj+1
end
-- Now create this field (reuse ii_fw and bg_col)
-- Bold (if in range) or not
if ii_show and (ii_show>=ii) and (ii_show<=(ii+N_rows_res-1)) then
ii_fw = 'font-weight: bold;'
else
ii_fw = 'font-weight: normal;'
end
-- Get background colour
bg_col = nil
if Args['result'..ii] then
bg_col = result_col[Args['col_'..result_local]] or Args['col_'..result_local] or 'white'
bg_col = 'background-color:'..bg_col..';' -- Full style tag
end
if not bg_col then bg_col = 'background-color:transparent;' end -- Becomes default if undefined
-- Check for notes
local note_res_string
if Args['note_res_'..result_local] then
notes_exist = true
local note_res_local = Args['note_res_'..result_local]
note_id = '"table_note_res_'..result_local..rand_val..'"' -- Identifier
-- Check whether it exists or not
if note_id_list[note_local] then
-- It exists already
note_res_string = frame:extensionTag{ name = 'ref', args = { group = 'lower-alpha', name = note_id} }
else
-- It doesn't exist yet
note_res_string = frame:expandTemplate{ title = 'efn', args = { group='Table_notes', name=note_id, Args['note_res_'..result_local]} }
end
else
note_res_string = ''
end
-- Get text
local text_result = Args['text_'..result_local] or ''
text_field_result = '| style="'..ii_fw..bg_col..'" rowspan="'..tostring(N_rows_res)..'" |'..text_result..note_res_string..'\n'
-- See whether it is needed (only when blank for all entries)
if results_defined then table.insert(t,text_field_result) end
end
end
-- Now, if needed, insert a split (solid line to indicate split in standings, but only when it is not at the last shown position)
if Args['split'..ii] and (ii<ii_end) then
if full_table then
table.insert(t,'|- style="background-color:'..result_col['black1']..'; line-height:3pt;"\n')
if results_defined then
-- With results column
table.insert(t,'||||||||||||||||||||||\n')
else
-- No results column
table.insert(t,'||||||||||||||||||||\n')
end
else
table.insert(t,'|- style="background-color:'..result_col['black1']..'; line-height:3pt;"\n')
table.insert(t,'||||||||\n')
end
end
end
-- Remove results header if it is unused
if full_table and not results_defined then
-- First get it as one string, then use string replace to replace that header by empty string
local t_str = tostring(table.concat(t))
t_str = mw.ustring.gsub( t_str, results_header_txt, '' )
t = {}
table.insert(t, t_str)
end
-- Close table
table.insert(t, '|}\n')
-- Get info for footer
local update = Args['update'] or 'unknown'
local source = Args['source'] or frame:expandTemplate{ title = 'citation needed', args = { reason='No source parameter defined', date=os.date('%B %Y') } }
local class_rules = Args['class_rules'] or nil
-- Create footer text
if update~='complete' and update~='' then
table.insert(t_footer,'Updated to game played on '..update..'. ')
end
table.insert(t_footer,'Source: '..source)
if class_rules and full_table then
table.insert(t_footer,'<br>Rules for classification: '..class_rules)
end
-- Now for the named status
local status_exist = false
local status_string = ''
local curr_letter
for jjj = 1,mw.ustring.len(status_letters) do
curr_letter = mw.ustring.upper(mw.ustring.sub(status_letters,jjj,jjj))
if status_called[curr_letter] then
status_string = status_string..'<span style="font-weight:bold">('..curr_letter..')</span> '..status_code[curr_letter]..'; '
status_exist = true
end
end
-- Now end it with a point instead (if it contains entries the '; ' needs to be removed)
if status_exist then
status_string = '<br>'..mw.ustring.sub(status_string,1,mw.ustring.len(status_string)-2)..'.'
table.insert(t_footer,status_string)
end
-- Add notes (if applicable)
if notes_exist then
table.insert(t_footer,'<br>Notes:')
table.insert(t_footer,frame:expandTemplate{ title = 'notelist', args = { group='Table_notes' } })
end
-- Embed within small tags
t_footer = '<div style="font-size:85%;">'..table.concat(t_footer)..'</div>'
-- Add footer to main text table
table.insert(t,t_footer)
return table.concat(t)
end
return p