Zum Inhalt springen

Modul:NeoWiki/API

aus AufEinWort, der freien Forschung
Dies ist eine alte Version dieser Seite, zuletzt bearbeitet am 2. November 2026 um 09:41 Uhr durch Nikolaos (Diskussion | Beiträge). Sie kann sich erheblich von der aktuellen Version unterscheiden.

Die Dokumentation für dieses Modul kann unter Modul:NeoWiki/API/Doku erstellt werden

-- Schema-gesteuerter Abruf; eigene Angaben haben bei Herkunft alle Vorrang.
local p = {}

local function backend()
    return require('mw.neowiki')
end

local function scalar(value)
    if type(value) == 'boolean' then return value and 'ja' or 'nein' end
    if type(value) == 'number' then return tostring(value) end
    if type(value) == 'string' then return value end
    error('NeoWiki/API: Nicht unterstützter Werttyp.', 0)
end

-- Own historical dates now follow the agreed Wikipedia calendar convention.
local function ownTime(raw)
    if type(raw) ~= 'string' then error('NeoWiki/API: Ungültige eigene Datierung.', 0) end
    local y, m, d = raw:match('^(-?%d%d%d%d)%-(%d%d)%-(%d%d)$')
    local precision = 11
    if not y then y, m = raw:match('^(-?%d%d%d%d)%-(%d%d)$'); precision = 10 end
    if not y then y = raw:match('^(-?%d%d%d%d)$'); precision = 9 end
    if not y or tonumber(y) == 0 then error('NeoWiki/API: Ungültige eigene Datierung.', 0) end
    local year, month, day = tonumber(y), tonumber(m) or 1, tonumber(d) or 1
    local gregorian = year > 1582 or year == 1582 and (month > 10 or month == 10 and day >= 15)
    local astronomical = year < 0 and year + 1 or year
    local leap = astronomical % 4 == 0 and (not gregorian or astronomical % 100 ~= 0 or astronomical % 400 == 0)
    local days = {31, leap and 29 or 28, 31,30,31,30,31,31,30,31,30,31}
    if month < 1 or month > 12 or day < 1 or day > days[month]
        or year == 1582 and month == 10 and day >= 5 and day <= 14 then
        error('NeoWiki/API: Ungültige eigene Datierung.', 0)
    end
    return { kind = 'time', own = true, raw = raw, year = year, month = m and month or nil, day = d and day or nil,
        sourceTime = { time = (year < 0 and '-' or '+') .. string.format('%04d', math.abs(year))
            .. '-' .. (m or '00') .. '-' .. (d or '00') .. 'T00:00:00Z',
            precision = precision, calendarmodel = 'http://www.wikidata.org/entity/' .. (gregorian and 'Q1985727' or 'Q1985786') } }
end

local function ownTimeLabel(value, definition, language)
    local en = language:match('^en') ~= nil
    local months = en and {'January','February','March','April','May','June','July','August','September','October','November','December'}
        or {'Januar','Februar','März','April','Mai','Juni','Juli','August','September','Oktober','November','Dezember'}
    local link = definition.wikiLink or {}
    local suffix = en and (link.dateBceEn or 'BC') or (link.dateBceDe or 'v. Chr.')
    local prefix = value.day and (value.day .. (en and ' ' or '. ')) or ''
    if value.month then prefix = prefix .. months[value.month] .. ' ' end
    return prefix .. math.abs(value.year) .. (value.year < 0 and (' ' .. suffix) or '')
end

