Jump to content

Mòideal:Wikidata4Bio

O Wiktionary

Mòideal: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:
Mòideal:Wikidata4Bio/sandbox is the sandbox of this module (to test new developments) which is used in the sandbox template {{VN/sandbox}}
Functions and their usage
  • dumpWikidata() to display all wikidata data accessible (not associated with wikidata)
  • getVN() is called by {{VN}}
  • getVNTitle() is called by {{VN}} to display '[modify wikidata]'
  • getLanguagesManagedByVN() is called by {{VN/doc}} to display all languageCodes managed by VN.
  • Wikidata checks (See here all wikidata properties for databases):
    • compareAvibaseIdWithWikidata() is called by {{Avibase}} with sciname
    • compareBioLibIdWithWikidata() is called by {{BioLib}} with decoratedSciname
    • compareEOLIdWithWikidata() is called by {{EOL}} with decoratedSciname
    • compareFaunaeurIdWithWikidata() is called by {{Faunaeur}} with decoratedSciname
    • compareFishBaseSpeciesIdWithWikidata() is called by {{FishBase species}} with sciname
    • compareFungorumIdWithWikidata() is called by {{Fungorum species}}, {{Fungorum genus}} and {{Fungorum taxon}} with sciname
    • compareGBIFIdWithWikidata() is called by {{GBIF}} with decoratedSciname
    • compareGRINURLWithWikidata() is called by {{GRIN species}}, {{GRIN genus}}, {{GRIN subtribe}}, {{GRIN tribe}}, {{GRIN subfamily}} and {{GRIN family}} with sciname
    • compareGymnospermDatabaseIdWithWikidata() is called by {{Gymnosperm Database}} with sciname
    • compareIPNIIdWithWikidata() is NOT called by {{IPNI}}, which should be renamed {{IPNI search}} because it does not use an id
    • compareITISIdWithWikidata() is called by {{ITIS}} with decoratedSciname
    • compareIUCNIdWithWikidata() is called by {{IUCN}} with sciname
    • compareLPSNUrlWithWikidata() is called by {{LPSN}} with sciname
    • compareMSWIdWithWikidata() is called by {{MSW}} with decoratedSciname
    • compareMycoBankIdWithWikidata() is called by {{MycoBank}} with sciname
    • compareNCBIIdWithWikidata() is called by {{NCBI}} with decoratedSciname
    • compareNRCSPlantsIdWithWikidata() is called by {{NRCS Plants}} with sciname
    • compareThePlantListIdWithWikidata() is called by {{ThePlantList species}} with sciname
    • compareTPDBIdWithWikidata() is called by {{TPDB}} with decoratedSciname
    • compareTropicosIdWithWikidata() is called by {{Tropicos}} with sciname
    • compareWoRMSIdWithWikidata() is called by {{WRMS}} and {{WRMS species}} with decoratedSciname
How to improve and test this module
  1. develop your modification in Mòideal:Wikidata4Bio/sandbox, the sandbox of this module
  2. verify your changes in Mòideal:Wikidata4Bio/sandbox/testcases
  3. verify your changes in {{VN}} in :
  4. verify your changes in {{ITIS}} in :
  5. report your modifications in Mòideal:Wikidata4Bio
  6. verify your changes in Mòideal:Wikidata4Bio/testcases
  7. verify your changes in Ailanthus altissima with normal {{VN}}
See also

local _common=require('Module:Biology')

-- global variable which receives debug info if not nil (Should be =nil in Wikidata4Bio and ={} in Wikidata4Bio/sandbox)
local _debug=nil
local property_P31_InstanceOf			= 'P31'
local property_P105_TaxonRank			= 'P105'
local property_P225_TaxonName			= 'P225'
local property_P301_CategoryMainTopic	= 'P301'
local property_P373_CommonsCategory		= 'P373'
local property_P910_TopicSMainCategory	= 'P910'
local property_P935_CommonsGallery		= 'P935'
local property_P1843_TaxonCommonName	= 'P1843'
local item_wikimedia_category			= 'Q4167836'
local item_clade						= 'Q713623'
local item_virus						= 'Q808'

----------------------------------------------------------------------------------------------------
---------- Debug utilities -------------------------------------------------------------------------
----------------------------------------------------------------------------------------------------

-- addDebug() adds debug/verbose/trace info to _debug
-- * param lang: either 'fr' or nil
-- * param functionName: the name of the calling function whithout ()
-- * param text: the text of the 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
	local 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
	local 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


----------------------------------------------------------------------------------------------------
---------- String utilities ------------------------------------------------------------------------
----------------------------------------------------------------------------------------------------

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

-- returns true if an item of listShortString in contained in longString
function stringContainsAnItemOfList(longString, listShortString)
	longString = string.lower(longString)
	for key, value in pairs(listShortString) do
		local listItem = string.lower(value)
		if string.contains(longString, listItem) then
			addDebug(nil, 'stringContainsAnItemOfList', 'longString= ' .. longString .. ' listShortString=' .. tableToString(listShortString,false) .. ' return true')
			return true
		end
	end
	addDebug(nil, 'stringContainsAnItemOfList', 'longString= ' .. longString .. ' listShortString=' .. tableToString(listShortString,false) .. ' return false')
	return false
end

-- returns true is searchedString is found in listString
function listContainsExactString(searchedString, listString)
	searchedString = string.lower(searchedString)
	for key, value in pairs(listString) do
		local listItem = string.lower(value)
		if searchedString == listItem then
			addDebug(nil, 'listContainsExactString', 'searchedString=' .. searchedString .. ' listString=' .. tableToString(listString,false) .. ' return true')
			return true
		end
	end
	addDebug(nil, 'listContainsExactString', 'searchedString=' .. searchedString .. ' listString=' .. tableToString(listString,false) .. ' return false')
	return false
end


----------------------------------------------------------------------------------------------------
---------- Table utilities -------------------------------------------------------------------------
----------------------------------------------------------------------------------------------------

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

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

function hasSmallerKeyThanFunctor(pair1, pair2)
	local key1 = pair1[1]
	if (type(key1) ~= 'string') then
		return false
	end
	local key2 = pair2[1]
	if (type(key2) ~= 'string') then
		return false
	end
	return key1 < key2
end

-- tableToString() returns a string out of a table for debug and non regression purpose sorted by keys
-- If withKey then format '<key>=<value>' is returned else format '<value>' is returned
function tableToString(mytable, withKey)
	-- Sort mytable into mytable2
	local mytable2 = {}
	for key, value in pairs(mytable) do
		table.insert(mytable2,{key,value})
	end
	table.sort(mytable2, hasSmallerKeyThanFunctor)
	
	-- Serialize mytable2 into result
	local result = nil
	for key, value in pairs(mytable2) do
		local currentResult = value[2]
		if withKey then
			currentResult = value[1] .. '=' .. currentResult
		end
		if result then
			result = result .. ', ' .. currentResult
		else
			result = currentResult
		end
	end
	if not result then
		return ''
	end
	return result
end


----------------------------------------------------------------------------------------------------
---------- Namespace/articleName utilities ---------------------------------------------------------
----------------------------------------------------------------------------------------------------

-- isLink(name) return true when name is a link syntax like '[[sdfs|dfsfsd]]'
-- It has a testcase/non-regression module Module:Wikidata4Bio/testcases using testcase_isLink()
function isLink(name)
	if not name then
		return false
	end
	if string.find(name, ']]', 1, true) then
		return true
	end
	if string.find(name, '[http:', 1, true) then
		return true
	end
	if string.find(name, '[https:', 1, true) then
		return true
	end
	if string.find(name, '<ref', 1, true) then
		return true
	end
	if string.find(name,'[[', 1, true) then
		return true
	end
	local cleanedName = mw.text.killMarkers(name)
	if cleanedName ~= name then
		-- name contains markers
		return true
	end
	return false
end


----------------------------------------------------------------------------------------------------
---------- Lang utilities --------------------------------------------------------------------------
----------------------------------------------------------------------------------------------------

local _defaultLangCode = mw.language.getContentLanguage():getCode()

function hasSmallerLangCodeThanFunctor(langCode1, langCode2)
	if langCode1 == _defaultLangCode then
		return true
	end
	if langCode2 == _defaultLangCode then
		return false
	end
	return langCode1 < langCode2
end

local _languageNamesByCode = nil
local _additionalLanguageNamesByCode = nil
local _languageCodes = nil
-- Fill _languageNamesByCode and _languageCodes
function calcLanguages(userLangCode)
	if _languageNamesByCode then
		return
	end
	_languageNamesByCode = mw.language.fetchLanguageNames()
	_languageCodes = {}
	for langCode, _ in pairs(_languageNamesByCode) do
		table.insert(_languageCodes, langCode)
	end
	_additionalLanguageNamesByCode = mw.language.fetchLanguageNames(userLangCode,'all')
	for langCode, langName in pairs(_additionalLanguageNamesByCode) do
		if _languageNamesByCode[langCode] then
			-- standard language
		else
			table.insert(_languageCodes, langCode)
		end
	end
	
	table.sort(_languageCodes, hasSmallerLangCodeThanFunctor)
end

-- getLanguagesManagedByVN() is called by {{VN/doc}} to display all languageCodes managed by VN.
-- It does not use calcLanguages()
-- We support 417 wikipedia languages + 302 additional languages (like 'en-us')
function getLanguagesManagedByVN(args)
	local userLangCode = args.lang
	local languageNamesByCode = mw.language.fetchLanguageNames(userLangCode)

	local languageCodes = {}
	for lang, _ in pairs(languageNamesByCode) do
		table.insert(languageCodes, lang)
	end
	table.sort(languageCodes, hasSmallerLangCodeThanFunctor)

	local languages = nil
	for _, langCode in pairs(languageCodes) do
		if languages then
			languages = languages .. ', '
		else
			languages = "'''" .. tostring(table.getn(languageCodes)) .. " languages (having interwiki and wikidata): '''"
		end
		languages = languages .. langCode .. ':' .. languageNamesByCode[langCode]
	end

--	Additional Languages from https://www.mediawiki.org/wiki/Extension:Cldr are not well managed by wikicommons.
--	You cannot retrieve all their autonym.
--	fetchLanguageNames(nil,'all') returns the same as fetchLanguageNames('en')
--	See https://www.mediawiki.org/wiki/Extension_talk:Scribunto/Lua_reference_manual#fetchLanguageNames
--	And https://doc.wikimedia.org/mediawiki-core/1.25.5/php/Language_8php_source.html  search for 'TODO: also include when'
--	And https://doc.wikimedia.org/mediawiki-core/REL1_25/php/Language_8php_source.html search for 'TODO: also include when'
	local languageNamesByCode2 = mw.language.fetchLanguageNames(userLangCode,'all')

	languageCodes = {}
	for lang, _ in pairs(languageNamesByCode2) do
		if not languageNamesByCode[lang] then
			table.insert(languageCodes, lang)
		end
	end
	table.sort(languageCodes, hasSmallerLangCodeThanFunctor)

	local first = true
	for _, langCode in pairs(languageCodes) do
		if first then
			languages = languages .. "\n\n:'''And " .. tostring(table.getn(languageCodes)) .. " additional languages (without interwiki and wikidata): '''"
			first = false
		else
			languages = languages .. ', '
		end
		if langCode == 'sms' then
			-- sms: is interpreted by wikimedia as an url leading to an unwanted blue link
			languages = languages .. mw.text.nowiki('sms:')
		else
			languages = languages .. langCode .. ':'
		end
		languages = languages .. languageNamesByCode2[langCode]
	end

	-- Determine if fetchLanguageNames(nil,'all') is buggy 
	local fetchLanguageNamesNilAllBug = true
	local languageNamesByCode3 = mw.language.fetchLanguageNames(nil,'all')
	for lang, _ in pairs(languageNamesByCode3) do
		if not languageNamesByCode[lang] then
			fetchLanguageNamesNilAllBug = false	-- No more buggy ???
			break
		end
	end

	local userLangName = tostring(languageNamesByCode[userLangCode])
	languages = languages .. "\n:'''Note: additional languages name are in your language (" .. userLangName

	if fetchLanguageNamesNilAllBug then
		languages = languages .. ") because of a current limitation on additional languages'''"
	else
		languages = languages .. ") but only very temporary'''"
	end

	return languages
end


