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ệ!",
	},
	"error-categories" = {
		default = 'Thể loại:Trang có lỗi của Mô đun Routemap'
	},
	html = {
		"cell-icon-fmt" = '\
%s%s',
		"cell-overlapicon-fmt" = '%s',
		"cell-icon-fmt-with-overlap" = '\
%s%s%s',
		"cell-text-fmt" = '\
%s%s%s%s',
		"cell-overlaptext-fmt" = '%s%s%s',
		"cell-text-fmt-with-overlap" = '\
%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"| 

',
		"cell-filler-empty-fmt" = '\n|style="padding:0; width:20px"|',
		"row-linfo4-fmt" = '\
%s',-- parameters:linfo4-width, linfo4
		"row-linfo3-fmt" = '%s ',
		"row-rinfo3-fmt" = ' %s',
		"row-rinfo4-fmt" = '\
%s',-- parameters:rinfo4-width, rinfo4
		"row-general-fmt" = '\
%s\
%s\
\
\
%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" = '\
\
',
		"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" = '\
\
',
		"empty-row-fmt" = '\n
		}
}
local p,q={},{}
local function formaterror(key,param)
	local result = string.format(i18n.html'colspan-fmt', , , , ,  .. string.format(i18n.errorskey or (tostring(key) .. ' %s'),
		tostring(param or )) .. )
	if mw.site.namespacesmw.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 colorscode 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
				t1 = mw.text.trim(t1) --trim parameter names
				table.insert(result, t1)
				resultt1 = t2 or  --fill table with pairs "property"="value"
			elseif resultresulti - 1 then
				table.insert(result, resulti - 1)
				resultresulti = resultresulti..','..t1 --if no equals sign then tack t1 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 values1 == 'inherit' then values1 = y end
				if values2 == 'inherit' then values2 = 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 valuesp
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 = tmp1, properties(tmp2) end
		tmp = mw.text.split(overlapIcons#overlapIcons, '!@')
		overlapIcons#overlapIcons = tmp1
		if #tmp > 1 then link = tmp2 end	
		tmp = mw.text.split(icon, '__')
		icon = tmp1
		if #tmp > 1 then iconProps = properties(tmp2) end
		for i, v in ipairs(overlapIcons) do
			tmp = mw.text.split(v, '__')
			overlapIconsi = mw.text.trim(tmp1)
			if #tmp > 1 then overlapPropsi = properties(tmp2) else overlapPropsi = {} end
		end
	else
		tmp = mw.text.split(icon, '!_')
		if #tmp > 1 then icon, cellProps = tmp1, properties(tmp2) end
		tmp = mw.text.split(icon, '!@')
		icon = mw.text.trim(tmp1)
		if #tmp > 1 then link = tmp2 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.align1, rowProps.align2) or rowProps.align
			cellProps.style, cellProps.textfmt = cellProps.style..';vertical-align:'..cellProps.align1..';text-align:'..cellProps.align2, ';vertical-align:'..cellProps.align1
		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 overlapPropsi.style then overlapProps.style = ';'..overlapPropsi.style else overlapProps.style =  end
			overlapPropsi.bg = overlapPropsi.bg or overlapPropsi.background or overlapPropsi.bgcolor
			if overlapPropsi.bg then overlapProps.style = overlapProps.style..';background:'..overlapPropsi.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
				overlapPropsi.color = overlapPropsi.color or overlapPropsi.colour ; overlapPropsi.bold = overlapPropsi.bold or overlapPropsi.b ; overlapPropsi.italic = overlapPropsi.italic or overlapPropsi.i or overlapPropsi.it
				if overlapPropsi.color then overlapProps.style = overlapProps.style..';color:'..overlapPropsi.color end
				if positive(overlapPropsi.italic) then overlapProps.style = overlapProps.style..';font-style:italic' end
				if positive(overlapPropsi.bold) then overlapProps.style = overlapProps.style..';font-weight:bold' end
				if rowProps.fontsize or cellProps.fontsize or overlapPropsi.fontsize == 'info' then
				elseif not overlapPropsi.fontsize then
					overlapProps.style = overlapProps.style..';font-size:10px'
				elseif overlapPropsi.fontsize == 'cmt' or overlapPropsi.fontsize == 'comment' then
					overlapProps._before, overlapProps._after = , 
				else
					overlapProps.style = overlapProps.style..';font-size:'..overlapPropsi.fontsize
				end
				if overlapPropsi.align or cellProps.align then
					cellProps.align = cellProps.align or {'middle', 'center'}
					overlapProps.align = alignment(overlapPropsi.align, cellProps.align1, cellProps.align2) or cellProps.align
					overlapProps.style, overlapProps.textfmt = overlapProps.style..';vertical-align:'..overlapProps.align1..';text-align:'..overlapProps.align2, ';vertical-align:'..overlapProps.align1
				else
					overlapProps.style, overlapProps.textfmt = overlapProps.style..';vertical-align:middle;text-align:center', ';vertical-align:middle;text-align:center'
				end
				if overlapPropsi.abbr then
					overlapProps.tag = {'abbr title="'..string.gsub(overlapPropsi.abbr, '"', '"')..'"', '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.tag1, overlapProps.textfmt, overlapProps._before, tmp_icontext, overlapProps._after, overlapProps.tag2))
			else
				v = mw.text.trim(v)
				if string.find(v, 'num') then
					if not string.find(v, 'numN%d+') then tracking = tracking..'Thể loại:Trang sử dụng Routemap có hình ảnh văn bản' end
				end
				table.insert(tmp, string.format(i18n.html'cell-overlapicon-fmt', overlapProps.style, v, link, icontip))
			end
		end
		if iconProps.style then tmp21 = 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 tmp21 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.align1, cellProps.align2) or cellProps.align
				iconProps.style, iconProps.textfmt = iconProps.style..';vertical-align:'..iconProps.align1..';text-align:'..iconProps.align2, ';vertical-align:'..iconProps.align1
			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, '"', '"')..'"', 'abbr'}
			else
				iconProps.tag = {'span', 'span'}
			end
			if tmp21 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.tag1, iconProps.textfmt, iconProps._before, icontext, iconProps._after, tracking, iconProps.tag2)
		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..'Thể loại:Trang sử dụng Routemap có hình ảnh văn bản' 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, '"', '"')..'"', '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.tag1, cellProps.textfmt, cellProps._before, icontext, cellProps._after, tracking, cellProps.tag2)
		else
			if string.find(icon, 'num') then
				if not string.find(icon, 'numN%d+') then tracking = tracking..'Thể loại:Trang sử dụng Routemap có hình ảnh văn bản' 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 = tmp1 ; right = tmp2
	else
		left =  ; right = tmp1 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(tmp1)
	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(tmp2))
		result'rinfo2+3' = mw.text.trim(tmp3)
		if #tmp > 3 then
			tmp4 = mw.text.trim(tmp4)
			if tmp4 ~=  then result'rinfo2+3' = result'rinfo2+3' .. string.format(i18n.html'row-rinfo3-fmt', tmp4) end
			if #tmp > 4 then
				tmp5 = mw.text.trim(tmp5)
				if tmp5 ~=  then
					result'rinfo4' = mw.getCurrentFrame():preprocess(tmp5)
					rcolspan = '1'
					rinfo4_fmt = string.format(i18n.html'row-rinfo4-fmt', , result'rinfo4')
				end
				if #tmp > 5 then result.rowProp = properties(mw.text.trim(tmp6)) end
			end
		end
	else--assume only rinfo2 was provided.
		result'rinfo2+3' = mw.text.trim(tmp2 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(tmp1, '\\')--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, '!~')
			iconsi = tmp1
			table.remove(tmp, 1)
			table.insert(overlapIcons, tmp)
		end
		for i, v in ipairs(icons) do table.insert(result'cells', cell(v, overlapIconsi, 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.align1..';text-align:'..result.rowProp.align2
	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(qtmp1)=="function" and tmp1 ~= 'isKeyword' then
		if justTest then return tmp1 else return qtmp1(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(rowsi, 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(rowsi, 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 = params2, params3 or , properties(table.concat(params, '-', 4))--params1 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(rowsi, 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_width1),
			'1', q.text_width2, linfo4_3_2_fmt, q.linfo1_pad, q.text_width3, 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_width1, tmp'linfo4'),
			'1', q.text_width2, tmp'linfo3+2', q.linfo1_pad, q.text_width3, tmp'linfo1', tmp.style, tmp'cells', q.rinfo1_pad, q.text_width4, tmp'rinfo1',
			'1', q.text_width5, rinfo2_3_4_fmt, string.format(i18n.html'row-collapsible-right-button-fmt', i18n.html'row-collapsible-right-button-width', q.text_width6))
	end
	if replace ~=  then
		if q.isKeyword(rowsi + 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(rowsi, 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_width2, linfo4_3_2_fmt,
				q.linfo1_pad, q.text_width3, 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_width1, tmp'linfo4'), '1', q.text_width2,
				tmp'linfo3+2', q.linfo1_pad, q.text_width3, tmp'linfo1', tmp.style, tmp'cells', q.rinfo1_pad, q.text_width4, tmp'rinfo1', '1', q.text_width5,
				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
	if params2 == 'end' then return  end
	local tmp, j, nrows, props = {}, 0, tonumber(params2), 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_width1 = q.text_width1 .. 'min-width:' .. i18n.html'row-collapsible-left-button-width' .. ';'
		else
			q.text_width6 = q.text_width6 .. '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_width1, q.text_width2, q.linfo1_pad, q.text_width3, q.rinfo1_pad, q.text_width4, q.text_width5, q.text_width6))
	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 pfuncName(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 args1 and args1 ~=  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..' \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
	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
		if string.find(args.bottom, '&action=edit') then result = result .. 'Thể loại:Trang sử dụng Routemap có một bản mẫu thanh điều hướng rời' 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
	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 = ' '..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 = ' ' end
	end
	if not t2 or string.find(t2, '^%s*$') then
		if not t1 then t1 =  end
		if string.find(t1, ) == 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 / 0.9144,
			'yd' = 0.9144,
			'ft' = 0.3048,
		}
		local sf = { -- 10 ^ floor(log10(cvtinp) + 0.5); or 10 ^ floor(log10(cvtinps1 * cvtinp) + 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..' '..outp
				v1 = tonumber(v1)
				t2 = floor(v1 / cvtinps1)..' '..inps1..' '..floor(v1 % cvtinps1 / cvtinps1 * cvtinp * mult * sfinp + 0.5) / mult / sfinp..' '..inps2
			else
				v1 = split(trim(v1), ';')
				t1 = v11..' '..inps1..' '..v12..' '..inps2
				t2 = floor((tonumber(v11) * cvtinps1 + tonumber(v12) * cvtinps1 / cvtinp) * mult / sfinp + 0.5) / mult * sfinp..' '..outp
			end
		else
			if swap then
				v2 = floor(tonumber(v1) / cvtinp * mult * sfinp + 0.5) / mult / sfinp
				inp, outp = outp, inp
			else
				v2 = floor(tonumber(v1) * cvtinp * mult / sfinp + 0.5) / mult * sfinp
			end
			t1 = v1..' '..inp
			t2 = v2..' '..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..' '
	if bs == 'to' or bs == 'srws' then
		if t1 == ' ' or t2 == ' ' then result = result..'Thể loại:Trang sử dụng BSto hoặc BSsrws có thiếu giá trị văn bản' end
	end
	if string.find(t1, ) ~= nil or string.find(t2, ) ~= nil then result = result..'Thể loại:Trang sử dụng BSto, BSsplit, BSsrws hoặc BScvt có thẻ br' end
	if bs == 'split' then
		if link and t1 and t2 then
			if string.find(link, '^'..t1..' '..t2..' ') then result = result..'Thể loại:Trang sử dụng BSsplit thay vì BSsrws' end
		end
	end
	return result
end
p.BSto = makeInvokeFunction('_BSto')
function p._BSto(args)
	args3 = args3 or args.L
	args4 = args4 or args.it or args.i
	args5 = args5 or args.b
	return base(args1,args2,args3,nil,nil,args4,nil,args5,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)
	args3 = args3 or args.L
	args4 = args4 or args.it or args.i
	args5 = args5 or args.b
	return base(args1,args2,args3,nil,args4,nil,nil,args5,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)
	args3 = args3 or args.S
	args4 = args4 or args.it or args.i
	args5 = args5 or args.b
	return base(args1,args2,nil,args3,args4,nil,nil,args5,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',args1,args2,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 = args1, args2, args3
	if not d then
		if args4 ~= ' ' then d, link, size = args2, args3, '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 = 'link='..link..
			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..'".Thể loại:Trang có giá trị hỏng cho bản mẫu RoutemapRoute'
	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 args1 then
		if args2 then args1 = args1..' – '..args2 end
	else
		args1 = args2 or 
	end
	if args1 ==  or args.enclosed == 'no' then
		args.b1, args.b2 = , 
	else
		args.b1, args.b2 = '(', ')'
	end
	return args.b1..args.l..args1..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.args1) or 'Paste legacy RDT markups between the nowiki tags'
	local res = org
	res = string.gsub(res, '