Module:Taxobox

From Wikipedia, the free encyclopedia
local p = {}

local infobox = require("Module:Infobox")
local infoboxImage = require("Module:InfoboxImage")
local arguments = require("Module:Arguments")
local ifPreview = require("Module:If preview")

local colors = {
	animalia = "ebebd2",
	archaea = "c3f5fa",
	archaeplastida = "b4fab4",
	bacteria = "dcebf5",
	eukaryota = "f5d7ff",
	fungi = "91fafa",
	incertae_sedis = "faf0e6",
	sar = "c8fa50",
	veterovata = "fafadc",
	virus = "fafabe",
}

-- aliases that use the same color
-- TODO: combine the tables?
local colorAliases = {
	animal = "animalia",
	
	nanoarchaeota = "archaea",
	nanarchaeota = "archaea",
	korarchaeota = "archaea",
	thaumarchaeota = "archaea",
	crenarchaeota = "archaea",
	euryarchaeota = "archaea",
	
	plantae = "archaeplastida",
	plant = "archaeplastida",
	viridiplantae = "archaeplastida",
	
	firmicutes = "bacteria",
	eubacteria = "bacteria",
	
	eukaryote = "eukaryota",
	eukarya = "eukaryota",
	excavata = "eukaryota",
	excavates = "eukaryota",
	protista = "eukaryota",
	protists = "eukaryota",
	amoebozoa = "eukaryota",
	opisthokonta = "eukaryota",
	choanozoa = "eukaryota",
	
	acritarcha = "incertae_sedis",
	
	chromalveolata = "sar",
	
	viroid = "virus",
	viroids = "virus",
	viruses = "virus",
	i = "virus",
	ii = "virus",
	iii = "virus",
	iv = "virus",
	v = "virus",
	vi = "virus",
	vii = "virus",
	vii = "virus",
}

-- This table shows which order the color search should go
local taxonOrder = {
	"phylum", "unranked_phylum", "divisio", "unranked_superdivisio", "superphylum", "unranked_superphylum", "superdivision", "unranked_superdivisio",
	"subregnum", "unranked_subregnum", "regnum", "unranked_regnum", "superregnum", "unranked_superregnum", "domain", "unranked_domain", "virus_group"
}

-- Gets the background color as a hex code, if possible
--
-- @param args the arguments passed in to the template
--
-- @return a hex color, or nil if no color should be shown
local function getColor(args)
	for _, taxon in ipairs(taxonOrder) do
		local v = args[taxon]
		if v then
			local delinked = mw.ustring.gsub(mw.ustring.lower(v), "[%]%[']", "") -- delink, converting "[[''name'']]" to "name"
			local i = mw.ustring.find(delinked, "|")
			local sanitized = i and mw.ustring.sub(delinked, 0, i - 1) or delinked -- get "abx" from "abc|xyz"
			local hex = colors[sanitized] or colors[colorAliases[sanitized]]
			if hex then
				return hex
			end
		end
	end
end

