Модуль:Routemap

Из Википедии, бесплатной энциклопедии

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

См. Шаблон:Маршрутная карта.

local i18n = {     errors = {         ["parameter-missing"] = "Не указан обязательный параметр!",         ["collapsible-block-not-closed"] = "Сворачивающийся блок не закрыт!",         ["collapsible-block-not-open"] = "Сворачивающийся блок не был открыт, либо лишняя команда закрытия",         ["collapsible-block-empty"] = "Невозможно создать пустой сворачивающийся блок!",         ["collapsible-block-no-first-row"] = "Нет первой строки для сворачивающегося блока!",         ["collapsible-block-no-replacement"] = "Нет строки замещения для сворачивающегося блока!",         ["colspan-less-rows-than-set"] = "Строк меньше, чем указано к объединению!",     },     ["error-categories"] = {         default = '[[Категория:Википедия:Статьи с неверно заполненными маршрутными картами]]'     },     html = {         ["cell-icon-fmt"] = '\ |[[File:BSicon_%s.svg|x20px|link=%s|alt=]]',         ["cell-overlapicon-fmt"] = '<div style="position:absolute;left:0px;top:0px;padding:0">[[File:BSicon_%s.svg|x20px|link=%s|alt=]]</div>',         ["cell-icon-fmt-with-overlap"] = '\ |<div style="position:relative">%s</div>[[File:BSicon_%s.svg|x20px|link=|alt=]]',         ["cell-filler-fmt"] = '\n|style="width:8px"| ||style="width:4px !important; background-color:%s"| ||style="width:8px"|',         ["cell-filler-empty-fmt"] = '\n|style="width:%s;min-width:%s"|',          ["row-linfo4-fmt"] = '\ |style="padding-right:3px;text-align:left;%s"|<div style="font-size:90%%;">%s</div>',-- parameters:linfo4-width, linfo4         ["row-linfo3-fmt"] = '<div style="font-size:90%%;">%s</div> ',         ["row-rinfo3-fmt"] = ' <div style="font-size:90%%;">%s</div>',         ["row-rinfo4-fmt"] = '\ |style="padding-left:3px;text-align:right;%s"|<div style="font-size:90%%;">%s</div>',-- parameters:rinfo4-width, rinfo4          -- в «margin:auto !important» у таблицы «!important» — для мобильного вида         ["row-general-fmt"] = '\ |-%s\ |colspan="%s" style="text-align:right;%s"|%s\ |style="text-align:left;padding:0 %s;%s"|<div style="font-size:90%%;">%s</div>\ |style="padding:0;background-color:%s"|\ {|cellspacing="0" cellpadding="0" style="margin:auto !important; text-align:center; line-height: 0px !important; padding:0 !important;"\ |-%s\ |}\ |style="text-align:right;padding:0 %s;%s"|<div style="font-size:90%%;">%s</div>\ |colspan="%s" style="text-align:left;%s"|%s%s',-- parameters: linfo4-fmt, colspan-left, linfo3+2-width, linfo3+2, linfo1-pad, linfo1-width, linfo1, bg, cells, rinfo1-pad, rinfo1-width, rinfo1, colspan-right, rinfo2+3-width, rinfo2+3, rinfo4-fmt          ["row-collapsible-begin-fmt"] = '\ |-\ |colspan="7" style="padding:0 !important;background-color:%s"|\ {|class="%s%s" cellpadding="0" cellspacing="0" style="%s padding:0 !important;vertical-align:middle;margin:none !important;white-space:nowrap"',-- parameters: bg, "collapsible "/"mw-collapsible mw-", collapse-state, "float:right;" / ""          ["row-collapsible-end-fmt"] = '\n|}',          ["row-collapsible-left-button-width"] = '50px',-- 50px is the minimal width for [показать] / [скрыть] button. Use 40px for [show] / [hide]         ["row-collapsible-left-button-fmt"] = '\n! style="padding-right:3px;min-width:%s;%s" |',--parameters: left-button-width, linfo4-width         ["row-collapsible-left-linfo4+3+2-fmt"] = '\ {|cellspacing="0" cellpadding="0" width="100%%"\ |style="padding:0 3px 0 1px;text-align:left;"| <div style="font-size:90%%;">%s</div>\ |style="text-align:right"| %s\ |}',-- parameters: linfo4, linfo3+2         ["row-collapsible-right-button-width"] = '72px',-- 72px is the minimal width for [развернуть] / [свернуть] button at 90%. Use 58px for [expand] / [collapse]         ["row-collapsible-right-rinfo2+3+4-fmt"] = '\ {|cellspacing="0" cellpadding="0" width="100%%"\ |style="text-align:left"| %s\ |style="padding:0 1px 0 3px;text-align:right;"| <div style="font-size:90%%;">%s</div>\ |}',-- parameters: rinfo2+3, linfo4         ["row-collapsible-right-button-fmt"] = '\n| style="padding-left:3px;font-size:90%%;min-width:%s;%s" |',--parameters: right-button-width, rinfo4-width           ["row-collapsible-replace-begin-fmt"] = '\ |-\ |colspan="7" style="padding:0 %s"|<div style="position:relative">\ {| cellspacing="0" cellpadding="0" style="position:absolute;bottom:0px;%s vertical-align:middle;white-space:nowrap;background-color:%s"',-- parameters: "right-button-width 0 0" / "0 0 left-button-width", "right:0px" / "", bg         ["row-collapsible-replace-end-fmt"] = '\n|}</div>',          ["colspan-fmt"] = '%s\n|-\n| colspan="7" style="background-color:%s;text-align:%s;%s"|\n%s',         ["empty-row-fmt"] = '\n|-\n| style="padding-right:3px;%s" |\n| style="%s" |\n| style="padding:0 %s;%s" |\n|\n| style="padding:0 %s;%s" |\n| style="%s" |\n| style="padding-left:3px;%s" |'         } } local p,q={},{}  local function formaterror(key,param)     local result = mw.ustring.format(i18n.html['colspan-fmt'], '', '', '', '', '<span class="error">' .. mw.ustring.format(i18n.errors[key] or (tostring(key) .. ' %s'),         tostring(param or '')) .. '</span>')     if mw.site.namespaces[mw.title.getCurrentTitle().namespace].isContent then result = result .. (i18n['errors-categories'][key] or i18n['errors-categories'].default or '') end     return result end  local function RGBbyCode(code)-- RGB codes for BSicon sets at Commons:Category:Icons for railway descriptions/other colors     local colors = {--       Any changes should be discussed at Commons:Talk:BSicon/Colors         bahn     = 'BE2D2C', ex          = 'D77F7E',         u        = '003399', uex         = '6281C0',         f        = '008000', fex         = '64B164',         g        = '2CA05A', gex         = '7EC49A',         azure    = '3399FF', ex_azure    = '99CCFF',         black    = '000000', ex_black    = '646464',         blue     = '0078BE', ex_blue     = '64ACD6',         brown    = '8D5B2D', ex_brown    = 'B89A7F',         carrot   = 'ED9121', ex_carrot   = 'F1BA76',         cerulean = '1A8BB9', ex_cerulean = '73B7D3',         cyan     = '40E0D0', ex_cyan     = '8AEAE1',         deepsky  = '00BFFF', ex_deepsky  = '63D6FC',         denim    = '00619F', ex_denim    = '649EC3',         fuchsia  = 'B5198D', ex_fuchsia  = 'D173B8',         golden   = 'D7C447', ex_golden   = 'E5DA8E',         green    = '2DBE2C', ex_green    = '7FD67E',         grey     = '999999', ex_grey     = 'C0C0C0',         jade     = '53B147', ex_jade     = '95CE8E',         lavender = '9999FF', ex_lavender = 'C0C0FF',         lime     = '99CC00', ex_lime     = 'D1E681',         maroon   = '800000', ex_maroon   = 'B16464',         ochre    = 'CC6600', ex_ochre    = 'DEA164',         olive    = '837902', ex_olive    = 'B2AC64',         orange   = 'FF6600', ex_orange   = 'FF9955',         pink     = 'F0668D', ex_pink     = 'F4A1B8',         purple   = '8171AC', ex_purple   = 'B1A8CB',         red      = 'EF161E', ex_red      = 'F37176',         ruby     = 'CC0066', ex_ruby     = 'DE64A1', ex_exruby = 'E89FC4',         saffron  = 'FFAB2E', ex_saffron  = 'FFC969',         sky      = '069DD3', ex_sky      = '67C2E3',         steel    = 'A1B3D4', ex_steel    = 'C6D1E5',         teal     = '339999', ex_teal     = '82C0C0', ex_exteal = 'B1D6D6',         violet   = '800080', ex_violet   = 'B164B1',         yellow   = 'FFD702', ex_yellow   = 'FFEB81',     }     return colors[code] or colors.bahn end  local function cell(icon,overlapIcons) --Icon handling. Each icon is defined as in the following example: --icon ID!~overlap icon ID!@image link target --No limit on overlap icons, just separate them by "!~".     local tmp,link={},''     if #overlapIcons>0 then         tmp = mw.text.split(overlapIcons[#overlapIcons], '!@')         overlapIcons[#overlapIcons] = tmp[1]         if #tmp > 1 then link = tmp[2] end         tmp = {}         for i,v in ipairs(overlapIcons) do             if i==#overlapIcons then local link=link else local link='' end             table.insert(tmp,mw.ustring.format(i18n.html['cell-overlapicon-fmt'],mw.text.trim(v),link))end         return mw.ustring.format(i18n.html['cell-icon-fmt-with-overlap'],mw.text.trim(table.concat(tmp)),icon)     end     tmp = mw.text.split(icon, '!@')     icon = mw.text.trim(tmp[1])     if #tmp > 1 then link = tmp[2] end     if icon ~= '' then     	return mw.ustring.format(i18n.html['cell-icon-fmt'], icon, link)     else     	return mw.ustring.format(i18n.html['cell-filler-empty-fmt'], '20px', '20px')     end end local function fillercell(code)     if code == '' then     	return mw.ustring.format(i18n.html['cell-filler-empty-fmt'], '20px', '20px')     elseif code == 'd' then     	return mw.ustring.format(i18n.html['cell-filler-empty-fmt'], '10px', '10px')     elseif string.sub(code,1,1) == '#' then     	return mw.ustring.format(i18n.html['cell-filler-fmt'], code)     else     	return mw.ustring.format(i18n.html['cell-filler-fmt'],'#' .. RGBbyCode(code))     end end local function properties(str) --str is a combination of properties with following syntax: --[property name=value[!@property name1=value1[!@property name1=value1]]] and so on     local result = {}     for i, v in ipairs(mw.text.split(str, '!@')) do         if v ~= '' then             local t = mw.text.split(v, '=')             table.insert(result, t[1])             result[t[1]] = table.concat(t, '=', 2) or ''--fill table with pairs "property"="value"         end     end     return result end  local function row(pattern,noformatting,filler) --Row handling. Each row looks like the following: --row properties~~linfo4~~linfo3~~linfo2~~linfo1! !(icon pattern)~~rinfo1~~rinfo2~~rinfo3~~rinfo4~~row properties     local result = {['linfo4'] = '', ['linfo3+2'] = '', ['linfo1'] = '', ['cells'] = {}, ['rinfo1'] = '', ['rinfo2+3'] = '', ['rinfo4'] = '', ['rowProp'] = {}}     local lcolspan, rcolspan, linfo4_fmt, rinfo4_fmt = '2', '2', '', ''     local left, rigth, icons, overlapIcons, tmp = {}, {}, {}, {}, mw.text.split(pattern, '! !')     if #tmp > 1 then--splitting the pattern by '! !'         left = tmp[1] ; right = tmp[2]     else         left = '' ; right = tmp[1] or ''     end      tmp = mw.text.split(left, '~~')--analysing the left part     if #tmp > 1 then--if there are several ~~         result['linfo1'] = mw.getCurrentFrame():preprocess(mw.text.trim(tmp[#tmp]))         result['linfo3+2'] = mw.text.trim(tmp[#tmp - 1])         if #tmp > 2 then             tmp[#tmp - 2] = mw.text.trim(tmp[#tmp - 2])             if tmp[#tmp - 2] ~= '' then result['linfo3+2'] = mw.ustring.format(i18n.html['row-linfo3-fmt'], tmp[#tmp - 2]) .. result['linfo3+2'] end             if #tmp > 3 then                 tmp[#tmp - 3] = mw.text.trim(tmp[#tmp - 3])                 if tmp[#tmp - 3] ~= '' then                     result['linfo4'] = mw.getCurrentFrame():preprocess(tmp[#tmp - 3])                     lcolspan = '1'                     linfo4_fmt = mw.ustring.format(i18n.html['row-linfo4-fmt'], '', result['linfo4'])                 end                 if #tmp > 4 then result['rowProp'] = properties(mw.text.trim(tmp[#tmp - 4])) end             end         end     else--assume only linfo2 was provided.         result['linfo3+2'] = mw.text.trim(tmp[1])     end     result['linfo3+2'] = mw.getCurrentFrame():preprocess(result['linfo3+2'])--expand possible templates in info.       tmp = mw.text.split(right, '~~')--analysing the right part     if #tmp > 2 then         result['rinfo1'] = mw.getCurrentFrame():preprocess(mw.text.trim(tmp[2]))         result['rinfo2+3'] = mw.text.trim(tmp[3])         if #tmp > 3 then             tmp[4] = mw.text.trim(tmp[4])             if tmp[4] ~= '' then result['rinfo2+3'] = result['rinfo2+3'] .. mw.ustring.format(i18n.html['row-rinfo3-fmt'], tmp[4]) end             if #tmp > 4 then                 tmp[5] = mw.text.trim(tmp[5])                 if tmp[5] ~= '' then                     result['rinfo4'] = mw.getCurrentFrame():preprocess(tmp[5])                     rcolspan = '1'                     rinfo4_fmt = mw.ustring.format(i18n.html['row-rinfo4-fmt'], '', result['rinfo4'])                 end                 if #tmp > 5 then result['rowProp'] = properties(mw.text.trim(tmp[6])) end             end         end     else--assume only rinfo2 was provided.         result['rinfo2+3'] = mw.text.trim(tmp[2] or '')     end     result['rinfo2+3'] = mw.getCurrentFrame():preprocess(result['rinfo2+3'])      icons = mw.text.split(tmp[1], '\\')--splitting the string of icons first by "\"     if type(filler) == 'string' then     	result['cells'][1] = 'style="height:' .. filler .. '"'--row parameter before any cells         for i, v in ipairs(icons) do table.insert(result['cells'], fillercell(v)) end--no !@ or !~ for filler row     else         for i, v in ipairs(icons) do             tmp = mw.text.split(v, '!~')             icons[i] = tmp[1]             table.remove(tmp, 1)             table.insert(overlapIcons, tmp)         end         for i, v in ipairs(icons) do table.insert(result['cells'], cell(v, overlapIcons[i])) end     end     result['cells'] = table.concat(result['cells'])     if result['rowProp']['bg'] == nil or result['rowProp']['bg'] == '' then result['rowProp']['bg'] = 'transparent' end      if noformatting then     	return result     else     	return  mw.ustring.format(i18n.html['row-general-fmt'], linfo4_fmt, lcolspan, '', result['linfo3+2'], q.linfo1_pad, '', result['linfo1'], result['rowProp']['bg'],     		result['cells'], q.rinfo1_pad, '', result['rinfo1'], rcolspan, '', result['rinfo2+3'], rinfo4_fmt)     end end  q = {collapsibles = -1, text_width = {'', '', '', '', '', ''}, linfo1_pad = '3px', rinfo1_pad = '3px', bg = '#f9f9f9'} q.isKeyword = function(pattern, i, rows, justTest)     if string.sub(pattern, 1, 1) ~= '-' then if justTest then return false else return nil end end--not a valid keyword     local tmp = mw.text.split(string.sub(pattern, 2), '%-')     if type(q[tmp[1]])=="function" and tmp[1] ~= 'isKeyword' then         if justTest then return tmp[1] else return q[tmp[1]](tmp, i, rows) end--valid keyword     else         if justTest then return false else return nil end     end end q['startCollapsible'] = function(params, i, rows)     table.remove(rows, i)     local tmp = q.isKeyword(rows[i], i, rows, true)     if tmp then     	if tmp == 'endCollapsible' then return formaterror('collapsible-block-empty')         else return formaterror('collapsible-block-no-first-row') ..  q.isKeyword(rows[i], i, rows) --no valid keywords that can follow "startCollapsible"         end     end     if q.collapsibles == -1 then q.collapsibles = 1 else q.collapsibles = q.collapsibles + 1 end--q.collapsibles == -1 means there are no collapsibles at all; 0 - all closed; >0 - some not closed     local collapsed, replace, props = params[2], params[3] or '', properties(table.concat(params, '-', 4))--params[1] is the keyword name so all indices are shifted by one.     if collapsed == nil or collapsed == '' then collapsed = 'collapsed' end     if props['bg'] == nil or props['bg'] == '' then props['bg'] = 'transparent' ; props['bg-replace'] = q.bg else props['bg-replace'] = props['bg'] end     local mode, float, result     if q.rinfo1_pad == '' then mode = 'collapsible ' ; float = 'float:right;'     else mode = 'mw-collapsible mw-' ; float = ''     end     result = mw.ustring.format(i18n.html["row-collapsible-begin-fmt"], props['bg'], mode, collapsed, float)     tmp = row(rows[i], true, nil)     local linfo4_3_2_fmt, rinfo2_3_4_fmt = '', ''     if q.rinfo1_pad == '' then         if tmp['linfo4'] ~= '' or tmp['linfo3+2'] ~= '' then linfo4_3_2_fmt = mw.ustring.format(i18n.html['row-collapsible-left-linfo4+3+2-fmt'], tmp['linfo4'], tmp['linfo3+2']) end         result = result .. mw.ustring.format(i18n.html['row-general-fmt'], mw.ustring.format(i18n.html['row-collapsible-left-button-fmt'], i18n.html['row-collapsible-left-button-width'], q.text_width[1]),         	'1', q.text_width[2], linfo4_3_2_fmt, q.linfo1_pad, q.text_width[3], tmp['linfo1'], tmp['rowProp']['bg'], tmp['cells'], '', '', '', '1', '', '', mw.ustring.format(i18n.html['row-rinfo4-fmt'], '', ''))     else         if tmp['rinfo4'] ~= '' or tmp['rinfo2+3'] ~= '' then rinfo2_3_4_fmt = mw.ustring.format(i18n.html['row-collapsible-right-rinfo2+3+4-fmt'], tmp['rinfo2+3'], tmp['rinfo4']) end         result = result .. mw.ustring.format(i18n.html['row-general-fmt'], mw.ustring.format(i18n.html['row-linfo4-fmt'], q.text_width[1], tmp['linfo4']),         	'1', q.text_width[2], tmp['linfo3+2'], q.linfo1_pad, q.text_width[3], tmp['linfo1'], tmp['rowProp']['bg'], tmp['cells'], q.rinfo1_pad, q.text_width[4], tmp['rinfo1'],         	'1', q.text_width[5], rinfo2_3_4_fmt, mw.ustring.format(i18n.html['row-collapsible-right-button-fmt'], i18n.html['row-collapsible-right-button-width'], q.text_width[6]))     end     if replace ~= '' then         if q.isKeyword(rows[i + 1], i, rows, true) then return result .. formaterror('collapsible-block-no-replacement') end--a plain row needed for replacement         table.remove(rows, i)         tmp = row(rows[i], true, nil)         local padding, right = i18n.html['row-collapsible-right-button-width'] .. ' 0 0', ''         if q.rinfo1_pad == '' then padding = '0 0 ' .. i18n.html['row-collapsible-left-button-width'] ; right = 'right:0px;' end         result = result .. mw.ustring.format(i18n.html['row-collapsible-replace-begin-fmt'], padding, right, props['bg-replace'])         linfo4_3_2_fmt = '' ; rinfo2_3_4_fmt = ''         if q.rinfo1_pad == '' then             if tmp['linfo4'] ~= '' or tmp['linfo3+2'] ~= '' then linfo4_3_2_fmt = mw.ustring.format(i18n.html['row-collapsible-left-linfo4+3+2-fmt'], tmp['linfo4'], tmp['linfo3+2']) end             result = result .. mw.ustring.format(i18n.html['row-general-fmt'], mw.ustring.format(i18n.html['row-linfo4-fmt'], '', ''), '1', q.text_width[2], linfo4_3_2_fmt,             	q.linfo1_pad, q.text_width[3], tmp['linfo1'], tmp['rowProp']['bg'], tmp['cells'], '', '', '', '1', '', '', mw.ustring.format(i18n.html['row-rinfo4-fmt'], '', ''))         else             if tmp['rinfo4'] ~= '' or tmp['rinfo2+3'] ~= '' then rinfo2_3_4_fmt = mw.ustring.format(i18n.html['row-collapsible-right-rinfo2+3+4-fmt'], tmp['rinfo2+3'], tmp['rinfo4']) end             result = result .. mw.ustring.format(i18n.html['row-general-fmt'], mw.ustring.format(i18n.html['row-linfo4-fmt'], q.text_width[1], tmp['linfo4']), '1', q.text_width[2],             	tmp['linfo3+2'], q.linfo1_pad, q.text_width[3], tmp['linfo1'], tmp['rowProp']['bg'], tmp['cells'], q.rinfo1_pad, q.text_width[4], tmp['rinfo1'], '1', q.text_width[5],             	rinfo2_3_4_fmt, mw.ustring.format(i18n.html['row-rinfo4-fmt'], '', ''))         end         result = result .. i18n.html['row-collapsible-replace-end-fmt']     end     return result end q['endCollapsible'] = function(params, i, rows)     if q.collapsibles > 0 then         q.collapsibles = q.collapsibles - 1         return i18n.html['row-collapsible-end-fmt']     else         return formaterror('collapsible-block-not-open')     end end q['colspan'] = function(params, i, rows)     if params[2] == 'end' then return '' end     local tmp, j, nrows, props = {}, 0, tonumber(params[2]), properties(table.concat(params, '-', 3))     if nrows ~= 0 then table.remove(rows, i) end     if nrows == nil then nrows = #rows - i + 1 end     while j < nrows and i <= #rows do         j = j + 1         if rows[i] == '-colspan-end'  then             j = nrows         else             table.insert(tmp, rows[i])         end         if nrows ~= j or i == #rows then table.remove(rows, i) end     end     if j < nrows then j = formaterror('colspan-less-rows-than-set',j) else j = '' end     return mw.ustring.format(i18n.html['colspan-fmt'], j, props['bg'] or '', props['align'] or '', props['style'] or '', mw.getCurrentFrame():preprocess(table.concat(tmp, '\n'))) end q['filler'] = function(params, i, rows) 	local tmp, height = table.concat(params, '-', 3), '5px'     if #params < 3 or tmp == '' then return formaterror('parameter-missing') end--TODO: указать имя нужного параметра. 	if params[2] ~= '' then height = params[2] end     return row(tmp, nil, height) end  function p.RGBbyCode(frame)     return RGBbyCode(mw.text.trim(frame.args[1] or '')) end  function p.route(frame)     local rows, tmp = mw.text.trim(frame.args['pattern'] or ''), {}     if rows == '' then return formaterror('parameter-missing') end     if mw.text.trim(frame.args['bg'] or '') ~= '' then q.bg = frame.args['bg'] end     tmp = mw.text.split(mw.text.trim(frame.args['text-width'] or ''), ',')     if #tmp == 6 then         for i = 1, 6 do if tmp[i] ~= '' then if tonumber(string.sub(tmp[i],-1)) then q.text_width[i] = 'width:' .. tmp[i] .. 'px;' else q.text_width[i] = 'width:' .. tmp[i] .. ';' end end end         if tmp[4] == '' and tmp[5] == '' and tmp[6] == '' then q.rinfo1_pad = ''--padding for rinfo1 column = 0, not 3px             elseif tmp[1] == '' and tmp[2] == '' and tmp[3] == '' then q.linfo1_pad = '' end--padding for linfo1 column = 0, not 3px     elseif #tmp == 3 then         for i = 1, 3 do if tmp[i] ~= '' then if tonumber(string.sub(tmp[i],-1)) then q.text_width[i + 3] = 'width:' .. tmp[i] .. 'px;' else q.text_width[i + 3] = 'width:' .. tmp[i] .. ';' end end end         q.linfo1_pad = ''     elseif #tmp == 1 and tmp[1]~='' then         if tonumber(string.sub(tmp[1],-1)) then q.text_width[5] = 'width:' .. tmp[1] .. 'px;' else q.text_width[5] = 'width:' .. tmp[1] .. ';' end         q.linfo1_pad = ''     end     tmp = {}          rows = mw.text.split(rows, '\n')     local i, j = next(rows), next(rows, i)--removing empty lines     while j ~= nil do         if mw.text.trim(rows[j]) == '' then table.remove(rows, j) else i = j end         j = next(rows, i)     end          for i, v in ipairs(rows) do         local keyword = q.isKeyword(v, i, rows)         if type(keyword) ~= "string" then table.insert(tmp, row(v, nil, nil)) else table.insert(tmp, keyword) end     end          if q.collapsibles > 0 then table.insert(tmp, formaterror('collapsible-block-not-closed') .. q['endCollapsible']()) end     if q.collapsibles ~= -1 then if q.rinfo1_pad == '' then q.text_width[1] = q.text_width[1] .. 'min-width:' .. i18n.html['row-collapsible-left-button-width'] .. ';'                                                        else q.text_width[6] = q.text_width[6] .. 'min-width:' .. i18n.html['row-collapsible-right-button-width'] .. ';' end end     -- ↓ empty row to set column widths; ↑ if q.collapsibles ≠ -1 and there are collapsible sections, leftmost or rightmost column should be wide enough to accomodate the button     table.insert(tmp, mw.ustring.format(i18n.html['empty-row-fmt'], q.text_width[1], q.text_width[2], q.linfo1_pad, q.text_width[3], q.rinfo1_pad, q.text_width[4], q.text_width[5], q.text_width[6]))     return table.concat(tmp) end  return p  --[[for testing in console:  print(p.route({['args']={['text-width']='',['pattern']=[=[ STR STR]=]}}))  ]]