Module:Wikidata4Bio

提供: VANGUARD FLIGHT wiki
2014年3月30日 (日) 23:14時点におけるTaku.oshino (トーク | 投稿記録)による版 (1版)

(差分) ← 古い版 | 最新版 (差分) | 新しい版 → (差分)
移動先: 案内検索
Module:Wikidata4Bio (talk<dot-separator>edit<dot-separator>hist<dot-separator>links<dot-separator>doc<dot-separator>subpages<dot-separator>tests / results<dot-separator>sandbox<dot-separator>all modules)



Usage
This module is used by:

Module:Wikidata4Bio/sandbox is the sandbox of this module (to test new developments) which is used in the sandbox template {{VN/sandbox}}
How to improve and test this module
  1. developp your modification in Module:Wikidata4Bio/sandbox, the sandbox of this module
  2. verify your changes in Module:Wikidata4Bio/sandbox/testcases
  3. verify your changes in {{VN}} in :
  4. verify your changes in {{ITIS}} in :
  5. report your modifications in Module:Wikidata4Bio
  6. verify your changes in Module:Wikidata4Bio/testcases
  7. verify your changes in Ailanthus altissima with normal {{VN}}

-- global variable which receives debug info if not nil
_debug=Nil

-- tableIsEmpty return true if the parameter mytable is Nil or empty
function tableIsEmpty(mytable)
	if not mytable then
		return true
	end
	for key, value in pairs(mytable) do
		return false
	end
	return true
end

-- addDebug() adds debug/verbose/trace info to _debug
function addDebug(lang, functionName, text)
	if not _debug then
		return
	end
	local index
	if lang then
		index = 'Lang ' .. lang
	else
		index = functionName .. '()'
	end
	local previousText = _debug[index]
	if previousText then
		previousText = previousText .. ', '
	else
		previousText = ''
	end
	if functionName and lang then
		previousText = previousText .. functionName .. ': ' .. text
	else
		previousText = previousText .. text
	end
	_debug[index] = previousText
end

-- getDebug() returns a formated version of _debug for the display
function getDebug()
	if not _debug then
		return ''
	end
	if tableIsEmpty(_debug) then
		return ''
	end

	-- Sort _debug into debug2
	debug2 = {}
	for key, value in pairs(_debug) do
		table.insert(debug2,{key,value})
	end
	table.sort(debug2, function(t1,t2) return t1[1] < t2[1] end)

	-- Serialize debug2 in displayedDebug
	displayedDebug = '<BR/>Debug:'
	for key, value in pairs(debug2) do
		displayedDebug = displayedDebug .. '<BR/>- ' .. value[1] .. ': ' .. mw.text.nowiki(value[2])
	end
	-- Clear debug:
	_debug={}
	return displayedDebug
end

-- dumpPath() is an advanced tostring(). It is recursive and displays 1 ligne per significant value.
-- For exemple dumpPath('entity', mw.wikibase.getEntity()) will generate:
--   entity.type=string:item
--   entity.sitelinks.nlwiki.title=string:Nachtorchis
--   entity.sitelinks.nlwiki.site=string:nlwiki
--   entity.sitelinks.plwiki.title=string:Podkolan
--   entity.sitelinks.plwiki.site=string:plwiki
--   ...
--   entity.descriptions.es.language=string:es
--   entity.descriptions.es.value=string:'género de plantas de la familia Orchidaceae'
--   entity.descriptions.de.language=string:de
--   entity.descriptions.de.value=string:'Gattung der Familie der Orchideen (Orchidaceae)'
--   ...
--   entity.id=string:Q161849
--   entity.claims.p910[0].mainsnak.snaktype=string:value
--   entity.claims.p910[0].mainsnak.property=string:P910
--   entity.claims.p910[0].mainsnak.datavalue.value.numeric-id=number:8765698
--   entity.claims.p910[0].mainsnak.datavalue.value.entity-type=string:item
--   entity.claims.p910[0].mainsnak.datavalue.type=string:wikibase-entityid
--   entity.claims.p910[0].type=string:statement
--   entity.claims.p910[0].id=string:Q161849$10B4A9CE-CF8C-4D2D-A0E0-AE494A71EBE1
function dumpPath(path,value)
	if not value then
		return path .. '=Nil'
	end
	local valueType = type(value)
	local valueStr = path .. '=' .. valueType .. ':'
	if valueType == 'table' then
		valueStr = ''
		for key, subvalue in pairs(value) do
			if string.len(valueStr) < 60000 and value ~= subvalue then
				if valueStr ~= '' then
					valueStr = valueStr .. '\n'
				end
				if (type(key) == 'number') then
					valueStr = valueStr .. dumpPath(path .. '[' .. key .. ']',subvalue)
				else
					valueStr = valueStr .. dumpPath(path .. '.' .. key,subvalue)
				end
			end
		end
	else
		return valueStr .. dumpValue(value,false)
	end
	return valueStr
