Saltar para o conteúdo

Módulo:Convert/wikidata

Ourige: Biquipédia, la anciclopédia lhibre.

La decumentaçon pa este módulo puode ser criada na páigina Módulo:Convert/wikidata/doc

-- Functions to access Wikidata for Module:Convert.

local function collection()
    -- Return a table to hold items.
    return {
        n = 0,
        add = function (self, item)
            self.n = self.n + 1
            self[self.n] = item
        end,
        join = function (self, sep)
            return table.concat(self, sep)
        end,
    }
end

local function strip_to_nil(text)
    -- If text is a non-empty string, return its trimmed content,
    -- otherwise return nothing (empty string or not a string).
    if type(text) == 'string' then
        return text:match('(%S.-)%s*$')
    end
end

local function make_unit(units, parms, uid)
    -- Return a unit code for convert or nil if unit unknown.
    -- If necessary, add a dummy unit to parms so convert will use it
    -- for the input without attempting a conversion since nothing
    -- useful is available (for example, with unit volt).
    local unit = units[uid]
    if type(unit) ~= 'table' then
        return nil
    end
    local ucode = unit.ucode
    if ucode and not unit.si then
        return ucode                -- a unit known to convert
    end
    parms.opt_ignore_error = true
    ucode = ucode or unit._ucode    -- must be a non-empty string
    local ukey, utable
    if unit.si then
        local base = units[unit.si]
        ukey = base.symbol          -- must be a non-empty string
        local n1 = base.name1
        local n2 = base.name2
        if not n1 then
            n1 = ukey
            n2 = n2 or n1           -- do not append 's'
        end
        utable = {
            _symbol = ukey,
            _name1 = n1,
            _name2 = n2,
            link = unit.link or base.link,
            utype = n1,
            prefixes = 1,
        }
    else
        ukey = ucode
        utable = {
            symbol = ucode,         -- must be a non-empty string
            name1 = unit.name1,     -- if nil, uses symbol
            name2 = unit.name2,     -- if nil, uses name1..'s'
            link = unit.link,       -- if nil, uses name1
            utype = unit.name1 or ucode,
        }
    end
    utable.scale = 1
    utable.default = ''
    utable.defkey = ''
    utable.linkey = ''
    utable.bad_mcode = ''
    parms.unittable = { [ukey] = utable }
    return ucode
end

local function get_statements(qid, pid)
    -- Get item for qid and return a list of statements for property pid.
    -- Statements are in Wikidata's order except that those with preferred
    -- rank are first, then normal rank. Any other rank is ignored.
    -- qid is nil for the current page's item, or is an item id (expensive).
    local result, n = {}, 0
    local entity = mw.wikibase.getEntity(qid)
    if type(entity) == 'table' then
        local statements = (entity.claims or {})[pid]
        if type(statements) == 'table' then
            for _, rank in ipairs({ 'preferred', 'normal' }) do
                for _, statement in ipairs(statements) do
                    if type(statement) == 'table' and rank == statement.rank then
                        n = n + 1
                        result[n] = statement
                    end
                end
            end
        end
    end
    return result
end

local function input_from_property(tdata, parms, qid, pid)
    -- Given that pid is a Wikidata property identifier like 'P123',
    -- return amount, ucode (two strings) for the item/property,
    -- or return nothing.
    for _, statement in ipairs(get_statements(qid, pid)) do
        if statement.mainsnak and statement.mainsnak.datatype == 'quantity' then
            local value = (statement.mainsnak.datavalue or {}).value
            if value then
                local amount = value.amount
                if amount then
                    amount = tostring(amount)  -- in case amount is ever a number
                    if amount:sub(1, 1) == '+' then
                        amount = amount:sub(2)
                    end
                    local unit = value.unit
                    if type(unit) == 'string' then
                        unit = unit:match('Q%d+$')  -- unit qid is at end of URL
                        local ucode = make_unit(tdata.wikidata_units, parms, unit)
                        if ucode then
                            return amount, ucode
                        end
                    end
                end
            end
        end
    end
end

local function input_from_text(tdata, parms, text)
    -- Given string should be of form "<value><space><unit>".
    -- Return value, ucode (two strings), or return nothing.
    text = text:gsub('&nbsp;', ' '):gsub('  +', ' ')
    local pos = text:find(' ', 1, true)
    if pos then
        -- Leave checking of value to convert which can handle fractions.
        local value = text:sub(1, pos - 1)
        local uid = text:sub(pos + 1)
        local ucode = make_unit(tdata.wikidata_units, parms, uid)
        return value, ucode or uid
    end
end

local function adjustparameters(tdata, parms, index)
    -- For Module:Convert, adjust parms (a table of {{convert}} parameters).
    -- Return true if successful or return false, t where t is an error message table.
    -- This is intended mainly for use in infoboxes where the input might be
    --    <value><space><unit>    or
    --    <wikidata-property-id>  (uses an optional qid item id)
    -- If successful, insert value and unit in parms, before given index.
    local amount, ucode
    local text = parms.input  -- should be a trimmed, non-empty string
    local qid = strip_to_nil(parms.qid)
    local pid = text:match('^P%d+$')
    if pid then
        parms.input_text = ''  -- output an empty string if an error occurs
        amount, ucode = input_from_property(tdata, parms, qid, pid)
    else
        amount, ucode = input_from_text(tdata, parms, text)
    end
    if amount and ucode then
        table.insert(parms, index, ucode)
        table.insert(parms, index, amount)
        return true
    end
    return false, pid and { 'cvt_no_output' } or { 'cvt_bad_input', text }
