Sari la conținut

Modul:LocationAndCountry

De la Wikipedia, enciclopedia liberă

-- will display a wikidata property representing a location, followed by a comma and the name of the country, both with wikilinks
-- the first argument specifies the parameter to be displayed
-- the second argument specifies the entity ID
-- the third argument specifies a timestamp showing the moment in time for which the country is to be identified
-- the fourth argument specifies the maximum number of values to be processed (default 1)
-- the fifth argument specifies the separator to use when displaying multiple values
local getArgs = require('Modul:Arguments').getArgs
local Wikidata = require('Modul:Wikidata')
local lacData = mw.loadData('Modul:LocationAndCountry/data')
local TableTools = require('Modul:TableTools')
local GregorianDate = require('Modul:GregorianDate')
local DateUtils = require('Modul:DateUtils')
local Set = require('Modul:Set')
local illWd = require('Modul:Ill-wd').fromArgs
local p = {}

local function getClaimsForParam(entity, param)
	local claims = nil
	local workingEntityId = nil
	if type(entity) == 'table' then
		workingEntityId = entity.id
		claims = entity:getBestStatements(param)
	else
		workingEntityId = entity
		if not workingEntityId then workingEntityId = mw.wikibase.getEntityIdForCurrentPage() end
		if type(entity) == 'number' then workingEntityId = 'Q' .. tostring(entity) end
		if workingEntityId == nil then return '' end
		claims = mw.wikibase.getBestStatements(workingEntityId, param)
	end
	return claims, workingEntityId
end

local function getTimestamp(timestamp, workingEntityId)
	local ts = nil
	if timestamp then
		if type(timestamp) == 'string' and mw.ustring.gmatch(timestamp, 'P%d+') then
			local wdDates = Wikidata.findDateValues(timestamp, workingEntityId)
			if wdDates then for _,eachWdDate in ipairs(wdDates) do
				if Wikidata.isClaimTrue(eachWdDate.claim) and Wikidata.hasValueSnak(eachWdDate.claim) then
					ts = GregorianDate.convertToGregorianIfInInterval(eachWdDate)
					break
				end
			end end
		end
		if ts == nil and type(timestamp) == 'string' then
			ts = DateUtils.parseYear(timestamp)
		elseif ts == nil and type(timestamp) == 'table' and timestamp.year then
			ts = timestamp
		end
	end
	return ts
end

local function getUatsAndCountryFromQuals(claim)
	if not Wikidata.isClaimTrue(claim) or not claim.qualifiers then return nil end
	local country = nil
	local uats = {}
	if claim.qualifiers['P17'] then for _,eachCountryQual in ipairs(claim.qualifiers['P17']) do
		if Wikidata.isValueSnak(eachCountryQual) then
			country = eachCountryQual.datavalue.value.id
			break
		end
	end end
	
	if claim.qualifiers['P131'] then for _,eachUatQual in ipairs(claim.qualifiers['P131']) do
		if Wikidata.isValueSnak(eachUatQual) then
			table.insert(uats, eachUatQual.datavalue.value.id)
		end
	end end
	
	return uats, country
end

local function getCountryIdForTimestamp(entityId, ts)
	local countryClaim = Wikidata.findClaimForTimestamp(entityId, 'P17', ts)
	if countryClaim and Wikidata.hasValueSnak(countryClaim) then
		return countryClaim.mainsnak.datavalue.value.id
	else
		return Wikidata.loadOneValueInChain({entityId, 'P17', 'raw'})
	end
end

-- returns 0, 1 or 2 - the number of admin units to be pulled
local function shouldPullUats(entityId)
	-- villages, rural settlements, should get 2 uats
	if Wikidata.isA(entityId, lacData.adminUnitExpandableLocationTypes) then
		return 2
	end
	-- locations in federal countries should get at least one UAT regardless of size
	local countryId = Wikidata.loadOneValueInChain({entityId, 'P17', 'raw'})
	if countryId and Wikidata.isA(countryId, { 'Q22676603' }) then
		return 1
	end
	-- only big cities should have just the country
	if not Wikidata.isA(entityId, {'Q1549591', 'Q1637706'}) then
		return 1
	end
	return 0