----------------------------------------------------------------------------------------------------
---------- Entities utilities ----------------------------------------------------------------------
----------------------------------------------------------------------------------------------------

-- areSameValidEntity() return true if  entities are set and have the same id
function areSameValidEntity(entity1, entity2)
	if not entity1 then
		return false
	end
	if not entity2 then
		return false
	end
	return (entity1.id == entity2.id)
end

function createEmptyEntities(paramEntity)
	return {entity=paramEntity, otherEntity=nil, useWikidata=false, useWikidataIsCalculated=false}
end

-- retrieveEntitiesSimple() returns {entity, otherEntity}
-- Called      by retrieveEntities() which is called by getVN() and getVNTitle()
-- Also called by compareSiteIdWithWikidata() (were the is no useWikidata=)
function retrieveEntitiesSimple(entities)
	if entities == nil then
		entities = createEmptyEntities(nil)
	end
	if not mw.wikibase then
		-- Case 1
		return entities
	end
	entities.entity = mw.wikibase.getEntityObject()
	if entities.entity then
		local P301values = getStringProperties(entities.entity, property_P301_CategoryMainTopic, true)
		addDebug(nil, 'retrieveEntitiesSimple', 'P301=' .. propertiesToString(P301values))
		if P301values.size > 1 then
 			-- Error message will be displayed by checkEntities except if entities.entity.id is in  _catItemsWithMultipleSubjects
			addDebug(nil, 'retrieveEntitiesSimple', 'multiple otherEntity (P301) found => none is used')
		elseif P301values.size == 1 then
			entities.otherEntity = mw.wikibase.getEntityObject(P301values[1])
			if entities.otherEntity then
				if areSameValidEntity(entities.otherEntity, entities.entity) then
					addDebug(nil, 'retrieveEntitiesSimple', 'otherEntity == entity')
					entities.otherEntity = nil
				else
					addDebug(nil, 'retrieveEntitiesSimple', 'otherEntity found')
				end
			else
				addDebug(nil, 'retrieveEntitiesSimple', 'otherEntity=nil when otherEntityId=' .. P301values[1])
			end
		end
	end

	return entities
end

-- retrieveEntities() returns {entity, otherEntity, useWikidata, useWikidataIsCalculated}
function retrieveEntities(args)
	local entities = createEmptyEntities(nil)
	if not mw.wikibase then
		-- Case 1
		return entities
	end
	-- wikidata library is enabled
 	if string.startsWith(args.useWikidata,'Q') then
 		-- useWikidata=Q1246
 		local success, myentity = pcall(mw.wikibase.getEntityObject, args.useWikidata)
		if not success or not myentity then
			-- Case 2
			addDebug(nil, 'retrieveEntities', 'Case2: useWikidata=' .. args.useWikidata .. ' but failed to load ' .. args.useWikidata)
			return
		end
		-- Case 3
		entities.entity = myentity
		entities.useWikidata = true
		if areSameValidEntity(entities.entity, mw.wikibase.getEntityObject()) then
			addDebug(nil, 'retrieveEntities', 'Case 3bis: useWikidata=' .. args.useWikidata .. ' but not needed as associated to the same item => useWikidata=true')
		else
			addDebug(nil, 'retrieveEntities', 'Case 3: useWikidata=' .. args.useWikidata .. ' => useWikidata=true')
		end
		entities.entitySpecified = true
 	else
 		retrieveEntitiesSimple(entities)
		if entities.entity then
			if string.isNilOrEmpty(args.useWikidata) then
				entities.useWikidataIsCalculated = true
				local sciname = string.trimOrNullify(args.sciname)
				if sciname then
					-- Case 4.true: useWikidata set to true
					-- Case 4.false: useWikidata set to false
					-- isScientificName(sciname) returns true if sciname==<category or gallery name>
					entities.useWikidata = isScientificName(entities, sciname)
					addDebug(nil, 'retrieveEntities', 'Case 4: useWikidata not set but sciname=' .. sciname .. ' => using useWikidata=isScientificName(sciname)=' .. tostring(entities.useWikidata))
				else
					-- Case 5.true: useWikidata set to true
					-- Case 5.false: useWikidata set to false
					entities.useWikidata = isVnCalledOnlyOnce()
					addDebug(nil, 'retrieveEntities', 'Case 5: useWikidata not set and sciname not set => using useWikidata=isVnCalledOnlyOnce()=' .. tostring(entities.useWikidata))
				end
			else
				-- Case 6.true: args.useWikidata=1
				-- Case 6.false: args.useWikidata=0
				entities.useWikidata = string.isTrue(args.useWikidata)
				addDebug(nil, 'retrieveEntities', 'Case 6: args.useWikidata=' .. tostring(args.useWikidata) .. ' => entities.useWikidata=' .. tostring(entities.useWikidata))
			end
		else
			-- Case 7.nil: args.useWikidata=nil but entity=nil => we avoid useWikidataIsCalculated
			-- Case 7.true: args.useWikidata=1 but entity=nil
			-- Case 7.false: args.useWikidata=0 and entity=nil
			entities.useWikidata = false
			addDebug(nil, 'retrieveEntities', 'Case 7: args.useWikidata=' .. tostring(args.useWikidata) .. ' but as entity=nil => entities.useWikidata=' .. tostring(entities.useWikidata))
		end
	end

	return entities
end


----------------------------------------------------------------------------------------------------
---------- Wikidata properties utilities -----------------------------------------------------------
----------------------------------------------------------------------------------------------------

-- Only used by getStringProperties()
function getStringProperty(claim, propertyListsEntityIds)
	local propertyValue = claim.mainsnak.datavalue
	if not propertyValue then
		addDebug(nil, 'getStringProperties', 'found claim without mainsnak.datavalue ' .. mw.text.jsonEncode(claim))
		return nil
	end
	propertyValue = propertyValue.value
	if not propertyValue then
		addDebug(nil, 'getStringProperties', 'found claim without mainsnak.datavalue.value ' .. mw.text.jsonEncode(claim))
		return nil
	end
	if propertyListsEntityIds then
		propertyValue = propertyValue.id
		if not propertyValue then
			addDebug(nil, 'getStringProperties', 'found claim without mainsnak.datavalue.value.id ' .. mw.text.jsonEncode(claim))
			return nil
		end
	end
	local propertyValueType = type(propertyValue)
	if propertyValueType ~= 'string' then
		addDebug(nil, 'getStringProperties', 'found non string (' .. propertyValueType .. ') ' .. mw.text.jsonEncode(numericId))
		return nil
	end
	propertyValue = mw.text.trim(propertyValue)
	if propertyValue == '' then
		-- get rid of this empty entry
		return nil
	end
	return propertyValue
end

-- getStringProperties() returns a list in the form {size=3, 1=<value>, 2=<value> ...} corresponding to <entity>.claims.<propertyName>.<index>.mainsnak.datavalue.value
-- if propertyListsEntityIds is set, the path is <entity>.claims.<propertyName>.<index>.mainsnak.datavalue.value.id
-- getStringProperties(entity, P373) returns {size=3, 1='NameOfCommonsCategory', 2='Name2OfCommonsCategory' ...}
function getStringProperties(entity, propertyName, propertyListsEntityIds)
	local claims = entity:getBestStatements( propertyName )  -- no need to check if return is nil, worst case it is an empty tab []
	local properties = {size=0}
	for _,claim in pairs(claims) do
		local propertyValue = getStringProperty(claim, propertyListsEntityIds)
		if propertyValue then
			properties.size = properties.size + 1
			properties[properties.size] = propertyValue
		end
	end
	return properties
end

-- propertiesContain() return true if searchedPropertyValue is found in properties
function propertiesContain(properties, searchedPropertyValue)
	for index=1, properties.size, 1
	do
		if properties[index] == searchedPropertyValue then
			return true
		end
	end
	return false
end

-- returns a string in the form 'item1, item2, item3'
function propertiesToString(properties)
	local stringToReturn = ''
	local prefix = ''
	for index=1, properties.size, 1
	do
		stringToReturn = stringToReturn .. prefix .. tostring(properties[index])
		prefix = ', '
	end
	return stringToReturn
end


----------------------------------------------------------------------------------------------------
---------- Wikidata P31 utilities ------------------------------------------------------------------
----------------------------------------------------------------------------------------------------

local _p31valuesForTaxon = {
	size=11,
	'Q16521',					-- Q16521=='Taxon'
	'Q310890',					-- Q310890='Monotypic taxon'
	'Q4150646',					-- Q4150646='Cultivar group'
	'Q4886',					-- Q4886='cultivar'
	'Q23038290',				-- Q23038290='fossil taxon'
	'Q47487597',				-- Q47487597='monotypic fossil taxon'
	item_clade,
	item_virus,
	'Q2568288',					-- Q2568288='Ichnotaxon'
	'Q1297859',  				-- Q1297859='species aggregate'
	item_wikimedia_category		-- Q4167836='page de catégorie de Wikimedia'
}

-- p31valuesIsAccepted(p31values) returns true if p31values contains on of the properties listed in _p31valuesForTaxon
function p31valuesIsAccepted(p31values)
	for index=1, _p31valuesForTaxon.size, 1
	do
		if propertiesContain(p31values, _p31valuesForTaxon[index]) then
			return true
		end
	end
	return false
end

----------------------------------------------------------------------------------------------------
---------- Wikidata link utilities -----------------------------------------------------------------
----------------------------------------------------------------------------------------------------

-- getWikidataLinkFormat1Common('Qxx','B','C') returns "[[wikidata:Qxx|'B']] <small>(C)</small>"
-- getWikidataLinkFormat1Common('Pxx','B','C') returns "[[wikidata:property:Qxx|'B']] <small>(C)</small>"
-- Called only by getWikidataLinkFormat1() and getWikidataLinkFormat1ForEntity()
function getWikidataLinkFormat1Common(itemId, linkLabel, complement)
	if string.startsWith(itemId,'P') then
		-- It is a property
		itemId = 'property:' .. itemId
	end
	local link = '[[wikidata:' .. itemId .. '|\'' .. linkLabel .. '\']]'
	if complement then
		link = link .. ' <small>(' .. complement .. ')</small>'
	end
	return link
end

-- getWikidataLinkFormat1(property_P301_CategoryMainTopic) return "[[wikidata:P301|'category's main topic']] <small>(P301)</small>"
-- getWikidataLinkFormat1('Q270') return "[[wikidata:Q270|'Nepenthaceae']] <small>(Q270)</small>"
-- getWikidataLinkFormat1('P373') return "[[wikidata:property:P373|'common category']] <small>(P373)</small>"
-- Called only by checkSpecifiedEntity()/checkSingleEntity()/checkTwoEntities() to display a red message
-- It has a testcase/non-regression module Module:Wikidata4Bio/testcases using testcase_getWikidataLinkFormat1()
function getWikidataLinkFormat1(itemId)
	local entity = mw.wikibase.getEntity(itemId)
	if entity then
		local label = entity:getLabel()
		if label then
			return getWikidataLinkFormat1Common(itemId, label, itemId)
		end
	end
	local defaultLabel = nil
	if itemId == property_P225_TaxonName then
		defaultLabel = 'taxon name'
	elseif itemId == property_P1843_TaxonCommonName then
		defaultLabel = 'taxon common name'
	elseif itemId == property_P301_CategoryMainTopic then
		defaultLabel = "category's main topic"
	elseif itemId == property_P910_TopicSMainCategory then
		defaultLabel = "topic's main category"
	elseif itemId == property_P373_CommonsCategory then
		defaultLabel = 'commons category'
	else
		return getWikidataLinkFormat1Common(itemId, itemId, nil)
	end
	return getWikidataLinkFormat1Common(itemId, defaultLabel, itemId)
end

-- getWikidataLinkFormat1ForEntity(entityQ2704296) return "[[wikidata:Q2704296|'Nepenthaceae']] <small>(Q2704296)</small>"
-- Called only by checkSpecifiedEntity()/checkSingleEntity()/checkTwoEntities() to display a red message
function getWikidataLinkFormat1ForEntity(entity)
	if entity then
		local label = entity:getLabel()
		if label then
			return getWikidataLinkFormat1Common(entity.id, label, entity.id)
		end
		return getWikidataLinkFormat1Common(entity.id, entity.id, nil)
	end
	return 'nil entity'
end

