Модуль:CiteGost

Материал из Википедии — свободной энциклопедии
Перейти к навигации Перейти к поиску
Документация

Модуль предназначен для автоматизированного оформления библиографических записей согласно руководствам русскоязычной Википедии (ВП:БИБГРАФ). В статьях модуль используется посредством шаблона-обёртки {{Источник информации}}.

Общий формат[править код]

authors. title : subtitle [contentType] = origin : в volumesCount т. : workType : info : Пер. с англ. / Ил.: illustrators; пер.: translators; гл. ред.: editorInChief; ред.: editors // publishedIn : publishedInSubtitle = publishedInOrigin : Пер. с англ. / Гл. ред.: publishedInEditorInChief; ред.: publishedInEditors. — edition. — location : publisher, 2000, 25 января. — Т. volume, вып. issue : issueTitle. — Статья articleId. — pages или pagesCount с. — (series). — (comment). — Дата обновления: 27 марта 4000. — Дата обращения: 26 февраля 3000. — ISSN issn. — ISBN isbn. — doi:doi. — PMID pmid. — PMC pmc. — S2SIC s2sic.

Параметры[править код]

Использование[править код]

Данный модуль не предназначен для использования в статьях напрямую. Использовать в статьях можно только обёртки над данным модулем.

Более подробная документация по особенностям модуля доступна в русскоязычной обёртке над ним — шаблоне {{Источник информации}}.

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

Тесты пройдены.

Примеры[править код]

Статьи в научных журналах[править код]

Источник Комментарий
Casotti G. Functional morphology of the avian medullary cone : [англ.] : [арх. 17 марта 2022] / G. Casotti, K. K. Lindberg, E. J. Braun // American journal of physiology. Regulatory, integrative and comparative physiology[d]. — 2000, 1 November. — Vol. 279, iss. 5. — P. R1722—30. — ISSN 0363-6119, 1522-1490. — doi:10.1152/ajpregu.2000.279.5.r1722. — PMID 11049855. — WD Q73118986. Статья в научном журнале, автоматическое получение полей полей
Giovanni Casotti. Functional morphology of the avian medullary cone : [англ.] : [арх. 17 марта 2022] / Giovanni Casotti, Kimberly K. Lindberg, Eldon J. Braun // American Journal of Physiology-Regulatory, Integrative and Comparative Physiology. — 2000, 1 November. — Vol. 279, iss. 5. — P. R1722–R1730. — ISSN 0363-6119. — doi:10.1152/ajpregu.2000.279.5.r1722. — PMID 11049855. Статья в научном журнале, ручное заполнение полей
Casotti G. Functional morphology of the avian medullary cone : [англ.] : [арх. 17 марта 2022] / G. Casotti, K. K. Lindberg, E. J. Braun // American journal of physiology. Regulatory, integrative and comparative physiology[d]. — 2000, 1 November. — Vol. 279, iss. 5. — P. R1722—30. — ISSN 0363-6119, 1522-1490. — doi:10.1152/ajpregu.2000.279.5.r1722. — PMID 11049855. — WD Q73118986. Статья в научном журнале, автоматическое получение полей полей, русифицированное отображение
Abdalla M. A.[d] Anatomical features in the kidney involved in water conservation through urine concentration in dromedaries (Camelus dromedarius) : [англ.] : [арх. 26 июля 2022] // Heliyon[d]. — 2020, 2 January. — Vol. 6, iss. 1. — Article e03139. — ISSN 2405-8440. — doi:10.1016/j.heliyon.2019.e03139. — PMID 31922050. — WD Q92544321. Статья в научном журнале, у которой указан PMC, автоматическое получение полей
The Pronephros; a Fresh Perspective : [англ.] : [арх. 3 марта 2022] / B. S. de Bakker, M. J. B. van den Hoff, P. D. Vize, R. J. Oostra // Integrative and Comparative Biology[d]. — 2019, 1 July. — Vol. 59, iss. 1. — P. 29—47. — ISSN 1540-7063, 1557-7023. — doi:10.1093/icb/icz001. — PMID 30649320. — WD Q91071822. Статья в научном журнале с 4 авторами, автоматическое получение полей
Systematic review or scoping review? Guidance for authors when choosing between a systematic or scoping review approach : [англ.] / Z. Munn[d], M. Peters[d], C. Stern [et al.] // BMC Medical Research Methodology[d]. — 2018, 19 November. — Vol. 18, iss. 1. — P. 143. — ISSN 1471-2288. — doi:10.1186/s12874-018-0611-x. — PMID 30453902. — WD Q59330138. Статья в научном журнале с 5 авторами, часть из которых известна в Викиданных, автоматическое получение полей
A Global Review of Food-Based Dietary Guidelines : [англ.] / Anna Herforth, Mary Arimond, Cristina Álvarez-Sánchez [et al.] // Advances in nutrition[d]. — 2019, 1 July. — Vol. 10, iss. 4. — P. 590—605. — ISSN 2161-8313, 2156-5376. — doi:10.1093/advances/nmy130. — PMID 31041447. — WD Q91658912. Статья в научном журнале с 5 авторами, часть из которых известна в Викиданных, автоматическое получение полей

Книги и их разделы[править код]

Источник Комментарий
Kriz W. Chapter 20 — Structural Organization of the Mammalian Kidney : [англ.] / W. Kriz, B. Kaissling // Seldin and Giebisch's The Kidney : Physiology and pathophysiology : in 2 vols. / Ed.: R. J. Alpern [et al.]. — Fifth edition. — Amsterdam : Academic Press, 2012, 31 December. — Vol. 1. — P. 595—691. — ISBN 978-0-12-381463-0, 978-0-12-381462-3. — WD Q114595502. Раздел книги, автоматическое заполнение части полей
Wilhelm Kriz. Chapter 20 — Structural Organization of the Mammalian Kidney : [англ.] / Wilhelm Kriz., Brigitte Kaissling // Seldin and Giebisch's The Kidney: Physiology and Pathophysiology : in 2 vols. / Ed.: Robert J. Alpern [et al.]. — Fifth Edition. — Amsterdam : Academic Press, 2012. — Vol. 1. — P. 595—691. — ISBN 978-0-12-381463-0. Раздел книги, заполнение полей вручную
Саган К. Мир, полный демонов : Наука — как свеча во тьме = The Demon-Haunted World : Science as a Candle in the Dark : Пер. с англ. / Пер.: Л. Сумм; ред.: Артур Кляницкий. — 5-е изд. — М. : Альпина нон-фикшн, 2019. — 538 с. — ISBN 978-5-91671-874-4. — WD Q114676884. Книга с явным указанием языка и года издания, автоматическое получение полей
Саган К. Мир, полный демонов : Наука — как свеча во тьме = The Demon-Haunted World : Science as a Candle in the Dark : Пер. с англ. / Пер.: Л. Сумм; ред.: Артур Кляницкий. — 5-е изд. — М. : Альпина нон-фикшн, 2019. — С. 1—100. — ISBN 978-5-91671-874-4. — WD Q114676884. Книга с заданием конкретных страниц, автоматическое получение полей
Саган К. Глава 1. Самое драгоценное // Мир, полный демонов : Наука — как свеча во тьме = The Demon-Haunted World : Пер. с англ. / К. Саган; Ред.: Артур Кляницкий. — 5-е изд. — М. : Альпина нон-фикшн, 2019. — С. 15—40. — ISBN 978-5-91671-874-4. — WD Q114676884. Глава книги, автоматическое получение полей
Sagan C. The Demon-Haunted World : Science as a Candle in the Dark : [англ.]. — NYC, 1997, 25 February. — 457 p. — ISBN 978-0-345-40946-1. — WD Q116142494. Книга на английском, автоматическое получение полей
Агеенко Ф. Л. Словарь собственных имён русского языка : [словарь]. — М. : Мир и Образование[d], 2010. — 880 с. — ISBN 978-5-94666-588-9. — WD Q114796497. Книга, у которой в Викиданных указана ссылка, автоматическое получение полей
Веракса Н. Е. Познавательное развитие в дошкольном возрасте : учебное пособие / Н. Е. Веракса, А. Н. Веракса. — М. : МОЗАИКА-СИНТЕЗ, 2012. — 336 с. — ISBN 978-5-4315-0097-8. — WD Q114831437. Книга, у которой в Викиданных указана ссылка, автоматическое получение полей
Красная книга Российской Федерации. Т. «Животные». — 2-ое издание. — М. : ФГБУ «ВНИИ Экология», 2021. — 1128 с. — (Красная книга Российской Федерации). — ISBN 978-5-6047425-0-1. — WD Q115189432. Книга, у которой том имеет название, автоматическое получение полей

