Oddi ar Wicipedia
Jump to navigation Jump to search

Lua error in Modiwl:Cite at line 166: attempt to concatenate local 'journaltitle' (a nil value). Lua error in Modiwl:Cite at line 166: attempt to concatenate local 'journaltitle' (a nil value).

This module is still unstable. Use with your own caution and report bugs and feature requests at Module talk:Taxobox or Wikidata talk:WikiProject_Taxonomy.

Taxobox.lua is a lua module which can automatically generate taxonomy infobox and is overwritable by classic taxobox parameters like species, unranked_ordo etc.

This module infobox is designed to be a replacement of Wikipedia's Taxobox. It provides configuration options which can control hypernym paths, show or hide certain ranks, specify content language and its configs, and make a callback and pass parameters to "classic" taxobox.

The following code

  | qid=Q464424

creates the taxobox on the right hand side. The item to show is given with qid.

If you want to have this taxobox show up on each Wikidata taxon item: [1]

Internal[golygu cod y dudalen]

Method taxobox[golygu cod y dudalen]

The taxobox method provides function runs the above example. The method itself can be invoked with

{{#invoke: Taxobox
| taxobox
| qid=Q464424
| config[count]=10

The number of parent taxons to show is given by config[count].

Method callback[golygu cod y dudalen]

The callback method provides function to retrieve all internal parameters and pass them to an external template. The method can be invoked with like this

{{#invoke: Taxobox
| callback
| qid=Q464424
| template=OtherTaxobox
| config[count]=10

callback accepts all arguments that taxobox accepted. It also accepts an extra argument template to specify name of the template to be expanded.

I18n[golygu cod y dudalen]

Change the i18n messages in Module:I18n/taxobox. I18n also specifys some format strings which can be use to customize the infobox output of certain language.

message description example(s)
rank-format Format of instances of rheng tacson (Q427626), or cytras (Q713623). Will be passed into 2 named arguments when rendering ranks:
Wiki link to the rank
Display title of the rank
  • "[[{link}|{label}]]": display name and give a hyperlink to the rank, or
  • "{label}" only display name of the rank
rank-format-<latinrank> This argument is similar to rank-format but can be use to specify the format of certain rank. The "<latinrank>" is a latin name, can be a instance of rheng tacson (Q427626), or cytras (Q713623). For example "rank-format-cladus" for cytras (Q713623). It accepts same 2 named arguments like rank-format
  • "": don't display anything at this rank. Can be used to hide the rank name when the taxon is a clade
  • "<i>(clade)</i>": use italic style
  • "[[{link}|<span style=\"color:gray\">{label}</span>]]": use link and change color
item-format-current-with-vernacular-name Format of instances of tacson (Q16521) or tacson un eitem (Q310890). current means either the item is the main taxon (specified by module argument qid), or the item and all taxa between the item and main taxon (if any) are all tacson un eitem (Q310890). with-vernacular-name means the item's Face-smile.svg or label exists and is different from Face-smile.svg.

4 named arguments will be passed on redering:

Wiki link to the taxon item
The "common name" (the value of Face-smile.svg or item label) of taxon item in current language
Full scientific name
Short scientific name, for example "P. leo" of Llew (Q140)

Note: Don't use italic style here on scientific names. This style can be set by scientific-name-pattern, scientific-name-repl and other related messages.

  • "<b>{scientificshort}</b>": only display the short scientific name in bold weight
  • "<b>{vernacular} {scientificshort}</b>": display both vernacular and short scientific name in bold weight
  • "[[{link}|{vernacular}]]": display a hyperlink to target taxon with its vernacular name as label
item-format-current-with-vernacular-name Format of instances of tacson (Q16521) or tacson un eitem (Q310890). without-vernacular-name means either both Face-smile.svg and label (in specified language) are empty, or the vernacular name is the same as Face-smile.svg.

4 named arguments will be passed on rendering, and they are same to item-format-current-with-vernacular-name.

  • "[[{link}|{scientificshort}]]": display a hyperlink to target taxon with its short scientific name as label
  • "[[{link}|{vernacular}]] ({scientficshort})": display both vernacular and short scientific name, also link to target taxon on from vernacular name
scientific-name-pattern, scientific-name-repl Pattern replacement to generate full scientific name. Can be override per rank by scientific-name-pattern-<latinrank> and scientific-name-repl-<latinrank>.

Not the format of scientific-name-pattern and other *pattern* messages are Lua's patterns, which are similar to Perl Compatible Regular Expressions (Q125267) but not identical. Read the manual to know how to write ones.

  • (pattern) "^.+$": match all non-empty strings
    • (repl) "%0": do nothing for replacement, or
    • (repl) "<i>%0</i>": put the scientific name in <i> tag so it can be rendered as italic
short-scientific-name-pattern, short-scientific-name-repl Pattern replacement to generate short scientific name. Can be override per rank by short-scientific-name-pattern-<latinrank> and short-scientific-name-repl-<latinrank>.
scientific-name-pattern-<latinrank>, scientific-name-repl-<latinrank> Pattern replcacement to generate full scientific name per rank. The "<latinrank>" is a latin name, can be a instance of rheng tacson (Q427626), or cytras (Q713623). For example "scientific-name-pattern-species" for rhywogaeth (Q7432).
  • (scientific-name-pattern-genus) nil: fallback to scientific-name-pattern's "^.+$"
    • (scientific-name-repl-genus) "<i>%0</i>": italicize full scientific name if its rank is genws (Q34740)
short-scientific-name-pattern-<latinrank>, short-scientific-name-repl-<latinrank> Pattern replcacement to generate short scientific name per rank. The "<latinrank>" is a latin name, can be a instance of rheng tacson (Q427626), or cytras (Q713623). For example "short-scientific-name-pattern-species" for rhywogaeth (Q7432).
  • (short-scientific-name-pattern-species) "^(%w)%w+ (%w+)$": pattern to fetch the first letter of genus name (as %1) and the whole epithet (as %2) from the binomial of a species.
    • (short-scientific-name-repl-species) "<i>%1. %2</i>": for example, the result for Llew (Q140) is "P. leo"
scientific-name-replaces or short-scientific-name-replaces Pattern replacements to apply for all full (or short) scientific names, after the name has been processed by pattern-repl pair described above.

The value for each of two messages is not a string but a table. The table contains multiple pattern-repl pairs which will be applied to scientific names.

Note: Lua's table object doesn't sort, so the replacement sequence CAN NOT be guaranteed. DON'T DEPEND ON THE SEQUENCE YOU SAW!

Input Parameters[golygu cod y dudalen]

The "<latinrank>" below is a latin name, can be an instance of rheng tacson (Q427626), or cytras (Q713623). For example "display[cladus]".

Config Options[golygu cod y dudalen]

  • config[lang]: content language (default: en).
  • config[count]: maximum count of taxon to be recursively iterated (default: 10).
  • config[references]: a space-separated list of item ids. The references to favor in case of alternative claims. Optional.
  • config[usetaxa]: a space-separated list of item ids. The taxa to favor in case of alternative claims. Optional.
  • config[link]: if the value is "sitelink" it will use local wiki site links instead of wikidata item links.
  • config[dryun]: used for callback method. Displaying a <pre> block contains wikitext instead of expanding and rendering the template. Dryrun can be used to find parameters to be overrided.
Examples[golygu cod y dudalen]

Lua error in Modiwl:Cite at line 166: attempt to concatenate local 'journaltitle' (a nil value).

{{#invoke: taxobox
| taxobox
| qid = Q140
| config[usetaxa] = Q27379
|config[count] = 7

Display Options[golygu cod y dudalen]

display[<latinrank>]: if the value is "n", "no", "false" or "hide", the specified rank (in latin name or QID) will be hide. Otherwise the rank will display.

For example, to hide all clades: Lua error in Modiwl:Cite at line 166: attempt to concatenate local 'journaltitle' (a nil value).


Classic Parameters[golygu cod y dudalen]

If there are more than one clades between two taxon ranks, you can override them by appending [<number>] index to the unranked_<latinrank> parameter.

For example, there are 3 clades between rank genus and rank species. You can override them like this:

|unranked_species[3] = Cladus closest to genus rank
|unranked_species[2] = The middle clade
|unranked_species[1] = Cladus closest to species rank

Output Parameters[golygu cod y dudalen]

The best way to see all output parameters is to use the config[dryrun] parameter:


The result:

|code = 13011
|color = #d3d3a4
|config[dryrun] = yes
|image = Lion waiting in Namibia.jpg
|iucn_status[id] = 278113
|iucn_status[image] = Status iucn3.1 VU.svg
|iucn_status[label] = vulnerable
|iucn_status[references] = Q56011232
|name = Lion
|qid = Q140
|range_map = Lion distribution.png
|rank[1][id] = 5868144
|rank[1][is_extinct] = no
|rank[1][is_monotypic] = no
|rank[1][is_subject] = no
|rank[1][latin] = superordo
|rank[1][link] = Q27379
|rank[1][raw_scientific] = Laurasiatheria
|rank[1][scientific] = Laurasiatheria
|rank[1][taxon] = [[Q27379|Laurasiatheria]]
|rank[2][id] = 713623
|rank[2][is_extinct] = no
|rank[2][is_monotypic] = no
|rank[2][is_subject] = no
|rank[2][latin] = cladus
|rank[2][link] = Q7439311
|rank[2][raw_scientific] = Scrotifera
|rank[2][scientific] = Scrotifera
|rank[2][taxon] = [[Q7439311|Scrotifera]]
|rank[3][id] = 713623
|rank[3][is_extinct] = no
|rank[3][is_monotypic] = no
|rank[3][is_subject] = no
|rank[3][latin] = cladus
|rank[3][link] = Q5444079
|rank[3][raw_scientific] = Fereuungulata
|rank[3][scientific] = Fereuungulata
|rank[3][taxon] = [[Q5444079|Fereuungulata]]
|rank[4][id] = 6462265
|rank[4][is_extinct] = no
|rank[4][is_monotypic] = no
|rank[4][is_subject] = no
|rank[4][latin] = grandordo
|rank[4][link] = Q20868
|rank[4][raw_scientific] = Ferae
|rank[4][scientific] = Ferae
|rank[4][taxon] = [[Q20868|Ferae]]
|rank[5][id] = 36602
|rank[5][is_extinct] = no
|rank[5][is_monotypic] = no
|rank[5][is_subject] = no
|rank[5][latin] = ordo
|rank[5][link] = Q25306
|rank[5][raw_scientific] = Carnivora
|rank[5][scientific] = Carnivora
|rank[5][taxon] = [[Q25306|Carnivora]]
|rank[6][id] = 5867959
|rank[6][is_extinct] = no
|rank[6][is_monotypic] = no
|rank[6][is_subject] = no
|rank[6][latin] = subordo
|rank[6][link] = Q27070
|rank[6][raw_scientific] = Feliformia
|rank[6][scientific] = Feliformia
|rank[6][taxon] = [[Q27070|Feliformia]]
|rank[7][id] = 35409
|rank[7][is_extinct] = no
|rank[7][is_monotypic] = no
|rank[7][is_subject] = no
|rank[7][latin] = familia
|rank[7][link] = Q25265
|rank[7][raw_scientific] = Felidae
|rank[7][scientific] = Felidae
|rank[7][taxon] = [[Q25265|Felidae]]
|rank[8][id] = 164280
|rank[8][is_extinct] = no
|rank[8][is_monotypic] = no
|rank[8][is_subject] = no
|rank[8][latin] = subfamilia
|rank[8][link] = Q230177
|rank[8][raw_scientific] = Pantherinae
|rank[8][scientific] = Pantherinae
|rank[8][taxon] = [[Q230177|Pantherinae]]
|rank[9][id] = 34740
|rank[9][is_extinct] = no
|rank[9][is_monotypic] = no
|rank[9][is_subject] = no
|rank[9][latin] = genus
|rank[9][link] = Q127960
|rank[9][raw_scientific] = Panthera
|rank[9][scientific] = <i>Panthera</i>
|rank[9][taxon] = [[Q127960|<i>Panthera</i>]]
|rank[9][vernacular] = Big cats
|rank[10][authority] = ([[Q1043|Linnaeus]], 1758)
|rank[10][id] = 7432
|rank[10][is_extinct] = no
|rank[10][is_monotypic] = no
|rank[10][is_subject] = yes
|rank[10][latin] = species
|rank[10][link] = Q140
|rank[10][raw_scientific] = Panthera leo
|rank[10][references] = Q82575 Q1538807
|rank[10][scientific] = <i>Panthera leo</i>
|rank[10][taxon] = <b><i>P. leo</i></b>
|rank[10][vernacular] = Lion
|rank[references] = Q22826076 Q1538807 Q19302303 Q82575 Q22814967 Q19858624
|rank[size] = 10

All output parameters can be overrided by specifying same name input parameters. For example this will replace genws (Q34740) to "Foo" and Isdeulu (Q2455704) "Bar":

Lua error in Modiwl:Cite at line 166: attempt to concatenate local 'journaltitle' (a nil value).

|subfamilia=<strong style="color: green">Bar</strong>
|rank[9][taxon]=<strong style="color: red">Foo</strong>

Supported properties[golygu cod y dudalen]

The taxobox currently supports:

  • Face-crying.svg for taxon images and red list status
  • Face-smile.svg: for tacson un eitem (Q310890) and recombination (Q14594740)
  • Face-smile.svg
  • Face-smile.svg also mark the taxon extinct if the value is difodwyd (Q237350)
  • Face-smile.svg
  • Face-smile.svg
  • Face-smile.svg
  • Face-smile.svg for references
  • Face-smile.svg
  • Face-smile.svg
  • Face-smile.svg
  • Face-smile.svg also mark the taxon extinct if present
  • Face-smile.svg
  • Face-smile.svg
  • Face-smile.svg mark the taxon extinct if present
  • Face-smile.svg
  • Face-smile.svg if ICNafp applies, otherwise:
  • Face-smile.svg, otherwise the last name of English language (en) is shown.
  • Face-smile.svg for authority string format and color.
  • Face-smile.svg, common name of a language to override the item label of the language

Wikipedia use[golygu cod y dudalen]

This module is designed to be a replacement for Wikipedia taxoboxes. However, it is still unstable and need plenty extra template works to allow a classic Nodyn:Blwch tacson (Q52496) to accept the new callback parameters. Suggestion and bug reports are welcome at Module talk:Taxobox or Wikidata talk:WikiProject_Taxonomy.

References[golygu cod y dudalen]

  1. Integrated Taxonomic Information System
  2. Martin Schorr and Dennis R. Paulson, World Odonata List, University of Puget Sound
  3. National Center for Biotechnology Information (ed.), Q13711410 (no title property provided!)
  4. Fossilworks
  5. Klaas-Douwe B. Dijkstra, Günter Bechly, Seth M. Bybee, Rory A. Dow, Henri J. Dumont, Günther Fleck, Rosser W. Garrison, Matti Hämäläinen, Vincent J. Kalkman, Haruki Karube, Michael L. May, Albert G. Orr, Dennis R. Paulson, Andrew C. Rehn, Günther Theischinger, John W. H. Trueman, Jan van Tol, Natalia von Ellenrieder and Jessica Ware, "The classification and diversity of dragonflies and damselflies (Odonata)", [[Q18642812|Zootaxa]], vol. 3703, 1, , doi: 10.11646/ZOOTAXA.3703.1.9

-- vim: set noexpandtab ft=lua ts=4 sw=4:

local config = {}  -- TODO find out what this is supposed to be and fix it!
local P_AUDIO = '?'	-- TODO find out what this is supposed to be and fix it!

local ENABLE_DEBUG = true

local Cite = require('Modiwl:Cite')
local fb = require('Modiwl:Fallback')

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 visited = {}
-- background colors for each code
local colors = {
	[false] = '#d3d3d3',
	[13011] = '#d3d3a4',	-- ICZN
	[693148] = '#9bcd9b',	-- ICNafp
	[764] = '#a4d3d3',		-- ICNCP
	[743780] = '#d3a4d3',	-- BC/ICNP
	[14920640] = '#2f6fab',	-- ICVCN

local i18nmessages = require("Modiwl: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_BASINYM= "P566"
local P_TAXON_YEAR = "P574"
local P_END_TIME = "P582"
local P_EX_AUTHOR = "P697"
local P_COMMON_NAME = "P1843"

-- readable item
local CLADE = 713623
local GENUS = 34740
local SUBGENUS = 3238261
local RED_DATA_LIST = 32059
local MONOTYPIC_TAXON = 310890
local GEOLOGICAL_ERA = 630830
local SYSTEMATICS = 3516404
local RECOMBINATION = 14594740
local EXTINCT = 237350

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

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

-- credit to
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]
	return (string.gsub(str, "({([^}]+)})",
			return vars[i] or whole
L.namedStringFormat = namedStringFormat

local function setLang(contentlang)
	_contentlang = contentlang or "en"
L.setLang = setLang

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

local function i18n(str)
	local message = i18nmessages[str]
	if type(message) == 'string' then
		return message
	return fb._langSwitch(message, getLang())
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
			local item = "Q" .. tonumber(string.sub(word, 2))
			items[item] = priority
	items.size = priority
	return items
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]"])  -- TODO what?
	local count = tonumber(args["config[count]"]) or 10
	if count > 25 then
		-- roughly about 100 expensive parser function calls
	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)
			-- TODO: i18n?
			if ({n=true, no=true, ["false"]=true, hide=true})[v] then
				hideranks[k] = true

	_linkconfig = "wikidata"
	if args["config[link]"] and
		mw.ustring.lower(args["config[link]"]) == "sitelink" then
		_linkconfig = "sitelink"

	return {
		["count"] = count,
		-- ["lang"] = lang,  -- this fails with "Tried to read nil global lang" from Module:No_globals required by Module:Wikidata
		["lang"] = config['lang'],  -- TODO this is a quick guess at what might have been intended
		["dryrun"] = args["config[dryrun]"],
		["link"] = _linkconfig

-- check if International Code of Nomenclature for algae, fungi, and plants (ICNafp) applies
local function icnafpApplies()
	return code == 693148
L.icnafpApplies = icnafpApplies

-- the label of the item if present in the specified language or 'no label'
-- TODO: merge with getLabel
local function getItemLabel(item, lang)
	local label = i18n('no-label')
	if item then
		label = item:getLabel(lang or getLang()) or label
	return label
L.getItemLabel = getItemLabel

local function getLabel(id)
	if type(id) == "number" then
		id = "Q" .. id
	return getItemLabel(mw.wikibase.getEntity(id))
L.getLabel = getLabel

local function getLink(id, label, format, named)
	if type(id) == "number" then
		id = "Q" .. id
	local link = id
	if _linkconfig == "sitelink" then
		link = mw.wikibase.sitelink(id)
	label = label or getItemLabel(mw.wikibase.getEntity(id))
	format = format or "[[%s|%s]]"
	if named then
		return namedStringFormat{format, link=link, label=label}
		return string.format(format, link, label)
L.getLink = getLink

-- Collect all claims of the given property of the item
-- Returns all claims and their references 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 and[property] then
		for _,claim in pairs([property]) do
				if claim.mainsnak.datavalue and claim.mainsnak.datavalue.value then
					local valueid = claim.mainsnak.datavalue.value['numeric-id']

					local refids = {}
					if claim.references then
						for _,ref in pairs(claim.references) do
							for prop, refclaim in pairs(ref.snaks[P_STATED_IN] or {}) do
								refids[tostring('Q' .. refclaim.datavalue.value['numeric-id'])] = true
					claims[claim.rank][valueid] = refids
					claims[claim.rank]['novalue'] = true -- snaktype not value
	return claims
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
	if next(claims.normal) then
		return claims.normal
	return claims.deprecated
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)
	local choosenclaim = {preferred = {}, normal = {}, deprecated = {}}
	local choosenqualifiers = {preferred = {}, normal = {}, deprecated = {}}
	local choosenreferences = {preferred = {}, normal = {}, deprecated = {}}
	if item and and[property] then
		for _,claim in pairs([property]) do
			if claim.mainsnak and claim.mainsnak.datavalue then
				local index = #choosenclaim[claim.rank] + 1
				mw.log(index, claim.mainsnak.datavalue.value)

				local refids = {}
				if claim.references then
					for _,ref in pairs(claim.references) do
						for prop, refclaim in pairs(ref.snaks[P_STATED_IN] or {}) do
							refids['Q' .. refclaim.datavalue.value['numeric-id']] = true
							mw.log('string ref', refclaim.datavalue.value['numeric-id'])
				if claim.mainsnak.datatype == 'monolingualtext' then
					choosenclaim[claim.rank][index] = tostring(claim.mainsnak.datavalue.value)
					choosenclaim[claim.rank][index] = tostring(claim.mainsnak.datavalue.value)
				choosenqualifiers[claim.rank][index] = claim.qualifiers
				choosenreferences[claim.rank][index] = refids

	return choosenclaim, choosenqualifiers, choosenreferences
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)
	local 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],
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 {})
	return c, q, r
L.mergeClaims = mergeClaims

-- Use only if the data type of the property is time
local function targetTime(item, property)
	if item and and[property] then
		for _,claim in pairs([property]) do
			if claim.mainsnak and claim.mainsnak.datavalue then
				return claim.mainsnak.datavalue.value.time
L.targetTime = targetTime

-- 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)
	return claims
L.qualifierTargetId = qualifierTargetId

-- same as targetTime but for qualifiers
-- TODO merge
local function qualifierTargetTime(qualifiers, property)
	local claims = {}
	if qualifiers and qualifiers[property] then
		for _,claim in pairs(qualifiers[property]) do
			if claim.datavalue then
				mw.log('time qualifier', property, claim.datavalue.value.time)
				return claim.datavalue.value.time
L.qualifierTargetTime = qualifierTargetTime

-- 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 icnafpApplies() then
						-- get author abbrieviation per IPNI set
						if targetStr(author, P_AUTHOR_ABBR_IPNI) then
							label = targetStr(author, P_AUTHOR_ABBR_IPNI)
					elseif targetStr(author, P_AUTHOR_ABBR_ZOOLOGY) then
						-- get zoologist author citation set
						label = targetStr(author, P_AUTHOR_ABBR_ZOOLOGY)
					if not label then
						-- use the "last" name if no abbreviation found
						-- also don't use the translated name
						label = getItemLabel(author, "en")
						if label ~= i18n('no-label') then
							local _
							_, _, label = mw.ustring.find(label, "(%w+)$")
				table.insert(authors, getLink(authorid, label))
	return authors
L.createLinks = createLinks

local function vernacularName(item)
	local vernacularname
	-- select vernacular name for current language
	if and[P_COMMON_NAME] then
		for _, claim in pairs([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
		if vernacularname == '' then
			vernacularname = nil

	if not vernacularname then
		-- test if item label is not one of the scientific names
		vernacularname = getItemLabel(item)
		local scnames = mergeClaims(targetStrs(item, P_TAXON_NAME))
		for _, n in pairs(scnames) do
			if vernacularname == n then
	if vernacularname == i18n("no-label") then
	return capitalize(vernacularname)
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)
	local authors = createLinks(authorids, true)
	if next(authors) then
		local last = table.remove(authors)
		local rest = ''
		if #authors > 0 then
			rest = table.concat(authors, concatstr) .. ' & '
		return rest .. last
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 icnafpApplies() then
			-- check for basionym
			local basionymids = targetId(item, P_BASINYM)
			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 .. ') '
					-- indicate missing basionym author
					basionymstr = '(????) '

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

			-- 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

			if recombination then
				authorsstr = '(' .. authorsstr .. ')'
	return authorsstr
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))
	if era1 and era2 and not (era1 == 'novalue' or era2 == 'novalue') then
		local era1item = mw.wikibase.getEntity("Q" .. era1)
		if era1item then
			params["era[1][id]"] = era1
			params["era[1][label]"] = getItemLabel(era1item)
			if not (era1 == era2) then
				local era2item = mw.wikibase.getEntity("Q" .. era2)
				if era2item then
					params["era[2][id]"] = era2
					params["era[2][label]"] = getItemLabel(era2item)

			-- merge references from era2 to era1, only show once
			for a, b in pairs(era2references) do
				era1references[a] = b

			-- TODO: return data structure instead of pure str here
			params["era[references]"] = era1references
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 ref = Cite.citeitem(id, getLang()) or 'Error during creation of citation. Please report [[' .. id .. ']] at [[Sgwrs modiwl:Cite]]'
			mw.log('refstr for ', id, ref)
			refstr = refstr .. frame:extensionTag('ref', ref, {name=id})
	return refstr
L.references = references

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

local function formatScientificName(ranklatin, scientific, short)
	local pf = "scientific-name"
	if short then
		pf = "short-" .. pf
	local scipattern = i18nByLatin(
		ranklatin, pf .. "-pattern", i18n(pf .. "-pattern"))
	local 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)
	return scientific
L.formatScientificName = formatScientificName

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

local function renderTableRow(text, extra_css)
	local css = "text-align: center;"
	if extra_css then
		css = css .. " " .. extra_css
	return mw.text.tag('tr', {}, mw.text.tag('td',
		{colspan='2', style=css}, text))
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)
	return renderTableHead(getLink(GEOLOGICAL_ERA) .. refstr, params.color) ..
		renderTableRow(table.concat(eralink, '&mdash;'))
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)
	-- TODO: support SVG systemLanguage here
	r[#r + 1] = renderTableRow(
		"[[File:" .. params["iucn_status[image]"] ..
		"|220px|" .. params["iucn_status[label]"] .. "]]")
	return table.concat(r)
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, true)
		scientific = formatScientificName(latin, scientific)
	local nf = "item-format-parent"
	if is_subject then
		nf = "item-format-current"
	if vernacular then
		nameformat = i18n(nf .. "-with-vernacular-name")
		nameformat = i18n(nf .. "-without-vernacular-name")
	local link = qid
	if _linkconfig == "sitelink" then
		link = mw.wikibase.sitelink(qid)
	if is_extinct then
		nameformat = i18n("extinct-mark") .. nameformat
	return namedStringFormat{
		nameformat, link=link, vernacular=vernacular,
		scientific=scientific, scientificshort=scientificshort},
