Module:Adjacent stations/sandbox

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 thenlocal title = mw.title.new('Module:Adjacent stations/' .. system -- .. '/sandbox')if not (title and title.exists) then return nil endendreturn require('Module:Adjacent stations/' .. system -- .. '/sandbox')endlocal function getLine(data, lineN)if lineN thenif data['aliases'] thenlineN = data['aliases'][mw.ustring.lower(lineN)] or lineNendlocal default = data['lines']['_default'] or {}local line = data['lines'][lineN] or {}for k, v in pairs(default) doif v then line[k] = line[k] or v endendline['title'] = line['title'] and mw.ustring.gsub(line['title'], '%%1', lineN)return line, lineNendendlocal function getColor(data, system, line, Type, frame)if system thenif line then return frame:expandTemplate{ title = system .. ' color', args = {line, ['branch'] = Type} } endreturn frame:expandTemplate{ title = system .. ' color' }elseline = (getLine(data, line))local default = data['lines']['_default']if line or default thendefault = default or {}if not line then line = mw.clone(default) endlocal 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 endreturn colorendreturn (default and (default['color'] or default['background color']) or data['system color'] or '')endendlocal lineN, typeNlocal function somethingMissing(name, key, formats)local formatKeys = {}for k in pairs(formats) dotable.insert(formatKeys, k)endreturn name .. ' was "' .. key .. '" but neither an entry for it nor a default was found. Choices were: ' .. table.concat(formatKeys, ', ')endlocal function getStation(station, _Format)if type(_Format) == 'table' thenlocal lineNformats = _Format_Format = lineNformats[lineN] or lineNformats[1]if not _Format thenerror(somethingMissing('lineN', lineN, lineNformats))elseif type(_Format) == 'table' thenlocal typeNformats = _Format_Format = typeNformats[typeN] or typeNformats[1]if not _Format thenerror(somethingMissing('typeN', typeN, typeNformats))endendendif typeN then _Format = mw.ustring.gsub(_Format, '%%3', typeN) endif lineN then _Format = mw.ustring.gsub(_Format, '%%2', lineN) endreturn (mw.ustring.match(_Format, '%[%[.+%]%]')) and (mw.ustring.gsub(_Format, '%%1', station)) or table.concat({'[[', mw.ustring.gsub(_Format, '%%1', station), '|', station, ']]'})endlocal 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 terminireturn 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 ''endif Format thenif type(var) == 'string' thenreturn subst(var)elseif type(var) == 'table' and #var > 0 thenlocal t = {subst(var, 1)}for i = 2, #var - 1 dot[i] = i18n[lang]['comma'](subst(var, i))endif #var > 1 then t[#var] = i18n[lang]['or'](subst(var, #var)) endif var['via'] thenif i18n[lang]['via-first'] thentable.insert(t, 1, i18n[lang]['via'](subst(var, 'via')))elsetable.insert(t, i18n[lang]['via'](subst(var, 'via')))endendreturn table.concat(t)elsereturn ''endelsereturn var or ''endendfunction p._main(_args) -- Arguments are processed here instead of the main functionlocal yesno = require('Module:Yesno')local trimq = require('Module:Trim quotes')._trimlocal boolean = {['oneway-left'] = true,['oneway-right'] = true,['reverse'] = true,['reverse-left'] = true,['reverse-right'] = true}local args = {} -- Processed argumentslocal index = {} -- A list of addresses corresponding to number suffixes in the argumentsfor 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] ~= '' thenlocal a = mw.ustring.match(k, '^(.*%D)%d+$') or k -- The parameter; address 1 can be omittedlocal b = tonumber(mw.ustring.match(k, '^.*%D(%d+)$')) or 1 -- The address for a given argument; address 1 can be omittedif boolean[a] thenv = yesno(v)endif not args[b] thenargs[b] = {[a] = v}table.insert(index, b)elseif args[b][a] thenreturn error(i18n[lang]['error_duplicate'](a .. b))elseargs[b][a] = vendendendtable.sort(index)local function small(s, italic)return italic and '<div class="isA">' .. s .. '</div>'or '<div class="smA">' .. s .. '</div>'endlocal style = { -- Style for each cell type['header cell'] = 'class="hcA"|',['header midcell'] = 'colspan="3" class="hmA"|',['body cell'] = 'class="bcA"|',['body banner'] = 'class="bbA" style="color:inherit;background-color:#',}local function rgb(var)if var:len() == 3 thenreturn {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 thenreturn {tonumber(var:sub(1, 2), 16), tonumber(var:sub(3, 4), 16), tonumber(var:sub(5, 6), 16)}endreturn {}endlocal data = {} -- A table of data modules for each addresslocal 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 moduledata[v] = args[v]['system'] and getData(args[v]['system'])-- If an address has no system, the row uses data from the previous addressor 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 langif args[v]['system'] and not args[v]['hide-system'] then -- Header rowlocal 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, '')endif args[v]['header'] then -- Subheadertable.insert(wikitable, '\n|-\n!colspan="5" class="hmA"|'.. args[v]['header'])table.insert(wikitable, '')table.insert(wikitable, '')table.insert(wikitable, '')endif args[v]['line'] or args[v]['left'] or args[v]['right'] or args[v]['nonstop'] thenif not args[v]['line'] and i > 1 and not args[v]['system'] thenargs[v]['line'] = args[index[i - 1]]['line']endlineN = args[v]['line'] or '_default'typeN = args[v]['type']if data[v]['aliases'] thenlineN = data[v]['aliases'][mw.ustring.lower(lineN)] or lineNif typeN then typeN = data[v]['aliases'][mw.ustring.lower(typeN)] or typeN endend-- get the line tablelocal 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 serviceif args[v]['nonstop'] thentable.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, '')elselocal Format = data[v]['station format'] or i18n[lang]['error_format']local color, color_2, background_color, circularlocal Type = line['types'] and line['types'][typeN] -- get the line type tableif Type thenif Type['color'] then-- line color is used as background if there is no background color in the line type tablebackground_color = Type['background color'] or line['color']color = Type['color']color_2 = Type['color2'] or colorelsebackground_color = Type['background color'] or line['background color']color = line['color'] or default['color'] or ''color_2 = line['color2'] or colorendif Type['circular'] then-- Type may override the circular status of the linecircular = Type['circular']endelsebackground_color = line['background color']color = line['color'] or default['color'] or ''color_2 = line['color2'] or colorcircular = line['circular']end-- Alternate termini can be specified based on typelocal sideCell = {true, true}for i, b in ipairs({'left', 'right'}) doif not args[v][b] then -- If no station is given on one side, the station is assumed to be the terminus on that sidelocal _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 endsideCell[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']) .. "''"elselocal terminusTlocal 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 reservedif type(terminusN) == 'string' or (type(terminusN) == 'table' and (terminusN[2] or terminusN['via'])) thenif args[v]['to-' .. b] thenterminusT = args[v]['to-' .. b]local _or = mw.ustring.match(terminusT, i18n[lang]['or-format'])if _or thenterminusT = mw.ustring.gsub(terminusT, i18n[lang]['or-format'], '\127_OR_\127')terminusT = mw.ustring.gsub(terminusT, i18n[lang]['comma-format'], '\127_OR_\127')endlocal _via = (mw.ustring.match(terminusT, i18n[lang]['via-format']))if _via thenterminusT = mw.ustring.gsub(terminusT, i18n[lang]['via-format'], '')terminusT = mw.text.split(terminusT, '\127_OR_\127')terminusT['via'] = _viaelseif _or thenterminusT = mw.text.split(terminusT, '\127_OR_\127')endelseterminusT = terminusNendelseif type(terminusN) == 'table' thenterminusT = terminusN[args[v]['to-' .. b]] or terminusN[args[v]['to']] or terminusN[1]endlocal 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 terminusTor i18n[lang]['towards'](getTerminusText(terminusT, Format))subText = small(subText, true)sideCell[i] = mainText .. subTextendendtable.insert(wikitable, '\n|-')table.insert(wikitable, '\n|' .. style['body cell'] .. sideCell[1])table.insert(wikitable, table.concat({'\n|', style['body banner'], color, '"|','\n|', (background_color and 'class="bcA" style="background-color:rgba(' .. table.concat(rgb(background_color), ',') .. ',.2)"|' or style['body cell']), line['title'],-- Type; table key 'types' in subpages (datatype table, with strings as keys). If table does not exist then the input is displayed as the text(typeN and '<div>' .. (Type and Type['title'] or typeN) .. '</div>' or ''),-- Note-mid; table key 'note-mid' in subpages. Overridden by user input((args[v]['note-mid'] and small(args[v]['note-mid'])) or (Type and Type['note-mid'] and small(Type['note-mid'])) or (line['note-mid'] and small(line['note-mid'])) or ''),-- Transfer; uses system's station link table(args[v]['transfer'] and small('transfer at ' .. getTerminusText(args[v]['transfer'], Format), true) or ''),'\n|', style['body banner'], color_2, '"|'}))table.insert(wikitable, '\n|' .. style['body cell'] .. sideCell[2])endendif args[v]['note-row'] then -- Noteif args[v]['note-row']:match('^%s*<tr') or args[v]['note-row']:match('^%s*%|%-') thentable.insert(wikitable, '\n' .. args[v]['note-row'])elsetable.insert(wikitable, '\n|-\n|colspan="5" ' .. style['body cell'] .. args[v]['note-row'])endtable.insert(wikitable, '')table.insert(wikitable, '')table.insert(wikitable, '')endendlocal function combine(t, n)if t[n + 4] ~= '' and t[n + 4] == t[n] thent[n + 4] = '' -- The cell in the next row is deletedlocal rowspan = 2while t[n + rowspan * 4] == t[n] dot[n + rowspan * 4] = ''rowspan = rowspan + 1endt[n] = mw.ustring.gsub(t[n], '\n|class="', '\n|rowspan="' .. rowspan .. '" class="')endendlocal M = #wikitablefor i = 3, M, 4 do combine(wikitable, i) endfor i = 4, M, 4 do combine(wikitable, i) endfor i = 5, M, 4 do combine(wikitable, i) endtable.insert(wikitable, '\n|}')return table.concat(wikitable)endlocal getArgs = require('Module:Arguments').getArgslocal 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)endendp.main = makeInvokeFunction('_main')function p._color(args, frame)local data = args.dataif args[1] or data thendata = data or getData(args[1], true)if not data then return getColor(nil, args[1], args[2], args[3], frame) endreturn getColor(data, nil, args[2], args[3])endendp.color = makeInvokeFunction('_color')function p._box(args, frame)local system = args[1] or args.systemlineN = args[2] or args.lineif not (system or lineN) then return '' endlocal line, Type, line_datalocal inline = args[3] or args.inlinetypeN = args.typelocal data = args.dataif system or data thendata = data or getData(system, true)local colorif data thenlocal default = data['lines']['_default'] or {}line, lineN = getLine(data, lineN)if typeN thentypeN = data['aliases'] and data['aliases'][mw.ustring.lower(typeN)] or typeNType = line['types'] and line['types'][typeN] and line['types'][typeN]['title'] or typeNendcolor = getColor(data, nil, lineN, typeN)if inline ~= 'box' thenline_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)endelsecolor = getColor(nil, system, lineN, typeN, frame)if inline ~= 'box' thenline = frame:expandTemplate{ title = system .. ' lines', args = {lineN, ['branch'] = typeN} }if mw.text.trim(line) == '' then return error(i18n[lang]['error_unknown'](lineN)) endendType = typeNendlocal resultif Type and Type ~= '' and inline ~= 'box' thenif line == '' thenline = Typeelseresult = ' – ' .. Typeendendif args.note then result = (result or '') .. ' ' .. args.note endresult = result or ''if not inline then -- [[Template:Legend]]result = '<div class="legend" style="page-break-inside:avoid;break-inside:avoid-column"><span class="legend-color" style="display:inline-block;min-width:1.25em;height:1.25em;line-height:1.25;margin:1px 0;border:1px solid black;background-color:#' .. color .. '"> </span> ' .. line .. result .. '</div>'elseif inline == 'yes' thenresult = '<span style="background-color:#' .. color .. ';border:1px solid #000">    </span>&nbsp;' .. line .. resultelseif inline == 'box' thenresult = '<span style="background-color:#' .. color .. ';border:1px solid #000">    </span>' .. resultelseif inline == 'link' thenlocal link = args.link or mw.ustring.match(line, '%[%[([^%[:|%]]+)[|%]]')if link thenresult = '[[' .. link .. '|<span style="background-color:#' .. color .. ';border:1px solid #000">    </span>]]' .. resultelseresult = '<span style="background-color:#' .. color .. ';border:1px solid #000">    </span>' .. resultendelseif inline == 'square' thenresult = '<span style="color:#' .. color .. ';line-height:initial">■</span>&nbsp;' .. line .. resultelseif inline == 'lsquare' thenlocal link = args.link or mw.ustring.match(line, '%[%[([^%[:|%]]+)[|%]]')if link thenresult = '[[' .. link .. '|<span style="color:#' .. color .. ';line-height:initial">■</span>]]'elseresult = '<span style="color:#' .. color .. ';line-height:initial">■</span>'endelseif inline == 'dot' thenresult = '<span style="color:#' .. color .. ';line-height:initial">●</span> ' .. line .. resultelseif inline == 'ldot' thenlocal link = args.link or mw.ustring.match(line, '%[%[([^%[:|%]]+)[|%]]')if link thenresult = '[[' .. link .. '|<span style="color:#' .. color .. ';line-height:initial">●</span>]]'elseresult = '<span style="color:#' .. color .. ';line-height:initial">●</span>'endelseif inline == 'small' thenresult = '<span style="background-color:#' .. color .. '"> </span>' .. ' ' .. line .. resultelselocal yesno = require("Module:Yesno")local link = args.link or mw.ustring.match(line, '%[%[([^%[:|%]]+)[|%]]')local border_color, text_colorlocal color_box = data['color box format'] or data['rail box format'] or {}if line_data thenif line_data['types'] and line_data['types'][typeN] thenlocal Type_data = line_data['types'][typeN]border_color = Type_data['border color'] or line_data['border color'] or colortext_color = Type_data['text color'] or line_data['text color']if color_box == 'title' and not args[4] thenlineN = Type_data['short name'] or line_data['short name'] or require('Module:Delink')._delink{line}elselineN = Type_data['short name'] or line_data['short name'] or lineNendelseborder_color = line_data['border color'] or colortext_color = line_data['text color']if color_box == 'title' and not args[4] thenlineN = line_data['short name'] or require('Module:Delink')._delink{line}elselineN = line_data['short name'] or lineNendendelseborder_color = colorendtext_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 = '' endif inline == 'route' then -- [[Template:RouteBox]]if link thenresult = '<span style="background-color:#' .. color .. ';border:.075em solid #' .. border_color .. ';padding:0 .3em">[[' .. link .. '|<span style="color:' .. text_color .. bold .. ';font-size:inherit;white-space:nowrap">' .. lineN .. '</span>]]</span>'elseresult = '<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>'endelseif inline == 'croute' then -- [[Template:Bahnlinie]]if link thenresult = '<span style="background-color:#' .. color .. ';border:.075em solid #' .. border_color .. ';border-radius:.5em;padding:0 .3em">[[' .. link .. '|<span style="color:' .. text_color .. bold .. ';font-size:inherit;white-space:nowrap">' .. lineN .. '</span>]]</span>'elseresult = '<span style="background-color:#' .. color .. ';border:.075em solid #' .. border_color .. ';border-radius:.5em;padding:0 .3em;color:' .. text_color .. bold .. ';font-size:inherit;white-space:nowrap">' .. lineN .. '</span>'endelseif inline == 'xroute' then -- [[Template:Bahnlinie]]if link thenresult = '<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>'elseresult = '<span style="border:.075em solid #' .. border_color .. ';border-radius:.5em;padding:0 .3em;color:#' .. color .. bold .. ';font-size:inherit;white-space:nowrap">' .. lineN .. '</span>'endelseif inline == 'broute' thenif link thenresult = '<span style="background-color:#' .. color .. ';border:.075em solid #000;padding:0 .3em">[[' .. link .. '|<span style="color:' .. text_color .. bold .. ';font-size:inherit;white-space:nowrap">' .. lineN .. '</span>]]</span>'elseresult = '<span style="background-color:#' .. color .. ';border:.075em solid #000;padding:0 .3em;color:' .. text_color .. bold .. ';font-size:inherit;white-space:nowrap">' .. lineN .. '</span>'endelse -- [[Template:Legend]] (fallback; duplication to simplify logic)result = '<div class="legend" style="page-break-inside:avoid;break-inside:avoid-column"><span class="legend-color" style="display:inline-block;min-width:1.25em;height:1.25em;line-height:1.25;margin:1px 0;border:1px solid black;background-color:#' .. color .. '"> </span> ' .. line .. result .. '</div>'endendresult = mw.ustring.gsub(result, ':%s*#transparent', ':transparent')return resultendendp.box = makeInvokeFunction('_box')function p._icon(args, frame)local system = args[1] or args.systemlocal data = args.dataif not system and not data thenreturnenddata = data or getData(system)local line, line_name = getLine(data, args[2] or args.line)local iconlocal icon_formatif line thenlocal line_type = args[3] or args.typeif line_type thenline_type = data.aliases and  data.aliases[mw.ustring.lower(line_type)] or line_typeline_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 iconicon_format = line_type['icon format'] or data['type icon format']if line_type.icon thenicon = line_type.iconendendif not icon thenicon = line.iconend-- Only if there is no icon use the icon_format.if not icon and not icon_format thenicon_format = line['icon format'] or data['line icon format']endlocal default = data.lines._default or {}if icon and string.find(icon, "%%1") and default and default.icon thenicon = mw.ustring.gsub(default.icon, '%%1', line_name)endendif not icon thenicon = data['system icon']endif not icon_format thenicon_format = data['system icon format']endif icon_format thenif icon_format ~= 'image' thenicon = 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 thenif line and line.title thenreturn icon .. " " .. line.titleendreturn icon .. " " .. data["system title"]endendendlocal size = args.sizeif size thenif mw.ustring.match(size, '%d$') thensize = '|' .. size .. 'px'elsesize = '|' .. sizeend-- 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-10local regex = {'|%s*%d*x?%d+px%s*([%]|])', -- '|%s*upright=%d+%.?%d*%s*([%]|])', '|%s*upright%s*([%]|])'}if mw.ustring.match(icon, regex[1]) thenicon = 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')elseicon = mw.ustring.gsub(icon, '(%[%[[^%]|]+)([%]|])', '%1' .. size .. '%2')endendlocal link = args.linkif link thenif mw.ustring.match(icon, '|%s*link=[^%]|]*[%]|]') thenicon = mw.ustring.gsub(icon, '|%s*link=[^%]|]*([%]|])', '|link=' .. link .. '%1')elseicon = mw.ustring.gsub(icon, '(%[%[[^%]|]+)([%]|])', '%1|link=' .. link .. '%2')endendlocal alt = args.alt or linkif alt thenif mw.ustring.match(icon, '|%s*alt=[^%]|]*[%]|]') thenicon = mw.ustring.gsub(icon, '|%s*alt=[^%]|]*([%]|])', '|alt=' .. alt .. '%1')elseicon = mw.ustring.gsub(icon, '(%[%[[^%]|]+)([%]|])', '%1|alt=' .. alt .. '%2')endendif args.name thenif line and line.title thenreturn icon .. " " .. line.titleendreturn icon .. " " .. data["system title"]endreturn iconendp.icon = makeInvokeFunction('_icon')function p._line(args, frame)local system = args[1] or args.systemlocal line = args[2] or args.lineif not line then return '' endlocal Type = args[3] or args.typelocal data = args.dataif system or data thendata = data or getData(system, true)if data thenline = (getLine(data, line)) or error(i18n[lang]['error_unknown'](line))if Type thenType = data['aliases'] and data['aliases'][mw.ustring.lower(Type)] or TypeType = line['types'] and line['types'][Type] and line['types'][Type]['title'] or Typeendline = line['title'] or error(i18n[lang]['error_missing']('title'))elseline = frame:expandTemplate{ title = system .. ' lines', args = {line, ['branch'] = Type} }if mw.text.trim(line) == '' then return error(i18n[lang]['error_unknown'](lineN)) endendif Type and Type ~= '' thenif line == '' thenline = Typeelseline = line .. ' – ' .. Typeendendreturn lineendendp.line = makeInvokeFunction('_line')function p._shortline(args, frame)local system = args[1] or args.systemlineN = args[2] or args.lineif not (system or lineN) then return '' endlocal line, Type, line_datatypeN = args.typelocal data = args.dataif system or data thendata = data or getData(system, true)if data thenlocal default = data['lines']['_default'] or {}line, lineN = getLine(data, lineN)if typeN thentypeN = data['aliases'] and data['aliases'][mw.ustring.lower(typeN)] or typeNType = line['types'] and line['types'][typeN] and line['types'][typeN]['title'] or typeNendline_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)elseline = frame:expandTemplate{ title = system .. ' lines', args = {lineN, ['branch'] = typeN} }if mw.text.trim(line) == '' then return error(i18n[lang]['error_unknown'](lineN)) endType = typeNendlocal resultif Type and Type ~= '' thenif line == '' thenline = Typeelseresult = ' – ' .. Typeendendif args.note then result = (result or '') .. ' ' .. args.note endresult = result or ''local link = args.link or mw.ustring.match(line, '%[%[([^%[:|%]]+)[|%]]')if line_data thenif line_data['types'] and line_data['types'][typeN] thenlocal Type_data = line_data['types'][typeN]lineN = Type_data['short name'] or line_data['short name'] or lineNelselineN = line_data['short name'] or lineNendendif link thenresult = '[[' .. link .. '|' .. lineN .. ']]'elseresult = lineNendresult = mw.ustring.gsub(result, ':%s*#transparent', ':transparent')return resultendendp.shortline = makeInvokeFunction('_shortline')function p._station(args, frame)local system = args[1] or args.systemlocal station = args[2] or args.stationif not station then return '' endlineN = args[3] or args.linetypeN = args[4] or args.typelocal data = args.dataif system or data thendata = data or getData(system, true)if data thenlocal _Format = data['station format'][station] or data['station format'][1]if _Format thenif data['aliases'] thenif lineN thenlineN = data['aliases'][mw.ustring.lower(lineN)] or lineNendif typeN thentypeN = data['aliases'][mw.ustring.lower(typeN)] or typeNendendstation = getStation(station, _Format)elsestation = station or ''endelsestation = frame:expandTemplate{ title = system .. ' stations', args = {['station'] = station, ['line'] = lineN, ['branch'] = typeN} }endreturn stationendendp.station = makeInvokeFunction('_station')function p._terminusTable(args, frame)local system = args[1] or args.systemlineN = args[2] or args.linelocal side = mw.ustring.sub(mw.ustring.lower(args[3] or args.side or ''), 1, 1)typeN = args.typelocal prefix = (side == 'r') and 'right' or 'left'local data = args.dataif system or data thendata = data or getData(system, true)endif data thenlocal 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 endlocal Type = line['types'] and line['types'][typeN]local circularif Type thenif Type['circular'] then-- Type may override the circular status of the linecircular = Type['circular']endelsecircular = line['circular']endreturn Type and Type[prefix .. ' terminus'] or line[prefix .. ' terminus'], data['station format'] or i18n[lang]['error_format'], circularelselocal terminus = frame:expandTemplate{ title = 'S-line/' .. system .. ' ' .. prefix .. '/' .. lineN }return mw.ustring.gsub(terminus, '{{{type}}}', typeN)endendfunction p._terminus(args, frame)local var, Format, circular = p._terminusTable(args, frame)return circular and var or getTerminusText(var, Format)endp.terminus = makeInvokeFunction('_terminus')function p._style(args, frame)local style = args[1] or args.stylelocal system = args[2] or args.systemlocal line = args[3] or args.linelocal station = args[4] or args.stationlocal result = {}local data = args.datalocal default = 'background-color:#efefef' -- Default background color for {{Infobox station}}if system or data thendata = data or getData(system, true)endif data thenlocal function getValue(var)if type(var) == 'table' thenvar = var[line] or var[1]if type(var) == 'table' thenvar = var[station] or var[1]endendif var ~= '' then return var endendif style == 'header' thenlocal tmp = data['name format'] and getValue(data['name format'])if tmp then table.insert(result, tmp) endelseif style == 'subheader' thenlocal tmp = data['header background color'] and getValue(data['header background color'])if tmp thentable.insert(result, 'background-color:#' .. tmp)local color = data['header text color'] and getValue(data['header text color'])if color thentable.insert(result, 'color:#' .. color)elselocal greatercontrast = require('Module:Color contrast')._greatercontrastif greatercontrast{tmp} == '#FFFFFF' then table.insert(result, 'color:#FFFFFF') endendelsetable.insert(result, default)local color = data['header text color'] and getValue(data['header text color'])if color then table.insert(result, 'color:#' .. color) endendendresult = table.concat(result, ';')elseif system thenlocal title = 'Template:' .. system .. ' style'local titleObj = mw.title.new(title)if titleObj and titleObj.exists thenlocal tmpif style == 'header' thentmp = frame:expandTemplate{ title = title, args = {'name_format', line, station} }if tmp ~= '' then table.insert(result, tmp) endelseif style == 'subheader' thentmp = frame:expandTemplate{ title = title, args = {'thbgcolor', line, station} }if tmp ~= '' thentable.insert(result, 'background-color:#' .. tmp)local color = frame:expandTemplate{ title = title, args = {'thcolor', line, station} }if color ~= '' thentable.insert(result, 'color:#' .. color)elselocal ratio = require('Module:Color contrast')._ratioif ratio{tmp, '222222'} < 4.5 then table.insert(result, 'color:#FFFFFF') end -- 222222 is the default text color in Vectorendelsetable.insert(result, default)tmp = frame:expandTemplate{ title = title, args = {'thcolor', line, station} }if tmp ~= '' thentable.insert(result, 'color:#' .. tmp)endendendresult = table.concat(result, ';')elseif style == 'subheader' thenresult = defaultelseresult = ''endendelseif style == 'subheader' thenresult = defaultelseresult = ''endendreturn resultendfunction p.style(frame)local args = getArgs(frame, {frameOnly = true})return p._style(args, frame)endfunction p.convert(frame)local args = frame.argslocal code = mw.text.split(mw.ustring.gsub(args[1], '^%s*{{(.*)}}%s*$', '%1'), '%s*}}%s*{{%s*')local systemlocal group = tonumber(args.offset or 0) or 0local firstgroup = group + 1local 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 = falsefor i, v in ipairs(code) docode[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]')) thennoclear = trueendif template == 's-line' thendata[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]) dolocal tmp = mw.text.split(n, '%s*=%s*')if tmp[3] thentmp[2] = mw.ustring.gsub(n, '^.-%s*=', '')endtmp[1] = replace[tmp[1]] or tmp[1]if tmp[2] then-- checks for matching bracketslocal 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) thenlocal count = mw.clone(m)+1while not (curly == 0 and square == 0) dotmp[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+1endenddata[i][tmp[1]] = tmp[2]endendif (this_system ~= system) or (not system) thensystem = this_systemdata[i]['system'] = systemelsedata[i]['system'] = nilendlocal last = data[i-1] or data[i-2] or data[i-3]if last thenfor r, s in pairs({['hide1'] = {'left', 'to-left', 'note-left', 'oneway-left'},['hide2'] = {'right', 'to-right', 'note-right', 'oneway-right'},['hidemid'] = {'type', 'note-mid'}}) doif data[i][r] thenfor m, n in ipairs(s) doif not data[i][n] thendata[i][n] = last[n]endendendendendcode[i] = {}local X = '|'local Y = (i+group)..'='if data[i]['system'] thentable.insert(code[i], '|system')table.insert(code[i], Y)table.insert(code[i], data[i]['system'])table.insert(code[i], '\n')endfor m, n in ipairs(order) doif data[i][n] thentable.insert(code[i], X)table.insert(code[i], n)table.insert(code[i], Y)table.insert(code[i], data[i][n])endendcode[i] = table.concat(code[i])elseif template == 's-note' thencode[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' thencode[i] = mw.ustring.gsub(code[i], '|%s*text%s*=', '|note-row'..i+group..'=')elseif delete[template] thencode[i] = ''table.insert(remove_rows, 1, i) -- at the start, so that the rows are deleted in reverse ordergroup = group-1endendfor i, v in ipairs(remove_rows) dotable.remove(code, v)endcode = 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 topsystem = mw.ustring.match(code, '|system%d*=([^|]*[^|\n])')code = mw.ustring.gsub(code, '|system%d*=[^|]*', '')code = '\n|system'..firstgroup..'='..system..codeelseif not mw.ustring.match(code, '^[^{%[]*|[^=|]+2=') then-- If there's only one parameter group then there's no need to have line breakscode = mw.ustring.gsub(code, '\n', '')code = mw.ustring.gsub(code, '(|[^=|]+)1=', '%1=')t[2] = '}}'if not mw.ustring.match(code, '[%[{]') thencode = mw.ustring.gsub(code, '|[^=|]*=$', '')code = mw.ustring.gsub(code, '|[^=|]*$', '')endendif not mw.ustring.match(code, '[%[{]') thencode = mw.ustring.gsub(code, '|[^=|]*=|', '|')code = mw.ustring.gsub(code, '|[^=|]*|', '|')code = mw.ustring.gsub(code, '|[^=|]*=\n', '\n')code = mw.ustring.gsub(code, '|[^=|]*\n', '\n')endreturn t[1]..code..t[2]endreturn p