Документы[править код]

Источник Комментарий
ГОСТ Р 7.0.5—2008 «Библиографическая ссылка. Общие требования и правила составления». — 2008, 28 апреля. — (Система стандартов по информации, библиотечному и издательскому делу). — WD Q19147272. ГОСТ, автоматическое получение полей

Статьи через идентификаторы в рамках издания[править код]

Источник Комментарий
The Editors of Encyclopaedia Britannica. Nephron : [англ.] : [арх. 2 июня 2022] // Encyclopædia Britannica : online encyclopedia. — Дата обновления: 22 августа 2022. — Дата обращения: 23 октября 2022. Автоматическое заполнение полей по идентификаторам из викиданных
Mars Atmosphere Density Model : [англ.] : [арх. 9 апреля 2022] // Planetary Image Archive[d]. — JPL, 2022, 5 April. — Дата обращения: 23 октября 2022. Автоматическое заполнение полей по идентификаторам из викиданных
Почки : [арх. 4 октября 2022] // Большая российская энциклопедия. Электронная версия. — Большая российская энциклопедия, 2016, 1 апреля. Статья без автора, автоматическое заполнение полей по идентификаторам из викиданных
Нефрон // Большая российская энциклопедия. Электронная версия. — Большая российская энциклопедия, 2016, 1 апреля. — WD Q114716001. Статья, у которой значится автор, автоматическое заполнение полей по идентификаторам из викиданных
Нефрон // Большая российская энциклопедия : в 35 т. / Гл. ред.: Ю. С. Осипов. — М. : Большая российская энциклопедия. — WD Q1768199. Статья из печатной БРЭ, у которой значится автор, автоматическое заполнение полей по идентификаторам из викиданных
Антарктида // Большая российская энциклопедия. Электронная версия. — Большая российская энциклопедия, 2016, 1 апреля. — WD Q114716001. Статья, у которой значатся несколько авторов, автоматическое заполнение полей по идентификаторам из викиданных

Крупные издания[править код]

Источник Комментарий
Большая российская энциклопедия. Электронная версия. — Большая российская энциклопедия, 2016, 1 апреля. — WD Q114716001. БРЭ, электронная версия
Большая российская энциклопедия : в 35 т. / Гл. ред.: Ю. С. Осипов. — М. : Большая российская энциклопедия, 2004—2017. — WD Q1768199. БРЭ, печатная версия

Видеоматериалы[править код]

Источник Комментарий
Зинкевич А. О. Лекция 1. Вводная лекция [видеозапись] : Лекция из курса // Python и облачные вычисления в науке. — МГУ. Лекция, ручное задание полей
Деятельность ВОЗ по научно-техническому анализу и прогнозированию в области здравоохранения [видеозапись] // YouTube. — ВОЗ, 2022, 8 июля. — Дата обращения: 22 ноября 2022. Видео на youtube, автоматическое получение данных по идентификаторам издателя и площадки

Аудиоматериалы[править код]

Источник Комментарий
Imagine : [англ.] // Imagine : studio album. — USA; UK, 1971, 9 September. — WD Q66423099. Песня, автоматическое получение полей из Викиданных

Разработка[править код]

См. также[править код]

  • WDFormat — модуль форматирования информации из Викиданных согласно задаваемым профилям.
  • WDSource — подмодуль получения информации об источнике из соответствующего элемента Викиданных.
  • Sources — выполняющий схожие c CiteGost функции модуль, на котором основан шаблон {{source}}.
require('strict')

local p = {}

--: https://www.mediawiki.org/wiki/Extension_default_namespaces
local NS_MAIN = 0
local NS_PROJECT = 4
local NS_TEMPLATE = 10
local NS_MODULE = 828
local moduleNamespace = mw.site.namespaces[NS_MODULE].name

local source = require(moduleNamespace .. ':CiteGost/WDSource')
local formatter = require(moduleNamespace .. ':WDFormat')
local f = formatter.f
local wdLang = require(moduleNamespace .. ':WDLang')

local wikidata = require(moduleNamespace .. ':WDCommon')

local i18n = require(moduleNamespace .. ':I18n')

local l10n = i18n.load()

local currentLangObj = mw.getContentLanguage()
local currentLang = currentLangObj:getCode()
local stylesUsed = false

local function commonLangVisible(source, fieldName)
	if not source[fieldName] then
		return false
	end

	if table.getn(source[fieldName]) > 0 then
		for _, lang in ipairs(source[fieldName]) do
			if lang.components and currentLang == lang.components.langCode.value then
				return false
			end
		end
	else
		if source[fieldName].components and currentLang == source[fieldName].components.langCode.value then
			return false
		end
	end
	return true
end

local function langVisible(source)
	return commonLangVisible(source, 'lang')
end

local function mainAuthorVisible(source)
	if not source.authors then
		return false
	end
	if table.getn(source.authors) == 0 or table.getn(source.authors) > 3 then
		return false
	end
	return true
end

local function langPropHintVisible(source)
	if table.getn(source.lang) > 1 then
		if source.lang == source.publishedInLang then
			return true
		end
	end
	return false
end

local function mainPublishedInAuthorVisible(source)
	if not source.publishedInAuthors then
		return false
	end
	if table.getn(source.publishedInAuthors) == 0 or table.getn(source.publishedInAuthors) > 3 then
		return false
	end
	return true
end

local function authorsVisible(source)
	if not source.authors then
		return false
	end
	if table.getn(source.authors) == 1 then
		return false
	end
	return true
end

local function originLangVisible(source)
	if not source.originLang then
		return false
	end
	if source.lang.entity == source.originLang.entity then
		return false
	end
	return source.originLang.entity ~= nil
end

local function publishedInOriginLangVisible(source)
	if not source.publishedInOriginLang then
		return false
	end
	if source.lang.entity == source.publishedInOriginLang.entity then
		return false
	end
	return source.publishedInOriginLang.entity ~= nil
end

local function publisherVisible(source)
	if source.publishedInIsPeriodic and not source.workVersion then
		if source.publisher.retrieved then
			return false
		end
	end
	return true
end

local function locationVisible(source)
	if source.publishedInIsPeriodic and not source.workVersion then
		if source.location.retrieved then
			return false
		end
	end
	return true
end

local function ruwikiAnchor(source)
	if not source.ref then
		return nil
	end

	local anchor = 'CITEREF' .. source.ref.value
    local date = source.date
    if date and date.value and date.value.year then
        anchor = anchor .. date.value.year
    end
    return anchor
end

local function formatTitle(source, processedData, result)
	if processedData.fieldTable.fromLabel then
		if source.workVersion then
			result.text = result.text .. '?'
			result.wikitext = result.wikitext .. '<sup>[[d:Property:P1476|P1476?]]</sup>'
		end
	end
end

local function formatUrlStatus(source, processedData, result)
	if not result.linked then
		return
	end
	local fieldTable = processedData.fieldTable
	if not fieldTable.components then
		return
	end
	local statusTable = fieldTable.components.urlStatus
	if not statusTable then
		return
	end
	
	local classesMap = {
		['Q1193907'] = 'cite-dead-url',
		['Q910845'] = 'cite-paywall-url',
		['Q232932'] = 'cite-open-access-url',
		['Q24707952'] = 'cite-free-access-url',
		['Q107459441'] = 'cite-reg-required-url',
	}
	local statusClass = classesMap[statusTable.entity]
	if not statusClass then
		mw.log('Unknown url status:')
		mw.logObject(statusTable)
		return
	end
	stylesUsed = true
	
	local spanTitle
	if statusTable.value then
		spanTitle = mw.ustring.gsub(statusTable.value, '^%l', mw.ustring.upper)
	else
		spanTitle = ''
	end

	result.wikitext = '<span class="' .. statusClass .. '" title="' .. spanTitle .. '">' .. result.wikitext .. '</span>'
