Модуль:WDFormat
Модуль предназначен для форматирования набора данных, полученных из Викиданных посредством модуля WDBackend или заданных вручную. Форматирование может осуществляться в произвольной форме, как в строчку, так и в табличном виде.
Конечное представление информации задаётся профилем, который описывает, какими тегами оформлять данные, группы полей и отдельные поля, а также какие преобразования над данными необходимо сделать.
ИспользованиеПравить
Модуль является библиотечным и предназначен для использования в других модулях. Данный модуль не предназначен для использования в статьях или других шаблонах напрямую через вызов #invoke
.
Требуемое форматирование описывается профилем, представляющим собой таблицу Lua. Профиль описывает представление отдельных полей, то есть то, как они должны отображаться. Недостающий функционал реализуется указанием в профиле собственных функций, принимающих определённый набор аргументов и возвращающих определённый результат.
Для форматирования профиля в совокупности с передаваемым набором данных необходимо использовать функцию format()
. В качестве результата возвращается отформатированный викитекст.
Для примера использования см. модуль Модуль:CiteGost.
Формат профиля в общем видеПравить
{
-- Корневой тег:
tag = {
name = 'Имя тега',
classes = { 'class1', 'class2', ... },
attr = { атрибут1='значение1', атрибут2='значение2', ... },
tag = Вложенный тег,
},
-- Группы:
groups = {
-- Начало 1-й группы:
{
-- Тег группы:
tag = {
name = 'Имя тега',
classes = { 'class1', 'class2', ... },
attr = { атрибут1='значение1', атрибут2='значение2', ... },
tag = Вложенный тег
},
ensureEnds = 'Символ/текст, которым должны заканчиваться поля группы',
delimiter = 'Разделитель полей в группе',
prefix = 'Текст до начала группы',
-- Поля группы:
parts = {
-- 1-е поле группы:
{
-- Тег поля:
tag = {
name = 'Имя тега',
classes = { 'class1', 'class2', ... },
attr = { атрибут1='значение1', атрибут2='значение2', ... },
tag = Вложенный тег
},
ensureEnds = 'Символ/текст, которым должен заканчиваться предшествующий текст',
delimiter = 'Разделитель, отделяющий текущий элемент от предыдущего',
-- Функция, разрешающая отображение поля:
cond = функция,
prefix = 'Текст до поля',
field = 'Название поля',
urlMaskProp = 'P-идентификатор свойства, отвечающего за маску ссылки',
-- Функции, через которые поле будет отформатировано:
format = { функция1, функция2, ... },
suffix = 'Текст после поля',
},
-- Остальные поля:
...
},
suffix = 'Текст в конце группы',
},
-- Остальные группы:
...
},
ensureEnds = 'Окончение форматированных данных, например, точка.'
}
Функции форматирования полейПравить
Форматирование поля задаётся через команду format
с указанием функции форматирования, принимающей определённый набор аргументов. Доступны следующие встроенные функции:
numericalRanges
— форматирование диапазона чисел (корректирует знак диапазона);dash
— оформление тире в тексте;unit
— получение единицы измерения у элемента Викиданных;abbr
— получить сокращённое обозначение (есть ограничения) у элемента Викиданных;short
— получить короткое название у элемента Викиданных;abbrWithHint
— получить сокращённое обозначение (есть ограничения) у элемента Викиданных с расшифровкой в подсказке;date
— форматирование даты;quantity
— форматирование количества с указанной в нём единицей измерения;entity
— получить значение идентификатора элемента Викиданных;wikilink
— оформить поле Викиссылкой, если это возможно;wikisource
— оформить поле ссылкой на Викитеку, если в элементе Викиданных указана соответствующая статья;link
— оформить поле внешней ссылкой, если это возможно;wikidata
— добавление к полю надстрочной ссылки на элемент Викиданных, если хотя бы в одном языковом разделе есть статья по теме;wikidataLink
— оформить поле ссылкой на элемент Викиданных, который с полем связан.
Внесение измененийПравить
При исправлении ошибки, пожалуйста, сначала добавьте тест, который будет проваливаться из-за обнаруженной ошибки, и только затем вносите исправление. При внесении исправления проверьте, чтобы все тесты проходили. Вносить исправление можно только, если оно не ломает другие тесты.
Добавление нового функционала рекомендуется делать у себя в песочнице, скопировав в неё модуль. В правке копирования необходимо указать тот факт, что делается копирование, и сделать ссылку на оригинальный модуль в виде викитекста. При добавлении нового функционала сначала желательно добавить тест на этот функционал, затем добавить сам функционал, убедившись, что все тесты при этом проходят.
ТестыПравить
Все тесты пройдены.
Название | Ожидается | Фактически | |
---|---|---|---|
test_format_array | |||
test_format_arrayCapitalize | |||
test_format_arrayForceCapitalize | |||
test_format_capitalize | |||
test_format_date | |||
test_format_ensureEndsAndDelimiter | |||
test_format_innerTags | |||
test_format_link | |||
test_format_linkOrder | |||
test_format_oneField | |||
test_format_person | |||
test_format_person_multipleNames | |||
test_format_prefixAndSuffix | |||
test_format_recurseGroups | |||
test_format_rootTag | |||
test_format_squareBrackets | |||
test_format_tableTag | |||
test_format_wikilink | |||
test_format_wikisource |
В качестве функциональных тестов использованы тесты модуля, который работает на основе текущего модуля. В будущем должны будут добавиться собственные тесты.
Все тесты успешно пройдены.
Тест | Ожидаемое значение | Фактическое значение | |
---|---|---|---|
{{#invoke:CiteGost|cite
|publishedInEntity = Q114595502 |publishedInUrl = https://www.google.ru/books/edition/Seldin_and_Giebisch_s_The_Kidney/w5nEg7VLEQ4C?hl=en&gbpv=1&pg=610 |title = Chapter 20 — Structural Organization of the Mammalian Kidney |url = https://www.sciencedirect.com/science/article/pii/B9780123814623000203 |authors = W. Kriz, B. Kaissling |volume = 1 |pages = 595-691 |ref = Kriz, Kaissling }} |
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. / Eds.: R. J. Alpern [et al.]. — Fifth edition. — Amsterdam : Academic Press, 2012. — Vol. 1. — 31 December. — P. 595—691. — ISBN 978-0-12-381463-0, 978-0-12-381462-3. | 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. / Eds.: R. J. Alpern [et al.]. — Fifth edition. — Amsterdam : Academic Press, 2012. — Vol. 1. — 31 December. — P. 595—691. — ISBN 978-0-12-381463-0, 978-0-12-381462-3. |
Тест | Ожидаемое значение | Фактическое значение | |
---|---|---|---|
{{#invoke:CiteGost|cite
|entity = Q114676884 |ref = Sagan }} |
Саган К. Мир, полный демонов : Наука — как свеча во тьме = 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. — 538 с. — ISBN 978-5-91671-874-4. — WD Q114676884. |
Тест | Ожидаемое значение | Фактическое значение | |
---|---|---|---|
{{#invoke:CiteGost|cite
|entity = Q115189432 |volumeTitle = Животные |ref = Красная книга РФ, т. «Животные» }} |
Красная книга Российской Федерации. Т. «Животные». — 2-ое издание. — М. : ФГБУ «ВНИИ Экология», 2021. — 1128 с. — (Красная книга Российской Федерации). — ISBN 978-5-6047425-0-1. — WD Q115189432. | Красная книга Российской Федерации. Т. «Животные». — 2-ое издание. — М. : ФГБУ «ВНИИ Экология», 2021. — 1128 с. — (Красная книга Российской Федерации). — ISBN 978-5-6047425-0-1. — WD Q115189432. |
Тест | Ожидаемое значение | Фактическое значение | |
---|---|---|---|
{{#invoke:CiteGost|cite
|entity = Q114831437 |ref = Веракса, Веракса }} |
Веракса Н. Е. Познавательное развитие в дошкольном возрасте : учебное пособие / Н. Е. Веракса, А. Н. Веракса. — М. : МОЗАИКА-СИНТЕЗ, 2012. — 336 с. — ISBN 978-5-4315-0097-8. — WD Q114831437. | Веракса Н. Е. Познавательное развитие в дошкольном возрасте : учебное пособие / Н. Е. Веракса, А. Н. Веракса. — М. : МОЗАИКА-СИНТЕЗ, 2012. — 336 с. — ISBN 978-5-4315-0097-8. — WD Q114831437. |
Тест | Ожидаемое значение | Фактическое значение | |
---|---|---|---|
{{#invoke:CiteGost|cite
|entity = Q1768199 |ref = БРЭ }} |
Большая российская энциклопедия : в 35 т. : энциклопедия / Гл. ред.: Ю. С. Осипов. — М. : Большая российская энциклопедия, 2004—2017. — WD Q1768199. | Большая российская энциклопедия : в 35 т. : энциклопедия / Гл. ред.: Ю. С. Осипов. — М. : Большая российская энциклопедия, 2004—2017. — WD Q1768199. |
Тест | Ожидаемое значение | Фактическое значение | |
---|---|---|---|
{{#invoke:CiteGost|cite
|entity = Q115634662 |ref = Clement, Davis, Harris }} |
Clement P.[d] Finches and Sparrows : [англ.] / Illus.: A. Harris, J. Davis. — 1st edition. — 1993. — 21 November. — 500 p. — ISBN 978-0-691-03424-9. — WD Q115634662. | Clement P.[d] Finches and Sparrows : [англ.] / Illus.: A. Harris, J. Davis. — 1st edition. — 1993. — 21 November. — 500 p. — ISBN 978-0-691-03424-9. — WD Q115634662. |
Тест | Ожидаемое значение | Фактическое значение | |
---|---|---|---|
{{Источник информации|Q24187606
|ref = Sutter, Saraswat, Driel }} |
An I. M. De Sutter. Antihistamines for the common cold : [англ.] / An I. M. De Sutter, A. Saraswat, Mieke van Driel // Cochrane Database of Systematic Reviews. — 2015. — 29 November. — ISSN 1469-493X, 1361-6137. — doi:10.1002/14651858.cd009345.pub2. — PMID 26615034. — WD Q24187606. | An I. M. De Sutter. Antihistamines for the common cold : [англ.] / An I. M. De Sutter, A. Saraswat, Mieke van Driel // Cochrane Database of Systematic Reviews. — 2015. — 29 November. — ISSN 1469-493X, 1361-6137. — doi:10.1002/14651858.cd009345.pub2. — PMID 26615034. — WD Q24187606. |
Тест | Ожидаемое значение | Фактическое значение | |
---|---|---|---|
{{#invoke:CiteGost|cite
|publishedInEntity = Q1768199 |topicEntity = Q58184 |ref = Наточин }} |
Наточин Ю. В. Нефрон : [арх. 15 июня 2022] // Большая российская энциклопедия : в 35 т. : энциклопедия / Гл. ред.: Ю. С. Осипов. — М. : Большая российская энциклопедия, 2017. | Наточин Ю. В. Нефрон : [арх. 15 июня 2022] // Большая российская энциклопедия : в 35 т. : энциклопедия / Гл. ред.: Ю. С. Осипов. — М. : Большая российская энциклопедия, 2017. |
Тест | Ожидаемое значение | Фактическое значение | |
---|---|---|---|
{{#invoke:CiteGost|cite
|publishedInEntity = Q5375741 |topicEntity = Q58184 |lastUpdate = 2022-08-22 |accessDate = 2022-10-23 |ref = Nephron, Britannica }} |
The Editors of Encyclopaedia Britannica. Nephron : [англ.] : [арх. 2 июня 2022] // Encyclopædia Britannica : online encyclopedia. — Дата обновления: 22 августа 2022. — Дата обращения: 23 октября 2022. | The Editors of Encyclopaedia Britannica. Nephron : [англ.] : [арх. 2 июня 2022] // Encyclopædia Britannica : online encyclopedia. — Дата обновления: 22 августа 2022. — Дата обращения: 23 октября 2022. |
Тест | Ожидаемое значение | Фактическое значение | |
---|---|---|---|
{{#invoke:CiteGost|cite
|authors = Ж. Н. Меренкова |title = Анемии при беременности |url = https://music.yandex.ru/album/10142231/track/70882375?activeTab=track-list |workType = профессиональный медицинский подкаст |publishedInEntity = Q4537983 |publisher = Специализированное издательство «Медицинская литература» |language = ru |date = 2020-09-04 |ref = Меренкова }} |
Меренкова Ж. Н. Анемии при беременности [аудиоконтент] : профессиональный медицинский подкаст // Яндекс Музыка. — Специализированное издательство «Медицинская литература», 2020. — 4 сентября. | Меренкова Ж. Н. Анемии при беременности [аудиоконтент] : профессиональный медицинский подкаст // Яндекс Музыка. — Специализированное издательство «Медицинская литература», 2020. — 4 сентября. |
Тест | Ожидаемое значение | Фактическое значение | |
---|---|---|---|
{{#invoke:CiteGost|cite
|title = Деятельность ВОЗ по научно-техническому анализу и прогнозированию в области здравоохранения |publisherEntity = Q7817 |id = 1m_4Y6pHmUw |publishedInEntity = Q866 |lang = ru |date = 2022-07-08 |accessDate = 2022-11-22 |ref = ВОЗ }} |
Деятельность ВОЗ по научно-техническому анализу и прогнозированию в области здравоохранения [видеозапись] // YouTube. — ВОЗ, 2022. — 8 июля. — Дата обращения: 22 ноября 2022. | Деятельность ВОЗ по научно-техническому анализу и прогнозированию в области здравоохранения [видеозапись] // YouTube. — ВОЗ, 2022. — 8 июля. — Дата обращения: 22 ноября 2022. |
Тест | Ожидаемое значение | Фактическое значение | |
---|---|---|---|
{{#invoke:CiteGost|cite
|entity = Q73118986 |ref = Casotti, Lindberg, Braun }} |
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. — Vol. 279, no. 5. — 1 November. — P. R1722—30. — ISSN 0363-6119, 1522-1490. — doi:10.1152/ajpregu.2000.279.5.r1722. — PMID 11049855. — WD Q73118986. | 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. — Vol. 279, no. 5. — 1 November. — P. R1722—30. — ISSN 0363-6119, 1522-1490. — doi:10.1152/ajpregu.2000.279.5.r1722. — PMID 11049855. — WD Q73118986. | |
{{#invoke:CiteGost|cite
|entity = Q92544321 |ref = Abdalla }} |
Abdalla M. A. Anatomical features in the kidney involved in water conservation through urine concentration in dromedaries (Camelus dromedarius) : [англ.] : [арх. 26 июля 2022] // Heliyon[d]. — 2020. — Vol. 6, no. 1. — 2 January. — Article e03139. — ISSN 2405-8440. — doi:10.1016/j.heliyon.2019.e03139. — PMID 31922050. — WD Q92544321. | Abdalla M. A. Anatomical features in the kidney involved in water conservation through urine concentration in dromedaries (Camelus dromedarius) : [англ.] : [арх. 26 июля 2022] // Heliyon[d]. — 2020. — Vol. 6, no. 1. — 2 January. — Article e03139. — ISSN 2405-8440. — doi:10.1016/j.heliyon.2019.e03139. — PMID 31922050. — WD Q92544321. | |
{{#invoke:CiteGost|cite
|entity = Q59330138 |ref = Munn, Peters, Stern, et al. }} |
Systematic review or scoping review? Guidance for authors when choosing between a systematic or scoping review approach : [англ.] / Z. Munn, M. Peters, C. Stern [et al.] // BMC Medical Research Methodology. — 2018. — Vol. 18, no. 1. — 19 November. — P. 143. — ISSN 1471-2288. — doi:10.1186/s12874-018-0611-x. — PMID 30453902. — WD Q59330138. | Systematic review or scoping review? Guidance for authors when choosing between a systematic or scoping review approach : [англ.] / Z. Munn, M. Peters, C. Stern [et al.] // BMC Medical Research Methodology. — 2018. — Vol. 18, no. 1. — 19 November. — P. 143. — ISSN 1471-2288. — doi:10.1186/s12874-018-0611-x. — PMID 30453902. — WD Q59330138. |
Тест | Ожидаемое значение | Фактическое значение | |
---|---|---|---|
{{#invoke:CiteGost|cite
|entity = Q115926218 |volumeTitle = Full text }} |
Lefever E. W. United Nations peacekeeping in the Congo: 1960-1964 : an analysis of political, executive and military control : [англ.]. In 4 vols. Vol. 2. Full text / E. W. Lefever, W. Joshua. — Washington : Brookings Institution, 1966. — 30 June. — 454 p. — OCLC 489825. — WD Q115926218. | Lefever E. W. United Nations peacekeeping in the Congo: 1960-1964 : an analysis of political, executive and military control : [англ.]. In 4 vols. Vol. 2. Full text / E. W. Lefever, W. Joshua. — Washington : Brookings Institution, 1966. — 30 June. — 454 p. — OCLC 489825. — WD Q115926218. |
План по улучшениюПравить
План работ:
|
План разработки следующей версииПравить
План работ:
|
См. такжеПравить
local p = {}
local wikidata = require('Модуль:WDCommon')
local function dump(obj, level)
if type(obj) ~= 'table' then
if type(obj) == 'string' then
return "'" .. obj .. "'"
else
return tostring(obj)
end
end
if not level then
level = 0
end
local indent = string.rep(' ', level)
local s = '{'
local isFirst = true
for i, v in pairs(obj) do
if not isFirst then
s = s .. ','
end
s = s .. '\n'
local currIndent = string.rep(' ', level + 4)
s = s .. currIndent
if type(i) == 'string' then
s = s .. i .. ' = '
end
s = s .. dump(v, level + 4)
isFirst = false
end
s = s .. '\n' .. indent .. '}'
return s
end
local Formatter = {
profile = nil,
processField = {},
}
function Formatter:tagToContainer(tag, parentContainer)
if not tag or not tag.name then
return parentContainer
end
local container = parentContainer
while tag do
if container then
container = container:tag(tag.name)
else
container = mw.html.create(tag.name)
end
if tag.classes then
for _, currClass in ipairs(tag.classes) do
container:addClass(currClass)
end
end
if tag.attr then
for attr, value in pairs(tag.attr) do
if type(value) == 'function' then
value = value(self.source)
end
container:attr(attr, value)
end
end
tag = tag.tag
end
return container
end
function Formatter:new(profile, source)
local obj = {}
setmetatable(obj, self)
self.__index = self
self.profile = profile
self.source = source
self.state = {
empty = true,
linkedEntities = {},
}
self.lastAddedText = ''
self.container = self:tagToContainer(profile.tag)
assert(self.container ~= nil, 'Not root container found. Use tag profile field.')
return obj
end
function Formatter.processField.numericalRanges(source, processedData, result)
local defaultLangObj = mw.getContentLanguage()
local defaultLang = defaultLangObj:getCode()
local rangeSign
if defaultLang == 'ru' then
rangeSign = '—'
elseif defaultLang == 'ko' then
rangeSign = '~'
else
rangeSign = '–'
end
result.wikitext = mw.ustring.gsub(result.wikitext, '-', rangeSign)
end
function Formatter.processField.squareBrackets(source, processedData, result)
result.text = '[' .. result.text .. ']'
result.wikitext = '[' .. result.wikitext .. ']'
end
function Formatter.processField.dash(source, processedData, result)
local defaultLangObj = mw.getContentLanguage()
local defaultLang = defaultLangObj:getCode()
local dashSign
if defaultLang == 'ru' then
dashSign = ' — '
end
result.wikitext = mw.ustring.gsub(result.wikitext, ' %- ', dashSign)
result.text = mw.ustring.gsub(result.text, ' %- ', dashSign)
end
function Formatter.processField.unit(source, processedData, result, state)
local entity = processedData.fieldTable.entity
if not entity then
return
end
local unit = wikidata.unit(entity, processedData.langCode)
if (unit and state.groupEmpty and processedData.capitalize) or processedData.forceCapitalize then
unit = mw.ustring.gsub(unit, '^%l', mw.ustring.upper)
end
result.text = unit
result.wikitext = unit
end
function Formatter.processField.abbr(source, processedData, result, state)
local entity = processedData.fieldTable.entity
if not entity then
return
end
local abbr, _, ok = wikidata.abbrBiblio(entity, processedData.langCode)
if (abbr and state.groupEmpty and processedData.capitalize) or processedData.forceCapitalize then
abbr = mw.ustring.gsub(abbr, '^%l', mw.ustring.upper)
end
if abbr and ok then
abbr = mw.ustring.gsub(abbr, ' ', ' ')
end
result.text = abbr
result.wikitext = abbr
end
function Formatter.processField.short(source, processedData, result, state)
local entity = processedData.fieldTable.entity
if not entity then
return
end
local short = wikidata.short(entity, processedData.langCode)
if (short and state.groupEmpty and processedData.capitalize) or processedData.forceCapitalize then
short = mw.ustring.gsub(short, '^%l', mw.ustring.upper)
end
if not short then
return
end
result.text = short
result.wikitext = short
end
function Formatter.processField.abbrWithHint(source, processedData, result, state)
local entity = processedData.fieldTable.entity
if not entity then
return
end
local abbr, _, ok = wikidata.abbrBiblio(entity, processedData.langCode)
if (abbr and state.groupEmpty and processedData.capitalize) or processedData.forceCapitalize then
abbr = mw.ustring.gsub(abbr, '^%l', mw.ustring.upper)
end
if abbr and ok then
abbr = mw.ustring.gsub(abbr, ' ', ' ')
end
if result.text ~= abbr then
result.wikitext = '<abbr title="' .. result.wikitext .. '">' .. abbr .. '</abbr>'
result.text = abbr
end
end
function Formatter.processField.date(source, processedData, result)
local langObj = mw.getLanguage(processedData.langCode)
result.text = langObj:formatDate('j xg Y', processedData.fieldTable.value.timestamp)
result.wikitext = result.text
end
function Formatter.processField.uriScheme(source, processedData, result)
local entity = processedData.fieldTable.entity
if not entity then
return
end
result.text = wikidata.uriScheme(entity)
result.wikitext = result.text
end
function Formatter.processField.quantity(source, processedData, result)
local fieldTable = processedData.fieldTable
if not fieldTable.unitEntity then
return
end
local unit = wikidata.unit(fieldTable.unitEntity, processedData.langCode)
if unit then
result.text = result.text .. ' ' .. unit
result.wikitext = result.wikitext .. ' ' .. unit
end
end
local function nameFromFieldTable(nameTable)
local value = nameTable.value
local ok = true
if not value then
value = mw.wikibase.getLabel(nameTable.entity) .. '<sup>[[d:' .. nameTable.entity .. '|?]]</sup>'
ok = false
end
return value, ok
end
local function nameToInitial(nameTable)
local value = nameTable.value
local ok
if not value then
value = mw.wikibase.getLabel(nameTable.entity) .. '<sup>[[d:' .. nameTable.entity .. '|?]]</sup>'
ok = false
else
value = mw.ustring.sub(value, 1, 1) .. '.'
ok = true
end
return value, ok
end
local function namesToInitial(nameTables)
local value, ok
if table.getn(nameTables) == 0 then
value, ok = nameToInitial(nameTables)
else
value = ''
ok = true
for i, nameTable in ipairs(nameTables) do
if i > 1 then
value = value .. ' '
end
local currValue, currOk = nameToInitial(nameTable)
value = value .. currValue
ok = ok and currOk
end
end
return value, ok
end
function Formatter.processField.person(source, processedData, result)
-- currently supports only names with givenName and pathronym/mathronym,
-- middle names are not supported if they are a part of givenName
local fieldTable = processedData.fieldTable
local name
if fieldTable.components and fieldTable.components.familyName and fieldTable.components.givenName then
local ok
name, ok = namesToInitial(fieldTable.components.givenName)
if fieldTable.components.ancestorName then
local ancestorName, ancestorNameOk = namesToInitial(fieldTable.components.ancestorName)
name = name .. ' ' .. ancestorName
ok = ok and ancestorNameOk
end
local familyName, familyNameOk = nameFromFieldTable(fieldTable.components.familyName)
name = name .. ' ' .. familyName
ok = ok and familyNameOk
if not ok then
result.linked = true
end
else
local entity = processedData.fieldTable.entity
if entity then
name = wikidata.abbr(entity, processedData.langCode)
else
name = processedData.value
end
if not name then
name = mw.wikibase.getLabel(entity) .. '<sup>[[d:' .. entity .. '|?]]</sup>'
end
end
result.text = name
result.wikitext = result.text
end
function Formatter.processField.personReversed(source, processedData, result)
-- currently supports only names with givenName and pathronym/mathronym,
-- middle names are not supported if they are a part of givenName
local fieldTable = processedData.fieldTable
local name
if fieldTable.components and fieldTable.components.familyName and fieldTable.components.givenName then
local ok
local familyName, ok = nameFromFieldTable(fieldTable.components.familyName)
local givenName, givenNameOk = namesToInitial(fieldTable.components.givenName)
ok = ok and givenNameOk
name = familyName .. ', ' .. givenName
if fieldTable.components.ancestorName then
local ancestorName, ancestorNameOk = namesToInitial(fieldTable.components.ancestorName)
ok = ok and ancestorNameOk
name = name .. ' ' .. ancestorName
end
if not ok then
result.linked = true
end
else
local entity = processedData.fieldTable.entity
if entity then
name = wikidata.abbr(entity, processedData.langCode)
else
return
end
if not name then
name = mw.wikibase.getLabel(entity) .. '<sup>[[d:' .. entity .. '|?]]</sup>'
end
end
result.text = name
result.wikitext = result.text
end
function Formatter.processField.personReversedNoComma(source, processedData, result)
-- currently supports only names with givenName and pathronym/mathronym,
-- middle names are not supported if they are a part of givenName
local fieldTable = processedData.fieldTable
local name
if fieldTable.components and fieldTable.components.familyName and fieldTable.components.givenName then
local ok
local familyName, ok = nameFromFieldTable(fieldTable.components.familyName)
local givenName, givenNameOk = namesToInitial(fieldTable.components.givenName)
ok = ok and givenNameOk
name = familyName .. ' ' .. givenName
if fieldTable.components.ancestorName then
local ancestorName, ancestorNameOk = namesToInitial(fieldTable.components.ancestorName)
ok = ok and ancestorNameOk
name = name .. ' ' .. ancestorName
end
if not ok then
result.linked = true
end
else
local entity = processedData.fieldTable.entity
if entity then
name = wikidata.abbr(entity, processedData.langCode)
else
return
end
if not name then
name = mw.wikibase.getLabel(entity) .. '<sup>[[d:' .. entity .. '|?]]</sup>'
end
end
result.text = name
result.wikitext = result.text
end
function Formatter.processField.lowercase(source, processedData, result)
result.text = mw.ustring.lower(result.text)
result.wikitext = mw.ustring.lower(result.wikitext)
end
function Formatter.processField.wikisource(source, processedData, result)
if result.linked then
return
end
local entity = processedData.fieldTable.entity
if not entity then
return
end
local title = mw.wikibase.getSitelink(entity, processedData.langCode .. 'wikisource')
if not title then
return
end
result.wikitext = '[[s:' .. title .. '|' .. result.text .. ']]'
result.linked = true
end
function Formatter.processField.link(source, processedData, result)
if result.linked or not processedData.url then
return
end
result.wikitext = '[' .. processedData.url .. ' ' .. result.wikitext .. ']'
result.linked = true
end
function Formatter.processField.wikilink(source, processedData, result)
if result.linked then
return
end
if processedData.wikilink then
result.wikitext = '[[' .. processedData.wikilink .. '|' .. result.wikitext .. ']]'
return
end
local entity = processedData.fieldTable.entity
if not entity then
return
end
if result.state.linkedEntities[entity] then
return
end
local wikitext
wikitext, entity = wikidata.base.wikilink(entity, result.wikitext)
if result.state.linkedEntities[entity] or not entity then
return
end
result.wikitext = wikitext
result.state.linkedEntities[entity] = true
result.linked = true
end
function Formatter.processField.wikidata(source, processedData, result)
if result.linked then
return
end
local entity = processedData.fieldTable.entity
if not entity or result.state.linkedEntities[entity] then
return
end
local entityObj = mw.wikibase.getEntity(entity)
if entityObj.sitelinks then
result.wikitext = result.wikitext .. '<sup>[[d:' .. entity .. '|[d]]]</sup>'
result.state.linkedEntities[entity] = true
result.linked = true
end
end
function Formatter.processField.forceWikidata(source, processedData, result)
local entity = processedData.fieldTable.entity
if not entity then
return
end
result.wikitext = result.wikitext .. '<sup>[[d:' .. entity .. '|[d]]]</sup>'
result.state.linkedEntities[entity] = true
result.linked = true
end
function Formatter.processField.wikidataLink(source, processedData, result)
if result.linked then
return
end
local entity = processedData.fieldTable.entity
if not entity then
return
end
result.wikitext = '[[d:' .. entity .. '|' .. result.wikitext .. ']]'
result.linked = true
end
function Formatter.processField.entity(source, processedData, result)
local entity = processedData.fieldTable.entity
if not entity then
return
end
result.text = entity
result.wikitext = entity
end
local function fieldValueByPath(source, path)
if type(path) == 'table' then
local currField = source
for _, pathEntry in ipairs(path) do
currField = currField[pathEntry]
end
if not currField.value then
return nil
end
if path.sub then
return currField.value[path.sub]
end
return currField.value
end
local fieldValue = source[path]
if not fieldValue then
return nil
end
return fieldValue.value
end
local function fieldTableByPath(source, path)
if type(path) == 'table' then
local currField = source
for _, pathEntry in ipairs(path) do
currField = currField[pathEntry]
end
return currField
end
return source[path]
end
local function urlFromMaskByPart(part, source, fieldValue, langCode)
if part.urlMaskProp then
local urlMask = wikidata.urlMask(part.urlMaskProp, langCode)
if urlMask then
return urlMask:gsub('%$1', fieldValue)
end
elseif part.urlField then
local urlTable = source[part.urlField]
if urlTable then
if type(urlTable) == 'table' then
return urlTable.value
else
return urlTable
end
end
end
return nil
end
local function wikilinkFromMaskByPart(part, source, fieldValue)
if part.wikilinkMask then
return part.wikilinkMask:gsub('%$1', fieldValue)
end
return nil
end
local function forceLangByPart(part, source)
local langCode
if not part.forceLang then
langCode = source.langCode
if type(langCode) == 'table' then
langCode = langCode.value
end
end
if langCode then
return langCode
elseif part.forceLang == 'fallback' then
langCode = 'en'
elseif part.forceLang == 'default' or not part.forceLang then
local defaultLangObj = mw.getContentLanguage()
local defaultLang = defaultLangObj:getCode()
langCode = defaultLang
else
langCode = part.forceLang
end
return langCode
end
local function formatField(source, part, fieldTable, fieldValue, state)
local processedData = {
field = part.field,
wikilink = wikilinkFromMaskByPart(part, source, fieldValue),
langCode = forceLangByPart(part, source),
fieldTable = fieldTable,
capitalize = (part.capitalize or (part.capitalize == nil)) and (state.index == 1),
forceCapitalize = (part.capitalize == true and state.index == 1),
}
processedData.url = urlFromMaskByPart(part, source, fieldValue, processedData.langCode)
local value = fieldValue
local sourceLangCode
if source.langCode then
sourceLangCode = source.langCode.value
end
local linked = false
if fieldTable.entity and (processedData.langCode ~= sourceLangCode or not value) then
if processedData.langCode then
value = mw.wikibase.getLabelByLang(fieldTable.entity, processedData.langCode)
if not value then
value = mw.wikibase.getLabel(fieldTable.entity)
if value then
value = value .. '<sup>[[d:' .. fieldTable.entity .. '|?]]</sup>'
linked = true
end
end
else
value = mw.wikibase.getLabel(fieldTable.entity)
end
end
if value and type(value) == 'string' then
if (state.groupEmpty and processedData.capitalize) or processedData.forceCapitalize then
value = mw.ustring.gsub(value, '^%l', mw.ustring.upper)
end
end
if type(value) == 'table' then
value = dump(value)
elseif value == nil then
value = '<b><s>(nil)</s></b>'
end
processedData.value = value
local result = {
wikitext = value,
text = value,
state = state,
linked = linked,
}
if not part.format then
state.groupEmpty = false
return result
end
for _, formatFunc in ipairs(part.format) do
formatFunc(source, processedData, result, state)
end
state.groupEmpty = false
return result
end
local function formatFieldAsArray(source, part, fieldTable, state)
local text = ''
local wikitext = ''
local count = table.getn(fieldTable)
local cutCount = 0
if part.limits and count > part.limits.max then
cutCount = count - part.limits.cutTo
count = part.limits.cutTo
end
local delimiter = part.itemsDelimiter
if not delimiter then
delimiter = ', '
end
for i=1, count do
state.index = i
local currFieldTable = fieldTable[i]
local currResult = formatField(source, part, currFieldTable, currFieldTable.value, state)
wikitext = wikitext .. currResult.wikitext
text = text .. currResult.text
if i < count then
text = text .. delimiter
wikitext = wikitext .. delimiter
end
end
if cutCount > 0 then
local cutText = ''
for i=count + 1, count + cutCount do
state.index = i
local currFieldTable = fieldTable[i]
local currResult = formatField(source, part, currFieldTable, currFieldTable.value, state)
cutText = cutText .. currResult.text
if i < count + cutCount then
cutText = cutText .. delimiter
end
end
local processedData = {
field = part.field,
langCode = forceLangByPart(part, source),
fieldTable = fieldTable,
}
local othersText, othersWikitext = part.limits.replaceBy(source, processedData, cutText)
text = text .. othersText
wikitext = wikitext .. othersWikitext
end
return text, wikitext
end
local function fieldTableAndValueByPart(source, part)
if part.entity or part.value then
return { entity = part.entity, value = part.value }, part.value
else
return fieldTableByPath(source, part.field), fieldValueByPath(source, part.field)
end
end
local function formatFieldComponents(source, part, state)
local fieldTable, fieldValue = fieldTableAndValueByPart(source, part)
if type(fieldTable) ~= 'table' then
error('Field ' .. dump(part.field) .. ' is not a table. Its type is ' .. type(fieldTable))
end
local text
local wikitext
if table.getn(fieldTable) > 0 then
text, wikitext = formatFieldAsArray(source, part, fieldTable, state)
else
state.index = 1
local result = formatField(source, part, fieldTable, fieldValue, state)
text = result.text
wikitext = result.wikitext
end
return { text = text, wikitext = wikitext }
end
function Formatter:commonFormatField(part, parentContainer)
self:addPrefix(part)
local result = formatFieldComponents(self.source, part, self.state)
local container = self:tagToContainer(part.tag, parentContainer)
container:wikitext(result.wikitext)
self.lastAddedText = result.text
self.state.empty = false
self:addSuffix(part)
end
function Formatter:ensureEndsWith(endsText, parentContainer)
if self.lastAddedText and endsText then
local len = mw.ustring.len(endsText)
if mw.ustring.sub(self.lastAddedText, -len) ~= endsText then
parentContainer:wikitext(endsText)
end
end
end
function Formatter:ensureEndsAndAddDelimiter(group, part, parentContainer, groupEmpty)
if self.state.empty then
return
end
local delimiter = part.delimiter
local endsText = part.ensureEnds
if (delimiter == nil or groupEmpty) and not part.forceDelimiter then
if group.delimiter or group.ensureEnds then
delimiter = group.delimiter
endsText = group.ensureEnds
end
end
self:ensureEndsWith(endsText, parentContainer)
parentContainer:wikitext(delimiter)
end
function Formatter:addPrefix(part)
local prefix = part.prefix
if not prefix then
return
end
self.container:wikitext(prefix)
end
function Formatter:addSuffix(part)
local suffix = part.suffix
if not suffix then
return
end
self.container:wikitext(suffix)
self.lastAddedText = self.lastAddedText .. suffix
end
local function fieldExists(field, source)
if type(field) == 'table' then
local specified = false
local currField = source
for _, currFieldName in ipairs(field) do
currField = currField[currFieldName]
if not currField then
return false
else
specified = true
end
end
if field.sub then
if not currField.value then
return false
end
currField = currField.value[field.sub]
if not currField then
return false
end
end
return specified
end
return (source[field] ~= nil)
end
local function partIsAvailable(part, source)
if part.parts then
for i, subpart in ipairs(part.parts) do
if partIsAvailable(subpart, source) then
return true
end
end
return false
end
if not fieldExists(part.field, source) and not part.entity and not part.value then
return false
end
local conflicts = part.conflicts
if conflicts then
if type(conflicts) == 'table' then
for _, currField in ipairs(conflicts) do
if fieldExists(currField, source) then
return false
end
end
else
if fieldExists(conflicts, source) then
return false
end
end
end
local depends = part.depends
if depends then
if type(depends) == 'table' then
for _, currField in ipairs(depends) do
if not fieldExists(currField, source) then
return false
end
end
else
if not fieldExists(depends, source) then
return false
end
end
end
if part.cond and not part.cond(source) then
return false
end
return true
end
function Formatter:formatGroup(group, parentContainer)
local state = self.state
state.groupEmpty = true
local groupContainer
for _, part in ipairs(group.parts) do
if partIsAvailable(part, self.source) then
if not groupContainer then
self:ensureEndsAndAddDelimiter(group, part, parentContainer, state.groupEmpty)
groupContainer = self:tagToContainer(group.tag, parentContainer) or parentContainer
local prefix = group.prefix
if prefix then
groupContainer:wikitext(prefix)
self.lastAddedText = self.lastAddedText .. prefix
end
else
self:ensureEndsAndAddDelimiter(group, part, groupContainer, state.groupEmpty)
end
if part.parts then
self:formatGroup(part, groupContainer)
else
self:commonFormatField(part, groupContainer)
end
end
end
if not state.groupEmpty then
local suffix = group.suffix
if suffix then
groupContainer:wikitext(suffix)
self.lastAddedText = self.lastAddedText .. suffix
end
end
end
function Formatter:formatGroups(groups, parentContainer)
for _, group in ipairs(groups) do
self:formatGroup(group, parentContainer)
end
end
function Formatter:format()
local groups = self.profile.groups
self:formatGroups(groups, self.container)
self:ensureEndsWith(self.profile.ensureEnds, self.container)
end
function Formatter:getAsText()
return tostring(self.container:allDone())
end
function p.format(profile, source)
local f = Formatter:new(profile, source)
f:format()
return f:getAsText()
end
p.f = Formatter.processField
return p