模組:Adjacent stations/sandbox

本页使用了标题或全文手工转换
维基百科,自由的百科全书
文档图示 模块文档[查看] [编辑] [历史] [清除缓存]

本模块实现{{Adjacent stations}}、{{Rail icon}}、{{Rail color box}}、{{Line link}}、{{Station link}}和{{Rail color}}的功能。欲了解如何使用这些模板,请参阅其文档。(本模块的convert函数的指引置于{{Adjacent stations}}的文档。)

前述的模板依赖于本模块的子模块所存放的数据。例如,{{Rail icon|MTR}}从Module:Adjacent stations/MTR生成。

下列示例对创建和编辑数据会有帮助,但有一定Lua基础能避免一些错误。如果你之前编写或是使用过Lua,你可以跳过下一小节。

术语

  • Lua有不同的数据类型(data types)。此处将会涉及到的有布尔型(boolean)、字符串(string)、数字(number)和表(table)。
    • 布尔型数据只能是true(真)或false(假)。
    • 字符串即文本,以字符列表的形式存储。Lua有数种方式在代码中表记字符串,此处的示例使用双引号包裹的方式表记(如"这是一个字符串")。
    • 数字即数值,像0.542
    • 表是可以包含其他对象的结构,并支持嵌套。
      • 空表在代码中形如{}
      • 表有键(key)和值(value)典型结构如["key"] = value;键值对(key-value pair)之间由西文逗号分隔。此处使用的键都是字符串或数字。
      • {"text", "more text"}等价于{[1] = "text", [2] = "more text"}
  • 使用local variable_name = "value"可以定义一个变量(variable)。
  • 空白(whitespace)包括制表符、换行符、空格及其任意组合。空白在Lua中不影响结果,但除了内置的文本,此处的所有示例都经过了缩进处理,根据键分行,以提高可读性。
  • 返回语句(return statement,如return variable_name)退出当前函数,并回报variable_name的值。此处的“函数”指主模块中调用子页面的代码,而variable_name应为表类型。

基本结构

下面展示两个模块:

空白
local p = {
	["system title"] = "",
	["system icon"] = "",
	["station format"] = {
		"",
		[""] = "",
	},
	["lines"] = {
		[""] = {
			["title"] = "",
			["color"] = "",
			["left terminus"] = "",
			["right terminus"] = "",
		},
	},
}

return p
示例
local x = "%1站 (高雄捷運)"
local y = "%1站 (臺灣)"

local p = {
    ["system title"] = "[[高雄捷運]]",
    ["station format"] = {
        "%1站",
        ["大湖"] = x,
        ["岡山"] = x,
        ["橋頭火車站"] = "[[橋頭車站 (台灣)|橋頭火車站]]",
        ["高雄車站"] = "%1",
        ["都會公園"] = y,
        ["左營"] = "[[高鐵左營站#高雄捷運左營/高鐵站|左營/高鐵]]",
        ["左營/高鐵"] = "[[高鐵左營站#高雄捷運左營/高鐵站|左營/高鐵]]",
        ["中央公園"] = y,
        ["凱旋"] = y,
        ["文化中心"] = y,
        ["鳳山西站"] = "%1",
        ["鳳山"] = x,
        ["鼓山"] = "[[鼓山車站]]",
        ["科工館"] = "[[科工館車站]]",
        ["台鐵美術館"] = "[[美術館車站]]",
        ["民族"] = "[[民族車站|民族]]",
        ["大港"] = x,
        ["小港"] = "[[小港站 (捷運)|小港]]",
    },
    ["lines"] = {
        ["紅"] = {
            ["title"] = "[[File:Kaohsiung Rapid Transit Red Line.svg|20px]] [[高雄捷運紅線|紅線]]",
            ["color"] = "e20b65",
            ["left terminus"] = "小港",
            ["right terminus"] = "南路竹"
        },
        ["橘"] = {
            ["title"] = "[[File:Kaohsiung Rapid Transit Orange Line.svg|20px]] [[高雄捷運橘線|橘線]]",
            ["color"] = "faa73f",
            ["left terminus"] = "西子灣",
            ["right terminus"] = "大寮"
        },
        ["環"] = {
            ["title"] = "[[File:Kaohsiung Rapid Transit Circular Line.svg|20px]] [[高雄環狀輕軌|環狀輕軌]]",
            ["color"] = "7cbd52",
            ["circular"] = true,
            ["left terminus"] = "逆行",
            ["right terminus"] = "順行"
        },
        ["黃"] = {
            ["title"] = "[[File:Kaohsiung Rapid Transit Yellow Line.svg|20px]] [[高雄捷運黃線|黃線]]",
            ["color"] = "ffc100",
            ["left terminus"] = "坔埔",
            ["right terminus"] = {"旅運中心", "前鎮高中"}
        }
    },
    ["aliases"] = {
        ["r"] = "紅",
        ["o"] = "橘",
        ["c"] = "環",
        ["y"] = "黃"
    }
}

return p

示例模块为Module:Adjacent stations/高雄捷運

  • 首先需要两条记录,"station format""lines"。前者用于生成指向车站条目的链接,后者包括各线路的表。
  • "system title"是标题行中间的文本。
  • "station format"定义车站条目名称的规则和例外。第一个变量"%1 metro station"是默认值。例外由键值对列出(如"岡山""岡山車站"),此处键是输入值。模块显示输入值,并根据处理后的值链接到条目,其中%1将被输入值替代。或者,也可以直接键入完整的维基链接,这样显示文本就可以与输入值不同。
  • "lines"列出线路。此处的名称仅在内部使用,并不用于显示,所以请尽量简短。
  • "line title"是显示在线路所在行中间的文本;"left terminus"是默认的左侧终点站,"right terminus"则是默认的右侧终点站。
  • 每个"color"记录线路的颜色。
  • 首行定义的x是用于格式化车站名称的字符串。"station format"表中,x被用于数个需要“ (高雄捷运)”后缀的车站。这个方法是可选的,但在数个车站条目命名遵循另一条规则时相当有用。

以下是Module:Adjacent stations/台灣高鐵

local p = {
	["system title"] = "[[台灣高速鐵路]]",
	["system color"] =  "c35617",
	["name format"] = "color: #FFFFFF; background-color: #C35617;",
	["station format"] = {
		"高鐵%1站",
		["南港"] = "[[南港車站|南港]]",
		["臺北"] = "[[臺北車站|臺北]]",
		["板橋"] = "[[板橋車站_(臺灣)|板橋]]",
},
["lines"] = {
		["_default"] = {
			["title"] = "[[台灣高鐵]]",
			["color"] = "c35617",
			["left terminus"] = "南港",
			["right terminus"] = "左營"
		}
	}
}

return p
  • 这个模块包括一条虚拟线路["_default"]。此处的标题和颜色被用于所有线路,除非被具体线路覆盖。当调用时不指定line=,此表就会被使用。