end

local function formatTitleUrlStatus(source, processedData, result)
	if not result.linked then
		return
	end
	formatUrlStatus(source, { fieldTable = source.url }, result)
end

local function formatPublishedInUrlStatus(source, processedData, result)
	if not result.linked then
		return
	end
	formatUrlStatus(source, { fieldTable = source.publishedInUrl }, result)
end

local function formatPublishedInTitle(source, processedData, result)
	if processedData.fieldTable.fromLabel then
		if source.publishedInVersion and not source.publishedInIsHosting then
			result.text = result.text .. '?'
			result.wikitext = result.wikitext .. '<sup>[[d:Property:P1476|P1476?]]</sup>'
		end
	end
end

local function formatArchiveUrl(source, processedData, result)
	local t = processedData.fieldTable
	local archiveUrl = t.components.archiveUrl
	if type(archiveUrl) == 'table' then
		archiveUrl = archiveUrl.value
	end
	local archiveDate = t.components.archiveDate
	if type(archiveDate) == 'table' and archiveDate.value then
		archiveDate = archiveDate.value
	end

	local langObj = mw.getLanguage(processedData.langCode)
	local dateStr = langObj:formatDate('j xg Y', archiveDate.timestamp)

	result.text = 'арх. ' .. dateStr
	result.wikitext = '[' .. archiveUrl .. ' <abbr title="' .. l10n('messages', 'archive-date-hint') .. '">' .. l10n('messages', 'archive-abbreviated') .. '</abbr>]&nbsp;' .. dateStr
end

local function formatDateMonth(source, processedData, result, state)
	local langObj
	if processedData.langCode then
		langObj = mw.getLanguage(processedData.langCode)
	else
		langObj = currentLangObj
	end

	local dateStr
	if source.date.value.day then
		dateStr = langObj:formatDate('j xg', source.date.value.timestamp)
	else
		-- Without this hack formatDate() gives previous month for the 00 day
		local timestamp = string.gsub(source.date.value.timestamp, '-00T00:00:00Z', '-01T00:00:00Z')
		dateStr = langObj:formatDate('F', timestamp)
		if (processedData.capitalize and state.groupEmpty) or processedData.forceCapitalize then
			dateStr = mw.ustring.gsub(dateStr, '^%l', mw.ustring.upper)
		end
	end
	result.text = dateStr
	result.wikitext = '<span class="nowrap">' .. dateStr .. '</span>'
end

local function formatPartsCount(source, processedData, result, state)
	local text
	local prefixByLang = {
		ru = 'в',
		uk = 'у',
		en = 'in',
		be = 'у',
		de = 'in',
		fr = 'en',
		el = 'σε',
		es = 'en',
		ar = 'في',
		no = 'i',
	}

	local prefix = prefixByLang[processedData.langCode]
	if not prefix then
		prefix = ''
	else
		prefix = prefix .. '&nbsp;'
	end
	text = prefix .. tostring(result.text)

	if (processedData.capitalize and state.groupEmpty) or processedData.forceCapitalize then
		text = mw.ustring.gsub(text, '^%l', mw.ustring.upper)
	end

	result.text = text
	result.wikitext = text
end

local function formatMainAuthorInitialsToEnd(source, processedData, result)
	local text = result.text
	local initials, familyName = mw.ustring.match(text, '^(.+%.) ([^%. ]+)$')
	if not initials or not familyName then
		return false
	end
	text = familyName .. '&nbsp;' .. initials
	result.text = text
	result.wikitext = text
end

local function formatTranslatedFrom(source, processedData, result)
	local abbrByLang = {
		el = 'Μετάφρ. από τα',
		ru = 'Пер.&nbsp;с',
		uk = 'Пер.&nbsp;з',
		en = 'Transl.&nbsp;from',
		de = 'Übers. aus dem',
	}
	local supported = abbrByLang[processedData.langCode]
	if supported then
		result.text = supported
		result.wikitext = result.text
	else
		result.text = result.text .. ':'
		result.wikitext = result.text
	end
end

local function formatEditorsLead(source, processedData, result, state)
	local abbrByLang = {
		el = 'επιμ.',
		en = 'ed.',
		es = 'ed.',
		de = 'hrsg.',
		uk = 'ред.',
		no = 'hrsg.',
	}
	local text = abbrByLang[processedData.langCode]
	if (text and state.groupEmpty and processedData.capitalize) or processedData.forceCapitalize then
		text = mw.ustring.gsub(text, '^%l', mw.ustring.upper)
	end
	if text then
		result.text = text
		result.wikitext = text
	end
end

local function formatEditorsInChiefLead(source, processedData, result, state)
	local abbrByLang = {
		el = 'αρχισυντ.',
		en = 'ed.&nbsp;in&nbsp;ch.',
		es = 'ed.&nbsp;jefe',
		de = 'ch.-Red.',
		uk = 'гол.&nbsp;ред.',
		no = 'ch.-Red.',
	}
	local text = abbrByLang[processedData.langCode]
	if (text and state.groupEmpty and processedData.capitalize) or processedData.forceCapitalize then
		text = mw.ustring.gsub(text, '^%l', mw.ustring.upper)
	end
	if text then
		result.text = text
		result.wikitext = text
	end
end

local function formatTranslatorsLead(source, processedData, result, state)
	local abbrByLang = {
		el = 'μεταφρ.',
		en = 'transl.',
		es = 'trad.',
		de = 'übers.',
	}
	local text = abbrByLang[processedData.langCode]
	if (text and state.groupEmpty and processedData.capitalize) or processedData.forceCapitalize then
		text = mw.ustring.gsub(text, '^%l', mw.ustring.upper)
	end
	if text then
		result.text = text
		result.wikitext = text
	end
end

local function formatIllustratorsLead(source, processedData, result, state)
	local abbrByLang = {
		el = 'εικον.',
		en = 'illus.',
	}
	local text = abbrByLang[processedData.langCode]
	if (text and state.groupEmpty and processedData.capitalize) or processedData.forceCapitalize then
		text = mw.ustring.gsub(text, '^%l', mw.ustring.upper)
	end
	if text then
		result.text = text
		result.wikitext = text
	end
end

local function formatPublishedInLink(source, processedData, result)
	if result.linked then
		return
	end

	if source.publishedInUrl and source.publishedInUrl.retrieved and mw.wikibase.getSitelink(processedData.fieldTable.entity) then
		return
	end
	f.link(source, processedData, result)
end

local function formatNonNumber(source, processedData, result)
	if string.match(result.text, '^[%d%-]+$') then
		f.numericalRanges(source, processedData, result)
		return
	elseif string.match(result.text, '^%d[%.%-%s]') then
		f.dash(source, processedData, result)
		return
	end

	if processedData.langCode == 'ru' then
		result.text = '«' .. result.text .. '»'
		result.wikitext = result.text
	end
end

local function replaceAuthors(source, processedData, others)
	local othersText
	local delimiter
	if processedData.langCode == 'ru' then
		othersText = '[и др.]'
		delimiter = ' '
	else
		-- the delimiter would probably be language-dependent
		othersText = '[' .. wikidata.abbr('Q311624', processedData.langCode) .. ']'
		delimiter = ' '
	end
	others = string.gsub(others, '%[%[d:([^%]]+)|[^%]]+%]%]', '(%1)')
	others = string.gsub(others, '<[^>]+>', '')
	local othersWikitext = delimiter .. '<abbr title="' .. others .. '">' .. othersText .. '</abbr>'
	return othersText, othersWikitext
end

local function replaceExceeded(source, processedData, others)
	local othersWikitext = ' <abbr title="' .. others .. '">' .. l10n('messages', 'et-al') .. '</abbr>'
	return l10n('messages', 'et-al'), othersWikitext
