Mô đun:Routemap

local i18n = {
errors = {
["parameter-missing"] = "Thiếu tham số!",
["collapsible-block-not-closed"] = "Phần đóng mở không đóng cửa đúng cách!",
["collapsible-block-not-open"] = "Đánh dấu bắt đầu đóng mở bị thiếu!",
["collapsible-block-empty"] = "Phần đóng mở phải không được trống!",
["collapsible-block-no-first-row"] = "Hàng đầu tiên không hợp lệ của phần đóng mở!",
["collapsible-block-no-replacement"] = "Hàng thay thế đóng mở không hợp lệ!",
["colspan-less-rows-than-set"] = "Thiết lập colspan không hợp lệ!",
},
["error-categories"] = {
default =
},
html = {
["cell-icon-fmt"] = '\
|style="padding:0%s"|%s',
["cell-overlapicon-fmt"] = ,
["cell-icon-fmt-with-overlap"] = '\
|style="padding:0%s"|%s%s',
["cell-text-fmt"] = '\
|style="padding:0;width:%s;min-width:%s;line-height:20px%s" title="%s"|%s%s%s%s',
["cell-overlaptext-fmt"] = '%s%s%s',
["cell-text-fmt-with-overlap"] = '\
|style="padding:0%s"|%s%s%s%s%s',
["cell-empty-fmt"] = '\n|style="padding:0;width:%s;min-width:%s;height:20px;min-height:20px%s"|%s',
["cell-empty-fmt-with-overlap"] = '\n|style="padding:0%s"|%s%s',

["cell-filler-fmt"] = '\n|style="padding:0; width:8px"| ||style="padding:0; width:4px; background-color:%s"| ||style="padding:0; width:8px"|',
["cell-filler-empty-fmt"] = '\n|style="padding:0; width:20px"|',

["row-linfo4-fmt"] = '\
|style="width:auto;vertical-align:middle;padding:0 3px 0 0;text-align:left;%s"|%s',-- parameters:linfo4-width, linfo4
["row-linfo3-fmt"] = '%s ',
["row-rinfo3-fmt"] = ' %s',
["row-rinfo4-fmt"] = '\
|style="width:auto;vertical-align:middle;padding:0 0 0 3px;text-align:right;%s"|%s',-- parameters:rinfo4-width, rinfo4

["row-general-fmt"] = '\
|- style="line-height:1" %s\
|colspan="%s" style="width:auto;vertical-align:middle;padding:0;text-align:right;%s"|%s\
|style="width:auto;vertical-align:middle;text-align:left;padding:0 %s;%s"|%s\
|style="width:auto;padding:0%s"|\
\
|style="width:auto;vertical-align:middle;text-align:right;padding:0 %s;%s"|%s\
|colspan="%s" style="width:auto;vertical-align:middle;padding:0;text-align:left;%s"|%s%s',-- parameters: linfo4-fmt, colspan-left, linfo3+2-width, linfo3+2, linfo1-pad, linfo1-width, linfo1, bg, cells, rinfo1-pad, rinfo1-width, rinfo1, colspan-right, rinfo2+3-width, rinfo2+3, rinfo4-fmt

["row-collapsible-begin-fmt"] = '\
|- style="line-height:1"\
|colspan="7" style="padding:0 !important;background-color:%s"|\
',

["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"] = '\
',-- 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"] = '\
',-- parameters: rinfo2+3, linfo4
["row-collapsible-right-button-fmt"] = '\n| style="width:auto;vertical-align:middle;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"|\
',

["colspan-fmt"] = '%s\n|-\n| colspan="7" style="background-color:%s;text-align:%s;%s"|\n%s',
["empty-row-fmt"] = '\n|-\n| style="width:auto;padding:0 3px 0 0;%s" |\n| style="width:auto;padding: 0;%s" |\n| style="width:auto;padding:0 %s;%s" |\n| style="width:auto;padding:0" |\n| style="width:auto;padding:0 %s;%s" |\n| style="width:auto;padding:0;%s" |\n| style="width:auto;padding:0 0 0 3px;%s" |'
}
}
local p,q={},{}

