Модуль:Wikidata

Для документации этого модуля может быть создана страница Модуль:Wikidata/doc

---settings, may differ from project to projectlocal fileDefaultSize = '267x400px'local outputReferences = truelocal writingSystemElementId = 'Q8209'local langElementId = 'Q33968'---Ссылки на используемые модули, которые потребуются в 99% случаев загрузки страниц (чтобы иметь на виду при переименовании)local moduleSources = require( 'Module:Sources' )local WDS = require( 'Module:WikidataSelectors' )---Константы---@type stringlocal CONTENT_LANGUAGE_CODE = mw.language.getContentLanguage():getCode()local p = {}local g_config, g_framelocal formatDatavalue, formatEntityId, formatRefs, formatSnak, formatStatement,formatStatementDefault, getSourcingCircumstances, getPropertyParams---@param obj table---@param target table---@param skipEmpty boolean | nil---@return tablelocal 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 targetend---@param prev number | nil---@param next number | nil---@return number | nillocal function min( prev, next )    if prev == nil or prev > next then        return next    end    return prevend---@param prev number | nil---@param next number | nil---@return number | nillocal function max( prev, next )    if prev == nil or prev < next then        return next    end    return prevend---@param section string---@param code string---@return any | nillocal 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 stringlocal 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 .. ']]'    endend---@param isoStr string | table---@return table | nillocal 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 | nillocal 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 stringlocal 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 defaultValueend---Обрачивает отформатированное значение в инлайновый или блочный тег.---@param value string value---@param attributes table of attributes---@return string HTML tag with valuelocal 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 valuelocal 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 valuelocal 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 valuelocal 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 entityend---Внутренняя функция для формирования сообщения об ошибке---@param key string Ключ элемента в таблице config.errors (например entity-not-found)---@return voidlocal function throwError( key )    error( getConfig( 'errors', key ) )end---Функция для получения идентификатора сущностей---@param value table---@return stringlocal 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 functionlocal 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 defaultFunctionend---Выбирает свойства по property id, дополнительно фильтруя их по рангу---@param context table---@param options table---@param propertySelector string---@return table | nillocal 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 resultend---Функция для получения значения свойства элемента в заданный момент времени.---@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 resultsend---@param context table---@param statement table---@param qualifierId string---@return table | nilfunction 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 | nilfunction 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 nilend---@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, langCodeend---@param context table---@param options table---@return stringlocal 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 outend---Create context---@param initOptions table---@return table | nillocal 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 contextend---Функция для оформления утверждений (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 tablefunction 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 circumstancesend---Функция для оформления одного утверждения (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 resultend---Функция для оформления части утверждения (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 resultend    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)    endend---Функция для оформления объектов-значений с файлами с Викисклада---@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 imageend---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 stringlocal 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 valueend---Функция для оформления числовых значений---@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 outend---Функция для оформления URL---@param context table---@param options table---@param value stringlocal 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 )endlocal 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.datatypeend---@param datavalue table---@return functionlocal 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 functionlocal 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 )endlocal 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' ) .. categoryend---Функция для оформления утверждений (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 tablefunction 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    endend    return propertyParamsend---Функция для оформления утверждений (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 booleanlocal 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 falseend---Функция оформления ссылок на источники (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 )endreturn p