Модуль:Wikidata/Places

Материал из Энциклопедии Гитармага
Перейти к: навигация, поиск

Для документации этого модуля может быть создана страница Модуль:Wikidata/Places/doc

local categorizeByPlaceOfBirthAndDeath = true;

local WDS = require('Module:WikidataSelectors')
local p = {}
local project = 'ruwiki';

local function min( prev, next )
	if ( prev == nil ) then return next;
	elseif ( prev > next ) then return next;
	else return prev; end
end

local function max( prev, next )
	if ( prev == nil ) then return next;
	elseif ( prev < next ) then return next;
	else return prev; end
end

local function getTimeBoundariesFromProperty( context, propertyId )
	mw.log( 'Get time boundaries for ' .. propertyId .. '...');

	local dateClaims = WDS.filter( context.entity.claims, propertyId );
	if ( not dateClaims or #dateClaims == 0 ) then return nil; end
	mw.log( 'Get time boundaries for ' .. propertyId .. '... Got ' .. #dateClaims .. ' date claim(s)');

	-- only support exact date so far, but need improvment
	local left = nil;
	local right = nil;
	for _, claim in pairs( dateClaims ) do
		if ( not claim.mainsnak ) then return nil; end
		local boundaries = context.parseTimeBoundariesFromSnak( claim.mainsnak );
		if ( not boundaries ) then return nil; end
		left = min( left, boundaries[1] );
		right = max( right, boundaries[2] );
	end

	if ( not left or not right ) then return nil; end

	mw.log( 'Time boundaries for ' .. propertyId .. ' are ' .. left .. ' and ' .. right );
	return { left, right };
end

local function getTimeBoundariesFromQualifiers( context, statement, qualifierId )
	-- only support exact date so far, but need improvment
	local left = nil;
	local right = nil;
	if ( statement.qualifiers and statement.qualifiers[qualifierId] ) then
		for _, qualifier in pairs( statement.qualifiers[qualifierId] ) do
			local boundaries = context.parseTimeBoundariesFromSnak( qualifier );
			if ( not boundaries ) then return nil; end
			left = min( left, boundaries[1] );
			right = max( right, boundaries[2] );
		end
	end

	if ( not left or not right ) then
		return nil;
	end

	return { left, right };
end

local function getParentsInBoundariesSnakImpl( context, entity, boundaries, propertyId )
	local results = {};

	if ( entity.claims and entity.claims[propertyId] ) then
		for _, claim in pairs( entity.claims[propertyId] ) do
			local startBoundaries = getTimeBoundariesFromQualifiers( context, claim, 'P580' );
			local endBoundaries = getTimeBoundariesFromQualifiers( context, claim, 'P582' );

			if ( (startBoundaries == nil or ( startBoundaries[2] <= boundaries[1]))
					and (endBoundaries == nil or ( endBoundaries[1] >= boundaries[2]))) then
				table.insert( results, claim.mainsnak );
			end 
		end
	end

	return results;
end

local function getParentsInBoundariesSnak( context, entity, boundaries )
	if ( not entity ) then error('entity must be specified'); end
	if ( type(entity) ~= 'table' ) then error('entity must be table'); end
	if ( not boundaries ) then error('boundaries must be specified'); end
	if ( type(boundaries) ~= 'table' ) then error('boundaries must be table'); end

	local results = getParentsInBoundariesSnakImpl( context, entity, boundaries, 'P131' )
	if ( not results ) then
		results = getParentsInBoundariesSnakImpl( context, entity, boundaries, 'P17' )
	end
	for r, result in pairs( results ) do
		local resultId = 'Q' .. result.datavalue.value['numeric-id'];
		if ( resultId == entity.id ) then
			return nil;
		end
	end
	return results;
end

local unions = { 
	Q1140229 = true, -- political union
	Q3623811 = true, -- Экономический союз
	Q4120211 = true -- региональная организация
}

local countries = { 
	Q6256 = true, -- страна
	Q7275 = true, -- государство
	Q3624078 = true -- суверенное государство
}

local function isSkipTopLevel( entity )
	local isCountry = false;
	local isUnion = false;
	if ( entity
			and entity.claims
			and entity.claims.P31 ) then
		for c, claim in pairs( entity.claims.P31 ) do
			if ( claim
					and claim.mainsnak
					and claim.mainsnak.datavalue
					and claim.mainsnak.datavalue.value
					and claim.mainsnak.datavalue.value['numeric-id'] ) then
				local typeId = 'Q' .. claim.mainsnak.datavalue.value['numeric-id'];
				isCountry = isCountry or countries[typeId];
				isUnion = isUnion or unions[typeId];
			end
		end
	end
	return isUnion and not isCountry;
end

local function isPartOfNext(prevLabel, nextLabel)
	return (mw.ustring.len(prevLabel) > mw.ustring.len(nextLabel))
		and (mw.ustring.sub( prevLabel, mw.ustring.len(prevLabel) - mw.ustring.len(nextLabel) + 1 ) == nextLabel);
end

--Property:P19, Property:P20, Property:P119
function p.formatPlaceWithQualifiers( context, options, statement )
	local property = mw.ustring.upper( options.property );
	mw.log( 'formatPlaceWithQualifiers(..., ' .. property .. ')');

	local entriesToLookupCategory = {};

	local circumstances = context.getSourcingCircumstances( statement );
	local result = context.formatSnak( options, statement.mainsnak, circumstances );
	insertFromSnak( statement.mainsnak, entriesToLookupCategory )

	local hasAdditionalQualifiers = false;
	if ( statement.qualifiers ) then
		--parent divisions
		if ( statement.qualifiers.P131 ) then
			for i, qualifier in ipairs( statement.qualifiers.P131 ) do
				result = result .. ', ' .. context.formatSnak( options, qualifier );
				insertFromSnak( qualifier, entriesToLookupCategory )
				hasAdditionalQualifiers = true;
			end
		end

		--country
		if ( statement.qualifiers.P17 ) then
			for i, qualifier in ipairs( statement.qualifiers.P17 ) do
				result = result .. ', ' .. context.formatSnak( options, qualifier );
				insertFromSnak( qualifier, entriesToLookupCategory )
				hasAdditionalQualifiers = true;
			end
		end
	end

	if ( statement.mainsnak
			and statement.mainsnak.datavalue
			and statement.mainsnak.datavalue.value
			and statement.mainsnak.datavalue.value['numeric-id'] ) then
		local entity = mw.wikibase.getEntity( 'Q' .. statement.mainsnak.datavalue.value['numeric-id'] );
		local parentSnaks = { statement.mainsnak };
		local parentEntities = { entity };

		local actualDateBoundariesProperty = nil;
		if ( property == 'P19' ) then actualDateBoundariesProperty = 'P569'; end
		if ( property == 'P20' ) then actualDateBoundariesProperty = 'P570'; end
		if ( actualDateBoundariesProperty ~= nil ) then
			local boundaries = getTimeBoundariesFromProperty( context, actualDateBoundariesProperty );
			if ( boundaries ) then
				local parent = entity;
				while ( parent ~= nil ) do
					-- get parent
					local newParentSnaks = getParentsInBoundariesSnak( context, parent, boundaries );
					if ( #newParentSnaks == 0 ) then
						parent = nil;
					elseif ( #newParentSnaks == 1 ) then
						local parentSnak = newParentSnaks[1];
						parent = mw.wikibase.getEntity( 'Q' .. parentSnak.datavalue.value['numeric-id'] );
						table.insert( parentSnaks, parentSnak );
						table.insert( parentEntities, parent );
					else
						parent = nil;
						result = result .. '[[Категория:Википедия:Страницы с неоднозначными геоцепочками]]';
					end
				end

				mw.logObject( parentSnaks );
				do
					local i = #parentSnaks;
					while ( i > 1 ) do
						local prevEntity = parentEntities[i - 1];
						local nextEntity = parentEntities[i];
						if ( prevEntity and nextEntity
								and prevEntity.labels and nextEntity.labels
								and prevEntity.labels.ru and nextEntity.labels.ru
								and prevEntity.labels.ru.value and nextEntity.labels.ru.value) then
							local prevLabel = prevEntity.labels.ru.value;
							local nextLabel = nextEntity.labels.ru.value;
							if ( prevLabel == nextLabel ) then
								-- do not output same label twice (NY, NY, USA)
								table.remove( parentSnaks, i );
								table.remove( parentEntities, i );
							elseif ( isPartOfNext( prevLabel, ' ' .. nextLabel) ) then
								-- do not output same label if it's part of previos
								table.remove( parentSnaks, i - 1 );
								table.remove( parentEntities, i - 1 );
							end
						end
						i = i - 1;
					end
				end

				if ( isSkipTopLevel( parentEntities[ #parentEntities ] ) ) then
					table.remove( parentSnaks, #parentEntities );
					table.remove( parentEntities, #parentEntities );
				end

				if ( not hasAdditionalQualifiers ) then
					for i=2,#parentSnaks,1 do
						local parentSnak = parentSnaks[i];
      					result = result .. ', ' .. context.formatSnak( options, parentSnak );
      					insertFromSnak( parentSnak, entriesToLookupCategory )
    				end
				end
			end
		end
	end

	result =  result .. context.formatRefs( options, statement );

	if ( categorizeByPlaceOfBirthAndDeath ) then
		if ( property == 'P19' ) then result = result .. getCategory( 'P1464', entriesToLookupCategory ); end
		if ( property == 'P20' ) then result = result .. getCategory( 'P1465', entriesToLookupCategory ); end
		if ( property == 'P119' ) then result = result .. getCategory( 'P1791', entriesToLookupCategory ); end
	end

	return result;
end

-- append entity id from snak to result
function insertFromSnak( snak, result )
	if ( not categorizeByPlaceOfBirthAndDeath ) then
		return;
	end
	if ( snak 
			and snak.datavalue
			and snak.datavalue.type == 'wikibase-entityid'
			and snak.datavalue.value
			and snak.datavalue.value['entity-type'] == 'item' ) then
		table.insert( result, 'Q' .. snak.datavalue.value['numeric-id'] );
	end
end

function getCategory( propertyToSearch, entriesToLookupCategoryFor )
	for _, placeId in pairs( entriesToLookupCategoryFor ) do
		local placeEntity = mw.wikibase.getEntity( placeId );
		local claims = WDS.filter( placeEntity.claims, propertyToSearch );

		if ( claims ) then
			for _, claim in pairs( claims ) do
				if ( claim.mainsnak
						and claim.mainsnak
						and claim.mainsnak.datavalue
						and claim.mainsnak.datavalue.type == "wikibase-entityid" ) then
					local catEntityId =  'Q' .. claim.mainsnak.datavalue.value["numeric-id"];
					local catEntity = mw.wikibase.getEntity( catEntityId );
					if ( catEntity and catEntity.sitelinks and catEntity.sitelinks[project] and catEntity.sitelinks[project].title ) then
						return '[[' .. catEntity.sitelinks[project].title .. ']]';
					end
				end
			end
		end
	end
	return '';
end

return p;