Модуль:Wikidata/Places

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

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 = '[[Категори:Википеди:Страницы с неоднозначными геоцепочками]]',catLoopInGeoChains = '[[Категори:Википеди:Страницы с зацикливающимися геоцепочками]]',catWikibaseError = '[[Категори:Википеди:Страницы с ошибками скриптов, использующих Викиданные]]'}};local function min( prev, next )if prev == nil thenreturn next;elseif prev > next thenreturn next;elsereturn prev;endendlocal function max( prev, next )if prev == nil thenreturn next;elseif prev < next thenreturn next;elsereturn prev;endendlocal function getTimeBoundariesFromProperty( context, propertyId )local dateClaims = WDS.filter( context.entity.claims, propertyId );if not dateClaims or #dateClaims == 0 thenreturn nil;end-- only support exact date so far, but need improvmentlocal left = nil;local right = nil;for _, claim in pairs( dateClaims ) doif not claim.mainsnak then return nil; endlocal boundaries = context.parseTimeBoundariesFromSnak( claim.mainsnak );if not boundaries then return nil; endleft = min( left, boundaries[ 1 ] );right = max( right, boundaries[ 2 ] );endif not left or not right then return nil; endreturn { left, right };endlocal function getTimeBoundariesFromProperties( context, propertyIds )for _, propertyId in ipairs( propertyIds ) dolocal result = getTimeBoundariesFromProperty( context, propertyId );if result thenreturn result;endendreturn nil;endlocal function getTimeBoundariesFromQualifiers( context, statement, qualifierId )-- only support exact date so far, but need improvmentlocal left = nil;local right = nil;if statement.qualifiers and statement.qualifiers[qualifierId] thenfor _, qualifier in pairs( statement.qualifiers[qualifierId] ) dolocal boundaries = context.parseTimeBoundariesFromSnak( qualifier );if not boundaries then return nil; endleft = min( left, boundaries[1] );right = max( right, boundaries[2] );endendif not left or not right thenreturn nil;endreturn { left, right };endlocal function getParentsInBoundariesSnakImpl( context, entityId, boundaries, propertyIds, selectors )local results = {};if not propertyIds or #propertyIds == 0 thenreturn results;endfor _, propertyId in ipairs( propertyIds ) doif (not string.match( propertyId, '^P%d+$' )) then error('Incorrect propertyId: ' + propertyId); endlocal selector;if (selectors ~= nil) thenselector = selectors[propertyId] or propertyId;elseselector = propertyId;endlocal entityClaims = {};entityClaims[propertyId] = mw.wikibase.getAllStatements( entityId, propertyId );local filteredClaims = WDS.filter( entityClaims, selector .. '[rank:preferred, rank:normal]' );if filteredClaims thenfor _, claim in pairs( filteredClaims ) doif not boundaries or not propertyIds or #propertyIds == 0 thentable.insert( results, claim.mainsnak );elselocal 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] )thentable.insert( results, claim.mainsnak );end endendendif #results > 0 thenbreak;endendreturn results;endlocal function getParentsInBoundariesSnak( context, entityId, boundaries )if not entityId then error('entityId must be specified'); endif type(entityId) ~= 'string' then error('entityId must be string'); endif not boundaries then error('boundaries must be specified'); endif type(boundaries) ~= 'table' then error('boundaries must be table'); endlocal results = getParentsInBoundariesSnakImpl( context, entityId, boundaries, {'P131'} ) -- located inif not results or #results == 0 thenresults = getParentsInBoundariesSnakImpl( context, entityId, boundaries, {'P17'} ) -- countryendfor r, result in pairs( results ) doif result.snaktype ~= 'value' thenreturn nil;endlocal resultId = result.datavalue.value.id;if resultId == entityId thenreturn nil;endendreturn results;endlocal unions = { Q1140229 = true, -- political unionQ3623811 = 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 andentity.claims andentity.claims.P31thenfor c, claim in pairs( entity.claims.P31 ) doif claim andclaim.mainsnak andclaim.mainsnak.datavalue andclaim.mainsnak.datavalue.value andclaim.mainsnak.datavalue.value.idthenlocal typeId = claim.mainsnak.datavalue.value.id;isCountry = isCountry or countries[ typeId ];isUnion = isUnion or unions[ typeId ];endendendreturn isUnion and not isCountry;endlocal 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:P119function p.formatPlaceWithQualifiers( context, options, statement )local property = mw.ustring.upper( options.property );local actualDateBoundariesProperties = nil;if property == 'P19' then actualDateBoundariesProperties = {'P569','P570'}; endif property == 'P20' then actualDateBoundariesProperties = {'P570','P569'}; endif property == 'P119' then actualDateBoundariesProperties = {'P570','P569'}; endif property == 'P159' then actualDateBoundariesProperties = {'P576'}; endlocal boundaries = nil;if actualDateBoundariesProperties ~= nil thenboundaries = getTimeBoundariesFromProperties( context, actualDateBoundariesProperties );if (boundaries == nil) and (property == 'P159') thenboundaries = {os.time() * 1000, os.time() * 1000};endendlocal entriesToLookupCategory = {};local circumstances = context.getSourcingCircumstances( statement );local result = '';local baseResult = context.formatSnak( options, statement.mainsnak, circumstances );if not baseResult thenreturn nil;endinsertFromSnak( statement.mainsnak, entriesToLookupCategory )local hasAdditionalQualifiers = false;if statement.qualifiers then--parent divisionsif statement.qualifiers.P131 thenfor i, qualifier in ipairs( statement.qualifiers.P131 ) doif qualifier.datavalue thenlocal 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 thenresult = link .. ', ' .. result;elseresult = result .. ', ' .. link;endinsertFromSnak( qualifier, entriesToLookupCategory )hasAdditionalQualifiers = true;endendend--countryif statement.qualifiers.P17 thenfor i, qualifier in ipairs( statement.qualifiers.P17 ) doif qualifier.datavalue thenlocal 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 thenresult = link .. ', ' .. result;elseresult = result .. ', ' .. link;endinsertFromSnak( qualifier, entriesToLookupCategory )hasAdditionalQualifiers = true;endendendendif statement.mainsnak andstatement.mainsnak.datavalue andstatement.mainsnak.datavalue.value andstatement.mainsnak.datavalue.value.idthenlocal entityId = statement.mainsnak.datavalue.value.id;local parentSnaks = { statement.mainsnak };local parentEntityIds = { entityId };if actualDateBoundariesProperties ~= nil thenlocal filterCapitalOf = {[ entityId ] = getParentsInBoundariesSnakImpl( context, entityId, boundaries, {'P1376'} )};if boundaries thenlocal 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 parentlocal newParentSnaks = getParentsInBoundariesSnak( context, parentId, boundaries );if not newParentSnaks or #newParentSnaks == 0 thenparentId = nil;elseif #newParentSnaks == 1 thenlocal parentSnak = newParentSnaks[ 1 ];parentId = parentSnak.datavalue.value.id;local hasLoop = falsefor _, parentEntityId in pairs(parentEntityIds) doif parentEntityId == parentId thenhasLoop = trueendendif hasLoop thenif p.config and p.config.catLoopInGeoChains thenresult = result .. p.config.catLoopInGeoChains;endbreak -- while parentId ~= nil doendtable.insert( parentSnaks, parentSnak );table.insert( parentEntityIds, parentId );filterCapitalOf[ parentId ] = getParentsInBoundariesSnakImpl( context, parentId, boundaries, { 'P1376' } );elseparentId = nil;if p.config and p.config.catAmbiguousGeoChains thenresult = result .. p.config.catAmbiguousGeoChains;endendendif not hasAdditionalQualifiers thenfor i = 2, #parentSnaks, 1 dolocal parentSnak = parentSnaks[ i ];insertFromSnak( parentSnak, entriesToLookupCategory )endend-- do not output similar countries like "Denmark, the Kingdom of Denmark"local simularCountries = {['Q41304'] = 'Q1206012', -- Weimar Republic / German Reich['Q7318'] = 'Q1206012', -- Weimar Republic / Nazi Germany['Q35'] = 'Q756617', -- Denmark / Danish Realm['Q55'] = 'Q29999', -- Netherlands / Kingdom of the Netherlands['Q32081'] = 'Q865', -- Taiwan Province / Taiwan}if (#parentSnaks > 1) thenfor smallerCountryId, largerCountryId in pairs( simularCountries ) doif parentSnaks[ #parentSnaks ].datavalue.value.id == largerCountryIdand parentSnaks[ #parentSnaks - 1 ].datavalue.value.id == smallerCountryIdthentable.remove( parentSnaks, #parentSnaks );table.remove( parentEntityIds, #parentEntityIds );endendend-- optimization for capital regionsif (#parentSnaks > 3) thenif parentSnaks[ #parentSnaks - 2 ].datavalue.value.id == 'Q23939248' --Greater London, Greater Londonand parentSnaks[ #parentSnaks - 3 ].datavalue.value.id == 'Q23306'thentable.remove( parentSnaks, #parentSnaks - 2 );table.remove( parentEntityIds, #parentEntityIds - 2 );endendif (#parentSnaks > 2) thenif parentSnaks[ #parentSnaks - 1 ].datavalue.value.id == 'Q240' --Brussels-Capital, Brusselsand parentSnaks[ #parentSnaks - 2 ].datavalue.value.id == 'Q90870'thentable.remove( parentSnaks, #parentSnaks - 2 );table.remove( parentEntityIds, #parentEntityIds - 2 );endend-- do not output (maternity) hospitals, houses and streets but do it for manor and English country houseslocal unignoredTypes = {'Q879050', -- manor house'Q1343246', -- English country house}local ignoredTypes = {'Q3947', -- house'Q16917', -- hospital'Q34442', -- road'Q79007', -- street'Q174782', -- square'Q958822', -- maternity hospital'Q2087181', -- historic house museum}if (#parentSnaks > 1) thenlocal p31 = mw.wikibase.getAllStatements( parentEntityIds[ 1 ], 'P31' );local doignore = true;for _, iOf in ipairs( p31 ) dofor _, unignoredTypeId in ipairs( unignoredTypes ) doif ( iOf.mainsnak.datavalue.value.id == unignoredTypeId ) thendoignore = false;unignoredTypes = {};ignoredTypes = {};break;endendendif (doignore) thenfor _, iOf in ipairs( p31 ) dofor _, ignoredTypeId in ipairs( ignoredTypes ) doif ( iOf.mainsnak.datavalue.value.id == ignoredTypeId ) thenbaseResult = '';unignoredTypes = {};ignoredTypes = {};break;endendendendenddolocal capofstate = false;local i = #parentSnaks;while i > 1 dolocal prevEntityId = parentEntityIds[ i - 1 ];-- TODO: use English labels, if there is no current language labelslocal 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 previostable.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 itemlocal capitalId = nil;for _capitalId, capitalSnaks in pairs( filterCapitalOf ) doif #capitalSnaks > 0 thenfor __, capitalSnak in pairs( capitalSnaks ) doifcapitalSnak.datavalue and parentSnaks[ i ].datavalue.value.id == capitalSnak.datavalue.value.id thencapitalId = _capitalId;if (i == #parentSnaks) thencapofstate = true;endbreak;endendendendif capitalId ~= nil thenif i == #parentSnaks theni = i - 1;end-- always ouput constituent countries like England or Russian SFSRif (i == (#parentSnaks-1)) and (capofstate == false) thenlocal p31 = mw.wikibase.getAllStatements(parentEntityIds[ i ], 'P31');for _, iOf in pairs (p31) doif (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) theni = i - 1;endendendwhile i > 1 and parentEntityIds[ i ] ~= capitalId dotable.remove( parentSnaks, i );table.remove( parentEntityIds, i );i = i - 1;endendendi = i - 1;endendif isSkipTopLevel( parentEntityIds[ #parentEntityIds ] ) thentable.remove( parentSnaks, #parentEntityIds );table.remove( parentEntityIds, #parentEntityIds );endif not hasAdditionalQualifiers thenfor i = 2, #parentSnaks, 1 dolocal parentSnak = parentSnaks[ i ];local parentOptions = context.cloneOptions( options );parentOptions['text'] = getLabel( context, parentEntityIds[ i ], boundaries );local comma;if ((baseResult == '') and (i == 2)) thencomma = '';elsecomma = ', ';endif p.config.reverseOrder thenresult = context.formatSnak( parentOptions, parentSnak ) .. comma .. result;elseresult = result .. comma .. context.formatSnak( parentOptions, parentSnak );endendendendendendif options[ 'thisLocationOnly' ] thenresult = baseResult;elseif p.config.reverseOrder thenresult = result .. baseResult;elseresult = baseResult .. result;endif options.references thenresult = result .. context.formatRefs( options, statement );endif categorizeByPlace thenif property == 'P19' then result = result .. getCategory( 'P1464', entriesToLookupCategory ); endif property == 'P20' then result = result .. getCategory( 'P1465', entriesToLookupCategory ); endif property == 'P119' then result = result .. getCategory( 'P1791', entriesToLookupCategory ); endendreturn result;end-- append entity id from snak to resultfunction insertFromSnak( snak, result )if not categorizeByPlace thenreturn;endif snak andsnak.datavalue andsnak.datavalue.type == 'wikibase-entityid' andsnak.datavalue.value andsnak.datavalue.value[ 'entity-type' ] == 'item'thentable.insert( result, snak.datavalue.value.id );endendfunction getCategory( propertyId, entriesToLookupCategoryFor )if mw.title.getCurrentTitle().namespace == 0 thenfor _, placeId in pairs( entriesToLookupCategoryFor ) dolocal claims = mw.wikibase.getBestStatements(placeId, propertyId);if claims thenfor _, claim in pairs( claims ) doif claim.mainsnak andclaim.mainsnak andclaim.mainsnak.datavalue andclaim.mainsnak.datavalue.type == 'wikibase-entityid'thenlocal catEntityId = claim.mainsnak.datavalue.value.id;local catSitelink = mw.wikibase.getSitelink(catEntityId);if (catSitelink) thenreturn '[[' .. catSitelink .. ']]';endendendendendendreturn '';endlocal historicNamesProperties = { 'P1813', 'P1448', 'P1705' };local langCode = mw.language.getContentLanguage():getCode();local historicNamesPropertySelectors = {P1813 = 'P1813[language:' .. langCode .. '][!P3831,P3831:Q105690470]',P1448 = 'P1448[language:' .. langCode .. '][!P3831,P3831:Q105690470]',P1705 = 'P1705[language:' .. langCode .. '][!P3831,P3831:Q105690470]'};-- get current of historic name of placefunction getLabel( context, entityId, boundaries )if not entityId thenreturn nil;endif (type(entityId) ~= 'string') then error('incorrect type of entityId argument'); end;    local label = nil;-- name from propertieslocal results = getParentsInBoundariesSnakImpl( context, entityId, boundaries,historicNamesProperties, historicNamesPropertySelectors);for r, result in pairs( results ) doif result.datavalue andresult.datavalue.value andresult.datavalue.value.textthenlabel = result.datavalue.value.text;break;endend    -- name from label    if label == nil thenlabel = mw.wikibase.getLabel( entityId );    end    return label;endp.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 thenfor i, qualifier in ipairs( statement.qualifiers.P582 ) dolocal parsedTime = context.parseTimeFromSnak( qualifier );if parsedTime thenreturn parsedTime;endendend-- check death day... do we have it at all?for h, propertyId in pairs( { "P570", "P577", "P576" } ) dolocal dateClaims = context.selectClaims( options, propertyId );if dateClaims thenfor i, statement in ipairs( dateClaims ) dolocal parsedTime = context.parseTimeFromSnak( statement.mainsnak );if parsedTime thenreturn parsedTime;endendendend-- TODO: check other "end" properties-- no death dayreturn os.time() * 1000;endlocal function deleteTwinAncestors( countryEntityId, propertyId ) --do not display countries which have twin ancestorslocal badTwinsif ( countryEntityId == 'Q174193' ) then--Great Britain and IrelandbadTwins = {'Q145'}--Great Brirani & NI    elseif ( countryEntityId == 'Q161885' ) then--Great BritainbadTwins = {'Q174193'}--Great Britain and Irelandelseif ( countryEntityId == 'Q43287' ) then--German ImpirebadTwins = {'Q41304', 'Q7318', 'Q2415901', 'Q183'}--Weimar Republic or Nazi Germany or Allied-occupied Germany or Germanyelseif ( countryEntityId == 'Q41304' ) then--Weimar RepublicbadTwins = {'Q7318', 'Q2415901', 'Q183'}--Nazi Germany or Allied-occupied Germany or Germanyelseif ( countryEntityId == 'Q7318' ) then--Nazi GermanybadTwins = {'Q2415901', 'Q183'}--Allied-occupied Germany or Germany    elseif ( countryEntityId == 'Q2415901' ) then--Allied-occupied GermanybadTwins = {'Q183'}--Germanyelseif ( countryEntityId == 'Q696908' ) then--Kingdom of PolandbadTwins = {'Q207272', 'Q211274', 'Q36'}--Second Polish Republic or Polish People's Republic or Polandelseif ( countryEntityId == 'Q207272' ) then--Second Polish RepublicbadTwins = {'Q211274', 'Q36'}--Polish People's Republic or Polandelseif ( countryEntityId == 'Q211274' ) then--Polish People's RepublicbadTwins = {'Q36'}--Polandelseif ( countryEntityId == 'Q203493' ) then--Kingdom of RomaniabadTwins = {'Q842794', 'Q218'}--Socialist Republic of Romania or Romaniaelseif ( countryEntityId == 'Q842794' ) then--Socialist Republic of RomaniabadTwins = {'Q218'}--Romaniaelseif ( countryEntityId == 'Q838261' ) then--FR of YugoslaviabadTwins = {'Q37024'}--Serbia & Montenegroelsereturn true;endlocal listforcheckif propertyId == 'P1532' thenlistforcheck = mw.wikibase.getAllStatements( mw.wikibase.getEntityIdForCurrentPage(), propertyId );elselistforcheck = mw.wikibase.getBestStatements( mw.wikibase.getEntityIdForCurrentPage(), propertyId );endfor _, claim in pairs( listforcheck ) doif ( claim and claim.mainsnakand claim.mainsnak.datavalueand claim.mainsnak.datavalue.valueand claim.mainsnak.datavalue.value.id ) thenlocal actualId = claim.mainsnak.datavalue.value.id;for index, value in ipairs(badTwins) doif ( value == actualId ) then            return false;        endendendendreturn true; endfunction 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 ornot statement.mainsnak.datavalue ornot statement.mainsnak.datavalue.value ornot statement.mainsnak.datavalue.value.idthenlocal result = context.formatStatementDefault( context, options, statement );if not result thenreturn '';endreturn '<span class="country-name">' .. result .. '</span>';endlocal 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) ) thenlocal countryOptions = context.cloneOptions( options );if not countryOptions['text'] or countryOptions['text'] == '' thencountryOptions['text'] = getLabel( context, countryEntityId, boundaries );endlocal flag = Flags.getFlag( context, countryEntityId, endDateTimestamp );if flag thenreturn flag .. '&nbsp;<span class="country-name">' ..context.formatStatementDefault( context, countryOptions, statement ) ..'</span>';endreturn '<span class="country-name">' ..context.formatStatementDefault( context, countryOptions, statement ) ..'</span>';elsereturn nil;endendreturn p;