end

local function localTitleDelimiter(source)
	if source.issue then
		return ' :&nbsp;'
	else
		return '&nbsp;'
	end
end

local GostProfile = {
	tag = {
		name = 'span',
		classes = { 'citation' },
		attr = {
			-- ref
			id = ruwikiAnchor,
		},
	},
	groups = {
		{
			{
				field = { 'authors', 1 },
				cond = mainAuthorVisible,
				format = { formatMainAuthorInitialsToEnd, f.personReversedNoComma, f.wikilink, f.wikidata },
				tag = {
					name = 'i',
				},
			},
			{
				conflicts = { 'authors', 'publishedInIsHosting' },
				field = { 'publishedInAuthors', 1 },
				cond = mainPublishedInAuthorVisible,
				format = { formatMainAuthorInitialsToEnd, f.personReversedNoComma, f.wikilink, f.wikidata },
				tag = {
					name = 'i',
				},
			},
			{
				delimiter = ' ',
				ensureEnds = '.',
				field = 'title',
				urlField = 'url',
				capitalize = true,
				format = { f.dash, f.link, formatTitleUrlStatus, f.wikisource, f.wikiversity, f.wikibooks, f.resolveWikilink, formatTitle },
			},
			{
				field = 'titleIsMissing',
				delimiter = ' ',
				ensureEnds = '.',
				capitalize = true,
				prefix='(',
				tag = {
					name = 'span',
					classes = { 'error' },
				},
				suffix = ')',
			},
			{
				delimiter = ' :&nbsp;',
				field = 'subtitle',
				format = { f.dash },
			},
			{
				tag = {
					name = 'sup',
					classes = { 'error' },
					attr = {
						title = l10n('errors', 'missing-lang-explanation'),
					},
				},
				field = 'langIsMissing',
				delimiter = '',
			},
			{
				delimiter = ' ',
				field = 'contentType',
				format = { f.squareBrackets },
			},
			{
				delimiter = ' = ',
				cond = originLangVisible,
				field = 'origin',
				urlField = 'originUrl',
				format = { f.dash, f.link, f.resolveWikilink },
			},
			{
				delimiter = ' :&nbsp;',
				depends = 'origin',
				cond = originLangVisible,
				field = 'originSubtitle',
				format = { f.dash },
			},
		},
		{
			childDelimiter = ' :&nbsp;',
			{
				field = 'lang',
				prefix = '&lsqb;',
				cond = langVisible,
				forceLang = 'default',
				capitalize = false,
				limits = {
					max = 2,
					cutTo = 2,
					replaceBy = replaceExceeded,
				},
				format = { f.abbrWithHint },
				suffix = '&rsqb;',
			},
			{
				cond = langPropHintVisible,
				delimiter = '',
				value = '<sup>[[d:Property:P407|P407?]]</sup>',
			},
			{
				depends = {
					{ isPath=true, 'url', 'archiveUrl'},
					{ isPath=true, 'url', 'archiveDate'},
				},
				field = 'url',
				forceLang = 'default',
				format = { formatArchiveUrl, f.squareBrackets },
			},
			{
				delimiter = ' ',
				prefix = '(',
				tag = {
					name = 'span',
					classes = { 'error' },
				},
				field = 'wrongArchiveDateFormat',
				itemsDelimiter = ' ',
				suffix = ')'
			},
			{
				conflicts = 'isVolume',
				delimiter = ' : ',
				field = 'partsCount',
				capitalize = false,
				format = { f.quantity, formatPartsCount },
			},
		},
		{
			{
				depends = 'isVolume',
				{
					ensureEnds = '.',
					delimiter = ' ',
					field = 'partsCount',
					format = { f.quantity, formatPartsCount },
				},
				{
					ensureEnds = '.',
					delimiter = ' ',
					depends = 'volume',
					entity = 'Q1238720',
					capitalize = true,
					format = { f.abbr },
				},
				{
					delimiter = '&nbsp;',
					field = 'volume',
					format = { formatNonNumber },
				},
				{
					delimiter = ' :&nbsp;',
					ensureEnds = '.',
					delimiter = ' ',
					depends = 'volume',
					field = 'volumeTitle',
					format = { f.dash },
				},
				{
					ensureEnds = '.',
					delimiter = ' ',
					conflicts = 'volume',
					depends = 'volumeTitle',
					entity = 'Q1238720',
					capitalize = true,
					format = { f.abbr },
				},
				{
					ensureEnds = '.',
					delimiter = '&nbsp;',
					conflicts = 'volume',
					field = 'volumeTitle',
					format = { formatNonNumber },
				},
			},
			{
				depends = 'isIssue',
				{
					ensureEnds = '.',
					delimiter = ' ',
					field = 'partsCount',
					format = { f.quantity, formatPartsCount },
				},
				{
					ensureEnds = '.',
					delimiter = ' ',
					depends = 'issue',
					entity = 'Q28869365',
					capitalize = true,
					format = { f.abbr },
				},
				{
					delimiter = '&nbsp;',
					field = 'issue',
					format = { formatNonNumber },
				},
				{
					delimiter = ' :&nbsp;',
					ensureEnds = '.',
					delimiter = ' ',
					depends = 'issue',
					field = 'issueTitle',
					format = { f.dash },
				},
				{
					ensureEnds = '.',
					delimiter = ' ',
					conflicts = 'issue',
					depends = 'issueTitle',
					entity = 'Q28869365',
					capitalize = true,
					format = { f.abbr },
				},
				{
					ensureEnds = '.',
					delimiter = '&nbsp;',
					conflicts = 'issue',
					field = 'issueTitle',
					format = { formatNonNumber },
				},
			},
			{
				delimiter = ' :&nbsp;',
				field = 'workType',
				capitalize = false,
			},
			{
				delimiter = ' :&nbsp;',
				field = 'info',
				format = { f.dash },
				capitalize = false,
			},
			{
				delimiter = ' :&nbsp;',
				prefix = '&lbrack;',
				field = 'detectedInfo',
				forceLang = 'default',
				format = { f.dash },
				suffix = '&rbrack;',
				capitalize = false,
			},
			{
				depends = 'originLang',
				cond = originLangVisible,
				delimiter = ' :&nbsp;',
				entity = 'Q7553',
				format = { f.abbr, formatTranslatedFrom },
			},
			{
				delimiter = ' ',
				cond = originLangVisible,
				field = 'originLang',
				format = { f.abbrWithHint },
			},
		},
		{
			delimiter = ' /&nbsp;',
			{
				field = 'authors',
				cond = authorsVisible,
				limits = {
					max = 4,
					cutTo = 3,
					replaceBy = replaceAuthors,
				},
				format = { f.person, f.wikilink, f.wikidata },
			},
			{
				depends = 'illustrators',
				delimiter = '; ',
				entity = 'Q644687',
				format = { f.abbr, formatIllustratorsLead },
			},
			{
				delimiter = ': ',
				field = 'illustrators',
				limits = {
					max = 2,
					cutTo = 1,
					replaceBy = replaceAuthors,
				},
				format = { f.person, f.wikilink, f.wikidata },
			},
			{
				depends = 'translators',
				delimiter = '; ',
				entity = 'Q333634',
				format = { f.abbr, formatTranslatorsLead },
			},
			{
				delimiter = ': ',
				field = 'translators',
				limits = {
					max = 2,
					cutTo = 1,
					replaceBy = replaceAuthors,
				},
				format = { f.person, f.wikilink, f.wikidata },
			},
			{
				depends = 'editorInChief',
				delimiter = '; ',
				entity = 'Q589298',
				format = { f.abbr, formatEditorsInChiefLead },
			},
			{
				delimiter = ': ',
				field = 'editorInChief',
				limits = {
					max = 2,
					cutTo = 1,
					replaceBy = replaceAuthors,
				},
				format = { f.person, f.wikilink, f.wikidata },
			},
			{
				depends = 'editors',
				delimiter = '; ',
				entity = 'Q1607826',
				format = { f.abbr, formatEditorsLead },
			},
			{
				delimiter = ': ',
				field = 'editors',
				limits = {
					max = 2,
					cutTo = 1,
					replaceBy = replaceAuthors,
				},
				format = { f.person, f.wikilink, f.wikidata },
			},
		},
		{
			delimiter = ' //&nbsp;',
			conflicts = { 'isVolume', 'isIssue' },
			{
				field = 'publishedIn',
				urlField = 'publishedInUrl',
				capitalize = true,
				format = { f.dash, formatPublishedInLink, formatPublishedInUrlStatus, f.wikisource, f.wikiversity, f.wikibooks, f.resolveWikilink, f.wikidata, formatPublishedInTitle },
			},
			{
				field = 'publishedInTitleIsMissing',
				delimiter = ' ',
				ensureEnds = '.',
				capitalize = true,
				prefix='(',
				tag = {
					name = 'span',
					classes = { 'error' },
				},
				suffix = ')',
			},
			{
				delimiter = ' :&nbsp;',
				field = 'publishedInSubtitle',
				format = { f.dash },
			},
			{
				delimiter = ' = ',
				cond = publishedInOriginLangVisible,
				field = 'publishedInOrigin',
				format = { f.dash, f.resolveWikilink },
			},
			{
				depends = 'publishedInOriginLang',
				cond = publishedInOriginLangVisible,
				delimiter = ' :&nbsp;',
				entity = 'Q7553',
				format = { f.abbr, formatTranslatedFrom },
			},
			{
				delimiter = ' :&nbsp;',
				field = 'publishedInPartsCount',
				capitalize = false,
				format = { f.quantity, formatPartsCount },
			},
			{
				delimiter = ' ',
				cond = publishedInOriginLangVisible,
				field = 'publishedInOriginLang',
				format = { f.abbrWithHint },
			},
			{
				delimiter = ' ',
				depends = {
					{ isPath=true, 'publishedInUrl', 'archiveUrl'},
					{ isPath=true, 'publishedInUrl', 'archiveDate'},
				},
				field = 'publishedInUrl',
				forceLang = 'default',
				format = { formatArchiveUrl, f.squareBrackets },
			},
			{
				delimiter = ' ',
				prefix = '(',
				tag = {
					name = 'span',
					classes = { 'error' },
				},
				field = 'wrongPublishedInArchiveDateFormat',
				itemsDelimiter = ' ',
				suffix = ')'
			},
			{
				conflicts = 'isArticle',
				delimiter = ' :&nbsp;',
				field = 'publishedInWorkType',
				capitalize = false,
			},
		},
		{
			delimiter = ' /&nbsp;',
			{
				conflicts = { 'isVolume' },
				{
					conflicts = { 'isArticle', 'publishedInIsPeriodic' },
					depends = 'publishedInEditionType',
					field = 'publishedInAuthors',
					limits = {
						max = 4,
						cutTo = 3,
						replaceBy = replaceAuthors,
					},
					format = { f.person, f.wikilink, f.wikidata },
				},
				{
					depends = { 'publishedInIllustrators', 'publishedInIsPeriodic' },
					delimiter = '; ',
					entity = 'Q644687',
					format = { f.abbr, formatIllustratorsLead },
				},
				{
					delimiter = ': ',
					field = { 'publishedInIllustrators', 'publishedInIsPeriodic' },
					limits = {
						max = 2,
						cutTo = 1,
						replaceBy = replaceAuthors,
					},
					format = { f.person, f.wikilink, f.wikidata },
				},
				{
					depends = { 'publishedInTranslators', 'publishedInIsPeriodic' },
					delimiter = '; ',
					entity = 'Q333634',
					format = { f.abbr, formatTranslatorsLead },
				},
				{
					delimiter = ': ',
					field = { 'publishedInTranslators', 'publishedInIsPeriodic' },
					limits = {
						max = 2,
						cutTo = 1,
						replaceBy = replaceAuthors,
					},
					format = { f.person, f.wikilink, f.wikidata },
				},
			},
			{
				conflicts = { 'isVolume', 'isArticle', 'publishedInIsPeriodic', 'publishedInIsHosting' },
				{
					depends = { 'publishedInEditorInChief' },
					delimiter = '; ',
					entity = 'Q589298',
					format = { f.abbr, formatEditorsInChiefLead },
				},
				{
					delimiter = ': ',
					field = 'publishedInEditorInChief',
					limits = {
						max = 2,
						cutTo = 1,
						replaceBy = replaceAuthors,
					},
					format = { f.person, f.wikilink, f.wikidata },
				},
				{
					depends = { 'publishedInEditors' },
					delimiter = '; ',
					entity = 'Q1607826',
					format = { f.abbr, formatEditorsLead },
				},
				{
					delimiter = ': ',
					field = 'publishedInEditors',
					limits = {
						max = 2,
						cutTo = 1,
						replaceBy = replaceAuthors,
					},
					format = { f.person, f.wikilink, f.wikidata },
				},
			},
		},
		{
			delimiter = ' — ',
			ensureEnds = '.',
			field = 'edition',
		},
		{
			delimiter = ' — ',
			ensureEnds = '.',
			{
				conflicts = 'isArticle',
				cond = locationVisible,
				field = 'location',
				itemsDelimiter = '; ',
				format = { f.abbr, f.wikilink, f.wikidata },
			},
			{
				conflicts = 'isArticle',
				cond = publisherVisible,
				field = 'publisher',
				delimiter = ' :&nbsp;',
				format = { f.short, f.wikilink, f.wikidata },
			},
			{
				field = { 'date', sub='year' },
				delimiter = ', ',
			},
			{
				delimiter = ', ',
				field = {'date', sub = 'month'},
				format = { formatDateMonth },
			},
			{
				field = { 'startDate', sub='year' },
				delimiter = ', ',
			},
			{
				delimiter = '—',
				field = { 'endDate', sub='year' },
			},
			{
				delimiter = ', ',
				prefix = '(',
				tag = {
					name = 'span',
					classes = { 'error' },
				},
				field = 'wrongDateFormat',
				itemsDelimiter = ' ',
				suffix = ')'
			},
		},
		{
			delimiter = ' — ',
			ensureEnds = '.',
			{
				conflicts = 'isVolume',
				{
					depends = { {'volume', --[[ or --]] 'volumeTitle'} },
					entity = 'Q1238720',
					format = { f.abbr },
				},
				{
					delimiter = '&nbsp;',
					field = 'volume',
					format = { f.numericalRanges },
				},
				{
					delimiter = localTitleDelimiter,
					field = 'volumeTitle',
				},
			},
			{
				conflicts = 'isIssue',
				passthrough = true,
				{
					depends = { {'issue', --[[ or --]] 'issueTitle'} },
					delimiter = ', ',
					entity = 'Q28869365',
					format = { f.abbr },
				},
				{
					delimiter = '&nbsp;',
					field = 'issue',
				},
				{
					delimiter = localTitleDelimiter,
					field = 'issueTitle',
				},
			},
		},
		{
			delimiter = ' — ',
			ensureEnds = '.',
			childDelimiter = ' — ',
			childEnsureEnds = '.',
			{
				depends = 'articleId',
				entity = 'Q191067',
				format = { f.abbr },
			},
			{
				delimiter = ' ',
				field = 'articleId',
			},
			{
				depends = 'pages',
				entity = 'Q1069725',
				format = { f.unit },
			},
			{
				delimiter = '&nbsp;',
				field = 'pages',
				format = { f.numericalRanges },
			},
			{
				conflicts = 'pages',
				field = 'pagesCount',
				format = { f.quantity },
			},
		},
		{
			delimiter = ' — ',
			ensureEnds = '.',
			prefix = '(',
			{
				field = 'series',
				format = { f.dash },
			},
			{
				ensureEnds = ';',
				delimiter = ' ',
				field = 'seriesIssue',
			},
			suffix = ')',
		},
		{
			delimiter = ' — ',
			ensureEnds = '.',
			prefix = '(',
			field = 'comment',
			suffix = ')',
		},
		{
			delimiter = ' — ',
			ensureEnds = '.',
			childDelimiter = ' — ',
			childEnsureEnds = '.',
			{
				depends = 'dedicatedTo',
				suffix = ':',
				entity = 'P825',
			},
			{
				delimiter = ' ',
				field = 'dedicatedTo',
				format = { f.person, f.wikilink },
			},
			{
				depends = 'lastUpdate',
				value = l10n('messages', 'last-update'),
			},
			{
				delimiter = ': ',
				field = 'lastUpdate',
				forceLang = currentLang,
				format = { f.date },
			},
			{
				delimiter = ' ',
				prefix = '(',
				tag = {
					name = 'span',
					classes = { 'error' },
				},
				field = 'wrongLastUpdateFormat',
				itemsDelimiter = ' ',
				suffix = ')'
			},
			{
				depends = 'accessDate',
				value = l10n('messages', 'access-date'),
			},
			{
				delimiter = ': ',
				field = 'accessDate',
				forceLang = currentLang,
				format = { f.date },
			},
			{
				delimiter = ' ',
				prefix = '(',
				tag = {
					name = 'span',
					classes = { 'error' },
				},
				field = 'wrongAccessDateFormat',
				itemsDelimiter = ' ',
				suffix = ')'
			},
		},
		{
			delimiter = ' — ',
			ensureEnds = '.',
			childDelimiter = ' — ',
			childEnsureEnds = '.',
			{
				depends = 'issn',
				entity = 'Q131276',
				format = { f.abbr , f.wikilink },
			},
			{
				delimiter = '&nbsp;',
				field = 'issn',
				urlMaskProp = 'P236',
				format = { f.link },
			},
			{
				depends = 'isbn',
				entity = 'Q33057',
				value = 'ISBN',
				format = { f.wikilink },
			},
			{
				delimiter = '&nbsp;',
				field = 'isbn',
				wikilinkMask = l10n('messages', 'wikilink-mask'),
				format = { f.wikilink },
			},
			{
				depends = 'doi',
				entity = 'Q25670',
				value = 'doi',
				capitalize = false,
				format = { f.wikilink },
			},
			{
				delimiter = ':',
				field = 'doi',
				urlMaskProp = 'P356',
				format = { f.lowercase, f.link, formatUrlStatus },
			},
			{
				conflicts = { 'isbn', 'pmid' },
				depends = 'oclc',
				entity = 'Q190593',
				value = 'OCLC',
				format = { f.wikilink },
			},
			{
				conflicts = { 'isbn', 'pmid' },
				delimiter = '&nbsp;',
				field = 'oclc',
				urlMaskProp = 'P243',
				format = { f.link },
			},
			{
				depends = 'pmid',
				entity = 'Q2082879',
				value = 'PMID',
				format = { f.wikilink },
			},
			{
				delimiter = '&nbsp;',
				field = 'pmid',
				urlMaskProp = 'P698',
				format = { f.link },
			},
			{
				depends = 'pmc',
				conflicts = 'workVersion',
				entity = 'Q229883',
				value = 'PMC',
				format = { f.wikilink },
			},
			{
				conflicts = 'workVersion',
				delimiter = '&nbsp;',
				field = 'pmc',
				urlMaskProp = 'P932',
				format = { f.link },
			},
			{
				depends = 's2sic',
				conflicts = 'workVersion',
				entity = 'Q22908627',
				value = 'S2SIC',
				format = { f.wikilink },
			},
			{
				conflicts = 'workVersion',
				delimiter = '&nbsp;',
				field = 's2sic',
				urlMaskProp = 'P8299',
				format = { f.link },
			},
			{
				childDelimiter = ' — ',
				childEnsureEnds = '.',
				{
					entity = 'Q2013',
					value = 'WD',
					isStatic = true,
					format = { f.wikilink },
				},
				{
					delimiter = '&nbsp;',
					field = 'workVersion',
					format = { f.entity, f.forceWikidataLink },
				},
				{
					conflicts = { 'workVersion' },
					delimiter = '&nbsp;',
					field = 'issueVersion',
					format = { f.entity, f.forceWikidataLink },
				},
				{
					conflicts = { 'workVersion', 'issueVersion' },
					delimiter = '&nbsp;',
					field = 'volumeVersion',
					format = { f.entity, f.forceWikidataLink },
				},
				{
					conflicts = { 'workVersion', 'issueVersion', 'volumeVersion', 'isArticle', 'publishedInIsPeriodic', 'publishedInIsHosting', 'id' },
					delimiter = '&nbsp;',
					field = 'publishedInVersion',
					format = { f.entity, f.forceWikidataLink },
				},
			},
		},
		{
			ensureEnds = '.',
			delimiter = ' — ',
			depends = 'unknownFields',
			tag = {
				name = 'span',
				classes = { 'error' },
			},
			prefix = '(',
			{
				value = l10n('errors', 'unknown-template-args')
			},
			{
				delimiter = ' ',
				field = 'unknownFields',
				format = { f.nowiki },
			},
			suffix = ')'
		},
		{
			ensureEnds = '.',
			delimiter = ' — ',
			prefix = '(',
			tag = {
				name = 'span',
				classes = { 'error' },
			},
			field = 'urlIsArchive',
			itemsDelimiter = ' ',
			suffix = ')'
		},
		{
			ensureEnds = '.',
			delimiter = ' — ',
			prefix = '(',
			tag = {
				name = 'span',
				classes = { 'error' },
			},
			field = 'archiveUrlWithoutUrl',
			itemsDelimiter = ' ',
			suffix = ')'
		},
		{
			ensureEnds = '.',
			delimiter = ' — ',
			prefix = '(',
			tag = {
				name = 'span',
				classes = { 'error' },
			},
			field = 'archiveUrlWithoutArchiveDate',
			itemsDelimiter = ' ',
			suffix = ')'
		},
		{
			ensureEnds = '.',
			delimiter = ' — ',
			prefix = '(',
			tag = {
				name = 'span',
				classes = { 'error' },
			},
			field = 'idWithoutPublishedIn',
			itemsDelimiter = ' ',
			suffix = ')'
		},
	},
	ensureEnds = '.',
}

