Module:TaxoTest

Une page de Wikipédia, l'encyclopédie libre.

 Documentation[créer] [purger]
local ENABLE_DEBUG = true


local p = {}	-- module exports
local L = {}	-- alias to local functions
				-- (so it can be iterated by p in debug mode)
local _linkconfig	-- use links from content language Wikipedia
					-- or from Wikidata, default to Wikidata
local _contentlang
local usereferences -- array of references to be prefered in the given order
local usetaxa -- array of taxa to be preferred in the given order
local hideranks -- array of ranks to be show or hide from display
local code = false
local subcode = false
local visited = {}

-- biological nomenclatures
NOMENCLATURE_ICZN = 13011     -- Zoo
NOMENCLATURE_ICNafp = 693148  -- Algae, Fungi and Plants
NOMENCLATURE_ICNCP = 764      -- Cultivated Plants
NOMENCLATURE_ICNP = 743780    -- Prokaryota/Bacteria
NOMENCLATURE_ICVCN = 14920640 -- Viruses

-- look for taxons for color selection
ARCHAEA = 10872 -- ICNP
FUNGI = 764 -- ICNafp
SAR = 137323
HAROSA = 18397957 -- SAR
CHROMALVEOLATA = 477950 -- SAR
CHROMISTA = 862296 -- SAR
RHIZARIA = 855740 -- SAR
AMOEBOZOA = 473809 -- Eukaryota
EXCAVATA = 691551 -- Eukaryota
EUKARYOTA = 19088


local i18nmessages = {}
i18nmessages.i18n = {
	["extinct-mark"] = {
		["en"] = "†"
	},
	["no-label"] = {
		["ar"] = "دون تسمية",
		["en"] = "No label",
		["mk"] = "Нема натпис",
		["ru"] = "Нет метки",
		["uk"] = "Немає мітки",
		["hu"] = "nincs címke",
		["zh-hans"] = "无标签",
		["zh-hant"] = "無標籤",
	},
	["unknown-rank"] = {
		["ar"] = "تصنيف غير معروف",
		["en"] = "Unknown rank",
		["mk"] = "Непознат чин",
		["ru"] = "Неизвестный ранг",
		["uk"] = "Невідомий ранг",
		["hu"] = "ismeretlen kategória",
		["zh-hans"] = "未知级别",
		["zh-hant"] = "不明級別",
	},
	["no-scientific-name"] = {
		["ar"] = "لا يوجد اسم علمي",
		["en"] = "No scientific name",
		["mk"] = "Нема научно име",
		["ru"] = "Нет латинского имени",
		["uk"] = "Немає латинської назви",
		["hu"] = "Nincs tudományos név",
		["zh-hans"] = "无学名",
		["zh-hant"] = "無學名",
	},
	["unknown-group"] = {
		["ar"] = "مجموعة غير معروفة",
		["en"] = "Unknown group",
		["hu"] = "ismeretlen csoport",
		["zh-hans"] = "未知组",
		["zh-hant"] = "不明組",
	},
	["scientific-name-of-taxon"] = {
		["ar"] = "الاسم العلمي لـ %s",
		["en"] = "Scientific name of %s",
		["mk"] = "Научно име на %s",
		["uk"] = "Наукова назва для %s",
		["hu"] = "%s tudományos neve",
		["zh-hans"] = "%s的学名",
		["zh-hant"] = "%s的學名",
	},
	["virus-group-rank"] = {
		["ar"] = "مجموعة",
		["en"] = "Group",
		["hu"] = "Csoport",
		["zh-hans"] = "组",
		["zh-hant"] = "組",
	},
	["virus-item-with-group"] = {
		["en"] = "Group {group} ([[{link}|{shortlabel}]])",
		["hu"] = "{group}. csoport ([[{link}|{shortlabel}]])",
		["zh-hans"] = "组{group}([[{link}|{shortlabel}]])",
		["zh-hant"] = "組{group}([[{link}|{shortlabel}]])",
	},
	["virus-item-without-group"] = {
		["en"] = "[[{link}|{shortlabel}]]",
		["hu"] = "[[{link}|{shortlabel}]]",
	},
	["subdivision-ranks"] = {
		["ar"] = "شعيبة",
		["en"] = "Subdivision ranks",
		["hu"] = "Csoportok",
		["zh-hans"] = "细分级别",
		["zh-hant"] = "細分級別",
	},
	["era-separator"] = {
		["en"] = "—", -- —
		["hu"] = "–", -- –
	},
	["range-map"] = {
		["ar"] = "خريطة الانتشار",
		["en"] = "Range map",
		["mk"] = "Карта на распространетост",
		["ru"] = "Карта ареала",
		["uk"] = "Карта ареалу",
		["hu"] = "Elterjedés",
		["zh-hans"] = "分布地图",
		["zh-hant"] = "分佈地圖",
	},
	["rank-format"] = {
		["en"] = "[[{link}|{label}]]",
		["hu"] = '[[{link}|{label}]]:',
	},
	["rank-format-cladus"] = {
		["en"] = "[[{link}|<i>{label}</i>]]",
		["de"] = "",
		["hu"] = "<i>[[{link}|{label}]]:</i>",
	},
	["item-format-current-with-vernacular-name"] = {
		["en"] = "<b>{scientificshort}</b>",
		["de"] = "<b>{vernacular}</b>",
		["mk"] = "<b>{vernacular}</b> <i>{scientificshort}</i>",
		["ru"] = "<b>{vernacular}</b> <i>{scientificshort}</i>",
		["hu"] = "'''{scientificshort}'''",
		["zh"] = "<b>{vernacular} {scientificshort}</b>",
	},
	["item-format-current-without-vernacular-name"] = {
		["en"] = "<b>{scientificshort}</b>",
	},
	["item-format-parent-with-vernacular-name"] = {
		["en"] = "[[{link}|{scientific}]]",
		["de"] = "[[{link}|{vernacular}]] ({scientific})",
		["mk"] = "[[{link}|{vernacular}]] ({scientific})",
		["nl"] = "[[{link}|{scientific}]] ({vernacular})",
		["ru"] = "[[{link}|{vernacular}]] ({scientific})",
		["zh"] = "[[{link}|{vernacular}]] {scientific}",
		["hu"] = "[[{link}|{vernacular}]] ({scientific})",
	},
	["item-format-parent-without-vernacular-name"] = {
		["en"] = "[[{link}|{scientific}]]",
	},
	["item-format-incertae-sedis"] = {
		["en"] = "''[[{link}|{label}]]''",
		["hu"] = "''[[{link}|{label}]]''",
		["zh"] = "[[{link}|{label}]]",
	},
	["item-format-synonym-render"] = {
		["en"] = "[[{link}|{scientific}]] <small>{author}</small>",
		["hu"] = "[[{link}|{scientific}]] <small>{author}</small>",
	},
	["item-format-synonym-callback"] = {
		["en"] = "* [[{link}|{scientific}]] <small>{author}</small>",
		["hu"] = "* [[{link}|{scientific}]] <small>{author}</small>",
	},
	["synonym-template"] = {
		["en"] = "", -- "Species list"
		["hu"] = "",
	},
	["scientific-name-pattern"] = {
		["en"] = "^.+$",
	},
	["scientific-name-repl"] = {
		["en"] = "%0",
		["de"] = "<i>%0</i>",
		["hu"] = "%0",
	},
	["short-scientific-name-pattern"] = {
		["en"] = "^.+$",
	},
	["short-scientific-name-repl"] = {
		["en"] = "%0",
		["de"] = "<i>%0</i>",
	},
	["scientific-name-repl-genus"] = {
		["en"] = "<i>%0</i>",
		["hu"] = "''%0''",
	},
	["short-scientific-name-repl-genus"] = {
		["en"] = "<i>%0</i>",
		["hu"] = "''%0''",
	},
	["scientific-name-pattern-subgenus"] = {
		["en"] = "^%w+$",
	},
	["scientific-name-repl-subgenus"] = {
		["en"] = "<i>%0</i>",
		["hu"] = "''%0''",
	},
	["short-scientific-name-pattern-subgenus"] = {
		["en"] = "^%w+$",
	},
	["short-scientific-name-repl-subgenus"] = {
		["en"] = "<i>%0</i>",
		["hu"] = "''%0''",
	},
	["scientific-name-repl-species"] = {
		["en"] = "<i>%0</i>",
		["hu"] = "''%0''",
	},
	["scientific-name-repl-subspecies"] = {
		["en"] = "<i>%0</i>",
		["hu"] = "''%0''",
	},
	["scientific-name-repl-virus"] = {
		["en"] = "<i>%0</i>",
	},
	["scientific-name-replaces"] = {
		["en"] = {
			-- subgenus format
			["^(%w+) subg%. (%w+)$"] = "<i>%1</i> subg. <i>%2</i>",
			-- varietas format
			["^(%w+) (%w+) var%. (%w+)$"] = "<i>%1 %2</i> var. <i>%3</i>",
			-- form format
			["^(%w+) (%w+) f%. (%w+)$"] = "<i>%1 %2</i> f. <i>%3</i>",
		}
	},
	["short-scientific-name-replaces-cladus"] = {
		["en"] = {}
	},
	["short-scientific-name-replaces"] = {
		["en"] = {
			-- subgenus format
			["^%w+ subg%. (%w+)$"] = "<i>%1</i>",
			-- species or subgenus format
			["^(%w)%w+ (%w+)$"] = "<i>%1. %2</i>",
			-- subspecies format
			["^(%w)%w+ (%w)%w+ (%w+)$"] = "<i>%1. %2. %3</i>",
			-- varietas format
			["^(%w)%w+ (%w)%w+ var%. (%w+)$"] = "<i>%1. %2.</i> var. <i>%3</i>",
			-- form format
			["^(%w)%w+ (%w)%w+ f%. (%w+)$"] = "<i>%1. %2.</i> f. <i>%3</i>",
		}
	}
}