-- getWikidataLinkFormat1ForProperties(properties) returns getWikidataLinkFormat1(item1), getWikidataLinkFormat1(item2)...
function getWikidataLinkFormat1ForProperties(properties)
	local list = ''
	local listPrefix = ''
	for index=1, properties.size, 1
	do
		list = list .. listPrefix .. getWikidataLinkFormat1(properties[index])
		listPrefix = ', '
	end
	return list
end

-- getWikidataLinkFormat2(entityQ2704296) returns "[[wikidata:Q2704296|wikidata 'Nepenthaceae']]"
-- * param entity must be an entity of a Qxxx (properties are not managed)
-- Called only by getVN() & compareSiteIdWithWikidata()
-- It has a testcase/non-regression module Module:Wikidata4Bio/testcases using testcase_getWikidataLinkFormat2()
function getWikidataLinkFormat2(entity)
	if entity then
		local label = entity:getLabel()
		if label then
			return '[[wikidata:' .. entity.id .. '|wikidata \'' .. label .. '\']]'
		end
		return '[[wikidata:' .. entity.id .. '|wikidata item ' .. entity.id .. ']]'
	end
	return 'nil entity'
end


----------------------------------------------------------------------------------------------------
---------- Wikidata entity checks ------------------------------------------------------------------
----------------------------------------------------------------------------------------------------

-- addWikidataError() returns a red error text and a Category:Pages with incorrect biology template usage
function addWikidataError(error, message, sortkey)
	if not sortkey then
		sortkey = '?'
	end
	return error .. '<BR/><span class="error">Error in Wikidata: ' .. message.. '</span>[[Category:Pages with biology property incorrect on Wikidata|' .. sortkey .. ']]'
end

local _catItemsWithMultipleSubjects = {
	Q8261220  = 1,  -- Category:Arctiinae		- There are 2 Arctiinae with narrow and wide sense
	Q7149713  = 1,  -- Category:Donkeys			- There are 2 syn: Equus asinus & Equus africanus asinus
	Q7328661  = 1,  -- Category:Eudicots		- There are 2 syn: eudicots & Eudicotyledoneae
	Q6256259  = 1,  -- Category:Gynatrix		- There are 2 sujects: scientific name + vernaculare name (for sv only)
	Q18282087 = 1,  -- Category:Cleistogenes	- There are 2 syn: Cleistogenes & Kengia
	Q8763590  = 1,  -- Category:Pitohui			- There are 2 sujects: scientific name + vernaculare name (for en only)
	Q9522340  = 1,  -- Category:Ruminantia		- There are 2 sujects: scientific name + Ruminant/Rumination
	Q9675089  = 1,  -- Category:Sinningia		- There are 2 sujects: scientific name + vernaculare name (for sv only)
	Q9414408  = 1,  -- Category:Homo sapiens	- There are 2 sujects: scientific name + vernaculare name
}
local _catItemsWithDisabledTests = {
	Q1456850  = 1,  -- Category:Birds			- A mess because it is a vernaculare name
	Q8700233  = 1,  -- Category:Aves			- A mess because of Category:Birds
	Q7157802  = 1,  -- Category:Animals			- A mess because it is a vernaculare name
	Q9470370  = 1,  -- Category:Gregarines		- A mess because it has many syn
	Q5608148  = 1,  -- Category:Insects			- A mess because it is a vernaculare name
}

-- checkEntities() checks that wikidata properties are coherents
-- Called by getVN()
function checkEntities(entities)
	if not entities.entity then
		-- No wikidata link => nothing to test
		return ''
	end
	if not entities.useWikidata then
		-- Case 4.false: useWikidata=<scientificName> != <category or gallery name>
		-- Case 5.false: VN is called multiple times
		-- Case 6.false: args.useWikidata=0
		return ''
	end
	if entities.entitySpecified then
		return checkSpecifiedEntity(entities.entity)
	end
	if not _common.isCurrentNamespaceACategoryOrAGallery() then
		return ''
	end
	if _catItemsWithDisabledTests[entities.entity.id] then
		-- No test possible
		return ''
	end
	if entities.otherEntity then
		return checkTwoEntities(entities)
	else
		return checkSingleEntity(entities)
	end
end

-- checkTaxonEntity() is called by checkSingleEntity() on entities.entity or by checkTwoEntities() on entities.otherEntity
-- checkTaxonEntity() checks that:
-- * P31_InstanceOf is filled
-- * P225_TaxonName is filled
-- * P105_TaxonRank is filled except for clades
function checkTaxonEntity(entity, error)
	if not entity then
		return error
	end
	local p31values = getStringProperties(entity, property_P31_InstanceOf, true)
   	if p31values.size > 0 then
   		-- 'instance of' (P31) has been set
		if propertiesContain(p31values, item_wikimedia_category) then
   			-- entity is like 'Category:Lissomini (Q30015898)': not much check can be done
   			return error
		elseif p31valuesIsAccepted(p31values) then 
   			-- entity is like 'Lissomini (Q21222258)': a lot of tests can be done
   		else
   			-- Strange P31 value
			error = addWikidataError(error, 'wikidata item ' .. getWikidataLinkFormat1ForEntity(entity) .. ' property ' .. getWikidataLinkFormat1(property_P31_InstanceOf) .. ' has a strange value '
				.. getWikidataLinkFormat1ForProperties(p31values) .. ' (currently accepted values: ' .. getWikidataLinkFormat1ForProperties(_p31valuesForTaxon) .. ')', 'C')
			return error
   		end
   	else
		error = addWikidataError(error, 'wikidata item ' .. getWikidataLinkFormat1ForEntity(entity) .. ' has no property ' .. getWikidataLinkFormat1(property_P31_InstanceOf), 'C')
		return error -- no property P31, so we cannot know it it is the taxon item => no more checks
   	end

	local properties = getStringProperties(entity, property_P225_TaxonName)
   	if properties.size > 0 then
   		-- 'taxon name' (P225) has been set
   	else
		error = addWikidataError(error, 'wikidata item ' .. getWikidataLinkFormat1ForEntity(entity) .. ' has no property ' .. getWikidataLinkFormat1(property_P225_TaxonName), 'D')
   	end

	if propertiesContain(p31values, item_clade) then
		-- clade can have ranks, no ranks or empty ranks
	elseif propertiesContain(p31values, item_virus) then
		-- virus can have ranks or no ranks
	else
		properties = getStringProperties(entity, property_P105_TaxonRank, true)
	   	if properties.size > 0 then
	   		-- taxon rank (P105) has been set
	   	else
			error = addWikidataError(error, 'wikidata item ' .. getWikidataLinkFormat1ForEntity(entity) .. ' has no property ' .. getWikidataLinkFormat1(property_P105_TaxonRank), 'E')
		end
   end
   return error
end

-- returns '[[:Category:INPUT|INPUT]]' out of 'INPUT'
function getCategoryLink(category)
	return '[[:Category:' .. category .. '|' .. category .. ']]'
end

function checkSpecifiedEntity(specifiedEntity)
	local cleanEntities = createEmptyEntities(nil)
	retrieveEntitiesSimple(cleanEntities)

	if areSameValidEntity(specifiedEntity, cleanEntities.entity) then
		if cleanEntities.otherEntity then
			addDebug(nil, 'checkSpecifiedEntity', 'specifiedEntity==cleanEntities.entity && cleanEntities.otherEntity => checkTwoEntities()')
			return checkTwoEntities(cleanEntities)
		else
			addDebug(nil, 'checkSpecifiedEntity', 'specifiedEntity==cleanEntities.entity but !cleanEntities.otherEntity => checkSingleEntity()')
			return checkSingleEntity(cleanEntities)
		end
	end
	if areSameValidEntity(specifiedEntity, cleanEntities.otherEntity) then
		addDebug(nil, 'checkSpecifiedEntity', 'specifiedEntity=cleanEntities.otherEntity => checkTwoEntities()')
		return checkTwoEntities(cleanEntities)
	end
	
	local error=''
	local entity_P373 = getStringProperties(specifiedEntity, property_P373_CommonsCategory)
	if entity_P373.size == 0 then
		error	= addWikidataError(error, 'wikidata item ' .. getWikidataLinkFormat1ForEntity(specifiedEntity) .. ' property ' .. getWikidataLinkFormat1(property_P373_CommonsCategory) .. ' should not be empty.', 'G')
	end
	
	local specifiedEntityCommons = specifiedEntity:getSitelink('commonswiki')
	if not specifiedEntityCommons then
		error	= addWikidataError(error, 'wikidata item ' .. getWikidataLinkFormat1ForEntity(specifiedEntity) .. ' sitelink to Commons should not be empty. Perhaps you should not set useWikidata= but instead set wikidata sitelink to Commons.', 'H')
	end

	return error
end

function isCurrentItemClearlyACategory(entity)
	local p31values = getStringProperties(entity, property_P31_InstanceOf, true)
	if p31values.size > 0 then
		-- 'instance of' (P31) has been set
		if propertiesContain(p31values, item_wikimedia_category) then
			-- entity is like 'Category:Lissomini (Q30015898)': not much check can be done
			return true
		end
	end
	return false
end

