Module:RoundN

local p = { RD = { 'Quarter-finals', 'Semi-finals', 'Final', 'Third Place' },	reuseStr = {}, saveStr = function(self, name, ...) if not self.reuseStr[name] then self.reuseStr[name] = table.concat{...} end return self.reuseStr[name] end }

local rowNum, head, m, col = {}, {}, {num = 1, phase = 0}, {} local tab = mw.html.create'table' local nodeFunc = { esc = { bs = require'Module:Escape',--backslash comma = {['(%([^,]*),([^%)]*%))'] = '%1|@!#|%2'},--escape commas in 	},	scanPattern = function(self, args, step)		self.pattern = nil		if args[step] then			self.pattern, self.nonFunc = string.match(self.esc.bs:text(args[step]), '^node_function{(.-)}(.*)')		end		if self.pattern then			for k, v in pairs(self.esc.comma) do				self.pattern = self.pattern:gsub(k, v)			end			self.nonFunc = self.nonFunc and self.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(self.esc.comma) do							arg = self.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 = { freeCells = function local v = p.getNodeFunc.helper.info local count = 0 if not v.occupied.full then for _, x in ipairs(v.occupied) do					for _, y in ipairs(x) do						count = count + 1 end end v.occupied.full = v.totalCells == count end if v.occupied.full then return false end return v.totalCells - count end, topBranch = function--node is top of fork if top is 0 return (m.num - col.top) % 2 end },	line = {--this node is omitted and replaced with a line main = function(x) local nF = p.getNodeFunc local h = nF.helper if h.freeCells == h.info.totalCells then local text, topId, isTop, notTop = nF.line.arg[m.num] or '', h.topBranch isTop = topId == 0 notTop = {[isTop and 1 or 0] = p.reuseStr.solid} for k = 0, 1 do					rowNum[col.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 }						:wikitext(text or nF.nonFunc) text = nil end h.info.occupied.full = true 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 nF = p.getNodeFunc local v = nF.helper.info nF.bridge.lay[v.col][m.num - col.top + 1 + (nF.helper.topBranch == 1 and 1 or -1)] = true return x		end, lay = {} },	canvas = {--Merges all cells in node. Content will be the next parameter. main = function(x) local helper = p.getNodeFunc.helper if helper.freeCells == helper.info.totalCells then tab.r = rowNum[col.r]:tag'td' :attr{ rowspan = 6, colspan = p.colspan }				helper.info.occupied.full = true 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 } }

function p.getNodeFunc return nodeFunc end

--Provides a convenient naming shortcut up to = 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

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

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 = '#f2f2f2' }	end end

function spacer(width) tab.r:tag'td' :attr{width = width} :wikitext(p.no_column_head and '' or ' ') end

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.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, {nodeFunc.esc.bs:undo(cycle, '%%'), table.concat(rinse)}) end, init = function(self, setting) self.cycles = {original = setting} for cycle in (setting and nodeFunc.esc.bs:text(setting) or '{<.->}'):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 }

function boldWin(s1, s2) return p.bold and s1 ~= s2 and (math[({'min', 'max'})[p.bold]](s1, s2) == s1 and {true} or {[2] = true}) or {} end

function maxSpan(span, start, rows) return math.min(span, math.max(0, rows - start + 1)) end

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 tab.r = rowNum[r]:tag'td' :css{ border = '1px solid #aaa', background = ({'gold', 'silver', '#C96', '#f9f9f9'})[f.color and f.phase or 4], [f[1] and 'text-align' or 'padding'] = f[1] or '0 .6ex', }			:css{ padding = f.sumBox and f.sumBox[1], ['border-left'] = f.borderLeft, }			:attr{rowspan = 2} tab.r:tag(f.bold and 'b' or ''):wikitext(v or ' ') end end

