Module:entityUtilities

From Rhizome Artbase

Documentation for this module may be created at Module:entityUtilities/doc

local p = {}

local m = require('Module:manifest')

-- Return entity from either an entity table or an entity ID.
-- Throw error if string cannot be parsed as entity ID or if
-- given table does not look like an entity table.)
function p.entity(e)
	if type(e) == 'string' then
		if not mw.wikibase.isValidEntityId(e) then
			error('No entity or entity ID provided.')
		end
		e = mw.wikibase.getEntity(e)
	elseif not p.isEntity(e) then
		error('Not an entity table: ' .. mw.dumpObject(e))
	end
	return e
end

-- Get list of statements that are not deprecated.
-- Arguments:
-- [1]: item (QID or item table)
-- [2]: property to list statements from (PID)
-- 'filter': only return items that match this 'instance of' (Single QID
--           or list of QIDs) - optional
-- 'order_by': sort by this propery, ascending (single PID) - optional
-- 'desc': if true, reverse order_by results
--
-- =mw.dumpObject( p.statementList{'Q2508', 'P45', order_by='P26', desc=true} )
function p.statementList(arg)
	local entity = p.entity(arg[1])
	local prop = arg[2] or nil
	local filter = arg.filter or nil
	local order_by = arg.order_by or nil
	local desc = arg.desc or false
	
	if filter then
		if type(filter) == 'string' then
			filter = {filter}
		end
	end
	
	local collected = {}
	local results = {}
	-- If staments with this property exist for the current entity
	-- collect  statements ranked as 'normal' or 'preferred'
	if entity.claims[prop] then
		for i,statement in pairs(entity.claims[prop]) do
			if p.valueInTable({'normal','preferred'}, statement.rank) then
				-- handle value statements (TODO: handle 'unknown' and 'no value')
				if statement.mainsnak.snaktype == 'value' then
					-- add statement to collected list
					table.insert(collected, statement)
				elseif statement.mainsnak.snaktype == 'somevalue' then
					-- 'unknown' value
					table.insert(collected, statement)
				end
			end
		end
	
		-- go through all the statements that were collected
		for i,statement in ipairs(collected) do
			local filter_match = true -- if no filter was requested, all entities match by default
			
			-- reference to another entity that might be subject to filtering
			if statement.mainsnak.snaktype == 'value' then -- skip 'unknown' or 'no value'
				if statement.mainsnak.datavalue.type == 'wikibase-entityid' then
					local nested_entity = mw.wikibase.getEntity(statement.mainsnak.datavalue.value.id)
					
					-- Filter 'for instance of'
					if filter then
						filter_match = p.entityIs(nested_entity, filter)
					end
				end
			end
				
			-- Add statement or referenced entity to result list
			if filter_match then
				local nested_entity = nil
				if statement.mainsnak.snaktype == 'value' then -- skip 'unknown' or 'no value'
					if statement.mainsnak.datavalue.type == 'wikibase-entityid' then
						nested_entity = mw.wikibase.getEntity(statement.mainsnak.datavalue.value.id)
					end
				end
				table.insert(results, nested_entity or statement)
			end
		end
	end
	
	-- If order_by is given, the results should be ordered by that property.
	-- Depending on what is the properties datatype, different functions will
	-- be used
	if order_by then
		-- check if the order key is a property
		if mw.wikibase.isValidEntityId(order_by) then
			local property = mw.wikibase.getEntity(order_by)
			-- check for type of property
			if property.type == 'property' then
				
				-- sort by a property of datatype time
				if property.datatype == 'time' then
					-- It's a time 
					local timeFromStatement = function(item)
						if p.keyInTable(item.claims, order_by) then
							local statements = mw.wikibase.getBestStatements(item.id, order_by)
							return statements[1].mainsnak.datavalue.value.time
						end
						return '0'
					end

					table.sort(results, function(statement1, statement2) return timeFromStatement(statement1) < timeFromStatement(statement2) end)
					
					-- reverse table if asked for it
					if desc then
						local resultsDesc = {}
    					local itemCount = #results
    					for k, v in ipairs(results) do
        					resultsDesc[itemCount + 1 - k] = v
    					end
    					results = resultsDesc
					end
				
				-- unsupported datatype
				else
					error('`sort_by` unsupported property type: ' .. property.datatype)
				end
			else
				error('`sort_by` must be PID of a property.')
			end
		end
	end
	
	return results