local function argIsEmpty(s)
	return (not s or s == '')
end

local function argIsSet(s)
	return (s and s ~= '')
end

local function strToArray(str, t)
	if type(str) == 'table' then
		if next(str) == nil then
			return t
		end
		return str
	end

	local array = t
	for item in str:gmatch('[^,]+') do
		item = item:gsub('^%s+', ''):gsub('%s+$', '')
		table.insert(array, { value=item })
	end
	return array
end

local function strToDate(str, t)
	local year, month, day = str:match('^(%d+)-(%d+)-(%d+)$')
	if not year then
		year, month = str:match('^(%d+)-(%d+)$')
		if not year then
			year = str:match('^(%d+)$')
		end
	end
	if not year then
		t.erroneous = true
		return t
	end

	year = tonumber(year)
	if month then
		month = tonumber(month)
		if month == 0 then
			month = nil
		end
	end
	if day then
		day = tonumber(day)
		if day == 0 then
			day = nil
		end
	end

	local timestamp
	if day then
		timestamp = string.format("%04d-%02d-%02d", year, month, day)
	elseif month then
		timestamp = string.format("%04d-%02d", year, month)
	else
		timestamp = string.format("%04d", year)
	end
	
	t.value = { year=year, month=month, day=day, timestamp=timestamp }
	return t
