Interested Article - Wikidata

Документация

Используется в {{ Wikidata }} (см. описания параметров там же). Настраивается при помощи Модуль:Wikidata/config .

Прежде чем вносить какие-либо изменения в данный модуль, просьба оттестировать их в /песочнице . Обратите внимание, что не всё корректно работает в песочнице.

Общие сведения

Функции данного модуля не предназначены для прямого вызова из шаблонов карточек или других модулей, не являющихся функциями расширения данного. Для вызова из шаблонов карточек используйте шаблон {{ wikidata }} или один из специализированных шаблонов для свойств. Для вызова функций Викиданных предназначенных для отображения чаще всего достаточно вызова frame:expandTemplate{} с вызовом шаблона, ответственного за отрисовку свойства. С другой стороны, вызов определённых функций модуля (в основном это касается getEntity() ) может в будущем стать предпочтительным. Данный Lua-функционал в любом случае стоит рассматривать как unstable с точки зрения сохранения совместимости на уровне кода (вместе с соответствующими функциями API для Wikibase Client).

Далее описывается внутренняя документация. Названия функций и параметров могут изменяться. При их изменении автор изменений обязан обновить шаблон {{ wikidata }} и специализированные шаблоны свойств. Изменения в других местах, если кто-то всё-таки вызывает функции модуля напрямую, остаются на совести автора «костыля». Итак, при вызове шаблона {{ wikidata }} или специализированного шаблона свойства управление отдаётся на функцию formatStatements, которая принимает frame. Из frame достаются следующие опции, которые так или иначе передаются в остальные функции:

  • plain — булевый переключатель (по умолчанию false). Если true, результат совпадает с обычным вызовом {{#property:pNNN}} (по факту им и будет являться)
  • references — булевый переключатель (по умолчанию true). Если true, после вывода значения параметра дополнительно выводит ссылки на источники, указанные в Викиданных. Для вывода используется Модуль:Sources . Обычно отключается для тех свойств, которые являются «самоописываемыми», например, внешними идентификаторами или ссылками (когда такая ссылка является доказательством своей актуальности), например, идентификаторы IMDb.
  • value — значение, которое надо выводить вместо значений из Викиданных (используется, если что-то задано уже в карточке в виде т. н. локального свойства)

По умолчанию модуль поддерживает вывод следующих значений без дополнительных настроек:

  • географические координаты (coordinates)
  • количественные значения (quantity)
  • моноязычный текст (monolingualtext)
  • строки (string)
  • даты (time)

Остальные типы данных требуют указания функции форматирования значения.

Кастомизация

Поддерживаются три типа параметров-функций, которые дополнительно указывают, как надо форматировать значения:

  • property-module , property-function — название модуля и функции модуля, которые отвечают за форматирование вывода массива значений свойства (property) с учётом квалификаторов, ссылок и прочего. Например, оформляет множество выводов в таблицу или график. Характерные примеры:
    Спецификация функции: function p.…( context, options ) , поведение по умолчанию: .
  • claim-module , claim-function — название модуля и функции модуля, которые отвечают за форматирование вывода значения свойства (statement, claim) с учётом квалификаторов, ссылок и прочего. Может, например, дополнительно к основному значению (main snak) вывести значения квалификаторов. Характерные примеры:
    Спецификация функции: function p.…( context, options, statement )
  • value-module , value-function — название модуля и функции модуля, которые отвечают за форматирование значения (snak, snak data value), в зависимости от контекста, как значений свойства, так и значений квалификатора (если вызывается из claim-module/claim-function ). Необходимо для изменения отображения свойства, например, генерации викиссылки вместо простой строки или даже вставки изображения вместо отображения имени файла изображения (так как ссылки на изображения хранятся как строки). Характерные примеры:
    Спецификация функции: function p.…( value, options )

Заготовки функций

Context API

Переменные

  • entity
  • frame

Методы

  • cloneOptions( options )
  • getSourcingCircumstances( statement )
  • formatProperty( options )
  • formatPropertyDefault( context, options )
  • formatSnak( options, snak, circumstances )
  • formatStatement( options, statement )
  • formatStatementDefault( context, options, statement )
  • formatRefs( options, statement )
  • formatValueDefault( context, options, value )
  • parseTimeBoundariesFromSnak( snak )
  • parseTimeFromSnak( snak )
  • selectClaims( options, propertyId )
  • wrapSnak( value, hash, attributes )
  • wrapStatement( value, propertyId, claimId, attributes )
  • wrapQualifier( value, qualifierId, attributes )

Функции для форматирования

property-function

claim-function

value-function

См. также

---settings, may differ from project to project
local fileDefaultSize = '267x400px'
local outputReferences = true
local writingSystemElementId = 'Q8209'
local langElementId = 'Q7737'

---Ссылки на используемые модули, которые потребуются в 99% случаев загрузки страниц (чтобы иметь на виду при переименовании)
local moduleSources = require( 'Module:Sources' )
local WDS = require( 'Module:WikidataSelectors' )

---Константы
---@type string
local CONTENT_LANGUAGE_CODE = mw.language.getContentLanguage():getCode()

local p = {}
local g_config, g_frame
local formatDatavalue, formatEntityId, formatRefs, formatSnak, formatStatement,
formatStatementDefault, getSourcingCircumstances, getPropertyParams

---@param obj table
---@param target table
---@param skipEmpty boolean | nil
---@return table
local function copyTo( obj, target, skipEmpty )
    for key, val in pairs( obj ) do
        if skipEmpty ~= true or ( val ~= nil and val ~= '' ) then
            target[ key ] = val
        end
    end
    return target
end

---@param prev number | nil
---@param next number | nil
---@return number | nil
local function min( prev, next )
    if prev == nil or prev > next then
        return next
    end
    return prev
end

---@param prev number | nil
---@param next number | nil
---@return number | nil
local function max( prev, next )
    if prev == nil or prev < next then
        return next
    end
    return prev
end

---@param section string
---@param code string
---@return any | nil
local function getConfig( section, code )
    if g_config == nil then
        g_config = require( 'Module:Wikidata/config' )
    end
    if not g_config then
        g_config = {}
    end

    if not section then
        return g_config
    end
    if not code then
        return g_config[ section ] or {}
    end

    if not g_config[ section ] then
        return nil
    end
    return g_config[ section ][ code ]
end

---@param code string
---@param sortKey string | nil
---@return string
local function getCategoryByCode( code, sortKey )
    local value = getConfig( 'categories', code )
    if not value or value == '' then
        return ''
    end

    if sortKey ~= nil then
        return '[[Category:' .. value .. '|' .. sortKey .. ']]'; -- экранировать?
    else
        return '[[Category:' .. value .. ']]'
    end
end

---@param isoStr string | table
---@return table | nil
local function splitISO8601( isoStr )
    if 'table' == type( isoStr ) then
        if isoStr.args and isoStr.args[ 1 ] then
            isoStr = '' .. isoStr.args[ 1 ]
        else
            return 'unknown argument type: ' .. type( isoStr ) .. ': ' .. table.tostring( isoStr )
        end
    end
    local Y, M, D = ( function( str )
        local pattern = "(%-?%d+)%-(%d+)%-(%d+)T"
        local _Y, _M, _D = mw.ustring.match( str, pattern )
        return tonumber( _Y ), tonumber( _M ), tonumber( _D )
    end )( isoStr )
    local h, m, s = ( function( str )
        local pattern = "T(%d+):(%d+):(%d+)%Z"
        local _H, _M, _S = mw.ustring.match( str, pattern )
        return tonumber( _H ), tonumber( _M ), tonumber( _S )
    end )( isoStr )
    local oh, om = ( function( str )
        if str:sub(-1) == "Z" then  -- ends with Z, Zulu time
            return 0, 0
        end
        -- matches ±hh:mm, ±hhmm or ±hh; else returns nils
        local pattern = "([-+])(%d%d):?(%d?%d?)$"
        local sign, oh, om = mw.ustring.match( str, pattern )
        sign, oh, om = sign or "+", oh or "00", om or "00"
        return tonumber( sign .. oh ), tonumber( sign .. om )
    end )( isoStr )
    return { year=Y, month=M, day=D, hour=( h + oh ), min=( m + om ), sec=s }
end

---@param time string
---@param precision number
---@return table | nil
local function parseTimeBoundaries( time, precision )
    local s = splitISO8601( time )
    if not s then
        return nil
    end

    if precision >= 0 and precision <= 8 then
        local powers = { 1000000000 , 100000000, 10000000, 1000000, 100000, 10000, 1000, 100, 10 }
        local power = powers[ precision + 1 ]
        local left = s.year - ( s.year % power )
        return { tonumber( os.time( { year=left, month=1, day=1, hour=0, min=0, sec=0 } ) ) * 1000,
                 tonumber( os.time( { year=left + power - 1, month=12, day=31, hour=29, min=59, sec=58 } ) ) * 1000 + 1999 }
    end

    if precision == 9 then
        return { tonumber( os.time( { year=s.year, month=1, day=1, hour=0, min=0, sec=0} )) * 1000,
                 tonumber( os.time( { year=s.year, month=12, day=31, hour=23, min=59, sec=58} )) * 1000 + 1999 }
    end

    if precision == 10 then
        local lastDays = { 31, 28.25, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
        local lastDay = lastDays[ s.month ]
        return { tonumber( os.time( { year=s.year, month=s.month, day=1, hour=0, min=0, sec=0 } ) ) * 1000,
                 tonumber( os.time( { year=s.year, month=s.month, day=lastDay, hour=23, min=59, sec=58 } ) ) * 1000 + 1999 }
    end

    if precision == 11 then
        return { tonumber( os.time( { year=s.year, month=s.month, day=s.day, hour=0, min=0, sec=0 } ) ) * 1000,
                 tonumber( os.time( { year=s.year, month=s.month, day=s.day, hour=23, min=59, sec=58 } ) ) * 1000 + 1999 }
    end

    if precision == 12 then
        return { tonumber( os.time( { year=s.year, month=s.month, day=s.day, hour=s.hour, min=0, sec=0 } ) ) * 1000,
                 tonumber( os.time( { year=s.year, month=s.month, day=s.day, hour=s.hour, min=59, sec=58 } ) ) * 1000 + 1999 }
    end

    if precision == 13 then
        return { tonumber( os.time( { year=s.year, month=s.month, day=s.day, hour=s.hour, min=s.min, sec=0 } ) ) * 1000,
                 tonumber( os.time( { year=s.year, month=s.month, day=s.day, hour=s.hour, min=s.min, sec=58 } ) ) * 1000 + 1999 }
    end

    if precision == 14 then
        local t = tonumber( os.time( { year=s.year, month=s.month, day=s.day, hour=s.hour, min=s.min, sec=0 } ) )
        return { t * 1000, t * 1000 + 999 }
    end

    error( 'Unsupported precision: ' .. precision )
end

---Функция для формирования категории на основе wikidata/config
---@param options table
---@param entityId string
---@return string
local function extractCategory( options, entityId )
    if not entityId or not options.category or options.nocat then
        return ''
    end
    if type( entityId ) ~= 'string' then
        entityId = entityId.id
    end
    local claims = WDS.load( entityId, options.category )
    if not claims then
        return ''
    end

    for _, claim in pairs( claims ) do
        if claim
                and claim.mainsnak
                and claim.mainsnak.datavalue
                and claim.mainsnak.datavalue.type == 'wikibase-entityid'
        then
            local catEntityId = claim.mainsnak.datavalue.value.id
            local wbStatus, catSiteLink = pcall( mw.wikibase.getSitelink, catEntityId )

            if wbStatus and catSiteLink then
                return '[[' .. catSiteLink .. ']]'
            end
        end
    end

    return ''
end

---Преобразует строку в булевое значение
---@param valueToParse string
---@return boolean Преобразованное значение, если его удалось распознать, или defaultValue во всех остальных случаях
local function toBoolean( valueToParse, defaultValue )
    if valueToParse ~= nil then
        if valueToParse == false or valueToParse == '' or valueToParse == 'false' or valueToParse == '0' then
            return false
        end
        return true
    end
    return defaultValue
end

---Обрачивает отформатированное значение в инлайновый или блочный тег.
---@param value string value
---@param attributes table of attributes
---@return string HTML tag with value
local function wrapValue( value, attributes )
    local tagName = 'span'
    local spacer = ''
    if string.match( value, '\n' )
            or string.match( value, '<t[dhr][ >]' )
            or string.match( value, '<div[ >]' )
            or string.find( value, 'UNIQ%-%-imagemap' )
    then
        tagName = 'div'
        spacer = '\n'
    end
    local attrString = ''
    for key, val in pairs( attributes or {} ) do
        local _key = mw.text.trim( key )
        local _value = mw.text.encode( mw.text.trim( val ) )
        attrString = attrString .. _key .. '="' .. _value .. '" '
    end
    return '<' .. tagName .. ' ' .. attrString .. '>' .. spacer .. value .. '</' .. tagName .. '>'
end

---Wraps formatted snak value into HTML tag with attributes.
---@param value string value of snak
---@param hash string
---@param attributes table of extra attributes
---@return string HTML tag with value
local function wrapSnak( value, hash, attributes )
    local newAttributes = mw.clone( attributes or {} )
    newAttributes[ 'class' ] = ( newAttributes[ 'class' ] or '' ) .. ' wikidata-snak'

    if hash then
        newAttributes[ 'data-wikidata-hash'] = hash
    else
        newAttributes[ 'class' ] = newAttributes[ 'class' ] .. ' wikidata-main-snak'
    end

    return wrapValue( value, newAttributes )
end

---Wraps formatted statement value into HTML tag with attributes.
---@param value string value of statement
---@param propertyId string PID of property
---@param claimId string ID of claim or nil for local value
---@param attributes table of extra attributes
---@return string HTML tag with value
local function wrapStatement( value, propertyId, claimId, attributes )
    local newAttributes = mw.clone( attributes or {} )
    newAttributes[ 'class' ] = newAttributes[ 'class' ] or ''
    newAttributes[ 'data-wikidata-property-id' ] = string.upper( propertyId )

    if claimId then
        newAttributes[ 'class' ] = newAttributes[ 'class' ] .. ' wikidata-claim'
        newAttributes[ 'data-wikidata-claim-id' ] = claimId
    else
        newAttributes[ 'class' ] = newAttributes[ 'class' ] .. ' no-wikidata'
    end

    return wrapValue( value, newAttributes )
end

---Wraps formatted qualifier's statement value into HTML tag with attributes.
---@param value string value of qualifier's statement
---@param qualifierId string PID of qualifier
---@param attributes table of extra attributes
---@return string HTML tag with value
local function wrapQualifier( value, qualifierId, attributes )
    local newAttributes = mw.clone( attributes or {} )
    newAttributes[ 'data-wikidata-qualifier-id' ] = string.upper( qualifierId )
    return wrapValue( value, newAttributes )
end

---Функция для получения сущности (еntity) для текущей страницы
---Подробнее о сущностях см. d:Wikidata:Glossary/ru
---@param id string Идентификатор (типа P18, Q42)
---@return table Таблица, элементы которой индексируются с нуля
local function getEntityFromId( id )
    local entity
    local wbStatus

    if id then
        wbStatus, entity = pcall( mw.wikibase.getEntity, id )
    else
        wbStatus, entity = pcall( mw.wikibase.getEntity )
    end

    return entity
end

---Внутренняя функция для формирования сообщения об ошибке
---@param key string Ключ элемента в таблице config.errors (например entity-not-found)
---@return void
local function throwError( key )
    error( getConfig( 'errors', key ) )
end

---Функция для получения идентификатора сущностей
---@param value table
---@return string
local function getEntityIdFromValue( value )
    local prefix = ''
    if value[ 'entity-type' ] == 'item' then
        prefix = 'Q'
    elseif value[ 'entity-type' ] == 'property' then
        prefix = 'P'
    else
        throwError( 'unknown-entity-type' )
    end
    return prefix .. value[ 'numeric-id' ]
end

---Проверка на наличие специализированной функции в опциях
---@param options table
---@param prefix string
---@return function
local function getUserFunction( options, prefix, defaultFunction )
    -- проверка на указание специализированных обработчиков в параметрах,
    -- переданных при вызове
    if options[ prefix .. '-module' ] or options[ prefix .. '-function' ] then
        -- проверка на пустые строки в параметрах или их отсутствие
        if not options[ prefix .. '-module' ] or not options[ prefix .. '-function' ] then
            throwError( 'unknown-' .. prefix .. '-module' )
        end
        -- динамическая загруза модуля с обработчиком указанным в параметре
        local formatter = require( 'Module:' .. options[ prefix .. '-module' ] )
        if formatter == nil then
            throwError( prefix .. '-module-not-found' )
        end
        local fun = formatter[ options[ prefix .. '-function' ] ]
        if fun == nil then
            throwError( prefix .. '-function-not-found' )
        end
        return fun
    end

    return defaultFunction
end

---Выбирает свойства по property id, дополнительно фильтруя их по рангу
---@param context table
---@param options table
---@param propertySelector string
---@return table | nil
local function selectClaims( context, options, propertySelector )
    if not context then error( 'context not specified' ); end
    if not options then error( 'options not specified' ); end
    if not options.entityId then error( 'options.entity is missing' ); end
    if not propertySelector then error( 'propertySelector not specified' ); end

    local result = WDS.load( options.entityId, propertySelector )

    if not result or #result == 0 then
        return nil
    end

    if options.limit and options.limit ~= '' and options.limit ~= '-'  then
        local limit = tonumber( options.limit, 10 )
        while #result > limit do
            table.remove( result )
        end
    end

    return result
end

---Функция для получения значения свойства элемента в заданный момент времени.
---@param entityId string
---@param boundaries table Временные границы
---@param propertyIds table<string>
---@param selectors table<string>
---@return table Таблица соответствующих значений свойства
local function getPropertyInBoundaries( context, entityId, boundaries, propertyIds, selectors )
    if type( entityId ) ~= 'string' then error( 'type of entityId argument expected string, but was ' .. type(entityId)); end

    local results = {}

    if not propertyIds or #propertyIds == 0 then
        return results
    end

    for i, propertyId in ipairs( propertyIds ) do
        local selector
        if selectors ~= nil then
            selector = selectors[ i ] or selectors[ propertyId ] or propertyId
        else
            selector = propertyId
        end

        local fakeAllClaims = {}
        fakeAllClaims[ propertyId ] = mw.wikibase.getAllStatements( entityId, propertyId )

        local filteredClaims = WDS.filter( fakeAllClaims, selector .. '[rank:preferred, rank:normal]' )
        if filteredClaims then
            for _, claim in pairs( filteredClaims ) do
                if not boundaries then
                    table.insert( results, claim.mainsnak )
                else
                    local startBoundaries = p.getTimeBoundariesFromQualifier( context.frame, context, claim, 'P580' )
                    local endBoundaries = p.getTimeBoundariesFromQualifier( context.frame, context, claim, 'P582' )

                    if ( startBoundaries == nil or startBoundaries[ 1 ] <= boundaries[ 1 ] ) and
                            ( endBoundaries == nil or endBoundaries[ 1 ] >= boundaries[ 2 ] )
                    then
                        table.insert( results, claim.mainsnak )
                    end
                end
            end
        end

        if #results > 0 then
            break
        end
    end

    return results
end

---@param context table
---@param statement table
---@param qualifierId string
---@return table | nil
function p.getTimeBoundariesFromQualifier( _, context, statement, qualifierId )
    -- only support exact date so far, but need improvement
    local left, right
    if statement.qualifiers and statement.qualifiers[ qualifierId ] then
        for _, qualifier in pairs( statement.qualifiers[ qualifierId ] ) do
            local boundaries = context.parseTimeBoundariesFromSnak( qualifier )
            if not boundaries then
                return nil
            end
            left = min( left, boundaries[ 1 ] )
            right = max( right, boundaries[ 2 ] )
        end
    end

    if not left or not right then
        return nil
    end

    return { left, right }
end

---@param frame table
---@param context table
---@param statement table
---@param qualifierIds table<string>
---@return table | nil
function p.getTimeBoundariesFromQualifiers( frame, context, statement, qualifierIds )
    if not qualifierIds then
        qualifierIds = { 'P582', 'P580', 'P585' }
    end

    for _, qualifierId in pairs( qualifierIds ) do
        local result = p.getTimeBoundariesFromQualifier( frame, context, statement, qualifierId )
        if result then
            return result
        end
    end

    return nil
end

---@type table<string>
local getLabelWithLang_DEFAULT_PROPERTIES = { 'P1813', 'P1448', 'P1705' }

---@type table<string>
local getLabelWithLang_DEFAULT_SELECTORS = {
    'P1813[language:' .. CONTENT_LANGUAGE_CODE .. '][!P282,P282:' .. writingSystemElementId .. '][!P3831,P3831:Q105690470]',
    'P1448[language:' .. CONTENT_LANGUAGE_CODE .. '][!P282,P282:' .. writingSystemElementId .. '][!P3831,P3831:Q105690470]',
    'P1705[language:' .. CONTENT_LANGUAGE_CODE .. '][!P282,P282:' .. writingSystemElementId .. '][!P3831,P3831:Q105690470]'
}

---Функция для получения метки элемента в заданный момент времени.
---@param context table
---@param options table
---@param entityId string
---@param boundaries table
---@param propertyIds table
---@param selectors table<string>
---@return string, string Текстовая метка элемента, язык метки
local function getLabelWithLang( context, options, entityId, boundaries, propertyIds, selectors )
    if type( entityId ) ~= 'string' then error( 'type of entityId argument expected string, but was ' .. type( entityId ) ); end
    if not entityId then
        return nil
    end

    local langCode = CONTENT_LANGUAGE_CODE

    -- name from label
    local label
    if options.text and options.text ~= '' then
        label = options.text
    else
        if not propertyIds then
            propertyIds = getLabelWithLang_DEFAULT_PROPERTIES
            selectors = getLabelWithLang_DEFAULT_SELECTORS
        end

        -- name from properties
        local results = getPropertyInBoundaries( context, entityId, boundaries, propertyIds, selectors )

        for _, result in pairs( results ) do
            if result.datavalue and result.datavalue.value then
                if result.datavalue.type == 'monolingualtext' and result.datavalue.value.text then
                    label = result.datavalue.value.text
                    langCode = result.datavalue.value.language
                    break
                elseif result.datavalue.type == 'string' then
                    label = result.datavalue.value
                    break
                end
            end
        end

        if not label then
            label, langCode = mw.wikibase.getLabelWithLang( entityId )
            if not langCode then
                return nil
            end
        end
    end

    return label, langCode
end

---@param context table
---@param options table
---@return string
local function formatPropertyDefault( context, options )
    if not context then error( 'context not specified' ); end
    if not options then error( 'options not specified' ); end
    if not options.entityId then error( 'options.entityId missing' ); end

    local claims
    if options.property then -- TODO: Почему тут может не быть property?
        if options.rank then -- передать настройки ранга из конфига
            claims = context.selectClaims( options, options.property .. options.rank )
        else
            claims = context.selectClaims( options, options.property )
        end
    end
    if claims == nil then
        return '' --TODO error?
    end

    -- Обход всех заявлений утверждения и с накоплением оформленных предпочтительных
    -- заявлений в таблице
    local formattedClaims = {}

    for _, claim in pairs( claims ) do
        local formattedStatement = context.formatStatement( options, claim )
        -- здесь может вернуться либо оформленный текст заявления, либо строка ошибки, либо nil
        if formattedStatement and formattedStatement ~= '' then
        	if not options.plain then
	            formattedStatement = context.wrapStatement( formattedStatement, options.property, claim.id )
            end
            table.insert( formattedClaims, formattedStatement )
        end
    end

    -- создание текстовой строки со списком оформленых заявлений из таблицы
    local out = mw.text.listToText( formattedClaims, options.separator, options.conjunction )
    if out ~= '' then
        if options.before then
            out = options.before .. out
        end
        if options.after then
            out = out .. options.after
        end
    end

    return out
end

---Create context
---@param initOptions table
---@return table | nil
local function initContext( initOptions )
    local context = {
        entityId = initOptions.entityId,
        entity = initOptions.entity,
        extractCategory = extractCategory,
        formatSnak = formatSnak,
        formatPropertyDefault = formatPropertyDefault,
        formatStatementDefault = formatStatementDefault,
        getPropertyInBoundaries = getPropertyInBoundaries,
        getTimeBoundariesFromQualifier = p.getTimeBoundariesFromQualifier,
        getTimeBoundariesFromQualifiers = p.getTimeBoundariesFromQualifiers,
        wrapSnak = wrapSnak,
        wrapStatement = wrapStatement,
        wrapQualifier = wrapQualifier,
    }
    context.cloneOptions = function( options )
        local entity = options.entity
        options.entity = nil

        local newOptions = mw.clone( options )
        options.entity = entity
        newOptions.entity = entity
        newOptions.frame = options.frame; -- На склонированном фрейме frame:expandTemplate()

        return newOptions
    end
    context.formatProperty = function( options )
        local func = getUserFunction( options, 'property', context.formatPropertyDefault )
        return func( context, options )
    end
    context.formatStatement = function( options, statement ) return formatStatement( context, options, statement ) end
    context.formatSnak = function( options, snak, circumstances ) return formatSnak( context, options, snak, circumstances ) end
    context.formatRefs = function( options, statement ) return formatRefs( context, options, statement ) end

    context.parseTimeFromSnak = function( snak )
        if snak and snak.datavalue and snak.datavalue.value and snak.datavalue.value.time then
            return tonumber( os.time( splitISO8601( tostring( snak.datavalue.value.time ) ) ) ) * 1000
        end
        return nil
    end
    context.parseTimeBoundariesFromSnak = function( snak )
        if snak and snak.datavalue and snak.datavalue.value and snak.datavalue.value.time and snak.datavalue.value.precision then
            return parseTimeBoundaries( snak.datavalue.value.time, snak.datavalue.value.precision )
        end
        return nil
    end
    context.getSourcingCircumstances = function( statement )
        return getSourcingCircumstances( statement )
    end
    context.selectClaims = function( options, propertyId )
        return selectClaims( context, options, propertyId )
    end

    return context
end

---Функция для оформления утверждений (statement)
---Подробнее о утверждениях см. d:Wikidata:Glossary/ru
---@param options table
---@return string Formatted wikitext.
local function formatProperty( options )
    -- Получение сущности по идентификатору
    local entity = getEntityFromId( options.entityId )
    if not entity then
        return -- throwError( 'entity-not-found' )
    end
    -- проверка на присутсвие у сущности заявлений (claim)
    -- подробнее о заявлениях см. d:Викиданные:Глоссарий
    if not entity.claims then
        return '' --TODO error?
    end

    -- improve options
    options.frame = g_frame
    options.entity = entity
    options.extends = function( self, newOptions )
        return copyTo( newOptions, copyTo( self, {} ) )
    end

    if options.i18n then
        options.i18n = copyTo( options.i18n, copyTo( getConfig( 'i18n' ), {} ) )
    else
        options.i18n = getConfig( 'i18n' )
    end

    local context = initContext( options )

    return context.formatProperty( options )
end

---Функция для оформления одного утверждения (statement)
---@param context table
---@param options table
---@param statement table
---@return string Formatted wikitext.
function formatStatement( context, options, statement )
    if not statement then
        error( 'statement is not specified or nil' )
    end
    if not statement.type or statement.type ~= 'statement' then
        throwError( 'unknown-claim-type' )
    end

    local functionToCall = getUserFunction( options, 'claim', context.formatStatementDefault )
    return functionToCall( context, options, statement )
end

---@param statement table
---@return table
function getSourcingCircumstances( statement )
    if not statement then
        error( 'statement is not specified' )
    end

    local circumstances = {}
    if statement.qualifiers and statement.qualifiers.P1480 then
        for _, qualifier in pairs( statement.qualifiers.P1480 ) do
            if qualifier
                    and qualifier.datavalue
                    and qualifier.datavalue.type == 'wikibase-entityid'
                    and qualifier.datavalue.value
                    and qualifier.datavalue.value[ 'entity-type'] == 'item'
            then
                table.insert( circumstances, qualifier.datavalue.value.id )
            end
        end
    end
    return circumstances
end

---Функция для оформления одного утверждения (statement)
---@param context table Context.
---@param options table Parameters.
---@param statement table
---@return string Formatted wikitext.
function formatStatementDefault( context, options, statement )
    if not context then error( 'context is not specified' ) end
    if not options then error( 'options is not specified' ) end
    if not statement then error( 'statement is not specified' ) end

    local circumstances = context.getSourcingCircumstances( statement )

    options.qualifiers = statement.qualifiers

    local result = context.formatSnak( options, statement.mainsnak, circumstances )

    if options.qualifier and statement.qualifiers and statement.qualifiers[ options.qualifier ] then
        local qualifierConfig = getPropertyParams( options.qualifier, nil, {} )
        if options.i18n then
            qualifierConfig.i18n = options.i18n
        end
        if qualifierConfig.datatype == 'time' then
            qualifierConfig.nolinks = true
        end
        local qualifierValues = {}
        for _, qualifierSnak in pairs( statement.qualifiers[ options.qualifier ] ) do
            local snakValue = context.formatSnak( qualifierConfig, qualifierSnak )
            if snakValue and snakValue ~= '' then
                table.insert( qualifierValues, snakValue )
            end
        end
        if result and result ~= '' and #qualifierValues then
            if qualifierConfig.invisible then
                result = result .. table.concat( qualifierValues, ', ' )
            else
                result = result .. ' (' .. table.concat( qualifierValues, ', ' ) .. ')'
            end
        end
    end

    if result and result ~= '' and options.references then
        result = result .. context.formatRefs( options, statement )
    end

    return result
end

---Функция для оформления части утверждения (snak)
---Подробнее о snak см. d:Викиданные:Глоссарий
---@param context table Context.
---@param options table Parameters.
---@param snak table
---@param circumstances table
---@return string Formatted wikitext.
function formatSnak( context, options, snak, circumstances )
    circumstances = circumstances or {}
    local result

    if snak.snaktype == 'somevalue' then
        if options[ 'somevalue' ] and options[ 'somevalue' ] ~= '' then
            result = options[ 'somevalue' ]
        else
            result = options.i18n[ 'somevalue' ]
        end
    elseif snak.snaktype == 'novalue' then
        if options[ 'novalue' ] and options[ 'novalue' ] ~= '' then
            result = options[ 'novalue' ]
        else
            result = options.i18n[ 'novalue' ]
        end
    elseif snak.snaktype == 'value' then
        result = formatDatavalue( context, options, snak.datavalue, snak.datatype )
        for _, item in pairs( circumstances ) do
            if options.i18n[ item ] then
                result = options.i18n[ item ] .. result
            end
        end
    else
        throwError( 'unknown-snak-type' )
    end

    if not result or result == '' then
        return nil
    end
    
    if options.plain then
    	return result
	end

    return context.wrapSnak( result, snak.hash )
end

---Функция для оформления объектов-значений с географическими координатами
---@param value string Raw value.
---@param options table Parameters.
---@return string Formatted string.
local function formatGlobeCoordinate( value, options )
    -- проверка на требование в параметрах вызова на возврат сырого значения
    if options[ 'subvalue' ] == 'latitude' then -- широты
        return value[ 'latitude' ]
    elseif options[ 'subvalue' ] == 'longitude' then -- долготы
        return value[ 'longitude' ]
    elseif options[ 'nocoord' ] and options[ 'nocoord' ] ~= '' then
        -- если передан параметр nocoord, то не выводить координаты
        -- обычно это делается при использовании нескольких карточек на странице
        return ''
    else
        -- в противном случае формируются параметры для вызова шаблона {{coord}}
        -- нужно дописать в документации шаблона, что он отсюда вызывается, и что
        -- любое изменние его парамеров  должно быть согласовано с кодом тут

        local coordModule = require( 'Module:Coordinates' )

        local globe = options.globe or ''
        if globe == '' and value[ 'globe' ] then
            local globes = require( 'Module:Wikidata/Globes' )
            globe = globes[ value[ 'globe' ] ] or ''
        end

        local display = 'inline'
        if options.display and options.display ~= '' then
            display = options.display
        elseif ( options.property:upper() == 'P625' ) then
            display = 'title'
        end

        local format = options.format or ''
        if format == '' then
            format = 'dms'
            if value[ 'precision' ] then
                local precision = value[ 'precision' ] * 60
                if precision >= 60 then
                    format = 'd'
                elseif precision >= 1 then
                    format = 'dm'
                end
            end
        end

        g_frame.args = {
            tostring( value[ 'latitude' ] ),
            tostring( value[ 'longitude' ] ),
            globe = globe,
            type = options.type and options.type or '',
            scale = options.scale and options.scale or '',
            display = display,
            format = format,
        }

        return coordModule.coord(g_frame)
    end
end

---Функция для оформления объектов-значений с файлами с Викисклада
---@param value string Raw value.
---@param options table Parameters.
---@return string Formatted string.
local function formatCommonsMedia( value, options )
    local image = value

    local caption = ''
    if options[ 'caption' ] and options[ 'caption' ] ~= '' then
        caption = options[ 'caption' ]
    end
    if caption ~= '' then
        caption = wrapQualifier( caption, 'P2096', { class = 'media-caption', style = 'display:block' } )
    end

    if not string.find( value, '[%[%]%{%}]' ) and not string.find( value, 'UNIQ%-%-imagemap' ) then
        -- если в value не содержится викикод или imagemap, то викифицируем имя файла
        -- ищем слово imagemap в строке, потому что вставляется плейсхолдер: [[phab:T28213]]
        image = '[[File:' .. value .. '|frameless'
        if options[ 'border' ] and options[ 'border' ] ~= '' then
            image = image .. '|border'
        end

        local size = options[ 'size' ]
        if size and size ~= '' then
            -- TODO: check localized pixel names too
            if not string.match( size, 'px$' ) then
                size = size .. 'px'
            end
        else
            size = fileDefaultSize
        end
        image = image .. '|' .. size

        if options[ 'alt' ] and options[ 'alt' ] ~= '' then
            image = image .. '|alt=' .. options[ 'alt' ]
        end

        if caption ~= '' then
            image = image .. '|' .. caption
        end
        image = image .. ']]'

        if caption ~= '' then
            image = image .. '<br>' .. caption
        end
    else
        image = image .. caption .. getCategoryByCode( 'media-contains-markup' )
    end

    return image
end

---Function for render math formulas
---@param value string Value.
---@param options table Parameters.
---@return string Formatted string.
local function formatMath( value, options )
    return options.frame:extensionTag{ name = 'math', content = value }
end

---Функция для оформления внешних идентификаторов
---@param value string
---@param options table
---@return string
local function formatExternalId( value, options )
    local formatter = options.formatter
    local propertyId = options.property:upper()

    if not formatter or formatter == '' then
        local isGoodFormat = false

        local wbStatus, formatRegexStatements = pcall( mw.wikibase.getBestStatements, propertyId, 'P1793' )
        if wbStatus and formatRegexStatements then
            for _, statement in pairs( formatRegexStatements ) do
                if statement.mainsnak.snaktype == 'value' then
                    local pattern = mw.ustring.gsub( statement.mainsnak.datavalue.value, '\\', '%' )
                    pattern = mw.ustring.gsub( pattern, '{%d+,?%d*}', '+' )
                    if ( string.find( pattern, '|' ) or string.find( pattern, '%)%?' )
                            or mw.ustring.match( value, '^' .. pattern .. '$' ) ~= nil ) then
                        isGoodFormat = true
                        break
                    end
                end
            end
        end

        if isGoodFormat then
            local formatterStatements
            wbStatus, formatterStatements = pcall( mw.wikibase.getBestStatements, propertyId, 'P1630' )
            if wbStatus and formatterStatements then
                for _, statement in pairs( formatterStatements ) do
                    if statement.mainsnak.snaktype == 'value' then
                        formatter = statement.mainsnak.datavalue.value
                        break
                    end
                end
            end
        end
    end

    if formatter and formatter ~= '' then
        local encodedValue = mw.ustring.gsub( value, '%%', '%%%%' ) -- ломается, если подставить внутрь другого mw.ustring.gsub

        local link = mw.ustring.gsub(
                mw.ustring.gsub( formatter, '$1', encodedValue ), '.',
                { [ ' ' ] = '%20', [ '+' ] = '%2b', [ '[' ] = '%5B', [ ']' ] = '%5D' } )

        local title = options.title
        if not title or title == '' then
            title = '$1'
        end
        title = mw.ustring.gsub(
                mw.ustring.gsub( title, '$1', encodedValue ), '.',
                { [ '[' ] = '(', [ ']' ] = ')' } )

        return '[' .. link .. ' ' .. title .. ']'
    end

    return value
end

---Функция для оформления числовых значений
---@param value table Объект-значение
---@param options table Таблица параметров
---@return string Оформленный текст
local function formatQuantity( value, options )
    -- диапазон значений
    local amount = string.gsub( value.amount, '^%+', '' )
    local lang = mw.language.getContentLanguage()
    local langCode = lang:getCode()

    local function formatNum( number, sigfig )
        local multiplier = ''

        if options.countByThousands then
            local powers = options.i18n.thousandPowers
            local pos = 1
            while math.abs( number ) >= 1000 and pos < #powers do
                number = number / 1000
                pos = pos + 1
            end
            multiplier = powers[ pos ]

            if math.abs( number ) >= 100 then
                sigfig = sigfig or 0
            elseif math.abs( number ) >= 10 then
                sigfig = sigfig or 1
            else
                sigfig = sigfig or 2
            end
        else
            sigfig = sigfig or 12 -- округление до 12 знаков после запятой, на 13-м возникает ошибка в точности
        end

        local iMultiplier = 10^sigfig
        number = math.floor( number * iMultiplier + 0.5 ) / iMultiplier
        return string.gsub( lang:formatNum( number ), '^-', '−' ) .. multiplier
    end

    local out = formatNum( tonumber( amount ) )
    if value.upperBound then
        local diff = tonumber( value.upperBound ) - tonumber( amount )
        if diff > 0 then -- временная провека, пока у большинства значений не будет убрано ±0
            -- Пробуем понять до какого знака округлять
            local integer, dot, decimals, _ = value.upperBound:match( '^+?-?(%d*)(%.?)(%d*)(.*)' )
            local precision
            if dot == '' then
                precision = -integer:match( '0*$' ):len()
            else
                precision = #decimals
            end
            local bound = formatNum( diff, precision )
            if string.match( bound, 'E%-(%d+)' ) then -- если в экспоненциальном формате
                local digits = tonumber( string.match( bound, 'E%-(%d+)' ) ) - 2
                bound = formatNum( diff * 10 ^ digits, precision )
                bound = string.sub( bound, 0, 2 ) .. string.rep( '0', digits ) .. string.sub( bound, -string.len( bound ) + 2 )
            end
            out = out .. ' ± ' .. bound
        end
    end

    if options.unit and options.unit ~= '' then
        if options.unit ~= '-' then
            out = out .. ' ' .. options.unit
        end
    elseif value.unit and string.match( value.unit, 'http://www.wikidata.org/entity/' ) then
        local unitEntityId = string.gsub( value.unit, 'http://www.wikidata.org/entity/', '' )
        if unitEntityId ~= 'undefined' then
            local wbStatus, unitEntity = pcall( mw.wikibase.getEntity, unitEntityId )
            if wbStatus == true and unitEntity then
                if unitEntity.claims.P2370 and
                        unitEntity.claims.P2370[ 1 ].mainsnak.snaktype == 'value' and
                        not value.upperBound and
                        options.siConversion == true
                then
                    local conversionToSiUnit = string.gsub( unitEntity.claims.P2370[ 1 ].mainsnak.datavalue.value.amount, '^%+', '' )
                    if math.floor( math.log10( conversionToSiUnit ) ) ~= math.log10( conversionToSiUnit ) then
                        -- Если не степени десятки (переводить сантиметры в метры не надо!)
                        local outValue = tonumber( amount ) * conversionToSiUnit

                        if outValue > 0 then
                            -- Пробуем понять до какого знака округлять
                            local integer, dot, decimals, _ = amount:match( '^(%d*)(%.?)(%d*)(.*)' )
                            local precision
                            if dot == '' then
                                precision = -integer:match( '0*$' ):len()
                            else
                                precision = #decimals
                            end
                            local adjust = math.log10( math.abs( conversionToSiUnit ) ) + math.log10( 2 )
                            local minPrecision = 1 - math.floor( math.log10( outValue ) + 2e-14 )
                            out = formatNum( outValue, math.max( math.floor( precision + adjust ), minPrecision ) )
                        else
                            out = formatNum( outValue, 0 )
                        end
                        unitEntityId = string.gsub( unitEntity.claims.P2370[ 1 ].mainsnak.datavalue.value.unit, 'http://www.wikidata.org/entity/', '' )
                        wbStatus, unitEntity = pcall( mw.wikibase.getEntity, unitEntityId )
                    end
                end

                local label = getLabelWithLang( context, options, unitEntity.id, nil, { "P5061", "P558", "P558" }, {
                    'P5061[language:' .. langCode .. ']',
                    'P558[P282:' .. writingSystemElementId .. ', P407:' .. langElementId .. ']',
                    'P558[!P282][!P407]'
                } )

                out = out .. ' ' .. label
            end
        end
    end

    return out
end

---Функция для оформления URL
---@param context table
---@param options table
---@param value string
local function formatUrlValue( context, options, value )
    if not options.length or options.length == '' then
        options.length = 25
    end

    local moduleUrl = require( 'Module:URL' )
    return moduleUrl.formatUrlSingle( context, options, value )
end

local DATATYPE_CACHE = {}

---Get property datatype by ID.
---@param propertyId string Property ID, e.g. 'P123'.
---@return string Property datatype, e.g. 'commonsMedia', 'time' or 'url'.
local function getPropertyDatatype( propertyId )
    if not propertyId or not string.match( propertyId, '^P%d+$' ) then
        return nil
    end

    local cached = DATATYPE_CACHE[ propertyId ]
    if cached ~= nil then
        return cached
    end

    local wbStatus, propertyEntity = pcall( mw.wikibase.getEntity, propertyId )
    if wbStatus ~= true or not propertyEntity then
        return nil
    end
    mw.log("Loaded datatype " .. propertyEntity.datatype .. " of " .. propertyId .. ' from wikidata, consider passing datatype argument to formatProperty call or to Wikidata/config' )

    DATATYPE_CACHE[ propertyId ] = propertyEntity.datatype
    return propertyEntity.datatype
end

---@param datavalue table
---@return function
local function getPlainValueFunction( datavalue, _ )
    if datavalue.type == 'wikibase-entityid' then
        return function( _, _, value )
            return getEntityIdFromValue( value )
        end
    elseif datavalue.type == 'string' then
        return function( _, _, value )
            return value
        end
    elseif datavalue.type == 'monolingualtext' then
        return function( _, _, value )
            return value.text
        end
    elseif datavalue.type == 'globecoordinate' then
        return function( _, _, value )
            return value.latitude .. ',' .. value.longitude
        end
    elseif datavalue.type == 'quantity' then
        return function( _, _, value )
            return value.amount
        end
    elseif datavalue.type == 'time' then
        return function( _, _, value )
            return value.time
        end
    end

    throwError( 'unknown-datavalue-type' )
end

---@param datavalue table
---@param datatype string
---@return function
local function getDefaultValueFunction( datavalue, datatype )
    -- вызов обработчиков по умолчанию для известных типов значений
    if datavalue.type == 'wikibase-entityid' then
        -- Entity ID
        return function( context, options, value )
            return formatEntityId( context, options, getEntityIdFromValue( value ) )
        end
    elseif datavalue.type == 'string' then
        -- String
        if datatype and datatype == 'commonsMedia' then
            -- Media
            return function( _, options, value )
                return formatCommonsMedia( value, options )
            end
        elseif datatype and datatype == 'external-id' then
            -- External ID
            return function( _, options, value )
                return formatExternalId( value, options )
            end
        elseif datatype and datatype == 'math' then
            -- Math formula
            return function( _, options, value )
                return formatMath( value, options )
            end
        elseif datatype and datatype == 'url' then
            -- URL
            return formatUrlValue
        end
        return function( _, _, value )
            return value
        end
    elseif datavalue.type == 'monolingualtext' then
        -- моноязычный текст (строка с указанием языка)
        return function( _, options, value )
            if options.monolingualLangTemplate == 'lang' then
                if value.language == CONTENT_LANGUAGE_CODE then
                    return value.text
                end
                return options.frame:expandTemplate{ title = 'lang-' .. value.language, args = { value.text } }
            elseif options.monolingualLangTemplate == 'ref' then
                return '<span class="lang" lang="' .. value.language .. '">' .. value.text .. '</span>' .. options.frame:expandTemplate{ title = 'ref-' .. value.language }
            else
                return '<span class="lang" lang="' .. value.language .. '">' .. value.text .. '</span>'
            end
        end
    elseif datavalue.type == 'globecoordinate' then
        -- географические координаты
        return function( _, options, value )
            return formatGlobeCoordinate( value, options )
        end
    elseif datavalue.type == 'quantity' then
        return function( _, options, value )
            return formatQuantity( value, options )
        end
    elseif datavalue.type == 'time' then
        return function( context, options, value )
            local moduleDate = require( 'Module:Wikidata/date' )
            return moduleDate.formatDate( context, options, value )
        end
    end

    -- во всех стальных случаях возвращаем ошибку
    throwError( 'unknown-datavalue-type' )
end

---Функция для оформления значений (value)
---Подробнее о значениях  см. d:Wikidata:Glossary/ru
---@param context table
---@param options table
---@param datavalue table
---@param datatype string
---@return string Оформленный текст
function formatDatavalue( context, options, datavalue, datatype )
    if not context then error( 'context not specified' ); end
    if not options then error( 'options not specified' ); end
    if not datavalue then error( 'datavalue not specified' ); end
    if not datavalue.value then error( 'datavalue.value is missing' ); end

    -- проверка на указание специализированных обработчиков в параметрах,
    -- переданных при вызове
    if options.plain then
        context.formatValueDefault = getPlainValueFunction( datavalue, datatype )
    else
        context.formatValueDefault = getDefaultValueFunction( datavalue, datatype )
    end
    local functionToCall = getUserFunction( options, 'value', context.formatValueDefault )
    return functionToCall( context, options, datavalue.value )
end

local DEFAULT_BOUNDARIES = { os.time() * 1000, os.time() * 1000}

---Функция для оформления идентификатора сущности
---@param context table
---@param options table
---@param entityId string
---@return string Оформленный текст
function formatEntityId( context, options, entityId )
    -- получение локализованного названия
    local boundaries
    if options.qualifiers then
        boundaries = p.getTimeBoundariesFromQualifiers( context.frame, context, { qualifiers = options.qualifiers } )
    end
    if not boundaries then
        boundaries = DEFAULT_BOUNDARIES
    end
    local label, labelLanguageCode = getLabelWithLang( context, options, entityId, boundaries )

    -- определение соответствующей показываемому элементу категории
    local category = context.extractCategory( options, { id = entityId } )

    -- получение ссылки по идентификатору
    local link = mw.wikibase.sitelink( entityId )
    if link then
        -- ссылка на категорию, а не добавление страницы в неё
        if mw.ustring.match( link, '^' .. mw.site.namespaces[ 14 ].name .. ':' ) then
            link = ':' .. link
        end
        if label and not options.rawArticle then
            if labelLanguageCode ~= CONTENT_LANGUAGE_CODE then
                label = '<span lang="' .. label .. '">' .. label .. '</span>'
            end
            local a = '[[' .. link .. '|' .. label .. ']]'
            if CONTENT_LANGUAGE_CODE ~= labelLanguageCode and 'mul' ~= labelLanguageCode then
                a = a .. getCategoryByCode( 'links-to-entities-with-missing-local-language-label' )
            end
            return a .. category
        else
            return '[[' .. link .. ']]' .. category
        end
    end

    if label then  -- TODO: возможно, лучше просто mw.wikibase.getLabel(entityId)
        -- красная ссылка
        -- TODO: разобраться, почему не всегда есть options.frame
        local title = mw.title.new( label )
        if title and not title.exists and options.frame then
            local moduleRedLink = require( 'Module:Wikidata/redLink' )
            local rawLabel = mw.wikibase.getLabel(entityId) or label -- без |text= и boundaries; or label - костыль
            local redLink = moduleRedLink.formatRedLinkWithInfobox(rawLabel, label, entityId)
            if CONTENT_LANGUAGE_CODE ~= labelLanguageCode and 'mul' ~= labelLanguageCode then
                redLink = '<span lang="' .. labelLanguageCode .. '">' .. redLink .. '</span>' ..
                        getCategoryByCode( 'links-to-entities-with-missing-local-language-label' )
            end
            return redLink .. '<sup>[[:d:' .. entityId .. '|[d]]]</sup>' .. category
        end

        -- TODO: перенести до проверки на существование статьи
        local sup = ''
        if ( not options.format or options.format ~= 'text' )
                and entityId ~= 'Q6581072' and entityId ~= 'Q6581097' -- TODO: переписать на format=text
        then
            sup = '<sup class="plainlinks noprint">[//www.wikidata.org/wiki/' .. entityId .. '?uselang=' .. CONTENT_LANGUAGE_CODE .. ' [d&#x5d;]</sup>'
        end

        -- одноимённая статья уже существует - выводится текст и ссылка на ВД
        return '<span class="iw" data-title="' .. label .. '">' .. label
                .. sup
                .. '</span>' .. category
    end
    -- сообщение об отсутвии локализованного названия
    -- not good, but better than nothing
    return '[[:d:' .. entityId .. '|' .. entityId .. ']]<span style="border-bottom: 1px dotted; cursor: help; white-space: nowrap" title="В Викиданных нет русской подписи к элементу. Вы можете помочь, указав русский вариант подписи.">?</span>' .. getCategoryByCode( 'links-to-entities-with-missing-label' ) .. category
end

---Функция для оформления утверждений (statement)
---Подробнее о утверждениях см. d:Wikidata:Glossary/ru
---@deprecated Use p.formatProperty() instead
---@param frame table
---@return string Строка оформленного текста, предназначенная для отображения в статье
function p.formatStatements( frame )
    return p.formatProperty( frame )
end

---Получение параметров, которые обычно используются для вывода свойства.
---@param propertyId string
---@param datatype string
---@param params table
function getPropertyParams( propertyId, datatype, params )
    local config = getConfig()

    -- Различные уровни настройки параметров, по убыванию приоритета
    local propertyParams = {}

    -- 1. Параметры, указанные явно при вызове
    if params then
        for key, value in pairs( params ) do
            if value ~= '' then
                propertyParams[ key ] = value
            end
        end
    end
    
    if toBoolean( propertyParams.plain, false ) then
    	propertyParams.separator = propertyParams.separator or ', '
		propertyParams.conjunction = propertyParams.conjunction or ', '
	else
	    -- 2. Настройки конкретного параметра
	    if config.properties and config.properties[ propertyId ] then
	        for key, value in pairs( config.properties[ propertyId ] ) do
	            if propertyParams[ key ] == nil then
	                propertyParams[ key ] = value
	            end
	        end
	    end
	
	    -- 3. Указанный пресет настроек
	    if propertyParams.preset and config.presets and
	            config.presets[ propertyParams.preset ]
	    then
	        for key, value in pairs( config.presets[ propertyParams.preset ] ) do
	            if propertyParams[ key ] == nil then
	                propertyParams[ key ] = value
	            end
	        end
	    end
	
	    datatype = datatype or params.datatype or propertyParams.datatype or getPropertyDatatype( propertyId )
	    if propertyParams.datatype == nil then
	        propertyParams.datatype = datatype
	    end
	
	    -- 4. Настройки для типа данных
	    if datatype and config.datatypes and config.datatypes[ datatype ] then
	        for key, value in pairs( config.datatypes[ datatype ] ) do
	            if propertyParams[ key ] == nil then
	                propertyParams[ key ] = value
	            end
	        end
	    end
	
	    -- 5. Общие настройки для всех свойств
	    if config.global then
	        for key, value in pairs( config.global ) do
	            if propertyParams[ key ] == nil then
	                propertyParams[ key ] = value
	            end
	        end
	    end
	end

    return propertyParams
end

---Функция для оформления утверждений (statement)
---Подробнее о утверждениях см. d:Wikidata:Glossary/ru
---@param frame table
---@return string Строка оформленного текста, предназначенная для отображения в статье
function p.formatProperty( frame )
    local args = copyTo( frame.args, {} )

    -- проверка на отсутствие обязательного параметра property
    if not args.property then
        throwError( 'property-param-not-provided' )
    end
    local override
    local propertyId = mw.language.getContentLanguage():ucfirst( string.gsub( args.property, '([^Pp0-9].*)$', function(w)
        if string.sub( w, 1, 1 ) == '~' then
            override = w
        end
        return ''
    end ) )

    if override then
        args[ override:match( '[,~]([^=]*)=' ) ] = override:match( '=(.*)' )
        args.property = propertyId
    end

    -- проброс всех параметров из шаблона {wikidata} и параметра from откуда угодно
    local p_frame = frame
    while p_frame do
        if p_frame:getTitle() == mw.site.namespaces[ 10 ].name .. ':Wikidata' then
            copyTo( p_frame.args, args, true )
        end
        if p_frame.args and p_frame.args.from and p_frame.args.from ~= '' then
            args.entityId = p_frame.args.from
        else
            args.entityId = mw.wikibase.getEntityIdForCurrentPage()
        end
        p_frame = p_frame:getParent()
    end

    args = getPropertyParams( propertyId, nil, args )
    local datatype = args.datatype

    -- перевод итоговых значений флагов в true/false и добавление значений
    -- по умолчанию только в том случае, если они нигде не были указаны ранее
    args.plain = toBoolean( args.plain, false )
    args.nocat = not args.plain and toBoolean( args.nocat, false )
    args.references = not args.plain and toBoolean( args.references, true )

    -- если значение передано в параметрах вызова то выводим только его
    if args.value and args.value ~= '' then
        -- специальное значение для скрытия Викиданных
        if args.value == '-' then
            return ''
        end
        local value = args.value

        -- опция, запрещающая оформление значения, поэтому никак не трогаем
        if args.plain then
            return value
        end

        local context = initContext( args )
        -- обработчики по типу значения
        local wrapperExtraArgs = {}
        if args[ 'value-module' ] and args[ 'value-function' ] and not string.find( value, '[%[%]%{%}]' ) then
            local func = getUserFunction( args, 'value' )
            value = func( context, args, value )
        elseif datatype == 'commonsMedia' then
            value = formatCommonsMedia( value, args )
        elseif datatype == 'external-id' and not string.find( value, '[%[%]%{%}]' ) then
            wrapperExtraArgs[ 'data-wikidata-external-id' ] = mw.text.killMarkers( value )
            value = formatExternalId( value, args )
            --elseif datatype == 'math' then
            -- args.frame = frame -- костыль: в formatMath нужно frame:extensionTag
            --	value = formatMath( value, args )
        elseif datatype == 'url' then
            value = formatUrlValue( context, args, value )
        end

        -- оборачиваем в тег для JS-функций
        if string.match( propertyId, '^P%d+$' ) then
            value = mw.text.trim( value )

            -- временная штрафная категория для исправления табличных вставок
            local allowTables = getPropertyParams( propertyId, nil, {} ).allowTables
            if not allowTables
                    and string.match( value, '<t[dhr][ >]' )
            -- and not string.match( value, '<table[ >]' )
            -- and not string.match( value, '^%{%|' )
            then
                value = value .. getCategoryByCode( 'value-contains-table', propertyId )
            else
                value = wrapStatement( value, propertyId, nil, wrapperExtraArgs )
            end
        end

        return value
    end

    -- ability to disable loading Wikidata
    if args.entityId == '-' then
        return ''
    end

    g_frame = frame
    -- после проверки всех аргументов -- вызов функции оформления для свойства (набора утверждений)
    return formatProperty( args )
end

---Функция проверки на присутствие источника в списке нерекомендованных.
---@param snaks table
---@return boolean
local function isReferenceDeprecated( snaks )
    if not snaks then
        return false
    end
    if snaks.P248
            and snaks.P248[ 1 ]
            and snaks.P248[ 1 ].datavalue
            and snaks.P248[ 1 ].datavalue.value.id
    then
        local entityId = snaks.P248[ 1 ].datavalue.value.id
        if getConfig( 'deprecatedSources', entityId ) then
            return true
        end
    elseif snaks.P1433
            and snaks.P1433[ 1 ]
            and snaks.P1433[ 1 ].datavalue
            and snaks.P1433[ 1 ].datavalue.value.id
    then
        local entityId = snaks.P1433[ 1 ].datavalue.value.id
        if getConfig( 'deprecatedSources', entityId ) then
            return true
        end
    end
    return false
end

---Функция оформления ссылок на источники (reference)
---Подробнее о ссылках на источники см. d:Wikidata:Glossary/ru
---
---Экспортируется в качестве зарезервированной точки для вызова из функций-расширения вида claim-module/claim-function через context
---Вызов из других модулей напрямую осуществляться не должен (используйте frame:expandTemplate вместе с одним из специлизированных шаблонов вывода значения свойства).
---@param context table
---@param options table
---@param statement table
---@return string Оформленные примечания для отображения в статье
function formatRefs( context, options, statement )
    if not context then error( 'context not specified' ); end
    if not options then error( 'options not specified' ); end
    if not options.entityId then error( 'options.entityId missing' ); end
    if not statement then error( 'statement not specified' ); end

    if not outputReferences then
        return ''
    end

    ---@type string[]
    local references = {}
    if statement.references then
        local hasNotDeprecated = false
        local displayCount = 0
        for _, reference in pairs( statement.references ) do
            if not isReferenceDeprecated( reference.snaks ) then
                hasNotDeprecated = true
            end
        end

        for _, reference in pairs( statement.references ) do
            local display = true
            if hasNotDeprecated then
                if isReferenceDeprecated( reference.snaks ) then
                    display = false
                end
            end
            if displayCount >= 2 then
                if options.entityId and options.property then
                    local propertyId = mw.ustring.match( options.property, '^[Pp][0-9]+' )  -- TODO: обрабатывать не тут, а раньше
                    local moreReferences = '<sup>[[d:' .. options.entityId .. '#' .. string.upper( propertyId ) .. '|[…]]]</sup>'
                    table.insert( references, moreReferences )
                end
                break
            end
            if display == true then
                ---@type string
                local refText = moduleSources.renderReference( g_frame, options.entityId, reference )
                if refText and refText ~= '' then
                    table.insert( references, refText )
                    displayCount = displayCount + 1
                end
            end
        end
    end
    return table.concat( references )
end

return p
Источник —

Same as Wikidata