Module:Sandbox/Habst
Appearance
-- Generates {{Medal}} template rows for athletes using [[commons:Data:Athletics_medals.tab]]
local p = {}
local DATA_PAGE = 'Athletics medals.tab'
local COMPETITION_ORDER = { 'Q715044', 'Q182653', 'Q725169', 'Q926006' }
local COMPETITIONS = {
['Q715044'] = '[[Athletics at the Summer Olympics|Olympic Games]]',
['Q182653'] = '[[World Athletics Championships|World Championships]]',
['Q725169'] = '[[World Athletics Indoor Championships|World Indoor Championships]]',
['Q926006'] = '[[World Athletics Relays|World Relays]]',
}
local MEDAL_NAME = { ['1'] = 'Gold', ['2'] = 'Silver', ['3'] = 'Bronze', ['DQ'] = 'DQ' }
local MEDAL_RANK = { Gold = 1, Silver = 2, Bronze = 3, DQ = 4 }
-- Row order within a competition. true: gold → silver → bronze → DQ, each
-- color ordered by edition year (current). false: edition year first, then
-- medal color (the original ordering).
local ORDER_BY_MEDAL = true
local GENDER_WORD = { ['Q6581097'] = "Men's", ['Q6581072'] = "Women's" }
-- ----------------------------------------------------------------------------
-- Data loading
-- ----------------------------------------------------------------------------
-- returns array of { competition, edition, event, medal, country, city, idx } or nil + error message
local function loadRows(athleteQid)
local dataset = mw.ext.data.get(DATA_PAGE)
if not dataset or not dataset.data or not dataset.schema then
return nil, 'could not load [[commons:Data:' .. DATA_PAGE .. ']]'
end
local col = {}
for i, field in ipairs(dataset.schema.fields) do
col[field.name] = i
end
for _, name in ipairs({ 'competition', 'edition', 'event', 'medal', 'athlete', 'country', 'city' }) do
if not col[name] then
return nil, 'Data:' .. DATA_PAGE ..
' is missing the "' .. name .. '" column'
end
end
local rows = {}
for _, record in ipairs(dataset.data) do
if record[col.athlete] == athleteQid then
local medal = MEDAL_NAME[record[col.medal]]
if medal then
rows[#rows + 1] = {
competition = record[col.competition],
edition = record[col.edition],
event = record[col.event],
medal = medal,
country = record[col.country],
city = record[col.city],
idx = #rows + 1,
}
end
end
end
return rows
end
-- ----------------------------------------------------------------------------
-- Wikidata helpers
-- ----------------------------------------------------------------------------
local sitelinkCache = {}
local function sitelinkOf(qid)
if qid == '' then return nil end
if sitelinkCache[qid] == nil then
sitelinkCache[qid] = mw.wikibase.getSitelink(qid) or false
end
return sitelinkCache[qid] or nil
end
local function titleOf(qid)
return sitelinkOf(qid) or mw.wikibase.getLabel(qid) or qid
end
local function yearOf(qid)
return tonumber(titleOf(qid):match('%d%d%d%d')) or 0
end
-- {{Medal|Sport|...}} cell
local function sportCell(qid)
local link = '[[Sport of athletics|athletics]]'
local entity = mw.wikibase.getEntity(qid)
if entity and entity.claims and entity.claims.P21 then
for _, claim in ipairs(entity.claims.P21) do
local snak = claim.mainsnak
local v = snak and snak.datavalue and snak.datavalue.value
if v and v.id and GENDER_WORD[v.id] then
return GENDER_WORD[v.id] .. ' ' .. link
end
end
end
return link
end
-- {{Medal|Country|...}} cell
local function countryCell(code)
return '{{flagg|cnuie|' .. code .. '}}'
end
-- {{Medal|Competition|...}} cell
local function competitionCell(qid)
if COMPETITIONS[qid] then return COMPETITIONS[qid] end
local link = sitelinkOf(qid)
if link then return '[[' .. link .. ']]' end
return titleOf(qid)
end
-- display text: metres -> m, steeplechase -> st., kilometres -> km, race walk -> walk
local function eventDisplay(title)
local frag = title:match('%s–%s(.+)$') or title
local mixed = false
if frag:sub(1, 6) == 'Mixed ' then
mixed = true
frag = frag:sub(7)
elseif frag:sub(1, 6) == "Men's " then
frag = frag:sub(7)
elseif frag:sub(1, 8) == "Women's " then
frag = frag:sub(9)
end
frag = frag:gsub(' kilometres', ' km'):gsub(' kilometre', ' km')
frag = frag:gsub(' metres', ' m'):gsub(' metre', ' m')
frag = frag:gsub('steeplechase', 'st.')
frag = frag:gsub('race walk', 'walk')
frag = frag:gsub('^%a', string.upper)
-- mixed relays: "4 × 400 m relay" -> "4 × 400 m mixed"
if mixed then
local n
frag, n = frag:gsub(' relay$', ' mixed')
if n == 0 then frag = frag .. ' mixed' end
end
return frag
end
-- ----------------------------------------------------------------------------
-- Grouping and rendering
-- ----------------------------------------------------------------------------
local function compOrder(qid)
for i, cq in ipairs(COMPETITION_ORDER) do
if cq == qid then return i end
end
return 100
end
function p.medals(frame)
local args = frame.args
local qid = mw.text.trim(args.qid or '')
if qid == '' then qid = mw.wikibase.getEntityIdForCurrentPage() or '' end
if qid == '' then
return '<span class="error">Athletics medals: no Wikidata item found; pass |qid=</span>'
end
qid = qid:upper()
local rows, err = loadRows(qid)
if not rows then
return '<span class="error">Athletics medals: ' .. err .. '</span>'
end
if #rows == 0 then
return '<span class="error">Athletics medals: no rows for ' .. qid .. ' in [[commons:Data:' .. DATA_PAGE .. ']]</span>'
end
-- group: country -> competition -> edition -> medal rows
local countries, countrySeen = {}, {}
for _, r in ipairs(rows) do
local cg = countrySeen[r.country]
if not cg then
cg = { code = r.country, comps = {}, compSeen = {}, minYear = math.huge }
countrySeen[r.country] = cg
countries[#countries + 1] = cg
end
local pg = cg.compSeen[r.competition]
if not pg then
pg = { qid = r.competition, editions = {}, edSeen = {} }
cg.compSeen[r.competition] = pg
cg.comps[#cg.comps + 1] = pg
end
local eg = pg.edSeen[r.edition]
if not eg then
eg = { qid = r.edition, city = r.city, rows = {} }
pg.edSeen[r.edition] = eg
pg.editions[#pg.editions + 1] = eg
end
eg.rows[#eg.rows + 1] = r
end
-- sort: countries by earliest medal year; within a competition, rows by
-- medal color then edition year (ORDER_BY_MEDAL), or the reverse
for _, cg in ipairs(countries) do
table.sort(cg.comps, function(a, b)
local oa, ob = compOrder(a.qid), compOrder(b.qid)
if oa ~= ob then return oa < ob end
return a.qid < b.qid
end)
for _, pg in ipairs(cg.comps) do
pg.flat = {}
for _, eg in ipairs(pg.editions) do
local y = yearOf(eg.qid)
if y > 0 and y < cg.minYear then cg.minYear = y end
for _, r in ipairs(eg.rows) do
r.year = y
pg.flat[#pg.flat + 1] = r
end
end
table.sort(pg.flat, function(a, b)
local ra, rb = MEDAL_RANK[a.medal] or 9, MEDAL_RANK[b.medal] or 9
if ORDER_BY_MEDAL and ra ~= rb then return ra < rb end
if a.year ~= b.year then return a.year < b.year end
if a.edition ~= b.edition then return a.edition < b.edition end
if ra ~= rb then return ra < rb end
return a.idx < b.idx
end)
end
end
table.sort(countries, function(a, b)
if a.minYear ~= b.minYear then return a.minYear < b.minYear end
return a.code < b.code
end)
-- render
local out = { '{{Medal|Sport|' .. sportCell(qid) .. '}}' }
local missingEvents, missingSeen = {}, {}
for _, cg in ipairs(countries) do
out[#out + 1] = '{{Medal|Country|' .. countryCell(cg.code) .. '}}'
for _, pg in ipairs(cg.comps) do
out[#out + 1] = '{{Medal|Competition|' .. competitionCell(pg.qid) .. '}}'
local linked = {}
for _, r in ipairs(pg.flat) do
-- link meet edition on its first occurrence only
local year = r.year
local place = (year > 0 and year or '?') .. ' ' .. r.city
local edTitle = sitelinkOf(r.edition)
local edCell
if edTitle and not linked[r.edition] then
edCell = '[[' .. edTitle .. '|' .. (year > 0 and year or '?') .. ' ' .. r.city .. ']]'
linked[r.edition] = true
else
edCell = place
end
local evTitle = sitelinkOf(r.event)
if not evTitle then
if not missingSeen[r.event] then
missingSeen[r.event] = true
missingEvents[#missingEvents + 1] =
r.event ~= '' and ('[[:d:' .. r.event .. ']]') or '(no event QID in the data row)'
end
else
out[#out + 1] = '{{Medal|' .. r.medal .. '|' .. edCell ..
'|[[' .. evTitle .. '|' .. eventDisplay(evTitle) .. ']]}}'
end
end
end
end
if #missingEvents > 0 then
return '<span class="error">Athletics medals: no English Wikipedia article for event(s) ' ..
table.concat(missingEvents, ', ') .. '</span>'
end
local text = table.concat(out, '\n')
local source = args.source or args.raw or ''
if source == 'yes' or source == '1' or source == 'true' then
return '<pre>' .. text .. '</pre>'
end
return frame:preprocess(text)
end
return p