end

local function strToDateToParentTable(value, parentTable, name)
	if not parentTable.components then
		parentTable.components = {}
	end

	parentTable.components[name] = strToDate(value, {})
	return parentTable
end


local function waybackStampToArchiveUrl(stamp, url)
	if argIsEmpty(stamp) then
		return nil
	end

	return { value='https://web.archive.org/web/' .. stamp .. '/' .. url.value }
end

local function waybackStampToArchiveDate(stamp)
	if argIsEmpty(stamp) then
		return nil
	end

	local year, month, day = string.match(stamp, '(%d%d%d%d)(%d%d)(%d%d)')
	local timestamp = string.format("%04d-%02d-%02d", year, month, day)
	return { value={ year=year, month=month, day=day, timestamp=timestamp } }
end

local function volumesCountToPartsCount(volumesCount, t)
	t.value = volumesCount
	t.unitEntity = 'Q1238720'
	return t
end

local function entityToTable(entity, t)
	t.entity = entity
	return t
end

local function valueToTable(value, t)
	t.value = value
	return t
end

local function valueToParentTable(value, parentTable, name)
	if not parentTable.components then
		parentTable.components = {}
	end

	local t = {
		value = value,
	}

	parentTable.components[name] = t
	return parentTable
end

local function urlStatusToTable(value, parentTable)
	if not parentTable.components then
		parentTable.components = {}
	end

	local status = l10n('args', 'reversed', 'urlStatus', value)
	local entityMap = {
		dead = 'Q1193907',
		paywall = 'Q910845',
		['open access'] = 'Q232932',
		['free access'] = 'Q24707952',
		['registration required'] = 'Q107459441',
	}

	local t = {
		value = value,
		lang = currentLang,
		entity = entityMap[status],
	}

	parentTable.components.urlStatus = t
	return parentTable
