Модуль:Sources: различия между версиями

м
Замена текста — «Википедия:» на «ЭАНМ:»
/>Vlsergey-at-work
(Новая страница: «Test»)
м (Замена текста — «Википедия:» на «ЭАНМ:»)
 
(не показана 41 промежуточная версия 8 участников)
Строка 1: Строка 1:
local p = {};
---@alias args table
---@alias frame { args: args, extensionTag: function, newChild: ( fun( args: args ): frame ) }
---@alias source { publication: source, [string]: any }
---@alias value: string | { id: string }
---@alias snak { datatype: string, snaktype: string, datavalue: { type: string, value: value } }
---@alias snaks table<string, table<number, snak>>
---@alias statement { mainsnak: snak, rank: string, qualifiers: snaks }
---@alias statements table<string, table<number, statement>>
---@alias map { name: string, ids: string[] }[]>


local i18nDefaultLanguage = 'ru';
---@type table
local p = {}
 
---@type table<string, string>
local NORMATIVE_DOCUMENTS = {
    Q20754888 = 'Закон Российской Федерации',
    Q20754884 = 'Закон РСФСР',
    Q20873831 = 'Распоряжение Президента Российской Федерации',
    Q20873834 = 'Указ исполняющего обязанности Президента Российской Федерации',
    Q2061228 = 'Указ Президента Российской Федерации',
}
 
---@type table<string, string>
local LANG_CACHE = {
    Q150 = 'fr',
    Q188 = 'de',
    Q1321 = 'es',
    Q1860 = 'en',
    Q652 = 'it',
    Q7737 = 'ru',
    Q8798 = 'uk',
}
 
---@type map
local PROPERTY_MAP = {
    { name = 'sourceId', ids = { 'P248', 'P805' } },
    { name = 'lang', ids = { 'P407', 'P364' } },
    { name = 'author', ids = { 'P50', 'P2093' } },
    { name = 'part', ids = { 'P958', 'P1810' } },
    { name = 'title', ids = { 'P1476' } },
    { name = 'subtitle', ids = { 'P1680' } },
    { name = 'url', ids = { 'P953', 'P1065', 'P854', 'P973', 'P2699', 'P888' } },
    { name = 'editor', ids = { 'P98' } },
    { name = 'translator', ids = { 'P655' } },
    { name = 'publication-id', ids = { 'P1433' } },
    { name = 'edition', ids = { 'P393' } },
    { name = 'publisher', ids = { 'P123' } },
    { name = 'place', ids = { 'P291' } },
    { name = 'volume', ids = { 'P478' } },
    { name = 'issue', ids = { 'P433' } },
    { name = 'dateOfCreation', ids = { 'P571' } },
    { name = 'dateOfPublication', ids = { 'P577' } },
    { name = 'pages', ids = { 'P304' } },
    { name = 'numberOfPages', ids = { 'P1104' } },
    { name = 'tirage', ids = { 'P1092' } },
    { name = 'isbn', ids = { 'P212', 'P957' } },
    { name = 'issn', ids = { 'P236' } },
    -- { name = 'accessdate', ids = { 'P813' } }, -- disable, creates duplicate references
    { name = 'docNumber', ids = { 'P1545' } },
    { name = 'type', ids = { 'P31' } },
    { name = 'arxiv', ids = { 'P818' } },
    { name = 'doi', ids = { 'P356' } },
    { name = 'pmid', ids = { 'P698' } },
}
-- table.insert( PROPERTY_MAP.url, 'P856' ) -- only as qualifier
 
---@type map
local PUBLICATION_PROPERTY_MAP = mw.clone( PROPERTY_MAP )
 
---@type string[]
local monthGen = { 'января', 'февраля', 'марта', 'апреля', 'мая', 'июня', 'июля', 'августа', 'сентября', 'октября', 'ноября', 'декабря' }
 
---@type string
local i18nDefaultLanguage = mw.language.getContentLanguage():getCode()
p.i18nDefaultLanguage = i18nDefaultLanguage
 
---@type string
local i18nEtAlDefault = ' et al.'
 
---@type table<string, string>
local i18nEtAl = {
    ru = ' и др.',
    uk = ' та ін.',
}
 
---@type table<string, string>
local i18nEditors = {
local i18nEditors = {
fr = '',
    fr = '',
de = 'Hrsg.: ',
    de = 'Hrsg.: ',
es = '',
    es = '',
en = '',
    en = '',
it = '',
    it = '',
ru = 'под ред. ',
    ru = 'под ред. ',
    uk = 'за ред. ',
}
}
local i18nEtAlDefault = ' et al.';
 
local i18nEtAl = {
---@type table<string, string>
ru = ' и др.',
local i18nTranslators = {
    fr = '',
    de = '',
    es = '',
    en = '',
    it = '',
    ru = 'пер. ',
    uk = 'пер. ',
}
}
---@type table<string, string>
local i18nVolume = {
local i18nVolume = {
fr = 'Vol.',
    de  = 'Vol.',
es = 'Vol.',
    fr = 'Vol.',
en = 'Vol.',
    es = 'Vol.',
it = 'Vol.',
    en = 'Vol.',
ru = 'Т.',
    it = 'Vol.',
    ru = 'Т.',
    uk = 'Т.',
}
}
---@type table<string, string>
local i18nIssue = {
local i18nIssue = {
en = 'Iss.',
    en = 'Iss.',
ru = 'вып.',
    ru = 'вып.',
    uk = 'вип.',
}
}
---@type table<string, string>
local i18nPages = {
local i18nPages = {
fr = 'P.',
    fr = 'P.',
de = 'S.',
    de = 'S.',
es = 'P.',
    es = 'P.',
en = 'P.',
    en = 'P.',
it = 'P.',
    it = 'P.',
ru = 'С.',
    ru = 'С.',
    uk = 'С.',
}
}


---@type table<string, string>
local i18nNumberOfPages = {
local i18nNumberOfPages = {
en = 'p.',
    en = 'p.',
ru = 'с.',
    ru = 'с.',
}
}


local NORMATIVE_DOCUMENTS = {
---@type table<string, string>
Q20754888 = 'Закон Российской Федерации',
local i18nTirage = {
Q20754884 = 'Закон РСФСР',
    en = 'ed. size: %d',
Q20873831 = 'Распоряжение Президента Российской Федерации',
    ru = '%d экз.',
Q20873834 = 'Указ исполняющего обязанности Президента Российской Федерации',
Q2061228 = 'Указ Президента Российской Федерации',
}
}


local monthg = {'января', 'февраля', 'марта', 'апреля', 'мая', 'июня', 'июля', 'августа', "сентября", "октября", "ноября", "декабря"};
---@param args args
---@return source
local function getFilledArgs( args )
    ---@type source
    local data = {}
 
    for key, value in pairs( args ) do
        if mw.text.trim( value ) ~= '' then
            if key == 1 then
                key = 'sourceId'
            end
            data[ key ] = mw.text.trim( value )
        end
    end
 
    return data
end
 
---Returns formatted pair {Family name(s), First name(s)}
---@param fullName string
---@return table<number, string>
local function tokenizeName( fullName )
    local space = '%s+' -- matches single or more spacing character
    local name = "(%a[%a%-']*)%.?" -- matches single name, have to start with letter, can contain apostrophe and hyphen, may end with dot
    local surname = "(%a[%a%-']*)" -- same as name, but can't end with dot
    local surnamePrefixes = { 'ван', 'van', 'де', 'de' }


local PREFIX_CITEREF = "CITEREF_";
    local nm, nm2, srn, srn2, pref


local options_arxiv = { separator = '; ', conjunction = '; ', format = function( id ) return '[http://arxiv.org/abs/' .. id .. ' arXiv:' .. id .. ']' end, nolinks = true, preferids = false };
    fullName = ' ' .. fullName .. ' '
local options_doi = { separator = '; ', conjunction = '; ', format = function( doi ) return '[http://dx.doi.org/' .. doi .. ' doi:' .. doi .. ']' end, nolinks = true, preferids = false };
    fullName = mw.ustring.gsub( fullName, ' оглы ', ' ' )
    fullName = mw.text.trim( fullName )


local options_commas = { separator = ', ', conjunction = ', ', format = function( src ) return src end, nolinks = false, preferids = false };
    -- Surname, Name
local options_commas_short = { separator = ', ', conjunction = ', ', format = function( src ) return src end, nolinks = false, preferids = false, short = true };
    local pattern = '^' .. surname .. ',' .. space .. name .. '$'
local options_commas_nolinks = { separator = ', ', conjunction = ', ', format = function( src ) return src end, nolinks = true, preferids = false };
    srn, nm = mw.ustring.match( fullName, pattern )
local options_commas_it = { separator = ', ', conjunction = ', ', format = function( src ) return "''" .. src .. "''" end, nolinks = false, preferids = false };
    if srn then
local options_commas_it_short = { separator = ', ', conjunction = ', ', format = function( src ) return "''" .. src .. "''" end, nolinks = false, preferids = false, short = true };
        return {
local options_commas_it_nolinks = { separator = ', ', conjunction = ', ', format = function( src ) return "''" .. src .. "''" end, nolinks = true , preferids = false };
            srn,
local options_citetypes = { separator = ' ', conjunction = ' ', format = function( src ) return 'citetype_' .. src end, nolinks = true , preferids = true };
            mw.ustring.sub( nm, 1, 1 ) .. '.'
        }
    end


function assertNotNull( argName, arg )
    -- Surname, Name prefix
if ( (not arg) or (arg == nil) ) then
    for _, surnamePrefix in pairs( surnamePrefixes ) do
error( argName .. ' is not specified' )
        pattern = '^' .. surname .. ',' .. space .. name .. space .. '(' .. surnamePrefix .. ')' .. '$'
end
        srn, nm, pref = mw.ustring.match( fullName, pattern )
        if srn then
            return {
                mw.ustring.sub( pref ) .. ' ' .. srn,
                mw.ustring.sub( nm, 1, 1 ) .. '.' }
        end
    end
 
    -- Surname, Name Name
    pattern = '^' .. surname .. ',' .. space .. name .. space .. name .. '$'
    srn, nm, nm2 = mw.ustring.match( fullName, pattern )
    if srn then
        return {
            srn,
            mw.ustring.sub( nm, 1, 1 ) .. '.&nbsp;' .. mw.ustring.sub( nm2, 1, 1 ) .. '.'
        }
    end
 
    -- Surname Surname, Name
    pattern = '^' .. surname .. space .. surname .. ',' .. space .. name .. '$'
    srn, srn2, nm = mw.ustring.match( fullName, pattern )
    if srn then
        return {
            srn .. '&nbsp;' .. srn2,
            mw.ustring.sub( nm, 1, 1 ) .. '.'
        }
    end
 
    -- Name Name Surname
    pattern = '^' .. name .. space .. name .. space .. surname .. '$'
    nm, nm2, srn = mw.ustring.match( fullName, pattern )
    if srn then
        return {
            srn,
            mw.ustring.sub( nm, 1, 1 ) .. '.&nbsp;' .. mw.ustring.sub( nm2, 1, 1 ) .. '.'
        }
    end
 
    -- Name Name prefix Surname
    for _, surnamePrefix in pairs( surnamePrefixes ) do
        pattern = '^' .. name .. space .. name .. space .. '(' .. surnamePrefix .. ')' .. space .. surname .. '$'
        nm, nm2, pref, srn = mw.ustring.match( fullName, pattern )
        if srn then
            return {
                mw.ustring.sub( pref ) .. ' ' .. srn,
                mw.ustring.sub( nm, 1, 1 ) .. '.&nbsp;' .. mw.ustring.sub( nm2, 1, 1 ) .. '.'
            }
        end
    end
 
    -- Surname, Name Name prefix
    for _, surnamePrefix in pairs( surnamePrefixes ) do
        pattern = '^' .. surname .. ',' .. space .. name .. space .. name .. space .. '(' .. surnamePrefix .. ')' .. '$'
        srn, nm, nm2, pref = mw.ustring.match( fullName, pattern )
        if srn then
            return {
            mw.ustring.sub( pref ) .. ' ' .. srn,
            mw.ustring.sub( nm, 1, 1 ) .. '.&nbsp;' .. mw.ustring.sub( nm2, 1, 1 ) .. '.'
            }
        end
    end
 
    -- Name{1,4} Surname
    for k = 1, 4 do
        pattern = '^' .. string.rep( name .. space, k ) .. surname .. '$'
        ---@type string[]
        local matched = { mw.ustring.match( fullName, pattern ) }
        if #matched ~= 0 then
            for j = 1, k do
                matched[ j ] = mw.ustring.sub( matched[ j ], 1, 1 )
            end
            return {
            matched[ k + 1 ],
            table.concat( matched, '.&nbsp;', 1, k ) .. '.'
            }
        end
    end
 
    -- Surname Name{1,4}
    for k = 1, 4 do
        pattern = '^' .. surname .. string.rep( space .. name, k ) .. '$'
        ---@type string[]
        local matched = { mw.ustring.match( fullName, pattern ) }
        if #matched ~= 0 then
            for j = 2, k + 1 do
                matched[ j ] = mw.ustring.sub( matched[ j ], 1, 1 )
            end
            return {
            matched[ 1 ],
            table.concat( matched, '.&nbsp;', 2, k + 1 ) .. '.'
            }
        end
    end
 
    return { fullName }
