Jump to content

Module:License Wikidata

Permanently protected module
From Wikisource

require('strict')

--[=[
WD pulls are disabled for works due to concerns over accuracy and precision.
]=]

local p = {} --p stands for package

local getArgs = require('Module:Arguments').getArgs
local tableToolsModule = require('Module:TableTools')

local property_ids = {
	['deathyear'] = 'P570',
	['pubyear'] = 'P577',
	['edition'] = 'P629'
}

local function clean_table(t)
	return tableToolsModule.removeDuplicates(tableToolsModule.compressSparseArray(t))
end

--[=[
	Death/publication year
]=]

local function getYearFromSingleStatement(statement)
	local snak = statement.mainsnak
	-- We're not using mw.wikibase.formatValue because we only want years.
	
	if snak.snaktype ~= 'value' then
		return nil
	end
	
	-- Precision is less than a year.
	if snak.datavalue.value.precision < 9 then
		return nil
	end
	
	-- Year is approximate.
	-- P1480 = sourcing circumstances, Q5727902 = circa.
	if statement.qualifiers ~= nil and statement.qualifiers.P1480 ~= nil then
		for _, qualifier in pairs(statement.qualifiers.P1480) do
			if qualifier.datavalue ~= nil and qualifier.datavalue.value.id == 'Q5727902' then
				return nil
			end
		end
	end
	
	-- Extract year from the time value.
	local _, _, extractedYear = string.find(snak.datavalue.value.time, '([%+%-]%d%d%d+)%-')
	local year = tonumber(extractedYear)
	
	-- Handle BCE years. -1 BCE = 0.
	if year < 0 then
		year = year + 1
	end
	
	return year
end

local function getItemFromWikidataID(wikidata_id)
	if wikidata_id ~= nil and wikidata_id ~= '' then
		return mw.wikibase.getEntity(wikidata_id) or nil
	else
		return mw.wikibase.getEntity() or nil
	end
end

local function getYearsFromWikidata(args)
	local item = getItemFromWikidataID(args.wikidata_id)
	if not item then
		return nil
	end
	
	local year_prop = args.year_prop
	
	-- Compile a list of year statements.
	local statements = item:getBestStatements(year_prop)
	if #statements == 0 then
		return nil
	end
	
	-- Compile a list of years, one from each statement.
	local years = {}
	for _, statement in pairs(statements) do
		table.insert(years, getYearFromSingleStatement(statement))
	end
	years = clean_table(years)
	
	if #years == 0 then
		return nil
	end
	
	-- Return a sorted list of years.
	table.sort(years)
	return years
end

local function sortForBestYear(args)
	local years = args.years
	if not years then
		return nil
	end
	
	if args.reverse_sort then
		table.sort(years, function(a, b) return a > b end)
	else
		table.sort(years)
	end
	
	for _, year in pairs(years) do
		if tonumber(year) then
			return tonumber(year)
		end
	end
	
	return nil
end

local function getBestYearFromWikidata(args)
	local wikidata_years = getYearsFromWikidata({
		['wikidata_id'] = args.wikidata_id,
		['year_prop'] = args.year_prop
	})
	return sortForBestYear({['years'] = wikidata_years, ['reverse_sort'] = args.reverse_sort})
end

local function getAuthorDeathYearFromWikidata(wikidata_id)
	return getBestYearFromWikidata({
		['wikidata_id'] = wikidata_id,
		['year_prop'] = property_ids['deathyear'],
		['reverse_sort'] = true
	})
end

local function getPublicationYearFromWikidata(wikidata_id)
	return getBestYearFromWikidata({
		['wikidata_id'] = wikidata_id,
		['year_prop'] = property_ids['pubyear'],
		['reverse_sort'] = false
	})
end

--[=[
	Creator death years
]=]

local function getCreatorsFromSingleProp(item, creator_prop)
	-- Compile a list of creator statements.
	local statements = item:getBestStatements(creator_prop)
	if #statements == 0 then
		return nil
	end
	
	-- Compile a list of creators, one from each statement.
	local creators = {}
	for _, statement in pairs(statements) do
		local snak = statement.mainsnak
		if snak.snaktype == 'value' and snak.datavalue['type'] == 'wikibase-entityid' and snak.datavalue.value['entity-type'] == 'item' then
			table.insert(creators, snak.datavalue.value.id)
		end
	end
	
	creators = clean_table(creators)
	if #creators == 0 then
		return nil
	end
	
	return creators
end

local function getCreators(item, creator_props)
	local all_creators = {}
	-- is there a better way to merge tables?
	for _, creator_prop in pairs(creator_props) do
		local creators = getCreatorsFromSingleProp(item, creator_prop)
		if creators then
			for _, creator in pairs(creators) do
				table.insert(all_creators, creator)
			end
		end
	end
	all_creators = clean_table(all_creators)
	if #all_creators == 0 then
		return nil
	end
	return all_creators
end