end

function dumpValue(value,withType)
	if not value then
		return 'Nil'
	end
	local valueType = type(value)
	local valueStr = ''
	if withType then
		valueStr = valueType .. ':'
	end
	if valueType == 'table' then
	elseif valueType == 'string' then
		if string.find(value, ' ', 1, true) then
			valueStr = valueStr .. "'" .. value .. "'"
		else
			valueStr = valueStr .. value
		end
	elseif valueType == 'number' then
		return valueStr .. value
	elseif valueType == 'boolean' then
		return valueStr .. tostring(value)
	end
	return valueStr
end

-- getTextAsAsciiCode('Paphiopedilum') returns '112.104.105.111.112.101.100.105.108.117.109'
function getTextAsAsciiCode(stri)
	local result = ''
	for i = 1, string.len(stri) do
		result = result .. '.' .. string.byte(string.sub(stri, i, i))
	end
	return result
end

-- getProperty(entity, true, {'claims','P301',0,'mainsnak','datavalue','value'}) does the same as
-- entity.claims.P301[0].mainsnak.datavalue.value except that it will never generate an error.
-- Instead of an error it will return Nil if verbose==false or an explanation (like 'entity.claims.P301[0] = Nil') if verbose
function getProperty(entity, verbose, propertyPath)
	local property = entity
	local currentPath = 'entity'
	for index, propertyPathItem in pairs(propertyPath) do
		property = property[propertyPathItem]
		if verbose then
			if (type(propertyPathItem) == 'number') then
				currentPath = currentPath .. '[' .. propertyPathItem .. ']'
			else
				currentPath = currentPath .. '.' .. propertyPathItem
			end
		end
		if not property then
			if verbose then
				return currentPath .. " = Nil"
			else
				return Nil
			end
		end
	end

	return property
end

-- suppressCategory() returns "Platanthera" out of "Category:Platanthera"
function suppressCategory(category)
	local twoPointStartPos, twoPointEndPos = string.find(category, ':', 1, true)
	if twoPointStartPos then
		category = string.sub(category,twoPointEndPos+1)
	end
	return category
end

-- suppressDisambiguation() returns "Platanthera" out of "Platanthera (genus)"
function suppressDisambiguation(name)
	name = string.gsub(name, '_', ' ')
	local start = string.find(name, '(', 1, true)
	if not start then
		return name
	end
	local endp = string.find(name, ')', start, true)
	if not endp then
		return name
	end
	local part1 = mw.text.trim(string.sub(name, 1, start-1))
	local part2 = mw.text.trim(string.sub(name, endp+1))
	if string.len(part1) == 0 then
		return part2
	elseif string.len(part2) == 0 then
		return part1
	else
		return part1 .. ' ' .. part2
	end
end

-- isLink(name) return true when name is a link syntax like '[[sdfs|dfsfsd]]'
function isLink(name)
	if not name then
		return false
	end
	if string.find(name, ']]', 1, true) then
		return true
	end
	if string.find(name,'[[', 1, true) then
		return true
	end
	return false
end

-- tableToString() returns a string out of a table for debug and non regression purpose
-- sorted by keys, only values are dumped
function tableToString(mytable)
	-- Sort mytable into mytable2
	mytable2 = {}
	for key, value in pairs(mytable) do
		table.insert(mytable2,{key,value})
	end
	table.sort(mytable2, function(t1,t2) return t1[1] < t2[1] end)
	
	-- Serialize mytable2 into display
	local display = Nil
	for key, value in pairs(mytable2) do
		if display then
			display = display .. ', ' .. value[2]
		else
			display = value[2]
		end
	end
	if not display then
		return ''
	end
	return display
end

-- global variable which stores the result of getScientificNamesFromWikidata()
_scientificNamesFromWikidata = Nil

-- getScientificNamesFromWikidata() return a dictionary containing all the scientific names described by wikidata property P225
function getScientificNamesFromWikidata()
	if _scientificNamesFromWikidata then
		return _scientificNamesFromWikidata
	end
	_scientificNamesFromWikidata = {}

	if not mw.wikibase then
		-- wikidata library is not enabled
		return _scientificNamesFromWikidata
	end

	local entity = mw.wikibase.getEntity()
	if entity then
		local p225 = getProperty(entity, false, {'claims', 'P225'})
		if p225 then
			local index = 0
			local name = ''
			while name do
				name = getProperty(p225, false, {index , 'mainsnak', 'datavalue', 'value'})
				if name then
					name = mw.text.trim(name)
					_scientificNamesFromWikidata[name] = name
					index = index + 1
				end
			end
		end
	end

	addDebug(Nil,'getScientificNamesFromWikidata',tableToString(_scientificNamesFromWikidata))
	return _scientificNamesFromWikidata
