Модуль:External links

Материал из Википедии — свободной энциклопедии
Перейти к навигации Перейти к поиску
Документация

Модуль используется в шаблоне {{Внешние ссылки}}. Список внешних ссылок см. на Модуль:External links/data. Примеры использования для тестов см. в Модуль:External links/песочница.

local data = require( 'Module:External links/data' )
-- Localizable part
-- Please, note, that labels to various sites and cataloges are taken from Wikidata (i.e. Wikidata label)

-- Feel free to correct labels and categories here
local categoryTemplateEmpty = 'Википедия:Шаблон «Внешние ссылки» пуст'
local templateLink = 'Внешние ссылки'

-- The language codes that should be always displayed even if they have normal rank and claim with another language and prefferered rank exists
local preferredLanguage = 'Q7737' -- russian

local templateColorName = 'цвет'
-- Some projects have "named" colors, defined by templates
local function colorByTitle( frame, colorTitle )
	local templateName = 'Цвет/' .. colorTitle
	local templateTitle = mw.title.makeTitle( 'Template', templateName )
	if not templateTitle or not templateTitle.exists then
		return false
	end
	return frame:expandTemplate{ title = templateName }
end

-- Non-localizable part (not need to localize )
local moduleNavbox = require( 'Module:Navbox' )
local moduleLanguages -- accessed if necessary

local propertyQualifiers = {
	P553 = {
		P554 = 'url',
	},
	P1343 = {
		P805 = 'iw',
		P248 = 'iw', -- deprecated, fallback for P805
		P953 = 'url',
		P854 = 'url', -- deprecated, fallback for P953
	},
}

local p = {}


-- Helper functions
local function replace( str, pattern, repl )
    pattern = mw.ustring.gsub( pattern, "[%(%)%.%+%-%*%?%[%]%^%$%%]", "%%%1" ) -- escape pattern
    repl = mw.ustring.gsub( repl, "[%%]", "%%%%" ) -- escape replacement
    repl = mw.ustring.gsub( repl, " ", "%%%%20" ) -- escape replacement
    return mw.ustring.gsub( str, pattern, repl )
end


-- Render functions
local function renderList( elements )
	if #elements == 0 then
		return ''
	end

	return '*' .. table.concat( elements , '\n*' ) .. '\n'
end

local function renderLabel( params )
	if type( params ) == 'string' then
		return params
	end

	local qid = params[ 1 ]
	local default = params[ 2 ]

	if #params >= 3 then
		local label = params[ 3 ]
		local link = mw.wikibase.sitelink( qid )
		if link then
			return '[[' .. link .. '|' .. label .. ']]'
		end
		local title = mw.wikibase.label( qid ) or default
		return '<span title="' .. title .. '" style="border-bottom: 1px dotted; cursor: help;">' .. label .. '</span>'
	end

	return mw.wikibase.label( qid ) or default
end

local function renderLink( resourceData, label, formatter, idAsLabel )
	if resourceData.itemId == nil then
		return '<span class="error">' .. label .. ': Не удаётся определить элемент</span>[[Категория:Статьи с ошибкой в шаблоне Внешние ссылки]]'
	end

	local link
	if not formatter then
		link = resourceData.itemId
		idAsLabel = false
	elseif type( formatter ) == 'string' then
		link = replace( formatter, '$1', resourceData.itemId )
	elseif type ( formatter ) == 'function' then
		link = formatter( resourceData.itemId, resourceData.qualifiers )
	end
	
	-- "Label: ID" without link
	if not link or link == '' then
		return renderLabel( label ) .. ':&nbsp;' .. resourceData.itemId
	end

	local resourceLabel = resourceData.itemId
	if not idAsLabel then
		resourceLabel = renderLabel( label )
	end

	local linkFirstChar = mw.ustring.sub( link, 1, 1 )
	if linkFirstChar == ':' then
		return '[[' .. link .. '|' .. resourceLabel .. ']]'
	end

	return '[' .. link .. ' ' .. resourceLabel .. ']'
end

local function renderRef( languages )
	local result = ''
	if languages and #languages > 0 then
		if moduleLanguages ~= false then -- not false, but maybe nil
			if mw.title.makeTitle( 'Module', 'Languages' ).exists
					and mw.title.makeTitle( 'Module', 'Languages/data' ).exists
					and mw.title.makeTitle( 'Module', 'Wikidata/Language-codes' ).exists then
				moduleLanguages = require( 'Module:Languages' )
			else
				moduleLanguages = false
			end
		end
		
		if moduleLanguages then
			for langIndex, language in pairs( languages ) do
				result = result .. '&nbsp;' .. moduleLanguages.getRefHtml( language )
			end
		end
	end

	return result
end

local function renderLinkWithRef( resourceData, label, formatter, idAsLabel )
	local link = renderLink( resourceData, label, formatter, idAsLabel )
	if link ~= '' then
		link = link .. renderRef( resourceData.languages )
	end
	return link
end


