Modul:Wikidata
Megjelenés
A modult a Modul:Wikidata/doc lapon tudod dokumentálni
require"strict"
local p = {}
local getArgs = require"Modul:Arguments".getArgs
local frame = mw.getCurrentFrame()
local i18n = {
["errors"] = {
["property-param-not-provided"] = "Hiányzó ''property='' paraméter",
["entity-not-found"] = "Nem létező Wikidata-elem",
["unknown-claim-type"] = "Ismeretlen az állítás típusa",
["unknown-snak-type"] = "Ismeretlen a snak típusa",
["unknown-datavalue-type"] = "Ismeretlen az érték típusa",
["unknown-entity-type"] = "Ismeretlen a Wikidata-elem típusa",
["unknown-value-module"] = "A ''value-module'' és ''value-function'' paramétert egyszerre kell beállítani",
["value-module-not-found"] = "A ''value-module'' nem létező modulra mutat",
["value-function-not-found"] = "A ''value-function'' nem létező funkcióra mutat",
["globecoordinate-direction"] = "Az érték típusa ''globecoordinate'' : kell ''direction=latitude'' vagy ''longitude''",
["invalid-value"] = "Érvénytelen érték",
["unknown-unit"] = "Ismeretlen mértékegység",
},
["somevalue"] = "''nem ismert''",
["novalue"] = "''nincs''"
}
local function formatError(key)
error(i18n.errors[key], 2)
end
local function getUpperLevelOfType(property, typeId, item)
local result, statements, visited = {}, {}, {[item.id] = true}
local function getStatements(item)
if not item or not item.claims or not item.claims[property] then
return
end
for _, s in ipairs(item:getBestStatements(property)) do
if s.mainsnak.snaktype == "value" then
local itemId = "Q" .. s.mainsnak.datavalue.value["numeric-id"]
if not visited[itemId] then
visited[itemId] = true
local item = mw.wikibase.getEntity(itemId)
if p.containsPropertyWithValue(item, "P31", typeId) then
statements[item.id] = s
else
getStatements(item)
end
end
end
end
end
getStatements(item)
for _, s in pairs(statements) do
table.insert(result, s)
end
return result
end
local function firstValue(statements)
for _, statement in ipairs(statements) do
if statement.rank == 'preferred' then
return {statement}
end
end
for _, statement in ipairs(statements) do
if statement.rank == 'normal' then
return {statement}
end
end
return {}
end
local function withRank(statements, ranks)
local result = {}
for _i, statement in ipairs(statements) do
for _j, rank in ipairs(ranks) do
if statement.rank == rank then
table.insert(result, statement)
break
end
end
end
return result
end
function p.withHighestRank(statements)
local preferred, normal = {}, {}
for _, statement in ipairs(statements) do
if statement.rank == "preferred" then
table.insert(preferred, statement)
elseif statement.rank == "normal" then
table.insert(normal, statement)
end
end
return #preferred > 0 and preferred or normal
end
local function atDate(statements, date)
local result = {}
local Time = require"Modul:Time"
local time = Time.newFromIso8601(date, true)
if not time then
return statements
end
for _, s in ipairs(statements) do
local startDate, endDate
if s.qualifiers and s.qualifiers.P580 and #s.qualifiers.P580 == 1 and s.qualifiers.P580[1].snaktype == "value" then
startDate = Time.newFromWikidataValue(s.qualifiers.P580[1].datavalue.value)
end
if s.qualifiers and s.qualifiers.P582 and #s.qualifiers.P582 == 1 and s.qualifiers.P582[1].snaktype == "value" then
endDate = Time.newFromWikidataValue(s.qualifiers.P582[1].datavalue.value)
end
if not startDate and endDate and time <= endDate or
startDate and not endDate and startDate <= time or
startDate and endDate and startDate <= time and time <= endDate then
table.insert(result, s)
end
end
return result
end
local function getEntityFromId(id)
if id and id ~= '' then
return mw.wikibase.getEntity(id)
end
return mw.wikibase.getEntity()
end
local function getEntityIdFromValue(value)
local prefix
if value['entity-type'] == 'item' then
prefix = 'Q'
elseif value['entity-type'] == 'property' then
prefix = 'P'
else
formatError"unknown-entity-type"
end
return prefix .. value['numeric-id']
end
local function populationWithPointInTime(statement, options)
local population = tonumber(statement.mainsnak.datavalue.value.amount)
local text = (population < 10000 and tostring(population) or mw.getContentLanguage():formatNum(population)) .. ' fő'
if statement.qualifiers and statement.qualifiers.P585 then -- dátum
local time, fDate = require'Modul:Time'.newFromWikidataValue(statement.qualifiers.P585[1].datavalue.value)
if time.precision >= 11 then
fDate = mw.getContentLanguage():formatDate('Y. M. j.', time:toIso8601())
else
fDate = tostring(time.year)
end
text = text .. ' ' .. mw.text.tag('span', {style = 'font-size:90%; white-space:nowrap;'}, '(' .. fDate .. ')')
end
return text
end
local function formatEntityId(entityId, options)
local link = mw.wikibase.sitelink(entityId)
if options.link == 'csak' then
return link
end
if link and options.link ~= 'nem' and mw.ustring.sub(link, 1, 10) == 'Kategória:' then
return '[[' .. link .. ']]'
end
local label = mw.wikibase.label(entityId)
if options.labelProperty then
local options2 = {}
for k, v in pairs(options) do
if k ~= "labelProperty" then
options2[k] = v
end
end
options2.property = options.labelProperty
options2.entityId = entityId
options2.rank = "valid"
options2.link = "nem"
options2.lang = options.lang or "hu"
options2.firstAfter = true
options2['felsorolás'] = nil -- felsorolásjel nélkül
local label2 = p.formatStatements(frame, options2)
if label2 and label2 ~= "" then
label = label2
end
end
if options.chrProperty and options.chrDate then
local time
if options.chrDate:match"^P%d+$" then
local dates = mw.wikibase.getEntity(options.entityId):getBestStatements(options.chrDate)
if #dates == 1 and dates[1].mainsnak.snaktype == "value" then
time = dates[1].mainsnak.datavalue.value.time
end
else
time = options.chrDate
end
if time then
local chrLabel = p.formatStatements(frame, {
property = options.chrProperty,
entityId = entityId,
atDate = time,
rank = "valid",
lang = options.lang or "hu",
firstAfter = true,
['felsorolás'] = nil -- felsorolásjel nélkül
})
if chrLabel and chrLabel ~= "" then
label = chrLabel
end
end
end
if label and options.format == "ucfirst" then
label = mw.language.getContentLanguage():ucfirst(label)
end
if link and options.link ~= 'nem' then
if label then
if mw.ustring.sub(label, 2) == mw.ustring.sub(link, 2) and
mw.ustring.lower(mw.ustring.sub(label, 1, 1)) == mw.ustring.lower(mw.ustring.sub(link, 1, 1)) then
return '[[' .. label .. ']]'
else
return '[[' .. link .. '|' .. label .. ']]'
end
else
return '[[' .. link .. ']]'
end
else
return label or link --TODO what if no links and label + fallback language?
end
end
local function formatTimeValue(value, options)
if options.format == "raw" then
return value.time
else
local time = require"Modul:Time".newFromWikidataValue(value)
if time then
if options.format == "iso" then
return tostring(time)
end
return time:formatDate(options)
else
formatError"invalid-value"
end
end
end
local function countryOf(itemId)
if not itemId then
return nil
end
local item = mw.wikibase.getEntity(itemId)
if not item then
return nil
end
return p.formatStatements(frame, {property = "P17"}, item)
end
local function formatNum(amount)
if amount < 10000 and -10000 < amount then
return tostring(amount):gsub('%.', ',')
else
return mw.getContentLanguage():formatNum(amount)
end
end
local function formatDatavalue(datavalue, options)
--Use the customize handler if provided
if options['value-module'] or options['value-function'] then
if not options['value-module'] or not options['value-function'] then
return formatError( 'unknown-value-module' )
end
local formatter = require ('Module:' .. options['value-module'])
if formatter == nil then
return formatError( 'value-module-not-found' )
end
local fun = formatter[options['value-function']]
if fun == nil then
return formatError( 'value-function-not-found' )
end
return fun( datavalue.value, options )
end
--Default formatters
if datavalue.type == 'wikibase-entityid' then
local itemId = getEntityIdFromValue(datavalue.value)
if options.format == 'raw' then
return itemId
end
local result = formatEntityId(itemId, options)
if not result then
return nil
end
local country = options.format == "with_country" and countryOf(itemId, options)
return result .. (country and ", " .. country or "")
elseif datavalue.type == 'string' then
return datavalue.value --TODO ids + media
elseif datavalue.type == 'time' then
return formatTimeValue(datavalue.value, options)
elseif datavalue.type == 'globecoordinate' then
if options.direction == 'latitude' then
return datavalue.value.latitude
elseif options.direction == 'longitude' then
return datavalue.value.longitude
else
return formatError('globecoordinate-direction')
end
elseif datavalue.type == 'quantity' then
if options.format == 'raw' then
return datavalue.value.amount
end
local result
local amount = tonumber(datavalue.value.amount)
if datavalue.value.unit == "1" then
if options.unit then
return nil
end
result = formatNum(amount)
else
local unitId = datavalue.value.unit:match"Q%d+"
local sourceUnit = mw.loadData"Modul:Wikidata/units".wikidata_item_ids[unitId]
if not sourceUnit then
if not options.unit then
local unitItem = mw.wikibase.getEntity(unitId)
if p.isOfType(unitItem, "Q8142") then -- currency
local symbol = unitItem:getBestStatements("P558")[1]
result = formatNum(amount) .. " " .. (symbol and p.formatStatement(symbol) or unitItem:getLabel())
else
result = formatNum(amount) .. " " .. unitItem:getLabel()
end
else
formatError"unknown-unit"
end
else
local targetUnit = options.unit or sourceUnit
result = frame:expandTemplate{title = "Convert", args = {amount, sourceUnit, targetUnit, disp = options.showUnit and
"out" or "number"
}}
end
end
return result
elseif datavalue.type == "monolingualtext" then
if not options.lang or options.lang == "~hu" and datavalue.value.language ~= "hu" then
return datavalue.value.text
end
for lang in mw.text.gsplit(options.lang, ",") do
if lang:match"^%s*(.-)%s*$" == datavalue.value.language then
return datavalue.value.text
end
end
return nil
else
formatError"unknown-datavalue-type"
end
end
local function formatSnak(snak, options)
local options = options or {}
if snak.snaktype == "somevalue" then
return options.somevalue or i18n["somevalue"]
elseif snak.snaktype == "novalue" then
return i18n["novalue"]
elseif snak.snaktype == "value" then
if options["value-module"] or options["value-function"] then
return formatDatavalue(snak.datavalue, options)
end
if snak.datatype == "math" then
return frame:extensionTag("math", snak.datavalue.value)
end
if snak.datatype == "external-id" then
if options['formatExternal'] then
local formatter = p.formatStatements{
entityId = snak.property,
property = 'P1630',
first = true
}
if formatter and formatter ~= '' then
local id = formatDatavalue(snak.datavalue, options)
local url = string.gsub( formatter, '%$1', id )
return string.format( '[%s %s]', url, id )
end
-- else fall back to plain version
end
return formatDatavalue(snak.datavalue, options)
end
return formatDatavalue(snak.datavalue, options)
else
formatError"unknown-snak-type"
end
end
local function formatSnaks(snaks, options)
local formattedSnaks = {}
for _, snak in ipairs(snaks) do
table.insert(formattedSnaks, formatSnak(snak, options))
end
return mw.text.listToText(formattedSnaks, options.separator, options.conjunction)
end
local function formatReference(reference, options)
if reference.snaks.P854 and (reference.snaks.P357 or reference.snaks.P1476) then -- url and title
local args, Time = {}
if reference.snaks.P577 or reference.snaks.P813 then
Time = require'Modul:Time'
end
args.url = reference.snaks['P854'] and formatSnak(reference.snaks['P854'][1], options)
args.title = reference.snaks['P357'] and formatSnak(reference.snaks['P357'][1], options)
args.author = reference.snaks['P50'] and formatSnak(reference.snaks['P50'][1], options)
if reference.snaks.P577 then
local time = Time.newFromWikidataValue(reference.snaks.P577[1].datavalue.value)
if time.precision >= 11 then
args.date = time:toIso8601()
else
args.year = tostring(time.year)
end
end
args.publisher = reference.snaks['P123'] and formatSnak(reference.snaks['P123'][1], options)
args.language = reference.snaks['P364'] and formatSnaks(reference.snaks['P364'], {link = 'nem'})
args.accessdate = reference.snaks['P813'] and Time.newFromWikidataValue(reference.snaks.P813[1].datavalue.value):toIso8601()
if args.url and args.url:sub(1, 17) == 'http://www.ksh.hu' then
args.url = args.url:gsub('p_lang=EN', 'p_lang=HU')
end
if not args.title and reference.snaks.P1476 then
for _, snak in ipairs(reference.snaks.P1476) do
if not args.title or (snak.datavalue and snak.datavalue.value.language == 'hu') then
args.title = snak.datavalue.value.text
end
end
end
return frame:expandTemplate{title = 'Cite web', args = args}
else
local result = {}
options.formatExternal = true
for key, referenceSnaks in pairs(reference.snaks) do
if key ~= 'P143' then -- imported from
for _, snak in ipairs(referenceSnaks) do
table.insert(result, formatSnak(snak, snak.datavalue and snak.datavalue.type == 'time' and {link = 'nem'} or options))
end
end
end
return table.concat(result, ', ')
end
end
local function formatStatements(options, item)
if not options.property then
formatError"property-param-not-provided"
end
local property = options.property:upper()
--Get entity
local entity = item
if not entity then
entity = getEntityFromId(options.entityId)
end
if not entity then
return '' --TODO error?
end
if not entity.claims or not entity.claims[property] then
return '' --TODO error?
end
local statements = entity.claims[property]
-- TODO Extract selection and filtering
if options.rank ~= 'all' then
if not options.rank then
statements = p.withHighestRank(statements)
elseif options.rank == 'valid' then
statements = withRank(statements, {'normal', 'preferred'})
else
statements = withRank(statements, {options.rank})
end
end
if options.typeId then
statements = getUpperLevelOfType(property, options.typeId, entity)
end
if options.atDate then
statements = atDate(statements, options.atDate)
end
if options.excludespecial then
local newStatements = {}
for _, s in ipairs(statements) do
if s.mainsnak.snaktype == "value" then
table.insert(newStatements, s)
end
end
statements = newStatements
end
if options.first then
statements = firstValue(statements)
end
-- TODO Extract sorting
if options.sort then
local comp = options.sort
if comp == "" then
comp = function (s1, s2)
if s1.mainsnak.snaktype ~= "value" or s2.mainsnak.snaktype ~= "value" then
return nil
end
local sortKey
local type = s1.mainsnak.datavalue.type
if type == "wikibase-entityid" then
sortKey = function(statement)
local id = "Q" .. statement.mainsnak.datavalue.value["numeric-id"]
local key = mw.wikibase.label(id)
if not key then
key = mw.wikibase.sitelink(id)
end
if not key then
return id
end
return mw.language.getContentLanguage():caseFold(key)
end
end
return sortKey(s1) < sortKey(s2)
end
end
table.sort(statements, comp)
end
--Format statement and concat them cleanly
local formattedStatements = {}
for _, statement in ipairs(statements) do
local fs
if property == 'P1082' and options.format == 'default' then -- population
fs = populationWithPointInTime(statement, options)
if statement.references then
for _, reference in ipairs(statement.references) do
local formattedReference = formatReference(reference, options)
if formattedReference and formattedReference ~= '' then
fs = fs .. frame:extensionTag('ref', formattedReference, {name = reference.hash})
end
end
end
else
fs = p.formatStatement(statement, options)
end
if fs then
if options['felsorolás'] == 'lista' then
fs = '* ' .. fs
elseif options['felsorolás'] == 'számozott lista' then
fs = '# ' .. fs
end
table.insert(formattedStatements, fs)
end
end
local function plainlist(items)
if #items == 0 then
return ""
end
if #items == 1 then
return items[1]
end
return frame:expandTemplate{ title = "Plainlist", args = { "\n* " .. table.concat(items, "\n* ") .. "\n" } }
end
if options['felsorolás'] == 'lista' or options['felsorolás'] == 'számozott lista' then
return table.concat(formattedStatements, '\n')
elseif options['felsorolás'] == 'sorok' then
return plainlist(formattedStatements)
elseif options['felsorolás'] == 'szöveg' then
return mw.text.listToText(formattedStatements)
elseif options.separator or options.conjunction then
options.separator = options.separator and string.gsub(options.separator, ' ', ' ')
options.conjunction = options.conjunction and string.gsub(options.conjunction, ' ', ' ')
return mw.text.listToText(formattedStatements, options.separator, options.conjunction)
else
if options.firstAfter then
return formattedStatements[1] or ""
end
return plainlist(formattedStatements)
end
end
local function formatQualifiers(statement, options)
local result, startDate, endDate = {}
for key, snaks in pairs(statement.qualifiers) do
if key == 'P580' then
startDate = formatSnak(snaks[1], {link = 'nem'})
elseif key == 'P582' then
endDate = formatSnak(snaks[1], {link = 'nem'})
else
for _, snak in ipairs(snaks) do
table.insert(result, formatSnak(snak, {link = 'nem'}))
end
end
end
if startDate and startDate ~= '' or endDate and endDate ~= '' then
table.insert(result, 1, (startDate or '') .. '–' .. (endDate or ''))
end
return table.concat(result, ', ')
end
function p.formatStatement(statement, options)
if not statement.type or statement.type ~= 'statement' then
formatError('unknown-claim-type')
end
local options = options or {}
local result
if statement.mainsnak.snaktype == "somevalue" and statement.mainsnak.datatype == "time" and statement.qualifiers and
(statement.qualifiers.P1319 or statement.qualifiers.P1326) then
-- TODO Extract method
if statement.qualifiers.P1319 then
if statement.qualifiers.P1326 then
result = formatSnak(statement.qualifiers.P1319[1]) .. " és " .. formatSnak(statement.qualifiers.P1326[1]) .. " között"
else
result = formatSnak(statement.qualifiers.P1319[1]) .. " után"
end
else
result = formatSnak(statement.qualifiers.P1326[1]) .. " előtt"
end
else
result = formatSnak(statement.mainsnak, options)
end
--TODO reference and qualifiers
if result and result ~= '' then
if options.showQualifiers and statement.qualifiers then
local formattedQualifiers = formatQualifiers(statement, options)
if formattedQualifiers and formattedQualifiers ~= '' then
result = result .. ' <small>(' .. formattedQualifiers .. ')</small>'
end
end
if options.showReferences and statement.references then
for _, reference in ipairs(statement.references) do
local formattedReference = formatReference(reference, options)
if formattedReference and formattedReference ~= '' then
result = result .. frame:extensionTag('ref', formattedReference, {name = reference.hash})
end
end
end
end
return result
end
function p.formatStatements(frame, args, item)
if not args then
args = getArgs(frame, { removeBlanks = false })
end
--If a value if already set, use it
if args.value and args.value ~= '' then
return args.value ~= '-' and args.value or ''
end
return formatStatements(args, item)
end
--[[
Returns string true if connected Wikibase item contains property specified
by property argument, empty string otherwise.
Used by template Wikidata-f in conditional expressions.
--]]
function p.containsProperty(frame, args, item)
if not args then
args = getArgs(frame, {frameOnly = true, readOnly = true})
end
if args.value == '-' then
return ''
end
if args.value then
return 'true'
end
if not args.property then
formatError('property-param-not-provided')
end
local entity = item or mw.wikibase.getEntity(args.entityId)
if not entity or not entity.claims or not entity.claims[args.property:upper()] then
return ''
end
return 'true'
end
function p.containsPropertyWithValue(item, property, value)
if not property or not value then
return false
end
if not item or not item.claims or not item.claims[property:upper()] then
return false
end
for _, statement in ipairs(item.claims[property:upper()]) do
if statement.rank ~= "deprecated" and statement.mainsnak.snaktype == "value" then
local type = statement.mainsnak.datavalue.type
if type == "wikibase-entityid" then
if "Q" .. statement.mainsnak.datavalue.value["numeric-id"] == value then
return true
end
end
end
end
return false
end
function p.isOfType(item, class)
if not item or not item.claims or not item.claims.P31 or not class then
return false
end
local isSubclass, visited
local function checkProperty(item, property)
for _, s in ipairs(item:getBestStatements(property)) do
if s.mainsnak.snaktype == "value" then
local itemId = "Q" .. s.mainsnak.datavalue.value["numeric-id"]
if itemId == class or isSubclass(itemId) then
return true
end
end
end
return false
end
isSubclass = function (itemId)
if visited[itemId] then
return false
end
local item = mw.wikibase.getEntity(itemId)
if not item then -- deleted item
return false
end
visited[itemId] = true
visited[item.id] = true
return checkProperty(item, "P279")
end
visited = { [item.id] = true }
return checkProperty(item, "P31")
end
return p