Module:Adjacent stations: Difference between revisions

Content deleted Content added
Implemented new version
fix text color in dark mode; this generally works but may cause problems, in which case revert or try specifying a different CSS value. Some of these icons and boxes don't even have text, but the Linter complains, making it difficult to find actual problems amid the noise.
 
(212 intermediate revisions by 27 users not shown)
Line 1:
local yesno = require("Module:Yesno"'strict')
local _style = {
['3'] = 'style="width: 30%;',
['c'] = 'style="text-align: center;',
['v'] = 'vertical-align: middle;',
['bd'] = 'border:',
['t'] = 'border-top:',
['bb'] = 'border-bottom:',
['L'] = 'border-left:',
['R'] = 'border-right:',
['n'] = 'none;',
['0'] = '0px none;',
['1'] = '1px solid #aaa;'
}
local style = {
['table'] = table.concat({'style="width: 55%; max-width: 50em; margin:0.5em auto; font-size:95%; clear:both;',
_style['t'], _style['0'],
'border-collapse: separate;" cellspacing="0" cellpadding="-1"'
}),
['header leftcell'] = table.concat({_style['3'], _style['v'],
_style['bd'], _style['1'],
_style['L'], _style['n'],
_style['bb'], _style['n'],
'"|'
}),
['header banner'] = table.concat({'style="',
_style['bd'], _style['n'],
_style['t'], _style['1'],
'"|'
}),
['header midcell'] = table.concat({'colspan="3" style="', _style['v'],
_style['bd'], _style['n'],
_style['t'], _style['1'],
'"|'
}),
['header rightcell'] = table.concat({_style['3'], _style['v'],
_style['bd'], _style['1'],
_style['R'], _style['n'],
_style['bb'], _style['n'],
'"|'
}),
['body leftcell'] = table.concat({_style['c'], _style['v'],
_style['L'], _style['0'],
_style['bb'], _style['0'],
_style['R'], _style['1'],
_style['t'], _style['1'],
'"|'
}),
['body banner'] = table.concat({_style['c'],
_style['L'], _style['0'],
_style['bb'], _style['0'],
_style['R'], _style['0'],
_style['t'], _style['1'],
'width: 8px; min-width: 8px; background-color: #'
}),
['body midcell'] = table.concat({_style['c'], _style['v'],
_style['bb'], _style['0'],
'"|'
}),
['body rightcell'] = table.concat({_style['c'], _style['v'],
_style['L'], _style['1'],
_style['bb'], _style['0'],
_style['R'], _style['0'],
_style['t'], _style['1'],
'"|'
})
}
 
local functionp subFormat(s)= {}
 
return '<div style="font-size: smaller; font-style: italic">' .. s .. '</div>'
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 pfunction =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)
function p.top()
if system then
return table.concat({'{| class="wikitable" ', style['table']})
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
function p.line(args, index, data)
 
local lineTitle = {}
local function somethingMissing(name, key, formats)
local colour = {}
local leftformatKeys = {}
for k in pairs(formats) do
local right = {}
table.insert(formatKeys, k)
local note = {}
end
local leftTerminus = {}
return name .. ' was "' .. key .. '" but neither an entry for it nor a default was found. Choices were: ' .. table.concat(formatKeys, ', ')
local rightTerminus = {}
end
 
local t = {}
local function getStation(station, _Format)
if type(_Format) == 'table' then
local lineNformats = _Format
_Format = lineNformats[lineN] or lineNformats[1]
if not _Format then
error(somethingMissing('lineN', lineN, lineNformats))
elseif type(_Format) == 'table' then
local typeNformats = _Format
_Format = typeNformats[typeN] or typeNformats[1]
if not _Format then
error(somethingMissing('typeN', typeN, typeNformats))
end
end
end
if typeN then _Format = mw.ustring.gsub(_Format, '%%3', typeN) end
if lineN then _Format = mw.ustring.gsub(_Format, '%%2', lineN) end
return (mw.ustring.match(_Format, '%[%[.+%]%]')) and (mw.ustring.gsub(_Format, '%%1', station)) or table.concat({'[[', mw.ustring.gsub(_Format, '%%1', station), '|', station, ']]'})
end
 