end

local function langCodeToTable(langCode, t)
	t.entity = wdLang.langEntity(langCode)
	t.components = {
		langCode = { value = langCode },
	}
	return t
end

local function pagesToTable(pagesCount, t)
	t.value = pagesCount
	t.unitEntity = 'Q1069725'
	return t
end

local function getTitles(frame)
	if not frame then
		frame = mw.getCurrentFrame()
	end

	local template
	local parentFrame = frame:getParent()
	local selfTitle = frame:getTitle()
	if parentFrame then
		template = parentFrame:getTitle()
	else
		template = selfTitle
	end
	return template, selfTitle
end

local function getTemplateNameFromTitle(template)
	local templateNamespace = mw.site.namespaces[NS_TEMPLATE].name
	return template:gsub('^' .. templateNamespace .. ':', '')
end

local function argsToSource(frame)
	local s = {}

	local argsMap = {
		entity = { entityToTable, 'workVersion' },
		origin = { valueToTable, 'origin' },
		originEntity = { entityToTable, 'origin' },
		originUrl = { valueToTable, 'originUrl' },
		originLang = { langCodeToTable, 'originLang' },
		contentTypeEntity = { entityToTable, 'contentType' },
		contentType = { valueToTable, 'contentType' },
		workTypeEntity = { entityToTable, 'workType' },
		workType = { valueToTable, 'workType' },
		authors = { strToArray, 'authors' },
		illustrators = { strToArray, 'illustrators' },
		editorInChief = { strToArray, 'editorInChief' },
		editors = { strToArray, 'editors' },
		translators = { strToArray, 'translators' },
		title = { valueToTable, 'title' },
		subtitle = { valueToTable, 'subtitle' },
		info = { valueToTable, 'info' },
		volumesCount = { volumesCountToPartsCount, 'partsCount' },
		publishedInEntity = { entityToTable, 'publishedInVersion' },
		publishedIn = { valueToTable, 'publishedIn' },
		publishedInSubtitle = { valueToTable, 'publishedInSubtitle' },
		publishedInOrigin = { valueToTable, 'publishedInOrigin' },
		publishedInOriginLang = { langCodeToTable, 'publishedInOriginLang' },
		publishedInLang = { langCodeToTable, 'publishedInLang' },
		publishedInAuthors = { strToArray, 'publishedInAuthors' },
		publishedInIllustrators = { strToArray, 'publishedInIllustrators' },
		publishedInEditors = { strToArray, 'publishedInEditors' },
		publishedInEditorInChief = { strToArray, 'publishedInEditorInChief' },
		publishedInTranslators = { strToArray, 'publishedInTranslators' },
		publishedInUrl = { valueToTable, 'publishedInUrl' },
		publishedInUrlStatus = { urlStatusToTable, 'publishedInUrl' },
		publishedInVolumes = { volumesCountToPartsCount, 'publishedInPartsCount' },
		publishedInWorkType = { valueToTable, 'publishedInWorkType' },
		comment = { valueToTable, 'comment' },
		edition = { valueToTable, 'edition' },
		location = { valueToTable, 'location' },
		locationEntity = { entityToTable, 'location' },
		publisher = { valueToTable, 'publisher' },
		publisherEntity = { entityToTable, 'publisher' },
		topic = { valueToTable, 'topic' },
		topicEntity = { entityToTable, 'topic' },
		id = { valueToTable, 'id' },
		date = { strToDate, 'date' },
		volume = { valueToTable, 'volume' },
		volumeTitle = { valueToTable, 'volumeTitle' },
		issue = { valueToTable, 'issue' },
		issueTitle = { valueToTable, 'issueTitle' },
		articleId = { valueToTable, 'articleId' },
		pages = { valueToTable, 'pages' },
		pagesCount = { pagesToTable, 'pagesCount' },
		series = { valueToTable, 'series' },
		seriesEntity = { entityToTable, 'series' },
		seriesIssue = { valueToTable, 'seriesIssue' },
		lastUpdate = { strToDate, 'lastUpdate' },
		accessDate = { strToDate, 'accessDate' },
		doi = { valueToTable, 'doi' },
		pmid = { valueToTable, 'pmid' },
		pmc = { valueToTable, 'pmc' },
		s2sic = { valueToTable, 's2sic' },
		oclc = { valueToTable, 'oclc' },
		issn = { strToArray, 'issn' },
		isbn = { strToArray, 'isbn' },
		url = { valueToTable, 'url' },
		urlStatus = { urlStatusToTable, 'url' },
		waybackStamp = { valueToTable, 'waybackStamp' },
		archiveUrl = { valueToParentTable, 'url', 'archiveUrl' },
		archiveDate = { strToDateToParentTable, 'url', 'archiveDate' },
		publishedInArchiveUrl = { valueToParentTable, 'publishedInUrl', 'archiveUrl' },
		publishedInArchiveDate = { strToDateToParentTable, 'publishedInUrl', 'archiveDate' },
		ref = { valueToTable, 'ref' },
		lang = { langCodeToTable, 'lang' },
	}

	local serviceArgs = {
		offline = true,
		forceSubst = true,
		pureWikitext = true,
	}

	local unknownFields = {}
	
	local template, selfTitle = getTitles(frame)

	local ok, templateInfo = pcall(mw.loadData, selfTitle .. '/args:' .. template)
	if ok then
		local parentFrame = frame:getParent()
		for parentKey, value in pairs(parentFrame.args) do
			local key = templateInfo.argsToModule[parentKey]
			if key then
				value = mw.text.trim(value)
				if argIsSet(value) then
					local argMap = argsMap[key]
					if argMap then
						local name = argMap[2]
						local childName = argMap[3]
						local fieldTable = s[name] or {}
						s[name] = argMap[1](value, fieldTable, childName)
					elseif not serviceArgs[key] then
						error(string.format(l10n('errors', 'unknown-module-arg'), key))
					end
				end
			else
				table.insert(unknownFields, { value = parentKey })
			end
		end
	else
		for key, value in pairs(frame.args) do
			if argIsSet(value) then
				local argMap = argsMap[key]
				if argMap then
					local name = argMap[2]
					local childName = argMap[3]
					local fieldTable = s[name] or {}
					s[name] = argMap[1](value, fieldTable, childName)
				elseif not serviceArgs[key] then
					error('Unknown module argument: ' .. key)
				end
			end
		end
	end

	if next(unknownFields) ~= nil then
		s.unknownFields = unknownFields
	end

	if s.publishedInVersion then
		s.publishedIn = entityToTable(s.publishedInVersion.entity, s.publishedIn or {})
	end

	if s.publishedInEditors or s.publishedInEditorInChief then
		s.isLikeBook = { entity = 'Q571' }
	end

	return s
end

