Modulo:Wikidata/Filterers

El Vikipedio, la libera enciklopedio

Dokumentado por ĉi tiu modulo povas esti kreata ĉe Modulo:Wikidata/Filterers/dokumentado

require('strict')

local p = {}

local lib = require 'Modulo:Wikidata/lib'

local function in_array(value, array)
	for _, val in ipairs(array) do
		if val == value then
			return true
		end
	end
	return false
end

local function checkLimit(array, limit)
	local limit = limit and tonumber(limit)
	if limit then
		return #array >= limit
	end
	return true
end

local function applyLimit(array, limit)
	local limit = limit and tonumber(limit)
	while limit and #array > limit do
		table.remove(array)
	end
end

local function isInLanguage(snak, langs)
	local langs = lib.textToTable(langs)
	if snak.datatype ~= 'monolingualtext' then
		return error(lib.raiseInvalidDatatype('isInLanguage', snak.datatype, 'monolingualtext'))
	else
		return lib.IsSnakValue(snak) and in_array(snak.datavalue.value.language, langs)
	end
end

local function hasSnaktype(snak, somevalue, novalue)
	local snaktype = snak.snaktype
	if snaktype == 'somevalue' then
		return somevalue or false
	elseif snaktype == 'novalue' then
		return novalue or false
	end
	return lib.IsSnakValue(snak)
end

local function hasTarget(snak, target)
	local Formatters = require 'Modulo:Wikidata/Formatters'
	return tostring(Formatters.getRawValue(snak)) == tostring(target)
end

local function hasQualifier(statement, prop, value)
	if statement.qualifiers then
		prop = prop:upper()
		for _, snak in ipairs(statement.qualifiers[prop] or {}) do
			if not value or hasTarget(snak, value) then
				return true
			end
		end
	end
	return false
end

local function hasRanks(statement, ranks)
	return in_array(statement.rank, ranks)
end

local function hasReferences(statement, options)
	if statement.references then
		if #p.filterReferences(statement.references, options) > 0 then
			return true
		end
	end
	return false
end

local function hasLabel(snak)
	local datatype = snak.datatype
	if datatype ~= 'wikibase-item' and datatype ~= 'wikibase-property' then
		return error(lib.raiseInvalidDatatype('hasLabel', datatype, { 'wikibase-item', 'wikibase-property' }))
	end
	if lib.IsSnakValue(snak) then
		local langs = { 'cs', 'sk', 'en' }
		local Formatters = require 'Modulo:Wikidata/Formatters'
		if lib.getLabelInLanguage(Formatters.getRawValue(snak), langs) then
			return true
		end
	end
	return false
end

local function hasSitelink(statement)
	local datatype = statement.mainsnak.datatype
	if datatype ~= 'wikibase-item' then
		return error(lib.raiseInvalidDatatype('hasSitelink', datatype, { 'wikibase-item' }))
	end
	if lib.IsSnakValue(statement.mainsnak) then
		if mw.wikibase.sitelink(Formatters.getRawValue(statement.mainsnak)) then
			return true
		end
	end
	return false
end

local function isInstance(snak, instance)
	local datatype = snak.datatype
	if datatype ~= 'wikibase-item' and datatype ~= 'wikibase-property' then
		return error(lib.raiseInvalidDatatype('isInstance', datatype, { 'wikibase-item', 'wikibase-property' }))
	end
	if lib.IsSnakValue(snak) then
		local Formatters = require 'Modulo:Wikidata/Formatters'
		local item = Formatters.getRawValue(snak)
		if mw.wikibase.getReferencedEntityId(item, 'P279', lib.textToTable(instance)) then
			return true
		end
	end
	return false
end

local function hasUnit(statement, unit)
	local datatype = statement.mainsnak.datatype
	if datatype ~= 'quantity' then
		return error(lib.raiseInvalidDatatype('hasUnit', datatype, { 'quantity' }))
	end
	if lib.IsSnakValue(statement.mainsnak) then
		return (lib.getItemIdFromURI(statement.mainsnak.datavalue.value.unit) or 'Q199') == unit
	else
		return false
	end
end

local function filter(array, callback, ...)
	local i = #array
	while i > 0 do
		if not callback(array[i], ...) then
			table.remove(array, i)
		end
		i = i - 1
	end
end

local function filterMainsnak(statements, callback, ...)
	filter(statements, function (statement, ...)
		return callback(statement.mainsnak, ...)
	end, ...)
end

local function getValuesFromQualifiers(qualifiers)
	local Values = {}
	local Formatters = require 'Modulo:Wikidata/Formatters'
	for key, array in pairs(lib.props) do
		for _, prop in ipairs(array) do
			for _, snak in ipairs(qualifiers[prop] or {}) do
				if lib.IsSnakValue(snak) then
					Values[key] = Formatters.getRawValue(snak)
					break
				end
			end
		end
	end
	return Values
end

function p.filterStatementsFromEntity(entity, options)
	if not options.property or options.property == '' then
		return error(lib.formatError('param-not-provided', 'property'))
	end
	if not entity or not entity.claims then
		return {}
	end
	local property = mw.ustring.upper(options.property)
	local statements = mw.clone(entity.claims[property])
	if not statements then
		return {}
	end

	p.filterStatements(statements, options)
	return statements
end