-- checkSingleEntity() checks that:
-- F) item has an english label
-- if item is a category:
--	8) item.P373 (commons category) == currentCommonsCategory
--  a) item should not have a P910 (topic's main category)
-- if item is a gallery:
--	9) item.P935 (commons gallery) == currentCommonsGallery
--  b) item should not have a P301 (category's main topic)
function checkSingleEntity(entities)
	local currentPageName = mw.title.getCurrentTitle().text
	local error = checkTaxonEntity(entities.entity, '')
	
	if string.isNilOrEmpty(entities.entity:getLabel('en')) then
		if _common.isCurrentNamespaceACategory() then
			if isCurrentItemClearlyACategory(entities.entity) then
				error	= addWikidataError(error, 'wikidata item ' .. getWikidataLinkFormat1ForEntity(entities.entity) .. ' has no english label (It could be ' .. _common.suppressDisambiguation(mw.title.getCurrentTitle().prefixedText) .. ').', 'F')
			else
				error	= addWikidataError(error, 'wikidata item ' .. getWikidataLinkFormat1ForEntity(entities.entity) .. ' has no english label (It could be ' .. _common.suppressDisambiguation(mw.title.getCurrentTitle().prefixedText) .. ' or ' .. _common.suppressDisambiguation(mw.title.getCurrentTitle().text) .. ').', 'F')
			end
		else
			error		= addWikidataError(error, 'wikidata item ' .. getWikidataLinkFormat1ForEntity(entities.entity) .. ' has no english label (It could be ' .. _common.suppressDisambiguation(mw.title.getCurrentTitle().text) .. ').', 'F')
		end
	end
	
	if _common.isCurrentNamespaceACategory() then
		local entity_P373 = getStringProperties(entities.entity, property_P373_CommonsCategory)
		if entity_P373.size == 1 then
			if currentPageName ~= entity_P373[1] then
				error	= addWikidataError(error, 'wikidata item ' .. getWikidataLinkFormat1ForEntity(entities.entity) .. ' property ' .. getWikidataLinkFormat1(property_P373_CommonsCategory) .. " should be '" .. currentPageName .. "' (not " .. getCategoryLink(entity_P373[1]) .. ').', '8')
			end
		elseif entity_P373.size > 1 then
			error		= addWikidataError(error, 'wikidata item ' .. getWikidataLinkFormat1ForEntity(entities.entity) .. ' property ' .. getWikidataLinkFormat1(property_P373_CommonsCategory) .. ' should not have multiple values.', '8')
		end
		local entity_P910 = getStringProperties(entities.entity, property_P910_TopicSMainCategory, true)
		if entity_P910.size >= 1 then
			error		= addWikidataError(error, 'wikidata item ' .. getWikidataLinkFormat1ForEntity(entities.entity) .. ' has ' .. getWikidataLinkFormat1(property_P910_TopicSMainCategory) .. '=' .. getWikidataLinkFormat1ForProperties(entity_P910) .. ' so you should move the link to current wikicommons category from ' .. getWikidataLinkFormat1ForEntity(entities.entity) .. ' to ' .. getWikidataLinkFormat1ForProperties(entity_P910) .. '.', 'A')
		end
	else
		local entity_P935 = getStringProperties(entities.entity, property_P935_CommonsGallery)
		if entity_P935.size == 1 then
			if currentPageName ~= entity_P935[1] then
				error	= addWikidataError(error, 'wikidata item ' .. getWikidataLinkFormat1ForEntity(entities.entity) .. ' property ' .. getWikidataLinkFormat1(property_P935_CommonsGallery) .. " should be '" .. currentPageName .. "' (not [[" .. entity_P935[1] .. ']]).', '9')
			end
		elseif entity_P935.size > 1 then
			error		= addWikidataError(error, 'wikidata item ' .. getWikidataLinkFormat1ForEntity(entities.entity) .. ' property ' .. getWikidataLinkFormat1(property_P935_CommonsGallery) .. ' should not have multiple values.', '9')
		else
			-- temporarely disabled (too much work)
			--error		= addWikidataError(error,   'wikidata item ' .. getWikidataLinkFormat1ForEntity(entities.entity) .. ' property ' .. getWikidataLinkFormat1(property_P935_CommonsGallery) .. " should be '" .. currentPageName .. "' (not empty).", '9')
		end
		local entity_P301 = getStringProperties(entities.entity, property_P301_CategoryMainTopic, true)
		if entity_P301.size >= 1 then
			error		= addWikidataError(error, 'wikidata item ' .. getWikidataLinkFormat1ForEntity(entities.entity) .. ' property ' .. getWikidataLinkFormat1(property_P301_CategoryMainTopic) .. '=' .. getWikidataLinkFormat1ForProperties(entity_P301) .. ' so you should move the link to current wikicommons gallery from ' .. getWikidataLinkFormat1ForEntity(entities.entity) .. ' to ' .. getWikidataLinkFormat1ForProperties(entity_P301) .. '.', 'B')
		end
	end
	return error
end

-- checkTwoEntities() checks that:
-- 1) entities.entity.P301.size (category's main topic) == 1 (list of exceptions: _catItemsWithMultipleSubjects)
-- 2) entities.entity.P301 (category's main topic) == entities.otherEntity
-- 3) entities.otherEntity.P910.size (topic's main category) > 1
-- 4) entities.entity is included in entities.otherEntity.P910 (topic's main category)
-- 5) entities.entity.P373 (commons category) == currentPageName (or is not set)
-- 6) entities.otherEntity.P373 (commons category) == currentPageName
-- 7) entities.entity.P935 (commons gallery) == entities.otherEntity.P935 (commons gallery) (They can be  empty)
-- F)  items have an english label
function checkTwoEntities(entities)
	local error = checkTaxonEntity(entities.otherEntity, '')

	if string.isNilOrEmpty(entities.entity:getLabel('en')) then
		error		= addWikidataError(error, 'wikidata cat item ' .. getWikidataLinkFormat1ForEntity(entities.entity) .. ' has no english label (It could be ' .. _common.suppressDisambiguation(mw.title.getCurrentTitle().prefixedText) .. ').', 'F')
	end
	if string.isNilOrEmpty(entities.otherEntity:getLabel('en')) then
		error		= addWikidataError(error, 'wikidata gallery item ' .. getWikidataLinkFormat1ForEntity(entities.otherEntity) .. ' has no english label (It could be ' .. _common.suppressDisambiguation(mw.title.getCurrentTitle().text) .. ').', 'F')
	end

	local entity_P301 = getStringProperties(entities.entity, property_P301_CategoryMainTopic, true)
	if entity_P301.size == 0 then
		error   	= addWikidataError(error, 'wikidata cat item ' .. getWikidataLinkFormat1ForEntity(entities.entity) .. ' property ' .. getWikidataLinkFormat1(property_P301_CategoryMainTopic) .. ' should be ' .. getWikidataLinkFormat1ForEntity(entities.otherEntity) .. ' (not empty).', '1')
	elseif entity_P301.size > 1 then
		if _catItemsWithMultipleSubjects[entities.entity.id] then
			-- These few items have multiple subjects and it seems normal. Sadly we will not be able to extract data for the otherentity as there are 2
		else
			error	= addWikidataError(error, 'wikidata cat item ' .. getWikidataLinkFormat1ForEntity(entities.entity) .. ' has multiple ' .. getWikidataLinkFormat1(property_P301_CategoryMainTopic) .. '.', '1')
		end
	else
		if entity_P301[1] ~= entities.otherEntity.id then
			error	= addWikidataError(error, 'wikidata cat item ' .. getWikidataLinkFormat1ForEntity(entities.entity) .. ' property ' .. getWikidataLinkFormat1(property_P301_CategoryMainTopic) .. ' should be ' .. getWikidataLinkFormat1ForEntity(entities.otherEntity) .. ' (not ' .. getWikidataLinkFormat1ForProperties(entity_P301) .. '.', '2')
		end
	end

	local otherEntity_P910 = getStringProperties(entities.otherEntity, property_P910_TopicSMainCategory, true)
	if otherEntity_P910.size == 0 then
		error		= addWikidataError(error, 'wikidata gallery item ' .. getWikidataLinkFormat1ForEntity(entities.otherEntity) .. ' property ' .. getWikidataLinkFormat1(property_P910_TopicSMainCategory) .. ' should be ' .. getWikidataLinkFormat1ForEntity(entities.entity) .. ' (not empty).', '3')
	else
		-- Multiple cat can be link to same gallery: Catégorie:Polygonaceae (Q7416468) & Catégorie:Polygonaceae (noms scientifiques) (Q9085824)
		--error = addWikidataError(error, '[[wikidata:' .. entities.otherEntity.id .. '|wikidata gallery item]] has multiple ' .. getWikidataLinkFormat1(property_P910_TopicSMainCategory) .. '.', '2')
		if not propertiesContain(otherEntity_P910, entities.entity.id) then
			error	= addWikidataError(error, 'wikidata gallery item ' .. getWikidataLinkFormat1ForEntity(entities.otherEntity) .. ' property ' .. getWikidataLinkFormat1(property_P910_TopicSMainCategory) .. ' should contain ' .. getWikidataLinkFormat1ForEntity(entities.entity) .. ' (currently ' .. getWikidataLinkFormat1ForProperties(otherEntity_P910) .. ').', '4')
		end
	end

	local currentPageName = mw.title.getCurrentTitle().text

	local entity_P373 = getStringProperties(entities.entity, property_P373_CommonsCategory)
	if entity_P373.size == 1 then
		if currentPageName ~= entity_P373[1] then
			error	= addWikidataError(error, 'wikidata cat item ' .. getWikidataLinkFormat1ForEntity(entities.entity) .. ' property ' .. getWikidataLinkFormat1(property_P373_CommonsCategory) .. " should be '" .. currentPageName .. "' (not " .. getCategoryLink(entity_P373[1]) .. ').', '5')
		end
	elseif entity_P373.size > 1 then
		error		= addWikidataError(error, 'wikidata cat item ' .. getWikidataLinkFormat1ForEntity(entities.entity) .. ' property ' .. getWikidataLinkFormat1(property_P373_CommonsCategory) .. ' should not have multiple values.', '5')
	end

	local otherEntity_P373 = getStringProperties(entities.otherEntity, property_P373_CommonsCategory)
	if otherEntity_P373.size == 1 then
		if currentPageName ~= otherEntity_P373[1] then
			error	= addWikidataError(error, 'wikidata gallery item ' .. getWikidataLinkFormat1ForEntity(entities.otherEntity) .. ' property ' .. getWikidataLinkFormat1(property_P373_CommonsCategory) .. " should be '" .. currentPageName .. "' (not " .. getCategoryLink(otherEntity_P373[1]) .. ').', '6')
		end
	elseif otherEntity_P373.size > 1 then
		error		= addWikidataError(error, 'wikidata gallery item ' .. getWikidataLinkFormat1ForEntity(entities.otherEntity) .. ' property ' .. getWikidataLinkFormat1(property_P373_CommonsCategory) .. ' should not have multiple values.', '6')
	else
		error		= addWikidataError(error, 'wikidata gallery item ' .. getWikidataLinkFormat1ForEntity(entities.otherEntity) .. ' property ' .. getWikidataLinkFormat1(property_P373_CommonsCategory) .. " should be '" .. currentPageName .. "' (not empty).", '6')
	end

	if error ~= '' then
		-- Excellent test to be activated later
		local entity_P935 = getStringProperties(entities.entity, property_P935_CommonsGallery)
		local otherEntity_P935 = getStringProperties(entities.otherEntity, property_P935_CommonsGallery)
	
		if entity_P935.size > 1 or otherEntity_P935.size > 1 then
			if entity_P935.size > 1 then
				error = addWikidataError(error, 'wikidata cat item ' .. getWikidataLinkFormat1ForEntity(entities.entity) .. ' property ' .. getWikidataLinkFormat1(property_P935_CommonsGallery) .. ' should not have multiple values.', '7')
			end
			if otherEntity_P935.size > 1 then
				error = addWikidataError(error, 'wikidata gallery item ' .. getWikidataLinkFormat1ForEntity(entities.otherEntity) .. ' property ' .. getWikidataLinkFormat1(property_P935_CommonsGallery) .. ' should not have multiple values.', '7')
			end
		elseif entity_P935.size == 1 then
			local galleryName = entity_P935[1]
			if otherEntity_P935.size == 1 then
				if galleryName ~= otherEntity_P935[1] then
					error = addWikidataError(error, 'wikidata cat item ' .. getWikidataLinkFormat1ForEntity(entities.entity) .. ' and gallery item ' .. getWikidataLinkFormat1ForEntity(entities.otherEntity)
						.. ' should have the same ' .. getWikidataLinkFormat1(property_P935_CommonsGallery) .. ' (not [[' .. galleryName .. ']] and [[' .. otherEntity_P935[1] .. ']]).', '7')
				end
			elseif otherEntity_P935.size == 0 then
				error = addWikidataError(error, 'wikidata gallery item ' .. getWikidataLinkFormat1ForEntity(entities.otherEntity) .. ' property ' .. getWikidataLinkFormat1(property_P935_CommonsGallery) .. " should be '" .. galleryName .. "' (not empty).", '7')
			end
		else
			-- entity_P935.size==0
			if otherEntity_P935.size == 1 then
				--error = addWikidataError(error, 'wikidata cat item ' .. getWikidataLinkFormat1ForEntity(entities.entity) .. ' property ' .. getWikidataLinkFormat1(property_P935_CommonsGallery) .. " should be '" .. otherEntity_P935[1] .. "' (not empty).", '7')
			elseif otherEntity_P935.size == 0 then
				--  are empty
			end
		end
	end

	--local entity_P935 = getProperty(entities.entity,      property_sitelinks_commonswiki_title)
	
	return error
end


----------------------------------------------------------------------------------------------------
---------- ScientificName utilities ----------------------------------------------------------------
----------------------------------------------------------------------------------------------------

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

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

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

	if entities.entity then
		local p225values = getStringProperties(entities.entity, property_P225_TaxonName)
		for index=1, p225values.size, 1
		do
			local name = p225values[index]
			_scientificNamesFromWikidata[name] = name
		end
	end
	if entities.otherEntity then
		local p225values = getStringProperties(entities.otherEntity, property_P225_TaxonName)
		for index=1, p225values.size, 1
		do
			local name = p225values[index]
			_scientificNamesFromWikidata[name] = name
		end
	end

	addDebug(nil,'getScientificNamesFromWikidata',tableToString(_scientificNamesFromWikidata,false))
	return _scientificNamesFromWikidata
end

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

function addScientificNameAndItsItalic(name)
	_scientificNames[name] = name
	name = "''" .. name .. "''"
	_scientificNames[name] = name
end

