This module is rated as beta, and is ready for widespread use. It is still new and should be used with some caution to ensure the results are as expected.
This module is intended to be used to create and edit intricate sports brackets that cannot be made by Module:RoundN or Module:Team bracket. For example, additional headers (for double-elimination brackets; 3rd-, 5th-, 7th-place matches; etc), singular omitted matches (i.e., having a horizonal line instead of a match), and N-way brackets (N teams per match). The syntax is slightly more complicated than the aforementioned brackets, but simpler than using the standard wikitable code.
set to yes to automatically set set maximum round to display based on entries.
no
rows
manually set the number of rows.
Automatic
teams-per-match
enter the number of teams in each match. Use colm-teams-per-match to set individual columns
2
colm-headers
(optional) enter the row numbers where headers are desired in column m. Separate entries with ,. Half integer values are allowed.
Automatic
colm-matches
enter the row numbers where a match is desired in column m. Matches take up two rows by default. Separate entries with ,. Half integer values are allowed.
RDmh-hide
set to yes to hide the (alpha) hth header and all matches beneath it in column m unless any of those entries are non empty. Useful for consolation matches.
colm-colm+1-paths
enter the starting and ending row numbers, separated by -, from columns m and m+1 where a path is desired. Separate entries with ,. Half integer values are allowed.
colm-colm+1-cross
enter the row number where paths intersect from column m to m+1.
RDm-altname
Alternate name for RDm (e.g., if |RD1-altname=first, then first-team1 can be used instead of RD1-team1). Use RDmh-altname for cells under header mh.
text-altname
Alternate name for RDm-textk (e.g., if |text-altname=details, then RDm-details1 can be used instead of RDm-text1).
maxround
final round to display. This parameter should be omitted unless it is less than the default value set by rounds.
minround
first round to display.
1
height
the amount of vertical visibility desired for the bracket. Creates a vertical scroll bar. Enter a number with units (e.g., 30em or 480px).
col-spacing
the amount of horizontal space between rounds. Enter as a plain number (e.g., 10 for 10px).
5
seed-width
the width of the cells for seeds. Plain numbers are assumed to be in px units (e.g., 25 for 25px 2em for 2em)
25
team-width
the width of the cells for team names. Plain numbers are assumed to be in px units (e.g., 200 for 200px or 15em for 15em)
150
score-width
the width of the cells for scores. Plain numbers are assumed to be in px units (e.g., 25 or 25px 2em for 2em)
25
seeds
set to no to omit seeds in in all matches. set to yes to show seed cells to show for all matches.
legs
the number of legs for all rounds. Use RDm-legs to individually set columns. Use RDm-legsk to individually set teams.
1
autolegs
set to yes to automatically generate score cells per team. If legs or RDm-legs is used, autolegs will be set to no .
no
byes
set to yes to hide any team cells that are empty. Alternatively, set to m to have rounds 1 through m hide any team cells that are empty. Use RDm-byes for just matches under column m. Use RDmh-byes for just matches under header h only in column m.
no
show-bye-paths
set to yes to replace any team cells that that are hidden byes with a path.
no
aggregate
set to yes to add an aggregate score box to each match. Only matches with two or more legs will show the aggregate score box.
no
boldwinner
set to yes to automatically bold the seed/team/score with the higher score in each match.
no
shift
vertically shifts all of the entries by the number entered. Use RDm-shift for individual in columns.
0
RDm, RDmh
The header text of the (alpha hth) header in column m (e.g., RD1 or RD1a for the first header and RD1b for the second header in column 1).
RDm-seedk
The seed of the kth team in column m. Alternatively, use RDmh-seedk for the kth team under header mh.
RDm-teamk
The name of the kth team in column m. Alternatively, use RDmh-teamk for the kth team under header mh.
RDm-scorek
The score of the kth team in column m. Alternatively, use RDmh-scorek for the kth team under header mh. Append the suffix -l for the lth leg or -agg for the aggregate score.
RDm-textk
The text above the kth match in column m. Alternatively, use RDmh-textk for the kth match under header mh.
RDm-groupk
The text for the kth group in column m. Group text will appear to the left of whenever two paths meet.
RD-shade
the background color (in hex format, e.g. #ABCDEF) of all headers. Use RDm-shade or RDmh-shade for individual headers.
set to numbered change the parameter name style of RDm-textk, RDm-seedk, RDm-teamk, and RDm-scorek to a numbered notation (|1=,|2=...). Set |seeds=yes add seeds.
indexed
↑Does not currently work for paths under subheaders.
↑numbered may not be compatible with certain other features, e.g. boldwinner and groups.
Parameter hierarchy
Whenever there are multiple headers in a single column, more than one parameter may be assigned to a cell value. For example, in the following bracket, both |RD1-team3= and |RD1b-team1= can be used to assign the third team in the first column. By default, entries with subheader prefixes will override those without. In the below example, RD1b-team1 will override any value that has been set by RD1-team3.
Upper round
Lower round
RD1-team3 or RD1b-team1
Parameters used in articles take precedence over parameters used in the template itself. For example, suppose ArticleX used the template NTeamBracket, and suppose NTeamBracket had the parameter |RD1-seed1=1 set. If ArticleX implemented {{NTeamBracket|RD1-seed1=2}}, then the first team in round 1 would have a seed of 2.
Path codes
Path codes are entered in the form a-b, where a is associated match in the first column, and b is associated match in the second column. Path codes can be grouped; for example, (a,b)-c is equivalent to a-c, b-c. To add color, append :color to the end of a path, e.g. 3-5:red. Only one extra color can be used in a bracket.
Example
Output
1-3
(1,5)-3
3-1,3-5:red
Examples
Note: These are only examples to illustrate parameters. Standard 4-team, 8-team, etc. brackets are better handled by Module:Team bracket.
local p = {}
local entries = {}
local pathEntries = {}
local pathcolor
local shift = {}
local hascross = {}
local teams_per_match = {}
local rlegs = {}
local maxlegs = {}
local autolegs
local byes = {}
local hide = {}
local matchgroup = {}
local autocol
local seeds
local forceseeds
local nowrap
local boldwinner
local aggregate
local paramstyle
local masterindex
local function isempty(s)
return s==nil or s==''
end
local function notempty(s)
return s~=nil and s~=''
end
local function bargs(s)
return pargs[s] or fargs[s]
end
local function toChar(num)
return string.char(string.byte("a")+num-1)
end
local function split(str,delim,tonum)
result = {};
local a = "[^"..table.concat(delim).."]+"
for w in str:gmatch(a) do
if tonum==true then
table.insert(result, tonumber(w));
else
table.insert(result, w);
end
end
return result;
end
local function getWidth(ctype, default)
local result = bargs(ctype..'-width')
if isempty(result) then return default end
if tonumber(result)~=nil then return result..'px' end
return result
end
local function matchGroups()
for j=minc,c do
matchgroup[j]={}
for i=1,r do
if entries[j][i]~= nil and entries[j][i]['ctype']=='team' then
matchgroup[j][i]=math.ceil(entries[j][i]['index']/teams_per_match[j])
entries[j][i]['group'] = math.ceil(entries[j][i]['index']/teams_per_match[j])
end
end
end
end
local function boldWinner()
local function boldScore(j,i,l)
if entries[j][i]~= nil and entries[j][i]['ctype']=='team' then
local myscore = entries[j][i]['score'][l]
if myscore == "" or myscore:find("%D") then return 'normal'
else myscore=tonumber(myscore) end
local compscore = {}
for k,v in pairs(matchgroup[j]) do
if matchgroup[j][i]==v and k~=i then
if entries[j][k]['score'][l]==nil or entries[j][k]['score'][l] == "" or entries[j][k]['score'][l]:find("%D") then return 'normal'
else table.insert(compscore,tonumber(entries[j][k]['score'][l])) end
end
end
for k,v in pairs(compscore) do
if myscore<=v then return 'normal' end
end
if l~='agg' then
entries[j][i]['wins'] = entries[j][i]['wins']+1
else
entries[j][i]['aggwins'] = 1
end
return 'bold'
end
end
local function boldEntry(j,i,agg)
local wins
if agg~=true then
wins = 'wins'
else
wins = 'aggwins'
end
local myteam = entries[j][i][wins]
local compteam = {}
for k,v in pairs(matchgroup[j]) do
if matchgroup[j][i]==v and k~=i then
table.insert(compteam,tonumber(entries[j][k][wins]))
end
end
for k,v in pairs(compteam) do
if myteam<=v then return 'normal' end
end
return 'bold'
end
for j=minc,c do
for i=1,r do
if entries[j][i]~= nil and entries[j][i]['ctype']=='team' then
entries[j][i]['wins'] = 0
entries[j][i]['aggwins'] = 0
end
end
for i=1,r do
if entries[j][i]~= nil and entries[j][i]['ctype']=='team' then
local legs = rlegs[j]
if notempty(entries[j][i]['legs']) then
legs = tonumber(entries[j][i]['legs'])
end
if autolegs then
local l=1
repeat l=l+1
until isempty(entries[j][i]['score'][l])
legs = l-1
end
for l=1,legs do
entries[j][i]['score']['weight'][l] = boldScore(j,i,l)
end
if aggregate and legs>1 then
entries[j][i]['score']['weight']['agg'] = boldScore(j,i,'agg')
end
end
end
for i=1,r do
if entries[j][i]~= nil and entries[j][i]['ctype']=='team' then
local agg
local legs = rlegs[j]
if notempty(entries[j][i]['legs']) then
legs = tonumber(entries[j][i]['legs'])
end
if autolegs then
local l=1
repeat l=l+1
until isempty(entries[j][i]['score'][l])
legs = l-1
end
if aggregate and legs>1 then agg=true end
entries[j][i]['weight'] = boldEntry(j,i,agg)
end
end
end
end
local function isBlankEntry(col,row,ctype)
if isempty(entries[col][row]) then return true end
if isempty(entries[col][row]['team']) and isempty(entries[col][row]['text']) then return true end
return false
end
local function showSeeds(j,i)
local showseed=false
if forceseeds or notempty(entries[j][i]['seed']) then
showseed=true
else
for k=1,teams_per_match[j]-1 do
if notempty(entries[j][i+2*k]) and entries[j][i]['group']==entries[j][i+2*k]['group'] and notempty(entries[j][i+2*k]['seed']) then
showseed=true
end
if notempty(entries[j][i-2*k]) and entries[j][i]['group']==entries[j][i-2*k]['group'] and notempty(entries[j][i-2*k]['seed']) then
showseed=true
end
end
end
return showseed
end
local function blankCell(tbl,j,i,rowspan,colspan,text,align)
local cell = tbl:tag('td')
if colspan~=1 then
cell:attr('colspan',colspan)
end
if rowspan~=1 then
cell:attr('rowspan',rowspan)
end
if notempty(align) then
cell:css('text-align',align)
end
if notempty(text) then
if nowrap then
cell:css('white-space', 'nowrap')
end
cell:wikitext(text)
end
return cell
end
local function headerCell(tbl,j,i,colspan)
local cell = tbl:tag('td')
:attr('rowspan',2)
:css('text-align','center')
:css('border','1px solid #aaa')
:css('background-color',entries[j][i]['shade'])
:wikitext(entries[j][i]['header'])
if colspan~=1 then
cell:attr('colspan',colspan)
end
if nowrap then
cell:css('white-space', 'nowrap')
end
if entries[j][i]['position']=='top' and entries[j][i-2]~=nil and entries[j][i-2]['ctype']~='blank' and entries[j][i-2]['ctype']~='text' and entries[j][i-2]['ctype']~='group' and (not byes[j][entries[j][i]['headerindex']] or not isBlankEntry(j,i-2)) then
cell:css('border-top','2px solid #aaa')
end
return cell
end
local function teamCell(tbl,k,j,i,l,colspan)
local bg = '#F2F2F2'
local cell = tbl:tag('td')
:attr('rowspan',2)
:css('border','1px solid #aaa')
if colspan~=1 and colspan~=nil then
cell:attr('colspan',colspan)
end
if k=='seed' or k=='score' then
cell:css('text-align','center')
end
if k=='seed' or k=='score' or nowrap then
cell:css('white-space', 'nowrap')
end
if k=='seed' then
cell:css('border-right','2px solid #aaa')
else
bg='#F9F9F9'
end
if k=='team' then
cell:css('padding-left','0.3em')
end
if l=='agg' then
cell:css('border-left','2px solid #aaa')
end
if (l==nil and entries[j][i]['weight']=='bold') or entries[j][i]['score']['weight'][l]=='bold' then
cell:css('font-weight','bold')
end
if entries[j][i]['position']=='top' and entries[j][i-2]~=nil and entries[j][i-2]['ctype']~='blank' and entries[j][i-2]['ctype']~='text' and entries[j][i-2]['ctype']~='group' and (not byes[j][entries[j][i]['headerindex']] or not isBlankEntry(j,i-2)) then
cell:css('border-top','2px solid #aaa')
end
cell:css('background-color',bg)
if l==nil then
cell:wikitext(entries[j][i][k])
else
cell:wikitext(tostring(entries[j][i][k][l]))
end
return cell
end
local function insertEntry(tbl,j,i)
local entry_colspan=maxlegs[j]+2
if not seeds then entry_colspan=entry_colspan-1 end
if (aggregate and maxlegs[j]>1) or maxlegs[j]==0 then
entry_colspan=entry_colspan+1
end
if entries[j][i]~=nil and entries[j][i]['ctype']=='blank' then
return
end
if entries[j][i]==nil then
if entries[j][i-1]~=nil or i==1 then
local rowspan = 0
local row = i
repeat
rowspan=rowspan+1
row=row+1
until entries[j][row]~=nil or row>r
return blankCell(tbl,j,i,rowspan,entry_colspan)
else
return
end
end
if entries[j][i]['ctype']=='header' then
if byes[j][entries[j][i]['headerindex']] then
local emptyround = true
local row = i+1
repeat
if not isBlankEntry(j,row) then
emptyround = false
end
row = row+1
until (entries[j][row]~=nil and entries[j][row]['ctype']=='header') or row>r
if emptyround == true then
return blankCell(tbl,j,i,2,entry_colspan)
end
end
if hide[j][entries[j][i]['headerindex']] then
return blankCell(tbl,j,i,2,entry_colspan)
end
if isempty(entries[j][i]['header']) then
if entries[j][i]['headerindex']==1 then
if j==c then entries[j][i]['header'] = 'Final'
elseif j==c-1 then entries[j][i]['header'] = 'Semifinals'
elseif j==c-2 then entries[j][i]['header'] = 'Quarterfinals'
else entries[j][i]['header'] = 'Round '..j
end
else
entries[j][i]['header'] = 'Lower round '..j
end
end
return headerCell(tbl,j,i,entry_colspan)
end
if entries[j][i]['ctype']=='team' then
if byes[j][entries[j][i]['headerindex']] and isBlankEntry(j,i) then
return blankCell(tbl,j,i,2,entry_colspan)
end
if hide[j][entries[j][i]['headerindex']] then
return blankCell(tbl,j,i,2,entry_colspan)
end
local legs = rlegs[j]
local team_colspan
if notempty(entries[j][i]['legs']) then
legs = tonumber(entries[j][i]['legs'])
end
if autolegs then
local l=1
repeat l=l+1
until isempty(entries[j][i]['score'][l])
legs = l-1
end
team_colspan = maxlegs[j]-legs+1
if aggregate and legs==1 and maxlegs[j]>1 then
team_colspan=team_colspan+1
end
if maxlegs[j]==0 then
team_colspan=team_colspan+1
end
if seeds then
if showSeeds(j,i)==true then
teamCell(tbl,'seed',j,i)
else
team_colspan=team_colspan+1
end
end
teamCell(tbl,'team',j,i,nil,team_colspan)
for l=1,legs do
teamCell(tbl,'score',j,i,l)
end
if aggregate and legs>1 then
teamCell(tbl,'score',j,i,'agg')
end
end
if entries[j][i]['ctype']=='text' then
blankCell(tbl,j,i,2,entry_colspan,entries[j][i]['text'])
end
if entries[j][i]['ctype']=='group' then
local colspan=0
for m=j,entries[j][i]['colspan']+j-1 do
colspan=colspan+maxlegs[m]+2
if not seeds then colspan=colspan-1 end
if (aggregate and maxlegs[m]>1) or maxlegs[m]==0 then
colspan=colspan+1
end
end
colspan = colspan+2*(entries[j][i]['colspan']-1)
blankCell(tbl,j,i,2,colspan,entries[j][i]['group'],'center')
end
if entries[j][i]['ctype']=='line' then
tbl:tag('td')
:attr('rowspan',2)
:attr('colspan',entry_colspan)
:css('border-bottom','3px solid black')
:wikitext(entries[j][i]['text'])
end
end
local function isRoundHidden(j,i,headerindex)
if notempty(entries[j][i]['pheader']) then
hide[j][entries[j][i]['headerindex']] = false
end
local row = i+1
repeat
if not isBlankEntry(j,row) then
hide[j][entries[j][i]['headerindex']] = false
end
row = row+1
until (entries[j][row]~=nil and entries[j][row]['ctype']=='header') or row>r
end
local function paramNames(cname,j,i,l)
local rname = {
{'RD'..j, bargs('RD'..j..'-altname') or 'RD'..j},
{'RD'..j..toChar(entries[j][i]['headerindex']),bargs('RD'..j..toChar(entries[j][i]['headerindex'])..'-altname') or 'RD'..j..toChar(entries[j][i]['headerindex'])}
}
local name = {cname, bargs(cname..'-altname') or cname}
local index = {entries[j][i]['index'], entries[j][i]['altindex']}
local result = {}
if cname=='header' then
if entries[j][i]['headerindex']==1 then
for k=1,2 do
table.insert(result,bargs(rname[1][3-k]) or '')
table.insert(result,bargs(rname[2][3-k]) or '')
end
else
for k=1,2 do
table.insert(result,bargs(rname[2][3-k]) or '')
end
end
elseif cname=='pheader' then
if entries[j][i]['headerindex']==1 then
for k=1,2 do
table.insert(result,pargs[rname[1][3-k]] or '')
table.insert(result,pargs[rname[2][3-k]] or '')
end
else
for k=1,2 do
table.insert(result,pargs[rname[2][3-k]] or '')
end
end
elseif cname=='score' then
for m=1,2 do for k=1,2 do
if l==1 then
table.insert(result,bargs(rname[3-m][3-k]..'-'..name[1]..index[3-m]) or bargs(rname[3-m][3-k]..'-'..name[1]..'0'..index[3-m]) or '')
end
table.insert(result,bargs(rname[3-m][3-k]..'-'..name[1]..index[3-m]..'-'..l) or bargs(rname[3-m][3-k]..'-'..name[1]..'0'..index[3-m]..'-'..l) or '')
end end
elseif cname=='shade' then
for k=1,2 do
if entries[j][i]['headerindex']==1 then
table.insert(result,bargs(rname[1][3-k]..'-'..name[1]) or '')
else
table.insert(result,bargs(rname[2][3-k]..'-'..name[1]) or '')
end
end
table.insert(result,bargs('RD-shade'))
table.insert(result,'#F2F2F2')
elseif cname=='text' then
for n=1,2 do for m=1,2 do for k=1,2 do
table.insert(result,bargs(rname[3-m][3-k]..'-'..name[3-n]..index[3-m]) or bargs(rname[3-m][3-k]..'-'..name[3-n]..'0'..index[3-m]) or '')
end end end
else
for m=1,2 do for k=1,2 do
table.insert(result,bargs(rname[3-m][3-k]..'-'..name[1]..index[3-m]) or bargs(rname[3-m][3-k]..'-'..name[1]..'0'..index[3-m]) or '')
end end
end
for k=1,#result do
if notempty(result[k]) then
return result[k]
end
end
return ''
end
local function indexedParams(j)
for i=1,r do
if entries[j][i]~=nil then
if entries[j][i]['ctype']=='team' then
local legs = rlegs[j]
if forceseeds then
entries[j][i]['seed'] = bargs(masterindex) or ''
masterindex = masterindex+1
end
entries[j][i]['team'] = bargs(tostring(masterindex)) or ''
masterindex = masterindex+1
entries[j][i]['legs'] = paramNames('legs',j,i)
entries[j][i]['score'] = {}
entries[j][i]['weight'] = 'normal'
entries[j][i]['score']['weight'] = {}
if notempty(entries[j][i]['legs']) then
legs = tonumber(entries[j][i]['legs'])
end
for l=1,legs do
entries[j][i]['score'][l] = bargs(tostring(masterindex)) or ''
masterindex = masterindex+1
entries[j][i]['score']['weight'][l] = 'normal'
end
if aggregate and legs>1 then
entries[j][i]['score']['agg'] = bargs(masterindex) or ''
masterindex = masterindex+1
entries[j][i]['score']['weight']['agg'] = 'normal'
end
end
if entries[j][i]['ctype']=='header' then
entries[j][i]['header'] = paramNames('header',j,i)
entries[j][i]['pheader'] = paramNames('pheader',j,i)
entries[j][i]['shade'] = paramNames('shade',j,i)
end
if entries[j][i]['ctype']=='text' then
entries[j][i]['text'] = bargs(tostring(masterindex)) or ''
masterindex = masterindex+1
end
if entries[j][i]['ctype']=='group' then
entries[j][i]['group'] = bargs(tostring(masterindex)) or ''
masterindex = masterindex+1
end
if entries[j][i]['ctype'] == 'line' and entries[j][i]['hastext']==true then
entries[j][i]['text'] = bargs(masterindex) or ''
masterindex = masterindex+1
end
end
end
end
local function assignParams()
masterindex = 1
local maxcol = 1
local byerows = 1
local hiderows = 1
for j=minc,c do
rlegs[j] = tonumber(bargs('RD'..j..'-legs')) or tonumber(bargs('legs')) or 1
if notempty(bargs('RD'..j..'-legs')) or bargs('legs') then autolegs = false end
if paramstyle == 'numbered' then
indexedParams(j)
else
for i=1,r do
if entries[j][i]~=nil then
if entries[j][i]['ctype']=='team' then
local legs = rlegs[j]
entries[j][i]['seed'] = paramNames('seed',j,i)
entries[j][i]['team'] = paramNames('team',j,i)
entries[j][i]['legs'] = paramNames('legs',j,i)
entries[j][i]['score'] = {}
entries[j][i]['weight'] = 'normal'
entries[j][i]['score']['weight'] = {}
if notempty(entries[j][i]['legs']) then
legs = tonumber(entries[j][i]['legs'])
end
if autolegs then
local l=1
repeat
entries[j][i]['score'][l] = paramNames('score',j,i,l)
entries[j][i]['score']['weight'][l] = 'normal'
l=l+1
until isempty(paramNames('score',j,i,l))
legs = l-1
else
for l=1,legs do
entries[j][i]['score'][l] = paramNames('score',j,i,l)
entries[j][i]['score']['weight'][l] = 'normal'
end
end
if aggregate and legs>1 then
entries[j][i]['score']['agg'] = paramNames('score',j,i,'agg')
entries[j][i]['score']['weight']['agg'] = 'normal'
end
end
if entries[j][i]['ctype']=='header' then
entries[j][i]['header'] = paramNames('header',j,i)
entries[j][i]['pheader'] = paramNames('pheader',j,i)
entries[j][i]['shade'] = paramNames('shade',j,i)
end
if entries[j][i]['ctype']=='text' then
entries[j][i]['text'] = paramNames('text',j,i)
end
if entries[j][i]['ctype']=='group' then
entries[j][i]['group'] = paramNames('group',j,i)
end
if entries[j][i]['ctype'] == 'line' and entries[j][i]['hastext']==true then
entries[j][i]['text'] = paramNames('text',j,i)
end
end
if autocol and not isBlankEntry(j,i) then
maxcol = math.max(maxcol,j)
end
end
end
for i=1,r do
if entries[j][i]~=nil and entries[j][i]['ctype']=='header' then
isRoundHidden(j,i)
end
if entries[j][i]~=nil and not hide[j][entries[j][i]['headerindex']] then
if not byes[j][entries[j][i]['headerindex']] or (byes[j][entries[j][i]['headerindex']] and not isBlankEntry(j,i)) then
byerows = math.max(byerows,i)
end
end
end
end
for j=minc,c do
for k=1,headerindex[j] do
if byes[j][k] or hide[j][k] then
r=byerows+1
end
end
end
if autocol then
c = maxcol
end
end
local function getHide(j,headerindex)
hide[j] = {}
for k=1,headerindex[j] do
if bargs('RD'..j..toChar(k)..'-hide')=='yes' or bargs('RD'..j..toChar(k)..'-hide')=='y' then
hide[j][k]=true
end
end
end
local function getByes(j,headerindex)
byes[j] = {}
for k=1,headerindex[j] do
if bargs('byes')=='yes' or bargs('byes')=='y' then
byes[j][k]=true else byes[j][k]=false
end
if bargs('RD'..j..'-byes')=='yes' or bargs('RD'..j..'-byes')=='y' then
byes[j][k]=true
elseif bargs('RD'..j..'-byes')=='no' or bargs('RD'..j..'-byes')=='n' then
byes[j][k]=false
end
if bargs('RD'..j..toChar(k)..'-byes')=='yes' or bargs('RD'..j..toChar(k)..'-byes')=='y' then
byes[j][k]=true
elseif bargs('RD'..j..'-byes')=='no' or bargs('RD'..j..'-byes')=='n' then
byes[j][k]=false
end
end
end
local function getAltIndices()
local teamindex=1
local textindex=1
local groupindex=1
for j=minc,c do
headerindex[j]=0
for i=1,r do
if entries[j][i]==nil and i==1 then
headerindex[j]=headerindex[j]+1
end
if entries[j][i]~=nil then
if entries[j][i]['ctype'] == 'header' then
entries[j][i]['altindex'] = headerindex[j]
teamindex=1
textindex=1
headerindex[j]=headerindex[j]+1
elseif entries[j][i]['ctype'] == 'team' then
entries[j][i]['altindex'] = teamindex
teamindex=teamindex+1
elseif entries[j][i]['ctype'] == 'text' then
entries[j][i]['altindex'] = textindex
textindex=textindex+1
elseif entries[j][i]['ctype'] == 'group' then
entries[j][i]['altindex'] = groupindex
groupindex=groupindex+1
elseif entries[j][i]['ctype'] == 'line' and entries[j][i]['hastext']==true then
entries[j][i]['altindex'] = textindex
textindex=textindex+1
end
entries[j][i]['headerindex'] = headerindex[j]
end
end
getByes(j,headerindex)
getHide(j,headerindex)
end
end
local function pathCell(tbl,j,i,k,a,b,c,bg,rowspan)
if not hascross[j] and k==2 then return end
if pathcolor==nil then pathcolor='black' end
local cell=tbl:tag('td')
if rowspan~=1 then
cell:attr('rowspan',rowspan)
end
if notempty(bg[k]) then
cell:css('background',bg[k])
end
if a[k][1]~=0 or a[k][2]~=0 or a[k][3]~=0 or a[k][4]~=0 then
cell:css('border','solid black')
:css('border-width',a[k][1]..'px '..a[k][2]..'px '..a[k][3]..'px '..a[k][4]..'px')
end
if c[k][2]~=0 then
cell:css('border-right',c[k][2]..'px solid '..pathcolor)
end
if b[k][2]~=0 then
cell:css('box-shadow', '0px '..b[k][2]..'px 0px '..pathcolor)
end
if c[k][4]~=0 then
cell:css('border-left',c[k][4]..'px solid '..pathcolor)
end
return cell
end
local function insertPath(tbl,j,i)
local function createCross(angle,color,percent)
local result = 'linear-gradient('..angle..', transparent calc('..percent..'% - 1px),'..color..' calc('..percent..'% - 1px),'..color..' calc('..percent..'% + 1px), transparent calc('..percent..'% + 1px))'
return result
end
local colspan = 2
if hascross[j] then colspan = 3 end
if pathEntries['black'][j][i]=='blank' then return end
if isempty(pathEntries['black'][j][i]) and isempty(pathEntries['color'][j][i]) then
if (pathEntries['black'][j][i-1]~='blank' and notempty(pathEntries['black'][j][i-1]) and (pathEntries['black'][j][i-1]['cross'][1] == 1 or pathEntries['black'][j][i-1]['cross'][2] == 1)) or (notempty(pathEntries['color'][j][i-1]) and (pathEntries['color'][j][i-1]['cross'][1] == 1 or pathEntries['color'][j][i-1]['cross'][2] == 1)) then
return
else
local cell = tbl:tag('td')
for k=1,colspan-1 do
cell:tag('td')
end
return cell
end
end
for key,_ in pairs(pathEntries) do
if notempty(pathEntries[key][j]) and notempty(pathEntries[key][j][i-1]) and pathEntries[key][j][i-1]~='blank' then
if pathEntries[key][j][i-1]['cross'][1]==1 or pathEntries[key][j][i-1]['cross'][2]==1 then return end
end
end
local a = {}
local b = {}
local c = {}
local bg = {}
local cross = {}
local rowspan = 1
for k=1,3 do
a[k]={0,0,0,0}
b[k]={0,0}
c[k]={0,0,0,0}
bg[k] = ''
cross[k] = {}
for n=1,2 do
cross[k][n] = ''
end
end
for key,_ in pairs(pathEntries) do
local color
if key=='black' then color='black' else color=pathcolor end
if notempty(pathEntries[key][j]) and notempty(pathEntries[key][j][i]) then
if key=='black' then
a[1][3] = 3*pathEntries['black'][j][i]['out'][1]
a[2][3] = 3*pathEntries['black'][j][i]['mid'][1]
a[3][3] = 3*pathEntries['black'][j][i]['in'][1]
a[1][2] = 2*pathEntries['black'][j][i]['out'][2]
a[3][4] = 2*pathEntries['black'][j][i]['in'][2]
else
if pathEntries['color'][j][i]['out'][3]=='below' then
b[1][2] = 4*pathEntries['color'][j][i]['out'][1]
end
if pathEntries['color'][j][i]['mid'][3]=='below' then
b[2][2] = 4*pathEntries['color'][j][i]['mid'][1]
end
if pathEntries['color'][j][i]['in'][3]=='below' then
b[3][2] = 4*pathEntries['color'][j][i]['in'][1]
end
c[1][2] = 2*pathEntries['color'][j][i]['out'][2]
c[3][4] = 2*pathEntries['color'][j][i]['in'][2]
end
if pathEntries[key][j][i]['cross'][1]==1 or pathEntries[key][j][i]['cross'][2]==1 then
rowspan = 2
if pathEntries[key][j][i]['cross'][1]==1 then
cross[1][1] = createCross('65.5deg',color,100)
cross[2][1] = createCross('to top right',color,50)
cross[3][1] = createCross('245.5deg',color,100)
end
if pathEntries[key][j][i]['cross'][2]==1 then
cross[1][2] = createCross('114.4deg',color,100)
cross[2][2] = createCross('to bottom right',color,50)
cross[3][2] = createCross('294.4deg',color,100)
end
end
end
end
if notempty(pathEntries['color'][j]) and notempty(pathEntries['color'][j][i-1]) then
if pathEntries['color'][j][i]['out'][3]=='above' then
b[1][2] = -4
end
if pathEntries['color'][j][i]['mid'][3]=='above' then
b[2][2] = -4
end
if pathEntries['color'][j][i]['in'][3]=='above' then
b[3][2] = -4
end
end
if notempty(cross[2][1]) and notempty(cross[2][2]) then
for k=1,3 do cross[k][1] = cross[k][1]..',' end
end
for k=1,3 do
bg[k] = cross[k][1]..cross[k][2]
end
for k=1,3 do
pathCell(tbl,j,i,k,a,b,c,bg,rowspan)
end
end
local function getPaths()
local paths = {}
pathEntries['black'] = {}
pathEntries['color'] = {}
for j=minc,c-1 do
pathEntries['black'][j] = {}
pathEntries['color'][j] = {}
for i=1,r do
pathEntries['black'][j][i] = {['out']={0,0,nil},['mid']={0,0,nil},['in']={0,0,nil},['cross']={0,0,nil}}
pathEntries['color'][j][i] = {['out']={0,0,nil},['mid']={0,0,nil},['in']={0,0,nil},['cross']={0,0,nil}}
end
paths[j] = {}
hascross[j] = false
local crossloc = split((fargs['col'..j..'-col'..(j+1)..'-cross'] or ''):gsub("%s+", ""),{","},true)
local str = fargs['col'..j..'-col'..(j+1)..'-paths'] or ''
for val in str:gsub("%s+","")
:gsub(",",", ")
:gsub("%S+","\0%0\0")
:gsub("%b()", function(s) return s:gsub("%z","") end)
:gmatch("%z(.-)%z") do
local result = split(val:gsub("%s+",""):gsub("%)",""):gsub("%(",""),{"-"})
for k,_ in pairs(result) do
result[k] = split(result[k],{","})
end
if notempty(result[2]) then
for m=1,#result[2] do
result[3] = {}
result[2][m] = split(result[2][m],{":"})
result[3][m] = result[2][m][2]
result[2][m] = result[2][m][1]
end
for n=1,#result[1] do
for m=1,#result[2] do
table.insert(paths[j],{tonumber(result[1][n]),tonumber(result[2][m]),['color']=result[3][m]})
end
end
end
end
if shift[j]~=0 and notempty(crossloc[1]) then
for n=1,#crossloc do
crossloc[n] = crossloc[n]+shift[j]
end
end
for k,v in ipairs(paths[j]) do
local color
local hidepath = false
if shift[j]~=0 then
paths[j][k][1] = paths[j][k][1]+shift[j]
end
if shift[j+1]~=0 then
paths[j][k][2] = paths[j][k][2]+shift[j+1]
end
local start = 2*paths[j][k][1]+(teams_per_match[j]-2)
local mid = {}
if notempty(crossloc[1]) then
for n=1,#crossloc do
mid[n] = 2*crossloc[n]+(teams_per_match[j]-2)
end
else
mid[1]=0
end
local stop = 2*paths[j][k][2]+(teams_per_match[j+1]-2)
if paths[j][k]['color']~=nil then
pathcolor = paths[j][k]['color']
color = 'color'
else
color = 'black'
end
if notempty(entries[j][start-1]) and (byes[j][entries[j][start-1]['headerindex']] and isBlankEntry(j,start-1) and isBlankEntry(j,start+1) or hide[j][entries[j][start-1]['headerindex']]) then
if bargs('show-bye-paths')~='y' and bargs('show-bye-paths')~='yes' then
hidepath=true
end
end
if notempty(entries[j+1][stop-1]) and (byes[j+1][entries[j+1][stop-1]['headerindex']] and isBlankEntry(j+1,stop-1) and isBlankEntry(j+1,stop+1) or hide[j+1][entries[j+1][stop-1]['headerindex']])then
if bargs('show-bye-paths')~='y' and bargs('show-bye-paths')~='yes' then
hidepath=true
end
end
if bargs('RD'..j..'-RD'..(j+1)..'-path')=='n' or bargs('RD'..j..'-RD'..(j+1)..'-path')=='no' or bargs('RD'..j..'-RD'..(j+1)..'-path')=='0' then
if notempty(entries[j][start-1]) and entries[j][start-1]['headerindex']==1 then
hidepath=true
end
end
local cross = 0
for n=1,#mid do
if (start<stop and mid[n]<stop and mid[n]>start) or (start>stop and mid[n]>stop and mid[n]<start) then
hascross[j]=true
cross = mid[n]
end
end
if not hidepath then
if start==stop then
if start>r then break end
start = math.min(start,r)
pathEntries[color][j][start]['out'][1] = 1
pathEntries[color][j][start]['in'][1] = 1
if cross==0 then
pathEntries[color][j][start]['mid'][1] = 1
end
elseif start<stop then
if stop>r then break end
pathEntries[color][j][start]['out'][1] = 1
pathEntries[color][j][stop]['in'][1] = 1
pathEntries[color][j][stop]['in'][2] = 1
if color~='black' then
pathEntries[color][j][start]['out'][3] = 'below'
pathEntries[color][j][stop+1]['in'][3] = 'above'
end
if cross==0 then
pathEntries[color][j][start]['mid'][1] = 1
pathEntries[color][j][start]['mid'][3] = 'below'
for i=start+1,stop-1 do pathEntries[color][j][i]['in'][2] = 1 end
else
pathEntries[color][j][cross]['cross'][1] = 1
for i=start+1,cross-1 do pathEntries[color][j][i]['out'][2] = 1 end
for i=cross+2,stop-1 do pathEntries[color][j][i]['in'][2] = 1 end
end
elseif start>stop then
if start>r then break end
pathEntries[color][j][stop]['in'][1] = 1
if cross==0 then
for i=stop+1,start-1 do pathEntries[color][j][i]['in'][2] = 1 end
pathEntries[color][j][start]['out'][1] = 1
pathEntries[color][j][start]['mid'][1] = 1
pathEntries[color][j][start]['in'][2] = 1
if color~='black' then
pathEntries[color][j][start+1]['out'][3] = 'above'
pathEntries[color][j][start+1]['mid'][3] = 'above'
pathEntries[color][j][stop]['in'][3] = 'below'
end
else
pathEntries[color][j][cross]['cross'][2] = 1
for i=stop+1,cross-1 do pathEntries[color][j][i]['in'][2] = 1 end
for i=cross+2,start-1 do pathEntries[color][j][i]['out'][2] = 1 end
pathEntries[color][j][start]['out'][1] = 1
pathEntries[color][j][start]['out'][2] = 1
if color~='black' then
pathEntries[color][j][start+1]['out'][3] = 'above'
pathEntries[color][j][stop]['in'][3] = 'below'
end
end
end
end
end
end
for j=minc,c-1 do
for i=1,r do
local empty = true
for key,_ in pairs(pathEntries) do
for k,v in pairs(pathEntries[key][j][i]) do
if v[1]~=0 or v[2]~=0 or v[3]~=nil then
empty = false
end
end
if empty then pathEntries[key][j][i] = nil end
end
end
end
for j=minc,c-1 do
if notempty(pathEntries['black'][j]) and notempty(pathEntries['black'][j+1]) then
for i=1,r do
if (entries[j+1][i-1]==nil or (byes[j+1][entries[j+1][i-1]['headerindex']]) and isBlankEntry(j+1,i-1)) and notempty(pathEntries['black'][j][i]) and notempty(pathEntries['black'][j+1][i]) then
if pathEntries['black'][j][i]['in'][1]==1 and pathEntries['black'][j+1][i]['out'][1]==1 then
entries[j+1][i-1]={['ctype']='line'}
entries[j+1][i]={['ctype']='blank'}
end
end
end
end
end
end
local function getGroups()
local function check(j,i)
local result=false
if entries[j][i] == nil then
if entries[j][i+1] == nil then
result=true
elseif entries[j][i+1]['ctype']=='text' and isBlankEntry(j,i+1) then
result=true
end
elseif entries[j][i]['ctype']=='text' and isBlankEntry(j,i) then
result=true
end
return result
end
for j=minc,c-1 do
if teams_per_match[j]==2 then
local n=0
for i=1,r do
if (notempty(pathEntries['black'][j][i]) and pathEntries['black'][j][i]['in'][1] == 1) or (notempty(pathEntries['color'][j][i]) and pathEntries['color'][j][i]['in'][1] == 1) then
n=n+1
if check(j,i) then
local k=minc-1
repeat
if entries[j-k][i+1]~=nil and entries[j-k][i+1]['ctype']=='text' and isBlankEntry(j-k,i+1) then
entries[j-k][i+2]=nil
end
entries[j-k][i]={['ctype']='blank'}
entries[j-k][i+1]={['ctype']='blank'}
if k>0 and isempty(pathEntries['black'][j-k][i]) and isempty(pathEntries['color'][j-k][i]) then
pathEntries['black'][j-k][i] = 'blank'
pathEntries['black'][j-k][i+1] = 'blank'
end
k=k+1
until k>j-1 or not check(j-k,i) or notempty(pathEntries['black'][j-k][i]) or notempty(pathEntries['color'][j-k][i])
k=k-1
entries[j-k][i]={['ctype']='group',['index']=n,['colspan']=k+1}
entries[j-k][i+1]={['ctype']='blank'}
entries[j-k][i]['group'] = bargs('RD'..j..'-group'..n)
end
end
end
end
end
end
local function getCells()
local maxrow = 1
local colentry = {}
local bool = true
for j=minc,c do
if notempty(fargs['col'..j..'-headers']) then bool=false end
teams_per_match[j] = tonumber(fargs['RD'..j..'-teams-per-match']) or tonumber(fargs['col'..j..'-teams-per-match']) or tonumber(fargs['teams-per-match']) or 2
maxtpm = math.max(maxtpm,teams_per_match[j])
end
for j=minc,c do
entries[j] = {}
shift[j] = tonumber(bargs('RD'..j..'-shift')) or tonumber(bargs('shift')) or 0
colentry[j] = {
split((fargs['col'..j..'-headers'] or ''):gsub("%s+", ""),{","},true),
split((fargs['col'..j..'-matches'] or ''):gsub("%s+", ""),{","},true),
split((fargs['col'..j..'-lines'] or ''):gsub("%s+", ""),{","},true),
split((fargs['col'..j..'-text'] or ''):gsub("%s+", ""),{","},true),
}
if bool==true and fargs['noheaders']~='y' and fargs['noheaders']~='yes' then
table.insert(colentry[j][1],1)
end
end
for j=minc,c do
local textindex=0
for k,v in ipairs(colentry[j]) do
table.sort(colentry[j][k])
local ctype
if k==1 then ctype='header'
elseif k==2 then ctype='team'
elseif k==3 then ctype='line'
elseif k==4 then ctype='text'
elseif k==5 then ctype='group'
end
for n=1,#colentry[j][k] do
if shift[j]~=0 and colentry[j][k][n]>1 then
colentry[j][k][n] = colentry[j][k][n]+shift[j]
end
local i=2*colentry[j][k][n]-1
maxrow = math.max(i+2*teams_per_match[j]-1,maxrow)
if ctype=='team' then
if entries[j][i-1]==nil and entries[j][i-2]==nil then
entries[j][i-2]={['ctype']='text',['index']=n}
entries[j][i-1]={['ctype']='blank'}
textindex=n
end
entries[j][i]={['ctype']=ctype,['index']=teams_per_match[j]*n-(teams_per_match[j]-1),['position']='top'}
entries[j][i+1]={['ctype']='blank'}
for m=2,teams_per_match[j] do
entries[j][i+2*(m-1)]={['ctype']=ctype,['index']=teams_per_match[j]*n-(teams_per_match[j]-m)}
entries[j][i+2*(m-1)+1]={['ctype']='blank'}
end
elseif ctype=='text' then
entries[j][i]={['ctype']=ctype,['index']=textindex+n}
entries[j][i+1]={['ctype']='blank'}
elseif ctype=='line' then
entries[j][i]={['ctype']=ctype}
entries[j][i+1]={['ctype']='blank'}
elseif ctype=='group' then
entries[j][i]={['ctype']=ctype,['index']=n}
entries[j][i+1]={['ctype']='blank'}
else
entries[j][i]={['ctype']=ctype,['index']=n,['position']='top'}
entries[j][i+1]={['ctype']='blank'}
end
end
end
end
if isempty(r) then
r = maxrow
end
end
function p.main(frame)
fargs = frame.args
pargs = frame:getParent().args;
r = tonumber(fargs.rows) or ''
c = tonumber(fargs.rounds) or 1
maxc = tonumber(pargs.maxrounds) or tonumber(pargs.maxround) or ''
minc = tonumber(pargs.minround) or 1
headerindex = {}
if notempty(maxc) then c=maxc end
if fargs.autocol=='yes' or fargs.autocol=='y' then autocol=true end
local colspacing = tonumber(fargs['col-spacing']) or 5
maxtpm = 1
seeds = true
forceseeds = false
boldwinner = bargs('boldwinner') or ''
if bargs('seeds')=='y' or bargs('seeds')=='yes' then forceseeds=true end
if bargs('seeds')=='n' or bargs('seeds')=='no' then seeds=false end
if bargs('aggregate')=='y' or bargs('aggregate')=='yes' then aggregate=true end
if bargs('autolegs')=='y' or bargs('autolegs')=='yes' then autolegs=true end
if bargs('nowrap')=='y' or bargs('nowrap')=='yes' then nowrap=true end
if bargs('paramstyle')=='numbered' then
paramstyle = 'numbered'
else
paramstyle = 'indexed'
end
getCells()
getAltIndices()
assignParams()
matchGroups()
if (boldwinner=='yes' or boldwinner=='y' or boldwinner=='high') then boldWinner() end
getPaths()
if minc==1 then
getGroups()
end
for j=minc,c do
maxlegs[j] = rlegs[j]
for i=1,r do
if notempty(entries[j][i]) then
if notempty(entries[j][i]['legs']) then
maxlegs[j] = math.max(rlegs[j],entries[j][i]['legs'])
end
if autolegs then
local l=1
repeat l=l+1
until isempty(entries[j][i]['score']) or isempty(entries[j][i]['score'][l])
maxlegs[j] = math.max(maxlegs[j],l-1)
end
end
end
end
local tbl = mw.html.create('table')
:attr('cellpadding','0')
:attr('cellspacing','0')
:css('font-size','90%')
:css('border-collapse','collapse')
:css('margin','1em 2em 1em 1em')
tbl:tag('tr'):css('visibility','collapse')
tbl:tag('td'):css('width','1px')
for j=minc,c do
if seeds then
tbl:tag('td'):css('width',getWidth('seed','25px'))
end
tbl:tag('td'):css('width',getWidth('team','150px'))
if maxlegs[j]==0 then
tbl:tag('td'):css('width',getWidth('score','25px'))
else
for l=1,maxlegs[j] do
tbl:tag('td'):css('width',getWidth('score','25px'))
end
end
if aggregate and maxlegs[j]>1 then
tbl:tag('td'):css('width',getWidth('score','25px'))
end
if j~=c then
if hascross[j] then
tbl:tag('td'):css('width',colspacing..'px')
tbl:tag('td'):css('width','10px')
tbl:tag('td'):css('width',colspacing..'px')
else
tbl:tag('td'):css('width',colspacing..'px')
tbl:tag('td'):css('width',colspacing..'px')
end
end
end
for i=1,r do
local row = tbl:tag('tr')
row:tag('td'):css('height','11px')
for j=minc,c do
insertEntry(row,j,i)
if j~=c then
insertPath(row,j,i)
end
end
end
return tostring(tbl)
end
return p