Модуль:WDBase
Модуль-библиотека с базовыми функциями для работы с Викиданными. Содержит функции для получения утверждений, фильтрации, получения значений утверждений, приведения утверждений к текстовому виду.
Модуль используется другим, более высокоуровневым модулем-библиотекой Модуль:WDCommon. на модуле WDBase основаны модули Модуль:WDBackend и Модуль:WDFormat.
Функции
В функциях используются понятия значения (value) и текстового представления (text). Значение может быть текстом, таблицей с датой или идентификатором элемента Викиданных. Текстовое представление предполагает преобразование значения в текстовый вид, которы уже можно использовать для отображения.
Получаемые из Викиданных данные представлены в специальном сериализованном формате. В этом формате есть понятия утверждения (statement) и снека (snak). Утверждения — это значения для заданного свойства с дополнительными уточняющими данными и источниками. Снеки — часть утверждений, у которых есть только значение. Каждое утверждение может иметь в себе основной снек и квалификаторы. Данные в квалификаторах представлены только лишь снеками.
Работа с элементом Викиданных:
- statements — получить массив утверждений для указанного свойства.
- statementsByProperties — получить массив утверждений для указанных свойств, скомбинировав их (учитываются порядковые номера, указанные через квалификаторы).
- value — получить значение свойства элемента Викиданных (берётся первое утверждение).
- text — получить текстовое представление свойства элемента Викиданных (берётся первое утверждение).
- wikilink — получить викиссылку на статью по элементу Викиданных.
- resolveParent — получить основной элемент Викиданных (издание, на котором основано текущее издание).
- instanceOf — проверить является ли элемент Викиданных частным случаем одной из указанных сущностей.
- valueByQualifier — получить значение утверждения, которому соответствует квалификатор с указанным свойством и значением.
Работа с утверждениями:
- valueByStatement — получить значение из утверждения.
- textByStatement — привести утверждение к текстовому виду.
- dataByStatement — получить таблицу с данными в удобном для использования формате из утверждения.
- statementQualifier — получить снек квалификатора по идентификатору свойства.
- tryFilterStatementsByLang — попытаться отфильтровать утверждения по языку. Если заданного языка не найдено, возвращает все утверждения. Если передан аргумент forceLang, то не возвращает утверждений, если по указанному языку они не найдены.
- filterStatementsByUnit — отфильтровать утверждения по QID единицы измерения.
Работа со снеками:
- valueBySnak — получить значение из снека.
- dataBySnak — получить таблицу с данными в удобном для использования формате из снека.
- tryFilterSnaksByLang — попытаться отфильтровать снеки по языку. Если заданного языка не найдено, возвращает все снеки.
Работа с типами:
- dateFromDatavalue — получить таблицу с распарсенной датой из данных снека.
- dateToStr — преобразовать таблицу с датой в строку.
Внесение изменений
При исправлении ошибки, пожалуйста, сначала добавьте тест, который будет проваливаться из-за обнаруженной ошибки, и только затем вносите исправление. При внесении исправления проверьте, чтобы все тесты проходили. Вносить исправление можно только, если оно не ломает другие тесты.
Добавление нового функционала рекомендуется делать у себя в песочнице, скопировав в неё модуль. В правке копирования необходимо указать тот факт, что делается копирование, и сделать ссылку на оригинальный модуль в виде викитекста. При добавлении нового функционала сначала желательно добавить тест на этот функционал, затем добавить сам функционал, убедившись, что все тесты при этом проходят.
Тесты
1 тест провалился.
Название | Ожидается | Фактически | |
---|---|---|---|
test_dataByStatement | Модуль:WDBase/testcases:50: More unit symbols expected.
Failed to assert that false is true | ||
test_dateFromDatavalue | |||
test_filterStatementsByUnit | |||
test_instanceOf | |||
test_resolveParent | |||
test_searchStatementByValue | |||
test_statements | |||
test_statementsByProperties | |||
test_text | |||
test_tryFilterSnaksByLang | |||
test_tryFilterStatementsByLang | |||
test_value | |||
test_wikilink |
План разработки
План работ:
Пока пусто.
|
-- The documentation for the module should be written in English in the LuaDoc
-- format to ease updating the module between different Wikimedia projects.
-- The localized version of the documentation can be added to the documentation
-- page of the module.
require('strict')
local p = {}
p.P_NAME_WORK_LANG = 'P407'
p.P_EDITION_FOR = 'P629'
p.P_OBJ_NAMED_AS = 'P1932'
p.P_ORDINAL_NUM = 'P1545'
p.Q_MULTILANG = 'Q20923490'
local defaultLangObj = mw.getContentLanguage()
local defaultLang = defaultLangObj:getCode()
local fallbackLang = 'en'
function p.statements(entity, property, cache)
if cache then
local entityCache = cache[entity]
if entityCache then
local props = entityCache.props or entityCache
local statements = props[property]
if statements then
return statements
end
end
end
local statements = mw.wikibase.getBestStatements(entity, property)
if not statements or next(statements) == nil then
return nil
end
return statements
end
function p.statementsByProperties(entity, properties)
local orderedStatements = {}
local statements = {}
for _, property in ipairs(properties) do
local currStatements = mw.wikibase.getBestStatements(entity, property)
if currStatements and next(currStatements) ~= nil then
for _, statement in ipairs(currStatements) do
local num
local qualifiers = statement.qualifiers
if qualifiers then
local qualifierSnaks = qualifiers[p.P_ORDINAL_NUM]
if qualifierSnaks then
local datavalue = qualifierSnaks[1].datavalue
if datavalue and datavalue.type == 'string' then
num = tonumber(datavalue.value)
end
end
end
if num then
orderedStatements[num] = statement
else
table.insert(statements, statement)
end
end
end
end
if next(orderedStatements) == nil then
return statements
elseif next(statements) == nil then
return orderedStatements
end
-- We assume that all statements must be either ordered or unordered, so
-- all other cases must be fixed in Wikidata, and the code below allowed
-- to be slow (rare exceptional cases)
local j = 1
for i = 1, table.getn(orderedStatements) do
if orderedStatements[i] == nil then
orderedStatements[i] = statements[j]
j = j + 1
if statements[j] == nil then
break;
end
end
end
for i = j, table.getn(statements) do
table.insert(statements, statements[i])
end
return orderedStatements
end
function p.valueBySnak(snak)
local datavalue = snak.datavalue
if not datavalue then
return nil
end
if datavalue.type == 'monolingualtext' then
return datavalue.value.text, datavalue.value.language
elseif datavalue.type == 'wikibase-entityid' then
return datavalue.value.id
elseif datavalue.type == 'string' then
return datavalue.value
elseif datavalue.type == 'time' then
return p.dateFromDatavalue(datavalue)
elseif datavalue.type == 'quantity' then
local unitEntity
if datavalue.value.unit then
unitEntity = datavalue.value.unit:gsub('^.+/([^/]+)$', '%1')
end
return datavalue.value.amount, unitEntity
end
end
function p.valueByStatement(statement)
local snak = statement.mainsnak
if not snak then
return nil
end
return p.valueBySnak(snak)
end
function p.textByStatement(statement, lang)
local datavalue = statement.mainsnak.datavalue
if not datavalue then
return nil
end
if datavalue.type == 'monolingualtext' then
return datavalue.value.text, datavalue.value.language
elseif datavalue.type == 'wikibase-entityid' then
local entity = datavalue.value.id
if lang then
return mw.wikibase.getLabelByLang(entity, lang), lang
else
return mw.wikibase.getLabelWithLang(entity)
end
elseif datavalue.type == 'string' then
return datavalue.value
elseif datavalue.type == 'time' then
return p.dateToStr(p.dateFromDatavalue(datavalue))
elseif datavalue.value.amount == 'quantity' then
local unit
local valueLang
if lang then
unit = mw.wikibase.getLabelByLang(entity, lang)
valueLang = lang
else
unit, valueLang = mw.wikibase.getLabelWithLang(entity)
end
return datavalue.value.amount .. ' ' .. datavalue.value.unit, valueLang
end
end
function p.value(entity, property)
local statements = p.statements(entity, property)
if not statements then
return nil
end
return p.valueByStatement(statements[1])
end
function p.text(entity, property, lang)
local statements = p.statements(entity, property)
if not statements then
return nil
end
return p.textByStatement(statements[1], lang)
end
function p.valueByQualifier(entity, property, qualifier, qualifierValue)
local statements = p.statements(entity, property)
if not statements then
return nil
end
for _, statement in ipairs(statements) do
if statement.mainsnak and statement.qualifiers then
local currQualifiers = statement.qualifiers[qualifier]
if currQualifiers and currQualifiers[1] then
local currQualifier = currQualifiers[1]
local value = p.valueBySnak(currQualifier)
if value == qualifierValue then
return p.valueByStatement(statement)
end
end
end
end
return nil
end
function p.tryFilterSnaksByLang(snaks, lang)
if not lang then
lang = defaultLang
end
local suitableValueInCurrLang = {}
local suitableValueInFallbackLang = {}
local suitableValue = {}
for _, snak in ipairs(snaks) do
local datavalue = snak.datavalue
if datavalue.type == 'monolingualtext' then
if datavalue.value.language == lang then
table.insert(suitableValue, snak)
elseif not suitableValueInCurrLang then
if datavalue.value.language == fallbackLang then
table.insert(suitableValueInCurrLang, snak)
end
elseif not suitableValueInFallbackLang then
if datavalue.value.language == fallbackLang then
table.insert(suitableValueInFallbackLang, snak)
end
end
elseif datavalue.type == 'string' and not suitableValue then
table.insert(suitableValue, snak)
end
end
if next(suitableValue) ~= nil then
return suitableValue
elseif next(suitableValueInCurrLang) ~= nil then
return suitableValueInCurrLang
elseif next(suitableValueInFallbackLang) ~= nil then
return suitableValueInFallbackLang
else
return snaks
end
end
function p.tryFilterStatementsByLang(statements, lang, forceLang)
if not lang then
lang = defaultLang
end
local suitableValueInCurrLang = {}
local suitableValueInFallbackLang = {}
local suitableValue = {}
for _, statement in ipairs(statements) do
local datavalue = statement.mainsnak.datavalue
if datavalue.type == 'monolingualtext' then
if datavalue.value.language == lang then
table.insert(suitableValue, statement)
elseif not forceLang then
if not suitableValueInCurrLang then
if datavalue.value.language == fallbackLang then
table.insert(suitableValueInCurrLang, statement)
end
elseif not suitableValueInFallbackLang then
if datavalue.value.language == fallbackLang then
table.insert(suitableValueInFallbackLang, statement)
end
end
end
elseif datavalue.type == 'string' and not suitableValue then
table.insert(suitableValue, statement)
end
end
if next(suitableValue) ~= nil then
return suitableValue
elseif next(suitableValueInCurrLang) ~= nil then
return suitableValueInCurrLang
elseif next(suitableValueInFallbackLang) ~= nil then
return suitableValueInFallbackLang
elseif not forceLang then
return statements
else
return nil
end
end
function p.filterStatementsByUnit(statements, unit)
local filteredStatements = {}
for _, statement in ipairs(statements) do
local snak = statement.mainsnak
if snak then
local datavalue = snak.datavalue
if datavalue and datavalue.type == 'quantity' then
local currUnit = datavalue.value.unit
if currUnit then
currUnit = currUnit:match('^.*/([^/]+)')
if currUnit == unit then
table.insert(filteredStatements, statement)
end
end
end
end
end
if next(filteredStatements) == nil then
return nil
end
return filteredStatements
end
function p.tryTextByLang(entity, property, lang)
local statements = p.statements(entity, property)
if not statements then
return nil
end
statements = p.tryFilterStatementsByLang(statements, lang, false)
return p.textByStatement(statements[1])
end
function p.textByLang(entity, property, lang)
local statements = p.statements(entity, property)
if not statements then
return nil
end
statements = p.tryFilterStatementsByLang(statements, lang, true)
if not statements then
return nil
end
return p.textByStatement(statements[1])
end
function p.searchStatementByValue(statements, value)
for _, statement in ipairs(statements) do
local datavalue = statement.mainsnak.datavalue
if datavalue.type == 'string' then
if datavalue.value == value then
return statement
end
elseif datavalue.type == 'monolingualtext' then
if datavalue.value.text == value then
return statement
end
elseif datavalue.type == 'wikibase-entityid' then
if datavalue.value.id == value then
return statement
end
elseif datavalue.type == 'quantity' then
if tonumber(datavalue.value.amount) == value then
return statement
end
end
end
end
function p.statementByValue(entity, property, value)
local statements = p.statements(entity, property)
if not statements then
return nil
end
return p.searchStatementByValue(statements, value)
end
function p.statementQualifier(statement, quailiferProperty)
if not statement.qualifiers then
return nil
end
local qualifiers = statement.qualifiers[quailiferProperty]
if not qualifiers then
return nil
end
return p.valueBySnak(qualifiers[1])
end
function p.resolveParent(entity)
local statements = p.statements(entity, p.P_EDITION_FOR)
if not statements then
return nil
end
local datavalue = statements[1].mainsnak.datavalue
if datavalue.type == 'wikibase-entityid' then
return datavalue.value.id
end
return nil
end
--- Get a wikilink to a page describing the given Wikidata entity
-- The function returns a wikilink to a page corresponding to the Wikidata
-- entity. The Wikilink will lead to the page in the local wiki project.
-- If the text of the wikilink is not specified, then the label
-- of the Wikidata entity will be used.
-- @param entity The QID of the Wikidata entity.
-- @text text The text of the resulting wikilink (optional).
-- @returns The code of the resulting Wikilink.
--
function p.wikilink(entity, text)
local title = mw.wikibase.getSitelink(entity)
if not title then
return nil
end
local wikilink
if text and text ~= title then
wikilink = '[[' .. title .. '|' .. text .. ']]'
else
wikilink = '[[' .. title .. ']]'
end
return wikilink, entity
end
function p.dateFromDatavalue(datavalue)
if datavalue.type ~= 'time' then
return nil
end
local precision = datavalue.value.precision
local date = {}
date.timestamp = datavalue.value.time
local sign
sign, date.year, date.month, date.day, date.hour, date.minute, date.second = string.match(date.timestamp, '([+-])(%d+)-(%d+)-(%d+)T(%d+):(%d+):(%d+)')
date.year = tonumber(date.year)
date.month = tonumber(date.month)
date.day = tonumber(date.day)
date.hour = tonumber(date.hour)
date.minute = tonumber(date.minute)
date.second = tonumber(date.second)
if sign == '-' then
date.year = -date.year
end
if precision < 14 then
date.second = nil
end
if precision < 13 then
date.minute = nil
end
if precision < 12 then
date.hour = nil
end
if precision < 11 then
date.day = nil
end
if precision < 10 then
date.month = nil
end
if precision == 8 then
date.decade = date.year
end
if precision == 7 then
date.century = date.year / 100
end
if precision == 6 then
date.millenium = date.year / 1000
end
return date
end
function p.dateToStr(date, lang)
local langObj = mw.getLanguage(lang)
if date.second then
return langObj:formatDate('d xg Y H:i:s', date.timestamp)
elseif date.minute then
return langObj:formatDate('d xg Y H:i', date.timestamp)
elseif date.hour then
return langObj:formatDate('d xg Y H:00', date.timestamp)
elseif date.day then
return langObj:formatDate('d xg Y', date.timestamp)
elseif date.month then
return langObj:formatDate('F Y', date.timestamp)
elseif date.year then
return langObj:formatDate('Y', date.timestamp)
end
return date.timestamp
end
function p.instanceOf(entity, property, ofEntities)
local statements = p.statements(entity, property)
if not statements then
return nil
end
for i=1, table.getn(statements) do
local statement = statements[i]
if statement.mainsnak then
local datavalue = statement.mainsnak.datavalue
if datavalue and datavalue.type == 'wikibase-entityid' then
local currEntity = datavalue.value.id
for j=1, table.getn(ofEntities) do
if currEntity == ofEntities[i] then
return currEntity
end
end
end
end
end
return nil
end
function p.dataBySnak(snak, lang, cache)
local datavalue = snak.datavalue
if not datavalue then
return nil
end
local data = {}
if datavalue.type == 'wikibase-entityid' then
data.entity = datavalue.value.id
if lang then
local cachedFound = false
if cache then
local cached = cache[data.entity]
if cached and cached.label then
local label = cached.label[self.lang]
if label then
cachedFound = true
data.value = label
end
end
end
if not cachedFound then
data.value = mw.wikibase.getLabelByLang(data.entity, lang)
end
data.lang = lang
data.fromLabel = true
else
data.value, data.lang = mw.wikibase.getLabelWithLang(data.entity)
data.fromLabel = true
end
elseif datavalue.type == 'monolingualtext' then
data.value = datavalue.value.text
data.lang = datavalue.value.language
elseif datavalue.type == 'time' then
data.value = p.dateFromDatavalue(datavalue)
elseif datavalue.type == 'quantity' then
data.value = tonumber(datavalue.value.amount)
data.unitEntity = datavalue.value.unit
if data.unitEntity then
data.unitEntity = data.unitEntity:match('^.*/([^/]+)')
end
else
data.value = datavalue.value
end
return data
end
function p.dataByStatement(statement, lang, cache, novalueQualifier)
local snak = statement.mainsnak
if not snak then
return nil
end
if not snak.datavalue then
if not novalueQualifier then
novalueQualifier = p.P_OBJ_NAMED_AS
end
if statement.qualifiers then
local qualifierSnaks = statement.qualifiers[novalueQualifier]
if qualifierSnaks then
return p.dataBySnak(qualifierSnaks[1], lang, cache)
end
end
return nil
end
return p.dataBySnak(snak, lang, cache)
end
function p.dataByEntity(entity, lang, cache)
local value, valueLang
if lang then
local cachedFound = false
if cache then
local cached = cache[entity]
if cached and cached.label then
local label = cached.label[lang]
if label then
cachedFound = true
value = label
end
end
end
if not cachedFound then
value = mw.wikibase.getLabelByLang(entity, lang)
end
valueLang = lang
else
value, valueLang = mw.wikibase.getLabelWithLang(entity)
end
return { value = value, lang = valueLang, entity = entity, fromLabel = true }
end
return p