Модуль:Autosorting: различия между версиями

Материал из in.wiki
Перейти к навигации Перейти к поиску
м (1 версия импортирована: Импорт из Википедии)
 
(не показаны 4 промежуточные версии 2 участников)
Строка 6: Строка 6:
  
 
local config = mw.loadData( 'Module:Autosorting/config' )
 
local config = mw.loadData( 'Module:Autosorting/config' )
 +
 +
local currentTitle = mw.title.getCurrentTitle()
  
 
local function isEmpty( val )
 
local function isEmpty( val )
Строка 11: Строка 13:
 
end
 
end
  
-- Получить назвние категории
+
-- Получить название категории
local function getCategoryName( str, name, val )
+
local function getCategoryName( str, name, ... )
val = val or ''
+
if isEmpty( name ) then
return string.format( str, mwLang:ucfirst( name ), val )
+
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
 
end
  
Строка 49: Строка 73:
 
end
 
end
  
-- Получить три первых значения из свойства в Викиданных
+
-- Получить num (по умолчанию все) первых значений из свойства в Викиданных
-- TODO: переделать на получение Q-элементов, чтобы не зависеть от языка
+
local function getWikidataProperty( frame, property, entityId, num )
local function getWikidataProperty( frame, property, entityId )
 
 
frame = frame or mw.getCurrentFrame()
 
frame = frame or mw.getCurrentFrame()
 
 
local callArgs = {
+
if isEmpty( property ) then
property,
+
return error( 'Autosorting: getWikidataProperty без property' )
}
+
end
if not isEmpty( entityId ) then
+
property = string.upper( property )
callArgs.from = entityId
+
 +
if isEmpty( entityId ) then
 +
entityId = mw.wikibase.getEntityIdForCurrentPage()
 
end
 
end
 
 
local success, result = pcall( frame.callParserFunction, frame, '#property', callArgs )
+
local result = {}
if success and not isEmpty( result ) then
+
local success, statements = pcall( mw.wikibase.getBestStatements, entityId, property )
if mw.ustring.len( result ) > 255 then
+
if success then
return {}
+
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
result = mw.text.split( result, ', ' )
 
 
return { unpack( result, 1, 3 ) }
 
 
end
 
end
 
 
return {}
+
return result
 
end
 
end
  
Строка 81: Строка 125:
 
frame = frame or mw.getCurrentFrame()
 
frame = frame or mw.getCurrentFrame()
 
 
local propValues = getWikidataProperty( frame, property, entityId )
+
local propValues = getWikidataProperty( frame, property, entityId, 3 )
 
 
local catName = getCategoryName( name, property )
+
local instanceOf = getWikidataProperty( frame, 'p31', entityId, 3 )
local instanceOf = getWikidataProperty( frame, 'p31', entityId )
 
 
instanceOf = #instanceOf > 0 and instanceOf[ 1 ] or ''
 
instanceOf = #instanceOf > 0 and instanceOf[ 1 ] or ''
local pageTitle = mw.title.getCurrentTitle().fullText
 
 
 
local result = string.format( '[[Category:%s|%s%s]]', catName, instanceOf, pageTitle )
+
local result = getCategory( name, instanceOf, property )
 
return #propValues, result
 
return #propValues, result
 
end
 
end
  
 
-- Сортировка по профессиям
 
-- Сортировка по профессиям
function p._byOccupation( frame, name, entityId )
+
function p._byOccupation( frame, name, entityId, key )
 
if isEmpty( name ) then
 
if isEmpty( name ) then
 
return ''
 
return ''
Строка 103: Строка 145:
 
 
 
local result = ''
 
local result = ''
for key, val in pairs( propValues ) do
+
local catName = ''
 +
local occupationExists = false
 +
local validCatsCounter = 0
 +
for k, val in pairs( propValues ) do
 
local value = mwLang:lcfirst( val )
 
local value = mwLang:lcfirst( val )
local catName = getCategoryName( 'Википедия:%s (тип: человек; род занятий: %s)', name, value )
+
catName = getCategoryName( 'Проект:%s (тип: человек; род занятий: %s)', name, value )
 +
occupationExists = true
 
 
-- Шаблон проставлял одну категорию
+
if isValidCategory( name, catName, property ) then
if result ~= '' then
+
result = result .. getCategory( catName, key )
 +
