Этот модуль составлен для реализации шаблон }.
Документация по использованию шаблона расположена на странице соответствующего шаблона.
Модуль развёрнут в двух вариантах: боевая версия (привязана к {{карточка/модуль}}, в дальнейшем будет перевязана к {{карточка}}) и песочница для тестирования (привязана к {{карточка/песочница}}).
Для песочницы развёрнут модуль юнит-тестирования ([1]).
Для тестирования в боевых условиях замените в частном шаблоне-карточке {{карточка}} на {{карточка/модуль}}. Несколько малоиспользуемых шаблонов, взятых на боевое тестирование:
Используемый в коде подход гарантирует корректную работу только при шаге между подзаголовками и парами метка-текст не больше 50! В разных карточках можно встретить что-то вроде метка12=...|текст12=...|метка13=...|текст13=...|метка120=... — такие места нужно исправлять вручную.
-- -- Модуль для реализации шаблона {{Карточка}} -- local p = {} local HtmlBuilder = require('Module:HtmlBuilder') local args = {} local origArgs local argsAliases = {} local root local function union(t1, t2) -- Возвращает объединение значений двух таблиц в виде последовательности. local vals = {} for k, v in pairs(t1) do vals[v] = true end for k, v in pairs(t2) do vals[v] = true end local ret = {} for k, v in pairs(vals) do table.insert(ret, k) end return ret end local function getArgNums(prefix) -- Возвращает таблицу индексов существующих полей с заданным префиксом, -- например, для префикса 'текст' и установленных 'текст1', 'текст2' и -- 'текст5' возвращает {1, 2, 5}. local nums = {} for k, v in pairs(args) do local num = tostring(k):match('^' .. prefix .. '([1-9]%d*)$') if num then table.insert(nums, tonumber(num)) end end table.sort(nums) return nums end local function addRow(rowArgs) -- Добавляет строку в карточку (заголовок или метку/текст). if rowArgs.header then root .tag('tr') .addClass(rowArgs.rowclass) .attr('id', rowArgs.rowid) .tag('th') .attr('colspan', 2) .attr('id', rowArgs.headerid) .addClass(rowArgs.class) .addClass(args['класс_заголовков']) .css('text-align', 'center') .cssText(args['стиль_заголовков']) .wikitext(rowArgs.header) elseif rowArgs.data then local row = root.tag('tr') row.addClass(rowArgs.rowclass) row.attr('id', rowArgs.rowid) if rowArgs.label then row .tag('th') .attr('scope', 'row') .attr('id', rowArgs.labelid) .cssText(args['стиль_меток']) .wikitext(rowArgs.label) .done() end local dataCell = row.tag('td') if not rowArgs.label then dataCell .attr('colspan', 2) .css('text-align', 'center') end dataCell .attr('id', rowArgs.dataid) .addClass(rowArgs.class) .cssText(rowArgs.datastyle) .newline() .wikitext(rowArgs.data) end end local function renderTitle() if not args['название'] then return end root .tag('caption') .addClass(args['класс_названия']) .cssText(args['стиль_названия']) .wikitext(args['название']) end local function renderAboveRow() if not args['вверху'] then return end root .tag('tr') .tag('th') .attr('colspan', 2) .addClass(args['класс_вверху']) .css('text-align', 'center') .css('font-size', '120%') .css('font-weight', 'bold') .cssText(args['стиль_вверху']) .wikitext(args['вверху']) end local function renderAbove2Row() if not args['вверху2'] then return end root .tag('tr') .tag('th') .attr('colspan', 2) .addClass(args['класс_вверху2']) .css('text-align', 'center') .css('font-style', 'oblique') .cssText(args['стиль_вверху2']) .wikitext(args['вверху2']) end local function renderBelowRow() if not args['внизу'] then return end root .tag('tr') .tag('td') .attr('colspan', 2) .addClass(args['класс_внизу']) .css('text-align', 'center') .cssText(args['стиль_внизу']) .newline() .wikitext(args['внизу']) end local function renderSubheaders() if args['подзаголовок'] then args['подзаголовок1'] = args['подзаголовок'] end if args['класс_ряда_подзаголовка'] then args['класс_ряда_подзаголовка1'] = args['класс_ряда_подзаголовка'] end local subheadernums = getArgNums('подзаголовок') for k, num in ipairs(subheadernums) do addRow({ data = args['подзаголовок' .. tostring(num)], datastyle = args['стиль_подзаголовков'] or args['стиль_подзаголовка' .. tostring(num)], class = args['класс_подзаголовков'], rowclass = args['класс_ряда_подзаголовка' .. tostring(num)] }) end end local function renderImages() if args['изображение'] then args['изображение1'] = args['изображение'] end if args['подпись'] then args['подпись1'] = args['подпись'] end local imagenums = getArgNums('изображение') for k, num in ipairs(imagenums) do local caption = args['подпись' .. tostring(num)] local data = HtmlBuilder.create().wikitext(args['изображение' .. tostring(num)]) if caption then data .tag('div') .cssText(args['стиль_подписи']) .wikitext(caption) end addRow({ data = tostring(data), datastyle = args['стиль_изображения'], class = args['класс_изображения'], rowclass = args['класс_ряда_изображения' .. tostring(num)] }) end end local function renderRows() -- Объединяет индексы заголовков и текстовых строк карточки -- и визуализирует их в правильном порядке через addRow. local rownums = union(getArgNums('заголовок'), getArgNums('текст')) table.sort(rownums) for k, num in ipairs(rownums) do addRow({ header = args['заголовок' .. tostring(num)], label = args['метка' .. tostring(num)], data = args['текст' .. tostring(num)], datastyle = args['стиль_текста'], class = args['класс' .. tostring(num)], rowclass = args['класс_ряда' .. tostring(num)], dataid = args['id_текста' .. tostring(num)], labelid = args['id_метки' .. tostring(num)], headerid = args['id_заголовка' .. tostring(num)], rowid = args['id_ряда' .. tostring(num)] }) end end local function renderNavBar() if not args['имя'] then return end root .tag('tr') .tag('td') .attr('colspan', 2) .css('text-align', 'right') .wikitext(mw.getCurrentFrame():expandTemplate({ title = 'Tnavbar', args = { args['имя'] } })) end local function isSet(x) -- Возвращает истину, если x задан и не пустой -- Внимание: отличается от enwiki! В enwiki проверяется на равенство 'yes' return x and x ~= '' end local function renderItalicTitle() -- Внимание: отличается от enwiki. В enwiki ожидается yes или force, здесь работает любое значение if isSet(args['заголовок_курсивом']) then root.wikitext(mw.getCurrentFrame():expandTemplate({title = 'Заголовок курсивом'})) end end local function renderTrackingCategories() if not isSet(args.nocat) then if #(getArgNums('текст')) == 0 and mw.title.getCurrentTitle().namespace == 0 then root.wikitext('[[Категория:Статьи с карточкой без заполненных данных]]') end if isSet(args['внедрение']) and args['название'] then root.wikitext('[[Категория:Статьи со встроенной карточкой и параметром названия]]') end end end local function _infobox() -- Задание общей страктуры карточки с добавлением стилей -- для карточек-потомков. if not isSet(args['внедрение']) then root = HtmlBuilder.create('table') root .addClass('infobox') .addClass(args['класс_тела']) if isSet(args['подкарточка']) then root .css('padding', '0') .css('border', 'none') .css('margin', '-1px') .css('width', 'auto') .css('min-width', '100%') .css('font-size', '100%') .css('clear', 'none') .css('float', 'none') .css('background-color', 'transparent') end -- Микроразметка if isSet(args['микр_тела']) then root .attr('itemscope', 'itemscope') .attr('itemtype', args['микр_тела']) end root .cssText(args['стиль_тела']) renderTitle() renderAboveRow() renderAbove2Row() else root = HtmlBuilder.create() root .wikitext(args['название']) end renderSubheaders() renderImages() renderRows() renderBelowRow() renderNavBar() renderItalicTitle() renderTrackingCategories() return tostring(root) end local function preprocessSingleArg(argName) -- Добавляет аргумент в таблицу аргументов, если он определён и не пустой. -- Пустые аргументы не обрабатываются, как и в ParserFunctions. if origArgs[argName] and origArgs[argName] ~= '' then args[argName] = origArgs[argName] end end local function translateArg(aliasArgName,localArgName) -- Функция добавляет поддержку алиасов параметров (например, на другом языке) -- Добавляем алиас параметра в таблицу алиасов -- Для одного параметра может быть несколько алиасов -- Нумерованные параметры(текст1 и т.д.) заносятся без номера if not argsAliases[localArgName] then argsAliases[localArgName] = {} end table.insert(argsAliases[localArgName], aliasArgName) -- Пока для тестирования: значения алиасов добавляются в таблицу аргументов -- Нумерованные параметры работать не будут if origArgs[localArgName] and origArgs[localArgName] ~= '' then -- параметр уже задан на локальном языке else -- если алиас задан и не пустой if origArgs[aliasArgName] and origArgs[aliasArgName] ~= '' then origArgs[localArgName] = origArgs[aliasArgName] end end end local function preprocessArgs(prefixTable, step) -- Сохраняет параметры с заданными префиксами в таблицу args, последовательно обходя -- аргументы в нужном порядке и с нужным шагом. Благодаря этому сноски и пр. появляются -- в правильном порядке. prefixTable — массив таблиц, каждая из которых может содержать -- два поля: поле-строку префикса (обязательно) и поле-таблицу зависимых параметров. -- Эта функция всегда обрабатывает параметры с префиксом, но зависимые параметры -- обрабатываются, только если параметр с префиксом задан и не пустой. if type(prefixTable) ~= 'table' then error("В качестве таблицы префиксов должна использоваться таблица", 2) end if type(step) ~= 'number' then error("Недопустимый тип параметра шага", 2) end -- Проверка правильности данных и обработка параметров без суффиксов. for i,v in ipairs(prefixTable) do if type(v) ~= 'table' or type(v.prefix) ~= "string" or (v.depend and type(v.depend) ~= 'table') then error('Недопустимая таблица префиксов preprocessArgs', 2) end preprocessSingleArg(v.prefix) -- Зависимые параметры обрабатываются, только если параметр с префиксом задан и не пустой. if args[v.prefix] and v.depend then for j, dependValue in ipairs(v.depend) do if type(dependValue) ~= 'string' then error('Недопустимый тип зависимого параметра в таблице preprocessArgs') end preprocessSingleArg(dependValue) end end end -- Обход нумерованных аргументов. local a = 1 -- Переменная-счётчик. local moreArgumentsExist = true while moreArgumentsExist == true do moreArgumentsExist = false for i = a, a + step - 1 do for j,v in ipairs(prefixTable) do local prefixArgName = v.prefix .. tostring(i) if origArgs[prefixArgName] then moreArgumentsExist = true -- Искать аргументы дальше, если был хотя бы один (в т. ч. пустой) preprocessSingleArg(prefixArgName) end -- Обрабатываем зависимые аргументы, если определена таблица зависимостей, -- а также задан не пустой аргумент с префиксом, либо обрабатывается -- "префикс1" и "префикс" задан (например, "изображение1" является синонимом для "изображение"). if v.depend and (args[prefixArgName] or (i == 1 and args[v.prefix])) then for j,dependValue in ipairs(v.depend) do local dependArgName = dependValue .. tostring(i) preprocessSingleArg(dependArgName) end end end end a = a + step end end function p.infobox(frame) -- При запуске через #invoke аргументы передаются через стандартную систему. -- При тестировании также можно передавать таблицу аргументов через frame. if frame == mw.getCurrentFrame() then origArgs = frame:getParent().args else origArgs = frame end -- Поддержка параметров из англовики translateArg('child','внедрение') translateArg('bodyclass','класс_тела') translateArg('subbox','подкарточка') translateArg('bodystyle','стиль_тела') translateArg('title','название') translateArg('titleclass','класс_названия') translateArg('titlestyle','стиль_названия') translateArg('above','вверху') translateArg('aboveclass','класс_вверху') translateArg('abovestyle','стиль_вверху') translateArg('subheader','подзаголовок') translateArg('subheaderstyle','стиль_подзаголовка') translateArg('subheaderrowclass','класс_подзаголовка') translateArg('subheaderstyle','стиль_подзаголовков') translateArg('subheaderclass','класс_подзаголовков') translateArg('image','изображение') translateArg('caption','подпись') translateArg('imagerowclass','класс_ряда_изображения') translateArg('captionstyle','стиль_подписи') translateArg('imagestyle','стиль_изображения') translateArg('imageclass','класс_изображения') translateArg('header','заголовок') translateArg('data','текст') translateArg('label','метка') translateArg('rowclass','класс_ряда') translateArg('class','класс') translateArg('dataid','id_текста') translateArg('labelid','id_метки') translateArg('headerid','id_заголовка') translateArg('rowid','id_ряда') translateArg('headerclass','класс_заголовков') translateArg('headerstyle','стиль_заголовков') translateArg('labelstyle','стиль_меток') translateArg('datastyle','стиль_текста') translateArg('below','внизу') translateArg('belowclass','класс_внизу') translateArg('belowstyle','стиль_внизу') translateArg('name','имя') --translateArg('italic title','заголовок_курсивом') --translateArg('','') -- Параметры обрабатываются по направлению чтения карточки, чтобы -- сноски и др. отображались в нужных местах. Параметры, зависящие -- от других параметров, обрабатываются только при наличии других параметров, -- чтобы в списке сносок не возникали нежелательные сноски. preprocessSingleArg('внедрение') preprocessSingleArg('класс_тела') preprocessSingleArg('подкарточка') preprocessSingleArg('стиль_тела') preprocessSingleArg('название') preprocessSingleArg('класс_названия') preprocessSingleArg('стиль_названия') preprocessSingleArg('вверху') preprocessSingleArg('класс_вверху') preprocessSingleArg('стиль_вверху') preprocessSingleArg('вверху2') preprocessSingleArg('класс_вверху2') preprocessSingleArg('стиль_вверху2') preprocessArgs({ {prefix = 'подзаголовок', depend = {'стиль_подзаголовка', 'класс_подзаголовка'}} }, 10) preprocessSingleArg('стиль_подзаголовков') preprocessSingleArg('класс_подзаголовков') preprocessArgs({ {prefix = 'изображение', depend = {'подпись', 'класс_ряда_изображения'}} }, 10) preprocessSingleArg('стиль_подписи') preprocessSingleArg('стиль_изображения') preprocessSingleArg('класс_изображения') preprocessArgs({ {prefix = 'заголовок'}, {prefix = 'текст', depend = {'метка'}}, {prefix = 'класс_ряда'}, {prefix = 'класс'}, {prefix = 'id_текста'}, {prefix = 'id_метки'}, {prefix = 'id_заголовка'}, {prefix = 'id_ряда'} }, 50) preprocessSingleArg('класс_заголовков') preprocessSingleArg('стиль_заголовков') preprocessSingleArg('стиль_меток') preprocessSingleArg('стиль_текста') preprocessSingleArg('внизу') preprocessSingleArg('класс_внизу') preprocessSingleArg('стиль_внизу') preprocessSingleArg('имя') preprocessSingleArg('заголовок_курсивом') preprocessSingleArg('nocat') return _infobox() end return p