Gaa na ọdịnaya

Module:Adjacent stations

Shí Wikipedia, njikotá édémédé nke onyobulạ

Lua error in Module:Lua_banner at line 113: attempt to index field 'edit' (a nil value).

Templeeti:Sidebar Adjacent stations

This module implements {{Adjacent stations}}, {{Rail icon}}, {{Rail color box}}, {{Line link}}, {{Station link}} and {{Rail color}}. Please see those templates' pages for documentation on how to use those templates. (Instructions for the convert function of this module are in the {{Adjacent stations}} documentation.)

The aforementioned templates rely on data stored in subpages for this module (list). For example, {{Rail icon|MTR}} generates MTR using Module:Adjacent stations/MTR.

It is possible to create and edit data by following existing examples, but having some knowledge of Lua helps prevent mistakes. If you have programmed or used Lua before, you may like to skip the next subsection.

Terms

  • Lua has data types. The ones relevant here are boolean, string, number, and table.
    • A boolean is either true or false.
    • A string is text, stored as a list of characters. While Lua has several ways of indicating strings within code, in the Lua examples here, strings are indicated by enclosing text in double quotes (e.g. "This is a string").
    • A number is a numerical value, like 0.5 or 42.
    • A table is a structure that can contain other objects, including other tables.
      • An empty table looks like {} in the code.
      • Tables have keys and values, which are typically of the structure ["key"] = value; each key–value pair is separated by a comma. All keys used here are strings or numbers.
      • {"text", "more text"} is equivalent to {[1] = "text", [2] = "more text"}.
  • A variable can be defined using local variable_name = "value".
  • Whitespace is any tab, line break or other space. Whitespace doesn't matter in Lua, but all examples here except for those inline with text are neatly indented for readability, with separate table keys on separate lines.
  • A return statement (e.g. return variable_name) causes a function to exit and reports the value of variable_name. The "function" here is the code in the main module calling the subpage, and the variable_name should be the data table.

Basic structure

Two modules are demonstrated below:

Blank Templeeti:Syntaxhighlight
Example Templeeti:Syntaxhighlight

The example module is Module:Adjacent stations/Kaohsiung Rapid Transit.

  • The two required table entries are "station format" and "lines". The former is a table with data to form links to station articles, and the latter is a table containing a table for each line.
  • "system title" is the text in the middle cell of the table header row.
  • "station format" defines the default name format for station articles and exceptions. The first variable ("%1 metro station") is the default. Exceptions are listed as key–value pairs (e.g. "Zuoying"–"Zuoying HSR station"), where the key is the input name. The module displays the input name and links to the article with the formatted name, replacing "%1" with the input. Alternatively, the full wikilink and be entered. This can be used to have the display be different from the input.
  • "lines" is where the lines are listed. The names here are used internally and not displayed, so choose a concise one.
  • "line title" is the text displayed in the middle of each row indicating the line; "left terminus" is the default station name for the left side terminus, and "right terminus" is the default station name for the right side terminus.
  • Each "color" entry is the colour of the line.

Below is Module:Adjacent stations/Taiwan High Speed Rail: Templeeti:Syntaxhighlight

  • x (defined in the first line) is a string used for formatting the station name. The variable x is used inside the "station format" table for the three articles where the title ends in "station" instead of "HSR station". This practice is optional but helpful when many articles fit a second pattern.
  • The module recognises a virtual line named ["_default"]. The title and colour of this line is used for all lines unless overridden. Parameters are used in the absence of a specified line= in transclusion.

Hierarchy and list of parameters

  1. The first layer of the table is data for the entire system, as well as output options.
  2. Under the system table is the list of lines.
  3. The third layer is data for a given line.
  4. Each line can have 'types'. This can be either types of services or branches of the line.
  5. The fifth layer is data for a given type.

If not specified, all keys and values are strings.

Main layer (1)