local function nonEmpty(values)
    local result = {}
    for _, value in ipairs(values or {}) do
        if value ~= nil and (type(value) ~= 'string' or mw.text.trim(value) ~= '') then
            result[#result + 1] = value
        end
    end
    return result
end

local function selectLabel(value, definition)
    for _, option in ipairs(definition.options or {}) do
        if value == option.label then return value end
    end
    for _, option in ipairs(definition.options or {}) do
        if value == option.id and type(option.label) == 'string' and option.label ~= '' then
            return option.label
        end
    end
    error('NeoWiki/API: Auswahlwert für „' .. definition.name .. '“ nicht auflösbar.', 0)
end

function p.collect(options)
    options = options or {}
    local origin = options.origin or 'all'
    if origin ~= 'all' and origin ~= 'own' and origin ~= 'external' then
        error('NeoWiki/API: Unbekannte Herkunft.', 0)
    end
    if options.page and options.subject then
        error('NeoWiki/API: Seite oder Subjekt angeben, nicht beides.', 0)
    end
    local nw = backend()
    local subject = options.subject and nw.getSubject(options.subject)
        or (not options.subject and nw.getMainSubject(options.page))
    if not subject then return { fields = {}, missing = true } end
    local schema = nw.getSchema(subject.schema)
    if not schema then error('NeoWiki/API: Schema ist nicht verfügbar.', 0) end
    local selected, needsExternal, found = {}, false, false
    for _, definition in ipairs(schema.properties or {}) do
        if (not options.field or options.field == definition.name)
            and (options.includeImages or definition.type ~= 'image') then
            found = true
            local own = origin == 'external' and {} or nonEmpty(nw.getAll(definition.name, { subject = subject.id }))
            selected[#selected + 1] = { definition = definition, own = own }
            if definition.externalProperty and origin ~= 'own' and #own == 0 then
                needsExternal = true
            end
        end
    end
    if options.field and not found then
        error('NeoWiki/API: Feld nicht im Schema gefunden: ' .. options.field, 0)
    end
    local external = {}
    if needsExternal then
        local bridge = require('mw.neowikibase')
        if type(bridge.getFields) ~= 'function' then
            error('NeoWiki/API: Lua-Anbindung für Quellenwerte fehlt.', 0)
        end
        local result = bridge.getFields({ page = options.page,
            subject = subject.id, language = options.language })
        if type(result) ~= 'table' or result.version ~= 1
            or result.scope ~= 'current' or result.mainSubjectId ~= subject.id
            or type(result.fields) ~= 'table' then
            error('NeoWiki/API: Ungültige Antwort der Quellen-Schnittstelle.', 0)
        end
        for _, field in ipairs(result.fields) do
            if external[field.name] then error('NeoWiki/API: Doppeltes Quellenfeld.', 0) end
            external[field.name] = field
        end
    end
    local fields = {}
    for _, selection in ipairs(selected) do
        local definition, own = selection.definition, selection.own
        local field = { name = definition.name, type = definition.type,
            externalProperty = definition.externalProperty, values = {},
            rawStatement = subject.statements and subject.statements[definition.name],
            hasStoredValues = subject.statements and subject.statements[definition.name]
                and #(subject.statements[definition.name].values or {}) > 0 or false }
        local seen = {}
        local function add(value, source, metadata)
            local text = scalar(value)
            if text ~= '' and not seen[text] then
                seen[text] = true
                field.values[#field.values + 1] = { value = text, origin = source, metadata = metadata }
            end
        end
        for _, value in ipairs(own) do
            if definition.type == 'historicalDate' then
                local time = ownTime(value)
                add(ownTimeLabel(time, definition, options.language or mw.language.getContentLanguage().code), 'own', time)
            elseif definition.type == 'select' then
                add(selectLabel(value, definition), 'own', { raw = value })
            else
                if type(value) ~= 'string' and type(value) ~= 'number' and type(value) ~= 'boolean' then
                    error('NeoWiki/API: Belegtes Feld „' .. definition.name .. '“ hat einen noch nicht unterstützten Werttyp.', 0)
                end
                if type(value) == 'number' and (value ~= value or value == math.huge or value == -math.huge) then
                    error('NeoWiki/API: Ungültige Zahl in „' .. definition.name .. '“.', 0)
                end
                add(value, 'own', { raw = value })
            end
        end
        if #own > 0 then
            field.status = 'own'
        elseif definition.externalProperty and origin ~= 'own' then
            local remote = external[definition.name]
            local manual = definition.type == 'historicalDate' and remote and remote.status == 'manual'
            if not remote or (remote.status ~= 'resolved' and remote.status ~= 'missing' and not manual) then
                error('NeoWiki/API: Quellenwerte für „' .. definition.name .. '“ nicht verfügbar.', 0)
            end
            if remote.source ~= definition.externalProperty.source
                or remote.propertyId ~= definition.externalProperty.id or type(remote.values) ~= 'table' then
                error('NeoWiki/API: Quellenzuordnung für „' .. definition.name .. '“ stimmt nicht überein.', 0)
            end
            field.status, field.reason = remote.status, remote.reason
            for _, value in ipairs(remote.values) do
                if value.kind == 'time' and definition.type == 'historicalDate' then
                    if type(value.sourceTime) ~= 'table' or type(value.label) ~= 'string' then
                        error('NeoWiki/API: Ungültiger Zeitwert für „' .. definition.name .. '“.', 0)
                    end
                    add(value.label, remote.source, value)
                elseif value.kind == 'entity' then
                    if type(value.label) ~= 'string' or value.label == '' or value.label == value.id then
                        error('NeoWiki/API: Wertbezeichnung für „' .. definition.name .. '“ fehlt.', 0)
                    end
                    add(value.label, remote.source, value)
                elseif value.kind ~= 'somevalue' and value.kind ~= 'novalue' then
                    error('NeoWiki/API: Nicht unterstützter Quellenwert für „' .. definition.name .. '“.', 0)
                end
            end
        else
            field.status = 'missing'
        end
        fields[#fields + 1] = field
    end
    return { subject = subject, schema = schema, fields = fields }
end

return p