Module:Adjacent stations

require('strict')

local p = {}

local lang = 'en-GB' -- 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 .. ' was "' .. key .. '" but neither an entry for it nor a default was found. Choices were: ' .. 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({, 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 ' ' .. s .. ' '			or ' ' .. s .. ' '	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 notheme" style="color:inherit;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 ' ' .. (Type and Type['title'] or typeN) .. ' ' 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('transfer at ' .. 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 = '    ' .. line .. result .. ' '		elseif inline == 'yes' then result = '      ' .. line .. result elseif inline == 'box' then result = '      ' .. result elseif inline == 'link' then local link = args.link or mw.ustring.match(line, '%[%[([^%[:|%]]+)[|%]]') if link then result = '      ' .. result else result = '      ' .. result end elseif inline == 'square' then result = ' ■ ' .. line .. result elseif inline == 'lsquare' then local link = args.link or mw.ustring.match(line, '%[%[([^%[:|%]]+)[|%]]') if link then result = ' ■ ' else result = ' ■ ' end elseif inline == 'dot' then result = ' ● ' .. line .. result elseif inline == 'ldot' then local link = args.link or mw.ustring.match(line, '%[%[([^%[:|%]]+)[|%]]') if link then result = ' ● ' else result = ' ● ' end elseif inline == 'small' then result = '   ' .. ' ' .. 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 = '' .. lineN .. ' ' else result = '' .. lineN .. ' '				end elseif inline == 'croute' then -- Template:Bahnlinie if link then result = '' .. lineN .. ' ' else result = '' .. lineN .. ' '				end elseif inline == 'xroute' then -- Template:Bahnlinie if link then result = ' ' .. lineN .. ' ' else result = '' .. lineN .. ' '				end elseif inline == 'broute' then if link then result = ' ' .. lineN .. ' ' else result = '' .. lineN .. ' '				end else -- Template:Legend (fallback; duplication to simplify logic) result = '    ' .. line .. result .. ' '			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._shortline(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 typeN = args.type local data = args.data if system or data then data = data or getData(system, true) 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 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) else line = frame:expandTemplate{ title = system .. ' lines', args = {lineN, ['branch'] = typeN} } if mw.text.trim(line) == '' then return error(i18n[lang]['error_unknown'](lineN)) end Type = typeN end

local result

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

local link = args.link or mw.ustring.match(line, '%[%[([^%[:|%]]+)[|%]]') if line_data then if line_data['types'] and line_data['types'][typeN] then local Type_data = line_data['types'][typeN] lineN = Type_data['short name'] or line_data['short name'] or lineN else lineN = line_data['short name'] or lineN end end if link then result =  .. lineN ..  else result = lineN end result = mw.ustring.gsub(result, ':%s*#transparent', ':transparent')

return result end end

p.shortline = makeInvokeFunction('_shortline')

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, '', 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 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