Moduuli:Fr:Wikidata/Analyse transitive


-- Helpers for queries using transitive properties

local p = {}
local wd = require "Module:Fr:Wikidata"

local function getids(item, query) 
	query.excludespecial = true
	query.displayformat = 'raw'
	query.entity = item
	return wd.stringTable(query)
end


-- add new items to a list, avoiding duplicates
local function addnewvalues(olditems, newitems, maxnum, stopval)
	if not newitems then
		return olditems
	end
	for _, qid in pairs(newitems) do
		if stopval and (qid == stopval) then
			table.insert(olditems, qid)
			return olditems
		end
		if maxnum and (#olditems >= maxnum) then
			return olditems
		end
		if not wd.isHere(olditems, qid) then
			table.insert(olditems, qid)
		end
	end
	return olditems
end

-- recursively adds a list of qid to an existing list, based on the results of a query
function p.addVals(list, query, maxdepth, maxnodes, stopval)
	maxdepth = tonumber(maxdepth) or 10
	maxnodes = tonumber(maxnodes) or 100
	if (maxdepth < 0) then
		return list
	end
	if stopval and wd.isHere(list, stopval) then
		return list
	end
	local origsize = #list
	for i = 1, origsize do
		-- tried a  "checkpos" param instead of starting to 1 each time, but no impact on performance
		local candidates = getids(list[i], query)
		list = addnewvalues(list, candidates, maxnodes, stopval)
		if list[#list] == stopval then
			return list
		end
		if #list >= maxnodes then
			return list
		end
	end
	if (#list == origsize) then
		return list
	end
	return p.addVals(list, query, maxdepth - 1, maxnodes, stopval, origsize + 1)
end

-- returns a list of items transitively matching a query (orig item is not included in the list)
function p.transitiveVals(item, query, maxdepth, maxnodes, stopval, astring)

	maxdepth = tonumber(maxdepth) or 5
	if type(query) == "string" then
		query = {property = query}
	end

	-- récupération des valeurs
	local vals = getids(item, query)
	if not vals then
		return nil
	end
	local v = p.addVals(vals, query, maxdepth - 1, maxnodes, stopval) 
	if not v then
		return nil
	end

	-- réarrangement des valeurs
	if query.valorder == "inverted" then
		local a = {}
		for i, j in pairs(v) do
			table.insert(a, 1, j)
		end
		v = a
	end

	-- mise en forme
 	-- soit sous forme de chaîne
	if astring and (astring ~= "-") then
		for i, j in pairs(v) do
			v[i] = wd.formatEntity(j)
-- il faudrait avoir les optins de mise en forme  wd.formatEntity(j, query) mais getids ajoute displayformat = "raw", il faudrait faire d'abord une copie de la table query
		end
		return wd.tableToText(v, query)
	end
	--- soit sous forme de table
	return v
end

-- returns true if an item is the value of a query, transitively
function p.inTransitiveVals(searchedval, sourceval, query, maxdepth, maxnodes )
	local vals = p.transitiveVals(sourceval, query, maxdepth, maxnodes, searchedval )
	if (not vals) then 
		return false
	end
	for _, val in ipairs(vals) do
		if (val == searchedval) then
			return true
		end
	end
	return false
end

-- returns true if an item is a superclass of another, based on P279
function p.isSubclass(class, item, maxdepth)
	local query = {property = 'P279'}
	if class == item then -- item is a subclass of itself iff it is a class
		if getids(item, query) then
			return true
		end
		return false
	end
	return p.inTransitiveVals(class, item, query, maxdepth )
end

-- returns true if one of the best ranked P31 values of an item is the target or a subclass of the target
-- rank = 'valid' would seem to make sense, but it would need to check for date qualifiers as some P31 values have begin or end date
function p.isInstance(targetclass, item, maxdepth)
	maxdepth = maxdepth or 10
	local directclasses = p.transitiveVals(item, {property = 'P31'}, 1)
	if not directclasses then
		return false
	end
	for i, class in pairs(directclasses) do
		if p.isSubclass(targetclass, class, maxdepth - 1) then
			return true
		end
	end
	return false
end

-- return the first value in a transitive query that belongs to a particular class. For instance find a value of P131 that is a province of Canada
function p.findVal(sourceitem, targetclass, query, recursion, instancedepth)
	if type(query) == "string" then
		query = {property = query}
	end
	local candidates = getids(sourceitem, query)
	if candidates then
		for i, j in pairs(candidates) do
			if p.isInstance(targetclass, j,  instancedepth) then
				return j
			end
		end
		if not recursion then
			recursion = 3
		else
			recursion = recursion - 1
		end
		if recursion < 0 then
			return nil
		end
		for i, candidate in pairs(candidates) do
			return p.findVal(candidate, targetclass, query, recursion, instancedepth)
		end
	end
end


return p