-- background colors for each code
local colors = {
	[false] = '#d3d3d3',
	[NOMENCLATURE_ICZN] = '#ebebd2',
	[NOMENCLATURE_ICNafp] = '#b4fab4',
	[NOMENCLATURE_ICNCP] = '#a4d3d3',
	[NOMENCLATURE_ICNP] = '#dcebf5',
	[NOMENCLATURE_ICVCN] = '#fafabe',
	[ARCHAEA] = '#c3f5fa',
	[FUNGI] = '#91fafa',
	[SAR] = '#c8fa50',
	[HAROSA] = '#c8fa50',
	[CHROMALVEOLATA] = '#c8fa50',
	[CHROMISTA] = '#c8fa50',
	[RHIZARIA] = '#c8fa50',
	[AMOEBOZOA] = '#f5d7ff',
	[EXCAVATA] = '#f5d7ff',
	[EUKARYOTA] = '#f5d7ff',
}
local virusgroups = {
	[2901600] = {group = 'I',   shortlabel = 'dsDNA'},
	[9094469] = {group = 'II',  shortlabel = 'ssDNA'},
	[3307900] = {group = 'III', shortlabel = 'dsRNA'},
	[9094478] = {group = 'IV',  shortlabel = 'ssRNA(+)'},
	[9285327] = {group = 'V',   shortlabel = 'ssRNA(-)'},
	[9094482] = {group = 'VI',  shortlabel = 'ssRNA-RT'},
	[3754200] = {group = 'VII', shortlabel = 'dsDNA-RT'},
 
	[44209729] = {group = nil,  shortlabel = 'ssDNA(-)'},
	[44209788] = {group = nil,  shortlabel = 'ssDNA(+)'},
	[44209909] = {group = nil,  shortlabel = 'ssDNA(+/-)'},
	[209917]   = {group = nil,  shortlabel = 'Viroid'},
	[44209519] = {group = nil,  shortlabel = 'ssRNA(+/-)'},
	[45181439] = {group = nil,  shortlabel = 'ssRNA'},
}

-- local i18nmessages = require("Module:I18n/taxobox")

-- readable taxon properties
local P_IMAGE = "P18"
local P_INSTANCE_OF = "P31"
local P_TAXON_RANK = "P105"
local P_IUCN_STATUS = "P141"
local P_TAXON_PARENT = "P171"
local P_SPREAD_MAP = "P181"
local P_TAXON_NAME = "P225"
local P_STATED_IN = "P248"
local P_AUTHOR = "P405"
local P_AUTHOR_ABBR_IPNI = "P428"
local P_ERA_START = "P523"
local P_ERA_END = "P524"
local P_BASIONYM = "P566"
local P_SYNONYM = "P1420"
local P_REPLACED_SYNONYM ="P694"
local P_ORIGINAL_COMBINATION = "P1403"
local P_TAXON_YEAR = "P574"
local P_START_TIME = "P580"
local P_END_TIME = "P582"
local P_EX_AUTHOR = "P697"
local P_AUTHOR_ABBR_ZOOLOGY = "P835"
local P_NOMENCLATURE_CODE = "P944"
local P_COMMON_NAME = "P1843"
local P_AUDIO = "P51"
local P_INCERTAE_SEDIS = "P678"
local P_TAXONOMIC_TYPE = "P427"
local P_VIRUS_GENOME = "P4628"
local P_SUBJECT_ROLE = "P2868"
local P_OF = "P642"

-- readable item
local CLADE = 713623
local GENUS = 34740
local SUBGENUS = 3238261
local ZOOSECTIO = 10861426
local ZOOSUBSECTIO = 10861375
local RED_DATA_LIST = 32059
local MONOTYPIC_TAXON = 310890
local GEOLOGICAL_ERA = 630830
local SYSTEMATICS = 3516404
local RECOMBINATION = 14594740
local EXTINCT = 237350
local INCERTAE_SEDIS = 235536
local SYNONYM_TAXON = 1040689
local TYPE_GENUS = 842832
local TYPE_SPECIES = 252730
local VIRUS_CLASSIFICATION = 478216
local PROTONYM = 14192851
local BASIONYM = 810198
local FOSSIL_TAXON = 23038290
local ET_AL = 311624


local function capitalize(text)
	return mw.ustring.gsub(text, "^%l", mw.ustring.upper)
end