local classification = {
	{ arg_name = "superdomain", label = "Superdomain" },
	{ arg_name = "domain", label = "Domain" },
	{ arg_name = "superregnum", label = "Superkingdom" },
	{ arg_name = "regnum", label = "Kingdom" },
	{ arg_name = "subregnum", label = "Subkingdom" },
	{ arg_name = "superdivisio", label = "Superdivision" },
	{ arg_name = "superphylum", label = "Superphylum" },
	{ arg_name = "divisio", label = "Division" },
	{ arg_name = "phylum", label = "Phylum" },
	{ arg_name = "subdivisio", label = "Subdivision" },
	{ arg_name = "subphylum", label = "Subphylum" },
	{ arg_name = "infraphylum", label = "Infraphylum" },
	{ arg_name = "microphylum", label = "Microphylum" },
	{ arg_name = "nanophylum", label = "Nanophylum" },
	{ arg_name = "superclassis", label = "Superclass" },
	{ arg_name = "classis", label = "Class" },
	{ arg_name = "subclassis", label = "Subclass" },
	{ arg_name = "infraclassis", "Infraclass" },
	{ arg_name = "magnordo", label = "Magnorder" },
	{ arg_name = "superordo", label = "Superorder" },
	{ arg_name = "ordo", label = "Order" },
	{ arg_name = "subordo", label = "Suborder" },
	{ arg_name = "infraordo", label = "Infraorder" },
	{ arg_name = "parvordo", label = "Parvorder" },
	{ arg_name = "zoodivisio", label = "Division" },
	{ arg_name = "zoosectio", label = "Section" },
	{ arg_name = "zoosubsectio", label = "Subsection" },
	{ arg_name = "superfamilia", label = "Superfamily" },
	{ arg_name = "familia", label = "Family" },
	{ arg_name = "subfamilia", label = "Subfamily" },
	{ arg_name = "supertribus", label = "Supertribe" },
	{ arg_name = "tribus", label = "Tribe" },
	{ arg_name = "subtribus", label = "Subtribe" },
	{ arg_name = "alliance", label = "''Alliance''" },
	{ arg_name = "genus", label = "Genus" },
	{ arg_name = "subgenus", label = "Subgenus" },
	{ arg_name = "sectio", label = "Section" },
	{ arg_name = "subsectio", label = "Subsection" },
	{ arg_name = "series", label = "Series" },
	{ arg_name = "subseries", label = "Subseries" },
	{ arg_name = "species_group", label = "''Species group''" },
	{ arg_name = "species_subgroup", label = "''Species subgroup''" },
	{ arg_name = "species_complex", label = "''Species complex''" },
	{ arg_name = "species", label = "Species" },
	{ arg_name = "subspecies", label = "Subspecies" },
	{ arg_name = "variety", label = "Variety" }, -- no unranked
	{ arg_name = "varias", label = "Variety" }, -- no unranked; alias of variety
	{ arg_name = "forma", label = "Form" }, -- no unranked
}