validCatsCounter = validCatsCounter + 1
 +
end
 +
 +
-- Ограничение количества выводимых категорий
 +
if validCatsCounter >= 3 then
 
break
 
break
 
end
 
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
 
if isValidCategory( name, catName, property ) then
result = result .. string.format( '[[Category:%s]]', catName )
+
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
 
end
 
end
Строка 121: Строка 191:
  
 
-- Сортировка по типам
 
-- Сортировка по типам
function p._byType( frame, name, entityId )
+
function p._byType( frame, name, entityId, key )
 
if isEmpty( name ) then
 
if isEmpty( name ) then
 
return ''
 
return ''
Строка 129: Строка 199:
 
local property = 'p31'
 
local property = 'p31'
 
local propValues = getWikidataProperty( frame, property, entityId )
 
local propValues = getWikidataProperty( frame, property, entityId )
 +
 +
local args = getArgs( frame )
 +
local defaultType = args[ 'default-type' ]
 +
local defaultOccupation = args[ 'default-occupation' ]
 
 
 
local defaultCatKey
 
local defaultCatKey
 
local result = ''
 
local result = ''
for key, val in pairs( propValues ) do
+
local catName = ''
local catName = getCategoryName( 'Википедия:%s (тип: %s)', name, mwLang:lcfirst( val ) )
+
local validCatsCounter = 0
 +
for k, val in pairs( propValues ) do
 +
catName = getCategoryName( 'Проект:%s (тип: %s)', name, mwLang:lcfirst( val ) )
 
-- TODO: переделать на получение Q-элементов, чтобы не зависеть от языка
 
-- TODO: переделать на получение Q-элементов, чтобы не зависеть от языка
 
if val == 'человек' or val == 'human' then
 
if val == 'человек' or val == 'human' then
 
local occupations = p._byOccupation( frame, name, entityId )
 
local occupations = p._byOccupation( frame, name, entityId )
 
if isEmpty( occupations ) then
 
if isEmpty( occupations ) then
result = result .. string.format( '[[Category:%s]]', catName )
+
result = result .. getCategory( catName, key )
 
else
 
else
 
result = result .. occupations
 
result = result .. occupations
 
end
 
end
 +
validCatsCounter = validCatsCounter + 1 -- "человек" считается всегда валидной
 
else
 
else
 
if isValidCategory( name, catName, property ) then
 
if isValidCategory( name, catName, property ) then
result = result .. string.format( '[[Category:%s]]', catName )
+
result = result .. getCategory( catName, key )
 +
validCatsCounter = validCatsCounter + 1
 
else
 
else
 
if isEmpty( defaultCatKey ) then
 
if isEmpty( defaultCatKey ) then
 
defaultCatKey = val
 
defaultCatKey = val
 
end
 
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
 
end
Строка 154: Строка 259:
 
 
 
-- Добавить стандартную категорию только при отсутствии иных
 
-- Добавить стандартную категорию только при отсутствии иных
local defaultCatName = getCategoryName( 'Википедия:%s (не распределённые по типам)', name )
+
local defaultCatName = getCategoryName( 'Проект:%s (не распределённые по типам)', name )
 
if result == '' and not isEmpty( defaultCatKey ) then
 
if result == '' and not isEmpty( defaultCatKey ) then
local pageTitle = mw.title.getCurrentTitle().fullText
+
if not isEmpty( key ) then
result = result .. string.format( '[[Category:%s|%s%s]]', defaultCatName, defaultCatKey, pageTitle )
+
defaultCatKey = key .. defaultCatKey
 +
end
 +
result = result .. getCategory( defaultCatName, defaultCatKey )
 
end
 
end
 
 
 
if result == '' then
 
if result == '' then
return getCategoryName( '[[Category:Википедия:%s (тип: не указан)]]', name )
+
return getCategory( 'Проект:%s (тип: не указан)', key, name )
 
end
 
end
 
 
 
return result
 
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
 
end
  
 
-- Сортировка по АТЕ
 
-- Сортировка по АТЕ
function p._bySubdivision( frame, name, entityId )
+
function p._bySubdivision( frame, name, entityId, key )
 
if isEmpty( name ) then
 
if isEmpty( name ) then
 
return ''
 
return ''
Строка 175: Строка 299:
 
 
 
