Документација модула[прикажи] [уреди] [историја] [освежи]

Принцип рада модула

уреди

Овај модул генерише текст коришћен у фуснотама изведеним са ставки Википодатака.

Функције

уреди

Спољашње

уреди

coalesce(arg1, arg2, arg3, arg4)

уреди

expandBookSeries(context, data)

уреди

isInstanceOf(entity, typeEntityId)

уреди

isEmpty(str)

уреди

copyArgsToSnaks(args, snaks)

уреди

getLangCode(langEntityId)

уреди

populateDataFromClaims(context, entityId, claims, data)

уреди

getSingle(value)

уреди

toString(context, value, options)

уреди

wrapInUrl(urls, text)

уреди

getElementLink(context, entityId, entity)

уреди

populateSourceDataImpl(context, entity, plainData)

уреди

expandPublication(context, sourceEntity, data)

уреди

expandSpecialsQualifiers(context, entity, propertyId, data)

уреди

Expand special types of references when additional data could be found in OTHER entity properties

expandSpecials(context, currentEntity, reference, data)

уреди

preprocessPlaces(data, lang)

уреди

toWikibaseEntityIdSnak(propertyId, entityId)

уреди

getEntity(context, entityId)

уреди

Унутрашње

уреди

getSingleStringQualifierValue(allQualifiers, qualifierPropertyId)

уреди

appendImpl_toTable(result, resultProperty)

уреди

tokenizeName(fullName)

уреди

appendImpl(datavalue, qualifiers, result, property, options)

уреди

appendSnaks(allSnaks, snakPropertyId, result, property, options)

уреди

appendQualifiers(claims, qualifierPropertyId, result, resultProperty, options)

уреди

assertNotNull(argName, arg)

уреди

toStringSnak(propertyId, strValue)

уреди

toUrlSnak(propertyId, strValue)

уреди

findClaimsByValue(entity, propertyId, value)

уреди

getBestStatements(entity, propertyId)

уреди

getNormativeTitle(entity)

уреди

renderLink(context, entityId, customTitle, options)

уреди

toTextWithTip(text, tip)

уреди

getPlaceName(lang, placeId)

уреди

local p = {};

local i18nDefaultLanguage = 'ru';
p.i18nDefaultLanguage = i18nDefaultLanguage;

local NORMATIVE_DOCUMENTS = {
	Q20754888 = 'Закон Российской Федерации',
	Q20754884 = 'Закон РСФСР',
	Q20873831 = 'Распоряжение Президента Российской Федерации',
	Q20873834 = 'Указ исполняющего обязанности Президента Российской Федерации',
	Q2061228 = 'Указ Президента Российской Федерации',
}

local monthg = {'јануара', 'фебруара', 'марта', 'априла', 'маја', 'јуна', 'јула', 'августа', "септембра", "октобра", "новембра", "децембра"}

local options_commas_nolinks = { separator = ', ', conjunction = ', ', format = function( src ) return src end, nolinks = true, preferids = false };