local function getTerminusText(var, Format)
local function subst(var1, var2)
-- var1 is the terminus or table of termini; var2 is the key for the table of termini
return type(var1) == 'string' and getStation(var1, (Format[var1] or Format[1]))
or type(var1) == 'table' and #var1 > 0 and getStation(var1[var2], (Format[var1[var2]] or Format[1]))
or ''
end
 
if Format then
if type(var) == 'string' then
return subst(var)
elseif type(var) == 'table' and #var > 0 then
local t = {subst(var, 1)}
 
for i = 2, #var - 1 do
t[i] = i18n[lang]['comma'](subst(var, i))
end
 
if #var > 1 then t[#var] = i18n[lang]['or'](subst(var, #var)) end
if var['via'] then
if i18n[lang]['via-first'] then
table.insert(t, 1, i18n[lang]['via'](subst(var, 'via')))
else
table.insert(t, i18n[lang]['via'](subst(var, 'via')))
end
end
 
return table.concat(t)
else
return ''
end
else
return var or ''
end
end
 
function p._main(_args) -- Arguments are processed here instead of the main function
 
local yesno = require('Module:Yesno')
local trimq = require('Module:Trim quotes')._trim
 
local boolean = {
['oneway-left'] = true,
['oneway-right'] = true,
['reverse'] = true,
['reverse-left'] = true,
['reverse-right'] = true
}
 
local args = {} -- Processed arguments
local index = {} -- A list of addresses corresponding to number suffixes in the arguments
 
for k, v in pairs(_args) do -- Maps each raw argument to processed arguments by string matching
_args[k] = v:match('^%s*(.-)%s*$')
if _args[k] and _args[k] ~= '' then
local a = mw.ustring.match(k, '^(.*%D)%d+$') or k -- The parameter; address 1 can be omitted
local b = tonumber(mw.ustring.match(k, '^.*%D(%d+)$')) or 1 -- The address for a given argument; address 1 can be omitted
 
if boolean[a] then
v = yesno(v)
end
 
if not args[b] then
args[b] = {[a] = v}
table.insert(index, b)
elseif args[b][a] then
return error(i18n[lang]['error_duplicate'](a .. b))
else
args[b][a] = v
end
end
end
table.sort(index)
 
local function small(s, italic)
return italic and '<div class="isA">' .. s .. '</div>'
or '<div class="smA">' .. s .. '</div>'
end
 
