Module:Medals table country
Appearance
![]() | 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. |
implements:
Usage
local p = {}
-- Load the flagicon data module used to map years to host country flags
local flagicon_data = require("Module:Medals table country/data/Olympic Games host flagicons")
function p.render(frame)
local maxRows = 50
-- Get arguments passed from the parent template
local args = frame:getParent().args
local country = args["country"] or "Country"
local show_games_flag = args["show_games_flag"] == "yes" -- Control flags
local show_dual_ranks = args["show_dual_ranks"] == "yes" -- Dual ranks (Gold medal table and Medal total table)
local total_col_bold = (args["total_col_bold"] or "yes"):lower() == "yes" -- Setting total column bold
-- Determine if the table is for Summer or Winter Olympics
local season = (args["season"] or "summer"):lower()
local is_winter = (season == "winter")
local season_name = is_winter and "Winter" or "Summer"
-- Dynamically require ranking data and games held based on season
local ranking_data
if is_winter then
ranking_data = require("Module:Medals table country/data/Winter Olympics ranking")
else
ranking_data = require("Module:Medals table country/data/Summer Olympics ranking")
end
-------------------------------------------------------------------
-- Helper functions
-------------------------------------------------------------------
-- Helper: border-style for a purple border around the row (hosted games).
local function get_border_style(position)
local base = 'border-top:3px solid purple; border-bottom:3px solid purple;'
if position == "left" then
return base .. ' border-left:3px solid purple;'
elseif position == "right" then
return base .. ' border-right:3px solid purple;'
else
return base
end
end
-- Helper: bold value if it’s the max
local function bold_if_max(val, max)
return val > 0 and val == max and ("'''" .. val .. "'''") or tostring(val)
end
-- Helper: extract leading numeric value for calculations while preserving full notext for display of added notes
local function extract_number_and_text(val)
val = tostring(val or "")
local num = val:match("^%s*(%d+)")
return tonumber(num) or 0, val
end
-- Helper: split number and text to extract the number (to compute things like max athletes), but separate the number from the trailing wikitext (like references)
local function split_number_and_text(val)
val = tostring(val or ""):match("^%s*(.-)%s*$") -- trim outer spaces
local num_str = val:match("^(%d+)")
local num = tonumber(num_str) or 0
local note = val:match("^%d+%s*(.*)") or ""
return num, note
end
-- Helper: process flag icon with optional flagicon
local function format_game_with_flag(game)
local expanded_game = frame:preprocess(game)
local year = expanded_game:match("(%d%d%d%d)")
if show_games_flag and year then
local flag_name = flagicon_data[season] and flagicon_data[season][year]
if flag_name then
return string.format("{{flagicon|%s}} %s", flag_name, expanded_game)
end
end
return expanded_game
end
-- Helper to find all-time rank by country from lists in external modules
-- ("Module:Medals table country/data/Winter Olympics ranking" and "Module:Medals table country/data/Summer Olympics ranking")
local function find_rank(list, name)
for _, entry in ipairs(list) do
if entry.country == name then
return entry.rank
end
end
return nil
end
-------------------------------------------------------------------
-- Fetch or derive all-time rank values
local alltime_gold_rank = args["alltime_gold_rank"]
if not alltime_gold_rank or alltime_gold_rank == "" then
local g = find_rank(ranking_data.gold_ranking, country)
alltime_gold_rank = g and tostring(g) or "–"
end
local alltime_medal_rank = args["alltime_medal_rank"]
if show_dual_ranks then
if not alltime_medal_rank or alltime_medal_rank == "" then
local t = find_rank(ranking_data.total_ranking, country)
alltime_medal_rank = t and tostring(t) or "–"
end
end
-------------------------------------------------------------------
-- Step 1: Collect raw input rows from arguments into a raw_rows list
-------------------------------------------------------------------
local raw_rows = {}
for i = 1, maxRows do
local games = args["row"..i.."_games"]
if games and games ~= "" then
if games == "note" then
local participation_text = args["row"..i.."_participation"] or ""
table.insert(raw_rows, {
is_note = true,
note_text = participation_text,
})
else
local participation = args["row"..i.."_participation"]
local is_host = args["row"..i.."_host"] == "yes"
-- Parse athletes and medals
local athletes_val, athletes_text = split_number_and_text(args["row"..i.."_athletes"])
local gold_num, gold_text = extract_number_and_text(args["row"..i.."_gold"])
local silver_num, silver_text = extract_number_and_text(args["row"..i.."_silver"])
local bronze_num, bronze_text = extract_number_and_text(args["row"..i.."_bronze"])
local rank_raw = args["row"..i.."_rank"] or ""
local medal_rank_raw = args["row"..i.."_medal_rank"] or ""
-- Add row to list
table.insert(raw_rows, {
games = games,
athletes_val = athletes_val,
athletes_text = athletes_text,
participation = participation,
gold = gold_num,
silver = silver_num,
bronze = bronze_num,
gold_display = gold_text,
silver_display = silver_text,
bronze_display = bronze_text,
rank_raw = rank_raw,
medal_rank_raw = medal_rank_raw,
is_host = is_host,
})
end
end
end
-------------------------------------------------------------------
-- Step 2: Process rows and merge participation rows with rowspan
-------------------------------------------------------------------
local rows = {}
local i = 1
local max_athletes, max_gold, max_silver, max_bronze, max_total = 0, 0, 0, 0, 0 -- (in Step 2a: Compute max athletes, max medals and totals)
local total_games, total_gold, total_silver, total_bronze, total_medals = 0, 0, 0, 0, 0
while i <= #raw_rows do
local row = raw_rows[i]
if row.is_note then
table.insert(rows, row)
i = i + 1
-- Merge rows that have identical participation notes
elseif row.participation and row.participation ~= "" and not row.is_host then
local rowspan = 1
for j = i + 1, #raw_rows do
if raw_rows[j].participation == row.participation and not raw_rows[j].is_host then
rowspan = rowspan + 1
else
break
end
end
local merged_games = {}
for k = i, i + rowspan - 1 do
table.insert(merged_games, raw_rows[k].games)
end
table.insert(rows, {
participation = row.participation,
rowspan = rowspan,
games_list = merged_games,
merged = true,
is_host = false,
year = merged_games[1] and merged_games[1]:match("(%d%d%d%d)"),
})
i = i + rowspan
elseif row.participation and row.participation ~= "" then
-- These rows should always be treated as individual participation rows, not medal rows
table.insert(rows, {
is_participation_row = true, -- flag to identify these rows
games = row.games,
year = row.games and row.games:match("(%d%d%d%d)"),
participation = row.participation,
is_host = row.is_host,
merged = false, -- These are not part of a rowspan merge
})
i = i + 1
else
-- Regular medal row: build structured row with medals and rankings
local year = row.games and row.games:match("(%d%d%d%d)") or ""
-- Athletes number and text extracted and splited (number to calc max value; text for wiki-linking)
local athletes_num = tonumber(row.athletes_val) or 0
local athletes_note = tostring(row.athletes_text) or ""
local athletes_cell = string.format("[[%s at the %s %s Olympics|%d]] %s", country, year, season_name, athletes_num, athletes_note)
-- Formatted athletes display after max athletes calc (Step 2a) in Step 5: Rendering
-------------------------------------------------------------------
-- Step 2a: Compute max athletes, max medals and totals
-------------------------------------------------------------------
-- Medal totals calculation
local total = row.gold + row.silver + row.bronze
total_games = total_games + 1
total_gold = total_gold + row.gold
total_silver = total_silver + row.silver
total_bronze = total_bronze + row.bronze
max_athletes = math.max(max_athletes, athletes_num)
max_gold = math.max(max_gold, row.gold)
max_silver = math.max(max_silver, row.silver)
max_bronze = math.max(max_bronze, row.bronze)
max_total = math.max(max_total, total)
-- Final totals
total_medals = total_gold + total_silver + total_bronze
-------------------------------------------------------------------
-- Step 2b: Rank links and color set for top 3 ranks
-------------------------------------------------------------------
-- Helper: build medal table rank links
local function make_rank_link(rank_raw)
local rank_num = tonumber(rank_raw)
local medal_table_title = string.format("%s %s Olympics medal table", year, season_name)
if rank_num then
return string.format("[[%s|%d]]", medal_table_title, rank_num), rank_num
elseif rank_raw == "" then
return string.format("[[%s|–]]", medal_table_title), nil
elseif rank_raw ~= "" then
return rank_raw, nil
else
return "", nil
end
end
-- Rank links per year ( --> all-time rank links outside loop)
local gold_rank_link, gold_rank_num = make_rank_link(row.rank_raw)
local medal_rank_link, medal_rank_num = make_rank_link(row.medal_rank_raw)
-- Background color for top 3 ranks
local function get_rank_color(rank)
return (rank == 1 and "#F7F6A8") or (rank == 2 and "#dce5e5") or (rank == 3 and "#ffdab9") or ""
end
local bgcolor_gold_ranking = get_rank_color(gold_rank_num)
local bgcolor_medal_ranking = get_rank_color(medal_rank_num)
-------------------------------------------------------------------
-- Add full row to output list
table.insert(rows, {
games = row.games,
year = year,
athletes_num = athletes_num,
athletes = athletes_cell,
participation = row.participation,
gold = row.gold,
silver = row.silver,
bronze = row.bronze,
gold_display = row.gold_display,
silver_display = row.silver_display,
bronze_display = row.bronze_display,
total = total,
total_medals = total_medals,
gold_rank = gold_rank_link,
total_rank = medal_rank_link,
bgcolor_gold_ranking = bgcolor_gold_ranking,
bgcolor_medal_ranking = bgcolor_medal_ranking,
is_host = row.is_host,
merged = false,
})
i = i + 1
end
end
-- All-time rank links (goldrank and alltime medal rank link with the same expression)
local function make_alltime_rank_link(rank)
return rank ~= "" and string.format("[[All-time Olympic Games medal table#Complete ranked medals (excluding precursors)|%s]]", rank) or ""
end
alltime_gold_rank = make_alltime_rank_link(alltime_gold_rank) -- all-time rank (already set above from args or module)
if show_dual_ranks then
alltime_medal_rank = make_alltime_rank_link(alltime_medal_rank)
end
-------------------------------------------------------------------
-- Step 3 Number of Olympic Games held calculation
-------------------------------------------------------------------
-- Configuration for Olympic Games history
local OLYMPIC_CONFIG = {
SUMMER = {
start_year = 1896,
canceled_games = {1916, 1940, 1944}, -- Years of canceled Summer Olympics
split_year = 1944, -- Year of last canceled game during World War II
games_before_last_canceled_game = 10 -- Number of Summer Games held until 1944, if counted with 4-year interval from 1944-adjusted start.
-- 1896 (1), 1900 (2), ..., 1936 (10), 1948 (11)
},
WINTER = {
start_year = 1924, -- First Winter Olympics
canceled_games = {1940, 1944}, -- Years of canceled Summer Olympics
split_year = 1994, -- Year Winter Olympics moved to offset
games_before_split_offset = 17 -- Number of Winter Games held until 1994 inclusive, if counted with 4-year interval from 1994-adjusted start.
-- 1924 (1), 1928 (2), ..., 1992 (16), 1994 (17)
}
}
local current_year = tonumber(os.date("%Y"))
local function get_games_held(current_olymp_in_progress_or_done)
if is_winter then
-- Winter Olympics logic
local years_since = current_year - OLYMPIC_CONFIG.WINTER.split_year
if years_since % 4 ~= 0 then -- Modulo for not Olympic years
return math.floor(years_since / 4) + OLYMPIC_CONFIG.WINTER.games_before_split_offset
elseif current_olymp_in_progress_or_done then -- games in current year and taking/taken place
return math.floor(years_since / 4) + OLYMPIC_CONFIG.games_before_split_offset
else
return math.floor(years_since / 4) + OLYMPIC_CONFIG.games_before_split_offset -1 -- games in current year, but not taken place yet, hence the "-1"
end
else
-- Summer Olympics logic
local years_since = current_year - OLYMPIC_CONFIG.SUMMER.split_year
if years_since % 4 ~= 0 then -- Modulo for not Olympic years
return math.floor(years_since / 4) + OLYMPIC_CONFIG.SUMMER.games_before_last_canceled_game
elseif current_olymp_in_progress_or_done then -- games in current year and taking/taken place
return math.floor(years_since / 4) + OLYMPIC_CONFIG.SUMMER.games_before_last_canceled_game
else
return math.floor(years_since / 4) + OLYMPIC_CONFIG.SUMMER.games_before_last_canceled_game - 1 -- games in current year, but not taken place yet, hence the "-1"
end
end
end
local function is_future_event(participation_text)
-- Strip common wiki formatting for comparison, removes whitespaces and sets it to lower case
local stripped_text = mw.ustring.lower(mw.text.trim(participation_text:gsub("'''", ""):gsub("''", ""))) -- Removes ''' and ''
return stripped_text == "future event"
end
local current_olymp_in_progress_or_done = false
for _, row in ipairs(rows) do
if row.athletes_num and row.year == tostring(current_year) then
current_olymp_in_progress_or_done = true
break
end
if (row.participation and is_future_event(row.participation)) and row.year == tostring(current_year) then
current_olymp_in_progress_or_done = false
break
end
if (row.participation and not is_future_event(row.participation)) and row.year == tostring(current_year) then
current_olymp_in_progress_or_done = true
break
end
end
local games_held_num = get_games_held(current_olymp_in_progress_or_done)
-------------------------------------------------------------------
-- Step 4: Build the wikitable header
-------------------------------------------------------------------
local sticky_header = frame:expandTemplate{ title = "sticky-header" }
local wikitext = '{| class="wikitable sticky-header" style="text-align:center; font-size:90%;"\n'
wikitext = wikitext .. "|-\n! Games !! Athletes"
wikitext = wikitext .. ' !! style="background:gold; width:3.7em; font-weight:bold;"| Gold'
wikitext = wikitext .. ' !! style="background:silver; width:3.7em; font-weight:bold;"| Silver'
wikitext = wikitext .. ' !! style="background:#c96; width:3.7em; font-weight:bold;"| Bronze'
wikitext = wikitext .. ' !! style="width:3.7em; font-weight:bold;"| Total'
-- Column headers for rank (1 or 2 columns depending on config)
if show_dual_ranks then
wikitext = wikitext .. ' !! style="width:3.7em; font-weight:bold;"| [[Olympic medal table|<small>{{abbr|Gold medal|Gold medal table rank }}</small>]]'
wikitext = wikitext .. ' !! style="width:3.7em; font-weight:bold;"| [[Olympic medal table|<small>{{abbr|Total medal|Total medal table rank}}</small>]]\n'
else
wikitext = wikitext .. ' !! style="width:3.7em; font-weight:bold;"| Rank\n'
end
-------------------------------------------------------------------
-- Step 5: Render each table row
-------------------------------------------------------------------
local colspan_for_note = show_dual_ranks and 8 or 7
local colspan_for_participation = show_dual_ranks and 7 or 6
for _, row in ipairs(rows) do
if row.is_note then
wikitext = wikitext .. string.format(
"|-\n| colspan=%d style=\"text-align:center;\" | %s\n",
colspan_for_note,
row.note_text
)
elseif row.merged then
-- Participation row with rowspan (only for non-future event and non-host merges)
wikitext = wikitext .. string.format(
"|-\n| align=left %s | %s || colspan=%d rowspan=%d %s | %s\n",
row.is_host and ('style="' .. get_border_style("left") .. '"') or "", -- is_host will be false here, but keep the check for robustness
format_game_with_flag(row.games_list[1]),
colspan_for_participation ,
row.rowspan,
row.is_host and ('style="' .. get_border_style("right") .. '"') or "", -- is_host will be false here
row.participation
)
for k = 2, row.rowspan do
wikitext = wikitext .. string.format(
"|-\n| align=left %s | %s\n",
row.is_host and ('style="' .. get_border_style() .. '"') or "", -- is_host will be false here
format_game_with_flag(row.games_list[k])
)
end
elseif row.is_participation_row then -- Handle individual participation rows (including host future events)
wikitext = wikitext .. string.format(
"|-\n| align=left %s | %s || colspan=%d %s | %s\n",
row.is_host and ('style="' .. get_border_style("left") .. '"') or "",
format_game_with_flag(row.games),
colspan_for_participation,
row.is_host and ('style="' .. get_border_style("right") .. '"') or "",
row.participation
)
else
-- Regular medal row
--------------------------------------------
local line = "|-\n"
local game_display = format_game_with_flag(row.games)
local athletes_display = (row.athletes_num == max_athletes) and ("'''" .. row.athletes .. "'''") or row.athletes -- Formatted athletes display after max athletes calc
if row.is_host then
line = line .. string.format('| align=left style="%s" | %s', get_border_style("left"), game_display)
line = line .. string.format(' || style="%s" | %s', get_border_style(), athletes_display)
line = line .. string.format(' || style="%s" | %s', get_border_style(), bold_if_max(row.gold, max_gold))
line = line .. string.format(' || style="%s" | %s', get_border_style(), bold_if_max(row.silver, max_silver))
line = line .. string.format(' || style="%s" | %s', get_border_style(), bold_if_max(row.bronze, max_bronze))
if total_col_bold then
line = line .. string.format(' || style="%s" | \'\'\'%s\'\'\'', get_border_style(), tostring(row.total))
else
line = line .. string.format(' || style="%s" | %s', get_border_style(), bold_if_max(row.total, max_total))
end
if show_dual_ranks then
line = line .. string.format(' || style="%s%s" | %s', row.bgcolor_gold_ranking ~= "" and 'background-color:' .. row.bgcolor_gold_ranking .. '; ' or "", get_border_style(), row.gold_rank)
line = line .. string.format(' || style="%s%s" | %s', row.bgcolor_medal_ranking ~= "" and 'background-color:' .. row.bgcolor_medal_ranking .. '; ' or "", get_border_style("right"), row.total_rank)
else
line = line .. string.format(' || style="%s%s" | %s', row.bgcolor_gold_ranking ~= "" and 'background-color:' .. row.bgcolor_gold_ranking .. '; ' or "", get_border_style("right"), row.gold_rank)
end
else
-- Regular non-hosted row
line = line .. "| align=left | " .. game_display
line = line .. " || " .. athletes_display
line = line .. " || " .. (max_gold > 0 and row.gold == max_gold and ("'''" .. row.gold_display .. "'''") or row.gold_display)
line = line .. " || " .. (max_silver > 0 and row.silver == max_silver and ("'''" .. row.silver_display .. "'''") or row.silver_display)
line = line .. " || " .. (max_bronze > 0 and row.bronze == max_bronze and ("'''" .. row.bronze_display .. "'''") or row.bronze_display)
if total_col_bold then
line = line .. " || " .. "'''" .. tostring(row.total) .. "'''"
else
line = line .. " || " .. bold_if_max(row.total, max_total)
end
if show_dual_ranks then
line = line .. (row.bgcolor_gold_ranking ~= "" and (' || style="background-color:' .. row.bgcolor_gold_ranking .. '" | ' .. row.gold_rank) or (" || " .. row.gold_rank))
line = line .. (row.bgcolor_medal_ranking ~= "" and (' || style="background-color:' .. row.bgcolor_medal_ranking .. '" | ' .. row.total_rank) or (" || " .. row.total_rank))
else
line = line .. (row.bgcolor_gold_ranking ~= "" and (' || style="background-color:' .. row.bgcolor_gold_ranking .. '" | ' .. row.gold_rank) or (" || " .. row.gold_rank))
end
end
wikitext = wikitext .. line .. "\n"
end
end
-------------------------------------------------------------------
-- Step 6: Add total row at bottom of table
-------------------------------------------------------------------
wikitext = wikitext .. "|-\n! colspan=2 | Total".. string.format(" (%d/%s)", total_games, games_held_num)
wikitext = wikitext .. string.format(" !! %d !! %d !! %d !! %d", total_gold, total_silver, total_bronze, total_medals)
if show_dual_ranks then
wikitext = wikitext .. " !! " .. alltime_gold_rank .. " !! " .. alltime_medal_rank .. "\n"
else
wikitext = wikitext .. " !! " .. alltime_gold_rank .. "\n"
end
wikitext = wikitext .. "|}"
-------------------------------------------------------------------
-- Final output
-------------------------------------------------------------------
local full_wikitext = sticky_header .. "\n" .. wikitext
return frame:preprocess(full_wikitext)
end
return p