Module:ComparisonFromDataItem

From OpenStreetMap Wiki
Jump to navigation Jump to search
[Edit] [Purge] Documentation

Usage

This module is used as a pass-through.

It processes input from Template:MultipleTagging and displays the result by using Template:MultipleTaggingDescription.

See also

local getArgs = require('Module:Arguments').getArgs
local titleParser = require('Module:OsmPageTitleParser')
local data = mw.loadData('Module:DescriptionFromDataItem/data')
local i18n = data.translations
local p = {}

-- ##########################################################################
--                             CONSTANTS
-- ##########################################################################

-- "fallback" - if this property is not set on the Tag item, check the corresponding Key item
-- "qid" - for item values, output item's Q ID
-- "en" - for item values, output english label
-- "map" - converts claim's value to the corresponding value in the given map
p.INSTANCE_OF = { id = 'P2', qid = true }
p.GROUP = { id = 'P25', fallback = true }
p.RENDER_IMAGE = { id = 'P38', fallback = true }
p.Q_EXCEPT = { id = 'P27' }
p.Q_LIMIT = { id = 'P26' }
p.KEY_ID = { id = 'P16', fallback = true }
p.TAG_ID = { id = 'P19' }
p.TAG_KEY = { id = 'P10' }
p.REL_ID = { id = 'P41' }
p.REL_TAG = { id = 'P40' }
p.ROLE_REL = { id = 'P43' }
p.WIKIDATA = { id = 'P12' }
p.INCOMPATIBLE_WITH = { id = 'P44', fallback = true, multi = true, strid = true }
p.IMPLIES = { id = 'P45', multi = true, strid = true }
p.COMBINATION = { id = 'P46', multi = true, strid = true }
p.SEE_ALSO = { id = 'P18', multi = true, strid = true }
p.REQUIRES = { id = 'P22', multi = true, strid = true }

p.STATUS_REF = { id = 'P11', is_reference = true }
p.IMG_CAPTION = { id = 'P47', is_qualifier = true }

p.STATUS = { id = 'P6', en = true, extra = p.STATUS_REF }
p.IMAGE = { id = 'P28', extra = p.IMG_CAPTION }

-- ##########################################################################
--                                   UTILITIES
-- ##########################################################################