-- Data fetching functions
local function getValueFromSnak( snak )
	if snak.datavalue.type == 'wikibase-entityid' then
		return snak.datavalue.value.id
	end
	if snak.datavalue.type == 'monolingualtext' then
		return snak.datavalue.value.text
	end
	return snak.datavalue.value
end

local function getQualifierSingleValue( statement, qualifierName )
	if not statement or not statement.qualifiers or not statement.qualifiers[ qualifierName ] then
		return nil
	end

	for qualifierIndex, qualifier in pairs( statement.qualifiers[ qualifierName ] ) do
		if qualifier.datavalue and qualifier.datavalue.type and qualifier.datavalue.value then
			return getValueFromSnak( qualifier )
		end
	end

	return nil
end

local function getQualifierValues( statement )
	if not statement or not statement.qualifiers then
		return {}
	end

	local result = {}

	for qualifierName, qualifiers in pairs( statement.qualifiers ) do
		for _, qualifier in pairs( qualifiers ) do
			if qualifier.datavalue and qualifier.datavalue.type and qualifier.datavalue.value then
				if not result[ qualifierName ] then
					result[ qualifierName ] = {}
				end
				table.insert( result[ qualifierName ], getValueFromSnak( qualifier ) )
			end
		end
	end

	return result
end

local function contains( tableStructure, value )
	if not tableStructure or not value then
		return true
	end
	for index, line in pairs( tableStructure ) do
		if line == value then
			return true
		end
	end
	return false
end

local function filterByRank( resourceDatas )
	-- itemId, languages. rank = rank

	local hasPreffered = false
	for index, resourceData in pairs( resourceDatas ) do
		if resourceData.rank == 'preferred' then
			hasPreffered = true
		end
	end

	if not hasPreffered then
		return resourceDatas
	end

	local result = {}
	for index, resourceData in pairs( resourceDatas ) do
		if resourceData.rank == 'preferred' or contains( resourceData.languages, preferredLanguage ) then
			table.insert( result, resourceData )
		end
	end

	return result
end

local function getLinkData( statement, qualifier, project )
	local rank = statement.rank or 'normal'
	if rank == 'deprecated' or not statement.mainsnak.datavalue then
		return nil
	end

	local itemId
	if qualifier then
		itemId = getQualifierSingleValue( statement, qualifier )
	else
		itemId = statement.mainsnak.datavalue.value
	end

	if itemId and project then
		itemId = mw.wikibase.getSitelink( itemId, project )
	end

	if not itemId then
		return nil
	end

	local qualifiers = getQualifierValues( statement )
	local languages = qualifiers[ 'P407' ]
	if not languages then
		languages = {}
	end

	return {
		itemId = itemId,
		qualifiers = qualifiers,
		languages = languages,
		rank = rank,
	}
end

local function collectLinks( configuration, elementId, separateLabel )
	-- Create rows
	local elements = {}
	local data = {}

	local item = mw.wikibase.getEntity( elementId )
	if item == nil or item.claims == nil then
		return elements
	end

	for _, params in pairs( configuration ) do
		local resourceId = params[ 2 ]
		local pid = resourceId
		local qid
		if string.match( resourceId, '^P%d+:Q%d+$' ) then
			local parts = mw.text.split( resourceId, ':', true )
			pid = parts[ 1 ]
			qid = parts[ 2 ]
		end

		local claims = item.claims[ pid ] or {}
		for _, statement in pairs( claims ) do
			local linkData
			if not qid then
				linkData = getLinkData( statement )
			elseif getValueFromSnak( statement.mainsnak ) == qid then
				for qualifierId, qualifierType in pairs( propertyQualifiers[ pid ] ) do
					local project = nil
					if qualifierType == 'iw' then
						project = params.project
					end
					linkData = getLinkData( statement, qualifierId, project )
					if linkData then
						break
					end
				end
			end

			if linkData then
				if not data[ resourceId ] then
					data[ resourceId ] = {}
				end
				table.insert( data[ resourceId ], linkData )
			end
		end
	end

	for resourceId, resourceDatas in pairs( data ) do
		data[ resourceId ] = filterByRank( resourceDatas )
	end

	for _, params in pairs( configuration ) do
		local label = params[ 1 ]
		local resourceId = params[ 2 ]
		local resourceDatas = data[ resourceId ]
		if resourceDatas ~= nil then
			local preitemId
			local links = {}
			for index, resourceData in pairs( resourceDatas ) do
				local itemId = resourceData.itemId
				if index == 2 then
					--даёт возможность поставить id из одного свойства в разные ссылки (что?)
					if itemId == preitemId then
						break
					end
				end

				if separateLabel then
					-- Label: ID1, ID2
					table.insert( links, renderLinkWithRef( resourceData, '', params[ 3 ], true ) )
				else
					-- Label · Label
					table.insert( elements, renderLinkWithRef( resourceData, label, params[ 3 ] ) )
				end
				preitemId = resourceData.itemId
			end
			if separateLabel and #links then
				local result = renderLabel( params[ 1 ] ) .. ': ' .. table.concat( links, ', ' )
				table.insert( elements, result )
			end
		end
	end

	return elements
