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'] or ''

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*

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

local function makeTemplateFunction(funcName)

-- makes a function that can be returned from #invoke, using

-- Module:Arguments

return function (frame)

local args = getArgs(frame, {frameOnly = 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')

p['rail icon'] = makeTemplateFunction('_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')

p['station link'] = makeTemplateFunction('_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