Module:Wikidata4Bio

From Wikimedia Commons, the free media repository
Jump to: navigation, search
Lua
CodeDiscussionEditHistoryNo test API DocumentationSubpagesLinksTestsResultsSandbox All modules

Module:Wikidata4Bio (talk · edit · hist · links · doc · subpages · tests / results · sandbox · all modules)


Usage
This module is used by:

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

Code

-- global variable which receives debug info if not nil (Should be =Nil in Wikidata4Bio and ={} in Wikidata4Bio/sandbox)
_debug=Nil

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

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

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

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


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

-- stringUpperFirstLowerOthers(str) returns "Hello" out of "heLLO"
function stringUpperFirstLowerOthers(str)
	if not str then
		return Nil
	end
	local length = string.len(str)
	if length == 0 then
		return ''
	elseif length == 1 then
		return string.upper(str)
	else
		return string.upper(string.sub(str,1,1)) .. string.lower(string.sub(str,2))
	end
end

-- stringUpperFirst(str) returns "HeLlO" out of "heLlO"
function stringUpperFirst(str)
	if not str then
		return Nil
	end
	local length = string.len(str)
	if length == 0 then
		return ''
	elseif length == 1 then
		return string.upper(str)
	else
		return string.upper(string.sub(str,1,1)) .. string.sub(str,2)
	end
end

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

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

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


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

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

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

-- 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
	mytable2 = {}
	for key, value in pairs(mytable) do
		table.insert(mytable2,{key,value})
	end
	table.sort(mytable2, function(t1,t2) return t1[1] < t2[1] end)
	
	-- Serialize mytable2 into 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 ---------------------------
-- getCurrentNamespace() returns the current namespace ('gallery' instead of '')
function getCurrentNamespace()
	local namespace = string.lower(mw.title.getCurrentTitle().nsText)
	if string.len(namespace) == 0 then
		namespace = 'gallery'
	end
	return namespace
end

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

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

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

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


-- getProperty(entity, true, {'claims','P301',1,'mainsnak','datavalue','value','numeric-id'}) does the same as
-- entity.claims.P301[0].mainsnak.datavalue.value.numeric-id except that it will never generate an error.
-- if verbose, then a debug will be added when the property has a Nil value
function getProperty(entity, verbose, propertyPath)
	local property = entity
	local currentPath = 'entity'
	for index, propertyPathItem in pairs(propertyPath) do
		property = property[propertyPathItem]
		if verbose then
			if (type(propertyPathItem) == 'number') then
				currentPath = currentPath .. '[' .. propertyPathItem .. ']'
			else
				currentPath = currentPath .. '.' .. propertyPathItem
			end
		end
		if not property then
			if verbose then
				addDebug(Nil, 'getProperty', currentPath .. '=Nil')
			end
			return Nil
		end
	end

	return property
end

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

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

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

	if entity then
		local p225 = getProperty(entity, false, {'claims', 'P225'})
		if p225 then
			for key, value in pairs(p225) do
				name = getProperty(p225, false, {key, 'mainsnak', 'datavalue', 'value'})
				if name then
					name = mw.text.trim(name)
					_scientificNamesFromWikidata[name] = name
				end
			end
		end
	end
	if otherEntity then
		local p225 = getProperty(otherEntity, false, {'claims', 'P225'})
		if p225 then
			for key, value in pairs(p225) do
				name = getProperty(p225, false, {key, 'mainsnak', 'datavalue', 'value'})
				if name then
					name = mw.text.trim(name)
					_scientificNamesFromWikidata[name] = name
				end
			end
		end
	end

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

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

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

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

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

	for key, value in pairs(getScientificNamesFromWikidata(entity, otherEntity)) do
		name = string.lower(value)
		_scientificNames[name] = name
		name = "''" .. name .. "''"
		_scientificNames[name] = name
	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(entity, otherEntity, name)
	if not name then
		return false
	end
	name = string.lower(suppressDisambiguation(mw.text.trim(name)))
	
	getScientificNames(entity, otherEntity)
	
	if _scientificNames[name] then
		return true
	else
		return false
	end
end

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

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

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

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

