Modul:TeamBracket-Tennis
--
-- This module implements many tennis bracket templates
--
local p = {}
local args = {}
local rounds
local sets = {}
local byes
local hideSeeds
local showSeeds
local hideHeadings
local showThird
local offsetThird
local tcats = ''
local function parseArgs(frame)
local fargs = frame.args
local pargs = frame:getParent().args;
local r = tonumber(fargs.rounds or '') or tonumber(pargs.rounds or '') or 2
local teams = math.pow(2, r)
local rdstr = 'RD' .. tostring(r)
local rdp1str = 'RD' .. tostring(r+1)
for i=1,2 do
local targs = (i == 1) and pargs or fargs
for k,v in pairs(targs) do
if type(k) == 'string' then
if k:find('^[R3][Dr][d1-9]%-[a-z][a-z]*00*') then
k = mw.ustring.gsub(k, '^([R3][Dr][d1-9]%-[a-z][a-z]*)00*', '%1')
if (teams < 10) then
tcats = tcats .. '[[Category:Pages using a tennis bracket with deprecated syntax|P]]'
end
end
if k:find('^' .. rdp1str .. '%-[a-z][a-z]*[12]') then
k = mw.ustring.gsub(k, '^' .. rdp1str .. '(%-[a-z][a-z]*[12])', '3rd%1')
tcats = tcats .. '[[Category:Pages using a tennis bracket with deprecated syntax|3]]'
elseif k:find('^' .. rdstr .. '%-[a-z][a-z]*3') then
k = mw.ustring.gsub(k, '^' .. rdstr .. '(%-[a-z][a-z]*)3', '3rd%11')
tcats = tcats .. '[[Category:Pages using a tennis bracket with deprecated syntax|3]]'
elseif k:find('^' .. rdstr .. '%-[a-z][a-z]*4') then
k = mw.ustring.gsub(k, '^' .. rdstr .. '(%-[a-z][a-z]*)4', '3rd%12')
tcats = tcats .. '[[Category:Pages using a tennis bracket with deprecated syntax|3]]'
elseif k:find('^group[0-9]') then
tcats = tcats .. '[[Category:Pages using a tennis bracket with deprecated syntax|G]]'
end
end
args[k] = v
end
end
end
local function parseSets(s)
s = mw.text.split((s or '5') .. '/', '[%s]*/[%s]*')
local n = showThird and (rounds + 1) or (rounds)
for r=1,n do
if s[r] ~= nil and s[r] ~= '' and tonumber(s[r]) then
sets[r] = tonumber(s[r])
elseif sets[r-1] then
sets[r] = sets[r-1]
else
sets[r] = 5
end
end
end
local function addTableRow(tbl)
return tbl:tag('tr')
end
local function addBlank(row, width)
local cell = row:tag('td')
if width then
cell:css('width', width)
end
return cell
end
local function addPath(rows, index, round, top, left, w)
local prop = top and 'border-bottom-width' or 'border-top-width'
if left and round == 1 then
addBlank(rows[index]):css('height', '7px')
addBlank(rows[index + 1]):css('height', '7px')
return nil
else
local cell = addBlank(rows[index])
:attr('rowspan', '2')
:css('border-width', '0')
:css('border-style', 'solid')
:css('border-color', 'black')
if left or round < rounds and not left then
cell:css(prop, w or '2px')
end
return cell
end
end
local function addCompactPath(rows, index, round, top, left, w)
local prop = top and 'border-bottom-width' or 'border-top-width'
if left and round == 1 then
addBlank(rows[index])
return nil
else
local cell = addBlank(rows[index])
:css('border-width', '0')
:css('border-style', 'solid')
:css('border-color', 'black')
if left or round < rounds and not left then
cell:css(prop, w or '2px')
end
return cell
end
end
local function getWidth(param, default)
local arg = args[param .. '-width']
if not arg or string.len(arg) == 0 then
arg = default
end
if tonumber(arg) ~= nil then
arg = arg .. 'px'
end
return arg
end
local function getTeamArgName(round, type, team)
if round > rounds then
return string.format('3rd-%s%d', type, team)
else
return string.format('RD%d-%s%d', round, type, team)
end
end
local function getShadeArg(round, team, s)
local argname = getTeamArgName(round, 'shade', team) .. (s and ('-' .. s) or '')
local value = args[argname]
if not value or string.len(value) == 0 then
return '#f9f9f9'
end
return value
end
local function getScoreArg(round, team, s)
local argname = getTeamArgName(round, 'score', team) .. (s and ('-' .. s) or '')
local value = args[argname]
if not value or string.len(value) == 0 then
return ''
end
return value
end
local function getTeamArg(round, type, team)
local argname = getTeamArgName(round, type, team)
local value = args[argname]
if not value or string.len(value) == 0 then
return ''
end
if mw.ustring.find(value, '[%s]*<[%s/]*[Bb][Rr][%s/]*>[%s ]*&[Nn][Bb][Ss][Pp];[%s]*') then
end
return mw.ustring.gsub(value, '[%s]*<[%s/]*[Bb][Rr][%s/]*>[%s ]*&[Nn][Bb][Ss][Pp];[%s]*', '<br/>')
end
local function getRoundName(round)
local name = args['RD' .. round]
if name and string.len(name) > 0 then
return name
end
local roundFromLast = rounds - round + 1
if roundFromLast == 1 then
return "Finals"
elseif roundFromLast == 2 then
return "Semifinals"
elseif roundFromLast == 3 then
return "Quarterfinals"
else
return "Round of " .. math.pow(2, roundFromLast)
end
end
local function isHidden(r, team, p)
local teamArg
if p == 0 then
teamArg = getTeamArg(r, 'team', team)
else
teamArg = getTeamArg(r, 'team', team % 2 == 0 and team - 1 or team + 1)
end
if not teamArg or string.len(teamArg) == 0 then
return true
end
return false
end
local function renderTeam(row, round, team, top, compact)
local seedCell
local seedArg = getTeamArg(round, 'seed', team)
-- seed value for the paired team
local pairSeedArg = getTeamArg(round, 'seed',
team % 2 == 0 and team - 1 or team + 1)
-- show seed if seed is defined for either or both
local showSeed = showSeeds
or (seedArg and string.len(seedArg) > 0)
or (pairSeedArg and string.len(pairSeedArg) > 0)
if showSeed and (not hideSeeds) then
seedCell = row:tag('td')
:css('text-align', 'center')
:css('background-color', '#f2f2f2')
:css('border-color', '#aaa')
:css('border-style', 'solid')
:css('border-top-width', '1px')
:css('border-left-width', '1px')
:css('border-right-width', '1px')
:css('border-bottom-width', top and '0' or '1px')
:attr('rowspan', (not compact) and '2' or nil)
:wikitext(seedArg)
:newline()
end
local teamArg = getTeamArg(round, 'team', team)
if not teamArg or string.len(teamArg) == 0 then
teamArg = ' '
end
local teamCell = row:tag('td')
:css('background-color', '#f9f9f9')
:css('border-color', '#aaa')
:css('border-style', 'solid')
:css('border-top-width', '1px')
:css('border-left-width', '1px')
:css('border-right-width', '1px')
:css('border-bottom-width', top and '0' or '1px')
:css('padding', '0 2px')
:attr('rowspan', (not compact) and '2' or nil)
:wikitext(teamArg)
:newline()
if not showSeed and (not hideSeeds) then
teamCell:attr('colspan', '2')
end
local scoreCells = {}
for s = 1, sets[round] do
scoreCells[s] = row:tag('td')
:css('text-align', 'center')
:css('border-color', '#aaa')
:css('border-style', 'solid')
:css('border-top-width', '1px')
:css('border-left-width', '0')
:css('border-right-width', '1px')
:css('border-bottom-width', top and '0' or '1px')
:css('background-color', getShadeArg(round, team, s))
:attr('rowspan', (not compact) and '2' or nil)
:wikitext(getScoreArg(round, team, s))
:newline()
end
end
local function renderRound(rows, count, r)
local teams = math.pow(2, rounds - r + 1)
local step = count / teams
local topTeam = true -- is top row in match-up
local topPair = true -- is top match-up in pair of match-ups
local team = 1
for i = 1, count, step do
local offset, height, blank
local hideteam = false
local hideleftpath = false
if byes then
hideteam = isHidden(r, team, 0)
if r > 1 then
hideleftpath = isHidden(r - 1, 2*team - 1, 0) and isHidden(r - 1, 2*team, 0)
end
end
-- leave room for groups for teams other than first and last
if team == 1 or team == teams then
offset = topTeam and i or i + 2
height = step - 2
else
offset = topTeam and i + 1 or i + 2
height = step - 3
end
if showThird and (r == rounds) and (not topTeam) then
height = 0
end
if height > 0 then
blank = addBlank(rows[offset])
:attr('colspan', tonumber(hideSeeds and '3' or '4') + sets[r])
:attr('rowspan', height)
:css('border-color', 'black')
:css('border-style', 'solid')
:css('border-width', '0')
end
-- add bracket
local j = topTeam and i + step - 2 or i
-- add left path
addPath(rows, j, r, topTeam, true, hideleftpath and '0' or '2px')
if hideteam then
rows[j]:tag('td')
:attr('colspan', tonumber(hideSeeds and '1' or '2') + sets[r])
:attr('rowspan', 2)
else
renderTeam(rows[j], r, team, topTeam, false)
end
local rightPath = addPath(rows, j, r, topTeam, false, hideteam and '0' or '2px')
if not topTeam then topPair = not topPair end
if not topPair and r < rounds and (not hideteam) then
if blank then blank:css('border-right-width', '2px') end
rightPath:css('border-right-width', '2px')
end
team = team + 1
topTeam = not topTeam
end
end
local function renderCompactRound(rows, count, r)
local teams = math.pow(2, rounds - r + 1)
local step = count / teams
local topTeam = true -- is top row in match-up
local topPair = true -- is top match-up in pair of match-ups
local team = 1
for i = 1, count, step do
local offset, height, blank
local hideteam = false
local hideleftpath = false
if byes then
hideteam = isHidden(r, team, 0)
if r > 1 then
hideleftpath = isHidden(r - 1, 2*team - 1, 0) and isHidden(r - 1, 2*team, 0)
end
end
-- empty space above or below
local offset = topTeam and i or i + 1
local height = step - 1
if showThird and (r == rounds) and (not topTeam) then
height = 0
end
if height > 0 then
blank = addBlank(rows[offset])
:attr('colspan', tonumber(hideSeeds and '3' or '4') + sets[r])
:css('border-color', 'black')
:css('border-style', 'solid')
:css('border-width', '0')
:attr('rowspan', height)
end
-- add bracket
local j = topTeam and i + step - 1 or i
-- add left path
addCompactPath(rows, j, r, topTeam, true, hideleftpath and '0' or '2px')
if hideteam then
rows[j]:tag('td')
:attr('colspan', tonumber(hideSeeds and '1' or '2') + sets[r])
else
renderTeam(rows[j], r, team, topTeam, true)
end
local rightPath = addCompactPath(rows, j, r, topTeam, false, hideteam and '0' or '2px')
if not topTeam then topPair = not topPair end
if not topPair and r < rounds and (not hideteam) then
if blank then blank:css('border-right-width', '2px') end
rightPath:css('border-right-width', '2px')
end
team = team + 1
topTeam = not topTeam
end
end
local function renderGroups(rows, count, round)
local roundFromLast = rounds - round + 1
local groups = math.pow(2, roundFromLast - 2)
local step = count / groups
local group = 1
local offset = 0
for r = 1,round do
offset = offset + (hideSeeds and 3 or 4) + sets[r]
end
for i = step / 2, count, step do
local name = 'RD' .. round .. '-group' .. group
addBlank(rows[i]):css('height', '7px')
addBlank(rows[i + 1]):css('height', '7px')
addBlank(rows[i])
:attr('rowspan', '2')
:attr('colspan', offset - 1)
:css('text-align', 'center')
:css('border-color', 'black')
:css('border-style', 'solid')
:css('border-width', '0 2px 0 0')
:wikitext(args[name])
:newline()
group = group + 1
end
end
local function renderThird(rows, count, compact)
local k = offsetThird
local row = rows[k]
local blank
--if (offsetThird < count) then
--blank = addBlank(row)
--blank:attr('colspan', tonumber(hideSeeds and '3' or '4') + sets[1])
--end
blank = addBlank(row)
row:tag('td')
:attr('colspan', tonumber(hideSeeds and '1' or '2') + sets[rounds + 1])
:css('text-align', 'center')
:css('border', '1px solid #aaa')
:css('background-color', '#f2f2f2')
:attr('rowspan', compact and 1 or 2)
:wikitext(args['3rd'])
:newline()
k = k + (compact and 2 or 3)
for i = 1,2 do
row = rows[k]
blank = addBlank(row)
renderTeam(row, rounds + 1, i, (i == 1) and true or false, compact)
k = k + (compact and 1 or 2)
end
end
local function renderTree(tbl, compact)
-- create 3 or 1 rows for every team
local count = math.pow(2, rounds) * (compact and 1 or 3)
local offsetcount = 2 * (compact and 1 or 3) + (compact and 2 or 3)
local rows = {}
offsetThird = (compact and 1 or 3) * (math.pow(2, rounds) - math.pow(2, rounds-3)) - (compact and 2 or 4)
if rounds < 4 then
offsetThird = compact and 8 or 17
if rounds < 3 then
offsetThird = compact and 6 or 10
if rounds < 2 then
offsetThird = compact and 4 or 6
end
end
end
for i = 1, count do
rows[i] = addTableRow(tbl)
end
if showThird then
for i = (count+1), (offsetcount + offsetThird) do
rows[i] = addTableRow(tbl)
if (rounds > 1) then
local blank = addBlank(rows[i])
blank:attr('colspan', tonumber(hideSeeds and '3' or '4') + sets[1])
if compact and (rounds > 2) then
blank = addBlank(rows[i])
blank:attr('colspan', tonumber(hideSeeds and '3' or '4') + sets[1])
end
end
end
end
if not compact then
-- fill rows with groups
for r = 1, rounds - 1 do
renderGroups(rows, count, r)
end
end
-- fill rows with bracket
for r = 1, rounds do
if compact then
renderCompactRound(rows, count, r)
else
renderRound(rows, count, r)
end
end
if showThird then
renderThird(rows, count, compact)
end
end
local function renderHeading(tbl, compact)
local titleRow = addTableRow((not hideHeadings) and tbl or mw.html.create('table'))
local widthRow = addTableRow(tbl)
for r = 1, rounds do
addBlank(titleRow)
addBlank(widthRow, r > 1 and '5px' or '1px')
titleRow:tag('td')
:attr('colspan', tonumber(hideSeeds and '1' or '2') + sets[r])
:css('text-align', 'center')
:css('border', '1px solid #aaa')
:css('background-color', '#f2f2f2')
:wikitext(getRoundName(r))
:newline()
local seedCell
if (not hideSeeds) then
seedCell = addBlank(widthRow, getWidth('seed', '25px'))
end
local teamCell = addBlank(widthRow, getWidth('team', '150px'))
local scoreCells = {}
for s = 1, sets[r] do
scoreCells[s] = addBlank(widthRow, getWidth('score', '12px'))
end
addBlank(titleRow)
addBlank(widthRow, r < rounds and '5px' or '1px')
if compact then
teamCell:css('height', '7px')
else
if seedCell then
seedCell:wikitext(' ')
end
teamCell:wikitext(' ')
for s = 1, sets[r] do
scoreCells[s]:wikitext(' ')
end
end
end
end
function p.teamBracket(frame)
parseArgs(frame)
rounds = tonumber(args.rounds) or 2
local teams = math.pow(2, rounds)
local compact = (args['compact'] and (args['compact'] == 'yes' or args['compact'] == 'y'))
hideSeeds = (args['seeds'] and (args['seeds'] == 'no' or args['seeds'] == 'n'))
showSeeds = (args['seeds'] and (args['seeds'] == 'yes' or args['seeds'] == 'y'))
byes = (args['byes'] and (args['byes'] == 'yes' or args['byes'] == 'y'))
hideHeadings = (args['headings'] and (args['headings'] == 'no' or args['headings'] == 'n'))
showThird = (args['3rd'] and args['3rd'] ~= '')
parseSets(args.sets)
-- create the table
local tbl = mw.html.create('table')
:css('border-style', 'none')
:css('font-size', '90%')
:css('margin', '1em 2em 1em 1em')
:css('border-collapse', 'separate')
:css('border-spacing', '0')
:attr('cellpadding', '0')
if (args['nowrap'] and (args['nowrap'] == 'yes' or args['nowrap'] == 'y')) then
tbl:css('white-space', 'nowrap')
end
renderHeading(tbl, compact)
renderTree(tbl, compact)
return tostring(tbl) .. tcats
end
return p