local function startswith(self, str)
    return self:sub(1, #str) == str
end

local formatKeyVal = function(key, value)
    if value then
        return key .. '=' .. value
    else
        return key
    end
end

local function localize(key, langCode, params)
    local msgTable = i18n[key]
    local msg
    if msgTable then
        msg = msgTable[langCode] or msgTable['en']
    end
    if not msg then
        return '<' .. key .. '>'
    end
    return mw.message.newRawMessage(msg, unpack(params or {})):plain()
end

-- Format as an edit link. Target is either a relative url that starts with a slash, or an item ID (e.g. Q104)
local function editLink(self, target, msgKey)
    local file
    if msgKey == 'desc_edit_mismatch_page' then
        file = 'Red pencil.svg'
    else
        file = 'Arbcom ru editing.svg'
    end
    if not startswith(target, '/') then
        target = 'Item:' .. target
    end
    return ('&nbsp;<span class=wb-edit-pencil>[[File:' .. file .. '|12px|' ..
            localize(msgKey, self.langCode) .. '|link=' .. target .. ']]</span>')
end

-- ##########################################################################
--                              DATA ITEM PARSING
-- ##########################################################################

-- p.Q_LIMIT  "limited to region qualifier"  if qualifier is present, include the statement
--      only if self.region equals any of the listed regions
-- p.Q_EXCEPT "excluding region qualifier"   if qualifier is present, include the statement
--      only if self.region does not equal all of the listed regions
local regionQualifiers = { { prop = p.Q_LIMIT, include = true }, { prop = p.Q_EXCEPT, include = false } }

-- Test if qualifiers indicate that current statement should be
-- included or excluded based on the rules table
-- Returns true/false if it should be included, and true/false if it was based on qualifiers
local function allowRegion(region, statement)
    if statement.rank ~= 'preferred' and statement.rank ~= 'normal' then
        return false, false
    end
    local qualifiers = statement.qualifiers
    if qualifiers then
        for _, value in pairs(regionQualifiers) do
            local qualifier = qualifiers[value.prop.id]
            if qualifier then
                local include = not value.include
                for _, q in pairs(qualifier) do
                    if region == q.datavalue.value.id then
                        include = value.include
                    end
                end
                -- return after the first found rule, because multiple rules
                -- do not make any sense on the same statement
                return include, true
            end
        end
    end
    return true, false -- by default, the statement should be included
end

local function qidToStrid(qid)
    local entity = p.wbGetEntity(qid)
    if not entity then return end

    local tag = p.getClaimValue(p.TAG_ID, 'en', entity)
    local eKey, eValue = titleParser.splitKeyValue(tag)
    if not eKey then
        eKey = p.getClaimValue(p.KEY_ID, 'en', entity)
        if eKey then
            return { eKey }
        end
    else
        return { eKey, eValue }
    end
end

-- Convert claim value into a string
-- property object specifies what value to get:
--  'qid'    - returns data item id
--  'strid'  - return referenced item
--  'map'    - use a map to convert qid into a string
--  'en'     - only english label
--   default - first try local, then english, then qid
local function claimToValue(datavalue, prop, langCode)
    local result = false
    if datavalue.type == 'wikibase-entityid' then
        local qid = datavalue.value.id
        if prop.map then
            result = prop.map[qid]
        elseif prop.strid then
            result = qidToStrid(qid)
            if not result then
                result = { 'Bad item: ' .. qid }
            end
        elseif not prop.qid then
            if not prop.en then
                result = p.wbGetLabelByLang(qid, langCode)
            end
            if not result then
                result = p.wbGetLabel(qid)
            end
        end
        if not result then
            result = qid
        end
    elseif datavalue.type == 'string' then
        result = datavalue.value
    else
        -- TODO:  handle other property types
        result = "Unknown datatype " .. datavalue.type
    end
    return result
end

local function getStatements(entity, prop)
    if prop.multi then
        return entity:getBestStatements(prop.id)
    else
        return entity:getAllStatements(prop.id)
    end
end

-- From a monolingual property, get either the given language or English
local function getMonoString(snakList, langCode)
    local enVal, val
    if snakList then
        for _, snak in pairs(snakList) do
            local lang = snak.datavalue.value.language
            val = snak.datavalue.value.text
            if langCode == lang then
                return val
            elseif langCode == 'en' then
                enVal = val
            end
        end
    end
    return enVal or val
end

-- Debug:  =mw.text.jsonEncode(p.getClaimValue(p.GROUP, 'en', mw.wikibase.getEntity('Q501')),0)
function p.getClaimValue(prop, langCode, entity, fallbackEntity)
    local usedFallback = false
    local region = data.regions[langCode]
    local statements = getStatements(entity, prop)
    if fallbackEntity and prop.fallback and next(statements) == nil then
        usedFallback = true
        statements = getStatements(fallbackEntity, prop)
    end

    if prop.multi then
        local result = {}
        for _, stmt in pairs(statements) do
            local val = claimToValue(stmt.mainsnak.datavalue, prop, langCode)
            if val then
                table.insert(result, val)
            end
        end
        return result
    end

    local match
    for _, stmt in pairs(statements) do
        local include, qualified = allowRegion(region, stmt)
        if include then
            match = stmt
            if qualified then
                -- Keep non-qualified statement until we look through all claims,
                -- if we see a qualified one (limited to the current region), we found the best match
                break
            end
        end
    end

    local result
    local extra
    if match then
        -- Get extra value if available (e.g. reference or image caption)
        if prop.extra then
            if prop.extra.is_reference and match.references then
                for _, ref in pairs(match.references) do
                    local snak = ref.snaks[prop.extra.id]
                    if snak and snak[1] then
                        extra = snak[1].datavalue.value
                        break
                    end
                end
            elseif prop.extra.is_qualifier and match.qualifiers then
                extra = getMonoString(match.qualifiers[prop.extra.id])
            end
        end
        result = claimToValue(match.mainsnak.datavalue, prop, langCode)
    end

    return result, usedFallback, extra
end

-- Get categories string, e.g. "[[category:xx]][[category:yy]]"

local function constructor(args)
    local self = {
        categories = {},
        args = args,
    }

    self.currentTitle = mw.title.getCurrentTitle()

    -- sets self.key, self.value, and self.language from the current title
    titleParser.parseTitleToObj(self, self.currentTitle)

    -- if lang parameter is set, overrides the one detected from the title
    if args.lang and mw.language.isSupportedLanguage(args.lang) then
        self.language = mw.getLanguage(args.lang)
    end
    self.langCode = self.language:getCode()

    toSitelink = function(key, value)
        return (value and 'Tag:' or 'Key:') .. formatKeyVal(key, value)
    end

    local entity1 = p.wbGetEntity(p.wbGetEntityIdForTitle(toSitelink(args.key1, args.value1)))
    local entity2 = p.wbGetEntity(p.wbGetEntityIdForTitle(toSitelink(args.key2, args.value2)))

	self.args.status1 = p.getClaimValue(p.STATUS, 'en', entity1)
	self.args.status2 = p.getClaimValue(p.STATUS, 'en', entity2)

	self.args.statusLink1 = args.statusLink1
	self.args.statusLink2 = args.statusLink2

	self.args.extra1 = args.extra1
	self.args.extra2 = args.extra2

    if args.key3 then
	    local entity3 = p.wbGetEntity(p.wbGetEntityIdForTitle(toSitelink(args.key3, args.value3)))
		self.args.status3 = p.getClaimValue(p.STATUS, 'en', entity3)
		self.args.statusLink3 = args.statusLink3
		self.args.extra3 = args.extra3
    end

    if args.key4 then
	    local entity4 = p.wbGetEntity(p.wbGetEntityIdForTitle(toSitelink(args.key4, args.value4)))
		self.args.status4 = p.getClaimValue(p.STATUS, 'en', entity4)
		self.args.statusLink4 = args.statusLink4
		self.args.extra4 = args.extra4
    end

    if args.key5 then
	    local entity5 = p.wbGetEntity(p.wbGetEntityIdForTitle(toSitelink(args.key5, args.value5)))
		self.args.status5 = p.getClaimValue(p.STATUS, 'en', entity5)
		self.args.statusLink5 = args.statusLink5
		self.args.extra5 = args.extra5
    end

    return self
end

-- ##########################################################################
--                                 ENTRY POINTS
-- ##########################################################################

-- If we found data item for this key/tag, compare template parameters
-- with what we have in the item, and add some extra formatting/links/...
-- If the values are not provided, just use the ones from the data item
function p.main(frame)
	if not mw.wikibase then
		return frame:expandTemplate { title = 'Warning', args = { text = "The OSM wiki is experiencing technical difficulties. Infoboxes will be restored soon." } }
	end
	
    local args = getArgs(frame)

    -- initialize self - parse title, language, and get relevant entities
    local self = constructor(args)

    return frame:expandTemplate { title = 'Template:MultipleTaggingDescription', args = args }
end



-- ##########################################################################
--                        DEBUGGING AND TESTING SUPPORT
-- ##########################################################################

-- These methods could be overwritten by unit tests
function p.wbGetEntity(entity)
    return mw.wikibase.getEntity(entity)
end

function p.wbGetEntityIdForTitle(title)
    return mw.wikibase.getEntityIdForTitle(title)
end
function p.wbGetLabel(qid)
    return mw.wikibase.getLabel(qid)
end

return p