Siirry sisältöön

Moduli:Sources

On otettu Мегавики-späi

---@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[] }[]>

---@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',
   Q36584 = 'olo',

}

---@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	= ' и др.',
   olo	= ' da muut',

}

---@type table<string, string> local i18nEditors = {

   fr	= ,
   de	= 'Hrsg.: ',
   es	= ,
   en	= ,
   it	= ,
   ru	= 'под ред. ',
   olo	= 'toim ',

}

---@type table<string, string> local i18nTranslators = {

   fr	= ,
   de	= ,
   es	= ,
   en	= ,
   it	= ,
   ru	= 'пер. ',
   olo	= 'kiän. ',

}

---@type table<string, string> local i18nVolume = {

   de  = 'Vol.',
   fr	= 'Vol.',
   es	= 'Vol.',
   en	= 'Vol.',
   it	= 'Vol.',
   ru	= 'Т.',
   olo	= 'Oza.',

}

---@type table<string, string> local i18nIssue = {

   en	= 'Iss.',
   ru	= 'вып.',
   olo	= 'piäst.',

}

---@type table<string, string> local i18nPages = {

   fr = 'P.',
   de = 'S.',
   es = 'P.',
   en = 'P.',
   it = 'P.',
   ru = 'С.',
   olo = 'S.',

}

---@type table<string, string> local i18nNumberOfPages = {

   en = 'p.',
   ru = 'с.',
   olo = 's.',

}

---@type table<string, string> local i18nTirage = {

   en	= 'ed. size: %d',
   ru	= '%d экз.',
   olo= '%d kappal.'

}

---@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 nm, nm2, srn, srn2, pref
   fullName = ' ' .. fullName .. ' '
   fullName = mw.ustring.gsub( fullName, ' оглы ', ' ' )
   fullName = mw.text.trim( fullName )
   -- Surname, Name
   local pattern = '^' .. surname .. ',' .. space .. name .. '$'
   srn, nm = mw.ustring.match( fullName, pattern )
   if srn then
       return {
           srn,
           mw.ustring.sub( nm, 1, 1 ) .. '.'
       }
   end
   -- Surname, Name prefix
   for _, surnamePrefix in pairs( surnamePrefixes ) do
       pattern = '^' .. surname .. ',' .. space .. name .. space .. '(' .. surnamePrefix .. ')' .. '$'
       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 ) .. '. ' .. 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 .. ' ' .. 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 ) .. '. ' .. 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 ) .. '. ' .. 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 ) .. '. ' .. 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, '. ', 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, '. ', 2, k + 1 ) .. '.'
           }
       end
   end
   return { fullName }

end

---@param fullName string | nil ---@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 ] .. ' ' .. tokenized[ 2 ]

end

---@param fullName string | nil ---@return string | nil local function personNameToResponsibleName( fullName )

   if not fullName then
       return nil
   end
   local tokenized = tokenizeName( fullName )
   if #tokenized == 1 then
       return tokenized[ 1 ]
   end
   return tokenized[ 2 ] .. ' ' .. tokenized[ 1 ]

end

---@alias options { separator: string, conjunction: string, format: ( fun( data: string ): string ), nolinks: boolean, preferids: boolean, short: boolean }

---@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

---@type options local options_commas_it_short = mw.clone( options_commas_short ) options_commas_it_short.format = function( data ) return "" .. data .. "" end

---@type options local options_commas_nolinks = mw.clone( options_commas ) options_commas_nolinks.nolinks = true

---@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 '.. id .. ' arXiv:' .. id .. '' end

---@type options local options_doi = mw.clone( options_ids ) options_doi.format = function( doi ) return '.. doi .. ' doi:' .. doi .. '' end

---@type options local options_issn = mw.clone( options_ids ) options_issn.format = function( issn ) return '.. issn .. ' ' .. issn .. '' end

---@type options local options_pmid = mw.clone( options_ids ) options_pmid.format = function( pmid ) return '.. pmid .. ' PMID:' .. pmid .. '' end

---@param str string | nil ---@return boolean local function isEmpty( str )

   return not str or #str == 0

end