local style = { -- Style for each cell type
['header cell'] = 'class="hcA"|',
['header midcell'] = 'colspan="3" class="hmA"|',
['body cell'] = 'class="bcA"|',
['body banner'] = 'class="bbA 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
if args.system[v] then
data[v] = args[v]['system'] and getData(args[v]['system'])
table.insert(t, table.concat({'\n|-',
-- If an address has no system, the row uses data from the previous address
'\n!', style['header leftcell'], 'Preceding station',
or data[index[i - 1]]
'\n!', style['header midcell'], (data[v]['system title'] or ('[['..args.system[v]..']]')),
or (args[v]['header'] and getData(args[index[i+1]]['system']))
'\n!', style['header rightcell'], 'Following station'
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(twikitable, '')
table.insert(twikitable, '')
table.insert(twikitable, '')
end
 
if args.line[v] or args.left[v'header'] orthen args.right[v]-- thenSubheader
table.insert(wikitable, '\n|-\n!colspan="5" class="hmA"|'.. args[v]['header'])
if not args.line[v] then args.line[v] = args.line[index[i-1]] end
table.insert(wikitable, '')
local _line = data[v]['lines'][args.line[v]]
table.insert(wikitable, '')
lineTitle[v] = _line['line title']
table.insert(wikitable, '')
colour[v] = _line.colour or _line.color
end
note[v] = args.note[v] or ''
 
if type(_lineargs[v]['line'] or args[v]['left'] terminusor args[v]['right']) ==or args[v]['stringnonstop'] then
if not args[v]['line'] and i > 1 and not args[v]['system'] then
_leftTerminus = _line['left terminus']
args[v]['line'] = args[index[i - 1]]['line']
elseif type(_line['left terminus']) == 'table' then
_leftTerminus = _line['left terminus'][args.TypeL[v] or args.Type[v] or 1]
end
 
if type(_line['right terminus']) == 'string' then
lineN = args[v]['line'] or '_default'
_rightTerminus = _line['right terminus']
typeN = args[v]['type']
elseif type(_line['right terminus']) == 'table' then
if data[v]['aliases'] then
_rightTerminus = _line['right terminus'][args.TypeR[v] or args.Type[v] or 1]
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 function station(var)
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 function subst(s)
local default = data[v]['lines']['_default'] or {}
if s then
line['title'] = line['title'] or default['title'] or ''
if string.match(s, '%[%[') then
returnline['title'] = mw.ustring.gsub(sline['title'], '%%1', varlineN)
 
elseif type(var) == 'string' then
-- cell across row for non-stop service
return table.concat({'[[', mw.ustring.gsub(s, '%%1', var), '|', var, ']]'})
if args[v]['nonstop'] then
elseif type(var) == 'table' and #var > 0 then
table.insert(wikitable,
local function _subst(k)
return table.concat({mw.ustring.gsub(s, '%%1', var[k]),\n|-\n|colspan="5" '|', var[k]})
endstyle['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'}))
local t = {})
)
t[1] = table.concat({'[[', _substinsert(1)wikitable, ']]'})
table.insert(wikitable, '')
table.insert(wikitable, '')
for i = 2, #var - 1 do
else
t[i] = table.concat({', [[', _subst(i), ']]'})
local Format = data[v]['station format'] or i18n[lang]['error_format']
end
 
local color, color_2, background_color, circular
if #var > 1 then
local Type = line['types'] and line['types'][typeN] -- get the line type table
t[#var] = table.concat({' or [[', _subst(#var), ']]'})
 
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]
if var['via'] then
t[#var + 1] = table.concat({' via&nbsp;[[', _subst('via'), ']]'})
end
return table.concat(t)
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|-')
if data[v]['station format'] then
table.insert(wikitable, '\n|' .. style['body cell'] .. sideCell[1])
return subst(data[v]['station format'][var] or data[v]['station format'][1]) or ''
table.insert(wikitable, table.concat({'\n|', style['body banner'], color, '"|',
else
'\n|', (background_color and 'class="bcA" style="background-color:rgba(' .. table.concat(rgb(background_color), ',') .. ',.2)"|' or style['body cell']), line['title'],
return var or ''
 
end
-- Type; table key 'types' in subpages (datatype table, with strings as keys). If table does not exist then the input is displayed as the text
(typeN and '<div>' .. (Type and Type['title'] or typeN) .. '</div>' or ''),
 
-- Note-mid; table key 'note-mid' in subpages. Overridden by user input
((args[v]['note-mid'] and small(args[v]['note-mid'])) or (Type and Type['note-mid'] and small(Type['note-mid'])) or (line['note-mid'] and small(line['note-mid'])) or ''),
 
-- Transfer; uses system's station link table
(args[v]['transfer'] and small('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 not args.left[v] then
if args[v]['note-row'] then -- Note
left[v] = "''Terminus''"
if args[v]['note-row']:match('^%s*<tr') or args[v]['note-row']:match('^%s*%|%-') then
leftTerminus[v] = ''
table.insert(wikitable, '\n' .. args[v]['note-row'])
else
table.insert(wikitable, '\n|-\n|colspan="5" ' .. style['body cell'] .. args[v]['note-row'])
left[v] = station(args.left[v])
end
if args.onewayL[v] == true then
table.insert(wikitable, '')
leftTerminus[v] = subFormat('One-way operation')
table.insert(wikitable, '')
elseif args.left[v] == _leftTerminus then
table.insert(wikitable, '')
leftTerminus[v] = subFormat('Terminus')
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 = '<div class="legend" style="page-break-inside:avoid;break-inside:avoid-column"><span class="legend-color" style="display:inline-block;min-width:1.25em;height:1.25em;line-height:1.25;margin:1px 0;border:1px solid black;color:inherit;background-color:#' .. color .. '"> </span> ' .. line .. result .. '</div>'
elseif inline == 'yes' then
result = '<span style="color:inherit;background-color:#' .. color .. ';border:1px solid #000">    </span>&nbsp;' .. line .. result
elseif inline == 'box' then
result = '<span style="color:inherit;background-color:#' .. color .. ';border:1px solid #000">    </span>' .. result
elseif inline == 'link' then
local link = args.link or mw.ustring.match(line, '%[%[([^%[:|%]]+)[|%]]')
if link then
result = '[[' .. link .. '|<span style="color:inherit;background-color:#' .. color .. ';border:1px solid #000">    </span>]]' .. result
else
result = '<span style="color:inherit;background-color:#' .. color .. ';border:1px solid #000">    </span>' .. result
end
elseif inline == 'square' then
result = '<span style="color:#' .. color .. ';line-height:initial">■</span>&nbsp;' .. line .. result
elseif inline == 'lsquare' then
local link = args.link or mw.ustring.match(line, '%[%[([^%[:|%]]+)[|%]]')
if link then
result = '[[' .. link .. '|<span style="color:#' .. color .. ';line-height:initial">■</span>]]'
else
result = '<span style="color:#' .. color .. ';line-height:initial">■</span>'
end
elseif inline == 'dot' then
result = '<span style="color:#' .. color .. ';line-height:initial">●</span> ' .. line .. result
elseif inline == 'ldot' then
local link = args.link or mw.ustring.match(line, '%[%[([^%[:|%]]+)[|%]]')
if link then
result = '[[' .. link .. '|<span style="color:#' .. color .. ';line-height:initial">●</span>]]'
else
result = '<span style="color:#' .. color .. ';line-height:initial">●</span>'
end
elseif inline == 'small' then
result = '<span style="color:inherit;background-color:#' .. color .. '"> </span>' .. ' ' .. line .. result
else
local yesno = require("Module:Yesno")
local link = args.link or mw.ustring.match(line, '%[%[([^%[:|%]]+)[|%]]')
local border_color, text_color
local color_box = data['color box format'] or data['rail box format'] or {}
if line_data then
if line_data['types'] and line_data['types'][typeN] then
local Type_data = line_data['types'][typeN]
border_color = Type_data['border color'] or line_data['border color'] or color
text_color = Type_data['text color'] or line_data['text color']
if color_box == 'title' and not args[4] then
lineN = Type_data['short name'] or line_data['short name'] or require('Module:Delink')._delink{line}
else
lineN = Type_data['short name'] or line_data['short name'] or lineN
end
else
border_color = line_data['border color'] or color
leftTerminus[v] = subFormat('towards ' .. station(_leftTerminus))
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
end
if not args.right[v] then
right[v] = "''Terminus''"
rightTerminus[v] = ''
else
border_color = color
right[v] = station(args.right[v])
end
if args.onewayR[v] == true then
text_color = text_color and '#' .. text_color or require('Module:Color contrast')._greatercontrast{color}
rightTerminus[v] = subFormat('One-way operation')
local bold = ';font-weight:bold'
elseif args.right[v] == _rightTerminus then
if (yesno(args.bold) == false) then bold = '' end
rightTerminus[v] = subFormat('Terminus')
if inline == 'route' then -- [[Template:RouteBox]]
if link then
result = '<span style="color:inherit;background-color:#' .. color .. ';border:.075em solid #' .. border_color .. ';padding:0 .3em">[[' .. link .. '|<span style="color:' .. text_color .. bold .. ';font-size:inherit;white-space:nowrap">' .. lineN .. '</span>]]</span>'
else
result = '<span style="background-color:#' .. color .. ';border:.075em solid #' .. border_color .. ';padding:0 .3em;color:' .. text_color .. bold .. ';font-size:inherit;white-space:nowrap">' .. lineN .. '</span>'
rightTerminus[v] = subFormat('towards ' .. station(_rightTerminus))
end
elseif inline == 'croute' then -- [[Template:Bahnlinie]]
if link then
result = '<span style="color:inherit;background-color:#' .. color .. ';border:.075em solid #' .. border_color .. ';border-radius:.5em;padding:0 .3em">[[' .. link .. '|<span style="color:' .. text_color .. bold .. ';font-size:inherit;white-space:nowrap">' .. lineN .. '</span>]]</span>'
else
result = '<span style="color:inherit;background-color:#' .. color .. ';border:.075em solid #' .. border_color .. ';border-radius:.5em;padding:0 .3em;color:' .. text_color .. bold .. ';font-size:inherit;white-space:nowrap">' .. lineN .. '</span>'
end
elseif inline == 'xroute' then -- [[Template:Bahnlinie]]
if link then
result = '<span style="border:.075em solid #' .. border_color .. ';border-radius:.5em;padding:0 .3em">[[' .. link .. '|<span style="color:#' .. color .. bold .. ';font-size:inherit;white-space:nowrap">' .. lineN .. '</span>]]</span>'
else
result = '<span style="border:.075em solid #' .. border_color .. ';border-radius:.5em;padding:0 .3em;color:#' .. color .. bold .. ';font-size:inherit;white-space:nowrap">' .. lineN .. '</span>'
end
elseif inline == 'broute' then
if link then
result = '<span style="color:inherit;background-color:#' .. color .. ';border:.075em solid #000;padding:0 .3em">[[' .. link .. '|<span style="color:' .. text_color .. bold .. ';font-size:inherit;white-space:nowrap">' .. lineN .. '</span>]]</span>'
else
result = '<span style="background-color:#' .. color .. ';border:.075em solid #000;padding:0 .3em;color:' .. text_color .. bold .. ';font-size:inherit;white-space:nowrap">' .. lineN .. '</span>'
end
else -- [[Template:Legend]] (fallback; duplication to simplify logic)
result = '<div class="legend" style="page-break-inside:avoid;break-inside:avoid-column"><span class="legend-color" style="display:inline-block;min-width:1.25em;height:1.25em;line-height:1.25;margin:1px 0;border:1px solid black;color:inherit;background-color:#' .. color .. '"> </span> ' .. line .. result .. '</div>'
end
table.insert(t, '\n|-')
table.insert(t, table.concat({'\n|', style['body leftcell'], left[v], leftTerminus[v]}))
table.insert(t, table.concat({'\n|', style['body banner'], colour[v], '"|',
'\n|', style['body midcell'], lineTitle[v], '<div style="font-size: smaller">', note[v], '</div>',
'\n|', style['body banner'], colour[v], '"|'}))
table.insert(t, table.concat({'\n|', style['body rightcell'], right[v], rightTerminus[v]}))
end
 
result = mw.ustring.gsub(result, ':%s*#transparent', ':transparent')
 
return result
end
end
 
local function combine(t, i)
p.box = makeInvokeFunction('_box')
if t[i + 4] ~= '' and t[i + 4] == t[i] then
 
t[i + 4] = '' -- The cell in the next row is deleted
function p._icon(args, frame)
local rowspan = 2
local system = args[1] or args.system
while t[i + rowspan * 4] == t[i] do
local data = args.data
t[i + rowspan * 4] = ''
 
rowspan = rowspan + 1
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
t[i] = mw.ustring.gsub(t[i], '\n|style="', table.concat({'\n|rowspan="', rowspan, '" style="'}))
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
local max = #t
icon = data['system icon']
for i = 2, max - 2, 4 do combine(t, i) end
end
for i = 3, max - 1, 4 do combine(t, i) end
 
for i = 4, max, 4 do combine(t, i) end
if not icon_format then
icon_format = data['system icon format']
return table.concat(t)
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.bottom()
p['rail icon'] = makeTemplateFunction('_icon')
return '\n|}'
 
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.main(frame)
local _args = frame:getParent().args
local args = {
system = {},
line = {},
left = {},
right = {},
Type = {},
TypeL = {},
TypeR = {},
note = {},
onewayL = {},
onewayR = {},
circular = {},
circularL = {},
circularR = {}
}
 
function p._shortline(args, frame)
-- to do: add rest of S-line parameters
local _indexsystem = {}args[1] or args.system
lineN = args[2] or args.line
local parameters = {
if not (system or lineN) then return '' end
['strings'] = {
local line, Type, line_data
['system'] = 'system',
typeN = args.type
['line'] = 'line',
local data = args.data
['left'] = 'left',
if system or data then
['right'] = 'right'
data = data or getData(system, true)
},
if data then
['optional strings'] = {
local default = data['lines']['_default'] or {}
['note-mid'] = 'note',
line, lineN = getLine(data, lineN)
['type'] = 'Type',
if typeN then
['type-left'] = 'TypeL',
typeN = data['aliases'] and data['aliases'][mw.ustring.lower(typeN)] or typeN
['type-right'] = 'TypeR',
Type = line['types'] and line['types'][typeN] and line['types'][typeN]['title'] or typeN
-- ['through'] = 'through', -- The way this would work is that it would replace either "Terminus" cell, rather than just one of them in particular.
end
-- ['through-left'] = 'throughL',
line_data = line or error(i18n[lang]['error_unknown'](lineN))
-- ['through-right'] = 'throughR',
line = line_data['title'] or default['title'] or error(i18n[lang]['error_missing']('title'))
-- ['branch'] = 'branch',
line = mw.ustring.gsub(line, '%%1', lineN)
-- ['transfer'] = 'transfer'
},else
line = frame:expandTemplate{ title = system .. ' lines', args = {lineN, ['branch'] = typeN} }
['optional boolean'] = {
if mw.text.trim(line) == '' then return error(i18n[lang]['error_unknown'](lineN)) end
['oneway-left'] = 'onewayL',
Type = typeN
['oneway-right'] = 'onewayR',
end
-- ['round-left'] = 'roundL',
 
-- ['round-right'] = 'roundR',
local result
['circular'] = 'circular',
 
['circular-left'] = 'circularL',
if Type and Type ~= '' then
['circular-right'] = 'circularR',
['through-left']if line == 'throughL', then
line = Type
['through-right'] = 'throughR'
} else
result = ' – ' .. Type
}
end
for k, v in pairs(_args) do
end
_args[k] = v:match('^%s*(.-)%s*$')
if args.note then result = (result or '') .. ' ' .. args.note end
if _args[k] and _args[k] ~= '' then
result = result or ''
local a = string.match(k, '^(.*%D)%d+$') or k
 
local b = tonumber(string.match(k, '^.*%D(%d+)$')) or 1
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 parameters['strings'][a]link then
result = '[[' .. link .. '|' .. lineN .. ']]'
args[parameters['strings'][a]][b] = v
else
_index[b] = true
result = lineN
elseif args[parameters['optional strings'][a]] then
end
args[parameters['optional strings'][a]][b] = v
elseif args[parameters['optional boolean'][a]] then
result = mw.ustring.gsub(result, ':%s*#transparent', ':transparent')
args[parameters['optional boolean'][a]][b] = yesno(v)
 
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')
local index = {}
p['station link'] = makeTemplateFunction('_station')
for k, v in pairs(_index) do
 
table.insert(index, k)
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
table.sort(index)
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 data = {}
local Type = line['types'] and line['types'][typeN]
for i, v in ipairs(index) do
 
if args.system[v] then
local circular
-- data[v] = require('Module:Adjacent stations/' .. args.system[v])
if Type then
data[v] = require('Module:Adjacent stations/' .. args.system[v] .. '/sandbox')
if Type['circular'] then
-- Type may override the circular status of the line
circular = Type['circular']
end
else
data[v]circular = data[indexline[i-1]'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
 
local rows = { -- Included for mixed used with S-line.
function p._terminus(args, frame)
['all'] = table.concat({p.top(), p.line(args, index, data), p.bottom()}),
local var, Format, circular = p._terminusTable(args, frame)
['top'] = table.concat({p.top(), p.line(args, index, data)}),
 
['mid'] = table.concat({p.line(args, index, data)}),
return circular and var or getTerminusText(var, Format)
['bottom'] = table.concat({p.line(args, index, data), p.bottom()})
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',
return rows[_args.rows] or rows['all']
'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