Модуль:Sources
Для документации этого модуля может быть создана страница Модуль:Sources/doc
---@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',
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 = {
fr = '',
de = 'Hrsg.: ',
es = '',
en = '',
it = '',
ru = 'под ред. ',
uk = 'за ред. ',
}
---@type table<string, string>
local i18nTranslators = {
fr = '',
de = '',
es = '',
en = '',
it = '',
ru = 'пер. ',
uk = 'пер. ',
}
---@type table<string, string>
local i18nVolume = {
de = 'Vol.',
fr = 'Vol.',
es = 'Vol.',
en = 'Vol.',
it = 'Vol.',
ru = 'Т.',
uk = 'Т.',
}
---@type table<string, string>
local i18nIssue = {
en = 'Iss.',
ru = 'вып.',
uk = 'вип.',
}
---@type table<string, string>
local i18nPages = {
fr = 'P.',
de = 'S.',
es = 'P.',
en = 'P.',
it = 'P.',
ru = 'С.',
uk = 'С.',
}
---@type table<string, string>
local i18nNumberOfPages = {
en = 'p.',
ru = 'с.',
}
---@type table<string, string>
local i18nTirage = {
en = 'ed. size: %d',
ru = '%d экз.',
}
---@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 '[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
---@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 '[[' .. url .. '|' .. 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, 'Категория:' ) 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 '<span title="' .. tip .. '" style="border-bottom: 1px dotted; cursor: help; white-space: nowrap">' .. text .. '</span>'
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 = '<i class="wef_low_priority_links">' .. result .. '</i> '
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 '<span class="wikidata_cite ' .. citeType .. '" data-entity-id="' .. data.sourceId .. '">' .. result .. '</span>'
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 " <small>Проверено" .. date_str .. ".</small>"
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 .. '<span class="wef_low_priority_links">'
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 .. '</span>'
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 = '<span class="citation" id="' .. mw.uri.anchorEncode( anchorValue ) .. '">' .. rendered .. '</span>'
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 not currentEntityId then
data.entityId = mw.wikibase.getEntityIdForCurrentPage()
elseif 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 ) } ) .. '[[Category:Википеди:ВикиДатăри çăлкуçлă статьясем]]'
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