Модуль:Wikidata/Places: различия между версиями
Перейти к навигации
Перейти к поиску
(datavalue может быть явно указанный no value) |
м (1 версия импортирована) |
(нет различий)
|
Текущая версия на 11:07, 15 октября 2020
Для документации этого модуля может быть создана страница Модуль: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 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 if qualifier.datavalue then 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 --country if statement.qualifiers.P17 then for i, qualifier in ipairs( statement.qualifiers.P17 ) do if qualifier.datavalue then 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 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; local hasLoop = false for _, parentEntityId in pairs(parentEntityIds) do if parentEntityId == parentId then hasLoop = true end end if hasLoop then if p.config and p.config.catLoopInGeoChains then result = result .. p.config.catLoopInGeoChains; end break -- while parentId ~= nil do end 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; elseif p.config.reverseOrder then result = result .. baseResult; else result = baseResult .. result; end if options.references then result = 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 ) if mw.title.getCurrentTitle().namespace == 0 then 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 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 ) --do not display countries which have twin ancestors local badTwins if ( countryEntityId == 'Q174193' ) then --Great Britain and Ireland badTwins = {'Q145'} --Great Brirani & NI elseif ( countryEntityId == 'Q161885' ) then --Great Britain badTwins = {'Q174193'} --Great Britain and Ireland elseif ( countryEntityId == 'Q43287' ) then --German Impire badTwins = {'Q41304', 'Q7318', 'Q2415901', 'Q183'} --Weimar Republic or Nazi Germany or Allied-occupied Germany or Germany elseif ( countryEntityId == 'Q41304' ) then --Weimar Republic badTwins = {'Q7318', 'Q2415901', 'Q183'} --Nazi Germany or Allied-occupied Germany or Germany elseif ( countryEntityId == 'Q7318' ) then --Nazi Germany badTwins = {'Q2415901', 'Q183'} --Allied-occupied Germany or Germany elseif ( countryEntityId == 'Q2415901' ) then --Allied-occupied Germany badTwins = {'Q183'} --Germany elseif ( countryEntityId == 'Q838261' ) then --FR of Yugoslavia badTwins = {'Q37024'} --Serbia & Montenegro else return true; end local listforcheck if propertyId == 'P1532' then listforcheck = mw.wikibase.getAllStatements( mw.wikibase.getEntityIdForCurrentPage(), propertyId ); else listforcheck = mw.wikibase.getBestStatements( mw.wikibase.getEntityIdForCurrentPage(), propertyId ); end for _, claim in pairs( listforcheck ) 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; for index, value in ipairs(badTwins) do if ( value == actualId ) then return false; end 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 '<span class="country-name">' .. result .. '</span>'; 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 .. ' <span class="country-name">' .. context.formatStatementDefault( context, countryOptions, statement ) .. '</span>'; end return '<span class="country-name">' .. context.formatStatementDefault( context, countryOptions, statement ) .. '</span>'; else return nil; end end return p;