local _common=require('Module:Biology')
-- global variable which receives debug info if not nil (Should be =nil in Wikidata4Bio and ={} in Wikidata4Bio/sandbox)
local _debug=nil
local property_P31_InstanceOf = 'P31'
local property_P105_TaxonRank = 'P105'
local property_P225_TaxonName = 'P225'
local property_P301_CategoryMainTopic = 'P301'
local property_P373_CommonsCategory = 'P373'
local property_P910_TopicSMainCategory = 'P910'
local property_P935_CommonsGallery = 'P935'
local property_P1843_TaxonCommonName = 'P1843'
local item_wikimedia_category = 'Q4167836'
local item_clade = 'Q713623'
local item_virus = 'Q808'
----------------------------------------------------------------------------------------------------
---------- Debug utilities -------------------------------------------------------------------------
----------------------------------------------------------------------------------------------------
-- addDebug() adds debug/verbose/trace info to _debug
-- * param lang: either 'fr' or nil
-- * param functionName: the name of the calling function whithout ()
-- * param text: the text of the debug
function addDebug(lang, functionName, text)
if not _debug then
return
end
local index
if lang then
index = 'Lang ' .. lang
else
index = functionName .. '()'
end
local previousText = _debug[index]
if previousText then
previousText = previousText .. ', '
else
previousText = ''
end
if functionName and lang then
previousText = previousText .. functionName .. ': ' .. text
else
previousText = previousText .. text
end
_debug[index] = previousText
end
-- getDebug() returns a formated version of _debug for the display
function getDebug()
if not _debug then
return ''
end
if tableIsEmpty(_debug) then
return ''
end
-- Sort _debug into debug2
local debug2 = {}
for key, value in pairs(_debug) do
table.insert(debug2,{key,value})
end
table.sort(debug2, function(t1,t2) return t1[1] < t2[1] end)
-- Serialize debug2 in displayedDebug
local displayedDebug = '<BR/>Debug:'
for key, value in pairs(debug2) do
displayedDebug = displayedDebug .. '<BR/>- ' .. value[1] .. ': ' .. mw.text.nowiki(value[2])
end
-- Clear debug:
_debug={}
return displayedDebug
end
----------------------------------------------------------------------------------------------------
---------- String utilities ------------------------------------------------------------------------
----------------------------------------------------------------------------------------------------
-- getTextAsAsciiCode('Paphiopedilum') returns '112.104.105.111.112.101.100.105.108.117.109'
function getTextAsAsciiCode(stri)
local result = ''
for i = 1, string.len(stri) do
result = result .. '.' .. string.byte(string.sub(stri, i, i))
end
return result
end
-- returns true if an item of listShortString in contained in longString
function stringContainsAnItemOfList(longString, listShortString)
longString = string.lower(longString)
for key, value in pairs(listShortString) do
local listItem = string.lower(value)
if string.contains(longString, listItem) then
addDebug(nil, 'stringContainsAnItemOfList', 'longString= ' .. longString .. ' listShortString=' .. tableToString(listShortString,false) .. ' return true')
return true
end
end
addDebug(nil, 'stringContainsAnItemOfList', 'longString= ' .. longString .. ' listShortString=' .. tableToString(listShortString,false) .. ' return false')
return false
end
-- returns true is searchedString is found in listString
function listContainsExactString(searchedString, listString)
searchedString = string.lower(searchedString)
for key, value in pairs(listString) do
local listItem = string.lower(value)
if searchedString == listItem then
addDebug(nil, 'listContainsExactString', 'searchedString=' .. searchedString .. ' listString=' .. tableToString(listString,false) .. ' return true')
return true
end
end
addDebug(nil, 'listContainsExactString', 'searchedString=' .. searchedString .. ' listString=' .. tableToString(listString,false) .. ' return false')
return false
end
----------------------------------------------------------------------------------------------------
---------- Table utilities -------------------------------------------------------------------------
----------------------------------------------------------------------------------------------------
-- tableIsEmpty return true if the parameter mytable is nil or empty
function tableIsEmpty(mytable)
if not mytable then
return true
end
for key, value in pairs(mytable) do
return false
end
return true
end
function dumpValue(value, withType)
if not value then
return 'nil'
end
local valueType = type(value)
local valueStr = ''
if withType then
valueStr = valueType .. ':'
end
if valueType == 'table' then
elseif valueType == 'string' then
if string.find(value, ' ', 1, true) then
valueStr = valueStr .. "'" .. value .. "'"
else
valueStr = valueStr .. value
end
elseif valueType == 'number' then
return valueStr .. value
elseif valueType == 'boolean' then
return valueStr .. tostring(value)
end
return valueStr
end
function hasSmallerKeyThanFunctor(pair1, pair2)
local key1 = pair1[1]
if (type(key1) ~= 'string') then
return false
end
local key2 = pair2[1]
if (type(key2) ~= 'string') then
return false
end
return key1 < key2
end
-- tableToString() returns a string out of a table for debug and non regression purpose sorted by keys
-- If withKey then format '<key>=<value>' is returned else format '<value>' is returned
function tableToString(mytable, withKey)
-- Sort mytable into mytable2
local mytable2 = {}
for key, value in pairs(mytable) do
table.insert(mytable2,{key,value})
end
table.sort(mytable2, hasSmallerKeyThanFunctor)
-- Serialize mytable2 into result
local result = nil
for key, value in pairs(mytable2) do
local currentResult = value[2]
if withKey then
currentResult = value[1] .. '=' .. currentResult
end
if result then
result = result .. ', ' .. currentResult
else
result = currentResult
end
end
if not result then
return ''
end
return result
end
----------------------------------------------------------------------------------------------------
---------- Namespace/articleName utilities ---------------------------------------------------------
----------------------------------------------------------------------------------------------------
-- isLink(name) return true when name is a link syntax like '[[sdfs|dfsfsd]]'
-- It has a testcase/non-regression module Module:Wikidata4Bio/testcases using testcase_isLink()
function isLink(name)
if not name then
return false
end
if string.find(name, ']]', 1, true) then
return true
end
if string.find(name, '[http:', 1, true) then
return true
end
if string.find(name, '[https:', 1, true) then
return true
end
if string.find(name, '<ref', 1, true) then
return true
end
if string.find(name,'[[', 1, true) then
return true
end
local cleanedName = mw.text.killMarkers(name)
if cleanedName ~= name then
-- name contains markers
return true
end
return false
end
----------------------------------------------------------------------------------------------------
---------- Lang utilities --------------------------------------------------------------------------
----------------------------------------------------------------------------------------------------
local _defaultLangCode = mw.language.getContentLanguage():getCode()
function hasSmallerLangCodeThanFunctor(langCode1, langCode2)
if langCode1 == _defaultLangCode then
return true
end
if langCode2 == _defaultLangCode then
return false
end
return langCode1 < langCode2
end
local _languageNamesByCode = nil
local _additionalLanguageNamesByCode = nil
local _languageCodes = nil
-- Fill _languageNamesByCode and _languageCodes
function calcLanguages(userLangCode)
if _languageNamesByCode then
return
end
_languageNamesByCode = mw.language.fetchLanguageNames()
_languageCodes = {}
for langCode, _ in pairs(_languageNamesByCode) do
table.insert(_languageCodes, langCode)
end
_additionalLanguageNamesByCode = mw.language.fetchLanguageNames(userLangCode,'all')
for langCode, langName in pairs(_additionalLanguageNamesByCode) do
if _languageNamesByCode[langCode] then
-- standard language
else
table.insert(_languageCodes, langCode)
end
end
table.sort(_languageCodes, hasSmallerLangCodeThanFunctor)
end
-- getLanguagesManagedByVN() is called by {{VN/doc}} to display all languageCodes managed by VN.
-- It does not use calcLanguages()
-- We support 417 wikipedia languages + 302 additional languages (like 'en-us')
function getLanguagesManagedByVN(args)
local userLangCode = args.lang
local languageNamesByCode = mw.language.fetchLanguageNames(userLangCode)
local languageCodes = {}
for lang, _ in pairs(languageNamesByCode) do
table.insert(languageCodes, lang)
end
table.sort(languageCodes, hasSmallerLangCodeThanFunctor)
local languages = nil
for _, langCode in pairs(languageCodes) do
if languages then
languages = languages .. ', '
else
languages = "'''" .. tostring(table.getn(languageCodes)) .. " languages (having interwiki and wikidata): '''"
end
languages = languages .. langCode .. ':' .. languageNamesByCode[langCode]
end
-- Additional Languages from https://www.mediawiki.org/wiki/Extension:Cldr are not well managed by wikicommons.
-- You cannot retrieve all their autonym.
-- fetchLanguageNames(nil,'all') returns the same as fetchLanguageNames('en')
-- See https://www.mediawiki.org/wiki/Extension_talk:Scribunto/Lua_reference_manual#fetchLanguageNames
-- And https://doc.wikimedia.org/mediawiki-core/1.25.5/php/Language_8php_source.html search for 'TODO: also include when'
-- And https://doc.wikimedia.org/mediawiki-core/REL1_25/php/Language_8php_source.html search for 'TODO: also include when'
local languageNamesByCode2 = mw.language.fetchLanguageNames(userLangCode,'all')
languageCodes = {}
for lang, _ in pairs(languageNamesByCode2) do
if not languageNamesByCode[lang] then
table.insert(languageCodes, lang)
end
end
table.sort(languageCodes, hasSmallerLangCodeThanFunctor)
local first = true
for _, langCode in pairs(languageCodes) do
if first then
languages = languages .. "\n\n:'''And " .. tostring(table.getn(languageCodes)) .. " additional languages (without interwiki and wikidata): '''"
first = false
else
languages = languages .. ', '
end
if langCode == 'sms' then
-- sms: is interpreted by wikimedia as an url leading to an unwanted blue link
languages = languages .. mw.text.nowiki('sms:')
else
languages = languages .. langCode .. ':'
end
languages = languages .. languageNamesByCode2[langCode]
end
-- Determine if fetchLanguageNames(nil,'all') is buggy
local fetchLanguageNamesNilAllBug = true
local languageNamesByCode3 = mw.language.fetchLanguageNames(nil,'all')
for lang, _ in pairs(languageNamesByCode3) do
if not languageNamesByCode[lang] then
fetchLanguageNamesNilAllBug = false -- No more buggy ???
break
end
end
local userLangName = tostring(languageNamesByCode[userLangCode])
languages = languages .. "\n:'''Note: additional languages name are in your language (" .. userLangName
if fetchLanguageNamesNilAllBug then
languages = languages .. ") because of a current limitation on additional languages'''"
else
languages = languages .. ") but only very temporary'''"
end
return languages
end
----------------------------------------------------------------------------------------------------
---------- Entities utilities ----------------------------------------------------------------------
----------------------------------------------------------------------------------------------------
-- areSameValidEntity() return true if both entities are set and have the same id
function areSameValidEntity(entity1, entity2)
if not entity1 then
return false
end
if not entity2 then
return false
end
return (entity1.id == entity2.id)
end
function createEmptyEntities(paramEntity)
return {entity=paramEntity, otherEntity=nil, useWikidata=false, useWikidataIsCalculated=false}
end
-- retrieveEntitiesSimple() returns {entity, otherEntity}
-- Called by retrieveEntities() which is called by getVN() and getVNTitle()
-- Also called by compareSiteIdWithWikidata() (were the is no useWikidata=)
function retrieveEntitiesSimple(entities)
if entities == nil then
entities = createEmptyEntities(nil)
end
if not mw.wikibase then
-- Case 1
return entities
end
entities.entity = mw.wikibase.getEntityObject()
if entities.entity then
local P301values = getStringProperties(entities.entity, property_P301_CategoryMainTopic, true)
addDebug(nil, 'retrieveEntitiesSimple', 'P301=' .. propertiesToString(P301values))
if P301values.size > 1 then
-- Error message will be displayed by checkEntities except if entities.entity.id is in _catItemsWithMultipleSubjects
addDebug(nil, 'retrieveEntitiesSimple', 'multiple otherEntity (P301) found => none is used')
elseif P301values.size == 1 then
entities.otherEntity = mw.wikibase.getEntityObject(P301values[1])
if entities.otherEntity then
if areSameValidEntity(entities.otherEntity, entities.entity) then
addDebug(nil, 'retrieveEntitiesSimple', 'otherEntity == entity')
entities.otherEntity = nil
else
addDebug(nil, 'retrieveEntitiesSimple', 'otherEntity found')
end
else
addDebug(nil, 'retrieveEntitiesSimple', 'otherEntity=nil when otherEntityId=' .. P301values[1])
end
end
end
return entities
end
-- retrieveEntities() returns {entity, otherEntity, useWikidata, useWikidataIsCalculated}
function retrieveEntities(args)
local entities = createEmptyEntities(nil)
if not mw.wikibase then
-- Case 1
return entities
end
-- wikidata library is enabled
if string.startsWith(args.useWikidata,'Q') then
-- useWikidata=Q1246
local success, myentity = pcall(mw.wikibase.getEntityObject, args.useWikidata)
if not success or not myentity then
-- Case 2
addDebug(nil, 'retrieveEntities', 'Case2: useWikidata=' .. args.useWikidata .. ' but failed to load ' .. args.useWikidata)
return
end
-- Case 3
entities.entity = myentity
entities.useWikidata = true
if areSameValidEntity(entities.entity, mw.wikibase.getEntityObject()) then
addDebug(nil, 'retrieveEntities', 'Case 3bis: useWikidata=' .. args.useWikidata .. ' but not needed as associated to the same item => useWikidata=true')
else
addDebug(nil, 'retrieveEntities', 'Case 3: useWikidata=' .. args.useWikidata .. ' => useWikidata=true')
end
entities.entitySpecified = true
else
retrieveEntitiesSimple(entities)
if entities.entity then
if string.isNilOrEmpty(args.useWikidata) then
entities.useWikidataIsCalculated = true
local sciname = string.trimOrNullify(args.sciname)
if sciname then
-- Case 4.true: useWikidata set to true
-- Case 4.false: useWikidata set to false
-- isScientificName(sciname) returns true if sciname==<category or gallery name>
entities.useWikidata = isScientificName(entities, sciname)
addDebug(nil, 'retrieveEntities', 'Case 4: useWikidata not set but sciname=' .. sciname .. ' => using useWikidata=isScientificName(sciname)=' .. tostring(entities.useWikidata))
else
-- Case 5.true: useWikidata set to true
-- Case 5.false: useWikidata set to false
entities.useWikidata = isVnCalledOnlyOnce()
addDebug(nil, 'retrieveEntities', 'Case 5: useWikidata not set and sciname not set => using useWikidata=isVnCalledOnlyOnce()=' .. tostring(entities.useWikidata))
end
else
-- Case 6.true: args.useWikidata=1
-- Case 6.false: args.useWikidata=0
entities.useWikidata = string.isTrue(args.useWikidata)
addDebug(nil, 'retrieveEntities', 'Case 6: args.useWikidata=' .. tostring(args.useWikidata) .. ' => entities.useWikidata=' .. tostring(entities.useWikidata))
end
else
-- Case 7.nil: args.useWikidata=nil but entity=nil => we avoid useWikidataIsCalculated
-- Case 7.true: args.useWikidata=1 but entity=nil
-- Case 7.false: args.useWikidata=0 and entity=nil
entities.useWikidata = false
addDebug(nil, 'retrieveEntities', 'Case 7: args.useWikidata=' .. tostring(args.useWikidata) .. ' but as entity=nil => entities.useWikidata=' .. tostring(entities.useWikidata))
end
end
return entities
end
----------------------------------------------------------------------------------------------------
---------- Wikidata properties utilities -----------------------------------------------------------
----------------------------------------------------------------------------------------------------
-- Only used by getStringProperties()
function getStringProperty(claim, propertyListsEntityIds)
local propertyValue = claim.mainsnak.datavalue
if not propertyValue then
addDebug(nil, 'getStringProperties', 'found claim without mainsnak.datavalue ' .. mw.text.jsonEncode(claim))
return nil
end
propertyValue = propertyValue.value
if not propertyValue then
addDebug(nil, 'getStringProperties', 'found claim without mainsnak.datavalue.value ' .. mw.text.jsonEncode(claim))
return nil
end
if propertyListsEntityIds then
propertyValue = propertyValue.id
if not propertyValue then
addDebug(nil, 'getStringProperties', 'found claim without mainsnak.datavalue.value.id ' .. mw.text.jsonEncode(claim))
return nil
end
end
local propertyValueType = type(propertyValue)
if propertyValueType ~= 'string' then
addDebug(nil, 'getStringProperties', 'found non string (' .. propertyValueType .. ') ' .. mw.text.jsonEncode(numericId))
return nil
end
propertyValue = mw.text.trim(propertyValue)
if propertyValue == '' then
-- get rid of this empty entry
return nil
end
return propertyValue
end
-- getStringProperties() returns a list in the form {size=3, 1=<value>, 2=<value> ...} corresponding to <entity>.claims.<propertyName>.<index>.mainsnak.datavalue.value
-- if propertyListsEntityIds is set, the path is <entity>.claims.<propertyName>.<index>.mainsnak.datavalue.value.id
-- getStringProperties(entity, P373) returns {size=3, 1='NameOfCommonsCategory', 2='Name2OfCommonsCategory' ...}
function getStringProperties(entity, propertyName, propertyListsEntityIds)
local claims = entity:getBestStatements( propertyName ) -- no need to check if return is nil, worst case it is an empty tab []
local properties = {size=0}
for _,claim in pairs(claims) do
local propertyValue = getStringProperty(claim, propertyListsEntityIds)
if propertyValue then
properties.size = properties.size + 1
properties[properties.size] = propertyValue
end
end
return properties
end
-- propertiesContain() return true if searchedPropertyValue is found in properties
function propertiesContain(properties, searchedPropertyValue)
for index=1, properties.size, 1
do
if properties[index] == searchedPropertyValue then
return true
end
end
return false
end
-- returns a string in the form 'item1, item2, item3'
function propertiesToString(properties)
local stringToReturn = ''
local prefix = ''
for index=1, properties.size, 1
do
stringToReturn = stringToReturn .. prefix .. tostring(properties[index])
prefix = ', '
end
return stringToReturn
end
----------------------------------------------------------------------------------------------------
---------- Wikidata P31 utilities ------------------------------------------------------------------
----------------------------------------------------------------------------------------------------
local _p31valuesForTaxon = {
size=11,
'Q16521', -- Q16521=='Taxon'
'Q310890', -- Q310890='Monotypic taxon'
'Q4150646', -- Q4150646='Cultivar group'
'Q4886', -- Q4886='cultivar'
'Q23038290', -- Q23038290='fossil taxon'
'Q47487597', -- Q47487597='monotypic fossil taxon'
item_clade,
item_virus,
'Q2568288', -- Q2568288='Ichnotaxon'
'Q1297859', -- Q1297859='species aggregate'
item_wikimedia_category -- Q4167836='page de catégorie de Wikimedia'
}
-- p31valuesIsAccepted(p31values) returns true if p31values contains on of the properties listed in _p31valuesForTaxon
function p31valuesIsAccepted(p31values)
for index=1, _p31valuesForTaxon.size, 1
do
if propertiesContain(p31values, _p31valuesForTaxon[index]) then
return true
end
end
return false
end
----------------------------------------------------------------------------------------------------
---------- Wikidata link utilities -----------------------------------------------------------------
----------------------------------------------------------------------------------------------------
-- getWikidataLinkFormat1Common('Qxx','B','C') returns "[[wikidata:Qxx|'B']] <small>(C)</small>"
-- getWikidataLinkFormat1Common('Pxx','B','C') returns "[[wikidata:property:Qxx|'B']] <small>(C)</small>"
-- Called only by getWikidataLinkFormat1() and getWikidataLinkFormat1ForEntity()
function getWikidataLinkFormat1Common(itemId, linkLabel, complement)
if string.startsWith(itemId,'P') then
-- It is a property
itemId = 'property:' .. itemId
end
local link = '[[wikidata:' .. itemId .. '|\'' .. linkLabel .. '\']]'
if complement then
link = link .. ' <small>(' .. complement .. ')</small>'
end
return link
end
-- getWikidataLinkFormat1(property_P301_CategoryMainTopic) return "[[wikidata:P301|'category's main topic']] <small>(P301)</small>"
-- getWikidataLinkFormat1('Q270') return "[[wikidata:Q270|'Nepenthaceae']] <small>(Q270)</small>"
-- getWikidataLinkFormat1('P373') return "[[wikidata:property:P373|'common category']] <small>(P373)</small>"
-- Called only by checkSpecifiedEntity()/checkSingleEntity()/checkTwoEntities() to display a red message
-- It has a testcase/non-regression module Module:Wikidata4Bio/testcases using testcase_getWikidataLinkFormat1()
function getWikidataLinkFormat1(itemId)
local entity = mw.wikibase.getEntity(itemId)
if entity then
local label = entity:getLabel()
if label then
return getWikidataLinkFormat1Common(itemId, label, itemId)
end
end
local defaultLabel = nil
if itemId == property_P225_TaxonName then
defaultLabel = 'taxon name'
elseif itemId == property_P1843_TaxonCommonName then
defaultLabel = 'taxon common name'
elseif itemId == property_P301_CategoryMainTopic then
defaultLabel = "category's main topic"
elseif itemId == property_P910_TopicSMainCategory then
defaultLabel = "topic's main category"
elseif itemId == property_P373_CommonsCategory then
defaultLabel = 'commons category'
else
return getWikidataLinkFormat1Common(itemId, itemId, nil)
end
return getWikidataLinkFormat1Common(itemId, defaultLabel, itemId)
end
-- getWikidataLinkFormat1ForEntity(entityQ2704296) return "[[wikidata:Q2704296|'Nepenthaceae']] <small>(Q2704296)</small>"
-- Called only by checkSpecifiedEntity()/checkSingleEntity()/checkTwoEntities() to display a red message
function getWikidataLinkFormat1ForEntity(entity)
if entity then
local label = entity:getLabel()
if label then
return getWikidataLinkFormat1Common(entity.id, label, entity.id)
end
return getWikidataLinkFormat1Common(entity.id, entity.id, nil)
end
return 'nil entity'
end
-- getWikidataLinkFormat1ForProperties(properties) returns getWikidataLinkFormat1(item1), getWikidataLinkFormat1(item2)...
function getWikidataLinkFormat1ForProperties(properties)
local list = ''
local listPrefix = ''
for index=1, properties.size, 1
do
list = list .. listPrefix .. getWikidataLinkFormat1(properties[index])
listPrefix = ', '
end
return list
end
-- getWikidataLinkFormat2(entityQ2704296) returns "[[wikidata:Q2704296|wikidata 'Nepenthaceae']]"
-- * param entity must be an entity of a Qxxx (properties are not managed)
-- Called only by getVN() & compareSiteIdWithWikidata()
-- It has a testcase/non-regression module Module:Wikidata4Bio/testcases using testcase_getWikidataLinkFormat2()
function getWikidataLinkFormat2(entity)
if entity then
local label = entity:getLabel()
if label then
return '[[wikidata:' .. entity.id .. '|wikidata \'' .. label .. '\']]'
end
return '[[wikidata:' .. entity.id .. '|wikidata item ' .. entity.id .. ']]'
end
return 'nil entity'
end
----------------------------------------------------------------------------------------------------
---------- Wikidata entity checks ------------------------------------------------------------------
----------------------------------------------------------------------------------------------------
-- addWikidataError() returns a red error text and a Category:Pages with incorrect biology template usage
function addWikidataError(error, message, sortkey)
if not sortkey then
sortkey = '?'
end
return error .. '<BR/><span class="error">Error in Wikidata: ' .. message.. '</span>[[Category:Pages with biology property incorrect on Wikidata|' .. sortkey .. ']]'
end
local _catItemsWithMultipleSubjects = {
Q8261220 = 1, -- Category:Arctiinae - There are 2 Arctiinae with narrow and wide sense
Q7149713 = 1, -- Category:Donkeys - There are 2 syn: Equus asinus & Equus africanus asinus
Q7328661 = 1, -- Category:Eudicots - There are 2 syn: eudicots & Eudicotyledoneae
Q6256259 = 1, -- Category:Gynatrix - There are 2 sujects: scientific name + vernaculare name (for sv only)
Q18282087 = 1, -- Category:Cleistogenes - There are 2 syn: Cleistogenes & Kengia
Q8763590 = 1, -- Category:Pitohui - There are 2 sujects: scientific name + vernaculare name (for en only)
Q9522340 = 1, -- Category:Ruminantia - There are 2 sujects: scientific name + Ruminant/Rumination
Q9675089 = 1, -- Category:Sinningia - There are 2 sujects: scientific name + vernaculare name (for sv only)
Q9414408 = 1, -- Category:Homo sapiens - There are 2 sujects: scientific name + vernaculare name
}
local _catItemsWithDisabledTests = {
Q1456850 = 1, -- Category:Birds - A mess because it is a vernaculare name
Q8700233 = 1, -- Category:Aves - A mess because of Category:Birds
Q7157802 = 1, -- Category:Animals - A mess because it is a vernaculare name
Q9470370 = 1, -- Category:Gregarines - A mess because it has many syn
Q5608148 = 1, -- Category:Insects - A mess because it is a vernaculare name
}
-- checkEntities() checks that wikidata properties are coherents
-- Called by getVN()
function checkEntities(entities)
if not entities.entity then
-- No wikidata link => nothing to test
return ''
end
if not entities.useWikidata then
-- Case 4.false: useWikidata=<scientificName> != <category or gallery name>
-- Case 5.false: VN is called multiple times
-- Case 6.false: args.useWikidata=0
return ''
end
if entities.entitySpecified then
return checkSpecifiedEntity(entities.entity)
end
if not _common.isCurrentNamespaceACategoryOrAGallery() then
return ''
end
if _catItemsWithDisabledTests[entities.entity.id] then
-- No test possible
return ''
end
if entities.otherEntity then
return checkTwoEntities(entities)
else
return checkSingleEntity(entities)
end
end
-- checkTaxonEntity() is called by checkSingleEntity() on entities.entity or by checkTwoEntities() on entities.otherEntity
-- checkTaxonEntity() checks that:
-- * P31_InstanceOf is filled
-- * P225_TaxonName is filled
-- * P105_TaxonRank is filled except for clades
function checkTaxonEntity(entity, error)
if not entity then
return error
end
local p31values = getStringProperties(entity, property_P31_InstanceOf, true)
if p31values.size > 0 then
-- 'instance of' (P31) has been set
if propertiesContain(p31values, item_wikimedia_category) then
-- entity is like 'Category:Lissomini (Q30015898)': not much check can be done
return error
elseif p31valuesIsAccepted(p31values) then
-- entity is like 'Lissomini (Q21222258)': a lot of tests can be done
else
-- Strange P31 value
error = addWikidataError(error, 'wikidata item ' .. getWikidataLinkFormat1ForEntity(entity) .. ' property ' .. getWikidataLinkFormat1(property_P31_InstanceOf) .. ' has a strange value '
.. getWikidataLinkFormat1ForProperties(p31values) .. ' (currently accepted values: ' .. getWikidataLinkFormat1ForProperties(_p31valuesForTaxon) .. ')', 'C')
return error
end
else
error = addWikidataError(error, 'wikidata item ' .. getWikidataLinkFormat1ForEntity(entity) .. ' has no property ' .. getWikidataLinkFormat1(property_P31_InstanceOf), 'C')
return error -- no property P31, so we cannot know it it is the taxon item => no more checks
end
local properties = getStringProperties(entity, property_P225_TaxonName)
if properties.size > 0 then
-- 'taxon name' (P225) has been set
else
error = addWikidataError(error, 'wikidata item ' .. getWikidataLinkFormat1ForEntity(entity) .. ' has no property ' .. getWikidataLinkFormat1(property_P225_TaxonName), 'D')
end
if propertiesContain(p31values, item_clade) then
-- clade can have ranks, no ranks or empty ranks
elseif propertiesContain(p31values, item_virus) then
-- virus can have ranks or no ranks
else
properties = getStringProperties(entity, property_P105_TaxonRank, true)
if properties.size > 0 then
-- taxon rank (P105) has been set
else
error = addWikidataError(error, 'wikidata item ' .. getWikidataLinkFormat1ForEntity(entity) .. ' has no property ' .. getWikidataLinkFormat1(property_P105_TaxonRank), 'E')
end
end
return error
end
-- returns '[[:Category:INPUT|INPUT]]' out of 'INPUT'
function getCategoryLink(category)
return '[[:Category:' .. category .. '|' .. category .. ']]'
end
function checkSpecifiedEntity(specifiedEntity)
local cleanEntities = createEmptyEntities(nil)
retrieveEntitiesSimple(cleanEntities)
if areSameValidEntity(specifiedEntity, cleanEntities.entity) then
if cleanEntities.otherEntity then
addDebug(nil, 'checkSpecifiedEntity', 'specifiedEntity==cleanEntities.entity && cleanEntities.otherEntity => checkTwoEntities()')
return checkTwoEntities(cleanEntities)
else
addDebug(nil, 'checkSpecifiedEntity', 'specifiedEntity==cleanEntities.entity but !cleanEntities.otherEntity => checkSingleEntity()')
return checkSingleEntity(cleanEntities)
end
end
if areSameValidEntity(specifiedEntity, cleanEntities.otherEntity) then
addDebug(nil, 'checkSpecifiedEntity', 'specifiedEntity=cleanEntities.otherEntity => checkTwoEntities()')
return checkTwoEntities(cleanEntities)
end
local error=''
local entity_P373 = getStringProperties(specifiedEntity, property_P373_CommonsCategory)
if entity_P373.size == 0 then
error = addWikidataError(error, 'wikidata item ' .. getWikidataLinkFormat1ForEntity(specifiedEntity) .. ' property ' .. getWikidataLinkFormat1(property_P373_CommonsCategory) .. ' should not be empty.', 'G')
end
local specifiedEntityCommons = specifiedEntity:getSitelink('commonswiki')
if not specifiedEntityCommons then
error = addWikidataError(error, 'wikidata item ' .. getWikidataLinkFormat1ForEntity(specifiedEntity) .. ' sitelink to Commons should not be empty. Perhaps you should not set useWikidata= but instead set wikidata sitelink to Commons.', 'H')
end
return error
end
function isCurrentItemClearlyACategory(entity)
local p31values = getStringProperties(entity, property_P31_InstanceOf, true)
if p31values.size > 0 then
-- 'instance of' (P31) has been set
if propertiesContain(p31values, item_wikimedia_category) then
-- entity is like 'Category:Lissomini (Q30015898)': not much check can be done
return true
end
end
return false
end
-- checkSingleEntity() checks that:
-- F) item has an english label
-- if item is a category:
-- 8) item.P373 (commons category) == currentCommonsCategory
-- a) item should not have a P910 (topic's main category)
-- if item is a gallery:
-- 9) item.P935 (commons gallery) == currentCommonsGallery
-- b) item should not have a P301 (category's main topic)
function checkSingleEntity(entities)
local currentPageName = mw.title.getCurrentTitle().text
local error = checkTaxonEntity(entities.entity, '')
if string.isNilOrEmpty(entities.entity:getLabel('en')) then
if _common.isCurrentNamespaceACategory() then
if isCurrentItemClearlyACategory(entities.entity) then
error = addWikidataError(error, 'wikidata item ' .. getWikidataLinkFormat1ForEntity(entities.entity) .. ' has no english label (It could be ' .. _common.suppressDisambiguation(mw.title.getCurrentTitle().prefixedText) .. ').', 'F')
else
error = addWikidataError(error, 'wikidata item ' .. getWikidataLinkFormat1ForEntity(entities.entity) .. ' has no english label (It could be ' .. _common.suppressDisambiguation(mw.title.getCurrentTitle().prefixedText) .. ' or ' .. _common.suppressDisambiguation(mw.title.getCurrentTitle().text) .. ').', 'F')
end
else
error = addWikidataError(error, 'wikidata item ' .. getWikidataLinkFormat1ForEntity(entities.entity) .. ' has no english label (It could be ' .. _common.suppressDisambiguation(mw.title.getCurrentTitle().text) .. ').', 'F')
end
end
if _common.isCurrentNamespaceACategory() then
local entity_P373 = getStringProperties(entities.entity, property_P373_CommonsCategory)
if entity_P373.size == 1 then
if currentPageName ~= entity_P373[1] then
error = addWikidataError(error, 'wikidata item ' .. getWikidataLinkFormat1ForEntity(entities.entity) .. ' property ' .. getWikidataLinkFormat1(property_P373_CommonsCategory) .. " should be '" .. currentPageName .. "' (not " .. getCategoryLink(entity_P373[1]) .. ').', '8')
end
elseif entity_P373.size > 1 then
error = addWikidataError(error, 'wikidata item ' .. getWikidataLinkFormat1ForEntity(entities.entity) .. ' property ' .. getWikidataLinkFormat1(property_P373_CommonsCategory) .. ' should not have multiple values.', '8')
else
error = addWikidataError(error, 'wikidata item ' .. getWikidataLinkFormat1ForEntity(entities.entity) .. ' property ' .. getWikidataLinkFormat1(property_P373_CommonsCategory) .. " should be '" .. currentPageName .. "' (not empty).", '8')
end
local entity_P910 = getStringProperties(entities.entity, property_P910_TopicSMainCategory, true)
if entity_P910.size >= 1 then
error = addWikidataError(error, 'wikidata item ' .. getWikidataLinkFormat1ForEntity(entities.entity) .. ' has ' .. getWikidataLinkFormat1(property_P910_TopicSMainCategory) .. '=' .. getWikidataLinkFormat1ForProperties(entity_P910) .. ' so you should move the link to current wikicommons category from ' .. getWikidataLinkFormat1ForEntity(entities.entity) .. ' to ' .. getWikidataLinkFormat1ForProperties(entity_P910) .. '.', 'A')
end
else
local entity_P935 = getStringProperties(entities.entity, property_P935_CommonsGallery)
if entity_P935.size == 1 then
if currentPageName ~= entity_P935[1] then
error = addWikidataError(error, 'wikidata item ' .. getWikidataLinkFormat1ForEntity(entities.entity) .. ' property ' .. getWikidataLinkFormat1(property_P935_CommonsGallery) .. " should be '" .. currentPageName .. "' (not [[" .. entity_P935[1] .. ']]).', '9')
end
elseif entity_P935.size > 1 then
error = addWikidataError(error, 'wikidata item ' .. getWikidataLinkFormat1ForEntity(entities.entity) .. ' property ' .. getWikidataLinkFormat1(property_P935_CommonsGallery) .. ' should not have multiple values.', '9')
else
-- temporarely disabled (too much work)
--error = addWikidataError(error, 'wikidata item ' .. getWikidataLinkFormat1ForEntity(entities.entity) .. ' property ' .. getWikidataLinkFormat1(property_P935_CommonsGallery) .. " should be '" .. currentPageName .. "' (not empty).", '9')
end
local entity_P301 = getStringProperties(entities.entity, property_P301_CategoryMainTopic, true)
if entity_P301.size >= 1 then
error = addWikidataError(error, 'wikidata item ' .. getWikidataLinkFormat1ForEntity(entities.entity) .. ' property ' .. getWikidataLinkFormat1(property_P301_CategoryMainTopic) .. '=' .. getWikidataLinkFormat1ForProperties(entity_P301) .. ' so you should move the link to current wikicommons gallery from ' .. getWikidataLinkFormat1ForEntity(entities.entity) .. ' to ' .. getWikidataLinkFormat1ForProperties(entity_P301) .. '.', 'B')
end
end
return error
end
-- checkTwoEntities() checks that:
-- 1) entities.entity.P301.size (category's main topic) == 1 (list of exceptions: _catItemsWithMultipleSubjects)
-- 2) entities.entity.P301 (category's main topic) == entities.otherEntity
-- 3) entities.otherEntity.P910.size (topic's main category) > 1
-- 4) entities.entity is included in entities.otherEntity.P910 (topic's main category)
-- 5) entities.entity.P373 (commons category) == currentPageName
-- 6) entities.otherEntity.P373 (commons category) == currentPageName
-- 7) entities.entity.P935 (commons gallery) == entities.otherEntity.P935 (commons gallery) (They can be both empty)
-- F) both items have an english label
function checkTwoEntities(entities)
local error = checkTaxonEntity(entities.otherEntity, '')
if string.isNilOrEmpty(entities.entity:getLabel('en')) then
error = addWikidataError(error, 'wikidata cat item ' .. getWikidataLinkFormat1ForEntity(entities.entity) .. ' has no english label (It could be ' .. _common.suppressDisambiguation(mw.title.getCurrentTitle().prefixedText) .. ').', 'F')
end
if string.isNilOrEmpty(entities.otherEntity:getLabel('en')) then
error = addWikidataError(error, 'wikidata gallery item ' .. getWikidataLinkFormat1ForEntity(entities.otherEntity) .. ' has no english label (It could be ' .. _common.suppressDisambiguation(mw.title.getCurrentTitle().text) .. ').', 'F')
end
local entity_P301 = getStringProperties(entities.entity, property_P301_CategoryMainTopic, true)
if entity_P301.size == 0 then
error = addWikidataError(error, 'wikidata cat item ' .. getWikidataLinkFormat1ForEntity(entities.entity) .. ' property ' .. getWikidataLinkFormat1(property_P301_CategoryMainTopic) .. ' should be ' .. getWikidataLinkFormat1ForEntity(entities.otherEntity) .. ' (not empty).', '1')
elseif entity_P301.size > 1 then
if _catItemsWithMultipleSubjects[entities.entity.id] then
-- These few items have multiple subjects and it seems normal. Sadly we will not be able to extract data for the otherentity as there are 2
else
error = addWikidataError(error, 'wikidata cat item ' .. getWikidataLinkFormat1ForEntity(entities.entity) .. ' has multiple ' .. getWikidataLinkFormat1(property_P301_CategoryMainTopic) .. '.', '1')
end
else
if entity_P301[1] ~= entities.otherEntity.id then
error = addWikidataError(error, 'wikidata cat item ' .. getWikidataLinkFormat1ForEntity(entities.entity) .. ' property ' .. getWikidataLinkFormat1(property_P301_CategoryMainTopic) .. ' should be ' .. getWikidataLinkFormat1ForEntity(entities.otherEntity) .. ' (not ' .. getWikidataLinkFormat1ForProperties(entity_P301) .. '.', '2')
end
end
local otherEntity_P910 = getStringProperties(entities.otherEntity, property_P910_TopicSMainCategory, true)
if otherEntity_P910.size == 0 then
error = addWikidataError(error, 'wikidata gallery item ' .. getWikidataLinkFormat1ForEntity(entities.otherEntity) .. ' property ' .. getWikidataLinkFormat1(property_P910_TopicSMainCategory) .. ' should be ' .. getWikidataLinkFormat1ForEntity(entities.entity) .. ' (not empty).', '3')
else
-- Multiple cat can be link to same gallery: Catégorie:Polygonaceae (Q7416468) & Catégorie:Polygonaceae (noms scientifiques) (Q9085824)
--error = addWikidataError(error, '[[wikidata:' .. entities.otherEntity.id .. '|wikidata gallery item]] has multiple ' .. getWikidataLinkFormat1(property_P910_TopicSMainCategory) .. '.', '2')
if not propertiesContain(otherEntity_P910, entities.entity.id) then
error = addWikidataError(error, 'wikidata gallery item ' .. getWikidataLinkFormat1ForEntity(entities.otherEntity) .. ' property ' .. getWikidataLinkFormat1(property_P910_TopicSMainCategory) .. ' should contain ' .. getWikidataLinkFormat1ForEntity(entities.entity) .. ' (currently ' .. getWikidataLinkFormat1ForProperties(otherEntity_P910) .. ').', '4')
end
end
local currentPageName = mw.title.getCurrentTitle().text
local entity_P373 = getStringProperties(entities.entity, property_P373_CommonsCategory)
if entity_P373.size == 1 then
if currentPageName ~= entity_P373[1] then
error = addWikidataError(error, 'wikidata cat item ' .. getWikidataLinkFormat1ForEntity(entities.entity) .. ' property ' .. getWikidataLinkFormat1(property_P373_CommonsCategory) .. " should be '" .. currentPageName .. "' (not " .. getCategoryLink(entity_P373[1]) .. ').', '5')
end
elseif entity_P373.size > 1 then
error = addWikidataError(error, 'wikidata cat item ' .. getWikidataLinkFormat1ForEntity(entities.entity) .. ' property ' .. getWikidataLinkFormat1(property_P373_CommonsCategory) .. ' should not have multiple values.', '5')
else
error = addWikidataError(error, 'wikidata cat item ' .. getWikidataLinkFormat1ForEntity(entities.entity) .. ' property ' .. getWikidataLinkFormat1(property_P373_CommonsCategory) .. " should be '" .. currentPageName .. "' (not empty).", '5')
end
local otherEntity_P373 = getStringProperties(entities.otherEntity, property_P373_CommonsCategory)
if otherEntity_P373.size == 1 then
if currentPageName ~= otherEntity_P373[1] then
error = addWikidataError(error, 'wikidata gallery item ' .. getWikidataLinkFormat1ForEntity(entities.otherEntity) .. ' property ' .. getWikidataLinkFormat1(property_P373_CommonsCategory) .. " should be '" .. currentPageName .. "' (not " .. getCategoryLink(otherEntity_P373[1]) .. ').', '6')
end
elseif otherEntity_P373.size > 1 then
error = addWikidataError(error, 'wikidata gallery item ' .. getWikidataLinkFormat1ForEntity(entities.otherEntity) .. ' property ' .. getWikidataLinkFormat1(property_P373_CommonsCategory) .. ' should not have multiple values.', '6')
else
error = addWikidataError(error, 'wikidata gallery item ' .. getWikidataLinkFormat1ForEntity(entities.otherEntity) .. ' property ' .. getWikidataLinkFormat1(property_P373_CommonsCategory) .. " should be '" .. currentPageName .. "' (not empty).", '6')
end
if error ~= '' then
-- Excellent test to be activated later
local entity_P935 = getStringProperties(entities.entity, property_P935_CommonsGallery)
local otherEntity_P935 = getStringProperties(entities.otherEntity, property_P935_CommonsGallery)
if entity_P935.size > 1 or otherEntity_P935.size > 1 then
if entity_P935.size > 1 then
error = addWikidataError(error, 'wikidata cat item ' .. getWikidataLinkFormat1ForEntity(entities.entity) .. ' property ' .. getWikidataLinkFormat1(property_P935_CommonsGallery) .. ' should not have multiple values.', '7')
end
if otherEntity_P935.size > 1 then
error = addWikidataError(error, 'wikidata gallery item ' .. getWikidataLinkFormat1ForEntity(entities.otherEntity) .. ' property ' .. getWikidataLinkFormat1(property_P935_CommonsGallery) .. ' should not have multiple values.', '7')
end
elseif entity_P935.size == 1 then
local galleryName = entity_P935[1]
if otherEntity_P935.size == 1 then
if galleryName ~= otherEntity_P935[1] then
error = addWikidataError(error, 'wikidata cat item ' .. getWikidataLinkFormat1ForEntity(entities.entity) .. ' and gallery item ' .. getWikidataLinkFormat1ForEntity(entities.otherEntity)
.. ' should have the same ' .. getWikidataLinkFormat1(property_P935_CommonsGallery) .. ' (not [[' .. galleryName .. ']] and [[' .. otherEntity_P935[1] .. ']]).', '7')
end
elseif otherEntity_P935.size == 0 then
error = addWikidataError(error, 'wikidata gallery item ' .. getWikidataLinkFormat1ForEntity(entities.otherEntity) .. ' property ' .. getWikidataLinkFormat1(property_P935_CommonsGallery) .. " should be '" .. galleryName .. "' (not empty).", '7')
end
else
-- entity_P935.size==0
if otherEntity_P935.size == 1 then
--error = addWikidataError(error, 'wikidata cat item ' .. getWikidataLinkFormat1ForEntity(entities.entity) .. ' property ' .. getWikidataLinkFormat1(property_P935_CommonsGallery) .. " should be '" .. otherEntity_P935[1] .. "' (not empty).", '7')
elseif otherEntity_P935.size == 0 then
-- Both are empty
end
end
end
--local entity_P935 = getProperty(entities.entity, property_sitelinks_commonswiki_title)
return error
end
----------------------------------------------------------------------------------------------------
---------- ScientificName utilities ----------------------------------------------------------------
----------------------------------------------------------------------------------------------------
-- global variable which stores the result of getScientificNamesFromWikidata()
local _scientificNamesFromWikidata = nil
-- getScientificNamesFromWikidata() return a dictionary containing all the scientific names described by wikidata property P225
function getScientificNamesFromWikidata(entities)
if _scientificNamesFromWikidata then
return _scientificNamesFromWikidata
end
_scientificNamesFromWikidata = {}
if not mw.wikibase then
-- wikidata library is not enabled
return _scientificNamesFromWikidata
end
if entities.entity then
local p225values = getStringProperties(entities.entity, property_P225_TaxonName)
for index=1, p225values.size, 1
do
local name = p225values[index]
_scientificNamesFromWikidata[name] = name
end
end
if entities.otherEntity then
local p225values = getStringProperties(entities.otherEntity, property_P225_TaxonName)
for index=1, p225values.size, 1
do
local name = p225values[index]
_scientificNamesFromWikidata[name] = name
end
end
addDebug(nil,'getScientificNamesFromWikidata',tableToString(_scientificNamesFromWikidata,false))
return _scientificNamesFromWikidata
end
-- global variable which stores the result of getScientificNames()
_scientificNames = nil
function addScientificNameAndItsItalic(name)
_scientificNames[name] = name
name = "''" .. name .. "''"
_scientificNames[name] = name
end
function addScientificName(name)
name = string.lower(name)
addScientificNameAndItsItalic(name)
local spacePos = string.find(name, ' × ', 1, true)
if spacePos then
-- Official WikiCommons syntax: 'A × B' => let us add 'A ×B' and 'A X B'
addScientificNameAndItsItalic(string.gsub(name, ' × ', ' ×', 2))
addScientificNameAndItsItalic(string.gsub(name, ' × ', ' x ', 2))
addScientificNameAndItsItalic(string.gsub(name, ' × ', ' ', 2))
else
spacePos = string.find(name, ' ×', 1, true)
if spacePos then
-- Syntax: 'A ×B' => let us add 'A × B' and 'A X B'
addScientificNameAndItsItalic(string.gsub(name, ' ×', ' × ', 2))
addScientificNameAndItsItalic(string.gsub(name, ' ×', ' x ', 2))
addScientificNameAndItsItalic(string.gsub(name, ' ×', ' ', 2))
else
spacePos = string.find(name, ' x ', 1, true)
if spacePos then
-- Syntax: 'A X B' => let us add 'A ×B' and 'A × B'
addScientificNameAndItsItalic(string.gsub(name, ' x ', ' ×', 2))
addScientificNameAndItsItalic(string.gsub(name, ' x ', ' × ', 2))
addScientificNameAndItsItalic(string.gsub(name, ' x ', ' ', 2))
end
end
end
--[=====[ Add GenusName out of species and subspecies.
-- Problem 'Puma concolor' vernacular name is perhaps 'Puma' because 'Puma yagouaroundi' is named Jaguarondi
local spacePos = string.find(name, ' ', 1, true)
if spacePos then
local genusName = mw.text.trim(string.sub(name, 1, spacePos-1))
addScientificNameAndItsItalic(genusName)
end
--]=====]
end
-- getScientificNames() return a dictionary containing all the possible lowercase scientific names of the taxon described out of:
-- * Case 1: current category/gallery name (which is supposed to be a scientific name)
-- * Case 2: {{pagename}}
-- * Case 3: wikidata property P225 (via getScientificNamesFromWikidata())
-- * Case 1bis and 3bis: genusName out of speciesName for monotypic genus (Category:Petrobium arboreum has interwiki named Petrobium)
-- It has a testcase/non-regression module Module:Wikidata4Bio/testcases using testcase_getScientificNames()
function getScientificNames(entities)
if _scientificNames then
return _scientificNames
end
_scientificNames = {}
-- Case 1:
local name = _common.suppressDisambiguation(mw.title.getCurrentTitle().text)
addScientificName(name)
-- Case 2:
addScientificName('{{pagename}}')
-- Case 3:
for key, value in pairs(getScientificNamesFromWikidata(entities)) do
addScientificName(value)
end
addDebug(nil,'getScientificNames',tableToString(_scientificNames,false))
return _scientificNames
end
-- isScientificName(name) return true when name == the category/gallery name, which is supposed to be the scientific name
function isScientificName(entities, name)
if not name then
return false
end
name = mw.text.trim(name)
name = _common.suppressDisambiguation(name)
name = string.lower(name)
getScientificNames(entities)
if _scientificNames[name] then
return true
end
name = string.gsub(name, ' ×', ' ', 2)
if _scientificNames[name] then
return true
end
return false
end
----------------------------------------------------------------------------------------------------
---------- VN utilities ----------------------------------------------------------------------------
----------------------------------------------------------------------------------------------------
-- getVNFromWikidataVN() returns the VernacularNames from wikidata P1843 for a specific lang
function getVNFromWikidataVN(entities, entityToTest, lang)
if not entityToTest then
return nil
end
local claimsP1843 = entityToTest:getBestStatements(property_P1843_TaxonCommonName) -- no need to check if return is nil, worst case it is an empty tab []
local vernacularNames = nil
for _,p1843 in pairs(claimsP1843) do
local value = p1843.mainsnak.datavalue
if value then
value = value.value
if value and value.language == lang then
if isScientificName(entities, value.text) then
-- this gallery/category vernaculare name is in fact a scientific name, so it is a vernaculare name
else
if vernacularNames then
vernacularNames = vernacularNames .. ', ' .. value.text
else
vernacularNames = value.text
end
end
end
end
end
return vernacularNames
end
-- getVNFromWikidataInterwiki() returns the VernacularName from wikidata interwiki for a specific lang
-- (if different from {{PAGENAME}} which is supposed to be the scientific name)
function getVNFromWikidataInterwiki(entities, lang, interwiki)
if not interwiki then
--addDebug(lang,nil,'Interwiki ' .. lang .. '=nil')
return nil
end
interwiki = _common.suppressCategory(interwiki)
interwiki = _common.suppressDisambiguation(interwiki)
if isScientificName(entities, interwiki) then
--addDebug(lang,nil,'Interwiki ' .. lang .. '=' .. interwiki .. ' rejected as it is a sciname')
return nil
end
-- this gallery/category interwiki is not a scientific name, so it is a vernaculare name
--addDebug(lang,nil,'Interwiki ' .. lang .. '=' .. interwiki .. ' is not a sciname')
return interwiki
end
-- getVNFromWikidataLabel() returns the VernacularName from wikidata label for a specific lang
-- (if different from {{PAGENAME}} which is supposed to be the scientific name)
function getVNFromWikidataLabel(entities, lang, entityToTest)
if not entityToTest then
return nil
end
local label = entityToTest:getLabel(lang)
if not label then
return nil
end
label = _common.suppressCategory(label)
label = _common.suppressDisambiguation(label)
if isScientificName(entities, label) then
--addDebug(lang,nil,'Label ' .. lang .. '=' .. label .. ' rejected as it is a sciname')
return nil
end
-- this gallery/category label is not a scientific name, so it is a vernaculare name
return label
end
-- calcVNEntry() puts together the different info coming from VN parameters (lang & default) and wikidata (interwiki & vnFromWikidata)
-- It has a testcase/non-regression module Module:Wikidata4Bio/testcases using testcase_calcVNEntry()
function calcVNEntry(lang, interwiki, otherInterwiki, vnFromWikidata, vnSource, default)
local vnEntry = vnFromWikidata
local vnEntryDescription = vnSource
local interwikiDebug = 'interwiki'
if not interwiki then
interwiki = otherInterwiki
interwikiDebug = 'otherInterwiki'
end
if interwiki then
vnEntry = '[[:' .. lang .. ':' .. interwiki .. '|' .. vnFromWikidata .. ']]'
vnEntryDescription = 'bis: [[' .. interwikiDebug ..'|' .. vnSource .. ']]'
else
vnEntryDescription = ': ' .. vnEntryDescription
end
if default then
if string.contains(vnFromWikidata, default) then -- string.contains(long,small)
-- default is in vnFromWikidata => no need to display default
-- example: default='cat' vnFomInterwiki='common cat'
addDebug(lang,nil,'parameter ' .. lang .. ' rejected as contained in ' .. vnSource .. ', Case1' .. vnEntryDescription)
return vnEntry
else
if string.contains(default, vnFromWikidata) then -- string.contains(long,small)
-- vnFromWikidata is in default => no need to display vnFromWikidata
-- example: default='[[:en:cat|]]' vnFomInterwiki='cat'
if isLink(default) then
addDebug(lang,nil,vnSource .. ' rejected as contained in VNparameter which is a link, Case2: VNparameter')
return default
else
if interwiki then
addDebug(lang,nil,vnSource .. ' rejected as contained in VNparameter, Case3: [[' .. interwikiDebug .. '|parameter]]')
return '[[:' .. lang .. ':' .. interwiki .. '|' .. default .. ']]'
else
-- happens when VN is called with de= + wikidata has no 'de' interwiki + wikidata has a 'de' label + VN|de= contains 'de' label
addDebug(lang,nil,vnSource .. ' rejected as contained in VNparameter + no interwiki, Case4: VNparameter')
return default
end
end
else
addDebug(lang,nil,'Case5' .. vnEntryDescription .. ', VNparameter')
return vnEntry .. ', ' .. default
end
end
else
addDebug(lang,nil,'Case6' .. vnEntryDescription)
return vnEntry
end
end
-- getVernacularNameFromWikidata() returns a vernacular name (often in form of a wiki link) for getVNEntry()
function getVernacularNameFromWikidata(entities, lang, default)
if isScientificName(entities, default) then
addDebug(lang,nil,lang .. ' parameter is a scientificName')
default = nil
end
local interwiki = entities.entity:getSitelink(lang .. 'wiki')
local otherInterwiki = nil
if entities.otherEntity then
otherInterwiki = entities.otherEntity:getSitelink(lang .. 'wiki')
end
-- First try entity.claims.P1843.<index>.mainsnak.datavalue.value.text
local vnFromP1843 = getVNFromWikidataVN(entities, entities.entity, lang)
if vnFromP1843 then
return calcVNEntry(lang, interwiki, otherInterwiki, vnFromP1843, property_P1843_TaxonCommonName, default)
end
local vnFromOtherP1843 = getVNFromWikidataVN(entities, entities.otherEntity, lang)
if vnFromOtherP1843 then
return calcVNEntry(lang, interwiki, otherInterwiki, vnFromOtherP1843, 'otherP1843', default)
end
-- Second try entity.sitelinks.frwiki.title (interwiki)
local vnFomInterwiki = getVNFromWikidataInterwiki(entities, lang, interwiki)
if vnFomInterwiki then
return calcVNEntry(lang, interwiki, otherInterwiki, vnFomInterwiki, 'interwiki', default)
end
local vnFomOtherInterwiki = getVNFromWikidataInterwiki(entities, lang, otherInterwiki)
if vnFomOtherInterwiki then
return calcVNEntry(lang, interwiki, otherInterwiki, vnFomOtherInterwiki, 'otherInterwiki', default)
end
-- Third try entity.labels.<lang>.value
local vnFromLabel = getVNFromWikidataLabel(entities, lang, entities.entity)
if vnFromLabel then
return calcVNEntry(lang, interwiki, otherInterwiki, vnFromLabel, 'label', default)
end
local vnFromOtherLabel = getVNFromWikidataLabel(entities, lang, entities.otherEntity)
if vnFromOtherLabel then
return calcVNEntry(lang, interwiki, otherInterwiki, vnFromOtherLabel, 'otherLabel', default)
end
-- Interwiki and label are not provided or are scientific name
if default and not isLink(default) then
if interwiki then
addDebug(lang,nil,'Case7: [[interwiki|VNparameter]]')
if lang == 'en-us' then
lang = 'en'
end
return '[[:' .. lang .. ':' .. interwiki .. '|' .. default .. ']]'
end
if otherInterwiki then
addDebug(lang,nil,'Case7bis: [[otherInterwiki|VNparameter]]')
if lang == 'en-us' then
lang = 'en'
end
return '[[:' .. lang .. ':' .. otherInterwiki .. '|' .. default .. ']]'
end
end
if default then
addDebug(lang,nil,'Case8: VNparameter')
else
-- no need to to a trace if no interwiki, no label, no VNparameter
end
return default
end
-- getVNEntry() returns HTML for one language in getVN()
function getVNEntry(entities, langCode, langName, additionalLang, default, useWikidata, bold)
local vernacularName
if not entities.entity then
-- This gallery/category has no wikidata element (you are perhaps in the template page)
vernacularName = default
elseif not useWikidata then
-- We are required not to use wikidata
vernacularName = default
elseif additionalLang then
-- additionalLang have no interwiki, no wikidata
if default then
addDebug(langCode,nil,'Case10: VNparameter because additionalLang')
end
vernacularName = default
else
vernacularName = getVernacularNameFromWikidata(entities, langCode, default)
end
if vernacularName and string.len(vernacularName) > 0 then
-- <bdi> is just like <span> but works better when there is mixed bidi text
local entry = "* '''" .. '<bdi lang="' .. langCode .. '">' .. langName
if _debug then
-- In VN/sandbox, let us display the langCode
entry = entry .. " (" .. langCode .. ")"
end
entry = entry .. "</bdi>:"
if not bold then
-- we are already in bold (to display lang), let us close bold to display vernacularName
entry = entry .. "'''"
end
-- Next line was starting with
entry = entry .. ' <bdi class="vernacular" lang="' .. langCode .. '">' .. vernacularName .. "</bdi>"
if bold then
-- close bold if not closed before
entry = entry .. "'''"
end
return entry .. '\n'
else
return nil
end
end
-- findTemplate() return the position of the first call of <templateName> after startPos
function findTemplate(wikicode, templateName, templateForms ,templateFormsDebug, startPos)
local firstPos = nil
for index, templateForm in pairs(templateForms) do
local currentPos = string.find(wikicode,templateForm,startPos,true)
if currentPos then
--addDebug(nil,'findTemplate','Found ' .. templateFormsDebug[index] .. ' at pos ' .. currentPos)
if firstPos then
firstPos = math.min(currentPos,firstPos)
else
firstPos = currentPos
end
end
end
--if firstPos then
--addDebug(nil,'findTemplate','Finaly: Found ' .. templateName .. ' starting from ' .. startPos .. ' at pos ' .. firstPos)
--else
--addDebug(nil,'findTemplate','Finaly: Found no ' .. templateName .. ' starting from ' .. startPos)
--end
return firstPos
end
-- isTemplateCalledOnlyOnce() returns true if there are 0..1 call of <templateName> in the calling page or false if there are 2 or more
function isTemplateCalledOnlyOnce(templateName, templateForms, templateFormsDebug)
local wikicode = mw.title.getCurrentTitle():getContent()
if not wikicode then
-- Called from preview before creation of page
return false
end
if not templateFormsDebug then
templateFormsDebug = templateForms
end
local firstPos = findTemplate(wikicode,templateName,templateForms,templateFormsDebug,1)
if not firstPos then
-- There is not even 1 call
--addDebug(nil,'isTemplateCalledOnlyOnce','no ' .. templateName .. ' found (strange)')
return false
end
firstPos = findTemplate(wikicode,templateName,templateForms,templateFormsDebug,firstPos+3)
if firstPos then
-- There is at least 2 calls
--addDebug(nil,'isTemplateCalledOnlyOnce','multiple ' .. templateName .. ' found => return false')
return false
else
-- Only one call to template
--addDebug(nil,'isTemplateCalledOnlyOnce','single ' .. templateName .. ' found => return true')
return true
end
end
nvForms = {'{{VN|', '{{VN\n', '{{VN ', '{{VN/', '{{VN}'}
nvFormsDebug = {'{{VN|', '{{VN<cr>', '{{VN<space>', '{{VN/', '{{VN}'}
-- isVnCalledOnlyOnce() returns true if there are 0..1 VN in the calling page or false if there are 2.. VN
function isVnCalledOnlyOnce()
return isTemplateCalledOnlyOnce('VN',nvForms,nvFormsDebug)
end
-- isEnglishName(name) tried to distinguish english name from scientific name
function isEnglishName(name)
local lowerName = string.lower(name)
if string.find(lowerName, 'hybrid', 1, true) then
-- like 'Mammal hybrids' or "''Ara''-Hybride" or "''Ara'' hybrids"
return true
elseif string.find(lowerName, ' goat', 1, true) then
-- Dutch white goat
--addDebug(nil,'isEnglishName', default .. ' is in fact an goat')
return true
elseif string.find(lowerName, 'cultivars', 1, true) then
-- like 'Neoreglia cultivars'
return true
elseif string.find(lowerName, 'fossil specimens', 1, true) then
-- like 'Oudenodon fossil specimens'
return true
else
-- Remove ' because next test is about first character.
-- We are not interesting in testing if ' is the first param
-- For example "''Magnolie''" was detected as an english name because M was not the first character
name = string.gsub(name, "'", '')
if string.upperFirstLowerOthers(name) ~= string.upperFirst(name) then
-- like 'Waterlily Dahlias' because of the D in the middle
return true
end
end
return false
end
-- verifyVNParameter() returns nil when a VN parameter is accepted or an error category when the parameter is incorrect
function verifyVNParameter(entities, lang, default)
if not default then
return nil
end
if isScientificName(entities, default) then
-- default is a scientific name
-- addDebug(lang,'verifyVNParameter',default .. ' is a scientific name')
if lang == 'la' then
-- Correct: case where scientific name is a real latin name (Abies)
elseif isEnglishName(default) then
-- Correct: |en=Mammal hybrids or |en=Dutch white goat or |en=Waterlily Dahlias
--addDebug(lang,'verifyVNParameter',default .. ' is in fact an goat')
else
return _common.incorrectBiologyTemplateUsage('VN', 'Parameter ' .. lang .. ' uses scientific name', 'VN')
end
end
if string.find(default, "'''", 1, true) then
-- default contains bold
return _common.incorrectBiologyTemplateUsage('VN', 'Parameter ' .. lang .. ' uses bold', 'VN')
end
if string.startsWith(default, "''") then
-- default starts with italic
if string.find(default, "'':", 1, true) then
-- Correct: |ja=''Vespa mandarinia japonica'': XXX
-- Incorrect: |en='''[[:en:whatever|]]'''
elseif isEnglishName(default) then
-- Correct: |ja=''Oudenodon'' fossil specimens |de=''Neoreglia'' cultivars |de=''Ara''-Hybride |en=''Ara'' hybrids
else
-- Incorrect: |en=''Vespa mandarinia japonica''
return _common.incorrectBiologyTemplateUsage('VN', 'Parameter ' .. lang .. ' uses italic', 'VN')
end
end
return nil
end
local _excludedWikiProjects = {
wikidatawiki = 1,
commonswiki = 1,
specieswiki = 1,
metawiki = 1,
mediawikiwiki = 1
}
-- calcAdditionalInterwiki() returns interwikis from specified entity (useWikidata=Qxxx) or otherEntity
function calcAdditionalInterwiki(entities)
local entitySource = entities.otherEntity
local entityAutomaticInterwiki = entities.entity
if entities.entitySpecified then
entitySource = entities.entity -- specified by user
entityAutomaticInterwiki = mw.wikibase.getEntityObject() -- not used by VN but used by page for automaticInterwiki
end
local additionalInterwiki = ''
if entitySource and entitySource.sitelinks then
for i, j in pairs(entitySource.sitelinks) do
local lang = mw.ustring.sub( j.site, 1, -5) -- split j.site into language and project parts
local proj = mw.ustring.sub( j.site, -4)
if not _excludedWikiProjects[j.site] and proj == 'wiki' then -- excludes sites on the list as well as Wikisource, Wikiquote, Wikivoyage etc
if (entityAutomaticInterwiki and (not entityAutomaticInterwiki.sitelinks or not entityAutomaticInterwiki.sitelinks[j.site])) or not entityAutomaticInterwiki then -- excludes interwiki to projects that already have sitelinks in the present page
lang = mw.ustring.gsub(lang, '_','-')
additionalInterwiki = additionalInterwiki .. '[[' .. lang .. ':' .. j.title .. ']]' -- put together a interwiki-link to other projects
end
end
end
if additionalInterwiki ~= '' then
additionalInterwiki = additionalInterwiki .. ' [[Category:Interwiki from wikidata]] '
end
end
addDebug(nil, 'calcAdditionalInterwiki', 'AdditionalInterwiki=' .. additionalInterwiki)
return additionalInterwiki
end
-- Used by {{VN}}
function getVN(args)
-- Calc entity & otherEntity & useWikidata & useWikidataIsCalculated
local entities = retrieveEntities(args)
--addDebug(nil, 'getVN', mw.text.jsonEncode(entities))
-- Access parameters
local nocat = string.isTrue(args.nocat)
local userLangCode = args.lang --frame:preprocess("{{int:Lang}}") or mw.language.getContentLanguage():getCode()
local default = string.trimOrNullify(args[userLangCode])
local provideNamesInCommonsIsPossible = not string.contains(args.provideNamesInCommons, "impossible")
--addDebug(nil,'getVN','userLangCode=' .. tostring(userLangCode))
-- Fill _languageNamesByCode, _additionalLanguageNamesByCode and _languageCodes
calcLanguages(userLangCode)
-- Calc first entry for user's language
local vn = ''
local additionalLang = false
local userlangName = _languageNamesByCode[userLangCode]
if userlangName then
local vnEntry = getVNEntry(entities, userLangCode, userlangName, additionalLang, default, entities.useWikidata, true)
if vnEntry then
vn = vnEntry
end
end
-- Calc entries for all other languages
local additionalCategory = ''
for _, langCode in pairs(_languageCodes) do
default = args[langCode]
if not nocat then
local verification = verifyVNParameter(entities, langCode, default)
if verification then
additionalCategory = verification
end
end
if langCode == userLangCode then
-- Already displayed in bold
else
local langName = _languageNamesByCode[langCode]
additionalLang = false
if not langName then
langName = _additionalLanguageNamesByCode[langCode]
additionalLang = true
end
vnEntry = getVNEntry(entities, langCode, langName, additionalLang, default, entities.useWikidata, false)
if vnEntry then
vn = vn .. vnEntry
end
end
end
-- Display error when wikidata values are incorrect
additionalCategory = additionalCategory .. checkEntities(entities)
-- Add Category:Biology_categories_without_double_wikidata_item
if entities.entity and not entities.otherEntity then
-- Commons category or gallery has a single wikidata item
local name = _common.suppressDisambiguation(mw.title.getCurrentTitle().text)
local isSpecies = string.find(name, ' ', 1, true)
if entities.entitySpecified then
addDebug(nil,'getVN','entitySpecified + isSpecies=' .. tostring(isSpecies))
if isSpecies then
additionalCategory = additionalCategory .. '[[Category:Biology_pages_with_wikidata_item_specified_in_VN]]'
else
additionalCategory = additionalCategory .. '[[Category:Biology_pages_with_wikidata_item_specified_in_VN| ]]'
end
else
if _common.isCurrentNamespaceACategory() then
-- Commons category has a single wikidata item
if isSpecies then
-- Species Category without gallery may have only one wikidata item (the taxon)
else
additionalCategory = additionalCategory .. '[[Category:Biology_categories_without_double_wikidata_item]]'
end
end
end
elseif entities.entity and entities.otherEntity then
-- Commons category has a double wikidata item
additionalCategory = additionalCategory .. '[[Category:Biology_categories_with_double_wikidata_item]]'
end
-- Add small information about wikidata
if string.len(vn) == 0 then
if provideNamesInCommonsIsPossible then
vn = 'No common name has yet been provided in this ' .. _common.getCurrentNamespace()
if not mw.wikibase then
-- Case 1: wikidata library is not enabled
vn = vn .. '. (Soon common names will be retrieved from wikidata)'
elseif args.useWikidata and string.startsWith(args.useWikidata,'Q') then
if entities.entity then
-- Case 3
vn = vn .. ' nor in ' .. getWikidataLinkFormat2(entities.entity)
else
-- Case 2
vn = vn .. incorrectBiologyTemplateUsage('VN', 'Wikidata ' .. args.useWikidata .. ' is not accessible', 'VN')
end
elseif entities.useWikidata then
-- Case 4.true, 5.true: args.useWikidata=nil + no problem
-- Case 6.true: args.useWikidata=1
if entities.entity then
vn = vn .. ' nor in ' .. getWikidataLinkFormat2(entities.entity)
if entities.otherEntity then
vn = vn .. ' nor in ' .. getWikidataLinkFormat2(entities.otherEntity)
end
else
-- Impossible ?
vn = vn .. ' and no [https://www.wikidata.org/w/index.php?button=&title=Special%3ASearch&limit=500&search=' .. string.gsub(mw.title.getCurrentTitle().text,' ','+') .. ' wikidata item] is associated with it'
end
else
if entities.useWikidataIsCalculated then
-- Case 4.false, 5.false: args.useWikidata=nil + (sciname!=<category or gallery name> or multiple VN in page) => don't display additional info in all VN
else
if entities.entity then
-- Case 6.false
vn = vn .. '. <small>(You could activate the search in ' .. getWikidataLinkFormat2(entities.entity) .. ' by adding useWikidata=1 inside {{VN}} )</small>'
else
if string.isNilOrEmpty(args.useWikidata) or string.isTrue(args.useWikidata) then
-- Case 7.nil, 7.true
vn = vn .. '. <small>(No [https://www.wikidata.org/w/index.php?button=&title=Special%3ASearch&limit=500&search=' .. string.gsub(mw.title.getCurrentTitle().text,' ','+') .. ' wikidata item] is associated with this ' .. _common.getCurrentNamespace() .. ')</small>'
else
-- Case 7.false => it is normal that VN is empty, associating with wikidata will change nothing
end
end
end
end
end
else
if not mw.wikibase then
-- Case 1: wikidata library is not enabled
elseif not entities.entity then
if string.startsWith(args.useWikidata,'Q') or string.isNilOrEmpty(args.useWikidata) or string.isTrue(args.useWikidata) then
-- Case 2, Case 7.nil, 7.true
vn = vn .. ' <small>(Note: no [https://www.wikidata.org/w/index.php?button=&title=Special%3ASearch&search=' .. string.gsub(mw.title.getCurrentTitle().text,' ','+') .. ' wikidata item] is associated with this ' .. _common.getCurrentNamespace() .. ')</small>'
else
-- Case 7.false => it is normal that VN is empty, associating with wikidata will change nothing
end
end
end
-- if entities.entity then
-- Uncomment following line to dump all properties
-- vn = vn .. '<BR/>' .. 'entities.entity=' .. mw.text.jsonEncode(entities.entity)
-- if entities.otherEntity then
-- Uncomment following line when 'Access to arbitrary items' is allowed
-- vn = vn .. '<BR/>' .. 'entities.otherEntity.claims=' .. mw.text.jsonEncode(entities.otherEntity.claims)
-- end
-- end
return vn .. additionalCategory .. calcAdditionalInterwiki(entities)
end
-- Used by {{VN}} to display '[modify wikidata]'
function getVNTitle(frame, args)
-- Calc entity & otherEntity & useWikidata & useWikidataIsCalculated
local entities = retrieveEntities(args)
if not entities.useWikidata then
return ''
end
if not entities.entity then
return ''
end
local userLangCode = args.lang --frame:preprocess("{{int:Lang}}") or mw.language.getContentLanguage():getCode()
--addDebug(nil,'getVNTitle','userLangCode=' .. tostring(userLangCode))
local userLang = mw.language.new(userLangCode)
--addDebug(nil, 'getVNTitle', 'userLang:Code=' .. userLang:getCode())
local langEdit = userLang:lc(frame:preprocess("{{int:edit}}"))
--addDebug(nil, 'getVNTitle', 'langEdit=' .. langEdit)
local label = entities.entity:getLabel()
if label then
label = '\'' .. label .. '\''
else
label = entities.entity.id
end
local wikidataLabel = langEdit .. ' wikidata ' .. label
local wikidataLabelOther = ''
local wikidataImageLabel = 'Wikidata ' .. label .. ' linked to current ' .. _common.getCurrentNamespace()
local wikidataImageLabelOther = ''
if entities.entitySpecified then
wikidataLabel = langEdit .. ' specified wikidata ' .. label
end
if entities.otherEntity then
local otherLabel = entities.otherEntity:getLabel()
if otherLabel then
otherLabel = '\'' .. otherLabel .. '\''
else
otherLabel = entities.otherEntity.id
end
-- [modifier wikidata 'Category:Aa' linked to current category] [modifier wikidata 'Abacoleptus' main topic of 'Category:Aa']
wikidataLabel = wikidataLabel .. ' linked to current ' .. _common.getCurrentNamespace()
wikidataLabelOther = langEdit .. ' wikidata ' .. otherLabel .. ' main topic of ' .. label
wikidataImageLabelOther = 'Wikidata item ' .. otherLabel .. ' main topic (P301) of ' .. label
else
-- [modifier wikidata 'Abacoleptus']
end
local toReturn = ' <small>' .. mw.text.nowiki('[') .. '[[wikidata:' .. entities.entity.id .. '|' .. wikidataLabel .. ']]]</small>'
toReturn = toReturn .. ' [[Image:Wikidata-logo-without-paddings.svg|20px|' .. wikidataImageLabel .. '|link=https://www.wikidata.org/wiki/' .. entities.entity.id .. ']]'
if entities.otherEntity then
toReturn = toReturn .. ' <small>' .. mw.text.nowiki('[') .. '[[wikidata:' .. entities.otherEntity.id .. '|' .. wikidataLabelOther .. ']]]</small>'
toReturn = toReturn .. ' [[Image:Wikidata-logo-without-paddings.svg|20px|' .. wikidataImageLabelOther .. '|link=https://www.wikidata.org/wiki/' .. entities.otherEntity.id .. ']]'
end
return toReturn
end
local _acceptedParameters = {
lang='lang', -- provided by Template:VN and Template:VNNoDisplay
provideNamesInCommons='provideNamesInCommons', -- provided only by Template:VNNoDisplay
sciname='sciname',
useWikidata='useWikidata',
nocat='nocat',
collapsed='collapsed',
edit='edit' -- managed directly by Template:VN
}
function detectBadParameters(args)
-- Fill _languageNamesByCode and _languageCodes
calcLanguages('en')
local toRet = ''
for optionKey, optionValue in pairs(args) do
if type(optionKey) == 'string' then
-- normal argument pair: en=EnglishName, lang=fr
if not _languageNamesByCode[optionKey] and not _additionalLanguageNamesByCode[optionKey] and not _acceptedParameters[optionKey] then
if toRet ~= '' then
toRet = toRet .. ', '
end
toRet = toRet .. '"' .. optionKey .. '"'
end
else
-- incorrect argument pair: 1=strangeParameter
if toRet ~= '' then
toRet = toRet .. ', '
end
toRet = toRet .. '"' .. optionValue .. '"'
end
end
if toRet == '' then
return toRet
else
return _common.incorrectBiologyTemplateUsage('VN', 'Incorrect parameter(s) ' .. toRet, 'VN')
end
end
-- returns "sciname" out od "''sciname'' author"
function extractSciNameOutOfDecoratedSciName(decoratedSciName)
-- It has a testcase/non-regression module Module:Wikidata4Bio/testcases using testcase_extractSciNameOutOfDecoratedSciName()
local startPos = string.find(decoratedSciName, "''", 1, true)
if startPos then
--addDebug(nil, 'extractSciNameOutOfDecoratedSciName', 'startPos= ' .. startPos)
while string.sub(decoratedSciName, startPos, startPos) == "'" do
startPos = startPos + 1
--addDebug(nil, 'extractSciNameOutOfDecoratedSciName', 'incr startPos= ' .. startPos)
end
local endPos = string.find(decoratedSciName, "''", startPos, true)
if endPos then
--addDebug(nil, 'extractSciNameOutOfDecoratedSciName', 'endPos= ' .. endPos)
local sciName = string.sub(decoratedSciName, startPos, endPos-1)
sciName = mw.text.trim(sciName)
return sciName
end
end
return nil
end
function calcSiteUrl(urlPrefix, siteIdStr, urlPostfix)
if urlPrefix then
siteIdStr = mw.text.trim(siteIdStr)
urlPrefix = mw.text.trim(urlPrefix)
if urlPostfix then
urlPostfix = mw.text.trim(urlPostfix)
else
urlPostfix = ''
end
if urlPrefix == 'firstParamIsAnUrl' then
return '[' .. siteIdStr .. urlPostfix .. ' this url]'
else
return '[' .. urlPrefix .. siteIdStr .. urlPostfix .. ' ' .. siteIdStr .. ']'
end
else
return siteIdStr
end
end
-- Compares the ITIS identifior provided to {{ITIS}} with the id stored in wikidata by property p815
-- If there is a difference, the returned string displays an error + adds category 'Pages with incorrect biology template usage'
-- See https://www.wikidata.org/wiki/Wikidata:Taxonomy_task_force for sitePropertyId
function compareSiteIdWithWikidata(frame, sitePropertyId, siteName, templateName, templateForms, urlPrefix, urlPostfix)
if not _common.isCurrentNamespaceACategoryOrAGallery() then
-- We do checks only in galleries and categories (When you look at Template, no param is provided
return ''
end
local commonsSiteId = string.trimOrNullify(frame.args['1'])
if not commonsSiteId then
return _common.incorrectBiologyTemplateUsage('Link', 'no ' .. siteName .. ' id has been provided.', templateName)
end
commonsSiteId = string.gsub(commonsSiteId, '=', '=')
local commonsSciName = string.trimOrNullify(frame.args['sciname'])
if not commonsSciName then
return _common.incorrectBiologyTemplateUsage('Link', 'no ' .. siteName .. ' sciName has been provided.', templateName)
end
local commonsDecoratedSciName = string.trimOrNullify(frame.args['decoratedSciname'])
local validity = string.trimOrNullify(frame.args['validity'])
if validity then
validity = string.lower(validity)
if validity == 'nv' then
-- taxon is invalid => no check is possible
return ''
else
-- validity parameter has an incorrect value
return _common.incorrectBiologyTemplateUsage('Link', 'Incorrect parameter validity.', templateName)
end
end
local checks = string.trimOrNullify(frame.args['checks'])
if checks and string.lower(checks) == 'no' then
-- checks have been disabled
return ''
end
local entities = retrieveEntitiesSimple(nil)
if not entities.entity then
-- Wikidata library is not enabled => no check is possible
-- Or there is no link to wikidata => no check is possible
return ''
end
getScientificNamesFromWikidata(entities)
if not tableIsEmpty(_scientificNamesFromWikidata) then
if commonsSciName ~= 'none' then
if not _scientificNamesFromWikidata[commonsSciName] then
-- ITIS sciname and wikidata sciname are different => cannot compare ids
addDebug(nil, 'compareSiteIdWithWikidata', 'Check Disabled: Different scientific name between www.wikidata.org/wiki/' .. entities.entity.id .. ' (' .. tableToString(_scientificNamesFromWikidata,false) .. ') and wikicommons (' .. commonsSciName .. ')')
return ''
--return ' <small>(Warning: different scientific name between [[wikidata:' .. entities.entity.id .. '|wikidata]] (' .. tableToString(_scientificNamesFromWikidata,false) .. ') and wikicommons (' .. commonsSciName .. '))</small>[[Category:Pages with biology property different than on Wikidata|' .. siteName .. ']]'
end
elseif commonsDecoratedSciName then
local sciName = extractSciNameOutOfDecoratedSciName(commonsDecoratedSciName)
if sciName then
if not listContainsExactString(sciName, _scientificNamesFromWikidata) then
-- wikidata sciname not included in ITIS sciname => cannot compare ids
addDebug(nil, 'compareSiteIdWithWikidata', 'Check Disabled: Different exact scientific name between www.wikidata.org/wiki/' .. entities.entity.id .. ' (' .. tableToString(_scientificNamesFromWikidata,false) .. ') and wikicommons (' .. sciName .. ')')
return ''
else
addDebug(nil, 'compareSiteIdWithWikidata', 'sciname (' .. sciName ..') extracted from decoratedScientificName (' .. commonsDecoratedSciName .. ') is part of wikidata scinames ' .. tableToString(_scientificNamesFromWikidata,false))
end
else
if not stringContainsAnItemOfList(commonsDecoratedSciName, _scientificNamesFromWikidata) then
-- wikidata sciname not included in ITIS decoratedSciname => cannot compare ids
addDebug(nil, 'compareSiteIdWithWikidata', 'Check Disabled: Different scientific name between www.wikidata.org/wiki/' .. entities.entity.id .. ' (' .. tableToString(_scientificNamesFromWikidata,false) .. ') and wikicommons (' .. commonsDecoratedSciName .. ')')
return ''
else
addDebug(nil, 'compareSiteIdWithWikidata', 'decoratedScientificName (' .. commonsDecoratedSciName .. ') is contained in wikidata scinames ' .. tableToString(_scientificNamesFromWikidata,false))
end
end
end
end
if not isTemplateCalledOnlyOnce(templateName,templateForms,templateForms) then
-- If template is called multiple times...
addDebug(nil, 'compareSiteIdWithWikidata', 'Check Disabled: Template ' .. templateName .. ' is called multiple times')
return ''
end
local problematicEntity = nil
local siteIdvalues = getStringProperties(entities.entity, sitePropertyId)
if siteIdvalues.size > 0 then
addDebug(nil, 'compareSiteIdWithWikidata', 'Property ' .. sitePropertyId .. ' found in entity: ' .. propertiesToString(siteIdvalues))
problematicEntity = entities.entity
else
-- Property (like p815 for 'identifiant ITIS') is not defined in wikidata => no check possible
if entities.otherEntity then
siteIdvalues = getStringProperties(entities.otherEntity, sitePropertyId)
if siteIdvalues.size == 0 then
-- Property (like p815 for 'identifiant ITIS') is not defined in wikidata => no check possible
addDebug(nil, 'compareSiteIdWithWikidata', 'Check Impossible: https://www.wikidata.org/wiki/' .. entities.entity.id .. ' and https://www.wikidata.org/wiki/' .. entities.otherEntity.id .. ' do not contain the property ' .. sitePropertyId)
return ''
end
addDebug(nil, 'compareSiteIdWithWikidata', 'Property ' .. sitePropertyId .. ' found in otherEntity: ' .. propertiesToString(siteIdvalues))
problematicEntity = entities.otherEntity
else
addDebug(nil, 'compareSiteIdWithWikidata', 'Check Impossible: https://www.wikidata.org/wiki/' .. entities.entity.id .. ' does not contain the property ' .. sitePropertyId)
return ''
end
end
if siteIdvalues[1] == commonsSiteId then
-- Both identifier are equal: perfect
addDebug(nil, 'compareSiteIdWithWikidata', 'Everything Perfect: ' .. siteName .. ' id is ' .. commonsSiteId .. ' on wikicommons and wikidata')
return ''
end
-- Both identifier are different
if siteIdvalues.size == 1 then
-- Property (like p815 for 'identifiant ITIS') has only 1 value in wikidata
elseif siteIdvalues[2] == commonsSiteId then
-- Both identifier are equal: perfect
addDebug(nil, 'compareSiteIdWithWikidata', 'Everything Perfect: ' .. siteName .. ' id is ' .. commonsSiteId .. ' on wikicommons and wikidata (second value)')
return ''
end
addDebug(nil, 'compareSiteIdWithWikidata', 'urlPrefix=' .. tostring(urlPrefix))
local wikidataSiteIdStr = calcSiteUrl(urlPrefix, siteIdvalues[1], urlPostfix)
if siteIdvalues.size > 1 then
wikidataSiteIdStr = wikidataSiteIdStr .. ', ' .. calcSiteUrl(urlPrefix, siteIdvalues[2], urlPostfix)
end
return ' <small>(Warning: different ' .. siteName .. ' Id between ' .. getWikidataLinkFormat2(problematicEntity) .. ' (' .. wikidataSiteIdStr .. ') and wikicommons (' .. calcSiteUrl(urlPrefix, commonsSiteId, urlPostfix) .. '))</small>[[Category:Pages with biology property different than on Wikidata|' .. siteName .. ']]'
end
function getAdditionalCategoryAboutWikidata()
if not _common.isCurrentNamespaceACategoryOrAGallery() then
-- We do checks only in galleries and categories (When you look at Template, no param is provided
return ''
end
if not mw.wikibase then
-- Wikidata library is not enabled => no check is possible
return ''
end
local wikicode = mw.title.getCurrentTitle():getContent()
if wikicode then
if string.find(wikicode,'redirect|',1,true) then
-- Category contains a redirect => we are not interested
return ''
end
if string.find(wikicode,'#REDIRECT',1,true) then
-- Gallery contains a redirect => we are not interested
return ''
end
else
-- Called from preview before creation of page
end
local entity = mw.wikibase.getEntityObject()
if entity then
return '[[Category:Biology pages with wikidata link]]'
else
if _common.pageNameCorrespondToSpeciesOrInfraspecies(mw.title.getCurrentTitle().text) then
return '[[Category:Infraspecies pages without wikidata link]]'
else
return '[[Category:Biology pages without wikidata link]]'
end
end
end
-- Used by {{#invoke:Wikidata4Bio/sandbox|test}} to discover Lua ;-)
function test(frame,args)
local entity = mw.wikibase.getEntity();
return getDebug()
end
----------------------------------------------------------------------------------------------------
---------- PUBLIC FUNCTIONS ------------------------------------------------------------------------
----------------------------------------------------------------------------------------------------
local p = {}
local getArgs = require('Module:Arguments').getArgs
-- Simply add {{#invoke:Wikidata4Bio|dumpWikidata}} to a page
function p.dumpWikidata(frame)
local entity = mw.wikibase.getEntity()
if entity then
return 'entity=' .. mw.text.jsonEncode(entity)
else
return 'not associated with wikidata'
end
end
function p.getVN(frame)
-- getArgs() will concatenate frame.args (lang=) with Template:VN arguments (useWikidata=, explain=, sciname= and all the <lang>=)
local args = getArgs(frame)
-- addDebug(nil, 'p.getVN', tableToString(args,true))
-- addDebug(nil, 'p.getVN', detectBadParameters(args))
local vn = getVN(args)
-- Now add debug traces if activated
if _debug then
vn = vn .. getDebug()
end
return vn .. getAdditionalCategoryAboutWikidata() .. detectBadParameters(args)
end
function p.getVNTitle(frame)
local args = getArgs(frame)
local title = getVNTitle(frame, args)
if _debug then
title = title .. getDebug()
end
return title
end
-- Used by {{VN/doc}}
function p.getLanguagesManagedByVN(frame)
local langs = getLanguagesManagedByVN(frame.args)
return langs
end
-- Used by {{Avibase}} (only species) with sciname
function p.compareAvibaseIdWithWikidata(frame)
-- frame, sitePropertyId, siteName, templateName, templateForms
return compareSiteIdWithWikidata(frame, 'P2026', 'Avibase', 'Avibase', {'{{Avibase'}, 'http://avibase.bsc-eoc.org/species.jsp?lang=EN&avibaseid=')
.. getDebug() .. getAdditionalCategoryAboutWikidata()
end
-- Used by {{BioLib}} with |sciname=none|decoratedSciname={{{2|{{PAGENAME}}}}}
function p.compareBioLibIdWithWikidata(frame)
-- frame, sitePropertyId, siteName, templateName, templateForms
return compareSiteIdWithWikidata(frame, 'P838', 'BioLib', 'BioLib', {'{{BioLib'}, 'http://www.biolib.cz/en/taxon/id')
.. getDebug() .. getAdditionalCategoryAboutWikidata()
end
-- Used by {{EOL}} with |sciname=none|decoratedSciname={{{2|{{PAGENAME}}}}}
function p.compareEOLIdWithWikidata(frame)
return compareSiteIdWithWikidata(frame, 'P830', 'EOL', 'EOL', {'{{EOL'}, 'http://www.eol.org/pages/')
.. getDebug() .. getAdditionalCategoryAboutWikidata()
end
-- Used by {{Faunaeur}} with |sciname=none|decoratedSciname={{{2|{{PAGENAME}}}}}
function p.compareFaunaeurIdWithWikidata(frame)
return compareSiteIdWithWikidata(frame, 'P1895', 'Faunaeur', 'Faunaeur', {'{{Faunaeur'}, 'http://www.faunaeur.org/full_results.php?id=')
.. getDebug() .. getAdditionalCategoryAboutWikidata()
end
-- Used by {{FishBase species}} with sciname
function p.compareFishBaseSpeciesIdWithWikidata(frame)
return compareSiteIdWithWikidata(frame, 'P938', 'FishBase', 'FishBase', {'{{FishBase'}, 'http://www.fishbase.org/Summary/speciesSummary.php?id=')
.. getDebug() .. getAdditionalCategoryAboutWikidata()
end
-- Used by {{Fungorum species}}, {{Fungorum genus}}, {{Fungorum family}} and {{Fungorum taxon}} with sciname
function p.compareFungorumIdWithWikidata(frame)
return compareSiteIdWithWikidata(frame, 'P1391', 'Fungorum', 'Fungorum', {'{{Fungorum'}, frame.args['urlPrefix'])
.. getDebug() .. getAdditionalCategoryAboutWikidata()
end
-- Used by {{GBIF}} with |sciname=none|decoratedSciname={{{2|{{PAGENAME}}}}}
function p.compareGBIFIdWithWikidata(frame)
return compareSiteIdWithWikidata(frame, 'P846', 'GBIF', 'GBIF', {'{{GBIF'}, 'http://www.gbif.org/species/')
.. getDebug() .. getAdditionalCategoryAboutWikidata()
end
-- Used by {{GRIN family}}, {{GRIN subfamily}}, {{GRIN tribe}} and {{GRIN subtribe}} with sciname
function p.compareGRINURLWithWikidata(frame)
local error = ''
local rank = string.trimOrNullify(frame.args['rank'])
if rank then
local commonsSciName = string.trimOrNullify(frame.args['sciname'])
if commonsSciName then
if rank == 'family' then
if not string.endsWith(commonsSciName,'ceae') then
error = _common.incorrectBiologyTemplateUsage('GRIN family', 'GRIN family should be used on families only (ending with "ceae"). Please Use GRIN subfamily, GRIN tribe, GRIN subtribe, GRIN genus or GRIN species', 'GRIN family')
end
elseif rank == 'subfamily' then
if not string.endsWith(commonsSciName,'oideae') then
error = _common.incorrectBiologyTemplateUsage('GRIN subfamily', 'GRIN subfamily should be used on subfamilies only (ending with "oideae"). Please Use GRIN family, GRIN tribe, GRIN subtribe, GRIN genus or GRIN species', 'GRIN subfamily')
end
elseif rank == 'tribe' then
if not string.endsWith(commonsSciName,'eae') then
error = _common.incorrectBiologyTemplateUsage('GRIN tribe', 'GRIN tribe should be used on tribes only (ending with "eae"). Please Use GRIN family, GRIN subfamily, GRIN subtribe, GRIN genus or GRIN species', 'GRIN tribe')
end
elseif rank == 'subtribe' then
if not string.endsWith(commonsSciName,'inae') then
error = _common.incorrectBiologyTemplateUsage('GRIN subtribe', 'GRIN subtribe should be used on subtribes only (ending with "inae"). Please Use GRIN family, GRIN subfamily, GRIN tribe, GRIN genus or GRIN species', 'GRIN subtribe')
end
end
-- else: Strange case treated by compareSiteIdWithWikidata()
end
end
return compareSiteIdWithWikidata(frame, 'P1421', 'GRIN', 'GRIN', {'{{GRIN'}, 'firstParamIsAnUrl')
.. error
.. getDebug() .. getAdditionalCategoryAboutWikidata()
end
-- Used by {{Gymnosperm Database}} with sciname
function p.compareGymnospermDatabaseIdWithWikidata(frame)
return compareSiteIdWithWikidata(frame, 'P1940', 'Gymnosperm Database', 'Gymnosperm Database', {'{{Gymnosperm Database'}, 'http://conifers.org/', '.php')
.. getDebug() .. getAdditionalCategoryAboutWikidata()
end
-- NOT used by {{IPNI}}, which should be renamed {{IPNI search}} because it does not use an id
function p.compareIPNIIdWithWikidata(frame)
return compareSiteIdWithWikidata(frame, 'P961', 'IPNI', 'IPNI', {'{{IPNI'}, 'http://www.ipni.org/ipni/simplePlantNameSearch.do?find_wholeName=')
.. getDebug() .. getAdditionalCategoryAboutWikidata()
end
-- Used by {{ITIS}} with |sciname=none|decoratedSciname={{{2|{{PAGENAME}}}}}
function p.compareITISIdWithWikidata(frame)
return compareSiteIdWithWikidata(frame, 'P815', 'ITIS', 'ITIS', {'{{ITIS'}, 'http://www.itis.gov/servlet/SingleRpt/SingleRpt?search_topic=TSN&search_value=')
.. getDebug() .. getAdditionalCategoryAboutWikidata()
end
-- Used by {{IUCN}} (only species) with sciname
function p.compareIUCNIdWithWikidata(frame)
return compareSiteIdWithWikidata(frame, 'P627', 'IUCN', 'IUCN', {'{{IUCN'}, 'http://www.iucnredlist.org/apps/redlist/details/')
.. getDebug() .. getAdditionalCategoryAboutWikidata()
end
-- Used by {{LPSN}} with |sciname=none|decoratedSciname={{{2|{{PAGENAME}}}}}
function p.compareLPSNUrlWithWikidata(frame)
return compareSiteIdWithWikidata(frame, 'P1991', 'LPSN', 'LPSN', {'{{LPSN'}, 'firstParamIsAnUrl')
.. getDebug() .. getAdditionalCategoryAboutWikidata()
end
-- Used by {{MSW}} with |sciname=none|decoratedSciname={{{2|{{PAGENAME}}}}}
function p.compareMSWIdWithWikidata(frame)
return compareSiteIdWithWikidata(frame, 'P959', 'MSW', 'MSW', {'{{MSW'}, 'https://www.departments.bucknell.edu/biology/resources/msw3/browse.asp?s=y&id=')
.. getDebug() .. getAdditionalCategoryAboutWikidata()
end
-- Used by {{MycoBank}} with sciname
function p.compareMycoBankIdWithWikidata(frame)
return compareSiteIdWithWikidata(frame, 'P962', 'MycoBank', 'MycoBank', {'{{MycoBank'}, 'http://www.mycobank.org/MycoTaxo.aspx?Link=T&Rec=')
.. getDebug() .. getAdditionalCategoryAboutWikidata()
end
-- Used by {{NCBI}} with |sciname=none|decoratedSciname={{{2|{{PAGENAME}}}}}
function p.compareNCBIIdWithWikidata(frame)
return compareSiteIdWithWikidata(frame, 'P685', 'NCBI', 'NCBI', {'{{NCBI'}, 'https://www.ncbi.nlm.nih.gov/Taxonomy/Browser/wwwtax.cgi?lin=s&p=has_linkout&id=')
.. getDebug() .. getAdditionalCategoryAboutWikidata()
end
-- Used by {{NRCS Plants}} with sciname
function p.compareNRCSPlantsIdWithWikidata(frame)
return compareSiteIdWithWikidata(frame, 'P1772', 'NRCS Plants', 'NRCS Plants', {'{{NRCS Plants'}, 'http://plants.usda.gov/core/profile?symbol=')
.. getDebug() .. getAdditionalCategoryAboutWikidata()
end
-- Used by {{ThePlantList species}} with sciname
function p.compareThePlantListIdWithWikidata(frame)
return compareSiteIdWithWikidata(frame, 'P1070', 'ThePlantList', 'ThePlantList species', {'{{ThePlantList species'}, 'http://www.theplantlist.org/tpl1.1/record/')
.. getDebug() .. getAdditionalCategoryAboutWikidata()
end
-- Used by {{TPDB}} with |sciname=none|decoratedSciname={{{2|{{PAGENAME}}}}}
function p.compareTPDBIdWithWikidata(frame)
return compareSiteIdWithWikidata(frame, 'P842', 'TPDB', 'TPDB', {'{{TPDB'}, 'http://fossilworks.org/bridge.pl?action=taxonInfo&is_real_user=1&taxon_no=')
.. getDebug() .. getAdditionalCategoryAboutWikidata()
end
-- Used by {{Tropicos}} with sciname
function p.compareTropicosIdWithWikidata(frame)
return compareSiteIdWithWikidata(frame, 'P960', 'Tropicos', 'Tropicos', {'{{Tropicos', '{{tropicos'}, 'http://www.tropicos.org/Name/')
.. getDebug() .. getAdditionalCategoryAboutWikidata()
end
-- Used by {{WRMS}} and {{WRMS species}} with |sciname=none|decoratedSciname={{{2|{{PAGENAME}}}}}
function p.compareWoRMSIdWithWikidata(frame)
return compareSiteIdWithWikidata(frame, 'P850', 'WoRMS', 'WRMS', {'{{WRMS'}, 'http://www.marinespecies.org/aphia.php?p=taxdetails&id=')
.. getDebug() .. getAdditionalCategoryAboutWikidata()
end
----------------------------------------------------------------------------------------------------
---------- Testcase public functions (return string) -----------------------------------------------
----------------------------------------------------------------------------------------------------
function p.test(frame)
return test(frame,frame.args)
end
function p.testcase_isLink(frame)
-- for testcases (return string when normal function returns boolean)
return tostring(not not isLink(frame.args['1']))
end
function p.testcase_calcVNEntry(frame)
-- for testcases
return mw.text.nowiki(calcVNEntry(frame.args['lang'], frame.args['interwiki'], frame.args['otherInterwiki'], frame.args['vnFromWikidata'], frame.args['vnSource'], frame.args['default'])) .. getDebug()
end
function p.testcase_getScientificNames()
-- for testcases (return string when normal function returns table)
local entities = retrieveEntitiesSimple(nil)
return mw.text.nowiki(tableToString(getScientificNames(entities),false))
end
function p.testcase_extractSciNameOutOfDecoratedSciName(frame)
-- for testcases (return 'nil' instead of nil)
local sciname = tostring(extractSciNameOutOfDecoratedSciName(frame.args['1']))
if _debug then
sciname = sciname .. getDebug()
end
return sciname
end
function p.testcase_getWikidataLinkFormat1(frame)
-- for testcases
return mw.text.nowiki(getWikidataLinkFormat1(frame.args['1']))
end
function p.testcase_getWikidataLinkFormat2(frame)
-- for testcases
local entity = mw.wikibase.getEntity(frame.args['1'])
if entity then
return mw.text.nowiki(getWikidataLinkFormat2(entity))
else
return 'Not a valid entity id'
end
end
return p