-- getVNEntry() returns HTML for one language in getVN()
function getVNEntry(entity, otherEntity, lang, default, useWikidata, bold)
	local vercularName = getVernacularNameFromWikidata(entity, otherEntity, lang, default, useWikidata)
	if vercularName and string.len(vercularName) > 0 then
		local success, langName = pcall(mw.language.fetchLanguageName, lang, lang)
		if lang == 'en-us' then
			success = true
			langName = 'American'
			lang = 'en'
		end
		if success and langName then
			-- <bdi> is just like <span> but works better when there is mixed bidi text
			local entry = "* '''" .. '<bdi lang="' .. lang .. '">' .. langName .. "</bdi>:"
			if not bold then
				-- we are already in bold (to display lang), let us close bold to display vercularName
				entry = entry .. "'''"
			end
			entry = entry .. '&nbsp;<bdi class="vernacular" lang="' .. lang .. '">' .. vercularName .. "</bdi>"
			if bold then
				-- close bold if not closed before
				entry = entry .. "'''"
			end
			return entry .. '\n'
		else
			return "* Unknown lang '" .. lang .. "'\n"
		end
	else
		return nil
	end
end

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

-- 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
	elseif stringUpperFirstLowerOthers(name) ~= stringUpperFirst(name) then
		-- like 'Waterlily Dahlias'
		return true
	end
	return false
end

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

	if isScientificName(entity, otherEntity, default) then
		-- default is a scientific name
		-- addDebug(lang,'verifyVNParameter',default .. ' is a scientific name')
		if default == 'Aves' then
			-- Correct: rare 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 ' [[Category:Pages with incorrect biology template usage|VN]] '
		end
	elseif string.find(default, "'''", 1, true) then
		-- default contains bold
		return ' [[Category:Pages with incorrect biology template usage|BoldOrItalic]] '
	elseif (string.sub(default, 1, 2) == "''") then
		-- default starts with italic
		if string.find(default, "'':", 1, true) then
			-- Correct: |ja=''Vespa mandarinia japonica'': XXX
			-- Incorrect: |en='''[[:en:whatever|]]'''
		elseif isEnglishName(default) then
			-- Correct: |ja=''Oudenodon'' fossil specimens |de=''Neoreglia'' cultivars |de=''Ara''-Hybride |en=''Ara'' hybrids
		else
			-- Incorrect: |en=''Vespa mandarinia japonica''
			return ' [[Category:Pages with incorrect biology template usage|BoldOrItalic]] '
		end
	end
	return Nil
end

-- getVN() is called by Template:VN
function getVN(options)
	local entity = Nil
	if mw.wikibase then
		-- wikidata library is enabled
		entity = mw.wikibase.getEntityObject()
	end
 	local otherEntity = Nil
	if entity then
	 	local otherEntityId = Nil
	 	-- getProperty(entity, true, {'claims', 'P301', 1, 'mainsnak', 'datavalue', 'value', 'numeric-id'})
	 	-- mw.wikibase.getEntityObject() returns "Lua error in Module:Wikidata4Bio/sandbox at line 701: Access to arbitrary items has been disabled.."
		if otherEntityId and string.len(otherEntityId) > 0 then
			otherEntity = mw.wikibase.getEntityObject(tostring(otherEntityId))
			if otherEntity then
				if otherEntity == entity then
					addDebug(Nil, 'getVN', 'otherEntity == entity')
				else
					addDebug(Nil, 'getVN', 'otherEntity found')
				end
			else
				addDebug(Nil, 'getVN', 'otherEntity=Nil when otherEntityId=' .. otherEntityId)
			end
		else
			addDebug(Nil, 'getVN', 'otherEntityId=Nil')
		end
	end

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

	local userLang = options.lang
	local default = options[userLang]
	local additionalCategory = ''

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

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

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

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