层级和参数列表

  1. 表的第一层是整个系统的数据和输出选项。
  2. 系统表下层是线路的列表。
  3. 第三层是具体线路的数据。
  4. 每条线可以有多个“类型”。“类型”可以是服务类型(如不同速度等级)或线路的支线。
  5. 第五层是具体类型的数据。

未指明类型的键和值都是字符串。

主层 (1)

参数 类型 被{{Adjacent stations}}使用 描述
["lang"] 字符串 默认且仅有"zh-CN",影响预置字符串的选择,本语言维基不使用此本地化方案,而是交由字词转换完成。
["system title"] 字符串 标题行中间的文本。
["system icon"] 字符串 {{Adjacent stations}}标题行中间和{{Rail icon}}使用的图标。
["system icon format"] 字符串 图标类型,由{{Rail icon}}使用。如果指定并且值不为"image",图标数据将交由{{Rail color box}}预处理。
["system color"] 字符串 RGB十六进制三元值(三或六个字符长,如"BE2D2C""039")。调用{{Rail color}}时仅用一个参数时返回。
["header stop noun"] 字符串 标题行左右单元格的“上一”和“下一”后使用的名词。默认值为"station"
["name format"] 字符串 {{Infobox station}}表头的CSS,亦可使用|1=header调用style函数取值。值可以为字符串或嵌套表,其中使用表的首层(对应{{Infobox station}}的|style2=)。第二层目前未使用。嵌套表的第一项(无键项或键为1)为默认。
["header background color"] 字符串 {{Infobox station}}次表头使用的RGB十六进制三元值,亦可使用|1=subheader调用style函数取值。默认为亮灰色。值可以为字符串或嵌套表,结构类似"name format"
["header text color"] 字符串 {{Infobox station}}次表头使用的RGB十六进制三元值,亦可使用|1=subheader调用style函数取值。默认根据背景色计算。值可以为字符串或嵌套表,结构类似"name format"
["station format"] 表或字符串 表包括车站格式化字符串。首条没有键的记录(也即键为1的记录)为默认,余下的记录须有匹配输入的键。没有维基链接括号的格式化字符串将会被转化为链接,输入(通常为车站名)将会被用作显示文本。本表可以嵌套表,用于表记传递给模板的线路和线路类型选项。

所有嵌套层级下的字符串都可以使用%1%2%3,分别用来表记输入的车站,输入的线路(经别名处理后)和输入的类型(经别名处理后)。

["lines"] 包含数据的线路表。
["aliases"] 包含线路(作为值)的别名(作为键)的表。所有的键都是小写,因为输入将被小写化,以实现大小写不敏感。

车站格式表 (2)

参数 类型 被{{Adjacent stations}}使用 描述
[1] 字符串 默认格式。
["non-default station name"] 字符串或表 不标准的车站格式,或是由线路确定的格式表。

依赖线路的格式表 (3)

参数 类型 被{{Adjacent stations}}使用 描述
[1] 字符串 默认格式。
["line name"] 字符串或表 不标准的车站格式,或是由类型确定的格式表。

依赖類型的格式表 (4)

参数 类型 被{{Adjacent stations}}使用 描述
[1] 字符串 默认格式。
["type name"] 字符串 不标准的车站格式。

线路表 (3)

可以添加虚拟线路["_default"]以为所有线路设定默认值。目前对两个参数有效。

参数 类型 被{{Adjacent stations}}使用 描述
["title"] 字符串 中间单元格显示的文本,通常是指向线路条目的链接。若不指定,将会使用["_default"]下的值(默认值中的%1为经别名处理的输入的线路)。
["short name"] 字符串 {{Rail color box}}使用的缩写。
["icon"] 字符串 {{Rail icon}}使用的图像。
["icon format"] 字符串 {{Rail icon}}使用的图像格式。如果指定并且值不为"image",图像数据将交由{{Rail color box}}预处理。
["color"] 字符串 RGB十六进制三元值。如果线路自身没有指定值,将会回落到["_default"]颜色,乃至系统颜色;类型将依次尝试获取所在线路、["_default"]和系统颜色。颜色用于{{Adjacent stations}}的第二和第四列,以及{{Rail color box}}和{{Rail icon}}的强调色。默认地,若类型和线路皆有颜色,那么线路颜色将会作为中间单元格背景色(见下一小节)。将类型背景色设为"""transparent"可以避免。
["background color"] 字符串 RGB十六进制三元值(三或六个字符)。颜色是可选的,并只在中间单元格线路名称后的背景显示。本模块会透明化颜色,使其上的文字可识读。
["border color"] 字符串 {{Rail color box}}使用的RGB十六进制三元值。
["text color"] 字符串 {{Rail color box}}使用的RGB十六进制三元值。
["left terminus"] 字符串 通常是线路的左终点。若默认有多个值,应嵌套为顺序表(如["left terminus"] = {"Chesham", "Amersham"})。表中的["via"]键可用于在其后加上“经”以及车站链接。
["right terminus"] 字符串 通常是线路的右终点,表现同["left terminus"]
["note-mid"] 字符串 显示在线路和类型下方的默认小号文本。调用时由|note-mid=覆盖。
["circular"] 布尔型 若值为true,终点文本将不会显示“往”。可以被类型覆盖。
["oneway-left"] 布尔型 若值为true,那么左终点站处将会显示“单向运行”。
["oneway-right"] 布尔型 对应oneway-left的右侧版本。
["types"] 嵌套线路类型表的表。

类型表 (5)

参数 类型 被{{Adjacent stations}}使用 描述
["title"] 字符串 线路类型的名称。在{{Adjacent stations}}中显示为普通大小的文本,位于中间单元格线路名称的下方;在{{Rail color box}}中,某些选项下将显示在线路名称后,以带空格的同n宽连接号(亦用于不停站文本)。设为""可避免显示。
["short name"] 字符串 {{Rail color box}}使用的缩写。
["icon"] 字符串 {{Rail icon}}使用的图像。
["icon format"] 字符串 {{Rail icon}}使用的图像格式。如果指定并且值不为"image",图像数据将交由{{Rail color box}}预处理。
["color"] 字符串 RGB十六进制三元值。如果线路自身没有指定值,将会回落到["_default"]颜色,乃至系统颜色;类型将依次尝试获取所在线路、["_default"]和系统颜色。颜色用于{{Adjacent stations}}的第二和第四列,以及{{Rail color box}}和{{Rail icon}}的强调色。默认地,若类型和线路皆有颜色,那么线路颜色将会作为中间单元格背景色(见下一小节)。将类型背景色设为"""transparent"可以避免。
["background color"] 字符串 RGB十六进制三元值(三或六个字符)。颜色是可选的,并只在中间单元格线路名称后的背景显示。本模块会透明化颜色,使其上的文字可识读。
["border color"] 字符串 {{Rail color box}}使用的RGB十六进制三元值。
["text color"] 字符串 {{Rail color box}}使用的RGB十六进制三元值。
["left terminus"] 字符串 通常是线路的左终点。若默认有多个值,应嵌套为顺序表(如["left terminus"] = {"Chesham", "Amersham"})。表中的["via"]键可用于在其后加上“经”以及车站链接。
["right terminus"] 字符串 通常是线路的右终点,表现同["left terminus"]
["note-mid"] 字符串 显示在线路和类型下方的默认小号文本。调用时由|note-mid=覆盖。
["circular"] 布尔型 若值为true,终点文本将不会显示“往”。