---@param allQualifiers snaks ---@param qualifierPropertyId string ---@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 ]
   for _, qualifier in pairs( propertyQualifiers ) do
       if ( qualifier
               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

---@param data table ---@param resultProperty string ---@return void local function appendImpl_toTable( data, resultProperty )

   if not data[ resultProperty ] then
       data[ resultProperty ] = {}
   elseif ( type( data[ resultProperty ] ) == 'string' or ( type( data[ resultProperty ] ) == 'table' and type( data[ resultProperty ].id ) == 'string' ) ) then
       data[ resultProperty ] = { data[ resultProperty ] }
   end

end

---@param datavalue table ---@param qualifiers snaks ---@param data table ---@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
       if not value then
           value = datavalue.value
           if options.format then
               value = options.format( value )
           end
       end
       appendImpl_toTable(data, propertyName)
       local pos = getSingleStringQualifierValue( qualifiers, 'P1545' )
       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

---@param entityId string ---@param propertyId string ---@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
   return {}

end

---@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
   return {}

end

---@param entityId string ---@param projectToCheck string? ---@return string | nil local function getSitelink( entityId, projectToCheck )

   ---@type boolean, string
   local wbStatus, sitelink
   if projectToCheck then
       wbStatus, sitelink = pcall( mw.wikibase.getSitelink, entityId, projectToCheck )
   else
       wbStatus, sitelink = pcall( mw.wikibase.getSitelink, entityId )
   end
   if not wbStatus then
       return nil
   end
   return sitelink

end

---@param args any[] ---@return any | nil local function coalesce( args )

   for _, arg in pairs( args ) do
       if not isEmpty( arg ) then return arg end
   end
   return nil

end

---@param value any ---@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
       for _, tableValue in pairs( value ) do
           return getSingle( tableValue )
       end
   end
   return nil

end

---@param langEntityId string ---@return string | nil local function getLangCode( langEntityId )

   if not langEntityId then
       return nil
   end
   langEntityId = getSingle( langEntityId )
   if not string.match( langEntityId, '^Q%d+$' ) then
       return langEntityId
   end
   local cached = LANG_CACHE[ langEntityId ]
   if cached then
       if cached ==  then
           return nil
       end
       return cached
   end
   local claims = getBestStatements( langEntityId, 'P424' )
   for _, claim in pairs( claims ) do
       if claim
               and claim.mainsnak
               and claim.mainsnak.datavalue
               and claim.mainsnak.datavalue.value
       then
           LANG_CACHE[ langEntityId ] = claim.mainsnak.datavalue.value
           return claim.mainsnak.datavalue.value
       end
   end
   LANG_CACHE[ langEntityId ] = 
   return nil

end

---@param entityId string ---@param propertyId string ---@param data source ---@param propertyName string ---@param options table? ---@return void local function appendEntitySnaks( entityId, propertyId, data, propertyName, options )

   options = options or {}
   -- do not populate twice
   if data[ propertyName ] and ( propertyName ~= 'author' or data[ propertyId ] ) then
       return
   end
   local statements = getBestStatements( entityId, propertyId )
   if propertyName == 'author' then
       data[ propertyId ] = true
   end
   local lang = getLangCode( data.lang ) or i18nDefaultLanguage
   if propertyId == 'P1680' then -- if there is a default language
       for _, statement in pairs( statements ) do
           if statement and
                   statement.mainsnak and
                   statement.mainsnak.datavalue and
                   statement.mainsnak.datavalue.value and
                   statement.mainsnak.datavalue.value.language == lang
           then
               --found default language string
               appendImpl( statement.mainsnak.datavalue, statement.qualifiers, data, propertyName, options )
               return
           end
       end
   end
   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

---@param claims table<number, statement> ---@param qualifierPropertyId string ---@param result table ---@param resultPropertyId string ---@param options table ---@return void 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

---@param entityId string ---@param propertyId string ---@param value any ---@return table<number, statement> local function findClaimsByValue( entityId, propertyId, value )

   local result = {}
   local claims = getAllStatements( entityId, propertyId )
   for _, claim in pairs( claims ) do
       if ( claim.mainsnak and claim.mainsnak.datavalue ) then
           local datavalue = claim.mainsnak.datavalue
           if ( datavalue.type == "string" and datavalue.value == value ) or
                   ( datavalue.type == "wikibase-entityid" and
                           datavalue.value[ "entity-type" ] == "item" and
                           tostring( datavalue.value.id ) == value )
           then
               table.insert( result, claim )
           end
       end
   end
   return result

end

---@param entityId string ---@param typeEntityId string ---@return boolean local function isInstanceOf( entityId, typeEntityId )

   return findClaimsByValue( entityId, 'P31', typeEntityId )[ 1 ] ~= nil

end

---@param entityId string ---@param typeEntityIds string[] ---@return string ---@todo Rewrite local function getFirstType( entityId, typeEntityIds )

   for _, typeEntityId in pairs( typeEntityIds ) do
       if isInstanceOf( entityId, typeEntityId ) then
           return typeEntityId
       end
   end
   return nil

end

---@param snaks snaks ---@param data source ---@param map map ---@return void local function populateDataFromSnaks( snaks, data, map )

   for _, row in ipairs( map ) do
       local parameterName, propertyIds = row.name, row.ids
       for _, propertyId in pairs( propertyIds ) do
           if not data[ parameterName ] and snaks[ propertyId ] then
               local options = {}
               if propertyId == 'P888' then
                   options = { format = function( id ) return 'http://www.jstor.org/stable/' .. id end }
               end
               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

---@param entityId string | nil ---@param data source ---@param map map ---@return void local function populateDataFromEntity( entityId, data, map )

   if not data.title then
       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
   local bookSeriesStatements = getBestStatements( entityId, 'P361' )
   for _, statement in pairs( bookSeriesStatements ) do
       if statement and
               statement.mainsnak and
               statement.mainsnak.datavalue and
               statement.mainsnak.datavalue.value and
               statement.mainsnak.datavalue.value.id
       then
           local possibleBookSeriesEntityId = statement.mainsnak.datavalue.value.id
           if isInstanceOf( possibleBookSeriesEntityId, 'Q277759' ) then
               appendImpl_toTable( data, 'bookSeries' )
               table.insert( data.bookSeries, { id = possibleBookSeriesEntityId } )
               appendQualifiers( { statement }, 'P478', data, 'bookSeriesVolume', {} )
               appendQualifiers( { statement }, 'P433', data, 'bookSeriesIssue', {} )
           end
       end
   end
   for _, row in ipairs( map ) do
       local parameterName, propertyIds = row.name, row.ids
       for _, propertyId in pairs( propertyIds ) do
           local options = {}
           if propertyId == 'P888' then
               options = { format = function( id ) return 'http://www.jstor.org/stable/' .. id end }
           end
           appendEntitySnaks( entityId, propertyId, data, parameterName, options )
       end
   end

end

---@param data source ---@return void local function expandPublication( data )

   if not data[ 'publication-id' ] then
       return
   end
   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 data[ 'publication-qualifiers' ] then
       populateDataFromSnaks( data[ 'publication-qualifiers' ], data.publication, PUBLICATION_PROPERTY_MAP )
   end
   populateDataFromEntity( publicationId, data.publication, PUBLICATION_PROPERTY_MAP )
   if type( data.publication.title ) == 'table' and data.publication.title[ 1 ] then
       data.publication.title = data.publication.title[ 1 ]
   end
   if type( data.publication.subtitle ) == 'table' and data.publication.subtitle[ 1 ] then
       data.publication.subtitle = data.publication.subtitle[ 1 ]
   end
   for key, value in pairs( data.publication ) do
       if key ~= 'sourceId' and key ~= 'title' and key ~= 'subtitle' and key ~= 'url' and not data[ key ] then
           data[ key ] = value
       end
   end

end

---@param data source ---@return void local function expandBookSeries( data )

   local bookSeries = data.bookSeries
   if not bookSeries then
       return
   end
   -- use only first one
   if type( bookSeries ) == 'table' and bookSeries[ 1 ] and bookSeries[ 1 ].id then
       data.bookSeries = bookSeries[ 1 ]
       bookSeries = data.bookSeries
   end
   if not bookSeries or not bookSeries.id then
       return
   end
   appendEntitySnaks( bookSeries.id, 'P123', data, 'publisher', {} )
   appendEntitySnaks( bookSeries.id, 'P291', data, 'place', {} )
   appendEntitySnaks( bookSeries.id, 'P236', data, 'issn', {} )

end

---@param entityId string ---@return string | nil local function getNormativeTitle( entityId )

   local possibleTypeIds = {}
   for typeId, _ in pairs( NORMATIVE_DOCUMENTS ) do
       table.insert( possibleTypeIds, typeId )
   end
   local foundTypeId = getFirstType( entityId, possibleTypeIds )
   if foundTypeId then
       return NORMATIVE_DOCUMENTS[ foundTypeId ]
   end
   return nil

end

---@param urls table<number, string> | string ---@param text string ---@return string local function wrapInUrl( urls, text )

   local url = getSingle( urls )
   if string.sub( url, 1, 1 ) == ':' then
       return '' .. text .. ''
   else
       return '[' .. url .. ' ' .. text .. ']'
   end

end

---@param entityId string ---@param lang string ---@return string local function getElementLink( entityId, lang )

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

end

---@param entityId string ---@param lang string ---@return string local function getLabel( entityId, lang )

   local wbStatus, label = pcall( mw.wikibase.getLabelByLang, entityId, lang )
   if not wbStatus then
       return 
   end
   if label and label ~=  then
       return label
   end
   wbStatus, label = pcall( mw.wikibase.getLabel, entityId )
   if not wbStatus then
       return 
   end
   return label or 

end

---@param lang string ---@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
   -- person name P1559
   -- labels
   if isEmpty( title ) then
       title = getLabel( entityId, lang )
       -- mw.log( 'Got title of ' .. entityId .. ' from label: «' .. title .. '» (' .. lang .. ')' )
   end
   local actualText = title or '\'\'(untranslated)\'\
   local link = getElementLink( entityId, lang )
   return wrapInUrl( link, actualText )

end

---@param lang string ---@param value value ---@param options options ---@return string 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
   if value.id then
       -- this is link
       if type( value.label or  ) ~= 'string' then
           mw.logObject( value, 'error value' )
           error( 'label of table value is not string but ' .. type( value.label ) )
       end
       local title
       if options.preferids then
           title = value.id
       elseif options.nolinks then
           title = value.label or getLabel( value.id, lang )
       else
           title = renderLink( lang, value.id, value.label, options )
       end
       if title ==  then
           title = "(untranslated title)"
       end
       return options.format( title )
   end
   local resultList = {}
   for _, tableValue in pairs( value ) do
       table.insert( resultList, asString( lang, tableValue, options ) )
   end
   return mw.text.listToText( resultList, options.separator, options.conjunction )

end

---@param entityId string ---@param data source ---@return source local function populateSourceDataImpl( entityId, data, map )

   local wsLink = getSitelink( entityId, 'ruwikisource' )
   if wsLink and not mw.ustring.gmatch( wsLink, 'Category:' ) then
       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 ..
                   " от " .. tostring( d ) .. " " .. monthGen[ m ]  .. " " .. tostring( y ) .. " г." ..
                   ( docNumber and ( " № " .. docNumber ) or  ) ..
                   ' «' .. title.. '»'
       }
   end
   if not data.title then
       local lang = getLangCode( data.lang ) or i18nDefaultLanguage
       local label = getLabel( entityId, lang )
       if label ~=  then
           data.title = { label }
       end
   end
   return data

