-- This module implements টেমপ্লেট:দলের উপস্থিতির তালিকা.  local convert = require('মডিউল:সংখ্যা রূপান্তরকারী').convert local p = {}  local data_competitions local data_old_names local function load_data(frame) 	-- Load data module (or its sandbox) and set variables from its exported data. 	if not data_competitions then 		frame = frame or mw.getCurrentFrame() 		local sandbox = frame:getTitle():find('sandbox', 1, true) and '/খেলাঘর' or 		local datamod = mw.loadData('মডিউল:দলের উপস্থিতির তালিকা/উপাত্ত' .. sandbox) 		data_competitions = datamod.competitions 		data_old_names = datamod.old_names 	end end  local function strip_to_nil(text) 	-- If text is a string, return its trimmed content, or nil if empty. 	-- Otherwise return text (which may, for example, be nil). 	if type(text) == 'string' then 		text = text:match('(%S.-)%s*$') 	end 	return text end  local function make_options(args) 	-- Return table of options from validated args or throw error. 	local options = {} 	local function valid_integer(name, min, max, is_optional) 		local arg = args[name] 		if arg == nil or arg ==  then 			if is_optional then 				return nil 			end 			error('প্যারামিটার ' .. name .. ' অনুপস্থিত') 		end 		arg = tonumber(arg) 		if type(arg) ~= 'number' then 			error('প্যারামিটার ' .. name .. ' সংখ্যা নয়') 		end 		if math.floor(arg) ~= arg then 			error('প্যারামিটার ' .. name .. ' পূর্ণ সংখ্যা নয়') 		end 		if not (min <= arg and arg <= max) then 			error('প্যারামিটার ' .. name .. ' বৈধ নয়') 		end 		return arg 	end 	local function valid_text(name) 		local arg = args[name] 		if arg == nil or arg == '' then 			error('প্যারামিটার ' .. name .. ' অনুপস্থিত') 		end 		if type(arg) ~= 'string' then 			error('প্যারামিটার ' .. name .. ' স্ট্রিং নয়') 		end 		return arg 	end 	options.competition = valid_text('competition') 	options.team = valid_text('team') 	options.competitions = data_competitions[options.competition] or data_competitions[data_old_names[options.competition]] 	local begin_optional 	if options.competitions then 		begin_optional = true 	else 		options.interval = valid_integer('interval', 1, 30) 	end 	options.begin_year = valid_integer('begin_year', 1800, 2100, begin_optional) 	options.end_year = valid_integer('end_year', 1800, 2100, true) 	if options.begin_year and options.end_year then 		if options.begin_year > options.end_year then 			error('Parameter end_year must not be before begin_year') 		end 	end 	options.disqualified_year = valid_integer('disqualified_year', 1800, 2100, true) 	return options end  local function extract_range(text) 	-- Return first (if text is a single year), or first, last if a range. 	-- The returned values are numbers. 	-- Return nothing if text is invalid. 	local year = text:match('^(%d+)$') 	if year then 		if #year == 4 then 			return tonumber(year) 		end 		return 	end 	local first, dash, last = text:match('^(%d+)(%D+)(%d+)$') 	if not (first and #first == 4) then 		return 	end 	dash = strip_to_nil(dash) 	if not (dash == '-' or dash == '–') then 		return 	end 	if #last ~= 4 then 		if #last == 2 then 			last = first:sub(1, 2) .. last 		else 			return 		end 	end 	first = tonumber(first) 	last = tonumber(last) 	if first < last then 		return first, last 	elseif first == last then 		return first 	end end  local function competition_absences(data) 	-- Return two tables with absent years and absent year ranges. 	-- Parameter data is an array of strings from template parameters, or 	-- numbers or strings from built-in data. 	-- Parameters that are blank or not numbers or strings are ignored. 	local absent_years, absent_ranges = {}, {} 	for _, item in ipairs(data) do 		if type(item) == 'number' then 			absent_years[item] = true 		else 			item = strip_to_nil(item) 			if type(item) == 'string' then 				local first, last = extract_range(item) 				if not first then 					error('Year ' .. item .. ' is not valid') 				end 				if last then 					table.insert(absent_ranges, {first, last}) 				else 					absent_years[first] = true 				end 			end 		end 	end 	return absent_years, absent_ranges end  local function competition_information(args) 	-- Return four tables with competition and team information: 	-- * List of competition years that the team attended or could have attended. 	-- * Table of disqualified years (the team was absent, but there is an 	--   article regarding the absent year). 	-- * Table of absent years (when the team did not attend). 	-- * List of pairs of years (absent for each year in range, inclusive). 	local options = make_options(args) 	local absences 	local comp_years = {} 	local begin_year = options.begin_year 	local end_year = options.end_year 	local competitions = options.competitions 	if competitions then 		absences = competitions[options.team] or competitions[data_old_names[options.team]] 		begin_year = begin_year or (absences and absences.begin_year) or 0 		end_year = end_year or (absences and absences.end_year) or 9999 		for _, y in ipairs(competitions) do 			if y > end_year then 				break 			elseif y >= begin_year then 				table.insert(comp_years, y) 			end 		end 	else 		end_year = end_year or (os.date('!*t').year + options.interval) 		for y = begin_year, end_year, options.interval do 			table.insert(comp_years, y) 		end 	end 	local disqualified_years = {} 	if options.disqualified_year then 		-- Input currently only allows entry of a single disqualified year. 		-- However processing works for any number of such years. 		disqualified_years[options.disqualified_year] = true 	end 	return comp_years, disqualified_years, competition_absences(absences or args) end  local function gameName(year, inputName) 	-- Modifies output of display being sent back to the hlist 	--  for games that have had a name change but are still considered 	--  the same competition. 	if inputName=="বিশ্ব অ্যাথলেটিক্স চ্যাম্পিয়নশিপ" or inputName=="World Championships in Athletics" then 		if year <= 2017 then 			return "World Championships in Athletics" 		else 			return "বিশ্ব অ্যাথলেটিক্স চ্যাম্পিয়নশিপ" 		end 	elseif (inputName=="ব্রিটিশ সাম্রাজ্য গেমস" 		or inputName=="ব্রিটিশ সাম্রাজ্য ও কমনওয়েলথ গেমস" 		or inputName=="ব্রিটিশ কমনওয়েলথ গেমস" 		or inputName=="কমনওয়েলথ গেমস") then 			if year <= 1950 then 				return "ব্রিটিশ সাম্রাজ্য গেমস" 			elseif year <= 1966 then 				return "ব্রিটিশ সাম্রাজ্য ও কমনওয়েলথ গেমস" 			elseif year <= 1974 then 				return "ব্রিটিশ কমনওয়েলথ গেমস" 			else 				return "কমনওয়েলথ গেমস" 			end 	elseif inputName=="Southeast Asian Peninsular Games" or inputName=="Southeast Asian Games" then 		if year <= 1975 then 			return "Southeast Asian Peninsular Games" 		else 			return "Southeast Asian Games" 		end 	elseif inputName=="Asian Indoor Games" or inputName=="Asian Indoor and Martial Arts Games" then 		if year <= 2009 then 			return "Asian Indoor Games" 		else 			return "Asian Indoor and Martial Arts Games" 		end 	elseif inputName=="Southern Cross Games" or inputName=="South American Games" then 		if year <= 1982 then 			return "Southern Cross Games" 		else 			return "South American Games" 		end 	elseif inputName=="সর্ব-আফ্রিকা গেমস" or inputName=="আফ্রিকান গেমস" then 		if year <= 2011 then 			return "সর্ব-আফ্রিকা গেমস" 		else 			return "আফ্রিকান গেমস" 		end 	else 		return inputName 	end end  local function teamName(year, inputName, comp) 	-- Modifies output of display being sent back to the hlist 	--  for games that have had a name change but are still considered 	--  the same competition. 	if inputName=="ইসোয়াতিনি" or inputName=="সোয়াজিল্যান্ড" then 		if year < 2018 or year == 2018 and comp == 'কমনওয়েলথ গেমস' then 			return "সোয়াজিল্যান্ড" 		else 			return "ইসোয়াতিনি" 		end 	elseif inputName=="Upper Volta" or inputName=="বুরকিনা ফাসো" then 			if year <= 1983 then 				return "Upper Volta" 			else 				return "বুরকিনা ফাসো" 			end 	elseif inputName=="কঙ্গো গণতান্ত্রিক প্রজাতন্ত্র" or inputName=="জায়ার" then 		if year >= 1984 and year <=1996 then 			return "জায়ার" 		else 			return "কঙ্গো গণতান্ত্রিক প্রজাতন্ত্র" 		end 	elseif (inputName=="স্বতন্ত্র অলিম্পিক ক্রীড়াবিদ"  		or inputName=="স্বাধীন অলিম্পিক ক্রীড়াবিদ"  		or inputName=="স্বাধীন অলিম্পিক অংশগ্রহণকারী" 		or inputName=="রাশিয়া থেকে অলিম্পিক ক্রীড়াবিদ" 		or inputName=="ROC") then 		if year == 1992 or year==2014 then 			return "স্বাধীন অলিম্পিক অংশগ্রহণকারী" 		elseif year == 2000 then 			return "স্বতন্ত্র অলিম্পিক ক্রীড়াবিদ" 		elseif year == 2012 or year==2016 then 			return "স্বাধীন অলিম্পিক ক্রীড়াবিদ" 		elseif year == 2018 then 			return "রাশিয়া থেকে অলিম্পিক ক্রীড়াবিদ" 		elseif year == 2020 or year==2022 then 			return "ROC" 		else 			return inputName 		end 	elseif (inputName=="Independent Paralympic Participants"  		or inputName=="Individual Paralympic Athletes"  		or inputName=="Independent Paralympic Athletes" 		or inputName=="RPC") then 		if year == 1992 then 			return "Independent Paralympic Participants" 		elseif year == 2000 then 			return "Individual Paralympic Athletes" 		elseif year==2016 then 			return "Independent Paralympic Athletes" 		elseif year == 2020 or year==2022 then 			return "RPC" 		else 			return inputName 		end 	elseif inputName=="উত্তর মেসিডোনিয়া" or inputName=="মেসিডোনিয়া" then 		if year < 2019 then 			return "মেসিডোনিয়া" 		else 			return "উত্তর মেসিডোনিয়া" 		end 	elseif inputName=="মালয়েশিয়া" or inputName=="মালয়" then 		if year < 1963 then 			return "মালয়" 		else 			return "মালয়েশিয়া" 		end 	else 		return inputName 	end end  function p._main(args) 	load_data()  -- in case this function is called by another module 	local hlist = require('Module:তালিকা').horizontal 	local competitions, disqualified_years, absent_years, absent_ranges = competition_information(args) 	local current_year = os.date('!*t').year 	local function is_absent(y) 		if absent_years[y] then 			return true 		end 		for _, range in ipairs(absent_ranges) do 			if range[1] <= y and y <= range[2] then 				return true 			end 		end 		return false 	end 	local appearances = {} 	local absent_first, absent_last 	for i = 1, #competitions + 1 do  -- +1 to handle any trailing absences 		local y = competitions[i] 		if y and is_absent(y) then 			if absent_first then 				absent_last = y 			else 				absent_first = y 			end 		else 			if absent_first then 				table.insert(appearances, 					'' .. 					convert('bn', tostring((absent_last and (absent_first .. '–' .. absent_last) or absent_first))) .. 					'') 				absent_first, absent_last = nil, nil 			end 			if y then 				local display = tostring(y) 				if y > current_year then 					display = '' .. display .. '' 				end 				if disqualified_years[y] then 					display = '' .. display .. '' 				end 				local compName = gameName(y, args.competition) 				local teamOut = teamName(y, args.team, args.competition) 				y_bn = convert('bn', tostring(y)) 				display_bn = convert('bn', tostring(display)) 				if compName == 'FIS Alpine World Ski Championships' then 					table.insert(appearances, string.format( 					'%s', 					teamOut, compName, y_bn, display_bn 					)) 				else 					table.insert(appearances, string.format( 						'%s', 						y_bn, compName, teamOut, display_bn 					)) 				end 			end 		end 	end 	return hlist(appearances) end 				 function p.main(frame) 	load_data(frame) 	return p._main(frame:getParent().args) end  return p