Модуль:Wikidata/Places
Wikimedia module / From Wikipedia, the free encyclopedia
local categorizeByPlace = true;
local WDS = require( 'Module:WikidataSelectors' ); local Flags = require( 'Module:Wikidata/Flags' ); local p = { config = { hideSameLabels = false, hidePartOfLabels = false, hideUnitsForCapitals = true, reverseOrder = false, catAmbiguousGeoChains = '', catWikibaseError = '' } };
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 ) local dateClaims = WDS.filter( context.entity.claims, propertyId ); if not dateClaims or #dateClaims == 0 then return nil; end
-- 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
return { left, right }; end
local function getTimeBoundariesFromProperties( context, propertyIds ) for _, propertyId in ipairs( propertyIds ) do local result = getTimeBoundariesFromProperty( context, propertyId ); if result then return result; end end
return nil; 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, entityId, boundaries, propertyIds, selectors ) local results = {};
if not propertyIds or #propertyIds == 0 then return results; end
for _, propertyId in ipairs( propertyIds ) do if (not string.match( propertyId, '^P%d+$' )) then error('Incorrect propertyId: ' + propertyId); end local selector; if (selectors ~= nil) then selector = selectors[propertyId] or propertyId; else selector = propertyId; end
local entityClaims = {}; entityClaims[propertyId] = mw.wikibase.getAllStatements( entityId, propertyId );
local filteredClaims = WDS.filter( entityClaims, selector .. '[rank:preferred, rank:normal]' ); if filteredClaims then for _, claim in pairs( filteredClaims ) do if not boundaries or not propertyIds or #propertyIds == 0 then table.insert( results, claim.mainsnak ); else 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 end
if #results > 0 then break; end end
return results; end
local function getParentsInBoundariesSnak( context, entityId, boundaries ) if not entityId then error('entityId must be specified'); end if type(entityId) ~= 'string' then error('entityId must be string'); 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, entityId, boundaries, {'P131'} ) -- located in if not results or #results == 0 then results = getParentsInBoundariesSnakImpl( context, entityId, boundaries, {'P17'} ) -- country end
for r, result in pairs( results ) do if result.snaktype ~= 'value' then return nil; end local resultId = result.datavalue.value.id; if resultId == entityId then return nil; end end return results; end
local unions = { Q1140229 = true, -- political union Q3623811 = true, -- Экономический союз Q4120211 = true -- региональная организация }
local countries = { Q6256 = true, -- страна Q7275 = true, -- государство Q3024240 = 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.id then local typeId = claim.mainsnak.datavalue.value.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 );
local actualDateBoundariesProperties = nil; if property == 'P19' then actualDateBoundariesProperties = {'P569','P570'}; end if property == 'P20' then actualDateBoundariesProperties = {'P570','P569'}; end if property == 'P119' then actualDateBoundariesProperties = {'P570','P569'}; end if property == 'P159' then actualDateBoundariesProperties = {'P576'}; end
local boundaries = nil; if actualDateBoundariesProperties ~= nil then boundaries = getTimeBoundariesFromProperties( context, actualDateBoundariesProperties ); if (boundaries == nil) and (property == 'P159') then boundaries = {os.time() * 1000, os.time() * 1000}; end end
local entriesToLookupCategory = {};
local circumstances = context.getSourcingCircumstances( statement ); local result = ; local baseResult = context.formatSnak( options, statement.mainsnak, circumstances ); if not baseResult then return nil; end
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 local parentOptions = context.cloneOptions( options ); local qualifierEntityId = qualifier.datavalue.value.id; parentOptions['text'] = getLabel( context, qualifierEntityId, boundaries ); local link = context.formatSnak( parentOptions, qualifier );
if p.config.reverseOrder then result = link .. ', ' .. result; else result = result .. ', ' .. link; end insertFromSnak( qualifier, entriesToLookupCategory ) hasAdditionalQualifiers = true; end end
--country if statement.qualifiers.P17 then for i, qualifier in ipairs( statement.qualifiers.P17 ) do local parentOptions = context.cloneOptions( options ); local qualifierEntityId = qualifier.datavalue.value.id; parentOptions[ 'text' ] = getLabel( context, qualifierEntityId, boundaries ); local link = context.formatSnak( parentOptions, qualifier );
if p.config.reverseOrder then result = link .. ', ' .. result; else result = result .. ', ' .. link; end 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.id then local entityId = statement.mainsnak.datavalue.value.id; local parentSnaks = { statement.mainsnak }; local parentEntityIds = { entityId };
if actualDateBoundariesProperties ~= nil then local filterCapitalOf = { [ entityId ] = getParentsInBoundariesSnakImpl( context, entityId, boundaries, {'P1376'} ) }; if boundaries then local entityOptions = context.cloneOptions( options ); entityOptions['text'] = getLabel( context, entityId, boundaries ); baseResult = context.formatSnak( entityOptions, statement.mainsnak, circumstances );
local parentId = entityId; while parentId ~= nil do -- get parent local newParentSnaks = getParentsInBoundariesSnak( context, parentId, boundaries ); if not newParentSnaks or #newParentSnaks == 0 then parentId = nil; elseif #newParentSnaks == 1 then local parentSnak = newParentSnaks[ 1 ]; parentId = parentSnak.datavalue.value.id;
table.insert( parentSnaks, parentSnak ); table.insert( parentEntityIds, parentId ); filterCapitalOf[ parentId ] = getParentsInBoundariesSnakImpl( context, parentId, boundaries, { 'P1376' } ); else parentId = nil; if p.config and p.config.catAmbiguousGeoChains then result = result .. p.config.catAmbiguousGeoChains; end end end
if not hasAdditionalQualifiers then for i = 2, #parentSnaks, 1 do local parentSnak = parentSnaks[ i ]; insertFromSnak( parentSnak, entriesToLookupCategory ) end end
--do not output similar countries like "Denmark, the Kingdom of Denmark" if (#parentSnaks > 1) then if ((parentSnaks[ #parentSnaks ].datavalue.value['numeric-id'] == 756617) and (parentSnaks[ #parentSnaks-1 ].datavalue.value['numeric-id'] == 35)) or ((parentSnaks[ #parentSnaks ].datavalue.value['numeric-id'] == 29999) and (parentSnaks[ #parentSnaks-1 ].datavalue.value['numeric-id'] == 55)) then table.remove( parentSnaks, #parentSnaks ); table.remove( parentEntityIds, #parentEntityIds ); end end
do local capofstate = false; local i = #parentSnaks; while i > 1 do local prevEntityId = parentEntityIds[ i - 1 ]; -- TODO: use English labels, if there is no current language labels local prevLabel = getLabel( context, prevEntityId, boundaries ) or ; local nextEntityId = parentEntityIds[ i ]; local nextLabel = getLabel( context, nextEntityId, boundaries ) or ; if p.config and p.config.hideSameLabels == true and prevLabel == nextLabel then -- do not output same label twice (NY, NY, USA) table.remove( parentSnaks, i ); table.remove( parentEntityIds, i ); elseif p.config and p.config.hidePartOfLabels == true and isPartOfNext( prevLabel, ' ' .. nextLabel ) then -- do not output same label if it's part of previos table.remove( parentSnaks, i - 1 ); table.remove( parentEntityIds, i - 1 ); elseif p.config and p.config.hideUnitsForCapitals == true then -- do not ouput items whose capital is the first item local capitalId = nil; for _capitalId, capitalSnaks in pairs( filterCapitalOf ) do if #capitalSnaks > 0 then for __, capitalSnak in pairs( capitalSnaks ) do if parentSnaks[ i ].datavalue.value.id == capitalSnak.datavalue.value.id then capitalId = _capitalId; if (i == #parentSnaks) then capofstate = true; end break; end end end end if capitalId ~= nil then if i == #parentSnaks then i = i - 1; end -- always ouput constituent countries like England or Russian SFSR if (i == (#parentSnaks-1)) and (capofstate == false) then local p31 = mw.wikibase.getAllStatements(parentEntityIds[ i ], 'P31'); for _, iOf in pairs (p31) do if (iOf.mainsnak.datavalue.value['numeric-id'] == 236036) or (iOf.mainsnak.datavalue.value['numeric-id'] == 3336843) or (iOf.mainsnak.datavalue.value['numeric-id'] == 12959600) or (iOf.mainsnak.datavalue.value['numeric-id'] == 56219758) or (iOf.mainsnak.datavalue.value['numeric-id'] == 15304003) or (iOf.mainsnak.datavalue.value['numeric-id'] == 66724388) then i = i - 1; end end end while i > 1 and parentEntityIds[ i ] ~= capitalId do table.remove( parentSnaks, i ); table.remove( parentEntityIds, i ); i = i - 1; end end end i = i - 1; end end
if isSkipTopLevel( parentEntityIds[ #parentEntityIds ] ) then table.remove( parentSnaks, #parentEntityIds ); table.remove( parentEntityIds, #parentEntityIds ); end
if not hasAdditionalQualifiers then for i = 2, #parentSnaks, 1 do local parentSnak = parentSnaks[ i ];
local parentOptions = context.cloneOptions( options ); parentOptions['text'] = getLabel( context, parentEntityIds[ i ], boundaries );
if p.config.reverseOrder then result = context.formatSnak( parentOptions, parentSnak ) .. ', ' .. result; else result = result .. ', ' .. context.formatSnak( parentOptions, parentSnak ); end end end end end end
if options[ 'thisLocationOnly' ] then result = baseResult .. context.formatRefs( options, statement ); elseif p.config.reverseOrder then result = result .. baseResult .. context.formatRefs( options, statement ); else result = baseResult .. result .. context.formatRefs( options, statement ); end
if categorizeByPlace 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 categorizeByPlace 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, snak.datavalue.value.id ); end end
function getCategory( propertyId, entriesToLookupCategoryFor ) for _, placeId in pairs( entriesToLookupCategoryFor ) do local claims = mw.wikibase.getBestStatements(placeId, propertyId);
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 = claim.mainsnak.datavalue.value.id; local catSitelink = mw.wikibase.getSitelink(catEntityId); if (catSitelink) then return '' .. catSitelink .. ''; end end end end end return ; end
local historicNamesProperties = { 'P1813', 'P1448', 'P1705' };
local langCode = mw.language.getContentLanguage():getCode(); local historicNamesPropertySelectors = { P1813 = 'P1813[language:' .. langCode .. ']', P1448 = 'P1448[language:' .. langCode .. ']', P1705 = 'P1705[language:' .. langCode .. ']' };
-- get current of historic name of place function getLabel( context, entityId, boundaries ) if not entityId then return nil; end if (type(entityId) ~= 'string') then error('incorrect type of entityId argument'); end;
local label = nil;
-- name from properties local results = getParentsInBoundariesSnakImpl( context, entityId, boundaries, historicNamesProperties, historicNamesPropertySelectors);
for r, result in pairs( results ) do if result.datavalue and result.datavalue.value and result.datavalue.value.text then label = result.datavalue.value.text; break; end end
-- name from label if label == nil then
label = mw.wikibase.getLabel( entityId );
end
return label;
end
p.getLabel = getLabel;
local function calculateEndDateTimestamp( context, options, statement ) if not context then error( 'context not specified' ) end; if not options then error( 'options not specified' ) end; if not options.entity then error( 'options.entity missing' ) end; if not statement then error( 'statement not specified' ) end;
if statement.qualifiers and statement.qualifiers.P582 then for i, qualifier in ipairs( statement.qualifiers.P582 ) do local parsedTime = context.parseTimeFromSnak( qualifier ); if parsedTime then return parsedTime; end end end
-- check death day... do we have it at all? for h, propertyId in pairs( { "P570", "P577", "P576" } ) do local dateClaims = context.selectClaims( options, propertyId ); if dateClaims then for i, statement in ipairs( dateClaims ) do local parsedTime = context.parseTimeFromSnak( statement.mainsnak ); if parsedTime then return parsedTime; end end end end
-- TODO: check other "end" properties
-- no death day return os.time() * 1000; end
local function deleteTwinAncestors( countryEntityId, propertyId ) local badTwin if ( countryEntityId == 'Q174193' ) then badTwin = 'Q145' elseif ( countryEntityId == 'Q838261' ) then badTwin = 'Q37024' else return true; end for _, claim in pairs( mw.wikibase.getBestStatements( mw.wikibase.getEntityIdForCurrentPage(), propertyId ) ) do if ( claim and claim.mainsnak and claim.mainsnak.datavalue and claim.mainsnak.datavalue.value and claim.mainsnak.datavalue.value.id ) then local actualId = claim.mainsnak.datavalue.value.id; if ( actualId == badTwin ) then return false; end end end return true; end
function p.formatCountryClaimWithFlag( context, options, statement ) if not context then error('context not specified') end; if not options then error('options not specified') end; if not options.entity then error('options.entity is missing') end; if not statement then error('statement not specified') end;
if not statement.mainsnak or not statement.mainsnak.datavalue or not statement.mainsnak.datavalue.value or not statement.mainsnak.datavalue.value.id then local result = context.formatStatementDefault( context, options, statement ); if not result then return ; end return '' .. result .. ''; end
local countryEntityId = statement.mainsnak.datavalue.value.id; local endDateTimestamp = calculateEndDateTimestamp( context, options, statement ); local boundaries = getTimeBoundariesFromProperties( context, {'P570', 'P577', 'P571'} );
if deleteTwinAncestors( countryEntityId, string.upper(options.property) ) then
local countryOptions = context.cloneOptions( options ); if not countryOptions['text'] or countryOptions['text'] == then countryOptions['text'] = getLabel( context, countryEntityId, boundaries ); end
local flag = Flags.getFlag( context, countryEntityId, endDateTimestamp ); if flag then return flag .. ' ' .. context.formatStatementDefault( context, countryOptions, statement ) .. ''; end
return '' .. context.formatStatementDefault( context, countryOptions, statement ) .. ''; else return nil; end end
return p;