function addScientificName(name)
	name = string.lower(name)
	addScientificNameAndItsItalic(name)

	local spacePos = string.find(name, ' × ', 1, true)
	if spacePos then
		-- Official WikiCommons syntax: 'A × B' => let us add 'A ×B' and 'A X B'
		addScientificNameAndItsItalic(string.gsub(name, ' × ', ' ×', 2))
		addScientificNameAndItsItalic(string.gsub(name, ' × ', ' x ', 2))
		addScientificNameAndItsItalic(string.gsub(name, ' × ', ' ', 2))
	else
		spacePos = string.find(name, ' ×', 1, true)
		if spacePos then
			-- Syntax: 'A ×B' => let us add 'A × B' and 'A X B'
			addScientificNameAndItsItalic(string.gsub(name, ' ×', ' × ', 2))
			addScientificNameAndItsItalic(string.gsub(name, ' ×', ' x ', 2))
			addScientificNameAndItsItalic(string.gsub(name, ' ×', ' ', 2))
		else
			spacePos = string.find(name, ' x ', 1, true)
			if spacePos then
				-- Syntax: 'A X B' => let us add 'A ×B' and 'A × B'
				addScientificNameAndItsItalic(string.gsub(name, ' x ', ' ×', 2))
				addScientificNameAndItsItalic(string.gsub(name, ' x ', ' × ', 2))
				addScientificNameAndItsItalic(string.gsub(name, ' x ', ' ', 2))
			end
		end
	end
	--[=====[ Add GenusName out of species and subspecies.
	-- Problem 'Puma concolor' vernacular name is perhaps 'Puma' because 'Puma yagouaroundi' is named Jaguarondi
	local spacePos = string.find(name, ' ', 1, true)
	if spacePos then
		local genusName = mw.text.trim(string.sub(name, 1, spacePos-1))
		addScientificNameAndItsItalic(genusName)
	end
	--]=====]
end

-- getScientificNames() return a dictionary containing all the possible lowercase scientific names of the taxon described out of:
-- * Case 1: current category/gallery name (which is supposed to be a scientific name)
-- * Case 2: {{pagename}}
-- * Case 3: wikidata property P225 (via getScientificNamesFromWikidata())
-- * Case 1bis and 3bis: genusName out of speciesName for monotypic genus (Category:Petrobium arboreum has interwiki named Petrobium)
-- It has a testcase/non-regression module Module:Wikidata4Bio/testcases using testcase_getScientificNames()
function getScientificNames(entities)
	if _scientificNames then
		return _scientificNames
	end
	_scientificNames = {}

	-- Case 1:
	local name = _common.suppressDisambiguation(mw.title.getCurrentTitle().text)
	addScientificName(name)

	-- Case 2:
	addScientificName('{{pagename}}')

	-- Case 3:
	for key, value in pairs(getScientificNamesFromWikidata(entities)) do
		addScientificName(value)
	end

	addDebug(nil,'getScientificNames',tableToString(_scientificNames,false))
	return _scientificNames
end

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

	if _scientificNames[name] then
		return true
	end

	name = string.gsub(name, ' ×', ' ', 2)
	if _scientificNames[name] then
		return true
	end

	return false
end


----------------------------------------------------------------------------------------------------
---------- VN utilities ----------------------------------------------------------------------------
----------------------------------------------------------------------------------------------------

-- getVNFromWikidataVN() returns the VernacularNames from wikidata P1843 for a specific lang
function getVNFromWikidataVN(entities, entityToTest, lang)
	if not entityToTest then
		return nil
	end
	local claimsP1843 = entityToTest:getBestStatements(property_P1843_TaxonCommonName)  -- no need to check if return is nil, worst case it is an empty tab []
	local vernacularNames = nil
	for _,p1843 in pairs(claimsP1843) do 
		local value = p1843.mainsnak.datavalue
		if value then
			value = value.value
			if value and value.language == lang then
				if isScientificName(entities, value.text) then
					-- this gallery/category vernaculare name is in fact a scientific name, so it is a vernaculare name
				else
					if vernacularNames then
						vernacularNames = vernacularNames .. ', ' .. value.text
					else
						vernacularNames = value.text
					end
				end
			end
		end
	end
	return vernacularNames
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(entities, lang, interwiki)
	if not interwiki then
		--addDebug(lang,nil,'Interwiki ' .. lang .. '=nil')
		return nil
	end
	interwiki = _common.suppressCategory(interwiki)
	interwiki = _common.suppressDisambiguation(interwiki)
	if isScientificName(entities, interwiki) then
		--addDebug(lang,nil,'Interwiki ' .. lang .. '=' .. interwiki .. ' rejected as it is a sciname')
		return nil
	end
	-- this gallery/category interwiki is not a scientific name, so it is a vernaculare name
	--addDebug(lang,nil,'Interwiki ' .. lang .. '=' .. interwiki .. ' is not a sciname')
	return interwiki
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(entities, lang, entityToTest)
	if not entityToTest then
		return nil
	end
	local label = entityToTest:getLabel(lang)
	if not label then
		return nil
	end
	label = _common.suppressCategory(label)
	label = _common.suppressDisambiguation(label)
	if isScientificName(entities, label) then
		--addDebug(lang,nil,'Label ' .. lang .. '=' .. label .. ' rejected as it is a sciname')
		return nil
	end
	-- this gallery/category label is not a scientific name, so it is a vernaculare name
	return label
end

