Модуль:WikidataSelectors/пісочниця
Зовнішній вигляд
| Ця сторінка — пісочниця для модуля Модуль:WikidataSelectors (різн.). Див. також супутню сторінку для тестів. |
| Цей модуль Lua використовується на близько 1 110 000 сторінках або близько 22% всіх сторінок. Для уникнення великомасштабних збоїв та непотрібного навантаження на сервер, будь-які зміни спочатку потрібно перевірити на підсторінках /пісочниці та /тестів цього модуля, або у вашій пісочниці модуля. Потім перевірені зміни можуть бути впроваджені на цій сторінці єдиним редагуванням. Будь ласка, обговорюйте будь-які зміни на сторінці обговорення перед їхнім впровадженням. |
Цей модуль обирає зі списку тверджень Вікіданих для певної властивості те, яку відповідає вказаними вимогам.
- Для ідентифікаторів Вікіданих, які починаються з «P» або «Q», регістр не має значення. Рядки залежать від регістру.
- До і після операторів ви можете вставляти пробіли.
Цей модуль не призначений для використання напряму на сторінках і в шаблонах, він розширює синтаксис модуля Wikidata і шаблона {{wikidata}}:
{{ wikidata | p123[ p456:q789 ] }}.
Умови
[ред. код]| Синтаксис | Приклад | Опис |
|---|---|---|
| Позиція | ||
property[ position ]
|
p18[ 1 ]
|
Тільки твердження на позиції position. Індекси починаються з 1.
|
P18[ !1 ]
|
Усі твердження окрім першого. | |
| Ранги | ||
property[ rank:value ]
|
p161[ rank:preferred ]
|
Фільтр за пріоритетом. Можливі значення:
|
| Значення | ||
property[ language:value ]
|
p1559[ language:uk ]
|
Фільтр за мовою для багатомовних полів. Якщо значення вказаною мовою не знайдено, але вказано mul (кілька мов), то виводиться це значення.
|
P1559[ !language:uk ]
|
Усі мови окрім вказаної. | |
property[ min ]
|
P123[ min ]
|
Обирає твердження з мінімальним значенням. Може повернути кілька однакових значень. |
property[ max ]
|
P123[ max ]
|
Обирає твердження з максимальним значенням. Може повернути кілька однакових значень. |
property[ unit:value ]
|
p2043[ unit:q828224 ]
|
Фільтр за одиницею виміру для кількісних полів. |
P2043[ !unit:Q828224 ]
|
Усі одиниці виміру окрім вказаної. | |
property[ value ]
|
P123[ Q456 ]
|
Перевірка, що значення властивості дорівнює value.
|
P123[ !Q456 ]
|
Усі значення окрім вказаного елемента. | |
| Кваліфікатори | ||
property[ qualifier ]
|
p123[ p456 ]
|
Перевірка на наявність кваліфікатора з ID qualifier з будь-яким значенням.
|
P123[ !P456 ]
|
Тільки твердження без вказаного кваліфікатора. | |
property[ qualifier:value ]
|
p123[ p456:789 ]
|
Перевірка, що значення кваліфікатора з ID qualifier дорівнює value. Вказується або чисте значення, або номер елемента QID.
|
P123[ P456:Q789 ]
| ||
P123[ P456!:789 ]
|
Будь-які твердження з властивістю як кваліфікатора, окрім вказаного елемента. | |
P123[ P456!:Q789 ]
| ||
P123[ !P456:789 ]
|
Будь-які твердження, крім тих, в яких як кваліфікатором вказано конкретне значення. | |
P123[ !P456:Q789 ]
| ||
property[ min:qualifier ]
|
P123[ min:P585 ]
|
Вибір твердження з мінімальним значенням кваліфікатора з ID qualifier.
|
property[ max:qualifier ]
|
P123[ max:P585 ]
|
Вибір твердження з максимальним значенням кваліфікатора з ID qualifier.
|
Комбіновані умови
[ред. код]| Синтаксис | Приклад | Опис |
|---|---|---|
property[ selector1, selector2, … ]
|
p348[ p548:q2122918, p548:q3295609 ]
|
Еквівалент логічного АБО. Твердження, які відповідають різним умовам, об'єднуються в один список.
|
property[ selector1 ][ selector2 ][ … ]
|
p166[ p111!:1946 ][ p111!:1972 ]
|
Еквівалент логічного АБО. Умова виконується одна за іншою. Порядок умов важливий:
можуть повернути різний результат. |
Підтримувані типи даних
[ред. код]Селектори працюють з наступними типами даних Wikibase:
- wikibase-entityid (Q-значення)
- quantity (кількісні значення)
- time (часові значення)
- monolingualtext (одномовний текст)
Документація вище включена з Модуль:WikidataSelectors/документація. (ред. | історія)
Дописувачі можуть експериментувати на підсторінках пісочниці (ред. | різн.) та протестувати зміни (ред.) цього модуля.
Будь ласка, додавайте категорії до підсторінки /документація. Підсторінки цієї сторінки.
Дописувачі можуть експериментувати на підсторінках пісочниці (ред. | різн.) та протестувати зміни (ред.) цього модуля.
Будь ласка, додавайте категорії до підсторінки /документація. Підсторінки цієї сторінки.
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