Module:Wikidata
Appearance
Documentation for this module may be created at Module:Wikidata/doc
--[[
* Modulo per implementare le funzionalità dei template:
* {{Wikidata}}, {{WikidataQ}}, {{WikidataIdx}}, {{WikidataN}} e {{WikidataLink}}.
* Permette di accedere a Wikidata in modo più avanzato rispetto a {{#property}}.
* Il modulo è stato importato inizialmente da:
* http://test2.wikipedia.org/w/index.php?title=Module:Wikidata&oldid=52322
]]
require('Module:No globals')
local getArgs = require('Module:Arguments').getArgs
-- Categoria per le pagine con errori
local errorCategory = '[[Categoria:Voci con errori del modulo Wikidata]]'
-- Messaggi di errore
local i18n = {
["errors"] = {
["entityid-param-not-provided"] = "Parametro ''entityid'' non fornito",
["property-param-not-provided"] = "Parametro ''property'' non fornito",
["qualifier-param-not-provided"] = "Parametro ''qualifier'' non fornito",
["value-param-not-provided"] = "Parametro ''valore'' da ricercare non fornito",
["unknown-snak-type"] = "Tipo di snak sconosciuto",
["unknown-datavalue-type"] = "Tipo di dato sconosciuto",
["property-not-found"] = "Property not found.",
["entity-not-found"] = "Wikidata entity not found.",
["unknown-claim-type"] = "Unknown claim type.",
["unknown-entity-type"] = "Unknown entity type.",
["qualifier-not-found"] = "Qualifier not found.",
["site-not-found"] = "Wikimedia project not found.",
},
["somevalue"] = "''valore sconosciuto''",
["novalue"] = "''nessun valore''",
["datetime"] =
{
-- $1 is a placeholder for the actual number
[0] = "$1 billion years", -- precision: billion years
[1] = "$100 million years", -- precision: hundred million years
[2] = "$10 million years", -- precision: ten million years
[3] = "$1 million years", -- precision: million years
[4] = "$100,000 years", -- precision: hundred thousand years
[5] = "$10,000 years", -- precision: ten thousand years
[6] = "$1 millenium", -- precision: millennium
[7] = "$1 century", -- precision: century
[8] = "$1s", -- precision: decade
-- the following use the format of #time parser function
[9] = "Y", -- precision: year,
[10] = "F Y", -- precision: month
[11] = "F j, Y", -- precision: day
[12] = "F j, Y ga", -- precision: hour
[13] = "F j, Y g:ia", -- precision: minute
[14] = "F j, Y g:i:sa", -- precision: second
["beforenow"] = "$1 BCE", -- how to format negative numbers for precisions 0 to 5
["afternow"] = "$1 CE", -- how to format positive numbers for precisions 0 to 5
["bc"] = '$1 "BCE"', -- how print negative years
["ad"] = "$1" -- how print positive years
},
["monolingualtext"] = '<span lang="%language">%text</span>',
["warnDump"] = "[[Category:Called function 'Dump' from module Wikidata]]"
}
-------------------------------------------------------------------------------
-- Formatters
-------------------------------------------------------------------------------
local function errhandler(msg)
local cat = mw.title.getCurrentTitle().namespace == 0 and errorCategory or ''
return string.format('<span class="error">%s</span>%s', msg, cat)
end
local function formatExtLink(url)
local protocols = { ftp = true, http = true, https = true }
local success, uri = pcall(function() return mw.uri.new(url) end)
if success and uri.protocol and protocols[uri.protocol] then
local dest = tostring(uri)
return string.format('[%s %s]', dest, dest:gsub(uri.protocol .. '://', ''))
else
return url
end
end
local function formatEntityId(entityId)
local label = mw.wikibase.label(entityId)
local link = mw.wikibase.sitelink(entityId)
if link then
if label then
return '[[' .. link .. '|' .. label .. ']]'
else
return '[[' .. link .. ']]'
end
else
return label or ''
end
end
local function formatTime(value, args)
local year, month, day
local ret = ''
year, month, day = value.time:match('(%d+)%-(%d%d)%-(%d%d).+')
if value.precision == 9 then
ret = tonumber(year)
elseif value.precision == 10 then
ret = mw.getLanguage('it'):formatDate('F Y', tonumber(year) .. '-' .. month)
elseif value.precision == 11 then
ret = mw.getLanguage('it'):formatDate('j F Y', tonumber(year) .. '-' .. month .. '-' .. day)
ret = ret:gsub('^1%s', '1º ')
end
if value.precision >= 9 and value.precision <= 11 then
ret = ret .. (value.time:sub(1, 1) == '-' and ' a.C.' or '')
end
return ret
end
local function formatGlobecoordinate(value, args)
local ret
if args.formatting == 'latitude' then
ret = value.latitude
elseif args.formatting == 'longitude' then
ret = value.longitude
else
ret = value.latitude .. ', ' .. value.longitude
end
return ret
end
local function formatFromPattern(str, args)
local pattern = args.pattern
pattern = mw.ustring.gsub(pattern, '\\{', '{')
pattern = mw.ustring.gsub(pattern, '\\}', '}')
return mw.getCurrentFrame():preprocess(mw.message.newRawMessage(pattern, str):plain())
end
local function getEntityIdFromValue(value)
local prefix = ''
if value['entity-type'] == 'item' then
prefix = 'Q'
elseif value['entity-type'] == 'property' then
prefix = 'P'
else
error(i18n.errors['unknown-entity-type'])
end
return prefix .. value['numeric-id']
end
local function formatDatavalue(datavalue, args)
local ret
--Default formatters
if datavalue.type == 'wikibase-entityid' then
if args.formatting == 'raw' then
ret = getEntityIdFromValue(datavalue.value)
else
ret = formatEntityId(getEntityIdFromValue(datavalue.value))
end
elseif datavalue.type == 'string' then
ret = datavalue.value
if args.formatting == 'extlink' then
ret = formatExtLink(ret)
end
elseif datavalue.type == 'monolingualtext' then
ret = datavalue.value.text
elseif datavalue.type == 'time' then
if args.formatting == 'raw' then
ret = datavalue.value.time
else
ret = formatTime(datavalue.value, args)
end
elseif datavalue.type == 'globecoordinate' then
ret = formatGlobecoordinate(datavalue.value, args)
elseif datavalue.type == 'quantity' then
ret = tonumber(datavalue.value.amount)
else
error(i18n.errors['unknown-datavalue-type'])
end
return ret
end
local function formatSnak(snak, args)
if snak.snaktype == 'somevalue' then
return i18n['somevalue']
elseif snak.snaktype == 'novalue' then
return i18n['novalue']
elseif snak.snaktype == 'value' then
return formatDatavalue(snak.datavalue, args)
else
error(i18n.errors['unknown-snak-type'])
end
end
local function formatStatement(statement, args)
if not statement.type or statement.type ~= 'statement' then
error(i18n.errors['unknown-claim-type'])
end
return formatSnak(statement.mainsnak, args)
end
local function formatStatements(claims, args, rawTable)
local formattedStatements = {}
local formattedStatement
for i, claim in pairs(claims) do
formattedStatement = formatStatement(claim, args)
-- eventuale pattern
if args.pattern then
formattedStatement = formatFromPattern(formattedStatement, args)
end
table.insert(formattedStatements, formattedStatement)
end
if rawTable then
return formattedStatements
end
if args.list or args.orderedlist then
table.insert(formattedStatements, 1, args.list and '<ul><li>' or '<ol><li>');
table.insert(formattedStatements, args.list and '</li></ul>' or '</li></ol>');
args.separator = '</li><li>'
args.conjunction = args.separator
end
return mw.text.listToText(formattedStatements, args.separator, args.conjunction)
end
-------------------------------------------------------------------------------
-- Lettura e selezione statement
-------------------------------------------------------------------------------
-- Ritorna true se lo statement contiene il qualifier richiesto con un dato valore
local function hasQualifierValue(statement, args)
local ret = false
for i, qualifier in pairs(statement.qualifiers[args.qualifier]) do
local isItem = qualifier.datavalue.snaktype == 'value' and
qualifier.datavalue.type == 'wikibase-entityid'
-- per le proprietà di tipo item il confronto è eseguito sull'id
if formatSnak(qualifier, isItem and { formatting = 'raw' } or {} ) == args.qualifiervalue then
ret = true
break
end
end
return ret
end
-- Ritorna i claim con il rank richiesto
local function filterRankValue(claims, rank)
local ret = {}
for i, claim in pairs(claims) do
if claim.rank == rank then
table.insert(ret, claim)
end
end
return ret
end
-- Ritorna una table contenente gli statement per la property richiesta,
-- oppure nil se l'entity o la proprietà non esistono.
-- Gli statement ritornati sono eventualmente filtrati in base ai parametri:
-- "rank", "qualifier", "qualifiertype" e "n"
local function getClaims(property, args)
local entity, claims, filteredClaims
-- get entity
entity = mw.wikibase.getEntityObject(args.from)
if not entity then
return nil
end
if property and entity.claims and entity.claims[property] and
#entity.claims[property] > 0 then
claims = entity.claims[property]
else
return nil
end
-- statements filtrati per rank
if args.rank then
if args.rank == 'best' then
filteredClaims = filterRankValue(claims, 'preferred')
if #filteredClaims == 0 then
filteredClaims = filterRankValue(claims, 'normal')
end
else
filteredClaims = filterRankValue(claims, args.rank)
end
claims = filteredClaims
end
-- statements filtrati per qualifier
if args.qualifier then
filteredClaims = {}
for i, claim in pairs(claims) do
if claim.qualifiers and claim.qualifiers[args.qualifier] then
if args.qualifiervalue then
if hasQualifierValue(claim, args) then
table.insert(filteredClaims, claim)
end
else
table.insert(filteredClaims, claim)
end
end
end
claims = filteredClaims
end
-- con args.qualifiertype=latest ritorna solo il più recente
if args.qualifier and args.qualifiertype == 'latest' then
local latest, latestTime
for i, claim in pairs(claims) do
if claim.qualifiers and claim.qualifiers[args.qualifier] then
for j, qualifier in pairs(claim.qualifiers[args.qualifier]) do
if qualifier.datavalue.type == 'time' then
if not latestTime or qualifier.datavalue.value.time > latestTime then
latest = claim
latestTime = qualifier.datavalue.value.time
end
end
end
end
end
claims = latest and {latest} or {}
end
-- con args.n ritorna solo l'n-esimo elemento
if args.n then
local n = tonumber(args.n)
claims = (n and n <= #claims) and {claims[n]} or {}
end
return claims
end
-------------------------------------------------------------------------------
-- API
-------------------------------------------------------------------------------
local p = {}
-- Ritorna il valore di una proprietà di Wikidata oppure nil se l'entity o
-- la proprietà non esistono, o se per parametri di selezione gli statement sono zero.
function p._getProperty(args, rawTable)
local property, value, claims
-- parametri posizionali
property = args[1] and string.upper(args[1]) or nil
if not property then
error(i18n.errors['property-param-not-provided'], 2)
end
value = args[2]
-- fix uppercase
args.qualifier = args.qualifier and string.upper(args.qualifier) or nil
-- if parameter value is already set, use it
if value then
if args.formatting == 'extlink' then
value = formatExtLink(value)
end
return args.pattern and formatFromPattern(value, args) or value
end
-- default rank
args.rank = args.rank or 'best'
-- get claims
claims = getClaims(property, args)
return (claims and #claims > 0) and formatStatements(claims, args, rawTable) or nil
end
-- Ritorna il valore di un qualifier di una proprietà di Wikidata,
-- o nil se l'entity o la proprietà non esistono, o se per parametri di selezione non ci sono risultati.
function p._getQualifier(args)
local property, qualifier, value, claims, formattedQualifier, formattedQualifiers
-- parametri posizionali
property = args[1] and string.upper(args[1]) or nil
if not property then
error(i18n.errors['property-param-not-provided'], 2)
end
qualifier = args[2] and string.upper(args[2]) or nil
if not qualifier then
error(i18n.errors['qualifier-param-not-provided'], 2)
end
value = args[3]
-- if parameter value is already set, use it
if value then
return args.pattern and formatFromPattern(value, args) or value
end
-- default rank
args.rank = args.rank or 'best'
-- get claims
claims = getClaims(property, args)
if not claims or #claims == 0 then
return nil
end
-- get qualifiers and format them
formattedQualifiers = {}
for i, claim in pairs(claims) do
if claim.qualifiers and claim.qualifiers[qualifier] then
for j, q in pairs(claim.qualifiers[qualifier]) do
formattedQualifier = formatSnak(q, args)
if args.pattern then
formattedQualifier = formatFromPattern(formattedQualifier, args)
end
table.insert(formattedQualifiers, formattedQualifier)
end
end
end
return #formattedQualifiers > 0 and
mw.text.listToText(formattedQualifiers, args.separator, args.conjunction) or nil
end
-- Ritorna l'indice dello statement con il valore richiesto, o -1 se non trovato.
function p._indexOf(args)
local ret, property, value, claims
-- parametri posizionali
property = args[1] and string.upper(args[1]) or nil
if not property then
error(i18n.errors['property-param-not-provided'], 2)
end
value = args[2]
if not value then
error(i18n.errors['value-param-not-provided'], 2)
end
-- default rank
args.rank = args.rank or 'best'
-- get claims
claims = getClaims(property, args)
if not claims or #claims == 0 then
return -1
end
args.formatting = 'raw'
for i, claim in pairs(claims) do
if formatStatement(claim, args) == value then
ret = i
break
end
end
return ret or -1
end
-- Ritorna il numero di statement di una proprietà di Wikidata.
function p._N(args)
local entity, property, count
-- parametri posizionali
property = args[1] and string.upper(args[1]) or nil
if not property then
error(i18n.errors['property-param-not-provided'], 2)
end
entity = mw.wikibase.getEntityObject(args.from)
if entity and entity.claims and entity.claims[property] then
count = #entity.claims[property]
end
return count or 0
end
-- Ritorna true se la proprietà P31 (instance of) ha come valore almeno uno tra gli entityId specificati
function p._instanceOf(args)
local statements = p._getProperty( { 'P31', from = args.from, formatting = 'raw' }, true)
if statements then
for _, statement in ipairs(statements) do
for _, entityId in ipairs(args) do
if statement == entityId then
return true
end
end
end
end
return false
end
-- Ritorna il titolo della pagina collegata a un dato item Wikidata.
function p._getLink(args)
-- parametri posizionali
local entityId = args[1] and string.upper(args[1]) or nil
if not entityId then
error(i18n.errors['entityid-param-not-provided'], 2)
end
return formatEntityId(entityId)
end
-- Entry-point per {{Wikidata}}
function p.getProperty(frame)
return select(2, xpcall(function()
return p._getProperty(getArgs(frame, {parentOnly = true}))
end, errhandler))
end
-- Entry-point per {{WikidataQ}}
function p.getQualifier(frame)
return select(2, xpcall(function()
return p._getQualifier(getArgs(frame, {parentOnly = true}))
end, errhandler))
end
-- Entry-point per {{WikidataIdx}}
function p.indexOf(frame)
return select(2, xpcall(function()
return p._indexOf(getArgs(frame, {parentOnly = true}))
end, errhandler))
end
-- Entry-point per {{WikidataN}}
function p.N(frame)
return select(2, xpcall(function()
return p._N(getArgs(frame, {parentOnly = true}))
end, errhandler))
end
-- Entry-point per {{WikidataLink}}
function p.getLink(frame)
return select(2, xpcall(function()
return p._getLink(getArgs(frame, {parentOnly = true}))
end, errhandler))
end
-- Entry-point per {{WikidataIstanza}}
function p.instanceOf(frame)
return select(2, xpcall(function()
return p._instanceOf(getArgs(frame, {parentOnly = true})) and 1 or 0
end, errhandler))
end
-- returns the page id (Q...) of the current page or nothing of the page is not connected to Wikidata
function p.pageId(frame)
local entity = mw.wikibase.getEntityObject()
if not entity then return nil else return entity.id end
end
function p.Dump(frame)
local data = mw.wikibase.getEntityObject()
if not data then
return i18n.warnDump
end
local f = frame.args[1] and frame or frame:getParent()
local i = 1
while true do
local index = f.args[i]
if not index then
return "<pre>"..mw.dumpObject(data).."</pre>".. i18n.warnDump
end
data = data[index] or data[tonumber(index)]
if not data then
return i18n.warnDump
end
i = i + 1
end
end
return p