-- This module implements  local p = {} local convert = require('মডিউল:সংখ্যা রূপান্তরকারী').convert  -- Internationalisation local labels = { 	teamround = 'দল ╲ রাউন্ড', 	source = 'উৎস:', 	notes = 'টীকা:', 	matches = 'ম্যাচ', 	updatedto = '<date> তারিখে খেলা <matches> পর্যন্ত হালনাগাদকৃত।', 	firstplayed = 'প্রথম <matches>টি <date> তারিখে খেলা হবে।', 	futuredate = '?', 	complete = 'সম্পন্ন', 	future = 'ভবিষ্যৎ' }  local modname = 'মডিউল:Sports rbr table' local templatestyles = 'Module:Sports rbr table/styles.css'  local args = nil  local preview, tracking = ,  local hasnotes = false  local colorlist = {} local textlist = {}  local color_map = { 		green1='#BBF3BB', green2='#CCF9CC', green3='#DDFCDD', green4='#EEFFEE', 		blue1='#BBF3FF', blue2='#CCF9FF', blue3='#DDFCFF', blue4='#EEFFFF', 		yellow1='#FFFFBB', yellow2='#FFFFCC', yellow3='#FFFFDD', yellow4='#FFFFEE', 		red1='#FFBBBB', red2='#FFCCCC', red3='#FFDDDD', red4='#FFEEEE', 		black1='#BBBBBB', black2='#CCCCCC', black3='#DDDDDD', black4='#EEEEEE', 		['1st']='#FFD700', ['2nd']='#C0C0C0', ['3rd']='#CC9966' 	}  local legend_symbols = {O='W/O'}  local legend_order_default = {'A', 'H', 'N', 'B', 'W', 'D', 'L', 'Ab', 'P', 'O'}  local function isnotempty(s) 	return s and s:match( '^%s*(.-)%s*$' ) ~= end  local function zeropad(n) 	if n>=0 and n < 10 then 		return '00' .. n 	end 	if n>=0 and n < 100 then 		return '0' .. n 	end 	return  .. n end  local function pad_key(k) 	-- Zero pad, fix ranges and dashes 	if k then 		k = k .. ' ' 		k = mw.ustring.gsub(k, '–', '-') 		k = mw.ustring.gsub(k, '_([%d][^%d])', '_0%1') 		k = mw.ustring.gsub(k, '%-([%d][^%d])', '-0%1') 		k = mw.ustring.gsub(k, '_([%d][%d][^%d])', '_0%1') 		k = mw.ustring.gsub(k, '%-([%d][%d][^%d])', '-0%1') 		k = mw.ustring.gsub(k, '([^%d])%-([%d])', '%1000-%2') 		k = mw.ustring.gsub(k, '([%d])%-%s*$', '%1-999') 		k = mw.ustring.gsub(k, '^%s*(.-)%s*$', '%1') 	end  	return k end  local function matches_date(text, m, d) 	return mw.ustring.gsub(mw.ustring.gsub(text .. , '<matches>', m), '<date>', d) end  local function escapetag(text) 	return mw.ustring.gsub(text, '</', '<FORWARDSLASH') end  local function unescapetag(text) 	return mw.ustring.gsub(text, '<FORWARDSLASH', '</') end  local function get_color(p) 	if p then 		p = mw.ustring.gsub(p, '</?[Aa][Bb][Bb][Rr][^<>]*>', ) 		p = mw.ustring.gsub(p, '<[Ss][Uu][Pp]>[^<>]*</[Ss][Uu][Pp]>', ) 		p = mw.ustring.gsub(p, '</?[Ss][^<>]*>', ) 		p = mw.ustring.gsub(p, '†%s*$', ) 		p = mw.ustring.gsub(p, '=%s*$', ) 		p = mw.ustring.gsub(p, '%[%[[^%[%]|]*|([^%[%]|]*)%]%]', '%1') 		if p:match('^%a%a*$') then 			if args['text_' .. p] == nil then 				tracking = tracking .. ''  					.. p:match('^(%a).*$') .. '' 			end 		end 	end 	local c = colorlist[p] or colorlist[zeropad(tonumber(p) or -1)] 	if c then 		return color_map[c] or c 	end 	p = tonumber(p or '0') or 0 	if p <= 0 then 		return nil 	end 	-- ranges in order of specificity 	local offset1, offset2 = 999, 999 	for k,v in pairs( colorlist ) do 		local r1 = tostring(k):match( '^%s*([%d]+)%-[%d]+%s*$' ) 		local r2 = tostring(k):match( '^%s*[%d]+%-([%d]+)%s*$' ) 		if r1 and r2 then 			r1 = tonumber(r1) 			r2 = tonumber(r2) 			if (r1 <= p) and (r2 >= p) then 				if (c == nil) or ((p - r1) <= offset1 and (r2 - p) <= offset2) then 					c = color_map[v] or v 					offset1 = p - r1 					offset2 = r2 - p 				end 			end 		end 	end 	return c end  local function check_arg(k, st) 	k = tostring(k) or 	if k == 'firstround' or k == 'sortable' or k == 'updated' or k == 'update' 		or k =='source' or k =='notes' or k == 'legendpos' or k == 'date'  		or k == 'header' or k == 'title' or k == 'start_date' or k == 'labelnowrap' 		or k == 'labelalign' or k == 'toptext' or st.addtl_args(k) then 	elseif k == 'legendorder' then 		tracking = tracking .. 'Category:Pages using sports rbr table with legendorder' 	elseif tostring(k):match( '^%s*text_?(.-)%s*$' ) then 	elseif tostring(k):match( '^%s*colou?r_?(.-)%s*$' ) then 	elseif tostring(k):match( '^%s*team[%d]+%s*$' ) then 	elseif tostring(k):match( '^%s*label[%d]+%s*$' ) then 		if args['header'] then 		else 			tracking = tracking .. 'ψ' 		end 	elseif tostring(k):match( '^%s*opp[%d]+%s*$' ) then 	elseif tostring(k):match( '^%s*pos[%d]+%s*$' ) then 	elseif tostring(k):match( '^%s*grnd[%d]+%s*$' ) then 	elseif tostring(k):match( '^%s*res[%d]+%s*$' ) then 	elseif tostring(k):match( '^%s*posc[%d]+%s*$' ) then 	elseif tostring(k):match( '^%s*grndc[%d]+%s*$' ) then 	elseif tostring(k):match( '^%s*resc[%d]+%s*$' ) then 	elseif tostring(k):match( '^%s*split[%d]+%s*$' ) then 	elseif k == 'rnd1' then 		tracking = tracking .. 'Category:Pages using sports rbr table with rnd parameters' 	elseif tostring(k):match( '^%s*rnd[%d]+%s*$' ) then 	elseif tostring(k):match( '^%s*opp_' ) then 	elseif tostring(k):match( '^%s*pos_' ) then 	elseif tostring(k):match( '^%s*grnd_' ) then 	elseif tostring(k):match( '^%s*res_' ) then 	elseif tostring(k):match( '^%s*posc_' ) then 	elseif tostring(k):match( '^%s*grndc_' ) then 	elseif tostring(k):match( '^%s*resc_' ) then 	elseif tostring(k):match( '^%s*name_' ) then 	elseif tostring(k):match( '^%s*note_' ) then 	elseif tostring(k):match( '^%s*pos[%d]+_rnd[%d]+_colou?r%s*$' ) then 		tracking = tracking .. 'Category:Pages using sports rbr table with per team and round coloring' 	elseif tostring(k):match( '^%s*res[%d]+_rnd[%d]+_colou?r%s*$' ) then 		tracking = tracking .. 'Category:Pages using sports rbr table with per team and round coloring' 	elseif tostring(k):match( '^%s*pos[%d]+_rnd[%d]+_note%s*$' ) then 	elseif tostring(k):match( '^%s*res[%d]+_rnd[%d]+_note%s*$' ) then 	else 		local vlen = mw.ustring.len(k) 		k = mw.ustring.sub(k, 1, (vlen < 25) and vlen or 25)  		k = mw.ustring.gsub(k, '[^%w\-_ ]', '?') 		preview = preview .. 'অজানা: "' .. k .. '"' 		tracking = tracking .. '' .. k .. '' 	end end  function p.table(frame) 	local getArgs = require('Module:Arguments').getArgs 	local yesno = require('Module:Yesno') 	args = getArgs(frame, {wrappers = {'Template:Sports rbr table'}}) 	 	local style_def = args['style'] 	local p_style = require(modname) 	if style_def ~= nil then p_style = require(modname .. '/' .. style_def) end 	 	args = p_style.defaults(args,yesno,color_map) 	 	local rounds = tonumber(args['rounds'] or '0') or 0 	local firstround = tonumber(args['firstround'] or 1) or 1 	local sortable = yesno(args['sortable'] or 'no') 	local updated = args['updated'] or args['update'] 	local source = args['source'] 	local notes = args['notes'] 	local delimiter = args['delimiter'] or '/' 	local addlegend = nil 	local legendpos = (args['legendpos'] or 'tr'):lower() 	local header, footer, prenotes  = , ,  	 	-- Lowercase two labels -- 	labels['complete'] = string.lower(labels['complete']) 	labels['future'] = string.lower(labels['future'])  	-- Adjust rounds 	rounds = rounds - (firstround - 1) 	 	-- Tracking 	if updated and updated:match(' %d%d%d%d$') then 		local YY = mw.ustring.gsub(updated, '^.*(%d%d)$', '%1') 		local pn = frame:getParent():getTitle() or 		if pn:match('^User:') or pn:match('^User talk:') or pn:match('^Draft:') or pn:match('^Talk:') then 		else 			if pn:match('%d%d' .. YY) or pn:match('[–%-]' .. YY) then 			else 				tracking = tracking .. 'Category:Pages using sports rbr table with dubious updated parameter' 			end 		end     end 	-- Require a source 	if source == nil then 		source = frame:expandTemplate{ title = 'citation needed', args = { reason='No source parameter defined', date=args['date'] or os.date('%B %Y') } } 	elseif source and source:match('[^%[]#') then  		if source:match('eason#') or source:match('%d%d#') then 			tracking = tracking .. 'Category:Pages using sports rbr table with an unusual source' 		elseif source:match('^[Hh][Tt][Tt][Pp]') then 			tracking = tracking .. 'Φ' 		end 	end 	 	-- Process team, pos, and color args 	local team_list = {} 	local maxrounds = 0 	local rowlength = {} 	for k, v in pairs( args ) do 		check_arg(k, p_style) 		-- Preprocess ranges 		if tostring(k):match( '^%s*text_?(.-)%s*$' ) then 			k = pad_key(k) 		end 		if tostring(k):match( '^%s*colou?r_?(.-)%s*$' ) then 			k = pad_key(k) 		end  		-- Create the list of teams and count rounds 		local i = tonumber( 				tostring(k):match( '^%s*team([%d]+)%s*$' ) or 				tostring(k):match( '^%s*label([%d]+)%s*$' ) or '0' 				) 		if ( i > 0 and isnotempty(v) ) then 			table.insert(team_list, i) 			local p = p_style.get_argvalues_for_maxround(args,i) 			if args['name_' .. v] then 				local t = args['team' .. i] or args['label' .. i] or  				p = p_style.get_argvalues_for_maxround(args,t,'_') 			end 			local pos = mw.text.split(escapetag(p), '%s*' .. delimiter .. '%s*') 			table.insert(rowlength, #pos) 			maxrounds = (#pos > maxrounds) and #pos or maxrounds 			-- maxrounds = p_style.get_maxrounds(args,team_list,i,v,rowlength,maxrounds,delimiter) 		end 		-- Create the list of colors 		local s = tostring(k):match( '^%s*colou?r_?(.-)%s*$' ) 		if ( s and isnotempty(v) ) then 			colorlist[s] = v:lower() 		end 		-- Check if we are adding a legend 		s = tostring(k):match( '^%s*text_?(.-)%s*$' ) 		if ( s and isnotempty(v) ) then 			textlist[s] = v 			addlegend = 1 		end 	end 	 	maxrounds = p_style.get_rounds_or_maxrounds(rounds,maxrounds,args,team_list) 	 	table.sort(rowlength) 	for k=2,#rowlength do 		if rowlength[k] ~= rowlength[k-1] then 			tracking = tracking .. 'k' 		end 	end  	-- sort the teams 	table.sort(team_list)  	local fs = 95 	if ((maxrounds - firstround) > 37 ) then 		fs = fs - 2*(maxrounds - firstround - 37) 		fs = (fs < 80) and 80 or fs 	end  	-- Build the table 	local root = mw.html.create('table') 	root:addClass('wikitable') 	root:addClass(sortable and 'sortable' or nil) 	root:addClass('sportsrbrtable') 	root:css('font-size', fs .. '%')  	if args['title'] then 		root:tag('caption'):wikitext(args['title']) 	end  	local navbar = 	if args['template_name'] then 		navbar = '' .. frame:expandTemplate{ title = 'navbar', args = { mini=1, style=, brackets=1, args['template_name']}} 		-- remove the next part if https://en.wikipedia.org/w/index.php?oldid=832717047#Sortable_link_disables_navbar_links? 		-- is ever fixed 		if sortable then 			navbar = mw.ustring.gsub(navbar, '<%/?abbr[^<>]*>', ' ') 		end 	end 	 	-- Heading row 	local row = p_style.header(root,args,labels,maxrounds,navbar,team_list,firstround)  	-- Team positions 	local prefixes = {'pos', 'res', 'grnd'} 	for k=1,#team_list do 		local i = team_list[k] 		local t = args['team' .. i] or args['label' .. i] or 		local o = args['opp' .. i] or  		local n = args['note' .. i] or 		local efnname = 'note' .. i 		local suf = i 		if args['name_' .. t] then 			o = args['opp_' .. t] or  			n = args['note_' .. t] or 			efnname = 'note' .. t 			suf = '_' .. t 			t = args['name_' .. t] 		end  		if n ~=  then 			if args['note_' .. n] then  				n = frame:expandTemplate{ title = 'efn', args = { name='note' .. n, } } 			else 				n = frame:expandTemplate{ title = 'efn', args = { name=efnname, n} } 			end 			hasnotes = true 		end 		 		local resfound = (args['grnd' .. i] and 1 or 0) + (args['pos' .. i] and 1 or 0) + (args['res' .. i] and 1 or 0) 		if args['name_' .. t] then 			resfound = (args['grnd_' .. t] and 1 or 0) + (args['pos_' .. t] and 1 or 0) + (args['res_' .. t] and 1 or 0) 		end 		if (resfound > 1) then 			tracking = tracking .. 'Category:Pages using sports rbr table with conflicting parameters' 		end 		local rowsdisp = 0 		for subrow,lbl in ipairs(prefixes) do 			local p = args[lbl .. suf] or  			local pc = args[lbl .. 'c' .. suf] or 			if p ~=  or (rowsdisp == 0 and subrow == 3) then 				rowsdisp = rowsdisp + 1 				row = root:tag('tr') 				row:tag('th') 					:addClass(args['team' .. i] and 'sportsrbrtable-team' or 'sportsrbrtable-lbl') 					:css('text-align', args['labelalign']) 					:css('white-space', args['labelnowrap'] and 'nowrap' or nil) 					:attr('scope', 'row') 					:wikitext(mw.ustring.gsub(t,'^%s*%-%s*$', ' ') .. n) 				if t:match('<%s*[Cc][Ee][Nn][Tt][Ee][Rr]%s*>') then 					tracking = tracking .. 'χ' 				end 				local opp = mw.text.split(escapetag(o), '%s*' .. delimiter .. '%s*') 				local pos = mw.text.split(escapetag(p), '%s*' .. delimiter .. '%s*') 				local clr = mw.text.split(escapetag(pc), '%s*' .. delimiter .. '%s*') 				for r=1,maxrounds do 					local s = args['team' .. i .. '_rnd' .. r .. '_' .. 'color'] or 						args['team' .. i .. '_rnd' .. r .. '_' .. 'colour'] or 						args[lbl .. i .. '_rnd' .. r .. '_' .. 'color'] or 						args[lbl .. i .. '_rnd' .. r .. '_' .. 'colour'] or nil 					local n = args['team' .. i .. '_rnd' .. r .. '_' .. 'note'] or 						args[lbl .. i .. '_rnd' .. r .. '_' .. 'note'] or nil 					if s then s = color_map[s] or s end 					local opprt, posrt = unescapetag(opp[r] or ), unescapetag(pos[r] or ) 					local posrc = isnotempty(clr[r]) and clr[r] or posrt  					if posrt:match('^%s*<[Uu]>[%d–]+[A-Za-z][A-Za-z0-9]*') then 						posrc = posrc:match('^%s*<[Uu]>[%d–]+([A-Za-z][A-Za-z0-9]*)') 						posrt = mw.ustring.gsub(posrt, '^%s*(<[Uu]>[%d–]+)[A-Za-z][A-Za-z0-9]*', '%1') 					elseif posrt:match('^%s*[%d–]+[A-Za-z][A-Za-z0-9]*') then 						posrc = posrc:match('^%s*[%d–]+([A-Za-z][A-Za-z0-9]*)') 						posrt = mw.ustring.gsub(posrt, '^%s*([%d–]+)[A-Za-z][A-Za-z0-9]*', '%1') 					end 			 					local ds 					if args['sortable'] and (opprt or posrt):match('^%s*[%d]+[^%d%s]') then 						ds = mw.ustring.gsub(opprt or posrt, '^%s*([%d]+)[^%d%s].*$', '%1') 					end  					if n then 						if args['note_' .. n] then 							n = frame:expandTemplate{ title = 'efn', args = { name='note' .. n, args['note_' .. n]} } 						else 							n = frame:expandTemplate{ title = 'efn', args = { name='note' .. i .. '_rnd_' .. r, n} } 						end 						hasnotes = true 					end 		 					row:tag('td') 						:attr('data-sort-value', ds) 						:css('background-color', s or get_color(p_style.rowbg(posrc, opprt))) 						:wikitext(p_style.rowtext(frame,args,legend_symbols,posrt,opprt) .. (n or )) 				end 				if args['split' .. i] and k ~= #team_list then 					row = root:tag('tr') 						:css('background-color', '#BBBBBB') 						:css('line-height', '3pt') 					row:tag('td') 						:attr('colspan', maxrounds + 1) 				end 			end 		end 	end  	-- build the legend 	if addlegend then 		-- Sort the keys for the legend 		local legendkeys = {} 		for k,v in pairs( textlist ) do 			table.insert(legendkeys, k) 		end 		table.sort(legendkeys)  		if args['legendorder'] then 			legendkeys = mw.text.split(args['legendorder'] .. delimiter .. 				table.concat(legend_order_default, delimiter) .. delimiter .. 				table.concat(legendkeys, delimiter), '%s*' .. delimiter .. '%s*') 		else 			legendkeys = mw.text.split( 				table.concat(legend_order_default, delimiter) .. delimiter .. 				table.concat(legendkeys, delimiter), '%s*' .. delimiter .. '%s*') 		end 		local lroot 		if (legendpos == 't' or legendpos == 'b') then 			lroot = mw.html.create() 			local firsttag = true 			for k,v in pairs( legendkeys ) do 				if v and textlist[v] then 					if firsttag == false then lroot:wikitext('; ') end 					local c = colorlist[v] or 					local l = lroot:tag('span') 						:css('margin', '0') 						:css('white-space', 'nowrap') 						:tag('span') 							:addClass('legend-text') 							:css('border', 'none') 							:css('padding', '1px .3em') 							:css('background-color', color_map[c] or c) 							:css('font-size', '95%') 							:css('border', '1px solid #BBB') 							:css('line-height', '1.25') 							:css('text-align', 'center') 							:wikitext(p_style.legendtext(legend_symbols,v)) 							:done() 						:wikitext(' = ' .. textlist[v]) 					textlist[v] = nil 					firsttag = false 				end 			end 		else 			lroot = mw.html.create('table') 			lroot:addClass('wikitable') 			lroot:css('font-size', '88%') 			if legendpos ~= 'tl' and legendpos ~= 'bl' then 				lroot:css('float', 'right') 				lroot:css('clear', 'right') 				-- lroot:css('width', 'auto') 			end 			for k,v in pairs( legendkeys ) do 				if v and textlist[v] then 					local c = colorlist[v] or  					local row = (legendpos == 'tl' or legendpos == 'bl') and lroot or lroot:tag('tr') 					local l = row:tag('th'):css('background-color', color_map[c] or c) 					if legend_symbols[v] then 						l:css('font-weight', 'normal') 							:css('padding', '1px 3px') 							:wikitext(legend_symbols[v]) 					else 						l:css('width', '10px') 					end 					row:tag('td') 						:css('padding', '1px 3px') 						:wikitext(textlist[v]) 					textlist[v] = nil 				end 			end 		end 		if (legendpos == 'bl' or legendpos == 'br') then 			footer = footer .. tostring(lroot) 		elseif (legendpos == 'b') then 			prenotes = prenotes .. tostring(lroot) 		elseif (legendpos == 't') then 			args['toptext'] = (args['toptext'] or ) 				.. frame:expandTemplate{ title = 'refbegin' } 				.. tostring(lroot) 				.. frame:expandTemplate{ title = 'refend' }  		else 			header = header .. tostring(lroot) 		end 	end  	-- simplify updated == complete case 	local lupdated = updated and string.lower(updated) or  	if lupdated == labels['complete'] or lupdated == 'complete' then 		lupdated = 	end  	-- add note list 	if hasnotes then 		footer = footer .. frame:expandTemplate{ title = 'notelist' } 	end 	 	-- build the footer	 	if prenotes ~=  or notes or source or lupdated ~=  then 		footer = footer .. frame:expandTemplate{ title = 'refbegin' } 		if lupdated ~=  then 			local mtext = args['matches_text'] or labels['matches'] 			if lupdated == labels['future'] or lupdated == 'future' then 				footer = footer .. matches_date(labels['firstplayed'] .. ' ', 					mtext, args['start_date'] or labels['futuredate']) 			else 				footer = footer .. matches_date(labels['updatedto'] .. ' ', 					mtext, updated) 			end 		end 		if source then 			footer = footer .. labels['source'] .. ' ' .. source 		end 		if prenotes ~=  then 			if lupdated ~=  or source then 				footer = footer .. '' 			end 			footer = footer .. prenotes 		end 		if notes then 			if prenotes ~=  or lupdated ~=  or source then 				footer = footer .. '' 			end 			footer = footer .. labels['notes'] .. ' ' .. notes 		end 		footer = footer .. frame:expandTemplate{ title = 'refend' } 	end 	-- add clear right for the legend if necessary 	footer = footer .. ((addlegend and (legendpos == 'bl' or legendpos == 'br')) 		and '' or ) 	if tracking ~=  then 		if frame:preprocess( "" ) == "" then 			tracking = preview 		end 	end 	return frame:extensionTag{ name = 'templatestyles', args = { src = templatestyles} } 		.. header .. (args['toptext'] or ) .. '' 		.. '' 		.. tostring(root) .. '' .. footer .. tracking end  function p.get_argvalues_for_maxround(args, x, del) 	del = del or  	return args['pos' .. del .. x] or args['res' .. del .. x] or '' end  function p.get_rounds_or_maxrounds(rounds, maxrounds) 	return (rounds > maxrounds) and rounds or maxrounds end  function p.addtl_args(k) 	-- just return 'true', no additional args 	return true end  function p.defaults(args) 	-- set nothing 	return args end  function p.header(root,args,labels,maxrounds,navbar,team_list,firstround) 	local row = root:tag('tr') 	row:tag('th') 		:attr('rowspan', args['sortable'] and 2 or nil) 		:wikitext((args['header'] or labels['teamround']) .. navbar) 	for r=1,maxrounds do 		row:tag('th') 			:addClass(args['sortable'] and 'sportsrbrtable-rnd-sort' or 'sportsrbrtable-rnd') 			:attr('scope', 'col') 			:wikitext(convert('bn', tostring(args['rnd' .. (r + (firstround - 1))] 				or (r + (firstround - 1))))) 	end 	if args['sortable'] then 		row = root:tag('tr') 		for r=1,maxrounds do 			row:tag('th') 				:addClass('sportsrbrtable-rnd-toggle') 		end 	end 	return row end  function p.rowtext(frame,args,legend_symbols,posrt,postrc,opprt,opprc) 	return legend_symbols[posrt] or convert('bn', tostring(posrt)) end  function p.rowbg(posrc) 	return posrc end  function p.legendtext(legend_symbols,v) 	return legend_symbols[v] or (v:match('^[^%d][^%d]?$') and v) or ' ' end  return p