Модуль:WikidataSelectors: различия между версиями
Admin (обсуждение | вклад) м (1 версия импортирована) |
Admin (обсуждение | вклад) м (1 версия импортирована) |
||
(не показана 1 промежуточная версия 1 участника) | |||
Строка 27: | Строка 27: | ||
local p = {} | 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 | Input: statements table, selector string |
Текущая версия от 18:34, 21 июня 2023
Внимание! Это один из самых используемых модулей. |
Этот модуль выбирает из списка утверждений Викиданных для определённого свойства те, которые удовлетворяют указанным условиям.
- Для идентификаторов Викиданных, начинающихся с «P» или «Q» регистр не имеет значения. Строки регистрозависимы.
- До и после операторов вы можете вставлять пробелы.
Этот модуль не предназначен для использования напрямую на страницах и в шаблонах, он расширяет синтаксис модуля Wikidata и шаблона {{wikidata}}:
{{ wikidata | P123[ P456:Q789 ] }}
.
УсловияПравить
Синтаксис | Пример | Описание |
---|---|---|
property[ position ]
|
P18[ 1 ]
|
Только утверждение на позиции position . Индексы начинаются с 1.
|
property[ rank:value ]
|
P161[ rank:preferred ]
|
Фильтр по приоритету. Возможные значения:
|
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 ]
|
Эквивалент логического И . Условия выполняются одно за другим. Порядок условий важен:
могут вернуть различный результат. |
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