local function mergeTable(a, b)
	for _, value in ipairs(b) do
		a[#a + 1] = value
	end
	return a
end
L.mergeTable = mergeTable


-- credit to http://lua-users.org/wiki/StringInterpolation
local function namedStringFormat(str, vars)
	-- Allow replace_vars{str, vars} syntax as well as
	-- replace_vars(str, {vars})
	if not vars then
		vars = str
		str = vars[1]
	end
	if (type(str) ~= "string") then
		return "nsnsns"
	end
	return (string.gsub(str, "({([^}]+)})",
		function(whole,i)
			return vars[i] or whole
		end))
end
L.namedStringFormat = namedStringFormat


local function setLang(contentlang)
	_contentlang = contentlang or mw.language.getContentLanguage():getCode()
end
L.setLang = setLang


local function getLang()
	return _contentlang
end
L.getLang = getLang


local function i18n(str)
	local message = i18nmessages[str]
	if type(message) == 'string' then
		return message
	end
	return { str, 'en' } -- fb._langSwitch(message, getLang())
end
L.i18n = i18n


-- parse item-ids like argument (like config[references]) which is a space
-- separated list of item numbers like "Q1 Q2 Q3"
local function parseItemIds(itemids)
	local items = {}
	local priority = 0
	if itemids then
		for word in string.gmatch(itemids, "%w+") do
			priority = priority + 1
			item = "Q" .. tonumber(string.sub(word, 2))
			items[item] = priority
		end
	end
	items.size = priority
	return items
end
L.parseItemIds = parseItemIds


-- parse config arguments passed by #invode:taxobox. below are all we
-- support currently:
-- - config[lang]: set content language (default: en)
-- - config[count]: maximum count of taxon to be recursively iterated
-- - config[references]: references to be preffered in the given order
-- - config[dryrun]: generate <pre> block instead of expanding template
-- - config[link]: local or wikidata
local function parseConfig(args)
	setLang(args["config[lang]"])
	local count = tonumber(args["config[count]"]) or 10
	if count > 25 then
		-- count = 25 is roughly about 100 expensive parser function calls
		error(i18n("taxon-count-too-high"))
	end
	usereferences = parseItemIds(args["config[references]"])
	usetaxa = parseItemIds(args["config[usetaxa]"])

	hideranks = {}
	local displaypattern = "^display%[([^]]+)%]$"
	local qidpattern = "^Q?(%d+)$"
	for k, v in pairs(args) do
		v = mw.ustring.lower(v)
		if string.match(k, displaypattern) then
			k = string.gsub(k, displaypattern, "%1")
			if string.match(k, qidpattern) then
				k = string.gsub(k, qidpattern, "%1")
				k = tonumber(k)
			end
			-- TODO: i18n?
			if ({n=true, no=true, ["false"]=true, hide=true})[v] then
				hideranks[k] = true
			end
		end
	end

	_linkconfig = string.match(mw.site.server, "wikidata") or "sitelink"
	if args["config[link]"] and
		mw.ustring.lower(args["config[link]"]) == "sitelink" then
		_linkconfig = "sitelink"
	end

	return {
		["count"] = count,
		["lang"] = lang,
		["dryrun"] = args["config[dryrun]"],
		["link"] = _linkconfig
	}
end


-- Adopted from source: c:Module:Wikidata_label
-- the label of the item if present in the specified language or 'no label'
local function getLabel(item, lang)
	local entity, label, language
	lang = lang or getLang()
	
	if type(item) == "number" then
		item = "Q" .. item
	end
	if type(item) ~= 'string' then -- "item" is not a q-code
		entity = item              -- "item" must be the entity
		item = entity.id           -- look-up q-code
	end
	local userLang = mw.getCurrentFrame():callParserFunction( "int", "lang" )
	-- get label (visible part of the link)
	if (userLang == lang) and (not entity) then -- call if requesting label in user's language, but skip if we already have entity
		label, language = mw.wikibase.getLabelWithLang(item) -- prefered way of calling that, as not needed to load the entire entity
	end
	-- hard way to get label by querying all the languages on the langList
	if not label then -- used if requesting label in language different than user's, or if we already have entity
		entity = entity or mw.wikibase.getEntity(item) -- load entity if we do not have it yet
		label = entity:getLabel(lang) or i18n('no-label')
	end
	return label
end
L.getLabel = getLabel


local function getLink(id, label, format, named, ucfirst)
	if type(id) == "number" then
		id = "Q" .. id
	end
	local link = id
	if _linkconfig == "sitelink" then
		link = mw.wikibase.sitelink(id) or "d:" .. id
	end
	label = label or getLabel(id)
	label = ucfirst and label and capitalize(label) or label
	format = format or "[[%s|%s]]"
	if named then
		return namedStringFormat{format, link=link, label=label}
	else
		return string.format(format, link, label)
	end
end
L.getLink = getLink


local function referenceTargetIds(references, property)
	local ids = {}
	if references then
		for _, ref in pairs(references) do
			for _, snak in pairs(ref.snaks[property] or {}) do
				if snak.datavalue and snak.datavalue.value then
					ids[tostring('Q' .. snak.datavalue.value['numeric-id'])] = true
					mw.log('string ref', snak.datavalue.value['numeric-id'])
				end
			end
		end
	end
	return ids
end
L.referenceTargetIds = referenceTargetIds


-- Collect all claims of the given property of the item
-- Returns all claims, their references and qualifiers in tables combined by the claims' rank.
-- result.preferred[target id of claim] = [target id of P248 reference]
-- use only if the data type of the property is item
local function targetIds(item, property)
	local claims = {preferred = {}, normal = {}, deprecated = {}}
	if item and item.claims and item.claims[property] then
		for _,claim in pairs(item.claims[property]) do
			local valueid = claim.mainsnak.datavalue and claim.mainsnak.datavalue.value
				and claim.mainsnak.datavalue.value['numeric-id']
				or 'novalue'
			local refids = referenceTargetIds(claim.references, P_STATED_IN)
			claims[claim.rank][valueid] = {refids = refids or true, qualifiers = claim.qualifiers}
		end
	end
	return claims
end
L.targetIds = targetIds


-- Gives the first highest ranked claim and its references.
-- use only if the data type of the property is item
local function targetId(item, property)
	local claims = targetIds(item, property)
	if next(claims.preferred) then
		return claims.preferred
	end
	if next(claims.normal) then
		return claims.normal
	end
	return claims.deprecated
end
L.targetId = targetId


-- Collect all claims of the given property of the item
-- Returns a triple of claims, their qualifiers, and their references in tables combined by the claims' rank.
-- Use only if the data type of the property is string
local function targetStrs(item, property)
	choosenclaim = {preferred = {}, normal = {}, deprecated = {}}
	choosenqualifiers = {preferred = {}, normal = {}, deprecated = {}}
	choosenreferences = {preferred = {}, normal = {}, deprecated = {}}
	if item and item.claims and item.claims[property] then
		for _,claim in pairs(item.claims[property]) do
			if claim.mainsnak and claim.mainsnak.datavalue then
				index = #choosenclaim[claim.rank] + 1
				mw.log(index, claim.mainsnak.datavalue.value)

				local refids = referenceTargetIds(claim.references, P_STATED_IN)
				if claim.mainsnak.datatype == 'monolingualtext' then
					choosenclaim[claim.rank][index] = tostring(claim.mainsnak.datavalue.value)
				else
					choosenclaim[claim.rank][index] = tostring(claim.mainsnak.datavalue.value)
				end
				choosenqualifiers[claim.rank][index] = claim.qualifiers
				choosenreferences[claim.rank][index] = refids
			end
		end
	end

	return choosenclaim, choosenqualifiers, choosenreferences
end
L.targetStrs = targetStrs


-- Gives the first highest ranked claim and its qualifiers and references.
-- Use only if the data type of the property is string
local function targetStr(item, property)
	choosenclaim, choosenqualifiers, choosenreferences = targetStrs(item, property)

	for _, priority in pairs({"preferred", "normal", "deprecated"}) do
		local index = next(choosenclaim[priority])
		if index then
			return choosenclaim[priority][index],
				choosenqualifiers[priority][index],
				choosenreferences[priority][index]
		end
	end
	return
end
L.targetStr = targetStr


-- helper function to merge all claims, regardless of rank
local function mergeClaims(claims, qualifiers, references)
	local c = {}
	local q = {}
	local r = {}
	for _, priority in pairs({"preferred", "normal", "deprecated"}) do
		mergeTable(c, claims[priority] or {})
		mergeTable(q, qualifiers[priority] or {})
		mergeTable(r, references[priority] or {})
	end
	return c, q, r
end
L.mergeClaims = mergeClaims


local function targetValue(item, property)
	if item and item.claims and item.claims[property] then
		for _,claim in pairs(item.claims[property]) do
			if claim.mainsnak and claim.mainsnak.datavalue then
				return claim.mainsnak.datavalue.value
			end
		end
	end
end
L.targetValue = targetValue


-- same as targetId but for qualifiers
-- TODO merge
local function qualifierTargetId(qualifiers, property)
	local claims = {}
	if qualifiers and qualifiers[property] then
		for _,claim in pairs(qualifiers[property]) do
			local valueid = claim.datavalue.value['numeric-id']
			table.insert(claims, valueid)
		end
	end
	return claims
end
L.qualifierTargetId = qualifierTargetId


-- same as targetValue but for qualifiers
local function qualifierTargetValue(qualifiers, property)
	local claims = {}
	if qualifiers and qualifiers[property] then
		for _,claim in pairs(qualifiers[property]) do
			if claim.datavalue then
				return claim.datavalue.value
			end
		end
	end
end
L.qualifierTargetValue = qualifierTargetValue


-- takes a list of item ids (the values of the given table) and creates wikilinks based on their labels
local function createLinks(list, authorAbbreviation)
	local authors = {}
	for _,authorid in pairs(list) do
		if authorid then
			local author = mw.wikibase.getEntity('Q' .. authorid)
			if author then
				local label
				if authorAbbreviation then
					if code == NOMENCLATURE_ICNafp then
						-- get author abbrieviation per IPNI set
						if targetStr(author, P_AUTHOR_ABBR_IPNI) then
							label = targetStr(author, P_AUTHOR_ABBR_IPNI)
						end
					elseif targetStr(author, P_AUTHOR_ABBR_ZOOLOGY) then
						-- get zoologist author citation set
						label = targetStr(author, P_AUTHOR_ABBR_ZOOLOGY)
					end
					if not label then
						-- use the "last" name if no abbreviation found
						-- also don't use the translated name
						label = getLabel(author, "en")
						if label ~= i18n('no-label') then
							_, _, label = mw.ustring.find(label, "(%w+)$")
						end
					end
				end
				table.insert(authors, getLink(authorid, label))
			end
		end
	end
	return authors
end
L.createLinks = createLinks


local function vernacularName(item)
	local vernacularname
	-- select vernacular name for current language
	if item.claims and item.claims[P_COMMON_NAME] then
		for _, claim in pairs(item.claims[P_COMMON_NAME]) do
			if claim.mainsnak and claim.mainsnak.datavalue and
				claim.mainsnak.datavalue.type == "monolingualtext" and
				claim.mainsnak.datavalue.value.language == getLang() then
				vernacularname = claim.mainsnak.datavalue.value.text
				break
			end
		end
		if vernacularname == '' then
			vernacularname = nil
		end
	end

	if not vernacularname then
		-- test if item label is not one of the scientific names
		vernacularname = getLabel(item)
		scnames = mergeClaims(targetStrs(item, P_TAXON_NAME))
		for _, n in pairs(scnames) do
			if vernacularname == n then
				return
			end
		end
	end
	if vernacularname == i18n("no-label") then
		return
	end
	return capitalize(vernacularname)
end
L.vernacularName = vernacularName


local function authorString(item, namequalifiers, pid)
	pid = pid or P_AUTHOR -- set default property
	local concatstr = ', '
	local authorids = qualifierTargetId(namequalifiers, pid) -- get qualifiers
	if not next(authorids) then -- no qualifiers found, check properties
		local authorset = targetId(item, pid)
		local authors = {}
		if authorset then -- create list from set
			authorids = {}
			for author,_ in pairs(authorset) do
				table.insert(authorids, author)
			end
		end
	end
	local authors = createLinks(authorids, true)
	if next(authors) then
		local authorstr = ''
		local comma = false
		for i = #authors, 1, -1 do
			local sep = ''
			if i > 1 then
				sep = ' '
				if authorids[i] ~= ET_AL then
					sep = comma and ', ' or ' & '
					comma = true
				end
			end
			authorstr = sep .. authors[i] .. authorstr
		end
		return authorstr
	end
end
L.authorString = authorString


-- create the taxon authors string, including year, ex authors and authors of the basionym
local function createAllAuthorsStr(item, namequalifiers, year)
	local authors = authorString(item, namequalifiers)
	local authorsstr = ''
	if authors or not year == '????' then
		if code == NOMENCLATURE_ICNafp then
			-- check for basionym
			local basionymids = targetId(item, P_BASIONYM)
			local basionymstr = ''
			if next(basionymids) then
				local basionym = mw.wikibase.getEntity('Q' .. next(basionymids))
				local _,basionymnamequalifiers = targetStr(basionym, P_TAXON_NAME)
				basionymstr = createAllAuthorsStr(basionym, basionymnamequalifiers)
				if basionymstr ~= '' then
					basionymstr = '(' .. basionymstr .. ') '
				else
					-- indicate missing basionym author
					basionymstr = '(????) '
				end
			end

			-- check ex-authors
			local exauthors = authorString(nil, namequalifiers, P_EX_AUTHOR)
			exauthorsstr = ''
			mw.log(exauthors)
			if exauthors then
				exauthorsstr = exauthors .. ' ex '
			end
			authorsstr = basionymstr .. exauthorsstr .. authors
			if year then
				authorsstr = authorsstr .. ' (' .. year .. ')'
			end
		else
			if year then
				authorsstr = authors .. ', ' .. year
			end

			-- parentheses needed if instance of recombination
			local recombination = false
			for _,tid in pairs(qualifierTargetId(namequalifiers, P_INSTANCE_OF)) do
				if tid == RECOMBINATION then
					recombination = true
				end
			end

			if recombination then
				authorsstr = '(' .. authorsstr .. ')'
			end
		end
	else
		-- check for original combination
		local basionymids = targetId(item, P_ORIGINAL_COMBINATION)
		local basionymstr = ''
		if next(basionymids) then
			local basionym = mw.wikibase.getEntity('Q' .. next(basionymids))
			local _, basionymnamequalifiers = targetStr(basionym, P_TAXON_NAME)
			local pubyear = qualifierTargetValue(basionymnamequalifiers, P_TAXON_YEAR) or
				targetValue(basionym, P_TAXON_YEAR)
			-- access year in time representation "+1758-00-00T00:00:00Z"
			local year = pubyear and string.sub(pubyear.time, 2, 5) or '????'
			basionymstr = createAllAuthorsStr(basionym, basionymnamequalifiers, year)
			if basionymstr ~= '' then
				basionymstr = '(' .. basionymstr .. ') '
			end
		end
		authorsstr = basionymstr
	end
	return authorsstr
end
L.createAllAuthorsStr = createAllAuthorsStr

-- show the stratigraphic range in which an extinct fossil existed
local function fossilParams(item, params)
	local era1, era1references = next(targetId(item, P_ERA_START))
	local era2, era2references = next(targetId(item, P_ERA_END))
	local era1value = targetValue(item, P_START_TIME)
	local era1time = era1value and era1value.time
	local era2value = targetValue(item, P_END_TIME)
	local era2time = era2value and era2value.time
	if era1 and not (era1 == 'novalue') or era1time and not (era1time == "0") then
		if era1 and not (era1 == 'novalue') then
			params["era[1][label]"] = getLabel(era1)
			params["era[1][id]"] = era1
		end
		if era1time and not (era1time == "0") then
			local year = string.match(era1time, "^-*(%d-)-")
			params["era[1][time]"] = year and tonumber(year) / 1000000
		end
		if era2 and not (era2 == 'novalue') then
			params["era[2][id]"] = era2
			params["era[2][label]"] = params["era[1][label]"]
			if not (era1 == era2) then
				params["era[2][label]"] = getLabel(era2)
			end
		end
		if era2time and not (era2time == "0") then
			local year = string.match(era2time, "^-*(%d-)-")
			params["era[2][time]"] = year and tonumber(year) / 1000000
		elseif era1time and not era2 and not (era2 == 'novalue') then
			params["era[2][time]"] = "0"
		end

		-- merge references from era2 to era1, only show once
		if era2 and not (era2 == 'novalue') and era2references and era2references.refids then
			for a, b in pairs(era2references.refids) do
				era1references.refids[a] = b
			end
		end

		-- TODO: return data structure instead of pure str here
		if era1references and era1references.refids then
			params["era[references]"] = era1references.refids
		end
	end
end
L.fossilParams = fossilParams


-- returns html for the given refids set
-- parameters:
-- refids: list of integer ID to create a list of <ref>-references
local function references(refids)
	local frame = mw.getCurrentFrame()
	local refstr = ''
	if refids then
		for id,_ in pairs(refids) do
			local ret = id
			-- local ref = Cite.citeitem(id, getLang()) or 'Error during creation of citation. Please report [[' .. id .. ']] at [[Module_talk:Cite]]'
			mw.log('refstr for ', id, ref)
			refstr = refstr .. frame:extensionTag('ref', ref, {name=id})
		end
	end
	return refstr
end
L.references = references


local function i18nByLatin(ranklatin, str, default)
	local suc, format = pcall(i18n, str .. "-" .. ranklatin)
	if not suc then
		format = default
	end
	return format
end
L.i18nByLatin = i18nByLatin


local function formatScientificName(ranklatin, scientific, short)
	local pf = "scientific-name"
	if short then
		pf = "short-" .. pf
	end
	scipattern = i18nByLatin(
		ranklatin, pf .. "-pattern", i18n(pf .. "-pattern"))
	scirepl = i18nByLatin(
		ranklatin, pf .. "-repl", i18n(pf .. "-repl"))
--	scientific = string.gsub(scientific, scipattern, scirepl)
	
	for scipattern, scirepl in pairs(
		i18nByLatin(ranklatin, pf .. "-replaces", i18n(pf .. "-replaces"))) do
		scientific = string.gsub(scientific, scipattern, scirepl)
	end
	return scientific
end
L.formatScientificName = formatScientificName


local function renderTableHead(text, color, extra_css)
	local css = "text-align: center;"
	if extra_css then
		css = css .. " " .. extra_css
	end
	if color then
		css = css .. " background-color: " .. color .. ";"
	end
	return mw.text.tag('tr', {}, mw.text.tag('th', {
		colspan='2', style=css}, text))
end
L.renderTableHead = renderTableHead


local function renderTableRow(text, extra_css)
	local css = "text-align: center;"
	if extra_css then
		css = css .. " " .. extra_css
	end
	return mw.text.tag('tr', {}, mw.text.tag('td',
		{colspan='2', style=css}, text))
end
L.renderTableRow = renderTableRow


local function renderFossilEra(params)
	local eralink = {}
	local refstr = references(params["era[references]"])
	for i = 1, 2 do
		local eraid = params[string.format("era[%d][id]", i)]
		if eraid then
			eralink[#eralink + 1] = getLink(eraid)
		end
	end
	local separator = i18n("era-separator")
	return renderTableHead(getLink(GEOLOGICAL_ERA) .. refstr, params.color) ..
		renderTableRow(table.concat(eralink, separator))
end
L.renderFossilEra = renderFossilEra


local function renderIUCNStatus(params)
	local r = {}
	local refstr = references(params["iucn_status[references]"])
	r[#r + 1] = renderTableHead(
		getLink(RED_DATA_LIST, getLabel(P_IUCN_STATUS)) .. refstr, params.color)
	r[#r + 1] = renderTableRow(
		"[[File:" .. params["iucn_status[image]"] ..
		"|lang=" .. getLang() ..
		"|220px|" .. params["iucn_status[label]"] .. "]]")
	return table.concat(r)
end
L.renderIUCNStatus = renderIUCNStatus


local function formatTaxon(
	latin, qid, scientific, vernacular, is_subject, is_extinct)
	local nameformat
	scientific = scientific or i18n("no-scientific-name")
	local scientificshort = scientific
	if latin then
		scientificshort = formatScientificName(latin, scientific, code ~= NOMENCLATURE_ICVCN)
		scientific = formatScientificName(latin, scientific)
	end
	local nf = "item-format-parent"
	if is_subject then
		nf = "item-format-current"
	end
	if vernacular then
		nameformat = i18n(nf .. "-with-vernacular-name")
	else
		nameformat = i18n(nf .. "-without-vernacular-name")
	end
	local link = qid
	if _linkconfig == "sitelink" then
		link = mw.wikibase.sitelink(qid) or "d:" .. qid
	end
	if is_extinct then
		nameformat = i18n("extinct-mark") .. nameformat
	end
	return namedStringFormat{
		nameformat, link=link, vernacular=vernacular,
		scientific=scientific, scientificshort=scientificshort},
		scientific
end
L.formatTaxon = formatTaxon


local function renderRank(i, params)
	local row
	local detailrows = {}
	local pf = string.format("rank[%d]", i)
	local ranklink = i18n("unknown-rank")
	local rankid = params[pf .. "[id]"]
	local ranklatin = params[pf .. "[latin]"]
	local is_subject = params[pf .. "[is_subject]"]
	local scientific = params[pf .. "[scientific]"]
	local formatted = params[pf .. "[taxon]"]
	
	if code == NOMENCLATURE_ICVCN and rankid == 22666877 then -- superdomain
		return nil, detailrows
	end
	if code == NOMENCLATURE_ICVCN and ranklatin == "regnum" then
		ranklink = getLink(VIRUS_CLASSIFICATION, i18n("virus-group-rank"), i18n("rank-format"), true, true)
		local virusgenome = params["virus[genome]"]
		if virusgenome then
			local group = virusgroups[virusgenome] and virusgroups[virusgenome].group
			local shortlabel = virusgroups[virusgenome] and virusgroups[virusgenome].shortlabel or ""
			local qid = "Q" .. virusgenome
			local link = (_linkconfig == "sitelink")
				and (mw.wikibase.sitelink(qid) or "d:" .. qid)
				or qid
			local label = getLabel(virusgenome)
			formatted = namedStringFormat{
				i18n("virus-item-" .. (group and "with" or "without") .. "-group"),
				group = group, link = link, shortlabel = shortlabel, label = label}
		else
			formatted = i18n("unknown-group")
		end
	else
		if rankid then
			local linkformat = i18nByLatin(
				ranklatin, "rank-format", i18n("rank-format"))
			ranklink = getLink(rankid, nil, linkformat, true, true)
		end
		if is_subject then
			local refstr = references(params[pf .. "[references]"])
			local authority = params[pf .. "[authority]"]
			if code ~= NOMENCLATURE_ICVCN then
				detailrows = {
					renderTableHead(capitalize(
						string.format(i18n("scientific-name-of-taxon"), getLabel(rankid)))
						.. refstr, params.color
					),
					renderTableRow(scientific),
					renderTableRow(authority, "font-variant: small-caps;")
				}
			elseif authority then
				detailrows = {renderTableRow(authority, "font-variant: small-caps;")}
			end
		end
	end
	
	row = mw.text.tag(
		'tr', {style="vertical-align: top;"},
		mw.text.tag('td', {}, ranklink) ..
		mw.text.tag('td', {}, formatted))
	return row, detailrows
end
L.renderRank = renderRank


-- in case of more than one parent taxa or rank: choose target according to the
-- references selected by usereferences
local function chooseByRef(item, property)
	local cand
	local nextparent = {}
	for id,refs in pairs(targetId(item, property)) do

		-- some taxon, like Q2382443, the parent taxon is null
		local novalue = id == "novalue"

		-- try to find match from usetaxa
		if not novalue and usetaxa["Q" .. id] then
			table.insert(nextparent, {usetaxa["Q" .. id], id, refs})
		end

		-- or according to usereferences
		if refs and refs.refids and type(refs.refids) ~= "boolean"  then
			for r, i in pairs(usereferences) do
				if refs.refids[r] then
					table.insert(nextparent, {i + usetaxa.size, id, refs})
				end
			end
		end

		if not novalue and not cand then -- if no item had references yet
			cand = {nil, id, refs} -- use this
		end
	end
	-- nextparent is not sorted, so sort it
	table.sort(nextparent, function(a, b)
		return a[1] < b[1]
	end)

	if next(nextparent) then
		_, cand = next(nextparent)
	end

	if cand and cand[1] == nil and cand[3] and cand[3].refids then
		for targetid, _ in pairs(cand[3].refids) do
			usereferences.size = usereferences.size + 1
			usereferences[targetid] = usereferences.size
		end
	end
	if cand then
		return cand[2], cand[3] or {}
	else
		return nil, {}
	end
end
L.chooseByRef = chooseByRef


-- Find out if this taxon is extinct already
local function isExtinct(item)

	-- check IUCN status
	local statusid, _ = next(targetId(item, P_IUCN_STATUS))
	if statusid == EXTINCT then
		return true
	end

	-- check temporal range end
	local eraend, _ = next(targetId(item, P_ERA_END))
	if eraend and not eraend == 'novalue' then
		return true
	end

	-- check end time
	local endtime = targetValue(item, P_END_TIME)
	if endtime then
		return true
	end
	
	-- check instance of fossil taxon
	if next(targetId(item, P_INSTANCE_OF)) == FOSSIL_TAXON then
		return true
	end

	return false
end
L.isExtinct = isExtinct


-- Find out if the item is a monotypic taxon
local function isMonotypic(item)
	return next(targetId(item, P_INSTANCE_OF)) == MONOTYPIC_TAXON
end
L.isMonotypic = isMonotypic


local function renderSynonyms(params)
	local pf = string.format("rank[%d]", params["rank[size]"])
	local ranklatin = params[pf .. "[latin]"]
	local rows = ""
	for i = 1, params["synonym[size]"] do
		pf = string.format("synonym[%d]", i)
		local synonym = namedStringFormat{i18n("item-format-synonym-render"),
			link = params[pf .. "[link]"],
			scientific = formatScientificName(ranklatin, params[pf .. "[name]"]),
			author = params[pf .. "[author]"]}
		rows = rows .. mw.text.tag('li', {}, synonym)
	end
	return rows
end
L.renderSynonyms = renderSynonyms


local function fetchDetails(qid, item, include_basionym_author, fetch_author)
	item = item or mw.wikibase.getEntity(qid)
	local name, namequalifiers, namereferences = targetStr(item, P_TAXON_NAME)
	local link = qid
	if _linkconfig == "sitelink" then
		link = mw.wikibase.sitelink(qid) or "d:" .. qid
	end
	local authorsstr
	if fetch_author then
		local pubyear = qualifierTargetValue(namequalifiers, P_TAXON_YEAR) or
			targetValue(item, P_TAXON_YEAR)
		-- access year in time representation "+1758-00-00T00:00:00Z"
		local year = pubyear and string.sub(pubyear.time, 2, 5) or '????'
		-- basionym author can be suppressed by not providing an item to search in:
		authorsstr = createAllAuthorsStr(include_basionym_author and item, namequalifiers, year)
	end
	return name, link, authorsstr, namereferences
end
L.fetchDetails = fetchDetails


local function getRank(item, id)
	local rankid = id or chooseByRef(item, P_TAXON_RANK)
	local ranklatin
	if not rankid or rankid == "novalue" then
		rankid = CLADE
	end
	if rankid then
		ranklatin = getLabel(rankid, 'la')
		ranklatin = ranklatin and mw.ustring.lower(ranklatin)
		if rankid == ZOOSECTIO or rankid == ZOOSUBSECTIO then
			ranklatin = 'zoo' .. ranklatin
		end
	end
	return rankid, ranklatin
end


local function taxonParams(qid, item, params, fetch_detail, is_subject, is_parent_extinct, incertae_sedis_ranks)
	local rankid
	local ranklatin
	local level = params["rank[size]"] + 1
	local pf = string.format("rank[%d]", level)
	local is_extinct = is_parent_extinct or isExtinct(item)

	if #incertae_sedis_ranks > 0 then
		local incertae_sedis_vernacular = getLabel(INCERTAE_SEDIS)
		local raw_scientific = getLabel(INCERTAE_SEDIS, 'la')
		local incertae_sedis_formatted = getLink(INCERTAE_SEDIS, nil, i18n("item-format-incertae-sedis"), true)
		for i = #incertae_sedis_ranks, 1, -1 do
			rankid, ranklatin = getRank(nil, incertae_sedis_ranks[i])
			if not (hideranks[rankid] or hideranks[ranklatin]) then
				params["rank[size]"] = level
				params[pf .. "[id]"] = rankid
				params[pf .. "[link]"] = "Q" .. INCERTAE_SEDIS
				params[pf .. "[vernacular]"] = incertae_sedis_vernacular
				params[pf .. "[raw_scientific]"] = raw_scientific
				params[pf .. "[latin]"] = ranklatin
				params[pf .. "[scientific]"] = raw_scientific
				params[pf .. "[taxon]"] = incertae_sedis_formatted
				level = level + 1
				pf = string.format("rank[%d]", level)
			end
		end
	end

	local name, link, authorsstr, namereferences = fetchDetails(qid, item, true, fetch_detail)
	params[pf .. "[references]"] = namereferences
	params[pf .. "[authority]"] = authorsstr
	local vernacular = vernacularName(item)

	rankid, ranklatin = getRank(item)
	if rankid == SUBGENUS and
		string.match(name, "^%w+$") and
		params[string.format("rank[%d][id]", level - 1)] == GENUS then
		-- follow ICZN to prepend genus name in front of subgenus name
		name = string.format("%s (%s)",
			params[string.format("rank[%d][raw_scientific]", level - 1)],
			capitalize(mw.ustring.lower(name)))
	end

	if (hideranks[rankid] or hideranks[ranklatin]) and not usetaxa[qid] then
		-- interrupt since this rank has been hided from display
		return params, is_extinct
	end

	name = name and capitalize(name)
	local ranklatinformat = (code == NOMENCLATURE_ICVCN) and "virus" or ranklatin
	local formatted, sciname = formatTaxon(
		ranklatinformat, qid, name, vernacular, is_subject, is_extinct)

	params["rank[size]"] = level
	params[pf .. "[id]"] = rankid
	params[pf .. "[link]"] = qid
	params[pf .. "[is_monotypic]"] = isMonotypic(item)
	params[pf .. "[vernacular]"] = vernacular
	params[pf .. "[raw_scientific]"] = name
	params[pf .. "[latin]"] = ranklatin
	params[pf .. "[is_extinct]"] = is_extinct
	params[pf .. "[scientific]"] = sciname
	params[pf .. "[is_subject]"] = fetch_detail
	params[pf .. "[taxon]"] = formatted

	return params, is_extinct
end
L.taxonParams = taxonParams


-- performs the loop up the hierarchy using P_TAXON_PARENT
local function iterateRanks(
		qid, count, fetch_detail, child_detailed, child_extinct, params)
	local params = params or {["rank[size]"] = 0, ["synonym[size]"] = 0}
	local item = mw.wikibase.getEntity(qid)

	name = targetStr(item, P_TAXON_NAME)
	if name == 'nil' then
		return params, {}, item
	end

	if not code then
		codeid = next(targetId(item, P_NOMENCLATURE_CODE))
		if codeid and colors[codeid] then
			code = codeid
		end
	end
	if not subcode and colors[tonumber(string.match(qid, "^Q(%d+)"))] then
		subcode = tonumber(string.match(qid, "^Q(%d+)"))
	end
	params["code"] = params["code"] or subcode or code
	params["color"] = colors[params["code"]]

	if not params["virus[genome]"] then
		local genomeid, genomereferences = next(targetId(item, P_VIRUS_GENOME))
		params["virus[genome]"] = genomeid
		if genomereferences and genomereferences.refids then
			params["virus[references]"] = genomereferences.refids
		end
	end

	local nextid, refsquals = chooseByRef(item, P_TAXON_PARENT)
	mw.log('nextid', nextid)
	if visited[nextid] then -- loop detection
		return params, {}, item
	elseif nextid then
		visited[nextid] = true
	end

	local is_subject = fetch_detail
	-- Monotypic taxon can contain extinct taxa,
	-- should not fetch detail in such circumstances
	-- for example: [[Q7105303]]
	local fetch_detail =
		fetch_detail or
		(child_detailed and not child_extinct and isMonotypic(item))
	local is_extinct, is_parent_extinct

	if nextid and (not code or count > 0) then
		params, refs, _, is_parent_extinct = iterateRanks(
			'Q' .. nextid, count - 1, false,
			fetch_detail, isExtinct(item), params)
		if refs then
			for ref, _ in pairs(refs) do
				refsquals.refids[ref] = true
			end
		end
	end
	if count > 0 then
		incertae_sedis_ranks = qualifierTargetId(refsquals.qualifiers, P_INCERTAE_SEDIS)
		params, is_extinct = taxonParams(
			qid, item, params, fetch_detail, is_subject, is_parent_extinct, incertae_sedis_ranks)
	end
	return params, refsquals.refids, item, is_extinct
end
L.iterateRanks = iterateRanks


local function cladusPostfixes(n)
	local plus = ""
	for i = 1, n do
		plus = plus .. "+"
	end
	return plus
end


-- use arguments from second table to override the first table
-- support classical {{taxobox}} parameters like "species", "unranked_ordo"
local function overrideParams(params, overrides)
	overrides = overrides or {}

	for key, val in pairs(overrides) do
		params[key] = overrides[key] or params[key]
	end

	-- classical taxonomic rank params
	local unranked = {}
	for i = 1, params["rank[size]"] do
		local pf = string.format("rank[%d]", i)
		local latin = params[pf .. "[latin]"]
		if latin == "clade" then
			unranked[#unranked + 1] = i
		else
			local txarg = pf .. "[taxon]"
			local atarg = pf .. "[authority]"
			params[txarg] = latin and overrides[latin] or params[txarg]
			params[atarg] = latin and overrides[latin .. "_authority"] or params[atarg]
			for j = #unranked, 1, -1 do
				local txarg = string.format("rank[%d][taxon]", unranked[j])
				local atarg = string.format("rank[%d][authority]", unranked[j])
				local argname = string.format("unranked_%s", latin)
				if j == #unranked then
					params[txarg] = overrides[argname] or overrides[argname .. "1"] or 
						overrides[latin .. "+"] or params[txarg]
					params[atarg] = overrides[argname .. "_authority"] or
						overrides[argname .. "1_authority"] or
						overrides[latin .. "+_authority"] or params[atarg]
				else
					params[txarg] = overrides[string.format('%s%d', argname, #unranked - j + 1)]
						or overrides[latin .. cladusPostfixes(#unranked - j + 1)]
						or params[txarg]
					params[atarg] = overrides[string.format('%s%d_authority', argname, #unranked - j + 1)]
						or overrides[latin .. cladusPostfixes(#unranked - j + 1) .. "_authority"]
						or params[atarg]
				end
			end
			unranked = {}
		end
		if latin == "species" then
			local scarg = pf .. "[scientific]" 
			params[scarg] = overrides["binomial"] or params[scarg]
		elseif ({subspecies=true, varietas=true, forma=true})[latin] then
			local scarg = pf .. "[scientific]" 
			params[scarg] = overrides["trinomial"] or params[scarg]
		end
	end

	return params
end
L.overrideParams = overrideParams


local function getTypeTaxon(qid, item, params)
	item = item or mw.wikibase.getEntity(qid)
	params["image"] = params["image"] or targetStr(item, P_IMAGE)
	params["audio"] = params["audio"] or targetStr(item, P_AUDIO)

	local id, refsquals = next(targetId(item, P_TAXONOMIC_TYPE))
	if not id then return params, nil end

	local typeitem = mw.wikibase.getEntity("Q" .. id)
	local ranklatin
	local rankid = next(targetId(typeitem, P_TAXON_RANK))
	if rankid then
		ranklatin = mw.wikibase.getEntity('Q' .. rankid):getLabel('la')
		if ranklatin then
			ranklatin = mw.ustring.lower(ranklatin)
		end
	end
	if not (ranklatin == "genus" or ranklatin == "species") then return params, nil end
	if ranklatin == "genus" then
		params = getTypeTaxon("Q" .. id, typeitem, params)
	end

	local name, link, authorsstr, namereferences = fetchDetails("Q" .. id, nil, true, true)
	if namereferences then
		for ref, _ in pairs(namereferences) do
			refsquals.refids[ref] = true
		end
	end
	params["type[" .. ranklatin .. "][name]"] = name
	params["type[" .. ranklatin .. "][link]"] = link
	params["type[" .. ranklatin .. "][authority]"] = authorsstr
	params["type[" .. ranklatin .. "][references]"] = refsquals and refsquals.refids

	return params
end
L.getTypeTaxon = getTypeTaxon


local function iterateSynonyms(qid, item, params)
	visited[tonumber(string.match(qid, "^Q(%d+)"))] = true

	params["image"] = params["image"] or targetStr(item, P_IMAGE)
	params["audio"] = params["audio"] or targetStr(item, P_AUDIO)
	params["range_map"] = params["range_map"] or targetStr(item, P_SPREAD_MAP)
	if not (params["era[1][label]"] or params["era[1][time]"]) then
		fossilParams(item, params)
	end
	if not params["iucn_status[id]"] then
		local statusid, statusreferences = next(targetId(item, P_IUCN_STATUS))
		if statusid then
			params["iucn_status[id]"] = statusid
			params["iucn_status[references]"] = statusreferences.refids
			local status = mw.wikibase.getEntity("Q" .. statusid)
			params["iucn_status[image]"] = targetStr(status, P_IMAGE)
			params["iucn_status[label]"] = getLabel(status)
		end
	end
	if not params["rank[" .. params["rank[size]"] .. "][latin]"] then
		local rankid, ranklatin = getRank(item)
		params["rank[" .. params["rank[size]"] .. "][id]"] = rankid
		params["rank[" .. params["rank[size]"] .. "][latin]"] = ranklatin
	end

	local synonyms = {}
	-- forward synonym properties
	for _, property in pairs({P_BASIONYM, P_SYNONYM, P_REPLACED_SYNONYM, P_ORIGINAL_COMBINATION}) do
		for id, refsquals in pairs(targetId(item, property)) do
			synonyms[id] = refsquals
		end
	end
	-- inverse synonym properties
	for _, property in pairs({P_SUBJECT_ROLE, P_INSTANCE_OF}) do
		for id, refsquals in pairs(targetId(item, property)) do
			if (id == PROTONYM or id == BASIONYM or id == SYNONYM_TAXON) and refsquals and refsquals.qualifiers then
				for _, protoid in pairs(qualifierTargetId(refsquals.qualifiers, P_OF)) do
					synonyms[protoid] = refsquals
				end
			end
		end
	end

	for id, refsquals in pairs(synonyms) do
		if id and not visited[id] then -- loop detection
			local synonym_qid = "Q" .. id
			local synonym_item = mw.wikibase.getEntity(synonym_qid)
			local refs
			params, refs = iterateSynonyms(synonym_qid, synonym_item, params)
			
			local name, link, authorsstr, namereferences = fetchDetails(synonym_qid, synonym_item, true, true)
			local level = params["synonym[size]"] + 1
			local pf = string.format("synonym[%d]", level)
			params[pf .. "[link]"] = link
			params[pf .. "[name]"] = name or getLabel(synonym_item)
			params[pf .. "[author]"] = authorsstr
			params["synonym[size]"] = level
			
			if refs then
				for ref, _ in pairs(refs) do
					refsquals.refids[ref] = true
				end
			end
			if namereferences then
				for ref, _ in pairs(namereferences) do
					refsquals.refids[ref] = true
				end
			end
		end
	end
	local ret_refs = refsquals and refsquals.refids
	return params, ret_refs
end
L.iterateSynonyms = iterateSynonyms


-- fetch params should passed to taxobox for the given qid (e.g., qid=Q729412
-- for Heloderma) and count higher levels of the taxon hierarchy.
-- developers: use this method for tests in the debug console, e.g.,
-- p.localFunction("getTaxoboxParams")('Q729412', 5)
local function getTaxoboxParams(qid, count)
	visited = {}
	local params, references, item = iterateRanks(qid, count, true)
	if params["rank[size]"] == 0 then
		return {}
	end
	params["rank[references]"] = references

	local scarg = string.format("rank[%d][scientific]", params["rank[size]"])
	local vnarg = string.format("rank[%d][vernacular]", params["rank[size]"])
	params["name"] = params[vnarg] or params[scarg]

	params, synonym_references = iterateSynonyms(qid, item, params)
	params["synonym[references]"] = synonym_references
	params = getTypeTaxon(qid, item, params)

	return params
end
L.getTaxoboxParams = getTaxoboxParams


local function callbackTaxobox(template, params, overrides, dryrun)
	local content = {}
	local frame = mw.getCurrentFrame()
	params = overrideParams(params, overrides)

	for key, val in pairs(params) do
		if type(val) == "boolean" then
			val = val and "yes" or "no"
		elseif type(val) == "table" and
			string.match(key, "%[references%]$") then
			local refs = {}
			for r, _ in pairs(val) do
				table.insert(refs, r)
			end
			val = table.concat(refs, " ")
		end
		if dryrun then
			content[#content + 1] = string.format(
				"|%s = %s", key, val)
		else
			params[key] = val
		end
	end

	if dryrun then
		table.sort(content, function(a, b)
			a = string.gsub(a, "%[(%d+)%]", function(i)
				return "[" .. string.rep("0", 3 - #i) .. i .. "]"
			end)
			b = string.gsub(b, "%[(%d+)%]", function(i)
				return "[" .. string.rep("0", 3 - #i) .. i .. "]"
			end)
			return a < b
		end)
		content = "{{" .. template .. "\n" ..
			table.concat(content, "\n") .. "\n}}\n"
		content = frame:callParserFunction("#tag", "nowiki", content)
		return mw.text.tag("pre", {}, content)
	else
		return frame:expandTemplate{title=template, args=params}
	end
end
L.callbackTaxobox = callbackTaxobox


-- creates the taxobox from giving params
local function renderTaxobox(params, overrides)
	local content = {}
	params = overrideParams(params, overrides)

	local color = params.color

	-- title
	content[#content + 1] = renderTableHead(params.name, color)

	-- image
	if params.image then
		content[#content + 1] = renderTableRow(
			"[[File:" .. params.image .. "|lang=" .. getLang() .. "|220px|.]]")
	end

	-- fossil era
	if params["era[1][id]"] then
		content[#content + 1] = renderFossilEra(params)
	end

	-- ranks
	if params["rank[size]"] > 0 then
		local refstr = references(params["rank[references]"]) or ""
		content[#content + 1] = renderTableHead(
			capitalize(params["virus[genome]"]
				and getLink(VIRUS_CLASSIFICATION, nil, nil, nil, true)
				or  getLink(SYSTEMATICS, nil, nil, nil, true))
			.. refstr, color)
		local taxondetails = {}
		for i = 1, params["rank[size]"] do
			local row, detailrows = renderRank(i, params)
			content[#content + 1] = row
			taxondetails[#taxondetails + 1] = table.concat(detailrows)
		end
		content[#content + 1] = table.concat(taxondetails)
	end
	
	-- synonyms
	if params["synonym[size]"] > 0 then
		local refstr = references(params["synonym[references]"])
		content[#content + 1] = renderTableHead(
			capitalize(getLink(SYNONYM_TAXON, nil, nil, nil, true)) .. refstr, color)
		content[#content + 1] = mw.text.tag(
			'tr', {colspan='2'}, mw.text.tag(
				'td', {colspan='2'}, mw.text.tag(
					'ul', {}, renderSynonyms(params))))
	end
	
	-- type taxons
	for header, rank in pairs({[TYPE_GENUS] = "genus", [TYPE_SPECIES] = "species"}) do
		if params["type[" .. rank .. "][name]"] then
			local refstr = references(params["type[" .. rank .. "][references]"])
			content[#content + 1] = renderTableHead(
				capitalize(getLink(header, nil, nil, nil, true)) .. refstr, color)
			local typetaxon = namedStringFormat{i18n("item-format-parent-without-vernacular-name"),
				link = params["type[" .. rank .. "][link]"],
				scientific = formatScientificName(rank, params["type[" .. rank .. "][name]"])}
			content[#content + 1] = renderTableRow(typetaxon)
			content[#content + 1] = renderTableRow(params["type[" .. rank .. "][authority]"], "font-variant: small-caps;")
		end
	end

	-- subdivision
	if params.subdivision and params.subdivision ~= "" then
		content[#content + 1] = renderTableHead(i18n('subdivision-ranks'), color)
		content[#content + 1] = mw.text.tag(
			'tr', {colspan='2'}, mw.text.tag(
				'td', {colspan='2', style=css}, params["subdivision"]))
	end

	-- range map
	if params.range_map then
		content[#content + 1] = renderTableHead(i18n('range-map'), color)
		content[#content + 1] = renderTableRow(
			"[[File:" .. params.range_map .. "|lang=" .. getLang() .. "|220px|.]]")
	end

	-- iucn status
	if params["iucn_status[id]"] then
		content[#content + 1] = renderIUCNStatus(params)
	end

	-- audio
	if params.audio then
		content[#content + 1] = renderTableHead(
			getLink(P_AUDIO, nil, nil, nil, true), color)
		content[#content + 1] =
			renderTableRow("[[File:" .. params.audio .. "]]")
	end

	return mw.text.tag('table', {
		style = [[
			width: 200px; border-width: 1px; float: right;
			border-style: solid; background-color: #f9f9f9;
		]]
	}, table.concat(content))
end
L.renderTaxobox = renderTaxobox


if debug then
	function p.debugParams(params)
		mw.log("Start of logging params")
		mw.log(string.rep("=", 20))
		for k, v in pairs(params) do
			mw.log(k, v)
		end
		mw.log("End of logging params")
	end

	function p.localFunction(name)
		return L[name]
	end
end


function p.taxobox(frame)
	local config = parseConfig(frame.args)
	local qid = frame.args.qid or mw.wikibase.getEntityIdForCurrentPage()
	local params = getTaxoboxParams(qid, config.count)
	return renderTaxobox(params, frame.args)
end


function p.callback(frame)
	local config = parseConfig(frame.args)
	local qid = frame.args.qid or mw.wikibase.getEntityIdForCurrentPage()
	local template = frame.args.template or "Taxobox"
	local params = getTaxoboxParams(qid, config.count)
	return callbackTaxobox(template, params, frame.args, config.dryrun)
end

function p.callback2(qid)
	local template = "Taxobox"
	local params = getTaxoboxParams(qid, 10)
	return callbackTaxobox(template, params, {}, true)
end

function p.printr(el, lim, sp)
	lim = lim or 10
	sp = sp or ""
	if (lim <= 0) then
		return "\n"
	end
	tp = type(el)
	if (tp ~= "table") then
		return sp .. tostring(el) .. "\n"
	end
	tmp = ""
	for k,v in pairs(el) do
		tmp = tmp .. p.printr(v, lim-1, sp .. " [" .. tostring(k) .. "] ")
	end
	return tmp
end

p.rangs = {
	[1] = {''},
}

-- table des contraintes de classification, par niveau
-- pour chaque une plage de rangs (cf table rangs) avec en valeur
-- les champs : 'doit' = 'Qxxx' → classification obligatoire pour ces rangs sinon erreur
--              'pref' = { 'Qxxx', 'Qxxx' } → table de préférences, dans l'ordre
-- si rien ne correspond (pas de rang décrit) alors la première trouvée est utilisée
p.sources = {
	["ReptileDB"] = {
		
	}
}


-- retourne la valeur ou nil
function p.valeur(root, elements)
	if not root then
		return nil -- problème
	end
	if not elements[1] then
		return nil -- on est vide
	end
	if not root[elements[1]] then
		return nil -- élément en cours non trouvé
	end
	val = table.remove(elements, 1)
	if not elements[1] then
		if (type(val) == 'table') then
			return val -- c'était le dernier donc la valeur (index ici)
		else
			return root[val]
		end
	end
	-- récursion
	return p.valeur(root[val], elements)
end

-- fonction qui recherche une valeur, potentiellement dans une liste d'éléments,
-- et avec une condition potentielle. Retourne l'index si condition (ou 1)
-- si tous=TRUE (et que présence d'un index) retourne une table de tous les
-- éléments qui correspondent
function p.recherche(item, elements, conditions, vcond, tous)
	if (not item) then
		return nil, 1
	end
	local index = 1
	if not conditions and not tous then
		return p.valeur(item, elements), 1 -- pas de condition : simple
	end
	-- présence d'un index ?
	local idx = nil
	for i, v in ipairs(elements) do
		if v == '%' then
			idx = i
			break
		end
	end
	-- si pas d'index et 'tous' demandé → problème
	if not idx and tous then
		return nil, 1
	end
	-- pas d'index, on récupère la première valeur si présente
	if not idx then
		val = p.valeur(item, elements)
		if not val then
			return nil, 1  -- de toute façon la valeur n'est pas présente
		end
		cond = p.valeur(item, conditions)
		if cond ~= vcond then
			return nil, 1  -- condition non trouvée ou différente
		end
		return cond
	end
	-- index : il faut faire une boucle sur l'index
	local retour = {}
	if conditions then
		local i = 1
		elements[idx] = i  -- on stocke l'index courant
		conditions[idx] = i
		tmp = p.valeur(item, conditions)
		while tmp do
			if (tmp == vcond) then
				val = p.valeur(item, elements)
				if (not tous) then
					return val, i -- condition trouvée : on retourne la valeur
				else
					table.insert(retour, {val, i})
				end
			end
			-- sinon on passe à la suivante
			i = i + 1
			elements[idx] = i
			conditions[idx] = i
			tmp = p.valeur(item, conditions)
		end
	else
		local i = 1
		elements[idx] = i  -- on stocke l'index courant
		tmp = p.valeur(item, elements)
		while tmp do
			table.insert(retour, {tmp, i})
			-- sinon on passe à la suivante
			i = i + 1
			elements[idx] = i
			tmp = p.valeur(item, elements)
		end
	
	end
	-- retours
	if (not tous) then
		-- non trouvé si on arrive ici
		return nil, 1
	else
		-- on retourne la table (à charge du receveur de vérifier que non vide)
		return retour
	end
end

-- retourne une table pour IUCN
-- { 'id' = identifiant IUCN ; 'url' = URL vers le taxon chez IUCN
--   'image' = image du statut (fr ou en) ; 'wiki' = wikilien en français vers
--   le statut (ou nil) }
function p.statut_iucn(id, qstatut)
	local out = {}
	
	out['id'] = id
	local item = mw.wikibase.getEntity('P627') -- IUCN
	local tmp = p.recherche(item, {'claims', 'P1630', 1, 'mainsnak', 'datavalue', 'value'})
	if tmp then
		out['url'] = string.gsub(tmp, "$1", id)
	end
	item = mw.wikibase.getEntity(qstatut)
	if item then
		tmp = p.recherche(item, {'claims', 'P18', '%', 'mainsnak', 'datavalue', 'value'},
			                    {'claims', 'P18', '%', 'qualifiers', 'P407', 1, 'datavalue', 'value', 'id'}, 'Q150')
		if not tmp then
			tmp = p.recherche(item, {'claims', 'P18', '%', 'mainsnak', 'datavalue', 'value'},
			                        {'claims', 'P18', '%', 'qualifiers', 'P407', 1, 'datavalue', 'value', 'id'}, 'Q1860')
		end
		if tmp then
			out['image'] = tmp
		end
		tmp = p.recherche(item, {'sitelinks', 'frwiki', 'title'})
		if tmp then
			out['wiki'] = tmp
		end
	end
	return out
end

-- retourne les infos (fr) sur un rang (WD)
function p.map_rang(qid)
	if (qid == "Q7432") then
		return { 'Espèce', 'Espèce' }
	elseif (qid == "Q34740") then
		return { 'Genre', 'Genre (biologie)' }
	elseif (qid == "35409") then
		return { 'Famille', 'Famille (biologie)' }
	elseif (qid == "36602") then
		return { 'Ordre', 'Ordre (biologie)' }
	else
		return qid
	end
end


-- retourne une tables des informations sur le taxon courant (item/qid=str)
-- si details = TRUE retourne les informations de citation
function p.taxon(ident, details)
	local out = {}
	local item
	
	-- on charge si besoin l'entrée
	if type(ident) == "string" then
		item = mw.wikibase.getEntity(ident)
	else
		item = ident
	end
	
	-- wikilien
	local tmp = p.recherche(item, {'sitelinks', 'frwiki', 'title'})
	if tmp then
		out['wiki'] = tmp
	end
	-- nom scientifique
	local idx
	tmp, idx = p.recherche(item, {'claims', 'P225', 1, 'mainsnak', 'datavalue', 'value'})
	if tmp then
		out['ns'] = tmp
	end
	
	-- rang
	local rg = p.recherche(item, {'claims', 'P105', 1, 'mainsnak', 'datavalue', 'value', 'id'})
	if rg then
		out['rang'] = p.map_rang(rg)
	end
	
	-- taxon supérieur
	local ts = p.recherche(item, {'claims', 'P171', 1, 'mainsnak', 'datavalue', 'value', 'id'})
	if ts then
		out['parent'] = ts
	end
	
	-- citation auteurs
	if details then
		local cit = ""
		-- date
		local dt = p.recherche(item, {'claims',  'P225', 1, 'qualifiers', 'P574', 1,  'datavalue', 'value', 'time'})
		if dt then
			out['date'] = string.gsub(dt, '^+([0-9]*)-.*$', '%1')
		end
		-- liste des auteurs
		out['auteurs'] = {}
		local tbl = p.recherche(item, {'claims', 'P225', 1, 'qualifiers', 'P405', '%',  'datavalue', 'value', 'id'}, nil, nil, true)
		if (type(tbl) == 'table') and (#tbl ~= 0) then
			-- on récupère les infos de chaque auteur
			for k,_v in pairs(tbl) do
				v = _v[1]
				out['auteurs'][v] = {}
				local aut
				-- on charge l'auteur
				aitem = mw.wikibase.getEntity(v)
				if not aitem then
					out['auteurs'][v] = "ERROR"
				else
					-- nom
					local x = p.recherche(aitem, {'labels', 'fr', 'value'})
					out['auteurs'][v]['nom'] = x
					-- wikilien
					x = p.recherche(aitem, {'sitelinks', 'frwiki', 'title'})
					out['auteurs'][v]['wiki'] = x
					-- abréviation
					x = p.recherche(aitem, {'claims', 'P835', 1, 'mainsnak', 'datavalue', 'value'})
					out['auteurs'][v]['abbr'] = x
				end
			end
		end
		-- recombinaison ?
		local rc = p.recherche(item, {'claims',  'P225', 1, 'qualifiers', 'P31', '%',  'datavalue', 'value', 'id'},
			                         {'claims',  'P225', 1, 'qualifiers', 'P31', '%',  'datavalue', 'value', 'id'},
			                         'Q14594740')
		if rc then
			out['recomb'] = 'oui'
		end
		-- construction de la citation
		if out['recomb'] then
			cit = "("
		end
		local blks = {}
		local nbt = 0
		for k,v in pairs(out['auteurs']) do
			nbt = nbt + 1
			if not v['abbr'] then
				v['abbr'] = "BLABLA"
			end
			if v['wiki'] then
				table.insert(blks, "[[" .. v['wiki'] .. '|' .. v['abbr'] .. ']]')
			else
				table.insert(blks, "[[" .. v['nom'] .. '|' .. v['abbr'] .. ']]')
			end
		end
		for k,v in pairs(blks) do
			if k > 1 then
				if k == nbt then
					cit = cit .. " & "
				else
					cit = cit .. ", "
				end
			end
			cit = cit .. v
		end
		if out['date'] then
			cit = cit .. ', [[' .. out['date'] .. ']]'
		end
		if out['recomb'] then
			cit = cit .. ")"
		end
		if cit ~= "" then
			out['citation'] = cit
		end
	end

	return out
end

function p.taxobox2(frame)
	--[[
	local tmp = mw.wikibase.getEntity('Q464424')
	if true then
local tbl = p.recherche(tmp, {'claims', 'P225', 1, 'qualifiers', 'P405', '%',  'datavalue', 'value', 'id'}, nil, nil, true)
mw.log(p.printr(tbl, 20, ""))
		return "<pre>" .. p.printr(tbl, 20, "") .. "</pre>"
	end
	--]]
	
	local id = frame.args['qid']
	local item = mw.wikibase.getEntity(id)
	
	local resu = {}
	
	if (item == nil) then
		return "<pre>ERROR</pre>"
	end
	
	-- vérification : est-ce bien un taxon
	tmp = p.recherche(item, {'claims', 'P31', 1, 'mainsnak', 'datavalue', 'value', 'id'})
	if (tmp ~= 'Q16521') then
		return "<pre>\nPas un taxon\n</pre>"
	end
	
	-- image
	resu['image'] = {}
	tmp = p.recherche(item, {'claims', 'P18', 1, 'mainsnak', 'datavalue', 'value'})
	if (tmp) then
		resu['image']['nom'] = tmp
	end
	-- légende associée
	leg = p.recherche(item, {'claims' , 'P18', 1, 'qualifiers', 'P2096', '%', 'datavalue', 'value', 'text'},
		                    {'claims' , 'P18', 1, 'qualifiers', 'P2096', '%', 'datavalue', 'value', 'language'}, 'fr')
	if leg then
		resu['image']['legende'] = leg
	end
	
	-- IUCN
	iucn = p.recherche(item, {'claims', 'P627', 1, 'mainsnak', 'datavalue', 'value'})
	if iucn then
		tmp = p.recherche(item, {'claims', 'P141', 1, 'mainsnak', 'datavalue', 'value', 'id'})
		if tmp then
			resu['iucn'] = p.statut_iucn(iucn, tmp)
		end
	end
	
	-- données sur le taxon
	resu['taxon'] = p.taxon(item, true)
	
	-- on boucle sur les parents pour remonter la classification
	resu['classification'] = {}
	local cur = resu['taxon']['parent']
	while cur do
		tmp = p.taxon(cur, false)
		table.insert(resu['classification'], tmp)
		if tmp['parent'] then
			cur = tmp['parent']
		else
			break
		end
	end

	
	local out = ""
	
	out = p.printr(resu, 20, "")
	
	out = out .. "\n\n"
	out = out .. p.printr(item, 20, "")
	
	return "<pre>\n" .. out .. "</pre>\n"
end

return p