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({crtUatId, 'P131', 'raw'})
		if (TableTools.contains(uatList, crtUatId)) then
			break
		end
		table.insert(uatList, crtUatId)
	end
	if #uatList > 0 and uatList[#uatList] == crtCountryId or uatList[#uatList] == timestampedCountryId then
		table.remove(uatList, #uatList)
	end
	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.shortNameLocationTypes) then
		return illWd(partId, Wikidata.loadOneValueInChain({partId, 'P1813'}))
	end
	if Wikidata.isA(partId, lacData.invariantLocationTypes) then
		return illWd(partId)
	end

	local officialLangClaims = Wikidata.findBestClaimsForProperty(partId, 'P37')
	if not officialLangClaims or #officialLangClaims == 0 then
		local countryId = getCountryIdForTimestamp(partId, ts)
		if not countryId then return illWd(partId) end
		officialLangClaims = Wikidata.findBestClaimsForProperty(countryId, 'P37')
	end
	local offLangCodes = {}
	for _,eachOffLangClaim in ipairs(officialLangClaims) 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
				local offLangCode = Wikidata.loadOneValueInChain({eachOffLangId, 'P218'})
				if offLangCode and offLangCode ~= '' then
					table.insert(offLangCodes, offLangCode)
				end
			end
		end
	end
	if #offLangCodes == 0 then table.insert(offLangCodes, 'ro') end
	for _,offLangCode in ipairs(offLangCodes) do
		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)
		end
	end

	return illWd(partId, Wikidata.findLabel(partId, offLangCodes[1]))
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(locationEntityId, ts)
				-- (1) see if admin unit needs to be pulled
				local uatsEnabled = shouldPullUats(locationEntityId)
				if uatsEnabled > 0 then
					-- (2) pull all admin units based on timestamp until reaching nothing, self or country
					local allUats = recurseUats(locationEntityId, 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)
				if not countryId and uatEntitiesIds and #uatEntitiesIds > 0 then
					countryId = Wikidata.loadOneValueInChain({uatEntitiesIds[#uatEntitiesIds], 'P17', 'raw'})
				end
				if countryId then
					table.insert(allPartLinks, illWd(countryId, lacData.countryNameOverrides[countryId]))
				end
				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