L.formatTaxon = formatTaxon

local function renderRank(i, params)
	local row
	local nameformat
	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 rankid then
		local linkformat = i18nByLatin(
			ranklatin, "rank-format", i18n("rank-format"))
		ranklink = getLink(rankid, nil, linkformat, true)
	row = mw.text.tag(
		'tr', {},
		mw.text.tag('td', {}, ranklink) ..
		mw.text.tag('td', {}, formatted))
	if is_subject then
		local refstr = references(params[pf .. "[references]"])
		local authority = params[pf .. "[authority]"]
		detailrows = {
				string.format(i18n("scientific-name-of-taxon"), getLabel(rankid))
				.. refstr, params.color
			renderTableRow(authority, "font-variant: small-caps;")
	return row, detailrows
L.renderRank = renderRank

-- in case of more than one parent taxa: choose target according to the
-- references selected by usereferences
local function chooseParent(item)
	local cand
	local nextparent = {}
	for id,refs in pairs(targetId(item, P_TAXON_PARENT)) 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})

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

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

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

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

-- 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

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

	-- check end time
	local endtime = targetTime(item, P_END_TIME)
	if endtime then
		return true

	return false
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
L.isMonotypic = isMonotypic

local function taxonParams(qid, item, params, fetch_detail, is_parent_extinct)
	local rankid
	local ranklatin
	local is_subject
	local level = params["rank[size]"] + 1
	local pf = string.format("rank[%d]", level)
	local name, namequalifiers, namereferences = targetStr(item, P_TAXON_NAME)
	local vernacular = vernacularName(item)
	local is_extinct = is_parent_extinct or isExtinct(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)],

	rankid = next(targetId(item, P_TAXON_RANK))
	if rankid == "novalue" then
		rankid = CLADE

	if rankid then
		local rankitem = mw.wikibase.getEntity('Q' .. rankid)
		ranklatin = rankitem:getLabel('la')
	if ranklatin then
		ranklatin = mw.ustring.lower(ranklatin)

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

	name = capitalize(name)
	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

	if fetch_detail then
		-- get detail
		is_subject = true
		params[pf .. "[references]"] = namereferences
		local year = qualifierTargetTime(namequalifiers, P_TAXON_YEAR) or
			targetTime(item, P_TAXON_YEAR)
		if year then
			-- access year in time representation "+1758-00-00T00:00:00Z"
			year = string.sub(year, 2, 5)
			year = '????'
		local authorsstr = createAllAuthorsStr(item, namequalifiers, year)
		params[pf .. "[authority]"] = authorsstr
		is_subject = false

	local formatted, sciname = formatTaxon(
		ranklatin, qid, name, vernacular, is_subject, is_extinct)

	params[pf .. "[scientific]"] = sciname
	params[pf .. "[is_subject]"] = is_subject
	params[pf .. "[taxon]"] = formatted

	return params, is_extinct
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 inneritem
	local params = params or {["rank[size]"] = 0}
	local item = mw.wikibase.getEntity(qid)

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

	local nextid, references = chooseParent(item)
	mw.log('nextid', nextid)
	if not code then
		local codeid = next(targetId(item, P_NOMENCLATURE_CODE))
		if codeid and colors[codeid] then
			code = codeid

	if visited[nextid] then -- loop detection
		return params, {}, item
	elseif nextid then
		visited[nextid] = true

	-- 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
		local refs, _
		params, refs, _, is_parent_extinct = iterateRanks(
			'Q' .. nextid, count - 1, false,
			fetch_detail, isExtinct(item), params)
		for ref, _ in pairs(refs) do
			references[ref] = true
	if count > 0 then
		params, is_extinct = taxonParams(
			qid, item, params, fetch_detail, is_parent_extinct)
	params["code"] = code
	params["color"] = colors[code]
	return params, references, item, is_extinct
