Документация

Принцип работы модуля

Тупатыны

Данный модуль генерирует текст, используемый в сносках, ссылающихся на элемент викиданных.

Ошибка Lua в package.lua на строке 80: module 'Module:UnitTests' not found.


Служебные подмодули

Тупатыны

Используемые параметры Викиданных

Тупатыны
Свойство Комментарий
автор (P50)
имя автора (строка) (P2093)
язык произведения или названия (P407)
язык оригинала фильма или телешоу (P364)
раздел, стих или параграф (P958) используется для указания названия статьи в энциклопедии
название (P1476) если требуется переопределить название из метки элемента
подзаголовок (P1680)
доступен по URL (P953)
архивный URL (P1065)
URL-ссылка на источник (P854)
опубликовано в (P1433)
номер издания (P393)
издатель (P123)
место публикации (P291)
редактор (P98)
страницы (P304)
количество страниц (P1104)
том (P478)
выпуск (P433)
дата возникновения (P571)
дата публикации (P577)
ISBN-13 (P212)
ISBN-10 (P957)
ISSN (P236)
порядковый номер (P1545)
код arXiv (P818)
JSTOR (P888)

Функции

Тупатыны

Внешние

Тупатыны

Внешние функции принимают объекты типа фрейм и предназначены для вызова из других модулей или через функцию парсера {{#invoke:}}.

Прямое обращение к функциям модулей в статьях крайне нежелательно! Используйте для этих целей подходящие шаблоны.

p.renderSource(frame)

Тупатыны

Выдаёт вики-текст ссылки на заданный источник для подстановки внутрь сноски или списка литературы. См. шаблоны {{source}} и {{ВД-Источник}}, использующие данную функцию. Поддерживает следующие аргументы:

  • frame.args[1] — анонимный аргумент, задающий идентификатор объекта на викиданных, по которому нужно сгенерировать ссылку. Например, Q20750516.
  • frame.args['ref'] — задаёт метку ref, которую в дальнейшем можно будет использовать в шаблонах типа {{sfn}}.
  • frame.args['ref-year'] — задаёт метку ref-year, которая используется аналогично метке ref.
  • frame.args['part'] — дополнительный аргумент, позволяющий уточнить часть источника, на которую идёт ссылка (например, главу в книге).
  • frame.args['parturl'] — ссылка, которую следует поставить на часть, описанную предыдущим аргументом.
  • frame.args['pages'] — конкретные страницы в источнике, на которые ведётся ссылка.
  • frame.args['url'] — позволяет явно указать, какую ссылку нужно будет проставить на источник.
  • frame.args['volume'] — позволяет явно указать том источника, на который идёт ссылка.
  • frame.args['issue'] — позволяет явно указать выпуск источника, на который идёт ссылка.

Пробрасывание большей части аргументов происходит в utils.copyArgsToSnaks. Сам переданный фрейм сохраняется в p.currentFrame для дальнейшего использования, а на основе переданных аргументов функцией artificialSnaks создаются искусственные снеки, которые ссылаются на источник, указанный в frame.args[1], через свойства P248 (stated in) и P805 (statement is subject of). Затем данные передаются в renderReferenceImpl для дальнейшей обработки.

p.renderReference(frame, currentEntity, reference)

Тупатыны

Выдаёт вики-текст готовой сноски на заданный источник. Поддерживает те же аргументы, что и p.renderSource, кроме ref и ref-year. См. шаблоны {{source-ref}} и {{ВД-Сноска}}, использующие данную функцию. Также используется в Модуль:Wikidata для отображения ссылок, указанных возле утверждений на викиданных. Если currentEntity и reference отсутствуют, создаются искусственные снеки с помощью функции artificialSnaks, после чего они передаются в renderReferenceImpl. Если вики-текст для сноски был успешно сгенерирован, он оборачивается в тэг <ref> с помощью frame:extensionTag, при этом имя для сноски генерируется путём хеширования её вики-текста через mw.hash.hashValue. Статьи, с такими сносками помещаются в Категория:Википедия:Статьи с источниками из Викиданных.

Внутренние

Тупатыны

tokenizeName(fullName)

Тупатыны

Преобразует полное имя в пару {фамилии через пробел, инициалы имён через пробел}. Реализована в виде разбора случаев, которые можно встретить на викиданных:

  1. Фамилия, Имя
  2. Фамилия, Имя Имя
  3. Фамилия Фамилия, Имя
  4. Имя Имя оглы Фамилия
  5. Имя Имя де Фамилия
  6. Имя … Имя Фамилия (хотя бы одно и не более четырёх единичных имён)

Здесь имя, в отличие от фамилии, может являться инициалом. Если ни один из форматов выше не выполнен, возвращает полное имя без изменений.

personNameToAuthorName(fullName)

Тупатыны

Преобразует полное имя в формат Фамилия И. О. с помощью tokenizeName.

personNameToResponsibleName(fullName)

Тупатыны

Преобразует полное имя в формат И. О. Фамилия с помощью tokenizeName.

getPeopleAsWikitext(context, value, options)

Тупатыны

Преобразует список имён value в викитекст в соответствии со списком опций options. В опциях должны быть проставлены следующие поля:

  1. separator — разделитель в списке;
  2. conjunction — разделитель перед последним элементом списка;
  3. format — функция, преобразующая имена к некоторому нормализованному виду (например, personNameToAuthorName);
  4. nolinks — логическое значение, должно быть истинным если проставление ссылок нежелательно;
  5. preferids — логическое значение, должно быть истинным если нужно вернуть id с викиданных, а не имена.

Если в списке больше maxAuthors (на текущий момент 10) людей, заменяет остальных на и др. или его аналоги (если в контексте указан язык, то используется i18nEtAl[context.lang], иначе используется i18nEtAlDefault).

appendProperty(result, context, src, conjunctor, property, url)

Тупатыны

Приписывает src[property] к result, разделяя их строкой, записанной в conjunctor. Если возможно, оформляет его ссылкой на src[url].

generateAuthorLinks(context, src)

Тупатыны

Возвращает список авторов src.author, оформленный через getPeopleAsWikitext и обрамлённый в <i class="wef_low_priority_links"></i>.

appendTitle(result, context, src)

Тупатыны

Дописывает к result строку src.part // src.title либо только src.title если src.part не указан. Если возможно, обрамляет src.part (или src.title если src.part не указан) в src.url.

appendLanguage(result, context, src)

Тупатыны

Если context.lang отличается от i18nDefaultLanguage (в нашем разделе русский), то указание об этом приписывается к result с помощью Модуль:Languages в формате {{ref-lang}}.

appendSubtitle(result, context, src)

Тупатыны

Дописывает к result строку : src.subtitle если src.subtitle определён.

appendOriginalTitle(result, context, src)

Тупатыны

Дописывает к result строку  = src.originaltitle если src.originaltitle определён.

appendPublication(result, context, src)

Тупатыны

Дописывает к result строку  // src.publication: src.publication.subtitle если определён src.publication.subtitle, либо  // src.publication если определён только src.publication.

appendEditor(result, context, src)

Тупатыны

Дописывает к result строку  / prefix src.editor если определён src.editor, где prefix определяется по context.lang (по умолчанию, под ред.).

appendEdition(result, context, src)

Тупатыны

Дописывает к result строку  — src.edition если src.edition определён.

appendPublicationData(result, context, src)

Тупатыны

Добавляет к result строку вида  — src.place: src.publisher, src.year. если хотя бы один из указанных параметров определён. Неуказанная часть опускается вместе с соответствующей пунктуацией. В частности, двоеточие ставится только если указано src.place и хотя бы что-то из src.publisher и src.year, запятая ставится только если указаны и src.publisher, и src.year. Тире и точка ставятся если указан хотя бы один из параметров.

appendVolumeAndIssue(result, context, src)

Тупатыны

Добавляет к result строку виду  — letter_vol src.volume, letter_iss src.issue. если хотя бы один из указанных параметров определён. Запятая ставится если указаны оба параметра. letter_vol и letter_iss определяются по context.lang (например, Т. и вып. для русских текстов, Vol. и Iss. для английских).

appendPages(result, context, src)

Тупатыны

Добавляет к result строку вида  — letter src.pages. если src.pages определён, при этом в качестве разделителя в src.pages, если это диапозон страниц, используется символ «—», а letter определяется исходя из context.lang (например, P. для английского и С. для русского).

appendNumberOfPages(result, context, src)

Тупатыны

Добавляет к result строку вида  — src.numberOfPages letter если src.numberOfPages определён. При этом letter определяется из context.lang (p. для английского и с. для русского).

appendBookSeries(result, context, src)

Тупатыны

Добавляет к result строку вида  — (src.bookSeries; letter_vol src.bookSeriesVolume, letter_iss src.bookSeriesIssue) если src.bookSeries определено. Точка с запятой ставится только если определено src.bookSeriesVolume или src.bookSeriesIssue, запятая ставится если определены оба параметра. letter_vol и letter_iss определяются из context.lang, аналогично тому, как это делается в appendVolumeAndIssue.

appendBookSeries(result, context, src)

Тупатыны

Добавляет к result информацию из src.tirage если тот определён. Формат определяется из context.lang, для английского это  — ed. size: src.tirage, а для русского  — src.tirage экз..

appendIdentifiers(result, context, src)

Тупатыны

Добавляет к result идентификаторы ISBN, ISSN, DOI, PMID и arXiv если те определены. Идентификаторы приписываются через тире, более точный формат определён в таблицах options_commas, options_issn, options_doi, options_pmid и options_arxiv.

appendSourceId(result, context, src)

Тупатыны

Оборачивает result в <span class="wikidata_cite citetype" data-entity-id="src.sourceId"></span>, где citetyle это src.type если это поле определено и citetype_unknown в противном случае.

appendAccessDate(result, context, src)

Тупатыны

Добавляет к result строку виду <small>Проверено dd month yyyy.</small>, где dd, month и yyyy берутся из src.accessdate если данное поле определено.

populateUrl(context, src)

Тупатыны

Если src.url не определено, но src.sourceId известен, пытается присвоить в src.url ссылку на викитеку.

populateYear(src)

Тупатыны

Если src.year не определён, пытается заполнить его из src.dateOfPublication и src.dateOfCreation.

populateTitle(src)

Тупатыны

Если src.title не определён, пытается присвоить ему src.url, если и это не получается, то присваивает ''(unspecified title)''.

renderSource(context, src)

Тупатыны

Внутренняя функция, генерирующая текст, который будет отображаться в сноске. Действует следующим образом:

  1. Записывает src.lang в context.lang (или i18nDefaultLanguage если src.lang записать не получилось).
  2. Вызывает populateUrl, populateTitle и populateYear.
  3. Заводит переменную result, изначально равную generateAuthorLinks(context, src).
  4. .Последовательно применяет к result функции appendTitle—appendAccessDate, при этом блок appendEditor—appendAccessDate дополнительно обрамляется в &lt;span class="wef_low_priority_links"&gt;&lt;/span&gt;

artificialSnaks(args)

Тупатыны

Создаёт искусственные снеки, которые ссылаются на источник с идентификатором frame.args[1] через свойства P248 (stated in) и P805 (statement is subject of), а также пробрасывает в них аргументы (том, выпуск и т. д.).

local p = {};

local i18nDefaultLanguage = 'udm';
local i18nEditors = {
	fr	= '',
	de	= 'Hrsg.: ',
	es	= '',
	en	= '',
	it	= '',
	ru	= 'под ред. ',
	udm = 'ред. улсын'
}
local i18nEtAlDefault = ' et al.';
local i18nEtAl = {
	ru	= ' и др.',
}
local i18nVolume = {
	fr	= 'Vol.',
	es	= 'Vol.',
	en	= 'Vol.',
	it	= 'Vol.',
	ru	= 'Т.',
	udm	= 'Т.',
}
local i18nIssue = {
	en	= 'Iss.',
	ru	= 'вып.',
}
local i18nPages = {
	fr	= 'P.',
	de	= 'S.',
	es	= 'P.',
	en	= 'P.',
	it	= 'P.',
	ru	= 'С.',
	udm	= 'С.',
}

local i18nNumberOfPages = {
	en = 'p.',
	ru = 'с.',
}

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

local monthg = {'толшоре', 'тулыспалэ', 'южтолэзе', 'оштолэзе', 'куартолэзе', 'инвожое', 'пӧсьтолэзе', 'гудырикошконэ', 'куарусёнэ', 'коньывуонэ', 'шуркынмонэ', 'толсуре'};

local PREFIX_CITEREF = "CITEREF_";

local options_arxiv = { separator = '; ', conjunction = '; ', format = function( id ) return '[http://arxiv.org/abs/' .. id .. ' arXiv:' .. id .. ']' end, nolinks = true, preferids = false };
local options_doi = { separator = '; ', conjunction = '; ', format = function( doi ) return '[http://dx.doi.org/' .. doi .. ' doi:' .. doi .. ']' end, nolinks = true, preferids = false };
local options_issn = { separator = '; ', conjunction = '; ', format = function( issn ) return '[https://www.worldcat.org/issn/' .. issn .. ' ' .. issn .. ']' end, nolinks = true, preferids = false };

local options_commas = { separator = ', ', conjunction = ', ', format = function( src ) return src end, nolinks = false, preferids = false };
local options_commas_short = { separator = ', ', conjunction = ', ', format = function( src ) return src end, nolinks = false, preferids = false, short = true };
local options_commas_nolinks = { separator = ', ', conjunction = ', ', format = function( src ) return src end, nolinks = true, preferids = false };
local options_commas_it = { separator = ', ', conjunction = ', ', format = function( src ) return "''" .. src .. "''" end, nolinks = false, preferids = false };
local options_commas_it_short = { separator = ', ', conjunction = ', ', format = function( src ) return "''" .. src .. "''" end, nolinks = false, preferids = false, short = true };
local options_commas_it_nolinks = { separator = ', ', conjunction = ', ', format = function( src ) return "''" .. src .. "''" end, nolinks = true , preferids = false };
local options_citetypes = { separator = ' ', conjunction = ' ', format = function( src ) return 'citetype_' .. src end, nolinks = true , preferids = true };

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

function 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

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

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["numeric-id"] ) then
			local actualTypeId = 'Q' .. claim.mainsnak.datavalue.value["numeric-id"];
			if ( actualTypeId == typeEntityId ) then
				return true;
			end
		end
	end

	return false;
end

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

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

	local result = mw.wikibase.getEntity( entityId );
	if ( result ) then
		context.cache[ entityId ] = result;
	end

	return result;
end

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

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 toWikibaseEntityIdSnak( propertyId, entityId )
	assertNotNull('propertyId', entityId)
	assertNotNull('entityId', entityId)
	if ( mw.ustring.sub( entityId, 1, 1 ) ~= 'Q' ) then error( 'Incorrect entity ID: «' .. entityId .. '»' ); end;

	local value = {};
	value["entity-type"] = 'item';
	value["numeric-id"] = mw.ustring.sub( entityId , 2);

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

function renderSource( context, src )
	context.lang = getLangCode( getSingle( src.lang ) ) or i18nDefaultLanguage;

	preprocessPlaces( src, context.lang );

	src.title = src.title or getSingle( src.url ) or '\'\'(unspecified title)\'\''

	if ( src.sourceId and not src.url ) then
		local entity = getEntity( context, src.sourceId );
		if ( entity.sitelinks and entity.sitelinks[ context.lang .. 'wikisource'] ) then
			src.url = ':' .. context.lang .. ':s:' .. entity.sitelinks[ context.lang .. 'wikisource' ].title;
		end
	end

	if ( not src.year and src.dateOfPublication ) then
		local date = getSingle( src.dateOfPublication );
		src.year = mw.ustring.sub( date, 2, 5 );
	end

	if ( not src.year and src.dateOfCreation ) then
		local date = getSingle( src.dateOfCreation );
		src.year = mw.ustring.sub( date, 2, 5 );
	end

	local result;
	if ( src.author ) then
		result = getPeopleAsAuthorWikitext( context, src.author, options_commas );
	end
	if ( not isEmpty( result )) then
		result = '<i class="wef_low_priority_links">' .. result .. '</i> ';
	else
		result = '';
	end
 
 	if ( src.part ) then
 		if ( src.url ) then
			result = result .. wrapInUrl( src.url, toString( context, src.part, options_commas_nolinks ) );
		else
			result = result .. toString( context, src.part, options_commas );
		end
		result = result .. ' // ' .. toString( context, src.title, options_commas );
	else
		-- title only
 		if ( src.url ) then
			result = result .. wrapInUrl( src.url, toString( context, src.title, options_commas_nolinks ) );
		else
			result = result .. toString( context, src.title, options_commas );
		end
 	end

	if ( src.subtitle ) then
		result = result .. ": " .. toString( context, src.subtitle, options_commas );
	end

	if ( src.originaltitle ) then
		result = result .. ' = ' .. toString( context, src.originaltitle, options_commas );
	end

	if ( src.publication ) then
		if ( type( src.publication.title or '') ~= 'string' ) then error('type of src.publication.title is not string but ' .. type( src.publication.title ) ) end;

		result = result .. ' // ' .. toString( context, src.publication, options_commas_it_short );
		if ( src.publication.subtitle ) then
			result = result .. ': ' .. toString( context, src.publication.subtitle, options_commas_it_short );
		end
	end

	result = result .. '<span class="wef_low_priority_links">';

	if ( src.editor ) then
		local prefix = i18nEditors[ context.lang ] or i18nEditors[ i18nDefaultLanguage ];
		result = result .. ' / ' .. prefix .. toString( context, src.editor, options_commas );
	end

	if ( src.edition ) then
		result = result .. ' — ' .. toString( context, src.edition, options_commas );
	end

	if ( src.place or src.publisher or src.year ) then
		result = result .. ' — ';
		if ( src.place ) then
			result = result .. toString( context, src.place, options_commas_short );
			if ( src.publisher or src.year ) then
				result = result .. ': ';
			end
		end
		if ( src.publisher ) then
			result = result .. toString( context, src.publisher, options_commas_short );
			if ( src.year ) then
				result = result .. ', ';
			end
		end
		if ( src.year ) then
			result = result .. toString( context, src.year, options_commas );
		end
		result = result .. '.';
	end
 
 	if ( src.volume or src.issue ) then
 		result = result .. ' — ';
		if ( src.volume ) then
			local letter = i18nVolume[ context.lang ] or i18nVolume[ i18nDefaultLanguage ];
			result = result .. letter .. '&nbsp;' .. toString( context, src.volume, options_commas );
			if ( src.issue ) then
				local letter = i18nIssue[ context.lang ] or i18nIssue[ i18nDefaultLanguage ];
				result = result .. ', ' .. letter .. '&nbsp;' .. toString( context, src.issue, options_commas ) .. '.';
			else
				result = result .. '.';
			end
		else
			local letter = i18nIssue[ context.lang ] or i18nIssue[ i18nDefaultLanguage ];
			result = result .. letter .. '&nbsp;' .. toString( context, src.issue, options_commas ) .. '.';
		end
 	end

	if ( src.pages ) then
		local letter = i18nPages[ context.lang ] or i18nPages[ i18nDefaultLanguage ];
		result = result .. ' — ' .. letter .. '&nbsp;' .. toString( context, src.pages, options_commas )  .. '.';
	end

	if ( src.numberOfPages ) then
		local letter = i18nNumberOfPages[ context.lang ] or i18nNumberOfPages[ i18nDefaultLanguage ];
		result = result .. ' — ' .. toString( context, src.numberOfPages, options_commas ) .. '&nbsp;' .. letter;
	end

	if ( src.bookSeries ) then
		result = result .. ' — (' .. toString( context, src.bookSeries, options_commas )

	 	if ( src.bookSeriesVolume or src.bookSeriesIssue ) then
	 		result = result .. '; ';
			if ( src.bookSeriesVolume ) then
				local letter = i18nVolume[ context.lang ] or i18nVolume[ i18nDefaultLanguage ];
				result = result .. letter .. '&nbsp;' .. toString( context, src.bookSeriesVolume, options_commas );
				if ( src.bookSeriesIssue ) then
					local letter = i18nIssue[ context.lang ] or i18nIssue[ i18nDefaultLanguage ];
					result = result .. ', ' .. letter .. '&nbsp;' .. toString( context, src.bookSeriesIssue, options_commas );
				else
					result = result;
				end
			else
				local letter = i18nIssue[ context.lang ] or i18nIssue[ i18nDefaultLanguage ];
				result = result .. letter .. '&nbsp;' .. toString( context, src.bookSeriesIssue, options_commas );
			end
	 	end

		result = result .. ')';
	end

	if ( src.isbn ) then
		result = result .. ' — ISBN ' .. toString( context, src.isbn, options_commas );
	end

	if ( src.issn ) then
		result = result .. ' — ISSN ' .. toString( context, src.issn, options_issn );
	end

	if ( src.doi ) then
		result = result .. ' — ' .. toString( context, src.doi, options_doi );
	end

	if ( src.arxiv ) then
		result = result .. ' — ' .. toString( context, src.arxiv, options_arxiv );
	end

	if ( src.sourceId ) then
		if ( src.type and src.sourceId ) then
			-- wrap into span to target from JS
			result = '<span class="wikidata_cite ' .. toString( context, src.type, options_citetypes ) .. '" data-entity-id="' .. getSingle( src.sourceId ) .. '">' .. result .. '</span>'
		else
			result = '<span class="wikidata_cite citetype_unknown" data-entity-id="' .. getSingle( src.sourceId ) .. '">' .. result .. '</span>'
		end
	end

	if ( src.accessdate ) then
			local date = getSingle( src.accessdate );
			local pattern = "(%-?%d+)%-(%d+)%-(%d+)T";
			local y, m, d = mw.ustring.match( date , pattern );
			y,m,d = tonumber(y),tonumber(m),tonumber(d);
			result = result .. " <small>Проверено " .. tostring(d) .. " " .. monthg[m]  .. " " .. tostring(y) .. ".</small>";
	end

	result = result .. '</span>';

    -- append invisible links to all elements used by source for tracking purposes
    local result = result .. '<div style="display:none">';
	for key, entity in pairs( context.cache ) do
		result = result .. '<a href="https://wikidata.org' ..  mw.uri.localUrl('Track:' .. key).path .. '"></a>';
	end
    result = result ..'</div>'

	return {text = result, code = src.code};
end

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

function renderShortReference( src )
	context = {
		cache = {},
		lang = getSingle( src.lang ) or i18nDefaultLanguage;
	};
	src.title = src.title or '\'\'(unspecified title)\'\''

	local result = '[[#' .. PREFIX_CITEREF .. src.code .. '|';
	if ( src.author ) then
		result = result .. toString( context, src.author, options_authors_nolinks );
	else
		result = result .. toString( context, src.title, options_commas_it_nolinks );
	end
	result = result .. ']]'

	if ( src.year ) then
		result = result .. ', ' .. toString( context, src.year, options_commas );
	end

	if ( src.volume ) then
		local letter = i18nVolume[ context.lang ] or i18nVolume[ i18nDefaultLanguage ];
		result = result .. ' — ' .. letter .. '&nbsp;' .. toString( context, src.volume, options_commas ) .. '.';
	end

	if ( src.issue ) then
		local letter = i18nIssue[ context.lang ] or i18nIssue[ i18nDefaultLanguage ];
		result = result .. ' — ' .. letter .. '&nbsp;' .. toString( context, src.issue, options_commas ) .. '.';
	end
 
	if ( src.pages ) then
		local letter = i18nPages[ context.lang ] or i18nPages[ i18nDefaultLanguage ];
		result = result .. ' — ' .. letter .. '&nbsp;' .. toString( context, src.pages, options_commas )  .. '.';
	end
 
end

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

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

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.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.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

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

function getPeopleAsAuthorWikitext( context, value, options )
	if ( type( value ) == 'string' ) then
		return personNameToAuthorName( value );
	elseif ( type( value ) == 'table' ) then
		if ( value.id ) then
			-- this is link
			if ( options.preferids ) then
				return value.id;
			else
				if ( options.nolinks ) then
					return getPersonNameAsAuthorLabel( context, value.id, value.label, options );
				else
					return getPersonNameAsAuthorWikitext( context, value.id, value.label, options );
				end
			end
		end

		local resultList = {};
		for i, tableValue in pairs( value ) do
			local nextWikitext = getPeopleAsAuthorWikitext( context, tableValue, options );
			if ( not isEmpty( nextWikitext ) ) then
				table.insert( resultList, nextWikitext );
				if ( #resultList == 4 ) then
					-- even 4 is too much, but we preserve 4th to mark that "it's more than 3"
					break;
				end
			end
		end

		local resultWikitext = '';
		for i, wikitext in pairs( resultList ) do
			if ( i == 4 ) then
				resultWikitext = resultWikitext .. ( i18nEtAl[ context.lang ] or i18nEtAlDefault );
				break;
			end
			if ( i ~= 1 ) then
				resultWikitext = resultWikitext .. ', ';
			end
			resultWikitext = resultWikitext .. wikitext;
		end

		return resultWikitext;
	end

	return options.format( '(unknown type)' );
end

function getPersonNameAsAuthorWikitext( context, entityId, customLabel, options )
	local personNameAsAuthor = getPersonNameAsAuthorLabel( context, entityId, customLabel, options);
	if ( personNameAsAuthor == nil ) then
		return nil;
	end

	local link = getElementLink( context, entityId, nil );
	return wrapInUrl( link, personNameAsAuthor );
end

function getPersonNameAsAuthorLabel( context, entityId, providedLabel, options )
	-- would custom label provided we don't need to check entity at all
	if ( not isEmpty( providedLabel ) ) then
		mw.log( 'Custom label provided for ' .. entityId );
		return personNameToAuthorName( providedLabel );
	end

	local entity = getEntity( context, entityId );
	if ( not entity ) then return '\'\'(entity ' .. entityId .. ' is missing)\'\'' end;
	if ( not isInstanceOf( entity, 'Q5' ) ) then
		mw.log( 'Entity ' .. entityId .. ' is not a person' );
		return nil;
	end

	local personName = nil;
	-- support only labels so far
	if ( entity.labels[ context.lang ] ) then
		personName = entity.labels[ context.lang ].value;
		mw.log('Got person name of ' .. entityId .. ' from label: «' .. personName .. '»' )
	end

	if ( isEmpty( personName ) ) then
		return '\'\'(not translated to ' .. context.lang .. ')\'\'';
	else
		return personNameToAuthorName( personName );
	end
end

function personNameToAuthorName( fullName )
	if ( not fullName ) then return fullName; end

	local f, i, o = mw.ustring.match( fullName, '^%s*(%a[%a\-]*)\,%s(%a[%a\-]*)%s(%a[%a\-]*)%s*$' );
	if ( f ) then
		mw.log( 'personNameToAuthorName: «' .. fullName .. '»: have «Fa, I. O.» match' );
		return f .. '&nbsp;'
			.. mw.ustring.sub( i, 1, 1 ) .. '.&nbsp;'
			.. mw.ustring.sub( o, 1, 1 ) .. '.';
	end

	local f1, f2, i = mw.ustring.match( fullName, '^%s*(%a[%a\-]*)%s(%a[%a\-]*)\,%s(%a[%a\-]*)%s*$' );
	if ( f1 ) then
		mw.log( 'personNameToAuthorName: «' .. fullName .. '»: have «Fa Fa, I» match' );
		return f1 .. '&nbsp;' .. f2 .. '&nbsp;'
			.. mw.ustring.sub( i, 1, 1 ) .. '.';
	end

	local i, o, f = mw.ustring.match( fullName, '^%s*(%a)\.%s(%a)\.%s(%a[%a\-]+)%s*$');
	if ( f ) then
		mw.log( 'personNameToAuthorName: «' .. fullName .. '»: have «I. O. Fa» match' );
		return f .. '&nbsp;' .. i .. '.&nbsp;' .. o .. '.';
	end

	local i1, i2, i3, f = mw.ustring.match( fullName, '^%s*(%a)\.%s(%a)\.%s(%a)\.%s(%a[%a\-]+)%s*$');
	if ( f ) then
		mw.log( 'personNameToAuthorName: «' .. fullName .. '»: have «I. O. ?. Fa» match' );
		return f .. '&nbsp;' .. i1 .. '.&nbsp;' .. i2 .. '.&nbsp;' .. i3 .. '.';
	end

    -- Joel J. P. C. Rodrigues
	local i1, i2, i3, i4, f = mw.ustring.match( fullName, '^%s*(%a)[%a\-]+%s(%a)\.%s(%a)\.%s(%a)\.%s(%a[%a\-]+)%s*$');
	if ( f ) then
		mw.log( 'personNameToAuthorName: «' .. fullName .. '»: have «I. O. ?. Fa» match' );
		return f .. '&nbsp;' .. i1 .. '.&nbsp;' .. i2 .. '.&nbsp;' .. i3 .. '.&nbsp;' .. i4 .. '.';
	end

	local i, o, f = mw.ustring.match( fullName, '^%s*(%a[%a\-]*)%s(%a)\.%s(%a[%a\-]*)%s*$');
	if ( f ) then
		mw.log( 'personNameToAuthorName: «' .. fullName .. '»: have «Im O. Fa» match' );
		return f .. '&nbsp;' .. mw.ustring.sub( i, 1, 1 ) .. '.&nbsp;' .. o .. '.';
	end

	local i, o, f = mw.ustring.match( fullName, '^%s*(%a[%a\-]*)%s(%a[%a\-]*)%s(%a[%a\-]*)%s*$');
	if ( f ) then
		mw.log( 'personNameToAuthorName: «' .. fullName .. '»: have «Im Ot Fa» match' );
		return f .. '&nbsp;' .. mw.ustring.sub( i, 1, 1 ) .. '.&nbsp;' .. mw.ustring.sub( o, 1, 1 ) .. '.';
	end

	local i, o, f = mw.ustring.match( fullName, '^%s*(%a[%a\-]+)%s(%a[%a\-]+)%s+оглы%s+(%a[%a\-]+)%s*$');
	if ( f ) then
		mw.log( 'personNameToAuthorName: «' .. fullName .. '»: have «Im Ot оглы Fa» match' );
		return f .. '&nbsp;' .. mw.ustring.sub( i, 1, 1 ) .. '.&nbsp;' .. mw.ustring.sub( o, 1, 1 ) .. '.';
	end

	local i, f = mw.ustring.match( fullName, '^%s*(%a[%a\-]+)%s(%a[%a\-]+)%s*$');
	if ( f ) then
		mw.log( 'personNameToAuthorName: «' .. fullName .. '»: have «Im Fa» match' );
		return f .. '&nbsp;' .. mw.ustring.sub( i, 1, 1 ) .. '.';
	end

	mw.log( 'Unmatched any pattern: «' .. fullName .. '»' );
	return fullName;
end

-- Expand special types of references when additional data could be found in OTHER entity properties
function expandSpecials( currentEntity, reference, data )
	if ( reference.snaks.P248
			and reference.snaks.P248[1]
			and reference.snaks.P248[1].datavalue
			and reference.snaks.P248[1].datavalue.value["numeric-id"]) then
		local sourceId = "Q" .. reference.snaks.P248[1].datavalue.value["numeric-id"];
		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( currentEntity, 'P227', data );
		end

		-- BNF -- specified by P268
		if ( 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( currentEntity, 'P268', data );
		end

		-- Union List of Artist Names -- specified by P245
		if ( sourceId == 'Q2494649' ) then
			appendSnaks( currentEntity.claims, 'P245', data, 'url', { format = function( id ) return 'http://www.getty.edu/vow/ULANFullDisplay?find=&role=&nation=&subjectid=' .. id end } );
			expandSpecialsQualifiers( currentEntity, 'P245', data );
		end

		-- imdb.com -- specified by P345
		if ( sourceId == 'Q37312' ) then
			appendSnaks( currentEntity.claims, 'P345', data, 'part', { format = function( id ) return 'Person Profile' end } );
			appendSnaks( currentEntity.claims, 'P345', data, 'url', { format = function( id ) return 'http://www.imdb.com/name/' .. id .. '/' end } );
			expandSpecialsQualifiers( currentEntity, 'P345', data );
		end

		-- wtatennis.com -- specified by P597
		if ( sourceId == 'Q14580067' ) then
			appendSnaks( currentEntity.claims, 'P597', data, 'part', { format = function( id ) return 'Player Profile' end } );
			appendSnaks( currentEntity.claims, 'P597', data, 'url', { format = function( id ) return 'http://www.wtatennis.com/players/player/' .. id end } );
			expandSpecialsQualifiers( currentEntity, 'P597', data );
		end

		-- Find a Grave -- specified by P535
		if ( sourceId == 'Q63056' ) then
			appendSnaks( currentEntity.claims, 'P535', data, 'url', { format = function( id ) return 'http://www.findagrave.com/cgi-bin/fg.cgi?page=gr&GRid=' .. id; end } );
			expandSpecialsQualifiers( currentEntity, 'P535', data );
		end

		-- Gran Enciclopèdia Catalana -- specified by P1296
		if ( sourceId == 'Q2664168' ) then
			appendSnaks( currentEntity.claims, 'P1296', data, 'url', { format = function( id ) return 'http://www.enciclopedia.cat/enciclop%C3%A8dies/gran-enciclop%C3%A8dia-catalana/EC-GEC-' .. id .. '.xml'; end } );
			expandSpecialsQualifiers( currentEntity, 'P1296', data );
		end

		-- Encyclopædia Britannica online -- specified by P1417
		if ( sourceId == 'Q5375741' ) then
			appendSnaks( currentEntity.claims, 'P1417', data, 'url', { format = function( id ) return 'http://global.britannica.com/' .. id; end } );
			expandSpecialsQualifiers( currentEntity, 'P1417', data );
		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( sourceId, claim.qualifiers, data );
			end
		end

		-- Electronic Jewish Encyclopedia (Elektronnaja Evrejskaja Entsiklopedia) -- specified by P1438
		if ( sourceId == 'Q1967250' ) then
			appendSnaks( currentEntity.claims, 'P1438', data, 'url', { format = function( id ) return 'http://www.eleven.co.il/article/' .. id; end } );
			expandSpecialsQualifiers( currentEntity, 'P1438', data );
		end

		-- sports-reference.com -- specified by P1447
		if ( sourceId == 'Q18002875' ) then
			appendSnaks( currentEntity.claims, 'P1447', data, 'url', { format = function( id ) return 'http://www.sports-reference.com/olympics/athletes/' .. id .. '.html'; end } );
			expandSpecialsQualifiers( currentEntity, 'P1447', data );
		end

		--  Dizionario Biografico degli Italiani -- specified by P1986
		if ( sourceId == 'Q1128537' ) then
			if ( not data.lang ) then data.lang = { id = 'Q652' } end;
			appendSnaks( currentEntity.claims, 'P1986', data, 'url', { format = function( id ) return 'http://www.treccani.it/enciclopedia/' .. id .. '_%28Dizionario_Biografico%29/' end } );
			expandSpecialsQualifiers( currentEntity, 'P1986', data );
		end
	end
end

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

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["numeric-id"] ) == mw.ustring.sub( value, 2 ) ) then
					table.insert( result, claim );
				end
			end
		end
	end
	return result;
end

function appendSnaks( allSnaks, snakPropertyId, result, property, options )
	-- do not populate twice
	if ( result[property] ) 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;

	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

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

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 );
		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 value = datavalue.value;
		appendImpl_toTable( result, property );
		local toInsert = {
			id = 'Q' .. value["numeric-id"],
			label = getSingleStringQualifierValue(qualifiers, 'P1932') -- stated as
		};
		table.insert( result[property], toInsert );
	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

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

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

