Module:Gratisdata4Bio

local _common=require('Module:Biology')

-- global variable which receives debug info if not nil (Should be =nil in Gratisdata4Bio and ={} in Gratisdata4Bio/sandbox) local _debug=nil local property_P3_InstanceOf			= 'P3' 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 = 'Debug:' for key, value in pairs(debug2) do displayedDebug = displayedDebug .. '- ' .. 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 ' = ' is returned else format ' ' 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 'dfsfsd' -- It has a testcase/non-regression module Module:Gratisdata4Bio/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 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 gratisdata): '''" 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 gratisdata): '''" 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, useGratisdata=false, useGratisdataIsCalculated=false} end

-- retrieveEntitiesSimple returns {entity, otherEntity} -- Called     by retrieveEntities which is called by getVN and getVNTitle -- Also called by compareSiteIdWithGratisdata (were the is no useGratisdata=) 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, useGratisdata, useGratisdataIsCalculated} function retrieveEntities(args) local entities = createEmptyEntities(nil) if not mw.wikibase then -- Case 1 return entities end -- gratisdata library is enabled if string.startsWith(args.useGratisdata,'Q') then -- useGratisdata=Q1246 local success, myentity = pcall(mw.wikibase.getEntityObject, args.useGratisdata) if not success or not myentity then -- Case 2 addDebug(nil, 'retrieveEntities', 'Case2: useGratisdata=' .. args.useGratisdata .. ' but failed to load ' .. args.useGratisdata) return end -- Case 3 entities.entity = myentity entities.useGratisdata = true if areSameValidEntity(entities.entity, mw.wikibase.getEntityObject) then addDebug(nil, 'retrieveEntities', 'Case 3bis: useGratisdata=' .. args.useGratisdata .. ' but not needed as associated to the same item => useGratisdata=true') else addDebug(nil, 'retrieveEntities', 'Case 3: useGratisdata=' .. args.useGratisdata .. ' => useGratisdata=true') end entities.entitySpecified = true else retrieveEntitiesSimple(entities) if entities.entity then if string.isNilOrEmpty(args.useGratisdata) then entities.useGratisdataIsCalculated = true local sciname = string.trimOrNullify(args.sciname) if sciname then -- Case 4.true: useGratisdata set to true -- Case 4.false: useGratisdata set to false -- isScientificName(sciname) returns true if sciname== entities.useGratisdata = isScientificName(entities, sciname) addDebug(nil, 'retrieveEntities', 'Case 4: useGratisdata not set but sciname=' .. sciname .. ' => using useGratisdata=isScientificName(sciname)=' .. tostring(entities.useGratisdata)) else -- Case 5.true: useGratisdata set to true -- Case 5.false: useGratisdata set to false entities.useGratisdata = isVnCalledOnlyOnce addDebug(nil, 'retrieveEntities', 'Case 5: useGratisdata not set and sciname not set => using useGratisdata=isVnCalledOnlyOnce=' .. tostring(entities.useGratisdata)) end else -- Case 6.true: args.useGratisdata=1 -- Case 6.false: args.useGratisdata=0 entities.useGratisdata = string.isTrue(args.useGratisdata) addDebug(nil, 'retrieveEntities', 'Case 6: args.useGratisdata=' .. tostring(args.useGratisdata) .. ' => entities.useGratisdata=' .. tostring(entities.useGratisdata)) end else -- Case 7.nil: args.useGratisdata=nil but entity=nil => we avoid useGratisdataIsCalculated -- Case 7.true: args.useGratisdata=1 but entity=nil -- Case 7.false: args.useGratisdata=0 and entity=nil entities.useGratisdata = false addDebug(nil, 'retrieveEntities', 'Case 7: args.useGratisdata=' .. tostring(args.useGratisdata) .. ' but as entity=nil => entities.useGratisdata=' .. tostring(entities.useGratisdata)) end end

return entities end

-- Gratisdata 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=, 2= ...} corresponding to .claims.. .mainsnak.datavalue.value -- if propertyListsEntityIds is set, the path is .claims.. .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

-- Gratisdata P3 utilities --

