Module:Wikidata Infobox
Jump to navigation
Jump to search
Lua
CodeDiscussionEditHistoryLinksLink count Subpages:DocumentationTestsResultsSandboxLive code All modules
Helper functions for {{Wikidata Infobox}}
Code
local p = {}
require('strict')
local WikidataIB = require( 'Module:WikidataIB' )
local i18n = require( 'Module:Wikidata Infobox/i18n' ).i18n
local getBestStatements = mw.wikibase.getBestStatements
local frame = mw.getCurrentFrame()
local config = {
-- toggle/customize infobox features:
defaultsort = true,
interwiki = true,
autocat = true,
trackingcats = true,
uploadlink = true,
sitelinks = true,
authoritycontrol = true,
helperlinks = true,
coordtemplate = 1, -- 0 = none, 1 = Geohack, 2 = Coord
mapwidth = 250,
mapheight = 250,
imagesize = '230x500px',
-- parameters for WikidataIB:
spf = '', -- suppressfields
fwd = 'ALL', -- fetchwikidata
osd = 'no', -- onlysourced
noicon = 'yes', -- pencil icon
wdlinks = 'id', -- add links to Wikidata if no label found
collapse = 10, -- collapse list of values if too many values
maxvals = 30, -- stop fetching Wikidata after this number of values
}
-- variables set by main():
local ITEM -- mw.wikibase.entity table
local QID -- qid of ITEM, e.g. 'Q42'
local CLAIMS -- ITEM.claims
local ISTAXON -- whether ITEM is a biological taxon
local INSTANCEOF = {} -- Hash set of ITEM's best "instance of" values
local MYLANG -- user's languge code
local LANG -- language object of user's language
local FALLBACKLANGS -- list containing MYLANG and its fallback languages
-- Can't have more than one {{#coordinates:primary}}, so keep track of count
local primary_coordinates = 0
--- Returns label of given Wikidata entity in user's language.
--- If label doesn't exist, returns the id as link to Wikidata.
--- @param id string
--- @param nolink? boolean: Whether to return link to Wikidata if no label found
local function getLabel( id, nolink )
local label = mw.wikibase.getLabel( id )
if label then
return mw.text.nowiki( label ) -- nowiki to prevent wikitext injection
elseif nolink then
return id
else
return '[[d:' .. id .. ']]'
end
end
--- Query Wikidata entity for the first best value of property _pid_.
--- Returns nil if first best value is novalue or somevalue.
--- Returns nil if entityOrId is neither table nor string.
--- @param entityOrId table|string: getEntity() or qid.
--- @param pid string
--- @return unknown|nil
local function getSingleValue( entityOrId, pid )
local claim
if type( entityOrId ) == 'table' then
claim = entityOrId:getBestStatements( pid )[1]
elseif type( entityOrId ) == 'string' then
claim = getBestStatements( entityOrId, pid )[1]
end
return claim and claim.mainsnak.datavalue and claim.mainsnak.datavalue.value
end
--- Iterator function over a list of Wikidata claims/statements
--- @param t table as returned by wikibase.getBestStatements
local function iclaims( t )
local i = 1
return function()
while i <= #t do
local dv = t[i].mainsnak.datavalue
local v = dv and dv.value
i = i + 1
if v then return v end
end
end
end
--- Returns Commons sitelink (full page title), preferably to category
--- @param qid string
--- @return string|nil
local function getCommonsLink( qid )
local sitelink = mw.wikibase.getSitelink( qid, 'commonswiki' )
if sitelink and sitelink:sub(1,9) == 'Category:' then
return sitelink -- sitelink to category page
end
local maincat = getSingleValue( qid, 'P910' ) -- topic's main category
if maincat and maincat.id then
local sl = mw.wikibase.getSitelink( maincat.id, 'commonswiki' )
if sl then return sl end
end
local listcat = getSingleValue( qid, 'P1754' ) -- category related to list
if listcat and listcat.id then
local sl = mw.wikibase.getSitelink( listcat.id, 'commonswiki' )
if sl then return sl end
end
local P373 = getSingleValue( qid, 'P373' ) -- Commons category
if P373 then
return 'Category:' .. P373
end
return sitelink -- sitelink to gallery page
end
local getSitelink = (mw.wikibase.getGlobalSiteId() == 'commonswiki') and getCommonsLink or mw.wikibase.getSitelink
--- Returns sitelink to Commons as wikilink or the label of the given Q-item
--- @param qid string
local function getLinkOrLabel( qid )
local sitelink = getSitelink( qid )
if sitelink then
return "[[:" .. sitelink .. "|" .. getLabel( qid, true ) .. "]]"
else
return getLabel( qid )
end
end
--- Renders snak as rich wikitext. Returns nil if snak is nil or false.
--- @param snak table: claim.mainsnak or claim.qualifiers[pid]
local function renderSnak( snak )
if not snak then return end
local snaktype = snak.snaktype
if snaktype == 'value' then
local datatype = snak.datatype
local value = snak.datavalue.value
if datatype == 'wikibase-item' then
return getLinkOrLabel( value.id )
else
return mw.wikibase.formatValue( snak )
end
elseif snaktype == 'somevalue' then
local label = mw.message.new('Wikibase-snakview-variations-somevalue-label'):inLanguage(MYLANG):plain()
return '<i style="color:#54595d">'..label..'</i>'
end
end
--- Returns claim whose "language of work or name" (P407) qualifier matches
--- langcode, or nil if none matches.
--- @param claims table as returned by getBestStatements()
--- @param langcode string, e.g. "en"
--- @return unknown|nil
local function getClaimByLang( claims, langcode )
for _, claim in ipairs( claims or {} ) do
for _, qual in ipairs( claim.qualifiers and claim.qualifiers['P407'] or {} ) do
if qual.datavalue and qual.datavalue.value and getSingleValue( qual.datavalue.value.id, 'P424' ) == langcode then
return claim
end
end
end
end
--- If the given snaks of datatype monolingualtext contain a string in one of
--- the user's fallback languages, the string is returned; otherwise a random
--- string is retuned. The second return value indicates whether finding a
--- string in one of the user's fallback languages was successful.
--- @param snaks table, e.g. claims.qualifiers['P2096']
--- @return string?, boolean? success
local function extractMonolingualText( snaks )
if not snaks or snaks == {} then return end
-- collect strings into hash table with langcodes as keys
local monotext = {}
for _, snak in ipairs( snaks ) do
local ms = snak.mainsnak or snak
local v = ms and ms.datavalue and ms.datavalue.value
if v then
monotext[v.language] = v.text
end
end
for _, lang in ipairs( FALLBACKLANGS ) do
if monotext[lang] then return monotext[lang], true end
end
-- return random string
local _, v = next( monotext )
return v, false
end
--- Parses a string in WikiHiero syntax
local function expandhiero( hiero )
return frame:callParserFunction{ name = '#tag:hiero', args = {hiero} }
end
--- Returns a string containing two table rows
local function format2rowline( header, content )
return '<tr><th class="wikidatainfobox-lcell" style="text-align:left" colspan="2">'..header..'</th></tr><tr><td style="vertical-align:top" colspan="2">'..content..'</td></tr>'
end
--- Returns a string containing a single table row
local function format1rowline( trqid, header, content )
return '<tr id="'..trqid..'"><th class="wikidatainfobox-lcell">'..header..'</th><td style="vertical-align:top">'..content..'</td></tr>'
end
--- Returns a string containing the HTML markup for an infobox row.
--- Returns nil if content is empty.
--- @param eid string: ID of Wikidata entity whose label shall be used as heading
--- @param content string|nil
--- @param mobile? boolean: Set to true to show on devices with narrow screens
local function formatLine( eid, content, mobile )
if not content or content == '' then return end
local row = mw.html.create( 'tr' )
if not mobile then
row:addClass( 'wdinfo_nomobile' ) -- [[Template:Wikidata_Infobox/styles.css]]
end
row:tag( 'th' )
:addClass( 'wikidatainfobox-lcell' )
:node( LANG:ucfirst( getLabel(eid) ) )
row:tag( 'td' )
:node( content )
return tostring( row )
end
--- Returns unbulleted HTML list if given a sequence table.
--- @param list string[]
local function ubl( list )
if #list == 0 then return end
local out = table.concat( list, '</li><li>' )
return '<div class="plainlist"><ul><li>'..out..'</li></ul></div>'
end
--- Given a language code, returns its databaseId (as used by Wikidata sitelinks).
--- All databaseIds that a wiki knows are stored in its [[mw:Manual:sites table]].
--- @param langcode string
local function databaseId( langcode )
local exceptions = {
['be-tarask'] = 'be_x_old', -- Belarusian (Taraškievica orthography)
['bho'] = 'bh', -- Bhojpuri
['cbk-zam'] = 'cbk_zam', -- Chavacano de Zamboanga
['gsw'] = 'als', -- Alemannic
['ike'] = 'iu', -- Inuktitut
['lzh'] = 'zh_classical', -- Classical Chinese
['map-bms'] = 'map_bms', -- Basa Banyumasan
['nan'] = 'zh_min_nan', -- Min Nan Chinese
['nb'] = 'no', -- Norwegian Bokmål
['nds-nl'] = 'nds_nl', -- Low Saxon
['mo'] = 'ro', -- Moldaawisk
['roa-tara'] = 'roa_tara', -- Tarantino
['rup'] = 'roa_rup', -- Aromanian
['sgs'] = 'bat_smg', -- Samogitian
['vro'] = 'fiu_vro', -- Võro
['yue'] = 'zh_yue', -- Cantonese
-- I did my best to make this list as comprehensive as possible.
-- Useful pages for finding exceptions:
-- [[mw:Manual:$wgExtraLanguageCodes]]
-- [[meta:Special_language codes]]
-- [[meta:List_of_Wikipedias#Nonstandard_language_codes]]
-- [[meta:Template:N en/list]]
-- [[meta:Template:Wikilangcode]]
}
local exception = exceptions[langcode]
if exception then return exception end
return langcode:gsub("-.*", "") -- delete everything after hyphen
end
-- Set of pids whose values should always be linked even if they are collapsed.
-- Adding new pids may slow down the infobox on certain pages.
local should_be_linked = {
-- pid property label rationale
P2789=true, -- connects with [[Template_talk:Wikidata_Infobox/Archive_5#P2789_-_connects_with]]
P527=true, -- has part(s) [[Template_talk:Wikidata_Infobox/Archive_5#P2789_-_connects_with]]
P1382=true, -- partially coincident with [[Template_talk:Wikidata_Infobox/Archive_5#P1382_vs._P527]]
P40=true, -- child [[Template_talk:Wikidata_Infobox#No_links_if_information_is_folded?]]
P3373=true, -- sibling [[Template_talk:Wikidata_Infobox#No_links_if_information_is_folded?]]
}
--- Wrapper around WikidataIB. Returns nil if the item has no _pid_ statement.
--- @param pid string: Wikidata property id
--- @param args? table: arguments for WikidataIB
--- @return string|nil
local function getValue( pid, args )
args = args or {}
local collapse = args.collapse or config.collapse
if collapse == 0 and args.linked == nil then
error("getValue: Must give linked='no' or linked='yes' if collapse=0", 2)
end
-- linking many values harms performance if the value items are big and the sitelink needs to be taken from P910, P1754 or P373
local linked = args.linked or should_be_linked[pid]
or #getBestStatements(args.qid or QID, pid) <= collapse
return WikidataIB._getValue{
pid,
name = pid,
qid = args.qid or QID,
linked = linked,
wdlinks = args.wdlinks or config.wdlinks,
prefix = args.prefix,
postfix = args.postfix,
linkprefix = ':', -- suppress categorization
qlinkprefix = ':', -- suppress categorization
sorted = args.sorted,
qual = args.qual or 'MOST',
qualsonly = args.qualsonly,
maxvals = args.maxvals or config.maxvals,
postmaxvals = '…',
collapse = collapse,
spf = args.spf or config.spf,
fwd = args.fwd or config.fwd,
osd = args.osd or config.osd,
rank = 'best',
noicon = args.noicon or config.noicon,
list = args.list or 'Unbulleted list',
sep = args.sep,
unitabbr = args.unitabbr,
df = args.df, -- date format
plaindate = args.plaindate,
lang = args.lang,
gendered = args.gendered,
}
end
--- Used if no custom logic was specified for pid.
local function defaultFunc( pid, args )
return formatLine( pid, getValue(pid, args) )
end
local function defaultFuncMobile( pid, args )
return formatLine( pid, getValue(pid, args), true )
end
local function defaultFuncMobileGendered( pid )
return formatLine( pid, getValue(pid, {gendered=true}), true )
end
local function getAudio( pid )
local audiofile = getSingleValue( ITEM, pid )
return audiofile and formatLine( pid, '[[File:' .. audiofile .. '|100px]]' )
end
local function getAudioByLang( pid )
local claims = ITEM:getBestStatements( pid )
local claim = claims[1]
for i = 1, #FALLBACKLANGS do
local c = getClaimByLang( claims, FALLBACKLANGS[i] )
if c then
claim = c
break
end
end
local audiofile = claim and claim.mainsnak.datavalue and claim.mainsnak.datavalue.value
return audiofile and formatLine( pid, '[[File:' .. audiofile .. '|100px]]' )
end
-- Example at [[Category:Thutmosis III]]
local function getHieroglyphs()
local rows = {}
for _, v in ipairs( ITEM:getBestStatements('P7383') ) do -- name in hiero markup
local idv = v.mainsnak.datavalue.value
if v.qualifiers and v.qualifiers['P3831'] then
for _, w in ipairs( v.qualifiers['P3831'] ) do
if w.datavalue then
local label = getLabel( w.datavalue.value.id )
rows[#rows+1] = format2rowline( label, expandhiero(idv) )
end
end
else
rows[#rows+1] = format2rowline( getLabel('Q82799', true), expandhiero(idv) )
end
end
return table.concat( rows )
end
--- WikidataIB arguments for birth and death related properties
local birthdeath_args = { list = '', quals = table.concat({
'P4241', -- refine date
'P805', -- statement is subject of
'P1932', -- object stated as
'P1810', -- subject named as
'P5102', -- nature of statement
'P1480', -- sourcing circumstances
'P459', -- determination method
'P1013', -- criterion used
'P1441', -- present in work
'P10663', -- applies to work
}, ',') }
local function getBirth( pid )
local out = {}
out[#out+1] = getValue( pid, birthdeath_args ) -- date
out[#out+1] = CLAIMS['P19'] and getValue( 'P19', birthdeath_args ) -- place
out[#out+1] = extractMonolingualText( ITEM:getBestStatements('P1477') ) -- name
return formatLine( pid, table.concat(out, '<br>') )
end
local function getDeath( pid )
local out = {}
out[#out+1] = getValue( pid, birthdeath_args ) -- date
out[#out+1] = CLAIMS['P20'] and getValue( 'P20', birthdeath_args ) -- place
return formatLine( pid, table.concat(out, '<br>') )
end
local function getWebsite( pid )
for _, claim in ipairs( ITEM:getBestStatements(pid) ) do
local quals = claim.qualifiers
local url = claim.mainsnak.datavalue and claim.mainsnak.datavalue.value
if url and not (quals and quals['P582']) then -- no "end time" qualifier
return '<tr><td colspan=2 style="text-align:center">['..url..' '..getLabel(pid)..']</td></tr>'
end
end
end
local function getSignature( pid )
local img = getSingleValue( ITEM, pid )
if img then
local alt = LANG:ucfirst( getLabel(pid, true) )
return '<tr class="wdinfo_nomobile"><td colspan=2 style="text-align:center"><span class="wpImageAnnotatorControl wpImageAnnotatorCaptionOff">[[File:'..img..'|150px|alt='..alt..']]</span></td></tr>'
-- equivalent to {{ImageNoteControl | caption=off | type=inline}}
end
end
--- If ITEM has a pid statement, this behaves exactly like defaultFunc. Otherwise
--- figures out the region that ITEM is in and queries the region item for pid.
--- It finds the region by first checking if ITEM has a regionPid value.
--- Otherwise it takes the region from the P971 (category combines topics)
--- statement of ITEM's main category.
--- Examples at Cat:Health_in_Gabon, Cat:Economy_of_Germany, Q7246071
--- @param pid string, e.g. "P2250" for life expectancy
--- @param regionPid string: usually "P17" (country) or "P276" (location)
--- @param collapse number: argument for WikidataIB
local function getByRegion( pid, regionPid, collapse )
local region = getSingleValue( ITEM, regionPid )
if CLAIMS[pid] then
region = QID
elseif region then
region = region.id
else
local maincat = getSingleValue( ITEM, 'P910' ) -- topic's main category
if maincat then
for topic in iclaims( getBestStatements(maincat.id, 'P971') ) do
local id = topic.id
if id ~= 'Q12147' and id ~= 'Q8434' and id ~= 'Q159810' then
-- assume id is QID of a region if it's not the QID for "health", "education", or "economy"
region = id
end
end
end
end
return region and defaultFunc( pid, {
qid = region,
collapse = collapse,
})
end
local function getByCountry( pid )
return getByRegion( pid, 'P17', 10 )
end
local function getByLocation( pid )
return getByRegion( pid, 'P276', 10 )
end
local function getByLocationCollapse4( pid )
return getByRegion( pid, 'P276', 4 )
end
local function getPrimeFactors()
local out = {}
for _, claim in ipairs( ITEM:getBestStatements('P5236') ) do
local quals = claim.qualifiers and claim.qualifiers['P1114']
local quantity = quals and quals[1].datavalue.value.amount
if quantity then
quantity = quantity:sub(2) -- strip plus sign
out[#out+1] = renderSnak(claim.mainsnak) .. '<sup>'..quantity..'</sup>'
else
out[#out+1] = renderSnak(claim.mainsnak)
end
end
return formatLine( 'Q4846249', table.concat(out, ' × ') )
end
local function getUnicodeChars( pid )
local rows = {}
for _, v in ipairs( ITEM:getBestStatements(pid) ) do
local idv = v.mainsnak.datavalue.value
for _, w in ipairs( v.qualifiers and v.qualifiers['P3831'] or {} ) do
if w.datavalue then
local qualid = w.datavalue.value.id
rows[#rows+1] = format1rowline( qualid, getLabel(qualid), idv )
end
end
end
return table.concat( rows )
end
local function getCodes( pid )
local rows = {}
for _, v in ipairs( ITEM:getBestStatements(pid) ) do
local idv = v.mainsnak.datavalue.value
for _, w in ipairs( v.qualifiers and v.qualifiers['P3294'] or {} ) do
if w.datavalue then
local qualid = w.datavalue.value.id
if qualid == "Q68101340" then
idv = expandhiero( idv )
end
rows[#rows+1] = format1rowline( qualid, getLinkOrLabel(qualid), idv )
end
end
end
return table.concat( rows )
end
local function getCodeImages( pid )
local rows = {}
for _, v in ipairs( ITEM:getBestStatements(pid) ) do
local idv = v.mainsnak.datavalue.value
for _, w in ipairs( v.qualifiers and v.qualifiers['P3294'] or {} ) do
if w.datavalue then
local qualid = w.datavalue.value.id
local img = '[[File:' .. idv .. '|none|35px]]'
rows[#rows+1] = format1rowline( qualid, getLabel(qualid), img )
end
end
end
return table.concat( rows )
end
local function getLocation()
local function fallback()
local set = {} -- locations as keys
local out = {} -- locations as values
for _, pid in ipairs{ 'P706', 'P276', 'P131', 'P17' } do
for _, claim in ipairs( ITEM:getBestStatements(pid) ) do
local location
if pid == 'P17' then -- don't link to countries
local dv = claim.mainsnak.datavalue
location = dv and getLabel( dv.value.id )
else
location = renderSnak( claim.mainsnak )
end
if location and not set[location] then
local n = #out + 1
set[location] = true -- we don't want duplicate values
out[n] = location -- we want to preserve the order
if n > config.maxvals then
out[n] = '…' -- postmaxvals
return formatLine( 'P276', ubl(out) )
end
end
end
end
return formatLine( 'P276', ubl(out) )
end
local P131,P276,P706 = CLAIMS['P131'] or {}, CLAIMS['P276'] or {}, CLAIMS['P706'] or {}
if (#P131 < 2) and (#P276 < 2) and (#P706 < 2) then
return formatLine( 'P276', WikidataIB.location{ args={QID} } ) or fallback()
else
return fallback()
end
end
local function getAuthors()
if CLAIMS['P50'] or CLAIMS['P2093'] then
local args = { list='', sep='</li><li>', collapse=0, maxvals=10, linked='yes', qual='P1545,P518,P5102,P3831' }
local authors = getValue( 'P50', args ) or ''
local namestrings = getValue( 'P2093', args )
return formatLine( 'P50', ubl{authors, namestrings} )
end
end
local function getDifferentFrom()
local out = {}
local i = 0
for different in iclaims( ITEM:getBestStatements('P1889') ) do
i = i + 1
if i > config.maxvals then break end
local href = getSitelink( different.id ) or ( 'd:'..different.id )
local label = getLabel( different.id, true )
local class = getSingleValue( different.id, 'P31' )
local isdab = class and class.id == 'Q4167410'
local icon = isdab and ' [[File:Disambig.svg|18px|alt='..mw.wikibase.getLabel('Q4167410')..']]'
local desc = mw.wikibase.getDescription( different.id )
if desc then
label = '<span title="'..mw.text.nowiki(desc)..'">'..label..'</span>'
end
out[#out+1] = string.format( '[[:%s|%s]]%s', href, label, icon or '' )
end
return formatLine( 'P1889', ubl(out) )
end
--- Returns common taxon name using [[Module:Wikidata4Bio]]
local function getVernacularName()
if ISTAXON then
local vn = frame:expandTemplate{ title = 'VNNoDisplay', args = {
useWikidata = QID
}}
if vn:sub(3,10) ~= 'Category' and not vn:match('class="error') then
-- we found at least one common name and there are no errors
local label = LANG:ucfirst( getLabel('Q502895') )
return '<tr><td colspan=2><table style="width:100%"><tr><th style="background: #cfe3ff>'..label..'</th></tr><tr><td><div style="overflow-wrap: break-word" class="mw-collapsible mw-collapsed wikidatainfoboxVN" id="wdinfoboxVN">'..vn..'</div></td></tr></table></td></tr>'
end
end
end
local function getTaxontree()
local content = require('Module:Taxontree').show{ args = {
qid = QID,
authorcite = 'y',
first = 'y',
}}
local label = LANG:ucfirst( getLabel('Q8269924') )
return '<tr><td colspan=2><table style="width:100%" id="wdinfo_taxon" class="mw-collapsible"><tr><th style="background: #cfe3ff" colspan=2>'..label..'</th></tr>'..content..'</table></td></tr>'
end
local function getOriginalCombination()
local ocomb = getSingleValue( ITEM, 'P1403' )
ocomb = ocomb and ocomb.id
local taxoname = ocomb and getSingleValue( ocomb, 'P225' ) or ''
local citation = ocomb and getSingleValue( ocomb, 'P6507' ) or ''
if taxoname then
return formatLine( 'P1403', '<i>'..taxoname..'</i>' .. ' ' .. citation )
end
end
--- Creates a taxon author citation from P405 and P574 qualifiers if
--- P6507 (taxon author citation as string) not present since otherwise
--- Taxontree already shows the citation.
local function getTaxonAuthor()
local claims = CLAIMS['P225'] -- P225 = taxon name
if #claims > 1 then
return defaultFunc( 'P225' ) -- Example at [[Category:Acacia stricta]]
elseif #claims == 1 then
if CLAIMS['P6507'] then -- P6507 = taxon author citation (string)
return -- Taxontree already shows citation, see [[Ophiogymna]]
end
local quals = claims[1].qualifiers
local author = renderSnak( quals and quals['P405'] and quals['P405'][1] )
local year = renderSnak( quals and quals['P574'] and quals['P574'][1] )
if author and year then
return formatLine( 'P405', author .. ', ' .. year )
elseif year then
return formatLine( 'P574', year ) -- [[Cat:Porphyrophora polonica]]
end
end
end
--- Given an area, returns a map zoom level to use with mw:Extension:Kartographer.
--- Fallback output is 15.
local function autoMapZoom( area )
if not area then return 15 end
if area.unit == 'http://www.wikidata.org/entity/Q35852' then -- hectare
area = area.amount / 100 -- convert to km²
elseif area.unit == 'http://www.wikidata.org/entity/Q25343' then -- m²
area = area.amount / 1e6 -- convert to km²
elseif area.unit == 'http://www.wikidata.org/entity/Q81292' then -- acre
area = area.amount * 0.004 -- convert to km²
else
area = tonumber( area.amount ) -- assume the unit is km²
end
local LUT = { 5000000, 1000000, 100000, 50000, 10000, 2000, 150, 50, 19, 14, 5, 1, 0.5 }
for zoom, scale in ipairs( LUT ) do
if area > scale then
return zoom + 1
end
end
return 15
end
local function getCoordinates( pid )
local coords = getSingleValue( ITEM, pid )
if coords then
local out
local long = coords.longitude
local lat = coords.latitude
local globeId = coords.globe:match( "Q%d+" )
if globeId == 'Q2' then -- coords are on Earth
local externaldata = { -- [[mw:Help:Extension:Kartographer]]
type = "ExternalData",
service = "geoshape",
ids = QID,
properties = {
['fill'] = "#999999",
['stroke'] = "#636363",
['stroke-width'] = 2
}
}
-- detect roads, mountain passes, rivers, borders etc.
if CLAIMS['P2043'] or CLAIMS['P16'] -- length, transport network
or CLAIMS['P974'] or CLAIMS['P4552'] -- tributary, mountain range
or CLAIMS['P177'] or CLAIMS['P1064'] -- crosses, track gauge
or CLAIMS['P15'] or CLAIMS['P14'] -- route map, traffic sign
or CLAIMS['P930'] or CLAIMS['P3858'] then -- electrification, route diagram
externaldata.service = 'geoline'
externaldata.properties['stroke'] = "#ff0000"
end
local geojson = {
externaldata,
{ type = "Feature",
geometry = { type="Point", coordinates = {long, lat} },
properties = {
['marker-size'] = "medium",
['marker-color'] = "006699"
},
},
}
local zoom
if CLAIMS['P402'] then -- OpenStreetMap relation ID
-- Let Kartographer figure out zoom level based on OSM geoshape.
-- Kartographer uses [[mw:Wikimedia_Maps/API#OSM_Geoshapes_and_lines]]
-- instead of P402 to find the OSM relation but there is no Lua
-- interface for that. You can help adding P402 statements using
-- https://mix-n-match.toolforge.org/#/catalog/688
else
local area = getSingleValue( ITEM, 'P2046' )
zoom = autoMapZoom( area )
end
out = frame:extensionTag( 'mapframe', mw.text.jsonEncode(geojson), {
frameless = 1,
lang = MYLANG,
width = config.mapwidth,
height = config.mapheight,
zoom = zoom,
align = 'center',
})
if config.trackingcats then
out = out ..'[[Category:Uses of Wikidata Infobox with maps]]'
end
if config.coordtemplate == 1 then
if primary_coordinates == 0 then
out = out .. frame:callParserFunction('#coordinates:primary', lat, long)
primary_coordinates = 1
end
out = out .. '<small>'..require('Module:Coordinates')._GeoHack_link{ lat=lat, lon=long, lang=MYLANG }..'</small>'
elseif config.coordtemplate == 2 then
local args = {
display = 'inline,title',
format = 'dms',
nosave = 1,
qid = QID
}
out = out .. '<small>'..frame:expandTemplate{ title = 'Coord', args = args }..'</small>'
end
else -- coords not on Earth
local globe = mw.wikibase.getLabelByLang( globeId, 'en' )
out = require('Module:Coordinates')._GeoHack_link{ lat=lat, lon=long, globe=globe, lang=MYLANG }
end
return '<tr class="wdinfo_nomobile"><td colspan=2 style="text-align:center">'..out..'</td></tr>'
elseif config.trackingcats and (CLAIMS['P706'] or CLAIMS['P131']) then
return '[[Category:Uses of Wikidata Infobox with no coordinate]]'
end
end
--- Show map using [[mw:Help:Map Data]] if ITEM has no coordinates
local function getCommonsMapData()
if CLAIMS['P625'] then return end
local commonsdata = getSingleValue( QID, 'P3896' )
if not commonsdata then return end
local geojson = {{
type = "ExternalData",
service = 'page',
title = commonsdata:sub(6), -- strip "Data:" prefix
}}
local out = frame:extensionTag( 'mapframe', mw.text.jsonEncode(geojson), {
frameless = 1,
lang = MYLANG,
width = config.mapwidth,
height = config.mapheight,
align = 'center',
})
if config.trackingcats then
out = out ..'[[Category:Uses of Wikidata Infobox with maps]]'
end
return '<tr class="wdinfo_nomobile"><td colspan=2 style="text-align:center">'..out..'</td></tr>'
end
local function getCelestialCoordinates()
local ra = getSingleValue( ITEM, 'P6257' ) -- right ascension
local de = getSingleValue( ITEM, 'P6258' ) -- declination
if ra and de then
local url = 'http://www.wikisky.org/?ra='..(ra.amount / 15)..'&de='..de.amount..'&de=&show_grid=1&show_constellation_lines=1&show_constellation_boundaries=1&show_const_names=1&show_galaxies=1&img_source=DSS2&zoom=9 '
local ra_unit = getLabel( ra.unit:match('Q%d+') )
local de_unit = getLabel( de.unit:match('Q%d+') )
local ra_fmt = LANG:formatNum( tonumber(ra.amount) )
local de_fmt = LANG:formatNum( tonumber(de.amount) )
local text = LANG:ucfirst( getLabel('P6257') )..' '..ra_fmt..' '..ra_unit..
'<br>'..LANG:ucfirst( getLabel('P6258') )..' '..de_fmt..' '..de_unit
return '<tr class="wdinfo_nomobile"><td colspan=2 style="text-align:center">['..url..text..']</td></tr>'
end
end
local autocats_by_id = {
P3596 = 'Archaeological monuments in Denmark with known IDs',
P1371 = 'ASI monuments with known IDs',
P2917 = 'Buildings of Madrid with COAM Register number',
P3170 = 'Cultural heritage monuments in Armenia with known IDs',
P2951 = 'Cultural heritage monuments in Austria with known IDs',
P4244 = 'Cultural heritage monuments in Bavaria with known IDs',
P2424 = 'Cultural heritage monuments in Berlin with known ID',
P2948 = 'Cultural heritage monuments in Estonia (with known IDs)',
P4009 = 'Cultural heritage monuments in Finland with known IDs',
P380 = 'Cultural heritage monuments in France with known IDs',
P4166 = 'Cultural heritage monuments in Georgia with known IDs',
P1769 = 'Cultural heritage monuments in Hesse with known ID',
P1369 = 'Cultural heritage monuments in Iran with known IDs',
P1799 = 'Cultural heritage monuments in Malta with known IDs',
P758 = 'Cultural heritage monuments in Norway with known IDs',
P1770 = 'Cultural heritage monuments in Romania with known IDs',
P1708 = 'Cultural heritage monuments in Saxony with known ID',
P808 = 'Cultural heritage monuments in Spain by ID',
P762 = 'Cultural monuments in the Czech Republic with known IDs',
P477 = 'Heritage properties in Canada with known IDs',
P5094 = 'HPIP with known IDs',
P1702 = 'IGESPAR with known IDs',
P5500 = 'IPHAN with known IDs',
P2783 = 'Listed buildings in Denmark with known IDs',
P1216 = 'Listed buildings in England with known IDs',
P1460 = 'Listed buildings in Northern Ireland with known IDs',
P709 = 'Listed buildings in Scotland with known IDs',
P1459 = 'Listed buildings in Wales with known IDs',
P649 = 'National Register of Historic Places with known IDs',
P4120 = 'Ontario Heritage Trust sites with known IDs',
P2961 = 'Periodicals in the Biblioteca Virtual de Prensa Histórica',
P7135 = 'Rijksmonumentcomplexen with known IDs',
P359 = 'Rijksmonumenten with known IDs',
P1700 = 'SIPA with known IDs',
P3759 = 'Uses of Wikidata Infobox providing SAHRA ids',
P809 = 'Uses of Wikidata Infobox providing WDPA ids',
}
--- qualifiers for "headquarters location" (P159)
local hq_quals = table.concat({
'P6375', -- street address
'P669', -- located on street
'P670', -- street number
'P4856', -- conscription number
'P281', -- postal code
'P580', -- start time
'P582', -- end time
'P585', -- point in time
'P1264', -- valid in period
'P3831', -- object has role
'P1810', -- subject named as
'P5102', -- nature of statement
}, ',' )
--- associates pids with a table of arguments for WikidataIB or with a function
--- that will be called with pid as the only argument
local property_logic = {
P51 = getAudio, -- audio
P989 = getAudioByLang, -- spoken text audio
P443 = getAudioByLang, -- pronunciation audio
P990 = getAudioByLang, -- recording of subject's voice
P7383 = getHieroglyphs, -- name in hiero markup
P569 = getBirth, -- date of birth
P570 = getDeath, -- date of death
P69 = { qual='P580,P582,P585,P512,P812' }, -- educated at
P185 = { collapse=4, maxvals=20 }, -- doctoral student
P106 = defaultFuncMobileGendered, -- occupation
P39 = { qual='P642,P580,P582,P585', collapse=6 }, -- position held
P2522 = { collapse=4 }, -- victory
P26 = { qual='DATES' }, -- spouse, TODO: sort by date qualifier (also P793)
P451 = { qual='DATES' }, -- partner
P166 = { qual='P585' }, -- award received
P856 = getWebsite, -- official website
P109 = getSignature, -- signature
P31 = defaultFuncMobile, -- instance of
P2250 = getByCountry, -- life expectancy
P4841 = getByCountry, -- total fertility rate
P5236 = getPrimeFactors, -- prime factor
P487 = getUnicodeChars, -- Unicode character
P3295 = getCodes, -- code
P7415 = getCodeImages, -- code (image)
P3270 = getByLocation, -- compulsory education (minimum age)
P3271 = getByLocation, -- compulsory education (maximum age)
P6897 = getByLocationCollapse4, -- literacy rate
P2573 = getByLocationCollapse4, -- number of out-of-school children
P971 = { osd='no' }, -- category combines topics
P180 = { list='prose', qual='' }, -- depicts
P276 = getLocation, -- location
P50 = getAuthors, -- author
P2789 = { qual='' }, -- connects with
P85 = { qual='DATES' }, -- anthem
P953 = { qual='P407', prefix="[", postfix="]" }, -- full work at
P127 = { qual='DATES' }, -- owned by
P159 = { qual=hq_quals }, -- headquarters location
P466 = { collapse=5 }, -- occupant
P126 = { collapse=5, maxvals=20 }, -- maintained by
P348 = { qual='P548,P577,P805' }, -- software version identifier
P286 = { collapse=3 }, -- head couch
P527 = { collapse=5, maxvals=20 }, -- has part
P1382 = { collapse=5, maxvals=20 }, -- partially coincident with
P1990 = { collapse=5 }, -- species kept
P1923 = { collapse=5, maxvals=10 }, -- participating team
P1346 = { collapse=5, maxvals=20 }, -- winner
P112 = { maxvals=20 }, -- founded by
P577 = {
linked = 'no', -- make film categories load much quicker
rank = 'preferred normal', -- See [[d:Property_talk:P577#Constraint_about_unique_best_value]]
},
P1082 = { qual='P585' }, -- population (qual = point in time)
P200 = { collapse=4, maxvals=20 }, -- lake inflows
P205 = { collapse=5, maxvals=20 }, -- basin country
P974 = { collapse=5, maxvals=20 }, -- tributary
P726 = { collapse=5 }, -- candidate
P1889 = getDifferentFrom, -- different from
P460 = { collapse=20, list='' }, -- same as (lots of values for given names)
P1843 = getVernacularName, -- taxon common name
P171 = getTaxontree, -- parent taxons
P1403 = getOriginalCombination, -- original combination
P225 = getTaxonAuthor, -- taxon name (and qualifiers)
P2078 = getWebsite, -- user manual URL
P625 = getCoordinates, -- coordinate location
P3896 = getCommonsMapData, -- geoshape
P6257 = getCelestialCoordinates, -- right ascension
}
--[==[----------------------------------------------------------------------
This table is used by main() to generate the infobox and by doc() to
generate [[Template:Wikidata Infobox/doc/properties]].
* `humans_allowed` determines whether the group should be displayed if the
item is a human (Q5) or a fictional human (Q15632617). It defaults to false.
* A group will only be displayed if `P31_allowed_values` contains the
"instance of" (P31) value of the item or `P31_allowed_values` is not present.
* If `bypass_property_exists_check` is set to true, the infobox tries to fetch
the values for each pid in the group, even if the item has no pid statement.
* `logic` can be a function that will be called with pid as the only argument.
`logic` can also be a WikidataIB arguments table for defaultFunc.
]==]
local property_groups = {
{ groupname = 'Switchable images', -- this group needs to be at index 1
comment = 'Users can switch between these images using [[MediaWiki:Gadget-Infobox.js|Gadget-Infobox.js]].',
humans_allowed = true,
pids = {'P2716','P18','P117','P8224','P1442','P1801','P3383','P4640','P4291','P3451','P5252','P2713','P8592','P8517','P5555','P5775','P7417','P9721','P3311','P7420','P7457','P8195','P1543','P996','P3030','P154','P2910','P41','P94','P4004','P158','P2425','P8766','P14','P1766','P15','P8512','P181','P207','P242','P1944','P1943','P1846','P1621','P367','P491','P6655','P10','P4896','P11101','P11702','P12565'},
},
{ groupname = 'Audio and hieroglyphs',
humans_allowed = true,
pids = {'P51','P989','P443','P990','P7383'},
},
{ groupname = 'Human',
P31_allowed_values = { 'Q5', 'Q15632617' },
humans_allowed = true,
pids = {'P1559','P569','P570','P1196','P509','P157','P119','P742','P2031','P2032','P1317','P27','P1532','P551','P69','P184','P185','P106','P2416','P6087','P54','P108','P463','P102','P39','P101','P135','P66','P103','P97','P2962','P2522','P53','P22','P25','P3373','P40','P26','P1038','P451','P937','P800','P1441','P166','P856','P109'},
},
{ groupname = 'Instance/subclass of',
pids = {'P31','P279'},
},
{ groupname = 'Health by region',
P31_allowed_values = { 'Q64027457' },
pids = {'P2250','P4841'},
bypass_property_exists_check = true,
},
{ groupname = 'Natural number',
P31_allowed_values = { 'Q21199' },
pids = {'P5236','P487','P3295','P7415'},
},
{ groupname = 'Education by region',
P31_allowed_values = { 'Q64801076' },
pids = {'P3270','P3271','P6897','P2573'},
bypass_property_exists_check = true,
},
{ groupname = 'National economy',
P31_allowed_values = { 'Q6456916' },
pids = {'P38','P2299','P4010','P2131','P2132','P2219','P1279','P2134','P2855'},
bypass_property_exists_check = true,
logic = getByLocationCollapse4,
},
{ groupname = 'Miscellaneous 1',
pids = {'P361','P1639','P1269','P921','P629','P1559','P452','P7163','P971','P4224','P831','P2317','P138','P825','P417','P547','P180','P2596','P186','P136','P376','P3018','P7532'},
},
{ groupname = 'Location',
comment = 'The properties {{P|131}}, {{P|276}}, {{P|706}}, and {{P|17}} together produce a single infobox row.',
pids = {'P276'},
bypass_property_exists_check = true,
},
{ groupname = 'Miscellaneous 2',
pids = {'P1001','P206','P5353','P4856','P6529','P9759','P6375','P669','P495','P1885','P149','P708','P2872','P16','P2789','P59','P65','P215','P223','P196','P36','P122','P194','P208','P209','P37','P85','P38','P35','P6','P210'},
},
{ groupname = 'Author',
comment = 'Will be displayed together with {{P|2093}}.',
pids = {'P50'},
bypass_property_exists_check = true,
},
{ groupname = 'Miscellaneous 3',
pids = {'P655','P123','P1433','P84','P193','P170','P86','P676','P87','P61','P189','P98','P58','P110','P162','P175','P393','P291','P4647','P407','P2635','P437','P953','P275','P1441','P1080','P88','P6291','P199','P169','P366','P121','P127','P159','P466','P137','P126','P177','P2505','P144','P822','P115','P5138','P118','P505','P286','P527','P1454','P1990','P2522','P1427','P1444','P1923','P1132','P1346','P176','P1071','P617','P504','P532','P8047','P289','P426','P113','P114','P375','P619','P1145','P522','P664','P823','P5804','P57','P161','P195','P217','P178','P112','P400','P306','P1435','P814','P141','P348','P585','P606','P729','P730','P580','P571','P577','P1191','P5444','P575','P1619','P3999','P582','P576','P2669','P793','P516','P2957','P2109','P618','P128','P129','P111','P179'},
},
{ groupname = 'Quantities',
pids = {'P1093','P2067','P2261','P2262','P2049','P2386','P2043','P3157','P2583','P2048','P5524','P2808','P2144','P3439','P4183','P5141','P4552','P2660','P2659','P610','P559','P7309','P1082','P2052','P2217','P2046','P2044','P2050','P2047'},
logic = { unitabbr='yes' },
},
{ groupname = 'Miscellaneous 4',
pids = {'P140','P1083','P2351','P2324','P6801','P6855','P3032','P3137','P770','P1398','P167','P81','P197','P833','P834'},
},
{ groupname = 'Water',
pids = {'P885','P403','P200','P201','P4614','P205','P974','P4792','P4661','P469','P2673','P2674'},
},
{ groupname = 'Miscellaneous 5',
pids = {'P155','P156','P1365','P1366','P3730','P3729'},
},
{ groupname = 'Elections',
pids = {'P991','P726','P1831','P1867','P1868','P1697','P5043','P5045','P5044'},
},
{ groupname = 'Miscellaneous 6',
pids = {'P1590','P1120','P1446','P1339','P1092','P784','P783','P785','P786','P787','P788','P789','P183','P2130','P2769','P1174','P859','P218','P78','P238','P239','P1889','P460','P1382','P2010','P2009','P2033','P1531','P8193'},
},
{ groupname = 'Taxon common name',
comment = "Common names are taken from the item's label, sitelink, and {{P|1843}}.",
pids = {'P1843'},
bypass_property_exists_check = true,
},
{ groupname = 'Taxonomy',
pids = {'P171','P1403','P225'},
},
{ groupname = 'Miscellaneous 7',
pids = {'P6591','P7422','P2078','P856','P6257'},
},
{ groupname = 'Maps',
comment = '{{P|3896}} is only used if no {{P|625}} statement exists. Tracked at {{c|Uses of Wikidata Infobox with maps}}.',
pids = {'P625','P3896'},
bypass_property_exists_check = true,
},
}
local externalIDs = {
{ groupname = 'Authority control',
pids = {'P213','P214','P227','P244','P268','P269','P270','P349','P409','P508','P640','P651','P691','P886','P902','P906','P947','P949','P950','P1003','P1006','P1015','P1048','P1157','P1207','P1225','P1415','P1695','P2558','P2581','P4819','P5034','P5587','P7293','P8189','P9371','P10539',}
},
{ groupname = 'Books/magazines/authors/libraries',
pids = {'P236','P271','P396','P648','P723','P724','P2961','P5199',}
},
{ groupname = 'Science',
pids = {'P356','P496','P549','P698','P717','P932','P1053','P2349','P3083','P8273',}
},
{ groupname = 'Biology',
pids = {'P428','P627','P685','P687','P6535','P815','P830','P838','P842','P846','P850','P938','P959','P960','P961','P962','P1070','P1076','P1348','P1391','P1421','P1727','P1745','P1746','P1747','P1761','P1772','P1832','P1895','P1940','P1991','P1992','P2007','P2026','P2036','P2040','P2426','P2434','P2455','P2464','P2752','P2833','P2946','P3031','P3060','P3064','P3099','P3100','P3101','P3102','P3151','P3240','P3288','P3398','P3420','P3444','P3591','P3594','P3606','P3746','P4024','P4122','P4194','P4301','P4526','P4567','P4728','P4758','P4855','P5036','P5037','P5055','P5216','P5221','P5257','P5299','P6678','P7051',}
},
{ groupname = 'Art',
pids = {'P245','P347','P434','P650','P781','P1882','P1901','P3293','P3634','P4399','P4659','P4701','P5950','P6506','P6631','P7704','P8386','P9394',}
},
{ groupname = 'Culture',
pids = {'P345','P539','P1219','P1220','P1248','P1362','P6113','P6132','P12037',}
},
{ groupname = 'Sports',
pids = {'P1146','P1440','P1469','P1665','P2020','P2276','P2446','P2458','P2574','P3171','P3537','P3538','P3681','P3924','P8286',}
},
{ groupname = 'Cultural heritage and architecture',
pids = {'P359','P380','P381','P454','P481','P649','P709','P718','P757','P758','P762','P808','P1216','P1305','P1459','P1483','P1600','P1700','P1702','P1708','P1764','P1769','P2424','P2783','P2081','P2917','P3038','P3177','P3178','P3318','P3449','P3596','P3758','P3759','P4009','P4075','P4102','P4244','P4360','P4372','P4868','P5094','P5310','P5313','P5500','P5525','P5528','P6102','P6542','P6736','P7006','P7170','P7304','P7630','P7659','P7694','P7900','P9148','P9154','P9339','P9342','P10486','P11351',}
},
{ groupname = 'Protected areas',
pids = {'P809','P3425','P3613','P3974','P5965','P6602','P6230','P6280','P6478','P6560','P6659','P3296','P677',}
},
{ groupname = 'Places and geographical features',
pids = {'P402','P11693','P10689','P3120','P3580','P3616','P3628','P4266','P6630','P7350','P7352','P7548','P8655','P8988','P10451','P4533',}
},
{ groupname = 'Administrative subdivisions',
pids = {'P772','P836','P1894','P3118','P3615','P3639','P3419','P7526','P2788','P7577','P7606','P7635','P7636','P7579','P7752','P7673','P7674','P7736','P7735',}
},
{ groupname = 'Other',
pids = {'P458','P587','P2037','P3112','P10557','P3479','P4344','P6228','P7721',}
},
}
--- @param group table
local function groupIsAllowed( group )
local ishuman = INSTANCEOF['Q5'] or INSTANCEOF['Q15632617']
if ishuman and not group.humans_allowed then return false end
local allowlist = group.P31_allowed_values
if not allowlist then return true end
for _, class in ipairs( allowlist ) do
if INSTANCEOF[class] then return true end
end
return false
end
local function noImage()
-- Wikidata classes that don't need an image
local dontNeedImg = {
'Q4167410', -- disambiguation page
'Q4167836', -- Wikimedia category
'Q11266439', -- Wikimedia template
'Q14204246', -- Wikimedia project page
'Q13406463', -- Wikimedia list article
'Q101352', -- family name
'Q202444', -- given name
'Q12308941', -- male given name
'Q11879590', -- female given name
'Q3409032', -- unisex given name
}
for _, class in ipairs( dontNeedImg ) do
if INSTANCEOF[class] then return end
end
local hasImg
for _, imgPid in ipairs( property_groups[1].pids ) do
if CLAIMS[imgPid] then
hasImg = true
break
end
end
if not hasImg then
return '[[Category:Uses of Wikidata Infobox with no image]]'
end
end
--- Returns string with all labels/descs/aliases for search engine optimization
local function seo()
local out = {}
for lang, v in pairs( ITEM.labels or {} ) do
out[#out+1] = v.value
end
for lang, v in pairs( ITEM.descriptions or {} ) do
out[#out+1] = v.value
end
for lang, v in pairs( ITEM.aliases or {} ) do
for _, w in ipairs( v ) do
out[#out+1] = w.value
end
end
return table.concat( out, '; ' )
end
-- wikiprojects that are not Wikipedia despite their IDs ending with 'wiki'
local excludedProjects = {
wikidatawiki = true, commonswiki = true, specieswiki = true,
metawiki = true, mediawikiwiki = true, outreachwiki = true,
sourceswiki = true, wikimaniawiki = true, incubatorwiki = true,
akwiki = true, foundationwiki = true, wikifunctionswiki = true,
}
-- Returns interwiki link if site is Wikipedia
local function interwikilink( site, title )
if site:sub(-4) == 'wiki' and not excludedProjects[site] then
local iwprefix = site:sub(1, -5):gsub('_', '-') -- "zh_yuewiki" to "zh-yue"
return string.format( '[[%s:%s]]', iwprefix, title )
end
end
--- Adds Wikipedia sitelinks from similar items. Example at Cat:Moore_(surname)
local function interwikis()
local out = {}
-- ITEM is usually P301 of connected item, so this is not redundant:
for site, v in pairs( ITEM.sitelinks or {} ) do
out[#out+1] = interwikilink( site, v.title )
end
for _, pid in ipairs{ 'P910', 'P2354', 'P1753', 'P460', 'P1420' } do -- topic's main category, has list, related list, said to be same as, taxon synonym
for similar in iclaims( ITEM:getBestStatements(pid) ) do
for site, v in pairs( mw.wikibase.getEntity(similar.id).sitelinks or {} ) do
out[#out+1] = interwikilink( site, v.title )
end
end
end
return table.concat( out )
end
local charMap -- memoized
local function stripDiacritics( str )
if not charMap then
local from = 'ÁÀÂÄǍĂĀÃÅẠĄƏĆĊĈČÇĎĐḐḌÐÉÈĖÊËĚĔƐƎỀỂỄẾỆĒẼĘẸĠĜĞĢĤĦḤİÍÌÎÏǏĬĪĨĮỊĴĶĹĿĽĻŁḶḸṂŃŇÑŅṆŊÓÒÔÖǑŎŌÕǪỌŐØꝚŔŘŖⱤɌƦȐȒṘṚṜŚŜŠŞȘṢŤŢȚṬÚÙÛÜǓŬŪŨŮŲỤŰǗǛǙǕŴÝŶŸỸȲŹŻŽ'..
'ằắắáẳàẵâäǎăāãåặầẩẫấậảạąəćċĉčçḑďđḍðéèėêëěɛǝềểễếệĕēẽęẹġĝğģḩĥħḥıíìîïǐĭīĩįịĵķĺŀľļłḷḹṃńňñņṇŋơóồòôöǒŏōõǫọőøꝛŕɽřŗṛṝɍʀȑȓṙśŝšşșṣťţțṭưúùûứừüǔŭūũůųụűǘǜǚǖŵýŷÿỹȳźżž'
local to = 'AAAAAAAAAAAACCCCCDDDDDEEEEEEEEEEEEEEEEEEGGGGHHHIIIIIIIIIIIJKLLLLLLLMNNNNNNOOOOOOOOOOOORRRRRRRRRRRRSSSSSSTTTTUUUUUUUUUUUUUUUUWYYYYYZZZ'..
'aaaaaaaaaaaaaaaaaaaaaaaacccccdddddeeeeeeeeeeeeeeeeeegggghhhhiiiiiiiiiiijklllllllmnnnnnnoooooooooooooorrrrrrrrrrrrssssssttttuuuuuuuuuuuuuuuuuuuwyyyyyzzz'
charMap = {}
for i = 1, mw.ustring.len( from ) do
charMap[mw.ustring.sub(from, i, i)] = mw.ustring.sub(to, i, i)
end
charMap['ß'] = 'ss'; charMap['ẞ'] = 'SS'
charMap['æ'] = 'ae'; charMap['ǣ'] = 'ae'; charMap['ǽ'] = 'ae'
charMap['Æ'] = 'AE'; charMap['Ǣ'] = 'AE'; charMap['Ǽ'] = 'AE'
charMap['œ'] = 'oe'; charMap['Œ'] = 'OE'
charMap['þ'] = 'th'; charMap['Þ'] = 'Th'
end
return (string.gsub( str, '[^\128-\191][\128-\191]*', charMap ))
end
local function humannames( out )
local surname = ITEM:formatPropertyValues('P734').value:gsub(',.*', '')
local givennames = ITEM:formatPropertyValues('P735').value:gsub(', ', ' ')
local spanish2nd = ITEM:formatPropertyValues('P1950').value:gsub(',.*', '')
if config.trackingcats then
if surname == '' then
out[#out+1] = '[[Category:Uses of Wikidata Infobox with no family name]]'
end
if givennames == '' then
out[#out+1] = '[[Category:Uses of Wikidata Infobox with no given name]]'
end
end
if config.autocat then
for _, pid in ipairs{ 'P734', 'P1950', 'P9139' } do
for name in iclaims( ITEM:getBestStatements(pid) ) do
local sitelink = getCommonsLink( name.id )
if sitelink and sitelink:sub(1,9) == 'Category:' then
if givennames == '' then
out[#out+1] = string.format('[[%s]]', sitelink)
else
out[#out+1] = string.format('[[%s|%s]]', sitelink, stripDiacritics(givennames))
end
else
name = mw.wikibase.getLabelByLang( name.id, 'en' )
if givennames == '' then
out[#out+1] = name and string.format('[[Category:%s (surname)]][[Category:Uses of Wikidata Infobox with no surname sitelink]]', name)
else
out[#out+1] = name and string.format('[[Category:%s (surname)|%s]][[Category:Uses of Wikidata Infobox with no surname sitelink]]', name, stripDiacritics(givennames))
end
end
end
end
for name in iclaims( ITEM:getBestStatements('P735') ) do
name = mw.wikibase.getLabelByLang( name.id, 'en' )
out[#out+1] = name and string.format('[[Category:%s (given name)]]', name)
-- no sort key needed because DEFAULTSORT starts with family name
end
end
if not config.defaultsort then
out[#out+1] = '[[Category:Uses of Wikidata Infobox with defaultsort suppressed]]'
elseif surname ~= '' and surname ~= 'no value' and surname ~= 'some value' then
if spanish2nd ~= '' then
surname = surname .. ' ' .. spanish2nd
end
local sortkey = stripDiacritics( surname..', '..givennames )
out[#out+1] = frame:preprocess('{{DEFAULTSORT:'..sortkey..'}}')
end
end
--- @param pid "P569"|"P570"
--- @param event "birth"|"death"
local function datecat( pid, event, out )
local year = WikidataIB._getValue{ pid, qid=QID, ps=1, df='y', plaindate='adj', lang='en', maxvals=1 }
if year and year ~= 'unknown value' then
local cat = 'Category:' .. year .. ' ' .. event .. 's'
if mw.title.new( cat ).exists then
out[#out+1] = '[['..cat..']]'
elseif config.trackingcats then
mw.addWarning( 'Categorization under [[:'..cat..']] supressed' )
out[#out+1] = '[[Category:Uses of Wikidata Infobox with unknown '..event..' category|'..year..']]'
end
end
end
local function countrycat( out )
local exceptions = {
Q30 = 'United States',
Q1005 = 'Gambia',
}
for country in iclaims( ITEM:getBestStatements('P27') ) do
local countryLabel = exceptions[country.id] or mw.wikibase.getLabelByLang( country.id, 'en' )
if countryLabel then
local sex = getSingleValue( ITEM, 'P21' )
local sexLabel = sex and ({
Q6581097 = 'Men',
Q2449503 = 'Men',
Q6581072 = 'Women',
Q1052281 = 'Women',
})[sex.id]
if sexLabel then
local cat1 = 'Category:'..sexLabel..' of the '..countryLabel..' by name'
local cat2 = 'Category:'..sexLabel..' of '..countryLabel..' by name'
if mw.title.new( cat1 ).exists then
out[#out+1] = '[['..cat1..']]'
elseif mw.title.new( cat2 ).exists then
out[#out+1] = '[['..cat2..']]'
elseif config.trackingcats then
mw.addWarning( 'Categorization under [[:'..cat2..']] supressed' )
out[#out+1] = '[[Category:Uses of Wikidata Infobox with unknown country category|'..countryLabel..']]'
end
end
end
end
end
local function autocat( out, pid, dict )
for _, claim in ipairs( ITEM:getAllStatements(pid) ) do
if claim.rank ~= "deprecated" then
local dv = claim.mainsnak.datavalue
local cat = dict[dv and dv.value.id]
out[#out+1] = cat and '[[Category:'..cat..']]'
end
end
end
local function metadata()
local out = {}
if config.trackingcats then
out[#out+1] = noImage()
if not (CLAIMS['P31'] or CLAIMS['P279']) then
out[#out+1] = '[[Category:Uses of Wikidata Infobox with no instance of]]'
end
if INSTANCEOF['Q5'] and not CLAIMS['P569'] then
out[#out+1] = '[[Category:Uses of Wikidata Infobox with no year of birth]]'
elseif INSTANCEOF['Q4167836'] and not (CLAIMS['P301'] or CLAIMS['P971']) then
out[#out+1] = '[[Category:Uses of Wikidata Infobox with no topic]]'
end
end
out[#out+1] = '<div style="display:none"><nowiki>'..seo()..'</nowiki></div>'
-- Add interwiki links from related items, inspired by Module:Interwiki
if config.interwiki and mw.title.getCurrentTitle().namespace == 14 then
out[#out+1] = interwikis()
end
if config.autocat then
for pid, cat in pairs( autocats_by_id ) do
local val = getSingleValue( ITEM, pid )
out[#out+1] = val and string.format( '[[Category:%s| %s]]', cat, val )
end
out[#out+1] = CLAIMS['P757'] and '[[Category:World Heritage Sites by name]]'
autocat( out, 'P1435', { -- heritage designation
Q34932610 = 'Conjuntos de Interesse Municipal in Portugal by name',
Q28419115 = 'Conjuntos de Interesse Público in Portugal by name',
Q54171320 = 'Monuments under study in Portugal by name',
Q15697324 = 'Imóveis de Interesse Público in Portugal by name',
Q11791 = 'Imóveis de Interesse Municipal in Portugal by name',
Q53806418 = 'Monuments included in classified sites in Portugal by name',
Q28423275 = 'Monumentos de Interesse Municipal in Portugal by name',
Q22222923 = 'Monumentos de Interesse Público in Portugal by name',
Q908411 = 'Monumentos Nacionais in Portugal by name',
Q28419400 = 'Sítios de Interesse Municipal in Portugal by name',
Q28419109 = 'Sítios de Interesse Público in Portugal by name',
Q54163210 = 'Pending classification monuments in Portugal by name',
})
autocat( out, 'P31', { -- instance of
Q235670 = 'Common years starting and ending on Sunday',
Q235673 = 'Common years starting and ending on Saturday',
Q235676 = 'Common years starting and ending on Wednesday',
Q235680 = 'Common years starting and ending on Friday',
Q235684 = 'Common years starting and ending on Tuesday',
Q235687 = 'Common years starting and ending on Monday',
Q235690 = 'Common years starting and ending on Thursday',
Q217041 = 'Leap years starting on Sunday and ending on Monday',
Q217026 = 'Leap years starting on Saturday and ending on Sunday',
Q217015 = 'Leap years starting on Wednesday and ending on Thursday',
Q217036 = 'Leap years starting on Friday and ending on Saturday',
Q217034 = 'Leap years starting on Tuesday and ending on Wednesday',
Q217024 = 'Leap years starting on Monday and ending on Tuesday',
Q217019 = 'Leap years starting on Thursday and ending on Friday',
Q66010119 = 'Months starting on Monday',
Q66010126 = 'Months starting on Tuesday',
Q66010132 = 'Months starting on Wednesday',
Q66010139 = 'Months starting on Thursday',
Q66010148 = 'Months starting on Friday',
Q66010153 = 'Months starting on Saturday',
Q66010158 = 'Months starting on Sunday',
Q3305213 = 'Individual painting categories',
})
if INSTANCEOF['Q5'] and mw.title.getCurrentTitle().namespace == 14 then
humannames( out )
datecat( 'P569', 'birth', out )
datecat( 'P570', 'death', out )
countrycat( out )
autocat( out, 'P21', { -- sex or gender
Q6581097 = 'Men by name',
Q6581072 = 'Women by name',
Q1052281 = 'LGBT people by name]][[Category:Women by name',
Q2449503 = 'LGBT people by name]][[Category:Men by name',
Q48270 = 'Non-binary people by name',
Q12964198 = 'LGBT people by name', -- genderqueer
Q1097630 = 'LGBT people by name', -- intersex
Q18116794 = 'LGBT people by name', -- genderfluid
Q505371 = 'LGBT people by name', -- agender
})
autocat( out, 'P509', { -- cause of death
Q2840 = "Deaths from influenza",
Q8277 = "Deaths from multiple sclerosis",
Q9687 = "Deaths from road accidents",
Q11081 = "Deaths from Alzheimer's disease",
Q11085 = "Deaths from Parkinson's disease",
-- Q12078 = "Deaths from cancer", -- too unspecific
Q12090 = "Deaths from cholera",
Q12152 = "Deaths from myocardial infarction",
Q12156 = "Deaths from malaria",
Q12192 = "Deaths from pneumonia",
Q12199 = "Deaths from AIDS",
Q12202 = "Deaths from stroke",
Q12204 = "Deaths from tuberculosis",
Q12206 = "Deaths from diabetes",
Q12214 = "Deaths from smallpox",
Q12796 = "Deaths by gunshot",
Q29496 = "Deaths from leukemia",
Q36956 = "Deaths from leprosy",
Q40867 = "Deaths by poisoning",
Q41083 = "Deaths from syphilis",
Q41571 = "Deaths from epilepsy",
Q47790 = "Deaths from tetanus",
Q47912 = "Deaths from lung cancer",
Q48143 = "Deaths from meningitis",
Q83030 = "Deaths from dementia",
Q83319 = "Deaths from typhoid fever",
Q128015 = "People executed by guillotine",
Q128581 = "Deaths from breast cancer",
Q131742 = "Deaths from hepatitis",
Q133462 = "People who committed seppuku",
Q133780 = "Deaths from plague (disease)",
Q134649 = "Deaths from diphtheria",
Q147778 = "Deaths from cirrhosis",
Q152234 = "Deaths from edema",
Q160105 = "Deaths from cervical cancer",
Q160649 = "Deaths from typhus",
Q172341 = "Deaths from ovarian cancer",
Q175111 = "Death by hanging",
Q178275 = "Deaths from Spanish flu",
Q180614 = "Deaths from melanoma",
Q181257 = "Deaths from prostate cancer",
Q181754 = "Deaths from heart failure",
Q183134 = "Deaths from sepsis",
Q188605 = "Deaths from emphysema",
Q188874 = "Deaths from colorectal cancer",
Q189389 = "Deaths from aneurysm",
Q189588 = "Deaths from stomach cancer",
Q190564 = "Deaths from Huntington's disease",
Q190805 = "Deaths from diseases and disorders of the heart",
Q192102 = "Deaths from skin cancer",
Q193840 = "Asphyxia",
Q199804 = "Deaths from chronic obstructive pulmonary disease",
Q200779 = "Deaths from genetic diseases and disorders",
Q202837 = "Deaths from cardiac arrest",
Q204933 = "People executed by decapitation",
Q206901 = "Deaths from amyotrophic lateral sclerosis",
Q208414 = "Deaths from lymphoma",
Q210392 = "Military people killed in action",
Q212961 = "Deaths from pancreatic cancer",
Q220570 = "Deaths from pulmonary embolism",
Q223102 = "Deaths from peritonitis",
Q261327 = "Deaths from thrombosis",
Q275466 = "Deaths from embolism",
Q372701 = "Deaths from esophageal cancer",
Q389735 = "Deaths from diseases and disorders of the cardiovascular system",
Q401402 = "Deaths from nephritis",
Q468455 = "People executed by burning",
Q476921 = "Deaths from kidney failure",
Q504775 = "Deaths from bladder cancer",
Q506616 = "Deaths from drowning",
Q621076 = "Self-immolation",
Q623031 = "Deaths from liver cancer",
Q707774 = "Deaths from coronary thrombosis",
Q744913 = "Victims of aviation accidents or incidents",
Q767485 = "Deaths from respiratory failure",
Q809831 = "BASE jumping deaths",
Q826522 = "Deaths from thyroid cancer",
Q847583 = "Deaths from cardiomyopathy",
Q852423 = "Deaths from laryngeal cancer",
Q857667 = "Deaths from pulmonary edema",
Q929737 = "Deaths from diseases and disorders of the liver",
Q949302 = "Deaths from diseases and disorders of the skin",
Q958797 = "Deaths from scleroderma",
Q970208 = "Deaths from liver failure",
Q977787 = "Deaths from gallbladder cancer",
Q1036696 = "Deaths from hypothermia",
Q1054718 = "Deaths from diseases and disorders of the kidneys",
Q1193870 = "Deaths from multiple organ failure",
Q1198391 = "Deaths from intracranial aneurysm",
Q1209744 = "Deaths from uterine cancer",
Q1368943 = "Deaths from cerebral hemorrhage",
Q1649580 = "Deaths from organ failure",
Q1963588 = "Deaths from diseases and disorders of the blood",
Q2140674 = "Deaths by gunshot",
Q2300099 = "Deaths from diseases and disorders of the digestive system",
Q2509220 = "Deaths from blood cancer",
Q2661443 = "Deaths from diseases and disorders of the endocrine system",
Q2967712 = "Deaths by horse-riding accident",
Q3010352 = "Deaths from diseases and disorders of the cerebrovascular system",
Q3242950 = "Deaths from kidney cancer",
Q3286546 = "Deaths from diseases and disorders of the respiratory system",
Q3339235 = "Deaths from diseases and disorders of the nervous system",
Q3392853 = "Deaths from diseases and disorders of the lungs",
Q3505252 = "Deaths from drug overdose",
-- Q3966286 = "Deaths from executions", -- too unspecific
Q4941552 = "Deaths from diseases and disorders of the skeletal system",
Q5526839 = "Deaths from gastrointestinal cancer",
Q7130407 = "Deaths from diseases and disorders of the pancreas",
Q7258523 = "Deaths in childbirth",
Q7692360 = "Deaths from volcanic eruptions",
Q7900883 = "Deaths from diseases and disorders of the genitourinary system",
Q8084905 = "Deaths from autoimmune diseases and disorders",
Q9303627 = "Deaths from brain cancer",
Q14467705 = "Deaths from surgical complications",
Q15747939 = "People executed by shooting",
Q18123741 = "Deaths from infectious diseases and disorders",
Q18554919 = "Deaths from bone cancer",
Q19403959 = "Victims of rail transport accidents or incidents",
Q55790434 = "Deaths from oral cancer",
-- Q84263196 = "Deaths from COVID-19", -- has a subcategory for every country
})
out[#out+1] = '[[Category:People by name]]'
out[#out+1] = CLAIMS['P570'] and '[[Category:Deceased people by name]]'
out[#out+1] = WikidataIB.getAwardCat{ args = {qid=QID, fwd='ALL', osd=config.osd, noicon='yes'} }
if not CLAIMS['P570'] then
-- This person has no death date, but are they really alive?
local birth = getSingleValue( ITEM, 'P569' )
local year = tonumber( birth and birth.time:gsub('-.*', '') )
if year and os.date('%Y') - year < 100 then
out[#out+1] = '[[Category:Living people]]'
end
end
end
end
return table.concat( out )
end
--- @return string|nil
local function getImage( pid )
local claims = ITEM:getBestStatements( pid )
local claim = getClaimByLang( claims, MYLANG ) or claims[1]
local ms = claim and claim.mainsnak
local file = ms and ms.datavalue and ms.datavalue.value
if file then
local panoramalink = (pid == 'P4640') and '|link=https://panoviewer.toolforge.org/#'..mw.uri.encode(file, 'WIKI') or ''
local img = '<span class="wpImageAnnotatorControl wpImageAnnotatorCaptionOff">[[File:'..file..'|'..config.imagesize..panoramalink..']]</span>' -- equivalent to {{ImageNoteControl | caption=off | type=inline}}
local medialegends = claim.qualifiers and claim.qualifiers['P2096']
if medialegends then
return img .. '<div>'..extractMonolingualText( medialegends )..'</div>'
else
return img -- no image caption
end
end
end
--- Returns images and sitelinks
--- @param uploadlink? boolean: Whether to show the "Upload media" link
local function header( uploadlink )
local imgs = {}
for _, imgPid in ipairs( property_groups[1].pids ) do
local formatted_img = getImage(imgPid)
imgs[#imgs+1] = formatted_img and { imgPid, formatted_img }
end
local switcherContainer = mw.html.create( 'div' )
switcherContainer:addClass( 'switcher-container' )
-- Only show switching labels if we have more than one image to show
if #imgs > 1 then
for _, img in ipairs( imgs ) do
switcherContainer:tag( 'div' )
:addClass( 'center' )
:node( img[2] )
:tag( 'span' )
:attr{ class = "switcher-label", style = "display:none" }
:node( ' ' .. getLabel(img[1]) .. ' ' )
end
elseif #imgs == 1 then
switcherContainer:tag( 'div' )
:addClass( 'center' )
:node( imgs[1][2] )
end
local images = mw.html.create( 'tr' )
images:tag( 'td' )
:attr{ colspan=2, class="wdinfo_nomobile" }
:css( 'text-align', 'center' )
:tag( 'div' )
:node( ITEM:getDescription() or '')
:done()
:node( switcherContainer )
local out = {}
if INSTANCEOF['Q4167410'] or INSTANCEOF['Q15407973'] then -- disambiguation page/category
if config.trackingcats then
out[1] = '[[Category:Uses of Wikidata Infobox for disambig pages]]'
end
elseif uploadlink then
local url = tostring(mw.uri.fullUrl('Special:UploadWizard', {
categories = mw.title.getCurrentTitle().text
}))
local text = mw.message.new('Cx-contributions-upload'):inLanguage(MYLANG):plain()
out[1] = '<tr><td colspan=2 style="text-align:center"><b>['..url..' '..text..']</b></td></tr>'
end
local sitelinks = ITEM.sitelinks
if config.sitelinks and sitelinks then
out[#out+1] = '<tr><td colspan=2 style="text-align:center; font-weight:bold">'
local langId = databaseId(MYLANG)
local langprefix = langId:gsub('_', '-')
local wikis = {
-- wikiId, prefix logo, qid, multilang
{ 'wiki', '', 'Wikipedia-logo-v2', 'Q52', false },
{ 'wikiquote', 'q', 'Wikiquote-logo', 'Q369', false },
{ 'wikisource', 's', 'Wikisource-logo', 'Q263', false },
{ 'wikibooks', 'b', 'Wikibooks-logo', 'Q367', false },
{ 'wikinews', 'n', 'Wikinews-logo', 'Q964', false },
{ 'wikiversity', 'v', 'Wikiversity-logo', 'Q370', false },
{ 'specieswiki', 'species', 'Wikispecies-logo', 'Q13679', true },
{ 'wikivoyage', 'voy', 'Wikivoyage-logo', 'Q373', false },
}
for _, v in ipairs( wikis ) do
local wikiId, prefix, logo, qid, multilang = unpack( v )
logo = '[[File:'..logo..'.svg|16x16px|alt=|link=]] '
if multilang then
local sitelink = sitelinks[wikiId]
if sitelink then
out[#out+1] = '<div>'..logo..'[['..prefix..':'..sitelink.title..'|'..getLabel(qid)..']]</div>'
end
else
local sitelink = sitelinks[langId .. wikiId]
if sitelink then
out[#out+1] = '<div>'..logo..'[['..prefix..':'..langprefix..':'..sitelink.title..'|'..getLabel(qid)..']]</div>'
end
end
end
out[#out+1] = '</td></tr>'
end
return tostring( images ) .. table.concat( out )
end
--- Returns "Edit at Wikidata" pencil
local function pencil()
local msg, lang = i18n( 'editlink-alttext', FALLBACKLANGS )
local out = mw.html.create( 'tr' )
out
:addClass( "wdinfo_nomobile" )
:tag( 'td' )
:css( 'text-align', 'right' )
:attr{ lang = lang, colspan = 2 }
:node( string.format('[[File:Blue pencil.svg|15px|link=d:%s|%s]]', QID, msg) )
return tostring( out )
end
--- Evaluates all non-image property groups and adds generated HTML rows to
--- the table given as argument.
local function getBodyContent( t )
for i, group in ipairs( property_groups ) do
if i > 1 and groupIsAllowed( group ) then
for _, pid in ipairs( group.pids ) do
if CLAIMS[pid] or group.bypass_property_exists_check then
local x = property_logic[pid] or group.logic or defaultFunc
if type(x) == 'function' then
t[#t+1] = x( pid )
else -- type(x) == 'table'
t[#t+1] = defaultFunc( pid, x )
end
end
end
end
end
end
--- Returns the infobox's main content
local function body()
if not CLAIMS then return '' end
local out = {}
getBodyContent( out )
-- If category combines at most 2 topics, show subinfoboxes for those topics.
-- See Category:Uses_of_Wikidata_Infobox_with_subinfoboxes
local topics = ITEM:getBestStatements( 'P971' )
if not topics or #topics > 2 then return table.concat( out ) end
-- country (Q6256), continent (Q5107), sovereign state (Q3624078), ocean (Q9430)
local geoEntities = { 'Q6256', 'Q5107', 'Q3624078', 'Q9430' }
-- The loop below modifies these variables and restores them afterwards
local qid, item, claims, istaxon, instanceof = QID, ITEM, CLAIMS, ISTAXON, INSTANCEOF
local map
for _, claim in ipairs( topics ) do
QID = claim.mainsnak.datavalue.value.id
ITEM = mw.wikibase.getEntity( QID )
if not ITEM then
out[#out+1] = '[[Category:Uses of Wikidata Infobox for deleted Wikidata items]]'
break
end
CLAIMS = ITEM.claims or {}
ISTAXON = CLAIMS['P105'] or CLAIMS['P171'] or CLAIMS['P225'] or CLAIMS['P1843']
INSTANCEOF = {}
for class in iclaims( ITEM:getBestStatements('P31') ) do
INSTANCEOF[class.id] = true
end
local skip
for _, geoEnt in ipairs( geoEntities ) do
if INSTANCEOF[geoEnt] then
skip = true
map = getCoordinates( 'P625' )
break
end
end
-- Skip if topic is a calendar year (Q3186692) or decade (Q39911)
skip = skip or INSTANCEOF['Q3186692'] or INSTANCEOF['Q39911']
if not skip and #getBestStatements(QID, 'P279') == 0 then -- subclass of
if config.trackingcats then
out[#out+1] = '[[Category:Uses of Wikidata Infobox with subinfoboxes]]'
end
out[#out+1] = '<tr><th colspan=2>'..(ITEM:getLabel() or QID)..'</th></tr>'
out[#out+1] = header( false )
getBodyContent( out )
out[#out+1] = pencil()
end
end
out[#out+1] = map
QID, ITEM, CLAIMS, ISTAXON, INSTANCEOF = qid, item, claims, istaxon, instanceof
return table.concat( out )
end
local function authoritycontrol()
if not config.authoritycontrol then return '' end
local ids = {}
for _, group in ipairs( externalIDs ) do
for _, pid in ipairs( group.pids ) do
if CLAIMS[pid] then
local icon = getSingleValue( pid, 'P2910' )
icon = icon and '[[File:'..icon..'|18px|alt=|link=]] ' or ''
local fmtSt = ITEM:formatStatements( pid )
if fmtSt.value ~= '' then
ids[#ids+1] = icon .. fmtSt.label .. ': ' .. fmtSt.value
end
end
end
end
local wdlogo = '[[File:Wikidata-logo.svg|20px|alt='..getLabel('Q2013')..'|link=d:'..QID..']]'
return table.concat{
'<tr><th style="background: #cfe3ff">',
LANG:ucfirst( getLabel('Q36524') ),
'</th></tr>',
'<tr><td style="text-align: center;">',
'<div style="overflow-wrap: break-word; font-size: smaller">',
wdlogo..' [[d:'..QID..'|'..QID..']]<br>',
'<span class="wdinfo_nomobile">',
table.concat(ids, '<br>'),
'</span>',
'</div>',
'</td></tr>',
}
end
local function helperlinks()
if not config.helperlinks then return '' end
local hl = {}
local title = mw.title.getCurrentTitle()
local pagename = title.text
local pagenamee = mw.uri.encode(pagename, 'WIKI')
local coords = getSingleValue( ITEM, 'P625' )
local otherplanet = coords and coords.globe ~= 'http://www.wikidata.org/entity/Q2'
hl[#hl+1] = '[https://reasonator.toolforge.org/?q='..QID..' '..getLabel('Q20155952')..']'
hl[#hl+1] = '[[toolforge:scholia/'..QID..'|'..getLabel('Q45340488')..']]'
hl[#hl+1] = '[https://wikidocumentaries-demo.wmcloud.org/'..QID..' '..getLabel('Q85947706')..']'
if title.namespace == 14 then
hl[#hl+1] = '[https://petscan.wmflabs.org/?language=commons&categories='..pagenamee..'&project=wikimedia&ns%5B6%5D=1 '..getLabel('Q23665536')..']'
hl[#hl+1] = '[https://glamtools.toolforge.org/glamorgan.html?&category='..pagenamee..'&depth=1&month=last '..getLabel('Q12483')..']'
if not otherplanet then
hl[#hl+1] = '[https://wikimap.toolforge.org/?cat='..pagenamee..'&subcats=true&subcatdepth=1&cluster=true '..getLabel('Q99232292')..']'
hl[#hl+1] = '[https://locator-tool.toolforge.org/#/geolocate?category='..pagenamee..' '..getLabel('Q66498380')..']'
end
end
hl[#hl+1] = '[https://kmlexport.toolforge.org/?project=commons&article='..mw.uri.encode(title.prefixedText)..' '..getLabel('P3096')..']'
if coords and not otherplanet then
hl[#hl+1] = '[https://wikishootme.toolforge.org/#q='..QID..'&main_commons_category='..pagenamee..' '..getLabel('Q26964791')..']'
hl[#hl+1] = '[https://overpass-api.de/api/interpreter?data='..mw.uri.encode('[out:custom];rel[wikidata='..QID..'];if(count(relations)==0){way[wikidata='..QID..'];if(count(ways)==0){node[wikidata='..QID..'];};};out 1;', 'PATH')..' '..getLabel('Q936')..']'
end
for i, v in ipairs( hl ) do
hl[i] = '<span style="white-space:nowrap">' .. v .. '</span>'
end
hl[#hl+1] = '[[Special:Search/haswbstatement:P180='..QID..'|'..i18n('search-depicted', FALLBACKLANGS)..']]'
hl[#hl+1] = ISTAXON and '[https://commons-query.wikimedia.org/#%23defaultView%3AImageGrid%0ASELECT%20%3Ffile%20%3Fimage%0AWITH%20%7B%0A%20%20SELECT%20%3Fitem%20WHERE%20%7B%0A%20%20%20%20SERVICE%20%3Chttps%3A%2F%2Fquery.wikidata.org%2Fsparql%3E%20%7B%0A%20%20%20%20%20%20%20%20%3Fitem%20wdt%3AP171%2Fwdt%3AP171%2a%20wd%3A'..QID..'.%0A%20%20%20%20%7D%20%0A%20%20%7D%0A%7D%20AS%20%25get_items%0AWHERE%20%7B%0A%20%20INCLUDE%20%25get_items%0A%20%20%3Ffile%20wdt%3AP180%20%3Fitem%20.%0A%20%20%3Ffile%20schema%3AcontentUrl%20%3Furl%20.%0A%20%20BIND%28IRI%28CONCAT%28%22http%3A%2F%2Fcommons.wikimedia.org%2Fwiki%2FSpecial%3AFilePath%2F%22%2C%20wikibase%3AdecodeUri%28SUBSTR%28STR%28%3Furl%29%2C53%29%29%29%29%20AS%20%3Fimage%29%0A%7D '..i18n('taxon-depicted', FALLBACKLANGS)..']'
return table.concat{
'<tr class="wdinfo_nomobile">',
'<td colspan=2 style="text-align: center"><small>',
'<div class="hlist hlist-separated"><ul>',
'<li>' .. table.concat(hl, '</li><li>') .. '</li>',
'</ul></div>',
'</small></td>',
'</tr>',
}
end
local function footer()
return (config.authoritycontrol or config.helperlinks) and table.concat{
'<tr><td colspan=2>',
'<table style="width:100%" id="wdinfo_ac" class="mw-collapsible">',
authoritycontrol(),
helperlinks(),
'</table>',
'</td></tr>',
} or ''
end
--- @param eid string: Wikidata entity ID starting with Q or P
local function entityLink( eid )
local label = getLabel( eid, true )
local ns = ( eid:sub(1, 1) == 'P' ) and 'Property:' or ''
return '[[d:'..ns..eid..'|'..label..' <small>('..eid..')</small>]]'
end
--- Generates [[Template:Wikidata Infobox/doc/properties]]
function p.doc()
local out = {}
for _, group in ipairs( property_groups ) do
out[#out+1] = '<h2>' .. group.groupname .. '</h2>'
if group.comment then
out[#out+1] = frame:preprocess( group.comment )
end
if group.P31_allowed_values then
local classes = {}
for _, class in ipairs( group.P31_allowed_values ) do
classes[#classes+1] = entityLink( class )
end
out[#out+1] = 'This group is only shown if the connected Wikidata item is an instance of ' .. table.concat(classes, ' or ') .. '.'
elseif group.humans_allowed then
out[#out+1] = 'This group is always shown.'
end
local props = {}
for _, pid in ipairs( group.pids ) do
props[#props+1] = entityLink( pid )
end
out[#out+1] = table.concat( props, ' • ' )
end
-- authority control
out[#out+1] = '<h2>'..getLabel('Q36524')..'</h2>'
out[#out+1] = 'This group is always shown.'
for _, group in ipairs( externalIDs ) do
out[#out+1] = '<h3>' .. group.groupname .. '</h3>'
local props = {}
for _, pid in ipairs( group.pids ) do
props[#props+1] = entityLink( pid )
end
out[#out+1] = table.concat( props, ' • ' )
end
return table.concat( out, '\n\n' )
end
local function configure( t )
config.defaultsort = t['defaultsort'] == 'y'
config.interwiki = t['interwiki'] == 'yes'
config.autocat = t['autocat'] == 'yes'
config.trackingcats = t['trackingcats'] == 'yes'
config.uploadlink = t['conf_upload'] == 'yes'
config.sitelinks = t['conf_sitelinks'] == 'yes'
config.authoritycontrol = t['conf_authoritycontrol'] == 'yes'
config.helperlinks = t['conf_helperlinks'] == 'yes'
if t['conf_coordtemplate'] then config.coordtemplate = tonumber( t['conf_coordtemplate'] ) end
if t['conf_mapwidth'] then config.mapwidth = t['conf_mapwidth'] end
if t['conf_mapheight'] then config.mapheight = t['conf_mapheight'] end
if t['conf_imagesize'] then config.imagesize = t['conf_imagesize'] end
if t['spf'] then config.spf = t['spf'] end
if t['fwd'] then config.fwd = t['fwd'] end
if t['osd'] then config.osd = t['osd'] end
if t['noicon'] then config.noicon = t['noicon'] end
end
function p.main( frame )
MYLANG = frame:callParserFunction( 'int', 'lang' ) or "en"
LANG = mw.language.new( MYLANG )
FALLBACKLANGS = { MYLANG, unpack(mw.language.getFallbacksFor(MYLANG)) }
QID = frame.args[1]
ITEM = mw.wikibase.getEntity( QID )
if not ITEM then
return '[[Category:Uses of Wikidata Infobox for deleted Wikidata items]]'
end
CLAIMS = ITEM.claims
if not CLAIMS then
local msg = i18n('noclaims', FALLBACKLANGS):gsub('$1', '[[d:'..QID..'|'..QID..']]' )
return '[[Category:Uses of Wikidata Infobox with no claims]]<table id="wdinfobox" class="fileinfotpl-type-information vevent infobox mw-content-'..LANG:getDir()..'"><tr><td><strong class="error">'..msg..'</strong></td></tr>'
end
-- identifying a taxon by checking whether it has a taxon property is faster than checking whether its P31 value is a subclass of taxon
ISTAXON = CLAIMS['P105'] or CLAIMS['P171'] or CLAIMS['P225'] or CLAIMS['P1843']
local parentframe = frame:getParent()
if parentframe then
configure( parentframe.args )
end
for class in iclaims( ITEM:getBestStatements('P31') ) do
INSTANCEOF[class.id] = true
end
local out = {
metadata(),
'<table id="wdinfobox" class="fileinfotpl-type-information vevent infobox mw-collapsible mw-content-'..LANG:getDir()..'">',
'<caption class="fn org" id="wdinfoboxcaption">',
'<b>' .. (ITEM:getLabel() or QID) .. ' </b>',
'</caption>',
header( config.uploadlink ),
body(),
footer(),
pencil(),
'</table>',
}
if config.trackingcats and os.clock() > 2.5 then -- longer than 2.5 seconds
out[#out+1] = '[[Category:Uses of Wikidata Infobox with bad performance]]'
end
return table.concat( out )
end
function p.debug( qid )
frame.args = { qid or 'Q42' }
return p.main( frame )
end
return p
-- Credits:
-- Original authors: Mike Peel with contributions by Jura1
-- 2022 rewrite: LennardHofmann