end

---@param entityId string ---@param propertyId string ---@param data source ---@return void 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

---Expand special types of references when additional data could be found in OTHER entity properties ---@param data source ---@return void local function expandSpecials( data )

   if not data.entityId then
       return
   end
   if data.sourceId == 'Q36578' then
       -- Gemeinsame Normdatei -- specified by P227
       appendEntitySnaks( data.entityId, 'P227', data, 'part', { format = function(gnd ) return 'Record #' .. gnd; end } )
       appendEntitySnaks( data.entityId, 'P227', data, 'url', { format = function(gnd ) return 'http://d-nb.info/gnd/' .. gnd .. '/'; end } )
       data.year = '2012—2016'
       expandSpecialsQualifiers( data.entityId, 'P227', data )
   elseif data.sourceId == 'Q15222191' then
       -- BNF -- specified by P268
       appendEntitySnaks( data.entityId, 'P268', data, 'part', { format = function(id ) return 'Record #' .. id; end } )
       appendEntitySnaks( data.entityId, 'P268', data, 'url', { format = function(id ) return 'http://catalogue.bnf.fr/ark:/12148/cb' .. id; end } )
       expandSpecialsQualifiers( data.entityId, 'P268', data )
   elseif data.sourceId == 'Q54919' then
       -- VIAF -- specified by P214
       appendEntitySnaks( data.entityId, 'P214', data, 'part', { format = function(id ) return 'Record #' .. id; end } )
       appendEntitySnaks( data.entityId, 'P214', data, 'url', { format = function(id ) return 'https://viaf.org/viaf/' .. id; end } )
       expandSpecialsQualifiers( data.entityId, 'P214', data )
   else
       -- generic property search
       for _, sourceClaim in pairs( getBestStatements( data.sourceId, 'P1687' ) ) do
           if sourceClaim.mainsnak.snaktype == 'value' then
               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
   -- do we have appropriate record in P1433 ?
   local claims = findClaimsByValue( currentEntityId, 'P1343', data.sourceId )
   if claims and #claims ~= 0 then
       for _, claim in pairs( claims ) do
           populateDataFromSnaks( claim.qualifiers, data, PROPERTY_MAP )
           populateDataFromEntity( data.sourceId, data, PROPERTY_MAP )
       end
   end

