Модуль:Autosorting

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

Реализация шаблонов {{Сортировка: по типам}}, {{Сортировка: по странам}} и {{Сортировка: по изображениям}}. Подмодуль конфигурации: Модуль:Autosorting/config.

Тесты: Шаблон:Сортировка: по типам/тесты и Шаблон:Сортировка: по странам/тесты.

require( 'strict' )
local p = {}

local mwLang = mw.getContentLanguage()
local getArgs = require( 'Module:Arguments' ).getArgs

local config = mw.loadData( 'Module:Autosorting/config' )

local currentTitle = mw.title.getCurrentTitle()

local function isEmpty( val )
	return val == nil or val == ''
end

-- Получить название категории
local function getCategoryName( str, name, ... )
	if isEmpty( name ) then
		return error( 'Autosorting: getCategoryName без name' )
	end
	return string.format( str, mwLang:ucfirst( name ), unpack( arg ) )
end

-- Получить категорию
local function getCategory( name, key, ... )
	if isEmpty( name ) then
		return error( 'Autosorting: getCategory без name' )
	end
	
	local catKey = ''
	if not isEmpty( key ) then
		catKey = string.format( '|%s %s', key, currentTitle.text )
	end
	
	if #arg > 0 then
		local title = mwLang:ucfirst( arg[ 1 ] )
		arg[ 1 ] = nil
		name = string.format( name, title, unpack( arg ) )
	end
	
	return string.format( '[[Category:%s%s]]', name, catKey )
end

-- Получить стандартный лимит
local function getPageLimit( name, property )
	name = mwLang:lcfirst( name )
	property = mwLang:uc( property )
	
	local propertyLimits = config.limits[ property ]
	if isEmpty( propertyLimits ) then
		return -1
	end
	
	return propertyLimits[ name ] or propertyLimits.default
end

-- Получить соответствие категории критериям
local function isValidCategory( name, catName, property, doNotCheck )
	if not doNotCheck then
		local success, title = pcall( mw.title.new, 'Category:' .. catName )
		if success and not isEmpty( title ) and title.exists then
			return true
		end
	end
	
	local success, catCount = pcall( mw.site.stats.pagesInCategory, catName, 'pages' )
	if success then
		local pageLimit = getPageLimit( name, property )
	
		return catCount > pageLimit
	end
	
	return false
end

-- Получить num (по умолчанию все) первых значений из свойства в Викиданных
local function getWikidataProperty( frame, property, entityId, num )
	frame = frame or mw.getCurrentFrame()
	
	if isEmpty( property ) then
		return error( 'Autosorting: getWikidataProperty без property' )
	end
	property = string.upper( property )
	
	if isEmpty( entityId ) then
		entityId = mw.wikibase.getEntityIdForCurrentPage()
	end
	
	local result = {}
	local success, statements = pcall( mw.wikibase.getBestStatements, entityId, property )
	if success then
		for i, propVal in ipairs( statements ) do
			if not isEmpty( num ) and #result >= num then
				break
			end
			local val = propVal[ 'mainsnak' ]
			local hasValue = val[ 'snaktype' ] == 'value'
			
			-- Media file values: P18 etc.
			if hasValue and val[ 'datatype' ] == 'commonsMedia' then
				local value = val[ 'datavalue' ][ 'value' ]
				if not isEmpty( value ) then
					table.insert( result, value )
				end
			end
			
			-- Link values: P17 etc.
			if hasValue and val[ 'datatype' ] == 'wikibase-item' then
				local valId = val[ 'datavalue' ][ 'value' ][ 'id' ]
				local success, label = pcall( mw.wikibase.getLabel, valId )
				if success and not isEmpty( label ) then
					table.insert( result, label )
				end
			end
		end
	end
	
	return result
end

-- Получить категорию для отсутствия изображений
local function getFileCategory( frame, name, property, entityId )
	if isEmpty( name ) then
		return ''
	end
	frame = frame or mw.getCurrentFrame()
	
	local propValues = getWikidataProperty( frame, property, entityId, 3 )
	
	local instanceOf = getWikidataProperty( frame, 'p31', entityId, 3 )
	instanceOf = #instanceOf > 0 and instanceOf[ 1 ] or ''
	
	local result = getCategory( name, instanceOf, property )
	return #propValues, result
end