end

local function recurseUats(entityId, ts)
	local crtUatId = entityId
	local crtCountryId = Wikidata.loadOneValueInChain({entityId, 'P17', 'raw'})
	local timestampedCountryId = getCountryIdForTimestamp(entityId, ts)
	local uatList = {}
	while crtUatId and crtUatId ~= crtCountryId and crtUatId ~= timestampedCountryId do
		local timestampedUatClaim = Wikidata.findClaimForTimestamp(crtUatId, 'P131', ts)
		crtUatId = timestampedUatClaim and Wikidata.hasValueSnak(timestampedUatClaim) and timestampedUatClaim.mainsnak.datavalue.value.id or Wikidata.loadOneValueInChain({entityId, 'P131', 'raw'})
		if (TableTools.contains(uatList, crtUatId)) then
			break
		end
		table.insert(uatList, crtUatId)
	end
	mw.logObject(uatList, 'uat list')
	table.remove(uatList, #uatList)
	return uatList
end

local function shouldSkipLocation(entityId)
	return Wikidata.isA(entityId, lacData.escalatableLocationTypes)
end

local function trimUats(uatList)
	while #uatList > 0 and (Wikidata.isA(uatList[1], lacData.skippableUatTypes) or TableTools.contains(lacData.skippableUats, uatList[1])) do
		table.remove(uatList, 1)
	end
	while #uatList > 0 and (Wikidata.isA(uatList[#uatList], lacData.skippableUatTypes) or TableTools.contains(lacData.skippableUats, uatList[#uatList])) do
		table.remove(uatList, #uatList)
	end
	return uatList
end

local function resolvePartName(partId, ts)
	if TableTools.contains(lacData.invariantLocations, partId) then
		return illWd(partId)
	end
	local crtCountryId = Wikidata.loadOneValueInChain({partId, 'P17', 'raw'})
	if crtCountryId and TableTools.contains(lacData.invariantLocationCountries, crtCountryId) then
		return illWd(partId)
	end
	if TableTools.size(Set.intersection(recurseUats(partId), lacData.invariantLocationUats)) > 0 then
		return illWd(partId)
	end
	if Wikidata.isA(partId, lacData.invariantLocationTypes) then
		return illWd(partId)
	end

	local countryId = getCountryIdForTimestamp(partId, ts)
	if not countryId then return illWd(partId) end
	local countryOfficialLangClaims = Wikidata.findBestClaimsForProperty(countryId, 'P37')
	local offLangCode = nil
	for _,eachOffLangClaim in ipairs(countryOfficialLangClaims) do
		if Wikidata.hasValueSnak(eachOffLangClaim) and (not eachOffLangClaim.qualifiers or not eachOffLangClaim.qualifiers['P518']) then
			local eachOffLangId = eachOffLangClaim.mainsnak.datavalue.value.id
			local writingSystem = Wikidata.loadOneValueInChain({eachOffLangId, 'P282', 'raw'})
			while writingSystem and writingSystem ~= '' and writingSystem ~= 'Q8229' do
				writingSystem = Wikidata.loadOneValueInChain({writingSystem, 'P282', 'raw'})
			end
			if writingSystem == 'Q8229' then
				offLangCode = Wikidata.loadOneValueInChain({eachOffLangId, 'P218'})
			end
		end
	end
	offLangCode = offLangCode or 'ro'
	local officialNameClaim = Wikidata.findClaimForTimestamp(partId, 'P1448', ts, offLangCode)
	if officialNameClaim and Wikidata.isClaimTrue(officialNameClaim) and Wikidata.hasValueSnak(officialNameClaim) then
		return illWd(partId, officialNameClaim.mainsnak.datavalue.value.text)
	else
		return illWd(partId, Wikidata.findLabel(partId, offLangCode))
	end
	
	return illWd(partId)
end

local function resolvePartNames(partIds, ts)
	if not partIds then return nil end
	local res = {}
	for _,partId in ipairs(partIds) do
		table.insert(res, resolvePartName(partId, ts))
	end
	return res
end

local function displayFromParams(param, entity, timestamp, maxvalues, separator)
	if param == nil then return '' end
	
	local claims, workingEntityId = getClaimsForParam(entity, param)
	local ts = getTimestamp(timestamp, workingEntityId)
	
	local valueList = {}
	local maxvalues = maxvalues or 1
	if claims and 0 < #claims then
		for _,actualClaim in ipairs(claims) do
			local uatEntitiesIds = {}
			local countryId = nil
			local locationEntityId = nil
			if Wikidata.isClaimTrue(actualClaim) and Wikidata.hasValueSnak(actualClaim) then
				-- get admin unit(s) and/or country from qualifiers
				uatEntitiesIds, countryId = getUatsAndCountryFromQuals(actualClaim)
				locationEntityId = actualClaim.mainsnak.datavalue.value.id
				countryId = countryId or getCountryIdForTimestamp(actualClaim.mainsnak.datavalue.value.id, ts)
				-- (1) see if admin unit needs to be pulled
				local uatsEnabled = shouldPullUats(actualClaim.mainsnak.datavalue.value.id)
				if uatsEnabled > 0 then
					-- (2) pull all admin units based on timestamp until reaching nothing, self or country
					local allUats = recurseUats(actualClaim.mainsnak.datavalue.value.id, ts)
					-- (3) if the location itself is to be skipped, keep the first admin unit in the list as location
					while #allUats > 0 and shouldSkipLocation(locationEntityId) do
						locationEntityId = table.remove(allUats, 1)
					end
					-- (4) apply rules to eliminate from both ends
					allUats = trimUats(allUats)
					-- (5) keep only first and last (or the only one if only one is left)
					uatEntitiesIds = #allUats == 0 and {} or #allUats == 1 and allUats or uatsEnabled == 1 and {allUats[#allUats]} or {allUats[1], allUats[#allUats]}
				else
					uatEntitiesIds = {}
				end
				local allPartIds = {}
				table.insert(allPartIds, locationEntityId)
				for __,eachUat in ipairs(uatEntitiesIds) do table.insert(allPartIds, eachUat) end
				local allPartLinks = resolvePartNames(allPartIds, ts)
				table.insert(allPartLinks, illWd(countryId, lacData.countryNameOverrides[countryId]))
				local crtVal = table.concat(allPartLinks, ', ')
				table.insert(valueList, crtVal)
			end
			if #valueList > maxvalues then
				break
			end
		end
	end
	return table.concat(valueList, separator or '; ')
end
p.displayFromParams = displayFromParams

local function displayFromArgs(args)
	local param = nil
	local entity = nil
	local timestamp = nil
	local maxvalues = 1
	local separator = '; '
	if args[1] or args['param'] then
		param = args[1] or args['param']
	end
	if args[2] or args['entityId'] then
		entity = args[2] or args['entityId']
	end
	if args[3] or args['timestamp'] then
		timestamp = args[3] or args['timestamp']
	end
	if args[4] or args['maxvalues'] then
		maxvalues = tonumber(args[4] or args['maxvalues'])
	end
	if args[5] or args['separator'] then
		separator = args[5] or args['separator']
	end
	return displayFromParams(param, entity, timestamp, maxvalues, separator)
end
p.displayFromArgs = displayFromArgs

p.displayFromFrame = function(frame)
	local args = getArgs(frame, { frameOnly = true })
	return displayFromArgs(args)
end

p.displayFromParentFrame = function(frame)
	local args = getArgs(frame, { parentOnly = true})
	return displayFromArgs(args)
end

return p