L.iterateRanks = iterateRanks

-- 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]

	-- 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
			local txarg = pf .. "[taxon]"
			local atarg = pf .. "[authority]"
			params[txarg] = overrides[latin] or params[txarg]
			params[atarg] = 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
					-- TODO Changed "override" to "overrides" twice, but that just a guess.
					params[txarg] = overrides[argname] or
						overrides[argname .. "1"] or params[txarg]
					params[atarg] = overrides[argname .. "_authority"] or
						overrides[argname .. "1_authority"] or params[atarg]
					params[txarg] = overrides[string.format('%s%d',
						argname, #unranked - j + 1)] or params[txarg]
					params[atarg] = overrides[string.format('%s%d_authority',
						argname, #unranked - j + 1)] or params[atarg]
			unranked = {}
		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]

	return params
L.overrideParams = overrideParams

-- 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 {}

	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]


	local image = targetStr(item, P_IMAGE)
	params["image"] = image

	fossilParams(item, params)

	params["rank[references]"] = references

	local map = targetStr(item, P_SPREAD_MAP)
	params["range_map"] = map

	local statusid, statusreferences = next(targetId(item, P_IUCN_STATUS))
	params["iucn_status[id]"] = statusid
	if statusid then
		local status = mw.wikibase.getEntity("Q" .. statusid)
		local img, imgqualifiers, imgreferences = targetStr(status, P_IMAGE)
		if img then
			params["iucn_status[label]"] = getItemLabel(status)
			params["iucn_status[references]"] = statusreferences
			params["iucn_status[image]"] = img

	local audio, audioreferences = targetStr(item, P_AUDIO)
	params["audio"] = audio

	return params
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)
			val = table.concat(refs, " ")
		if dryrun then
			content[#content + 1] = string.format(
				"|%s = %s", key, val)
			params[key] = val

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

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

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

	-- systematics
	local refstr = references(params["rank[references]"])
	content[#content + 1] = renderTableHead(
		getLink(SYSTEMATICS) .. refstr, color)

	-- ranks
	if params["rank[size]"] > 0 then
		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)
		content[#content + 1] = table.concat(taxondetails)

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

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

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

	return mw.text.tag('table', {
		style = [[
			width: 200px; border-width: 1px; float: right;
			border-style: solid; background-color: #f9f9f9;
	}, table.concat(content))
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)
		mw.log("End of logging params")

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

function p.taxobox(frame)
	local config = parseConfig(frame.args)
	local qid = frame.args.qid
	local params = getTaxoboxParams(qid, config.count)
	return renderTaxobox(params, frame.args)

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

return p