Модуль:Sources
Перейти к навигации
Перейти к поиску
Для документации этого модуля может быть создана страница Модуль:Sources/doc
---@alias args table
---@alias frame { args: args, extensionTag: function, newChild: ( fun( args: args ): frame ) }
local p = {}
local monthGen = { 'января', 'февраля', 'марта', 'апреля', 'мая', 'июня', 'июля', 'августа', 'сентября', 'октября', 'ноября', 'декабря' }
local i18nDefaultLanguage = 'ru'
p.i18nDefaultLanguage = i18nDefaultLanguage
local i18nEtAl = {
ru = ' и др.',
en = ' et al.',
}
local i18nEditors = {
ru = 'под ред. ',
en = 'ed. by ',
}
local i18nTranslators = {
ru = 'пер. ',
en = 'transl. ',
}
local i18nVolume = {
ru = 'Т.',
en = 'Vol.',
}
local i18nIssue = {
ru = 'вып.',
en = 'Iss.',
}
local i18nPages = {
ru = 'С.',
en = 'P.',
}
local i18nNumberOfPages = {
ru = 'с.',
en = 'p.',
}
--- Получение и очистка аргументов
local function getFilledArgs( args )
local data = {}
for key, value in pairs( args ) do
if type(value) == 'string' and mw.text.trim( value ) ~= '' then
data[ key ] = mw.text.trim( value )
end
end
return data
end
--- Форматирование имени автора (разбор на Фамилию и Инициалы)
local function tokenizeName( fullName )
local space = '%s+'
local name = "(%a[%a%-']*)%.?"
local surname = "(%a[%a%-']*)"
local surnamePrefixes = { 'ван', 'van', 'де', 'de' }
local nm, nm2, srn, 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
-- 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
return { fullName }
end
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
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
--- Базовые обертки и добавления свойств
local function wrapInUrl( url, text )
if not url or url == '' then return text end
return '[' .. url .. ' ' .. text .. ']'
end
local function appendProperty( data, conjunction, propertyName, urlPropertyName )
if not data[ propertyName ] or data[ propertyName ] == '' then return '' end
local text = data[ propertyName ]
if urlPropertyName and data[ urlPropertyName ] and data[ urlPropertyName ] ~= '' then
text = wrapInUrl( data[ urlPropertyName ], text )
end
return conjunction .. text
end
local function appendTitle( data )
local conjunction = ''
local result = ''
if data.part then
result = result .. appendProperty( data, '', 'part', 'parturl' )
conjunction = ' // '
end
local title = data.title or 'Без названия'
if data.url then
title = wrapInUrl( data.url, title )
end
return result .. conjunction .. title
end
local function appendLanguage( lang )
if not lang or lang == i18nDefaultLanguage then
return ''
end
return ' <span style="color:#555; font-size:0.9em;">(на ' .. lang .. ' яз.)</span>'
end
local function appendSubtitle( data )
return appendProperty( data, ': ', 'subtitle', nil )
end
local function appendOriginalTitle( data )
return appendProperty( data, ' = ', 'originaltitle', nil )
end
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 .. personNameToResponsibleName( data.editor )
if data.translator then result = result .. ', ' end
end
if data.translator then
local prefix = i18nTranslators[ lang ] or i18nTranslators[ i18nDefaultLanguage ]
result = result .. prefix .. personNameToResponsibleName( data.translator )
end
return result
end
local function appendEdition( data )
return appendProperty( data, ' — ', 'edition', nil )
end
local function appendPublicationData( data )
if not data.place and not data.publisher and not data.year then return '' end
local result = ' — '
if data.place then
result = result .. data.place
if data.publisher or data.year then result = result .. ': ' end
end
if data.publisher then
result = result .. data.publisher
if data.year then result = result .. ', ' end
end
if data.year then
result = result .. data.year
end
result = result .. '.'
return result
end
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 .. letter_vol .. ' ' .. data.volume
if data.issue then result = result .. ', ' .. letter_iss .. ' ' .. data.issue end
else
result = result .. letter_iss .. ' ' .. data.issue
end
result = result .. '.'
return result
end
local function appendPages( lang, data )
if not data.pages then return '' end
local letter = i18nPages[ lang ] or i18nPages[ i18nDefaultLanguage ]
local strPages = mw.ustring.gsub( data.pages, '[-—]', '—' )
return ' — ' .. letter .. ' ' .. strPages .. '.'
end
local function appendNumberOfPages( lang, data )
if not data.numberOfPages then return '' end
local letter = i18nNumberOfPages[ lang ] or i18nNumberOfPages[ i18nDefaultLanguage ]
return ' — ' .. data.numberOfPages .. ' ' .. letter .. '.'
end
local function appendIdentifier( data, key, prefix, urlPrefix )
if not data[key] then return '' end
local value = data[key]
if urlPrefix then
value = wrapInUrl( urlPrefix .. value, value )
end
return ' — ' .. prefix .. value
end
local function appendAccessDate( data )
if not data.accessdate then return '' end
return " <small>Проверено " .. data.accessdate .. ".</small>"
end
local function preprocessPlace( data )
if not data.place then return end
-- Сокращения для крупных городов
if data.place == 'Москва' then
data.place = '<span title="Москва" style="border-bottom: 1px dotted; cursor: help;">М.</span>'
elseif data.place == 'Санкт-Петербург' then
data.place = '<span title="Санкт-Петербург" style="border-bottom: 1px dotted; cursor: help;">СПб.</span>'
end
end
--- Главная функция сборки строки источника
local function renderSource( data )
local lang = data.lang or i18nDefaultLanguage
preprocessPlace( data )
if not data.title and not data.url then return '' end
local result = ''
if data.author then
result = '<i class="wef_low_priority_links">' .. personNameToAuthorName( data.author ) .. '</i> '
end
result = result .. appendTitle( data )
result = result .. appendLanguage( lang )
result = result .. appendSubtitle( data )
result = result .. appendOriginalTitle( data )
result = result .. '<span class="wef_low_priority_links">'
result = result .. appendEditor( lang, data )
result = result .. appendEdition( data )
result = result .. appendPublicationData( data )
result = result .. appendVolumeAndIssue( lang, data )
result = result .. appendPages( lang, data )
result = result .. appendNumberOfPages( lang, data )
result = result .. appendIdentifier( data, 'isbn', 'ISBN ' )
result = result .. appendIdentifier( data, 'issn', 'ISSN ', 'https://www.worldcat.org/issn/' )
result = result .. appendIdentifier( data, 'doi', 'doi:', 'https://dx.doi.org/' )
result = result .. appendIdentifier( data, 'pmid', 'PMID:', 'https://www.ncbi.nlm.nih.gov/pubmed/?term=' )
result = result .. appendIdentifier( data, 'arxiv', 'arXiv:', 'https://arxiv.org/abs/' )
result = result .. appendAccessDate( data )
result = result .. '</span>'
return result
end
--- Точка входа для обычного вывода
function p.renderSource( frame )
p.currentFrame = frame
local data = getFilledArgs( frame.args or {} )
local rendered = renderSource( data )
if not rendered or rendered == '' then return '' end
if data.ref then
local anchorValue = 'CITEREF' .. data.ref .. ( data.year or '' )
rendered = '<span class="citation" id="' .. mw.uri.anchorEncode( anchorValue ) .. '">' .. rendered .. '</span>'
end
return rendered
end
--- Точка входа для генерации тега <ref> (сноски)
function p.renderReference( frame )
local rendered = p.renderSource( frame )
if not rendered or rendered == '' then return '' end
-- Используем стандартный хеш MediaWiki для уникального имени сноски
local hash = mw.hash.hashValue( 'fnv164', rendered )
return frame:extensionTag( 'ref', rendered, { name = '_' .. hash } )
end
return p