end


function p.render( frame )
	local colorArg = ''
	local elementId = nil
	if frame ~= nil then
		local parentArgs = frame:getParent().args
		colorArg = parentArgs[ templateColorName ] or parentArgs[ 'color' ] or parentArgs[ 1 ] or ''
		if parentArgs[ 'from' ] and parentArgs[ 'from' ] ~= '' then
			elementId = string.upper( parentArgs[ 'from' ] )
		elseif parentArgs[ 'd' ] and parentArgs[ 'd' ] ~= '' then
			elementId = string.upper( parentArgs[ 'd' ] )
		end
		if colorArg ~= '' then
			local firstChar = mw.ustring.sub( colorArg, 1, 1 )
			if firstChar ~= '#' then
				local byTemplate = colorByTitle( frame, colorArg )
				if byTemplate then
					colorArg = byTemplate
				end
			end
		end
	end

	local navboxData = {
		name  = 'External links',
		navboxclass = 'navbox ruwikiArticleExternalLinksTable',
		bodyclass = 'hlist',
	}
	if colorArg and colorArg ~= '' then
		navboxData.groupstyle = 'background: ' .. colorArg .. ';'
	end

	local rowIndex = 1

	for _, groupData in pairs( data ) do
		local isAuthorityControl = groupData.hider
		local groupLabel = groupData.label
		local groupList = groupData.list
		local groupElements = collectLinks( groupList, elementId, isAuthorityControl )
		if #groupElements > 0 then
			navboxData[ 'group' .. rowIndex ] = groupLabel
			navboxData[ 'list' .. rowIndex ] = renderList( groupElements )

			if isAuthorityControl then
				navboxData['group' .. rowIndex] = groupLabel
				local groupExtElements = collectLinks( groupData.ext, elementId, true )
				navboxData['list' .. rowIndex] = navboxData['list' .. rowIndex] .. renderList( groupExtElements )
				if #groupElements > 5 then
					navboxData[ 'group' .. rowIndex ] = nil
					package.loaded[ 'Module:Navbox' ] = nil
					local templateStyles = frame:extensionTag{
						name = 'templatestyles',
						args = { src = 'Шаблон:Навигационная таблица/styles.css' }
					}
					local collapsibleNavbox = require( 'Module:Navbox' )._navbox( {
						title = groupLabel,
						list1 = navboxData['list' .. rowIndex],
						border = 'subgroup',
						navbar = 'plain',
						state = 'collapsed',
						titleclass = 'ts-navbox-plaintitle',
						bodyclass = 'authoritycontrol',
						titlestyle = navboxData.groupstyle,
						bodystyle = 'text-align: left;',
					} )
					navboxData[ 'list' .. rowIndex ] = templateStyles .. collapsibleNavbox
				end
			end

			rowIndex = rowIndex + 1
		end
	end

	if rowIndex == 1 then
		if mw.title.getCurrentTitle().namespace == 0 then
			return '[[Category:' .. categoryTemplateEmpty .. ']]'
		end
		return ''
	end

	local tnavbar = frame:expandTemplate{ title = 'tnavbar-view', args = { templateLink } }
	if navboxData[ 'group1' ] then
		navboxData[ 'group1' ] = '<div style="padding: 0 18px 0 0; width: 100%;">' ..
			'<div style="float: left;">' .. tnavbar .. '</div>&nbsp;&nbsp;' ..
			navboxData[ 'group1' ] .. '</div>'
	else
		navboxData[ 'group1' ] = '<div style="padding: 0; width: 100%;">' .. tnavbar .. '</div>'
	end

	return moduleNavbox._navbox( navboxData )
end


-- Documentation functions
local function renderDocumentationLine( params )
	local result = ''

	local resourceLabel = renderLabel( params[ 1 ] )
	local pid = params[ 2 ]
	local qid
	if string.match( pid, '^P%d+:Q%d+$' ) then
		local parts = mw.text.split( pid, ':', true )
		pid = parts[ 1 ]
		qid = parts[ 2 ]
	end		

	result = result .. '| ' .. resourceLabel .. '\n'
	result = result .. '| [[:d:Property:' .. pid .. '|' .. pid .. ']]'
	if qid then
		result = result .. ' = [[:d:' .. qid .. '|' .. qid .. ']]'
	end
	result = result .. '\n'

	result = result .. '|-\n'

	return result
end

function p.renderDocumentation()
	local result = ''
	for _, groupData in pairs( data ) do
		local groupLabel = groupData.label
		local groupList = groupData.list

		result = result .. '|-\n'
		result = result .. '! colspan=2 | ' .. groupLabel .. '\n'
		result = result .. '|-\n'

		for _, linkParams in pairs( groupList ) do
			result = result .. renderDocumentationLine( linkParams )
		end
	end
	return result
end

return p