关于开发者

本模块及其配套模版是由英语维基人进行编写和维护。中文维基版将会及时同步英文版本。如果您需要增加新功能,您需要在英文版本的讨论页面提出。

子模块

require('strict')

local p = {}

local lang = 'zh-CN' -- local default language

--	Below these comments: Internationalization table
--	How to translate this module (for languages without variants):
--	• Characters inside single and double quotation marks are called strings.
--	  The strings in this i18n table are used as output.
--	• Strings within square brackets are keys.
--	• Strings are concatenated (joined) with two dots.
--	• Set the string after «local lang =» to your language's code.
--	  Change the first key after "i18n" (usually "en-GB") to the same thing.
--	• For each string which is not inside a function, translate it directly.
--	• Strings with keys named "format" are Lua regular expressions.
--	  «()» is a match; «.+» means all characters; «%s+» means all spaces.
--	• For each string which is concatenated to the variable «var»,
--	  translate the phrase assuming that «var» will be a noun.
--	• Remove any unnecessary translations.

local i18n = require("Module:Adjacent stations/i18n")
local function getData(system, verify)
	if verify then
		local title = mw.title.new('Module:Adjacent stations/' .. system -- .. '/sandbox'
			)
		if not (title and title.exists) then return nil end
	end
	return require('Module:Adjacent stations/' .. system -- .. '/sandbox'
		)
end

local function getLine(data, lineN)
	if lineN then
		if data['aliases'] then
			lineN = data['aliases'][mw.ustring.lower(lineN)] or lineN
		end
		local default = data['lines']['_default'] or {}
		local line = data['lines'][lineN] or {}
		for k, v in pairs(default) do
			if v then line[k] = line[k] or v end
		end
		line['title'] = line['title'] and mw.ustring.gsub(line['title'], '%%1', lineN)
		return line, lineN
	end
end

local function getColor(data, system, line, Type, frame)
	if system then
		if line then return frame:expandTemplate{ title = system .. ' color', args = {line, ['branch'] = Type} } end
		return frame:expandTemplate{ title = system .. ' color' }
	else
		line = (getLine(data, line))
		local default = data['lines']['_default']
		if line or default then
			default = default or {}
			if not line then line = mw.clone(default) end
			local color = line['color'] or line['background color'] or default['color'] or default['background color'] or data['system color']
			local Type_value = Type and line['types'] and (line['types'][Type] and line['types'][Type]['color'])
			if Type_value then color = Type_value end
			return color
		end
		return (default and (default['color'] or default['background color']) or data['system color'] or '')
	end
end

local lineN, typeN

local function somethingMissing(name, key, formats)
	local formatKeys = {}
	for k in pairs(formats) do
		table.insert(formatKeys, k)
	end
	return name .. ' 曾是 "' .. key .. '" 但既没有找到它的条目,也没有找到默认值。选择是: ' .. table.concat(formatKeys, ', ')
end

local function getStation(station, _Format)
	if type(_Format) == 'table' then
		local lineNformats = _Format
		_Format = lineNformats[lineN] or lineNformats[1]
		if not _Format then
			error(somethingMissing('lineN', lineN, lineNformats))
		elseif type(_Format) == 'table' then
			local typeNformats = _Format
			_Format = typeNformats[typeN] or typeNformats[1]
			if not _Format then
				error(somethingMissing('typeN', typeN, typeNformats))
			end
		end
	end
	if typeN then _Format = mw.ustring.gsub(_Format, '%%3', typeN) end
	if lineN then _Format = mw.ustring.gsub(_Format, '%%2', lineN) end
	return (mw.ustring.match(_Format, '%[%[.+%]%]')) and (mw.ustring.gsub(_Format, '%%1', station)) or table.concat({'[[', mw.ustring.gsub(_Format, '%%1', station), '|', station, ']]'})
end

local function getTerminusText(var, Format)
	local function subst(var1, var2)
		-- var1 is the terminus or table of termini; var2 is the key for the table of termini
		return type(var1) == 'string' and getStation(var1, (Format[var1] or Format[1]))
		or type(var1) == 'table' and #var1 > 0 and getStation(var1[var2], (Format[var1[var2]] or Format[1]))
		or ''
	end

	if Format then
		if type(var) == 'string' then
			return subst(var)
		elseif type(var) == 'table' and #var > 0 then
			local t = {subst(var, 1)}

			for i = 2, #var - 1 do
				t[i] = i18n[lang]['comma'](subst(var, i))
			end

			if #var > 1 then t[#var] = i18n[lang]['or'](subst(var, #var)) end
			if var['via'] then
				if i18n[lang]['via-first'] then
					table.insert(t, 1, i18n[lang]['via'](subst(var, 'via')))
				else
					table.insert(t, i18n[lang]['via'](subst(var, 'via')))
				end
			end

			return table.concat(t)
		else
			return ''
		end
	else
		return var or ''
	end
end

