Modul:Sources
Görünüş
Ümumi məlumatlar
Bu moduldan Vikidata istinadlarını formatlaşdırmaq üçün istifadə olunur.
Alt modullar:
- Modul:Sources/utils (köməkçi funksiyalar üçün)
- Modul:Sources/i18n (dil tənzimləmələri üçün)
- Modul:Sources/config (konfiqurasiya üçün)
local p = {}
local utils = require('Modul:Sources/utils')
local sourceConfig = require('Modul:Sources/config')
local i18n = mw.loadData('Modul:Sources/i18n')
local Ordinal = require('Modul:Ordinal')
local i18nDefaultLanguage = i18n.defaultLanguage
local i18nEtAlDefault = i18n.etAlDefault
local i18nEtAl = i18n.etAl
local i18nEditors = i18n.editors
local i18nTranslators = i18n.translators
local i18nVolume = i18n.volume
local i18nIssue = i18n.issue
local i18nPages = i18n.pages
local i18nNumberOfPages = i18n.numberOfPages
local i18nTirage = i18n.tirage
local monthg = i18n.months
local options_commas = { separator = ', ', conjunction = ', ', format = function( src ) return src end, nolinks = false, preferids = false };
local options_commas_nolinks = { separator = ', ', conjunction = ', ', format = function( src ) return src end, nolinks = true, preferids = false };
local options_citetypes = { separator = ' ', conjunction = ' ', format = function( src ) return 'citetype_' .. src end, nolinks = true , preferids = true };
local function capitalizeFirstCharacter(value)
if type(value) ~= 'string' or value == '' then
return value
end
return mw.ustring.upper(mw.ustring.sub(value, 1, 1)) .. mw.ustring.sub(value, 2)
end
local options_commas_authors = { format = capitalizeFirstCharacter, excludedIds = sourceConfig.ignoredAuthors or {} };
local options_commas_responsible = { format = function( src ) return src end };
local options_arxiv = { separator = '; ', conjunction = '; ', format = function( id ) return '[http://arxiv.org/abs/' .. id .. ' arXiv:' .. id .. ']' end, nolinks = true, preferids = false };
local options_doi = { separator = '; ', conjunction = '; ', format = function( doi ) return '[https://doi.org/' .. doi .. ' DOI]' end, nolinks = true, preferids = false };
local options_issn = { separator = '; ', conjunction = '; ', format = function( issn ) return '[https://www.worldcat.org/issn/' .. issn .. ' ' .. issn .. ']' end, nolinks = true, preferids = false };
local options_pmid = { separator = '; ', conjunction = '; ', format = function( pmid ) return '[https://www.ncbi.nlm.nih.gov/pubmed/?term=' .. pmid .. ' PMID:' .. pmid .. ']' end, nolinks = true, preferids = false };
local function hasTerminalPunctuation(value)
local ending = mw.text.trim(value or '')
local previous
repeat
previous = ending
ending = mw.ustring.gsub(ending, '%s*<[^<>]+>%s*$', '')
until ending == previous
return mw.ustring.match(ending, '[%.%!%?]$') ~= nil or
mw.ustring.match(ending, '[%.%!%?]%]+$') ~= nil
end
local function getPersonNameAsLabel( context, entityId, providedLabel, options )
if ( not utils.isEmpty( providedLabel ) ) then
mw.log( 'Custom label provided for ' .. entityId );
return options.format( providedLabel );
end
local entity = utils.getEntity( context, entityId );
if ( not entity ) then return '\'\'(entity ' .. entityId .. ' is missing)\'\'' end;
local personName = nil;
local labels = entity.labels or {};
local label = labels[ context.lang ] or labels.en or labels[ i18nDefaultLanguage ];
if label then
personName = label.value;
mw.log('Got person name of ' .. entityId .. ' from label: "' .. personName .. '"' )
end
if ( not utils.isInstanceOf( entity, 'Q5' ) ) then
mw.log( 'Entity ' .. entityId .. ' is not a person' );
return personName;
end
if ( utils.isEmpty( personName ) ) then
return '\'\'(not translated to ' .. context.lang .. ')\'\'';
else
return options.format( personName );
end
end
local function getPersonNameAsWikitext( context, entityId, customLabel, options )
local personName = getPersonNameAsLabel( context, entityId, customLabel, options);
if ( personName == nil ) then
return nil;
end
local link = utils.getElementLink( context, entityId, nil );
return utils.wrapInUrl( link, personName );
end
local function getPeopleAsWikitext( context, value, options )
if type( value ) == 'string' then
return options.format( value )
elseif type( value ) == 'table' then
if value.id then
if options.excludedIds and options.excludedIds[value.id] then
return ''
end
if options.preferids then
return value.id
else
if options.nolinks then
return getPersonNameAsLabel( context, value.id, value.label, options )
else
return getPersonNameAsWikitext( context, value.id, value.label, options )
end
end
end
local maxAuthors = 10
local resultList = {}
for i, tableValue in pairs( value ) do
local nextWikitext = getPeopleAsWikitext( context, tableValue, options )
if not utils.isEmpty( nextWikitext ) then
table.insert( resultList, nextWikitext )
if #resultList == maxAuthors + 1 then
break
end
end
end
local resultWikitext = ''
for i, wikitext in pairs( resultList ) do
if i == maxAuthors + 1 then
resultWikitext = resultWikitext .. ( i18nEtAl[ context.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
local function generateAuthorLinks(context, src)
local result = ''
if src.author then
result = getPeopleAsWikitext( context, src.author, options_commas_authors )
if not utils.isEmpty(result) then
result = '<i class="wef_low_priority_links">' .. result .. '</i>. '
end
end
return result
end
local function appendProperty(result, context, src, conjunctor, property, url)
if src[property] then
if url and src[url] then
result = result .. conjunctor .. utils.wrapInUrl( src[url], utils.toString( context, src[property], options_commas_nolinks ) )
else
result = result .. conjunctor .. utils.toString( context, src[property], options_commas )
end
end
return result
end
local function appendTitle(result, context, src)
local conjunctor = ''
if src.part then
result = appendProperty(result, context, src, '', 'part')
conjunctor = ' // '
end
result = appendProperty(result, context, src, conjunctor, 'title', 'url')
return result
end
local function appendLanguage(result, context)
if context.lang ~= i18nDefaultLanguage then
local success, indicator = pcall(function()
local langs = require('Module:Languages')
return langs.list_ref(p.currentFrame:newChild{ args = {context.lang} })
end)
if success and indicator then
result = result .. indicator
end
end
return result
end
local function appendSubtitle(result, context, src)
result = appendProperty(result, context, src, ': ', 'subtitle')
return hasTerminalPunctuation(result) and result or result .. '.'
end
local function appendPublication(result, context, src)
if src.publication then
result = result .. ' // ' .. utils.toString( context, src.publication, options_commas )
if src.publication.subtitle then
result = result .. ': ' .. utils.toString( context, src.publication.subtitle, options_commas )
end
result = result .. '.'
end
return result
end
local function appendEditor(result, context, src)
if src.editor or src.translator then
result = result .. ' / '
if src.editor then
local prefix = i18nEditors[ context.lang ] or i18nEditors[ i18nDefaultLanguage ]
result = result .. prefix .. getPeopleAsWikitext( context, src.editor, options_commas_responsible )
if src.translator then
result = result .. ', '
end
end
if src.translator then
local prefix = i18nTranslators[ context.lang ] or i18nTranslators[ i18nDefaultLanguage ]
result = result .. prefix .. getPeopleAsWikitext( context, src.translator, options_commas_responsible )
end
result = result .. '.'
end
return result
end
local function appendEdition(result, context, src)
return appendProperty(result, context, src, ' ', 'edition')
end
local function appendPublicationData(result, context, src)
local publicationDate = src.publicationDate or src.year
if src.place then
result = result .. ' ' .. utils.toString( context, src.place, options_commas )
result = result .. (src.publisher and ': ' or '.')
end
if src.publisher then
if not src.place then
result = result .. ' '
end
result = result .. utils.toString( context, src.publisher, options_commas ) .. '.'
end
if publicationDate then
result = result .. ' ' .. utils.toString( context, publicationDate, options_commas ) .. '.'
end
return result
end
local function appendVolumeAndIssue(result, context, src)
if src.volume or src.issue then
result = result .. ' '
local letter_vol = i18nVolume[ context.lang ] or i18nVolume[ i18nDefaultLanguage ]
local letter_iss = i18nIssue[ context.lang ] or i18nIssue[ i18nDefaultLanguage ]
if src.volume then
result = appendProperty(result, context, src, letter_vol .. ' ', 'volume')
result = appendProperty(result, context, src, ', ' .. letter_iss .. ' ', 'issue')
else
result = appendProperty(result, context, src, letter_iss .. ' ', 'issue')
end
result = result .. '.'
end
return result
end
local function appendPages(result, context, src)
if src.pages then
local letter = i18nPages[ context.lang ] or i18nPages[ i18nDefaultLanguage ]
local strPages = utils.toString( context, src.pages, options_commas )
strPages = mw.ustring.gsub( strPages, '[-—]', '–' );
result = result .. ' ' .. letter .. ' ' .. strPages .. '.'
end
return result
end
local function appendNumberOfPages(result, context, src)
if src.numberOfPages then
local letter = i18nNumberOfPages[ context.lang ] or i18nNumberOfPages[ i18nDefaultLanguage ]
result = appendProperty(result, context, src, ' ', 'numberOfPages') .. ' ' .. letter
end
return result
end
local function appendBookSeries(result, context, src)
if src.bookSeries then
result = appendProperty(result, context, src, ' — (', 'bookSeries')
if src.bookSeriesVolume or src.bookSeriesIssue then
result = result .. '; '
local letter_vol = i18nVolume[ context.lang ] or i18nVolume[ i18nDefaultLanguage ]
local letter_iss = i18nIssue[ context.lang ] or i18nIssue[ i18nDefaultLanguage ]
if ( src.bookSeriesVolume ) then
result = appendProperty(result, context, src, letter_vol .. ' ', 'bookSeriesVolume')
result = appendProperty(result, context, src, ', ' .. letter_iss .. ' ', 'bookSeriesIssue')
else
result = appendProperty(result, context, src, letter_iss .. ' ', 'bookSeriesIssue')
end
end
result = result .. ')'
end
return result
end
local function appendTirage(result, context, src)
if src.tirage then
local tirageTemplate = i18nTirage[ context.lang ] or i18nTirage[ i18nDefaultLanguage ]
result = result .. ' ' .. utils.toString( context, src.tirage, { separator = '; ', conjunction = '; ', format = function( data ) return mw.ustring.format(tirageTemplate, data) end } )
end
return result
end
local function appendIdentifiers(result, context, src)
if src.isbn then result = result .. ' ISBN ' .. utils.toString( context, src.isbn, options_commas ) end
if src.issn then result = result .. ' ISSN ' .. utils.toString( context, src.issn, options_issn ) end
if src.doi then result = result .. ' ' .. utils.toString( context, src.doi, options_doi ) end
if src.pmid then result = result .. ' ' .. utils.toString( context, src.pmid, options_pmid ) end
if src.arxiv then result = result .. ' ' .. utils.toString( context, src.arxiv, options_arxiv ) end
return result
end
local function appendSourceId(result, context, src)
if src.sourceId then
local citetype = src.type and utils.toString(context, src.type, options_citetypes) or 'citetype_unknown'
result = '<span class="wikidata_cite ' .. citetype .. '" data-entity-id="' .. utils.getSingle(src.sourceId) .. '">' .. result .. '</span>'
end
return result
end
local function formatSourceDate(value, ordinalYear)
local date = utils.getSingle(value)
if type(date) ~= 'string' then
return nil
end
local y, m, d = mw.ustring.match(date, "(%d+)%-(%d+)%-(%d+)T")
y, m, d = tonumber(y), tonumber(m), tonumber(d)
if not y then
return date
end
local year = ordinalYear and Ordinal._ordinal(y) or tostring(y)
if not m or m == 0 then
return year
end
if not monthg[m] then
return date
end
if not d or d == 0 then
return monthg[m] .. ' ' .. year
end
return tostring(d) .. ' ' .. monthg[m] .. ' ' .. year
end
local function isExternalSourceUrl(value)
local url = utils.getSingle(value)
if type(url) ~= 'string' then
return false
end
url = mw.ustring.lower(mw.text.trim(url))
return mw.ustring.match(url, '^https?://') ~= nil or mw.ustring.sub(url, 1, 2) == '//'
end
local function extractWebsiteDomain(url)
local domain = mw.ustring.match(url, '^https?://([^/%?#]+)') or
mw.ustring.match(url, '^//([^/%?#]+)')
if not domain then
return nil
end
domain = mw.ustring.gsub(domain, '^www%.', '')
return mw.ustring.gsub(domain, ':%d+$', '')
end
local function normalizeWebsiteRule(address)
address = mw.ustring.lower(mw.text.trim(address or ''))
address = mw.ustring.gsub(address, '^https?://', '')
address = mw.ustring.gsub(address, '^//', '')
address = mw.ustring.gsub(address, '^www%.', '')
address = mw.ustring.gsub(address, '[%?#].*$', '')
return mw.ustring.gsub(address, '/+$', '')
end
local sourceWebsiteEntityRules = {}
for entityId, addresses in pairs(sourceConfig.sourceWebsiteEntities or {}) do
for _, address in ipairs(addresses) do
local prefix = normalizeWebsiteRule(address)
if prefix ~= '' then
table.insert(sourceWebsiteEntityRules, {prefix = prefix, entityId = entityId})
end
end
end
table.sort(sourceWebsiteEntityRules, function(a, b)
if #a.prefix == #b.prefix then
return a.prefix < b.prefix
end
return #a.prefix > #b.prefix
end)
local function extractWebsiteAddress(url)
local domain = extractWebsiteDomain(url)
if not domain then
return nil
end
local path = mw.ustring.match(url, '^https?://[^/%?#]+([^%?#]*)') or
mw.ustring.match(url, '^//[^/%?#]+([^%?#]*)') or ''
path = mw.ustring.gsub(path, '/+$', '')
return domain .. path
end
local function findWebsiteEntity(url)
local address = extractWebsiteAddress(url)
if not address then
return nil
end
for _, rule in ipairs(sourceWebsiteEntityRules) do
if mw.ustring.sub(address, 1, #rule.prefix) == rule.prefix then
local boundary = mw.ustring.sub(address, #rule.prefix + 1, #rule.prefix + 1)
if boundary == '' or boundary == '/' then
return rule.entityId
end
end
end
return nil
end
local function appendWebsiteDomain(result, context, src)
if src.hasStatedIn or src.publication or src.publisher or not isExternalSourceUrl(src.url) then
return result
end
local url = mw.ustring.lower(mw.text.trim(utils.getSingle(src.url)))
local domain = extractWebsiteDomain(url)
if domain == 'wikidata-externalid-url.toolforge.org' then
local targetPrefix = mw.ustring.match(url, '[?&]url_prefix=([^&#]+)')
url = targetPrefix and mw.ustring.lower(mw.uri.decode(targetPrefix, 'QUERY')) or ''
domain = extractWebsiteDomain(url)
end
if not domain then
return result
end
local entityId = findWebsiteEntity(url)
if entityId then
local label = mw.wikibase.label(entityId)
return result .. ' ' .. utils.toString(context, {id = entityId, label = label}, options_commas) .. '.'
end
return result .. ' ' .. domain .. '.'
end
local function appendAccessAndArchiveDates(result, src)
local details = {}
if src.archiveurl or src.archivedate then
local archiveDate = formatSourceDate(src.archivedate, true)
local archiveUrl = utils.getSingle(src.archiveurl)
local archiveLabel = archiveUrl and
utils.wrapInUrl(archiveUrl, archiveDate and i18n.archived or i18n.archiveCopy) or
(archiveDate and i18n.archived or i18n.archiveCopy)
local archiveText = archiveDate and
archiveDate .. ' ' .. i18n.inYear .. ' ' .. archiveLabel or archiveLabel
table.insert(details, archiveText)
end
if src.accessdate and (isExternalSourceUrl(src.url) or isExternalSourceUrl(src.archiveurl)) then
local accessDate = formatSourceDate(src.accessdate, false)
if accessDate then
table.insert(details, i18n.accessDate .. ': ' .. accessDate)
end
end
if #details > 0 then
result = result .. ' <small>(' .. table.concat(details, '. ') .. ')</small>.'
end
return result
end
local function ensureTerminalPunctuation(result)
if mw.text.trim(result) == '' or hasTerminalPunctuation(result) then
return result
end
return result .. '.'
end
local function populateUrl(context, src)
if src.sourceId and not src.url then
local entity = utils.getEntity(context, src.sourceId)
if entity and entity.sitelinks and entity.sitelinks[context.lang .. 'wikisource'] then
src.url = ':' .. context.lang .. ':s:' .. entity.sitelinks[context.lang .. 'wikisource'].title
end
end
end
local function populateYear(src)
if src.dateOfPublication then
src.publicationDate = formatSourceDate(src.dateOfPublication, false)
end
if not src.year and src.dateOfPublication then
local date = utils.getSingle(src.dateOfPublication)
src.year = type(date) == 'string' and mw.ustring.match(date, '(%d%d%d%d)') or nil
end
end
local function hasTitleValue(value)
if type(value) == 'string' then
return mw.text.trim(value) ~= ''
elseif type(value) == 'table' then
if type(value.id) == 'string' and mw.text.trim(value.id) ~= '' then
return true
end
for _, item in ipairs(value) do
if hasTitleValue(item) then return true end
end
end
return false
end
local function populateTitle(src)
if hasTitleValue(src.title) then
return true
end
local url = utils.getSingle(src.url)
if type(url) == 'string' and
(isExternalSourceUrl(src.url) or mw.ustring.sub(mw.text.trim(url), 1, 1) == ':') then
src.title = url
return true
end
return false
end
local function renderSource(context, src)
context.lang = utils.getLangCode(utils.getSingle(src.lang)) or src.referenceLanguage or i18nDefaultLanguage
utils.preprocessPlaces(src, context.lang)
populateUrl(context, src)
if not populateTitle(src) then
return ''
end
populateYear(src)
local result = generateAuthorLinks(context, src)
result = appendTitle(result, context, src)
result = appendLanguage(result, context)
result = appendSubtitle(result, context, src)
result = appendPublication(result, context, src)
result = appendWebsiteDomain(result, context, src)
result = result .. '<span class="wef_low_priority_links">'
result = appendEditor(result, context, src)
result = appendEdition(result, context, src)
result = appendPublicationData(result, context, src)
result = appendVolumeAndIssue(result, context, src)
result = appendPages(result, context, src)
result = appendNumberOfPages(result, context, src)
result = appendBookSeries(result, context, src)
result = appendTirage(result, context, src)
result = appendIdentifiers(result, context, src)
result = ensureTerminalPunctuation(result)
result = appendSourceId(result, context, src)
result = appendAccessAndArchiveDates(result, src)
result = result .. '</span>'
return result
end
local function hasReferenceValue(snaks, propertyIds)
for _, propertyId in ipairs(propertyIds) do
for _, snak in ipairs(snaks[propertyId] or {}) do
if snak.datavalue and snak.datavalue.value then
return true
end
end
end
return false
end
function p.isUnnamedReferenceUrl(reference)
local snaks = reference and reference.snaks
if not snaks or sourceConfig.ignoreUnnamedReferenceUrls == false then
return false
end
if not hasReferenceValue(snaks, {'P854'}) then
return false
end
return not hasReferenceValue(snaks, {'P1476', 'P248', 'P1433'})
end
function p.isHelperOnlyReference(reference)
local snaks = reference and reference.snaks
if not snaks then
return false
end
local helperProperties = sourceConfig.referenceHelperProperties or {}
local hasValue = false
for propertyId in pairs(snaks) do
if hasReferenceValue(snaks, {propertyId}) then
hasValue = true
if not helperProperties[propertyId] then
return false
end
end
end
return hasValue
end
local function renderReferenceImpl(currentEntity, reference, refAnchor, refAnchorYear)
if not reference.snaks then
return nil
end
if p.isUnnamedReferenceUrl(reference) or p.isHelperOnlyReference(reference) then
return nil
end
local context = {
cache = {}
}
local data = {}
utils.populateDataFromClaims(context, nil, reference.snaks, data)
utils.expandSpecials(context, currentEntity, reference, data)
if hasReferenceValue(reference.snaks, {'P248'}) then
data.hasStatedIn = true
if data.part and not data.title then
data.title = data.part
data.part = nil
end
end
local sourceEntity = nil
if data.sourceId then
sourceEntity = utils.getEntity(context, data.sourceId)
if sourceEntity then
if data.title and not data.publication then
data.publication = {id = utils.getSingle(data.sourceId)}
end
utils.populateSourceDataImpl(context, sourceEntity, data)
end
end
if data.publication then
utils.expandPublication(context, sourceEntity, data)
end
utils.expandBookSeries(context, data)
if next(data) == nil then
return nil
end
local rendered = renderSource(context, data)
if mw.ustring.len(rendered) == 0 then
return nil
end
if refAnchor then
local anchorValue = 'CITEREF' .. refAnchor .. (utils.coalesce(refAnchorYear, data.year) or '')
rendered = '<span class="citation" id="' .. mw.uri.anchorEncode(anchorValue) .. '">' .. rendered .. '</span>'
end
return rendered
end
local function artificialSnaks(args)
local snaks = {}
if args[1] then
local entityId = mw.text.trim(args[1])
snaks.P248 = {utils.toWikibaseEntityIdSnak("P248", entityId)}
snaks.P805 = {utils.toWikibaseEntityIdSnak("P805", entityId)}
end
utils.copyArgsToSnaks(args, snaks)
return mw.wikibase.getEntity(), {snaks = snaks}
end
local function renderReferenceWithKey(frame, currentEntity, reference)
p.currentFrame = frame
if frame and not currentEntity and not reference then
currentEntity, reference = artificialSnaks(frame.args)
end
local rendered = renderReferenceImpl(currentEntity, reference)
if not rendered then
return '', nil
end
local referenceKey = mw.hash.hashValue('fnv164', rendered)
return frame:extensionTag('ref', rendered, {name = '_' .. referenceKey}) .. '[[Category:Vikipediya:Vikidatada mənbələri olan məqalələr]]', referenceKey
end
function p.renderReferenceWithKey(frame, currentEntity, reference)
return renderReferenceWithKey(frame, currentEntity, reference)
end
function p.renderSource(frame)
p.currentFrame = frame
local currentEntity, reference = artificialSnaks(frame.args)
return renderReferenceImpl(currentEntity, reference, frame.args['ref'], frame.args['ref-year'])
end
return p;