end

---@param text string ---@param tip string ---@return string local function toTextWithTip( text, tip )

   return '' .. text .. ''

end

---@param lang string ---@param placeId string ---@return string local function getPlaceName( placeId, lang )

   -- ГОСТ Р 7.0.12—2011
   if lang == 'ru' then
       if placeId == 'Q649' then return toTextWithTip( 'М.', 'Москва' ); end
       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

---@param data source ---@param lang string ---@return void local function preprocessPlace( data, lang )

   if not data.place then
       return
   end
   ---@type table<number, string>
   local newPlace = {}
   for index, place in pairs( data.place ) do
       if place.id then
           local newPlaceStr = getPlaceName( place.id, lang )
           if newPlaceStr then
               newPlace[ index ] = newPlaceStr
           else
               newPlace[ index ] = getLabel( place.id, lang )
           end
       else
           newPlace[ index ] = place
       end
   end
   data.place = newPlace

end

---@param entityId string ---@param lang string ---@param providedLabel string | nil ---@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
   return options.format( personName )

end

---@param entityId string ---@param lang string ---@param customLabel string | nil ---@param options options ---@return string local function getPersonNameAsWikitext( entityId, lang, customLabel, options )

   local personName = getPersonNameAsLabel( entityId, lang, customLabel, options )
   local link = getElementLink( entityId, lang )
   return wrapInUrl( link, personName )