function stringContainsAnItemOfList(longString, listShortString)
	longString = string.lower(longString)
	for key, value in pairs(listShortString) do
		local listItem = string.lower(value)
		if stringContains(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

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

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

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

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

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

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

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

	if commonsSciName ~= 'none' then
		getScientificNamesFromWikidata(entity, Nil)
		if not tableIsEmpty(_scientificNamesFromWikidata) 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/' .. entity.id .. ' (' .. tableToString(_scientificNamesFromWikidata,false) .. ') and wikicommons (' .. commonsSciName .. ')')
				return ''
				--return ' <small>(Warning: different scientific name between [[wikidata:' .. entity.id .. '|wikidata]] (' .. tableToString(_scientificNamesFromWikidata,false) .. ') and wikicommons (' .. commonsSciName .. '))</small>[[Category:Pages with biology property different than on Wikidata|' .. siteName .. ']]'
			end
		end
	elseif commonsDecoratedSciName then
		getScientificNamesFromWikidata(entity, Nil)
		if not tableIsEmpty(_scientificNamesFromWikidata) then
			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/' .. entity.id .. ' (' .. tableToString(_scientificNamesFromWikidata,false) .. ') and wikicommons (' .. commonsDecoratedSciName .. ')')
				return ''
			else
				addDebug(Nil, 'compareSiteIdWithWikidata', 'decoratedScientificName {' .. commonsDecoratedSciName .. '} contains sciname ' .. tableToString(_scientificNamesFromWikidata,false))
			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 wikidataSiteId = trimOrNullify(getProperty(entity, true, {'claims', sitePropertyId, 1, 'mainsnak', 'datavalue', 'value'}))
	if not wikidataSiteId then
		-- Property (like p815 for 'identifiant ITIS') is not defined in wikidata => no check possible
		addDebug(Nil, 'compareSiteIdWithWikidata', 'Check Impossible: www.wikidata.org/wiki/' .. entity.id .. ' does not contain the property ' .. sitePropertyId)
		return ''
	elseif wikidataSiteId == commonsSiteId then
		-- Both identifier are equal: perfect
		addDebug(Nil, 'compareSiteIdWithWikidata', 'Everything Perfect: ' .. siteName .. ' id is ' .. commonsSiteId .. ' on wikicommons and wikidata')
		return ''
	else
		-- Both identifier are different
		local wikidataSiteId2 = trimOrNullify(getProperty(entity, true, {'claims', sitePropertyId, 2, 'mainsnak', 'datavalue', 'value'}))
		if not wikidataSiteId2 then
			-- Property (like p815 for 'identifiant ITIS') has only 1 value in wikidata
		elseif wikidataSiteId2 == commonsSiteId then
			-- Both identifier are equal: perfect
			addDebug(Nil, 'compareSiteIdWithWikidata', 'Everything Perfect: ' .. siteName .. ' id is ' .. commonsSiteId .. ' on wikicommons and wikidata (second value)')
			return ''
		end
		
		local wikidataSiteIdStr = wikidataSiteId
		if wikidataSiteId2 then
			wikidataSiteIdStr = wikidataSiteId .. ', ' .. wikidataSiteId2
		end
		return ' <small>(Warning: different ' .. siteName .. ' Id between [[wikidata:' .. entity.id .. '|wikidata]] (' .. wikidataSiteIdStr .. ') and wikicommons (' .. commonsSiteId .. '))</small>[[Category:Pages with biology property different than on Wikidata|' .. siteName .. ']]'
	end
end

function getAdditionalCategoryAboutWikidata()
	if not 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
		return '[[Category:Biology_pages_without_wikidata_link]]'
	end
end

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

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

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

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('params','p.getVN',tableToString(args,true))
	local vn = getVN(args)
	-- Now add debug traces if activated
	if _debug then
		vn = vn .. getDebug()
	end
	return vn .. getAdditionalCategoryAboutWikidata()
end

function p.compareEOLIdWithWikidata(frame)
	-- frame, sitePropertyId, siteName, templateName, templateForms
	return compareSiteIdWithWikidata(frame, 'P830', 'EOL', 'EOL', {'{{EOL'}) .. getDebug() .. getAdditionalCategoryAboutWikidata()
end

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

function p.compareFungorumIdWithWikidata(frame)
	return compareSiteIdWithWikidata(frame, 'P1391', 'Fungorum', 'Fungorum', {'{{Fungorum'}) .. getDebug() .. getAdditionalCategoryAboutWikidata()
end

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

function p.compareGRINURLWithWikidata(frame)
	return compareSiteIdWithWikidata(frame, 'P1421', 'GRIN', 'GRIN', {'{{GRIN'}) .. getDebug() .. getAdditionalCategoryAboutWikidata()
end

function p.compareIPNIIdWithWikidata(frame)
	return compareSiteIdWithWikidata(frame, 'P961', 'IPNI', 'IPNI', {'{{IPNI'}) .. getDebug() .. getAdditionalCategoryAboutWikidata()
end

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

function p.compareIUCNIdWithWikidata(frame)
	return compareSiteIdWithWikidata(frame, 'P627', 'IUCN', 'IUCN', {'{{IUCN'}) .. getDebug() .. getAdditionalCategoryAboutWikidata()
end

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

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

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

function p.compareNRCSPlantsIdWithWikidata(frame)
	return compareSiteIdWithWikidata(frame, 'P1772', 'NRCS Plants', 'NRCS Plants', {'{{NRCS Plants'}) .. getDebug() .. getAdditionalCategoryAboutWikidata()
end

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

return p