Модуль:Песочница/Игорь Темиров/Wikidata
Перейти к навигации
Перейти к поиску
Документация
--script that retrieves basic data stored in Wikidata, for the datamodel, see https://www.mediawiki.org/wiki/Extension:Wikibase_Client/Lua
local p = {}
local fb = require('Модуль:Песочница/Игорь Темиров/Fallback')
local linguistic = require('Модуль:Песочница/Игорь Темиров/Linguistic')
local i18n = {
["errors"] = {
["property-param-not-provided"] = "property parameter missing",
["qualifier-param-not-provided"] = "qualifier parameter missing",
["entity-not-found"] = "entity not found",
["unknown-claim-type"] = "unknown claim type",
["unknown-snak-type"] = "unknown snak type",
["unknown-datavalue-type"] = "unknown datavalue type.",
["unknown-entity-type"] = "unknown entity type",
["invalid-id"] = "invalid ID"
},
["no-label"] = "no label",
['no description'] = "no description",
["novalue"] = "not applicable"
}
local function formatError( key )
return '<span class="error">' .. i18n.errors[key] .. '</span>'
end
local function formatLink(value)
local link = value
local showntext = value
if string.sub(showntext, 1, 7) == "http://" then
showntext = string.sub(showntext,8 )
end
if string.sub(showntext, -1) == '/' then -- when last char is a char, should possibly be fixed directly in the item instead ?
showntext = string.sub(showntext, 1, #showntext - 1)
end
return '[' .. link .. ' ' .. showntext .. ']'
end
local function samevalue(snak, target)
if snak.snaktype == 'value' and p.getRawvalue(snak) == target then
return true
end
end
local function getEntityFromId( id )
return mw.wikibase.getEntity(id)
end
local function formattable(statements, params) -- transform a table of claims into a table of formatted values
for i, j in pairs(statements) do
j = p.formatStatement(j, params)
end
return statements
end
local function tableToText(values, params) -- takes a list of already formatted values and make them a text
if not values then
return nil
end
return values, params.lang, params.conjtype--linguistic.conj( values, params.lang, params.conjtype )
end
local function getDate(statement)
--[[
return a "timedata" object as used by the date modules with the date of an item from the p580, p582 and p585 qualifiers
object format:
* timestamp 28 char string value for the timepoint, or if non the beginning or if none, the end (for easy sorting)
* timepoint: snak
* begin: snak
]]--
local timedata = {}
local q = statement.qualifiers
if not q or not (q.P585 or q.P580 or q.P582) then
return nil
end
if q.P582 and q.P582[1].snaktype == 'value' then
timedata.endpoint = q.P582[1].datavalue.value
timedata.timestamp = q.P582[1].datavalue.value.time
end
if q.P580 and q.P580[1].snaktype == 'value' then
timedata.startpoint = q.P580[1].datavalue.value
timedata.timestamp = q.P580[1].datavalue.value.time
end
if q.P585 and q.P585[1].snaktype == 'value' then
timedata.timepoint = q.P585[1].datavalue.value
timedata.timestamp = q.P585[1].datavalue.value.time
end
return timedata
end
local function withtargetvalue(claims, targetvalue)
targetvalue = string.upper(targetvalue)
local oldclaims = claims
local claims = {}
for i, statement in pairs(oldclaims) do
if samevalue(statement.mainsnak, targetvalue) then
table.insert(claims, statement)
end
end
return claims
end
local function validclaims(claims)
local oldclaims = claims
local claims = {}
for i, statement in pairs(oldclaims) do
if statement.rank == 'preferred' or statement.rank == 'normal' then
table.insert(claims, statement)
end
end
return claims
end
local function withrank(claims, rank)
if rank == 'best' then
local preferred = withrank(claims, 'preferred')
if #preferred > 0 then
return preferred
else
return withrank(claims, 'normal')
end
end
if rank == 'valid' then
return validclaims(claims)
end
local oldclaims = claims
local claims = {}
for i, statement in pairs(oldclaims) do
if statement.rank == rank then
table.insert(claims, statement)
end
end
return claims
end
local function withqualifier(claims, qualifier, qualifiervalue)
qualifier, qualifiervalue = string.upper(qualifier), string.upper(qualifiervalue or '')
local oldclaims = claims
local claims = {}
for i, statement in pairs(oldclaims) do
if statement.qualifiers and statement.qualifiers then
if qualifiervalue ~= '' then
for j, qualif in pairs(statement.qualifiers[qualifier]) do
if p.getRawvalue(qualif) == qualifiervalue then
table.insert(claims, statement)
end
end
else
table.insert(claims, statement)
end
end
end
return claims
end
local function withsource(claims, source, sourceproperty)
local oldclaims = claims
local claims = {}
sourceproperty = string.upper(sourceproperty or 'P248')
local sourcevalue = string.upper(source or '')
for i, statement in pairs(oldclaims) do
local success
if statement.references then
for j, reference in pairs(statement.references) do
if success then break end -- sp that it does not return twice the same reference when the property is used twice
for prop, content in pairs(reference.snaks) do
if prop == sourceproperty then
if sourcevalue == '' then
table.insert(claims, statement)
success = true
else
for l, m in pairs(content) do
if p.getRawvalue(m) == source then
table.insert(claims, statement)
success = true
end
end
end
end
end
end
end
end
return claims
end
local function excludespecial(claims)
local oldclaims = claims
local claims = {}
for i, statement in pairs(oldclaims) do
if statement.mainsnak.snaktype == 'value' then
table.insert(claims, statement)
end
end
return claims
end
local function comparedate(a, b) -- returns true if a is earlier than B or if a has a date but not b
if a and b then
return a.timestamp < b.timestamp
elseif a then
return true
end
end
local function chronosort(claims, inverted)
table.sort(claims, function(a,b)
local timeA = getDate(a)
local timeB = getDate(b)
if inverted then
return comparedate(timeB, timeA)
else
return comparedate(timeA, timeB)
end
end
)
return claims
end
function p.sortclaims(claims, sorttype)
if sorttype == 'chronological' then
return chronosort(claims)
elseif sorttype == 'inverted' then
return chronosort(claims, true)
elseif type(sorttype) == 'function' then
table.sort(claims, sorttype)
return claims
end
return claims
end
local function numval(claims, numval)
local numval = tonumber(numval) or 0 -- raise a error if numval is not a positive integer ?
local newclaims = {}
for i, j in pairs(claims) do
if #newclaims == numval then
return newclaims
end
table.insert(newclaims,j)
end
return newclaims
end
function p.getRawvalue(snak)
return p.getDatavalue(snak, {format = 'raw'})
end
function p.getDatavalue(snak, params)
if snak.snaktype ~= 'value' then
return nil
end
local datatype = snak.datavalue.type
local value = snak.datavalue.value
local displayformat = params.format
if datatype == 'wikibase-entityid' then
if displayformat == 'raw' then
return "Q" .. tostring(value['numeric-id'])
else
return p.formatEntityId("Q" .. tostring(value['numeric-id']), params)
end
elseif datatype == 'string' then
if displayformat == 'weblink' then
return formatLink(value)
end
return value
elseif datatype == 'time' then -- format example: +00000001809-02-12T00:00:00Z
if displayformat == 'raw' then
return value.time
else
return require('Module:Date').Wikibasedate(value, params.lang)
end
elseif datatype == 'globecoordinate' then
local latitude, longitude = value.latitude, value.longitude
local globe = require('Module:Wikidata/Globes')[value.globe] -- transforme l'ID du globe en nom anglais utilisable par geohack
local precision = value.precision
if precision < 0.00016 then
precision = 'dmsand2'
elseif precision < 0.016 then
precision = 'dms'
elseif precision < 1 then
precision = 'dm'
else
precision = 'd'
end
return require('Module:Coordinates')._coord{latitude = latitude, longitude = longitude, globe = globe, precision = precision}
elseif datatype == 'quantity' then
if displayformat == 'raw' then
return value.amount
else
local str = string.sub(value.amount,2)
return require('Module:Formatnum').formatnum(str, {lang= params.lang})
end
else
return formatError( 'unknown-datavalue-type' )
end
end
local function getMultipleClaims(args)
local newargs = args
local claims = {}
for i, j in pairs(args.property) do
newargs.property = j
local newclaims = p.getClaims(args)
for k, l in pairs(newclaims) do
table.insert(claims, l)
end
end
return claims
end
function p.getClaims( frame ) -- returns a table of the claims matching some conditions given in args
local args = frame.args -- parameters not provided directly in the calling template, to avoid update issues
if not args.item then
args = frame:getParent().args
end
-- do return args.lang == nil end
if not args.property then
return formatError( 'property-param-not-provided' )
end
if type(args.property) == 'table' then
return getMultipleClaims(args)
end
--Get entity
local entity = nil
local property = string.upper(args.property)
if args.item then -- item used as a synonym for entity
args.entity = args.item
end
if not args.entity then
return nil
end
if type( args.entity ) == "table" then
entity = args.entity
else
entity = getEntityFromId( args.entity )
end
if not entity or not entity.claims or not entity.claims[property] then
return nil
end
claims = entity.claims[property]
-- mettre ~= '' pour le cas ou le paramètre est écrit mais laissé blanc ({{#invoke:formatStatements|property=pXX|targetvalue = xx}})
if args.targetvalue and args.targetvalue ~= '' then
claims = withtargetvalue(claims, args.targetvalue)
end
if args.qualifier and args.qualifier ~= '' then
claims = withqualifier(claims, args.qualifier, args.qualifiervalue)
end
if (args.source and args.source) or (args.sourceproperty and args.sourceproperty ~= '') then
claims = withsource(claims, args.source, args.sourceproperty)
end
if args.excludespecial and args.excludespecial ~= '' then
claims = excludespecial(claims)
end
if args.rank ~= 'all' then
if not args.rank or args.rank == '' then
args.rank = 'best'
end
claims = withrank(claims, args.rank)
end
if args.sorttype then
claims = p.sortclaims(claims, args.sorttype)
end
if args.numval and args.numval ~= '' then --keep at the end, after other filters have been implmented
claims = numval(claims, args.numval)
end
if #claims > 0 then
return claims
end
end
function p.getQualifiers(args)
local claims = p.getClaims(args)
if not claims then
return nil
end
local targetqualif = args.qualifier
local found = {}
for i, j in pairs(claims) do
if (not j.qualifiers) or (not j.qualifiers[targetqualif]) then
break
end
for k, l in pairs(j.qualifiers[targetqualif]) do
table.insert(found, l)
end
end
if #found > 0 then
return found
end
end
function p.getFormattedQualifiers(args)
local qualifs = p.getQualifiers(args)
if not qualifs then
return nil
end
for i, j in pairs(qualifs) do
qualifs[i] = p.formatSnak(j, args)
end
return tableToText(qualifs, args)
end
function p.formatQualifier( frame )
return p.getFormattedQualifiers(frame.args)
end
function p.formatClaimList(claims, args)
if not claims then
return nil
end
for i, j in pairs(claims) do
claims[i] = p.formatStatement(j, args)
end
return claims
end
function p.stringTable(args) -- like getClaims, but get a list of string rather than a list of snaks, for easier manipulation
local claims = p.getClaims(args)
return p.formatClaimList(claims, args)
end
function p.formatStatements( args )--Format statement and concat them cleanly
local valuetable = p.stringTable(args)
return tableToText(valuetable, args)
end
function p.formatStatement( statement, args )
if not statement.type or statement.type ~= 'statement' then
return formatError( 'unknown-claim-type' )
end
local str = p.formatSnak( statement.mainsnak, args )
if args.showqualifiers then -- very ugly
local targetqualif = string.upper(args.showqualifiers)
local foundvalues = {}
if statement.qualifiers and statement.qualifiers[targetqualif] then
for i, j in pairs(statement.qualifiers[targetqualif]) do
table.insert(foundvalues, p.getDatavalue(j, {lang=args.lang}))
end
str = str .. linguistic.inparentheses(mw.text.listToText(foundvalues), lang)
end
end
if args.withdate then -- when "withdate and chronosorty are borth set, date retrieval is performed twice
local timedata = getDate(statement)
if timedata then
local dat = require('Module:Daterange').wikibasedaterange(timedata, args.lang)
local formattteddate = linguistic.inparentheses(dat, args.lang)
str = str .. formattteddate
end
end
if args.showsource and statement.references then -- not great
local sourcestring = ''
for i, ref in pairs(statement.references) do
if ref.snaks.P248 then
for j, source in pairs(ref.snaks.P248) do
if source.snaktype == 'value' then
local page
if ref.snaks.P304 and ref.snaks.P304[1].snaktype == 'value' then
page = ref.snaks.P304[1].datavalue.value
end
local s = require('Module:Cite/sandbox').citeitem('Q' .. source.datavalue.value['numeric-id'], args.lang, page)
s = mw.getCurrentFrame():extensionTag( 'ref', s )
sourcestring = sourcestring .. s
end
end
elseif ref.snaks.P854 and ref.snaks.P854[1].snaktype == 'value' then
s = mw.getCurrentFrame():extensionTag( 'ref', formatLink(ref.snaks.P854[1].datavalue.value))
sourcestring = sourcestring .. s
end
end
str = str .. sourcestring
end
return str
end
function p.formatSnak( snak, args )
if not args then args = {} end -- pour faciliter l'appel depuis d'autres modules
if snak.snaktype == 'somevalue' then
return fb._langSwitch(require('Module:I18n/unknown'), args.lang)
elseif snak.snaktype == 'novalue' then
return i18n['novalue'] --todo
elseif snak.snaktype == 'value' then
return p.getDatavalue( snak, args)
else
return formatError( 'unknown-snak-type' )
end
end
function p._getLabel(entity, lang, default)
if type(entity) == 'string' then
entity = mw.wikibase.getEntity(entity)
end
if not entity then return nil end
local label = entity:getLabel(lang)
if label then
return label
end
for i, j in pairs (mw.language.getFallbacksFor(lang)) do
label = entity:getLabel(j)
if label then
return label
end
end
if default == 'nolabel' then
return i18n['no-label']
end
return entity.id
end
function p._getDescription(entity, lang)
if type(entity) == 'string' then
entity = mw.wikibase.getEntity(entity)
end
if not entity then return nil end
if not entity.descriptions then
return i18n['no description']
end
local descriptions = entity.descriptions
if not descriptions then
return nil
end
if descriptions[lang] then
return descriptions[lang].value
end
local fblist = require('Модуль:Песочница/Игорь Темиров/Fallback').fblist(lang) -- list of fallback languages in no label in the desired language
for i, j in pairs (mw.language.getFallbacksFor(lang)) do
if descriptions.lang then
return descriptions[lang].value
end
end
if default == 'nolabel' then
return i18n['no-label']
end
return entity.id
end
local function formattedLabel(label, entity, args)
if args.link== '-' then
return label
end
local lang = args.lang
local link
if args.link == 'wikipedia' then
link = entity:getSitelink(lang .. 'wiki')
if link then
link = ':' .. lang .. ':' .. link
end
elseif args.link == 'anywikipedia' then -- if Wikipeia links does not exist in the user's language, goes through the fallbackchain
local sitelinks = entity.sitelinks
if sitelinks then
local hack = {}
for i, j in pairs(sitelinks) do
if string.sub(i, #i-4, #i) then
hack[string.sub(i, 1, #i - 4)] = j.title
end
end
if not hack['en'] then hack['en'] = '~' end
local linklang
link, linklang = require('Модуль:Песочница/Игорь Темиров/Fallback')._langSwitch(hack, lang)
if link then
link = ':' .. linklang .. ':' .. link
end end
end
if not link then
link = entity.id
end
return '[[' .. link .. '|' .. label .. ']]'
end
function p.formatEntityId( entity, args )
local entity = getEntityFromId(entity)
local label = p._getLabel(entity, args.lang)
return formattedLabel(label, entity, args)
end
function p.getmainid(claim)
if claim and claim.mainsnak.snaktype == 'value' then
return 'Q' .. claim.mainsnak.datavalue.value['numeric-id']
end
end
function p.formatEntity( entity, args )
if type(entity) == 'string' then
entity = mw.wikibase.getEntity(entity)
end
label = p._getLabel(entity, args.lang)
return formattedLabel(label, entity, args)
end
function p.getLabel(frame) -- simple for simple templates like {{Q|}}}
local args = frame.args
local entity = args.entity
local lang = args.lang
if not lang or lang == '' then
lang = frame:preprocess('{{int:lang}}')
end
if string.sub(entity, 1, 10) == 'Property:P' then
entity = string.sub(entity, 10)
elseif (string.sub(entity, 1, 1) ~= 'P' and string.sub(entity, 1, 1) ~= 'Q') or (not tonumber(string.sub(entity, 2))) then
return i18n.errors['invalid-id']
end
if not args.link or args.link == '' then -- by default: no link
args.link = '-'
end
if args.link == '-' then
return p._getLabel(entity, lang) or i18n.errors['invalid-id']
else
args.lang = lang
return p.formatEntity(entity, args)
end
end
function p.getDescription(frame) -- simple for simple templates like {{Q|}}}
local entity = frame.args.entity
local lang = frame.args.lang
if not lang or lang == '' then
lang = frame:preprocess('{{int:lang}}')
end
if (string.sub(entity, 1, 1) ~= 'P' and string.sub(entity, 1, 1) ~= 'Q') or (not tonumber(string.sub(entity, 2))) then
return i18n.errors['invalid-id']
end
return p._getDescription(entity, lang) or i18n.errors['invalid-id']
end
function p._formatStatements(args)
return p.formatStatements(args)
end
function p.formatStatementsE( frame )
local args = frame.args -- parameters not provided directly in the calling template, to avoid update issues
if not args.item then
args = frame:getParent().args
end
if (not args.item) or (args.item == '') then
return "no item given"
end
if (not args.property) or (args.property == '') then
return "no property given"
end
if not args.lang then
args.lang = frame:preprocess('{{int:lang}}')
end
return p.formatStatements( args )
end
return p