end

-- global variable which stores the result of getScientificNames()
_scientificNames = Nil

-- getScientificNames() return a dictionary containing all the possible lowercase scientific names of the taxon described out of:
-- * current category/gallery name (which is supposed to be a scientific name)
-- * {{pagename}}
-- * wikidata property P225 (via getScientificNamesFromWikidata())
function getScientificNames()
	if _scientificNames then
		return _scientificNames
	end
	_scientificNames = {}

	local name = string.lower(suppressDisambiguation(mw.title.getCurrentTitle().text))
	_scientificNames[name] = name
	name = "''" .. name .. "''"
	_scientificNames[name] = name

	name = "{{pagename}}"
	_scientificNames[name] = name
	name = "''{{pagename}}''"
	_scientificNames[name] = name

	for key, value in pairs(getScientificNamesFromWikidata()) do
		name = string.lower(value)
		_scientificNames[name] = name
		name = "''" .. name .. "''"
		_scientificNames[name] = name
	end

	addDebug(Nil,'getScientificNames',tableToString(_scientificNames))
	return _scientificNames
end

-- isScientificName(name) return true when name == the category/gallery name, which is supposed to be the scientific name
function isScientificName(name)
	if not name then
		return false
	end
	name = string.lower(suppressDisambiguation(mw.text.trim(name)))
	
	getScientificNames()
	
	if _scientificNames[name] then
		return true
	else
		return false
	end
end

-- getVNFromWikidataInterwiki() returns the VernacularName from wikidata interwiki for a specific lang
-- (if different from {{PAGENAME}} which is supposed to be the scientific name)
function getVNFromWikidataInterwiki(entity,lang,interwiki)
	if interwiki then
		interwiki = suppressCategory(interwiki)
		if isScientificName(interwiki) then
			-- this gallery/category interwiki is the scientific name (not vernaculare)
			return Nil
		else
			return interwiki
		end
	else 
		return Nil
	end
end

-- getVNFromWikidataLabel() returns the VernacularName from wikidata label for a specific lang
-- (if different from {{PAGENAME}} which is supposed to be the scientific name)
function getVNFromWikidataLabel(entity,lang)
	local label = getProperty(entity, false, {'labels', lang, 'value'})
	if label then
		label = suppressCategory(label)
		if isScientificName(label) then
			-- this gallery/category label is the scientific name (not vernaculare)
			return Nil
		else
			return label
		end
	else 
		return Nil
	end
end

-- strContains(long,small) is an improvment of string.find(longlower, smalllower)
-- it is case insensitiv + also return true is long == small
function strContains(long,small)
	if not long or not small then
		return false
	end
	local longlower = string.lower(long)
	local smalllower = string.lower(small)
	return longlower == smalllower or string.find(longlower, smalllower, 1, true)
end

-- calcVNEntry() puts together the different info coming from VN parameters (lang & default) and wikidata (interwiki & vnFromWikidata)
function calcVNEntry(lang,interwiki,vnFromWikidata,vnSource,default)
	local vnEntry = vnFromWikidata
	local vnEntryDescription = vnSource
	if interwiki then
		vnEntry = '[[:' .. lang .. ':' .. interwiki .. '|' .. vnFromWikidata .. ']]'
		vnEntryDescription = 'bis: [[interwiki|' .. vnSource .. ']]'
	else
		vnEntryDescription = ': ' .. vnEntryDescription
	end
	if default then
		if strContains(vnFromWikidata,default) then -- strContains(long,small)
			-- default is in vnFromWikidata => no need to display default
			-- example: default='cat' vnFomInterwiki='common cat'
			addDebug(lang,Nil,'VNparameter rejected as contained in ' .. vnSource .. ', Case1' .. vnEntryDescription)
			return vnEntry
		else
			if strContains(default,vnFromWikidata) then -- strContains(long,small)
				-- vnFromWikidata is in default => no need to display vnFromWikidata
				-- example: default='[[:en:cat|]]' vnFomInterwiki='cat'
				if isLink(default) then
					addDebug(lang,Nil,vnSource .. ' rejected as contained in VNparameter which is a link, Case2: VNparameter')
					return default
				else
					if interwiki then
						addDebug(lang,Nil,vnSource .. ' rejected as contained in VNparameter, Case3: [[interwiki|parameter]]')
						return '[[:' .. lang .. ':' .. interwiki .. '|' .. default .. ']]'
					else
						-- happens when VN is called with de= + wikidata has no 'de' interwiki + wikidata has a 'de' label + VN|de= contains 'de' label
						addDebug(lang,Nil,vnSource .. ' rejected as contained in VNparameter + no interwiki, Case4: VNparameter')
						return default
					end
				end
			else
				addDebug(lang,Nil,'Case5' .. vnEntryDescription .. ', VNparameter')
				return vnEntry .. ', ' .. default
			end
		end
	else
		addDebug(lang,Nil,'Case6' .. vnEntryDescription)
		return vnEntry
	end
