-- This module implements   local p = {}  local templatestyles = 'টেমপ্লেট:চিত্রশালা/শৈলী.css' local yesno = require('Module:Yesno') local plaintextModule = require('Module:Plain text')  local function plaintext(text) 	-- stips out external links without labels, 	-- and then passes to the Plain_text module to clean the rest     return plaintextModule.main({ args = {     	text:gsub("([^%[])%[([^%[%]%s]+)%]", '%1')     	, encode = "no" } }) end  local function trim(s) 	return mw.ustring.gsub(mw.ustring.gsub(s or , '%s', ' '), '^%s*(.-)%s*$', '%1') end  local tracking, preview  local function isImage(file)     local file = trim(file):lower() -- Case insensitive check      -- Check if it starts with "File:", "Image:", or "Media:"     local prefix = file:match("^(%a+):")     if prefix and (prefix == "file" or prefix == "image" or prefix == "media") then         return true     end          local valid_extensions = {         "apng", "djvu", "flac", "gif", "jfi", "jfif", "jif", "jpe", "jpeg", "jpg",         "m1a", "m1v", "m2a", "m2v", "mid", "mp1", "mp2", "mp3", "mpa", "mpe", "mpeg", "mpg",         "mpv", "oga", "ogg", "ogv", "opus", "pdf", "png", "stl", "svg", "svgz", "tif", "tiff",         "wav", "wave", "webm", "webp", "xcf"     }          -- Extract file extension, of 3 or 4 characters only     local ext = file:match("%.(%w%w%w%w?)$")          -- Check if the extension is in the valid list     if ext then         for _, valid_ext in ipairs(valid_extensions) do             if ext == valid_ext then             	table.insert(tracking, 'Category:Pages using gallery without a media namespace prefix')                 return true             end         end     end     return false end  local function checkarg(k,v) 	if k and type(k) == 'string' then 		if k == 'align' or k == 'state' or k == 'style' or k == 'title' or 			k == 'width' or k == 'height' or k == 'whitebg' or 			k == 'mode' or k == 'footer' or k == 'perrow' or k == 'noborder' or 			k:match('^alt%d+$') or k:match('^class%d+$') or k:match('^%d+$') then 			-- valid 		elseif k == 'captionstyle' then 			if not v:match('^text%-align%s*:%s*center[;%s]*$') then 				table.insert(tracking, 'Category:Pages using gallery with the captionstyle parameter') 			end 		else 			-- invalid 			local vlen = mw.ustring.len(k) 			k = mw.ustring.sub(k, 1, (vlen < 25) and vlen or 25) 			k = mw.ustring.gsub(k, '[^%w%-_ ]', '?') 			table.insert(tracking, '' .. k .. '') 			table.insert(preview, '"' .. k .. '"') 		end 	end end  function p.gallery(frame) 	-- If called via #invoke, use the args passed into the invoking template. 	-- Otherwise, for testing purposes, assume args are being passed directly in. 	local origArgs = (type(frame.getParent) == 'function') and frame:getParent().args or frame      -- ParserFunctions considers the empty string to be false, so to preserve the previous     -- behavior of , change any empty arguments to nil, so Lua will consider     -- them false too.     local args = {}     tracking, preview = {}, {}     for k, v in pairs(origArgs) do     	if v ~=  then     		args[k] = v     		checkarg(k,v)     	end 	end 	 	if (args.mode or ) == 'packed' and (args.align or ) ==  then 		args.align = 'center' 	end  	if (args.align or ) == 'centre' then 		args.align = 'center' 	end  	local tbl = mw.html.create('div') 	tbl:addClass('mod-gallery')  	if args.state then 		tbl 			:addClass('mod-gallery-collapsible') 			:addClass('collapsible') 			:addClass(args.state) 	end 	 	if args.style then 		tbl:cssText(args.style) 	else 		tbl:addClass('mod-gallery-default') 	end 	 	if args.align then 		tbl:addClass('mod-gallery-' .. args.align:lower()) 	end  	if args.title then 		tbl:tag('div') 			:addClass('title') 				:tag('div') 					:wikitext(args.title) 	end 	 	local gargs = {} 	gargs['class'] = 'nochecker' .. (args.noborder and  or ' bordered-images') 	gargs['widths'] = tonumber(args.width) or 180 	gargs['heights'] = tonumber(args.height) or 180 	gargs['style'] = args.captionstyle 	gargs['perrow'] = args.perrow 	gargs['mode'] = args.mode 	if yesno(args.whitebg or 'yes') then 		gargs['class'] = gargs['class'] .. ' whitebg' 	end 	 	local virtualgallery = {} 	local gallery = {} 	 	local imageCount = 0  	local zwsp = string.char(0xE2, 0x80, 0x8B) -- U+200B Zero Width Space 	local zwnj = string.char(0xE2, 0x80, 0x8C) -- U+200C Zero Width Non-Joiner 	 	-- create a coding to identify classes 	-- using unicode non-printing characters 	-- this is a workaround until we get the class arg in the <gallery> tag 	-- https://phabricator.wikimedia.org/T344784   	local skininvert    = zwsp .. zwsp .. zwsp; 	local bgtransparent = zwsp .. zwsp .. zwnj; 	 	for i = 1, #args do 	    local currentfield = trim(args[i]) or  	 	    if currentfield ==  then 	        -- Skip empty fields 	    elseif isImage(currentfield) then 	        imageCount = imageCount + 1 	        virtualgallery[imageCount] = { currentfield } 	    elseif imageCount > 0 and virtualgallery[imageCount][2] == nil then 	    	-- In case of multiple captions, use the first and ignore the laters 	        virtualgallery[imageCount][2] = currentfield 	    end 	end 	 	local altCount = 0; 	 	-- Run through virtualgallery and builds gallery 	for n = 1, #virtualgallery do 	    local img = virtualgallery[n][1] 	    local caption = virtualgallery[n][2] or  	    local alt = trim(args['alt' .. n] or ) 	    local class = trim(args['class' .. n] or ) 	     	    -- we count alt text only before any modification 	    if alt ~=  then 	    	altCount = altCount + 1 	    else 	    	-- if alt is empty, we use the caption as a alt text. 	    	-- It is necessary because we add classes codes in the next step, 	    	-- we do not want to let the alt empty with just the non-printing characters. 	    	alt = (caption ~= ) and plaintext(caption) or 	    end  	    -- we attach the non-printing code to the end of the alt text 	    if mw.ustring.find(class, 'skin%-invert') then -- this matches both skin-invert and skin-invert-image 	    	alt = alt .. skininvert 	    end 	     	    -- as it is possible to combine multiple classes, we use find instead of == 	    if mw.ustring.find(class, 'bg%-transparent') then 	    	alt = alt .. bgtransparent 	    end 	     	    -- Some space between the arguments and the pipe to prevent unexpected behaviors. 	    -- for example: in some cases the parser interpret the pipe as part of urls 	    table.insert(gallery, img ..  	    	(alt ~=  and (' |alt=' .. alt) or ) .. 	    	(caption ~=  and (' |' .. caption) or )) 	end 	 	-- For tracking and verifying during migration to the new algorimth. 	-- It can be removed once everything is verified 	if math.ceil(#args / 2) > imageCount then 		if altCount > 0 then 			table.insert(tracking, 'Category:Pages using gallery with potential alt text mismatch') 		--else 		--	table.insert(tracking, 'Category:Pages using gallery with extra empty fields') 		end 	end 	 	tbl:tag('div') 		:addClass('main') 		:tag('div') 			:wikitext( 				frame:extensionTag{ name = 'gallery', content = '\n' .. table.concat(gallery,'\n'), args = gargs} 				)  	if args.footer then 		tbl:tag('div') 			:addClass('footer') 				:tag('div') 					:wikitext(args.footer) 	end  	local trackstr = (#tracking > 0) and table.concat(tracking, ) or '' 	if #preview > 0 then 		trackstr = require('Module:If preview')._warning({ 			'অজানা পরামিতি: ' .. table.concat(preview, '; ') .. '.' 		}) .. trackstr 	end 	 	return frame:extensionTag{ name = 'templatestyles', args = { src = templatestyles} } .. tostring(tbl) .. trackstr end  return p