Модуль:WikidataSelectors

Из Википедии, бесплатной энциклопедии

Документация

Этот модуль выбирает из списка утверждений Викиданных для определённого свойства те, которые удовлетворяют указанным условиям.

  • Для идентификаторов Викиданных, начинающихся с «P» или «Q» регистр не имеет значения. Строки регистрозависимы.
  • До и после операторов вы можете вставлять пробелы.

Этот модуль не предназначен для использования напрямую на страницах и в шаблонах, он расширяет синтаксис модуля Wikidata и шаблона {{wikidata}}:
{{ wikidata | P123[ P456:Q789 ] }}.

Условия

Синтаксис Пример Описание
property[ position ] P18[ 1 ] Только утверждение на позиции position. Индексы начинаются с 1.
property[ rank:value ] P161[ rank:preferred ] Фильтр по приоритету. Возможные значения:
  • best (preferred, если есть, иначе normal; по умолчанию)
  • preferred
  • normal
  • deprecated
property[ language:value ] P1559[ language:ru ] Фильтр по языку для моноязычных полей.
property[ unit:value ] P2043[ unit:Q828224 ] Фильтр по единице измерения для количественных полей.
property[ qualifier ] P123[ P456 ] Проверка на существование квалификатора с ID qualifier с любым значением.
property[ qualifier:value ] P123[ P456:789 ] Проверка, что значение квалификатора с ID qualifier равно value. Если значением квалификатора является элемент, в значении нужно указать его ID, начинающийся с «Q»: P123[ P456:Q789 ].
property[ qualifier!:value ] P123[ P456!:789 ] Проверка, что значение квалификатора с ID qualifier не равно value. Если значением квалификатора является элемент, в значении нужно указать его ID, начинающийся с «Q»: P123[ P456!:Q789 ].
property[ value ] P123[ Q456 ] Проверка, что значения свойства равно value.
property[ min ] P123[ min ] Фильтр утверждений с минимальным значением. Может вернуть несколько одинаковых значений.
property[ max ] P123[ max ] Фильтр утверждений с максимальным значением. Может вернуть несколько одинаковых значений.
property[ min:qualifier ] P123[ min:P585 ] Выбор утверждения с минимальным значением квалификатора с ID qualifier.
property[ max:qualifier ] P123[ max:P585 ] Выбор утверждения с максимальным значением квалификатора с ID qualifier.

Комбинирование условий

Синтаксис Пример Описание
property[ !selector ] P348[ ! P548:Q2122918 ] Условие, обратное указанному.
property[ selector1, selector2, … ] P348[ P548:Q2122918, P548:Q3295609 ] Эквивалент логического ИЛИ. Утверждения, соответствующие различным условиям, объединяются в один список.
property[ selector1 ][ selector2 ][ … ] P166[ P111!:1946 ][ P111!:1972 ] Эквивалент логического И. Условия выполняются одно за другим. Порядок условий важен:
  • property[ selector ][ position ] и
  • property[ position ][ selector ]

могут вернуть различный результат.