end

---@param value value ---@param lang string ---@param options options ---@return string local function getPeopleAsWikitext( value, lang, options )

   if type( value ) == 'string' then
       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
       local resultWikitext = 
       for i, wikitext in pairs( resultList ) do
           if i == maxAuthors + 1 then
               resultWikitext = resultWikitext .. ( i18nEtAl[ lang ] or i18nEtAlDefault )
               break
           end
           if i ~= 1 then
               resultWikitext = resultWikitext .. ', '
           end
           resultWikitext = resultWikitext .. wikitext
       end
       return resultWikitext
   end
   return  -- options.format( '(unknown type)' )

end

---@param lang string ---@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 = '' .. result .. ' '
   end
   return result

end

---@param lang string ---@param data source ---@param conjunction string ---@param propertyName string ---@param urlPropertyName string? ---@return string local function appendProperty( lang, data, conjunction, propertyName, urlPropertyName )

   if not data[ propertyName ] then
       return 
   end
   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 not out or out ==  then
       return 
   end
   return conjunction .. out

end

---@param lang string ---@param data source ---@return string local function appendTitle( lang, data )

   local conjunction = 
   local result = 
   if data.part then
       result = result .. appendProperty( lang, data, , 'part', 'parturl' )
       conjunction = ' // '
   end
   return result .. appendProperty( lang, data, conjunction, 'title', 'url' )

end