function p._main(_args) -- Arguments are processed here instead of the main function

	local yesno = require('Module:Yesno')
	local trimq = require('Module:Trim quotes')._trim

	local boolean = {
		['oneway-left'] = true,
		['oneway-right'] = true,
		['reverse'] = true,
		['reverse-left'] = true,
		['reverse-right'] = true
	}

	local args = {} -- Processed arguments
	local index = {} -- A list of addresses corresponding to number suffixes in the arguments

	for k, v in pairs(_args) do -- Maps each raw argument to processed arguments by string matching
		_args[k] = v:match('^%s*(.-)%s*$')
		if _args[k] and _args[k] ~= '' then
			local a = mw.ustring.match(k, '^(.*%D)%d+$') or k -- The parameter; address 1 can be omitted
			local b = tonumber(mw.ustring.match(k, '^.*%D(%d+)$')) or 1 -- The address for a given argument; address 1 can be omitted

			if boolean[a] then
				v = yesno(v)
			end

			if not args[b] then
				args[b] = {[a] = v}
				table.insert(index, b)
			elseif args[b][a] then
				return error(i18n[lang]['error_duplicate'](a .. b))
			else
				args[b][a] = v
			end
		end
	end
	table.sort(index)

	local function small(s, italic)
		return italic and '<div class="isA">' .. s .. '</div>'
			or '<div class="smA">' .. s .. '</div>'
	end

	local style = { -- Style for each cell type
		['header cell'] = 'class="hcA"|',
		['header midcell'] = 'colspan="3" class="hmA"|',
		['body cell'] = 'class="bcA"|',
		['body banner'] = 'class="bbA" style="background-color:#',
	}

	local function rgb(var)
		if var:len() == 3 then
			return {tonumber(var:sub(1, 1), 16) * 17, tonumber(var:sub(2, 2), 16) * 17, tonumber(var:sub(2, 2), 16) * 17}
		elseif var:len() == 6 then
			return {tonumber(var:sub(1, 2), 16), tonumber(var:sub(3, 4), 16), tonumber(var:sub(5, 6), 16)}
		end
		return {}
	end

	local data = {} -- A table of data modules for each address
	local noclearclass = (((_args.noclear or '') ~= '') and ' adjacent-stations-noclear' or '')
	local wikitable = {'{| class="wikitable adjacent-stations' .. noclearclass .. '"'}

	for i, v in ipairs(index) do
		-- If an address has a system argument, indexes the data module
		data[v] = args[v]['system'] and getData(args[v]['system'])
		-- If an address has no system, the row uses data from the previous address
			or data[index[i - 1]]
			or (args[v]['header'] and getData(args[index[i+1]]['system']))
			or error(i18n[lang]['error_unknown'](args[v]['system']))

		local lang = data[v]['lang'] or lang

		if args[v]['system'] and not args[v]['hide-system'] then -- Header row
			local stop_noun = data[v]['header stop noun'] or i18n[lang]['stop_noun']
			table.insert(wikitable, table.concat({'\n|-',
				'\n! scope="col" ', style['header cell'], i18n[lang]['preceding'](stop_noun),
				'\n! scope="col" ', style['header midcell'], (data[v]['system icon'] and data[v]['system icon'] .. ' ' or ''), (data[v]['system title'] or ('[['.. args[v]['system'] ..']]')),
				'\n! scope="col" ', style['header cell'], i18n[lang]['following'](stop_noun)
			}))
			table.insert(wikitable, '')
			table.insert(wikitable, '')
			table.insert(wikitable, '')
		end

		if args[v]['header'] then -- Subheader
			table.insert(wikitable, '\n|-\n!colspan="5" class="hmA"|'.. args[v]['header'])
			table.insert(wikitable, '')
			table.insert(wikitable, '')
			table.insert(wikitable, '')
		end

		if args[v]['line'] or args[v]['left'] or args[v]['right'] or args[v]['nonstop'] then
			if not args[v]['line'] and i > 1 and not args[v]['system'] then
				args[v]['line'] = args[index[i - 1]]['line']
			end

			lineN = args[v]['line'] or '_default'
			typeN = args[v]['type']
			if data[v]['aliases'] then
				lineN = data[v]['aliases'][mw.ustring.lower(lineN)] or lineN
				if typeN then typeN = data[v]['aliases'][mw.ustring.lower(typeN)] or typeN end
			end

			-- get the line table
			local line = data[v]['lines'] and (mw.clone(data[v]['lines'][lineN]) or error(i18n[lang]['error_unknown'](args[v]['line']))) or error(i18n[lang]['error_line'])
			local default = data[v]['lines']['_default'] or {}
			line['title'] = line['title'] or default['title']
			line['title'] = mw.ustring.gsub(line['title'], '%%1', lineN)

			-- cell across row for non-stop service
			if args[v]['nonstop'] then
				table.insert(wikitable,
					table.concat({'\n|-\n|colspan="5" ',
						style['body cell'],
						((args[v]['nonstop'] == 'former') and i18n[lang]['nonstop_past'] or i18n[lang]['nonstop_present'])(p._box({data = data[v], line = lineN, Type = typeN, inline = 'yes'}))
					})
				)
				table.insert(wikitable, '')
				table.insert(wikitable, '')
				table.insert(wikitable, '')
			else
				local Format = data[v]['station format'] or i18n[lang]['error_format']

				local color, color_2, background_color, circular
				local Type = line['types'] and line['types'][typeN] -- get the line type table

				if Type then
					if Type['color'] then
						-- line color is used as background if there is no background color in the line type table
						background_color = Type['background color'] or line['color']
						color = Type['color']
						color_2 = Type['color2'] or color
					else
						background_color = Type['background color'] or line['background color']
						color = line['color'] or default['color'] or ''
						color_2 = line['color2'] or color
					end
					if Type['circular'] then
						-- Type may override the circular status of the line
						circular = Type['circular']
					end
				else
					background_color = line['background color']
					color = line['color'] or default['color'] or ''
					color_2 = line['color2'] or color
					circular = line['circular']
				end

				-- Alternate termini can be specified based on type
				local sideCell = {true, true}
				for i, b in ipairs({'left', 'right'}) do
					if not args[v][b] then -- If no station is given on one side, the station is assumed to be the terminus on that side
						local _through = args[v]['through-' .. b] or args[v]['through']
						local _through_data = getLine(data[v], _through)
						if _through_data then _through = _through_data['title'] or _through end
						sideCell[i] = _through and "''" .. i18n[lang]['through'](trimq(_through)) .. "''"
							or "''" .. trimq((args[v]['reverse-' .. b]
							or args[v]['reverse']) and i18n[lang]['reverse']
							or i18n[lang]['terminus']) .. "''"
					else
						local terminusT
						local terminusN = Type and Type[b .. ' terminus'] or line[b .. ' terminus']

						-- If the terminus table has more than one numbered key or has the via key then the table shows only the default termini, since terminusN[2] cannot be used and terminusN[via] is reserved
						if type(terminusN) == 'string' or (type(terminusN) == 'table' and (terminusN[2] or terminusN['via'])) then
							if args[v]['to-' .. b] then
								terminusT = args[v]['to-' .. b]
								local _or = mw.ustring.match(terminusT, i18n[lang]['or-format'])
								if _or then
									terminusT = mw.ustring.gsub(terminusT, i18n[lang]['or-format'], '\127_OR_\127')
									terminusT = mw.ustring.gsub(terminusT, i18n[lang]['comma-format'], '\127_OR_\127')
								end
								local _via = (mw.ustring.match(terminusT, i18n[lang]['via-format']))
								if _via then
									terminusT = mw.ustring.gsub(terminusT, i18n[lang]['via-format'], '')
									terminusT = mw.text.split(terminusT, '\127_OR_\127')
									terminusT['via'] = _via
								elseif _or then
									terminusT = mw.text.split(terminusT, '\127_OR_\127')
								end
							else
								terminusT = terminusN
							end
						elseif type(terminusN) == 'table' then
							terminusT = terminusN[args[v]['to-' .. b]] or terminusN[args[v]['to']] or terminusN[1]
						end

						local mainText = args[v]['note-' .. b] and getTerminusText(args[v][b], Format) .. small(args[v]['note-' .. b]) or getTerminusText(args[v][b], Format)

						local subText = (args[v]['oneway-' .. b] or line['oneway-' .. b]) and i18n[lang]['oneway']
							or args[v][b] == terminusT and i18n[lang]['terminus']
							or circular and terminusT
							or i18n[lang]['towards'](getTerminusText(terminusT, Format))
						subText = small(subText, true)

						sideCell[i] = mainText .. subText
					end
				end

				table.insert(wikitable, '\n|-')
				table.insert(wikitable, '\n|' .. style['body cell'] .. sideCell[1])
				table.insert(wikitable, table.concat({'\n|', style['body banner'], color, '"|',
					'\n|', (background_color and 'class="bcA" style="background-color:rgba(' .. table.concat(rgb(background_color), ',') .. ',.2)"|' or style['body cell']), line['title'],

					-- Type; table key 'types' in subpages (datatype table, with strings as keys). If table does not exist then the input is displayed as the text
					(typeN and '<div>' .. (Type and Type['title'] or typeN) .. '</div>' or ''),

					-- Note-mid; table key 'note-mid' in subpages. Overridden by user input
					((args[v]['note-mid'] and small(args[v]['note-mid'])) or (Type and Type['note-mid'] and small(Type['note-mid'])) or (line['note-mid'] and small(line['note-mid'])) or ''),

					-- Transfer; uses system's station link table
					(args[v]['transfer'] and small('于' .. getTerminusText(args[v]['transfer'], Format) .. '换乘', true) or ''),

					'\n|', style['body banner'], color_2, '"|'}))
				table.insert(wikitable, '\n|' .. style['body cell'] .. sideCell[2])
			end
		end

		if args[v]['note-row'] then -- Note
			if args[v]['note-row']:match('^%s*<tr') or args[v]['note-row']:match('^%s*%|%-') then
				table.insert(wikitable, '\n' .. args[v]['note-row'])
			else
				table.insert(wikitable, '\n|-\n|colspan="5" ' .. style['body cell'] .. args[v]['note-row'])
			end
			table.insert(wikitable, '')
			table.insert(wikitable, '')
			table.insert(wikitable, '')
		end
	end

	local function combine(t, n)
		if t[n + 4] ~= '' and t[n + 4] == t[n] then
			t[n + 4] = '' -- The cell in the next row is deleted
			local rowspan = 2
			while t[n + rowspan * 4] == t[n] do
				t[n + rowspan * 4] = ''
				rowspan = rowspan + 1
			end
			t[n] = mw.ustring.gsub(t[n], '\n|class="', '\n|rowspan="' .. rowspan .. '" class="')
		end
	end

	local M = #wikitable
	for i = 3, M, 4 do combine(wikitable, i) end
	for i = 4, M, 4 do combine(wikitable, i) end
	for i = 5, M, 4 do combine(wikitable, i) end

	table.insert(wikitable, '\n|}')

	return table.concat(wikitable)