end

-- getVernacularNameFromWikidata() returns a vernacular name (often in form of a wiki link) for getVNEntry()
function getVernacularNameFromWikidata(entity,lang,default,useWikidata)
	if not useWikidata then
		return default
	end
	if not entity then
		-- This gallery/category has no wikidata element (you are perhaps in the template page)
		return default
	end
	if default and string.len(default) == 0 then
		default = Nil
	end
	if isScientificName(default) then
		addDebug(lang,Nil,'VNparameter is a scientificName')
		default = Nil
	end
  
	-- First try entity.sitelinks.frwiki.title (interwiki)
	local interwiki = getProperty(entity, false, {'sitelinks', lang .. 'wiki', 'title'})
	local vnFomInterwiki = getVNFromWikidataInterwiki(entity,lang,interwiki)
	if vnFomInterwiki then
		return calcVNEntry(lang,interwiki,vnFomInterwiki,'interwiki',default)
	else 
		-- Second try 'entity.labels.fr.value'
		local vnFromLabel = getVNFromWikidataLabel(entity,lang)
		if vnFromLabel then
			return calcVNEntry(lang,interwiki,vnFromLabel,'label',default)
		else
			-- Interwiki and label are not provided or are scientific name
			if default and not isLink(default) and interwiki then
				addDebug(lang,Nil,'Case7: [[interwiki|VNparameter]]')
				if lang == 'en-us' then
					lang = 'en'
				end
				return '[[:' .. lang .. ':' .. interwiki .. '|' .. default .. ']]'
			end
			if default then
				addDebug(lang,Nil,'Case8: VNparameter')
			else
				-- no need to to a trace if no interwiki, no label, no VNparameter
			end
			return default
		end
	end
end

-- getVNEntry() returns HTML for one language in getVN()
function getVNEntry(entity,lang,default,useWikidata,bold)
	local vercularName = getVernacularNameFromWikidata(entity,lang,default,useWikidata)
	if vercularName and string.len(vercularName) > 0 then
		local success, langName = pcall(mw.language.fetchLanguageName, lang, lang)
		if lang == 'en-us' then
			success = true
			langName = 'American'
			lang = 'en'
		end
		if success and langName then
			local entry = "'''" .. langName .. ":"
			if not bold then
				entry = entry .. "'''"
			end
			entry = entry .. '&nbsp;<span class="vernacular" lang="' .. lang .. '">' .. vercularName .. "</span>&nbsp;"
			if not bold then
				entry = entry .. "'''"
			end
			entry = entry .. "·'''&#32;"
			return entry;
		else
			return "Unknown lang '" .. lang .. "'"
		end
	else
		return nil
	end
end

