--[[************************************************************************* 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. ]]  require('Module:No globals') local TaxonItalics = require('Module:TaxonItalics') -- use a function from Module:TaxonItalics to italicize a taxon name local TableRow = '|-\n' local TableEnd = '|}\n' local p = {} -- functions made public local l = {} -- internal functions, kept separate local colour =  -- colour for taxobox and taxonomy listings  --[[========================================================================= 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. The value can be obtained externally via  =============================================================================]] 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) 	return p.getTaxoboxColour(frame, frame.args[1] or ) end  function p.getTaxoboxColour(frame, currTaxon) 	-- note that colour is global to this function; default is empty string 	local i = 1 -- count levels processed 	local searching = currTaxon ~=  -- still searching for a colour? 	local foundICTaxon = false -- record whether 'incertae sedis' found 	while searching and i <= MaxSearchLevels do 		local plainCurrTaxon, dummy = l.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 		if foundICTaxon then 			colour = frame:expandTemplate{ title = 'Template:Taxobox colour', args = { 'incertae sedis' } } 		elseif searching then 			-- hierarchy exceeds MaxSearchLevels levels 			colour = frame:expandTemplate{ title = 'Template:Taxobox/Error colour', args = { } } 		else 			colour = 'transparent' 		end 	end 	return colour end  --[[= = = = = = = = = = = = =  topLevelTaxon  = = = = = = = = = = = = = = = =  Defines the correct top level taxa, one of which should terminate every taxonomic hierarchy encoded in taxonomy templates.  = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = ]] function l.topLevelTaxon(taxon) 	return  taxon == 'Life' or taxon == 'Veterovata' or taxon == 'Ichnos' 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  	if currTaxon ==  then return  end 	local displayN = (tonumber(frame.args['display_taxa']) or 1) + 1 	local authTable = {} 	authTable[1] = frame.args['authority'] or 	authTable[2] = frame.args['parent_authority'] or  	authTable[3] = frame.args['gparent_authority'] or 	authTable[4] = frame.args['ggparent_authority'] or  	authTable[5] = frame.args['gggparent_authority'] or 	local boldFirst = frame.args['bold_first'] or 'link' -- values 'link' or 'bold' 	local virus = frame.args['virus'] or 'no' -- values 'yes' or 'no' 	local offset = tonumber(frame.args['offset'] or 0) 	-- adjust the authority table if 'authority' refers to a rank lower than the target taxon 	if offset ~= 0 then 		for i = 1, 5 do 			local j = i + offset 			if j <= 5 then 				authTable[i] = authTable[j] 			else 				authTable[i] =  			end 		end 	end 	local taxonTable, taxonRankTable = l.makeTable(frame, currTaxon) 	local res = 	local topTaxonN = taxonTable.n 	-- display all taxa above possible greatgreatgrandparent, without authority 	for i = topTaxonN, 6, -1 do 		res = res .. l.showTaxon(frame, taxonTable[i], taxonRankTable[i], topTaxonN==i, , displayN >= i, , virus) 	end 	-- display all taxa above possible parent, with authority if given 	for i = math.min(topTaxonN, 5), 2, -1 do 		res = res .. l.showTaxon(frame, taxonTable[i], taxonRankTable[i], topTaxonN==i, authTable[i], displayN >= i, , virus) 	end 	-- display target taxon, always displayed and emboldened 	res = res .. l.showTaxon(frame, taxonTable[1], taxonRankTable[1], topTaxonN==1, authTable[1], true, boldFirst, virus) 	return res end  --[[= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = Show one taxon row in a taxobox.  = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = ]] function l.showTaxon(frame, taxon, rank, isTopTaxon, auth, force, boldFirst, virus) 	-- it's an error if this is the top taxon and it's not a top level taxon (e.g. "Life") 	if isTopTaxon then 		if l.topLevelTaxon(taxon) then 			return  -- don't display a top level taxon 		elseif (mw.title.new('Taxonomy/'..taxon, 'Template') or {}).exists then 			-- taxonomy template for this taxon has no parent specified 			return frame:expandTemplate{ title = 'Template:Create taxonomy', args = {taxon, msg='Taxonomy template does not specify a parent'} } .. '\n' .. TableRow 		else 			-- no taxonomy template for this taxon 			return frame:expandTemplate{ title = 'Template:Create taxonomy', args = {taxon, msg='Missing taxonomy template'} } .. '\n' .. TableRow 		end 	else 		-- if showing is not already forced, force if it's a principal rank or an authority is specified 		force = force or frame:expandTemplate{ title = 'Template:Principal rank', args = {rank} } == 'yes' or 		        auth ~=  		if not force then 			-- if showing is still not already forced, force if the taxonomy template has 'always_display' set 			local ok, alwaysDisplay = p.getTaxonInfoItem(frame, taxon, 'always_display') 			force = alwaysDisplay == 'yes' or  alwaysDisplay == 'true' 		end 		if force then 			local res = l.tableCell(frame:expandTemplate{ title = 'Template:Anglicise rank', args = {rank} } .. ':') 			local bold = 'no' 			if boldFirst == 'bold' then bold = 'yes' end 			if auth ~=  then 				auth = '' .. auth .. '' 			end 			local res = res .. l.tableCell(l.getTaxonLink(frame, taxon, rank, bold, , , virus) .. auth) -- italic, abbreviated 			return res .. TableRow 		else 			return  		end 	end 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 '{|class="infobox biota"\n' .. TableRow .. l.tableCell() .. l.tableCell('ERROR: no taxon supplied') .. TableEnd 	end 	local taxonTable, taxonRankTable = l.makeTable(frame, currTaxon) 	local rankValTable = l.getRankTable() 	local lastRankVal = 1000000 	local orderOk = true 	-- check whether the taxonomy is for viruses; use already determined taxon colour if possible 	local virus = 'no' 	local taxoColour = colour 	if taxoColour ==  then 		if  taxonTable[taxonTable.n] == 'Ichnos' or taxonTable[taxonTable.n] == 'Veterovata' then 			taxoColour = frame:expandTemplate{ title = 'Template:Taxobox colour', args = { taxonTable[taxonTable.n] } } 		else 			taxoColour = frame:expandTemplate{ title = 'Template:Taxobox colour', args = { taxonTable[taxonTable.n - 1] } } 		end 	end 	if taxoColour == frame:expandTemplate{ title = 'Template:Taxobox colour', args = { 'virus' } } then 		virus = 'yes' 	end 	-- add information message 	local res = 'ট্যাক্সাগুলা ট্যাক্সোবক্সে ঠিক যেভাবে দেখানো হবে তা গাঢ় র‍্যাঙ্কগুলো দ্বারা বুঝানো হয়েছেযেহেতু র‍্যাঙ্কগুলো তুলনামুলক প্রধান বিষয় বা always_display=yes।\n'  	-- start table 	res =  res .. '{| class="infobox biota" style="text-align: left; font-size:100%"\n' .. TableRow .. '! colspan=4 style="text-align: center; background-color: ' 	            .. taxoColour .. '"|Ancestral taxa\n' 	-- deal first with the top level taxon; if there are no errors, it should be Life/Veterovata/Ichnos, which are  	-- not displayed 	local taxon = taxonTable[taxonTable.n] 	if not l.topLevelTaxon(taxon) then 		local msg = 'Taxonomy template missing' 		if mw.title.new('Taxonomy/'..taxon, 'Template').exists then 			msg = 'Parent taxon needed' 		end 		res = res .. TableRow .. l.tableCell('colspan=2', frame:expandTemplate{title = 'Template:Create taxonomy', args = {taxon, msg = msg}}) 	end 	-- now output the rest of the table 	local currRankVal 	for i = taxonTable.n-1, 1, -1 do 		-- check ranks are in right order in the hierarchy 		taxon = taxonTable[i] 		local rank = taxonRankTable[i] 		currRankVal = l.lookupRankVal(rankValTable, rank) 		if currRankVal then 			orderOk = currRankVal < lastRankVal 			if orderOk then lastRankVal = currRankVal end 		else 			orderOk = true 		end 		-- see if the row will be displayed in a taxobox; bold the rank if so 		local boldRank = false 		local ok, alwaysDisplay = p.getTaxonInfoItem(frame, taxon, 'always_display') 		if ok and (alwaysDisplay == 'yes' or alwaysDisplay == 'true') then 			boldRank = true 		else 			boldRank = frame:expandTemplate{ title = 'Template:Principal rank', args = {rank} } == 'yes' 		end 		-- now return a row of the taxonomy table with anomalous ranks marked 		local errorStr = 		if not orderOk then errorStr = 'yes' end 		local link = l.getTaxonLink(frame, taxon, rank, , , , virus) -- bold, italic, abbreviated 		res = res .. l.taxonomyListRow(frame, taxon, rank, link, boldRank, errorStr) 	end 	-- close table 	res = res .. TableEnd 	-- error-tracking for taxonomy templates 	-- if the last row has an anomalous rank, put the page in an error-tracking category 	local errCat1 = 	if not orderOk then 		errCat1 = 'Category:Taxonomy templates showing anomalous ranks\n' 	end 	-- if the last row has a taxon name in the page name that does not match the link text, 	-- put the taxonomy template in a tracking category 	local dummy, linkText = p.getTaxonInfoItem(frame, taxon, 'link_text') 	local match = l.matchTaxonLink(taxon, linkText, currRankVal and currRankVal < rankValTable['genus']) 	local errCat2 =  	if not match then 		errCat2 = '' .. taxon .. '\n' 	end 	if errCat1..errCat2 ~=  then 		res = res .. frame:expandTemplate{ title = 'Template other', args = { errCat1..errCat2} } 	end 	return res end  --[[ = = = = = = = = = = = = = = taxonomyListRow  = = = = = = = = = = = = = =  Returns a single row of the taxonomy table displayed on the right hand side  of "Template:Taxonomy...." pages.  = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = ]] function l.taxonomyListRow(frame, taxon, rank, link, boldRank, error) 	local res =  	if taxon ==  or rank ==  then return res end 	local baseTaxon, qualifier = l.stripExtra(taxon) 	-- if appropriate, make it clear that some taxa have been skipped via a ... row 	if qualifier == '/skip' then 		res = res .. TableRow .. l.tableCell('.....') .. l.tableCell('.....') 	end 	-- now generate a row of the table 	res = res .. TableRow 	local cellContent = 	local anglicizedRank = frame:expandTemplate{ title = 'Template:Anglicise rank', args = { rank } } 	if boldRank then 		cellContent = cellContent .. '' .. anglicizedRank .. ':' 	else 		cellContent = cellContent .. anglicizedRank .. ':' 	end 	if error == 'yes' then 		cellContent = '' .. cellContent .. '' 	end 	res = res .. l.tableCell(cellContent) 	          .. l.tableCell('' .. link .. '') 	          .. l.tableCell('' .. qualifier  .. '') 	          .. l.tableCell('' .. frame:expandTemplate{ title = 'Template:Edit a taxon', args = { taxon } } .. '') 	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 PARENT = 1 local RANK = 2 local LINK_TARGET = 3 local LINK_TEXT = 4 local ALWAYS_DISPLAY = 5 local EXTINCT = 6 local SAME_AS = 7 local REFS = 8  function p.callTaxonomyKey(frame) 	local taxon = frame.args['taxon'] or  	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 টেমপ্লেট:শ্রেণীবিন্যাসবিদ্যা/' .. 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 	-- check consistency of extinct status; if this taxon is not extinct, parent must not be either 	local extinctError = 'no' 	if parent ~=  and (extinct ==  or extinct == 'no' or extinct == 'false') then 		local ok, parentExtinct = p.getTaxonInfoItem(frame, parent, 'extinct') 		if ok and (parentExtinct == 'yes' or parentExtinct == 'true') then extinctError = 'yes' end 	end 	return frame:expandTemplate{ title = 'Template:Taxonomy key', 			args = {taxon=taxon, parent=parent, rank=rank, extinct=extinct, always_display=alwaysDisplay, link_target=linkTarget, link=link, refs=refs, same_as=sameAsTaxon, extinct_error = extinctError} } end  --[[============================= showRefs ================================== Shows the refs field in a taxonomy template, handing incertae sedis taxa and using '–' for absent refs. Usage:  =============================================================================]] function p.showRefs(frame) 	local taxonName = frame.args[1] or  	local refs = frame.args[2] or 	return l.doShowRefs(taxonName, refs) end  --[[= = = = = = = = = = = = = = doShowRefs  = = = = = = = = = = = = = = = = = Show the refs field in a taxonomy template.  = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = ]] function l.doShowRefs(taxonName, refs) 	if mw.text.split(taxonName, '/', true)[1] == 'Incertae sedis' then 		refs = 'not applicable (incertae sedis)' 	elseif refs ==  then 		refs = '–' 	end 	return refs 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', '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  --[[= = = = = = = = = = = getTaxonInfoItem  = = = = = = = = = = = = = = = = = 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) 	local ok, info 	-- item == 'dagger' is a special case 	if item == 'dagger' then 		ok, info = p.getTaxonInfoItem(frame, taxon, 'extinct') 		if ok then 			if info == 'yes' or info == 'true' then 				info = '†' 			else 				info =  			end 		end 	-- item ~= 'dagger' 	else 		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 	end 	if ok then 		-- if item is 'link_text', trim info and check whether '(?)' needs to be added 		if item == 'link_text' then 			-- there is a newline at the end of linkText when taxonomy template has "|link = LINK_TARGET|LINK_TEXT" 			info = mw.text.trim(info) 			if string.sub(taxon, -2) == '/?' and not string.find(info, '?', 1, true) then 				info = info .. ' (?)' 			end 		end 	else 		info = 'টেমপ্লেট:শ্রেণীবিন্যাসবিদ্যা/' .. taxon .. '' --error indicator in code before conversion to Lua 	end 	return ok, 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 abbreviated = frame.args['abbreviated'] 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 	return l.makeLink(taxon, extinct, bold, italic, abbreviated, linkTarget, linkText) end  --[[= = = = = = = = = = = = = = getTaxonLink  = = = = = = = = = = = = = = = = Internal function to drive l.makeLink().  = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = ]] function l.getTaxonLink(frame, taxon, rank, bold, italic, abbreviated, virus) 	local ok, extinct = p.getTaxonInfoItem(frame, taxon, 'extinct') 	if italic ==  then 		italic = frame:expandTemplate{ title = 'Template:Is italic taxon', args = { rank, virus = virus } } 	end 	local ok, linkTarget = p.getTaxonInfoItem(frame, taxon, 'link_target') 	local ok, linkText = p.getTaxonInfoItem(frame, taxon, 'link_text') 	return l.makeLink(taxon, extinct, bold, italic, abbreviated, linkTarget, linkText) end  --[[= = = = = = = = = = = = = = makeLink  = = = = = = = = = = = = = = = = = = Actually make the link.  = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = ]] function l.makeLink(taxon, extinct, bold, italic, abbreviated, linkTarget, linkText) 	local dummy 	-- 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, dummy = l.stripExtra(taxon) 		end 	end 	if linkTarget ==  then linkTarget = linkText end 	if italic == 'yes' then linkText = TaxonItalics.italicizeTaxonName(linkText, false, abbreviated=='yes') end 	local link = 	if bold == 'yes' then link = '' .. linkText .. '' 	else 		if linkTarget == linkText then link = linkText 		else link = linkTarget .. '|' .. linkText 		end 		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 = l.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 .. TableRow .. l.tableCell(k) .. l.tableCell(rankShown) .. l.tableCell(v) 	end 	return res .. TableEnd end  --[[============================== find ===================================== Returns the taxon above the specified taxon with a given rank. Usage:  =============================================================================]] function p.find(frame) 	local currTaxon = frame.args[1] or  	if currTaxon ==  then return 'no taxon supplied' end 	local rank = frame.args[2] or  	if rank ==  then return 'no rank supplied' end 	local inHierarchy = true -- still in the taxonomic hierarchy or off the top? 	local searching = true -- still searching 	while inHierarchy and searching do 		local ok, parent = p.getTaxonInfoItem(frame, currTaxon, 'parent') 			if ok and parent ~=  then 			currTaxon = parent 			local ok, currRank = p.getTaxonInfoItem(frame, currTaxon, 'rank') 			if currRank == rank then 				searching = false 			end 		else 			inHierarchy = false 		end 	end 	if inHierarchy and not searching then return currTaxon 	else return 'rank not found' 	end 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 l.doListAll(l.makeTable(frame, currTaxon)) end  function l.doListAll(taxonTable, taxonRankTable) 	local lst = taxonTable[1] .. '-' .. tostring(taxonRankTable[1]) 	for i = 2, taxonTable.n, 1 do 		lst = lst .. ', ' .. taxonTable[i] .. '-' .. taxonRankTable[i] 	end 	return lst end  --[[=========================== removeQualifier ================================ External utility function to remove a qualifier (any part after a "/") from a  taxon name. Usage:  =============================================================================]] function p.removeQualifier(frame) 	local baseName, qualifier = l.stripExtra(frame.args[1]) 	return baseName end  --[[========================================================================= Internal functions =============================================================================]]  --[[= = = = = = = = = = = = stripExtra  = = = = = = = = = = = = = = = = = = = Internal utility function to strip off any extra parts of a taxon name, i.e. anything after a '/'. Thus 'Felidae/?' would be split into 'Felidae' and '?'.  = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = ]] function l.stripExtra(taxonName) 	local i = mw.ustring.find(taxonName, '/', 1, true) 	if i then 		return mw.ustring.sub(taxonName, 1, i-1), mw.ustring.sub(taxonName, i, -1) 	else 		return taxonName, 	end end  --[[= = = = = = = = = = = = splitTaxonName  = = = = = = = = = = = = = = = = = Internal utility function to split a taxon name into its parts and return them. Possible formats include:  taxon  taxon (disambig)  taxon (Subgenus)  taxon/qualifier  combinations, e.g. taxon (disambig)/qualifier  = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = ]] function l.splitTaxonName(taxon) 	-- get any qualifier present 	local qualifier =  	local i = mw.ustring.find(taxon, '/', 1, true) 	if i then 		qualifier = mw.ustring.sub(taxon, i+1, -1) 		taxon = mw.ustring.sub(taxon, 1, i-1) 	end 	-- get any disambiguator or subgenus 	local disambig = 	local subgenus =  	i = mw.ustring.find(taxon, ' (', 1, true) 	if i then 		local parenTerm = mw.ustring.sub(taxon, i+2, -2) 		taxon = mw.ustring.sub(taxon, 1, i-1) 		local char1 = mw.ustring.sub(parenTerm, 1, 1) 		if char1 == mw.ustring.lower(char1) then 			disambig = parenTerm 		else 			subgenus = parenTerm 		end 	end 	return taxon, disambig, subgenus, qualifier end  --[[= = = = = = = = = = = = matchTaxonLink  = = = = = = = = = = = = = = = = = Function to determine whether the taxon name derived from the name of the  taxonomy template (passed in the parameter taxon) matches the link text (passed in the parameter linkText). The taxon name may have any of the formats:  baseTaxon/qualifier  baseTaxon (disambig)  baseTaxon (Subgenus) [distinguished by the capital letter]  a qualifier may be present after the previous two formats.  Examples of matches (baseTaxon ~ linkText):  Pinus ~ Pinus  Pinus sect. Trifoliae ~ Pinus sect. Trifoliae  Pinus sect. Trifoliae ~ Pinus sect. Trifoliae [italic markers ignored]  Pinus sect. Trifoliae ~ P. sect. Trifoliae [abbreviated genus name matches]  Bombus (Pyrobombus) ~ Bombus (Pyrobombus)  Bombus (Pyrobombus) ~ B. (Pyrobombus)  Bombus (Pyrobombus) ~ Pyrobombus [link text may just be the subgenus]  Heteractinida ~ "Heteractinida" [double-quotes are ignored in link text]  "Heteractinida" ~ Heteractinida [double-quotes are ignored in base taxon name]  Incertae sedis ~ anything [link text is ignored for matching in this case]  Cetotheriidae with qualifier=? ~ Cetotheriidae (?)  = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = ]] function l.matchTaxonLink(taxon, linkText, rankBelowGenus) 	local dummy 	linkText, dummy = mw.ustring.gsub(linkText, "", ) -- remove any italic wikitext in the link text 	linkText, dummy = mw.ustring.gsub(linkText, '<.->', ) -- strip all tags used to format the link text 	linkText, dummy = mw.ustring.gsub(linkText, '"', ) -- remove any occurrences of " in the link text 	local baseTaxon, disambig, subgenus, qualifier = l.splitTaxonName(taxon) -- split up the taxon name 	baseTaxon, dummy = mw.ustring.gsub(linkText, '"', ) -- remove any occurrences of " in the base taxon name 	local match = linkText == baseTaxon or 	              linkText == subgenus or 	              linkText == baseTaxon .. ' (' .. subgenus .. ')' or 	              linkText ==  mw.ustring.sub(baseTaxon, 1, 1) .. '. (' .. subgenus .. ')' or 	              baseTaxon == 'Incertae sedis' or 	              rankBelowGenus and linkText == mw.ustring.gsub(baseTaxon, '([A-Z]).- (.*)', '%1. %2') or  	              mw.ustring.find(qualifier, '?', 1, true) and mw.ustring.find(linkText, baseTaxon, 1, true) == 1 	return match end  --[[= = = = = = = = = = = = = makeTable = = = = = = = = = = = = = = = = = = = 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. The last taxon in the table will either (a) have a taxonomy template but with no parent given (e.g. 'Life') or (b) not have a taxonomy template.  = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = ]] function l.makeTable(frame, currTaxon) 	local taxonTable = {} 	local taxonRankTable = {} 	local ok, rank, parent 	local i = 1 	local topReached = false -- reached the top of the taxonomic hierarchy? 	repeat 		taxonTable[i] = currTaxon 		ok, rank = p.getTaxonInfoItem(frame, currTaxon, 'rank') 		if ok then taxonRankTable[i] = string.lower(rank) else taxonRankTable[i] =  end 		ok, parent = p.getTaxonInfoItem(frame, currTaxon, 'parent') 		if ok and parent ~=  then 			currTaxon = parent 			i = i + 1 		else 			topReached = true -- reached the top of the hierarchy or tried to use a non-existent taxonomy template 		end 	until topReached or i > MaxSearchLevels 	taxonTable.n = math.min(i, MaxSearchLevels) 	return taxonTable, taxonRankTable end  --[[= = = = = = = = = = = = getRankTable  = = = = = = = = = = = = = = = = = = 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 l.getRankTable() 	return { 		classis = 1400, 		cohort = 1100, 		divisio = 1500, 		domain = 1700, 		epifamilia =  802, 		familia = 800, 		forma = 100, 		genus = 600, 		grandordo = 1005, 		['grandordo-mb'] = 1002, 		hyperfamilia = 805; 		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, 		parvclassis = 1396; -- same as subterclassis 		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, 		subterclassis = 1396; -- same as parvclassis 		subtribus = 698, 		superclassis = 1403, 		supercohort = 1103, 		superdivisio = 1503, 		superdomain = 1703, 		superfamilia = 803, 		superlegio = 1203, 		superordo = 1003, 		superphylum = 1503, 		superregnum = 1603, 		supersectio = 503, 		supertribus = 703, 		tribus = 700, 		varietas = 200, 		zoodivisio = 1300, 		zoosectio = 900, 		zoosubdivisio = 1298, 		zoosubsectio = 898, 	} end  --[[= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = Function to look up the arbitrary numerical value of a rank in a rank value table. "Ichno" and "oo" ranks are not stored separately, so if present the prefix is removed.  = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = ]] function l.lookupRankVal(rankValTable, rank) 	local rankVal = rankValTable[rank] 	if not rankVal then 		-- may be an "ichno" or "oo" rank; try removing "ichno-" or "oo-" 		local baseRank = mw.ustring.gsub(mw.ustring.gsub(rank, '^ichno', ), '^oo', ) 		if baseRank == 'rdo' then baseRank = 'ordo' end 		-- if an "ichno" or "oo" rank, lower rank value slightly so it is ok below the base rank 		rankVal = rankValTable[baseRank] 		if rankVal then 			rankVal = rankVal - 0.1 		end 	end 	return rankVal end  --[[= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =  = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = ]] function l.tableCell(arg1, arg2) 	local text, style 	if arg2 then 		style = arg1 		text = arg2 	else 		style =  		text = arg1 	end 	local res = '|' 	if style ~= '' then 		res = res .. style .. '|' 	end 	return res .. text .. '\n' end  return p