-- Сортировка по профессиям
function p._byOccupation( frame, name, entityId, key )
	if isEmpty( name ) then
		return ''
	end
	frame = frame or mw.getCurrentFrame()
	
	local property = 'p106'
	local propValues = getWikidataProperty( frame, property, entityId )
	
	local result = ''
	local catName = ''
	local occupationExists = false
	local validCatsCounter = 0
	for k, val in pairs( propValues ) do
		local value = mwLang:lcfirst( val )
		catName = getCategoryName( 'Википедия:%s (тип: человек; род занятий: %s)', name, value )
		occupationExists = true
		
		if isValidCategory( name, catName, property ) then
			result = result .. getCategory( catName, key )
			validCatsCounter = validCatsCounter + 1
		end
		
		-- Ограничение количества выводимых категорий
		if validCatsCounter >= 3 then
			break
		end
		
	end
	
	-- Подходящей категории нет, либо в ВД нет рода занятий, но есть дефолтные значения,
	-- тогда поставляем категорию на их основе при её наличии
	local args = getArgs( frame )
	local defaultOccupation = args[ 'default-occupation' ]
	
	if result == '' and not isEmpty( defaultOccupation ) then
		occupationExists = true
		catName = getCategoryName( 'Википедия:%s (тип: человек; род занятий: %s)', name, defaultOccupation )
		if isValidCategory( name, catName, property ) then
			result = result .. getCategory( catName, key )
		end
	end
	
	-- Есть род занятий (на ВД или через параметр), но подходящей категории нет,
	-- тогда подставляем служебную категорию "не распределён" при её наличии
	if result == '' and occupationExists then
		catName = getCategoryName( 'Википедия:%s (тип: человек; род занятий: не распределён)', name )
		if isValidCategory( name, catName, property ) then
			result = result .. getCategory( catName, key )
		end
	end
	
	return result
end

-- Сортировка по типам
function p._byType( frame, name, entityId, key )
	if isEmpty( name ) then
		return ''
	end
	frame = frame or mw.getCurrentFrame()
	
	local property = 'p31'
	local propValues = getWikidataProperty( frame, property, entityId )

	local args = getArgs( frame )
	local defaultType = args[ 'default-type' ]
	local defaultOccupation = args[ 'default-occupation' ]
	
	local defaultCatKey
	local result = ''
	local catName = ''
	local validCatsCounter = 0
	for k, val in pairs( propValues ) do
		catName = getCategoryName( 'Википедия:%s (тип: %s)', name, mwLang:lcfirst( val ) )
		-- TODO: переделать на получение Q-элементов, чтобы не зависеть от языка
		if val == 'человек' or val == 'human' then
			local occupations = p._byOccupation( frame, name, entityId )
			if isEmpty( occupations ) then
				result = result .. getCategory( catName, key )
			else
				result = result .. occupations
			end
			validCatsCounter = validCatsCounter + 1 -- "человек" считается всегда валидной
		else
			if isValidCategory( name, catName, property ) then
				result = result .. getCategory( catName, key )
				validCatsCounter = validCatsCounter + 1
			else
				if isEmpty( defaultCatKey ) then
					defaultCatKey = val
				end
			end
		end
		
		-- Ограничение количества выводимых категорий
		if validCatsCounter >= 3 then
			break
		end
	end
	
	-- Если ничего не нашлось, попытка добавить категорию на основе переданных дефолтных значений
	if result == '' and not isEmpty( defaultType ) then
		if defaultType == 'человек' and not isEmpty( defaultOccupation ) then -- человек, есть занятие
			catName = getCategoryName( 'Википедия:%s (тип: человек; род занятий: %s)', name, defaultOccupation )
			if isValidCategory( name, catName, property ) then
				result = result .. getCategory( catName, key )
			else
				-- человек, есть занятие, но категория для занятия не прошла проверки
				result = result .. getCategory( 'Википедия:%s (тип: человек)', key, name )
			end
		elseif defaultType == 'человек' then
			-- человек, нет занятия
			result = result .. getCategory( 'Википедия:%s (тип: человек)', key, name )
		else
			-- нечеловек
			catName = getCategoryName( 'Википедия:%s (тип: %s)', name, defaultType )
			if isValidCategory( name, catName, property ) then
				result = result .. getCategory( catName, key )
			end
		end
	end
	
	-- Добавить стандартную категорию только при отсутствии иных
	local defaultCatName = getCategoryName( 'Википедия:%s (не распределённые по типам)', name )
	if result == '' and not isEmpty( defaultCatKey ) then
		if not isEmpty( key ) then
			defaultCatKey = key .. defaultCatKey
		end
		result = result .. getCategory( defaultCatName, defaultCatKey )
	end
	
	if result == '' then
		return getCategory( 'Википедия:%s (тип: не указан)', key, name )
	end
	
	return result
end

-- Шаблон сортировки по типам
function p.byType( frame )
	local args = getArgs( frame )
	local name = args[ 1 ]
	local key = args[ 'key' ]
	local entityId = args[ 'from' ]
	if isEmpty( name ) or not isEmpty( args.nocat ) then
		return nil
	end
	
	if mw.ustring.find( name, 'статьи' ) and currentTitle.namespace ~= 0 then
		return nil
	end
	
	return p._byType( frame, name, entityId, key )