local function urlIsArchive(urlTable)
	local url = urlTable.value:gsub('^(https?://)www%.', '%1')
	local isArchive = false
	if url:match('^https?://web%.archive%.org/web/') or url:match('^https?://webcitation%.org/%w+') then
		isArchive = true
	end
	if url:match('^https?://archive%.%a+/%w+') then
		if url:match('^https?://archive%.today/') or
		   url:match('^https?://archive%.ph/') or
		   url:match('^https?://archive%.is/') or
		   url:match('^https?://archive%.li/') or
		   url:match('^https?://archive%.vn/') or
		   url:match('^https?://archive%.fo/') or
		   url:match('^https?://archive%.md/') then
			isArchive = true
		end
	end
	return isArchive
end

local function appendErrorField(s, fieldName, errorKey)
	s[fieldName] = s[fieldName] or {}
	table.insert(s[fieldName], {
		value = l10n('errors', errorKey),
		lang = currentLang,
	})
end

local function checkFields(s)
	local hasErrors = false

	if not s.lang or (table.getn(s.lang) == 0 and s.lang.isDefault) then
		appendErrorField(s, 'langIsMissing', 'missing-lang')
		hasErrors = true
	end
	if s.url then
		if s.url.value then
			if urlIsArchive(s.url) then
				appendErrorField(s, 'urlIsArchive', 'url-contains-archiveUrl')
				hasErrors = true
			end
		elseif s.url.components then
			if s.url.components.archiveUrl then
				appendErrorField(s, 'archiveUrlWithoutUrl', 'archiveUrl-without-url')
				hasErrors = true
			end
			if s.url.components.urlStatus then
				appendErrorField(s, 'urlIsMissing', 'urlStatus-without-url')
				hasErrors = true
			end
		end

		if s.url.components then
			if s.url.components.archiveDate then
				if s.url.components.archiveDate.erroneous then
					appendErrorField(s, 'wrongArchiveDateFormat', 'wrong-date-format')
					hasErrors = true
				end
			elseif s.url.components.archiveUrl then
				appendErrorField(s, 'archiveUrlWithoutArchiveDate', 'archiveUrl-without-archiveDate')
				hasErrors = true
			end
		end
	end

	if s.publishedInUrl then
		if s.publishedInUrl.value then
			if urlIsArchive(s.publishedInUrl) then
				appendErrorField(s, 'urlIsArchive', 'publishedIn-url-contains-archiveUrl')
				hasErrors = true
			end
		elseif s.publishedInUrl.components then
			if s.publishedInUrl.components.archiveUrl then
				appendErrorField(s, 'archiveUrlWithoutUrl', 'publishedIn-archiveUrl-without-url')
				hasErrors = true
			end
			if s.publishedInUrl.components.urlStatus then
				appendErrorField(s, 'urlIsMissing', 'publishedIn-urlStatus-without-url')
				hasErrors = true
			end
		end
	
		if s.publishedInUrl.components then
			if s.publishedInUrl.components.archiveDate then
				if s.publishedInUrl.components.archiveDate.erroneous then
					appendErrorField(s, 'wrongPublishedInArchiveDateFormat', 'wrong-date-format')
					hasErrors = true
				end
			elseif s.publishedInUrl.components.archiveUrl then
				appendErrorField(s, 'archiveUrlWithoutArchiveDate', 'publishedIn-archiveUrl-without-archiveDate')
				hasErrors = true
			end
		end
	end

	if (not s.url or not s.url.value) and (not s.publishedInUrl or not s.publishedInUrl.value) then
		if s.accessDate then
			appendErrorField(s, 'urlIsMissing', 'accessDate-without-url')
			hasErrors = true
		end
	end

	if not s.title then
		appendErrorField(s, 'titleIsMissing', 'missing-title')
		hasErrors = true
	end

	if (s.publishedInUrl or s.publishedInSubtitle) and (not s.publishedIn or not s.publishedIn.value) then
		appendErrorField(s, 'publishedInTitleIsMissing', 'missing-publishedIn')
		hasErrors = true
	end

	if s.date and s.date.erroneous then
		appendErrorField(s, 'wrongDateFormat', 'wrong-date-format')
		hasErrors = true
	end
	if s.accessDate and s.accessDate.erroneous then
		appendErrorField(s, 'wrongAccessDateFormat', 'wrong-date-format')
		hasErrors = true
	end
	if s.lastUpdate and s.lastUpdate.erroneous then
		appendErrorField(s, 'wrongLastUpdateFormat', 'wrong-date-format')
		hasErrors = true
	end

	if s.id and not s.id.retrieved and not (s.publishedIn and s.publishedIn.entity) then
		appendErrorField(s, 'idWithoutPublishedIn', 'id-without-publishedInEntity')
		hasErrors = true
	end
	return hasErrors
end

local function categoriesAllowed()
	if not mw.title.getCurrentTitle():inNamespaces(NS_MAIN, NS_TEMPLATE, NS_PROJECT) then
		return false
	end
	return true
end

local function getErrorCategories(source, categories, templateName)
	local categoriesStr = ''
	for _, cat in ipairs(categories) do
		local found = false
		for _, fieldName in ipairs(cat) do
			if source[fieldName] then
				found = true
				break
			end
		end
		if found then
			categoriesStr = categoriesStr .. '[[' .. mw.ustring.format(l10n('categories', cat.name), templateName) .. ']]'
		end
	end
	return categoriesStr
end

function p.cite(frame)
	local s = argsToSource(frame)
	local langCode
	if not s.offline or not s.offline.value then
		s, langCode = source.fetch(s)
	elseif s.lang then
		if table.getn(s.lang) > 0 then
			for _, lang in ipairs(s.lang) do
				if lang.components and s.lang.components.langCode then
					langCode = lang.components.langCode.value
					break
				end
			end
		else
			if s.lang.components and s.lang.components.langCode then
				langCode = s.lang.components.langCode.value
			end
		end
	else
		langCode = currentLangObj:getCode()
	end

	if s.url and s.url.value and s.waybackStamp then
		if not s.url.components or (not s.url.components.archiveUrl or not s.url.components.archiveUrl.retrieved) then
			if not s.url.components then
				s.url.components = {}
			end
			s.url.components.archiveUrl = waybackStampToArchiveUrl(s.waybackStamp.value, s.url)
			s.url.components.archiveDate = waybackStampToArchiveDate(s.waybackStamp.value)
		end
	end

	local hasErrors = checkFields(s)

	local args = frame.args
	if (mw.isSubsting() or argIsSet(args.forceSubst)) and argIsEmpty(args.pureWikitext) then
		local template, selfTitle = getTitles(frame)
		local argsProfile = require(selfTitle .. '/Subst')
		local langCode = s.lang and s.lang.components and s.lang.components.langCode
		return '{{' .. getTemplateNameFromTitle(template) .. '|' .. formatter.format(argsProfile, s, langCode) .. '}}'
	end
	
	local formattedCitation = formatter.format(GostProfile, s, langCode)

	local styles = ''
	if stylesUsed then
		styles = frame:extensionTag('templatestyles', '', { src = frame:getTitle() .. '/styles.css' })
	end
	
	local categoriesStr = ''

	if not argIsSet(args.noCat) then
		if hasErrors and categoriesAllowed() then
			local template, selfTitle = getTitles(frame)
			local templateName = getTemplateNameFromTitle(template)
			local categories = {
				{
					'unknownFields',
					name = 'Unknown parameters',
				},
				{
					'archiveUrlWithoutUrl',
					'archiveUrlWithoutArchiveDate',
					'urlIsArchive',
					name = 'Wrong archive url parameters',
				},
				{
					name = 'Lang is not specified',
					'langIsMissing',
				},
				{
					'urlIsMissing',
					name = 'URL is not specified',
				},
				{
					'titleIsMissing',
					name = 'Title is not specified',
				},
				{
					'publishedInTitleIsMissing',
					name = 'PublishedIn is not specified',
				},
				{
					'wrongDateFormat',
					'wrongAccessDateFormat',
					'wrongLastUpdateFormat',
					'wrongArchiveDate',
					'wrongPublishedInArchiveDateFormat',
					name = 'Wrong date format',
				},
				{
					'idWithoutPublishedIn',
					name = 'Id without publishedIn',
				},
			}
			categoriesStr = getErrorCategories(s, categories, templateName)
		end
	end
	
	return styles .. formattedCitation .. categoriesStr
end

return p