end

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, frame)
	end
end

p.main = makeInvokeFunction('_main')

function p._color(args, frame)
	local data = args.data
	if args[1] or data then
		data = data or getData(args[1], true)
		if not data then return getColor(nil, args[1], args[2], args[3], frame) end
		return getColor(data, nil, args[2], args[3])
	end
end

p.color = makeInvokeFunction('_color')

function p._box(args, frame)
	local system = args[1] or args.system
	lineN = args[2] or args.line
	if not (system or lineN) then return '' end
	local line, Type, line_data
	local inline = args[3] or args.inline
	typeN = args.type
	local data = args.data
	if system or data then
		data = data or getData(system, true)
		local color
		if data then
			local default = data['lines']['_default'] or {}
			line, lineN = getLine(data, lineN)
			if typeN then
				typeN = data['aliases'] and data['aliases'][mw.ustring.lower(typeN)] or typeN
				Type = line['types'] and line['types'][typeN] and line['types'][typeN]['title'] or typeN
			end
			color = getColor(data, nil, lineN, typeN)
			if inline ~= 'box' then
				line_data = line or error(i18n[lang]['error_unknown'](lineN))
				line = line_data['title'] or default['title'] or error(i18n[lang]['error_missing']('title'))
				line = mw.ustring.gsub(line, '%%1', lineN)
			end
		else
			color = getColor(nil, system, lineN, typeN, frame)
			if inline ~= 'box' then
				line = frame:expandTemplate{ title = system .. ' lines', args = {lineN, ['branch'] = typeN} }
				if mw.text.trim(line) == '' then return error(i18n[lang]['error_unknown'](lineN)) end
			end
			Type = typeN
		end

		local result

		if Type and Type ~= '' and inline ~= 'box' then
			if line == '' then
				line = Type
			else
				result = ' – ' .. Type
			end
		end
		if args.note then result = (result or '') .. ' ' .. args.note end
		result = result or ''

		if not inline then -- [[Template:Legend]]
			result = '<div class="legend" style="page-break-inside:avoid;break-inside:avoid-column"><span class="legend-color" style="display:inline-block;min-width:1.25em;height:1.25em;line-height:1.25;margin:1px 0;border:1px solid black;background-color:#' .. color .. '"> </span> ' .. line .. result .. '</div>'
		elseif inline == 'yes' then
			result = '<span style="background-color:#' .. color .. ';border:1px solid #000">    </span>&nbsp;' .. line .. result
		elseif inline == 'box' then
			result = '<span style="background-color:#' .. color .. ';border:1px solid #000">    </span>' .. result
		elseif inline == 'link' then
			local link = args.link or mw.ustring.match(line, '%[%[([^%[:|%]]+)[|%]]')
			if link then
				result = '[[' .. link .. '|<span style="background-color:#' .. color .. ';border:1px solid #000">    </span>]]' .. result
			else
				result = '<span style="background-color:#' .. color .. ';border:1px solid #000">    </span>' .. result
			end
		elseif inline == 'square' then
			result = '<span style="color:#' .. color .. ';line-height:initial">■</span>&nbsp;' .. line .. result
		elseif inline == 'lsquare' then
			local link = args.link or mw.ustring.match(line, '%[%[([^%[:|%]]+)[|%]]')
			if link then
				result = '[[' .. link .. '|<span style="color:#' .. color .. ';line-height:initial">■</span>]]'
			else
				result = '<span style="color:#' .. color .. ';line-height:initial">■</span>'
			end
		elseif inline == 'dot' then
			result = '<span style="color:#' .. color .. ';line-height:initial">●</span> ' .. line .. result
		elseif inline == 'ldot' then
			local link = args.link or mw.ustring.match(line, '%[%[([^%[:|%]]+)[|%]]')
			if link then
				result = '[[' .. link .. '|<span style="color:#' .. color .. ';line-height:initial">●</span>]]'
			else
				result = '<span style="color:#' .. color .. ';line-height:initial">●</span>'
			end
		elseif inline == 'small' then
			result = '<span style="background-color:#' .. color .. '"> </span>' .. ' ' .. line .. result
		else
			local yesno = require("Module:Yesno")
			local link = args.link or mw.ustring.match(line, '%[%[([^%[:|%]]+)[|%]]')
			local border_color, text_color
			local color_box = data['color box format'] or data['rail box format'] or {}
			if line_data then
				if line_data['types'] and line_data['types'][typeN] then
					local Type_data = line_data['types'][typeN]
					border_color = Type_data['border color'] or line_data['border color'] or color
					text_color = Type_data['text color'] or line_data['text color']
					if color_box == 'title' and not args[4] then
						lineN = Type_data['short name'] or line_data['short name'] or require('Module:Delink')._delink{line}
					else
						lineN = Type_data['short name'] or line_data['short name'] or lineN
					end
				else
					border_color = line_data['border color'] or color
					text_color = line_data['text color']
					if color_box == 'title' and not args[4] then
						lineN = line_data['short name'] or require('Module:Delink')._delink{line}
					else
						lineN = line_data['short name'] or lineN
					end
				end
			else
				border_color = color
			end
			text_color = text_color and '#' .. text_color or require('Module:Color contrast')._greatercontrast{color}
			local bold = ';font-weight:bold'
			if (yesno(args.bold) == false) then bold = '' end
			if inline == 'route' then -- [[Template:RouteBox]]
				if link then
					result = '<span style="background-color:#' .. color .. ';border:.075em solid #' .. border_color .. ';padding:0 .3em">[[' .. link .. '|<span style="color:' .. text_color .. bold .. ';font-size:inherit;white-space:nowrap">' .. lineN .. '</span>]]</span>'
				else
					result = '<span style="background-color:#' .. color .. ';border:.075em solid #' .. border_color .. ';padding:0 .3em;color:' .. text_color .. bold .. ';font-size:inherit;white-space:nowrap">' .. lineN .. '</span>'
				end
			elseif inline == 'croute' then -- [[Template:Bahnlinie]]
				if link then
					result = '<span style="background-color:#' .. color .. ';border:.075em solid #' .. border_color .. ';border-radius:.5em;padding:0 .3em">[[' .. link .. '|<span style="color:' .. text_color .. bold .. ';font-size:inherit;white-space:nowrap">' .. lineN .. '</span>]]</span>'
				else
					result = '<span style="background-color:#' .. color .. ';border:.075em solid #' .. border_color .. ';border-radius:.5em;padding:0 .3em;color:' .. text_color .. bold .. ';font-size:inherit;white-space:nowrap">' .. lineN .. '</span>'
				end
			elseif inline == 'xroute' then -- [[Template:Bahnlinie]]
				if link then
					result = '<span style="border:.075em solid #' .. border_color .. ';border-radius:.5em;padding:0 .3em">[[' .. link .. '|<span style="color:#' .. color .. bold .. ';font-size:inherit;white-space:nowrap">' .. lineN .. '</span>]]</span>'
				else
					result = '<span style="border:.075em solid #' .. border_color .. ';border-radius:.5em;padding:0 .3em;color:#' .. color .. bold .. ';font-size:inherit;white-space:nowrap">' .. lineN .. '</span>'
				end
			elseif inline == 'broute' then
				if link then
					result = '<span style="background-color:#' .. color .. ';border:.075em solid #000;padding:0 .3em">[[' .. link .. '|<span style="color:' .. text_color .. bold .. ';font-size:inherit;white-space:nowrap">' .. lineN .. '</span>]]</span>'
				else
					result = '<span style="background-color:#' .. color .. ';border:.075em solid #000;padding:0 .3em;color:' .. text_color .. bold .. ';font-size:inherit;white-space:nowrap">' .. lineN .. '</span>'
				end
			else -- [[Template:Legend]] (fallback; duplication to simplify logic)
				result = '<div class="legend" style="page-break-inside:avoid;break-inside:avoid-column"><span class="legend-color" style="display:inline-block;min-width:1.25em;height:1.25em;line-height:1.25;margin:1px 0;border:1px solid black;background-color:#' .. color .. '"> </span> ' .. line .. result .. '</div>'
			end
		end

		result = mw.ustring.gsub(result, ':%s*#transparent', ':transparent')

		return result
	end