end

-- Сортировка по АТЕ
function p._bySubdivision( frame, name, entityId, key )
	if isEmpty( name ) then
		return ''
	end
	frame = frame or mw.getCurrentFrame()
	
	local property = 'p131'
	local propValues = getWikidataProperty( frame, property, entityId, 3 )
	
	local result = ''
	for k, val in pairs( propValues ) do
		local catName = getCategoryName( 'Википедия:%s (АТЕ: %s)', name, val )
		
		if isValidCategory( name, catName, property, false ) then
			result = result .. getCategory( catName, key )
		end
	end
	
	return result
end

-- Сортировка по странам
function p._byCountry( frame, name, entityId, key )
	if isEmpty( name ) then
		return ''
	end
	frame = frame or mw.getCurrentFrame()
	
	local property = 'p17'
	local propValues = getWikidataProperty( frame, property, entityId, 3 )
	
	local result = ''
	for k, val in pairs( propValues ) do
		if isEmpty( val ) then
			break
		end
		local catName = getCategoryName( 'Википедия:%s (страна: %s)', name, val )
		
		result = result .. getCategory( catName, key )
		if isValidCategory( name, catName, property, false ) then
			result = result .. p._bySubdivision( frame, name, entityId, key )
		end
	end
	
	return result
end

-- Шаблон сортировки по странам
function p.byCountry( frame )
	local args = getArgs( frame )
	local name = args[ 1 ]
	local key = args[ 'key' ]
	local entityId = args[ 'from' ]
	if isEmpty( name ) or not isEmpty( args.nocat ) then
		return nil
	end
	
	if mw.ustring.find( name, 'статьи' ) and mw.title.getCurrentTitle().namespace ~= 0 then
		return nil
	end
	
	return p._byCountry( frame, name, entityId, key )
end

-- Сортировка по наличию/отсутствию изображений
function p._byImage( frame, file, localFileProps, entityId )
	frame = frame or mw.getCurrentFrame()
	
	-- Возможный сброс значения с Викиданных
	if file ~= nil and mw.text.trim( file ) == '-' then
		file = nil
	end
	
	-- Вывести категории при заполненном несуществующем файле (= файле с Викисклада)
	if not isEmpty( file ) then
		local success, title = pcall( mw.title.new, 'File:' .. file )
		
		-- Игнорировать при заполненном локальном файле
		if success and not isEmpty( title ) and title.exists then
			return nil
		end
		
		local catName = 'Википедия:Статьи с изображениями: заполнить свойство %s в Викиданных'
		local result = ''
		local p18, p18Category = getFileCategory( frame, catName, 'p18', entityId )
		local p373, p373Category = getFileCategory( frame, catName, 'p373', entityId )
		if p18 == 0 then
			result = result .. p18Category
		end
		if p373 == 0 then
			result = result .. p373Category
		end
		
		return result
	end
	
	-- Игнорировать при наличии изображений в указанных свойствах
	for _, val in pairs( localFileProps ) do
		local propValue = getWikidataProperty( frame, val, entityId, 3 )
		if #propValue > 0 then
			return ''
		end
	end
	
	-- Вывести категории при отсутствии игнорируемых свойств
	local catName = 'Википедия:Статьи без изображений (указано в Викиданных: %s)'
	local result = p._byCountry( frame, 'статьи без изображений', entityId )
	
	local p18, p18Category = getFileCategory( frame, catName, 'p18', entityId )
	local p242, p242Category = getFileCategory( frame, catName, 'p242', entityId )
	local p373, p373Category = getFileCategory( frame, catName, 'p373', entityId )
	if p18 > 0 then
		result = result .. p18Category
	end
	if p242 > 0 then
		result = result .. p242Category
	end
	if p373 > 0 then
		result = result .. p373Category
	end
	result = result .. p._byType( frame, 'статьи без изображений', entityId )
	
	return result
end

-- Шаблон сортировки по изображениям
function p.byImage( frame )
	if mw.title.getCurrentTitle().namespace ~= 0 then
		return nil
	end
	local args = getArgs( frame )
	local file = args[ 1 ]
	local entityId = args[ 'from' ]
	if not isEmpty( args.nocat ) then
		return nil
	end
	
	-- Игнорирование по умолчанию статей с указанным p18
	local uses = args[ 'uses' ]
	if isEmpty( uses ) then
		uses = 'p18'
	end
	local localFileProps = mw.text.split( uses, ', ' )
	if uses == '-' then
		localFileProps = {}
	end
	
	return p._byImage( frame, file, localFileProps, entityId )
end

return p