function p.filterStatements(statements, options)
	local options = lib.common.cleanArgs(options)
	-- apply filter by rank
	local rank = options.rank or "valid"
	if rank ~= "all" then
		if rank == "valid" or rank == "best" then
			filter(statements, hasRanks, { "normal", "preferred" })
			if rank == "best" and #statements > 0 then
				for _, statement in ipairs(statements) do
					if statement.rank == "preferred" then
						filter(statements, hasRanks, { "preferred" })
						break
					end
				end
			end
		else
			filter(statements, hasRanks, { rank })
		end
		if #statements == 0 then return end
	end
	-- apply filter by source
	if options.ref then
		filter(statements, hasReferences, options)
		if #statements == 0 then return end
	end
	-- apply filter by snak type
	filterMainsnak(statements, hasSnaktype, options.somevalue and true, options.novalue and true)
	if #statements == 0 then return end
	-- apply filter by target value
	if options.withtarget then
		filterMainsnak(statements, hasTarget, options.withtarget)
		if #statements == 0 then return end
	end
	-- apply filter by qualifier property
	if options.withqualifier then
		filter(statements, hasQualifier, options.withqualifier, options.withqualifiervalue)
		if #statements == 0 then return end
	end
	-- apply filter by language
	if options.withlang then
		filterMainsnak(statements, isInLanguage, options.withlang)
		if #statements == 0 then return end
	end
	-- apply filter by unit
	if options.withunit then
		filter(statements, hasUnit, options.withunit)
		if #statements == 0 then return end
	end
	-- apply filter by time
	if options.date then
		local date
		local Time = require 'Modulo:Time'
		if type(options.date) == 'table' then
			date = options.date
		elseif options.date == '#now' then
			date = Time.new(os.date('!*t'))
		else
			date = Time.newFromIso8601(options.date)
		end
		if not date then
			return error(lib.formatError('invalid-date', tostring(options.date)))
		end

		local oldStatements = mw.clone(statements)
		while #statements > 0 do table.remove(statements) end
		for _, statement in ipairs(oldStatements) do
			local Values = getValuesFromQualifiers(statement.qualifiers or {})
			if Values.point then
				if date == Values.point then
					filter(statements, function(st)
						local val = getValuesFromQualifiers(st.qualifiers).point
						if val then
							return val == Values.point
						end
						return true
					end)
					table.insert(statements, statement)
				elseif Values.point < date then
					if #statements == 0 then
						table.insert(statements, statement)
					else
						local same, ins
						for _, st in ipairs(statements) do
							local val = getValuesFromQualifiers(st.qualifiers).point
							if val then
								if date == Values.point then
									same = true
									break
								end
								if val == Values.point or val < Values.point then
									ins = true
								end
							end
						end
						if ins and not same then
							filter(statements, function(st)
								local val = getValuesFromQualifiers(st.qualifiers).point
								return not val or val == Values.point
							end)
							table.insert(statements, statement)
						end
					end
				end
			else
				if Values.begin then
					if Values.begin < date then
						if not Values.ending then
							table.insert(statements, statement)
						elseif date < Values.ending then
							table.insert(statements, statement)
						end
					end
				elseif Values.ending then
					if date < Values.ending then
						if not Values.begin then
							table.insert(statements, statement)
						elseif Values.begin < date then
							table.insert(statements, statement)
						end
					end
				end
			end
		end
		if #statements == 0 then return end
	end
	if lib.IsOptionTrue(options, 'withlabel') then
		filterMainsnak(statements, hasLabel)
		if #statements == 0 then return end
	end
	if lib.IsOptionTrue(options, 'withsitelink') then
		filter(statements, hasSitelink)
		if #statements == 0 then return end
	end
	-- apply filter by class
	if options.instance then
		filterMainsnak(statements, isInstance, options.instance)
		if #statements == 0 then return end
	end
	-- sort statements if needed
	if options.sort then -- patří to sem?
		local Sorters = require 'Modulo:Wikidata/Sorters'
		Sorters.sortStatements(statements, options)
	end
	-- apply filter by limit
	applyLimit(statements, options.limit)
end

function p.filterQualifiers(qualifiers, options)
	local options = lib.common.cleanArgs(options)
	filter(qualifiers, hasSnaktype, options['qualifiers somevalue'] and true, options['qualifiers novalue'] and true)
	if #qualifiers == 0 then return end
	if options['qualifiers withlang'] then
		filter(qualifiers, isInLanguage, options['qualifiers withlang'])
		if #qualifiers == 0 then return end
	end
	if lib.IsOptionTrue(options, 'qualifiers withlabel') then
		filter(qualifiers, hasLabel)
		if #qualifiers == 0 then return end
	end
	if options['qualifiers instance'] then
		filter(qualifiers, isInstance, options['qualifiers instance'])
		if #qualifiers == 0 then return end
	end
	if options['qualifiers sort'] then
		local Sorters = require 'Modulo:Wikidata/Sorters'
		Sorters.sortQualifiers(qualifiers, options)
	end
	applyLimit(qualifiers, options['qualifiers limit'])
end

function p.filterReferences(references, options)
	local options = lib.common.cleanArgs(options)
	if options.ref == '#any' then
		-- @deprecated
		return references
	end

	local oldReferences, References = references, {}
	if options.ref == 'valid' then
		local map = (require 'Modulo:Wikidata/cite').props
		for _, ref in ipairs(oldReferences) do
			for _, props in pairs(map) do
				for _, prop in ipairs(props) do
					if ref.snaks[prop] then
						table.insert(References, ref)
					end
				end
			end
		end
	end

	if options.min_ref and not checkLimit(References, options.min_ref) then
		return {}
	end
	-- @deprecated
	return References
end

return p