end
end


function isEmpty( str )
---@param fullName string | nil
return ( not str ) or ( str == nil ) or ( #str == 0 );
---@return string | nil
local function personNameToAuthorName( fullName )
    if not fullName then
        return nil
    end
 
    local tokenized = tokenizeName( fullName )
    if #tokenized == 1 then
        return tokenized[ 1 ]
    end
 
    return tokenized[ 1 ] .. '&nbsp;' .. tokenized[ 2 ]
end
end


function isInstanceOf( entity, typeEntityId )
---@param fullName string | nil
if ( not entity or not entity.claims or not entity.claims.P31 ) then
---@return string | nil
return false;
local function personNameToResponsibleName( fullName )
end
    if not fullName then
        return nil
    end


for _, claim in pairs( entity.claims.P31 ) do
    local tokenized = tokenizeName( fullName )
if ( claim and claim.mainsnak and claim.mainsnak.datavalue and claim.mainsnak.datavalue.value and claim.mainsnak.datavalue.value["numeric-id"] ) then
    if #tokenized == 1 then
local actualTypeId = 'Q' .. claim.mainsnak.datavalue.value["numeric-id"];
        return tokenized[ 1 ]
if ( actualTypeId == typeEntityId ) then
    end
return true;
end
end
end


return false;
    return tokenized[ 2 ] .. '&nbsp;' .. tokenized[ 1 ]
end
end


function getEntity( context, entityId )
---@alias options { separator: string, conjunction: string, format: ( fun( data: string ): string ), nolinks: boolean, preferids: boolean, short: boolean }
assertNotNull( 'context', context );
 
assertNotNull( 'entityId', entityId );
---@type options
local options_commas = {
    separator = ', ',
    conjunction = ', ',
    format = function( data ) return data end,
    nolinks = false,
    preferids = false,
    short = false,
}
 
---@type options
local options_commas_short = mw.clone( options_commas )
options_commas_short.short = true


local cached = context.cache[ entityId ];
---@type options
if ( cached ) then return cached; end;
local options_commas_it_short = mw.clone( options_commas_short )
options_commas_it_short.format = function( data ) return "''" .. data .. "''" end


local result = mw.wikibase.getEntity( entityId );
---@type options
if ( result ) then
local options_commas_nolinks = mw.clone( options_commas )
context.cache[ entityId ] = result;
options_commas_nolinks.nolinks = true
end


return result;
---@type options
local options_citetypes = {
    separator = ' ',
    conjunction = ' ',
    format = function( data ) return 'citetype_' .. data end,
    nolinks = true ,
    preferids = true,
    short = false,
}
 
---@type options
local options_commas_authors = mw.clone( options_commas )
options_commas_authors.format = personNameToAuthorName
 
---@type options
local options_commas_responsible = mw.clone( options_commas )
options_commas_responsible.format = personNameToResponsibleName
 
---@type options
local options_ids = {
    separator = '; ',
    conjunction = '; ',
    format = function( id ) return id end,
    nolinks = true,
    preferids = false,
    short = false,
}
 
---@type options
local options_arxiv = mw.clone( options_ids )
options_arxiv.format = function( id ) return '[https://arxiv.org/abs/' .. id .. ' arXiv:' .. id .. ']' end
 
---@type options
local options_doi = mw.clone( options_ids )
options_doi.format = function( doi ) return '[https://dx.doi.org/' .. doi .. ' doi:' .. doi .. ']' end
 
---@type options
local options_issn = mw.clone( options_ids )
options_issn.format = function( issn ) return '[https://www.worldcat.org/issn/' .. issn .. ' ' .. issn .. ']' end
 
---@type options
local options_pmid = mw.clone( options_ids )
options_pmid.format = function( pmid ) return '[https://www.ncbi.nlm.nih.gov/pubmed/?term=' .. pmid .. ' PMID:' .. pmid .. ']' end
 
---@param str string | nil
---@return boolean
local function isEmpty( str )
    return not str or #str == 0
end
end


function toStringSnak( propertyId, strValue )
---@param allQualifiers snaks
assertNotNull('propertyId', strValue)
---@param qualifierPropertyId string
assertNotNull('strValue', strValue)
---@return string | nil
local function getSingleStringQualifierValue( allQualifiers, qualifierPropertyId )
    if not allQualifiers or not allQualifiers[ qualifierPropertyId ] then
        return nil
    end
 
    ---@type table<number, snak>
    local propertyQualifiers = allQualifiers[ qualifierPropertyId ]


local snak = { snaktype = "value", property = propertyId, datatype = 'string'};
    for _, qualifier in pairs( propertyQualifiers ) do
snak["datavalue"] = { value = strValue, type = 'string' };
        if ( qualifier
return snak;
                and qualifier.datatype == 'string'
                and qualifier.datavalue
                and qualifier.datavalue.type == 'string'
                and qualifier.datavalue.value ~= ''
        ) then
            return qualifier.datavalue.value
        end
    end
 
    return nil
end
end


function toUrlSnak( propertyId, strValue )
---@param data table
assertNotNull('propertyId', strValue)
---@param resultProperty string
assertNotNull('strValue', strValue)
---@return void
 
local function appendImpl_toTable( data, resultProperty )
local snak = { snaktype = "value", property = propertyId, datatype = 'string'};
    if not data[ resultProperty ] then
snak["datavalue"] = { value = strValue, type = 'url' };
        data[ resultProperty ] = {}
return snak;
    elseif ( type( data[ resultProperty ] ) == 'string' or ( type( data[ resultProperty ] ) == 'table' and type( data[ resultProperty ].id ) == 'string' ) ) then
        data[ resultProperty ] = { data[ resultProperty ] }
    end
end
end


function toWikibaseEntityIdSnak( propertyId, entityId )
---@param datavalue table
assertNotNull('propertyId', entityId)
---@param qualifiers snaks
assertNotNull('entityId', entityId)
---@param data table
if ( mw.ustring.sub( entityId, 1, 1 ) ~= 'Q' ) then error( 'Incorrect entity ID: «' .. entityId .. '»' ); end;
---@param propertyName string
---@param options table
local function appendImpl( datavalue, qualifiers, data, propertyName, options )
    data[ propertyName ] = data[ propertyName ] or {}
    if propertyName == 'issn' then
        table.insert( data[ propertyName ], datavalue.value )
    elseif propertyName == 'url' or datavalue.type == 'url' then
        local value = datavalue.value
        if options.format then
            value = options.format( value )
        end
        appendImpl_toTable( data, propertyName )
        table.insert( data[ propertyName ], value )
    elseif datavalue.type == 'string' then
        local value = getSingleStringQualifierValue( qualifiers, 'P1932' )
        if not value then
            value = getSingleStringQualifierValue( qualifiers, 'P1810' )
        end


local value = {};
        if not value then
value["entity-type"] = 'item';
            value = datavalue.value
value["numeric-id"] = mw.ustring.sub( entityId , 2);
            if options.format then
                value = options.format( value )
            end
        end


local snak = { snaktype = "value", property = propertyId, datatype = 'wikibase-item'};
        appendImpl_toTable(data, propertyName)
snak["datavalue"] = { value = value, type = 'wikibase-entityid' };
        local pos = getSingleStringQualifierValue( qualifiers, 'P1545' )
return snak;
        if pos then
            table.insert( data[ propertyName ], tonumber(pos), value )
        else
            table.insert( data[ propertyName ], value )
        end
    elseif datavalue.type == 'monolingualtext' then
        local value = datavalue.value.text
        if options.format then
            value = options.format( value )
        end
        appendImpl_toTable( data, propertyName )
        table.insert( data[ propertyName ], value )
    elseif datavalue.type == 'quantity' then
        local value = datavalue.value.amount
        if ( mw.ustring.sub( value , 1, 1 ) == '+' ) then
            value = mw.ustring.sub( value , 2 )
        end
        if options.format then
            value = options.format( value )
        end
        appendImpl_toTable( data, propertyName )
        table.insert( data[ propertyName ], value )
    elseif datavalue.type == 'wikibase-entityid' then
        local pos = getSingleStringQualifierValue( qualifiers, 'P1545' )
        local value = datavalue.value
        appendImpl_toTable(data, propertyName)
        local label = getSingleStringQualifierValue( qualifiers, 'P1932' )
        if not label then
            label = getSingleStringQualifierValue( qualifiers, 'P1810' )
        end
        local toInsert = {
            id = value.id,
            label = label
        }
        if pos and tonumber( pos ) then
            table.insert( data[ propertyName ], tonumber( pos ), toInsert )
        else
            table.insert( data[ propertyName ], toInsert )
        end
    elseif datavalue.type == 'time' then
        local value = datavalue.value
        if options.format then
            value = options.format( value )
        end
        appendImpl_toTable( data, propertyName )
        table.insert( data[ propertyName ], tostring( value.time ) )
    end
end
end


function renderSource( context, src )
---@param entityId string
mw.logObject( src );
---@param propertyId string
context.lang = getLangCode( getSingle( src.lang ) ) or i18nDefaultLanguage;
---@return table<number, statement>
local function getAllStatements( entityId, propertyId )
    ---@type boolean, table<number, statement>
    local wdStatus, statements = pcall( mw.wikibase.getAllStatements, entityId, propertyId )
    if wdStatus and statements then
        return statements
    end


preprocessPlaces( src, context.lang );
    return {}
end


src.title = src.title or getSingle( src.url ) or '\'\'(unspecified title)\'\''
---@param entityId string
---@param propertyId string
---@return table<number, statement>
local function getBestStatements( entityId, propertyId )
    ---@type boolean, table<number, statement>
    local wdStatus, statements = pcall( mw.wikibase.getBestStatements, entityId, propertyId )
    if wdStatus and statements then
        return statements
    end


if ( src.entityId and not src.url ) then
    return {}
local entity = getEntity( context, src.entityId );
end
if ( entity.sitelinks and entity.sitelinks[ context.lang .. 'wikisource'] ) then
src.url = ':' .. context.lang .. ':s:' .. entity.sitelinks[ context.lang .. 'wikisource' ].title;
end
end


if ( not src.year and src.dateOfPublication ) then
---@param entityId string
local date = getSingle( src.dateOfPublication );
---@param projectToCheck string?
src.year = mw.ustring.sub( date, 2, 5 );
---@return string | nil
end
local function getSitelink( entityId, projectToCheck )
    ---@type boolean, string
    local wbStatus, sitelink


if ( not src.year and src.dateOfCreation ) then
    if projectToCheck then
local date = getSingle( src.dateOfCreation );
        wbStatus, sitelink = pcall( mw.wikibase.getSitelink, entityId, projectToCheck )
src.year = mw.ustring.sub( date, 2, 5 );
    else
end
        wbStatus, sitelink = pcall( mw.wikibase.getSitelink, entityId )
    end


local result;
    if not wbStatus then
if ( src.author ) then
        return nil
result = getPeopleAsAuthorWikitext( context, src.author, options_commas );
    end
end
if ( not isEmpty( result )) then
result = '\'\'' .. result .. '\'\' ';
else
result = '';
end
if ( src.part ) then
if ( src.url ) then
result = result .. wrapInUrl( src.url, toString( context, src.part, options_commas_nolinks ) );
else
result = result .. toString( context, src.part, options_commas );
end
result = result .. ' // ' .. toString( context, src.title, options_commas );
else
-- title only
if ( src.url ) then
result = result .. wrapInUrl( src.url, toString( context, src.title, options_commas_nolinks ) );
else
result = result .. toString( context, src.title, options_commas );
end
end


if ( src.subtitle ) then
    return sitelink
result = result .. ": " .. toString( context, src.subtitle, options_commas );
end
end


if ( src.originaltitle ) then
---@param args any[]
result = result .. ' = ' .. toString( context, src.originaltitle, options_commas );
---@return any | nil
end
local function coalesce( args )
    for _, arg in pairs( args ) do
        if not isEmpty( arg ) then return arg end
    end
    return nil
end


if ( src.publication ) then
---@param value any
if ( type( src.publication.title or '') ~= 'string' ) then error('type of src.publication.title is not string but ' .. type( src.publication.title ) ) end;
---@return string | nil
local function getSingle( value )
    if type( value ) == 'string' then
        return tostring( value )
    elseif type( value ) == 'table' then
        if value.id then
            return tostring( value.id )
        end


result = result .. ' // ' .. toString( context, src.publication, options_commas_it_short );
        for _, tableValue in pairs( value ) do
if ( src.publication.subtitle ) then
            return getSingle( tableValue )
result = result .. ': ' .. toString( context, src.publication.subtitle, options_commas_it_short );
        end
end
    end
end


if ( src.editor ) then
    return nil
local prefix = i18nEditors[ context.lang ] or i18nEditors[ i18nDefaultLanguage ];
end
result = result .. ' / ' .. prefix .. toString( context, src.editor, options_commas );
end


if ( src.place or src.publisher or src.year ) then
---@param langEntityId string
result = result .. ' — ';
---@return string | nil
if ( src.place ) then
local function getLangCode( langEntityId )
result = result .. toString( context, src.place, options_commas_short );
    if not langEntityId then
if ( src.publisher or src.year ) then
        return nil
result = result .. ': ';
    end
end
end
if ( src.publisher ) then
result = result .. toString( context, src.publisher, options_commas_short );
if ( src.year ) then
result = result .. ', ';
end
end
if ( src.year ) then
result = result .. toString( context, src.year, options_commas );
end
result = result .. '.';
end
if ( src.volume or src.issue ) then
result = result .. ' — ';
if ( src.volume ) then
local letter = i18nVolume[ context.lang ] or i18nVolume[ i18nDefaultLanguage ];
result = result .. letter .. '&nbsp;' .. toString( context, src.volume, options_commas );
if ( src.issue ) then
local letter = i18nIssue[ context.lang ] or i18nIssue[ i18nDefaultLanguage ];
result = result .. ', ' .. letter .. '&nbsp;' .. toString( context, src.issue, options_commas ) .. '.';
else
result = result .. '.';
end
else
local letter = i18nIssue[ context.lang ] or i18nIssue[ i18nDefaultLanguage ];
result = result .. letter .. '&nbsp;' .. toString( context, src.issue, options_commas ) .. '.';
end
end


if ( src.pages ) then
    langEntityId = getSingle( langEntityId )
local letter = i18nPages[ context.lang ] or i18nPages[ i18nDefaultLanguage ];
result = result .. ' — ' .. letter .. '&nbsp;' .. toString( context, src.pages, options_commas ) .. '.';
end


if ( src.numberOfPages ) then
    if not string.match( langEntityId, '^Q%d+$' ) then
local letter = i18nNumberOfPages[ context.lang ] or i18nNumberOfPages[ i18nDefaultLanguage ];
        return langEntityId
result = result .. ' — ' .. toString( context, src.numberOfPages, options_commas ) .. '&nbsp;' .. letter;
    end
end


if ( src.bookSeries ) then
    local cached = LANG_CACHE[ langEntityId ]
result = result .. ' — (' .. toString( context, src.bookSeries, options_commas )
    if cached then
        if cached == '' then
            return nil
        end
        return cached
    end


if ( src.bookSeriesVolume or src.bookSeriesIssue ) then
    local claims = getBestStatements( langEntityId, 'P424' )
result = result .. '; ';
    for _, claim in pairs( claims ) do
if ( src.bookSeriesVolume ) then
        if claim
local letter = i18nVolume[ context.lang ] or i18nVolume[ i18nDefaultLanguage ];
                and claim.mainsnak
result = result .. letter .. '&nbsp;' .. toString( context, src.bookSeriesVolume, options_commas );
                and claim.mainsnak.datavalue
if ( src.bookSeriesIssue ) then
                and claim.mainsnak.datavalue.value
local letter = i18nIssue[ context.lang ] or i18nIssue[ i18nDefaultLanguage ];
        then
result = result .. ', ' .. letter .. '&nbsp;' .. toString( context, src.bookSeriesIssue, options_commas );
            LANG_CACHE[ langEntityId ] = claim.mainsnak.datavalue.value
else
            return claim.mainsnak.datavalue.value
result = result;
        end
end
    end
else
local letter = i18nIssue[ context.lang ] or i18nIssue[ i18nDefaultLanguage ];
result = result .. letter .. '&nbsp;' .. toString( context, src.bookSeriesIssue, options_commas );
end
end


result = result .. ')';
    LANG_CACHE[ langEntityId ] = ''
end
    return nil
end


if ( src.isbn ) then
---@param entityId string
result = result .. ' — ISBN ' .. toString( context, src.isbn, options_commas );
---@param propertyId string
end
---@param data source
---@param propertyName string
---@param options table?
---@return void
local function appendEntitySnaks( entityId, propertyId, data, propertyName, options )
    options = options or {}


if ( src.issn ) then
    -- do not populate twice
result = result .. ' — ISSN ' .. toString( context, src.issn, options_commas );
    if data[ propertyName ] and ( propertyName ~= 'author' or data[ propertyId ] ) then
end
        return
    end


if ( src.doi ) then
    local statements = getBestStatements( entityId, propertyId )
result = result .. ' — ' .. toString( context, src.doi, options_doi );
end


if ( src.arxiv ) then
    if propertyName == 'author' then
result = result .. ' — ' .. toString( context, src.arxiv, options_arxiv );
        data[ propertyId ] = true
end
    end


if ( src.entityId ) then
    local lang = getLangCode( data.lang ) or i18nDefaultLanguage
if ( src.type and src.entityId ) then
-- wrap into span to target from JS
result = '<span class="wikidata_cite ' .. toString( context, src.type, options_citetypes ) .. '" data-entity-id="' .. getSingle( src.entityId ) .. '">' .. result .. '</span>'
else
result = '<span class="wikidata_cite citetype_unknown" data-entity-id="' .. getSingle( src.entityId ) .. '">' .. result .. '</span>'
end
end


if ( src.accessdate ) then
    if propertyId == 'P1680' then -- if there is a default language
local date = getSingle( src.accessdate );
        for _, statement in pairs( statements ) do
local pattern = "(%-?%d+)%-(%d+)%-(%d+)T";
            if statement and
local y, m, d = mw.ustring.match( date , pattern );
                    statement.mainsnak and
y,m,d = tonumber(y),tonumber(m),tonumber(d);
                    statement.mainsnak.datavalue and
result = result .. " <small>Проверено " .. tostring(d) .. " " .. monthg[m]  .. " " .. tostring(y) .. ".</small>";
                    statement.mainsnak.datavalue.value and
end
                    statement.mainsnak.datavalue.value.language == lang
            then
                --found default language string
                appendImpl( statement.mainsnak.datavalue, statement.qualifiers, data, propertyName, options )
                return
            end
        end
    end


return {text = result, code = src.code};
    for _, statement in pairs( statements ) do
        if statement and statement.mainsnak and statement.mainsnak.datavalue then
            appendImpl( statement.mainsnak.datavalue, statement.qualifiers or {}, data, propertyName, options )
            if propertyName == 'publication-id' and statement.qualifiers then
                data[ 'publication-qualifiers' ] = statement.qualifiers
            end
        end
    end
end
end


function wrapInUrl( urls, text )
---@param claims table<number, statement>
local url = getSingle( urls );
---@param qualifierPropertyId string
if ( string.sub( url, 1, 1 ) == ':' ) then
---@param result table
return '[[' .. url .. '|' .. text .. ']]';
---@param resultPropertyId string
else
---@param options table
return '[' .. url .. ' ' .. text .. ']';
---@return void
end
local function appendQualifiers( claims, qualifierPropertyId, result, resultPropertyId, options )
    -- do not populate twice
    if not claims or result[ resultPropertyId ] then
        return
    end
 
    for _, claim in pairs( claims ) do
        if claim.qualifiers and claim.qualifiers[ qualifierPropertyId ] then
            ---@type table<number, snak>
            local propertyQualifiers = claim.qualifiers[ qualifierPropertyId ]
            for _, qualifier in pairs( propertyQualifiers ) do
                if qualifier and qualifier.datavalue then
                    appendImpl( qualifier.datavalue, nil, result, resultPropertyId, options )
                end
            end
        end
    end
end
end


function renderShortReference( src )
---@param entityId string
context = {
---@param propertyId string
cache = {},
---@param value any
lang = getSingle( src.lang ) or i18nDefaultLanguage;
---@return table<number, statement>
};
local function findClaimsByValue( entityId, propertyId, value )
src.title = src.title or '\'\'(unspecified title)\'\''
    local result = {}


local result = '[[#' .. PREFIX_CITEREF .. src.code .. '|';
    local claims = getAllStatements( entityId, propertyId )
if ( src.author ) then
    for _, claim in pairs( claims ) do
result = result .. toString( context, src.author, options_authors_nolinks );
        if ( claim.mainsnak and claim.mainsnak.datavalue ) then
else
            local datavalue = claim.mainsnak.datavalue
result = result .. toString( context, src.title, options_commas_it_nolinks );
            if ( datavalue.type == "string" and datavalue.value == value ) or
end
                    ( datavalue.type == "wikibase-entityid" and
result = result .. ']]'
                            datavalue.value[ "entity-type" ] == "item" and
                            tostring( datavalue.value.id ) == value )
            then
                table.insert( result, claim )
            end
        end
    end


if ( src.year ) then
    return result
result = result .. ', ' .. toString( context, src.year, options_commas );
end
end
 
---@param entityId string
---@param typeEntityId string
---@return boolean
local function isInstanceOf( entityId, typeEntityId )
    return findClaimsByValue( entityId, 'P31', typeEntityId )[ 1 ] ~= nil
end


if ( src.volume ) then
---@param entityId string
local letter = i18nVolume[ context.lang ] or i18nVolume[ i18nDefaultLanguage ];
---@param typeEntityIds string[]
result = result .. ' — ' .. letter .. '&nbsp;' .. toString( context, src.volume, options_commas ) .. '.';
---@return string
end
---@todo Rewrite
local function getFirstType( entityId, typeEntityIds )
    for _, typeEntityId in pairs( typeEntityIds ) do
        if isInstanceOf( entityId, typeEntityId ) then
            return typeEntityId
        end
    end


if ( src.issue ) then
    return nil
local letter = i18nIssue[ context.lang ] or i18nIssue[ i18nDefaultLanguage ];
result = result .. ' — ' .. letter .. '&nbsp;' .. toString( context, src.issue, options_commas ) .. '.';
end
if ( src.pages ) then
local letter = i18nPages[ context.lang ] or i18nPages[ i18nDefaultLanguage ];
result = result .. ' — ' .. letter .. '&nbsp;' .. toString( context, src.pages, options_commas )  .. '.';
end
end
end


function getSingle( value )
---@param snaks snaks
if ( not value ) then
---@param data source
return;
---@param map map
end
---@return void
if ( type( value ) == 'string' ) then
local function populateDataFromSnaks( snaks, data, map )
return value;
    for _, row in ipairs( map ) do
elseif ( type( value ) == 'table' ) then
        local parameterName, propertyIds = row.name, row.ids
if ( value.id ) then
        for _, propertyId in pairs( propertyIds ) do
return value.id;
            if not data[ parameterName ] and snaks[ propertyId ] then
end
                local options = {}
 
                if propertyId == 'P888' then
for i, tableValue in pairs( value ) do
                    options = { format = function( id ) return 'http://www.jstor.org/stable/' .. id end }
return getSingle( tableValue );
                end
end
end


return '(unknown)';
                for _, snak in pairs( snaks[ propertyId ] ) do
                    if snak and snak.datavalue then
                        appendImpl( snak.datavalue, {}, data, parameterName, options )
                    end
                end
            end
        end
    end
end
end


function toString( context, value, options )
---@param entityId string | nil
if ( type( value ) == 'string' ) then
---@param data source
return options.format( value );
---@param map map
elseif ( type( value ) == 'table' ) then
---@return void
if ( value.id ) then
local function populateDataFromEntity( entityId, data, map )
-- this is link
    if not data.title then
if ( type( value.label or '' ) ~= 'string' ) then mw.logObject( value ); error('label of table value is not string but ' .. type( value.label ) ) end
        if not isEmpty( entityId ) then
            local optionsAsLinks = { format = function( text ) return { id = entityId, label = text } end }
            appendEntitySnaks( entityId, 'P1476', data, 'title', optionsAsLinks )
        else
            appendEntitySnaks( entityId, 'P1476', data, 'title', {} )
        end
        appendEntitySnaks( entityId, 'P1680', data, 'subtitle', {} )
    end


if ( options.preferids ) then
    local bookSeriesStatements = getBestStatements( entityId, 'P361' )
return options.format( value.id );
    for _, statement in pairs( bookSeriesStatements ) do
else
        if statement and
if ( options.nolinks ) then
                statement.mainsnak and
return options.format( value.label or mw.wikibase.label( value.id ) or '\'\'(untranslated title)\'\'' );
                statement.mainsnak.datavalue and
else
                statement.mainsnak.datavalue.value and
return options.format( renderLink( context, value.id, value.label, options ) );
                statement.mainsnak.datavalue.value.id
end
        then
end
            local possibleBookSeriesEntityId = statement.mainsnak.datavalue.value.id
end
            if isInstanceOf( possibleBookSeriesEntityId, 'Q277759' ) then
                appendImpl_toTable( data, 'bookSeries' )
                table.insert( data.bookSeries, { id = possibleBookSeriesEntityId } )


local resultList = {};
                appendQualifiers( { statement }, 'P478', data, 'bookSeriesVolume', {} )
for i, tableValue in pairs( value ) do
                appendQualifiers( { statement }, 'P433', data, 'bookSeriesIssue', {} )
table.insert( resultList, toString( context, tableValue, options ) );
            end
end
        end
    end


return mw.text.listToText( resultList, options.separator, options.conjunction);
    for _, row in ipairs( map ) do
else
        local parameterName, propertyIds = row.name, row.ids
return options.format( '(unknown type)' );
        for _, propertyId in pairs( propertyIds ) do
end
            local options = {}
            if propertyId == 'P888' then
                options = { format = function( id ) return 'http://www.jstor.org/stable/' .. id end }
            end


return '';
            appendEntitySnaks( entityId, propertyId, data, parameterName, options )
        end
    end
end
end


function renderLink( context, entityId, customTitle, options )
---@param data source
if ( not entityId ) then error("entityId is not specified") end
---@return void
if ( type( entityId ) ~= 'string' ) then error('entityId is not string, but ' .. type( entityId ) ) end
local function expandPublication( data )
if ( type( customTitle or '' ) ~= 'string' ) then error('customTitle is not string, but ' .. type( customTitle ) ) end
    if not data[ 'publication-id' ] then
        return
    end


local title = customTitle;
    local publicationId = getSingle( data[ 'publication-id' ] )
    data.publication = {}
    for key, value in pairs( data ) do
        if not string.match( key, '^publication-' ) then
            data.publication[ key ] = value
        end
    end
    data.publication.sourceId = publicationId
    data.publication.title = data[ 'publication-title' ]
    data.publication.subtitle = data[ 'publication-subtitle' ]


if ( isEmpty( title ) ) then
    if data[ 'publication-qualifiers' ] then
local entity = getEntity( context, entityId );
        populateDataFromSnaks( data[ 'publication-qualifiers' ], data.publication, PUBLICATION_PROPERTY_MAP )
    end
    populateDataFromEntity( publicationId, data.publication, PUBLICATION_PROPERTY_MAP )


-- ISO 4
    if type( data.publication.title ) == 'table' and data.publication.title[ 1 ] then
if ( isEmpty( title ) ) then
        data.publication.title = data.publication.title[ 1 ]
if ( entity.claims and entity.claims.P1160 ) then
    end
for _, claim in pairs( entity.claims.P1160 ) do
    if type( data.publication.subtitle ) == 'table' and data.publication.subtitle[ 1 ] then
if ( claim
        data.publication.subtitle = data.publication.subtitle[ 1 ]
and claim.mainsnak
    end
and claim.mainsnak.datavalue
and claim.mainsnak.datavalue.value
and claim.mainsnak.datavalue.value.language == context.lang ) then
title = claim.mainsnak.datavalue.value.text;
mw.log('Got title of ' .. entityId .. ' from ISO 4 claim: «' .. title .. '»' )
break;
end
end
end
end


-- official name P1448
    for key, value in pairs( data.publication ) do
-- short name P1813
        if key ~= 'sourceId' and key ~= 'title' and key ~= 'subtitle' and key ~= 'url' and not data[ key ] then
if ( isEmpty( title ) and options.short ) then
            data[ key ] = value
if ( entity.claims and entity.claims.P1813 ) then
        end
for _, claim in pairs( entity.claims.P1813 ) do
    end
if ( claim
and claim.mainsnak
and claim.mainsnak.datavalue
and claim.mainsnak.datavalue.value
and claim.mainsnak.datavalue.value.language == context.lang ) then
title = claim.mainsnak.datavalue.value.text;
mw.log('Got title of ' .. entityId .. ' from short name claim: «' .. title .. '»' )
break;
end
end
end
end
-- person name P1559
-- labels
if ( isEmpty( title ) and entity.labels[ context.lang ] ) then
title = entity.labels[ context.lang ].value;
mw.log('Got title of ' .. entityId .. ' from label: «' .. title .. '»' )
end
end
 
local actualText = title or '\'\'(untranslated)\'\'';
local link = getElementLink( context, entityId, entity);
return wrapInUrl( link, actualText );
end
end


function getElementLink( context, entityId, entity )
---@param data source
-- fast sitelink lookup, not an expensive operation
---@return void
local link = mw.wikibase.sitelink( entityId )
local function expandBookSeries( data )
if ( link ) then return ':' .. link end
    local bookSeries = data.bookSeries
    if not bookSeries then
        return
    end


if ( not entity and entityId ) then
    -- use only first one
entity = getEntity( context, entityId )
    if type( bookSeries ) == 'table' and bookSeries[ 1 ] and bookSeries[ 1 ].id then
end
        data.bookSeries = bookSeries[ 1 ]
        bookSeries = data.bookSeries
    end


if ( entity ) then
    if not bookSeries or not bookSeries.id then
-- link to entity in source context language
        return
local projectToCheck = context.lang .. 'wiki';
    end
if ( entity.sitelinks and entity.sitelinks[ projectToCheck ] ) then
return ':' .. context.lang .. ':' .. entity.sitelinks[ projectToCheck ].title;
end
end


-- if ( entityId ) then return ':d:' .. entityId end;
    appendEntitySnaks( bookSeries.id, 'P123', data, 'publisher', {} )
if ( entityId ) then return 'https://tools.wmflabs.org/reasonator/?q=' .. entityId .. '&lang=ru' end;
    appendEntitySnaks( bookSeries.id, 'P291', data, 'place', {} )
return nil;
    appendEntitySnaks( bookSeries.id, 'P236', data, 'issn', {} )
end
end


function getPeopleAsAuthorWikitext( context, value, options )
---@param entityId string
if ( type( value ) == 'string' ) then
---@return string | nil
return personNameToAuthorName( value );
local function getNormativeTitle( entityId )
elseif ( type( value ) == 'table' ) then
    local possibleTypeIds = {}
if ( value.id ) then
    for typeId, _ in pairs( NORMATIVE_DOCUMENTS ) do
-- this is link
        table.insert( possibleTypeIds, typeId )
if ( options.preferids ) then
    end
return value.id;
else
if ( options.nolinks ) then
return getPersonNameAsAuthorLabel( context, value.id, value.label, options );
else
return getPersonNameAsAuthorWikitext( context, value.id, value.label, options );
end
end
end


local resultList = {};
    local foundTypeId = getFirstType( entityId, possibleTypeIds )
for i, tableValue in pairs( value ) do
    if foundTypeId then
local nextWikitext = getPeopleAsAuthorWikitext( context, tableValue, options );
        return NORMATIVE_DOCUMENTS[ foundTypeId ]
if ( not isEmpty( nextWikitext ) ) then
    end
table.insert( resultList, nextWikitext );
if ( #resultList == 4 ) then
-- even 4 is too much, but we preserve 4th to mark that "it's more than 3"
break;
end
end
end


local resultWikitext = '';
    return nil
for i, wikitext in pairs( resultList ) do
end
if ( i == 4 ) then
resultWikitext = resultWikitext .. ( i18nEtAl[ context.lang ] or i18nEtAlDefault );
break;
end
if ( i ~= 1 ) then
resultWikitext = resultWikitext .. ', ';
end
resultWikitext = resultWikitext .. wikitext;
end


return resultWikitext;
---@param urls table<number, string> | string
end
---@param text string
---@return string
local function wrapInUrl( urls, text )
    local url = getSingle( urls )


return options.format( '(unknown type)' );
    if string.sub( url, 1, 1 ) == ':' then
        return '[[' .. url .. '|' .. text .. ']]'
    else
        return '[' .. url .. ' ' .. text .. ']'
    end
end
end


function getPersonNameAsAuthorWikitext( context, entityId, customLabel, options )
---@param entityId string
local personNameAsAuthor = getPersonNameAsAuthorLabel( context, entityId, customLabel, options);
---@param lang string
if ( personNameAsAuthor == nil ) then
---@return string
return nil;
local function getElementLink( entityId, lang )
end
    local sitelink = getSitelink( entityId, nil )
    if sitelink then
        return ':' .. sitelink
    end


local link = getElementLink( context, entityId, nil );
    if lang ~= 'mul' then
return wrapInUrl( link, personNameAsAuthor );
        -- link to entity in source language
        sitelink = getSitelink( entityId, lang .. 'wiki' )
        if sitelink then
            return ':' .. lang .. ':' .. sitelink
        end
    end
 
    return ':d:' .. entityId
end
end


function getPersonNameAsAuthorLabel( context, entityId, providedLabel, options )
---@param entityId string
-- would custom label provided we don't need to check entity at all
---@param lang string
if ( not isEmpty( providedLabel ) ) then
---@return string
mw.log( 'Custom label provided for ' .. entityId );
local function getLabel( entityId, lang )
return personNameToAuthorName( providedLabel );
    local wbStatus, label = pcall( mw.wikibase.getLabelByLang, entityId, lang )
end
    if not wbStatus then
        return ''
    end


local entity = getEntity( context, entityId );
    if label and label ~= '' then
if ( not entity ) then return '\'\'(entity ' .. entityId .. ' is missing)\'\'' end;
        return label
if ( not isInstanceOf( entity, 'Q5' ) ) then
    end
mw.log( 'Entity ' .. entityId .. ' is not a person' );
return nil;
end


local personName = nil;
    wbStatus, label = pcall( mw.wikibase.getLabel, entityId )
-- support only labels so far
    if not wbStatus then
if ( entity.labels[ context.lang ] ) then
        return ''
personName = entity.labels[ context.lang ].value;
    end
mw.log('Got person name of ' .. entityId .. ' from label: «' .. personName .. '»' )
end


if ( isEmpty( personName ) ) then
    return label or ''
return '\'\'(not translated to ' .. context.lang .. ')\'\'';
else
return personNameToAuthorName( personName );
end
end
end


function personNameToAuthorName( fullName )
---@param lang string
if ( not fullName ) then return fullName; end
---@param entityId string
---@param customTitle string
---@param options table
local function renderLink( lang, entityId, customTitle, options )
    if not entityId then
        error( 'entityId is not specified' )
    end
    if type( entityId ) ~= 'string' then
        error( 'entityId is not string, but ' .. type( entityId ) )
    end
    if type( customTitle or '' ) ~= 'string' then
        error( 'customTitle is not string, but ' .. type( customTitle ) )
    end
 
    local title = customTitle
 
    -- ISO 4
    if isEmpty( title ) then
        local propertyStatements = getBestStatements( entityId, 'P1160' )
        for _, claim in pairs( propertyStatements ) do
            if ( claim
                    and claim.mainsnak
                    and claim.mainsnak.datavalue
                    and claim.mainsnak.datavalue.value
                    and claim.mainsnak.datavalue.value.language == lang
            ) then
                title = claim.mainsnak.datavalue.value.text
                -- mw.log( 'Got title of ' .. entityId .. ' from ISO 4 claim: «' .. title .. '»' )
                break
            end
        end
    end
 
    -- official name P1448
    -- short name P1813
    if isEmpty( title ) and options.short then
        local propertyStatements = getBestStatements( entityId, 'P1813' )
        for _, claim in pairs( propertyStatements ) do
            if ( claim
                    and claim.mainsnak
                    and claim.mainsnak.datavalue
                    and claim.mainsnak.datavalue.value
                    and claim.mainsnak.datavalue.value.language == lang
            ) then
                title = claim.mainsnak.datavalue.value.text
                -- mw.log( 'Got title of ' .. entityId .. ' from short name claim: «' .. title .. '» (' .. lang .. ')' )
                break
            end
        end
    end


local f, i, o = mw.ustring.match( fullName, '^%s*(%a[%a\-]*)\,%s(%a[%a\-]*)%s(%a[%a\-]*)%s*$' );
    -- person name P1559
if ( f ) then
    -- labels
mw.log( 'personNameToAuthorName: «' .. fullName .. '»: have «Fa, I. O.» match' );
    if isEmpty( title ) then
return f .. '&nbsp;'
        title = getLabel( entityId, lang )
.. mw.ustring.sub( i, 1, 1 ) .. '.&nbsp;'
        -- mw.log( 'Got title of ' .. entityId .. ' from label: «' .. title .. '» (' .. lang .. ')' )
.. mw.ustring.sub( o, 1, 1 ) .. '.';
    end
end


local i, o, f = mw.ustring.match( fullName, '^%s*(%a)\.%s(%a)\.%s(%a[%a\-]+)%s*$');
    local actualText = title or '\'\'(untranslated)\'\''
if ( f ) then
    local link = getElementLink( entityId, lang )
mw.log( 'personNameToAuthorName: «' .. fullName .. '»: have «I. O. Fa» match' );
    return wrapInUrl( link, actualText )
return f .. '&nbsp;' .. i .. '.&nbsp;' .. o .. '.';
end
end


local i1, i2, i3, f = mw.ustring.match( fullName, '^%s*(%a)\.%s(%a)\.%s(%a)\.%s(%a[%a\-]+)%s*$');
---@param lang string
if ( f ) then
---@param value value
mw.log( 'personNameToAuthorName: «' .. fullName .. '»: have «I. O. ?. Fa» match' );
---@param options options
return f .. '&nbsp;' .. i1 .. '.&nbsp;' .. i2 .. '.&nbsp;' .. i3 .. '.';
---@return string
end
local function asString( lang, value, options )
    if type( value ) == 'string' then
        return options.format( value )
    end
    if type( value ) ~= 'table' then
        return options.format( '(unknown type)' )
    end


     -- Joel J. P. C. Rodrigues
     if value.id then
local i1, i2, i3, i4, f = mw.ustring.match( fullName, '^%s*(%a)[%a\-]+%s(%a)\.%s(%a)\.%s(%a)\.%s(%a[%a\-]+)%s*$');
        -- this is link
if ( f ) then
        if type( value.label or '' ) ~= 'string' then
mw.log( 'personNameToAuthorName: «' .. fullName .. '»: have «I. O. ?. Fa» match' );
            mw.logObject( value, 'error value' )
return f .. '&nbsp;' .. i1 .. '.&nbsp;' .. i2 .. '.&nbsp;' .. i3 .. '.&nbsp;' .. i4 .. '.';
            error( 'label of table value is not string but ' .. type( value.label ) )
end
        end


local i, o, f = mw.ustring.match( fullName, '^%s*(%a[%a\-]*)%s(%a)\.%s(%a[%a\-]*)%s*$');
        local title
if ( f ) then
        if options.preferids then
mw.log( 'personNameToAuthorName: «' .. fullName .. '»: have «Im O. Fa» match' );
            title = value.id
return f .. '&nbsp;' .. mw.ustring.sub( i, 1, 1 ) .. '.&nbsp;' .. o .. '.';
        elseif options.nolinks then
end
            title = value.label or getLabel( value.id, lang )
        else
            title = renderLink( lang, value.id, value.label, options )
        end


local i, o, f = mw.ustring.match( fullName, '^%s*(%a[%a\-]*)%s(%a[%a\-]*)%s(%a[%a\-]*)%s*$');
        if title == '' then
if ( f ) then
            title = "''(untranslated title)''"
mw.log( 'personNameToAuthorName: «' .. fullName .. '»: have «Im Ot Fa» match' );
        end
return f .. '&nbsp;' .. mw.ustring.sub( i, 1, 1 ) .. '.&nbsp;' .. mw.ustring.sub( o, 1, 1 ) .. '.';
end


local i, o, f = mw.ustring.match( fullName, '^%s*(%a[%a\-]+)%s(%a[%a\-]+)%s+оглы%s+(%a[%a\-]+)%s*$');
        return options.format( title )
if ( f ) then
    end
mw.log( 'personNameToAuthorName: «' .. fullName .. '»: have «Im Ot оглы Fa» match' );
return f .. '&nbsp;' .. mw.ustring.sub( i, 1, 1 ) .. '.&nbsp;' .. mw.ustring.sub( o, 1, 1 ) .. '.';
end


local i, f = mw.ustring.match( fullName, '^%s*(%a[%a\-]+)%s(%a[%a\-]+)%s*$');
    local resultList = {}
if ( f ) then
    for _, tableValue in pairs( value ) do
mw.log( 'personNameToAuthorName: «' .. fullName .. '»: have «Im Fa» match' );
        table.insert( resultList, asString( lang, tableValue, options ) )
return f .. '&nbsp;' .. mw.ustring.sub( i, 1, 1 ) .. '.';
    end
end


mw.log( 'Unmatched any pattern: «' .. fullName .. '»' );
    return mw.text.listToText( resultList, options.separator, options.conjunction )
return fullName;
end
end


-- Expand special types of references when additional data could be found in OTHER entity properties
---@param entityId string
function expandSpecials( currentEntity, reference, data )
---@param data source
if ( reference.snaks.P248
---@return source
and reference.snaks.P248[1]
local function populateSourceDataImpl( entityId, data, map )
and reference.snaks.P248[1].datavalue
    local wsLink = getSitelink( entityId, 'ruwikisource' )
and reference.snaks.P248[1].datavalue.value["numeric-id"]) then
    if wsLink and not mw.ustring.gmatch( wsLink, 'Категория:' ) then
local sourceId = "Q" .. reference.snaks.P248[1].datavalue.value["numeric-id"];
        data.url = ":ru:s:" .. wsLink
    end
    populateDataFromEntity( entityId, data, map )
 
    local normativeTitle = getNormativeTitle( entityId )
    if normativeTitle then
        local y, m, d = mw.ustring.match( getSingle( data.dateOfCreation ) , "(%-?%d+)%-(%d+)%-(%d+)T" )
        y, m, d = tonumber( y ),tonumber( m ), tonumber( d )
        local title = asString( 'ru', data.title, options_commas_nolinks )
        local docNumber = getSingle( data.docNumber )
        data.title = {
            normativeTitle ..
                    " от&nbsp;" .. tostring( d ) .. "&nbsp;" .. monthGen[ m ] .. " " .. tostring( y ) .. "&nbsp;г." ..
                    ( docNumber and ( " №&nbsp;" .. docNumber ) or '' ) ..
                    ' «' .. title.. '»'
        }
    end


-- Gemeinsame Normdatei -- specified by P227
    if not data.title then
if ( sourceId == 'Q36578' ) then
        local lang = getLangCode( data.lang ) or i18nDefaultLanguage
appendSnaks( currentEntity.claims, 'P227', data, 'title', { format = function( gnd ) return 'Record #' .. gnd; end } );
        local label = getLabel( entityId, lang )
appendSnaks( currentEntity.claims, 'P227', data, 'url', { format = function( gnd ) return 'http://d-nb.info/gnd/' .. gnd .. '/'; end } );
        if label ~= '' then
data.publication = { id = 'Q36578', label = 'Gemeinsame Normdatei' }
            data.title = { label }
data.year = '2012—2015'
        end
end
    end


-- BNF -- specified by P268
    return data
if ( sourceId == 'Q15222191' ) then
end
appendSnaks( currentEntity.claims, 'P268', data, 'title', { format = function( id ) return 'Record #' .. id; end } );
appendSnaks( currentEntity.claims, 'P268', data, 'url', { format = function( id ) return 'http://catalogue.bnf.fr/ark:/12148/cb' .. id; end } );
expandSpecialsQualifiers( currentEntity, 'P268', data );
end


-- Find a Grave -- specified by P535
---@param entityId string
if ( sourceId == 'Q63056' ) then
---@param propertyId string
appendSnaks( currentEntity.claims, 'P535', data, 'url', { format = function( id ) return 'http://www.findagrave.com/cgi-bin/fg.cgi?page=gr&GRid=' .. id; end } );
---@param data source
expandSpecialsQualifiers( currentEntity, 'P535', data );
---@return void
end
local function expandSpecialsQualifiers( entityId, propertyId, data )
    local statements = getBestStatements( entityId, propertyId )
    for _, statement in pairs( statements ) do
        populateDataFromSnaks( statement.qualifiers or {}, data, PROPERTY_MAP )
    end
end


-- Dizionario Biografico degli Italiani -- specified by P1986
---Expand special types of references when additional data could be found in OTHER entity properties
if ( sourceId == 'Q1128537' ) then
---@param data source
if ( not data.lang ) then data.lang = { id = 'Q652' } end;
---@return void
appendSnaks( currentEntity.claims, 'P1986', data, 'url', { format = function( id ) return 'http://www.treccani.it/enciclopedia/' .. id .. '_%28Dizionario_Biografico%29/' end } );
local function expandSpecials( data )
expandSpecialsQualifiers( currentEntity, 'P1986', data );
    if not data.entityId then
end
        return
    end


-- Union List of Artist Names -- specified by P245
    if data.sourceId == 'Q36578' then
if ( sourceId == 'Q2494649' ) then
        -- Gemeinsame Normdatei -- specified by P227
appendSnaks( currentEntity.claims, 'P245', data, 'url', { format = function( id ) return 'http://www.getty.edu/vow/ULANFullDisplay?find=&role=&nation=&subjectid=' .. id end } );
        appendEntitySnaks( data.entityId, 'P227', data, 'part', { format = function(gnd ) return 'Record #' .. gnd; end } )
expandSpecialsQualifiers( currentEntity, 'P245', data );
        appendEntitySnaks( data.entityId, 'P227', data, 'url', { format = function(gnd ) return 'http://d-nb.info/gnd/' .. gnd .. '/'; end } )
end
        data.year = '2012—2016'
        expandSpecialsQualifiers( data.entityId, 'P227', data )


-- Gran Enciclopèdia Catalana -- specified by P1296
    elseif data.sourceId == 'Q15222191' then
if ( sourceId == 'Q2664168' ) then
        -- BNF -- specified by P268
appendSnaks( currentEntity.claims, 'P1296', data, 'url', { format = function( id ) return 'http://www.enciclopedia.cat/enciclop%C3%A8dies/gran-enciclop%C3%A8dia-catalana/EC-GEC-' .. id .. '.xml'; end } );
        appendEntitySnaks( data.entityId, 'P268', data, 'part', { format = function(id ) return 'Record #' .. id; end } )
expandSpecialsQualifiers( currentEntity, 'P1296', data );
        appendEntitySnaks( data.entityId, 'P268', data, 'url', { format = function(id ) return 'http://catalogue.bnf.fr/ark:/12148/cb' .. id; end } )
end
        expandSpecialsQualifiers( data.entityId, 'P268', data )


-- Encyclopædia Britannica online -- specified by P1417
    elseif data.sourceId == 'Q54919' then
if ( sourceId == 'Q5375741' ) then
        -- VIAF -- specified by P214
appendSnaks( currentEntity.claims, 'P1417', data, 'url', { format = function( id ) return 'http://global.britannica.com/EBchecked/topic/' .. id .. '/'; end } );
        appendEntitySnaks( data.entityId, 'P214', data, 'part', { format = function(id ) return 'Record #' .. id; end } )
expandSpecialsQualifiers( currentEntity, 'P1417', data );
        appendEntitySnaks( data.entityId, 'P214', data, 'url', { format = function(id ) return 'https://viaf.org/viaf/' .. id; end } )
end
        expandSpecialsQualifiers( data.entityId, 'P214', data )


-- Electronic Jewish Encyclopedia (Elektronnaja Evrejskaja Entsiklopedia) -- specified by P1438
    else
if ( sourceId == 'Q1967250' ) then
        -- generic property search
appendSnaks( currentEntity.claims, 'P1438', data, 'url', { format = function( id ) return 'http://www.eleven.co.il/article/' .. id; end } );
        for _, sourceClaim in pairs( getBestStatements( data.sourceId, 'P1687' ) ) do
expandSpecialsQualifiers( currentEntity, 'P1438', data );
            if sourceClaim.mainsnak.snaktype == 'value' then
end
                local sourcePropertyId = sourceClaim.mainsnak.datavalue.value.id
                for _, sourcePropertyClaim in pairs( getBestStatements( sourcePropertyId, 'P1630' ) ) do
                    if sourcePropertyClaim.mainsnak.snaktype == 'value' then
                        appendEntitySnaks( data.entityId, sourcePropertyId, data, 'url', {
                            format = function( id )
                                return mw.ustring.gsub( mw.ustring.gsub( sourcePropertyClaim.mainsnak.datavalue.value, '$1', id ), ' ', '%%20' )
                            end
                        } )
                        expandSpecialsQualifiers( data.entityId, sourcePropertyId, data )
                        break
                    end
                end
            end
        end
    end


-- sports-reference.com -- specified by P1447
    -- do we have appropriate record in P1433 ?
if ( sourceId == 'Q18002875' ) then
    local claims = findClaimsByValue( currentEntityId, 'P1343', data.sourceId )
appendSnaks( currentEntity.claims, 'P1447', data, 'url', { format = function( id ) return 'http://www.sports-reference.com/olympics/athletes/' .. id .. '.html'; end } );
    if claims and #claims ~= 0 then
expandSpecialsQualifiers( currentEntity, 'P1447', data );
        for _, claim in pairs( claims ) do
end
            populateDataFromSnaks( claim.qualifiers, data, PROPERTY_MAP )
end
            populateDataFromEntity( data.sourceId, data, PROPERTY_MAP )
        end
    end
end
end


function expandSpecialsQualifiers( entity, propertyId, result )
---@param text string
if ( entity.claims ~= nil and entity.claims[propertyId] ~= nil ) then
---@param tip string
local claims = entity.claims[propertyId];
---@return string
appendQualifiers( claims, 'P958', result, 'part', {} );
local function toTextWithTip( text, tip )
appendQualifiers( claims, 'P953', result, 'url', {} );
    return '<span title="' .. tip .. '" style="border-bottom: 1px dotted; cursor: help; white-space: nowrap">' .. text .. '</span>'
appendQualifiers( claims, 'P1065', result, 'url', {} );
appendQualifiers( claims, 'P854', result, 'url', {} );
appendQualifiers( claims, 'P357', result, 'title', {} ); -- obsolete
appendQualifiers( claims, 'P1476', result, 'title', {} );
appendQualifiers( claims, 'P478', result, 'volume', {} );
appendQualifiers( claims, 'P433', result, 'issue', {} );
end
end
end


function findClaimsByValue( entity, propertyId, value )
---@param lang string
local result = {};
---@param placeId string
if ( entity and entity.claims and entity.claims[propertyId] ) then
---@return string
for i, claim in pairs( entity.claims[propertyId] ) do
local function getPlaceName( placeId, lang )
if ( claim.mainsnak and claim.mainsnak.datavalue ) then
    -- ГОСТ Р 7.0.12—2011
local datavalue = claim.mainsnak.datavalue;
    if lang == 'ru' then
if ( datavalue.type == "string" and datavalue.value == value
        if placeId == 'Q649' then return toTextWithTip( .', 'Москва' ); end
or datavalue.type == "wikibase-entityid" and datavalue.value["entity-type"] == "item" and tostring( datavalue.value["numeric-id"] ) == mw.ustring.sub( value, 2 ) ) then
        if placeId == 'Q656' then return toTextWithTip( 'СПб.', 'Санкт-Петербург' ); end
table.insert( result, claim );
        if placeId == 'Q891' then return toTextWithTip( . Новгород', 'Нижний Новгород' ); end
end
        if placeId == 'Q908' then return toTextWithTip( 'Ростов н/Д.', 'Ростов-на-Дону' ); end
end
    end
end
    return nil
end
return result;
end
end


function appendSnaks( allSnaks, snakPropertyId, result, property, options )
---@param data source
-- do not populate twice
---@param lang string
if ( result[property] ) then return result end;
---@return void
if ( not allSnaks ) then return result; end;
local function preprocessPlace( data, lang )
    if not data.place then
local selectedSnakes = allSnaks[ snakPropertyId ];
        return
if ( not selectedSnakes ) then return result; end;
    end
 
    ---@type table<number, string>
    local newPlace = {}


local hasPreferred = false;
    for index, place in pairs( data.place ) do
for k, snak in pairs( selectedSnakes ) do
        if place.id then
if ( snak and snak.mainsnak and snak.mainsnak.datavalue and snak.rank == 'preferred' ) then
            local newPlaceStr = getPlaceName( place.id, lang )
--it's a preferred claim
            if newPlaceStr then
appendImpl( snak.mainsnak.datavalue, snak.qualifiers, result, property, options );
                newPlace[ index ] = newPlaceStr
hasPreferred = true;
            else
end
                newPlace[ index ] = getLabel( place.id, lang )
end
            end
if ( hasPreferred ) then return result; end;
        else
            newPlace[ index ] = place
        end
    end


for k, snak in pairs( selectedSnakes ) do
    data.place = newPlace
if ( snak and snak.mainsnak and snak.mainsnak.datavalue and snak.rank ~= 'deprecated' ) then
--it's a claim
appendImpl( snak.mainsnak.datavalue, snak.qualifiers, result, property, options );
elseif ( snak and snak.datavalue ) then
-- it's a snak
appendImpl( snak.datavalue, nil, result, property, options );
end
end
end
end


function appendQualifiers( claims, qualifierPropertyId, result, resultProperty, options )
---@param entityId string
-- do not populate twice
---@param lang string
if ( not claims ) then return result end;
---@param providedLabel string | nil
if ( result[resultProperty] ) then return result end;
---@param options options
---@return string
local function getPersonNameAsLabel( entityId, lang, providedLabel, options )
    -- would custom label provided we don't need to check entity at all
    if not isEmpty( providedLabel ) then
        return options.format( providedLabel )
    end
 
    if lang == 'mul' then
        lang = i18nDefaultLanguage
    end
 
    ---@type string | nil
    local personName = getLabel( entityId, lang )
 
    if isEmpty( personName ) then
        return '\'\'(not translated to ' .. lang .. ')\'\''
    end
 
    if not isInstanceOf( entityId, 'Q5' ) then
        return personName
    end


for i, claim in pairs( claims ) do
    return options.format( personName )
if ( claim.qualifiers and claim.qualifiers[ qualifierPropertyId ] ) then
for k, qualifier in pairs( claim.qualifiers[ qualifierPropertyId ] ) do
if ( qualifier and qualifier.datavalue ) then
appendImpl( qualifier.datavalue, nil, result, resultProperty, options );
end
end
end
end
end
end


function appendImpl( datavalue, qualifiers, result, property, options )
---@param entityId string
if ( datavalue.type == 'string' ) then
---@param lang string
local value = datavalue.value;
---@param customLabel string | nil
if ( options.format ) then
---@param options options
value = options.format( value );
---@return string
end
local function getPersonNameAsWikitext( entityId, lang, customLabel, options )
appendImpl_toTable( result, property );
    local personName = getPersonNameAsLabel( entityId, lang, customLabel, options )
table.insert( result[property], value);
    local link = getElementLink( entityId, lang )
elseif ( datavalue.type == 'monolingualtext' ) then
    return wrapInUrl( link, personName )
local value = datavalue.value.text;
if ( options.format ) then
value = options.format( value );
end
appendImpl_toTable( result, property );
table.insert( result[property], value);
elseif ( datavalue.type == 'quantity' ) then
local value = datavalue.value.amount;
if ( mw.ustring.sub( value , 1, 1 ) == '+' ) then
value = mw.ustring.sub( value , 2 );
end
if ( options.format ) then
value = options.format( value );
end
appendImpl_toTable( result, property );
table.insert( result[property], value);
elseif ( datavalue.type == 'wikibase-entityid' ) then
local value = datavalue.value;
appendImpl_toTable( result, property );
local toInsert = {
id = 'Q' .. value["numeric-id"],
label = getSingleStringQualifierValue(qualifiers, 'P1932') -- stated as
};
table.insert( result[property], toInsert );
elseif datavalue.type == 'time' then
local value = datavalue.value;
if ( options.format ) then
value = options.format( value );
end
appendImpl_toTable( result, property );
table.insert( result[property], tostring( value.time ));
    end
end
end


function appendImpl_toTable(result, resultProperty)
---@param value value
if ( not result[resultProperty] ) then
---@param lang string
result[resultProperty] = {};
---@param options options
elseif ( type( result[resultProperty] ) == 'string' or ( type( result[resultProperty] ) == 'table' and type( result[resultProperty].id ) == 'string' ) ) then
---@return string
result[resultProperty] = { result[resultProperty] };
local function getPeopleAsWikitext( value, lang, options )
end
    if type( value ) == 'string' then
end
        return options.format( value )
    elseif type( value ) == 'table' then
        if value.id then
            -- this is link
            if options.preferids then
                return tostring( value.id )
            else
                if options.nolinks then
                    return getPersonNameAsLabel( value.id, lang, value.label, options )
                else
                    return getPersonNameAsWikitext( value.id, lang, value.label, options )
                end
            end
        end
 
        local maxAuthors = 10 -- need some restrictions, as some publications have enormous amount of authors (e.g. 115 authors of Q68951544)
        local resultList = {}
        for _, tableValue in pairs( value ) do
            local nextWikitext = getPeopleAsWikitext( tableValue, lang, options )
            if not isEmpty( nextWikitext ) then
                table.insert( resultList, nextWikitext )
                if #resultList == maxAuthors + 1 then
                    -- keep one more to indicate that there are too many
                    break
                end
            end
        end


function getSingleStringQualifierValue( allQualifiers, qualifierPropertyId )
        local resultWikitext = ''
if ( not allQualifiers ) then return end
        for i, wikitext in pairs( resultList ) do
if ( not allQualifiers[qualifierPropertyId] ) then return end
            if i == maxAuthors + 1 then
                resultWikitext = resultWikitext .. ( i18nEtAl[ lang ] or i18nEtAlDefault )
                break
            end
            if i ~= 1 then
                resultWikitext = resultWikitext .. ', '
            end
            resultWikitext = resultWikitext .. wikitext
        end


for k, qualifier in pairs( allQualifiers[qualifierPropertyId] ) do
        return resultWikitext
if ( qualifier and qualifier.datatype == 'string'
    end
and qualifier.datavalue and qualifier.datavalue.type == 'string' and not isEmpty( qualifier.datavalue.value ) ) then
return qualifier.datavalue.value;
end
end


return;
    return '' -- options.format( '(unknown type)' )
end
end


function expandDescribed ( context, sourceEntity, data )
---@param lang string
local described = data.described;
---@param data source
---@return string
local function generateAuthorLinks( lang, data )
    local result = ''
    if data.author then
        result = getPeopleAsWikitext( data.author, lang, options_commas_authors )
        result = '<i class="wef_low_priority_links">' .. result .. '</i> '
    end
    return result
end


-- use only first one
---@param lang string
if ( type( described ) == 'table' and described[1] and described[1].id ) then
---@param data source
data.described = described[1];
---@param conjunction string
described = data.described;
---@param propertyName string
end
---@param urlPropertyName string?
---@return string
local function appendProperty( lang, data, conjunction, propertyName, urlPropertyName )
    if not data[ propertyName ] then
        return ''
    end


data.publication = data.described;
    local out
    if urlPropertyName and data[ urlPropertyName ] then
        out = wrapInUrl( data[ urlPropertyName ], asString( lang, data[ propertyName ], options_commas_nolinks ) )
    else
        out = asString( lang, data[ propertyName ], options_commas )
    end


if ( described and described.id ) then
    if not out or out == '' then
if ( sourceEntity ) then
        return ''
-- do we have appropriate record in P1433 ?
    end
local claims = findClaimsByValue( sourceEntity, 'P1343', described.id );
 
if ( claims and #claims ~= 0 ) then
    return conjunction .. out
appendQualifiers( claims, 'P958', data, 'part', {} );
appendQualifiers( claims, 'P50', data, 'author', {} );
appendQualifiers( claims, 'P953', data, 'url', {} );
appendQualifiers( claims, 'P1065', data, 'url', {} );
appendQualifiers( claims, 'P854', data, 'url', {} );
appendQualifiers( claims, 'P357', data, 'title', {} ); -- obsolete
appendQualifiers( claims, 'P1476', data, 'title', {} );
appendQualifiers( claims, 'P478', data, 'volume', {} );
end
end
local titleWerePresent = not (not data.title);
local desEntity = getEntity( context, described.id );
if ( desEntity ) then
populateSourceDataImpl( desEntity, data );
if ( titleWerePresent and isEmpty( data.publication.label ) ) then
appendSnaks( desEntity.claims, 'P1160', data, 'publication-title', {} ); -- obsolete
data.publication.label = getSingle( data['publication-title'] );
end
if ( titleWerePresent and isEmpty( data.publication.label ) ) then
appendSnaks( desEntity.claims, 'P357', data, 'publication-title', {} ); -- obsolete
appendSnaks( desEntity.claims, 'P1476', data, 'publication-title', {} );
appendSnaks( desEntity.claims, 'P1680', data, 'publication-subtitle', {} );
data.publication.label = getSingle( data['publication-title'] );
data.publication.subtitle = getSingle( data['publication-subtitle'] );
end
end
end
end
end


function expandPublication( context, sourceEntity, data )
---@param lang string
local publication = data.publication;
---@param data source
---@return string
local function appendTitle( lang, data )
    local conjunction = ''
    local result = ''


-- use only first one
    if data.part then
if ( type( publication ) == 'table' and publication[1] and publication[1].id ) then
        result = result .. appendProperty( lang, data, '', 'part', 'parturl' )
data.publication = publication[1];
        conjunction = ' // '
publication = data.publication;
    end
end


if ( not publication ) then return end;
    return result .. appendProperty( lang, data, conjunction, 'title', 'url' )
if ( not publication.id ) then return end;
end


if ( sourceEntity ) then
---@param lang string
-- do we have appropriate record in P1433 ?
---@return string
local function appendLanguage( lang )
    if lang == i18nDefaultLanguage then
        return ''
    end


local claims = findClaimsByValue( sourceEntity, 'P1433', publication.id );
    ---@type { getRefHtml: ( fun( lang: string ): string ), list_ref: ( fun( frame: frame ): string ) }
if ( claims and #claims ~= 0 ) then
    local langs = require( 'Module:Languages' )
appendQualifiers( claims, 'P958', data, 'part', {} );
    return langs.list_ref( p.currentFrame:newChild{ args = { lang } } )
appendQualifiers( claims, 'P953', data, 'url', {} );
end
appendQualifiers( claims, 'P1065', data, 'url', {} );
appendQualifiers( claims, 'P854', data, 'url', {} );
appendQualifiers( claims, 'P856', data, 'url', {} );
appendQualifiers( claims, 'P123', data, 'publisher', {} );
appendQualifiers( claims, 'P291', data, 'place', {} );
appendQualifiers( claims, 'P304', data, 'pages', {} );
appendQualifiers( claims, 'P1104', data, 'numberOfPages', {} );
appendQualifiers( claims, 'P478', data, 'volume', {} );
appendQualifiers( claims, 'P433', data, 'issue', {} );
appendQualifiers( claims, 'P571', data, 'dateOfCreation', {} );
appendQualifiers( claims, 'P577', data, 'dateOfPublication', {} );
appendQualifiers( claims, 'P212', data, 'isbn', {} ); -- ISBN-13
appendQualifiers( claims, 'P957', data, 'isbn', {} ); -- ISBN-10
end
end


local titleWerePresent = not (not data.title);
---@param lang string
local pubEntity = getEntity( context, publication.id );
---@param data source
populateSourceDataImpl( pubEntity, data );
---@return string
if ( titleWerePresent and isEmpty( data.publication.label ) ) then
local function appendSubtitle( lang, data )
appendSnaks( pubEntity.claims, 'P1160', data, 'publication-title', {} ); -- obsolete
    return appendProperty( lang, data, ': ', 'subtitle', nil )
data.publication.label = getSingle( data['publication-title'] );
end
end
 
if ( titleWerePresent and isEmpty( data.publication.label ) ) then
---@param lang string
appendSnaks( pubEntity.claims, 'P357', data, 'publication-title', {} ); -- obsolete
---@param data source
appendSnaks( pubEntity.claims, 'P1476', data, 'publication-title', {} );
---@return string
appendSnaks( pubEntity.claims, 'P1680', data, 'publication-subtitle', {} );
local function appendOriginalTitle( lang, data )
data.publication.label = getSingle( data['publication-title'] );
    return appendProperty( lang, data, ' = ', 'originaltitle', nil )
data.publication.subtitle = getSingle( data['publication-subtitle'] );
end
end


if ( pubEntity.claims and pubEntity.claims.P361 ) then
---@param lang string
for c, claim in pairs( pubEntity.claims.P361 ) do
---@param data source
if ( claim and claim.mainsnak and claim.mainsnak.datavalue and claim.mainsnak.datavalue.value and claim.mainsnak.datavalue.value["numeric-id"] ) then
---@return string
local possibleBookSeriesEntityId = 'Q' .. claim.mainsnak.datavalue.value["numeric-id"];
local function appendPublication( lang, data )
local possibleBookSeriesEntity = getEntity( context, possibleBookSeriesEntityId );
    if not data.publication then
if ( isInstanceOf( possibleBookSeriesEntity, 'Q277759' ) ) then
        return ''
appendImpl_toTable( data, 'bookSeries' );
    end
table.insert( data.bookSeries, { id = possibleBookSeriesEntityId } );


appendQualifiers( { claim }, 'P361', data, 'bookSeriesVolume', {} );
    local result = ' // ' .. asString( lang, data.publication.title, options_commas_it_short )
appendQualifiers( { claim }, 'P433', data, 'bookSeriesIssue', {} );
    if data.publication.subtitle and data.publication.subtitle ~= '' then
end
        result = result .. ': ' .. asString( lang, data.publication.subtitle, options_commas_it_short )
end
    end
end
end


expandBookSeries( context, data );
    return result
end
end


function expandBookSeries( context, data )
---@param lang string
local bookSeries = data.bookSeries;
---@param data source
if ( not bookSeries ) then return end;
---@return string
local function appendEditor( lang, data )
    if not data.editor and not data.translator then
        return ''
    end


-- use only first one
    local result = ' / '
if ( type( bookSeries ) == 'table' and bookSeries[1] and bookSeries[1].id ) then
    if data.editor then
data.bookSeries = bookSeries[1];
        local prefix = i18nEditors[ lang ] or i18nEditors[ i18nDefaultLanguage ]
bookSeries = data.bookSeries;
        result = result .. prefix .. getPeopleAsWikitext( data.editor, lang, options_commas_responsible )
end
        if data.translator then
            result = result .. ', '
        end
    end
    if data.translator then
        local prefix = i18nTranslators[ lang ] or i18nTranslators[ i18nDefaultLanguage ]
        result = result .. prefix .. getPeopleAsWikitext( data.translator, lang, options_commas_responsible )
    end


if ( not bookSeries ) then return end;
    return result
if ( not bookSeries.id ) then return end;
end


local bookSeriesEntity = getEntity( context, bookSeries.id );
---@param lang string
appendSnaks( bookSeriesEntity.claims, 'P123', data, 'publisher', {} );
---@param data source
appendSnaks( bookSeriesEntity.claims, 'P291', data, 'place', {} );
local function appendEdition( lang, data )
appendSnaks( bookSeriesEntity.claims, 'P236', data, 'issn', {} );
    return appendProperty( lang, data, ' ', 'edition', nil )
end
end


function populateSourceDataImpl( entity, plainData )
---@param lang string
populateDataFromClaims( entity.id, entity.claims, plainData );
---@param data source
---@return string
local function appendPublicationData( lang, data )
    if not data.place and not data.publisher and not data.year then
        return ''
    end


local normativeTitle = getNormativeTitle( entity )
    local result = ' — '
if ( normativeTitle ) then
    if data.place then
local y, m, d = mw.ustring.match( getSingle( plainData.dateOfCreation ) , "(%-?%d+)%-(%d+)%-(%d+)T" );
        result = result .. asString( lang, data.place, options_commas_short )
y,m,d = tonumber(y),tonumber(m),tonumber(d);
        if data.publisher or data.year then
local title = toString( { lang='ru' }, plainData.title, options_commas_nolinks );
            result = result .. ': '
plainData.title = { normativeTitle .. " от&nbsp;" .. tostring(d) .. "&nbsp;" .. monthg[m]  .. " " .. tostring(y) .. "&nbsp;г. №&nbsp;" .. getSingle( plainData.docNumber ) .. ' «' .. title.. '»' }
        end
end
    end
    if data.publisher then
        result = result .. asString( lang, data.publisher, options_commas_short )
        if data.year then
            result = result .. ', '
        end
    end
    if data.year then
        result = result .. asString( lang, data.year, options_commas )
    end
    result = result .. '.'


if ( not plainData.title ) then
    return result
if ( entity.labels and entity.labels.ru and entity.labels.ru.value ) then
plainData.title = { entity.labels.ru.value };
end
end
 
return plainData;
end
end


function populateDataFromClaims( entityId, claims, data )
---@param lang string
appendSnaks( claims, 'P50', data, 'author', {} );
---@param data source
appendSnaks( claims, 'P407', data, 'lang', {} );
---@return string
appendSnaks( claims, 'P364', data, 'lang', {} );
local function appendVolumeAndIssue( lang, data )
appendSnaks( claims, 'P958', data, 'part', {} );
    if not data.volume and not data.issue then
if ( not data.title ) then
        return ''
if ( not isEmpty( entityId ) ) then
     end
local optionsAsLinks = { format = function( text ) return { id = entityId, label = text } end };
appendSnaks( claims, 'P357', data, 'title', optionsAsLinks ); -- obsolete
appendSnaks( claims, 'P1476', data, 'title', optionsAsLinks );
else
appendSnaks( claims, 'P357', data, 'title', {} ); -- obsolete
appendSnaks( claims, 'P1476', data, 'title', {} );
end
appendSnaks( claims, 'P1680', data, 'subtitle', {} );
end
appendSnaks( claims, 'P953', data, 'url', {} );
appendSnaks( claims, 'P1065', data, 'url', {} );
appendSnaks( claims, 'P854', data, 'url', {} );
appendSnaks( claims, 'P856', data, 'url', {} );
appendSnaks( claims, 'P1343', data, 'described', {} );
appendSnaks( claims, 'P1433', data, 'publication', {} );
appendSnaks( claims, 'P123', data, 'publisher', {} );
appendSnaks( claims, 'P291', data, 'place', {} );
appendSnaks( claims, 'P304', data, 'pages', {} );
appendSnaks( claims, 'P1104', data, 'numberOfPages', {} );
appendSnaks( claims, 'P478', data, 'volume', {} );
appendSnaks( claims, 'P433', data, 'issue', {} );
appendSnaks( claims, 'P571', data, 'dateOfCreation', {} );
appendSnaks( claims, 'P577', data, 'dateOfPublication', {} );
appendSnaks( claims, 'P212', data, 'isbn', {} ); -- ISBN-13
appendSnaks( claims, 'P957', data, 'isbn', {} ); -- ISBN-10
appendSnaks( claims, 'P236', data, 'issn', {} );
     -- web
-- appendSnaks( claims, 'P813', data, 'accessdate', {} );


     -- docs
     local result = ' — '
appendSnaks( claims, 'P1545', data, 'docNumber', {} );
    local letter_vol = i18nVolume[ lang ] or i18nVolume[ i18nDefaultLanguage ]
    local letter_iss = i18nIssue[ lang ] or i18nIssue[ i18nDefaultLanguage ]
    if data.volume then
        result = result .. appendProperty( lang, data, letter_vol .. '&nbsp;', 'volume', nil )
        result = result ..appendProperty( lang, data, ', ' .. letter_iss .. '&nbsp;', 'issue', nil )
    else
        result = result .. appendProperty( lang, data, letter_iss .. '&nbsp;', 'issue', nil )
    end
    result = result .. '.'


-- other
    return result
appendSnaks( claims, 'P31', data, 'type', {} );
end


appendSnaks( claims, 'P818', data, 'arxiv', {} );
---@param lang string
appendSnaks( claims, 'P356', data, 'doi', {} );
---@param data source
-- JSTOR
---@return string
appendSnaks( claims, 'P888', data, 'url', { format = function( id ) return 'http://www.jstor.org/stable/' .. id end } );
local function appendPages( lang, data )
    if not data.pages then
        return ''
    end


return src;
    local letter = i18nPages[ lang ] or i18nPages[ i18nDefaultLanguage ]
    local strPages = asString( lang, data.pages, options_commas )
    strPages = mw.ustring.gsub( strPages, '[-—]', '—' )
    return ' — ' .. letter .. '&nbsp;' .. strPages .. '.'
end
end


function updateWithRef( reference, src )
---@param lang string
-- specified
---@param data source
if ( reference.snaks.P662 ) then
---@return string
local cid = reference.snaks.P662[1].datavalue.value;
local function appendNumberOfPages( lang, data )
src.code = src.code .. '-cid:' .. cid;
    if not data.numberOfPages then
src.title = 'Compound Summary for: CID ' .. cid;
        return ''
src.url = 'http://pubchem.ncbi.nlm.nih.gov/summary/summary.cgi?cid=' .. cid;
    end
src.publication = { id = 'Q278487', label = 'PubChem' };
end


populateDataFromClaims( nil, reference.snaks, src);
    local letter = i18nNumberOfPages[ lang ] or i18nNumberOfPages[ i18nDefaultLanguage ]
return src;
    return appendProperty( lang, data, ' — ', 'numberOfPages', nil ) .. '&nbsp;' .. letter
end
end


function p.renderSource( frame )
---@param lang string
local arg = frame.args[1];
---@param data source
local refAnchor = frame.args['ref'];
---@return string
local args = {};
local function appendBookSeries( lang, data )
args.refAnchor = frame.args['ref'];
    if not data.bookSeries then
args.part = frame.args['part'];
        return ''
args.pages = frame.args['pages'];
    end


return p.renderSourceImpl( mw.text.trim( arg ), args );
    local result = appendProperty( lang, data, ' — (', 'bookSeries', nil )
    if data.bookSeriesVolume or data.bookSeriesIssue then
        result = result .. '; '
        local letter_vol = i18nVolume[ lang ] or i18nVolume[ i18nDefaultLanguage ]
        local letter_iss = i18nIssue[ lang ] or i18nIssue[ i18nDefaultLanguage ]
        if data.bookSeriesVolume then
            result = result .. appendProperty( lang, data, letter_vol .. '&nbsp;', 'bookSeriesVolume', nil )
            result = result .. appendProperty( lang, data, ', ' .. letter_iss .. '&nbsp;', 'bookSeriesIssue', nil )
        else
            result = result .. appendProperty( lang, data, letter_iss .. '&nbsp;', 'bookSeriesIssue', nil )
        end
    end
    result = result .. ')'
 
    return result
end
end


function copyArgsToSnaks( args, snaks )
---@param lang string
if ( not isEmpty( args.part ) ) then snaks.P958 = { toStringSnak( 'P958', tostring( args.part ) ) } end
---@param data source
if ( not isEmpty( args.pages ) ) then snaks.P304 = { toStringSnak( 'P304', tostring( args.pages ) ) } end
---@return string
if ( not isEmpty( args.issue ) ) then snaks.P433 = { toStringSnak( 'P433', tostring( args.issue ) ) } end
local function appendTirage( lang, data )
if ( not isEmpty( args.volume ) ) then snaks.P478 = { toStringSnak( 'P478', tostring( args.volume ) ) } end
    if not data.tirage then
if ( not isEmpty( args.url ) ) then snaks.P953 = { toUrlSnak( 'P953', tostring( args.url ) ) } end
        return ''
    end
 
    local tirageTemplate = i18nTirage[ lang ] or i18nTirage[ i18nDefaultLanguage ]
    ---@type options
    local optionsTirage = {
        separator = '; ',
        conjunction = '; ',
        format = function( _data ) return tostring( mw.ustring.format( tirageTemplate, _data ) ) end,
        short = false,
        nolinks = false,
        preferids = false,
    }
    return ' — ' .. asString( lang, data.tirage, optionsTirage )
end
end


function p.renderSourceImpl( entityId, args )
---@param lang string
args = args or {};
---@param value string | nil
---@param options options
---@param prefix string?
---@return string
local function appendIdentifier( lang, value, options, prefix )
    if not value then
        return ''
    end
 
    return ' — ' .. ( prefix or '' ) .. asString( lang, value, options )
end


local snaks = {};
---@param result string
snaks.P248 = { toWikibaseEntityIdSnak( 'P248', entityId ) };
---@param lang string
copyArgsToSnaks( args, snaks );
---@param data source
---@return string
local function wrapSourceId( result, lang, data )
    if not data.sourceId then
        return result
    end


local rendered = renderReferenceImpl( mw.wikibase.getEntity(), { snaks = snaks }, args.refAnchor );
    local citeType = data.type and asString( lang, data.type, options_citetypes ) or 'citetype_unknown'
if ( rendered ) then return rendered.text end;
    return '<span class="wikidata_cite ' .. citeType .. '" data-entity-id="' .. data.sourceId .. '">' .. result .. '</span>'
end
end


function p.renderReference( frame, currentEntity, reference )
---@param data source
-- template call
---@return string
if ( frame and not currentEntity and not reference ) then
local function appendAccessDate( data )
local args = frame.args;
    if not data.accessdate then
if ( #frame.args == 0 ) then
        return ''
args = frame:getParent().args;
    end
end


local snaks = {};
    local date = getSingle( data.accessdate )
    local pattern = "(%-?%d+)%-(%d+)%-(%d+)T"
    local y, m, d = mw.ustring.match( date, pattern )
    y, m, d = tonumber( y ), tonumber( m ), tonumber( d )
    local date_str = ( d > 0 and ' ' .. tostring( d ) or '' )
            .. ( m > 0 and ' ' .. monthGen[ m ] or '' )
            .. ( y > 0 and ' ' .. tostring( y ) or '' )


if ( args[1] ) then
snaks.P248 = { toWikibaseEntityIdSnak( "P248", args[1] ) };
end
copyArgsToSnaks( args, snaks );


currentEntity = mw.wikibase.getEntity();
    return " <small>Проверено" .. date_str .. ".</small>"
reference = { snaks = snaks };
end
end


local rendered = renderReferenceImpl( currentEntity, reference );
---@param data source
---@param lang string
---@return void
local function populateUrl( data, lang )
    if data.sourceId and not data.url then
        local sitelink = getSitelink( data.sourceId, lang .. 'wikisource' )
        if sitelink then
            data.url = ':' .. lang .. ':s:' .. sitelink
        end
    end
end


if ( not rendered ) then
---@param data source
return '';
---@return void
end
local function populateYear( data )
    if not data.year and data.dateOfPublication then
        local date = getSingle( data.dateOfPublication )
        data.year = mw.ustring.sub( date, 2, 5 )
    end
    if not data.year and data.dateOfCreation then
        local date = getSingle( data.dateOfCreation )
        data.year = mw.ustring.sub( date, 2, 5 )
    end
end


local result;
---@param data source
local code = rendered.code or mw.text.encode( rendered.text );
---@return void
result = frame:extensionTag( 'ref', rendered.text, {name = code} ) .. '[[К:ЭАНМ:Статьи с источниками из Викиданных]]';
local function populateTitle( data )
 
    data.title = data.title or getSingle( data.url )
return result;
end
end


---@param data source
---@return string
local function renderSource( data )
    local lang = getLangCode( data.lang ) or i18nDefaultLanguage


function renderReferenceImpl( currentEntity, reference, refAnchor )
    preprocessPlace( data, lang )
if ( not reference.snaks ) then
    populateUrl( data, lang )
return nil;
    populateTitle( data )
end
    if not data.title then
        return ''
    end


-- контекст, содержит также кеш элементов
    populateYear( data )
local context = {
cache = {},
}


-- данные в простом формате, согласованном с модулями формирования библиографического описания
    local result = generateAuthorLinks( lang, data )
local data = {};
    result = result .. appendTitle( lang, data )
    result = result .. appendLanguage( lang )
    result = result .. appendSubtitle( lang, data )
    result = result .. appendOriginalTitle( lang, data )
    result = result .. appendPublication( lang, data )


local entityId, sourceEntity;
    result = result .. '<span class="wef_low_priority_links">'
if ( reference and reference.snaks and reference.snaks.P248 ) then
    result = result .. appendEditor( lang, data ) -- Might take current editor instead of actual. Use with caution
for _, snak in pairs ( reference.snaks.P248 ) do
    result = result .. appendEdition( lang, data )
if ( snak.datavalue ) then
    result = result .. appendPublicationData( lang, data )
entityId = 'Q' .. snak.datavalue.value["numeric-id"];
    result = result .. appendVolumeAndIssue( lang, data )
sourceEntity = getEntity( context, entityId );
    result = result .. appendPages( lang, data )
data.code = entityId;
    result = result .. appendNumberOfPages( lang, data )
data.entityId = entityId;
    result = result .. appendBookSeries( lang, data )
break;
    result = result .. appendTirage( lang, data )
end
end
end


updateWithRef( reference, data );
    result = result .. appendIdentifier( lang, data.isbn, options_commas, 'ISBN ' )
-- update ref name with ref-specific properties
    result = result .. appendIdentifier( lang, data.issn, options_issn, 'ISSN ' )
if ( data.code ) then
    result = result .. appendIdentifier( lang, data.doi, options_doi, nil )
if ( data.part ) then data.code = data.code .. '-' .. getSingle( data.part ) end
    result = result .. appendIdentifier( lang, data.pmid, options_pmid, nil )
if ( data.pages ) then data.code = data.code .. '-' .. getSingle( data.pages ) end
    result = result .. appendIdentifier( lang, data.arxiv, options_arxiv, nil )
if ( data.volume ) then data.code = data.code .. '-' .. getSingle( data.volume ) end
    result = result .. appendAccessDate( data )
if ( data.issue ) then data.code = data.code .. '-' .. getSingle( data.issue ) end
    result = result .. '</span>'
if ( data.url ) then data.code = data.code .. '-' .. getSingle( data.url ) end
end


expandSpecials( currentEntity, reference, data );
    return wrapSourceId( result, lang, data )
if ( sourceEntity ) then
end
populateSourceDataImpl( sourceEntity, data );
end
if ( not data.publication and data.described ) then
expandDescribed( context, sourceEntity, data );
else
if ( data.publication ) then expandPublication( context, sourceEntity, data ); end
end


if ( next( data ) == nil ) then
---@param data source Данные в простом формате, согласованном с модулями формирования библиографического описания
return nil;
---@param snaks snaks
end
---@return string | nil
local function renderReferenceImpl( data, snaks )
    -- не показывать источники с "импортировано из"
    if snaks.P143 then
        return nil
    end


local rendered;
    -- забрать данные из reference
if ( p.short ) then
    populateDataFromSnaks( snaks or {}, data, PROPERTY_MAP )
rendered = renderShortReference( data );
    data.sourceId = getSingle( data.sourceId )
if ( mw.ustring.len( rendered.text ) == 0 ) then
    populateDataFromEntity( data.sourceId, data, PROPERTY_MAP )
return nil;
end


else
    expandSpecials( data )
rendered = renderSource( context, data );
    populateSourceDataImpl( data.sourceId, data, PROPERTY_MAP )
if ( mw.ustring.len( rendered.text ) == 0 ) then
return nil;
end


if ( refAnchor ) then
    expandPublication( data )
local anchorValue = 'CITEREF' .. refAnchor;
    expandBookSeries( data )
if ( data.year ) then
anchorValue = anchorValue .. data.year;
end
rendered.text = '<span class="citation" id="' .. mw.uri.anchorEncode( anchorValue ) .. '">' .. rendered.text .. '</span>';
end
end


return rendered;
    if next( data ) == nil then
end
        return nil
    end


function getNormativeTitle( entity )
    local rendered = renderSource( data )
if ( not entity or not entity.claims or not entity.claims.P31 ) then
    if mw.ustring.len( rendered ) == 0 then
return;
        return nil
end
    end


for _, claim in pairs( entity.claims.P31 ) do
    if data.ref then
if ( claim and claim.mainsnak and claim.mainsnak.datavalue and claim.mainsnak.datavalue.value and claim.mainsnak.datavalue.value["numeric-id"] ) then
        local anchorValue = 'CITEREF' .. data.ref .. ( coalesce( { data[ 'ref-year' ], data.year } ) or '' )
local classId = 'Q' .. claim.mainsnak.datavalue.value["numeric-id"];
        rendered = '<span class="citation" id="' .. mw.uri.anchorEncode( anchorValue ) .. '">' .. rendered .. '</span>'
local title = NORMATIVE_DOCUMENTS[ classId ];
    end
if ( title ) then
return title;
end
end
end


return;
    return rendered
end
end


local LANG_CACHE = {
---@param frame frame
Q150 = 'fr',
---@param currentEntityId string | { id: string }
Q188 = 'de',
---@param reference table{ snaks: snaks }
Q1321 = 'es',
---@return string | nil
Q1860 = 'en',
function p.renderSource( frame, currentEntityId, reference )
Q652 = 'it',
    reference = reference or { snaks = {} }
Q7737 = 'ru',
    p.currentFrame = frame
}


function getLangCode( langEntityId )
    local data = getFilledArgs( frame.args or {} )
if ( not langEntityId ) then
    populateDataFromSnaks( reference.snaks, data, PROPERTY_MAP )
return;
    data.sourceId = getSingle( data.sourceId )
end


-- small optimization
    if not currentEntityId then
local cached = LANG_CACHE[ langEntityId ];
    data.entityId = mw.wikibase.getEntityIdForCurrentPage()
if ( cached ) then return cached; end
    elseif type( currentEntityId ) == 'string' then
        data.entityId = currentEntityId
    elseif type( currentEntityId ) == 'table' and currentEntityId.id then
        data.entityId = currentEntityId.id
    end


local langEntity = mw.wikibase.getEntity( langEntityId );
    ---@type string
if ( not langEntity ) then
    local rendered = renderReferenceImpl( data, reference.snaks or {} )
mw.log( '[getLangCode] Missing entity ' .. langEntityId );
    if not rendered then
else
        return ''
if ( langEntity.claims and langEntity.claims.P424 ) then
    end
for _, claim in pairs( langEntity.claims.P424 ) do
if ( claim
and claim.mainsnak
and claim.mainsnak.datavalue
and claim.mainsnak.datavalue.value ) then
return '' .. claim.mainsnak.datavalue.value;
end
end
end
end


return;
    return rendered
end
end


function preprocessPlaces( data, lang )
if ( not data.place ) then
return;
end;


local newPlaces = {};
---@param frame frame
for index, place in pairs( data.place ) do
---@param currentEntityId string
if ( place.id ) then
---@param reference table
local newPlaceStr = getPlaceName(lang, place.id)
---@return string
if ( newPlaceStr ) then
function p.renderReference( frame, currentEntityId, reference )
newPlaces[index] = newPlaceStr;
    local rendered = p.renderSource( frame, currentEntityId, reference )
else
    if not rendered or rendered == '' then
newPlaces[index] = place;
        return ''
end
    end
else
 
newPlaces[index] = place;
    -- Про выбор алгоритма хеширования см. [[Модуль:Hash]]. Знак подчёркивания в начале позволяет
end
    -- исключить ошибку, когда имя сноски — чисто числовое значение, каковыми иногда бывают хеши.
end
    return frame:extensionTag( 'ref', rendered, { name = '_' .. mw.hash.hashValue( 'fnv164', rendered ) } ) .. '[[Category:ЭАНМ:Статьи с источниками из Викиданных]]'
data.place = newPlaces;
end
end


function getPlaceName( lang, placeId )
---@param frame frame
-- ГОСТ Р 7.0.12—2011
---@return string | nil
if ( lang == 'ru' ) then
function p.testPersonNameToAuthorName( frame )
if ( placeId == 'Q649' ) then return toTextWithTip('М.', 'Москва'); end
    return personNameToAuthorName( frame.args[ 1 ] )
if ( placeId == 'Q656' ) then return toTextWithTip('СПб.', 'Санкт-Петербург'); end
if ( placeId == 'Q891' ) then return toTextWithTip('Н. Новгород', 'Нижний Новгород'); end
if ( placeId == 'Q908' ) then return toTextWithTip('Ростов н/Д.', 'Ростов-на-Дону'); end
end
return nil;
end
end


function toTextWithTip( text, tip )
---@param frame frame
return '<span title="' .. tip .. '" style="border-bottom: 1px dotted; cursor: help; white-space: nowrap">' .. text .. '</span>';
---@return string | nil
function p.testPersonNameToResponsibleName( frame )
    return personNameToResponsibleName( frame.args[ 1 ] )
end
end


return p;
return p