local function getCreatorDeathYearsFromWikidata(args)
	local item = getItemFromWikidataID(args.wikidata_id)
	if not item then
		return nil
	end
	
	-- What I really want is a list of all subproperties of P170 and P767
	-- (unless there are some that shouldn't be included).
	local creator_props = args.creator_props or {
		['creator'] = 'P170',
		['author'] = 'P50',
		['librettist'] = 'P87',
		['composer'] = 'P86',
		['orchestrator'] = 'P10806',
		['adapted by'] = 'P5202',
		['screenwriter'] = 'P58',
		['lyrics by'] = 'P676',
		['performer'] = 'P175',
		['illustrator'] = 'P110',
		['letterer'] = 'P9191',
		['inker'] = 'P10836',
		['penciller'] = 'P10837',
		['colorist'] = 'P6338',
		['contributor'] = 'P767',
		['translator'] = 'P655'
	}
	
	if not creator_props then
		return nil
	end
	
	local creators = getCreators(item, creator_props)
	if not creators then
		return nil
	end
	
	local deathyears = {}
	for _, creator in pairs(creators) do
		local deathyear = getAuthorDeathYearFromWikidata(creator)
		if deathyear then
			table.insert(deathyears, deathyear)
		else
			return nil -- if creator doesn't have deathyear
		end
	end
	
	deathyears = clean_table(deathyears)
	if #deathyears == 0 then
		return nil
	end
	table.sort(deathyears)
	
	return deathyears
end

local function getBestCreatorDeathYearFromWikidata(args)
	local wikidata_years = getCreatorDeathYearsFromWikidata({
		['wikidata_id'] = args.wikidata_id,
		['creator_props'] = args.creator_props
	})
	return sortForBestYear({['years'] = wikidata_years, ['reverse_sort'] = true})
end

local function getBestYear(args)
	local manual_years = args.manual_years
	
	if manual_years then
		for _, year in pairs(manual_years) do
			if tonumber(year) then
				return tonumber(year)
			end
		end
	end
	
	local wikidata_function = args.wikidata_function
	if wikidata_function and args.useWD then
		return wikidata_function(args.wikidata_function_args)
	end
	
	return nil
end

local function getAuthorDeathYear(args)
	return getBestYear({
		['manual_years'] = args.deathyears,
		['wikidata_function'] = getAuthorDeathYearFromWikidata,
		['wikidata_function_args'] = args.wikidata_id,
		['useWD'] = args.useWD
	})
end

local function getWorkCreatorDeathYear(args)
	return getBestYear({
		['manual_years'] = args.deathyears,
		['wikidata_function'] = getBestCreatorDeathYearFromWikidata,
		['wikidata_function_args'] = {['wikidata_id'] = args.wikidata_id, ['creator_props'] = args.creator_props},
		['useWD'] = args.useWD
	})
end

local function getPublicationYear(args)
	return getBestYear({
		['manual_years'] = args.pubyears, 
		['wikidata_function'] = getPublicationYearFromWikidata,
		['wikidata_function_args'] = args.wikidata_id,
		['useWD'] = args.useWD
	})
end

local function getWorksFromEdition(wikidata_id)
	local item = getItemFromWikidataID(wikidata_id)
	if not item then
		return nil
	end
	
	local statements = item:getBestStatements(property_ids['edition'])
	if #statements == 0 then
		return nil
	end
	
	local work_ids = {}
	for i, statement in ipairs(statements) do
		if statement.mainsnak and statement.mainsnak.datatype == 'wikibase-item' and statement.mainsnak.datavalue and statement.mainsnak.datavalue.value and statement.mainsnak.datavalue.value.id then
			table.insert(work_ids, statement.mainsnak.datavalue.value.id)
		end
	end
	
	if #work_ids == 0 then
		return nil
	end
	
	return work_ids
end

local function getWorkOrAuthorYear(workYearFunction, authorYearFunction, args)
	args = args or {}
	
	local namespace = args.namespace or mw.title.getCurrentTitle().nsText
	local is_author = namespace == 'Author' or namespace == 'Author talk'
	
	local yearFunction
	if is_author then
		yearFunction = authorYearFunction
	else
		yearFunction = workYearFunction
	end
	
	args.useWD = is_author -- this could change later
	
	if args.useWD and not is_author then
		local wikidata_id = args.wikidata_id
		local work_ids = getWorksFromEdition(wikidata_id) or {}
		for i, work_id in ipairs(work_ids) do
			args.wikidata_id = work_id
			local work_year = yearFunction(args)
			if work_year then
				return work_year
			end
		end
		args.wikidata_id = wikidata_id
	elseif yearFunction then
		return yearFunction(args)
	end
	
	return nil
end

--[=[
	Public functions
]=]

function p.getWorkCreatorOrAuthorDeathYear(args)
	return getWorkOrAuthorYear(getWorkCreatorDeathYear, getAuthorDeathYear, args)
end

function p.getWorkOrAuthorPublicationYear(args)
	return getWorkOrAuthorYear(getPublicationYear, nil, args)
end

return p