Модуль:WDBase

Версия от 11:18, 28 мая 2023; ru>D6194c-1cc (Утверждения, которым не был присвоен номер в упорядоченном массиве должны автоматически попадать в пропуски упорядоченного массива, оставшиеся – в конец)
(разн.) ← Предыдущая версия | Текущая версия (разн.) | Следующая версия → (разн.)

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

require('strict')

local p = {}

p.P_NAME_WORK_LANG = 'P407'
p.P_EDITION_FOR = 'P629'
p.P_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)
	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 neither ordered or unordered, so
	-- all other cases must be fixed in Wikidata, and the below code 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

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)
	local datavalue = snak.datavalue
	if not datavalue then
		return nil
	end

	local entity, value, unit, valueLang

	if datavalue.type == 'wikibase-entityid' then
		entity = datavalue.value.id
		if lang then
			value = mw.wikibase.getLabelByLang(entity, lang)
			valueLang = lang
		else
			value, valueLang = mw.wikibase.getLabelWithLang(entity)
		end
	elseif datavalue.type == 'monolingualtext' then
		value = datavalue.value.text
		valueLang = datavalue.value.language
	elseif datavalue.type == 'time' then
		value = p.dateFromDatavalue(datavalue)
	elseif datavalue.type == 'quantity' then
		value = tonumber(datavalue.value.amount)
		unit = datavalue.value.unit
		if unit then
			unit = unit:match('^.*/([^/]+)')
		end
	else
		value = datavalue.value
	end

	return { value = value, lang = valueLang, entity = entity, unitEntity = unit }
end

function p.dataByStatement(statement, lang, novalueQualifier)
	local snak = statement.mainsnak
	if not snak then
		return nil
	end
	if not snak.datavalue then
		if not novalueQualifier then
			novalueQualifier = p.P_NAMED_AS
		end
		if statement.qualifiers then
			local qualifierSnaks = statement.qualifiers[novalueQualifier]
			if qualifierSnaks then
				return p.dataBySnak(qualifierSnaks[1], lang)
			end
		end
		return nil
	end

	return p.dataBySnak(snak, lang)
end

function p.dataByEntity(entity, lang)
	local value, valueLang
	if lang then
		value = mw.wikibase.getLabelByLang(entity, lang)
		valueLang = lang
	else
		value, valueLang = mw.wikibase.getLabelWithLang(entity)
	end

	return { value = value, lang = valueLang, entity = entity }
end

return p