Модуль:Sources: различия между версиями

Материал из Википедии — свободной энциклопедии
Перейти к навигации Перейти к поиску
[отпатрулированная версия][отпатрулированная версия]
Содержимое удалено Содержимое добавлено
Нет описания правки
временно убираем дату проверки, которая вызывает ошибки в сносках
Строка 835: Строка 835:


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


-- docs
-- docs

Версия от 17:58, 15 октября 2015

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

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

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

Тесты [ править ]

33 тестов из 33 провалено.

test_personNameToAuthorName
Тест Ожидаемое значение Фактическое значение
N {{#invoke:Sources | testPersonNameToAuthorName | Ломоносов, Михаил Васильевич }} Ломоносов М. В. Ошибка скрипта: Функции «testPersonNameToAuthorName» не существует.
N {{#invoke:Sources | testPersonNameToAuthorName | Ломоносов, Михаил }} Ломоносов М. Ошибка скрипта: Функции «testPersonNameToAuthorName» не существует.
N {{#invoke:Sources | testPersonNameToAuthorName | Михаил Васильевич Ломоносов }} Ломоносов М. В. Ошибка скрипта: Функции «testPersonNameToAuthorName» не существует.
N {{#invoke:Sources | testPersonNameToAuthorName | Михаил Ломоносов }} Ломоносов М. Ошибка скрипта: Функции «testPersonNameToAuthorName» не существует.
N {{#invoke:Sources | testPersonNameToAuthorName | М. В. Ломоносов }} Ломоносов М. В. Ошибка скрипта: Функции «testPersonNameToAuthorName» не существует.
N {{#invoke:Sources | testPersonNameToAuthorName | М. Ломоносов }} Ломоносов М. Ошибка скрипта: Функции «testPersonNameToAuthorName» не существует.
N {{#invoke:Sources | testPersonNameToAuthorName | Ломоносов М. В. }} Ломоносов М. В. Ошибка скрипта: Функции «testPersonNameToAuthorName» не существует.
N {{#invoke:Sources | testPersonNameToAuthorName | Ломоносов М. }} Ломоносов М. Ошибка скрипта: Функции «testPersonNameToAuthorName» не существует.
N {{#invoke:Sources | testPersonNameToAuthorName | Топчибашев, Мустафа Агабек оглы }} Топчибашев М. А. Ошибка скрипта: Функции «testPersonNameToAuthorName» не существует.
N {{#invoke:Sources | testPersonNameToAuthorName | Гельмонт, Ян Баптиста ван }} ван Гельмонт Я. Б. Ошибка скрипта: Функции «testPersonNameToAuthorName» не существует.
N {{#invoke:Sources | testPersonNameToAuthorName | Гельмонт, Ян ван }} ван Гельмонт Я. Ошибка скрипта: Функции «testPersonNameToAuthorName» не существует.
N {{#invoke:Sources | testPersonNameToAuthorName | Jan Baptista van Helmont }} van Helmont J. B. Ошибка скрипта: Функции «testPersonNameToAuthorName» не существует.
N {{#invoke:Sources | testPersonNameToAuthorName | Jan van Helmont }} van Helmont J. Ошибка скрипта: Функции «testPersonNameToAuthorName» не существует.
test_personNameToResponsibleName
Тест Ожидаемое значение Фактическое значение
N {{#invoke:Sources | testPersonNameToResponsibleName | Ломоносов, Михаил Васильевич }} М. В. Ломоносов Ошибка скрипта: Функции «testPersonNameToResponsibleName» не существует.
N {{#invoke:Sources | testPersonNameToResponsibleName | Ломоносов, Михаил }} М. Ломоносов Ошибка скрипта: Функции «testPersonNameToResponsibleName» не существует.
N {{#invoke:Sources | testPersonNameToResponsibleName | Михаил Васильевич Ломоносов }} М. В. Ломоносов Ошибка скрипта: Функции «testPersonNameToResponsibleName» не существует.
N {{#invoke:Sources | testPersonNameToResponsibleName | Михаил Ломоносов }} М. Ломоносов Ошибка скрипта: Функции «testPersonNameToResponsibleName» не существует.
N {{#invoke:Sources | testPersonNameToResponsibleName | М. В. Ломоносов }} М. В. Ломоносов Ошибка скрипта: Функции «testPersonNameToResponsibleName» не существует.
N {{#invoke:Sources | testPersonNameToResponsibleName | М. Ломоносов }} М. Ломоносов Ошибка скрипта: Функции «testPersonNameToResponsibleName» не существует.
N {{#invoke:Sources | testPersonNameToResponsibleName | Ломоносов М. В. }} М. В. Ломоносов Ошибка скрипта: Функции «testPersonNameToResponsibleName» не существует.
N {{#invoke:Sources | testPersonNameToResponsibleName | Ломоносов М. }} М. Ломоносов Ошибка скрипта: Функции «testPersonNameToResponsibleName» не существует.
N {{#invoke:Sources | testPersonNameToResponsibleName | Топчибашев, Мустафа Агабек оглы }} М. А. Топчибашев Ошибка скрипта: Функции «testPersonNameToResponsibleName» не существует.
N {{#invoke:Sources | testPersonNameToResponsibleName | Гельмонт, Ян Баптиста ван }} Я. Б. ван Гельмонт Ошибка скрипта: Функции «testPersonNameToResponsibleName» не существует.
N {{#invoke:Sources | testPersonNameToResponsibleName | Гельмонт, Ян ван }} Я. ван Гельмонт Ошибка скрипта: Функции «testPersonNameToResponsibleName» не существует.
N {{#invoke:Sources | testPersonNameToResponsibleName | Jan Baptista van Helmont }} J. B. van Helmont Ошибка скрипта: Функции «testPersonNameToResponsibleName» не существует.
N {{#invoke:Sources | testPersonNameToResponsibleName | Jan van Helmont }} J. van Helmont Ошибка скрипта: Функции «testPersonNameToResponsibleName» не существует.
test_renderSource
Тест Ожидаемое значение Фактическое значение
N {{#invoke:Sources | renderSource | Q20750516}} президент Российской Федерации Указ Президента Российской Федерации от 15 января 1992 г. № 23 «О Генеральном директоре Агентства федеральной безопасности Российской Федерации и Министре внутренних дел Российской Федерации» // Собрание законодательства Российской Федерации — 1992. Указ Президента Российской Федерации от 15 января 1992 г. № 23 «О Генеральном директоре Агентства федеральной безопасности Российской Федерации и Министре внутренних дел Российской Федерации» // Собрание законодательства Российской Федерации
N {{#invoke:Sources | renderSource | Q21683979}} Advances in Cryptology — EUROCRYPT 2004 (англ.): International Conference on the Theory and Applications of Cryptographic Techniques, Interlaken, Switzerland, May 2-6, 2004. Proceedings / C. Cachin, J. L. CamenischSpringer, Berlin, Heidelberg, 2004. — 630 p. — ISBN 978-3-540-21935-4doi:10.1007/B97182 [[::d:Q21683979|Advances in Cryptology — EUROCRYPT 2004]] — Springer Berlin Heidelberg, 2004. — 630 p. — ISBN 978-3-540-21935-4doi:10.1007/B97182
N {{#invoke:Sources | renderSource | Q21683981}} Nguyen P. Can We Trust Cryptographic Software? Cryptographic Flaws in GNU Privacy Guard v1.2.3 (англ.) // Advances in Cryptology — EUROCRYPT 2004: International Conference on the Theory and Applications of Cryptographic Techniques, Interlaken, Switzerland, May 2-6, 2004. Proceedings / C. Cachin, J. L. CamenischSpringer, Berlin, Heidelberg, 2004. — P. 555—570. — 630 p. — ISBN 978-3-540-21935-4doi:10.1007/978-3-540-24676-3_33 Nguyen P. Can We Trust Cryptographic Software? Cryptographic Flaws in GNU Privacy Guard v1.2.3 // Advances in Cryptology — EUROCRYPT 2004Springer Berlin Heidelberg, 2004. — P. 555-570. — 630 p. — ISBN 978-3-540-21935-4doi:10.1007/978-3-540-24676-3_33
N {{#invoke:Sources | renderSource | Q21725400}} Eichenauer J., Lehn J. A non-linear congruential pseudo random number generator (англ.) // Statistische HefteSpringer Berlin Heidelberg, Springer Science+Business Media, 1986. — Vol. 27, Iss. 1. — P. 315—326. — ISSN 0932-5026; 1613-9798doi:10.1007/BF02932576 Eichenauer-Herrmann J., Lehn J. A non-linear congruential pseudo random number generator // Statistical PapersSpringer Berlin Heidelberg, Springer Science+Business Media, 1986. — Vol. 27. — Iss. 1. — P. 315-326. — ISSN 0932-5026, 1613-9798 — doi:10.1007/BF02932576
N {{#invoke:Sources | renderSource | Q21725116}} Menezes A. J., Oorschot P. v., Vanstone S. A. Handbook of Applied Cryptography (англ.)CRC Press, 1996. — 816 p. — (Discrete Mathematics and Its Applications) — ISBN 978-0-8493-8523-0 Menezes A. J., Oorschot P. v., Vanstone S. A. Handbook of Applied CryptographyCRC Press, 1996. — 816 p. — ISBN 978-0-8493-8523-0
N {{#invoke:Sources | renderSource | Q27450585}} Введение в криптографию / под ред. В. В. ЯщенкоМ.: МЦНМО, 2000. — 271 с. — ISBN 978-5-900916-26-2 [[::d:Q27450585|Введение в криптографию]] — М.: Московский центр непрерывного математического образования, 2000. — 271 с. — ISBN 978-5-900916-26-2
test_templates
Тест Ожидаемое значение Фактическое значение
N {{source | Q21725116}} Menezes A. J., Oorschot P. v., Vanstone S. A. Handbook of Applied Cryptography (англ.)CRC Press, 1996. — 816 p. — (Discrete Mathematics and Its Applications) — ISBN 978-0-8493-8523-0 Menezes A. J., Oorschot P. v., Vanstone S. A. Handbook of Applied CryptographyCRC Press, 1996. — 816 p. — ISBN 978-0-8493-8523-0


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

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

Свойство Комментарий
автор (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;
local p = {};

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

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_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_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 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 renderSource( src )
	mw.logObject( src );
	
	local context = {
		cache = {},
		lang = getLangCode( getSingle( src.lang ) ) or i18nDefaultLanguage,
	}

	preprocessPlaces( src, context.lang );

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

	if ( src.code and not src.url ) then
		local entity = mw.wikibase.getEntity( src.code );
		if ( entity.sitelinks and entity.sitelinks[ context.lang .. 'wikisource'] ) then
			src.url = ':' .. context.lang .. ':s:' .. entity.sitelinks[ context.lang .. 'wikisource' ].title;
		else
			src.url = ':' .. (mw.wikibase.sitelink( src.code ) or ( 'd:' .. src.code ))
			src.url = ':' .. src.url;
		end
	end

	if ( not src.year and src.dateOfPublication ) then
		local date = getSingle( src.dateOfPublication );
		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 = '\'\'' .. result .. '\'\' ';
	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.originaltitle ) then
		result = result .. ' = ' .. toString( context, src.originaltitle, options_commas );
	end

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

	if ( src.editor ) then
		local prefix = i18nEditors[ context.lang ] or i18nEditors[ i18nDefaultLanguage ];
		result = result .. ' / ' .. prefix .. toString( context, src.editor, 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 );
			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 ) 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

	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.issn ) then
		result = result .. ' — ISSN ' .. toString( context, src.issn, options_commas );
	else
		if ( src.isbn ) then
			result = result .. ' — ISBN ' .. toString( context, src.isbn, options_commas );
		end
	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.entityId ) then
		if ( src.type and src.entityId ) then
			-- wrap into span to target from JS
			result = '<span class="wikidata_cite ' .. toString( context, src.type, options_citetypes ) .. '" data-entity-id="' .. getSingle( src.entityId ) .. '">' .. result .. '</span>'
		else
			result = '<span class="wikidata_cite citetype_unknown" data-entity-id="' .. getSingle( src.entityId ) .. '">' .. 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

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

	local title = customTitle;

	if ( isEmpty( title ) ) then
		local entity = getEntity( context, entityId );
		-- 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;
						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 '[[' .. 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;
	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 '[[' .. 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( customLabel ) ) then
		mw.log( 'Custom label provided for ' .. entityId );
		return personNameToAuthorName( customLabel );
	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
	mw.log( 'personNameToAuthorName: «' .. fullName .. '»' );

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

	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"];

		-- Gemeinsame Normdatei -- specified by P227
		if ( sourceId == 'Q36578' ) then
			appendSnaks( currentEntity.claims, 'P227', data, 'title', { 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.publication = { id = 'Q36578', label = 'Gemeinsame Normdatei' }
			data.year = '2012—2015'
		end

		-- BNF -- specified by P268
		if ( sourceId == 'Q15222191' ) then
			appendSnaks( currentEntity.claims, 'P268', data, 'title', { 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

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

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

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

		-- 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/EBchecked/topic/' .. id .. '/'; end } );
			expandSpecialsQualifiers( currentEntity, 'P1417', data );
		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

		-- do we have appropriate record in P1343 ?
		local claims = findClaimsByValue( currentEntity, 'P1343', sourceId );
		if ( claims and #claims ~= 0 ) then
			appendQualifiers( claims, 'P958', data, 'part', {} );
			appendQualifiers( claims, 'P50', data, 'author', {} );
			appendQualifiers( claims, 'P953', data, 'url', {} );
			appendQualifiers( claims, 'P1065', data, 'url', {} );
			appendQualifiers( claims, 'P854', data, 'url', {} );
			appendQualifiers( claims, 'P357', data, 'title', {} ); -- obsolete
			appendQualifiers( claims, 'P1476', data, 'title', {} );
			appendQualifiers( claims, 'P478', data, 'volume', {} );
		end
	end
end

function expandSpecialsQualifiers( entity, propertyId, result )
	if ( entity.claims ~= nil and entity.claims[propertyId] ~= nil ) then
		local claims = entity.claims[propertyId];
		appendQualifiers( claims, 'P958', result, 'part', {} );
		appendQualifiers( claims, 'P953', result, 'url', {} );
		appendQualifiers( claims, 'P1065', result, 'url', {} );
		appendQualifiers( claims, 'P854', result, 'url', {} );
		appendQualifiers( claims, 'P357', result, 'title', {} ); -- obsolete
		appendQualifiers( claims, 'P1476', result, 'title', {} );
		appendQualifiers( claims, 'P478', result, 'volume', {} );
		appendQualifiers( claims, 'P433', result, 'issue', {} );
	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 ( allSnaks and allSnaks[ snakPropertyId ] ) then
		local hasPreferred = false;
		for k, snak in pairs( allSnaks[ snakPropertyId ] ) do
			if ( snak and snak.mainsnak and snak.mainsnak.datavalue and snak.rank == 'preferred' ) then
				--it's a preferred claim
				appendImpl( snak.mainsnak.datavalue, result, property, options );
				hasPreferred = true;
			end
		end

		if ( hasPreferred ) then
			return;
		end

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

function appendQualifiers( claims, qualifierPropertyId, result, property, options )
	-- do not populate twice
	if ( result[property] ) 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, result, property, options );
				end
			end
		end
	end
end

function appendImpl( datavalue, result, property, options )
	if ( datavalue.type == 'string' ) then
		local value = datavalue.value;
		if ( options.format ) then
			value = options.format( value );
		end
		if ( not result[property] ) then
			result[property] = {};
		elseif ( type( result[property] ) == 'string' or ( type( result[property] ) == 'table' and type( result[property].id ) == 'string' ) ) then
			result[property] = { result[property] };
		end
		table.insert( result[property], value);
	elseif ( datavalue.type == 'monolingualtext' ) then
		local value = datavalue.value.text;
		if ( options.format ) then
			value = options.format( value );
		end
		if ( not result[property] ) then
			result[property] = {};
		elseif ( type( result[property] ) == 'string' or ( type( result[property] ) == 'table' and type( result[property].id ) == 'string' ) ) then
			result[property] = { result[property] };
		end
		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
		if ( not result[property] ) then
			result[property] = {};
		elseif ( type( result[property] ) == 'string' or ( type( result[property] ) == 'table' and type( result[property].id ) == 'string' ) ) then
			result[property] = { result[property] };
		end
		table.insert( result[property], value);
	elseif ( datavalue.type == 'wikibase-entityid' ) then
		local value = datavalue.value;
		if ( not result[property] ) then
			result[property] = {};
		elseif ( type( result[property] ) == 'string' or ( type( result[property] ) == 'table' and type( result[property].id ) == 'string' ) ) then
			result[property] = { result[property] };
		end
		table.insert( result[property], { id = 'Q' .. value["numeric-id"] });
	elseif datavalue.type == 'time' then
		local value = datavalue.value;
		if ( options.format ) then
			value = options.format( value );
		end
		if ( not result[property] ) then
			result[property] = {};
		elseif ( type( result[property] ) == 'string' or ( type( result[property] ) == 'table' and type( result[property].id ) == 'string' ) ) then
			result[property] = { result[property] };
		end
		table.insert( result[property], tostring( value.time ));
    end 
end

function expandPublication( 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 ( publication and publication.id ) then
		if ( sourceEntity ) then
			-- do we have appropriate record in P1433 ?
			local claims = findClaimsByValue( sourceEntity, 'P1433', publication.id );
			if ( claims and #claims ~= 0 ) then
				appendQualifiers( claims, 'P958', data, 'part', {} );
				appendQualifiers( claims, 'P953', data, 'url', {} );
				appendQualifiers( claims, 'P1065', data, 'url', {} );
				appendQualifiers( claims, 'P854', data, 'url', {} );
				appendQualifiers( claims, 'P856', data, 'url', {} );
				appendQualifiers( claims, 'P123', data, 'publisher', {} );
				appendQualifiers( claims, 'P291', data, 'place', {} );
				appendQualifiers( claims, 'P304', data, 'pages', {} );
				appendQualifiers( claims, 'P1104', data, 'numberOfPages', {} );
				appendQualifiers( claims, 'P478', data, 'volume', {} );
				appendQualifiers( claims, 'P433', data, 'issue', {} );
				appendQualifiers( claims, 'P571', data, 'dateOfCreation', {} );
				appendQualifiers( claims, 'P577', data, 'dateOfPublication', {} );
				appendQualifiers( claims, 'P212', data, 'isbn', {} ); -- ISBN-13
				appendQualifiers( claims, 'P957', data, 'isbn', {} ); -- ISBN-10
			end
		end

		populateSourceData( publication.id, data );
	end

end

function populateSourceData( entityId, plainData )
	return populateSourceDataImpl( mw.wikibase.getEntity( entityId ), plainData );
end

function populateSourceDataImpl( entity, plainData )
	populateDataFromClaims( 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);
		plainData.title = { normativeTitle .. " от&nbsp;" .. tostring(d) .. "&nbsp;" .. monthg[m]  .. " " .. tostring(y) .. "&nbsp;г. №&nbsp;" .. getSingle( plainData.docNumber ) .. ' «' .. getSingle( plainData.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( claims, data )
	appendSnaks( claims, 'P50', data, 'author', {} );
	appendSnaks( claims, 'P407', data, 'lang', {} );
	appendSnaks( claims, 'P364', data, 'lang', {} );
	appendSnaks( claims, 'P958', data, 'part', {} );
	appendSnaks( claims, 'P357', data, 'title', {} ); -- obsolete
	appendSnaks( claims, 'P1476', data, 'title', {} );
	appendSnaks( claims, 'P953', data, 'url', {} );
	appendSnaks( claims, 'P1065', data, 'url', {} );
	appendSnaks( claims, 'P854', data, 'url', {} );
	appendSnaks( claims, 'P856', data, 'url', {} );
	appendSnaks( claims, 'P1433', data, 'publication', {} );
	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 updateWithRef( reference, src )
	-- specified
	if ( reference.snaks.P662 ) then
		local cid = reference.snaks.P662[1].datavalue.value;
		src.code = src.code .. '-cid:' .. cid;
		src.title = 'Compound Summary for: CID ' .. cid;
		src.url = 'http://pubchem.ncbi.nlm.nih.gov/summary/summary.cgi?cid=' .. cid;
		src.publication = { id = 'Q278487', label = 'PubChem' };
	end

	populateDataFromClaims(reference.snaks, src);
	return src;
end

function p.renderSource( frame )
	local arg = frame.args[1];
	return p.renderSourceImpl( mw.text.trim( arg ) );
end

function p.renderSourceImpl( 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"] = string.sub( entityId , 2);
	local snak = { datavalue = { value = value } };
	local properties = {};
	properties[1] = snak;
	
	local rendered = renderReferenceImpl( mw.wikibase.getEntity(), { snaks = { P248 = properties } } );
	if ( rendered ) then return rendered.text end;
end

function p.renderReference( frame, currentEntity, reference )
	-- 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
			local value = {};
			value["entity-type"] = 'item';
			value["numeric-id"] = string.sub( args[1] , 2);
			snaks.P248 = {  { datavalue = { value = value } } };
		end
		if ( args.pages ) then snaks.P304 = {  { datavalue = { type = 'string', value = args.pages } } } end
		if ( args.issue ) then snaks.P433 = {  { datavalue = { type = 'string', value = args.issue } } } end
		if ( args.volume ) then snaks.P478 = {  { datavalue = { type = 'string', value = args.volume } } } end
		if ( args.url ) then snaks.P953 = {  { datavalue = { type = 'string', value = args.url } } } end

		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 mw.text.encode( rendered.text );
	result = frame:extensionTag( 'ref', rendered.text, {name = code} ) .. '[[К:Википедия:Статьи с источниками из Викиданных]]';

	return result;
end

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

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

	local entityId, sourceEntity;
	if ( reference and reference.snaks and reference.snaks.P248 ) then
		for _, snak in pairs ( reference.snaks.P248 ) do
			if ( snak.datavalue ) then
				entityId = 'Q' .. snak.datavalue.value["numeric-id"];
				sourceEntity = mw.wikibase.getEntity( entityId );
				data.code = entityId;
				data.entityId = entityId;
				break;
			end
		end
	end

	updateWithRef( reference, data );
	expandSpecials( currentEntity, reference, data );
	if ( sourceEntity ) then
		populateSourceDataImpl( sourceEntity, data );
	end
	expandPublication( sourceEntity, data );

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

	local rendered;
	if ( p.short ) then
		rendered = renderShortReference( data );
	else
		rendered = renderSource( data );
	end

	if ( mw.ustring.len( rendered.text ) == 0 ) then
		return nil;
	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)
			newPlaces[index] = newPlaceStr;
		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;