Məzmuna keç

Modul:Sources

Sənədləşdirmə

Ümumi məlumatlar

Bu moduldan Vikidata istinadlarını formatlaşdırmaq üçün istifadə olunur.

Alt modullar:

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 .. '&nbsp;', 'volume')
			result = appendProperty(result, context, src, ', ' .. letter_iss .. '&nbsp;', 'issue')
		else
			result = appendProperty(result, context, src, letter_iss .. '&nbsp;', '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 .. '&nbsp;' .. 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') .. '&nbsp;' .. 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 .. '&nbsp;', 'bookSeriesVolume')
				result = appendProperty(result, context, src, ', ' .. letter_iss .. '&nbsp;', 'bookSeriesIssue')
			else
				result = appendProperty(result, context, src, letter_iss .. '&nbsp;', '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;