Module:Taxonbar
require('Module:No globals')
local conf = require( "Module:Taxonbar/conf" ) -- configuration module local TaxonItalics = require("Module:TaxonItalics") -- use a function from Module:TaxonItalics to italicize a taxon name
local function getIdsFromWikidata( item, property ) local ids = {} if not item.claims[property] then return ids end for _, statement in pairs( item.claims[property] ) do if statement.mainsnak.datavalue then table.insert( ids, statement.mainsnak.datavalue.value ) end end return ids end
local function getLink( property, val ) local link, returnVal = "", {}
returnVal.isError = false
if mw.ustring.find( val, '//' ) then link = val else if type(property) == 'number' and property > 0 then local entityObject = mw.wikibase.getEntity('P'..property) local dataType
if entityObject then dataType = entityObject.datatype else returnVal.isError = true end
if dataType == "external-id" then local formatterURL = entityObject:getBestStatements('P1630')[1] if formatterURL then link = formatterURL.mainsnak.datavalue.value end elseif dataType == "url" then local subjectItem = entityObject:getBestStatements('P1629')[1] if subjectItem then local officialWebsite = mw.wikibase.getEntity(subjectItem.mainsnak.datavalue.value.id):getBestStatements('P856')[1] if officialWebsite then link = officialWebsite.mainsnak.datavalue.value end end elseif dataType == "string" then local formatterURL = entityObject:getBestStatements('P1630')[1] if formatterURL then link = formatterURL.mainsnak.datavalue.value else local subjectItem = entityObject:getBestStatements('P1629')[1] if subjectItem then local officialWebsite = mw.wikibase.getEntity(subjectItem.mainsnak.datavalue.value.id):getBestStatements('P856')[1] if officialWebsite then link = officialWebsite.mainsnak.datavalue.value end end end else returnVal.isError = true end elseif type(property) == 'string' then link = property end
local valurl = val if mw.ustring.find( link, 'antweb.org' ) then valurl = mw.ustring.gsub(valurl, " ", "%%20") end valurl = mw.ustring.gsub(valurl,"%%","%%%%") link = mw.ustring.gsub(link, '$1', valurl) end
link = mw.ustring.gsub(link, '^[Hh][Tt][Tt][Pp]([Ss]?)://', 'http%1://') -- fix wikidata URL val = mw.ustring.match(val, '([^=/]*)/?$') -- get display name from end of URL if mw.ustring.find( link, '//' ) then returnVal.text = '['..link..' '..mw.text.encode(mw.uri.decode(val, "PATH"),'%[%]')..']' elseif link == "" then returnVal.text = val else returnVal.text = ''..val..'' end return returnVal end
local function createRow( id, label, rawValue, link, withUid ) if link then local outStr = '*' .. label .. ' <span' if withUid then outStr = outStr..' class="uid"' end return outStr..'>' .. link .. '\n' else return '* ' .. mw.text.tag('span', {class='error'}, 'The identifier ' .. id .. ' ' .. rawValue .. ' is not valid.') .. '\n' end end
local function copyTable(inTable) if type(inTable) ~= 'table' then return inTable end local outTable = setmetatable({}, getmetatable(inTable)) for key, value in pairs (inTable) do outTable[copyTable(key)] = copyTable(value) end return outTable end
local p = {}
function p.authorityControlTaxon( frame ) local resolve = require( "Module:ResolveEntityId" ) local parentArgs = copyTable(frame:getParent().args) local currentTitle = mw.title.getCurrentTitle() local currentEntityId = mw.wikibase.getEntityIdForCurrentPage()
local stringArgs = false local fromTitleCount, firstRow, rowCount = 1, 0, 0 local outString, errors = "", "" local tfroms = {} --non-sequential table of unique froms local ifroms = 0 --integer size of tfroms, b/c Lua local categories = { , , , -- [3] placeholder for , -- [4] placeholder for , -- [5] placeholder for , -- [6] placeholder for , -- [7] placeholder for , -- [8] placeholder for , -- [9] placeholder for , --[10] placeholder for , --[11] placeholder for , --[12] placeholder for , --[13] placeholder for , --[14] placeholder for , --[15] placeholder for , --[16] placeholder for }
--Assess the page's relationship with Wikidata local currentItem = nil if currentTitle.namespace == 10 then --i.e. Module:Taxonbar/sandbox, Template:Taxonbar/doc, etc. if resolve._entityid(frame, parentArgs['from']) then currentItem = mw.wikibase.getEntity(parentArgs['from']) end if currentItem == nil then if resolve._entityid(frame, parentArgs['from1']) then currentItem = mw.wikibase.getEntity(parentArgs['from1']) end end elseif resolve._entityid(frame, currentEntityId) then currentItem = mw.wikibase.getEntity(currentEntityId) else --currentEntityId == nil categories[6] = end if currentItem then local acceptable = { ['Q16521'] = 'taxon', ['Q310890'] = 'monotypic taxon', ['Q2568288'] = 'ichnotaxon', ['Q23038290'] = 'fossil taxon', ['Q47487597'] = 'monotypic fossil taxon', ['Q42621'] = 'hybrid', ['Q235536'] = 'incertae sedis', ['Q713623'] = 'clade', ['Q848328'] = 'serotype', ['Q17487588'] = 'unavailable combination', } categories[11] = --unset if acceptable found for _, instanceOfState in pairs ( currentItem:getBestStatements('P31') ) do --instance of local instanceOf = instanceOfState.mainsnak.datavalue.value.id if acceptable[instanceOf] then categories[11] = break end end end
--Cleanup args for k, v in pairs( frame:getParent().args ) do if type(k) == 'string' then --make args case insensitive local lowerk = mw.ustring.lower(k) if not parentArgs[lowerk] or parentArgs[lowerk] == then parentArgs[k] = nil parentArgs[lowerk] = v end --remap abc to abc1 if not mw.ustring.find(lowerk,"%d$") then --if no number at end of param if not parentArgs[lowerk..'1'] or parentArgs[lowerk..'1'] == then parentArgs[lowerk] = nil lowerk = lowerk..'1' parentArgs[lowerk] = v end end if v and v ~= then --remap "for" to "title" if mw.ustring.sub(lowerk,1,3) == "for" then local forTitle = mw.ustring.gsub(lowerk,"^for","title",1) if parentArgs[forTitle] == or not parentArgs[forTitle] then parentArgs[lowerk] = nil lowerk = forTitle parentArgs[lowerk] = v end end --find highest from or title param if mw.ustring.sub(lowerk,1,4) == "from" then local fromNumber = tonumber(mw.ustring.sub(lowerk,5,-1)) if fromNumber and fromNumber >= fromTitleCount then fromTitleCount = fromNumber end --look for duplicate froms while we're here if mw.ustring.find(v, "^Q%d") then if tfroms[v] then categories[8] = tfroms[v] = tfroms[v] + 1 else tfroms[v] = 1 ifroms = ifroms + 1 end if ifroms > 1 then categories[3] = end end elseif mw.ustring.sub(lowerk,1,5) == "title" then local titleNumber = tonumber(mw.ustring.sub(lowerk,4,-1)) if titleNumber and titleNumber >= fromTitleCount then fromTitleCount = titleNumber end elseif mw.ustring.lower(v) ~= 'no' then stringArgs = true categories[5] = end end end end
--Append basionym to arg list, if not already provided if currentItem then local currentBasState = currentItem:getBestStatements('P566')[1] --basionym if currentBasState then local basionymId = currentBasState.mainsnak.datavalue.value.id if basionymId and resolve._entityid(frame, basionymId) and tfroms[basionymId] == nil then --check that basionym is a strict instance of taxon local basionymItem = mw.wikibase.getEntity(basionymId) if basionymItem then local acceptable = { ['Q16521'] = 'taxon', ['Q310890'] = 'monotypic taxon', ['Q2568288'] = 'ichnotaxon', ['Q23038290'] = 'fossil taxon', ['Q47487597'] = 'monotypic fossil taxon', } for _, instanceOfState in pairs ( basionymItem:getBestStatements('P31') ) do --instance of local instanceOf = instanceOfState.mainsnak.datavalue.value.id if acceptable[instanceOf] then --housekeeping tfroms[basionymId] = 1 ifroms = ifroms + 1 fromTitleCount = fromTitleCount + 1 --append basionym & track parentArgs['from'..fromTitleCount] = basionymId categories[14] = break end end end end end end
--Append monotypic genus/species to arg list of monotypic species/genus, if not already provided if currentItem then for _, instanceOfState in pairs ( currentItem:getBestStatements('P31') ) do --instance of local taxonRank = nil local parentItem = nil local parentTaxon = nil local parentTaxonRank = nil local parentMonoGenus = nil --holy grail/tbd local instanceOf = instanceOfState.mainsnak.datavalue.value.id if instanceOf and (instanceOf == 'Q310890' or instanceOf == 'Q47487597') then --monotypic/fossil taxon local taxonRankState = currentItem:getBestStatements('P105')[1] --taxon rank if taxonRankState then taxonRank = taxonRankState.mainsnak.datavalue.value.id end
if taxonRank and taxonRank == 'Q7432' then --species --is monotypic species; add genus local parentTaxonState = currentItem:getBestStatements('P171')[1] --parent taxon if parentTaxonState then parentTaxon = parentTaxonState.mainsnak.datavalue.value.id end --confirm parent taxon rank == genus & monotypic (too careful/expensive? remove/mod this first if hitting any request limits) if parentTaxon and resolve._entityid(frame, parentTaxon) then parentItem = mw.wikibase.getEntity(parentTaxon) if parentItem then local parentTaxonRankState = parentItem:getBestStatements('P105')[1] --taxon rank if parentTaxonRankState then parentTaxonRank = parentTaxonRankState.mainsnak.datavalue.value.id end if parentTaxonRank and parentTaxonRank == 'Q34740' then -- genus for _, parentInstanceOfState in pairs ( parentItem:getBestStatements('P31') ) do --instance of local parentInstanceOf = parentInstanceOfState.mainsnak.datavalue.value.id if parentInstanceOf and (parentInstanceOf == 'Q310890' or parentInstanceOf == 'Q47487597') then --monotypic/fossil taxon parentMonoGenus = parentTaxon --confirmed break end end if parentMonoGenus and tfroms[parentMonoGenus] == nil then --housekeeping tfroms[parentMonoGenus] = 1 ifroms = ifroms + 1 fromTitleCount = fromTitleCount + 1 --append monotypic genus & track parentArgs['from'..fromTitleCount] = parentMonoGenus categories[15] = break end end end end if parentMonoGenus == nil or tfroms[parentMonoGenus] == nil then categories[16] = break end elseif taxonRank and taxonRank == 'Q34740' then --genus --is monotypic genus; add species --... end
end end end
--Setup navbox local navboxParams = { name = 'Taxonbar', bodyclass = 'hlist', listclass = , groupstyle = 'text-align: left;', }
for f = 1,fromTitleCount,1
do
local elements, title = {}, nil
--cleanup parameters
if parentArgs['from'..f] == then parentArgs['from'..f] = nil end
if parentArgs['title'..f] == then parentArgs['title'..f] = nil end
--remap aliases
for _, a in pairs( conf.aliases ) do
local alias, name = mw.ustring.lower(a[1]), mw.ustring.lower(a[2])
if parentArgs[alias..f] and not parentArgs[name..f] then
parentArgs[name..f] = parentArgs[alias..f]
parentArgs[alias..f] = nil
end
end
--Fetch Wikidata item
local from = resolve._entityid(frame, parentArgs['from'..f])
local item = mw.wikibase.getEntity(from)
local label = nil
if type(item) == 'table' then
local statements = item:getBestStatements('P225')[1] --taxon name
if statements then
local datavalue = statements.mainsnak.datavalue
if datavalue then
label = datavalue.value
end
end
label = label or item:getLabel()
else
if parentArgs['from'..f] then
categories[1] =
categories[4] =
errors = errors .. mw.text.tag('strong', {class='error'}, 'Error: "' ..
parentArgs['from'..f] .. '" is not a valid Wikidata entity ID.
')
end
end
if label and label ~= then
title = mw.title.new(label)
end
if not title and parentArgs['title'..f] then
title = mw.title.new(parentArgs['title'..f])
end
if not title and f == 1 then
title = currentTitle
end
if title then if (not parentArgs['wikidata'..f] or parentArgs['wikidata'..f] == ) and (title.namespace == 0) then if parentArgs['from'..f] then parentArgs['wikidata'..f] = parentArgs['from'..f] elseif item then parentArgs['wikidata'..f] = item.id end end if title.namespace == 0 or stringArgs then --Only in the main namespace or if there are manual overrides local sourcesFound = false local sourceCount = 0 for _, params in pairs( conf.databases ) do params[1] = mw.ustring.lower(params[1]) local propId = params[3] --Wikidata fallback if requested if (item ~= nil and item.claims ~= nil) and (type(propId) == 'string' or (type(propId) == 'number' and propId > 0)) then local wikidataIds = getIdsFromWikidata( item, 'P' .. propId ) local v = parentArgs[params[1]..f] if wikidataIds[1] then if (not v or v == ) then parentArgs[params[1]..f] = wikidataIds[1] else if v and v ~= 'no' and v ~= wikidataIds[1] then categories[9] = elseif v and v == wikidataIds[1] then categories[10] = end end end end local val = parentArgs[params[1]..f] if val and val ~= and mw.ustring.lower(val) ~= 'no' then if type(propId) == 'number' then if propId < 0 then propId = -propId end --allow link if propId > 0 then --link table.insert( elements, createRow( params[1], params[2]..':', val, getLink( propId, val ).text, true ) ) else --propId == 0; no link table.insert( elements, createRow( params[1], params[2]..':', val, val, true ) ) end else table.insert( elements, createRow( params[1], params[2]..':', val, getLink( propId, val ).text, true ) ) end if params[1] ~= 'wikidata' then sourcesFound = true sourceCount = sourceCount + 1 end end end
if sourceCount >= 20 then categories[12] = end if sourceCount >= 25 then categories[13] = end
--Generate navbox title if sourcesFound then rowCount = rowCount + 1 if firstRow == 0 then firstRow = f end --set title from wikidata if it doesn't exist if parentArgs['title'..f] == or not parentArgs['title'..f] then parentArgs['noTitle'..f] = true parentArgs['title'..f] = title.text end --if it exists now, set row heading to title if parentArgs['title'..f] and parentArgs['title'..f] ~= then navboxParams['group'..f] = TaxonItalics.italicizeTaxonName(parentArgs['title'..f], false) else navboxParams['group'..f] = "" end navboxParams['list'..f] = table.concat( elements ) elseif currentEntityId then categories[7] = end
--Categorize if parentArgs['from'..f] and parentArgs['from'..f] ~= then --blank "missing from" if 'from' exists categories[1] = --blank "desynced" if 'from' matches current page if parentArgs['from'..f] == currentEntityId then categories[2] = end end --cannot be "desynced" if no 'from' params if categories[1] ~= then categories[2] = end end end end
if rowCount > 0 then
local Navbox = require('Module:Navbox')
if rowCount > 1 then
--remove duplicates and move page title to top
local rowIDs = {}
for f = 1,fromTitleCount,1
do
if parentArgs['title'..f] and parentArgs['title'..f] ~= then
if rowIDs[parentArgs['wikidata'..f]] then --remove duplicate
navboxParams['group'..f] = nil
navboxParams['list'..f] = nil
else
rowIDs[parentArgs['wikidata'..f]] = true
if f > firstRow and (parentArgs['title'..f] == currentTitle.text or
parentArgs['wikidata'..f] == currentEntityId) then --move item linked to page to top
if navboxParams['group'..f] and
navboxParams['group'..f] ~= and
navboxParams['list'..f] and
navboxParams['list'..f] ~= then
local tempGroup, tempList = navboxParams['group'..f], navboxParams['list'..f]
navboxParams['group'..f], navboxParams['list'..f] = navboxParams['group'..firstRow], navboxParams['list'..firstRow]
navboxParams['group'..firstRow], navboxParams['list'..firstRow] = tempGroup, tempList
end
end
end
end
end
--adjust navbox for number of rows
navboxParams['title'] = "Taxon identifiers"
if rowCount > 2 then
navboxParams['navbar'] = 'plain'
else
navboxParams['state'] = 'off'
navboxParams['navbar'] = 'off'
end
elseif parentArgs['noTitle'..firstRow] then
navboxParams['group'..firstRow] = 'Taxon identifiers'
else
navboxParams['group'..firstRow] = 'Taxon identifiers
' .. navboxParams['group'..firstRow]
end
--return navbox outString = Navbox._navbox(navboxParams) end
--add categories
if string.sub(currentTitle.subpageText,1,9) == 'testcases' then parentArgs['demo'] = true end
if parentArgs['demo'] and parentArgs['demo'] ~= then
outString = outString .. mw.text.nowiki(table.concat(categories)) .. '
'
elseif currentTitle.namespace == 0 then
outString = outString .. table.concat(categories)
end
return outString .. errors end
return p
- Taxonbars without from parameter
- Taxonbars desynced from Wikidata
- Taxonbars using multiple manual Wikidata items
- Taxonbars with invalid from parameters
- Taxonbars using manual taxon IDs
- Taxonbar pages requiring a Wikidata item
- Taxonbar pages without Wikidata taxon IDs
- Taxonbars with duplicate from parameters
- Taxonbars with manual taxon IDs differing from Wikidata
- Taxonbars with manual taxon IDs identical to Wikidata
- Taxonbars on possible non-taxon pages
- Taxonbars with 20+ taxon IDs
- Taxonbars with 25+ taxon IDs
- Taxonbars with automatically added basionyms
- Taxonbars with automatically added monotypic genera
- Taxonbars of monotypic species missing genera