local property = 'p131'
 
local property = 'p131'
local propValues = getWikidataProperty( frame, property, entityId )
+
local propValues = getWikidataProperty( frame, property, entityId, 3 )
 
 
 
local result = ''
 
local result = ''
for key, val in pairs( propValues ) do
+
for k, val in pairs( propValues ) do
local catName = getCategoryName( 'Википедия:%s (АТЕ: %s)', name, val )
+
local catName = getCategoryName( 'Проект:%s (АТЕ: %s)', name, val )
 
 
 
if isValidCategory( name, catName, property, false ) then
 
if isValidCategory( name, catName, property, false ) then
result = result .. string.format( '[[Category:%s]]', catName )
+
result = result .. getCategory( catName, key )
 
end
 
end
 
end
 
end
Строка 190: Строка 314:
  
 
-- Сортировка по странам
 
-- Сортировка по странам
function p._byCountry( frame, name, entityId )
+
function p._byCountry( frame, name, entityId, key )
 
if isEmpty( name ) then
 
if isEmpty( name ) then
 
return ''
 
return ''
Строка 197: Строка 321:
 
 
 
local property = 'p17'
 
local property = 'p17'
local propValues = getWikidataProperty( frame, property, entityId )
+
local propValues = getWikidataProperty( frame, property, entityId, 3 )
 
 
 
local result = ''
 
local result = ''
for key, val in pairs( propValues ) do
+
for k, val in pairs( propValues ) do
 
if isEmpty( val ) then
 
if isEmpty( val ) then
 
break
 
break
 
end
 
end
local catName = getCategoryName( 'Википедия:%s (страна: %s)', name, val )
+
local catName = getCategoryName( 'Проект:%s (страна: %s)', name, val )
 
 
result = result .. string.format( '[[Category:%s]]', catName )
+
result = result .. getCategory( catName, key )
 
if isValidCategory( name, catName, property, false ) then
 
if isValidCategory( name, catName, property, false ) then
result = result .. p._bySubdivision( frame, name, entityId )
+
result = result .. p._bySubdivision( frame, name, entityId, key )
 
end
 
end
 
end
 
end
 
 
 
return result
 
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
 
end
  
Строка 233: Строка 374:
 
end
 
end
 
 
local catName = 'Википедия:Статьи с изображениями: заполнить свойство %s в Викиданных'
+
local catName = 'Проект:Статьи с изображениями: заполнить свойство %s в Викиданных'
 
local result = ''
 
local result = ''
 
local p18, p18Category = getFileCategory( frame, catName, 'p18', entityId )
 
local p18, p18Category = getFileCategory( frame, catName, 'p18', entityId )
Строка 249: Строка 390:
 
-- Игнорировать при наличии изображений в указанных свойствах
 
-- Игнорировать при наличии изображений в указанных свойствах
 
for _, val in pairs( localFileProps ) do
 
for _, val in pairs( localFileProps ) do
local propValue = getWikidataProperty( frame, val, entityId )
+
local propValue = getWikidataProperty( frame, val, entityId, 3 )
 
if #propValue > 0 then
 
if #propValue > 0 then
 
return ''
 
return ''
Строка 256: Строка 397:
 
 
 
-- Вывести категории при отсутствии игнорируемых свойств
 
-- Вывести категории при отсутствии игнорируемых свойств
local catName = 'Википедия:Статьи без изображений (указано в Викиданных: %s)'
+
local catName = 'Проект:Статьи без изображений (указано в Викиданных: %s)'
 
local result = p._byCountry( frame, 'статьи без изображений', entityId )
 
local result = p._byCountry( frame, 'статьи без изображений', entityId )
 
 
Строка 274: Строка 415:
 
 
 
return result
 
return result
end
 
 
-- Шаблон сортировки по типам
 
function p.byType( frame )
 
local args = getArgs( frame )
 
local name = args[ 1 ]
 
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._byType( frame, name, entityId )
 
end
 
 
-- Шаблон сортировки по странам
 
function p.byCountry( frame )
 
local args = getArgs( frame )
 
local name = args[ 1 ]
 
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 )
 
 
end
 
end
  

Текущая версия от 21:16, 12 апреля 2025

Для документации этого модуля может быть создана страница Модуль:Autosorting/doc

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