-- InfoboxImage: [[File:Status <status_system> <file>.svg|link=|alt=<category>]]
-- AddCaption: <label>
-- AddCategory: [[Category:<category>]]
local statusSystems = {
	["iucn2.3"] = {
		link = "IUCN Red List",
		EX = { file = "EX", label = "[[Extinct]]", category = "IUCN Red List extinct species" },
		EW = { file = "EW", label = "[[Extinct in the wild]]", category = "IUCN Red List extinct in the wild species" },
		CR = { file = "CR", label = "[[Critically endangered]]", category = "IUCN Red List critically endangered species" },
		EN = { file = "EN", label = "[[Endangered species|Endangered]]", category = "IUCN Red List endangered species" },
		VU = { file = "VU", label = "[[Vulnerable species|Vulnerable]]", category = "IUCN Red List vulnerable species" },
		LR = {file = "blank", label = "Lower risk", category = "Invalid conservation status" },
		CD = { file = "CD", label = "[[Conservation dependent]]", category = "IUCN Red List conservation dependent species" },
		["LR/CD"] = { file = "CD", label = "[[Conservation dependent]]", category = "IUCN Red List conservation dependent species" }, -- duplicate
		NT = { file = "NT", label = "[[Near threatened]]", category = "IUCN Red List near threatened species" },
		["LR/NT"] = { file = "NT", label = "[[Near threatened]]", category = "IUCN Red List near threatened species" }, -- duplicate
		LC = { file = "LC", label = "[[Least concern]]", category = "IUCN Red List near threatened species" },
		["LR/LC"] = { file = "LC", label = "[[Least concern]]", category = "IUCN Red List near threatened species" },
		DD = { file = "blank", label = "[[Least deficient]]", category = "IUCN Red List data deficient species" },
		NE = { label = "''Not evaluated''" },
		NR = { label = "''Not recognized''" },
		PE = { file = "CR", label = "[[Critically endangered]], possibly extinct", category = "IUCN Red List critically endangered species" },
		PEW = { file = "CR", label = "[[Critically endangered]], possibly extinct in the wild", category = "IUCN Red List critically endangered species" },
	},
	["iucn3.1"] = {
		link = "IUCN Red List",
		EX = { file = "EX", label = "[[Extinct]]", category = "IUCN Red List extinct species" },
		EW = { file = "EW", label = "[[Extinct in the wild]]", category = "IUCN Red List extinct in the wild species" },
		CR = { file = "CR", label = "[[Critically endangered species|Critically endangered]]", category = "IUCN Red List critically endangered species" },
		EN = { file = "EN", label = "[[Endangered species (IUCN status)|Endangered]]", category = "IUCN Red List endangered species" },
		VU = { file = "VU", label = "[[Vulnerable species|Vulnerable]]", category = "IUCN Red List vulnerable species" },
		NT = { file = "NT", label = "[[Near threatened]]", category = "IUCN Red List near threatened species" },
		LC = { file = "LC", label = "[[Least concern]]", category = "IUCN Red List least concern species" },
		DD = { file = "blank", label = "[[Data deficient]]", category = "IUCN Red List data deficient species" },
		NE = { label = "''Not evaluated''" },
		NR = { label = "''Not recognized''" },
		PE = { file = "CR", label = "[[Critically endangered]]", category = "IUCN Red List critically endangered species" },
		PEW = { file = "CR", label = "[[Critically endangered]]", category = "IUCN Red List critically endangered species" },
	},
	["epbc"] = {
		link = "EPBC Act",
		EX = { file = "EX", label = "[[Extinct]]", category = "EPBC Act extinct biota" },
		EW = { file = "EW", label = "[[Extinct in the wild]]", category = "EPBC Act extinct in the wild biota" },
		CR = { file = "CR", label = "[[Critically endangered]]", category = "EPBC Act critically endangered biota" },
		EN = { file = "EN", label = "[[Endangered]]", category = "EPBC Act endangered biota" },
		VU = { file = "VU", label = "[[Vulnerable species|Vulnerable]]", category = "EPBC Act vulnerable biota" },
		CD = { file = "CD", label = "[[Conservation dependent]]", category = "EPBC Act conservation dependent biota" },
		DL = { file = "DL", label = "Delisted" },
	},
}

local function getFirst(e, p)
	return e and e[p] and e[p][1]
end

function p.main(frame)
	local args = arguments.getArgs(frame)
	return p._main(args, frame)
end

-- Helper function to dynamically retrieve the taxon "authority"
-- and present it in paretheses
--
-- @param args the table of arguments passed into the template
-- @param taxon the taxon to check
--
-- @return nil or text to serve as the taxon name and its authority
local function authorityHelp(args, taxon)
	if args[taxon] then
		local authority = args[taxon .. "_authority"]
		return args[taxon] .. (authority and " (" .. authority .. ")" or "") -- when authority is nil no parentheses appear
	end
end

-- uses the qualifiers table provided to construct an authority string
function p._authorityHelp(qualifiers)
	local p405 = getFirst(qualifiers, "P405")
	local authority = p405 and p405["datavalue"] and p405["datavalue"]["value"] and p405["datavalue"]["value"]["id"]
	local q = authority and mw.wikibase.getEntity(authority)
	local lastNameP = q and q["claims"] and getFirst(q["claims"], "P835")
	local lastName = lastNameP and lastNameP["mainsnak"] and lastNameP["mainsnak"]["datavalue"] and lastNameP["mainsnak"]["datavalue"]["value"]
	
	local p574 = getFirst(qualifiers, "P574")
	local year = p574 and p574["datavalue"] and p574["datavalue"]["value"] and p574["datavalue"]["value"]["time"]
	
	if lastName and year then return " (" .. lastName .. ", " .. year .. ")"
	elseif lastName or year then return " (" .. (lastName or year) .. ")"
	else return ""
	end
end