local languages = {
	'aa', 'ab', 'af', 'ak', 'als', 'am', 'an', 'ang', 'ar', 'arc', 'arn', 'arz', 'as', 'ast', 'av', 'ay', 'az', 
	'ba', 'bar', 'bat-smg', 'bcl', 'be', 'be-x-old', 'bg', 'bh', 'bi', 'bm', 'bn', 'bo', 'bpy', 'br', 'bs', 'bug', 'bxr', 
	'ca', 'cbk-zam', 'cdo', 'ce', 'ceb', 'ch', 'cho', 'chr', 'chy', 'co', 'cr', 'crh', 'cs', 'csb', 'cu', 'cv', 'cy', 
	'da', 'de', 'diq', 'dk', 'dsb', 'dv', 'dz', 
	'ee', 'el', 'eml', 'en', 'en-us', 'eo', 'es', 'et', 'eu', 'ext', 
	'fa', 'ff', 'fi', 'fiu-vro', 'fj', 'fo', 'fr', 'frp', 'frr', 'fur', 'fy', 
	'ga', 'gan', 'gd', 'gl', 'glk', 'gn', 'got', 'gu', 'gv', 
	'ha', 'hak', 'haw', 'he', 'hi', 'hif', 'ho', 'hr', 'hsb', 'ht', 'hu', 'hy', 'hz', 
	'ia', 'id', 'ie', 'ig', 'ii', 'ik', 'ilo', 'io', 'is', 'it', 'iu', 
	'ja', 'jbo', 'jv', 
	'ka', 'kaa', 'kab', 'kg', 'ki', 'kj', 'kk', 'kl', 'km', 'kn', 'ko', 'koi', 'kr', 'ks', 'ksh', 'ku', 'kv', 'kw', 'ky', 
	'la', 'lad', 'lb', 'lbe', 'lg', 'li', 'lij', 'lmo', 'ln', 'lo', 'lt', 'lv', 
	'map-bms', 'mdf', 'mg', 'mh', 'mhr', 'mi', 'mk', 'ml', 'mn', 'mo', 'mov', 'mr', 'mrc', 'mrj', 'ms', 'mt', 'mus', 'my', 'myv', 'mzn', 
	'na', 'nah', 'nan', 'nap', 'nb', 'nds', 'nds-nl', 'ne', 'new', 'ng', 'nl', 'nn', 'no', 'nov', 'nrm', 'nv', 'ny', 
	'oc', 'om', 'ood', 'or', 'os', 
	'pa', 'pag', 'pam', 'pap', 'pcd', 'pdc', 'pi', 'pih', 'pl', 'pms', 'pnt', 'ps', 'pt', 'pt-br', 
	'qu', 
	'rm', 'rmy', 'rn', 'ro', 'roa-rup', 'roa-tara', 'ru', 'rw', 
	'sa', 'sah', 'sc', 'scn', 'sco', 'sd', 'se', 'sei', 'sg', 'sh', 'si', 'sk', 'sl', 'sm', 'sn', 'so', 'sq', 'sr', 'srn', 'ss', 'st', 'stq', 'su', 'sv', 'sw', 'szl', 
	'ta', 'te', 'tet', 'tg', 'th', 'ti', 'tk', 'tl', 'tn', 'to', 'tp', 'tpi', 'tr', 'ts', 'tt', 'tum', 'tw', 'ty', 
	'udm', 'ug', 'uk', 'ur', 'uz', 
	've', 'vec', 'vi', 'vls', 'vo', 
	'wa', 'war', 'wo', 'wuu', 
	'xal', 'xh', 
	'yi', 'yo', 
	'za', 'zea', 'zh', 'zh-classical', 'zh-hans', 'zh-hant', 'zh-min-nan', 'zh-tw', 'zh-yue', 'zu'
}

-- isTrue() transforms a string into a bool
-- Nil, '', '0', 'false', 'no' return false
-- other values return true
function isTrue(stringValue)
	if not stringValue then
		return false
	end
	if string.len(stringValue) == 0 then
		return false
	end
	stringValue = string.lower(mw.text.trim(stringValue))
	if stringValue == '0' or stringValue == 'false' or stringValue == 'no' then
		return false
	end
	return true
end

-- getCurrentNamespace() returns the current namespace ('gallery' instead of '')
function getCurrentNamespace()
	local namespace = string.lower(mw.title.getCurrentTitle().nsText)
	if string.len(namespace) == 0 then
		namespace = 'gallery'
	end
	return namespace
end

-- isCurrentNamespaceACategoryOrAGallery() returns true for category and gallery
function isCurrentNamespaceACategoryOrAGallery()
	local namespace = string.lower(mw.title.getCurrentTitle().nsText)
	if string.len(namespace) == 0 then
		return true
	elseif namespace == 'category' then
		return true
	else
		return false
	end
end


-- findTemplate() return the position of the first call of <templateName> after startPos
function findTemplate(wikicode,templateName,templateForms,templateFormsDebug,startPos)
	local firstPos = Nil
	for index, templateForm in pairs(templateForms) do
		local currentPos = string.find(wikicode,templateForm,startPos,true)
		if currentPos then
			--addDebug(Nil,'findTemplate','Found ' .. templateFormsDebug[index] .. ' at pos ' .. currentPos)
			if firstPos then
				firstPos = math.min(currentPos,firstPos)
			else
				firstPos = currentPos
			end
		end
	end
	--if firstPos then
		--addDebug(Nil,'findTemplate','Finaly: Found ' .. templateName .. ' starting from ' .. startPos .. ' at pos ' .. firstPos)
	--else
		--addDebug(Nil,'findTemplate','Finaly: Found no ' .. templateName .. ' starting from ' .. startPos)
	--end
	return firstPos
end