local function isEmpty( str )
	return ( not str ) or ( str == nil ) or ( #str == 0 );
end
p.isEmpty = isEmpty

local function getSingleStringQualifierValue( allQualifiers, qualifierPropertyId )
	if ( not allQualifiers ) then return end
	if ( not allQualifiers[qualifierPropertyId] ) then return end

	for k, qualifier in pairs( allQualifiers[qualifierPropertyId] ) do
		if ( qualifier and qualifier.datatype == 'string'
				and qualifier.datavalue and qualifier.datavalue.type == 'string' and not isEmpty( qualifier.datavalue.value ) ) then
			return qualifier.datavalue.value;
		end
	end

	return;
end

local function appendImpl_toTable(result, resultProperty)
	if ( not result[resultProperty] ) then
		result[resultProperty] = {};
	elseif ( type( result[resultProperty] ) == 'string' or ( type( result[resultProperty] ) == 'table' and type( result[resultProperty].id ) == 'string' ) ) then
		result[resultProperty] = { result[resultProperty] };
	end
end

local function appendImpl( datavalue, qualifiers, result, property, options )
	if ( datavalue.type == 'string' ) then
		local statedAs = getSingleStringQualifierValue(qualifiers, 'P1932');
		local value;
		if ( statedAs ) then
			value = statedAs;
		else
			value = datavalue.value;
			if ( options.format ) then
				value = options.format( value );
			end
		end
		appendImpl_toTable( result, property );
		local pos = getSingleStringQualifierValue(qualifiers, 'P1545')
		if pos then
			table.insert( result[property], pos, value );
		else
			table.insert( result[property], value);
		end
	elseif ( datavalue.type == 'url' ) then
		local statedAs = getSingleStringQualifierValue(qualifiers, 'P1932');
		local value = datavalue.value;
		if ( options.format ) then
			value = options.format( value );
		end
		appendImpl_toTable( result, property );
		table.insert( result[property], value);
	elseif ( datavalue.type == 'monolingualtext' ) then
		local value = datavalue.value.text;
		if ( options.format ) then
			value = options.format( value );
		end
		appendImpl_toTable( result, property );
		table.insert( result[property], value);
	elseif ( datavalue.type == 'quantity' ) then
		local value = datavalue.value.amount;
		if ( mw.ustring.sub( value , 1, 1 ) == '+' ) then
			value = mw.ustring.sub( value , 2 );
		end
		if ( options.format ) then
			value = options.format( value );
		end
		appendImpl_toTable( result, property );
		table.insert( result[property], value);
	elseif ( datavalue.type == 'wikibase-entityid' ) then
		local pos = getSingleStringQualifierValue(qualifiers, 'P1545')
		local value = datavalue.value;
		appendImpl_toTable( result, property );
		local toInsert = {
			id = value.id,
			label = getSingleStringQualifierValue(qualifiers, 'P1932') -- stated as
		};
		if pos then
			table.insert( result[property], pos, toInsert );
		else
			table.insert( result[property], toInsert );
		end
	elseif datavalue.type == 'time' then
		local value = datavalue.value;
		if ( options.format ) then
			value = options.format( value );
		end
		appendImpl_toTable( result, property );
		table.insert( result[property], tostring( value.time ));
    end 
end

local function appendSnaks( allSnaks, snakPropertyId, result, property, options )
	-- do not populate twice
	if ( result[property] and property ~= 'author') then return result end;
	if ( not allSnaks ) then return result; end;
	
	local selectedSnakes = allSnaks[ snakPropertyId ];
	if ( not selectedSnakes ) then return result; end;

	local hasPreferred = false;
	for k, snak in pairs( selectedSnakes ) do
		if ( snak and snak.mainsnak and snak.mainsnak.datavalue and snak.rank == 'preferred' ) then
			--it's a preferred claim
			appendImpl( snak.mainsnak.datavalue, snak.qualifiers, result, property, options );
			hasPreferred = true;
		end
	end
	if ( hasPreferred ) then return result; end;

	if ( snakPropertyId == 'P1680' ) then -- if there is a russian 
		for k, snak in pairs( selectedSnakes ) do
			if ( snak and snak.mainsnak and snak.mainsnak.datavalue and snak.mainsnak.datavalue.value and 
				 snak.rank ~= 'deprecated' and snak.mainsnak.datavalue.value.language  == i18nDefaultLanguage ) then
				--found russian string
				appendImpl( snak.mainsnak.datavalue, snak.qualifiers, result, property, options );
				return result;
			end
		end
	end;

	for k, snak in pairs( selectedSnakes ) do
		if ( snak and snak.mainsnak and snak.mainsnak.datavalue and snak.rank ~= 'deprecated' ) then
			--it's a claim
			appendImpl( snak.mainsnak.datavalue, snak.qualifiers, result, property, options );
		elseif ( snak and snak.datavalue ) then
			-- it's a snak
			appendImpl( snak.datavalue, nil, result, property, options );
		end
	end
end

local function appendQualifiers( claims, qualifierPropertyId, result, resultProperty, options )
	-- do not populate twice
	if ( not claims ) then return result end;
	if ( result[resultProperty] ) then return result end;

	for i, claim in pairs( claims ) do
		if ( claim.qualifiers and claim.qualifiers[ qualifierPropertyId ] ) then
			for k, qualifier in pairs( claim.qualifiers[ qualifierPropertyId ] ) do
				if ( qualifier and qualifier.datavalue ) then
					appendImpl( qualifier.datavalue, nil, result, resultProperty, options );
				end
			end
		end
	end
end

local function assertNotNull( argName, arg )
	if ( (not arg) or (arg == nil) ) then
		error( argName .. ' is not specified' )
	end
end

function p.coalesce( arg1, arg2, arg3, arg4 )
	if ( not isEmpty( arg1 ) ) then return arg1 end
	if ( not isEmpty( arg2 ) ) then return arg2 end
	if ( not isEmpty( arg3 ) ) then return arg3 end
	if ( not isEmpty( arg4 ) ) then return arg4 end
	return nil;
end

local function toStringSnak( propertyId, strValue )
	assertNotNull('propertyId', strValue)
	assertNotNull('strValue', strValue)

	local snak = { snaktype = "value", property = propertyId, datatype = 'string'};
	snak["datavalue"] = { value = strValue, type = 'string' };
	return snak;
end

local function toUrlSnak( propertyId, strValue )
	assertNotNull('propertyId', strValue)
	assertNotNull('strValue', strValue)

	local snak = { snaktype = "value", property = propertyId, datatype = 'string'};
	snak["datavalue"] = { value = strValue, type = 'url' };
	return snak;
end

function p.copyArgsToSnaks( args, snaks )
	if ( not isEmpty( args.part ) ) then snaks.P958 = { toStringSnak( 'P958', tostring( args.part ) ) } end
	if ( not isEmpty( args.pages ) ) then snaks.P304 = { toStringSnak( 'P304', tostring( args.pages ) ) } end
	if ( not isEmpty( args.issue ) ) then snaks.P433 = { toStringSnak( 'P433', tostring( args.issue ) ) } end
	if ( not isEmpty( args.volume ) ) then snaks.P478 = { toStringSnak( 'P478', tostring( args.volume ) ) } end
	if ( not isEmpty( args.url ) ) then snaks.P953 = { toUrlSnak( 'P953', tostring( args.url ) ) } end
	if ( not isEmpty( args.parturl ) ) then snaks.P953 = { toUrlSnak( 'P953', tostring( args.parturl ) ) } end
end

local LANG_CACHE = 	{
	Q150	= 'fr',
	Q188	= 'de',
	Q1321	= 'es',
	Q1860	= 'en',
	Q652	= 'it',
	Q7737	= 'ru',
}

function p.getLangCode( langEntityId )
	if ( not langEntityId ) then
		return;
	end

	-- small optimization
	local cached = LANG_CACHE[ langEntityId ];
	if ( cached ) then return cached; end

	local claims = mw.wikibase.getBestStatements( langEntityId, 'P424' );
	if ( claims ) then
		for _, claim in pairs( claims ) do
			if ( claim
					and claim.mainsnak
					and claim.mainsnak.datavalue
					and claim.mainsnak.datavalue.value ) then
				return '' .. claim.mainsnak.datavalue.value;
			end
		end
	end

	return;
end

local function findClaimsByValue( entity, propertyId, value )
	local result = {};
	if ( entity and entity.claims and entity.claims[propertyId] ) then
		for i, claim in pairs( entity.claims[propertyId] ) do
			if ( claim.mainsnak and claim.mainsnak.datavalue ) then
				local datavalue = claim.mainsnak.datavalue;
				if ( datavalue.type == "string" and datavalue.value == value 
					or datavalue.type == "wikibase-entityid" and datavalue.value["entity-type"] == "item" and tostring( datavalue.value.id ) == value ) then
					table.insert( result, claim );
				end
			end
		end
	end
	return result;
end

local function getBestStatements( entity, propertyId )
	local resultClaims = {};
	if ( entity and entity.claims and entity.claims[ propertyId ] ) then
		local rank = 'normal';
		for i, statement in pairs( entity.claims[ propertyId ] ) do
			if ( statement.rank == 'preferred' ) then
				rank = 'preferred';
				break;
			end
		end
		for i, statement in pairs( entity.claims[ propertyId ] ) do
			if ( statement.rank == rank ) then
				table.insert( resultClaims, statement );
			end
		end
	end
	return resultClaims;
end

local function getEntity( context, entityId )
	assertNotNull( 'context', context );
	assertNotNull( 'entityId', entityId );

	local cached = context.cache[ entityId ];
	if ( cached ) then
		return cached;
	end;

	local wbStatus, result = pcall( mw.wikibase.getEntity, entityId );
	if ( wbStatus ~= true ) then
		return nil;
	end
	
	if ( result ) then
		context.cache[ entityId ] = result;
	end

	return result;
end
p.getEntity = getEntity

function p.expandBookSeries( context, data )
	local bookSeries = data.bookSeries;
	if ( not bookSeries ) then return end;

	-- use only first one
	if ( type( bookSeries ) == 'table' and bookSeries[1] and bookSeries[1].id ) then
		data.bookSeries = bookSeries[1];
		bookSeries = data.bookSeries;
	end

	if ( not bookSeries ) then return end;
	if ( not bookSeries.id ) then return end;

	local bookSeriesEntity = getEntity( context, bookSeries.id );
	appendSnaks( bookSeriesEntity.claims, 'P123', data, 'publisher', {} );
	appendSnaks( bookSeriesEntity.claims, 'P291', data, 'place', {} );
	appendSnaks( bookSeriesEntity.claims, 'P236', data, 'issn', {} );
end

local function isInstanceOf( entity, typeEntityId )
	if ( not entity or not entity.claims or not entity.claims.P31 ) then
		return false;
	end

	for _, 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 actualTypeId = claim.mainsnak.datavalue.value.id;
			if ( actualTypeId == typeEntityId ) then
				return true;
			end
		end
	end

	return false;
end
p.isInstanceOf = isInstanceOf

local function populateDataFromClaims( context, entityId, claims, data )
	appendSnaks( claims, 'P50', data, 'author', {} );
	appendSnaks( claims, 'P2093', data, 'author', {} );
	appendSnaks( claims, 'P407', data, 'lang', {} );
	appendSnaks( claims, 'P364', data, 'lang', {} );
	appendSnaks( claims, 'P958', data, 'part', {} );
	if ( not data.title ) then
		if ( not isEmpty( entityId ) ) then
			local optionsAsLinks = { format = function( text ) return { id = entityId, label = text } end };
			appendSnaks( claims, 'P1476', data, 'title', optionsAsLinks );
		else
			appendSnaks( claims, 'P1476', data, 'title', {} );
		end
		appendSnaks( claims, 'P1680', data, 'subtitle', {} );
	end
	appendSnaks( claims, 'P953', data, 'url', {} );
	appendSnaks( claims, 'P1065', data, 'url', {} );
	appendSnaks( claims, 'P854', data, 'url', {} );
	appendSnaks( claims, 'P973', data, 'url', {} );
    appendSnaks( claims, 'P2699', data, 'url', {} );
	-- temp disable, use only for current entity, see Q22338048 for example of incorrect work
	-- appendSnaks( claims, 'P856', data, 'url', {} );

	appendSnaks( claims, 'P98', data, 'editor', {} );
	appendSnaks( claims, 'P655', data, 'translator', {} );

	appendSnaks( claims, 'P1433', data, 'publication', {} );
	appendSnaks( claims, 'P393', data, 'edition', {} );
	appendSnaks( claims, 'P123', data, 'publisher', {} );
	appendSnaks( claims, 'P291', data, 'place', {} );

	if ( claims and claims.P361 ) then
		for c, claim in pairs( claims.P361 ) do
			if ( claim and claim.mainsnak
					and claim.mainsnak.datavalue
					and claim.mainsnak.datavalue.value
					and claim.mainsnak.datavalue.value.id ) then
				local possibleBookSeriesEntityId = claim.mainsnak.datavalue.value.id;
				local possibleBookSeriesEntity = getEntity( context, possibleBookSeriesEntityId );
				if ( isInstanceOf( possibleBookSeriesEntity, 'Q277759' ) ) then
					appendImpl_toTable( data, 'bookSeries' );
					table.insert( data.bookSeries, { id = possibleBookSeriesEntityId } );

					appendQualifiers( { claim }, 'P478', data, 'bookSeriesVolume', {} );
					appendQualifiers( { claim }, 'P433', data, 'bookSeriesIssue', {} );
				end
			end
		end
	end

	appendSnaks( claims, 'P478', data, 'volume', {} );
	appendSnaks( claims, 'P433', data, 'issue', {} );
	appendSnaks( claims, 'P571', data, 'dateOfCreation', {} );
	appendSnaks( claims, 'P577', data, 'dateOfPublication', {} );
	appendSnaks( claims, 'P304', data, 'pages', {} );
	appendSnaks( claims, 'P1104', data, 'numberOfPages', {} );
	appendSnaks( claims, 'P1092', data, 'tirage', {} );
	appendSnaks( claims, 'P212', data, 'isbn', {} ); -- ISBN-13
	appendSnaks( claims, 'P957', data, 'isbn', {} ); -- ISBN-10
	appendSnaks( claims, 'P236', data, 'issn', {} );

    -- web
	-- appendSnaks( claims, 'P813', data, 'accessdate', {} );

    -- docs
	appendSnaks( claims, 'P1545', data, 'docNumber', {} );

	-- other
	appendSnaks( claims, 'P31', data, 'type', {} );

	appendSnaks( claims, 'P818', data, 'arxiv', {} );
	appendSnaks( claims, 'P356', data, 'doi', {} );
	appendSnaks( claims, 'P698', data, 'pmid', {} );

	-- JSTOR
	appendSnaks( claims, 'P888', data, 'url', { format = function( id ) return 'http://www.jstor.org/stable/' .. id end } );

	return src;
end
p.populateDataFromClaims = populateDataFromClaims

local function getNormativeTitle( entity )
	if ( not entity or not entity.claims or not entity.claims.P31 ) then
		return;
	end

	for _, 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 classId = claim.mainsnak.datavalue.value.id;
			local title = NORMATIVE_DOCUMENTS[ classId ];
			if ( title ) then
				return title;
			end
		end
	end

	return;
end

local function getSingle( value )
	if ( not value ) then
		return;
	end
	if ( type( value ) == 'string' ) then
		return value;
	elseif ( type( value ) == 'table' ) then
		if ( value.id ) then
			return value.id;
		end

		for i, tableValue in pairs( value ) do
			return getSingle( tableValue );
		end
	end

	return '(unknown)';
end
p.getSingle = getSingle

local function wrapInUrl( urls, text )
	local url = getSingle( urls );
	if ( string.sub( url, 1, 1 ) == ':' ) then
		return '[[' .. url .. '|' .. text .. ']]';
	else
		return '[' .. url .. ' ' .. text .. ']';
	end
end
p.wrapInUrl = wrapInUrl

local function getElementLink( context, entityId, entity )
	-- fast sitelink lookup, not an expensive operation
	local link = mw.wikibase.sitelink( entityId )
	if ( link ) then return ':' .. link end

	if ( not entity and entityId ) then
		entity = getEntity( context, entityId )
	end

	if ( entity ) then
		-- link to entity in source context language
		local projectToCheck = context.lang .. 'wiki';
		if ( entity.sitelinks and entity.sitelinks[ projectToCheck ] ) then
			return ':' .. context.lang .. ':' .. entity.sitelinks[ projectToCheck ].title;
		end
	end

	if ( entityId ) then return ':d:' .. entityId end;
	-- if ( entityId ) then return 'https://tools.wmflabs.org/reasonator/?q=' .. entityId .. '&lang=ru' end;
	return nil;
end
p.getElementLink = getElementLink

local function renderLink( context, entityId, customTitle, options )
	if ( not entityId ) then error("entityId is not specified") end
	if ( type( entityId ) ~= 'string' ) then error('entityId is not string, but ' .. type( entityId ) ) end
	if ( type( customTitle or '' ) ~= 'string' ) then error('customTitle is not string, but ' .. type( customTitle ) ) end

	local title = customTitle;

	if ( isEmpty( title ) ) then
		local entity = getEntity( context, entityId );

		-- ISO 4
		if ( isEmpty( title ) ) then
			if ( entity and entity.claims and entity.claims.P1160 ) then
				for _, claim in pairs( entity.claims.P1160 ) do
					if ( claim
							and claim.mainsnak
							and claim.mainsnak.datavalue
							and claim.mainsnak.datavalue.value
							and claim.mainsnak.datavalue.value.language == context.lang ) then
						title = claim.mainsnak.datavalue.value.text;
						mw.log('Got title of ' .. entityId .. ' from ISO 4 claim: «' .. title .. '»' )
						break;
					end
				end
			end
		end

		-- official name P1448
		-- short name P1813
		if ( isEmpty( title ) and options.short ) then
			if ( entity and entity.claims and entity.claims.P1813 ) then
				for _, claim in pairs( entity.claims.P1813 ) do
					if ( claim
							and claim.mainsnak
							and claim.mainsnak.datavalue
							and claim.mainsnak.datavalue.value
							and claim.mainsnak.datavalue.value.language == context.lang ) then
						title = claim.mainsnak.datavalue.value.text;
						mw.log('Got title of ' .. entityId .. ' from short name claim: «' .. title .. '»' )
						break;
					end
				end
			end
		end
		-- person name P1559
		-- labels
		if ( isEmpty( title ) and entity.labels[ context.lang ] ) then
			title = entity.labels[ context.lang ].value;
			mw.log('Got title of ' .. entityId .. ' from label: «' .. title .. '»' )
		end
	end

	local actualText = title or '\'\'(untranslated)\'\'';
	local link = getElementLink( context, entityId, entity);
	return wrapInUrl( link, actualText );
end

local function toString( context, value, options )
	if ( type( value ) == 'string' ) then
		return options.format( value );
	elseif ( type( value ) == 'table' ) then
		if ( value.id ) then
			-- this is link
			if ( type( value.label or '' ) ~= 'string' ) then mw.logObject( value ); error('label of table value is not string but ' .. type( value.label ) ) end

			if ( options.preferids ) then
				return options.format( value.id );
			else
				if ( options.nolinks ) then
					return options.format( value.label or mw.wikibase.label( value.id ) or '\'\'(untranslated title)\'\'' );
				else
					return options.format( renderLink( context, value.id, value.label, options ) );
				end
			end
		end

		local resultList = {};
		for i, tableValue in pairs( value ) do
			table.insert( resultList, toString( context, tableValue, options ) );
		end

		return mw.text.listToText( resultList, options.separator, options.conjunction);
	else
		return options.format( '(unknown type)' );
	end

	return '';
end
p.toString = toString

local function populateSourceDataImpl( context, entity, plainData )
    local wsLink = mw.wikibase.getSitelink( entity.id, 'ruwikisource' );
    if ( wsLink ) then
       plainData.url = ":ru:s:" .. wsLink;
    end
	populateDataFromClaims( context, entity.id, entity.claims, plainData );

	local normativeTitle = getNormativeTitle( entity )
	if ( normativeTitle ) then
		local y, m, d = mw.ustring.match( getSingle( plainData.dateOfCreation ) , "(%-?%d+)%-(%d+)%-(%d+)T" );
		y,m,d = tonumber(y),tonumber(m),tonumber(d);
		local title = toString( { lang='ru' }, plainData.title, options_commas_nolinks );
		plainData.title = { normativeTitle .. " от " .. tostring(d) .. " " .. monthg[m]  .. " " .. tostring(y) .. " г. № " .. getSingle( plainData.docNumber ) .. ' «' .. title.. '»' }
	end

	if ( not plainData.title ) then
		if ( entity and entity.labels and entity.labels.ru and entity.labels.ru.value ) then
			plainData.title = { entity.labels.ru.value };
		end
	end

	return plainData;
end
p.populateSourceDataImpl = populateSourceDataImpl

function p.expandPublication( context, sourceEntity, data )
	local publication = data.publication;

	-- use only first one
	if ( type( publication ) == 'table' and publication[1] and publication[1].id ) then
		data.publication = publication[1];
		publication = data.publication;
	end

	if ( not publication ) then return end;
	if ( not publication.id ) then return end;

	if ( sourceEntity ) then
		-- do we have appropriate record in P1433 ?
		local claims = findClaimsByValue( sourceEntity, 'P1433', publication.id );
		if ( claims and #claims ~= 0 ) then
			for _, claim in pairs( claims ) do
				populateDataFromClaims( context, sourceEntity, claim.qualifiers, data );
				break;
			end
		end
	end

	local titleWerePresent = not (not data.title);
	local pubEntity = getEntity( context, publication.id );
	populateSourceDataImpl( context, pubEntity, data );
	if ( titleWerePresent and isEmpty( data.publication.label ) ) then
		appendSnaks( pubEntity.claims, 'P1160', data, 'publication-title', {} ); -- obsolete
		data.publication.label = getSingle( data['publication-title'] );
	end
	if ( titleWerePresent and isEmpty( data.publication.label ) ) then
		appendSnaks( pubEntity.claims, 'P1476', data, 'publication-title', {} );
		appendSnaks( pubEntity.claims, 'P1680', data, 'publication-subtitle', {} );
		data.publication.label = getSingle( data['publication-title'] );
		data.publication.subtitle = getSingle( data['publication-subtitle'] );
	end
end

local function expandSpecialsQualifiers( context, entity, propertyId, data )
	if ( entity and entity.claims and entity.claims[propertyId] ) then
		for _, claim in pairs( entity.claims[propertyId] ) do
			populateDataFromClaims( context, nil, claim.qualifiers, data );
		end
	end
end

-- Expand special types of references when additional data could be found in OTHER entity properties
function p.expandSpecials( context, currentEntity, reference, data )
	local sourceId;
	if ( reference.snaks.P805
			and reference.snaks.P805[1]
			and reference.snaks.P805[1].datavalue
			and reference.snaks.P805[1].datavalue.value.id ) then
		sourceId = reference.snaks.P805[1].datavalue.value.id;
	elseif ( reference.snaks.P248
			and reference.snaks.P248[1]
			and reference.snaks.P248[1].datavalue
			and reference.snaks.P248[1].datavalue.value.id ) then
		sourceId = reference.snaks.P248[1].datavalue.value.id;
	end
	
	if sourceId then
		data.sourceId = sourceId;

		-- Gemeinsame Normdatei -- specified by P227
		if ( sourceId == 'Q36578' ) then
			appendSnaks( currentEntity.claims, 'P227', data, 'part', { format = function( gnd ) return 'Record #' .. gnd; end } );
			appendSnaks( currentEntity.claims, 'P227', data, 'url', { format = function( gnd ) return 'http://d-nb.info/gnd/' .. gnd .. '/'; end } );
			data.year = '2012—2016'
			expandSpecialsQualifiers( context, currentEntity, 'P227', data );

		-- BNF -- specified by P268
		elseif ( sourceId == 'Q15222191' ) then
			appendSnaks( currentEntity.claims, 'P268', data, 'part', { format = function( id ) return 'Record #' .. id; end } );
			appendSnaks( currentEntity.claims, 'P268', data, 'url', { format = function( id ) return 'http://catalogue.bnf.fr/ark:/12148/cb' .. id; end } );
			expandSpecialsQualifiers( context, currentEntity, 'P268', data );

		-- VIAF -- specified by P214
		elseif ( sourceId == 'Q54919' ) then
			appendSnaks( currentEntity.claims, 'P214', data, 'part', { format = function( id ) return 'Record #' .. id; end } );
			appendSnaks( currentEntity.claims, 'P214', data, 'url', { format = function( id ) return 'https://viaf.org/viaf/' .. id; end } );
			expandSpecialsQualifiers( context, currentEntity, 'P214', data );

		-- generic property search
		else
			local sourceEntity = getEntity( context, sourceId );
			if ( sourceEntity ) then
				for _, sourceClaim in ipairs( getBestStatements( sourceEntity, 'P1687' ) ) do
					if ( sourceClaim.mainsnak.snaktype == 'value' ) then
						local sourcePropertyId = sourceClaim.mainsnak.datavalue.value.id;
						local sourcePropertyEntity = getEntity( context, sourcePropertyId );
						if ( sourcePropertyEntity ) then
							for _, sourcePropertyClaim in ipairs( getBestStatements( sourcePropertyEntity, 'P1630' ) ) do
								if ( sourcePropertyClaim.mainsnak.snaktype == 'value' ) then
									appendSnaks( currentEntity.claims, sourcePropertyId, data, 'url', {
										format = function( id )
											return mw.ustring.gsub( mw.ustring.gsub( sourcePropertyClaim.mainsnak.datavalue.value, '$1', id ), ' ', '%%20' )
										end;
									} );
									expandSpecialsQualifiers( context, currentEntity, sourcePropertyId, data );
									break;
								end
							end
						end
					end
				end
			end
		end

		-- do we have appropriate record in P1433 ?
		local claims = findClaimsByValue( currentEntity, 'P1343', sourceId );
		if ( claims and #claims ~= 0 ) then
			for _, claim in pairs( claims ) do
				populateDataFromClaims( context, sourceId, claim.qualifiers, data );
			end
		end
	end
end

local function toTextWithTip( text, tip )
	return '<span title="' .. tip .. '" style="border-bottom: 1px dotted; cursor: help; white-space: nowrap">' .. text .. '</span>';
end

local function getPlaceName( lang, placeId )
	-- ГОСТ Р 7.0.12—2011
	if ( lang == 'ru' ) then
		if ( placeId == 'Q649' ) then return toTextWithTip('М.', 'Москва'); end
		if ( placeId == 'Q656' ) then return toTextWithTip('СПб.', 'Санкт-Петербург'); end
		if ( placeId == 'Q891' ) then return toTextWithTip('Н. Новгород', 'Нижний Новгород'); end
		if ( placeId == 'Q908' ) then return toTextWithTip('Ростов н/Д.', 'Ростов-на-Дону'); end
	end
	return nil;
end

function p.preprocessPlaces( data, lang )
	if ( not data.place ) then
		return;
	end;

	local newPlaces = {};
	for index, place in pairs( data.place ) do
		if ( place.id ) then
			local newPlaceStr = getPlaceName(lang, place.id)
			if ( newPlaceStr ) then
				newPlaces[index] = newPlaceStr;
			else
				newPlaces[index] = place;
			end
		else
			newPlaces[index] = place;
		end
	end
	data.place = newPlaces;
end

function p.toWikibaseEntityIdSnak( propertyId, entityId )
	assertNotNull('propertyId', propertyId)
	assertNotNull('entityId', entityId)
	if ( mw.ustring.sub( entityId, 1, 1 ) ~= 'Q' ) then error( 'Incorrect entity ID: «' .. entityId .. '»' ); end;

	local value = {
		["entity-type"] = 'item',
		["id"] = entityId,
	};

	local snak = { snaktype = "value", property = propertyId, datatype = 'wikibase-item'};
	snak["datavalue"] = { value = value, type = 'wikibase-entityid' };
	return snak;
end

return p;