---@param lang string ---@return string local function appendLanguage( lang )

   if lang == i18nDefaultLanguage then
       return 
   end
   ---@type { getRefHtml: ( fun( lang: string ): string ), list_ref: ( fun( frame: frame ): string ) }
   local langs = require( 'Module:Languages' )
   return langs.list_ref( p.currentFrame:newChild{ args = { lang } } )

end

---@param lang string ---@param data source ---@return string local function appendSubtitle( lang, data )

   return appendProperty( lang, data, ': ', 'subtitle', nil )

end

---@param lang string ---@param data source ---@return string local function appendOriginalTitle( lang, data )

   return appendProperty( lang, data, ' = ', 'originaltitle', nil )

end

---@param lang string ---@param data source ---@return string local function appendPublication( lang, data )

   if not data.publication then
       return 
   end
   local result = ' // ' .. asString( lang, data.publication.title, options_commas_it_short )
   if data.publication.subtitle and data.publication.subtitle ~=  then
       result = result .. ': ' .. asString( lang, data.publication.subtitle, options_commas_it_short )
   end
   return result

end

---@param lang string ---@param data source ---@return string local function appendEditor( lang, data )

   if not data.editor and not data.translator then
       return 
   end
   local result = ' / '
   if data.editor then
       local prefix = i18nEditors[ lang ] or i18nEditors[ i18nDefaultLanguage ]
       result = result .. prefix .. getPeopleAsWikitext( data.editor, lang, options_commas_responsible )
       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
   return result

end

---@param lang string ---@param data source local function appendEdition( lang, data )

   return appendProperty( lang, data, ' — ', 'edition', nil )

end

---@param lang string ---@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 result = ' — '
   if data.place then
       result = result .. asString( lang, data.place, options_commas_short )
       if data.publisher or data.year then
           result = result .. ': '
       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 .. '.'
   return result

end

---@param lang string ---@param data source ---@return string local function appendVolumeAndIssue( lang, data )

   if not data.volume and not data.issue then
       return 
   end
   local result = ' — '
   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 .. ' ', 'volume', nil )
       result = result ..appendProperty( lang, data, ', ' .. letter_iss .. ' ', 'issue', nil )
   else
       result = result .. appendProperty( lang, data, letter_iss .. ' ', 'issue', nil )
   end
   result = result .. '.'
   return result

end

---@param lang string ---@param data source ---@return string local function appendPages( lang, data )

   if not data.pages then
       return 
   end
   local letter = i18nPages[ lang ] or i18nPages[ i18nDefaultLanguage ]
   local strPages = asString( lang, data.pages, options_commas )
   strPages = mw.ustring.gsub( strPages, '[-—]', '—' )
   return ' — ' .. letter .. ' ' .. strPages .. '.'

end

---@param lang string ---@param data source ---@return string local function appendNumberOfPages( lang, data )

   if not data.numberOfPages then
       return 
   end
   local letter = i18nNumberOfPages[ lang ] or i18nNumberOfPages[ i18nDefaultLanguage ]
   return appendProperty( lang, data, ' — ', 'numberOfPages', nil ) .. ' ' .. letter

end

---@param lang string ---@param data source ---@return string local function appendBookSeries( lang, data )

   if not data.bookSeries then
       return 
   end
   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 .. ' ', 'bookSeriesVolume', nil )
           result = result .. appendProperty( lang, data, ', ' .. letter_iss .. ' ', 'bookSeriesIssue', nil )
       else
           result = result .. appendProperty( lang, data, letter_iss .. ' ', 'bookSeriesIssue', nil )
       end
   end
   result = result .. ')'
   return result

end

---@param lang string ---@param data source ---@return string local function appendTirage( lang, data )

   if not data.tirage then
       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

---@param lang string ---@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

---@param result string ---@param lang string ---@param data source ---@return string local function wrapSourceId( result, lang, data )

   if not data.sourceId then
       return result
   end
   local citeType = data.type and asString( lang, data.type, options_citetypes ) or 'citetype_unknown'
   return '' .. result .. ''

end

---@param data source ---@return string local function appendAccessDate( data )

   if not data.accessdate then
       return 
   end
   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  )


   return " Проверено" .. date_str .. "."

end

---@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

---@param data source ---@return void 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

---@param data source ---@return void local function populateTitle( data )

   data.title = data.title or getSingle( data.url )

