Модуль:Wikidata/Places
Поделись знанием:
Документация
Содержит функции форматирования для свойств (claims) Викиданных, представляющих собой ссылки на географические элементы (обычно — административные единицы или страны).
Вызов всех функций данного шаблона осуществляется только из промежуточных шаблонов wikidata/pNNN:
- formatCountryClaimWithFlag ← {{wikidata/p17}}, {{wikidata/p27}}, {{wikidata/p495}}
- formatPlaceWithQualifiers ← {{wikidata/p19}}, {{wikidata/p20}}
Функции модуля нельзя вызывать напрямую — они передаются в виде аргументов из соответствующих связанных шаблонов (и только из них, в другие шаблоны их также вставлять не нужно).
Во избежание поломок страниц, использующих данный модуль, желательно экспериментировать в Песочнице для модулей.
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 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 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 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 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'} ) if not results or #results == 0 then results = getParentsInBoundariesSnakImpl( context, entity, boundaries, {'P17'} ) end for r, result in pairs( results ) do if result.snaktype ~= 'value' then return nil; end 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 = ''; 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 = options; -- local parentOptions = mw.clone( 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['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 entityOptions = options; -- local entityOptions = mw.clone( options ); entityOptions['text'] = getLabel( context, entity, boundaries ); baseResult = context.formatSnak( entityOptions, statement.mainsnak, circumstances ); local parent = entity; while ( 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]; 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 if ( not hasAdditionalQualifiers ) then for i=2,#parentSnaks,1 do local parentSnak = parentSnaks[i]; insertFromSnak( parentSnak, entriesToLookupCategory ) end end -- mw.logObject( parentSnaks ); 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 ( 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 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 = options; -- local parentOptions = mw.clone( options ); parentOptions['text'] = getLabel( context, parentEntities[i], boundaries ); result = result .. ', ' .. context.formatSnak( parentOptions, parentSnak ); end end end end end result = baseResult .. 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 -- 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 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 getFlag( context, countryEntityId, actualDate ) local countryEntity = mw.wikibase.getEntity( countryEntityId ); if ( not countryEntity or not countryEntity.claims or not countryEntity.claims.P41 ) then return nil; end local countryFlags = {}; local flagImageStatements = countryEntity.claims.P41; for _, flagImageStatement in pairs( countryEntity.claims.P41 ) do if ( flagImageStatement.rank ~= 'deprecated' ) then local flagImage; if ( flagImageStatement and flagImageStatement.mainsnak and flagImageStatement.mainsnak.datavalue and flagImageStatement.mainsnak.datavalue.value ) then flagImage = flagImageStatement.mainsnak.datavalue.value; end local flagStartTime = -9223372036854775808; if ( flagImageStatement.qualifiers and flagImageStatement.qualifiers.P580 and flagImageStatement.qualifiers.P580[1] ) then local parsedFlagStartTime = context.parseTimeFromSnak( flagImageStatement.qualifiers.P580[1] ); if ( parsedFlagStartTime ) then flagStartTime = parsedFlagStartTime; end end if ( flagImage ) then countryFlags[ flagStartTime ] = flagImage; end end end local goodFlag = nil; if ( countryFlags ) then local ordered_dates = {} for flagBeginDate in pairs(countryFlags) do table.insert(ordered_dates, flagBeginDate) end table.sort(ordered_dates) for i = 1, #ordered_dates do local flagBeginDate, flag = ordered_dates[i], countryFlags[ ordered_dates[i] ]; if ( actualDate >= flagBeginDate ) then goodFlag = flag; end end end if ( goodFlag ) then return '[[File:' .. goodFlag .. '|20x15px|border]]'; end return nil; 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; if ( statement.mainsnak and statement.mainsnak.datavalue and statement.mainsnak.datavalue.value and statement.mainsnak.datavalue.value["numeric-id"] ) then countryEntityId = 'Q' .. statement.mainsnak.datavalue.value["numeric-id"]; countryEntity = mw.wikibase.getEntity( countryEntityId ); end if not countryEntity then return '<span class="country-name">' .. context.formatStatementDefault( context, options, statement ) .. '</span>'; end local endDateTimestamp = calculateEndDateTimestamp( context, options, statement ); local boundaries = getTimeBoundariesFromProperties( context, {'P570', 'P577', 'P571'} ); local countryOptions = mw.clone( options ); if not countryOptions['text'] or countryOptions['text'] == '' then countryOptions['text'] = getLabel( context, countryEntity, boundaries ); end local flag = 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;