function p._main(args, frame) 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',		'color',		'3rdplace',		'omit_blanks',		'scroll_head_unlock',		'previewnumbers',		'flex_tree',		'no_column_head',		'short_brackets'	}) 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.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 p.colspan = p.scoreBoxes > 0 and (p.scoreBoxes + 1) or nil local nodeArgs = { score = p.scoreBoxes - sumBox, team = {offset = 1 + p.scoreBoxes - sumBox}, all = 1 + (1 + p.scoreBoxes - sumBox), tableSum = { __add = function(v, t)				local s = v				for i, n in ipairs(t) do					s = s + n - (p.scoreSumBox and i > 1 and 1 or 0) end return s			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} local fontSize1dec = math.ceil(fontSize * 10) / 10 end end tab :cssText(table.concat{args.scroll_height and 'padding' or 'margin', ':', fontSize1dec or '.9', 'em 2em 1em 1em;border:0;', fontSize and '' or 'font-size:90%;', 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 scoreWidth = args['score-width'] and mw.text.split(args:clean('score-width', {pattern = '[^%d;]'}), ';') or {} 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 }	if p.template then p.template = mw.title.new(args.name) p.templateFixedName = (p.template.namespace == 0 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('/]', '%1') self.count = math.max(self.count, count) end return text end }	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 = 'tnavbar-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) }	for r = 1, rows do		newRow(r) end p.reuseStr.solid = 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		if c > 1 then col.tot = col.tot + math.pow(2, p.tCols - c)			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 = 'tnavbar-header', args = {'', p.templateFixedName} })			)		end local bumps = bump col.top = m.num nodeFunc.bridge.lay[c] = {} p.span = p.tCols > c and bump * 2 or math.max((bump - 1) / 2, 2) local colorFinal, bumpMid = p.color and p.cols == p.tCols and c == p.cols, mw.clone(bumpBase):attr{rowspan = p.span} for r = 1, rows, 2 do			col.r = r + bumps if rowNum[col.r] and m.num <= col.tot then if m.phase == 0 then m.showBox = setmetatable({1, p.colspan or 1, p.colspan or 1}, nodeArgs.tableSum) if nodeFunc:scanPattern(args, step) then nodeFunc.called = {} nodeFunc.helper.info = { occupied = {{},{}}, totalCells = 6 * (p.colspan or 1), col = c						} end elseif nodeFunc.pattern and m.phase == 1 then if nodeFunc.called.canvas or nodeFunc.called.bridge then tab.r:wikitext(nodeFunc.nonFunc) step = step + 1 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 start = col.r + (nodeFunc.pattern and nodeFunc.called.canvas and 6 or 0) rowNum[start]:tag'td':attr{rowspan = maxSpan((start > col.r and 0 or 6) + bump * 2, start, rows), colspan = p.colspan} elseif m.phase == 2 then m.num = m.num + 1 step = step + (p.omit_blanks and 0 or nodeArgs.all) bumps = bumps + maxSpan(p.span, col.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 not nodeFunc.helper.freeCells then step = step + 1 m.showBox = nodeArgs.blank end end if m.showBox[1] then dpBox(nodeFunc.pattern and nodeFunc.nonFunc or args[step], col.r, skipMatch[m.num]) if p.previewnumbers then p.namespace = p.namespace or mw.title.getCurrentTitle.namespace if p.namespace ~= 0 then tab.r: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 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.scoreSumBox and #m.nonEmpty > 1 then table.insert(m.nonEmpty, {-step, -step - 1}) end if p.bold and m.showBox[2] and m.showBox[3] and not unBold[m.num] then m.bold = { box = {}, clean = {} }							for s, i in ipairs(m.nonEmpty) do								if p.scoreSumBox and s == #m.nonEmpty and #m.nonEmpty > 1 then args[i[1]], args[i[2]] = p.scoreWasher.sum(m.bold.clean) m.bold.box[s] = boldWin(args[i[1]], args[i[2]]) else m.bold.clean[s] = {p.scoreWasher:main(args[i[1]]), p.scoreWasher:main(args[i[2]])} m.bold.box[s] = (not p.scoreSumBox or #m.nonEmpty == 1) and boldWin(m.bold.clean[s][1], m.bold.clean[s][2]) or {} end end m.bold.win = m.bold.box[#m.nonEmpty] or {} else m.bold = nil end end else if m.showBox[m.phase] then local base = {color = colorFinal, phase = m.phase} teamBox(args[step + nodeArgs.team[m.phase]], col.r, setmetatable(base, {__index = {bold = m.bold and m.bold.win[m.phase]}})) if p.colspan then if #m.nonEmpty == 0 then for s = 1, p.scoreBoxes do									teamBox('', col.r, base) end else if #m.nonEmpty < p.scoreBoxes then tab.r:attr{colspan = 1 + p.scoreBoxes - #m.nonEmpty} end local thinLeft = p.scoreSumBox and {[1] = {}, [#m.nonEmpty] = {'0 1ex'}} or {} for s, i in ipairs(m.nonEmpty) do									teamBox(args[i[m.phase]], col.r, setmetatable(base, {__index = {'center', borderLeft = not thinLeft[s] and 0 or nil, sumBox = thinLeft[s], bold = m.bold and m.bold.box[s][m.phase]}})) end end end end if m.phase == 2 then if p.scoreWasher.demo and p.scoreWasher.demo == m.num and mw.title.getCurrentTitle.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 : ', mw.dumpObject{scores = m.bold.clean, cycles = p.scoreWasher.cycles, sum = p.scoreSumBox and {m.nonEmpty[#m.nonEmpty][1], m.nonEmpty[#m.nonEmpty][1]}}, ' ',							}						end if nodeFunc.orphan.num == m.num then skipMatch[m.num] = 'orphan' end step = step + (m.showBox.unchanged and nodeArgs.full or m.showBox) m.num = m.num + 1 if bump > 0 and rowNum[col.r + 2] and not (nodeFunc.pattern and nodeFunc.called.canvas) then bumps = bumps + p.span rowNum[col.r + 2]:node(bumpMid) end r = r + 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 = (rows - rows / math.pow(2, c)) / math.pow(2, p.tCols - c)			bumps = p.unit + 1 rowNum[1] :tag'td':attr{rowspan = bumps} :tag'td' :attr{rowspan = bump + 4} :css(nodeFunc.bridge.lay[c][0] and						{['border-right'] = p.reuseStr.solid}						or {}					) 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.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.unit]:tag'td' :attr{rowspan = maxSpan(p.unit * 4, r + 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 if p.tCols == p.cols and (p['3rdplace'] or p.cols > 3 and p['3rdplace'] == nil and not p.no_column_head) then--begin 3rd place col.r = rows / 2 + 4 + math.max(p.span, 3) for r = rows + 1, col.r + 7 do			newRow(r) end if rowNum[rows + 1] and p.cols > 1 then --if 3rd place extends below bottom cell rowNum[rows + 1]:tag'td':attr{ rowspan = col.r + 7 - rows, colspan = (p.cols - 1) * 4 }		end drawHead(args.Consol or args['RD' .. (p.cols + 1)] or p.RD[4], col.r)		dpBox(args[step], col.r + 2) m.bold = boldWin(p.scoreWasher:main(args[step + 2]), p.scoreWasher:main(args[step + 4]), unBold[m.num]) for k, v in ipairs({			{4, {color = p.color and 3, bold = m.bold[1]}},			{4, {'center', color = p.color and 3, bold = m.bold[1]}},			{6, {bold = m.bold[2]}},			{6, {'center', bold = m.bold[2]}}		}) do			teamBox(args[step + k], col.r + v[1], v[2]) 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

function p.main(frame, columns) local args = require'Module:Arguments'.getArgs(frame, {trim = false}) args.columns = args.columns or columns return p._main(args, frame) end

return p