Модуль:Wikidata/Places
Материал из Томская энциклопедии
Для документации этого модуля может быть создана страница Модуль:Wikidata/Places/doc
local categorizeByPlaceOfBirthAndDeath = true; local WDS = require( 'Module:WikidataSelectors' ); local Flags = require( 'Module:Wikidata/Flags' ); local p = { config = { hideSameLabels = false, hidePartOfLabels = false, hideUnitsForCapitals = true, 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 ) -- 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 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, entity, boundaries, propertyIds ) local results = {}; if not propertyIds or #propertyIds == 0 then return results; end if entity.claims then for _, propertyId in ipairs( propertyIds ) do local filteredClaims = WDS.filter( entity.claims, propertyId .. '[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 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'} ) -- located in if not results or #results == 0 then results = getParentsInBoundariesSnakImpl( context, entity, 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 == 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.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 ); -- mw.log( 'formatPlaceWithQualifiers(..., ' .. property .. ')'); local entriesToLookupCategory = {}; local circumstances = context.getSourcingCircumstances( statement ); local result = ''; local baseResult = 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 local parentOptions = context.cloneOptions( options ); parentOptions['text'] = getLabel( context, qualifier, boundaries ); result = result .. ', ' .. context.formatSnak( parentOptions, 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.id ) then local wbStatus, entity = pcall( mw.wikibase.getEntity, statement.mainsnak.datavalue.value.id ); local parentSnaks = { statement.mainsnak }; local parentEntities = { entity }; 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 ( wbStatus == true and actualDateBoundariesProperties ~= nil ) then local boundaries = getTimeBoundariesFromProperties( context, actualDateBoundariesProperties ); local filterCapitalOf = { getParentsInBoundariesSnakImpl( context, entity, boundaries, {'P1376'} ) }; if ( boundaries ) then local entityOptions = context.cloneOptions( options ); entityOptions['text'] = getLabel( context, entity, boundaries ); baseResult = context.formatSnak( entityOptions, statement.mainsnak, circumstances ); local parent = entity; local wbStatus = true; while ( wbStatus == true and parent ~= nil ) do -- get parent local newParentSnaks = getParentsInBoundariesSnak( context, parent, boundaries ); if ( not newParentSnaks or #newParentSnaks == 0 ) then parent = nil; elseif ( #newParentSnaks == 1 ) then local parentSnak = newParentSnaks[1]; wbStatus, parent = pcall( mw.wikibase.getEntity, parentSnak.datavalue.value.id ); if wbStatus == true then table.insert( parentSnaks, parentSnak ); table.insert( parentEntities, parent ); table.insert( filterCapitalOf, getParentsInBoundariesSnakImpl( context, parent, boundaries, {'P1376'} ) ); end else parent = 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 local i = #parentSnaks; while ( i > 1 ) do local prevEntity = parentEntities[i - 1]; -- TODO: use English labels, if there is no current language labels local prevLabel = getLabel( context, prevEntity, boundaries ) or ''; local nextEntity = parentEntities[i]; local nextLabel = getLabel( context, nextEntity, 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( parentEntities, 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( parentEntities, i - 1 ); elseif ( p.config and p.config.hideUnitsForCapitals == true and i ~= #parentSnaks ) then -- do not ouput items whose capital is the first item local isCapital = false; for _, capitalSnaks in pairs( filterCapitalOf ) do for __, capitalSnak in pairs( capitalSnaks ) do if parentSnaks[i].datavalue.value.id == capitalSnak.datavalue.value.id then isCapital = true; break; end end end if isCapital then table.remove( parentSnaks, i ); table.remove( parentEntities, i ); 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]; local parentOptions = context.cloneOptions( options ); parentOptions['text'] = getLabel( context, parentEntities[i], boundaries ); result = result .. ', ' .. context.formatSnak( parentOptions, parentSnak ); end end end end if wbStatus ~= true and p.config and p.config.catWikibaseError then result = result .. p.config.catWikibaseError; end end if ( options['thisLocationOnly'] ) then result = baseResult .. context.formatRefs( options, statement ); else result = baseResult .. result .. context.formatRefs( options, statement ); end 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, snak.datavalue.value.id ); end end function getCategory( propertyToSearch, entriesToLookupCategoryFor ) for _, placeId in pairs( entriesToLookupCategoryFor ) do local wbStatus, placeEntity = pcall( mw.wikibase.getEntity, placeId ); if wbStatus == true and placeEntity then 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 = claim.mainsnak.datavalue.value.id; local wbStatus, catEntity = pcall( mw.wikibase.getEntity, catEntityId ); if ( wbStatus == true and catEntity and catEntity:getSitelink() ) then return '[[' .. catEntity:getSitelink() .. ']]'; end end end end end end return ''; end -- get current of historic name of place function getLabel( context, entity, boundaries ) if not entity then return nil; end local lang = mw.language.getContentLanguage(); local langCode = lang:getCode(); -- name from label -- TODO: lang:getFallbackLanguages() local label = nil; if entity.labels then if entity.labels[langCode] and entity.labels[langCode].value then label = entity.labels[langCode].value; elseif entity.labels.en and entity.labels.en.value then label = entity.labels.en.value; end end -- name from properties local results = getParentsInBoundariesSnakImpl( context, entity, boundaries, { 'P1813[language:' .. langCode .. ']', 'P1448[language:' .. langCode .. ']', 'P1705[language:' .. langCode .. ']' } ); 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 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", "P571" } ) 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 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; local countryEntityId = nil; local countryEntity = nil; local wbStatus = false; if ( statement.mainsnak and statement.mainsnak.datavalue and statement.mainsnak.datavalue.value and statement.mainsnak.datavalue.value.id ) then countryEntityId = statement.mainsnak.datavalue.value.id; wbStatus, countryEntity = pcall( mw.wikibase.getEntity, countryEntityId ); end if wbStatus ~= true or not countryEntity then local result = '<span class="country-name">' .. context.formatStatementDefault( context, options, statement ) .. '</span>'; if wbStatus ~= true and p.config and p.config.catWikibaseError then result = result .. p.config.catWikibaseError; end return result; end local endDateTimestamp = calculateEndDateTimestamp( context, options, statement ); local boundaries = getTimeBoundariesFromProperties( context, {'P570', 'P577', 'P571'} ); local countryOptions = context.cloneOptions( options ); if not countryOptions['text'] or countryOptions['text'] == '' then countryOptions['text'] = getLabel( context, countryEntity, 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>'; end return p;