-- isTemplateCalledOnlyOnce() returns true if there are 0..1 call of <templateName> in the calling page or false if there are 2 or more
function isTemplateCalledOnlyOnce(templateName,templateForms,templateFormsDebug)
	local wikicode = mw.title.getCurrentTitle():getContent()
	if not wikicode then
		-- Called from preview before creation of page
		return false
	end
	if not templateFormsDebug then
		templateFormsDebug = templateForms
	end
	local firstPos = findTemplate(wikicode,templateName,templateForms,templateFormsDebug,1)
	if not firstPos then
		-- There is a bug
		--addDebug(Nil,'isTemplateCalledOnlyOnce','no ' .. templateName .. ' found (strange)')
		return true
	end
	firstPos = findTemplate(wikicode,templateName,templateForms,templateFormsDebug,firstPos+3)
	if firstPos then
		-- There is at least 2 calls
		--addDebug(Nil,'isTemplateCalledOnlyOnce','multiple ' .. templateName .. ' found => return false')
		return false
	else
		-- Only one call to template
		--addDebug(Nil,'isTemplateCalledOnlyOnce','single ' .. templateName .. ' found => return true')
		return true
	end
end

nvForms		 = {'{{VN|', '{{VN\n', '{{VN ', '{{VN/', '{{VN}'}
nvFormsDebug = {'{{VN|', '{{VN<cr>', '{{VN<space>', '{{VN/', '{{VN}'}

-- isVnCalledOnlyOnce() returns true if there are 0..1 VN in the calling page or false if there are 2.. VN
function isVnCalledOnlyOnce()
	return isTemplateCalledOnlyOnce('VN',nvForms,nvFormsDebug)
end

-- verifyVNParameter() returns Nil when a VN parameter is accepted or an error category when the parameter is incorrect
function verifyVNParameter(default)
	if not default then
		return Nil
	end

	if isScientificName(default) and default ~= 'Aves' then
		return ' [[Category:Pages with incorrect biology template usage|VN]] '
	elseif string.find(default, "'''", 1, true) then
		-- default contains bold
		return ' [[Category:Pages with incorrect biology template usage|BoldOrItalic]] '
	elseif (string.sub(default, 1, 2) == "''") then
		-- default starts with italic
		if string.find(default, "'':", 1, true) then
			-- Correct: |ja=''Vespa mandarinia japonica'': XXX
			-- Incorrect: |en='''[[:en:whatever|]]'''
		elseif string.find(default, 'fossil specimens', 1, true) then
			-- Correct: |ja=''Oudenodon'' fossil specimens
		elseif string.find(string.lower(default), 'hybrid', 1, true) then
			-- Correct: |de=''Ara''-Hybride
			-- Correct: |en=''Ara'' hybrids
		elseif string.find(string.lower(default), 'cultivars', 1, true) then
			-- Correct: |de=''Neoreglia'' cultivars
		else
			-- Incorrect: |en=''Vespa mandarinia japonica''
			return ' [[Category:Pages with incorrect biology template usage|BoldOrItalic]] '
		end
	end
	return Nil
end

function trimOrNullify(str)
	if not str then
		return Nil
	end
	str = mw.text.trim(str)
	if str == '' then
		return Nil
	end
	return str
end

-- getVN() is called by Template:VN
function getVN(options)
	local useWikidata = isTrue(options.useWikidata)
	local useWikidataIsCalculated = not options.useWikidata or string.len(options.useWikidata) == 0
	if useWikidataIsCalculated and mw.wikibase then
		local sciname = trimOrNullify(options.sciname)
		if sciname then
			-- isScientificName(sciname) returns true if sciname==<category or gallery name>
			useWikidata = isScientificName(sciname)
			addDebug(Nil,'getVN','useWikidata not set but sciname=' .. sciname .. ' => using useWikidata=equalsCategoryName()=' .. tostring(useWikidata))
		else
			useWikidata = isVnCalledOnlyOnce()
			addDebug(Nil,'getVN','useWikidata not set and sciname not set => using useWikidata=isVnCalledOnlyOnce()=' .. tostring(useWikidata))
		end
	end

	local entity = Nil
	if mw.wikibase then
		-- wikidata library is enabled
		entity = mw.wikibase.getEntity()
	end
	local userLang = options.lang
	local default = options[userLang]
	local additionalCategory = ''

	local vn = ''
	local vnEntry = getVNEntry(entity,userLang,default,useWikidata,true)
	if vnEntry then
		vn = vnEntry
	end

	for index, lang in pairs(languages) do
	 	default = options[lang]
	 	local verification = verifyVNParameter(default,additionalCategory)
	 	if verification then
	 		additionalCategory = verification
	 	end
		if lang == userLang then
			-- Already displayed in bold
		else
		 	vnEntry = getVNEntry(entity,lang,default,useWikidata,false)
		 	if vnEntry then
			 	vn = vn .. vnEntry
			end
		end
	end

	if string.len(vn) == 0 then
		vn = 'No common name has yet been provided in this ' .. getCurrentNamespace()
		if not mw.wikibase then
			-- wikidata library is not enabled
			vn = vn .. '. (Soon common names will be retrieved from wikidata)'
		elseif useWikidata then
			if entity then
				vn = vn .. ' nor in [[wikidata:' .. entity.id .. '|wikidata]]'
			else
				vn = vn .. ' and no [https://www.wikidata.org/w/index.php?button=&title=Special%3ASearch&search=' .. string.gsub(mw.title.getCurrentTitle().text,' ','+') .. ' wikidata item] is associated with it'
			end
		else
			if useWikidataIsCalculated then
				-- useWikidata= not provided + (sciname!=<category or gallery name> or multiple VN in page) => don't display additional info in all VN
			else
				if entity then
					vn = vn .. '. <small>(You could activate the search in [[wikidata:' .. entity.id .. '|wikidata]] by adding useWikidata=1 inside {{VN}} )</small>'
				else
					vn = vn .. '. <small>(No [https://www.wikidata.org/w/index.php?button=&title=Special%3ASearch&search=' .. string.gsub(mw.title.getCurrentTitle().text,' ','+') .. ' wikidata item] is associated with this ' .. getCurrentNamespace() .. ')</small>'
				end
			end
		end
	else
		if not mw.wikibase then
			-- wikidata library is not enabled
		elseif not entity then
			vn = vn .. ' <small>(Note: no [https://www.wikidata.org/w/index.php?button=&title=Special%3ASearch&search=' .. string.gsub(mw.title.getCurrentTitle().text,' ','+') .. ' wikidata item] is associated with this ' .. getCurrentNamespace() .. ')</small>'
		end
	end
	return vn .. additionalCategory
