Mòdul:Wikidata
Aspet
La documentazione per questo modulo può essere creata in Mòdul:Wikidata/man
--[[
* Modulo per implementare le funzionalità dei template:
* {{Wikidata}}, {{WikidataQ}}, {{WikidataIdx}}, {{WikidataN}},
* {{WikidataLabel}}, {{WikidataLink}}, {{WikidataTipo}} e {{WikidataId}}.
* 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
local mConvert = require('Module:Conversione')
local mLanguages = require('Module:Linguaggi')
-- Categoria per le pagine con errori
local errorCategory = '[[Categoria:Voci con errori del modulo Wikidata]]'
local p = {}
-- 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",
["entity-not-found"] = "Entità non trovata",
["unknown-claim-type"] = "Tipo asserzione sconosciuta",
["unknown-snak-type"] = "Tipo di snak sconosciuto",
["unknown-datavalue-type"] = "Tipo di dato sconosciuto",
["unknown-entity-type"] = "Tipo di entità sconosciuta"
},
["somevalue"] = "''valore sconosciuto''",
["novalue"] = "''nessun valore''"
}
-------------------------------------------------------------------------------
-- 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 formatList(values, ordered)
local fmt = ordered and '<ol><li>%s</li></ol>' or '<ul><li>%s</li></ul>'
return #values > 0 and string.format(fmt, mw.text.listToText(values, '</li><li>', '</li><li>')) or ''
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('<div style="word-break: break-all;">[%s %s]</div>', 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 and label ~= link then
return '[[' .. link .. '|' .. label .. ']]'
else
return '[[' .. link .. ']]'
end
else
return label or ''
end
end
local function formatMonolingualtext(value, args)
local ret = ''
if not args.includelang or args.includelang:match('%f[a-z]' .. value.language .. '%f[^a-z]') then
if not args.excludelang or not args.excludelang:match('%f[a-z]' .. value.language .. '%f[^a-z]') then
ret = value.text
if args.showlang then
ret = mLanguages.lingue( { value.language } ) .. ' ' .. ret
end
end
end
return ret
end
local function formatTimeWithPrecision(time, precision)
local months = {
'genar', 'febrar', 'marz', 'april', 'magg', 'giugn',
'luj', 'agost', 'settember', 'ottober', 'november', 'dicember'
}
local ret, year, month, day
year, month, day = time:match('(%d+)%-(%d%d)%-(%d%d).+')
year, month, day = tonumber(year), tonumber(month), tonumber(day)
if precision == 9 then
ret = year
elseif precision == 10 then
ret = months[month] .. ' ' .. year
elseif precision == 11 then
ret = day .. ' ' .. months[month] .. ' ' .. year
ret = ret:gsub('^1%s', '1º ')
end
if precision >= 9 and precision <= 11 then
ret = ret .. (time:sub(1, 1) == '-' and ' a.C.' or '')
end
return ret
end
local function formatTime(value, args)
local ret
if args.time == 'precision' then
ret = value.precision
elseif args.time == 'calendarmodel' then
ret = value.calendarmodel
elseif args.time == 'year' and value.precision >= 9 then
ret = formatTimeWithPrecision(value.time, 9)
elseif args.time == 'month' and value.precision >= 10 then
ret = formatTimeWithPrecision(value.time, 10)
elseif args.time == 'day' and value.precision >= 11 then
ret = formatTimeWithPrecision(value.time, 11)
elseif not args.time then
ret = formatTimeWithPrecision(value.time, value.precision)
end
return ret or ''
end
local function formatGlobecoordinate(value, args)
local ret
if args.coord == 'latitude' then
ret = value.latitude
elseif args.coord == 'longitude' then
ret = value.longitude
elseif args.coord == 'globe' then
ret = value.globe
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 formatUserValue(value, args)
if args.extlink then
value = formatExtLink(value)
end
return args.pattern and formatFromPattern(value, args) or value
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 formatUnitSymbol(entityId, args)
local ret = p._getProperty( { 'P558', n = 1, from = entityId } )
local space = ret == '°' and '' or ' '
if ret and args.showunitlink then
local link = mw.wikibase.sitelink(entityId)
if link then
ret = string.format('[[%s|%s]]', link, ret)
end
end
return ret and (space .. ret) or ''
end
-- http://lua-users.org/wiki/SimpleRound
local function round(num, idp)
local mult = 10 ^ (idp or 0)
return math.floor(num * mult + 0.5) / mult
end
local function formatQuantity(value, args)
local ret = tonumber(value.amount)
if (args.unit or args.showunit) and value.unit ~= '1' then
local unitId = mw.ustring.match(value.unit, 'Q%d+')
if args.unit then
local opts = {
showunit = args.showunit,
showunitlink = args.showunitlink,
formatnum = args.formatnum,
rounding = args.rounding
}
ret = mConvert._main(ret, unitId, args.unit, opts)
else
-- se è richiesto solo il simbolo dell'unità
-- senza la conversione lo ottiene da P558
ret = args.rounding and round(ret, args.rounding) or ret
if args.formatnum then
ret = mw.language.getContentLanguage():formatNum(ret)
end
ret = ret .. formatUnitSymbol(unitId, args)
end
elseif args.formatnum then
ret = args.rounding and round(ret, args.rounding) or ret
ret = mw.language.getContentLanguage():formatNum(ret)
elseif args.formatduration and value.unit ~= '1' then
local unitId = mw.ustring.match(value.unit, 'Q%d+')
ret = mConvert._main(ret, unitId, 'second')
ret = ret and mw.language.getContentLanguage()
:formatDuration(tonumber(ret), { 'days', 'hours', 'minutes', 'seconds' } )
end
return ret
end
local function formatDatavalue(datavalue, snakdatatype, args)
local ret
if datavalue.type == 'wikibase-entityid' then
local entityId = getEntityIdFromValue(datavalue.value)
if args.showprop then
ret = p._getProperty( { args.showprop, n = 1, from = entityId } ) or ''
else
ret = args.formatting == 'raw' and entityId or formatEntityId(entityId)
end
elseif datavalue.type == 'string' then
ret = datavalue.value
if args.extlink and snakdatatype == 'url' then
ret = formatExtLink(ret)
elseif args.urlencode then
ret = mw.uri.encode(ret)
end
elseif datavalue.type == 'monolingualtext' then
ret = formatMonolingualtext(datavalue.value, args)
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 = formatQuantity(datavalue.value, args)
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, snak.datatype, args)
else
error(i18n.errors['unknown-snak-type'])
end
end
-- È al plurale perché anche i qualifier possono avere più di un valore
-- (si ottiene inserendo due volte lo stesso qualifier)
local function formatQualifiers(claim, qualifier, args, rawTable, retTable)
local formattedQualifiers = retTable or {}
if claim.qualifiers and claim.qualifiers[qualifier] then
local qualifiers = claim.qualifiers[qualifier]
-- con args.nq seleziona solo l'n-esimo qualifier
if args.nq then
local n = tonumber(args.nq)
qualifiers = (n and n <= #qualifiers) and { qualifiers[n] } or {}
end
for _, q in pairs(qualifiers) do
local formattedQualifier = formatSnak(q, args)
if formattedQualifier ~= '' then
if args.pattern then
formattedQualifier = formatFromPattern(formattedQualifier, args)
end
table.insert(formattedQualifiers, formattedQualifier)
end
end
end
if rawTable then
return formattedQualifiers
end
return #formattedQualifiers > 0 and
mw.text.listToText(formattedQualifiers, args.separator, args.conjunction) or nil
end
local function appendQualifiers(statement, text, args)
local formattedQualifiers = {}
local qualifiers = mw.text.split(args.showqualifiers, ',')
for _, qualifier in ipairs(qualifiers) do
if statement.qualifiers[qualifier] then
local formattedQualifier = formatQualifiers(statement, qualifier, args)
table.insert(formattedQualifiers, formattedQualifier)
end
end
if #formattedQualifiers > 0 then
text = string.format('%s (%s)', text, mw.text.listToText(formattedQualifiers, ', ', ', '))
end
return text
end
local function formatStatement(statement, args)
if not statement.type or statement.type ~= 'statement' then
error(i18n.errors['unknown-claim-type'])
end
local ret = formatSnak(statement.mainsnak, args)
-- eventuale showqualifiers
if args.showqualifiers and statement.qualifiers then
ret = appendQualifiers(statement, ret, args)
end
return ret
end
local function formatStatements(claims, args, rawTable)
local formattedStatements = {}
for i, claim in pairs(claims) do
local formattedStatement = formatStatement(claim, args)
if formattedStatement ~= '' then
-- eventuale pattern
if args.pattern then
formattedStatement = formatFromPattern(formattedStatement, args)
end
table.insert(formattedStatements, formattedStatement)
end
end
if rawTable then
return formattedStatements
end
return ((args.list or args.orderedlist) and #formattedStatements > 1) and
formatList(formattedStatements, args.orderedlist ~= nil) or
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, qualifierId, qualifierValue)
local ret = false
for i, qualifier in pairs(statement.qualifiers[qualifierId]) do
local isItem = qualifier.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 {} ) == 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.getEntity(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 (default 'best')
args.rank = args.rank or 'best'
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
-- 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.qualifier, args.qualifiervalue) then
table.insert(filteredClaims, claim)
end
else
table.insert(filteredClaims, claim)
end
end
end
claims = filteredClaims
end
-- statements filtrati per essere senza un qualifier
if args.noqualifier then
filteredClaims = {}
for i, claim in pairs(claims) do
if not (claim.qualifiers and claim.qualifiers[args.noqualifier]) then
table.insert(filteredClaims, claim)
end
end
claims = filteredClaims
end
-- statements filtrati per non avere un certo valore a un certo qualifier opzionale
if args.qualifieroptnovalue and args.qualifiervalue then
filteredClaims = {}
for i, claim in pairs(claims) do
if claim.qualifiers and claim.qualifiers[args.qualifieroptnovalue] then
if not hasQualifierValue(claim, args.qualifieroptnovalue, args.qualifiervalue) then
table.insert(filteredClaims, claim)
end
else
table.insert(filteredClaims, claim)
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
-------------------------------------------------------------------------------
function p._getClaims(property, args)
return getClaims(property, args or {})
end
function p._formatStatement(statement, args)
return formatStatement(statement, args or {})
end
function p._formatQualifiers(claim, qualifier, args, rawTable, retTable)
return formatQualifiers(claim, qualifier, args or {}, rawTable, retTable)
end
-- 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, ret
-- 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 value then
ret = formatUserValue(value, args)
elseif args.wd ~= 'no' then
claims = getClaims(property, args)
ret = (claims and #claims > 0) and formatStatements(claims, args, rawTable) or nil
end
return ret
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, ret
-- 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 value then
ret = formatUserValue(value, args)
elseif args.wd ~= 'no' then
claims = getClaims(property, args)
if claims and #claims > 0 then
local formattedQualifiers = {}
for _, claim in pairs(claims) do
formattedQualifiers = formatQualifiers(claim, qualifier, args, true, formattedQualifiers)
end
ret = #formattedQualifiers > 0 and
mw.text.listToText(formattedQualifiers, args.separator, args.conjunction) or nil
end
end
return ret
end
-- Ritorna l'indice dello statement con il valore richiesto, o nil 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
claims = getClaims(property, args)
if claims and #claims > 0 then
args.formatting = 'raw'
for i, claim in pairs(claims) do
if formatStatement(claim, args) == value then
ret = i
break
end
end
end
return ret
end
-- Ritorna il numero di statement di una proprietà di Wikidata.
function p._N(args)
local property, 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
-- get claims
claims = getClaims(property, args)
return claims and #claims or 0
end
--[[
* La proprietà specificata ha come valore almeno uno tra gli entityId specificati?
*
* @param string property Wikidata Property ID
* @param table args values (mixed with .from)
* @return boolean
]]
function p._propertyHasEntity(property, args)
local statements = p._getProperty( {property, 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 true se la proprietà P31 (instance of) ha come valore almeno uno tra gli entityId specificati
function p._instanceOf(args)
return p._propertyHasEntity('P31', args)
end
-- Ritorna true se la proprietà P279 (subclass of) ha come valore almeno uno tra gli entityId specificati
function p._subclassOf(args)
return p._propertyHasEntity('P279', args)
end
-- Ritorna l'etichetta di un item o di una proprietà Wikidata.
function p._getLabel(args)
-- parametri posizionali
local entityId = args[1] and string.upper(args[1]) or nil
local langCode = args[2]
local entity = mw.wikibase.getEntity(entityId)
if not entity then
error(i18n.errors['entity-not-found'], 2)
end
return entity:getLabel(langCode)
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 entityId:sub(1, 1) == 'Q' and formatEntityId(entityId) or nil
end
-- Ritorna il datatype di una proprietà Wikidata.
function p._getDatatype(args)
local ret, property, entity, datatype
-- 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.getEntity(property)
if not entity then
error(i18n.errors['entity-not-found'], 2)
end
datatype = entity.datatype
if datatype == 'commonsMedia' then
ret = 'file multimediale su Commons'
elseif datatype == 'globe-coordinate' then
ret = 'coordinate geografiche'
elseif datatype == 'monolingualtext' then
ret = 'testo monolingua'
elseif datatype == 'quantity' then
ret = 'quantità'
elseif datatype == 'string' then
ret = 'stringa'
elseif datatype == 'time' then
ret = 'data e ora'
elseif datatype == 'url' then
ret = 'URL'
elseif datatype == 'external-id' then
ret = 'identificatore esterno'
elseif datatype == 'wikibase-item' then
ret = 'elemento'
elseif datatype == 'wikibase-property' then
ret = 'proprietà'
elseif datatype == 'math' then
ret = 'espressione matematica'
else
error(i18n.errors['unknown-datavalue-type'], 2)
end
return ret
end
-- Ritorna l'ID dell'item Wikidata collegato alla pagina corrente.
function p._getId()
return mw.wikibase.getEntityIdForCurrentPage()
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 {{WikidataLabel}}
function p.getLabel(frame)
return select(2, xpcall(function()
return p._getLabel(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 ''
end, errhandler))
end
-- Entry-point per {{WikidataTipo}}
function p.getDatatype(frame)
return select(2, xpcall(function()
return p._getDatatype(getArgs(frame, {parentOnly = true}))
end, errhandler))
end
-- Entry-point per {{WikidataId}}
function p.getId(frame)
return select(2, xpcall(function()
return p._getId()
end, errhandler))
end
return p