end

-- collect statements from nested entities
-- entity is the base entity to start from
-- nesting_property is the property om the base entity that contains
--     more entities; these are the nested entities
-- collect_property is the property value on a nested entity
--     that should be collected
function p.nestedStatementList(entity, nesting_property, collect_property)
	local entity = p.entity(entity)
	
	-- check if the nested property is of type wikibase-item
	local nesting_property_entity = p.entity(nesting_property)
	if nesting_property_entity.datatype ~= 'wikibase-item' then
		error(nesting_property .. ' is not of type "wikibase-item"')
	end

	local results = {}
	local nested_items = p.statementList{entity, nesting_property}
	for _,nested_item in ipairs(nested_items) do
		local collected_values = p.statementList{nested_item, collect_property}
		for __,v in ipairs(collected_values) do
			table.insert(results, v)
		end
	end
	return results
end


-- Check if string is empty
function p.isEmpty(str)
	if str == nil or string.len(str:gsub('%s*', '')) == 0 then
		return true
	else
		return false
	end
end

-- Check if table t contains value v
function p.valueInTable(t, v)
	if type(v) == 'string' then 
		v={v} 
	end
	for table_key,table_value in pairs(t) do
		for i,match_value in pairs(v) do
			if table_value == match_value then
				return table_key
			end
		end
	end
	return false
end

-- Check if table t has key k
function p.keyInTable(t, k)
	return t[k] ~= nil
end


-- Retrieve QID that is either handed over in a frame object, 
-- or that is taken from the SiteLink, or from the page's URL.
function p.entityIdFromFrame(frame)
	-- Is the first argument of the frame a QID?
	if type(frame) == 'table' then
		if type(frame.args) == 'table' then
			if type(frame.args[1]) == 'string' then
				-- Remove space characters (can appear when invoked
				-- from a wikipage)
				local entity_id = string.gsub(frame.args[1], "%s+", "")
				-- is this an entity ID at all?
				if mw.wikibase.isValidEntityId(entity_id) then
					return entity_id
				end
			else
				local page_title = frame:getParent():getTitle()
				if mw.wikibase.isValidEntityId(page_title) then
					return page_title
				end
			end
		end
	end
	-- If no QID given, try to take from SiteLink.
	local entity_id = mw.wikibase.getEntityIdForCurrentPage()
	if not entity_id then
		-- Everything failed!
		error('No QID given, page has no SiteLink, and the page URL does not contain an entity ID')
	else
		return entity_id
	end
end

-- check if a given table looks like an entity table
function p.isEntity(var)
	if type(var) == 'table' and var.type and p.valueInTable({'item', 'property'}, var.type) then
		return true
	else
		return false
	end	
end


-- check if entity is an instance of an item (like 'is this an artwork?')
function p.entityIs(entity, type_QID)
	local entity = p.entity(entity)
	if type(type_QID) == 'string' then 
		type_QID = {type_QID} 
	end

	local instances_of = entity:getBestStatements(m.prop.instance_of)
	for _,instance_of in pairs(instances_of) do
		if p.valueInTable(type_QID, instance_of.mainsnak.datavalue.value.id) then
			return true
		end
	end
	return false
end


-- set of functions to check if statement references an
-- 'unknown' or 'no value' statement
function p.entityIsValueType(entity, value_type)
	if type(entity) == 'table' then 
		if p.keyInTable(entity, 'mainsnak') then
			if entity.mainsnak.datatype == 'wikibase-item' then
				if entity.mainsnak.snaktype == value_type then
					return true
				end
			end
		end
	end
	return false
end
function p.entityIsUnknown(entity)
	return p.entityIsValueType(entity, 'somevalue')
end
function p.entityIsNoValue(entity)
	return p.entityIsValueType(entity, 'novalue')
end


-- get all 'instance of' IDs of an entity
function p.instances_of(entity)
	local entity = p.entity(entity)
	local instance_of_ids = {}
	local instance_of_statements = entity:getBestStatements(m.prop.instance_of)
	for _,statement in ipairs(instance_of_statements) do
		table.insert(instance_of_ids, statement.mainsnak.datavalue.value.id)
	end
	return instance_of_ids
end

--------------------------------------------------------------------------------

-- check if a wikipage exists
function p.pageExists(pageName)
	local page = mw.title.new(pageName)
	return page.exists
end

--------------------------------------------------------------------------------

return p