function p._main(args, frame)
	
	local warnings = {} -- warnings to display on preview
	local passing = {} -- arguments passed to Module:Infobox
	
	-- Safely replace spaces in parameters with underscores, removing the original parameter and sending a warning
	for k, _ in pairs(args) do
		if mw.ustring.find(k, " ") then
			local k1 = mw.ustring.gsub(s, " ", "_")
			if not args[k1] then
				table.insert(warnings, "deprecated parameter \"" .. k .. "\". Please use \"" .. k1 .. "\" instead.")
				args[k1] = args[k] -- copy value
				args[k] = nil -- empty
			else
				table.insert(warnings, "found \"" .. k .. "\" and \"" .. k1 .. "\". Using \"" .. k1 .. "\" instead.")
			end
		end
	end
	
	if args.name then
		passing.above = args.name
	elseif args.genus or args.species or args.binomial then
		local title = mw.title.getCurrentTitle().baseText
		local g = mw.ustring.gsub(args.genus or args.species or args.binomial, "'", "")
		if title == g or "<abbr title=\"Extinct\" aria-label=\"Extinct\" style=\"border: none; text-decoration: none; cursor: inherit; font-weight: normal; font-style: normal;\">†</abbr>" .. title == g or "†" .. title == g then
			passing.above = "''" .. g .. "''"
			-- TODO: add {{Italic title}}
		else
			passing.above = title
		end
	else
		passing.above = mw.title.getCurrentTitle().baseText
	end
	
	if args.fossil_range then
		passing.subheader = "Temporal range: " .. args.fossil_range
	end
	
	-- apply header styles
	local hex = args.color_as or args.colour_as or getColor(args)
	if hex then
		local style = "background-color: #" .. hex .. ";"
		passing.abovestyle = style
		passing.subheaderstyle = style
		passing.headerstyle = style
	end
	
	if args.image then
		passing.image = infoboxImage.InfoboxImage({args = {image = args.image, upright = args.upright or args.image_upright, alt = args.alt or args.image_alt}})
		passing.caption = args.caption or args.image_caption
	end
	if args.image2 then
		passing.image2 = infoboxImage.InfoboxImage({args = {image = args.image2, upright = args.upright2 or args.image2_upright, alt = args.alt2 or args.image2_alt}})
		passing.caption2 = args.caption2 or args.image2_caption
	end
	
	
	local i = 1
	
	-- Embed photos into the infobox via frame:expandTemplate
	-- Trying to call Module:Infobox keeps the previous state
	if args.status or args.status2 then
		local subbox = {
			child = "yes",
		}
		
		if args.status and args.status_system then
			args.status = mw.ustring.upper(args.status)
			if args.status_system == "IUCN2.3" or args.status_system == "IUCN3.1" then
				args.status_system = mw.ustring.lower(args.status_system)
			end
			
			if statusSystems[args.status_system] and statusSystems[args.status_system][args.status] then
				local x = statusSystems[args.status_system][args.status]
				subbox.image1 = infoboxImage.InfoboxImage({args = { image = "Status " .. args.status_system .. " " .. args.status .. ".svg", alt = (x.category or nil)}})
				subbox.caption1 = x.label .. " ([[" .. statusSystems[args.status_system].link .. "|" .. args.status_system .. "]])" .. (args.status_ref or "")
			end
		end
		
		if args.status2 and args.status2_system then
			args.status2 = mw.ustring.upper(args.status2)
			if args.status2_system == "IUCN2.3" or args.status2_system == "IUCN3.1" then
				args.status2_system = mw.ustring.lower(args.status2_system)
			end
			
			if statusSystems[args.status2_system] and statusSystems[args.status2_system][args.status2] then
				local x = statusSystems[args.status2_system][args.status2]
				subbox.image2 = infoboxImage.InfoboxImage({args = { image = "Status " .. args.status2_system .. " " .. args.status .. ".svg", alt = (x.category or nil)}})
				subbox.caption2 = x.label .. " ([[" .. statusSystems[args.status2_system].link .. "|" .. args.status2_system .. "]])" .. (args.status2_ref or "")
			end
		end
		
		passing["header" .. i] = "[[Conservation status]]"
		passing["data" .. i + 1] = frame:expandTemplate({title = "infobox", args = subbox})
		
		i = i + 2
	end
	
	if args.virus or args.virus_group then
		passing["header" .. i] = "[[Virus classification]]"
	elseif args.ichnos then
		passing["header" .. i] = "[[Trace fossil classification]]"
	elseif args.veterovata then
		passing["header" .. i] = "[[Egg fossil classification]]"
	else
		passing["header" .. i] = "[[Scientific classification]]"
	end
	
	i = i + 1
	
	if args.virus_group then
		local g = {
			i = "Group I ([[dsDNA]])",
			ii = "Group II ([[ssDNA]])",
			iii = "Group III ([[dsRDNA]])",
			iv = "Group IV ([[(+)ssRNA]])",
			v = "Group V ([[(-)ssRNA]])",
			vi = "Group VI ([[ssRNA-RT]])",
			["vi/vii"] = "Groups VI and VII",
			vii = "Group VII ([[dsDNA-RT]])",
		}
		
		passing["label" .. i] = "Virus group"
		passing["data" .. i] = g[args.virus_group] or args.virus_group
		i = i + 1
	end
	
	local q = args['wikidata_item'] or args['qid'];
	local entity = mw.wikibase.getEntity(q)
	
	if args.superdomain or args.domain or args.superregnum or args.regnum or args.subregnum or args.superdivisio
	or args.superphylum or args.divisio or args.phylum or args.subdivisio or args.subphylum or args.infraphylum
	or args.microphylum or args.nanophylum or args.superclassis or args.classis or args.subclassis or args.infraclassis
	or args.magnordo or args.superordo or args.ordo or args.subordo or args.infraordo or args.parvordo or args.zoodivisio
	or args.zoosectio or args.zoosubsectio or args.superfamilia or args.familia or args.subfamilia or args.supertribus
	or args.tribus or args.subtribus or args.alliance or args.genus or args.subgenus or args.sectio or args.subsectio
	or args.series or args.subseries or args.species_group or args.species_subgroup or args.species_complex
	or args.species or args.subspecies or args.variety or args.varias or args.forma then
	
		for _, t in ipairs(classification) do
			local k = t.arg_name
			local v = t.label
			if args["unranked_" .. k] then
				passing["label" .. i] = "(unranked)"
				passing["data" .. i] = authorityHelp(args, "unranked_" .. k)
				i = i + 1
			end
			if args[k] then
				passing["label" .. i] = v
				passing["data" .. i] = authorityHelp(args, k)
				i = i + 1
			end
		end
	elseif entity then
		local p171 = getFirst(entity["claims"], "P171")
		if p171 and p171["mainsnak"] and p171["mainsnak"]["datavalue"] and p171["mainsnak"]["datavalue"]["value"] and p171["mainsnak"]["datavalue"]["value"]["id"] then
			local parent = mw.wikibase.getEntity(p171["mainsnak"]["datavalue"]["value"]["id"])
			if parent then
				local p105 = getFirst(parent["claims"], "P105")
				passing["label" .. i] = p105 and p105["mainsnak"] and p105["mainsnak"]["datavalue"] and p105["mainsnak"]["datavalue"]["value"] and p105["mainsnak"]["datavalue"]["value"]["id"] or nil
				local p225 = getFirst(parent["claims"], "P225")
				passing["data" .. i] = p225 and p225["mainsnak"] and p225["mainsnak"]["datavalue"] and p225["mainsnak"]["datavalue"]["value"] or nil
				i = i + 2
			end
		end
	end
	
	if args.virus_infrasp and not args.virus_infrasp_rank then
		table.insert(warnings, "\"virus_infrasp_rank\" missing")
	elseif args.virus_infrasp then
		passing["label" .. i] = args.virus_infrasp_rank
	end
	
	if args.binomial then
		passing["header" .. i] = "[[Binomial name]]"
		passing["data" .. i + 1] = authorityHelp(args, "binomial")
		i = i + 2
	elseif entity then
		local binomial = getFirst(entity["claims"], "P225")
		if binomial and binomial["mainsnak"] and binomial["mainsnak"]["datavalue"] and binomial["mainsnak"]["datavalue"]["value"] then
			passing["header" .. i] = "[[Binomial name]]"
			passing["data" .. i + 1] = binomial["mainsnak"]["datavalue"]["value"] .. p._authorityHelp(binomial["qualifiers"])
		end
	end
	
	if args.trinomial then
		passing["header" .. i] = "[[Trinomial name]]"
		passing["data" .. i + 1] = authorityHelp(args, "trinomial")
		i = i + 2
	end
	
	if args.range_map then
		passing["data" .. i] = frame:expandTemplate({title = "Infobox", args = { child = "yes", image = infoboxImage.InfoboxImage({args = {image = args.range_map, alt = args.range_map_alt, upright = args.range_map_upright}}), caption = args.range_map_caption}})
		i = i + 1
	end
	
	if args.range_map2 then
		passing["data" .. i] = frame:expandTemplate({title = "Infobox", args = { child = "yes", image = infoboxImage.InfoboxImage({args = {image = args.range_map2, alt = args.range_map2_alt, upright = args.range_map2_upright}}), caption = args.range_map2_caption}})
		i = i + 1
	end
	
	if args.range_map3 then
		passing["data" .. i] = frame:expandTemplate({title = "Infobox", args = { child = "yes", image = infoboxImage.InfoboxImage({args = {image = args.range_map3, alt = args.range_map3_alt, upright = args.range_map3_upright}}), caption = args.range_map3_caption}})
		i = i + 1
	end
	
	if args.range_map4 then
		passing["data" .. i] = frame:expandTemplate({title = "Infobox", args = { child = "yes", image = infoboxImage.InfoboxImage({args = {image = args.range_map4, alt = args.range_map4_alt, upright = args.range_map4_upright}}), caption = args.range_map4_caption}})
		i = i + 1
	end
	
	if args.type_genus then
		passing["header" .. i] = "[[Type ichnogenus]]"
		passing["data" .. i + 1] = authorityHelp(args, "type_genus")
		i = i + 2
	end
	
	if args.type_oogenus then
		passing["header" .. i] = "[[Type oogenus]]"
		passing["data" .. i + 1] = authorityHelp(args, "type_oogenus")
		i = i + 2
	end
	
	if args.type_species then
		passing["header" .. i] = "[[Type species]]"
		passing["data" .. i + 1] = authorityHelp(args, "type_species")
		i = i + 2
	end
	
	if args.type_oospecies then
		passing["header" .. i] = "[[Type oospecies]]"
		passing["data" .. i + 1] = authorityHelp(args, "type_oospecies")
		i = i + 2
	end
	
	if args.type_strain then
		passing["header" .. i] = "[[Type strain]]"
		passing["data" .. i + 1] = authorityHelp(args, "type_strain") .. (args.type_strain_ref or "")
		i = i + 2
	end
	
	if args.subdivision then
		mw.log(args.subdivision)
		passing["header" .. i] = args.subdivision_ranks or "Species" .. (args.subdivision_ref or "")
		passing["data" .. i + 1] = "<div style=\"text-align: left;\">\n" .. args.subdivision .. "</div>"
		i = i + 2
	end
	
	if args.possible_subdivision then
		passing["header" .. i] = args.possible_subdivision_ranks or "Possible species" .. (args.possible_subdividion_ref or "")
		passing["data" .. i + 1] = args.possible_subdivision
		i = i + 2
	end
	
	if args.synonyms then
		passing["header" .. i] = "[[Synonym (taxonomy)|Synonyms]]" .. ( args.synonyms_ref or "")
		passing["data" .. i + 1] = "<div style=\"text-align: left;\">\n" .. args.synonyms .. "</div>"
	end
	
	local out = infobox.infobox(passing)
	
	for _, warning in ipairs(warnings) do
		local w = ifPreview._warning({warning})
		if w and w ~= '' then
			out = out .. w
		end
	end
	
	return out
end

return p