local _p3valuesForTaxon = { 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' }

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

-- Gratisdata link utilities -

-- getGratisdataLinkFormat1Common('Qxx','B','C') returns "'B' (C) " -- getGratisdataLinkFormat1Common('Pxx','B','C') returns "'B' (C) " -- Called only by getGratisdataLinkFormat1 and getGratisdataLinkFormat1ForEntity function getGratisdataLinkFormat1Common(itemId, linkLabel, complement) if string.startsWith(itemId,'P') then -- It is a property itemId = 'property:' .. itemId end local link = '\ .. linkLabel .. '\ if complement then link = link .. ' (' .. complement .. ') ' end return link end

-- getGratisdataLinkFormat1(property_P301_CategoryMainTopic) return "'category's main topic' (P301) " -- getGratisdataLinkFormat1('Q270') return "'Nepenthaceae' (Q270) " -- getGratisdataLinkFormat1('P373') return "'common category' (P373) " -- Called only by checkSpecifiedEntity/checkSingleEntity/checkTwoEntities to display a red message -- It has a testcase/non-regression module Module:Gratisdata4Bio/testcases using testcase_getGratisdataLinkFormat1 function getGratisdataLinkFormat1(itemId) local entity = mw.wikibase.getEntity(itemId) if entity then local label = entity:getLabel if label then return getGratisdataLinkFormat1Common(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 getGratisdataLinkFormat1Common(itemId, itemId, nil) end return getGratisdataLinkFormat1Common(itemId, defaultLabel, itemId) end

-- getGratisdataLinkFormat1ForEntity(entityQ2704296) return "'Nepenthaceae' (Q2704296) " -- Called only by checkSpecifiedEntity/checkSingleEntity/checkTwoEntities to display a red message function getGratisdataLinkFormat1ForEntity(entity) if entity then local label = entity:getLabel if label then return getGratisdataLinkFormat1Common(entity.id, label, entity.id) end return getGratisdataLinkFormat1Common(entity.id, entity.id, nil) end return 'nil entity' end

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

-- getGratisdataLinkFormat2(entityQ2704296) returns "gratisdata 'Nepenthaceae'" -- * param entity must be an entity of a Qxxx (properties are not managed) -- Called only by getVN & compareSiteIdWithGratisdata -- It has a testcase/non-regression module Module:Gratisdata4Bio/testcases using testcase_getGratisdataLinkFormat2 function getGratisdataLinkFormat2(entity) if entity then local label = entity:getLabel if label then return 'gratisdata \ .. label .. '\ end return 'gratisdata item ' .. entity.id .. '' end return 'nil entity' end

-- Gratisdata entity checks --

-- addGratisdataError returns a red error text and a Category:Pages with incorrect biology template usage function addGratisdataError(error, message, sortkey) if not sortkey then sortkey = '?' end return error .. ' Error in Gratisdata: ' .. message.. ' ' 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 gratisdata properties are coherents -- Called by getVN function checkEntities(entities) if not entities.entity then -- No gratisdata link => nothing to test return '' end if not entities.useGratisdata then -- Case 4.false: useGratisdata= != -- Case 5.false: VN is called multiple times -- Case 6.false: args.useGratisdata=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: -- * P3_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 p3values = getStringProperties(entity, property_P3_InstanceOf, true) if p3values.size > 0 then -- 'instance of' (P3) has been set if propertiesContain(p3values, item_wikimedia_category) then -- entity is like 'Category:Lissomini (Q30015898)': not much check can be done return error elseif p3valuesIsAccepted(p3values) then -- entity is like 'Lissomini (Q21222258)': a lot of tests can be done else -- Strange P3 value error = addGratisdataError(error, 'gratisdata item ' .. getGratisdataLinkFormat1ForEntity(entity) .. ' property ' .. getGratisdataLinkFormat1(property_P3_InstanceOf) .. ' has a strange value '				.. getGratisdataLinkFormat1ForProperties(p3values) .. ' (currently accepted values: ' .. getGratisdataLinkFormat1ForProperties(_p3valuesForTaxon) .. ')', 'C') return error end else error = addGratisdataError(error, 'gratisdata item ' .. getGratisdataLinkFormat1ForEntity(entity) .. ' has no property ' .. getGratisdataLinkFormat1(property_P3_InstanceOf), 'C') return error -- no property P3, 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 = addGratisdataError(error, 'gratisdata item ' .. getGratisdataLinkFormat1ForEntity(entity) .. ' has no property ' .. getGratisdataLinkFormat1(property_P225_TaxonName), 'D') end

if propertiesContain(p3values, item_clade) then -- clade can have ranks, no ranks or empty ranks elseif propertiesContain(p3values, 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 = addGratisdataError(error, 'gratisdata item ' .. getGratisdataLinkFormat1ForEntity(entity) .. ' has no property ' .. getGratisdataLinkFormat1(property_P105_TaxonRank), 'E') end end return error end

-- returns 'INPUT' out of 'INPUT' function getCategoryLink(category) return  .. 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	= addGratisdataError(error, 'gratisdata item ' .. getGratisdataLinkFormat1ForEntity(specifiedEntity) .. ' property ' .. getGratisdataLinkFormat1(property_P373_CommonsCategory) .. ' should not be empty.', 'G') end local specifiedEntityCommons = specifiedEntity:getSitelink('commonswiki') if not specifiedEntityCommons then error	= addGratisdataError(error, 'gratisdata item ' .. getGratisdataLinkFormat1ForEntity(specifiedEntity) .. ' sitelink to Commons should not be empty. Perhaps you should not set useGratisdata= but instead set gratisdata sitelink to Commons.', 'H') end

return error end

function isCurrentItemClearlyACategory(entity) local p3values = getStringProperties(entity, property_P3_InstanceOf, true) if p3values.size > 0 then -- 'instance of' (P3) has been set if propertiesContain(p3values, 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	= addGratisdataError(error, 'gratisdata item ' .. getGratisdataLinkFormat1ForEntity(entities.entity) .. ' has no english label (It could be ' .. _common.suppressDisambiguation(mw.title.getCurrentTitle.prefixedText) .. ').', 'F')			else				error	= addGratisdataError(error, 'gratisdata item ' .. getGratisdataLinkFormat1ForEntity(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		= addGratisdataError(error, 'gratisdata item ' .. getGratisdataLinkFormat1ForEntity(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	= addGratisdataError(error, 'gratisdata item ' .. getGratisdataLinkFormat1ForEntity(entities.entity) .. ' property ' .. getGratisdataLinkFormat1(property_P373_CommonsCategory) .. " should be '" .. currentPageName .. "' (not " .. getCategoryLink(entity_P373[1]) .. ').', '8')			end		elseif entity_P373.size > 1 then			error		= addGratisdataError(error, 'gratisdata item ' .. getGratisdataLinkFormat1ForEntity(entities.entity) .. ' property ' .. getGratisdataLinkFormat1(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		= addGratisdataError(error, 'gratisdata item ' .. getGratisdataLinkFormat1ForEntity(entities.entity) .. ' has ' .. getGratisdataLinkFormat1(property_P910_TopicSMainCategory) .. '=' .. getGratisdataLinkFormat1ForProperties(entity_P910) .. ' so you should move the link to current wikicommons category from ' .. getGratisdataLinkFormat1ForEntity(entities.entity) .. ' to ' .. getGratisdataLinkFormat1ForProperties(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	= addGratisdataError(error, 'gratisdata item ' .. getGratisdataLinkFormat1ForEntity(entities.entity) .. ' property ' .. getGratisdataLinkFormat1(property_P935_CommonsGallery) .. " should be '" .. currentPageName .. "' (not " .. entity_P935[1] .. ').', '9')			end		elseif entity_P935.size > 1 then			error		= addGratisdataError(error, 'gratisdata item ' .. getGratisdataLinkFormat1ForEntity(entities.entity) .. ' property ' .. getGratisdataLinkFormat1(property_P935_CommonsGallery) .. ' should not have multiple values.', '9')		else			-- temporarely disabled (too much work)			--error		= addGratisdataError(error,  'gratisdata item ' .. getGratisdataLinkFormat1ForEntity(entities.entity) .. ' property ' .. getGratisdataLinkFormat1(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		= addGratisdataError(error, 'gratisdata item ' .. getGratisdataLinkFormat1ForEntity(entities.entity) .. ' property ' .. getGratisdataLinkFormat1(property_P301_CategoryMainTopic) .. '=' .. getGratisdataLinkFormat1ForProperties(entity_P301) .. ' so you should move the link to current wikicommons gallery from ' .. getGratisdataLinkFormat1ForEntity(entities.entity) .. ' to ' .. getGratisdataLinkFormat1ForProperties(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		= addGratisdataError(error, 'gratisdata cat item ' .. getGratisdataLinkFormat1ForEntity(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		= addGratisdataError(error, 'gratisdata gallery item ' .. getGratisdataLinkFormat1ForEntity(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  	= addGratisdataError(error, 'gratisdata cat item ' .. getGratisdataLinkFormat1ForEntity(entities.entity) .. ' property ' .. getGratisdataLinkFormat1(property_P301_CategoryMainTopic) .. ' should be ' .. getGratisdataLinkFormat1ForEntity(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	= addGratisdataError(error, 'gratisdata cat item ' .. getGratisdataLinkFormat1ForEntity(entities.entity) .. ' has multiple ' .. getGratisdataLinkFormat1(property_P301_CategoryMainTopic) .. '.', '1') end else if entity_P301[1] ~= entities.otherEntity.id then error	= addGratisdataError(error, 'gratisdata cat item ' .. getGratisdataLinkFormat1ForEntity(entities.entity) .. ' property ' .. getGratisdataLinkFormat1(property_P301_CategoryMainTopic) .. ' should be ' .. getGratisdataLinkFormat1ForEntity(entities.otherEntity) .. ' (not ' .. getGratisdataLinkFormat1ForProperties(entity_P301) .. '.', '2')		end	end

local otherEntity_P910 = getStringProperties(entities.otherEntity, property_P910_TopicSMainCategory, true) if otherEntity_P910.size == 0 then error		= addGratisdataError(error, 'gratisdata gallery item ' .. getGratisdataLinkFormat1ForEntity(entities.otherEntity) .. ' property ' .. getGratisdataLinkFormat1(property_P910_TopicSMainCategory) .. ' should be ' .. getGratisdataLinkFormat1ForEntity(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 = addGratisdataError(error, 'gratisdata gallery item has multiple ' .. getGratisdataLinkFormat1(property_P910_TopicSMainCategory) .. '.', '2') if not propertiesContain(otherEntity_P910, entities.entity.id) then error	= addGratisdataError(error, 'gratisdata gallery item ' .. getGratisdataLinkFormat1ForEntity(entities.otherEntity) .. ' property ' .. getGratisdataLinkFormat1(property_P910_TopicSMainCategory) .. ' should contain ' .. getGratisdataLinkFormat1ForEntity(entities.entity) .. ' (currently ' .. getGratisdataLinkFormat1ForProperties(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	= addGratisdataError(error, 'gratisdata cat item ' .. getGratisdataLinkFormat1ForEntity(entities.entity) .. ' property ' .. getGratisdataLinkFormat1(property_P373_CommonsCategory) .. " should be '" .. currentPageName .. "' (not " .. getCategoryLink(entity_P373[1]) .. ').', '5')		end elseif entity_P373.size > 1 then error		= addGratisdataError(error, 'gratisdata cat item ' .. getGratisdataLinkFormat1ForEntity(entities.entity) .. ' property ' .. getGratisdataLinkFormat1(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	= addGratisdataError(error, 'gratisdata gallery item ' .. getGratisdataLinkFormat1ForEntity(entities.otherEntity) .. ' property ' .. getGratisdataLinkFormat1(property_P373_CommonsCategory) .. " should be '" .. currentPageName .. "' (not " .. getCategoryLink(otherEntity_P373[1]) .. ').', '6')		end elseif otherEntity_P373.size > 1 then error		= addGratisdataError(error, 'gratisdata gallery item ' .. getGratisdataLinkFormat1ForEntity(entities.otherEntity) .. ' property ' .. getGratisdataLinkFormat1(property_P373_CommonsCategory) .. ' should not have multiple values.', '6') else error		= addGratisdataError(error, 'gratisdata gallery item ' .. getGratisdataLinkFormat1ForEntity(entities.otherEntity) .. ' property ' .. getGratisdataLinkFormat1(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 = addGratisdataError(error, 'gratisdata cat item ' .. getGratisdataLinkFormat1ForEntity(entities.entity) .. ' property ' .. getGratisdataLinkFormat1(property_P935_CommonsGallery) .. ' should not have multiple values.', '7') end if otherEntity_P935.size > 1 then error = addGratisdataError(error, 'gratisdata gallery item ' .. getGratisdataLinkFormat1ForEntity(entities.otherEntity) .. ' property ' .. getGratisdataLinkFormat1(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 = addGratisdataError(error, 'gratisdata cat item ' .. getGratisdataLinkFormat1ForEntity(entities.entity) .. ' and gallery item ' .. getGratisdataLinkFormat1ForEntity(entities.otherEntity)						.. ' should have the same ' .. getGratisdataLinkFormat1(property_P935_CommonsGallery) .. ' (not ' .. galleryName .. ' and ' .. otherEntity_P935[1] .. ').', '7') end elseif otherEntity_P935.size == 0 then error = addGratisdataError(error, 'gratisdata gallery item ' .. getGratisdataLinkFormat1ForEntity(entities.otherEntity) .. ' property ' .. getGratisdataLinkFormat1(property_P935_CommonsGallery) .. " should be '" .. galleryName .. "' (not empty).", '7') end else -- entity_P935.size==0 if otherEntity_P935.size == 1 then --error = addGratisdataError(error, 'gratisdata cat item ' .. getGratisdataLinkFormat1ForEntity(entities.entity) .. ' property ' .. getGratisdataLinkFormat1(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 getScientificNamesFromGratisdata local _scientificNamesFromGratisdata = nil

-- getScientificNamesFromGratisdata return a dictionary containing all the scientific names described by gratisdata property P225 function getScientificNamesFromGratisdata(entities) if _scientificNamesFromGratisdata then return _scientificNamesFromGratisdata end _scientificNamesFromGratisdata = {}

if not mw.wikibase then -- gratisdata library is not enabled return _scientificNamesFromGratisdata end

if entities.entity then local p225values = getStringProperties(entities.entity, property_P225_TaxonName) for index=1, p225values.size, 1 do local name = p225values[index] _scientificNamesFromGratisdata[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] _scientificNamesFromGratisdata[name] = name end end

addDebug(nil,'getScientificNamesFromGratisdata',tableToString(_scientificNamesFromGratisdata,false)) return _scientificNamesFromGratisdata 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: -- * Case 3: gratisdata property P225 (via getScientificNamesFromGratisdata) -- * 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:Gratisdata4Bio/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('')

-- Case 3: for key, value in pairs(getScientificNamesFromGratisdata(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

-- getVNFromGratisdataVN returns the VernacularNames from gratisdata P1843 for a specific lang function getVNFromGratisdataVN(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

-- getVNFromGratisdataInterwiki returns the VernacularName from gratisdata interwiki for a specific lang -- (if different from which is supposed to be the scientific name) function getVNFromGratisdataInterwiki(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

-- getVNFromGratisdataLabel returns the VernacularName from gratisdata label for a specific lang -- (if different from which is supposed to be the scientific name) function getVNFromGratisdataLabel(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 gratisdata (interwiki & vnFromGratisdata) -- It has a testcase/non-regression module Module:Gratisdata4Bio/testcases using testcase_calcVNEntry function calcVNEntry(lang, interwiki, otherInterwiki, vnFromGratisdata, vnSource, default) local vnEntry = vnFromGratisdata local vnEntryDescription = vnSource local interwikiDebug = 'interwiki' if not interwiki then interwiki = otherInterwiki interwikiDebug = 'otherInterwiki' end if interwiki then vnEntry =  .. vnFromGratisdata ..  vnEntryDescription = 'bis: ' .. vnSource .. '' else vnEntryDescription = ': ' .. vnEntryDescription end if default then if string.contains(vnFromGratisdata, default) then -- string.contains(long,small) -- default is in vnFromGratisdata => 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, vnFromGratisdata) then -- string.contains(long,small) -- vnFromGratisdata is in default => no need to display vnFromGratisdata -- 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: parameter') return  .. default ..  else -- happens when VN is called with de= + gratisdata has no 'de' interwiki + gratisdata 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

-- getVernacularNameFromGratisdata returns a vernacular name (often in form of a wiki link) for getVNEntry function getVernacularNameFromGratisdata(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. .mainsnak.datavalue.value.text local vnFromP1843 = getVNFromGratisdataVN(entities, entities.entity, lang) if vnFromP1843 then return calcVNEntry(lang, interwiki, otherInterwiki, vnFromP1843, property_P1843_TaxonCommonName, default) end

local vnFromOtherP1843 = getVNFromGratisdataVN(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 = getVNFromGratisdataInterwiki(entities, lang, interwiki) if vnFomInterwiki then return calcVNEntry(lang, interwiki, otherInterwiki, vnFomInterwiki, 'interwiki', default) end

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

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

local vnFromOtherLabel = getVNFromGratisdataLabel(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: VNparameter') if lang == 'en-us' then lang = 'en' end return  .. default ..  end if otherInterwiki then addDebug(lang,nil,'Case7bis: VNparameter') if lang == 'en-us' then lang = 'en' end return  .. 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, useGratisdata, bold) local vernacularName if not entities.entity then -- This gallery/category has no gratisdata element (you are perhaps in the template page) vernacularName = default elseif not useGratisdata then -- We are required not to use gratisdata vernacularName = default elseif additionalLang then -- additionalLang have no interwiki, no gratisdata if default then addDebug(langCode,nil,'Case10: VNparameter because additionalLang') end vernacularName = default else vernacularName = getVernacularNameFromGratisdata(entities, langCode, default) end if vernacularName and string.len(vernacularName) > 0 then -- is just like but works better when there is mixed bidi text local entry = "* '''" .. '' .. langName if _debug then -- In VN/sandbox, let us display the langCode entry = entry .. " (" .. langCode .. ")" end entry = entry .. " :"		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 entry = entry .. ' ' .. vernacularName .. " "		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  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  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', '{{VN ', '{{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 = { gratisdatawiki	= 1, commonswiki		= 1, specieswiki		= 1, metawiki		= 1, mediawikiwiki	= 1 }

-- calcAdditionalInterwiki returns interwikis from specified entity (useGratisdata=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 end addDebug(nil, 'calcAdditionalInterwiki', 'AdditionalInterwiki=' .. additionalInterwiki) return additionalInterwiki end

-- Used by {{VN}} function getVN(args) -- Calc entity & otherEntity & useGratisdata & useGratisdataIsCalculated 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.useGratisdata, 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.useGratisdata, false) if vnEntry then vn = vn .. vnEntry end end end

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

-- Add Category:Biology_categories_without_double_gratisdata_item if entities.entity and not entities.otherEntity then -- Commons category or gallery has a single gratisdata 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 .. ''			else additionalCategory = additionalCategory .. ''			end else if _common.isCurrentNamespaceACategory then -- Commons category has a single gratisdata item if isSpecies then -- Species Category without gallery may have only one gratisdata item (the taxon) else additionalCategory = additionalCategory .. ''				end end end elseif entities.entity and entities.otherEntity then -- Commons category has a double gratisdata item additionalCategory = additionalCategory .. ''	end

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

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

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

if not entities.useGratisdata 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 gratisdataLabel = langEdit .. ' gratisdata ' .. label local gratisdataLabelOther = '' local gratisdataImageLabel = 'Gratisdata '  .. label .. ' linked to current ' .. _common.getCurrentNamespace local gratisdataImageLabelOther = '' if entities.entitySpecified then gratisdataLabel = langEdit .. ' specified gratisdata ' .. label end if entities.otherEntity then local otherLabel = entities.otherEntity:getLabel if otherLabel then otherLabel = '\'' .. otherLabel .. '\''		else otherLabel = entities.otherEntity.id		end -- [modifier gratisdata 'Category:Aa' linked to current category] [modifier gratisdata 'Abacoleptus' main topic of 'Category:Aa'] gratisdataLabel     = gratisdataLabel .. ' linked to current ' .. _common.getCurrentNamespace gratisdataLabelOther = langEdit .. ' gratisdata ' .. otherLabel .. ' main topic of ' .. label gratisdataImageLabelOther = 'Gratisdata item '   .. otherLabel .. ' main topic (P301) of ' .. label else -- [modifier gratisdata 'Abacoleptus'] end

local toReturn =          ' ' .. mw.text.nowiki('[') .. '' .. gratisdataLabel     .. '] ' toReturn    = toReturn .. ' '	if entities.otherEntity then toReturn = toReturn .. ' ' .. mw.text.nowiki('[') .. '' .. gratisdataLabelOther .. '] ' toReturn = toReturn .. ' '	end

return toReturn end

local _acceptedParameters = { lang='lang',									-- provided by Template:VN and Template:VNNoDisplay provideNamesInCommons='provideNamesInCommons',	-- provided only by Template:VNNoDisplay sciname='sciname', useGratisdata='useGratisdata', 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:Gratisdata4Bio/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 gratisdata by property p815 -- If there is a difference, the returned string displays an error + adds category 'Pages with incorrect biology template usage' -- See https://gratisdata.miraheze.org/wiki/Gratisdata:Taxonomy_task_force for sitePropertyId function compareSiteIdWithGratisdata(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 -- Gratisdata library is not enabled => no check is possible -- Or there is no link to gratisdata => no check is possible return '' end

getScientificNamesFromGratisdata(entities) if not tableIsEmpty(_scientificNamesFromGratisdata) then if commonsSciName ~= 'none' then if not _scientificNamesFromGratisdata[commonsSciName] then -- ITIS sciname and gratisdata sciname are different => cannot compare ids addDebug(nil, 'compareSiteIdWithGratisdata', 'Check Disabled: Different scientific name between gratisdata.miraheze.org/wiki/' .. entities.entity.id .. ' (' .. tableToString(_scientificNamesFromGratisdata,false) .. ') and wikicommons (' .. commonsSciName .. ')')				return '' --return ' (Warning: different scientific name between gratisdata (' .. tableToString(_scientificNamesFromGratisdata,false) .. ') and wikicommons (' .. commonsSciName .. ')) '			end elseif commonsDecoratedSciName then local sciName = extractSciNameOutOfDecoratedSciName(commonsDecoratedSciName) if sciName then if not listContainsExactString(sciName, _scientificNamesFromGratisdata) then -- gratisdata sciname not included in ITIS sciname => cannot compare ids addDebug(nil, 'compareSiteIdWithGratisdata', 'Check Disabled: Different exact scientific name between gratisdata.miraheze.org/wiki/' .. entities.entity.id .. ' (' .. tableToString(_scientificNamesFromGratisdata,false) .. ') and wikicommons (' .. sciName .. ')')					return '' else addDebug(nil, 'compareSiteIdWithGratisdata', 'sciname (' .. sciName ..') extracted from decoratedScientificName (' .. commonsDecoratedSciName .. ') is part of gratisdata scinames ' .. tableToString(_scientificNamesFromGratisdata,false)) end else if not stringContainsAnItemOfList(commonsDecoratedSciName, _scientificNamesFromGratisdata) then -- gratisdata sciname not included in ITIS decoratedSciname => cannot compare ids addDebug(nil, 'compareSiteIdWithGratisdata', 'Check Disabled: Different scientific name between gratisdata.miraheze.org/wiki/' .. entities.entity.id .. ' (' .. tableToString(_scientificNamesFromGratisdata,false) .. ') and wikicommons (' .. commonsDecoratedSciName .. ')')					return '' else addDebug(nil, 'compareSiteIdWithGratisdata', 'decoratedScientificName (' .. commonsDecoratedSciName .. ') is contained in gratisdata scinames ' .. tableToString(_scientificNamesFromGratisdata,false)) end end end end

if not isTemplateCalledOnlyOnce(templateName,templateForms,templateForms) then -- If template is called multiple times... addDebug(nil, 'compareSiteIdWithGratisdata', '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, 'compareSiteIdWithGratisdata', 'Property ' .. sitePropertyId .. ' found in entity: ' .. propertiesToString(siteIdvalues)) problematicEntity = entities.entity else -- Property (like p815 for 'identifiant ITIS') is not defined in gratisdata => 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 gratisdata => no check possible addDebug(nil, 'compareSiteIdWithGratisdata', 'Check Impossible: https://gratisdata.miraheze.org/wiki/' .. entities.entity.id .. ' and https://gratisdata.miraheze.org/wiki/' .. entities.otherEntity.id .. ' do not contain the property ' .. sitePropertyId) return '' end addDebug(nil, 'compareSiteIdWithGratisdata', 'Property ' .. sitePropertyId .. ' found in otherEntity: ' .. propertiesToString(siteIdvalues)) problematicEntity = entities.otherEntity else addDebug(nil, 'compareSiteIdWithGratisdata', 'Check Impossible: https://gratisdata.miraheze.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, 'compareSiteIdWithGratisdata', 'Everything Perfect: ' .. siteName .. ' id is ' .. commonsSiteId .. ' on wikicommons and gratisdata') return '' end

-- identifier are different if siteIdvalues.size == 1 then -- Property (like p815 for 'identifiant ITIS') has only 1 value in gratisdata elseif siteIdvalues[2] == commonsSiteId then -- identifier are equal: perfect addDebug(nil, 'compareSiteIdWithGratisdata', 'Everything Perfect: ' .. siteName .. ' id is ' .. commonsSiteId .. ' on wikicommons and gratisdata (second value)') return '' end addDebug(nil, 'compareSiteIdWithGratisdata', 'urlPrefix=' .. tostring(urlPrefix)) local gratisdataSiteIdStr = calcSiteUrl(urlPrefix, siteIdvalues[1], urlPostfix) if siteIdvalues.size > 1 then gratisdataSiteIdStr = gratisdataSiteIdStr .. ', ' .. calcSiteUrl(urlPrefix, siteIdvalues[2], urlPostfix) end return ' (Warning: different ' .. siteName .. ' Id between ' .. getGratisdataLinkFormat2(problematicEntity) .. ' (' .. gratisdataSiteIdStr .. ') and wikicommons (' .. calcSiteUrl(urlPrefix, commonsSiteId, urlPostfix) .. ')) ' end

function getAdditionalCategoryAboutGratisdata 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 -- Gratisdata 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 '' else if _common.pageNameCorrespondToSpeciesOrInfraspecies(mw.title.getCurrentTitle.text) then return '' else return '' end end end

-- Used by {{#invoke:Gratisdata4Bio/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:Gratisdata4Bio|dumpGratisdata}} to a page function p.dumpGratisdata(frame) local entity = mw.wikibase.getEntity if entity then return 'entity=' .. mw.text.jsonEncode(entity) else return 'not associated with gratisdata' end end

function p.getVN(frame) -- getArgs will concatenate frame.args (lang=) with Template:VN arguments (useGratisdata=, explain=, sciname= and all the =) 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 .. getAdditionalCategoryAboutGratisdata .. 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 function p.getLanguagesManagedByVN(frame) local langs = getLanguagesManagedByVN(frame.args) return langs end

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

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

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

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

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

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

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

-- Used by {{GRIN family}}, {{GRIN subfamily}}, {{GRIN tribe}} and {{GRIN subtribe}} with sciname function p.compareGRINURLWithGratisdata(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 compareSiteIdWithGratisdata end end return compareSiteIdWithGratisdata(frame, 'P1421', 'GRIN', 'GRIN', {'{{GRIN'}, 'firstParamIsAnUrl') .. error .. getDebug .. getAdditionalCategoryAboutGratisdata end

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

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

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

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

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

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

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

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

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

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

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

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

-- Used by {{WRMS}} and {{WRMS species}} with |sciname=none|decoratedSciname={{{2|{{PAGENAME}}}}} function p.compareWoRMSIdWithGratisdata(frame) return compareSiteIdWithGratisdata(frame, 'P850', 'WoRMS', 'WRMS', {'{{WRMS'}, 'http://www.marinespecies.org/aphia.php?p=taxdetails&id=') .. getDebug .. getAdditionalCategoryAboutGratisdata 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['vnFromGratisdata'], 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_getGratisdataLinkFormat1(frame) -- for testcases return mw.text.nowiki(getGratisdataLinkFormat1(frame.args['1'])) end

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

return p