function 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( sourceEntity, claim.qualifiers, data );
				break;
			end
		end
	end

	local titleWerePresent = not (not data.title);
	local pubEntity = getEntity( context, publication.id );
	populateSourceDataImpl( 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, 'P357', data, 'publication-title', {} ); -- obsolete
		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

	if ( pubEntity.claims and pubEntity.claims.P361 ) then
		for c, claim in pairs( pubEntity.claims.P361 ) do
			if ( claim and claim.mainsnak and claim.mainsnak.datavalue and claim.mainsnak.datavalue.value and claim.mainsnak.datavalue.value["numeric-id"] ) then
				local possibleBookSeriesEntityId = 'Q' .. claim.mainsnak.datavalue.value["numeric-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

	expandBookSeries( context, data );
end

function 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

function populateSourceDataImpl( entity, plainData )
	populateDataFromClaims( 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 .. " от&nbsp;" .. tostring(d) .. "&nbsp;" .. monthg[m]  .. " " .. tostring(y) .. "&nbsp;г. №&nbsp;" .. getSingle( plainData.docNumber ) .. ' «' .. title.. '»' }
	end

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

	return plainData;
end

function populateDataFromClaims( 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, 'P357', data, 'title', optionsAsLinks ); -- obsolete
			appendSnaks( claims, 'P1476', data, 'title', optionsAsLinks );
		else
			appendSnaks( claims, 'P357', data, 'title', {} ); -- obsolete
			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', {} );
	-- temp disable, use only for current entity, see Q22338048 for example of incorrect work
	-- appendSnaks( claims, 'P856', data, 'url', {} );
	appendSnaks( claims, 'P1433', data, 'publication', {} );
	appendSnaks( claims, 'P393', data, 'edition', {} );
	appendSnaks( claims, 'P123', data, 'publisher', {} );
	appendSnaks( claims, 'P291', data, 'place', {} );
	appendSnaks( claims, 'P304', data, 'pages', {} );
	appendSnaks( claims, 'P1104', data, 'numberOfPages', {} );
	appendSnaks( claims, 'P478', data, 'volume', {} );
	appendSnaks( claims, 'P433', data, 'issue', {} );
	appendSnaks( claims, 'P571', data, 'dateOfCreation', {} );
	appendSnaks( claims, 'P577', data, 'dateOfPublication', {} );
	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', {} );
	-- JSTOR
	appendSnaks( claims, 'P888', data, 'url', { format = function( id ) return 'http://www.jstor.org/stable/' .. id end } );

	return src;
end

function p.renderSource( frame )
	p.currentFrame = frame;

	local arg = frame.args[1];
	local refAnchor = frame.args['ref'];
	local refAnchorYear = frame.args['ref-year'];
	local args = {};
	args.refAnchor = frame.args['ref'];
	args.refAnchorYear = frame.args['ref-year'];
	args.part = frame.args['part'];
	args.pages = frame.args['pages'];

	return p.renderSourceImpl( mw.text.trim( arg ), args );
end

function 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
end

function p.renderSourceImpl( entityId, args )
	args = args or {};

	local snaks = {};
	snaks.P248 = { toWikibaseEntityIdSnak( 'P248', entityId ) };
	copyArgsToSnaks( args, snaks );

	local rendered = renderReferenceImpl( mw.wikibase.getEntity(), { snaks = snaks }, args.refAnchor, args.refAnchorYear );
	if ( rendered ) then return rendered.text end;
end

function p.renderReference( frame, currentEntity, reference )
	p.currentFrame = frame;

	-- template call
	if ( frame and not currentEntity and not reference ) then
		local args = frame.args;
		if ( #frame.args == 0 ) then
			args = frame:getParent().args;
		end

		local snaks = {};

		if ( args[1] ) then
			snaks.P248 = { toWikibaseEntityIdSnak( "P248", args[1] ) };
		end
		copyArgsToSnaks( args, snaks );

		currentEntity = mw.wikibase.getEntity();
		reference = { snaks = snaks };
	end

	local rendered = renderReferenceImpl( currentEntity, reference );

	if ( not rendered ) then
		return '';
	end

	local result;
	local code = rendered.code or rendered.text;
	result = frame:extensionTag( 'ref', rendered.text, {name = code} ) .. '[[Category:Википедия:Викиданнойёсысь источникъёсын статьяос]]';

	return result;
end

function renderReferenceImpl( currentEntity, reference, refAnchor, refAnchorYear )
	if ( not reference.snaks ) then
		return nil;
	end

	-- контекст, содержит также кеш элементов
	local context = {
		cache = {},
	}

	-- данные в простом формате, согласованном с модулями формирования библиографического описания
	local data = {};

    -- забрать данные из reference
    populateDataFromClaims( nil, reference.snaks, data )

	-- update ref name with ref-specific properties
	if ( data.code ) then
		if ( data.part ) then data.code = data.code .. '-' .. getSingle( data.part ) end
		if ( data.pages ) then data.code = data.code .. '-' .. getSingle( data.pages ) end
		if ( data.volume ) then data.code = data.code .. '-' .. getSingle( data.volume ) end
		if ( data.issue ) then data.code = data.code .. '-' .. getSingle( data.issue ) end
		if ( data.url ) then data.code = data.code .. '-' .. getSingle( data.url ) end
	end

	expandSpecials( currentEntity, reference, data );

	local sourceEntity = nil;
	if ( data.sourceId ) then
		sourceEntity = getEntity( context, data.sourceId );
		if ( sourceEntity ) then
			populateSourceDataImpl( sourceEntity, data );
		end
	end

	if ( data.publication ) then
		expandPublication( context, sourceEntity, data );
	end

	if ( next( data ) == nil ) then
		return nil;
	end

	local rendered;
	if ( p.short ) then
		rendered = renderShortReference( data );
		if ( mw.ustring.len( rendered.text ) == 0 ) then
			return nil;
		end

	else
		rendered = renderSource( context, data );
		if ( mw.ustring.len( rendered.text ) == 0 ) then
			return nil;
		end

		if ( refAnchor ) then
			local anchorValue = 'CITEREF' .. refAnchor .. ( coalesce( refAnchorYear, data.year ) or '' );
			rendered.text = '<span class="citation" id="' .. mw.uri.anchorEncode( anchorValue ) .. '">' .. rendered.text .. '</span>';
		end
	end

	return rendered;
end

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

	return;
end

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

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

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

	local langEntity = mw.wikibase.getEntity( langEntityId );
	if ( not langEntity ) then
		mw.log( '[getLangCode] Missing entity ' .. langEntityId );
	else
		if ( langEntity.claims and langEntity.claims.P424 ) then
			for _, claim in pairs( langEntity.claims.P424 ) do
				if ( claim
						and claim.mainsnak
						and claim.mainsnak.datavalue
						and claim.mainsnak.datavalue.value ) then
					return '' .. claim.mainsnak.datavalue.value;
				end
			end
		end
	end

	return;
end

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

return p;