--[[ This module provides support to the automated taxobox system – the templates Automatic taxobox, Speciesbox, Subspeciesbox, Infraspeciesbox, etc.  In particular it provides a way of traversing the taxonomic hierarchy encoded in taxonomy templates (templates with names of the form "Template:Taxonomy/TAXON_NAME") without causing template expansion depth errors. ]]  local p = {}  --[[========================================================================= Limit the maximum depth of a taxonomic hierarchy that can be traversed; avoids excessive processing time and protects against incorrectly set up hierarchies, e.g. loops. =============================================================================]] local MaxSearchLevels = 100  function p.getMaxSearchLevels() 	return MaxSearchLevels end  --[[========================== taxoboxColour ================================ Determines the correct colour for a taxobox, by searching up the taxonomic hierarchy from the supplied taxon for the first taxon (other than 'incertae sedis') that sets a taxobox colour. It is assumed that a valid taxobox colour is defined using CSS rgb() syntax. If no taxon that sets a taxobox colour is found, then 'transparent' is returned unless the taxonomic hierarchy is too deep, when the error colour is returned. Usage:  =============================================================================]] function p.taxoboxColour(frame) 	local currTaxon = frame.args[1] or 	local i = 1 -- count levels processed 	local searching = currTaxon ~=  -- still searching for a colour? 	local foundICTaxon = false -- record whether 'incertae sedis' found 	local colour =  -- default is no colour 	while searching and i <= MaxSearchLevels do 		local plainCurrTaxon = p.stripExtra(currTaxon) -- remove trailing text after / 		if string.lower(plainCurrTaxon) == 'incertae sedis' then 			foundICTaxon = true 		else 			local possibleColour = frame:expandTemplate{ title = 'Template:Taxobox colour', args = { plainCurrTaxon } } 			if string.sub(possibleColour,1,3) == 'rgb' then 				colour = possibleColour 				searching = false 			end 		end 		if searching then 			local ok, parent = p.getTaxonInfoItem(frame, currTaxon, 'parent') 			if ok and parent ~=  then 				currTaxon = parent 				i = i + 1 			else 				searching = false -- run off the top of the hierarchy or tried to use non-existent taxonomy template 			end 		end 	end 	if colour ~=  then 		return colour 	elseif foundICTaxon then 		return frame:expandTemplate{ title = 'Template:Taxobox colour', args = { 'incertae sedis' } } 	elseif searching then 		-- hierarchy exceeds MaxSearchLevels levels 		return frame:expandTemplate{ title = 'Template:Taxobox/Error colour', args = { } } 	else 		return 'transparent' 	end end  --[[=========================== taxoboxList ================================= Returns the rows of taxa in an automated taxobox, based on the taxonomic hierarchy for the supplied taxon. Usage:  =============================================================================]] function p.taxoboxList(frame) 	local currTaxon = frame.args[1] or  	local displayN = (tonumber(frame.args['display_taxa']) or 1) + 1 	local auth = frame.args['authority'] or 	local parentAuth = frame.args['parent_authority'] or  	local gParentAuth = frame.args['gparent_authority'] or 	local ggParentAuth = frame.args['ggparent_authority'] or  	local gggParentAuth = frame.args['gggparent_authority'] or 	local boldFirst = frame.args['bold_first'] or 'link' -- values 'link' or 'bold' 	local taxonTable = p.makeTable(frame, currTaxon) 	local res =  	-- display all taxa above possible greatgreatgrandparent 	for i = taxonTable.n, 6, -1 do 		res = res .. frame:expandTemplate{ title = 'Template:Taxobox/showtaxon', args = { taxonTable[i], fc = tostring(displayN >= i) } } 	end 	-- display greatgreatgrandparent, if it exists 	if taxonTable.n >= 5 then 		res = res .. frame:expandTemplate{ title = 'Template:Taxobox/showtaxon', args = { taxonTable[5], authority = gggParentAuth, fc = tostring(displayN >= 5) } } 	end 	-- display greatgrandparent, if it exists; force the display if an infrataxon is below 	if taxonTable.n >= 4 then 		local force = tostring(displayN >= 4) or 		              frame.expandTemplate{ title = 'Template:Infrataxon()', args = { taxonTable[3] } } == 'true' or 		              frame.expandTemplate{ title = 'Template:Infrataxon()', args = { taxonTable[2] } } == 'true' 		res = res .. frame:expandTemplate{ title = 'Template:Taxobox/showtaxon', args = { taxonTable[4], authority = ggParentAuth, fc = tostring(force) } } 	end 	-- display grandparent, if it exists; force the display if an infrataxon is below 	if taxonTable.n >= 3 then 		local force = tostring(displayN >= 3) or                       frame.expandTemplate{ title = 'Template:Infrataxon()', args = { taxonTable[2] } } == 'true'         res = res .. frame:expandTemplate{ title = 'Template:Taxobox/showtaxon', args = { taxonTable[3], authority = gParentAuth, fc = tostring(force) } } 	end 	-- display parent, if it exists 	if taxonTable.n >= 2 then 		res = res .. frame:expandTemplate{ title = 'Template:Taxobox/showtaxon', args = { taxonTable[2], authority = parentAuth, fc = tostring(displayN >= 2) } } 	end 	-- display target taxon 	res = res .. frame:expandTemplate{ title = 'Template:Taxobox/showtaxon', args = { taxonTable[1], authority = auth, fc = 'true', format = boldFirst  } } 	return res end  --[[========================== taxonomyList ================================= Returns the cells of the taxonomy table displayed on the right hand side of "Template:Taxonomy...." pages. Usage:  =============================================================================]] function p.taxonomyList(frame) 	local currTaxon = frame.args[1] or 	if currTaxon ==  then return '| ||ত্রুটি: কোনো ট্যাক্স না দেয়া হয় নি\n|-' end 	local taxonTable = p.makeTable(frame, currTaxon) 	local rankTable = p.getRankTable() 	local lastRankVal = 1000000 	local orderOk 	local res = 	for i = taxonTable.n, 1, -1 do 		-- check ranks are in right order in the hierarchy 		local ok, rank = p.getTaxonInfoItem(frame, taxonTable[i], 'rank') 		local currRankVal = rankTable[string.lower(rank)] 		if currRankVal then 			orderOk = currRankVal < lastRankVal 			if orderOk then lastRankVal = currRankVal end 		else 			orderOk = true 		end 		-- now return a row of the taxonomy table with anomalous ranks marked 		local errorStr =  		if not orderOk then errorStr = 'true' end 		res = res .. frame:expandTemplate{ title = 'Template:Taxonomy links', args = { taxonTable[i], error = errorStr } } 	end 	-- if the last row has an anomalous rank, put the page in the error-tracking category; category statements don't work 	-- inside tables, so need to close the current table first and then open a dummy one (close is in Template:Taxonomy key) 	if not orderOk then 		res = res .. '\n|}\nCategory:Taxonomy templates showing anomalous ranks\n{|\n' 	end 	return res end  --[[========================= callTaxonomyKey =============================== Prepares for, and then calls, Template:Taxonomy key to display a taxonomy template page. It does this by building up the information the template requires, following one 'same as' link, if required. Usage:  =============================================================================]] local SAME_AS = 7 local PARENT = 1 local RANK = 2 local LINK_TARGET = 3 local LINK_TEXT = 4 local ALWAYS_DISPLAY = 5 local EXTINCT = 6 local REFS = 8  function p.callTaxonomyKey(frame) 	local parent = frame.args['parent'] or 	local rank = frame.args['rank'] or  	local extinct = string.lower(frame.args['extinct']) or 	local alwaysDisplay = string.lower(frame.args['always_display']) or  	local linkTarget = frame.args['link_target'] or 	local linkText = frame.args['link_text'] or  -- this is the "raw" link text, and can be 	local refs = frame.args['refs'] or  	local sameAsTaxon = frame.args['same_as'] or 	if sameAsTaxon ~=  then 		-- try using the 'same as' taxon; it's an error if it doesn't exist 		local ok, sameAsInfoStr = pcall(frame.expandTemplate, frame, { title = 'টেমপ্লেট:শ্রেণীবিন্যাসবিদ্যা/' .. sameAsTaxon, args = {['machine code'] = 'all' } }) 		if ok then 			local sameAsInfo = mw.text.split(sameAsInfoStr, '$', true) 			--'same as' taxon's taxonomy template must not have a 'same as' link 			if sameAsInfo[SAME_AS] ==  then 				if parent ==  then parent = sameAsInfo[PARENT] end 				if rank ==  then rank = sameAsInfo[RANK] end 				if extinct ==  then extinct = string.lower(sameAsInfo[EXTINCT]) end 				if alwaysDisplay ==  then alwaysDisplay = string.lower(sameAsInfo[ALWAYS_DISPLAY]) end 				if linkTarget ==  then linkTarget = sameAsInfo[LINK_TARGET] end 				if linkText ==  then linkText = sameAsInfo[LINK_TEXT] end 				if refs ==  and parent == sameAsInfo[PARENT] then refs = sameAsInfo[REFS] end 			else 				return 'Error: attempt to follow two "same as" links: same_as = ' .. sameAsTaxon .. ', but Template:Taxonomy/' .. sameAsTaxon .. ' also has asame_as parameter.' 			end 		else 			return frame:expandTemplate{ title = 'Template:Taxonomy key/missing template', args = {taxon=sameAsTaxon, msg='given as the value of same as'} } 		end 	end 	local link = linkTarget 	if linkText ~=  and linkText ~= linkTarget then link = link .. "|" .. linkText end 	return frame:expandTemplate{ title = 'Template:Taxonomy key', 			args = {parent=parent, rank=rank, extinct=extinct, always_display=alwaysDisplay, link_target=linkTarget, link=link, refs=refs, same_as=sameAsTaxon} } end  --[[============================ taxonInfo ================================== Extracts and returns information from Template:Taxonomy/TAXON, following one 'same as' link if required. Usage:  ITEM is one of: 'parent', 'rank', 'link target', 'link text', 'link', 'extinct', 'always display', 'refs', 'same as' or 'all'. If ITEM is not specified, the default is 'all' – all values in a single string separated by '$'. =============================================================================]] function p.taxonInfo(frame) 	local taxon = frame.args[1] or  	local item = frame.args[2] or 	if item ==  then item = 'all' end 	local ok, info = p.getTaxonInfoItem(frame, taxon, item) 	return info end  --[[============================ taxonLink ================================== Returns a wikilink to a taxon, if required including '†' before it and ' (?)' after it, and optionally italicized or bolded without a wikilink. Usage:  =============================================================================]] function p.taxonLink(frame) 	local taxon = frame.args['taxon'] or 	local extinct = string.lower(frame.args['extinct'] or ) 	local bold = frame.args['bold'] or 	local italic = frame.args['italic'] or  	local linkTarget = frame.args['link_target'] or 	local linkText = frame.args['link_text'] or frame.args['plain_link_text'] or  --temporarily allow alternative args 	-- if link text is missing, try to find a replacement 	if linkText ==  then 		if string.find(taxon, 'Incertae sedis', 1, true) then 			linkText = "incertae sedis" 			linkTarget = 'Incertae sedis' 		else 			linkText = p.stripExtra(taxon) 		end 	end 	local link =  	if bold == 'yes' then link = '' .. linkText .. '' 	else 		if linkTarget == linkText or linkTarget ==  then link = linkText 		else link = linkTarget .. '|' .. linkText 		end 		link = '' .. link .. '' 	end 	if italic == 'yes' and not string.find(link, "", 1, true) then link = '' .. link .. '' end 	if (extinct == 'yes' or extinct == 'true') and not string.find(link, '†', 1, true) then 		link = '†' .. link 	end 	if string.sub(taxon, -2) == '/?' and not string.find(link, '?', 1, true) then 		link = link .. ' (?)' 	end 	return link end  --[[========================== showRankTable ================================ Returns a wikitable showing the ranks and their values as set up by getRankTable(). Usage:  =============================================================================]]  function p.showRankTable(frame) 	local rankTable = p.getRankTable() 	local res = '{| class="wikitable sortable"\n|+শ্রেণীবিন্যাস টেমপ্লেটে গৃহীত ধাপ\n! ধাপ !! প্রদর্শন !! মান\n' 	for k, v in pairs(rankTable) do 		local rankShown = frame:expandTemplate{ title = 'Template:Anglicise rank', args = { k } } 		res = res .. '|-\n|' .. k .. '||' .. rankShown .. '||' .. v .. '\n' 	end 	return res .. '|}\n' end  --[[=============================== nth ===================================== External utility function primarily intended for use in checking and debugging. Returns the nth level above a taxon in a taxonomic hierarchy, where the taxon itself is counted as the first level. Usage:  =============================================================================]] function p.nth(frame) 	local currTaxon = frame.args[1] or 	if currTaxon ==  then return 'ERROR: no taxon supplied' end 	local n = tonumber(frame.args['n'] or 1) 	if n > MaxSearchLevels then 		return 'Exceeded maximum number of levels allowed (' .. MaxSearchLevels .. ')' 	end 	local i = 1 	local inHierarchy = true -- still in the taxonomic hierarchy or off the top? 	while i < n and inHierarchy do 		local ok, parent = p.getTaxonInfoItem(frame, currTaxon, 'parent') 			if ok and parent ~=  then 			currTaxon = parent 			i = i + 1 		else 			inHierarchy = false 		end 	end 	if inHierarchy then return currTaxon 	else return 'Level ' .. n .. ' is past the top of the taxonomic hierarchy' 	end end  --[[============================= nLevels =================================== External utility function primarily intended for use in checking and debugging. Returns number of levels in a taxonomic hierarchy, starting from the supplied taxon as level 1. Usage:  =============================================================================]] function p.nLevels(frame) 	local currTaxon = frame.args[1] or  	if currTaxon ==  then return 'ERROR: no taxon supplied' end 	local i = 1 	local inHierarchy = true -- still in the taxonomic hierarchy or off the top? 	while inHierarchy and i < MaxSearchLevels  do 		local ok, parent = p.getTaxonInfoItem(frame, currTaxon, 'parent') 		if ok and parent ~=  then 			currTaxon = parent 			i = i + 1 		else 			inHierarchy = false 		end 	end 	if inHierarchy then return MaxSearchLevels .. '+' 	else return i 	end end  --[[============================= listAll =================================== External utility function primarily intended for use in checking and debugging. Returns a comma separated list of a taxonomic hierarchy, starting from the supplied taxon. Usage:  =============================================================================]] function p.listAll(frame) 	local currTaxon = frame.args[1] or 	if currTaxon ==  then return 'ERROR: no taxon supplied' end 	return p.listTaxa(p.makeTable(frame, currTaxon)) end  --[[========================================================================= Internal functions =============================================================================]]  --[[= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = Internal utility function to strip off any extra parts of a taxon name, i.e. anything after a '/'. Thus "Felidae/?" would be reduced to "Felidae".  = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = ]] function p.stripExtra(taxonName) 	local i = string.find(taxonName,'/') 	if i then 		return string.sub(taxonName,1,i-1) 	else 		return taxonName 	end end  --[[= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = Internal utility function to convert a taxon table to a comma-separated list.  = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = ]] function p.listTaxa(taxonTable) 	local lst = taxonTable[1] 	for i = 2, taxonTable.n, 1 do 		lst = lst .. ', ' .. taxonTable[i] 	end 	return lst end  --[[= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = Internal utility function to extract an item of information from a  taxonomy template, following one 'same as' link if required.  = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = ]] function p.getTaxonInfoItem(frame, taxon, item) 	-- item == 'link' is a special case 	if item == 'link' then 		return p.getTaxonInfoLink(frame, taxon) 	end 	-- item ~= 'link' 	local ok, info = pcall(frame.expandTemplate, frame, { title = 'টেমপ্লেট:শ্রেণীবিন্যাসবিদ্যা/' .. taxon, args = {['machine code'] = item } }) 	if ok then 		if info ==  then 			-- try 'same as' 			local sameAsTaxon = frame:expandTemplate{ title = 'টেমপ্লেট:শ্রেণীবিন্যাসবিদ্যা/' .. taxon, args = {['machine code'] = 'same as' } } 			if sameAsTaxon ~=  then 				ok, info = pcall(frame.expandTemplate, frame, { title = 'টেমপ্লেট:শ্রেণীবিন্যাসবিদ্যা/' .. sameAsTaxon, args = {['machine code'] = item } }) 			end 		end 	end 	if ok then 		-- if item is 'link_text' check whether '(?)' needs to be added 		if item == 'link_text' and string.sub(taxon, -2) == '/?' and not string.find(info, '?', 1, true) then 			info = info .. ' (?)' 		end 	else 		info = 'টেমপ্লেট:শ্রেণীবিন্যাসবিদ্যা/' .. taxon .. '' --error indicator in code before conversion to Lua 	end 	return ok, info end  function p.getTaxonInfoLink(frame, taxon) 	local ok, linkText, linkTarget 	local link = 	ok, linkText = p.getTaxonInfoItem(frame, taxon, 'link_text') 	if ok then 		ok, linkTarget = p.getTaxonInfoItem(frame, taxon, 'link_target') 		if ok then 			if linkText == linkTarget then link = linkText 			else link = linkTarget .. '|' .. linkText 			end 		end 	end 	return ok, link end  --[[= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = Internal utility function to return a table (array) constructed from a taxonomic hierarchy stored in "Template:Taxonomy/..." templates. TABLE.n holds the total number of taxa; TABLE[1]..TABLE[TABLE.n] the taxon names.  = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = ]] function p.makeTable(frame, currTaxon) 	local i = 1 	local inHierarchy = true -- still in the taxonomic hierarchy or off the top? 	local taxonTable = {} 	taxonTable[1] = currTaxon; 	while i < MaxSearchLevels and inHierarchy do 		local ok, parent = p.getTaxonInfoItem(frame, currTaxon, 'parent') 		if ok and parent ~=  then 			currTaxon = parent 			i = i + 1 			taxonTable[i] = currTaxon 		else 			inHierarchy = false -- run off the top of the hierarchy or tried to use non-existent taxonomy template 		end 	end 	taxonTable.n = i 	return taxonTable end  --[[= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = Internal utility function to set up a table of numerical values corresponding to 'Linnaean' ranks, with upper ranks having higher values. In a valid taxonomic hierarchy, a lower rank should never have a higher value than a higher rank. The actual numerical values are arbitrary so long as they are ordered. The ranks should correspond to those in Template:Anglicise ranks.  = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = ]] function p.getRankTable() 	return { 		classis = 1400, 		cohort = 1100, 		divisio = 1500, 		domain = 1700, 		familia = 800, 		forma = 100, 		genus = 600, 		grandordo = 1005, 		['grandordo-mb'] = 1002, 		infraclassis = 1397, 		infralegio = 1197, 		infraordo = 997, 		infraphylum = 1497, 		infraregnum = 1597, 		infratribus = 697, 		legio = 1200, 		magnordo = 1006, 		microphylum = 1495, 		micrordo = 995, 		mirordo = 1004, 		['mirordo-mb'] = 1001, 		nanophylum = 1494, 		nanordo = 994, 		ordo = 1000, 		parafamilia = 800, 		parvordo = 996, 		phylum = 1500, 		regnum = 1600, 		sectio = 500, 		--series = 400, used too inconsistently to check 		species = 300, 		subclassis = 1398, 		subcohort = 1098, 		subdivisio = 1498, 		subfamilia = 798, 		subgenus = 598, 		sublegio = 1198, 		subordo = 998, 		subphylum = 1498, 		subregnum = 1598, 		subsectio = 498, 		subspecies = 298, 		subtribus = 698, 		superclassis = 1403, 		supercohort = 1103, 		superdivisio = 1503, 		superdomain = 1703, 		superfamilia = 803, 		superlegio = 1203, 		superordo = 1003, 		superphylum = 1503, 		superregnum = 1603, 		supertribus = 703, 		tribus = 700, 		varietas = 200, 		zoodivisio = 1300, 		zoosectio = 900, 		zoosubdivisio = 1298, 		zoosubsectio = 898, 	} end  return p