Parameter Type Used in {{Adjacent stations}} Description
["lang"] String Yes Values are "en-US" and "en-GB". If not set, "en-GB" is assumed.
["system title"] String Yes Text in the middle cell of the header.
["system icon"] String Yes Image used in the middle cell of the {{Adjacent stations}} header and by {{Rail icon}}.
["system icon format"] String No Icon type, used by {{Rail icon}}. If specified and not "image", the value is passed to the function that implements {{Rail color box}}.
["system color"] String No RGB hex triplet (three or six characters, like "BE2D2C" or "039"). Can be called by using only one parameter in {{Rail color}}.
["header stop noun"] String Yes The noun after 'preceding' and 'following' in the left and right header cells. Default value is "station".
["name format"] String No CSS for the header of {{Infobox station}} and anything else using the style function with |1=header. Values can be strings or nested tables, with the first level being for the line (whatever's in |style2= of {{Infobox station}}). The second level is currently unused. The first entry in a nested table with no key (i.e. with key 1) is the default.
["header background color"] String No RGB hex triplet for {{Infobox station}} subheaders and anything else using the style function with |1=subheader. By default, it is a light gray. Values can be strings or nested tables, like those for "name format".
["header text color"] String No RGB hex triplet for {{Infobox station}} subheaders and anything else using the style function with |1=subheader. By default, it is calculated based on the header background color. Values can be strings or nested tables, like those for ["name format"].
["station format"] Table or string Yes Table containing station format strings. The first entry without a specified key (i.e. with the key being the number 1) is the default, and all other entries must have keys corresponding to the input. Format strings without wikilink brackets are converted to links, with the input (usually the station name) used as the displayed text. Tables can be nested within this table to indicate options based on the line and line type passed to this template.

%1, %2 and %3 can be used in all strings regardless of the level of nesting to be replaced respectively by the station input, the line input (after alias replacement) and the type input (after alias replacement).

["lines"] Table Yes Data table containing line tables.
["aliases"] Table Yes Table containing aliases (as table keys) for lines (as values). All keys are lowercase, as the input is treated as case-insensitive by being lower-cased.

Station format table (2)

Parameter Type Used in {{Adjacent stations}} Description
[1] String Yes Default format.
["non-default station name"] String or table Yes Format for a non-default station, or line-specific format table.

Line-specific format table (3)

Parameter Type Used in {{Adjacent stations}} Description
[1] String Yes Default format.
["line name"] String or table Yes Format for a non-default station, or type-specific format table.

Type-specific format table (4)

Parameter Type Used in {{Adjacent stations}} Description
[1] String Yes Default format.
["type name"] String Yes Format for a non-default station.

Line table (3)

A virtual line named ["_default"] can be added to set default values for all lines. Currently, this is available for three parameters.

Parameter Type Used in {{Adjacent stations}} Description
["title"] String Yes The text displayed in the middle cell, typically a link to the line's article. If not specified, then the data in ["_default"] is used (%1 in the default value is replaced by the input after alias replacement).
["short name"] String No Abbreviated line name used by {{Rail color box}}.
["icon"] String No Image used by {{Rail icon}}. If not specified, then the data in ["_default"] is used (%1 in the default value is replaced by the input after alias replacement).
["icon format"] String No Icon type used by {{Rail icon}}. If specified and not "image", the value is passed to the function that implements {{Rail color box}}.
["color"] String Yes RGB hex triplet. Lines fall back to the ["_default"] colour (if any) or the system's colour if they themselves do not have one; types fall back to the line's colour (if any), to the ["_default"] colour (if any) or to the system's colour. This colour is used in the second and fourth columns of {{Adjacent stations}}, and by {{Rail color box}} and {{Rail icon}} as the emphasised colour. By default, if a type and its line both have a colour, then the line's colour will be treated as the background colour (see next section) for the line name in the middle cell. This can be turned off by setting the type's background colour to "" or "transparent".
["background color"] String Yes RGB hex triplet (three or six characters). This colour is optional and is only displayed behind the line name in the middle cell. The module adds transparency so that all text displayed over the background is legible.
["border color"] String No RGB hex triplet used by {{Rail color box}}.
["text color"] String No RGB hex triplet used by {{Rail color box}}.
["left terminus"] String Yes The station which is usually the left terminus of the line. If there are multiple stations by default, the value should be a table containing numbered values (e.g. ["left terminus"] = {"Chesham", "Amersham"}). The key ["via"] in that table can be used to append 'via' and the value's station link.
["right terminus"] String Yes The station which is usually the right terminus of the line; behaves like ["left terminus"].
["note-mid"] String Yes Default small text below line and type names. Overridden by |note-mid= in transclusion.
["circular"] Boolean Yes If the value is true then the termini will display without 'toward'/'towards'. May be overridden by type.
["oneway-left"] Boolean Yes If the value is true then 'One-way operation' will display instead of the left terminus.
["oneway-right"] Boolean Yes Right counterpart of oneway-left.
["types"] Table Yes Table containing the line type tables.

Type table (5)

Parameter Type Used in {{Adjacent stations}} Description
["title"] String Yes The name of the line type. In {{Adjacent stations}}, this is displayed as normal-sized text below the line name in the middle cell; in {{Rail color box}}, for some options this is displayed after the line name, separated from it by a spaced en dash (this is also used for the nonstop text). To avoid displaying a type name, set this to "".
["short name"] String No Abbreviated line name used by {{Rail color box}}.
["icon"] String No Image used by {{Rail icon}}.
["icon format"] String No Icon type used by {{Rail icon}}. If specified and not "image", the value is passed to the function that implements {{Rail color box}}.
["color"] String Yes RGB hex triplet. Lines fall back to the ["_default"] colour (if any) or the system's colour if they themselves do not have one; types fall back to the line's colour (if any), to the ["_default"] colour (if any) or to the system's colour. This colour is used in the second and fourth columns of {{Adjacent stations}}, and by {{Rail color box}} and {{Rail icon}} as the emphasised colour. By default, if a type and its line both have a colour, then the line's colour will be treated as the background colour (see next section) for the line name in the middle cell. This can be turned off by setting the type's background colour to "" or "transparent".
["background color"] String Yes RGB hex triplet (three or six characters). This colour is optional and is only displayed behind the line name in the middle cell. The module adds transparency so that all text displayed over the background is legible.
["border color"] String No RGB hex triplet used by {{Rail color box}}.
["text color"] String No RGB hex triplet used by {{Rail color box}}.
["left terminus"] String Yes The station which is usually the left terminus of the line. Overrides line terminus. If there are multiple stations by default, the value should be a table containing numbered values (e.g. ["left terminus"] = {"Chesham", "Amersham"}). The key ["via"] in that table can be used to append 'via' and the value's station link.
["right terminus"] String Yes The station which is usually the right terminus of the line; behaves like ["left terminus"].
["note-mid"] String Yes Default small text below line and type names. Overridden by |note-mid= in transclusion.
["circular"] Boolean Yes If the value is true then the termini will display without 'toward'/'towards'.

For editors

Disambiguating stations

Station links are generated using the station format part of the data module. Each data module defines a default case and then exceptions, if necessary. Station format is an array, similar to a switch with cases. Take Module:Adjacent stations/Incheon Subway, shown below:

Templeeti:Syntaxhighlight

The default case is "%1 station", listed first. The "%1" expands to whatever the passed name of the station is. "Bakchon" becomes Bakchon station. There are several exceptions. The two usual reasons for exceptions are disambiguation or presenting a name in a non-standard way. In this case, the Incheon Subway serves three stations whose names are disambiguated: Arts Center station (Incheon), Central Park station (Incheon), and Mansu station (Incheon). This is handled by specifying a key for each station and supplying a different format. Since all three are disambiguated the same way, you can define a local variable at the top of the module:

Templeeti:Syntaxhighlight

This can then be used with the exceptions:

Templeeti:Syntaxhighlight

Were it written out, it would look like this:

Templeeti:Syntaxhighlight

For developers

Suggestions are welcomed on the talk page.

To-do list

  • Convert more systems from {{S-line}}, {{rail line}}, {{J-rserv}} and {{J-route}}
  • Make an example module which contains all of the module's features, to avoid excessive examples in the documentation (maybe based on {{Rdt demo}})
  • Allow direct replacement of {{Rail line}}?
  • Before translation: figure out how to handle grammatical gender and inflection in various languages with the i18n table (e.g. these phrases)
  • Allow inline sources to be added
  • Figure out Wikidata integration (require sources on Wikidata end) :
    should be doable with P197, P5051, P1192 and P81. Bouzinac (talk) 09:17, 3 December 2021 (UTC)
  • Add a short list of changes from {{S-line}}, for the convenience of the many editors who have used it in the last 11 years
    • changes in function (new structure, data inside module, circular and branch functionality changed, replacement of manual cell merging…)
    • parameter name changes (-left and -right, mostly – search {{S-line}} for Templeeti:(((, maybe with the TemplateData generator, to make a list)

local p, data, defaultData, lineData, typeData, isFuncAdjacent = {}
local function dig ( ... )
	-- Digs through a table with sub-tables using arguments as keys, returning the value of the last key argument
	-- Analogous to returning a file given a file path with sub-folders
	-- Returns nil if any given sub-table does not exist
	local arg, n = { ... }, select ( '#', ... )
	local a, i = arg[1], 1
	while a and i < n do
		a = a[arg[i + 1]]
		i = i + 1
	end
	return a or nil
end
local function makeInvokeFunc ( funcName )
	return function ( frame )
		local args = require ( 'Module:Arguments' ).getArgs ( frame )
		if funcName == '_adjacent' then
			isFuncAdjacent = true
			local tableTools = require ( 'Module:TableTools' )
			args = tableTools.numData ( args )
			if args.other then
				args[1] = args[1] or {}
				for k, _ in pairs ( args.other ) do args[1][k] = args[1][k] or args['other'][k] end
			end
			return p[funcName]( tableTools.compressSparseArray ( args ))
		else
			args.system = args.system or args[1]
			if args.system then
				local s = 'Module:Adjacent stations/' .. args.system
				data = mw.title.new ( s ).exists and mw.loadData ( s )
			end
			if funcName ~= '_infobox' then
				args.line = args.line or args[funcName == '_station' and 3 or 2]
				if data and args.line then
					args.line =
						dig ( data, 'lines', args.line ) and args.line or
						dig ( data, 'aliases', string.lower ( args.line ))
				end
				args['type'] = args['type'] or args[funcName == '_station' and 4 or 3]
				if funcName ~= '_station' then
					defaultData = dig ( data, 'lines', '_default' )
					lineData = dig ( data, 'lines', args.line )
					typeData = dig ( lineData, 'types', args['type'] )
				end
			end
			return p[funcName]( args, frame )
		end
	end
end
p.infobox = makeInvokeFunc ( '_infobox' )
function p._infobox ( args, frame )
	local data = dig ( data, 'infobox station') or data
	if args[3] == 'header' then return
		dig ( data, 'header', args[2] or 1) or
		dig ( data, 'name format', args[2] or 1) or
		(args[2] or args.system) and frame:expandTemplate {
			title = ( args[2] or args.system ) .. ' style',
			args = { 'name_format' }
		}
	elseif args[3] == 'sub-header' then
		if dig ( data, 'sub-header') then
			return dig ( data, 'sub-header', args[2] or 1)
		else
			local t = {}
			t[1] = 'background:'
			t[2] =
				dig ( data, 'header background color', args[2] or 1) or
				(args[2] or args.system) and frame:expandTemplate {
					title = ( args[2] or args.system ) .. ' style',
					args = { 'thbgcolor' }
				} or
				'#EFEFEF'
			t[2] = string.match ( t[2], '#') and t[2] or '#' .. t[2]
			t[3] = ';color:'
			t[4] =
				dig ( data, 'header text color', args[2] or 1) or
				(args[2] or args.system) and frame:expandTemplate {
					title = ( args[2] or args.system ) .. ' style',
					args = { 'thcolor' }
				} or
				require ( 'Module:Color contrast' )._greatercontrast ({ t[2] })
			t[4] = string.match ( t[4], '#') and t[4] or '#' .. t[4]
			return table.concat ( t )
		end
	end
end
local function stationTitle ( station, args, n )
	if station and data then
		local data = isFuncAdjacent and data[n] or data
		local line = isFuncAdjacent and args[n]['line'] or args['line']
		local typ = isFuncAdjacent and args[n]['type'] or args['type']
		local link
		if type ( data['station format'] ) == 'table' then
			local defaultFormat = data['station format']
			if type ( defaultFormat[station] ) == 'table' then
				local stationFormat = defaultFormat[station]
				if line then
					line =
						stationFormat[line] and line or
						dig ( data, 'aliases', string.lower ( line ))
				end
				if type ( stationFormat[line] ) == 'table' then
					local lineFormat = stationFormat[line]
					link = lineFormat[typ] or lineFormat[1]
				else
					link = stationFormat[line] or stationFormat[1]
				end
			elseif type ( defaultFormat[station] ) == 'number' then
				link = defaultFormat[defaultFormat[station]]
			else
				link = defaultFormat[station] or defaultFormat[1]
			end
		else
			link = data['station format']
		end
		link = ( string.gsub ( link, '%%1', station ))
		return
			string.match ( link, '%[%[.-%]%]' ) and link or
			table.concat ({'[[', link, '|', station, ']]' })
	end
end
p.station = makeInvokeFunc ( '_station' )
function p._station ( args, frame )
	args.station = args.station or args[2]
	if data then
		return stationTitle ( args.station, args )
	else
		return frame:expandTemplate {
			title = args.system and args.system .. ' stations',
			args = {
				['station'] = args.station,
				['line'] = args.line,
				['branch'] = args['type']
			}
		}
	end
end
p.color = makeInvokeFunc ( '_color' )
function p._color ( args, frame )
	if data then
		return mw.text.nowiki (
			typeData and typeData['color'] or
			lineData and lineData['color'] or
			defaultData and defaultData['color']			
		)
	else
		return frame:expandTemplate {
			title = args.system and args.system .. ' color',
			args = { args.line, ['branch'] = args['type'] }
		}
	end
end
local root = mw.html.create ( 'div' )
local function renderBox ( style, colour, lineName, lineTitle )
	if colour then
		colour = string.match ( colour, '#' ) and colour or '#' .. colour
	end
	local box = mw.html.create ( 'span' )
	-- Colour settings
	if
		style == 'dot' or
		style == 'ldot' or
		style == 'square' or
		style == 'lsquare' or
		style == 'xroute' then
		box:css ( 'color', colour )
	else
		box:css ( 'background-color', colour )
		if style == 'route' or style == 'croute' then
			box
				:css ( 'color',
					typeData and typeData['text color'] or
					lineData and lineData['text color'] or
					require ( 'Module:Color contrast' )._greatercontrast ({ colour })
				)
		end
	end
	-- Border settings
	if
		style == 'legend' or style == nil or
		style == 'linked' or style == 'link' or
		style == 'inline' or style == 'yes' or
		style == 'box' then
		box:css ( 'border', '1px solid black' )
	elseif style and string.match ( style, 'route' ) then
		box
			:css ( 'border', '.075em solid ' .. (
				typeData and typeData['border color'] or
				lineData and lineData['border color'] or
				colour
				)
			)
		if style ~= 'route' then
			box:css ( 'border-radius', '.5em' )
		end
	end
	-- Additional style-specific settings
	if style == 'legend' or style == nil then
		root
			:addClass ( 'legend' )
			:css ( '-webkit-column-break-inside', 'avoid' )
			:css ( 'page-break-inside', 'avoid' )
			:css ( 'break-inside', 'avoid-column' )
		box
			:addClass ( 'legend-color' )
			:css ( 'display', 'inline-block' )
			:css ( 'width', '1.5em' )
			:css ( 'height', '1.5em' )
			:css ( 'margin', '1px' )
	elseif
		style == 'dot' or
		style == 'ldot' or
		style == 'square' or
		style == 'lsquare' then
		box:css ( 'line-height', 'initial' )
	elseif style and string.match ( style, 'route' ) then
		box
			:css ( 'font-weight', 'bold' )
			:css ( 'font-size', 'inherit' )
			:css ( 'white-space', 'nowrap' )
			:css ( 'padding', '0 .3em' )
	end
	-- Text for the span tag
	local boxText = {
		['inline'] = string.rep ( '&nbsp;',4 ), ['yes'] = string.rep ( '&nbsp;', 4 ),
		['box'] = string.rep ( '&nbsp;', 4 ),
		['linked'] = string.rep ( '&nbsp;', 4 ), ['link'] = string.rep ( '&nbsp;', 4 ),
		['small'] = string.rep ( '&nbsp;', 1 ),
		['legend'] = string.rep ( '&nbsp;', 1 ),
		['dot'] = '●',
		['ldot'] = '●',
		['square'] = '■',
		['lsquare'] = '■',
		['route'] = lineName,
		['croute'] = lineName,
		['xroute'] = lineName
	}
	box:wikitext ( boxText[style] or string.rep ( '&nbsp;', 1 ))
	-- Adds link to text
	if
		style == 'linked' or style == 'link' or
		style == 'ldot' or
		style == 'lsquare' or
		style and string.match ( style, 'route' ) then
			if string.match ( lineTitle, '%[%[.-%]%]' ) then
				if string.match ( lineTitle, '|' ) then
					root:wikitext (( string.gsub ( lineTitle, '%[%[(.-)|.-%]%]', '[[%1|' .. tostring ( box ) .. ']]' )))
				else
					root:wikitext (( string.gsub ( lineTitle, '%[%[(.-)%]%]',  '[[%1|' .. tostring ( box ) .. ']]' )))
				end
			else
				root:wikitext ( tostring ( box ))
			end
	else
		root:wikitext ( tostring ( box ))
	end
end
p.line = makeInvokeFunc ( '_line' )
function p._line ( args, frame )
	if data then
		local s = 
			typeData and typeData['title'] and table.concat ({ lineData['title'], ' (', typeData['title'], ')' }) or
			lineData and lineData['title'] or
			defaultData and ( string.gsub ( defaultData['title'], '%%1', args.line or '_default' ))
		if args.icon then
			if args.icon == 'image' then
				args.style = nil
				return p._icon ( args ) .. '&nbsp;' .. s
			elseif
				args.icon == 'legend' or
				args.icon == 'inline' or
				args.icon == 'small' or
				args.icon == 'dot' or
				args.icon == 'square' then
				renderBox ( args.icon, p._color ( args ), args.line, s )
				root:wikitext ( '&nbsp;', s )
				return root
			end
		else
			return s
		end
	else
		return frame:expandTemplate {
			title = args.system and args.system .. ' lines',
			args = { args.line, ['branch'] = args['type'] }
		}
	end
end
p.icon = makeInvokeFunc ( '_icon' )
function p._icon ( args, frame )
	if data then
		local s =
			typeData and typeData['icon'] or
			lineData and lineData['icon'] or
			data and data['system icon']
		if
			not s or
			args.style == 'box' or
			args.style == 'linked' or args.style == 'link' or
			args.style == 'ldot' or
			args.style == 'lsquare' or
			args.style and string.match ( args.style, 'route' ) then
			renderBox ( args.style or 'inline', p._color ( args ), args.line, p._line ( args ))
			return root
		else
			return s
		end
	else
		return frame:expandTemplate {
			title = 'Rail-interchange',
			args = { string.lower ( args.system ), string.lower ( args.line ),	['size'] = args.size }
		}
	end
end
p.box = makeInvokeFunc ( '_box' )
function p._box ( args, frame )
	local lineTitle = p._line ( args, frame )
	local style =
		args.style or args.inline or args[3] or
		dig ( typeData, 'icon format' ) or
		dig ( lineData, 'icon format' ) or
		dig ( data, 'system icon format' )
	renderBox ( style, p._color ( args, frame ), args.line, lineTitle )
	if
		style == 'legend' or style == nil or
		style == 'inline' or style == 'yes' or
		style == 'small' or
		style == 'dot' or
		style == 'square' then
		root:wikitext ( '&nbsp;', lineTitle )
	end
	return root
end
p.main = makeInvokeFunc ( '_adjacent' )
p.adjacent = makeInvokeFunc ( '_adjacent' )
function p._adjacent ( args )
	local yesNo = require ( 'Module:Yesno' )
	local i18n = require ( 'Module:Adjacent stations/i18n' )
	local root, lang = mw.html.create ( 'table' ), 'en-GB'
	root:addClass ( 'wikitable adjacent-stations' )
	local function renderHeader ( stopNoun, systemIcon, systemTitle )
		root
			:tag ( 'tr' )
				:tag ( 'th' )
					:addClass ( 'hcA' )
					:wikitext ( i18n[lang]['preceding']( stopNoun ))
					:done ()
				:tag ( 'th' )
					:attr ( 'colspan', 3 )
					:css ( 'vertical-align', 'middle' )
					:wikitext (
						systemIcon and systemTitle and systemIcon .. '&nbsp;' .. systemTitle or
						systemTitle or
						systemIcon
					)
					:done ()
				:tag ( 'th' )
					:addClass ( 'hcA' )
					:wikitext ( i18n[lang]['following']( stopNoun ))
	end
	local function renderSubHeader ( subHeader )
		root
			:tag ( 'tr' )
				:tag ( 'th' )
					:attr ( 'colspan', 5 )
					:addClass ( 'hmA' )
					:wikitext ( subHeader )
	end
	local function renderSideCell ( row, rowSpan, adjacent, terminus, oneWay, circular, through, Reverse, note )
		local mainText, subText = mw.html.create ( 'div' )
		if adjacent then
			mainText:wikitext ( adjacent )
			subText = mw.html.create ( 'div' )
			subText:addClass ( 'isA' )
			if adjacent == terminus then
				subText:wikitext ( 'Terminus' )
			else
				subText:wikitext ( oneWay and 'one-way operation' or circular and terminus or i18n[lang]['towards']( terminus ))
			end
		else
			mainText:css ( 'font-style', 'italic' )
			mainText:wikitext ( Reverse and 'Reverses direction' or through and i18n[lang]['through']( through ) or 'Terminus' )
		end
		row
			:tag ( 'td' )
				:attr ( 'rowspan', rowSpan )
				:addClass ( 'bcA' )
				:node ( mainText )
					:tag ( 'div' )
						:css ( 'font-size', 'smaller' )
						:wikitext ( note )
						:done ()
				:node ( subText )
	end
	local function renderMidCells ( row, rowSpan, colour, backgroundColour, lineTitle, typeTitle, note, transfer )
		if colour then
			colour = string.match ( colour, '#' ) and colour or '#' .. colour
		end
		row
			:tag ( 'td' )
				:attr ( 'rowspan', rowSpan )
				:addClass ( 'bbA' )
				:css ( 'background-color', colour )
				:done ()
			:tag ( 'td' )
				:attr ( 'rowspan', rowSpan )
				:addClass ( 'bcA' )
				:css ( 'background-color', backgroundColour )
				:wikitext ( lineTitle )
					:tag ( 'div' )
						:wikitext ( typeTitle )
						:done ()
					:tag ( 'div' )
						:css ( 'font-size', 'smaller' )
						:wikitext ( note )
						:done ()
					:tag ( 'div' )
						:addClass ( 'isA' )
						:wikitext ( i18n[lang]['transfer']( transfer ))
						:done ()
				:done ()
			:tag ( 'td' )
				:attr ( 'rowspan', rowSpan )
				:addClass ( 'bbA' )
				:css ( 'background-color', colour )
	end
	local function renderNonStopRow ( title, colour, isFormer )
		if colour then
			colour = string.match ( colour, '#' ) and colour or '#' .. colour
		end
		root
			:tag ( 'tr' )
				:tag ( 'td' )
					:attr ( 'colspan', 5 )
					:addClass ( 'bcA' )
						:tag ( 'div' )
							:tag ( 'span' )
								:css ( 'border', '1px solid black' )
								:css ( 'background-color', colour )
								:wikitext ( string.rep ( '&nbsp;', 4 ))
								:done ()
							:wikitext ( '&nbsp;', isFormer == true and i18n[lang]['nonstop_past']( title ) or i18n[lang]['nonstop_present']( title ))
	end
	local function renderNoteRow ( note )
		root
			:tag ( 'tr' )
				:tag ( 'td' )
					:attr ( 'colspan', 5 )
					:addClass ( 'bcA' )
					:wikitext ( note )
	end
	local function makeTerminusFunc ( n, fallback )
		return function ( side )
			local termini = fallback ( side .. ' terminus' )
			if type ( termini ) == 'string' then
				return stationTitle ( termini, args, n )
			elseif type ( termini ) == 'table' then
				local i, t, to = 1, {}, args[n]['to-' .. side] or args[n]['to']
				while termini[i] and to ~= termini[i] do
					t[i] = stationTitle ( termini[i], args, n )
					i = i + 1
				end
				local via = stationTitle ( termini['via'], args, n )
				return
					stationTitle ( termini[i], args, n ) or
					via and mw.text.listToText ( t, nil, ' or ' ) .. ' via ' .. via or
					mw.text.listToText ( t, nil, ' or ' )
			end
		end
	end
	data = {}
	local j, k, l = 2, 2, 2
	for i, v in ipairs ( args ) do
		if v.header then renderSubHeader ( v.header ) end
		args[i]['system'] = args[i]['system'] or i > 1 and args[i - 1]['system']
		if args[i]['system'] then
			data[i] = mw.loadData ( 'Module:Adjacent stations/' .. args[i]['system'] )
			lang = dig ( data[i], 'lang' ) or 'en-GB'
			defaultData = function ( n ) return dig ( data[n], 'lines', '_default' ) end
			if args[i]['line'] then
				args[i]['line'] =
					dig ( data[i], 'lines', args[i]['line'] ) and args[i]['line'] or
					dig ( data[i], 'aliases', string.lower ( args[i]['line'] )) or
					error ( i18n[lang]['error_unknown']( args[i]['line'] ))
			else
				args[i]['line'] = i == 1 and '_default' or args[i - 1]['line']
			end
			lineData = function ( n, line ) return
				dig ( data[n], 'lines', line or v.line ) or
				dig ( data[n], 'lines', dig ( data[n], 'aliases', string.lower ( line or v.line )))
			end
			typeData = function ( n ) return dig ( lineData ( n ), 'types', v['type'] ) end
			local function fallback ( parameter, n )
				return dig ( typeData ( n or i ), parameter ) or dig ( lineData ( n or i ), parameter ) or dig ( defaultData ( n or i ), parameter )
			end
			local terminus = makeTerminusFunc ( i, fallback )
			if i == 1 or args[i]['system'] ~= args[i - 1]['system'] then
				renderHeader (
					data[i]['header stop noun'] or i18n[lang]['stop_noun'],
					data[i]['system icon'],
					data[i]['system title']
			)
			end
			if v.nonstop then
				renderNonStopRow ( fallback ( 'title' ), fallback ( 'color' ), v.nonstop == 'former' )
			else
				local row = root:tag ( 'tr' )
				if i > j - 2  then
					while args[j] and
						args[j]['left'] == args[i]['left'] and
						args[j]['to-left'] == args[i]['to-left'] and
						args[j]['oneway-left'] == args[i]['oneway-left'] and
						args[j]['note-left'] == args[i]['note-left'] and
						( args[j]['through-left'] or args[j]['through'] ) == ( args[i]['through-left'] or args[i]['through'] ) and
						( args[j]['reverse-left'] or args[j]['reverse'] ) == ( args[i]['reverse-left'] or args[i]['reverse'] ) and
						fallback ( 'oneway-left', j ) == fallback ( 'oneway-left' ) and
						fallback ( 'circular', j ) == fallback ( 'circular' ) and
						not args[j]['nonstop'] and
						not args[j]['note-row'] do
						j = j + 1
					end
					renderSideCell (
						row,
						j - i,
						stationTitle ( v.left, args, i ),
						yesNo ( fallback ( 'circular' )) and fallback ( 'left terminus' ) or terminus ( 'left' ),
						yesNo ( v['oneway-left'] or fallback ( 'oneway-left' )),
						yesNo ( fallback ( 'circular' )),
						( v['through-left'] or v['through'] ) and dig ( lineData ( i, v['through-left'] or v['through'] ), 'title' ),
						yesNo ( v['reverse-left'] or v['reverse'] ),
						v['note-left']
				)
					j = j + 1
				end
				if i > k - 2 then
					while args[k] and
						args[k]['line'] == args[i]['line'] and
						args[k]['type'] == args[i]['type'] and
						args[k]['note-mid'] == args[i]['note-mid'] and
						not args[k]['nonstop'] and
						not args[k]['note-row'] do
						k = k + 1
					end
					renderMidCells (
						row,
						k - i,
						fallback ( 'color' ),
						fallback ( 'background color' ),
						lineData ( i )['title'] or ( string.gsub ( dig ( defaultData ( i ), 'title' ), '%%1', v.line )),
						typeData ( i ) and typeData ( i )['title'],
						v['note-mid'] or lineData ( i )['note-mid'],
						stationTitle ( v.transfer, args, i )
				)
					k = k + 1
				end
				if i > l - 2  then
					while args[l] and
						args[l]['right'] == args[i]['right'] and
						args[l]['to-right'] == args[i]['to-right'] and
						args[l]['oneway-right'] == args[i]['oneway-right'] and
						args[l]['note-right'] == args[i]['note-right'] and
						( args[l]['through-right'] or args[l]['through'] ) == ( args[i]['through-right'] or args[i]['through'] ) and
						( args[l]['reverse-right'] or args[l]['reverse'] ) == ( args[i]['reverse-right'] or args[i]['reverse'] ) and
						fallback ( 'oneway-right', l ) == fallback ( 'oneway-right' ) and
						fallback ( 'circular', l ) == fallback ( 'circular' ) and
						not args[l]['nonstop'] and
						not args[l]['note-row'] do
						l = l + 1
					end
					renderSideCell (
						row,
						l - i,
						stationTitle ( v.right, args, i ),
						yesNo ( fallback ( 'circular' )) and fallback ( 'right terminus' ) or terminus ( 'right' ),
						yesNo ( v['oneway-right'] or fallback ( 'oneway-right' )),
						yesNo ( fallback ( 'circular' )),
						( v['through-right'] or v['through'] ) and dig ( lineData ( i, v['through-right'] or v['through'] ), 'title' ),
						yesNo ( v['reverse-right'] or v['reverse'] ),
						v['note-right']
				)
					l = l + 1
				end
			end
		end
		if v['note-row'] then renderNoteRow ( v['note-row'] ) end
	end
	return root
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 = 0
	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 = {}
	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 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', '\n}}'}
	system = mw.ustring.match(code, '|system(%d*)=')
	code = mw.ustring.gsub(code, '\n\n+', '\n')
	if tonumber(system) > 1 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|system1='..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