local function formaterror(key,param)
local result = string.format(i18n.html['colspan-fmt'], , , , , .. string.format(i18n.errors[key] or (tostring(key) .. ' %s'),
tostring(param or )) .. )
if mw.site.namespaces[mw.title.getCurrentTitle().namespace].isContent then result = result .. (i18n['errors-categories'][key] or i18n['errors-categories'].default or ) end
return result
end

local function RGBbyCode(code)-- RGB codes for BSicon sets at Commons:Category:Icons for railway descriptions/other colors
local colors = {-- Any changes should be discussed at Commons:Talk:BSicon/Colors
bahn = 'BE2D2C', ex = 'D77F7E',
u = '003399', uex = '6281C0',
f = '008000', fex = '64B164',
g = '2CA05A', gex = '7EC49A',
azure = '3399FF', ex_azure = '99CCFF',
black = '000000', ex_black = '646464',
blue = '0078BE', ex_blue = '64ACD6',
brown = '8D5B2D', ex_brown = 'B89A7F',
cerulean = '1A8BB9', ex_cerulean = '73B7D3',
cyan = '40E0D0', ex_cyan = '8AEAE1',
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',
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',
saffron = 'FFAB2E', ex_saffron = 'FFC969',
sky = '069DD3', ex_sky = '67C2E3',
steel = 'A1B3D4', ex_steel = 'C4CFE3',
teal = '339999', ex_teal = '82C0C0',
violet = '800080', ex_violet = 'B164B1',
yellow = 'FFD702', ex_yellow = 'FFEB81',
}
return colors[code] or colors.bahn
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 widths(p)
local values = {['w'] = 160, ['bs'] = 120, ['s'] = 80, ['b'] = 40, [] = 20, ['cd'] = 15, ['d'] = 10, ['c'] = 5,} -- in px
return values[p]
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, width = {}, {}, {}, {}, {}, , ,
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
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 = ,
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..';vertical-align:'..cellProps.align[1]..';text-align:'..cellProps.align[2], ';vertical-align:'..cellProps.align[1]
else
cellProps.style, cellProps.textfmt = cellProps.style..';vertical-align:middle;text-align:center', ';vertical-align:middle;text-align:center'
end
end
if overlapIcons > 0 then
tmp = {}
for i, v in ipairs(overlapIcons) do
if i ~= overlapIcons then link = end
if link and link ~= then icontip = link 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
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.style = overlapProps.style..';font-size:10px'
elseif overlapProps[i].fontsize 'cmt' or overlapProps[i].fontsize 'comment' then
overlapProps._before, overlapProps._after = ,
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..';vertical-align:'..overlapProps.align[1]..';text-align:'..overlapProps.align[2], ';vertical-align:'..overlapProps.align[1]
else
overlapProps.style, overlapProps.textfmt = overlapProps.style..';vertical-align:middle;text-align:center', ';vertical-align:middle;text-align:center'
end
if overlapProps[i].abbr then
overlapProps.tag = {'abbr title="'..string.gsub(overlapProps[i].abbr, '"', '&quot;')..'"', 'abbr'}
else
overlapProps.tag = {'span', 'span'}
end
width = (widths(tmp_iconpre) or 20)..'px'
table.insert(tmp, string.format(i18n.html['cell-overlaptext-fmt'], width, width, overlapProps.style, icontip, overlapProps.tag[1], overlapProps.textfmt, 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.. end
end
table.insert(tmp, string.format(i18n.html['cell-overlapicon-fmt'], overlapProps.style, v, link, 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
width = widths(icon)
if width 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)), width..'px', width..'px', iconProps.style, tracking)
elseif icontext then
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.style = iconProps.style..';font-size:10px'
elseif iconProps.fontsize 'cmt' or iconProps.fontsize 'comment' then
iconProps._before, iconProps._after = ,
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..';vertical-align:'..iconProps.align[1]..';text-align:'..iconProps.align[2], ';vertical-align:'..iconProps.align[1]
else
iconProps.style, iconProps.textfmt = iconProps.style..';vertical-align:middle;text-align:center', ';vertical-align:middle;text-align:center'
end
if iconProps.abbr then
iconProps.tag = {'abbr title="'..string.gsub(iconProps.abbr, '"', '&quot;')..'"', 'abbr'}
else
iconProps.tag = {'span', 'span'}
end
if tmp2[1] then iconProps.style = ';'..iconProps.style end
width = (widths(iconpre) or 20)..'px'
return string.format(i18n.html['cell-text-fmt-with-overlap'], cellProps.style, mw.text.trim(table.concat(tmp)), width, width, iconProps.style, icontip, iconProps.tag[1], iconProps.textfmt, 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.. 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
width = widths(icon)
if width then
return string.format(i18n.html['cell-empty-fmt'], width..'px', width..'px', 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.style = cellProps.style..';font-size:10px' end
if cellProps.abbr then
cellProps.tag = {'abbr title="'..string.gsub(cellProps.abbr, '"', '&quot;')..'"', 'abbr'}
else
cellProps.tag = {'span', 'span'}
end
width = (widths(iconpre) or 20)..'px'
return string.format(i18n.html['cell-text-fmt'], width, width, cellProps.style, icontip, cellProps.tag[1], cellProps.textfmt, 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.. end
end
return string.format(i18n.html['cell-icon-fmt'], cellProps.style, icon, link, icontip, tracking)
end
end
end

local function fillercell(code)
--Creates a 5px-high row.
--Values in icon pattern can only be [blank], d, [BSicon color] or [hex triplet].
if code then
return string.format(i18n.html['cell-filler-empty-fmt'], '20px', '20px')
elseif code 'd' then
return string.format(i18n.html['cell-filler-empty-fmt'], '10px', '10px')
elseif mw.ustring.sub(code,1,1) == '' then
return string.format(i18n.html['cell-filler-fmt'], code)
else
return string.format(i18n.html['cell-filler-fmt'], ''..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'] = , ['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 = ,
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['cells'][1] = 'style="height:' .. filler .. '"'--row parameter before any cells
for i, v in ipairs(icons) do table.insert(result['cells'], fillercell(v)) end--no !@ or !~ for filler row
else
for i, v in ipairs(icons) do
tmp = mw.text.split(v, '!~')
icons[i] = tmp[1]
table.remove(tmp, 1)
table.insert(overlapIcons, tmp)
end
for i, v in ipairs(icons) do table.insert(result['cells'], cell(v, overlapIcons[i], result.rowProp)) end
end
result['cells'] = table.concat(result['cells'])
if result.rowProp.style then result.style = ';'..result.rowProp.style else result.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.align then
result.style = result.style..';vertical-align:'..result.rowProp.align[1]..';text-align:'..result.rowProp.align[2]
else
result.style = result.style..';vertical-align:middle;text-align:center'
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 = '3px', rinfo1_pad = '3px', 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 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 table.concat(tmp)
end

local getArgs = require('Mô đun:Arguments').getArgs

local function makeInvokeFunction(funcName)
-- makes a function that can be returned from invoke, using
-- Mô đun:Arguments.
return function (frame)
local args = getArgs(frame, {parentOnly = true})
return p[funcName](args)
end
end

local function getArgNums(prefix, args)
-- Copied from Mô đun: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('Mô đun: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-collapsed'] = 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-collapsed'] 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
args['title bg color'] = args['title bg color'] 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('Mô đun:Navbar').navbar
local navtable = {}
args.navbar = args.navbar or args.tnavbar
if args.navbar then
navtable = {args.navbar, text = 'Sơ đồ này:'}
args.navbar = navbar(navtable)
else
args.navbar =
end
local result =
if args.inline then result = result..'&32;\n' end
result = result..
if positive(args['map'..v..'-collapsed']) then args.map_collapsed = ' mw-collapsed autocollapse' else args.map_collapsed = end
result = result..'\n|-\n|\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 .. 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 = '&32;'..rowstart..lh
if line then result = result..';border-bottom:1px solid gray' end
local bgpad = ';padding-left:0.5em;padding-right:0.5em'
local function bgtext(v)
return ';color:'..greatercontrast{v, 'FFF', rgb_black}
end
if bg1 then
result = result..bgpad..bgtext(bg1)..';background-color:'..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, ) == nil then t1 = '&nbsp;' end
end
if not t2 or string.find(t2, '^%s$') then
if not t1 then t1 = end
if string.find(t1, ) == nil then t2 = '&nbsp;' 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 / 0.9144,
['yd'] = 0.9144,
['ft'] = 0.3048,
}
local sf = { -- 10 ^ floor(log10(cvt[inp]) + 0.5); or 10 ^ floor(log10(cvt[inps[1]] cvt[inp]) + 0.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'] = 1/100,
['m'] = 1,
['yd'] = 1,
['ft'] = 1/10,
}
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..'&nbsp;'..outp
v1 = tonumber(v1)
t2 = floor(v1 / cvt[inps[1]])..'&nbsp;'..inps[1]..'&nbsp;'..floor(v1 % cvt[inps[1]] / cvt[inps[1]] cvt[inp] mult sf[inp] + 0.5) / mult / sf[inp]..'&nbsp;'..inps[2]
else
v1 = split(trim(v1), ';')
t1 = v1[1]..'&nbsp;'..inps[1]..'&nbsp;'..v1[2]..'&nbsp;'..inps[2]
t2 = floor((tonumber(v1[1]) cvt[inps[1]] + tonumber(v1[2]) cvt[inps[1]] / cvt[inp]) mult / sf[inp] + 0.5) / mult sf[inp]..'&nbsp;'..outp
end
else
if swap then
v2 = floor(tonumber(v1) / cvt[inp] mult sf[inp] + 0.5) / mult / sf[inp]
inp, outp = outp, inp
else
v2 = floor(tonumber(v1) cvt[inp] mult / sf[inp] + 0.5) / mult sf[inp]
end
t1 = v1..'&nbsp;'..inp
t2 = v2..'&nbsp;'..outp
end
end
if t1 then
if link then
result = result....t1..
else
result = result..t1
end
end
local rowend =
result = result..rowend..rowstart..lh
if bg2 then
result = result..bgpad..bgtext(bg2)..';background-color:'..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....t2..
else
result = result..t2
end
end
result = result..rowend..'&32;'
if bs 'to' or bs 'srws' then
if t1 '&nbsp;' or t2 '&nbsp;' then result = result.. end
end
if string.find(t1, ) ~= nil or string.find(t2, ) ~= nil then result = result.. end
if bs == 'split' then
if link and t1 and t2 then
if string.find(link, '^'..t1..' '..t2..' ') then result = result.. 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%','92%','0.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','0.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','0.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','0.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 =
end
end
end
if not result then
return 'Giá trị mũi tên Bản mẫu:RoutemapRoute không hợp lệ "'..d..'".'
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),' '}..'&nbsp;' else args.l = end
if args.r then args.r = '&nbsp;'..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]..'&nbsp;–&nbsp;'..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 ....
return ..
mw.text.nowiki(text) ..
(text:sub(-1) == '\n' and or '\n') ..
'\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.

local org = mw.text.unstripNoWiki(frame.args[1]) or 'Paste legacy RDT markups between the nowiki tags'
local res = org
res = string.gsub(res, , , '|map=')
res = string.gsub(res, '}', '-endCollapsible-')
res = string.gsub(res, , '-endCollapsible-')
res = string.gsub(res, '|}\n|}', '}}') -- Replace ending of Railway line header map setup.
res = string.gsub(res, '\n\n%-%-%-%-', '-colspan-2\n----')
res = string.gsub(res, '&lt;', )
if string.find(res, '!replace') or string.find(res, '{{[Bb][Ss]%d%-?2?text') 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, '{{[Bb][Ss]%dtext') then
local tmp = {}
restable[i] = mw.text.split(string.gsub(string.gsub(restable[i], '^$', ), '|')
restable[i][1] = tonumber(string.match(restable[i][1], '%d+')) or 1
for q, r in ipairs(restable[i]) do
if q > 1 then
if q (restable[i][1] + 2) then
table.insert(tmp, '~~'..(r or ' '))
end
end
end
restable[i] = table.concat(tmp)
end
end
res = table.concat(restable, '\n')
end
return "\nThay thế an toàn:\n" .. pre_block(res) .. "Nguồn gốc:\n" .. pre_block(org)
end

return p