end

p.box = makeInvokeFunction('_box')

function p._icon(args, frame)
	local system = args[1] or args.system
	local data = args.data

	if not system and not data then
		return
	end

	data = data or getData(system)

	local line, line_name = getLine(data, args[2] or args.line)

	local icon
	local icon_format

	if line then
		local line_type = args[3] or args.type
		if line_type then
			line_type = data.aliases and  data.aliases[mw.ustring.lower(line_type)] or line_type
			line_type = line.types and line.types[line_type] -- If there's no type table or entry for this type, then it can't have its own icon
			icon_format = line_type['icon format'] or data['type icon format']

			if line_type.icon then
				icon = line_type.icon
			end
		end

		if not icon then
			icon = line.icon
		end

		-- Only if there is no icon use the icon_format.
		if not icon and not icon_format then
			icon_format = line['icon format'] or data['line icon format']
		end

		local default = data.lines._default or {}
		if icon and string.find(icon, "%%1") and default and default.icon then
			icon = mw.ustring.gsub(default.icon, '%%1', line_name)
		end

	end

	if not icon then
		icon = data['system icon']
	end

	if not icon_format then
		icon_format = data['system icon format']
	end

	if icon_format then
		if icon_format ~= 'image' then
			icon = p._box({data = data, [2] = (args[2] or args.line), [3] = icon_format, type = (args[3] or args.type), bold = args.bold, link = args.link}, frame)

			if args.name then
				if line and line.title then
					return icon .. " " .. line.title
				end
				return icon .. " " .. data["system title"]
			end
		end
	end

	local size = args.size
	if size then
		if mw.ustring.match(size, '%d$') then
			size = '|' .. size .. 'px'
		else
			size = '|' .. size
		end
		-- Upright values are to be disabled until there is use of upright scaling in subpages; doesn't seem to work anyway as of 2018-08-10
		local regex = {
			'|%s*%d*x?%d+px%s*([%]|])', -- '|%s*upright=%d+%.?%d*%s*([%]|])', '|%s*upright%s*([%]|])'
		}
		if mw.ustring.match(icon, regex[1]) then
			icon = mw.ustring.gsub(icon, regex[1], size .. '%1')
	--	elseif mw.ustring.match(icon, regex[2]) then
	--		icon = gsub(icon, regex[2], size .. '%1')
	--	elseif mw.ustring.match(icon, regex[3]) then
	--		icon = gsub(icon, regex[3], size .. '%1')
		else
			icon = mw.ustring.gsub(icon, '(%[%[[^%]|]+)([%]|])', '%1' .. size .. '%2')
		end
	end

	local link = args.link
	if link then
		if mw.ustring.match(icon, '|%s*link=[^%]|]*[%]|]') then
			icon = mw.ustring.gsub(icon, '|%s*link=[^%]|]*([%]|])', '|link=' .. link .. '%1')
		else
			icon = mw.ustring.gsub(icon, '(%[%[[^%]|]+)([%]|])', '%1|link=' .. link .. '%2')
		end
	end

	local alt = args.alt or link
	if alt then
		if mw.ustring.match(icon, '|%s*alt=[^%]|]*[%]|]') then
			icon = mw.ustring.gsub(icon, '|%s*alt=[^%]|]*([%]|])', '|alt=' .. alt .. '%1')
		else
			icon = mw.ustring.gsub(icon, '(%[%[[^%]|]+)([%]|])', '%1|alt=' .. alt .. '%2')
		end
	end

	if args.name then
		if line and line.title then
			return icon .. " " .. line.title
		end
		return icon .. " " .. data["system title"]
	end
	return icon