end

-- Compares the ITIS identifior provided to {{ITIS}} with the id stored in wikidata by property p815
-- If there is a difference, the returned string displays an error + adds category 'Pages with incorrect biology template usage'
-- See https://www.wikidata.org/wiki/Wikidata:Taxonomy_task_force for sitePropertyId
function compareSiteIdWithWikidata(frame, sitePropertyId, siteName, templateName, templateForms)
	if not isCurrentNamespaceACategoryOrAGallery() then
		-- We do checks only in galleries and categories (When you look at Template, no param is provided
		return ''
	end

	local commonsSiteId = trimOrNullify(frame.args['1'])
	if not commonsSiteId then
		return ' Error: no ' .. siteName .. ' id has been provided. [[Category:Pages with incorrect biology template usage|id]]'
	end

	local commonsSciName = trimOrNullify(frame.args['sciname'])
	if not commonsSciName then
		return ' Error: no ' .. siteName .. ' sciName has been provided. [[Category:Pages with incorrect biology template usage|sciname]]'
	end

	local validity = trimOrNullify(frame.args['validity'])
	if validity then
		validity = string.lower(validity)
		if validity == 'nv' then
			-- taxon is invalid => no check is possible
			return ''
		else
			-- validity parameter has an incorrect value
			return ' Error: Incorrect parameter validity [[Category:Pages with incorrect biology template usage|validity]]'
		end
	end

	local checks = trimOrNullify(frame.args['checks'])
	if checks and string.lower(checks) == 'no' then
		-- checks have been disabled
		return ''
	end

	if not mw.wikibase then
		-- Wikidata library is not enabled => no check is possible
		return ''
	end

	local entity = mw.wikibase.getEntity()
	if not entity then
		-- There is no link to wikidata => no check is possible
		return ''
	end

	if not isTemplateCalledOnlyOnce(templateName,templateForms,templateForms) then
		-- If template is called multiple times...
		return ''
	end

	if commonsSciName ~= 'none' then
		getScientificNamesFromWikidata()
		if not tableIsEmpty(_scientificNamesFromWikidata) then
			if not _scientificNamesFromWikidata[commonsSciName] then
				return ' <small>(Warning: different scientific name between [[wikidata:' .. entity.id .. '|wikidata]] (' .. tableToString(_scientificNamesFromWikidata) .. ') and wikicommons (' .. commonsSciName .. '))</small>[[Category:Pages with biology property different than on Wikidata|' .. siteName .. ']]'
			end
		end
	end

	local wikidataSiteId = trimOrNullify(getProperty(entity, false, {'claims', sitePropertyId, 0 , 'mainsnak', 'datavalue', 'value'}))
	if not wikidataSiteId then
		-- Property (like p815 for 'identifiant ITIS') is not defined in wikidata => no check possible
		return ''
	elseif wikidataSiteId == commonsSiteId then
		-- Both identifier are equal: perfect
		return ''
	else
		return ' <small>(Warning: different ' .. siteName .. ' Id between [[wikidata:' .. entity.id .. '|wikidata]] (' .. wikidataSiteId .. ') and wikicommons (' .. commonsSiteId .. '))</small>[[Category:Pages with biology property different than on Wikidata|' .. siteName .. ']]'
	end
end

-- This function is called by User:Liné1/sandbox2 to discover Lua ;-)
function test(frame,args)
	--local text = ''
	--return mw.text.nowiki(mw.title.getCurrentTitle():getContent())
	addDebug('fr','myfunction','hello')
	addDebug('fr','yourfunction','salut')
	addDebug('de','myfunction','hello')
	return getDebug()
