Modul:Wikidata

Documentation for this module may be created at Modul:Wikidata/doc

-- settings, may differ from project to projectlocal fileDefaultSize = '267x400px';local outputReferences = true;-- Ссылки на используемые модули, которые потребуются в 99% случаев загрузки страниц (чтобы иметь на виду при переименовании)local moduleSources = require( 'Module:Sources' )local WDS = require( 'Module:WikidataSelectors' );-- Константыlocal contentLanguageCode = mw.getContentLanguage():getCode();local p = {};local config = nil;local formatDatavalue, formatEntityId, formatRefs, formatSnak, formatStatement,formatStatementDefault, formatProperty, getSourcingCircumstances,getPropertyDatatype, getPropertyParams, throwError, toBoolean;local function copyTo( obj, target, skipEmpty )for k, v in pairs( obj ) doif skipEmpty ~= true or ( v ~= nil and v ~= '' ) thentarget[k] = v;endendreturn target;endlocal function min( prev, next )if ( prev == nil ) then return next;elseif ( prev > next ) then return next;else return prev; endendlocal function max( prev, next )if ( prev == nil ) then return next;elseif ( prev < next ) then return next;else return prev; endendlocal function getConfig( section, code )if config == nil thenconfig = require( 'Module:Wikidata/config' );end;if not config thenconfig = {};endif not section thenreturn config;endif not code thenreturn config[ section ] or {};endif not config[ section ] thenreturn nil;endreturn config[ section ][ code ];endlocal function getCategoryByCode( code, sortkey )local value = getConfig( 'categories', code );if not value or value == '' thenreturn '';endif sortkey ~= nil thenreturn '[[Category:' .. value .. '|' .. sortkey .. ']]'; -- экранировать?elsereturn '[[Category:' .. value .. ']]';endendlocal function splitISO8601(str)if 'table' == type(str) thenif str.args and str.args[1] thenstr = '' .. str.args[1]elsereturn 'unknown argument type: ' .. type( str ) .. ': ' .. table.tostring( str )endendlocal Y, M, D = (function(str)local pattern = "(%-?%d+)%-(%d+)%-(%d+)T"local Y, M, D = mw.ustring.match( str, pattern )return tonumber(Y), tonumber(M), tonumber(D)end) (str);local h, m, s = (function(str)local pattern = "T(%d+):(%d+):(%d+)%Z";local H, M, S = mw.ustring.match( str, pattern);return tonumber(H), tonumber(M), tonumber(S);end) (str);local oh,om = ( function(str)if str:sub(-1)=="Z" then return 0,0 end; -- ends with Z, Zulu time-- matches ±hh:mm, ±hhmm or ±hh; else returns nilslocal pattern = "([-+])(%d%d):?(%d?%d?)$";local sign, oh, om = mw.ustring.match( str, pattern);sign, oh, om = sign or "+", oh or "00", om or "00";return tonumber(sign .. oh), tonumber(sign .. om);end )(str)return {year=Y, month=M, day=D, hour=(h+oh), min=(m+om), sec=s};endlocal function parseTimeBoundaries( time, precision )local s = splitISO8601( time );if (not s) then return nil; endif ( precision >= 0 and precision <= 8 ) thenlocal powers = { 1000000000 , 100000000, 10000000, 1000000, 100000, 10000, 1000, 100, 10 }local power = powers[ precision + 1 ];local left = s.year - ( s.year % power );return { tonumber(os.time( {year=left, month=1, day=1, hour=0, min=0, sec=0} )) * 1000,tonumber(os.time( {year=left + power - 1, month=12, day=31, hour=29, min=59, sec=58} )) * 1000 + 1999 };endif ( precision == 9 ) thenreturn { tonumber(os.time( {year=s.year, month=1, day=1, hour=0, min=0, sec=0} )) * 1000,tonumber(os.time( {year=s.year, month=12, day=31, hour=23, min=59, sec=58} )) * 1000 + 1999 };endif ( precision == 10 ) thenlocal lastDays = {31, 28.25, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};local lastDay = lastDays[s.month];return { tonumber(os.time( {year=s.year, month=s.month, day=1, hour=0, min=0, sec=0} )) * 1000,tonumber(os.time( {year=s.year, month=s.month, day=lastDay, hour=23, min=59, sec=58} )) * 1000 + 1999 };endif ( precision == 11 ) thenreturn { tonumber(os.time( {year=s.year, month=s.month, day=s.day, hour=0, min=0, sec=0} )) * 1000,tonumber(os.time( {year=s.year, month=s.month, day=s.day, hour=23, min=59, sec=58} )) * 1000 + 1999 };endif ( precision == 12 ) thenreturn { tonumber(os.time( {year=s.year, month=s.month, day=s.day, hour=s.hour, min=0, sec=0} )) * 1000,tonumber(os.time( {year=s.year, month=s.month, day=s.day, hour=s.hour, min=59, sec=58} )) * 1000 + 1999 };endif ( precision == 13 ) thenreturn { tonumber(os.time( {year=s.year, month=s.month, day=s.day, hour=s.hour, min=s.min, sec=0} )) * 1000,tonumber(os.time( {year=s.year, month=s.month, day=s.day, hour=s.hour, min=s.min, sec=58} )) * 1000 + 1999 };endif ( precision == 14 ) thenlocal t = tonumber(os.time( {year=s.year, month=s.month, day=s.day, hour=s.hour, min=s.min, sec=0} ) );return { t * 1000, t * 1000 + 999 };enderror('Unsupported precision: ' .. precision );end--[[Функция для формирования категории на основе wikidata/config]]local function extractCategory( options, value )if ( not options.category or options.nocat ) thenreturn '';endlocal propertyId = string.gsub( options.category, '([^Pp0-9].*)$', '');local wbStatus, claims = pcall( mw.wikibase.getAllStatements, value.id, propertyId );if ( wbStatus ~= true or not claims ) then return ''; endallClaims = {}allClaims[ propertyId ] = claimsclaims = WDS.filter( allClaims, options.category )if not claims then return ''; endfor _, claim in pairs( claims ) doif ( claimand claim.mainsnakand claim.mainsnak.datavalueand claim.mainsnak.datavalue.type == 'wikibase-entityid' ) thenlocal catEntityId = claim.mainsnak.datavalue.value.id;local wbStatus, catSiteLink = pcall( mw.wikibase.getSitelink, catEntityId );if ( wbStatus == true and catSiteLink ) thenreturn '[[' .. catSiteLink .. ']]';endendendreturn '';end--[[ Преобразует строку в булевое значение Принимает: строковое значение (может отсутствовать) Возвращает: булевое значение true или false, если получается распознать значение, или defaultValue во всех остальных  случаях]]local function toBoolean( valueToParse, defaultValue )if ( valueToParse ~= nil ) thenif valueToParse == false or valueToParse == '' or valueToParse == 'false' or valueToParse == '0' thenreturn falseendreturn trueendreturn defaultValue;end-- Обрачивает отформатированное значение в инлайновый или блочный тег.-- @param value String value-- @param attributes Table of attributes-- @return string HTML tag with valuelocal function wrapValue( value, attributes )local tagName = 'span';local spacer = '';if (string.match( value, '\n' )or string.match( value, '<t[dhr][ >]' )or string.match( value, '<div[ >]' )or string.find( value, 'UNIQ%-%-imagemap' )) thentagName = 'div';spacer = '\n'endlocal attrString = ''for key, value in pairs( attributes or {} ) dolocal _key = mw.text.trim( key )local _value = mw.text.encode( mw.text.trim( value ) )attrString = attrString .. _key .. '="' .. _value .. '" 'endreturn '<' .. tagName .. ' ' .. attrString .. '>' .. spacer .. value .. '</' .. tagName .. '>';end-- Wraps formatted snak value into HTML tag with attributes.-- @param value String value of snak-- @param hash Snak hash-- @param attributes Table of extra attributes-- @return string HTML tag with valuelocal function wrapSnak( value, hash, attributes )local newAttributes = mw.clone( attributes or {} )newAttributes['class'] = ( newAttributes['class'] or '' ) .. ' wikidata-snak'if hash thennewAttributes['data-wikidata-hash'] = hashelsenewAttributes['class'] = newAttributes['class'] .. ' wikidata-main-snak'endreturn wrapValue( value, newAttributes )end-- Wraps formatted statement value into HTML tag with attributes.-- @param value String value of statement-- @param propertyId String PID of property-- @param claimId String ID of claim or nil for local value-- @param attributes Table of extra attributes-- @return string HTML tag with valuelocal function wrapStatement( value, propertyId, claimId, attributes )local newAttributes = mw.clone( attributes or {} )newAttributes['class'] = newAttributes['class'] or ''newAttributes['data-wikidata-property-id'] = string.upper( propertyId )if claimId thennewAttributes['class'] = newAttributes['class'] .. ' wikidata-claim'newAttributes['data-wikidata-claim-id'] = claimIdelsenewAttributes['class'] = newAttributes['class'] .. ' no-wikidata'endreturn wrapValue( value, newAttributes )end-- Wraps formatted qualifier's statement value into HTML tag with attributes.-- @param value String value of qualifier's statement-- @param propertyId String PID of qualifier-- @param attributes Table of extra attributes-- @return string HTML tag with valuelocal function wrapQualifier( value, qualifierId, attributes )local newAttributes = mw.clone( attributes or {} )newAttributes['data-wikidata-qualifier-id'] = string.upper( qualifierId )return wrapValue( value, newAttributes )end--[[Функция для получения сущности (еntity) для текущей страницыПодробнее о сущностях см. d:Wikidata:Glossary/ruПринимает: строковый индентификатор (типа P18, Q42)Возвращает: объект таблицу, элементы которой индексируются с нуля]]local function getEntityFromId( id )local entity;local wbStatus;if id thenwbStatus, entity = pcall( mw.wikibase.getEntityObject, id )elsewbStatus, entity = pcall( mw.wikibase.getEntityObject );endreturn entity;end--[[Внутрення функция для формирования сообщения об ошибкеПринимает: ключ элемента в таблице config.errors (например entity-not-found)Возвращает: строку сообщения]]local function throwError( key )error( getConfig( 'errors', key ) );end--[[Функция для получения идентификатора сущностейПринимает: объект таблицу сущностиВозвращает: строковый индентификатор (типа P18, Q42)]]local function getEntityIdFromValue( value )local prefix = ''if value['entity-type'] == 'item' thenprefix = 'Q'elseif value['entity-type'] == 'property' thenprefix = 'P'elsethrowError( 'unknown-entity-type' )endreturn prefix .. value['numeric-id']end-- проверка на наличие специилизированной функции в опцияхlocal function getUserFunction( options, prefix, defaultFunction )-- проверка на указание специализированных обработчиков в параметрах,-- переданных при вызовеif options[ prefix .. '-module' ] or options[ prefix .. '-function' ] then-- проверка на пустые строки в параметрах или их отсутствиеif not options[ prefix .. '-module' ] or not options[ prefix .. '-function' ] thenthrowError( 'unknown-' .. prefix .. '-module' );end-- динамическая загруза модуля с обработчиком указанным в параметреlocal formatter = require( 'Module:' .. options[ prefix .. '-module' ] );if formatter == nil thenthrowError( prefix .. '-module-not-found' )endlocal fun = formatter[ options[ prefix .. '-function' ] ]if fun == nil thenthrowError( prefix .. '-function-not-found' )endreturn fun;endreturn defaultFunction;end-- Выбирает свойства по property id, дополнительно фильтруя их по рангуlocal function selectClaims( context, options, propertySelector )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 propertySelector ) then error( 'propertySelector not specified' ); end;result = WDS.filter( options.entity.claims, propertySelector );if ( not result or #result == 0 ) thenreturn nil;endif options.limit and options.limit ~= '' and options.limit ~= '-'  thenlocal limit = tonumber( options.limit, 10 );while #result > limit dotable.remove( result );endendreturn result;end--[[Функция для получения значения свойства элемента в заданный момент времени.Принимает: контекст, элемент, временные границы, таблица ID свойстваВозвращает: таблицу соответствующих значений свойства]]local function getPropertyInBoundaries( context, entityId, boundaries, propertyIds, selectors )if (type(entityId) ~= 'string') then error('type of entityId argument expected string, but was ' .. type(entityId)); endlocal results = {};if not propertyIds or #propertyIds == 0 thenreturn results;endfor _, propertyId in ipairs( propertyIds ) dolocal selector = selectors[_];local propertyClaims = mw.wikibase.getAllStatements( entityId, propertyId );local fakeAllClaims = {};fakeAllClaims[propertyId] = propertyClaims;local filteredClaims = WDS.filter( fakeAllClaims, selector .. '[rank:preferred, rank:normal]' );if filteredClaims thenfor _, claim in pairs( filteredClaims ) doif not boundaries thentable.insert( results, claim.mainsnak );elselocal startBoundaries = p.getTimeBoundariesFromQualifier( context.frame, context, claim, 'P580' );local endBoundaries = p.getTimeBoundariesFromQualifier( context.frame, context, claim, 'P582' );if ( (startBoundaries == nil or ( startBoundaries[2] <= boundaries[1]))and (endBoundaries == nil or ( endBoundaries[1] >= boundaries[2]))) thentable.insert( results, claim.mainsnak );endendendendif #results > 0 thenbreak;endendreturn results;end--[[TODO]]function p.getTimeBoundariesFromQualifier( frame, 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 };end--[[TODO]]function p.getTimeBoundariesFromQualifiers( frame, context, statement, qualifierIds )if not qualifierIds thenqualifierIds = { 'P582', 'P580', 'P585' };endfor _, qualifierId in ipairs( qualifierIds ) dolocal result = p.getTimeBoundariesFromQualifier( frame, context, statement, qualifierId );if result thenreturn result;endendreturn nil;endlocal CONTENT_LANGUAGE_CODE = mw.language.getContentLanguage():getCode();local getLabelWithLang_DEFAULT_PROPERTIES = { "P1813", "P1448", "P1705" };local getLabelWithLang_DEFAULT_SELECTORS = {'P1813[language:' .. CONTENT_LANGUAGE_CODE .. '][!P3831,P3831:Q105690470]','P1448[language:' .. CONTENT_LANGUAGE_CODE .. '][!P3831,P3831:Q105690470]','P1705[language:' .. CONTENT_LANGUAGE_CODE .. '][!P3831,P3831:Q105690470]'};--[[Функция для получения метки элемента в заданный момент времени.Принимает: контекст, элемент, временные границыВозвращает: текстовую метку элемента, язык метки]]local function getLabelWithLang( context, options, entityId, boundaries, propertyIds, selectors )if (type(entityId) ~= 'string') then error('type of entityId argument expected string, but was ' .. type(entityId)); endif not entityId thenreturn nil;endlocal langCode = CONTENT_LANGUAGE_CODE;-- name from labellocal label = nil;if ( options.text and options.text ~= '' ) thenlabel = options.text;elseif not propertyIds thenpropertyIds = getLabelWithLang_DEFAULT_PROPERTIES;selectors = getLabelWithLang_DEFAULT_SELECTORS;end-- name from propertieslocal results = getPropertyInBoundaries( context, entityId, boundaries, propertyIds, selectors );for _, result in pairs( results ) doif result.datavalue and result.datavalue.value thenif result.datavalue.type == 'monolingualtext' and result.datavalue.value.text thenlabel = result.datavalue.value.text;langCode = result.datavalue.value.language;break;elseif result.datavalue.type == 'string' thenlabel = result.datavalue.value;break;endendendif (not label) thenlabel, langCode = mw.wikibase.getLabelWithLang( entityId );if not langCode thenreturn nil;endendendreturn label, langCode;endlocal function formatPropertyDefault( context, options )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;local claims;if options.property then -- TODO: Почему тут может не быть property?if options.rank then -- передать настройки ранга из конфигаclaims = context.selectClaims( options, options.property .. options.rank );elseclaims = context.selectClaims( options, options.property );endendif claims == nil thenreturn '' --TODO error?end-- Обход всех заявлений утверждения и с накоплением оформленных предпочтительных-- заявлений в таблицеlocal formattedClaims = {}for i, claim in ipairs(claims) dolocal formattedStatement = context.formatStatement( options, claim )-- здесь может вернуться либо оформленный текст заявления, либо строка ошибки, либо nilif ( formattedStatement and formattedStatement ~= '' ) thenformattedStatement = context.wrapStatement( formattedStatement, options.property, claim.id )table.insert( formattedClaims, formattedStatement )endend-- создание текстовой строки со списком оформленых заявлений из таблицыlocal out = mw.text.listToText( formattedClaims, options.separator, options.conjunction )if out ~= '' thenif options.before thenout = options.before .. outendif options.after thenout = out .. options.afterendendreturn outend-- create contextlocal function initContext( options )local context = {entity = options.entity,extractCategory = extractCategory,formatSnak = formatSnak,formatPropertyDefault = formatPropertyDefault,formatStatementDefault = formatStatementDefault,wrapSnak = wrapSnak,wrapStatement = wrapStatement,wrapQualifier = wrapQualifier,}context.cloneOptions = function( options )local entity = options.entity;options.entity = nil;newOptions = mw.clone( options );options.entity = entity;newOptions.entity = entity;newOptions.frame = options.frame; -- На склонированном фрейме frame:expandTemplate()return newOptions;end;context.formatProperty = function( options )local func = getUserFunction( options, 'property', context.formatPropertyDefault );return func( context, options )end;context.formatStatement = function( options, statement ) return formatStatement( context, options, statement ) end;context.formatSnak = function( options, snak, circumstances ) return formatSnak( context, options, snak, circumstances ) end;context.formatRefs = function( options, statement ) return formatRefs( context, options, statement ) end;context.parseTimeFromSnak = function( snak )if ( snak and snak.datavalue and snak.datavalue.value and snak.datavalue.value.time ) thenreturn tonumber(os.time( splitISO8601( tostring( snak.datavalue.value.time ) ) ) ) * 1000;endreturn nil;endcontext.parseTimeBoundariesFromSnak = function( snak )if ( snak and snak.datavalue and snak.datavalue.value and snak.datavalue.value.time and snak.datavalue.value.precision ) thenreturn parseTimeBoundaries( snak.datavalue.value.time, snak.datavalue.value.precision );endreturn nil;endcontext.getSourcingCircumstances = function( statement ) return getSourcingCircumstances( statement ) end;context.selectClaims = function( options, propertyId ) return selectClaims( context, options, propertyId ) end;return contextend--[[Функция для оформления утверждений (statement)Подробнее о утверждениях см. d:Wikidata:Glossary/ruПринимает: таблицу параметровВозвращает: строку оформленного текста, предназначенного для отображения в статье]]local function formatProperty( options )-- Получение сущности по идентификаторуlocal entity = getEntityFromId( options.entityId )if not entity thenreturn -- throwError( 'entity-not-found' )end-- проверка на присутсвие у сущности заявлений (claim)-- подробнее о заявлениях см. d:Викиданные:Глоссарийif (entity.claims == nil) thenreturn '' --TODO error?end-- improve optionsoptions.frame = g_frame;options.entity = entity;options.extends = function( self, newOptions )return copyTo( newOptions, copyTo( self, {} ) )endif ( options.i18n ) thenoptions.i18n = copyTo( options.i18n, copyTo( getConfig( 'i18n' ), {} ) );elseoptions.i18n = getConfig( 'i18n' );endlocal context = initContext( options );return context.formatProperty( options );end--[[Функция для оформления одного утверждения (statement)Принимает: объект-таблицу утверждение и таблицу параметровВозвращает: строку оформленного текста с заявлением (claim)]]function formatStatement( context, options, statement )if ( not statement ) thenerror( 'statement is not specified or nil' );endif not statement.type or statement.type ~= 'statement' thenthrowError( 'unknown-claim-type' )endlocal functionToCall = getUserFunction( options, 'claim', context.formatStatementDefault );return functionToCall( context, options, statement );endfunction getSourcingCircumstances( statement )if (not statement) then error('statement is not specified') end;local circumstances = {};if ( statement.qualifiersand statement.qualifiers.P1480 ) thenfor i, qualifier in pairs( statement.qualifiers.P1480 ) doif ( qualifierand qualifier.datavalueand qualifier.datavalue.type == 'wikibase-entityid'and qualifier.datavalue.valueand qualifier.datavalue.value['entity-type'] == 'item' ) thentable.insert(circumstances, qualifier.datavalue.value.id)endendendreturn circumstances;end--[[Функция для оформления одного утверждения (statement)Принимает: объект-таблицу утверждение, таблицу параметров,объект-функцию оформления внутренних структур утверждения (snak) иобъект-функцию оформления ссылки на источники (reference)Возвращает: строку оформленного текста с заявлением (claim)]]function formatStatementDefault( context, options, statement )if (not context) then error('context is not specified') end;if (not options) then error('options is not specified') end;if (not statement) then error('statement is not specified') end;local circumstances = context.getSourcingCircumstances( statement );options.qualifiers = statement.qualifiers;local result = context.formatSnak( options, statement.mainsnak, circumstances );    if ( options.qualifier and statement.qualifiers and statement.qualifiers[ options.qualifier ] ) then    qualConfig = getPropertyParams( options.qualifier, nil, {})    if options.i18n then qualConfig.i18n = options.i18n end    local qualifierValues = {};for _, qualifierSnak in pairs( statement.qualifiers[ options.qualifier ] ) dolocal snakValue = context.formatSnak( qualConfig, qualifierSnak );if snakValue and snakValue ~= '' thentable.insert( qualifierValues, snakValue );endendif ( result and result ~= '' and #qualifierValues ) thenif qualConfig.invisible then         result = result .. table.concat( qualifierValues, ', ' );else        result = result .. ' (' .. table.concat( qualifierValues, ', ' ) .. ')';        end        end    endif ( result and result ~= '' and options.references ) thenresult = result .. context.formatRefs( options, statement );endreturn result;end--[[Функция для оформления части утверждения (snak)Подробнее о snak см. d:Викиданные:ГлоссарийПринимает: таблицу snak объекта (main snak или же snak от квалификатора) и таблицу опцийВозвращает: строку оформленного викитекста]]function formatSnak( context, options, snak, circumstances )circumstances = circumstances or {};if snak.snaktype == 'somevalue' thenif ( options['somevalue'] and options['somevalue'] ~= '' ) thenresult = options['somevalue'];elseresult = options.i18n['somevalue'];endelseif snak.snaktype == 'novalue' thenif ( options['novalue'] and options['novalue'] ~= '' ) thenresult = options['novalue'];elseresult = options.i18n['novalue'];endelseif snak.snaktype == 'value' thenresult = formatDatavalue( context, options, snak.datavalue, snak.datatype );for _, item in pairs(circumstances) doif options.i18n[item] thenresult = options.i18n[item] .. result;endendelsethrowError( 'unknown-snak-type' );endif ( not result or result == '' ) thenreturn nil;endreturn context.wrapSnak( result, snak.hash )end--[[Функция для оформления объектов-значений с географическими координатамиПринимает: объект-значение и таблицу параметров,Возвращает: строку оформленного текста]]local function formatGlobeCoordinate( value, options )-- проверка на требование в параметрах вызова на возврат сырого значенияif options['subvalue'] == 'latitude' then -- широтыreturn value['latitude']elseif options['subvalue'] == 'longitude' then -- долготыreturn value['longitude']elseif options['nocoord'] and options['nocoord'] ~= '' then-- если передан параметр nocoord, то не выводить координаты-- обычно это делается при использовании нескольких карточек на страницеreturn ''else-- в противном случае формируются параметры для вызова шаблона {{coord}}-- нужно дописать в документации шаблона, что он отсюда вызывается, и что-- любое изменние его парамеров  должно быть согласовано с кодом тутcoord_mod = require( "Module:Coordinates" );local globe = options.globe or ''if globe == '' and value['globe'] thenglobes = require( 'Module:Wikidata/Globes' )globe = globes[value['globe']] or ''endlocal display = 'inline'if options.display and options.display ~= '' thendisplay = options.displayelseif ( options.property:upper() == 'P625' ) thendisplay = 'title'endlocal format = options.format or ''if format == '' thenformat = 'dms'if value['precision'] thenlocal precision = value['precision'] * 60if precision >= 60 thenformat = 'd'elseif precision >= 1 thenformat = 'dm'endendendg_frame.args = {tostring(value['latitude']),tostring(value['longitude']),globe = globe,type = options.type and options.type or '',scale = options.scale and options.scale or '',display = display,format = format,}return coord_mod.coord(g_frame)endend--[[Функция для оформления объектов-значений с файлами с ВикискладаПринимает: объект-значение и таблицу параметров,Возвращает: строку оформленного текста]]local function formatCommonsMedia( value, options )local image = value;local caption = '';if options[ 'caption' ] and options[ 'caption' ] ~= '' thencaption = options[ 'caption' ];endif caption ~= '' thencaption = wrapQualifier( caption, 'P2096', { class = 'media-caption', style = 'display:block' } );endif not string.find( value, '[%[%]%{%}]' ) and not string.find( value, 'UNIQ%-%-imagemap' ) then-- если в value не содержится викикод или imagemap, то викифицируем имя файла-- ищем слово imagemap в строке, потому что вставляется плейсхолдер: [[PHAB:T28213]]image = '[[File:' .. value .. '|frameless';if options[ 'border' ] and options[ 'border' ] ~= '' thenimage = image .. '|border';endlocal size = options[ 'size' ];if size and size ~= '' then-- TODO: check localized pixel names tooif not string.match( size, 'px$' ) thensize = size .. 'px'endelsesize = fileDefaultSize;endimage = image .. '|' .. size;if options[ 'alt' ] and options[ 'alt' ] ~= '' thenimage = image .. '|alt=' .. options[ 'alt' ];endif caption ~= '' thenimage = image .. '|' .. captionendimage = image .. ']]';if caption ~= '' thenimage = image .. '<br>' .. caption;endelseimage = image .. caption .. getCategoryByCode( 'media-contains-markup' );endreturn imageend--[[Fonction for render math formulas@param string Value.@param table Parameters.@return string Formatted string.]]local function formatMath( value, options )return options.frame:extensionTag{ name = 'math', content = value };end--[[Функция для оформления внешних идентификаторовПринимает: объект-значение и таблицу параметров,Возвращает: строку оформленного текста]]local function formatExternalId( value, options )local formatter = options.formatter;if not formatter or formatter == '' thenlocal wbStatus, propertyEntity = pcall( mw.wikibase.getEntity, options.property:upper() )if wbStatus == true and propertyEntity thenlocal isGoodFormat = false;local statements = propertyEntity:getBestStatements( 'P1793' );for _, statement in pairs( statements ) doif statement.mainsnak.snaktype == 'value' thenlocal pattern = mw.ustring.gsub( statement.mainsnak.datavalue.value, '\\', '%' );pattern = mw.ustring.gsub( pattern, '{%d+,?%d*}', '+' );if ( string.find( pattern, '|' ) or string.find( pattern, '%)%?' )or mw.ustring.match( value, '^' .. pattern .. '$' ) ~= nil ) thenisGoodFormat = true;break;endendendif ( isGoodFormat == true ) thenstatements = propertyEntity:getBestStatements( 'P1630' );for _, statement in pairs( statements ) doif statement.mainsnak.snaktype == 'value' thenformatter = statement.mainsnak.datavalue.value;breakendendendendendif formatter and formatter ~= '' thenlocal encodedValue = mw.ustring.gsub( value, '%%', '%%%%' ) -- ломается, если подставить внутрь другого mw.ustring.gsublocal link = mw.ustring.gsub( mw.ustring.gsub( formatter, '$1', encodedValue ), '.',{ [' '] = '%20', ['+'] = '%2b', ['['] = '%5B', [']'] = '%5D' } )local title = options.titleif not title or title == '' thentitle = '$1'endtitle = mw.ustring.gsub( mw.ustring.gsub( title, '$1', encodedValue ), '.', { ['['] = '(', [']'] = ')' } )return '[' .. link .. ' ' .. title .. ']'endreturn valueend--[[Функция для оформления числовых значенийПринимает: объект-значение и таблицу параметров,Возвращает: строку оформленного текста]]local function formatQuantity( value, options )-- диапазон значенийlocal amount = string.gsub( value['amount'], '^%+', '' );local lang = mw.language.getContentLanguage();local langCode = lang:getCode();local function formatNum( number, sigfig )local multiplier = ''if options.countByThousands thenlocal powers = options.i18n['thousandPowers']local pos = 1while math.abs(number) >= 1000 and pos < #powers donumber = number / 1000pos = pos + 1endmultiplier = powers[pos]if math.abs(number) >= 100 thensigfig = sigfig or 0elseif math.abs(number) >= 10 thensigfig = sigfig or 1elsesigfig = sigfig or 2endelsesigfig = sigfig or 12 -- округление до 12 знаков после запятой, на 13-м возникает ошибка в точностиendlocal mult = 10^sigfig;number = math.floor( number * mult + 0.5 ) / mult;return string.gsub( lang:formatNum( number ), '^-', '−' ) .. multiplier;endlocal out = formatNum( tonumber( amount ) );if value.upperBound thenlocal diff = tonumber( value.upperBound ) - tonumber( amount )if diff > 0 then -- временная провека, пока у большинства значений не будет убрано ±0-- Пробуем понять до какого знака округлятьlocal integer, dot, decimals, expstr = value.upperBound:match( '^+?-?(%d*)(%.?)(%d*)(.*)' )local prec if dot == '' thenprec = -integer:match('0*$'):len()elseprec = #decimalsendbound = formatNum( diff, prec )if string.match( bound, 'E%-(%d+)' ) then -- если в экспоненциальном форматеdigits = tonumber( string.match( bound, 'E%-(%d+)' ) ) - 2bound = formatNum( diff * 10 ^ digits, prec )bound = string.sub( bound, 0, 2 ) .. string.rep( '0', digits ) .. string.sub( bound, -string.len( bound ) + 2 )endout = out .. ' ± ' .. boundendendif options.unit and options.unit ~= '' thenif options.unit ~= '-' thenout = out .. ' ' .. options.unitendelseif value.unit and string.match( value.unit, 'http://www.wikidata.org/entity/' ) thenlocal unitEntityId = string.gsub( value.unit, 'http://www.wikidata.org/entity/', '' );if unitEntityId ~= 'undefined' then local wbStatus, unitEntity = pcall( mw.wikibase.getEntity, unitEntityId );if wbStatus == true and unitEntity thenif unitEntity.claims.P2370 andunitEntity.claims.P2370[1].mainsnak.snaktype == 'value' andnot value.upperBound andoptions.siConversion == truethenconversionToSIunit = string.gsub( unitEntity.claims.P2370[1].mainsnak.datavalue.value.amount, '^%+', '' );if math.floor( math.log10( conversionToSIunit )) ~= math.log10( conversionToSIunit ) then-- Если не степени десятки (переводить сантиметры в метры не надо!)outValue = tonumber( amount ) * conversionToSIunitif ( outValue > 0 ) then-- Пробуем понять до какого знака округлятьlocal integer, dot, decimals, expstr = amount:match( '^(%d*)(%.?)(%d*)(.*)' )local prec if dot == '' thenprec = -integer:match('0*$'):len()elseprec = #decimalsendlocal adjust = math.log10( math.abs( conversionToSIunit )) + math.log10( 2 )local minprec = 1 - math.floor( math.log10( outValue ) + 2e-14 );out = formatNum( outValue, math.max( math.floor( prec + adjust ), minprec ));elseout = formatNum( outValue, 0 )endunitEntityId = string.gsub( unitEntity.claims.P2370[1].mainsnak.datavalue.value.unit, 'http://www.wikidata.org/entity/', '' );wbStatus, unitEntity = pcall( mw.wikibase.getEntity, unitEntityId );endendlocal writingSystemElementId = 'Q8209';local langElementId = 'Q7737';local label = getLabelWithLang( context, options, unitEntity.id, nil, { "P5061", "P558", "P558" }, {'P5061[language:' .. langCode .. ']','P558[P282:' .. writingSystemElementId .. ', P407:' .. langElementId .. ']','P558[!P282][!P407]'} );out = out .. ' ' .. label;endendendreturn out;end-- Функция для оформления URLlocal function formatUrlValue( context, options, value )if not options.length or options.length == '' thenoptions.length = 25endlocal moduleUrl = require( 'Module:URL' )return moduleUrl.formatUrlSingle( context, options, value )endlocal DATATYPE_CACHE = {}--[[Get property datatype by ID.@param string Property ID, e.g. 'P123'.@return string Property datatype, e.g. 'commonsMedia', 'time' or 'url'.]]local function getPropertyDatatype( propertyId )if not propertyId or not string.match( propertyId, '^P%d+$' ) thenreturn nil;endlocal cached = DATATYPE_CACHE[propertyId];if (cached ~= nil) then return cached; endlocal wbStatus, propertyEntity = pcall( mw.wikibase.getEntity, propertyId );if wbStatus ~= true or not propertyEntity thenreturn nil;endmw.log("Loaded datatype " .. propertyEntity.datatype .. " of " .. propertyId .. ' from wikidata, consider passing datatype argument to formatProperty call or to Wikidata/config' )DATATYPE_CACHE[propertyId] = propertyEntity.datatype;return propertyEntity.datatype;endlocal function getDefaultValueFunction( datavalue, datatype )-- вызов обработчиков по умолчанию для известных типов значенийif datavalue.type == 'wikibase-entityid' then-- Entity IDreturn function( context, options, value ) return formatEntityId( context, options, getEntityIdFromValue( value ) ) end;elseif datavalue.type == 'string' then-- Stringif datatype and datatype == 'commonsMedia' then-- Mediareturn function( context, options, value )return formatCommonsMedia( value, options )end;elseif datatype and datatype == 'external-id' then-- External IDreturn function( context, options, value )return formatExternalId( value, options )endelseif datatype and datatype == 'math' then-- Math formulareturn function( context, options, value )return formatMath( value, options )endelseif datatype and datatype == 'url' then-- URLreturn formatUrlValueendreturn function( context, options, value ) return value end;elseif datavalue.type == 'monolingualtext' then-- моноязычный текст (строка с указанием языка)return function( context, options, value )if ( options.monolingualLangTemplate == 'lang' ) thenif ( value.language == contentLanguageCode ) thenreturn value.text;endreturn options.frame:expandTemplate{ title = 'lang-' .. value.language, args = { value.text } };elseif ( options.monolingualLangTemplate == 'ref' ) thenreturn '<span class="lang" lang="' .. value.language .. '">' .. value.text .. '</span>' .. options.frame:expandTemplate{ title = 'ref-' .. value.language };elsereturn '<span class="lang" lang="' .. value.language .. '">' .. value.text .. '</span>';endend;elseif datavalue.type == 'globecoordinate' then-- географические координатыreturn function( context, options, value ) return formatGlobeCoordinate( value, options )  end;elseif datavalue.type == 'quantity' thenreturn function( context, options, value ) return formatQuantity( value, options )  end;elseif datavalue.type == 'time' thenreturn function( context, options, value )local moduleDate = require( 'Module:Wikidata/date' )return moduleDate.formatDate( context, options, value );end;else-- во всех стальных случаях возвращаем ошибкуthrowError( 'unknown-datavalue-type' )endend--[[Функция для оформления значений (value)Подробнее о значениях  см. d:Wikidata:Glossary/ruПринимает: объект-значение и таблицу параметров,Возвращает: строку оформленного текста]]function formatDatavalue( context, options, datavalue, datatype )if ( not context ) then error( 'context not specified' ); end;if ( not options ) then error( 'options not specified' ); end;if ( not datavalue ) then error( 'datavalue not specified' ); end;if ( not datavalue.value ) then error( 'datavalue.value is missng' ); end;-- проверка на указание специализированных обработчиков в параметрах,-- переданных при вызовеcontext.formatValueDefault = getDefaultValueFunction( datavalue, datatype );local functionToCall = getUserFunction( options, 'value', context.formatValueDefault );return functionToCall( context, options, datavalue.value );endlocal DEFAULT_BOUNDARIES = { os.time() * 1000, os.time() * 1000};--[[Функция для оформления идентификатора сущностиПринимает: строку индентификатора (типа Q42) и таблицу параметров,Возвращает: строку оформленного текста]]function formatEntityId( context, options, entityId )-- получение локализованного названияlocal boundaries = nilif options.qualifiers thenboundaries = p.getTimeBoundariesFromQualifiers( frame, context, { qualifiers = options.qualifiers } )endif not boundaries thenboundaries = DEFAULT_BOUNDARIES;endlocal label, labelLanguageCode = getLabelWithLang( context, options, entityId, boundaries )-- определение соответствующей показываемому элементу категорииlocal category = context.extractCategory( options, { id = entityId } )-- получение ссылки по идентификаторуlocal link = mw.wikibase.sitelink( entityId )if link then-- ссылка на категорию, а не добавление страницы в неёif mw.ustring.match( link, '^' .. mw.site.namespaces[ 14 ].name .. ':' ) thenlink = ':' .. linkendif label and not options.rawArticle thenlocal a = link == label and ('[[' .. link .. ']]') or '[[' .. link .. '|' .. label .. ']]';if ( contentLanguageCode ~= labelLanguageCode and 'mul' ~= labelLanguageCode ) thena = a .. getCategoryByCode( 'links-to-entities-with-missing-local-language-label' );endreturn a .. category;elsereturn '[[' .. link .. ']]' .. category;endendif label then  -- TODO: возможно, лучше просто mw.wikibase.label(entityId)-- красная ссылка-- TODO: разобраться, почему не всегда есть options.framelocal title = mw.title.new( label );if title and not title.exists and options.frame thenlocal moduleRedLink = require( 'Module:Wikidata/redLink' )local rawLabel = mw.wikibase.label(entityId) or label -- без |text= и boundaries; or label - костыльlocal redLink = moduleRedLink.formatRedLinkWithInfobox(rawLabel, label, entityId)if ( contentLanguageCode ~= labelLanguageCode and 'mul' ~= labelLanguageCode ) thenredLink = redLink .. getCategoryByCode( 'links-to-entities-with-missing-local-language-label' );endreturn redLink .. '<sup>[[:d:' .. entityId .. '|[d]]]</sup>' .. categoryend-- TODO: перенести до проверки на существование статьиlocal sup = '';if ( not options.format or options.format ~= 'text' )and entityId ~= 'Q6581072' and entityId ~= 'Q6581097' -- TODO: переписать на format=textthensup = '<sup class="plainlinks noprint">[//www.wikidata.org/wiki/' .. entityId .. '?uselang=' .. contentLanguageCode .. ' [d&#x5d;]</sup>'end-- одноимённая статья уже существует - выводится текст и ссылка на ВДreturn '<span class="iw" data-title="' .. label .. '">' .. label.. sup.. '</span>' .. categoryend-- сообщение об отсутвии локализованного названия-- not good, but better than nothingreturn '[[:d:' .. entityId .. '|' .. entityId .. ']]<span style="border-bottom: 1px dotted; cursor: help; white-space: nowrap" title="В Викиданных нет русской подписи к элементу. Вы можете помочь, указав русский вариант подписи.">?</span>' .. getCategoryByCode( 'links-to-entities-with-missing-label' ) .. category;end--[[Функция для оформления утверждений (statement)Подробнее о утверждениях см. d:Wikidata:Glossary/ruПринимает: таблицу параметровВозвращает: строку оформленного текста, предназначенного для отображения в статье]]-- устаревшее имя, не использоватьfunction p.formatStatements( frame )return p.formatProperty( frame );end--[[Получение параметров, которые обычно используются для вывода свойства.]]function getPropertyParams( propertyId, datatype, params )local config = getConfig();-- Различные уровни настройки параметров, по убыванию приоритетаlocal propertyParams = {};-- 1. Параметры, указанные явно при вызовеif params thenfor key, value in pairs( params ) doif value ~= '' thenpropertyParams[ key ] = value;endendend-- 2. Настройки конкретного параметраif config[ 'properties' ] and config[ 'properties' ][ propertyId ] thenfor key, value in pairs( config[ 'properties' ][ propertyId ] ) doif propertyParams[ key ] == nil thenpropertyParams[ key ] = value;endendend-- 3. Указанный пресет настроекif propertyParams[ 'preset' ] and config[ 'presets' ] andconfig[ 'presets' ][ propertyParams[ 'preset' ] ]thenfor key, value in pairs( config[ 'presets' ][ propertyParams[ 'preset' ] ] ) doif propertyParams[ key ] == nil thenpropertyParams[ key ] = value;endendendlocal datatype = datatype or params.datatype or propertyParams.datatype or getPropertyDatatype( propertyId );if propertyParams.datatype == nil thenpropertyParams.datatype = datatype;end-- 4. Настройки для типа данныхif datatype and config[ 'datatypes' ] and config[ 'datatypes' ][ datatype ] thenfor key, value in pairs( config[ 'datatypes' ][ datatype ] ) doif propertyParams[ key ] == nil thenpropertyParams[ key ] = value;endendend-- 5. Общие настройки для всех свойствif config[ 'global' ] thenfor key, value in pairs( config[ 'global' ] ) doif propertyParams[ key ] == nil thenpropertyParams[ key ] = value;endendendreturn propertyParams;endfunction p.formatProperty( frame )local args = frame.args-- проверка на отсутствие обязательного параметра propertyif not args.property thenthrowError( 'property-param-not-provided' )endlocal override;local propertyId = mw.language.getContentLanguage():ucfirst( string.gsub( args.property, '([^Pp0-9].*)$', function(w) if string.sub( w, 1, 1 ) == '~' then override = w; endreturn ''; end ) ) args = getPropertyParams( propertyId, nil, args );if (override) then args[override:match('[,~]([^=]*)=')] = override:match('=(.*)')args['property'] = propertyIdendlocal datatype = args.datatype;-- проброс всех параметров из шаблона {wikidata} и параметра from откуда угодноp_frame = framewhile p_frame doif p_frame:getTitle() == mw.site.namespaces[10].name .. ':Wikidata' thencopyTo( p_frame.args, args, true );endif p_frame.args and p_frame.args.from and p_frame.args.from ~= '' thenargs.entityId = p_frame.args.from;endp_frame = p_frame:getParent();endargs.plain = toBoolean( args.plain, false );args.nocat = toBoolean( args.nocat, false );args.references = toBoolean( args.references, true );-- если значение передано в параметрах вызова то выводим только егоif args.value and args.value ~= '' then-- специальное значение для скрытия Викиданныхif args.value == '-' thenreturn ''endlocal value = args.value-- опция, запрещающая оформление значения, поэтому никак не трогаемif args.plain thenreturn valueendlocal context = initContext( args );-- обработчики по типу значенияlocal wrapperExtraArgs = {}if args['value-module'] and args['value-function'] and not string.find( value, '[%[%]%{%}]' ) thenlocal func = getUserFunction( args, 'value' );value = func( context, args, value );elseif datatype == 'commonsMedia' thenvalue = formatCommonsMedia( value, args );elseif datatype == 'external-id' and not string.find( value, '[%[%]%{%}]' ) thenwrapperExtraArgs['data-wikidata-external-id'] = mw.text.killMarkers( value );value = formatExternalId( value, args );--elseif datatype == 'math' then-- args.frame = frame -- костыль: в formatMath нужно frame:extensionTag--value = formatMath( value, args );elseif datatype == 'url' thenvalue = formatUrlValue( context, args, value );end-- оборачиваем в тег для JS-функцийif string.match( propertyId, '^P%d+$' ) thenvalue = mw.text.trim( value )-- временная штрафная категория для исправления табличных вставокlocal allowTables = getPropertyParams(propertyId, nil, {})['allowTables']if ( not allowTablesand string.match( value, '<t[dhr][ >]' )-- and not string.match( value, '<table[ >]' )-- and not string.match( value, '^%{%|' )) thenvalue = value .. getCategoryByCode( 'value-contains-table', propertyId )elsevalue = wrapStatement( value, propertyId, nil, wrapperExtraArgs );endendreturn valueendif ( args.plain ) then -- вызова стандартного обработчика без оформления, если передана опция plainlocal callArgs = { propertyId };if args.entityId thencallArgs.from = args.entityId;endreturn frame:callParserFunction( '#property', callArgs );endg_frame = frame-- после проверки всех аргументов -- вызов функции оформления для свойства (набора утверждений)return formatProperty( args )end--[[Функция проверки на присутствие источника в списке нерекомендованных.Принимает: таблицу snak'овВозвращает: true/false]]function isReferenceDeprecated( snaks )if not snaks thenreturn falseendif snaks.P248and snaks.P248[1]and snaks.P248[1].datavalueand snaks.P248[1].datavalue.value.idthenlocal entityId = snaks.P248[1].datavalue.value.idif getConfig( 'deprecatedSources', entityId ) thenreturn trueendelseif snaks.P1433and snaks.P1433[1]and snaks.P1433[1].datavalueand snaks.P1433[1].datavalue.value.idthenlocal entityId = snaks.P1433[1].datavalue.value.idif getConfig( 'deprecatedSources', entityId ) thenreturn trueendendreturn falseend--[[Функция оформления ссылок на источники (reference)Подробнее о ссылках на источники см. d:Wikidata:Glossary/ruЭкспортируется в качестве зарезервированной точки для вызова из функций-расширения вида claim-module/claim-function через contextВызов из других модулей напрямую осуществляться не должен (используйте frame:expandTemplate вместе с одним из специлизированных шаблонов вывода значения свойства).Принимает: объект-таблицу утверждениеВозвращает: строку оформленных ссылок для отображения в статье]]function formatRefs( 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 ( not outputReferences ) thenreturn '';endlocal references = {};if ( statement.references ) thenlocal allReferences = statement.references;local hasNotDeprecated = false;local displayCount = 0;for _, reference in pairs( statement.references ) dolocal entityId = nil;if not isReferenceDeprecated( reference.snaks ) thenhasNotDeprecated = true;endendfor _, reference in pairs( statement.references ) dolocal display = true;if ( hasNotDeprecated ) thenif isReferenceDeprecated( reference.snaks ) thendisplay = false;endendif ( displayCount > 2 ) thenif ( options.entity and options.property ) thenlocal propertyID = mw.ustring.match( options.property, '^[Pp][0-9]+' )  -- TODO: обрабатывать не тут, а раньшеlocal moreReferences = '<sup>[[d:' .. options.entity.id .. '#' .. string.upper( propertyID ) .. '|[…]]]</sup>';table.insert( references, moreReferences );endbreak;endif ( display == true ) thenlocal refText = moduleSources.renderReference( g_frame, options.entity, reference );if ( refText ~= '' ) thentable.insert( references, refText );displayCount = displayCount + 1;endendendendreturn table.concat( references );endreturn p