-- calcVNEntry() puts together the different info coming from VN parameters (lang & default) and wikidata (interwiki & vnFromWikidata)
-- It has a testcase/non-regression module Module:Wikidata4Bio/testcases using testcase_calcVNEntry()
function calcVNEntry(lang, interwiki, otherInterwiki, vnFromWikidata, vnSource, default)
	local vnEntry = vnFromWikidata
	local vnEntryDescription = vnSource
	local interwikiDebug = 'interwiki'
	if not interwiki then
		interwiki = otherInterwiki
		interwikiDebug = 'otherInterwiki'
	end
	if interwiki then
		vnEntry = '[[:' .. lang .. ':' .. interwiki .. '|' .. vnFromWikidata .. ']]'
		vnEntryDescription = 'bis: [[' .. interwikiDebug ..'|' .. vnSource .. ']]'
	else
		vnEntryDescription = ': ' .. vnEntryDescription
	end
	if default then
		if string.contains(vnFromWikidata, default) then -- string.contains(long,small)
			-- default is in vnFromWikidata => no need to display default
			-- example: default='cat' vnFomInterwiki='common cat'
			addDebug(lang,nil,'parameter ' .. lang .. ' rejected as contained in ' .. vnSource .. ', Case1' .. vnEntryDescription)
			return vnEntry
		else
			if string.contains(default, vnFromWikidata) then -- string.contains(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: [[' .. interwikiDebug .. '|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(entities, lang, default)
	if isScientificName(entities, default) then
		addDebug(lang,nil,lang .. ' parameter is a scientificName')
		default = nil
	end
  
	local interwiki      = entities.entity:getSitelink(lang .. 'wiki')
	local otherInterwiki = nil
	if entities.otherEntity then
		otherInterwiki = entities.otherEntity:getSitelink(lang .. 'wiki')
	end
	
	-- First try entity.claims.P1843.<index>.mainsnak.datavalue.value.text
	local vnFromP1843 = getVNFromWikidataVN(entities, entities.entity, lang)
	if vnFromP1843 then
		return calcVNEntry(lang, interwiki, otherInterwiki, vnFromP1843, property_P1843_TaxonCommonName, default)
	end

	local vnFromOtherP1843 = getVNFromWikidataVN(entities, entities.otherEntity, lang)
	if vnFromOtherP1843 then
		return calcVNEntry(lang, interwiki, otherInterwiki, vnFromOtherP1843, 'otherP1843', default)
	end

	-- Second try entity.sitelinks.frwiki.title (interwiki)
	local vnFomInterwiki = getVNFromWikidataInterwiki(entities, lang, interwiki)
	if vnFomInterwiki then
		return calcVNEntry(lang, interwiki, otherInterwiki, vnFomInterwiki, 'interwiki', default)
	end

	local vnFomOtherInterwiki = getVNFromWikidataInterwiki(entities, lang, otherInterwiki)
	if vnFomOtherInterwiki then
		return calcVNEntry(lang, interwiki, otherInterwiki, vnFomOtherInterwiki, 'otherInterwiki', default)
	end

	-- Third try entity.labels.<lang>.value
	local vnFromLabel = getVNFromWikidataLabel(entities, lang, entities.entity)
	if vnFromLabel then
		return calcVNEntry(lang, interwiki, otherInterwiki, vnFromLabel, 'label', default)
	end

	local vnFromOtherLabel = getVNFromWikidataLabel(entities, lang, entities.otherEntity)
	if vnFromOtherLabel then
		return calcVNEntry(lang, interwiki, otherInterwiki, vnFromOtherLabel, 'otherLabel', default)
	end

	-- Interwiki and label are not provided or are scientific name
	if default and not isLink(default) then
		if interwiki then
			addDebug(lang,nil,'Case7: [[interwiki|VNparameter]]')
			if lang == 'en-us' then
				lang = 'en'
			end
			return '[[:' .. lang .. ':' .. interwiki .. '|' .. default .. ']]'
		end
		if otherInterwiki then
			addDebug(lang,nil,'Case7bis: [[otherInterwiki|VNparameter]]')
			if lang == 'en-us' then
				lang = 'en'
			end
			return '[[:' .. lang .. ':' .. otherInterwiki .. '|' .. default .. ']]'
		end
	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

-- getVNEntry() returns HTML for one language in getVN()
function getVNEntry(entities, langCode, langName, additionalLang, default, useWikidata, bold)
	local vernacularName
	if not entities.entity then
		-- This gallery/category has no wikidata element (you are perhaps in the template page)
		vernacularName = default
	elseif not useWikidata then
		-- We are required not to use wikidata
		vernacularName = default
	elseif additionalLang then
		-- additionalLang have no interwiki, no wikidata
		if default then
			addDebug(langCode,nil,'Case10: VNparameter because additionalLang')
		end
		vernacularName = default
	else
		vernacularName = getVernacularNameFromWikidata(entities, langCode, default)
	end
	if vernacularName and string.len(vernacularName) > 0 then
		-- <bdi> is just like <span> but works better when there is mixed bidi text
		local entry = "* '''" .. '<bdi lang="' .. langCode .. '">' .. langName
		if _debug then
			-- In VN/sandbox, let us display the langCode
			entry = entry .. " (" .. langCode .. ")"
		end
		entry = entry .. "</bdi>:"
		if not bold then
			-- we are already in bold (to display lang), let us close bold to display vernacularName
			entry = entry .. "'''"
		end
		-- Next line was starting with &nbsp;
		entry = entry .. '&nbsp;<bdi class="vernacular" lang="' .. langCode .. '">' .. vernacularName .. "</bdi>"
		if bold then
			-- close bold if not closed before
			entry = entry .. "'''"
		end
		return entry .. '\n'
	else
		return nil
	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 not even 1 call
		--addDebug(nil,'isTemplateCalledOnlyOnce','no ' .. templateName .. ' found (strange)')
		return false
	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

-- isEnglishName(name) tried to distinguish english name from scientific name
function isEnglishName(name)
	local lowerName = string.lower(name)
	if string.find(lowerName, 'hybrid', 1, true) then
		-- like 'Mammal hybrids' or "''Ara''-Hybride" or "''Ara'' hybrids"
		return true
	elseif string.find(lowerName, ' goat', 1, true) then
		-- Dutch white goat
		--addDebug(nil,'isEnglishName', default .. ' is in fact an goat')
		return true
	elseif string.find(lowerName, 'cultivars', 1, true) then
		-- like 'Neoreglia cultivars'
		return true
	elseif string.find(lowerName, 'fossil specimens', 1, true) then
		-- like 'Oudenodon fossil specimens'
		return true
	else
		-- Remove ' because next test is about first character.
		-- We are not interesting in testing if ' is the first param
		-- For example "''Magnolie''" was detected as an english name because M was not the first character
		name = string.gsub(name, "'", '')
		if string.upperFirstLowerOthers(name) ~= string.upperFirst(name) then
			-- like 'Waterlily Dahlias' because of the D in the middle
			return true
		end
	end
	return false
end

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

	if isScientificName(entities, default) then
		-- default is a scientific name
		-- addDebug(lang,'verifyVNParameter',default .. ' is a scientific name')
		if lang == 'la' then
			-- Correct: case where scientific name is a real latin name (Abies)
		elseif isEnglishName(default) then
			-- Correct: |en=Mammal hybrids or |en=Dutch white goat or |en=Waterlily Dahlias
			--addDebug(lang,'verifyVNParameter',default .. ' is in fact an goat')
		else
			return _common.incorrectBiologyTemplateUsage('VN', 'Parameter ' .. lang .. ' uses scientific name', 'VN')
		end
	end
	if string.find(default, "'''", 1, true) then
		-- default contains bold
		return _common.incorrectBiologyTemplateUsage('VN', 'Parameter ' .. lang .. ' uses bold', 'VN')
	end
	if string.startsWith(default, "''") then
		-- default starts with italic
		if string.find(default, "'':", 1, true) then
			-- Correct: |ja=''Vespa mandarinia japonica'': XXX
			-- Incorrect: |en='''[[:en:whatever|]]'''
		elseif isEnglishName(default) then
			-- Correct: |ja=''Oudenodon'' fossil specimens |de=''Neoreglia'' cultivars |de=''Ara''-Hybride |en=''Ara'' hybrids
		else
			-- Incorrect: |en=''Vespa mandarinia japonica''
			return _common.incorrectBiologyTemplateUsage('VN', 'Parameter ' .. lang .. ' uses italic', 'VN')
		end
	end
	return nil
end

local _excludedWikiProjects = {
	wikidatawiki	= 1,
	commonswiki		= 1,
	specieswiki		= 1,
	metawiki		= 1,
	mediawikiwiki	= 1
}

-- calcAdditionalInterwiki() returns interwikis from specified entity (useWikidata=Qxxx) or otherEntity
function calcAdditionalInterwiki(entities)
	local entitySource = entities.otherEntity
	local entityAutomaticInterwiki = entities.entity
	if entities.entitySpecified then
		entitySource = entities.entity -- specified by user
		entityAutomaticInterwiki = mw.wikibase.getEntityObject() -- not used by VN but used by page for automaticInterwiki
	end

	local additionalInterwiki = ''
	if entitySource and entitySource.sitelinks then
		for i, j in pairs(entitySource.sitelinks) do
			local lang = mw.ustring.sub( j.site, 1, -5) -- split j.site into language and project parts
			local proj = mw.ustring.sub( j.site, -4)
			if not _excludedWikiProjects[j.site] and proj == 'wiki' then -- excludes sites on the list as well as Wikisource, Wikiquote, Wikivoyage etc
				if (entityAutomaticInterwiki and (not entityAutomaticInterwiki.sitelinks or not entityAutomaticInterwiki.sitelinks[j.site])) or not entityAutomaticInterwiki then -- excludes interwiki to projects that already have sitelinks in the present page
					lang = mw.ustring.gsub(lang, '_','-')
					additionalInterwiki = additionalInterwiki .. '[[' .. lang .. ':' .. j.title .. ']]' -- put together a interwiki-link to other projects
				end
			end
		end
		if additionalInterwiki ~= '' then
			additionalInterwiki = additionalInterwiki .. ' [[Category:Interwiki from wikidata]] '
		end
	end
	addDebug(nil, 'calcAdditionalInterwiki', 'AdditionalInterwiki=' .. additionalInterwiki)
	return additionalInterwiki
end

-- Used by {{VN}}
function getVN(args)
	-- Calc entity & otherEntity & useWikidata & useWikidataIsCalculated
	local entities = retrieveEntities(args)

	--addDebug(nil, 'getVN', mw.text.jsonEncode(entities))

	-- Access parameters
	local nocat = string.isTrue(args.nocat)
	local userLangCode = args.lang --frame:preprocess("{{int:Lang}}") or mw.language.getContentLanguage():getCode()
	local default = string.trimOrNullify(args[userLangCode])
	local provideNamesInCommonsIsPossible = not string.contains(args.provideNamesInCommons, "impossible")
	--addDebug(nil,'getVN','userLangCode=' .. tostring(userLangCode))

	-- Fill _languageNamesByCode, _additionalLanguageNamesByCode and _languageCodes
	calcLanguages(userLangCode)

	-- Calc first entry for user's language
	local vn = ''
	local additionalLang = false
	local userlangName = _languageNamesByCode[userLangCode]
	if userlangName then
		local vnEntry = getVNEntry(entities, userLangCode, userlangName, additionalLang, default, entities.useWikidata, true)
		if vnEntry then
			vn = vnEntry
		end
	end

	-- Calc entries for all other languages
	local additionalCategory = ''
	for _, langCode in pairs(_languageCodes) do
	 	default = args[langCode]
	 	if not nocat then
	 		local verification = verifyVNParameter(entities, langCode, default)
	 		if verification then
		 		additionalCategory = verification
	 		end
	 	end
		if langCode == userLangCode then
			-- Already displayed in bold
		else
			local langName = _languageNamesByCode[langCode]
			additionalLang = false
			if not langName then
				langName = _additionalLanguageNamesByCode[langCode]
				additionalLang = true
			end

		 	vnEntry = getVNEntry(entities, langCode, langName, additionalLang, default, entities.useWikidata, false)
		 	if vnEntry then
			 	vn = vn .. vnEntry
			end
		end
	end

	-- Display error when wikidata values are incorrect
	additionalCategory = additionalCategory .. checkEntities(entities)

	-- Add Category:Biology_categories_without_double_wikidata_item
	if entities.entity and not entities.otherEntity then
		-- Commons category or gallery has a single wikidata item
		local name = _common.suppressDisambiguation(mw.title.getCurrentTitle().text)
		local isSpecies = string.find(name, ' ', 1, true)
		if entities.entitySpecified then
			addDebug(nil,'getVN','entitySpecified + isSpecies=' .. tostring(isSpecies))
			if isSpecies then
				additionalCategory = additionalCategory .. '[[Category:Biology_pages_with_wikidata_item_specified_in_VN]]'
			else
				additionalCategory = additionalCategory .. '[[Category:Biology_pages_with_wikidata_item_specified_in_VN| ]]'
			end
		else
			if _common.isCurrentNamespaceACategory() then
				-- Commons category has a single wikidata item
				if isSpecies then
					-- Species Category without gallery may have only one wikidata item (the taxon)
				else
					additionalCategory = additionalCategory .. '[[Category:Biology_categories_without_double_wikidata_item]]'
				end
			end
		end
	elseif entities.entity and entities.otherEntity then
		-- Commons category has a double wikidata item
		additionalCategory = additionalCategory .. '[[Category:Biology_categories_with_double_wikidata_item]]'
	end

	-- Add small information about wikidata
	if string.len(vn) == 0 then
		if provideNamesInCommonsIsPossible then
			vn = 'No common name has yet been provided in this ' .. _common.getCurrentNamespace()
			if not mw.wikibase then
				-- Case 1: wikidata library is not enabled
				vn = vn .. '. (Soon common names will be retrieved from wikidata)'
			elseif args.useWikidata and string.startsWith(args.useWikidata,'Q') then
				if entities.entity then
					-- Case 3
					vn = vn .. ' nor in ' .. getWikidataLinkFormat2(entities.entity)
				else
					-- Case 2
					vn = vn .. incorrectBiologyTemplateUsage('VN', 'Wikidata ' .. args.useWikidata .. ' is not accessible', 'VN')
				end
			elseif entities.useWikidata then
				-- Case 4.true, 5.true: args.useWikidata=nil + no problem
				-- Case 6.true:         args.useWikidata=1
				if entities.entity then
					vn = vn .. ' nor in ' .. getWikidataLinkFormat2(entities.entity)
					if entities.otherEntity then
						vn = vn .. ' nor in ' .. getWikidataLinkFormat2(entities.otherEntity)
					end
				else
					-- Impossible ?
					vn = vn .. ' and no [https://www.wikidata.org/w/index.php?button=&title=Special%3ASearch&limit=500&search=' .. string.gsub(mw.title.getCurrentTitle().text,' ','+') .. ' wikidata item] is associated with it'
				end
			else
				if entities.useWikidataIsCalculated then
					-- Case 4.false, 5.false: args.useWikidata=nil + (sciname!=<category or gallery name> or multiple VN in page) => don't display additional info in all VN
				else
					if entities.entity then
						-- Case 6.false
						vn = vn .. '. <small>(You could activate the search in ' .. getWikidataLinkFormat2(entities.entity) .. ' by adding useWikidata=1 inside {{VN}} )</small>'
					else
						if string.isNilOrEmpty(args.useWikidata) or string.isTrue(args.useWikidata) then
							-- Case 7.nil, 7.true
							vn = vn .. '. <small>(No [https://www.wikidata.org/w/index.php?button=&title=Special%3ASearch&limit=500&search=' .. string.gsub(mw.title.getCurrentTitle().text,' ','+') .. ' wikidata item] is associated with this ' .. _common.getCurrentNamespace() .. ')</small>'
						else
							-- Case 7.false => it is normal that VN is empty, associating with wikidata will change nothing
						end
					end
				end
			end
		end
	else
		if not mw.wikibase then
			-- Case 1: wikidata library is not enabled
		elseif not entities.entity then
			if string.startsWith(args.useWikidata,'Q') or string.isNilOrEmpty(args.useWikidata) or string.isTrue(args.useWikidata) then
				-- Case 2, Case 7.nil, 7.true
				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 ' .. _common.getCurrentNamespace() .. ')</small>'
			else
				-- Case 7.false => it is normal that VN is empty, associating with wikidata will change nothing
			end			
		end
	end

	-- if entities.entity then
		-- Uncomment following line to dump all properties
		-- vn = vn .. '<BR/>' .. 'entities.entity=' .. mw.text.jsonEncode(entities.entity)
		-- if entities.otherEntity then
			-- Uncomment following line when 'Access to arbitrary items' is allowed
			-- vn = vn .. '<BR/>' .. 'entities.otherEntity.claims=' .. mw.text.jsonEncode(entities.otherEntity.claims)
		-- end
	-- end
	return vn .. additionalCategory .. calcAdditionalInterwiki(entities)
end

-- Used by {{VN}} to display '[modify wikidata]'
function getVNTitle(frame, args)
	-- Calc entity & otherEntity & useWikidata & useWikidataIsCalculated
	local entities = retrieveEntities(args)

	if not entities.useWikidata then
		return ''
	end
	if not entities.entity then
		return ''
	end

	local userLangCode = args.lang --frame:preprocess("{{int:Lang}}") or mw.language.getContentLanguage():getCode()
	--addDebug(nil,'getVNTitle','userLangCode=' .. tostring(userLangCode))
	local userLang = mw.language.new(userLangCode)
	--addDebug(nil, 'getVNTitle', 'userLang:Code=' .. userLang:getCode())
	local langEdit = userLang:lc(frame:preprocess("{{int:edit}}"))
	--addDebug(nil, 'getVNTitle', 'langEdit=' .. langEdit)

	local label = entities.entity:getLabel()
	if label then
		label = '\'' .. label .. '\''
	else
		label = entities.entity.id
	end

	local wikidataLabel = langEdit .. ' wikidata ' .. label
	local wikidataLabelOther = ''
	local wikidataImageLabel = 'Wikidata '   .. label .. ' linked to current ' .. _common.getCurrentNamespace()
	local wikidataImageLabelOther = ''
	if entities.entitySpecified then
		wikidataLabel = langEdit .. ' specified wikidata ' .. label
	end
	if entities.otherEntity then
		local otherLabel = entities.otherEntity:getLabel()
		if otherLabel then
			otherLabel = '\'' .. otherLabel .. '\''
		else
			otherLabel = entities.otherEntity.id
		end
		-- [modifier wikidata 'Category:Aa' linked to current category] [modifier wikidata 'Abacoleptus' main topic of 'Category:Aa']
		wikidataLabel      = wikidataLabel .. ' linked to current ' .. _common.getCurrentNamespace()
		wikidataLabelOther = langEdit .. ' wikidata ' .. otherLabel .. ' main topic of ' .. label
		wikidataImageLabelOther = 'Wikidata item '    .. otherLabel .. ' main topic (P301) of ' .. label
	else
		-- [modifier wikidata 'Abacoleptus']
	end

	local toReturn =           '&nbsp;<small>' .. mw.text.nowiki('[') .. '[[wikidata:' .. entities.entity.id      .. '|' .. wikidataLabel      .. ']]]</small>'
	toReturn     = toReturn .. ' [[Image:Wikidata-logo-without-paddings.svg|20px|' .. wikidataImageLabel      .. '|link=https://www.wikidata.org/wiki/' .. entities.entity.id      .. ']]'
	if entities.otherEntity then
		toReturn = toReturn .. '&nbsp;<small>' .. mw.text.nowiki('[') .. '[[wikidata:' .. entities.otherEntity.id .. '|' .. wikidataLabelOther .. ']]]</small>'
		toReturn = toReturn .. ' [[Image:Wikidata-logo-without-paddings.svg|20px|' .. wikidataImageLabelOther .. '|link=https://www.wikidata.org/wiki/' .. entities.otherEntity.id .. ']]'
	end

	return toReturn
end

local _acceptedParameters = {
	lang='lang',									-- provided by Template:VN and Template:VNNoDisplay
	provideNamesInCommons='provideNamesInCommons',	-- provided only by Template:VNNoDisplay
	sciname='sciname',			
	useWikidata='useWikidata',
	nocat='nocat',
	collapsed='collapsed',
	edit='edit' 				-- managed directly by Template:VN
}

function detectBadParameters(args)
	-- Fill _languageNamesByCode and _languageCodes
	calcLanguages('en')

	local toRet = ''
	for optionKey, optionValue in pairs(args) do
		if type(optionKey) == 'string' then
			-- normal argument pair: en=EnglishName, lang=fr
			if not _languageNamesByCode[optionKey] and not _additionalLanguageNamesByCode[optionKey] and not _acceptedParameters[optionKey] then
				if toRet ~= '' then
					toRet = toRet .. ', '
				end
				toRet = toRet .. '"' .. optionKey .. '"'
			end
		else
			-- incorrect argument pair: 1=strangeParameter
			if toRet ~= '' then
				toRet = toRet .. ', '
			end
			toRet = toRet .. '"' .. optionValue .. '"'
		end
	end
	if toRet == '' then
		return toRet
	else
		return _common.incorrectBiologyTemplateUsage('VN', 'Incorrect parameter(s) ' .. toRet, 'VN')
	end
end

-- returns "sciname" out od "''sciname'' author"
function extractSciNameOutOfDecoratedSciName(decoratedSciName)
-- It has a testcase/non-regression module Module:Wikidata4Bio/testcases using testcase_extractSciNameOutOfDecoratedSciName()
	local startPos = string.find(decoratedSciName, "''", 1, true)
	if startPos then
		--addDebug(nil, 'extractSciNameOutOfDecoratedSciName', 'startPos= ' .. startPos)
		while string.sub(decoratedSciName, startPos, startPos) == "'" do
			startPos = startPos + 1
			--addDebug(nil, 'extractSciNameOutOfDecoratedSciName', 'incr startPos= ' .. startPos)
		end
		local endPos = string.find(decoratedSciName, "''", startPos, true)
		if endPos then
			--addDebug(nil, 'extractSciNameOutOfDecoratedSciName', 'endPos= ' .. endPos)
			local sciName = string.sub(decoratedSciName, startPos, endPos-1)
			sciName = mw.text.trim(sciName)
			return sciName
		end
	end
	return nil
end

function calcSiteUrl(urlPrefix, siteIdStr, urlPostfix)
	if urlPrefix then
		siteIdStr = mw.text.trim(siteIdStr)
		urlPrefix = mw.text.trim(urlPrefix)
		if urlPostfix then
			urlPostfix = mw.text.trim(urlPostfix)
		else
			urlPostfix = ''
		end
		if urlPrefix == 'firstParamIsAnUrl' then
			return '[' .. siteIdStr .. urlPostfix .. ' this url]'
		else
			return '[' .. urlPrefix .. siteIdStr .. urlPostfix .. ' ' .. siteIdStr .. ']'
		end
	else
		return siteIdStr
	end
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, urlPrefix, urlPostfix)
	if not _common.isCurrentNamespaceACategoryOrAGallery() then
		-- We do checks only in galleries and categories (When you look at Template, no param is provided
		return ''
	end

	local commonsSiteId = string.trimOrNullify(frame.args['1'])
	if not commonsSiteId then
		return _common.incorrectBiologyTemplateUsage('Link', 'no ' .. siteName .. ' id has been provided.', templateName)
	end
	commonsSiteId = string.gsub(commonsSiteId, '&#61;', '=')

	local commonsSciName = string.trimOrNullify(frame.args['sciname'])
	if not commonsSciName then
		return _common.incorrectBiologyTemplateUsage('Link', 'no ' .. siteName .. ' sciName has been provided.', templateName)
	end
	local commonsDecoratedSciName = string.trimOrNullify(frame.args['decoratedSciname'])

	local validity = string.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 _common.incorrectBiologyTemplateUsage('Link', 'Incorrect parameter validity.', templateName)
		end
	end

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

	local entities = retrieveEntitiesSimple(nil)
	if not entities.entity then
		-- Wikidata library is not enabled => no check is possible
		-- Or there is no link to wikidata => no check is possible
		return ''
	end

	getScientificNamesFromWikidata(entities)
	if not tableIsEmpty(_scientificNamesFromWikidata) then
		if commonsSciName ~= 'none' then
			if not _scientificNamesFromWikidata[commonsSciName] then
				-- ITIS sciname and wikidata sciname are different => cannot compare ids
				addDebug(nil, 'compareSiteIdWithWikidata', 'Check Disabled: Different scientific name between www.wikidata.org/wiki/' .. entities.entity.id .. ' (' .. tableToString(_scientificNamesFromWikidata,false) .. ') and wikicommons (' .. commonsSciName .. ')')
				return ''
				--return ' <small>(Warning: different scientific name between [[wikidata:' .. entities.entity.id .. '|wikidata]] (' .. tableToString(_scientificNamesFromWikidata,false) .. ') and wikicommons (' .. commonsSciName .. '))</small>[[Category:Pages with biology property different than on Wikidata|' .. siteName .. ']]'
			end
		elseif commonsDecoratedSciName then
			local sciName = extractSciNameOutOfDecoratedSciName(commonsDecoratedSciName)
			if sciName then
				if not listContainsExactString(sciName, _scientificNamesFromWikidata) then
					-- wikidata sciname not included in ITIS sciname => cannot compare ids
					addDebug(nil, 'compareSiteIdWithWikidata', 'Check Disabled: Different exact scientific name between www.wikidata.org/wiki/' .. entities.entity.id .. ' (' .. tableToString(_scientificNamesFromWikidata,false) .. ') and wikicommons (' .. sciName .. ')')
					return ''
				else
					addDebug(nil, 'compareSiteIdWithWikidata', 'sciname (' .. sciName ..') extracted from decoratedScientificName (' .. commonsDecoratedSciName .. ') is part of wikidata scinames ' .. tableToString(_scientificNamesFromWikidata,false))
				end
			else
				if not stringContainsAnItemOfList(commonsDecoratedSciName, _scientificNamesFromWikidata) then
					-- wikidata sciname not included in ITIS decoratedSciname => cannot compare ids
					addDebug(nil, 'compareSiteIdWithWikidata', 'Check Disabled: Different scientific name between www.wikidata.org/wiki/' .. entities.entity.id .. ' (' .. tableToString(_scientificNamesFromWikidata,false) .. ') and wikicommons (' .. commonsDecoratedSciName .. ')')
					return ''
				else
					addDebug(nil, 'compareSiteIdWithWikidata', 'decoratedScientificName (' .. commonsDecoratedSciName .. ') is contained in wikidata scinames ' .. tableToString(_scientificNamesFromWikidata,false))
				end
			end
		end
	end

	if not isTemplateCalledOnlyOnce(templateName,templateForms,templateForms) then
		-- If template is called multiple times...
		addDebug(nil, 'compareSiteIdWithWikidata', 'Check Disabled: Template ' .. templateName .. ' is called multiple times')
		return ''
	end

	local problematicEntity = nil
	local siteIdvalues = getStringProperties(entities.entity, sitePropertyId)
	if siteIdvalues.size > 0 then
		addDebug(nil, 'compareSiteIdWithWikidata', 'Property ' .. sitePropertyId .. ' found in entity: ' .. propertiesToString(siteIdvalues))
		problematicEntity = entities.entity
	else
		-- Property (like p815 for 'identifiant ITIS') is not defined in wikidata => no check possible
		if entities.otherEntity then
			siteIdvalues = getStringProperties(entities.otherEntity, sitePropertyId)
			if siteIdvalues.size == 0 then
				-- Property (like p815 for 'identifiant ITIS') is not defined in wikidata => no check possible
				addDebug(nil, 'compareSiteIdWithWikidata', 'Check Impossible: https://www.wikidata.org/wiki/' .. entities.entity.id ..  ' and https://www.wikidata.org/wiki/' .. entities.otherEntity.id .. ' do not contain the property ' .. sitePropertyId)
				return ''
			end
			addDebug(nil, 'compareSiteIdWithWikidata', 'Property ' .. sitePropertyId .. ' found in otherEntity: ' .. propertiesToString(siteIdvalues))
			problematicEntity = entities.otherEntity
		else
			addDebug(nil, 'compareSiteIdWithWikidata', 'Check Impossible: https://www.wikidata.org/wiki/' .. entities.entity.id .. ' does not contain the property ' .. sitePropertyId)
			return ''
		end
	end

	if siteIdvalues[1] == commonsSiteId then
		--  identifier are equal: perfect
		addDebug(nil, 'compareSiteIdWithWikidata', 'Everything Perfect: ' .. siteName .. ' id is ' .. commonsSiteId .. ' on wikicommons and wikidata')
		return ''
	end

	--  identifier are different
	if siteIdvalues.size == 1 then
		-- Property (like p815 for 'identifiant ITIS') has only 1 value in wikidata
	elseif siteIdvalues[2] == commonsSiteId then
		--  identifier are equal: perfect
		addDebug(nil, 'compareSiteIdWithWikidata', 'Everything Perfect: ' .. siteName .. ' id is ' .. commonsSiteId .. ' on wikicommons and wikidata (second value)')
		return ''
	end
	
	addDebug(nil, 'compareSiteIdWithWikidata', 'urlPrefix=' .. tostring(urlPrefix))
	local wikidataSiteIdStr = calcSiteUrl(urlPrefix, siteIdvalues[1], urlPostfix)
	if siteIdvalues.size > 1 then
		wikidataSiteIdStr = wikidataSiteIdStr .. ', ' .. calcSiteUrl(urlPrefix, siteIdvalues[2], urlPostfix)
	end
	return ' <small>(Warning: different ' .. siteName .. ' Id between ' .. getWikidataLinkFormat2(problematicEntity) .. ' (' .. wikidataSiteIdStr .. ') and wikicommons (' .. calcSiteUrl(urlPrefix, commonsSiteId, urlPostfix) .. '))</small>[[Category:Pages with biology property different than on Wikidata|' .. siteName .. ']]'
end

function getAdditionalCategoryAboutWikidata()
	if not _common.isCurrentNamespaceACategoryOrAGallery() then
		-- We do checks only in galleries and categories (When you look at Template, no param is provided
		return ''
	end

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

	local wikicode = mw.title.getCurrentTitle():getContent()
	if wikicode then
		if string.find(wikicode,'redirect|',1,true) then
			-- Category contains a redirect => we are not interested
			return ''
		end
		if string.find(wikicode,'#REDIRECT',1,true) then
			-- Gallery contains a redirect => we are not interested
			return ''
		end
	else
		-- Called from preview before creation of page
	end

	local entity = mw.wikibase.getEntityObject()
	if entity then
		return '[[Category:Biology pages with wikidata link]]'
	else
		if _common.pageNameCorrespondToSpeciesOrInfraspecies(mw.title.getCurrentTitle().text) then
			return '[[Category:Infraspecies pages without wikidata link]]'
		else
			return '[[Category:Biology pages without wikidata link]]'
		end
	end
end

-- Used by {{#invoke:Wikidata4Bio/sandbox|test}} to discover Lua ;-)
function test(frame,args)
	local entity = mw.wikibase.getEntity();

	return getDebug()
end

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

local getArgs = require('Module:Arguments').getArgs

-- Simply add {{#invoke:Wikidata4Bio|dumpWikidata}} to a page
function p.dumpWikidata(frame)
	local entity = mw.wikibase.getEntity()
	if entity then
		return 'entity=' .. mw.text.jsonEncode(entity)
	else
		return 'not associated with wikidata'
	end
end

function p.getVN(frame)
	-- getArgs() will concatenate frame.args (lang=) with Template:VN arguments (useWikidata=, explain=, sciname= and all the <lang>=)
	local args = getArgs(frame)
	-- addDebug(nil, 'p.getVN', tableToString(args,true))
	-- addDebug(nil, 'p.getVN', detectBadParameters(args))

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

function p.getVNTitle(frame)
	local args = getArgs(frame)
	local title = getVNTitle(frame, args)
	if _debug then
		title = title .. getDebug()
	end
	return title
end

-- Used by {{VN/doc}}
function p.getLanguagesManagedByVN(frame)
	local langs = getLanguagesManagedByVN(frame.args)
	return langs
end

-- Used by {{Avibase}} (only species) with sciname
function p.compareAvibaseIdWithWikidata(frame)
	-- frame, sitePropertyId, siteName, templateName, templateForms
	return compareSiteIdWithWikidata(frame, 'P2026', 'Avibase', 'Avibase', {'{{Avibase'}, 'http://avibase.bsc-eoc.org/species.jsp?lang=EN&avibaseid=')
		.. getDebug() .. getAdditionalCategoryAboutWikidata()
end

-- Used by {{BioLib}} with |sciname=none|decoratedSciname={{{2|{{PAGENAME}}}}}
function p.compareBioLibIdWithWikidata(frame)
	-- frame, sitePropertyId, siteName, templateName, templateForms
	return compareSiteIdWithWikidata(frame, 'P838', 'BioLib', 'BioLib', {'{{BioLib'}, 'http://www.biolib.cz/en/taxon/id')
		.. getDebug() .. getAdditionalCategoryAboutWikidata()
end

-- Used by {{EOL}} with |sciname=none|decoratedSciname={{{2|{{PAGENAME}}}}}
function p.compareEOLIdWithWikidata(frame)
	return compareSiteIdWithWikidata(frame, 'P830', 'EOL', 'EOL', {'{{EOL'}, 'http://www.eol.org/pages/')
		.. getDebug() .. getAdditionalCategoryAboutWikidata()
end

-- Used by {{Faunaeur}} with |sciname=none|decoratedSciname={{{2|{{PAGENAME}}}}}
function p.compareFaunaeurIdWithWikidata(frame)
	return compareSiteIdWithWikidata(frame, 'P1895', 'Faunaeur', 'Faunaeur', {'{{Faunaeur'}, 'http://www.faunaeur.org/full_results.php?id=')
		.. getDebug() .. getAdditionalCategoryAboutWikidata()
end

-- Used by {{FishBase species}} with sciname
function p.compareFishBaseSpeciesIdWithWikidata(frame)
	return compareSiteIdWithWikidata(frame, 'P938', 'FishBase', 'FishBase', {'{{FishBase'}, 'http://www.fishbase.org/Summary/speciesSummary.php?id=')
		.. getDebug() .. getAdditionalCategoryAboutWikidata()
end

-- Used by {{Fungorum species}}, {{Fungorum genus}}, {{Fungorum family}} and {{Fungorum taxon}} with sciname
function p.compareFungorumIdWithWikidata(frame)
	return compareSiteIdWithWikidata(frame, 'P1391', 'Fungorum', 'Fungorum', {'{{Fungorum'}, frame.args['urlPrefix'])
		.. getDebug() .. getAdditionalCategoryAboutWikidata()
end

-- Used by {{GBIF}} with |sciname=none|decoratedSciname={{{2|{{PAGENAME}}}}}
function p.compareGBIFIdWithWikidata(frame)
	return compareSiteIdWithWikidata(frame, 'P846', 'GBIF', 'GBIF', {'{{GBIF'}, 'http://www.gbif.org/species/')
		.. getDebug() .. getAdditionalCategoryAboutWikidata()
end

-- Used by {{GRIN family}}, {{GRIN subfamily}}, {{GRIN tribe}} and {{GRIN subtribe}} with sciname
function p.compareGRINURLWithWikidata(frame)
	local error = ''
	local rank = string.trimOrNullify(frame.args['rank'])
	if rank then
		local commonsSciName = string.trimOrNullify(frame.args['sciname'])
		if commonsSciName then
			if rank == 'family' then
				if not string.endsWith(commonsSciName,'ceae') then
					error = _common.incorrectBiologyTemplateUsage('GRIN family', 'GRIN family should be used on families only (ending with "ceae"). Please Use GRIN subfamily, GRIN tribe, GRIN subtribe, GRIN genus or GRIN species', 'GRIN family')
				end
			elseif rank == 'subfamily' then
				if not string.endsWith(commonsSciName,'oideae') then
					error = _common.incorrectBiologyTemplateUsage('GRIN subfamily', 'GRIN subfamily should be used on subfamilies only (ending with "oideae"). Please Use GRIN family, GRIN tribe, GRIN subtribe, GRIN genus or GRIN species', 'GRIN subfamily')
				end
			elseif rank == 'tribe' then
				if not string.endsWith(commonsSciName,'eae') then
					error = _common.incorrectBiologyTemplateUsage('GRIN tribe', 'GRIN tribe should be used on tribes only (ending with "eae"). Please Use GRIN family, GRIN subfamily, GRIN subtribe, GRIN genus or GRIN species', 'GRIN tribe')
				end
			elseif rank == 'subtribe' then
				if not string.endsWith(commonsSciName,'inae') then
					error = _common.incorrectBiologyTemplateUsage('GRIN subtribe', 'GRIN subtribe should be used on subtribes only (ending with "inae"). Please Use GRIN family, GRIN subfamily, GRIN tribe, GRIN genus or GRIN species', 'GRIN subtribe')
				end
			end
		-- else: Strange case treated by compareSiteIdWithWikidata()
		end
	end
	return compareSiteIdWithWikidata(frame, 'P1421', 'GRIN', 'GRIN', {'{{GRIN'}, 'firstParamIsAnUrl')
		.. error
		.. getDebug() .. getAdditionalCategoryAboutWikidata()
end

-- Used by {{Gymnosperm Database}} with sciname
function p.compareGymnospermDatabaseIdWithWikidata(frame)
	return compareSiteIdWithWikidata(frame, 'P1940', 'Gymnosperm Database', 'Gymnosperm Database', {'{{Gymnosperm Database'}, 'http://conifers.org/', '.php')
		.. getDebug() .. getAdditionalCategoryAboutWikidata()
end

-- NOT used by {{IPNI}}, which should be renamed {{IPNI search}} because it does not use an id
function p.compareIPNIIdWithWikidata(frame)
	return compareSiteIdWithWikidata(frame, 'P961', 'IPNI', 'IPNI', {'{{IPNI'}, 'http://www.ipni.org/ipni/simplePlantNameSearch.do?find_wholeName=')
		.. getDebug() .. getAdditionalCategoryAboutWikidata()
end

-- Used by {{ITIS}} with |sciname=none|decoratedSciname={{{2|{{PAGENAME}}}}}
function p.compareITISIdWithWikidata(frame)
	return compareSiteIdWithWikidata(frame, 'P815', 'ITIS', 'ITIS', {'{{ITIS'}, 'http://www.itis.gov/servlet/SingleRpt/SingleRpt?search_topic=TSN&search_value=')
		.. getDebug() .. getAdditionalCategoryAboutWikidata()
end

-- Used by {{IUCN}} (only species) with sciname
function p.compareIUCNIdWithWikidata(frame)
	return compareSiteIdWithWikidata(frame, 'P627', 'IUCN', 'IUCN', {'{{IUCN'}, 'http://apiv3.iucnredlist.org/api/v3/taxonredirect/')
		.. getDebug() .. getAdditionalCategoryAboutWikidata()
end

-- Used by {{LPSN}} with |sciname=none|decoratedSciname={{{2|{{PAGENAME}}}}}
function p.compareLPSNUrlWithWikidata(frame)
	return compareSiteIdWithWikidata(frame, 'P1991', 'LPSN', 'LPSN', {'{{LPSN'}, 'firstParamIsAnUrl')
		.. getDebug() .. getAdditionalCategoryAboutWikidata()
end

-- Used by {{MSW}} with |sciname=none|decoratedSciname={{{2|{{PAGENAME}}}}}
function p.compareMSWIdWithWikidata(frame)
	return compareSiteIdWithWikidata(frame, 'P959', 'MSW', 'MSW', {'{{MSW'}, 'https://www.departments.bucknell.edu/biology/resources/msw3/browse.asp?s=y&id=')
		.. getDebug() .. getAdditionalCategoryAboutWikidata()
end

-- Used by {{MycoBank}} with sciname
function p.compareMycoBankIdWithWikidata(frame)
	return compareSiteIdWithWikidata(frame, 'P962', 'MycoBank', 'MycoBank', {'{{MycoBank'}, 'http://www.mycobank.org/MycoTaxo.aspx?Link=T&Rec=')
		.. getDebug() .. getAdditionalCategoryAboutWikidata()
end

-- Used by {{NCBI}} with |sciname=none|decoratedSciname={{{2|{{PAGENAME}}}}}
function p.compareNCBIIdWithWikidata(frame)
	return compareSiteIdWithWikidata(frame, 'P685', 'NCBI', 'NCBI', {'{{NCBI'}, 'https://www.ncbi.nlm.nih.gov/Taxonomy/Browser/wwwtax.cgi?lin=s&p=has_linkout&id=')
		.. getDebug() .. getAdditionalCategoryAboutWikidata()
end

-- Used by {{NRCS Plants}} with sciname
function p.compareNRCSPlantsIdWithWikidata(frame)
	return compareSiteIdWithWikidata(frame, 'P1772', 'NRCS Plants', 'NRCS Plants', {'{{NRCS Plants'}, 'http://plants.usda.gov/core/profile?symbol=')
		.. getDebug() .. getAdditionalCategoryAboutWikidata()
end

-- Used by {{ThePlantList species}} with sciname
function p.compareThePlantListIdWithWikidata(frame)
	return compareSiteIdWithWikidata(frame, 'P1070', 'ThePlantList', 'ThePlantList species', {'{{ThePlantList species'}, 'http://www.theplantlist.org/tpl1.1/record/')
		.. getDebug() .. getAdditionalCategoryAboutWikidata()
end

-- Used by {{TPDB}} with |sciname=none|decoratedSciname={{{2|{{PAGENAME}}}}}
function p.compareTPDBIdWithWikidata(frame)
	return compareSiteIdWithWikidata(frame, 'P842', 'TPDB', 'TPDB', {'{{TPDB'}, 'http://fossilworks.org/bridge.pl?action=taxonInfo&is_real_user=1&taxon_no=')
		.. getDebug() .. getAdditionalCategoryAboutWikidata()
end

-- Used by {{Tropicos}} with sciname
function p.compareTropicosIdWithWikidata(frame)
	return compareSiteIdWithWikidata(frame, 'P960', 'Tropicos', 'Tropicos', {'{{Tropicos', '{{tropicos'}, 'http://www.tropicos.org/Name/')
		.. getDebug() .. getAdditionalCategoryAboutWikidata()
end

-- Used by {{WRMS}} and {{WRMS species}} with |sciname=none|decoratedSciname={{{2|{{PAGENAME}}}}}
function p.compareWoRMSIdWithWikidata(frame)
	return compareSiteIdWithWikidata(frame, 'P850', 'WoRMS', 'WRMS', {'{{WRMS'}, 'http://www.marinespecies.org/aphia.php?p=taxdetails&id=')
		.. getDebug() .. getAdditionalCategoryAboutWikidata()
end

----------------------------------------------------------------------------------------------------
---------- Testcase public functions (return string) -----------------------------------------------
----------------------------------------------------------------------------------------------------

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

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

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

function p.testcase_getScientificNames()
	-- for testcases (return string when normal function returns table)
	local entities = retrieveEntitiesSimple(nil)
	return mw.text.nowiki(tableToString(getScientificNames(entities),false))
end

function p.testcase_extractSciNameOutOfDecoratedSciName(frame)
	-- for testcases (return 'nil' instead of nil)
	local sciname = tostring(extractSciNameOutOfDecoratedSciName(frame.args['1']))
	if _debug then
		sciname = sciname .. getDebug()
	end
	return sciname
end

function p.testcase_getWikidataLinkFormat1(frame)
	-- for testcases
	return mw.text.nowiki(getWikidataLinkFormat1(frame.args['1']))
end

function p.testcase_getWikidataLinkFormat2(frame)
	-- for testcases
	local entity = mw.wikibase.getEntity(frame.args['1'])
	if entity then
		return mw.text.nowiki(getWikidataLinkFormat2(entity))
	else
		return 'Not a valid entity id'
	end
end

return p