end

p.icon = makeInvokeFunction('_icon')

function p._line(args, frame)
	local system = args[1] or args.system
	local line = args[2] or args.line
	if not line then return '' end
	local Type = args[3] or args.type
	local data = args.data
	if system or data then
		data = data or getData(system, true)
		if data then
			line = (getLine(data, line)) or error(i18n[lang]['error_unknown'](line))
			if Type then
				Type = data['aliases'] and data['aliases'][mw.ustring.lower(Type)] or Type
				Type = line['types'] and line['types'][Type] and line['types'][Type]['title'] or Type
			end
			line = line['title'] or error(i18n[lang]['error_missing']('title'))
		else
			line = frame:expandTemplate{ title = system .. ' lines', args = {line, ['branch'] = Type} }
			if mw.text.trim(line) == '' then return error(i18n[lang]['error_unknown'](lineN)) end
		end

		if Type and Type ~= '' then
			if line == '' then
				line = Type
			else
				line = line .. ' – ' .. Type
			end
		end
		return line
	end
end

p.line = makeInvokeFunction('_line')

function p._station(args, frame)
	local system = args[1] or args.system
	local station = args[2] or args.station
	if not station then return '' end
	lineN = args[3] or args.line
	typeN = args[4] or args.type
	local data = args.data
	if system or data then
		data = data or getData(system, true)
		if data then
			local _Format = data['station format'][station] or data['station format'][1]
			if _Format then
				if data['aliases'] then
					if lineN then
						lineN = data['aliases'][mw.ustring.lower(lineN)] or lineN
					end
					if typeN then
						typeN = data['aliases'][mw.ustring.lower(typeN)] or typeN
					end
				end
				station = getStation(station, _Format)
			else
				station = station or ''
			end
		else
			station = frame:expandTemplate{ title = system .. ' stations', args = {['station'] = station, ['line'] = lineN, ['branch'] = typeN} }
		end

		return station
	end
end

p.station = makeInvokeFunction('_station')

function p._terminusTable(args, frame)
	local system = args[1] or args.system
	lineN = args[2] or args.line
	local side = mw.ustring.sub(mw.ustring.lower(args[3] or args.side or ''), 1, 1)
	typeN = args.type
	local prefix = (side == 'r') and 'right' or 'left'
	local data = args.data

	if system or data then
		data = data or getData(system, true)
	end
	if data then
		local line = getLine(data, lineN) or error(i18n[lang]['error_unknown'](lineN))
		if typeN and data and data['aliases'] then typeN = data['aliases'][mw.ustring.lower(typeN)] or typeN end
		local Type = line['types'] and line['types'][typeN]

		local circular
		if Type then
			if Type['circular'] then
				-- Type may override the circular status of the line
				circular = Type['circular']
			end
		else
			circular = line['circular']
		end

		return Type and Type[prefix .. ' terminus'] or line[prefix .. ' terminus'], data['station format'] or i18n[lang]['error_format'], circular
	else
		local terminus = frame:expandTemplate{ title = 'S-line/' .. system .. ' ' .. prefix .. '/' .. lineN }
		return mw.ustring.gsub(terminus, '{{{type}}}', typeN)
	end
end

function p._terminus(args, frame)
	local var, Format, circular = p._terminusTable(args, frame)

	return circular and var or getTerminusText(var, Format)
end

p.terminus = makeInvokeFunction('_terminus')

function p._style(args, frame)
	local style = args[1] or args.style
	local system = args[2] or args.system
	local line = args[3] or args.line
	local station = args[4] or args.station
	local result = {}
	local data = args.data
	local default = 'background-color:#efefef' -- Default background color for {{Infobox station}}
	if system or data then
		data = data or getData(system, true)
	end
	if data then
		local function getValue(var)
			if type(var) == 'table' then
				var = var[line] or var[1]
				if type(var) == 'table' then
					var = var[station] or var[1]
				end
			end
			if var ~= '' then return var end
		end

		if style == 'header' then
			local tmp = data['name format'] and getValue(data['name format'])
			if tmp then table.insert(result, tmp) end
		elseif style == 'subheader' then
			local tmp = data['header background color'] and getValue(data['header background color'])
			if tmp then
				table.insert(result, 'background-color:#' .. tmp)
				local color = data['header text color'] and getValue(data['header text color'])
				if color then
					table.insert(result, 'color:#' .. color)
				else
					local greatercontrast = require('Module:Color contrast')._greatercontrast
					if greatercontrast{tmp} == '#FFFFFF' then table.insert(result, 'color:#FFFFFF') end
				end
			else
				table.insert(result, default)
				local color = data['header text color'] and getValue(data['header text color'])
				if color then table.insert(result, 'color:#' .. color) end
			end
		end
		result = table.concat(result, ';')
	elseif system then
		local title = 'Template:' .. system .. ' style'
		local titleObj = mw.title.new(title)
		if titleObj and titleObj.exists then
			local tmp
			if style == 'header' then
				tmp = frame:expandTemplate{ title = title, args = {'name_format', line, station} }
				if tmp ~= '' then table.insert(result, tmp) end
			elseif style == 'subheader' then
				tmp = frame:expandTemplate{ title = title, args = {'thbgcolor', line, station} }
				if tmp ~= '' then
					table.insert(result, 'background-color:#' .. tmp)
					local color = frame:expandTemplate{ title = title, args = {'thcolor', line, station} }
					if color ~= '' then
						table.insert(result, 'color:#' .. color)
					else
						local ratio = require('Module:Color contrast')._ratio
						if ratio{tmp, '222222'} < 4.5 then table.insert(result, 'color:#FFFFFF') end -- 222222 is the default text color in Vector
					end
				else
					table.insert(result, default)
					tmp = frame:expandTemplate{ title = title, args = {'thcolor', line, station} }
					if tmp ~= '' then
						table.insert(result, 'color:#' .. tmp)
					end
				end
			end
			result = table.concat(result, ';')
		else
			if style == 'subheader' then
				result = default
			else
				result = ''
			end
		end
	else
		if style == 'subheader' then
			result = default
		else
			result = ''
		end
	end

	return result