end

--------------------------------------------------------------------------------
--- List units and check syntax of definitions ---------------------------------
--------------------------------------------------------------------------------
local specifications = {
    -- seq = sequence in which fields are displayed
    base = {
        title = 'SI base units',
        fields = {
            symbol = { seq = 2, mandatory = true },
            name1  = { seq = 3, mandatory = true },
            name2  = { seq = 4 },
            link   = { seq = 5 },
        },
        noteseq = 6,
        header = '{| class="wikitable"\n!si !!symbol !!name1 !!name2 !!link !!note',
        item = '|-\n|%s ||%s ||%s ||%s ||%s ||%s',
        footer = '|}',
    },
    alias = {
        title = 'Aliases for convert',
        fields = {
            ucode  = { seq = 2, mandatory = true },
            si     = { seq = 3 },
        },
        noteseq = 4,
        header = '{| class="wikitable"\n!alias !!ucode !!base !!note',
        item = '|-\n|%s ||%s ||%s ||%s',
        footer = '|}',
    },
    known = {
        title = 'Units known to convert',
        fields = {
            ucode  = { seq = 2, mandatory = true },
            label  = { seq = 3, mandatory = true },
        },
        noteseq = 4,
        header = '{| class="wikitable"\n!qid !!ucode !!label !!note',
        item = '|-\n|%s ||%s ||%s ||%s',
        footer = '|}',
    },
    unknown = {
        title = 'Units not known to convert',
        fields = {
            _ucode = { seq = 2, mandatory = true },
            si     = { seq = 3 },
            name1  = { seq = 4 },
            name2  = { seq = 5 },
            link   = { seq = 6 },
            label  = { seq = 7, mandatory = true },
        },
        noteseq = 8,
        header = '{| class="wikitable"\n!qid !!_ucode !!base !!name1 !!name2 !!link !!label !!note',
        item = '|-\n|%s ||%s ||%s ||%s ||%s ||%s ||%s ||%s',
        footer = '|}',
    },
}

local function listunits(tdata, ulookup)
    -- For Module:Convert, make wikitext to list the built-in Wikidata units.
    -- Return true, wikitext if successful or return false, t where t is an
    -- error message table. Currently, an error return never occurs.
    -- The syntax of each unit definition is checked and a note is added if
    -- a problem is detected.
    local function safe_cells(t)
        -- This is not currently needed, but in case definitions ever use wikitext
        -- like '[[kilogram|kg]]', escape the text so it works in a table cell.
        local result = {}
        for i, v in ipairs(t) do
            if v:find('|', 1, true) then
                v = v:gsub('(%[%[[^%[%]]-)|(.-%]%])', '%1\0%2')  -- replace pipe in piped link with a zero byte
                v = v:gsub('|', '&#124;')                        -- escape '|'
                v = v:gsub('%z', '|')                            -- restore pipe in piped link
            end
            result[i] = v:gsub('{', '&#123;')                    -- escape '{'
        end
        return unpack(result)
    end
    local wdunits = tdata.wikidata_units
    local speckeys = { 'base', 'alias', 'unknown', 'known' }
    for _, sid in ipairs(speckeys) do
        specifications[sid].units = collection()
    end
    local keys, n = {}, 0
    for k, v in pairs(wdunits) do
        n = n + 1
        keys[n] = k
    end
    table.sort(keys)
    local note_count = 0
    for _, key in ipairs(keys) do
        local unit = wdunits[key]
        local ktext, sid
        if key:match('^Q%d+$') then
            ktext = '[[d:' .. key .. '|' .. key .. ']]'
            if unit.ucode then
                sid = 'known'
            else
                sid = 'unknown'
            end
        elseif unit.ucode then
            ktext = key
            sid = 'alias'
        else
            ktext = key
            sid = 'base'
        end
        local result = { ktext }
        local spec = specifications[sid]
        local fields = spec.fields
        local note = collection()
        for k, v in pairs(unit) do
            if fields[k] then
                local seq = fields[k].seq
                if result[seq] then
                    note:add('duplicate ' .. k)  -- cannot happen since keys are unique
                else
                    result[seq] = v
                end
            else
                note:add('invalid ' .. k)
            end
        end
        for k, v in pairs(fields) do
            local value = result[v.seq]
            if value then
                if k == 'si' and not wdunits[value] then
                    note:add('need si ' .. value)
                end
                if k == 'label' then
                    local wdl = mw.wikibase.label(key)
                    if wdl ~= value then
                        note:add('label changed to ' .. tostring(wdl))
                    end
                end
            else
                result[v.seq] = ''
                if v.mandatory then
                    note:add('missing ' .. k)
                end
            end
        end
        local text
        if note.n > 0 then
            note_count = note_count + 1
            text = '*' .. note:join('<br />')
        end
        result[spec.noteseq] = text or ''
        spec.units:add(result)
    end
    local results = collection()
    if note_count > 0 then
        local text = note_count .. (note_count == 1 and ' note' or ' notes')
        results:add("'''Search for * to see " .. text .. "'''\n")
    end
    for _, sid in ipairs(speckeys) do
        local spec = specifications[sid]
        results:add("'''" .. spec.title .. "'''")
        results:add(spec.header)
        local fmt = spec.item
        for _, unit in ipairs(spec.units) do
            results:add(string.format(fmt, safe_cells(unit)))
        end
        results:add(spec.footer)
    end
    return true, results:join('\n')
end

return { _adjustparameters = adjustparameters, _listunits = listunits }