Usage
This is a module for meta templates such as {{Round16}}, {{Round8}}, etc. Though originally designed for sports, it can be used to present any tree.
Basic form:
{{#invoke:RoundN|main|columns = [number of columns (3 columns for Round8, 4 columns for Round16, etc.)]}}
Alternatively, the shorthand form {{#invoke:RoundN|N16}}
(or any power of 2 between N2 and N512) is equivalent to Template:Code
Parameter List
parameter names are required.
Node FunctionsFor greater customization, node functions may be added to the parameter usually used for providing match information such as dates and location. Simply insert
For the next three node functions, the parameters usually used for team names and scores should be omitted if these node functions are used.
Undocumented featuresAlas User:Codehydro seriously overbuilt this module and never found the time to document even half the features available. Check out the following examples which may contain some advanced features: Partial List of undocumented params
Note that some of these features may not have been documented due to incomplete implementation. |
local p = { RD = { 'Quarter-finals', 'Semi-finals', 'Final', 'Third place' }, bgColor = {head = '#f2f2f2', 'gold', 'silver', '#C96', '#f9f9f9'}, reuseStr = {}, saveStr = function(self, name, ...) if not self.reuseStr[name] then self.reuseStr[name] = table.concat{...} end return self.reuseStr[name] end } --Provides a convenient naming shortcut up to {{#invoke:RoundN|N512}} = {{invoke:RoundN|main|columns = 9}} for columns = 1, 9 do local N = math.pow(2, columns) p['N' .. N] = function(frame) return p.main(frame.args, columns) end p['n' .. N] = p['N' .. N]--to make case insensitive end --saves memory and avoids errors when using a nil as a table by providing a temporary table; if using nil as false; use 'table(k)' to look up table[k] p.nilAsTab = { __index = function(t, i) return setmetatable({}, setmetatable(p.nilAsTab, {__index = {t = t, i = i}})) end, __newindex = function (pt, pi, v) --store new values in actual table rather than temporary rawset(p.nilAsTab.t, p.nilAsTab.i, {})[p.nilAsTab.i][pi] = v setmetatable(p.nilAsTab.t[p.nilAsTab.i], {__call = p.nilAsTab.__call}) end, __call = function(t, i) return t and rawget(t, i) end } --never assign a value to these or they will stop being empty local infiniteEmpty = setmetatable({}, {__index = setmetatable({}, p.nilAsTab), p.nilAsTab}) -- infiniteEmpty[1][2][3]...[infinity] = {} local callableEmpty = setmetatable({}, p.nilAsTab) local rowNum, head, m, col, tab, esc = {}, {}, {num = 1, phase = 0, bold = infiniteEmpty}, {}, mw.html.create'table', { bs = require'Module:Escape',--backslash comma = {['(%([^,]*),([^%)]*%))'] = '%1|@!#|%2'},--escape commas in () } local nodeFunc = { scanPattern = function(self, args, step) self.pattern = nil if args[step] then self.pattern, self.nonFunc = string.match(esc.bs:text(args[step]), '^node_function{(.-)}(.*)') end if self.pattern then for k, v in pairs(esc.comma) do self.pattern = self.pattern:gsub(k, v) end self.nonFunc = self.nonFunc and esc.bs:undo(self.nonFunc) self.pattern = mw.text.split(self.pattern, '%s*,%s*') for k, v in ipairs(self.pattern) do local func, arg = string.match(v, '^(%w+)%(?([^%)]*)') if func and self[func] and self[func].main then self.pattern[k] = func if arg then for x, y in pairs(esc.comma) do arg = esc.bs:undo(arg):gsub(y:gsub('%%%d', ''), x:match('%)([^%(])%(') or x:gsub('\\', '')) end self[func].arg = self[func].arg or {} self[func].arg[m.num] = arg end end end end return self.pattern end, helper = { topBranch = function()--node is top of fork if top is 0 return (m.num - col.top) % 2 end, addText = function(text) if text and text ~= '' then tab.r:wikitext(text) end end }, line = {--this node is omitted and replaced with a line main = function(x) local h = p.getNodeFunc() if m.available then local text, topId, isTop, notTop = h.line.arg[m.num] or '', h.topBranch() isTop = topId == 0 notTop = {[isTop and 1 or 0] = p.reuseStr.solid} for k = 0, 1 do tab.r = rowNum[m.r + k * 4]:tag'td' :css(notTop[k] and {[isTop and 'border-top' or 'border-bottom'] = notTop[k]} or {} ) :attr{ rowspan = ({[0] = 4, 2})[k], colspan = p.colspan } h.addText(text or h.nonFunc) text = nil end m.available = false else return nil end return x end }, bridge = {--Draw a line to the neighboring node in the same column that is not connected to the current node main = function(x) local h = p.getNodeFunc() h.bridge.lay[col.c][m.num - col.top + 1 + (h.topBranch() == 1 and 1 or -1)] = true h.addText(nonFunc) return x end, lay = setmetatable({}, p.nilAsTab) }, canvas = {--Merges all cells in node. Content will be the next parameter. main = function(x) local h = p.getNodeFunc() if m.available then tab.r = rowNum[m.r]:tag'td' :attr{ rowspan = 6, colspan = p.colspan } h.addText(h.nonFunc) m.available = false return x else return nil end end }, orphan = {--sets a flag for skipMatch to be set by p._main main = function(x) p.getNodeFunc().orphan.num = m.num return x end }, skipAllowed = {--table of supported node functions when node is skipped (i.e. by skipmatch) bridge = true, canvas = true } } setmetatable(nodeFunc.helper, {__index = nodeFunc}) function p.getNodeFunc() return nodeFunc.helper end local function newRow(bodyRow) local first = p.flex_tree.merge and mw.clone(p.flex_tree.cell) or p.flex_tree.cell tab.r = tab:tag'tr' :node(first) if bodyRow then table.insert(rowNum, bodyRow, tab.r) if p.flex_tree.merge then rowNum[bodyRow].first = first rowNum[bodyRow].first.unchanged = true end end end local function drawHead(text, row3rd) local td = (row3rd and rowNum[row3rd]:tag'td':attr{rowspan = 2} or head.row:tag'td') :attr{colspan = p.colspan} if text ~= 'omit_label' then td:wikitext(text):css{ ['text-align'] = 'center', border = '1px solid #aaa', background = p.bgColor.head } end end local function spacer(width) tab.r:tag'td' :attr{width = width} :wikitext(p.no_column_head and '' or ' ') end local function dpBox(v, r) p.dpBoxBase = p.dpBoxBase or mw.html.create'td':attr{rowspan = 2, colspan = p.colspan} if not v then p.dpBoxEmpty = p.previewnumbers and mw.clone(p.dpBoxBase) or p.dpBoxEmpty or mw.clone(p.dpBoxBase):wikitext(p.flex_tree.wt) rowNum[r]:node(p.dpBoxEmpty) else rowNum[r]:node(mw.clone(p.dpBoxBase):wikitext(v)) end end p.scoreWasher = { numberFormat = '%-?%d+%.?%d*', main = function (self, s) if s then for _, cycle in ipairs(self.cycles) do s = s:gsub(unpack(cycle)) end if p.scoreSumBox and self.plus then local t = 0 for _, part in ipairs(mw.text.split(s, self.plus)) do t = t + (tonumber(part:match('%-?%d+%.?%d*')) or 0) end return t end return tonumber(s:match(self.numberFormat)) or math.huge end return 0 end, spin = function(self, v) table.insert(self, v) return self end, load = function (self, cycle) local wash, rinse = 0, {spin = self.spin} for v in cycle:gfind('%(([^%(%)]-)%)') do if v == '_plus_' then self.plus = v rinse:spin(v) cycle = cycle:gsub('%(_plus_%)', '', 1) else wash = wash + 1 rinse:spin('%'):spin(wash) end end table.insert(self.cycles, {esc.bs:undo(cycle, '%%'), table.concat(rinse)}) end, init = function(self, setting) self.cycles = {original = setting} for cycle in (setting and esc.bs:text(setting) or '{<.->} {[^%d]*}'):gfind('{(.-)}') do self:load(cycle) end end, sum = function (clean) local sum = {0, 0} for _, box in ipairs(clean) do for team, score in ipairs(box) do sum[team] = sum[team] + score end end return unpack(math.max(unpack(sum)) == math.huge and {'—', '—'} or sum) end } local function boldWin(s1, s2) return setmetatable( p.bold and s1 ~= s2 and (math[({'min', 'max'})[p.bold]](s1, s2) == s1 and {true} or {[2] = true}) or callableEmpty, p.nilAsTab ) end local function maxSpan(span, start, rows) return math.min(span, math.max(0, rows - start + 1)) end --in case of templates like RDseed need padding value p.teamBoxPadding = function() return '.6ex' end p.teamBoxPadTab = {padding = '0 ' .. p.teamBoxPadding()} p.teamBoxNormal = {border = '1px solid #aaa', background = p.bgColor[4]} local function teamBox(v, r, f) if p.flex_tree.merge and not v and f.phase == 2 then for i = -2, 0 do if rowNum[r + i].first.unchanged then rowNum[r + i].first.unchanged = nil rowNum[r + i].first:node(p.unflex_div) end end tab.r:attr{rowspan = 4}:css{['vertical-align'] = 'center'} else if not p.bold then --backwards compatability (wikitemplates bold each arg individually) local hasBold, b = tostring(v):gsub("([^']*)'''([^']*)", '%1<b>%2</b>') if b == 1 then v = hasBold end end local cell if f[1] then cell = f.sumBox and f.sumBox[1] and {padding = f.sumBox[1]} or {['border-left'] = f.borderLeft} cell['text-align'] = v and f[1] else cell = p.teamBoxPadTab end tab.r = rowNum[r]:tag'td' :css(p.teamBoxCSS) :css(cell) :attr{rowspan = 2} :node(mw.html.create(f.bold and 'b'):wikitext(v or f[1] and '' or ' ')) end end function p._main(args) function args:clean(key, params)--prevent html comments from breaking named args and reduces repeat concatenation params = params or {} local clean = args[key] or params.ifNil if clean then params.append = params.append or '' clean = mw.text.decode(clean):gsub('<!%-.-%->', ''):gsub(params.pattern or '[^%w-;%.]', '') .. params.append clean = clean ~= params.append and clean or params.ifNil end args[key] = params.keepOld and args[key] or clean return clean end p.cols = tonumber(args:clean('columns', {pattern = '%D'})) p.tCols = (tonumber(args:clean('final_RDs_excluded', {pattern = '%D'})) or 0) + p.cols local matchPer = { pattern = '%d*per%d+[%-x]%d+', vals = '(%d*)per(%d+)([%-x])(%d+)' } local skipMatch, unBold = {}, {}--(skip|manualbold)match# to boolean for k, _ in pairs(args) do local mType, mNum = string.match(k, '^(%l+)match(%d*)$') mType, mNum = ({skip = skipMatch, manualbold = unBold})[mType], tonumber(mNum) if mType then if mNum then mType[mNum] = args:clean(k) == 'yes' or args[k] == 'true' else for pattern in args:clean(k, {ifNil = ''}):gfind(matchPer.pattern) do local d1, period, op, d2 = pattern:match(matchPer.vals) d1 = tonumber(d1) or 1 d2 = op == '-' and d2 or (d1 + period * (d2 - 1)) for y = d1, d2, period do mType[y] = true end end for _, x in ipairs(mw.text.split(args[k]:gsub(matchPer.pattern, ''):gsub('[;%-%a][;%-%a]+', ';'):match('^;*(.-)[;%-]*$'), ';')) do x = mw.text.split(x, '-') for y = tonumber(x[1]) or 1, tonumber(x[2] or x[1]) or 0 do mType[y] = true end end end end end for _, v in ipairs({--more args to boolean 'widescore', 'template', 'article_include', 'color', '3rdplace', 'omit_blanks', 'scroll_head_unlock', 'previewnumbers', 'flex_tree', 'no_column_head', 'short_brackets', 'branch_upwards' }) do if args[v] and (p[v] == nil or type(p[v]) == 'boolean') then p[v] = args:clean(v) == 'yes' or args[v] == 'true' end end p.namespace = mw.title.getCurrentTitle().namespace p.previewnumbers = p.namespace ~= 0 and p.previewnumbers p.scoreWasher:init(args['score-clean']) p.scoreWasher.demo = args.demoWash and tonumber(args:clean('demoWash', {pattern = '%D'}), 10) p.scoreSumBox = args['score-boxes'] and args['score-boxes']:match('%d ?%+ ?sum') p.bold = ({low = 1, high = 2})[args:clean('bold_winner')] or p.scoreSumBox and 2 local sumBox = p.scoreSumBox and 1 or 0 p.scoreBoxes = (tonumber(args:clean('score-boxes', {pattern = '%D'})) or 1) + sumBox p.scoreSumBox = p.scoreBoxes > 0 and p.scoreSumBox or nil local boxStyle = p.scoreBoxes > 1 and (p.scoreSumBox and setmetatable( {{}, [p.scoreBoxes] = {'0 1ex'}}, {__call = function(t, i) if t[i] then return nil end return 0 end} ) or setmetatable( {}, {__call = function() return 0 end} ) ) or setmetatable({}, {__call = function() return nil end}) p.colspan = p.scoreBoxes > 0 and (p.scoreBoxes + 1) or nil local nodeArgs = { score = p.scoreBoxes - sumBox, team = {offset = 1 + p.scoreBoxes - sumBox} } nodeArgs.all = 1 + nodeArgs.team.offset * 2 nodeArgs.tableSum = { __add = function(v, t) if #t == 3 then return v + nodeArgs.all end local s = v for i, n in ipairs(t) do s = s + n end return s--[[ + (p.scoreSumBox and #t == 3 and -2 or 0) --merging disabled with score boxes, uncomment if enable]] end } nodeArgs.team[1] = 1--constant to be replaced later by new param nodeArgs.team[2] = nodeArgs.team[1] + nodeArgs.team.offset nodeArgs.blank = setmetatable({}, nodeArgs.tableSum) p.unflex_div = mw.html.create'div' :css{overflow = 'hidden', height = '1ex'} :wikitext' ' p.flex_tree = setmetatable({},{__index = { merge = p.flex_tree and p.scoreBoxes == 0, wt = p.flex_tree and '' or ' ', cell = mw.html.create'td' :node(not p.flex_tree and p.unflex_div or nil) }}) if args:clean'scroll_height' then local fontSize, fontUnit = args.style and args.style:match('font%-size *: *(%d+)([^ ]+)') if fontSize then local units = { em = 1, ex = 2, ['%'] = 0.01 } fontSize, fontUnit = {fontSize * fontUnit} end end tab :cssText(table.concat{args.scroll_height and 'padding' or 'margin', ':', fontSize and (math.ceil(fontSize * 10) / 10) or '.9', 'em 2em 1em 1em;border:0;', fontSize and '' or 'font-size:90%;border-collapse:separate;', args.style}) :attr{cellpadding = 0, cellspacing = 0} if not p.no_column_head then--headings row newRow() head.row = tab.r :css{['white-space'] = args.scroll_height and 'nowrap'} newRow() else tab.r = tab:tag'tr' tab.r:tag'td' end local sp = {--set column widths args['team-width'] or 170, p.widescore and 40 or 30, p.short_brackets and 6 or 15, p.short_brackets and 4 or 20 } local scoreWidth = args:clean('score-width', {pattern = '[^%d;]'}) and mw.text.split(args['score-width'], ';') or {} scoreWidth[1] = tonumber(scoreWidth[1], 10) if p.scoreSumBox and #scoreWidth ~= 1 then local _scoreWidth = {} for k = 1, p.scoreBoxes - 1 do _scoreWidth[k] = tonumber(scoreWidth[k], 10) or math.ceil(sp[2] * 0.75) end setmetatable(scoreWidth, _scoreWidth) end if p.template or p.article_include then p.template = mw.title.new(args.name) p.templateFixedName = (p.template.namespace == 0 and not p.article_include and 'Template:' or '') .. p.template.fullText end p.template = p.template and mw.title.new(args:clean('name', {pattern = ''})) local head_br = { count = 0, compare = function (self, text) if text and args.scroll_height then local _, count = text:gsub('<br[ >/]', '%1') self.count = math.max(self.count, count) end return text end } p.branch_upwards = p.branch_upwards and 0 for k = 1, p.cols do if k > 1 then spacer(sp[3]) spacer(sp[4]) if not p.no_column_head then head.row:tag'td':attr{colspan = 2} end end spacer(sp[1]) for s = 1, p.scoreBoxes do spacer(#scoreWidth == 1 and scoreWidth[1] or scoreWidth[s] or sp[2]) end if not p.no_column_head then head.wt = head_br:compare(args:clean('RD' .. k, {pattern = ''})) or p.RD[#p.RD + k - p.tCols - 1] or ('Round of ' .. math.pow(2, p.tCols - k + 1)) drawHead( k == 1 and p.template and mw.getCurrentFrame():expandTemplate{ title = 'navbar-header', args = {head.wt, p.templateFixedName} } or head.wt ) end end sp.row = tab.r col.tot = math.pow(2, p.tCols - 1) local step, bump, bumpBase, rows = 1, 0, mw.html.create'td':attr{colspan = p.colspan}, col.tot * 6--Begin body row output args.line_px = table.concat{args:clean('line_px') or 3, args.line_px ~= '0' and 'px' or nil} tab.line = {--reduces concats and 'or' statements { [true] = args.line_px, [false] = 0 }, args.line_px:rep(2):gsub('(%a)(%d)', '%1 %2', 1) } p['3rdplace'] = p.tCols == p.cols and (p['3rdplace'] or p.cols > 3 and nil == p['3rdplace'] and not p.no_column_head) if p['3rdplace'] then p.textThird = args.Consol or args['RD' .. (p.cols + 1)] or p.RD[4] local no3rdText = p.no_column_head or p.textThird and p.textThird:match('omit_label') rowNum.third = math.max(math.pow(2, p.branch_upwards and -3 or p.cols - 2) * 9 + (no3rdText and 4 or 9), no3rdText and 12 or 17, rows) end for r = 1, rowNum.third or rows do newRow(r) end p:saveStr('solid', tab.line[1][true], ' solid') p.cornerDiv = mw.html.create'div':css{height = tab.line[1][true], ['border-right'] = p.reuseStr.solid} for c = 1, p.cols do col.c = c local bumps = bump if c > 1 then col.tot = col.tot + math.pow(2, p.tCols - c) if p.branch_upwards then bumps = 0 rowNum[1]:tag'td':attr{rowspan = 4} else rowNum[1]:node(c < p.cols and mw.clone(bumpBase):attr{rowspan = bump} or p.no_column_head and p.template and mw.html.create'td':wikitext(mw.getCurrentFrame():expandTemplate{ title = 'navbar-header', args = {'', p.templateFixedName} }) ) end end col.top = m.num p.span = p.tCols > c and bump * 2 or p.branch_upwards or math.max((bump - 1) / 2, 2) col.show3rd = p['3rdplace'] and c == p.tCols and rowNum.third local colorFinal, bumpMid = p.color and c == p.tCols, p.span > 0 and mw.clone(bumpBase):attr{rowspan = p.span} or nil for r = 1, col.show3rd or rows, 2 do m.r = r + bumps if col.show3rd or rowNum[m.r] and m.num <= col.tot then if m.phase == 0 then m.showBox = setmetatable({1, nodeArgs.team.offset, nodeArgs.team.offset}, nodeArgs.tableSum) if nodeFunc:scanPattern(args, step) then nodeFunc.called = {} m.available = true else m.available = nil end end if skipMatch[m.num] then if m.phase == 0 then if nodeFunc.pattern then for x, y in ipairs(nodeFunc.pattern) do if nodeFunc.skipAllowed[y] then nodeFunc.called[y] = nodeFunc[y].main(x) end end end local canvas = nodeFunc.pattern and nodeFunc.called.canvas and 6 rowNum[m.r + (canvas or 0)]:tag'td':attr{rowspan = maxSpan((canvas and 0 or 6) + bump * 2, m.r + (canvas or 0), rows), colspan = p.colspan} elseif m.phase == 2 then if nodeFunc.pattern and (nodeFunc.called.bridge or nodeFunc.called.canvas) then step = step + 1 end m.num = m.num + 1 step = step + (p.omit_blanks and 0 or m.showBox) bumps = bumps + (col.show3rd and 0 or maxSpan(p.span, m.r, rows)) end elseif m.phase == 0 then if nodeFunc.pattern then for x, y in ipairs(nodeFunc.pattern) do if nodeFunc[y] and nodeFunc[y].main then nodeFunc.called[y] = nodeFunc[y].main(x) end end if m.available == false then m.showBox = nodeArgs.blank step = step + 1 end end if m.showBox[1] then if col.show3rd then col.show3rd = (m.num - col.top) * 2 if col.show3rd == 2 then if p.textThird:match('omit_label') then p.textThird = nil end if rowNum[rows + 1] and p.cols > 1 then --if 3rd place extends below bottom cell rowNum[rows + 1]:tag'td':attr{ rowspan = m.r + 9 - rows - (text and 0 or 2), colspan = (p.cols - 1) * (3 + p.scoreBoxes) } end if p.tCols == 1 then bumps = p.textThird and 3 or 0 elseif p.branch_upwards then r = 7 bumps = p.textThird and 2 or 0 end m.r = r + bumps if p.textThird then drawHead(p.textThird, m.r) bumps = bumps + 2 m.r = r + bumps end end end dpBox(nodeFunc.pattern and nodeFunc.nonFunc or args[step], m.r) if p.previewnumbers then rowNum[m.r].nodes[#rowNum[m.r].nodes] :tag'div' :css{ float = 'left', border = '1px solid red', padding = '0 .5ex', ['color'] = 'red' } :wikitext(m.num) :attr{title = 'Number only visible outside article space (e.g. template) when |numberpreview=yes'} end end if p.colspan then m.nonEmpty = {} for s = step + 2, step + nodeArgs.team.offset do local i = {s, s + nodeArgs.team.offset} if args[i[1]] or args[i[2]] then table.insert(m.nonEmpty, i) end end if p.bold and m.showBox[2] and m.showBox[3] and not unBold[m.num] then m.bold = { box = {}, clean = {} } local notSummed = not p.scoreSumBox or #m.nonEmpty < 2 for s, i in ipairs(m.nonEmpty) do m.bold.clean[s] = {p.scoreWasher:main(args[i[1]]), p.scoreWasher:main(args[i[2]])} m.bold.box[s] = notSummed and boldWin(m.bold.clean[s][1], m.bold.clean[s][2]) or callableEmpty end if p.scoreSumBox and m.nonEmpty[2] then local i = {-step, -step - 1} table.insert(m.nonEmpty, i) args[i[1]], args[i[2]] = p.scoreWasher.sum(m.bold.clean) m.bold.box[p.scoreBoxes] = boldWin(args[i[1]], args[i[2]]) end getmetatable(boxStyle).__index = p.scoreSumBoxes and {[#m.nonEmpty] = boxStyle[p.scoreBoxes]} m.bold.win = m.bold.box[#m.nonEmpty] or callableEmpty else m.bold = infiniteEmpty end end else if m.showBox[m.phase] then p.teamBoxCSS = colorFinal and {border = p.teamBoxNormal.border, background = p.bgColor[m.phase + (col.show3rd or 0)]} or p.teamBoxNormal local f = {phase = m.phase, bold = m.bold.win(m.phase)} teamBox(args[step + nodeArgs.team[m.phase]], m.r, f) f[1] = 'center' if p.colspan then if m.nonEmpty[1] then local loneSum if #m.nonEmpty < p.scoreBoxes then loneSum = #m.nonEmpty == 1 and boxStyle[p.scoreBoxes] tab.r:attr{colspan = 1 + p.scoreBoxes - #m.nonEmpty} end for s, i in ipairs(m.nonEmpty) do f.borderLeft = boxStyle(s) f.sumBox = loneSum or boxStyle[s] f.bold = m.bold.box[s](m.phase) teamBox(args[i[m.phase]], m.r, f) end else for s = 1, p.scoreBoxes do f.borderLeft = boxStyle(s) teamBox(nil, m.r, f) end end end end if m.phase == 2 then col.show3rd = col.show3rd ~= 2 and col.show3rd or nil if p.scoreWasher.demo and p.scoreWasher.demo == m.num and p.namespace ~= 0 then table.insert(m.bold.clean, 1, {args[step + nodeArgs.team[1]], args[step + nodeArgs.team[2]]}) return table.concat{ 'Score data for match specified by <code>|demoWash=</code>:<br>', mw.dumpObject{scores = m.bold.clean, cycles = p.scoreWasher.cycles, sum = p.scoreSumBox and {m.nonEmpty[#m.nonEmpty][1], m.nonEmpty[#m.nonEmpty][1]}}, '<table>', tostring(sp.row), '<tr>', tostring(rowNum[m.r - 4]), '<tr>', tostring(rowNum[m.r - 2]), '<tr>', tostring(rowNum[m.r]), '</table>', } end if nodeFunc.orphan.num == m.num then skipMatch[m.num] = 'orphan' end step = step + m.showBox m.num = m.num + 1 if bump > 0 and rowNum[m.r + 2] and not (nodeFunc.pattern and nodeFunc.called.canvas) then bumps = bumps + p.span rowNum[m.r + 2]:node(bumpMid) end r = r + (col.show3rd or bump) end end m.phase = (m.phase + 1) % 3 end end if p.cols > c then--draw lines to next round p.unit = bump + 3 bump = 3 * math.pow(2, c) - 3 bumps = p.branch_upwards and 4 or (p.unit + 1) rowNum[1] :tag'td':attr{rowspan = bumps} if not p.branch_upwards then rowNum[1]:tag'td' :attr{rowspan = (p.branch_upwards or bump) + 4} :css(nodeFunc.bridge.lay[c](0) and {['border-right'] = p.reuseStr.solid} or {} ) end col.n = 0 for r = bumps + 1, rows, p.unit * 2 do tab.r = rowNum[r]:tag'td' local interval = ((r - bumps - 1) / (p.unit * 2)) % 4 if interval % 2 == 0 then --col.t and col.t2 control whether lines are drawn col.t = col.t2 or skipMatch[col.tot + col.n / 2 + 1] and 3 or ((skipMatch[col.top] and 1 or 0) + (skipMatch[col.top + 1] and 2 or 0)) col.n = col.n + 2 col.t2 = skipMatch[col.tot + col.n / 2 + 1] and 3 or ((skipMatch[col.top + col.n] and 1 or 0) + (skipMatch[col.top + col.n + 1] and 2 or 0)) if col.t == 0 then tab.r :attr{rowspan = maxSpan(p.unit * 2, r, rows)} :css(skipMatch[col.tot + col.n / 2] and {} or { border = p.reuseStr.solid, ['border-left'] = 0 }) else tab.r :attr{rowspan = maxSpan(p.unit, r, rows)} :cssText(col.t == 2 and p:saveStr('topRight', 'border-width:', tab.line[2], ' 0 0;border-style:solid') or col.t == 1 and (nodeFunc.bridge.lay[c](col.n - 2) and p:saveStr('right', ';border-right:', p.reuseStr.solid) or 'vertical-align:bottom' ) or nil ) :node(col.t == 1 and interval > 0 and not nodeFunc.bridge.lay[c](col.n - 2) and p.cornerDiv) rowNum[r + (p.branch_upwards and (4 - bump) or p.unit)]:tag'td' :attr{rowspan = maxSpan(p.unit, r + p.unit, rows)} :cssText(col.t == 1 and p:saveStr('bttmRght', 'border-width:0 ', tab.line[2], ' 0;border-style:solid') or col.t == 2 and (nodeFunc.bridge.lay[c](col.n + 2) and p:saveStr('right', ';border-right:', p.reuseStr.solid) or 'vertical-align:top' ) or nil ) :node(col.t == 2 and interval ~= 2 and not nodeFunc.bridge.lay[c](col.n + 2) and p.cornerDiv) end col.t = { col.t < 3, rowNum[r + p.unit * 5] and col.t2 < 3 or false } rowNum[r + (p.branch_upwards or p.unit)]:tag'td' :attr{rowspan = maxSpan(p.unit * 4, r + (p.branch_upwards and (4 - bump) or p.unit), rows)} :css(interval == 0 and (col.t[1] or col.t[2]) and { ['border-width'] = table.concat{tab.line[1][col.t[1]], ' 0 ', tab.line[1][col.t[2]]}, ['border-style'] = 'solid' } or {}) else tab.r :attr{rowspan = maxSpan(p.unit * 2, r, rows)} :css(nodeFunc.bridge.lay[c](col.n) and {['border-right'] = p.reuseStr.solid} or {} ) end end end end local lock_height = (head_br.count or 0) + 1 return args.scroll_height and mw.html.create'div' :cssText'border-bottom:1px solid #eee;display:inline-block' :node(not (p.scroll_head_unlock or p.no_column_head) and mw.html.create'div' :css{ overflow = 'hidden', height = lock_height * 1.4 + 1.6 .. 'em', ['border-bottom'] = 'inherit', ['margin-right'] = '17px' } :node(mw.clone(tab)) ) :tag'div' :css{ ['overflow-y'] = 'scroll', ['max-height'] = tonumber(args.scroll_height, 10) and args.scroll_height .. 'px' or args.scroll_height } :node(not (p.scroll_head_unlock or p.no_column_head) and tab:css{['margin-top'] = math.floor(-10 * (lock_height * 1.4 + 1.6)/(fontSize or .9)) / 10 .. 'em', ['padding-top'] = '-3px'} or tab ) :done() or tab end --[[local standard = { 'beta' = { bold_winner = 'high', omit_blanks = 'yes', auto_3rd = 'yes' } }--]] function p.main(frame, columns) local args = require'Module:Arguments'.getArgs(frame, {trim = false}) args.columns = args.columns or columns return p._main(args) end function p.seed(frame) local parent = frame:getParent() or frame local function arg(k, alt) return parent.args[k] or frame.args[k] or alt end local padding, width = arg(2, p.teamBoxPadding()), arg(3, arg('widescore') and 40 or 30) padding = tonumber(padding) and tonumber(padding) .. 'px' or padding width = tonumber(width) and tonumber(width) .. 'px' or width return mw.html.create'div' :css{ margin = ('-1px %s -1px -%s'):format(padding, padding), float = 'left', ['background-color'] = p.bgColor.head, border = '1px solid #aaa', ['text-align'] = 'center', width = width } :wikitext(arg(1, ' ')) end return p