Documentation for this module may be created at ମଡ୍ୟୁଲ:Autotaxobox/sandbox/doc

--[[
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: {{#invoke:Autotaxobox|taxoboxColour|TAXON}}
=============================================================================]]
function p.taxoboxColour(frame)
	local currTaxon = frame.args[1]
	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 = pcall(frame.expandTemplate, frame, { title = 'Template:Taxonomy/' .. currTaxon, args = {['machine code'] = '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 =================================
Displays the rows of taxa in an automated taxobox, based on the taxonomic
hierarchy for the supplied taxon.
Usage:
{{#invoke:Autotaxobox|taxoboxList|TAXON
|display_taxa = the number of taxa *above* TAXON to force to be displayed
|authority = taxonomic authority for TAXON
|parent_authority = taxonomic authority for TAXON's parent
|gparent_authority = taxonomic authority for TAXON's grandparent
|ggparent_authority = taxonomic authority for TAXON's greatgrandparent
|ggparent_authority = taxonomic authority for TAXON's greatgreatgrandparent
|bold_first = 'bold' to bold TAXON in its row
}}
=============================================================================]]
function p.taxoboxList(frame)
	local currTaxon = frame.args[1]
	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
		lres = 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 "Template:Taxonomy...."
pages.
Usage: {{#invoke:Autotaxobox|taxonomyList|TAXON}}
=============================================================================]]
function p.taxonomyList(frame)
	local currTaxon = frame.args[1]
	if currTaxon == '' then return 'ERROR: no taxon supplied' 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
		local rank = frame:expandTemplate{ title = 'Template:Taxonomy/' .. taxonTable[i], args = {['machine code'] = 'rank' } }
		local currRankVal = rankTable[rank]
		if currRankVal then
			orderOk = currRankVal < lastRankVal
			if orderOk then lastRankVal = currRankVal end
		else
			orderOk = true
		end
		if orderOk then
			res = res .. frame:expandTemplate{ title = 'Template:Taxonomy links', args = { taxonTable[i] } }
		else
			res = res .. frame:expandTemplate{ title = 'Template:Taxonomy links', args = { taxonTable[i], error = 'true' } }
		end
	end
	return res
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: {{#invoke:Autotaxobox|nth|TAXON|n=N}}
=============================================================================]]
function p.nth(frame)
	local currTaxon = frame.args[1]
	if currTaxon == '' then return 'ERROR: no taxon supplied' end
	local n = tonumber(frame.args['n'])
	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 parent = frame:expandTemplate{ title = 'Template:Taxonomy/' .. currTaxon, args = {['machine code'] = 'parent' } }
		if 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: {{#invoke:Autotaxobox|nLevels|TAXON}}
=============================================================================]]
function p.nLevels(frame)
	local currTaxon = frame.args[1]
	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 parent = frame:expandTemplate{ title = 'Template:Taxonomy/' .. currTaxon, args = {['machine code'] = 'parent' } }
		if 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: {{#invoke:Autotaxobox|listAll|TAXON}}
=============================================================================]]
function p.listAll(frame)
	local currTaxon = frame.args[1]
	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 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 = pcall(frame.expandTemplate, frame, { title = 'Template:Taxonomy/' .. currTaxon, args = {['machine code'] = '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,
		genus = 600,
		grandordo = 1030,
		infraclassis = 1370,
		infralegio = 1170,
		infraordo = 970,
		infraphylum = 1470,
		infraregnum = 1570,
		infratribus = 670,
		legio = 1200,
		magnordo = 1040,
		micrordo = 960,
		mirordo = 1020,
		nanordo = 990,
		ordo = 1000,
		parafamilia = 799,
		parvordo = 970,
		phylum = 1500,
		regnum = 1600,
		sectio = 500,
		series = 400,
		species = 300,
		subclassis = 1380,
		subcohort = 1080,
		subdivisio = 1480,
		subfamilia = 780,
		subgenus = 580,
		sublegio = 1180,
		subordo = 980,
		subphylum = 1480,
		subregnum = 1580,
		subsectio = 480,
		subspecies = 280,
		subtribus = 680,
		superclassis = 1410,
		supercohort = 1110,
		superdivisio = 1510,
		superdomain = 1710,
		superfamilia = 810,
		superlegio = 1210,
		superordo = 1010,
		superphylum = 1510,
		superregnum = 1610,
		supertribus = 710,
		tribus = 700,
		varietas = 200,
		zoodivisio = 1300,
		zoosectio = 900,
		zoosubdivisio = 1280,
		zoosubsectio = 880
	}
end

--[[=========================== SANDBOX ONLY ================================
]]

--[[= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =]]
function p.getTaxonInfo(frame)
	local currTaxon = frame.args[1]
	if currTaxon == '' then return 'ERROR: no taxon supplied' end
	local infoStr = frame:expandTemplate{ title = 'Template:Taxonomy/' .. currTaxon, args = {['machine code'] = 'all' } }
	local info = mw.text.split(infoStr, '$', true)
	if info[7] ~= '' then
		local sameAsInfoStr = frame:expandTemplate{ title = 'Template:Taxonomy/' .. info[7], args = {['machine code'] = 'all' } }
		local sameAsInfo = mw.text.split(sameAsInfoStr, '$', true)
		for i = 1,6,1 do
			if info[i] == '' then info[i] = sameAsInfo[i] end
		end
	end
	--return information
	local res = 'parent='..info[1]..'; rank='..info[2]..'; link='..info[3]..'; link text='..info[4]..
		        '; always displayed='..info[5]..'; extinct='..info[6]..'; same as='..info[7]
	return res
end

function p.chkRanks(frame)
	local currTaxon = frame.args[1]
	if currTaxon == '' then return 'ERROR: no taxon supplied' end
	local taxonTable = p.makeTable(frame, currTaxon)
	local rankTable = p.getRankTable()
	local res = ''
	local lastRankVal = -1
	for i = 1,taxonTable.n,1 do
		local rank = frame:expandTemplate{ title = 'Template:Taxonomy/' .. taxonTable[i], args = {['machine code'] = 'rank' } }
		local currRankVal = rankTable[rank]
		if currRankVal then
			--res = res .. rank .. '=' .. currRankVal .. ', '
			if currRankVal <= lastRankVal then res = res .. 'ERROR: ranks out of order at ' .. taxonTable[i] .. 
			                                         ', rank = ' .. rank .. '\n'
			end
			lastRankVal = currRankVal
		end
	end
	return res
end

return p