end

function p.style(frame)
	local args = getArgs(frame, {frameOnly = true})
	return p._style(args, frame)
end

function p.convert(frame)
	local args = frame.args
	local code = mw.text.split(mw.ustring.gsub(args[1], '^%s*{{(.*)}}%s*$', '%1'), '%s*}}%s*{{%s*')
	local system
	local group = tonumber(args.offset or 0) or 0
	local firstgroup = group + 1
	local delete = {
		['s-rail'] = true,
		['s-rail-next'] = true,
		['s-rail-national'] = true,
		['s-start'] = true,
		['s-rail-start'] = true,
		['start'] = true,
		['s-end'] = true,
		['end'] = true
	}
	local order = {
		'line', 'left', 'right', 'to-left', 'to-right',
		'oneway-left', 'oneway-right', 'through-left', 'through-right',
		'reverse', 'reverse-left', 'reverse-right',
		'note-left', 'note-mid', 'note-right', 'transfer'
		-- circular: use module subpage
		-- state: not implemented
	}
	local replace = {
		['previous'] = 'left',
		['next'] = 'right',
		['type'] = 'to-left',
		['type2'] = 'to-right',
		['branch'] = 'type',
		['note'] = 'note-left',
		['notemid'] = 'note-mid',
		['note2'] = 'note-right',
		['oneway1'] = 'oneway-left',
		['oneway2'] = 'oneway-right',
		['through1'] = 'through-left',
		['through2'] = 'through-right'
	}
	local remove_rows = {}
	local data = {}
	local noclear = false
	for i, v in ipairs(code) do
		code[i] = mw.ustring.gsub(code[i], '\n', ' ')
		local template = mw.ustring.lower(mw.text.trim(mw.ustring.match(code[i], '^[^|]+')))
		code[i] = mw.ustring.match(code[i], '(|.+)$')
		if (mw.ustring.match(code[i] or '', 'noclear%s*=%s*[a-z]')) then
			noclear = true
		end
		if template == 's-line' then
			data[i] = {}
			local this_system = mw.text.trim(mw.ustring.match(code[i], '|%s*system%s*=([^|]+)'))
			code[i] = mw.text.split(code[i], '%s*|%s*')
			for m, n in ipairs(code[i]) do
				local tmp = mw.text.split(n, '%s*=%s*')
				if tmp[3] then
					tmp[2] = mw.ustring.gsub(n, '^.-%s*=', '')
				end
				tmp[1] = replace[tmp[1]] or tmp[1]
				if tmp[2] then
					-- checks for matching brackets
					local curly = select(2, mw.ustring.gsub(tmp[2], "{", ""))-select(2, mw.ustring.gsub(tmp[2], "}", ""))
					local square = select(2, mw.ustring.gsub(tmp[2], "%[", ""))-select(2, mw.ustring.gsub(tmp[2], "%]", ""))
					if not (curly == 0 and square == 0) then
						local count = mw.clone(m)+1
						while not (curly == 0 and square == 0) do
							tmp[2] = tmp[2]..'|'..code[i][count]
							curly = curly+select(2, mw.ustring.gsub(code[i][count], "{", ""))-select(2, mw.ustring.gsub(code[i][count], "}", ""))
							square = square+select(2, mw.ustring.gsub(code[i][count], "%[", ""))-select(2, mw.ustring.gsub(code[i][count], "%]", ""))
							code[i][count] = ''
							count = count+1
						end
					end
					data[i][tmp[1]] = tmp[2]
				end
			end
			if (this_system ~= system) or (not system) then
				system = this_system
				data[i]['system'] = system
			else
				data[i]['system'] = nil
			end
			local last = data[i-1] or data[i-2] or data[i-3]
			if last then
				for r, s in pairs({
					['hide1'] = {'left', 'to-left', 'note-left', 'oneway-left'},
					['hide2'] = {'right', 'to-right', 'note-right', 'oneway-right'},
					['hidemid'] = {'type', 'note-mid'}
					}) do
					if data[i][r] then
						for m, n in ipairs(s) do
							if not data[i][n] then
								data[i][n] = last[n]
							end
						end
					end
				end
			end
			code[i] = {}
			local X = '|'
			local Y = (i+group)..'='
			if data[i]['system'] then
				table.insert(code[i], '|system')
				table.insert(code[i], Y)
				table.insert(code[i], data[i]['system'])
				table.insert(code[i], '\n')
			end
			for m, n in ipairs(order) do
				if data[i][n] then
					table.insert(code[i], X)
					table.insert(code[i], n)
					table.insert(code[i], Y)
					table.insert(code[i], data[i][n])
				end
			end
			code[i] = table.concat(code[i])
		elseif template == 's-note' then
			code[i] = mw.ustring.gsub(code[i], '|%s*text%s*=', '|header'..i+group..'=')
			code[i] = mw.ustring.gsub(code[i], '|%s*wide%s*=[^|]*', '')
		elseif template == 's-text' then
			code[i] = mw.ustring.gsub(code[i], '|%s*text%s*=', '|note-row'..i+group..'=')
		elseif delete[template] then
			code[i] = ''
			table.insert(remove_rows, 1, i) -- at the start, so that the rows are deleted in reverse order
			group = group-1
		end
	end
	for i, v in ipairs(remove_rows) do
		table.remove(code, v)
	end
	code = table.concat(code, '\n')
	local t = {'{{Adjacent stations' .. (noclear and '|noclear=y\n' or ''), '\n}}'}
	system = mw.ustring.match(code, '|system(%d*)=')
	code = mw.ustring.gsub(code, '\n\n+', '\n')
	if tonumber(system) > firstgroup then
		-- If s-line isn't the first template then the system will have to be moved to the top
		system = mw.ustring.match(code, '|system%d*=([^|]*[^|\n])')
		code = mw.ustring.gsub(code, '|system%d*=[^|]*', '')
		code = '\n|system'..firstgroup..'='..system..code
	elseif not mw.ustring.match(code, '^[^{%[]*|[^=|]+2=') then
		-- If there's only one parameter group then there's no need to have line breaks
		code = mw.ustring.gsub(code, '\n', '')
		code = mw.ustring.gsub(code, '(|[^=|]+)1=', '%1=')
		t[2] = '}}'
		if not mw.ustring.match(code, '[%[{]') then
			code = mw.ustring.gsub(code, '|[^=|]*=$', '')
			code = mw.ustring.gsub(code, '|[^=|]*$', '')
		end
	end
	if not mw.ustring.match(code, '[%[{]') then
		code = mw.ustring.gsub(code, '|[^=|]*=|', '|')
		code = mw.ustring.gsub(code, '|[^=|]*|', '|')
		code = mw.ustring.gsub(code, '|[^=|]*=\n', '\n')
		code = mw.ustring.gsub(code, '|[^=|]*\n', '\n')
	end
	return t[1]..code..t[2]
end

return p