local i18n = {     ["errors"] = {         ["rank-not-valid"] = "Некорретное значение приоритета (rank)",         ["cant-parse-condition"] = "Не удалось разобрать условие"     } }  local validRanks = { 	'best', 	'preferred', 	'normal', 	'deprecated' }   --[[    Internal function for error message     Input: key in errors table   Output: error message ]] local function throwError( key )     error( i18n.errors[key] ) end   local p = {}  --[[   Load property and filter statements     Input: entityId, selector string   Output: filtered statements table ]] function p.load( entityId, propertySelector ) 	local propertyId = mw.ustring.match( propertySelector, '^[Pp]%d+' ) 	if not propertyId then 		return nil 	end 	propertyId = string.upper( propertyId )  	local allStatements = {} 	allStatements[ propertyId ] = mw.wikibase.getAllStatements( entityId, propertyId )  	return p.filter( allStatements, propertySelector ) end  --[[   Parse selectors and filter statements     Input: statements table, selector string   Output: filtered statements table ]] function p.filter( allClaims, propertySelector ) 	propertySelector = mw.text.trim( propertySelector )  	-- Get property ID from selector 	local propertyId = mw.ustring.match( propertySelector, '^[Pp]%d+' ) 	if not propertyId then 		propertyId = '' 	end 	local initPos = #propertyId + 1 	propertyId = string.upper( propertyId )     if ( not allClaims ) then     	return nil     end 	local allPropertyClaims = allClaims[propertyId]     if ( not allPropertyClaims ) then     	return nil     end  	-- Gathering rules 	local rules = p.matchSelectors( propertySelector, initPos )  	-- If there is no rank filter, than default rank is 'best' 	local isRanked = false 	for i, subRules in ipairs( rules ) do 		for j, rule in ipairs( subRules ) do 			if rule['type'] == 'rank' then 				isRanked = true 				break 			end 		end 	end 	if not isRanked then 		table.insert( rules, 1, { { type = 'rank', value = 'best' } } ) 	end  	-- Execute rules 	allPropertyClaims = p.applyRules( allPropertyClaims, rules )  	return allPropertyClaims end   --[[   Match and gather selector rules     Input: string with selectors rules, start position   Output: rules table ]] function p.matchSelectors( selectorsString, initPos ) 	local rules = {} 	local rawRulePattern = '^%s*%[%s*[^%[%]]+%s*%]%s*' 	local rulePattern = '^%s*%[%s*([^%[%]]+)%s*%]%s*$' 	 	if not initPos then 		initPos = 1 	end  	local rawRule = mw.ustring.match( selectorsString, rawRulePattern, initPos )  	while rawRule do 		initPos = initPos + #rawRule 		rule = mw.ustring.match( rawRule, rulePattern ) 		rule = mw.text.trim( rule )  		local subRules = mw.text.split( rule, '%s*,%s*' ) 		local commands = {} 		local comm  		for i, subRule in ipairs( subRules ) do 			local isInversed = false 			if mw.ustring.match( subRule, '^!' ) then 				isInversed = true 				subRule = mw.ustring.match( subRule, '^!%s*(.+)$' ) 			end  			-- p123[1] 			if mw.ustring.match( subRule, '^%d+$' ) then 				table.insert( commands, { 					type = 'position', 					value = subRule, 					inversed = isInversed 				} ) 	 			-- p123[rank:preferred] 			elseif mw.ustring.match( subRule, '^rank%s*:%s*(%a+)$' ) then 				rank = mw.ustring.match( subRule, '^rank%s*:%s*(%a+)$' ) 				table.insert( commands, { 					type = 'rank', 					value = rank, 					inversed = isInversed 				} )  			-- p123[language:xx] 			elseif mw.ustring.match( subRule, '^language%s*:%s*([%a%-]+)$' ) then 				value = mw.ustring.match( subRule, '^language%s*:%s*([%a%-]+)$' ) 				table.insert( commands, { 					type = 'language', 					value = value, 					inversed = isInversed 				} )  			-- p123[language!:xx] 			elseif mw.ustring.match( subRule, '^language%s*!:%s*([%a%-]+)$' ) then 				value = mw.ustring.match( subRule, '^language%s*!:%s*([%a%-]+)$' ) 				table.insert( commands, { 					type = 'language', 					value = value, 					inversed = not isInversed 				} )  			-- p123[min] 			elseif mw.ustring.match( subRule, '^min$' ) then 				table.insert( commands, { type = 'value_min' } )  			-- p123[max] 			elseif mw.ustring.match( subRule, '^max$' ) then 				table.insert( commands, { type = 'value_max' } )  			-- p123[min:p456] 			elseif mw.ustring.match( subRule, '^min%s*:%s*[Pp]%d+$' ) then 				value = mw.ustring.match( subRule, ':%s*([Pp]%d+)$' ) 				table.insert( commands, { 					type = 'qualifier_min', 					qualifier = value 				} )  			-- p123[max:p456] 			elseif mw.ustring.match( subRule, '^max%s*:%s*[Pp]%d+$' ) then 				value = mw.ustring.match( subRule, ':%s*([Pp]%d+)$' ) 				table.insert( commands, { 					type = 'qualifier_max', 					qualifier = value 				} )  			-- p123[unit:q789] 			elseif mw.ustring.match( subRule, '^unit%s*:%s*[^%[%],:]+$' ) then 				value = mw.ustring.match( subRule, ':%s*([^%[%],:]+)$' ) 				table.insert( commands, { 					type = 'unit', 					value = value, 					inversed = isInversed 				} )  			-- p123[unit!:q789] 			elseif mw.ustring.match( subRule, '^unit%s*!:%s*[^%[%],:]+$' ) then 				value = mw.ustring.match( subRule, '!:%s*([^%[%],:]+)$' ) 				table.insert( commands, { 					type = 'unit', 					value = value, 					inversed = not isInversed 				} )  			-- p123[p456] 			elseif mw.ustring.match( subRule, '^[Pp]%d+$' ) then 				qualifier = mw.ustring.match( subRule, '^[Pp]%d+' ) 				table.insert( commands, { 					type = 'qualifier', 					qualifier = qualifier, 					value = nil, 					inversed = isInversed 				} )  			-- p123[p456:q789] 			elseif mw.ustring.match( subRule, '^[Pp]%d+%s*:%s*[^%[%],:]+$' ) then 				qualifier = mw.ustring.match( subRule, '^([Pp]%d+)%s*:?' ) 				value = mw.ustring.match( subRule, ':%s*([^%[%],:]+)$' ) 				table.insert( commands, { 					type = 'qualifier', 					qualifier = qualifier, 					value = value, 					inversed = isInversed 				} )  			-- p123[p456!:q789] 			elseif mw.ustring.match( subRule, '^[Pp]%d+%s*!:%s*[^%[%],:]+$' ) then 				qualifier = mw.ustring.match( subRule, '^([Pp]%d+)%s*!:?' ) 				value = mw.ustring.match( subRule, '!:%s*([^%[%],:]+)$' ) 				table.insert( commands, { 					type = 'qualifier', 					qualifier = qualifier, 					value = value, 					inversed = not isInversed 				} )  			-- p123[q456] 			elseif mw.ustring.match( subRule, '^[Qq]%d+$' ) then 				value = mw.ustring.match( subRule, '^[Qq]%d+' ) 				table.insert( commands, { 					type = 'value', 					value = value, 					inversed = isInversed 				} )  			else 				throwError( 'cant-parse-condition' ) 			end 		end  		if #commands then 			table.insert( rules, commands )  		end  		rawRule = mw.ustring.match( selectorsString, rawRulePattern, initPos ) 	end 	 	return rules end   --[[   Intercept statements with selector rules     Input: statements table, selector rules   Output: filtered statements table ]] function p.applyRules( claims, rules ) 	for i, subRules in ipairs( rules ) do 		local newClaims = {} 		for j, rule in ipairs( subRules ) do 			if rule['type'] == 'rank' then 				table.insert( newClaims, p.filterByRank( claims, rule['value'], rule['inversed'] ) ) 			elseif rule['type'] == 'language' then 				table.insert( newClaims, p.filterByLanguage( claims, rule['value'], rule['inversed'] ) ) 			elseif rule['type'] == 'unit' then 				table.insert( newClaims, p.filterByUnit( claims, rule['value'], rule['inversed'] ) ) 			elseif rule['type'] == 'position' then 				table.insert( newClaims, p.filterByPosition( claims, rule['value'], rule['inversed'] ) ) 			elseif rule['type'] == 'qualifier' then 				table.insert( newClaims, p.filterByQualifier( claims, rule['qualifier'], rule['value'], rule['inversed'] ) ) 			elseif rule['type'] == 'qualifier_min' then 				table.insert( newClaims, p.filterUtterByQualifier( claims, rule['qualifier'], true ) ) 			elseif rule['type'] == 'qualifier_max' then 				table.insert( newClaims, p.filterUtterByQualifier( claims, rule['qualifier'], false ) ) 			elseif rule['type'] == 'value' then 				table.insert( newClaims, p.filterByValue( claims, rule['value'], rule['inversed'] ) ) 			elseif rule['type'] == 'value_min' then 				table.insert( newClaims, p.filterUtter( claims, true ) ) 			elseif rule['type'] == 'value_max' then 				table.insert( newClaims, p.filterUtter( claims, false ) ) 			end 		end 		claims = {}  		--[[ 			Merge all claims 			TODO: It's not good 		]] 		for j, newSubClaims in ipairs( newClaims ) do 			for k, newClaim in ipairs( newSubClaims ) do 				local isNew = true 				for l, oldClaim in ipairs( claims ) do 					if oldClaim['id'] == newClaim['id'] then 						isNew = false 						break 					end 				end 				if isNew then 					table.insert( claims, newClaim ) 				end 			end 		end 	end  	return claims end   --[[   Filter statements by rank     Input: claims table, rank value, inversion   Output: filtered statements table ]] function p.filterByRank( claims, rank, inversed ) 	if not inversed then 		inversed = false 	end  	if not rank then 		rank = 'best' 	end 	 	-- Check if rank value is valid 	local isValidRank = false 	for i, validRank in ipairs( validRanks ) do 		if rank == validRank then 			isValidRank = true 			break 		end 	end 	if not isValidRank then 		throwError( 'rank-not-valid' ) 	end  	-- Find the best rank 	if rank == 'best' then 		rank = 'normal' -- default rank (don't use deprecated even if it's no more claims)  		-- If we have at least one preferred rank, mark it as best 		for i, statement in pairs( claims ) do 			if (statement.rank == 'preferred') then 				rank = 'preferred' 				break 			end 		end 	end  	local resultClaims = {}; 	for i, statement in pairs( claims ) do 		if ( statement.rank == rank ) ~= inversed then 			table.insert( resultClaims, statement ) 		end 	end  	return resultClaims end   --[[   Filter statements by language of value     Input: claims table, language, inversion   Output: filtered statements table ]] function p.filterByLanguage( claims, language, inversed ) 	if not inversed then 		inversed = false 	end  	local resultClaims = {} 	local mulStatement = {} 	for i, statement in ipairs( claims ) do 		isMatchLanguage = false 		if statement['mainsnak'] 				and statement['mainsnak']['datavalue'] 				and statement['mainsnak']['datavalue']['value'] 				and statement['mainsnak']['datavalue']['value']['language'] then 				 			if statement['mainsnak']['datavalue']['value']['language'] == language then 				isMatchLanguage = true 			end 			if statement['mainsnak']['datavalue']['value']['language'] == 'mul' then 				mulStatement = statement 			end 		end 		if isMatchLanguage ~= inversed then 			table.insert( resultClaims, statement ) 		end 	end 	 	if next(resultClaims) == nil and next(mulStatement) ~= nil then 		-- if specific language is not found, but there is Q20923490 value 		table.insert( resultClaims, mulStatement ) 	end  	return resultClaims end   --[[   Filter statements by unit of value     Input: claims table, unit, inversion   Output: filtered statements table ]] function p.filterByUnit( claims, unit, inversed ) 	if not inversed then 		inversed = false 	end  	unit = 'http://www.wikidata.org/entity/' .. string.upper( unit )  	local resultClaims = {} 	for i, statement in ipairs( claims ) do 		isMatchUnit = false 		if statement['mainsnak'] 				and statement['mainsnak']['datavalue'] 				and statement['mainsnak']['datavalue']['value'] 				and statement['mainsnak']['datavalue']['value']['unit'] 				and statement['mainsnak']['datavalue']['value']['unit'] == unit then 			isMatchUnit = true 		end 		if isMatchUnit ~= inversed then 			table.insert( resultClaims, statement ) 			break 		end 	end  	return resultClaims end   --[[   Filter statements by position     Input: claims table, position, inversion   Output: filtered statements table ]] function p.filterByPosition( claims, position, inversed ) 	if not inversed then 		inversed = false 	end  	local resultClaims = {}; 	for statementPosition, statement in ipairs( claims ) do 		if ( statementPosition == tonumber( position ) ) ~= inversed then 			table.insert( resultClaims, statement ) 			break 		end 	end  	return resultClaims end   --[[   Filter statements by qualifier existance or it's value     Input: claims table, ID of qualifier's property, qualifier's value, inversion   Output: filtered statements table ]] function p.filterByQualifier( claims, qualifierId, value, inversed ) 	if not inversed then 		inversed = false 	end  	qualifierId = string.upper( qualifierId )  	local resultClaims = {} 	for i, statement in ipairs( claims ) do 		if statement['qualifiers'] and statement['qualifiers'][qualifierId] then 			if value == nil then 				if ( #statement['qualifiers'][qualifierId] > 0 ) ~= inversed then 					table.insert( resultClaims, statement ) 				end 			else 				local isQualifierFound = false 				for j, qualifier in ipairs( statement['qualifiers'][qualifierId] ) do 					if qualifier['datavalue'] then 						local qualifierValue = qualifier['datavalue']['value'] 						if qualifier['datavalue']['type'] == 'wikibase-entityid' then 							qualifierValue = qualifierValue.id 							value = string.upper( value ) 						end 			 						if qualifierValue == value then 							isQualifierFound = true 							break 						end 					end 				end 				if isQualifierFound ~= inversed then 					table.insert( resultClaims, statement ) 				end 			end 		elseif inversed then 			table.insert( resultClaims, statement ) 		end 	end 	return resultClaims end   --[[   Filter statements by it's values     Input: claims table, value, inversed   Output: filtered statements table ]] function p.filterByValue( claims, value, inversed ) 	inversed = inversed or false  	local resultClaims = {} 	for i, statement in ipairs( claims ) do 		local statementValue 		if statement['mainsnak']  		and statement['mainsnak']['datavalue']  		and statement['mainsnak']['datavalue']['type'] 		then  			statementValue = statement['mainsnak']['datavalue']['value'] 			if statement['mainsnak']['datavalue']['type'] == 'quantity' then 				statementValue = statementValue.amount 			end 			if statement['mainsnak']['datavalue']['type'] == 'time' then 				statementValue = statementValue.time 			end 			if statement['mainsnak']['datavalue']['type'] == 'wikibase-entityid' then 				statementValue = statementValue.id 				value = string.upper( value ) 			end 		end  		if ( statementValue == value ) ~= inversed then 			table.insert( resultClaims, statement ) 		end 	end  	return resultClaims end   --[[   Find a statement with minimum or maximum value     Input: claims table, asc, inversed   Output: filtered statements table ]] function p.filterUtter( claims, asc, inversed ) 	local resultValue = nil 	for i, statement in ipairs( claims ) do 		local statementValue 		if statement['mainsnak'] and 			statement['mainsnak']['datavalue'] and 			statement['mainsnak']['datavalue']['type'] 		then 			statementValue = statement['mainsnak']['datavalue']['value'] 			if statement['mainsnak']['datavalue']['type'] == 'quantity' then 				statementValue = statementValue.amount 			end 			if statement['mainsnak']['datavalue']['type'] == 'time' then 				statementValue = statementValue.time 			end 			if statement['mainsnak']['datavalue']['type'] == 'wikibase-entityid' then 				statementValue = statementValue.id 			end  			if not resultValue or ( statementValue < resultValue ) == asc then 				resultValue = statementValue 			end 		end 	end  	mw.logObject( resultValue, 'resultValue' ) 	return p.filterByValue( claims, resultValue, inversed ) end   --[[   Find a statement with minimum or maximum qualifier value     Input: claims table, qualifierId, asc   Output: filtered statements table ]] function p.filterUtterByQualifier( claims, qualifierId, asc ) 	qualifierId = string.upper( qualifierId )  	local resultValue = nil 	local resultStatement = nil 	for i, statement in ipairs( claims ) do 		if not statement['qualifiers'] and not statement['qualifiers'][qualifierId] then 			if resultStatement == nil then 				resultStatement = statement 			end 		else 			for _, qualifier in ipairs( statement['qualifiers'][qualifierId] ) do 				if qualifier['datavalue'] then 					local qualifierValue = qualifier['datavalue']['value'] 					if qualifier['datavalue']['type'] == 'quantity' then 						qualifierValue = qualifierValue.amount 					end 					if qualifier['datavalue']['type'] == 'time' then 						qualifierValue = qualifierValue.time 					end 					if qualifier['datavalue']['type'] == 'wikibase-entityid' then 						qualifierValue = qualifierValue.id 					end  					if not resultValue or ( qualifierValue < resultValue ) == asc then 						resultStatement = statement 						resultValue = qualifierValue 					end 				end 			end 		end 	end  	return { resultStatement } end   return p