end

---@param data source ---@return string local function renderSource( data )

   local lang = getLangCode( data.lang ) or i18nDefaultLanguage
   preprocessPlace( data, lang )
   populateUrl( data, lang )
   populateTitle( data )
   if not data.title then
       return 
   end
   populateYear( data )
   local result = generateAuthorLinks( lang, 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 )
   result = result .. ''
   result = result .. appendEditor( lang, data ) -- Might take current editor instead of actual. Use with caution
   result = result .. appendEdition( lang, data )
   result = result .. appendPublicationData( lang, data )
   result = result .. appendVolumeAndIssue( lang, data )
   result = result .. appendPages( lang, data )
   result = result .. appendNumberOfPages( lang, data )
   result = result .. appendBookSeries( lang, data )
   result = result .. appendTirage( lang, data )
   result = result .. appendIdentifier( lang, data.isbn, options_commas, 'ISBN ' )
   result = result .. appendIdentifier( lang, data.issn, options_issn, 'ISSN ' )
   result = result .. appendIdentifier( lang, data.doi, options_doi, nil )
   result = result .. appendIdentifier( lang, data.pmid, options_pmid, nil )
   result = result .. appendIdentifier( lang, data.arxiv, options_arxiv, nil )
   result = result .. appendAccessDate( data )
   result = result .. ''
   return wrapSourceId( result, lang, data )

end

---@param data source Данные в простом формате, согласованном с модулями формирования библиографического описания ---@param snaks snaks ---@return string | nil local function renderReferenceImpl( data, snaks )

   -- не показывать источники с "импортировано из"
   if snaks.P143 then
       return nil
   end
   -- забрать данные из reference
   populateDataFromSnaks( snaks or {}, data, PROPERTY_MAP )
   data.sourceId = getSingle( data.sourceId )
   populateDataFromEntity( data.sourceId, data, PROPERTY_MAP )
   expandSpecials( data )
   populateSourceDataImpl( data.sourceId, data, PROPERTY_MAP )
   expandPublication( data )
   expandBookSeries( data )
   if next( data ) == nil then
       return nil
   end
   local rendered = renderSource( data )
   if mw.ustring.len( rendered ) == 0 then
       return nil
   end
   if data.ref then
       local anchorValue = 'CITEREF' .. data.ref .. ( coalesce( { data[ 'ref-year' ], data.year } ) or  )
       rendered = '' .. rendered .. ''
   end
   return rendered

end

---@param frame frame ---@param currentEntityId string | { id: string } ---@param reference table{ snaks: snaks } ---@return string | nil function p.renderSource( frame, currentEntityId, reference )

   reference = reference or { snaks = {} }
   p.currentFrame = frame
   local data = getFilledArgs( frame.args or {} )
   populateDataFromSnaks( reference.snaks, data, PROPERTY_MAP )
   data.sourceId = getSingle( data.sourceId )
   if type( currentEntityId ) == 'string' then
       data.entityId = currentEntityId
   elseif type( currentEntityId ) == 'table' and currentEntityId.id then
       data.entityId = currentEntityId.id
   end
   ---@type string
   local rendered = renderReferenceImpl( data, reference.snaks or {} )
   if not rendered then
       return 
   end
   return rendered

end


---@param frame frame ---@param currentEntityId string ---@param reference table ---@return string function p.renderReference( frame, currentEntityId, reference )

   local rendered = p.renderSource( frame, currentEntityId, reference )
   if not rendered or rendered ==  then
       return 
   end
   -- Про выбор алгоритма хеширования см. Модуль:Hash. Знак подчёркивания в начале позволяет
   -- исключить ошибку, когда имя сноски — чисто числовое значение, каковыми иногда бывают хеши.
   return frame:extensionTag( 'ref', rendered, { name = '_' .. mw.hash.hashValue( 'fnv164', rendered ) } ) .. 

end

---@param frame frame ---@return string | nil function p.testPersonNameToAuthorName( frame )

   return personNameToAuthorName( frame.args[ 1 ] )

end

---@param frame frame ---@return string | nil function p.testPersonNameToResponsibleName( frame )

   return personNameToResponsibleName( frame.args[ 1 ] )

end

return p