end

----------------------------------------------------------------------------------------------------------------
---------- PUBLIC FUNCTIONS ------------------------------------------------------------------------------------
----------------------------------------------------------------------------------------------------------------
local p = {}

function p.getVN(frame)
	local vn = getVN(frame.args)
	-- Now add debug traces if activated
	if _debug then
		vn = vn .. getDebug()
	end
	return vn
end

function p.compareITISIdWithWikidata(frame)
	return compareSiteIdWithWikidata(frame, 'p815', 'ITIS', 'ITIS', {'{{ITIS'}) .. getDebug()
end

function p.compareNCBIIdWithWikidata(frame)
	return compareSiteIdWithWikidata(frame, 'p685', 'NCBI', 'NCBI', {'{{NCBI'}) .. getDebug()
end

function p.compareTPDBIdWithWikidata(frame)
	return compareSiteIdWithWikidata(frame, 'p842', 'TPDB', 'TPDB', {'{{TPDB'}) .. getDebug()
end

function p.compareWoRMSIdWithWikidata(frame)
	return compareSiteIdWithWikidata(frame, 'p850', 'WoRMS', 'WRMS', {'{{WRMS'}) .. getDebug()
end

function p.compareFishBaseSpeciesIdWithWikidata(frame)
	return compareSiteIdWithWikidata(frame, 'p938', 'FishBase', 'FishBase', {'{{FishBase'}) .. getDebug()
end

function p.compareMSWIdWithWikidata(frame)
	return compareSiteIdWithWikidata(frame, 'p959', 'MSW', 'MSW', {'{{MSW'}) .. getDebug()
end

function p.compareEOLIdWithWikidata(frame)
	return compareSiteIdWithWikidata(frame, 'p830', 'EOL', 'EOL', {'{{EOL'}) .. getDebug()
end

function p.compareGBIFIdWithWikidata(frame)
	return compareSiteIdWithWikidata(frame, 'p846', 'GBIF', 'GBIF', {'{{GBIF'}) .. getDebug()
end

function p.compareThePlantListIdWithWikidata(frame)
	return compareSiteIdWithWikidata(frame, 'p1070', 'ThePlantList', 'ThePlantList species', {'{{ThePlantList species'}) .. getDebug()
end

function p.compareTropicosIdWithWikidata(frame)
	return compareSiteIdWithWikidata(frame, 'p960', 'Tropicos', 'Tropicos', {'{{Tropicos', '{{tropicos'}) .. getDebug()
end

function p.compareMycoBankIdWithWikidata(frame)
	return compareSiteIdWithWikidata(frame, 'p962', 'MycoBank', 'MycoBank', {'{{MycoBank'}) .. getDebug()
end

function p.test(frame)
	return test(frame,frame.args)
end

function p.suppressCategory(frame)
	-- for testcases
	return suppressCategory(frame.args['1'])
end

function p.suppressDisambiguation(frame)
	-- for testcases
	return suppressDisambiguation(frame.args['1'])
end

function p.isLink(frame)
	-- for testcases (return string when normal function returns boolean)
	return tostring(not not isLink(frame.args['1']))
end

function p.strContains(frame)
	-- for testcases (return string when normal function returns boolean)
	return tostring(not not strContains(frame.args['long'],frame.args['small']))
end

function p.calcVNEntry(frame)
	-- for testcases
	return mw.text.nowiki(calcVNEntry(frame.args['lang'],frame.args['interwiki'],frame.args['vnFromWikidata'],frame.args['vnSource'],frame.args['default'])) .. getDebug()
end

function p.isTrue(frame)
	-- for testcases (return string when normal function returns boolean)
	return tostring(not not isTrue(frame.args['1']))
end

function p.getCurrentNamespace()
	-- for testcases
	return getCurrentNamespace()
end

function p.isCurrentNamespaceACategoryOrAGallery()
	-- for testcases (return string when normal function returns boolean)
	return tostring(not not isCurrentNamespaceACategoryOrAGallery())
end

function p.getScientificNames()
	-- for testcases (return string when normal function returns table)
	return mw.text.nowiki(tableToString(getScientificNames()))
end

return p