Module:Routemap
Appearance
This module is rated as ready for general use. It has reached a mature form and is thought to be relatively bug-free and ready for use wherever appropriate. It is ready to mention on help pages and other Wikipedia resources as an option for new users to learn. To reduce server load and bad output, it should be improved by sandbox testing rather than repeated trial-and-error editing. |
This Lua module is used on approximately 28,000 pages and changes may be widely noticed. Test changes in the module's /sandbox or /testcases subpages, or in your own module sandbox. Consider discussing changes on the talk page before implementing them. |
Transwikiing this module requires TemplateStyles, Module:Navbar and Module:Color contrast. |
Related pages |
---|
This module depends on the following other modules: |
This module handles the {{Routemap}}, {{BSsplit}}, {{BSto}}, {{BScvt}}, {{BSsrws}}, {{RoutemapRoute}} and {{RoutemapRouteIcon}} templates. For usage instructions, see the respective templates' pages.
For sandbox test results, see Template:Routemap/testcases (purged) and Template:BSsplit/testcases (purged).
Tracking categories are at Category:Routemap tracking categories.
To do
- Make it easier to change sizes
- Long-term: Add support for merging and splitting table cells
- Add a link to where the TemplateStyles are defined
local i18n = {
errors = {
["parameter-missing"] = "Missing parameter!",
["collapsible-block-not-closed"] = "Collapsible section not closed properly!",
["collapsible-block-not-open"] = "Missing start-Collapsible markup!",
["collapsible-block-empty"] = "Collapsible section must not be empty!",
["collapsible-block-no-first-row"] = "Invalid first row of collapsible section!",
["collapsible-block-no-replacement"] = "Invalid collapsible replacement row!",
["colspan-less-rows-than-set"] = "Invalid colspan set!",
},
["error-categories"] = {
default = '[[Category:Pages using Routemap with errors]]',
["text-images"] = '[[Category:Pages using Routemap with text images]]',
["separate-navbar"] = '[[Category:Pages using Routemap with a separate navbar template]]',
["missing-text-values"] = '[[Category:Pages using BSto or BSsrws with missing text values]]',
["br-tags"] = '[[Category:Pages using BSto, BSsplit, BSsrws or BScvt with br tags]]',
["srws"] = '[[Category:Pages using BSsplit instead of BSsrws]]',
["rmr-error"] = '[[Category:Pages with bad value for RoutemapRoute template]]'
},
text = {
navbar_mini = false, -- for navbar pos 2 only
navbar_text = 'This diagram:', -- for navbar pos 2 only
legend_text = 'Legend',
legend = {
default = '[[Template:Railway line legend',
track = '[[Template:Railway track legend',
bus = '[[Template:Bus route legend',
road = '[[Template:Roads legend',
canal = '[[Template:Waterways legend',
water = '[[Template:Waterways legend',
waterway = '[[Template:Waterways legend',
foot = '[[Template:Trails legend',
footpath = '[[Template:Trails legend',
walkway = '[[Template:Trails legend'
}
},
html = {
["cell-icon-fmt"] = '<div style="%s">[[File:BSicon_%s.svg|x20px|link=%s|alt=|%s]]%s</div>',
["cell-overlapicon-fmt"] = '<div class="RMic" style="%s">[[File:BSicon_%s.svg|x20px|link=%s|alt=|%s]]</div>',
["cell-icon-fmt-with-overlap"] = '<div style="%s"><div class="RMov">%s</div><div%s>[[File:BSicon_%s.svg|x20px|link=|alt=|%s]]%s</div></div>',
["cell-text-fmt"] = '<div class="RMtx RM%s" style="%s"><div%s style="%s" title="%s">%s%s%s%s%s%s</div></div>',
["cell-overlaptext-fmt"] = '<div class="RMtx RM%s" style="%s"><div%s style="%s" title="%s">%s%s%s%s%s</div></div>',
["cell-text-fmt-with-overlap"] = '<div class="RM%s" style="%s"><div class="RMov">%s</div><div class="RMtx RM%s" style="%s"><div%s style="%s" title="%s">%s%s%s%s%s%s</div></div></div>',
["cell-empty-fmt"] = '<div class="RM%s" style="%s">%s</div>',
["cell-empty-fmt-with-overlap"] = '<div style="%s"><div class="RMov">%s</div><div class="RMsp RM%s" style="%s">%s</div></div>',
["cell-filler-fmt"] = '<div class="RMf_" style="%s"><div class="RMfm" style="background:%s"></div></div>',
["cell-filler-empty-fmt"] = '<div class="RMf_ RM%s" style="%s"></div>',
["row-linfo4-fmt"] = '\
|class="RMl4" style="%s"|<div class="RMsi">%s</div>',-- parameters:linfo4-width, linfo4
["row-linfo3-fmt"] = '<div class="RMsi">%s</div> ',
["row-rinfo3-fmt"] = ' <div class="RMsi">%s</div>',
["row-rinfo4-fmt"] = '\
|class="RMr4" style="%s"|<div class="RMsi">%s</div>',-- parameters:rinfo4-width, rinfo4
["row-general-fmt"] = '\
|- %s\
|class="RMl" colspan="%s" style="%s"|%s\
|%s style="%s"|<div class="RMsi">%s</div>\
|class="RMir" style="%s"|%s\
|%s style="%s"|<div class="RMsi">%s</div>\
|class="RMr" colspan="%s" style="%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"] = '\
|- style="line-height:1"\
|colspan="7" style="padding:0 !important;background:%s"|\
{|class="RMcollapse %s%s" style="%s"',-- parameters: bg, "collapsible "/"mw-collapsible mw-", collapse-state, "float:right" / ""
["row-collapsible-end-fmt"] = '\n|}',
["row-collapsible-left-button-width"] = '45px',-- 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" style="line-height:1;width:100%%;padding:0 !important;margin:0 !important"\
|style="padding:0 3px 0 1px;text-align:left"|<div class="RMsi">%s</div>\
|style="text-align:right"| %s\
|}',-- parameters: linfo4, linfo3+2
["row-collapsible-right-button-width"] = '45px',-- 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" style="line-height:1;width:100%%;padding:0 !important;margin:0 !important"\
|style="text-align:left"| %s\
|style="padding:0 1px 0 3px;text-align:right"|<div class="RMsi">%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"] = '\
|- style="line-height:1"\
|colspan="7" style="padding:0 %s"|<div style="position:relative">\
{| class="RMreplace" style="%sbackground:%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:%s;text-align:%s;%s"|\n%s',
["empty-row-fmt"] = '\n|-\n|style="padding:0 3px 0 0;%s"|\n|style="%s"|\n|%s style="%s"|\n|\n|%s style="%s"|\n|style="%s"|\n|style="padding:0 0 0 3px;%s"|'
}
}
local p,q={},{}
local getArgs = require('Module:Arguments').getArgs
local function makeInvokeFunction(funcName)
-- makes a function that can be returned from #invoke, using
-- [[Module:Arguments]].
return function (frame)
local args = getArgs(frame, {parentOnly = true})
return p[funcName](args)
end
end
local function makeTemplateFunction(funcName)
-- makes a function for calling via #invoke within a template
return function (frame)
local args = getArgs(frame, {frameOnly = true})
return p[funcName](args)
end
end
local function formaterror(key,param)
local result = string.format(i18n.html['colspan-fmt'], '', '', '', '', '<span class="error">' .. string.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['error-categories'][key] or i18n['error-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', excarrot = 'F1BA76', ex_excarrot = 'F3D2A9',
cerulean = '1A8BB9', ex_cerulean = '73B7D3',
cyan = '40E0D0', ex_cyan = '8AEAE1',
deepsky = '00BFFF', ex_deepsky = '7FDFFF',
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', exruby = 'DE64A1', ex_exruby = 'E89FC4',
saffron = 'FFAB2E', ex_saffron = 'FFC969',
sky = '069DD3', ex_sky = '67C2E3',
steel = 'A1B3D4', ex_steel = 'C4CFE3',
teal = '339999', ex_teal = '82C0C0', exteal = '82C0C0', ex_exteal = 'B1D6D6',
violet = '800080', ex_violet = 'B164B1',
yellow = 'FFD702', ex_yellow = 'FFEB81',
}
return colors[code] or colors.bahn
end
p.RGBbyCode = makeInvokeFunction('_RGBbyCode')
function p._RGBbyCode(args)
return RGBbyCode(args[1])
end
local function properties(str)
--str is a combination of properties with following syntax:
--property name=value,property name1=value1,property name2=value2 and so on
local result = {}
for i, v in ipairs(mw.text.split(str, ',')) do
if v then
local t = mw.text.split(v, '=')
if string.find(v, '=') then
t[1] = mw.text.trim(t[1]) --trim parameter names
table.insert(result, t[1])
result[t[1]] = t[2] or '' --fill table with pairs "property"="value"
elseif result[result[i - 1]] then
table.insert(result, result[i - 1])
result[result[i]] = result[result[i]]..','..t[1] --if no equals sign then tack t[1] onto the previous result
else
table.insert(result, '~~')
end
else
table.insert(result, '~~')
end
end
return result
end
local function positive(x)
if not x then return nil else x = string.lower(x) end
if x == 'yes' or x == 'y' or x == '1' or x == 'true' then return 1 end
end
local function negative(x)
if not x then return nil else x = string.lower(x) end
if x == 'no' or x == 'n' or x == '0' or x == 'false' then return 0 end
end
local function alignment(x, y, z)
if not x then return nil end
local directions = {
['inherit-left'] = {'l', 'left',},
['inherit-right'] = {'r', 'right',},
['top-inherit'] = {'a', 't', 'top',},
['bottom-inherit'] = {'e', 'b', 'bottom',},
['top-left'] = {'la', 'tl', 'c4', 'nw', 'top-left', 'topleft',},
['top-right'] = {'ra', 'tr', 'c1', 'ne', 'top-right', 'topright',},
['bottom-left'] = {'le', 'bl', 'c3', 'sw', 'bottom-left', 'bottomleft',},
['bottom-right'] = {'re', 'br', 'c2', 'se', 'bottom-right', 'bottomright',},
['inherit-center'] = {'c', 'center', 'centre',},
['middle-inherit'] = {'m', 'middle',},
['top-center'] = {'ma', 'tc', 'top-center', 'top-centre', 'topcenter', 'topcentre',},
['bottom-center'] = {'me', 'bc', 'bottom-center', 'bottom-centre', 'bottomcenter', 'bottomcentre',},
['middle-left'] = {'lm', 'ml', 'middle-left', 'middleleft',},
['middle-right'] = {'rm', 'mr', 'middle-right', 'middleright',},
['middle-center'] = {'cm', 'mc', 'middle-center', 'middle-centre', 'middlecenter', 'middlecentre',},
}
for k, v in pairs(directions) do
for _, name in ipairs(v) do
if x:lower() == name then
local values = mw.text.split(k, '-')
if values[1] == 'inherit' then values[1] = y end
if values[2] == 'inherit' then values[2] = z end
return values
end
end
end
return {y, z}
end
local function cell(icon,overlapIcons,rowProps)--[[
Icon handling. Each icon is defined as in the following example:
icon ID!~overlap icon ID!@image link target
Values for an icon ID containing "*" are treated as text, with the letter(s) before "*" as width prefix(es).
No limit on overlapping icons or text; just separate them by "!~".
Parameters can be added after every object, separated to the left by "!_". This, if there is a link, must be after the link.
Parameters for individual objects in an overlapping stack can also be added, separated to the left by "__".
Unless a link is provided, each cell will have mouseover text indicating its contents.
]]
local tmp, tmp2, cellProps, iconProps, overlapProps, tmp_sep, link, tracking, icontext, iconpre = {}, {}, {}, {}, {}, '', '', ''
if #overlapIcons > 0 then
tmp = mw.text.split(overlapIcons[#overlapIcons], '!_')
if #tmp > 1 then overlapIcons[#overlapIcons], cellProps = tmp[1], properties(tmp[2]) end
tmp = mw.text.split(overlapIcons[#overlapIcons], '!@')
overlapIcons[#overlapIcons] = tmp[1]
if #tmp > 1 then link = tmp[2] end
tmp = mw.text.split(icon, '__')
icon = tmp[1]
if #tmp > 1 then iconProps = properties(tmp[2]) end
for i, v in ipairs(overlapIcons) do
tmp = mw.text.split(v, '__')
overlapIcons[i] = mw.text.trim(tmp[1])
if #tmp > 1 then overlapProps[i] = properties(tmp[2]) else overlapProps[i] = {} end
end
else
tmp = mw.text.split(icon, '[!_]_')
if #tmp > 1 then icon, cellProps = tmp[1], properties(tmp[2]) end
tmp = mw.text.split(icon, '!@')
icon = mw.text.trim(tmp[1])
if #tmp > 1 then link = tmp[2] end
end
if #overlapIcons > 0 and icon ~= '' then tmp_sep = '; ' end
local icontip = mw.text.nowiki(mw.text.unstripNoWiki(icon..tmp_sep..table.concat(overlapIcons, '; ')))
local textspl = string.find(icon, '%*')
if textspl then
icontext = mw.text.trim(mw.ustring.sub(icon, textspl + 1))
if textspl ~= 1 then iconpre = mw.text.trim(mw.ustring.sub(icon, 1, textspl - 1)) end
end
cellProps.class = ''
if cellProps.style then cellProps.style = ';'..cellProps.style else cellProps.style = '' end
cellProps.bg = cellProps.bg or cellProps.background or cellProps.bgcolor
if cellProps.bg then cellProps.style = cellProps.style..';background:'..cellProps.bg end
if #overlapIcons > 0 or icontext then
cellProps._before, cellProps._after = rowProps._before or '', rowProps._after or ''
cellProps.color = cellProps.color or cellProps.colour ; cellProps.bold = cellProps.bold or cellProps.b ; cellProps.italic = cellProps.italic or cellProps.i or cellProps.it
if cellProps.color then cellProps.style = cellProps.style..';color:'..cellProps.color end
if positive(cellProps.italic) then cellProps.style = cellProps.style..';font-style:italic' end
if positive(cellProps.bold) then cellProps.style = cellProps.style..';font-weight:bold' end
if not cellProps.fontsize or rowProps.fontsize or cellProps.fontsize == 'info' then
elseif cellProps.fontsize == 'cmt' or cellProps.fontsize == 'comment' then
cellProps._before, cellProps._after = '<div class="RMsi">', '</div>'
else
cellProps.style = cellProps.style..';font-size:'..cellProps.fontsize
end
if cellProps.align or rowProps.align then
rowProps.align = rowProps.align or {'middle', 'center'}
cellProps.align = alignment(cellProps.align, rowProps.align[1], rowProps.align[2]) or rowProps.align
cellProps.style, cellProps.textfmt = cellProps.style..';text-align:'..cellProps.align[2], ';vertical-align:'..cellProps.align[1]
else
cellProps.textfmt = ''
end
end
if #overlapIcons > 0 then
tmp = {}
for i, v in ipairs(overlapIcons) do
local thislink = link
if i ~= #overlapIcons then thislink = '' end
if thislink and thislink ~= '' then icontip = thislink end
if overlapProps[i].style then overlapProps.style = ';'..overlapProps[i].style else overlapProps.style = '' end
overlapProps[i].bg = overlapProps[i].bg or overlapProps[i].background or overlapProps[i].bgcolor
if overlapProps[i].bg then overlapProps.style = overlapProps.style..';background:'..overlapProps[i].bg end
local tmp_textspl = string.find(v, '%*')
if tmp_textspl then
overlapProps.class = ''
local tmp_icontext, tmp_iconpre = mw.text.trim(mw.ustring.sub(v, tmp_textspl + 1)), ''
if tmp_textspl ~= 1 then tmp_iconpre = mw.text.trim(mw.ustring.sub(v, 1, tmp_textspl - 1)) end
overlapProps._before, overlapProps._after = cellProps._before, cellProps._after
overlapProps[i].color = overlapProps[i].color or overlapProps[i].colour ; overlapProps[i].bold = overlapProps[i].bold or overlapProps[i].b ; overlapProps[i].italic = overlapProps[i].italic or overlapProps[i].i or overlapProps[i].it
if overlapProps[i].color then overlapProps.style = overlapProps.style..';color:'..overlapProps[i].color end
if positive(overlapProps[i].italic) then overlapProps.style = overlapProps.style..';font-style:italic' end
if positive(overlapProps[i].bold) then overlapProps.style = overlapProps.style..';font-weight:bold' end
if rowProps.fontsize or cellProps.fontsize or overlapProps[i].fontsize == 'info' then
elseif not overlapProps[i].fontsize then
overlapProps.class = ' class="RMts"'
elseif overlapProps[i].fontsize == 'cmt' or overlapProps[i].fontsize == 'comment' then
overlapProps._before, overlapProps._after = '<div class="RMsi">', '</div>'
else
overlapProps.style = overlapProps.style..';font-size:'..overlapProps[i].fontsize
end
if overlapProps[i].align or cellProps.align then
cellProps.align = cellProps.align or {'middle', 'center'}
overlapProps.align = alignment(overlapProps[i].align, cellProps.align[1], cellProps.align[2]) or cellProps.align
overlapProps.style, overlapProps.textfmt = overlapProps.style..';text-align:'..overlapProps.align[2], ';vertical-align:'..overlapProps.align[1]
else
overlapProps.style, overlapProps.textfmt = overlapProps.style..';text-align:center', ';vertical-align:middle'
end
if overlapProps[i].abbr then
overlapProps.tag = {'<abbr title="'..string.gsub(overlapProps[i].abbr, '"', '"')..'">', '</abbr>'}
else
overlapProps.tag = {'', ''}
end
table.insert(tmp, string.format(i18n.html['cell-overlaptext-fmt'], (tmp_iconpre and tmp_iconpre ~= '' and tmp_iconpre or '_'), overlapProps.style, overlapProps.class, overlapProps.textfmt, icontip, overlapProps.tag[1], overlapProps._before, tmp_icontext, overlapProps._after, overlapProps.tag[2]))
else
v = mw.text.trim(v)
if string.find(v, 'num') then
if not string.find(v, 'numN%d+') then tracking = tracking..(i18n['error-categories']['text-images'] or i18n['error-categories'].default) end
end
table.insert(tmp, string.format(i18n.html['cell-overlapicon-fmt'], overlapProps.style, v, thislink, icontip))
end
end
if iconProps.style then tmp2[1] = true else iconProps.style = '' end
iconProps.bg = iconProps.bg or iconProps.background or iconProps.bgcolor
if iconProps.bg then iconProps.style = iconProps.style..';background:'..iconProps.bg end
if string.match(icon, '^[%+_]?o?c?d?b?s?w?$') then
if tmp2[1] then iconProps.style = ';'..iconProps.style end
return string.format(i18n.html['cell-empty-fmt-with-overlap'], cellProps.style, mw.text.trim(table.concat(tmp)), (string.match(icon, '^.+$') or '_'), iconProps.style, tracking)
elseif icontext then
iconProps.class = ''
iconProps._before, iconProps._after = cellProps._before, cellProps._after
iconProps.color = iconProps.color or iconProps.colour ; iconProps.bold = iconProps.bold or iconProps.b ; iconProps.italic = iconProps.italic or iconProps.i or iconProps.it
if iconProps.color then iconProps.style = iconProps.style..';color:'..iconProps.color end
if positive(iconProps.italic) then iconProps.style = iconProps.style..';font-style:italic' end
if positive(iconProps.bold) then iconProps.style = iconProps.style..';font-weight:bold' end
if rowProps.fontsize or cellProps.fontsize or iconProps.fontsize == 'info' then
elseif not iconProps.fontsize then
iconProps.class = ' class="RMts"'
elseif iconProps.fontsize == 'cmt' or iconProps.fontsize == 'comment' then
iconProps._before, iconProps._after = '<div class="RMsi">', '</div>'
else
iconProps.style = iconProps.style..';font-size:'..iconProps.fontsize
end
if iconProps.align or cellProps.align then
cellProps.align = cellProps.align or {'middle', 'center'}
iconProps.align = alignment(iconProps.align, cellProps.align[1], cellProps.align[2]) or cellProps.align
iconProps.style, iconProps.textfmt = iconProps.style..';text-align:'..iconProps.align[2], ';vertical-align:'..iconProps.align[1]
else
iconProps.style, iconProps.textfmt = iconProps.style..';text-align:center', ';vertical-align:middle'
end
if iconProps.abbr then
iconProps.tag = {'<abbr title="'..string.gsub(iconProps.abbr, '"', '"')..'">', '</abbr>'}
else
iconProps.tag = {'', ''}
end
if tmp2[1] then iconProps.style = ';'..iconProps.style end
return string.format(i18n.html['cell-text-fmt-with-overlap'], (iconpre and iconpre ~= '' and iconpre or '_'), cellProps.style, mw.text.trim(table.concat(tmp)), (iconpre and iconpre ~= '' and iconpre or '_'), iconProps.style, iconProps.class, iconProps.textfmt, icontip, iconProps.tag[1], iconProps._before, icontext, iconProps._after, tracking, iconProps.tag[2])
else
if iconProps.style ~= '' then iconProps.style = string.gsub(' style="'..iconProps.style..'"', '";', '"', 1) end
if string.find(icon, 'num') then
if not string.find(icon, 'numN%d+') then tracking = tracking..(i18n['error-categories']['text-images'] or i18n['error-categories'].default) end
end
return string.format(i18n.html['cell-icon-fmt-with-overlap'], cellProps.style, mw.text.trim(table.concat(tmp)), iconProps.style, icon, icontip, tracking)
end
end
if string.match(icon, '^[%+_]?o?c?d?b?s?w?$') then
return string.format(i18n.html['cell-empty-fmt'], (string.match(icon, '^.+$') or '_'), cellProps.style, tracking)
else
if link and link ~= '' then icontip = link end
if icontext then
if not cellProps.fontsize and not rowProps.fontsize then cellProps.class = ' class="RMts"' end
if cellProps.abbr then
cellProps.tag = {'<abbr title="'..string.gsub(cellProps.abbr, '"', '">')..'">', '</abbr>'}
else
cellProps.tag = {'', ''}
end
return string.format(i18n.html['cell-text-fmt'], (iconpre and iconpre ~= '' and iconpre or '_'), cellProps.style, cellProps.class, cellProps.textfmt, icontip, cellProps.tag[1], cellProps._before, icontext, cellProps._after, tracking, cellProps.tag[2])
else
if string.find(icon, 'num') then
if not string.find(icon, 'numN%d+') then tracking = tracking..(i18n['error-categories']['text-images'] or i18n['error-categories'].default) end
end
return string.format(i18n.html['cell-icon-fmt'], cellProps.style, icon, link, icontip, tracking)
end
end
end
local function fillercell(code, height)
--Creates a 5px-high row.
--Values in icon pattern can only be [blank], d, [BSicon color] or #[hex triplet].
height = mw.text.trim(height)
if height ~= '' then
if tonumber(height) then height = height..'px' end
height = 'height:'..height..';min-height:'..height
end
if string.match(code, '^[%+_]?o?c?d?b?s?w?$') then
return string.format(i18n.html['cell-filler-empty-fmt'], (string.match(code, '^.+$') or '_'), height)
elseif mw.ustring.sub(code,1,1) == '#' then
return string.format(i18n.html['cell-filler-fmt'], height, code)
else
return string.format(i18n.html['cell-filler-fmt'], height, '#'..RGBbyCode(code))
end
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'] = '', rowstyle = '', ['cells'] = {}, ['rinfo1'] = '', ['rinfo2+3'] = '', ['rinfo4'] = '', ['rowProp'] = {}}
local lcolspan, rcolspan, linfo4_fmt, rinfo4_fmt = '2', '2', '', ''
local left, right, 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'] = string.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 = string.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'] .. string.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 = string.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'])
-- The below parameter functions are passed through to the cells.
if result.rowProp.fontsize == 'cmt' or result.rowProp.fontsize == 'comment' then
result.rowProp._before, result.rowProp._after = '<div class="RMsi">', '</div>'
end
if result.rowProp.align then
result.rowProp.align = alignment(result.rowProp.align, 'middle', 'center') or {'middle', 'center'}
end
icons = mw.text.split(tmp[1], '\\')--splitting the string of icons first by "\"
if type(filler) == 'string' then
result.style = ';font-size:0px'
for i, v in ipairs(icons) do table.insert(result['cells'], fillercell(v, filler)) end--no !@ or !~ for filler row
else
result.style = ''
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], result.rowProp)) end
end
result['cells'] = table.concat(result['cells'])
if result.rowProp.style then result.style = result.style..';'..result.rowProp.style end
result.rowProp.bg = result.rowProp.bg or result.rowProp.background or result.rowProp.bgcolor ; result.rowProp.color = result.rowProp.color or result.rowProp.colour ; result.rowProp.bold = result.rowProp.bold or result.rowProp.b ; result.rowProp.italic = result.rowProp.italic or result.rowProp.i or result.rowProp.it
if result.rowProp.bg then result.style = result.style..';background:'..result.rowProp.bg end
if result.rowProp.color then result.style = result.style..';color:'..result.rowProp.color end
if positive(result.rowProp.italic) then result.style = result.style..';font-style:italic' end
if positive(result.rowProp.bold) then result.style = result.style..';font-weight:bold' end
if result.rowProp.fontsize and result.rowProp._after == '' and result.rowProp.fontsize ~= 'info' then
result.style = result.style..';font-size:'..result.rowProp.fontsize
end
if noformatting then
return result
else
return string.format(i18n.html['row-general-fmt'], linfo4_fmt, lcolspan, '', result['linfo3+2'], q.linfo1_pad, '', result['linfo1'], result.style,
result['cells'], q.rinfo1_pad, '', result['rinfo1'], rcolspan, '', result['rinfo2+3'], rinfo4_fmt)
end
end
--↓ This table handles diagram rows beginning with a hyphen ("-").
q = {collapsibles = -1, text_width = {'', '', '', '', '', ''}, linfo1_pad = 'class="RMl1"', rinfo1_pad = 'class="RMr1"', bg = 'var(--background-color-neutral-subtle, #f8f9fa)'}
q.isKeyword = function(pattern, i, rows, justTest)
if mw.ustring.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 = string.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 = string.format(i18n.html['row-collapsible-left-linfo4+3+2-fmt'], tmp['linfo4'], tmp['linfo3+2']) end
result = result .. string.format(i18n.html['row-general-fmt'], string.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.style, tmp['cells'], '', '', '', '1', '', '', string.format(i18n.html['row-rinfo4-fmt'], '', ''))
else
if tmp['rinfo4'] ~= '' or tmp['rinfo2+3'] ~= '' then rinfo2_3_4_fmt = string.format(i18n.html['row-collapsible-right-rinfo2+3+4-fmt'], tmp['rinfo2+3'], tmp['rinfo4']) end
result = result .. string.format(i18n.html['row-general-fmt'], string.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.style, tmp['cells'], q.rinfo1_pad, q.text_width[4], tmp['rinfo1'],
'1', q.text_width[5], rinfo2_3_4_fmt, string.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 .. string.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 = string.format(i18n.html['row-collapsible-left-linfo4+3+2-fmt'], tmp['linfo4'], tmp['linfo3+2']) end
result = result .. string.format(i18n.html['row-general-fmt'], string.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.style, tmp['cells'], '', '', '', '1', '', '', string.format(i18n.html['row-rinfo4-fmt'], '', ''))
else
if tmp['rinfo4'] ~= '' or tmp['rinfo2+3'] ~= '' then rinfo2_3_4_fmt = string.format(i18n.html['row-collapsible-right-rinfo2+3+4-fmt'], tmp['rinfo2+3'], tmp['rinfo4']) end
result = result .. string.format(i18n.html['row-general-fmt'], string.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.style, tmp['cells'], q.rinfo1_pad, q.text_width[4], tmp['rinfo1'], '1', q.text_width[5],
rinfo2_3_4_fmt, string.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 string.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), (params[2] or '')
if #params < 3 or tmp == '' then return formaterror('parameter-missing') end--TODO: specify the name of the parameter
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
local function localroute(pattern,ptw,pbg,process)
local tmp = {}
if mw.text.trim(pbg) ~= '' then q.bg = pbg end
tmp = mw.text.split(mw.text.trim(ptw), '%s*,%s*')
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;min-width:' .. tmp[i] .. 'px;'
else
q.text_width[i] = 'width:' .. tmp[i] .. ';min-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;min-width:' .. tmp[i] .. 'px;'
else
q.text_width[i + 3] = 'width:' .. tmp[i] .. ';min-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;min-width:' .. tmp[1] .. 'px;'
else
q.text_width[5] = 'width:' .. tmp[1] .. ';min-width:' .. tmp[1] .. ';'
end
q.linfo1_pad = ''
end
for i = 1, 6 do
tmp = tonumber(mw.ustring.match(q.text_width[i], ':([0-9]+%.?[0-9]*)px;'))
if tmp then
tmp = tmp*3/40
q.text_width[i] = 'width:' .. tmp .. 'em;min-width:' .. tmp .. 'em;'
end
end
tmp = {}
local index = 0
local rows = {}
if not process or process == '' or negative(process) then
pattern = mw.ustring.gsub(pattern, '\n(#[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F])', '%1')
end
for item in pattern:gmatch('([^\n]*)\n?') do
item = mw.text.trim(item)
if item ~= '' then
index = index + 1
rows[index] = item
end
end
if index == 0 then return formaterror('parameter-missing') 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, string.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 mw.ustring.gsub(mw.ustring.gsub(mw.ustring.gsub(mw.ustring.gsub(mw.ustring.gsub(mw.ustring.gsub(mw.ustring.gsub(table.concat(tmp), 'style=";* *', 'style="'), '\n| *style="" *|', '\n|'), ' ?style=""', ''), ' ?colspan="1"', ''), '<div class="RMsi"></div>', ''), 'class="RM%+', 'class="RM_'), '(class="[^"]* RM)%+', '%1_')
end
local function getArgNums(prefix, args)
-- Copied from Module:Infobox on enwiki.
-- Returns a table containing the numbers of the arguments that exist
-- for the specified prefix. For example, if the prefix were 'data', and
-- 'data1', 'data2', and 'data5' existed, this would return {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 greatercontrast = require('Module:Color contrast')._greatercontrast
local rgb_black = '#252525' -- class .mw-body in Mediawiki:Common.css
p.infobox = makeInvokeFunction('_infobox')
p.infoboxTemplate = makeTemplateFunction('_infobox')
function p._infobox(args) -- Creates a pretty box.
args.map1, args.tw, args['map1-title'], args['map1-collapsible'], args['map1-collapse'] = args.map1 or args.map, args.tw or args['text-width'] or args['text width'], args['map1-title'] or args['map-title'], args['map1-collapsible'] or args['map-collapsible'], args['map1-collapse'] or args['map1-collapsed'] or args['map-collapse'] or args['map-collapsed']
local function map_prefix(x) return 'map'..x end
local mapnums, prefix = {}
if args[1] and args[1] ~= '' then
prefix = tonumber
for k, v in pairs(args) do
if type(k) == 'number' then table.insert(mapnums, k) end
end
else
prefix = map_prefix
mapnums = getArgNums('map', args)
table.sort(mapnums)
end
local classes = {}
args['title bg color'] = args['title bg color'] or args['title bg'] or args['title-bg'] or '#27404E'
args['title color'] = args['title color'] or args['title-color'] or greatercontrast{args['title bg color'], '#FFF', rgb_black}
args.legend = args.legend or ''
local navbar = require('Module:Navbar').navbar
local navtable = {}
if args.navbar then
navtable = {args.navbar, mini = i18n.text.navbar_mini, text = i18n.text.navbar_text}
args.navbar = navbar(navtable)
else
args.navbar = ''
end
local result = ''
if args.inline then result = result..' \n' end
result = result..'{|'
args.collapse = args.collapse or args.collapsed
if args.inline or negative(args.collapsible) then
args.collapsible = '0'
else
table.insert(classes, 'collapsible')
if args.collapse then table.insert(classes, 'collapsed') end
end
args.float = args.float or 'right'
if args.float == 'right' then
args.margin = 'margin-left:1em;'
elseif args.float == 'left' then
args.margin = 'margin-right:1em;'
else
args.margin = ''
end
args.fontsize2 = 10000/88
if args.inline then
table.insert(classes, 'RMinline')
args.fontsize = 100
else
table.insert(classes, 'RMbox')
args.fontsize = 88 -- as above: CSS rule for .infobox in %
end
args.bg = args.bg or 'var(--background-color-neutral-subtle, f8f9fa)'
args.style = args.style or ''
result = result .. 'class="' .. table.concat(classes, ' ') .. '" cellspacing="0" cellpadding="0" style="float:' .. args.float .. ';clear:' .. args.float .. ';margin-top:0;margin-bottom:1em;' .. args.margin .. 'empty-cells:show;border-collapse:collapse;font-size:' .. args.fontsize .. '%;background:' .. args.bg .. ';color:inherit;' .. args.style .. '"'
args.title = args.title or ''
if args.inline or args.title == 'no' or args.title == '0' then
else
result = result .. '\n! style="color:' .. args['title color'] .. ';background:' .. args['title bg color'] .. ';text-align:center;padding:5px"|'
if args['navbar pos'] then
result = result .. '<div>'
else
if args.navbar ~= '' then
navtable.mini = true
navtable.brackets = true
navtable.style = 'float:left;margin-right:5px;white-space:nowrap'
navtable.fontstyle = 'font-size:'..args.fontsize2..'%;color:' .. args['title color']
args.navbar = navbar(navtable) .. '<div style="margin-left:55px">'
else
args.navbar = '<div>'
end
result = result .. args.navbar
end
result = result .. '<div style="white-space:nowrap;'
if args.collapsible == '0' and (args['navbar pos'] or args.navbar == '<div>') then
else
result = result .. 'margin-right:55px;'
if args['navbar pos'] or args.navbar == '<div>' then
result = result .. 'margin-left:55px;'
end
end
result = result .. 'font-size:'..args.fontsize2..'%">' .. args.title .. '</div></div>'
end
args.top = args.top or args['on top']
if args.top then
result = result .. '\n|-\n|style="padding:0px 5px;text-align:center;'..(args.topstyle or '')..'"|' .. args.top
end
result = result .. '\n|-\n|style="line-height:normal;padding:4px 5px"|'
if args.navbar ~= '' and args['navbar pos'] == '1' then
if not positive(args['navbar long']) and not negative(args['navbar mini']) then navtable.mini = true; args.navbar = navbar(navtable) end
result = result .. '<div style="float:left;padding-right:5px">' .. args.navbar .. '</div>'
end
args.legend2 = mw.ustring.lower(args.legend)
if args.legend2 ~= 'no' and args.legend2 ~= '0' then
args.legend = i18n.text.legend[args.legend2] or ((args.legend2 ~= '') and ('[['..args.legend) or i18n.text.legend.default)
args.legend = args.legend .. '|' .. (args['legend alt'] or i18n.text.legend_text) .. ']]'
result = result .. '<div class="selfreference noprint" style="text-align:right;font-size:97%">' .. args.legend .. '</div>'
end
if args.inline then args.padding2 = '0px' else args.padding2 = '0px 6px' end
for k, v in ipairs(mapnums) do
if not mapnums[k + 1] then
if not args.inline then args.padding2 = '0px 6px 6px' end
end
args.ending = ''
if args['map'..v..'-title'] then
args.header_margin = '0'
if k == 1 then args.border_header = 'border-bottom: 5px solid '..args.bg..';' else args.border_header = 'border-top: 5px solid '..args.bg..';border-bottom: 5px solid '..args.bg..';' end
if positive(args['map'..v..'-collapsible']) then
args.header_margin = '0 55px'
args.ending = '\n|}'
if positive(args['map'..v..'-collapse']) or positive(args['map'..v..'-collapsed']) then args.map_collapsed = ' mw-collapsed autocollapse' else args.map_collapsed = '' end
result = result..'\n|-\n|\n{|class="mw-collapsible'..args.map_collapsed..'" cellpadding="0" cellspacing="0" style="display:table;min-width:100%;margin:0 auto"'
end
if args.inline then args.header_style2 = ' style="line-height:normal"' else args.header_style2 = '' end
result = result..'\n|-'..args.header_style2..'\n!style="'..args.border_header..'padding:3px 5px;text-align:center;vertical-align:middle;color:'..rgb_black..';background:#EEEEEE" | <div style="margin:'..args.header_margin..';font-size:'..10000/args.fontsize..'%">'..args['map'..v..'-title']..'</div>'
args.border_top = ''
else
if k == 1 then args.border_top = '' else args.border_top = 'border-top: 5px solid '..args.bg..';' end
end
result = result .. '\n|-\n|style="'..args.border_top..'padding:' .. args.padding2 .. '"|\n{|class="nogrid routemap" style="font-size:'..(args.fontsize2 * .95)..'%"\n'..localroute(args[prefix(v)], (args['tw'..v] or args['text-width'..v] or args['text width'..v] or args.tw or ''), args.bg, args.process)..'\n|}'..args.ending
end
args.bottom = args.bottom or args.footnote
if args.bottom then
if args.inline then args.padding2 = '6px' else args.padding2 = '0px' end
result = result .. '\n|-\n|style="line-height:normal;text-align:right;padding:' .. args.padding2 .. ' 5px 5px;'..(args.bottomstyle or args.footnotestyle or '')..'"|' .. args.bottom
if string.find(args.bottom, '&action=edit') then result = result .. (i18n['error-categories']['separate-navbar'] or i18n['error-categories'].default) end
end
if args.navbar ~= '' and args['navbar pos'] == '2' then
if negative(args['navbar long']) or positive(args['navbar mini']) then navtable.mini = true; args.navbar = navbar(navtable) end
if args.inline and not args.bottom then args.padding2 = '6px' else args.padding2 = '0px' end
result = result .. '\n|-\n|style="line-height:normal;padding:' .. args.padding2 .. ' 5px 3px;text-align:center"|' .. args.navbar
end
return result .. '\n|}'
end
local function base(t1,t2,link,stn,italic,it,it2,bold,align,style,bg1,bg2,line,fs1,fs2,lh,v1,swap,inp,bs)
--Creates an inline table with two rows of text. Can be used in any Routemap text cell.
--Implemented in the BSsplit, BSto, BSsrws and BScvt templates.
if not align then
if bs == 'cvt' then
align = 'right'
else
align = 'inherit'
end
end
style = style or ''
local result = ' <table cellspacing="0" cellpadding="0" class="RMsplit" style="text-align:'..align
if italic or it == 'all' then result = result..';font-style:italic' end
if bold then result = result..';font-weight:bold' end
local rowstart = '<tr><td style="text-align:inherit;padding:0;line-height:'
result = result..';'..style..'">'..rowstart..lh
if line then result = result..';border-bottom:1px solid gray' end
local bgpad = ';padding-left:.5em;padding-right:.5em'
local function bgtext(v)
return ';color:'..greatercontrast{v, '#FFF', rgb_black}
end
if bg1 then
result = result..bgpad..bgtext(bg1)..';background:'..bg1
elseif bg2 then
result = result..bgpad
end
if fs1 then result = result..';font-size:'..fs1 end
result = result..'">'
if not t1 or string.find(t1, '^%s*$') then
if not t2 then t2 = '' end
if string.find(t2, '<br ?/?>') == nil then t1 = ' ' end
end
if not t2 or string.find(t2, '^%s*$') then
if not t1 then t1 = '' end
if string.find(t1, '<br ?/?>') == nil then t2 = ' ' end
end
if bs == 'srws' then
if stn then
link = t1..' '..t2..' '..stn
else
link = t1..' '..t2..' railway station'
end
elseif bs == 'cvt' then
local split, floor, outp, v2, mult = mw.text.split, math.floor
local function trim(x)
return string.gsub(x, '%s', '')
end
local cvt = { -- conversion values
['mi'] = 1.609344,
['ch'] = 20.1168,
['mi;ch'] = 80,
['m'] = 1 / .9144,
['yd'] = .9144,
['ft'] = .3048,
}
local sf = { -- 10 ^ floor(log10(cvt[inp]) + .5); or 10 ^ floor(log10(cvt[inps[1]] * cvt[inp]) + .5) for dual-unit inputs. this corrects the accuracy of result so that it usually has same significant figures
['mi'] = 1,
['ch'] = 10,
['mi;ch'] = .01,
['m'] = 1,
['yd'] = 1,
['ft'] = .1,
}
if not inp then inp = 'mi' end
inp = string.lower(trim(inp))
if inp == 'ch' or inp == 'yd' or inp == 'ft' then -- output unit
outp = 'm'
elseif inp == 'm' then
outp = 'yd'
else
if inp ~= 'mi;ch' then inp = 'mi' end
outp = 'km'
end
local pos = string.find(v1, '%.')
if not pos then
mult = 1
else
mult = 10 ^ (string.len(v1) - pos)
end
local inps = string.find(inp, ';')
if inps then
inps = split(inp, ';')
if swap then
t1 = v1..' '..outp
v1 = tonumber(v1)
t2 = floor(v1 / cvt[inps[1]])..' '..inps[1]..' '..floor(v1 % cvt[inps[1]] / cvt[inps[1]] * cvt[inp] * mult * sf[inp] + .5) / mult / sf[inp]..' '..inps[2]
else
v1 = split(trim(v1), ';')
t1 = v1[1]..' '..inps[1]..' '..v1[2]..' '..inps[2]
t2 = floor((tonumber(v1[1]) * cvt[inps[1]] + tonumber(v1[2]) * cvt[inps[1]] / cvt[inp]) * mult / sf[inp] + .5) / mult * sf[inp]..' '..outp
end
else
if swap then
v2 = floor(tonumber(v1) / cvt[inp] * mult * sf[inp] + .5) / mult / sf[inp]
inp, outp = outp, inp
else
v2 = floor(tonumber(v1) * cvt[inp] * mult / sf[inp] + .5) / mult * sf[inp]
end
t1 = v1..' '..inp
t2 = v2..' '..outp
end
end
if t1 then
if link then
result = result..'[['..link..'|'..t1..']]'
else
result = result..t1
end
end
local rowend = '</td></tr>'
result = result..rowend..rowstart..lh
if bg2 then
result = result..bgpad..bgtext(bg2)..';background:'..bg2
elseif bg1 then
result = result..bgpad
end
if fs2 then result = result..';font-size:'..fs2 end
if (it ~= 'off' and bs == 'to') or it2 == 'italic' then
result = result..';font-style:italic'
elseif it == 'off' then
result = result..';font-style:normal'
end
result = result..'">'
if t2 then
if link then
result = result..'[['..link..'|'..t2..']]'
else
result = result..t2
end
end
result = result..rowend..'</table> '
if bs == 'to' or bs == 'srws' then
if t1 == ' ' or t2 == ' ' then result = result..(i18n['error-categories']['missing-text-values'] or i18n['error-categories'].default) end
end
if string.find(t1, '<br ?/?>') ~= nil or string.find(t2, '<br ?/?>') ~= nil then result = result..(i18n['error-categories']['br-tags'] or i18n['error-categories'].default) end
if bs == 'split' then
if link and t1 and t2 then
if string.find(link, '^'..t1..' '..t2..' ') then result = result..(i18n['error-categories']['srws'] or i18n['error-categories'].default) end
end
end
return result
end
p.BSto = makeInvokeFunction('_BSto')
function p._BSto(args)
args[3] = args[3] or args.L
args[4] = args[4] or args.it or args.i
args[5] = args[5] or args.b
return base(args[1],args[2],args[3],nil,nil,args[4],nil,args[5],args.align,args.style,args.bg1,args.bg2,args.line,'105%','inherit','.9',nil,nil,nil,'to')
end
p.BSsplit = makeInvokeFunction('_BSsplit')
function p._BSsplit(args)
args[3] = args[3] or args.L
args[4] = args[4] or args.it or args.i
args[5] = args[5] or args.b
return base(args[1],args[2],args[3],nil,args[4],nil,nil,args[5],args.align,args.style,args.bg1,args.bg2,args.line,'inherit','inherit','.9',nil,nil,nil,'split')
end
p.BSsrws = makeInvokeFunction('_BSsrws')
function p._BSsrws(args)
args[3] = args[3] or args.S
args[4] = args[4] or args.it or args.i
args[5] = args[5] or args.b
return base(args[1],args[2],nil,args[3],args[4],nil,nil,args[5],args.align,args.style,args.bg1,args.bg2,args.line,'inherit','inherit','.9',nil,nil,nil,'srws')
end
p.BScvt = makeInvokeFunction('_BScvt')
function p._BScvt(args)
return base(nil,nil,nil,nil,nil,nil,args.alt,nil,args.align,args.style,args.bg1,args.bg2,args.line,'inherit','inherit','.9',args[1],args[2],args['in'],'cvt')
end
p.rmri = makeInvokeFunction('_rmri')
function p._rmri(args)--[[
Displays a blue arrow pointing in one of eight directions.
Implemented in the RoutemapRouteIcon template.
]]
local directions, result = {
['Up'] = {'u', 'up'},
['Down'] = {'d', 'dn', 'down'},
['Left'] = {'l', 'left'},
['Right'] = {'r', 'right'},
['UpperRight'] = {'ur', 'ne', 'c1', 'upperright'},
['LowerRight'] = {'lr', 'se', 'c2', 'lowerright'},
['LowerLeft'] = {'ll', 'sw', 'c3', 'lowerleft'},
['UpperLeft'] = {'ul', 'nw', 'c4', 'upperleft'},
}
local d, link, size = args[1], args[2], args[3]
if not d then
if args[4] ~= ' ' then d, link, size = args[2], args[3], 's' end
end
for k, v in pairs(directions) do
for _, name in ipairs(v) do
if d:lower() == name then
if size == 's' then
size = '7'
elseif not size then
size = '10'
end
if not link then link = '' end
result = '[[File:Arrow Blue '..k..' 001.svg|'..size..'px|alt='..k..' arrow|link='..link..']]'
end
end
end
if not result then
return '<span style="color:#f00">Invalid [[Template:RoutemapRoute]] arrow value "<span style="font-style:italic">'..d..'</span>".</span>'..(i18n['error-categories']['rmr-error'] or i18n['error-categories'].default)
else
return result
end
end
p.rmr = makeInvokeFunction('_rmr')
function p._rmr(args)--[[
Displays text between two blue arrows (or to the left/right side of one).
Text can be split with an en dash if entered in both first and second numbered parameters.
Implemented in the RoutemapRoute template.
]]
args.l = args.l or args.Licon or args.licon or args.L
args.r = args.r or args.Ricon or args.ricon or args.R
if args.l then args.l = p._rmri{args.l,args.llink,(args.lsize or args.size),' '}..' ' else args.l = '' end
if args.r then args.r = ' '..p._rmri{args.r,args.rlink,(args.rsize or args.size),' '} else args.r = '' end
if args[1] then
if args[2] then args[1] = args[1]..' – '..args[2] end
else
args[1] = args[2] or ''
end
if args[1] == '' or args.enclosed == 'no' then
args.b1, args.b2 = '', ''
else
args.b1, args.b2 = '(', ')'
end
return args.b1..args.l..args[1]..args.r..args.b2
end
function p.BSrow(frame)
local args = getArgs(frame, {
parentOnly = true,
removeBlanks = false,
})
return p._BSrow(args)
end
function p._BSrow(args)--[[
Creates Routemap syntax for a diagram row based on parameters.
Implemented in the RDTr template.
]]
args.n = tonumber(args.n or '')
if not args.n then
local icontotal = getArgNums('', args)
table.sort(icontotal)
args.n = icontotal[#icontotal] or 1
end
local count, icons, overlaps, overlapCalc = tonumber(args['$count']) or 1, {}, {}, math.log10(args.n)
local text = (args.text and '*') or ''
if overlapCalc == math.floor(overlapCalc) then overlapCalc = 10^(overlapCalc) else overlapCalc = 10^(math.floor(overlapCalc) + 1) end
while count <= args.n do
local cellparams, overlapn = {}, (string.match(count/overlapCalc, '%.(0+)') or '')..count
table.insert(icons, (text..(args[count] or '')))
if args['O'..overlapn] then
local iconparams, overlapparams, overlapt = {}, {}, {}
for k, v in pairs({bg = (args['O'..overlapn..'0-bg'] or args['O'..overlapn..'0-background'] or args['O'..overlapn..'0-bgcolor']), color = (args['O'..overlapn..'0-color'] or args['O'..overlapn..'0-colour']), b = (args['O'..overlapn..'0-b'] or args['O'..overlapn..'0-bold']), i = (args['O'..overlapn..'0-i'] or args['O'..overlapn..'0-it'] or args['O'..overlapn..'0-italic']), align = args['O'..overlapn..'0-align'], fontsize = args['O'..overlapn..'0-fontsize'], abbr = args['O'..overlapn..'0-abbr'], style = args['O'..overlapn..'0-style']}) do
if v then table.insert(iconparams, k..'='..v) end
end
if iconparams[1] then icons[count] = icons[count]..'__'..table.concat(iconparams, ',') end
for k, v in pairs({bg = (args['O'..overlapn..'-bg'] or args['O'..overlapn..'-background'] or args['O'..overlapn..'-bgcolor']), color = (args['O'..overlapn..'-color'] or args['O'..overlapn..'-colour']), b = (args['O'..overlapn..'-b'] or args['O'..overlapn..'-bold']), i = (args['O'..overlapn..'-i'] or args['O'..overlapn..'-it'] or args['O'..overlapn..'-italic']), align = args['O'..overlapn..'-align'], fontsize = args['O'..overlapn..'-fontsize'], abbr = args['O'..overlapn..'-abbr'], style = args['O'..overlapn..'-style']}) do
if v then table.insert(overlapparams, k..'='..v) end
end
if overlapparams[1] then args['O'..overlapn] = args['O'..overlapn]..'__'..table.concat(overlapparams, ',') end
overlaps = getArgNums('O'..overlapn, args) or {}
table.sort(overlaps)
if overlaps[1] then
for i, v in ipairs(overlaps) do
overlapparams = {}
for k, v2 in pairs({bg = (args['O'..overlapn..v..'-bg'] or args['O'..overlapn..v..'-background'] or args['O'..overlapn..v..'-bgcolor']), color = (args['O'..overlapn..v..'-color'] or args['O'..overlapn..v..'-colour']), b = (args['O'..overlapn..v..'-b'] or args['O'..overlapn..v..'-bold']), i = (args['O'..overlapn..v..'-i'] or args['O'..overlapn..v..'-it'] or args['O'..overlapn..v..'-italic']), align = args['O'..overlapn..v..'-align'], fontsize = args['O'..overlapn..v..'-fontsize'], abbr = args['O'..overlapn..v..'-abbr'], style = args['O'..overlapn..v..'-style']}) do
if v2 then table.insert(overlapparams, k..'='..v2) end
end
if overlapparams[1] then args['O'..overlapn..v] = args['O'..overlapn..v]..'__'..table.concat(overlapparams, ',') end
table.insert(overlapt, text..args['O'..overlapn..v])
end
overlaps = '!~'..text..args['O'..overlapn]..'!~'..table.concat(overlapt, '!~')
else
overlaps = '!~'..text..args['O'..overlapn]
end
icons[count] = icons[count]..overlaps
else
if args[count..'-abbr'] then table.insert(cellparams, 'abbr='..args[count..'-abbr']) end
end
if args[count..'-link'] then icons[count] = icons[count]..'!@'..args[count..'-link'] end
for k, v in pairs({bg = (args[count..'-bg'] or args[count..'-background'] or args[count..'-bgcolor']), color = (args[count..'-color'] or args[count..'-colour']), b = (args[count..'-b'] or args[count..'-bold']), i = (args[count..'-i'] or args[count..'-it'] or args[count..'-italic']), align = args[count..'-align'], fontsize = args[count..'-fontsize'], style = args[count..'-style']}) do
if v then table.insert(cellparams, k..'='..v) end
end
if cellparams[1] then icons[count] = icons[count]..'!_'..table.concat(cellparams, ',') end
count = count + 1
end
local row, rowparams, left, right = table.concat(icons, '\\'), {}
for k, v in pairs({bg = (args.bg or args.background or args.bgcolor), color = (args.color or args.colour), b = (args.b or args.bold), i = (args.i or args.it or args.italic), align = args.align, fontsize = args.fontsize, style = args.style}) do
if v then table.insert(rowparams, k..'='..v) end
end
if rowparams[1] then args.R5 = table.concat(rowparams, ',') end
for i, v in ipairs({'R1', 'R2', 'R3', 'R4', 'R5', 'L1', 'L2', 'L3', 'L4'}) do
if not args[v] or string.find(args[v], '^%s*$') then args[v] = nil end
end
if args.R5 then
right = {(args.R1 or ' '), (args.R2 or ' '), (args.R3 or ' '), (args.R4 or ' '), args.R5}
elseif args.R4 then
right = {(args.R1 or ' '), (args.R2 or ' '), (args.R3 or ' '), args.R4}
elseif args.R3 then
right = {(args.R1 or ' '), (args.R2 or ' '), args.R3}
elseif args.R1 then
right = {args.R1, (args.R2 or '')}
elseif args.R2 then
right = {args.R2}
end
if right then row = row..'~~'..table.concat(right, '~~') end
if args.L4 then
left = {args.L4, (args.L3 or ' '), (args.L2 or ' '), (args.L1 or '')}
elseif args.L3 then
left = {args.L3, (args.L2 or ' '), (args.L1 or '')}
elseif args.L1 then
left = {(args.L2 or ''), args.L1}
elseif args.L2 then
left = {args.L2}
end
if left then row = table.concat(left, '~~')..'! !'..row end
return row
end
function p.BSrow_lite(frame)
local args = getArgs(frame, {
removeBlanks = false,
})
return p._BSrow_lite(args)
end
function p._BSrow_lite(args)--[[
Creates Routemap syntax for a diagram row based on parameters.
Intended to be used to substitute legacy templates.
Note that for compatibility the link and sidebar parameter names are different.
]]
args.n = tonumber(args.n or '')
if not args.n then
local icontotal = getArgNums('', args)
table.sort(icontotal)
args.n = icontotal[#icontotal] or 1
end
local count, icons, overlaps, overlapCalc = tonumber(args['$count']) or 1, {}, {}, math.log10(args.n)
local text = (args.text and '*') or ''
if overlapCalc == math.floor(overlapCalc) then overlapCalc = 10^(overlapCalc) else overlapCalc = 10^(math.floor(overlapCalc) + 1) end
while count <= args.n do
local cellparams, overlapn = {}, (string.match(count/overlapCalc, '%.(0+)') or '')..count
table.insert(icons, (text..(args[count] or '')))
if args['O'..overlapn] then
local overlapt = {}
overlaps = getArgNums('O'..overlapn, args) or {}
table.sort(overlaps)
if overlaps[1] then
for i, v in ipairs(overlaps) do table.insert(overlapt, text..args['O'..overlapn..v]) end
overlaps = '!~'..text..args['O'..overlapn]..'!~'..table.concat(overlapt, '!~')
else
overlaps = '!~'..text..args['O'..overlapn]
end
icons[count] = icons[count]..overlaps
end
if args['L'..count] then icons[count] = icons[count]..'!@'..args['L'..count] end
count = count + 1
end
local row, rowparams, left, right = table.concat(icons, '\\'), {}
for k, v in pairs({bg = (args.bg or args.background or args.bgcolor), style = args.style}) do
if v then table.insert(rowparams, k..'='..v) end
end
if rowparams[1] then args.r5 = table.concat(rowparams, ',') end
for i, v in ipairs({'r1', 'r2', 'r3', 'r4', 'r5', 'l1', 'l2', 'l3', 'l4'}) do
if not args[v] or string.find(args[v], '^%s*$') then args[v] = nil end
end
if args.r5 then
right = {(args.r1 or ' '), (args.r2 or ' '), (args.r3 or ' '), (args.r4 or ' '), args.r5}
elseif args.r4 then
right = {(args.r1 or ' '), (args.r2 or ' '), (args.r3 or ' '), args.r4}
elseif args.r3 then
right = {(args.r1 or ' '), (args.r2 or ' '), args.r3}
elseif args.r1 then
right = {args.r1, (args.r2 or '')}
elseif args.r2 then
right = {args.r2}
end
if right then row = row..'~~'..table.concat(right, '~~') end
if args.l4 then
left = {args.l4, (args.l3 or ' '), (args.l2 or ' '), (args.l1 or '')}
elseif args.l3 then
left = {args.l3, (args.l2 or ' '), (args.l1 or '')}
elseif args.l1 then
left = {(args.l2 or ''), args.l1}
elseif args.l2 then
left = {args.l2}
end
if left then row = table.concat(left, '~~')..'! !'..row end
return row
end
local function pre_block(text)
-- Pre tags returned by a module do not act like wikitext <pre>...</pre>.
return '<pre>' ..
mw.text.nowiki(text) ..
(text:sub(-1) == '\n' and '' or '\n') ..
'</pre>\n'
end
function p.convertbs(frame)--[[
Converts a legacy route diagram into Routemap format.
Code to be used is displayed in preview mode or after saving the page, above the original code.
{{#invoke:Routemap|convertbs|<nowiki>
(Original diagram)
</nowiki>}}
]]
local org = mw.text.unstripNoWiki(frame.args[1] or 'Paste legacy RDT markup between nowiki tags')
local res = org
res = string.gsub(res, '{{[Bb][Ss]%-?map', '{{Routemap') -- "%-" is an escape for hyphen which is used as "between" in pattern.
res = string.gsub(res, '{|%s?{{[Rr]ailway line header}}', '{{Routemap')
res = string.gsub(res, '{{[Bb][Ss]%-header%d?|', '{{safesubst:BS-header/safesubst|') -- "%d?" means optional digit in case use of variant template like BS-header3.
res = string.gsub(res, '{{[Bb][Ss]%-table%d?}}', '|map =')
res = string.gsub(res, '{{[Bb][Ss](%d?)(%d?)|', '{{safesubst:BS%1%2/safesubst|')
res = string.gsub(res, '{{[Bb][Ss](%d?)(%d?)%-replace|', '!replace{{safesubst:BS%1%2/safesubst|')
res = string.gsub(res, '{{[Bb][Ss](%d?)(%d?)%-startCollapsible|', '-startCollapsible-collapsed\n{{safesubst:BS%1%2/safesubst|')
res = string.gsub(res, '{{[Bb][Ss](%d?)(%d?)%-sc|', '-startCollapsible-collapsed\n{{safesubst:BS%1%2/safesubst|')
res = string.gsub(res, '{{[Bb][Ss](%d?)(%d?)%text|', '{{safesubst:BS%1%2text/safesubst|')
res = string.gsub(res, '{{[Bb][Ss](%d?)(%d?)%-2|', '{{safesubst:BS%1%2-2/safesubst|')
res = string.gsub(res, '{{[Bb][Ss](%d?)(%d?)%-2replace|', '!replace{{safesubst:BS%1%2-2|')
res = string.gsub(res, '{{[Bb][Ss](%d?)(%d?)%-2sc|', '-startCollapsible-collapsed\n{{safesubst:BS%1%2-2|')
res = string.gsub(res, '{{!}}}', '-endCollapsible-')
res = string.gsub(res, '{{[Ee]nd}}', '-endCollapsible-')
res = string.gsub(res, '|}\n?|}', '}}') -- Replace ending of Railway line header map setup.
res = string.gsub(res, '{{[Bb][Ss]%-colspan}}\n{{safesubst', '{{safesubst') -- BS-colspan is unnecessary and would cause error in Routemap.
res = string.gsub(res, '{{[Bb][Ss]%-colspan}}\n%-%-%-%-', '-colspan-2\n----')
res = string.gsub(res, '<', '<')
res = string.gsub(res, '>', '>')
if string.find(res, '!replace') or string.find(res, '|%s*bg%s*=') then
local restable = mw.text.split(res, '\n')
for i, v in ipairs(restable) do
if string.find(v, '!replace') then
restable[i] = string.gsub(restable[i], '!replace', '')
restable[i-2] = string.gsub(restable[i-2], 'collapsed', 'collapsed-replace')
end
if (string.find(v, '|%s*bg%s*=%s*#?[a-zA-Z0-9]+') or string.find(v, '|%s*bg%s*=%s*#?{{[^{}]+}}%s*|') or string.find(v, '|%s*bg%s*=%s*#?{{[^{}]+}}%s*}}')) and string.find(restable[i-1], '^-startCollapsible') then
local bg = string.match(v, '|%s*bg%s*=%s*(#?[a-zA-Z0-9]+)') or string.find(v, '|%s*bg%s*=%s*(#?{{[^{}]+}})%s*|') or string.find(v, '|%s*bg%s*=%s*(#?{{[^{}]+}})%s*}}')
restable[i] = string.gsub(restable[i], '|%s*bg%s*=%s*'..bg, '')
restable[i-1] = string.gsub(restable[i-1], '%-?$', '--bg=')..bg
if string.find(restable[i+1], '!replace') then
restable[i+1] = string.gsub(restable[i+1], '!replace', '')
restable[i-1] = string.gsub(restable[i-1], 'collapsed%-', 'collapsed-replace')
if (string.find(restable[i+1], '|%s*bg%s*=%s*#?[a-zA-Z0-9]+') or string.find(restable[i+1], '|%s*bg%s*=%s*#?{{[^{}]+}}%s*|') or string.find(restable[i+1], '|%s*bg%s*=%s*#?{{[^{}]+}}%s*}}')) then
local bg2 = string.match(restable[i+1], '|%s*bg%s*=%s*(#?[a-zA-Z0-9]+)') or string.find(restable[i+1], '|%s*bg%s*=%s*(#?{{[^{}]+}})%s*|') or string.find(restable[i+1], '|%s*bg%s*=%s*(#?{{[^{}]+}})%s*}}')
if bg2 == bg then restable[i+1] = string.gsub(restable[i], '|%s*bg%s*=%s*'..bg2, '') end
end
end
end
end
res = table.concat(restable, '\n')
end
return "\n'''Safe substitution''':\n" .. pre_block(res) .. "'''''Original''''':\n" .. pre_block(org)
end
return p