Modul:LocationAndCountry

De la Wikipedia, enciclopedia liberă
Sari la navigare Sari la căutare

Implementează formatul {{Loc, Țară}}, dar poate fi apelat prin intermediul funcției displayFromParams și din alte module pentru a afișa țara atașată de un loc la un anumit moment de timp.


-- 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 Transliteration = require('Modul:Transliteration')
local getValueByTimestamp = require('Modul:TimestampedTable').getValueByTimestamp
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
		local retVal = countryClaim.mainsnak.datavalue.value.id
		local claimTs = nil
		if countryClaim.qualifiers and countryClaim.qualifiers['P582'] then
			for _,endTQual in ipairs(countryClaim.qualifiers['P582']) do if Wikidata.isValueSnak(endTQual) then
				claimTs = GregorianDate.convertToGregorianIfInInterval(DateUtils.extractDateFromWikidataSnak(endTQual))
				break
			end end
		end
		return retVal, claimTs
	else
		return Wikidata.loadOneValueInChain({entityId, 'P17', 'raw'}), nil
	end
end

-- returns 0, 1 or 2 - the number of admin units to be pulled
local function shouldPullUats(entityId, countryId)
	-- 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
	if countryId and Wikidata.isA(countryId, { 'Q22676603', 'Q43702' }) 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
	local uatListCountryId = nil
	if #uatList > 0 and (uatList[#uatList] == crtCountryId or uatList[#uatList] == timestampedCountryId or Wikidata.isA(uatList[#uatList], {'Q6256'})) then
		uatListCountryId  = table.remove(uatList, #uatList)
	end
	return uatList, uatListCountryId
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, crtCountryId, uats)

	if lacData.locationNameOverrides[partId] then
		local timestampedLabel = getValueByTimestamp(lacData.locationNameOverrides[partId], ts)
		return illWd(partId, timestampedLabel)
	end

	local offLangCodes = {}
	if TableTools.contains(lacData.invariantLocations, partId) or
		(crtCountryId and TableTools.contains(lacData.invariantLocationCountries, crtCountryId))
		or TableTools.size(Set.intersection(recurseUats(partId), lacData.invariantLocationUats)) > 0
		or Wikidata.isA(partId, lacData.invariantLocationTypes) then
			
		offLangCodes = { 'ro' }
	else
		-- look for (1) official language of the location
		local officialLangClaims = Wikidata.findBestClaimsForProperty(partId, 'P37')
		--          (2) official language of one of the UATs
		if not officialLangClaims or #officialLangClaims == 0 then
			for _,eachUat in ipairs(uats or {}) do
				local uatOfficialLangClaims = Wikidata.findBestClaimsForProperty(eachUat, 'P37')
				if not officialLangClaims or #officialLangClaims == 0 then
					officialLangClaims = uatOfficialLangClaims
				end
			end
		end
		--          (3) official language og the country
		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
		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
				local offLangCode = Wikidata.loadOneValueInChain({eachOffLangId, 'P218'})
				if offLangCode and offLangCode ~= '' then
					if writingSystem == 'Q8229' or Transliteration.isTransliterationSupported(offLangCode) then
						table.insert(offLangCodes, offLangCode)
					end
				end
			end
		end
	end
	if #offLangCodes == 0 then table.insert(offLangCodes, 'ro') end
	for _,offLangCode in ipairs(offLangCodes) do
		local wdPropForName = Wikidata.isA(partId, lacData.shortNameLocationTypes) and 'P1813' or 'P1448'

		local officialNameClaim = Wikidata.findClaimForTimestamp(partId, wdPropForName, ts, offLangCode)
		if officialNameClaim and Wikidata.isClaimTrue(officialNameClaim) and Wikidata.hasValueSnak(officialNameClaim) then
			local nameInOffLang = officialNameClaim.mainsnak.datavalue.value.text
			if wdPropForName == 'P1448' and (not officialNameClaim.qualifiers or not officialNameClaim.qualifiers['P582']) then -- if this is a former name, use it
			-- if this is the current name, then just get the label from that language
				nameInOffLang = mw.wikibase.getLabelByLang(partId, offLangCode)
			end
			return illWd(partId, Transliteration.isTransliterationSupported(offLangCode) and Transliteration.transliterate(nameInOffLang, offLangCode) or nameInOffLang)
		end
	end

	return illWd(partId, Wikidata.findLabel(partId, offLangCodes[1]))
end

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

local function collapseCapitalledUat(locationEntityId, uatEntitiesIds)
	if not uatEntitiesIds or #uatEntitiesIds == 0 then return uatEntitiesIds end
	local out = {}
	local firstUatEntCapital = Wikidata.loadOneValueInChain({uatEntitiesIds[1], 'P36', 'raw'})
	if not firstUatEntCapital or firstUatEntCapital ~= locationEntityId then
		table.insert(out, uatEntitiesIds[1])
	end
	for idx = 2,#uatEntitiesIds do table.insert(out, uatEntitiesIds[idx]) end
	return out
		
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
				local countryByTs, countryTs = getCountryIdForTimestamp(locationEntityId, ts)
				countryId = countryId or countryByTs
				-- (1) see if admin unit needs to be pulled
				local uatsEnabled = shouldPullUats(locationEntityId, countryId)
				-- (2) pull all admin units based on timestamp until reaching nothing, self or country
				local allUats, uatCountryId = recurseUats(locationEntityId, ts)
				if uatsEnabled > 0 then
					if uatCountryId and uatCountryId ~= countryId and not countryTs then
						countryId = uatCountryId
					elseif countryTs and uatCountryId and uatCountryId ~= countryId then
						allUats = {}
					end
					-- (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
					local trimmedUats = trimUats(allUats)
					-- (5) keep only first and last (or the only one if only one is left)
					uatEntitiesIds = #trimmedUats == 0 and {} or #trimmedUats == 1 and trimmedUats or uatsEnabled == 1 and {trimmedUats[#trimmedUats]} or {trimmedUats[1], trimmedUats[#trimmedUats]}
					if #uatEntitiesIds > 1 then
						uatEntitiesIds = collapseCapitalledUat(locationEntityId, uatEntitiesIds)
					end
				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, Wikidata.loadOneValueInChain({locationEntityId, 'P17', 'raw'}), allUats)
				if not countryId and uatEntitiesIds and #uatEntitiesIds > 0 then
					countryId = Wikidata.loadOneValueInChain({uatEntitiesIds[#uatEntitiesIds], 'P17', 'raw'})
				end
				if countryId and not TableTools.contains(allPartIds, countryId) then
					table.insert(allPartLinks, lacData.countryNameOverrides[countryId] and illWd(countryId, lacData.countryNameOverrides[countryId]) or resolvePartName(countryId, ts, 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