Module:Routemap
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 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.
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 with errors of Module Routemap]]', ["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', 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 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 = '#F9F9F9'} 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') 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 = {} args.navbar = args.navbar or args.tnavbar 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 '#F9F9F9' 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 .. ';' .. 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" style="font-weight:inherit;color:inherit;background:transparent;margin-top:-2px;margin-bottom:-2px;display:inline-table;vertical-align:middle;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 markups between the 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