This Lua module is used on approximately 77,000 pages and changes may be widely noticed. Test changes in the module's /sandbox or /testcases subpages, or in your own module sandbox. Consider discussing changes on the talk page before implementing them. |
This module depends on the following other modules: |
This module is subject to page protection. It is a highly visible module in use by a very large number of pages, or is substituted very frequently. Because vandalism or mistakes would affect many pages, and even trivial editing might cause substantial load on the servers, it is protected from editing. |
Pages related to |
Module:Adjacent stations |
---|
(talk | sandbox | sub-pages) |
{{Infobox station}} |
(talk | sandbox | testcases) |
{{Station link}} |
(talk | sandbox | testcases) |
{{Station icon link}} |
(talk | sandbox | testcases) |
{{Rail color}} |
(talk | sandbox | testcases) |
{{Line link}} |
(talk | sandbox | testcases) |
{{Rail icon}} |
(talk | sandbox | testcases) |
{{Rail color box}} |
(talk | sandbox | testcases) |
{{Adjacent stations}} |
(talk | sandbox | testcases) |
{{Line terminus link}} |
(talk | sandbox | testcases) |
This module implements {{Adjacent stations}}, {{Rail icon}}, {{Rail color box}}, {{Line link}}, {{Station link}} and {{Rail color}}. Please see those templates' pages for documentation on how to use those templates. (Instructions for the convert
function of this module are in the {{Adjacent stations}} documentation.)
The aforementioned templates rely on data stored in subpages for this module (list). For example, {{Rail icon|MTR}}
generates [REDACTED] using Module:Adjacent stations/MTR.
It is possible to create and edit data by following existing examples, but having some knowledge of Lua helps prevent mistakes. If you have programmed or used Lua before, you may like to skip the next subsection.
Terms
For a more comprehensive overview of Lua, see mw:Extension:Scribunto/Lua reference manual.- Lua has data types. The ones relevant here are boolean, string, number, and table.
- A boolean is either
true
orfalse
. - A string is text, stored as a list of characters. While Lua has several ways of indicating strings within code, in the Lua examples here, strings are indicated by enclosing text in double quotes (e.g.
"This is a string"
). - A number is a numerical value, like
0.5
or42
. - A table is a structure that can contain other objects, including other tables.
- An empty table looks like
{}
in the code. - Tables have keys and values, which are typically of the structure
= value
; each key–value pair is separated by a comma. All keys used here are strings or numbers. {"text", "more text"}
is equivalent to{ = "text", = "more text"}
.
- An empty table looks like
- A boolean is either
- A variable can be defined using
local variable_name = "value".
- Whitespace is any tab, line break or other space. Whitespace doesn't matter in Lua, but all examples here except for those inline with text are neatly indented for readability, with separate table keys on separate lines.
- A return statement (e.g.
return variable_name
) causes a function to exit and reports the value ofvariable_name
. The "function" here is the code in the main module calling the subpage, and thevariable_name
should be the data table.
Basic structure
Two modules are demonstrated below:
Blank | local p = { = "", = "", = { "", = "", }, = { = { = "", = "", = "", = "", }, }, } return p |
Example | local x = "%1 light rail station" local p = { = "]", = "]", = "title", = { "%1 metro station", = "Central Park metro station (Taiwan)", = "Ciaotou station", ="Gangshan station", = "%1", = "%1 HSR station", = "%1 station", -- Circular light rail = x, = x, = x, = x, = x, = x, = x, = x, = x, = x, = x, = x, = x }, = { = { = "]" }, = { = "e20b65", = "]", = "Gangshan", = "Siaogang" }, = { = "faa73f", = "]", = "Hamasen", = "Daliao" }, = { = "]", = "Circular line", = "7cbd52", = "]", = 1, = "outer loop / anticlockwise", = "inner loop / clockwise" }, -- Future lines = { = "ffc100", = "]", = {"Cruise Terminal", "Cianjhen Senior High School"}, = "Dipu" }, -- Proposed = { = "]", = "c100ff" }, = { = "]", = "008583" }, = { = "]", = "007fff" }, = { = "]", = "50cb00" }, = { = "]", = "aa34c0" }, = { = "]", = "e10a65" }, }, = { = "Circular", = "Circular", = "Orange", = "Orange", = "Red", = "Red", = "Yellow", = "Yellow" } } return p |
The example module is Module:Adjacent stations/Kaohsiung Rapid Transit.
- The two required table entries are "station format" and "lines". The former is a table with data to form links to station articles, and the latter is a table containing a table for each line.
- "system title" is the text in the middle cell of the table header row.
- "station format" defines the default name format for station articles and exceptions. The first variable ("%1 metro station") is the default. Exceptions are listed as key–value pairs (e.g. "Zuoying"–"Zuoying HSR station"), where the key is the input name. The module displays the input name and links to the article with the formatted name, replacing "%1" with the input. Alternatively, the full wikilink and be entered. This can be used to have the display be different from the input.
- "lines" is where the lines are listed. The names here are used internally and not displayed, so choose a concise one.
- "line title" is the text displayed in the middle of each row indicating the line; "left terminus" is the default station name for the left side terminus, and "right terminus" is the default station name for the right side terminus.
- Each "color" entry is the colour of the line.
Below is Module:Adjacent stations/Taiwan High Speed Rail:
local x = "%1 station" local p = { = "]", = "]", = "c35617", = "color: #FFFFFF; background-color: #C35617;", = { "%1 HSR station", = "Taipei Main Station", = x, = x }, = { = { = "]", = "c35617", = "Nangang", = "Zuoying" } } } return p
- x (defined in the first line) is a string used for formatting the station name. The variable x is used inside the "station format" table for the three articles where the title ends in "station" instead of "HSR station". This practice is optional but helpful when many articles fit a second pattern.
- The module recognises a virtual line named
. The title and colour of this line is used for all lines unless overridden. Parameters are used in the absence of a specified
line=
in transclusion.
Hierarchy and list of parameters
- The first layer of the table is data for the entire system, as well as output options.
- Under the system table is the list of lines.
- The third layer is data for a given line.
- Each line can have 'types'. This can be either types of services or branches of the line.
- The fifth layer is data for a given type.
If not specified, all keys and values are strings.
Main layer (1)
Parameter | Type | Used in {{Adjacent stations}} | Description |
---|---|---|---|
|
String | Yes | Values are "en-US" and "en-GB" . If not set, "en-GB" is assumed.
|
|
String | Yes | Text in the middle cell of the header. |
|
String | Yes | Image used in the middle cell of the {{Adjacent stations}} header and by {{Rail icon}}. |
|
String | No | Icon type, used by {{Rail icon}}. If specified and not "image" , the value is passed to the function that implements {{Rail color box}}.
|
|
String | No | RGB hex triplet (three or six characters, like "BE2D2C" or "039" ). Can be called by using only one parameter in {{Rail color}}.
|
|
String | Yes | The noun after 'preceding' and 'following' in the left and right header cells. Default value is "station" .
|
|
String | No | CSS for the header of {{Infobox station}} and anything else using the style function with |1=header . Values can be strings or nested tables, with the first level being for the line (whatever's in |style2= of {{Infobox station}}). The second level is currently unused. The first entry in a nested table with no key (i.e. with key 1 ) is the default.
|
|
String | No | RGB hex triplet for {{Infobox station}} subheaders and anything else using the style function with |1=subheader . By default, it is a light gray. Values can be strings or nested tables, like those for "name format" .
|
|
String | No | RGB hex triplet for {{Infobox station}} subheaders and anything else using the style function with |1=subheader . By default, it is calculated based on the header background color. Values can be strings or nested tables, like those for .
|
|
Table or string | Yes | Table containing station format strings. The first entry without a specified key (i.e. with the key being the number 1 ) is the default, and all other entries must have keys corresponding to the input. Format strings without wikilink brackets are converted to links, with the input (usually the station name) used as the displayed text. Tables can be nested within this table to indicate options based on the line and line type passed to this template.
|
|
Table | Yes | Data table containing line tables. |
|
Table | Yes | Table containing aliases (as table keys) for lines (as values). All keys are lowercase, as the input is treated as case-insensitive by being lower-cased. |
Station format table (2)
Parameter | Type | Used in {{Adjacent stations}} | Description |
---|---|---|---|
|
String | Yes | Default format. |
|
String or table | Yes | Format for a non-default station, or line-specific format table. |
Line-specific format table (3)
Parameter | Type | Used in {{Adjacent stations}} | Description |
---|---|---|---|
|
String | Yes | Default format. |
|
String or table | Yes | Format for a non-default station, or type-specific format table. |
Type-specific format table (4)
Parameter | Type | Used in {{Adjacent stations}} | Description |
---|---|---|---|
|
String | Yes | Default format. |
|
String | Yes | Format for a non-default station. |
Line table (3)
A virtual line named can be added to set default values for all lines. Currently, this is available for three parameters.
Parameter | Type | Used in {{Adjacent stations}} | Description |
---|---|---|---|
|
String | Yes | The text displayed in the middle cell, typically a link to the line's article. If not specified, then the data in is used (%1 in the default value is replaced by the input after alias replacement).
|
|
String | No | Abbreviated line name used by {{Rail color box}}. |
|
String | No | Image used by {{Rail icon}}. If not specified, then the data in is used (%1 in the default value is replaced by the input after alias replacement).
|
|
String | No | Icon type used by {{Rail icon}}. If specified and not "image" , the value is passed to the function that implements {{Rail color box}}.
|
|
String | Yes | RGB hex triplet. Lines fall back to the colour (if any) or the system's colour if they themselves do not have one; types fall back to the line's colour (if any), to the colour (if any) or to the system's colour. This colour is used in the second and fourth columns of {{Adjacent stations}}, and by {{Rail color box}} and {{Rail icon}} as the emphasised colour. By default, if a type and its line both have a colour, then the line's colour will be treated as the background colour (see next section) for the line name in the middle cell. This can be turned off by setting the type's background colour to "" or "transparent" .
|
|
String | Yes | RGB hex triplet (three or six characters). This colour is optional and is only displayed behind the line name in the middle cell. The module adds transparency so that all text displayed over the background is legible. |
|
String | No | RGB hex triplet used by {{Rail color box}}. |
|
String | No | RGB hex triplet used by {{Rail color box}}. |
|
String | Yes | The station which is usually the left terminus of the line. If there are multiple stations by default, the value should be a table containing numbered values (e.g. = {"Chesham", "Amersham"} ). The key in that table can be used to append 'via' and the value's station link.
|
|
String | Yes | The station which is usually the right terminus of the line; behaves like .
|
|
String | Yes | Default small text below line and type names. Overridden by |note-mid= in transclusion.
|
|
Boolean | Yes | If the value is true then the termini will display without 'toward'/'towards'. May be overridden by type.
|
|
Boolean | Yes | If the value is true then 'One-way operation' will display instead of the left terminus.
|
|
Boolean | Yes | Right counterpart of oneway-left. |
|
Table | Yes | Table containing the line type tables. |
Type table (5)
Parameter | Type | Used in {{Adjacent stations}} | Description |
---|---|---|---|
|
String | Yes | The name of the line type. In {{Adjacent stations}}, this is displayed as normal-sized text below the line name in the middle cell; in {{Rail color box}}, for some options this is displayed after the line name, separated from it by a spaced en dash (this is also used for the nonstop text). To avoid displaying a type name, set this to "" .
|
|
String | No | Abbreviated line name used by {{Rail color box}}. |
|
String | No | Image used by {{Rail icon}}. |
|
String | No | Icon type used by {{Rail icon}}. If specified and not "image" , the value is passed to the function that implements {{Rail color box}}.
|
|
String | Yes | RGB hex triplet. Lines fall back to the colour (if any) or the system's colour if they themselves do not have one; types fall back to the line's colour (if any), to the colour (if any) or to the system's colour. This colour is used in the second and fourth columns of {{Adjacent stations}}, and by {{Rail color box}} and {{Rail icon}} as the emphasised colour. By default, if a type and its line both have a colour, then the line's colour will be treated as the background colour (see next section) for the line name in the middle cell. This can be turned off by setting the type's background colour to "" or "transparent" .
|
|
String | Yes | RGB hex triplet (three or six characters). This colour is optional and is only displayed behind the line name in the middle cell. The module adds transparency so that all text displayed over the background is legible. |
|
String | No | RGB hex triplet used by {{Rail color box}}. |
|
String | No | RGB hex triplet used by {{Rail color box}}. |
|
String | Yes | The station which is usually the left terminus of the line. Overrides line terminus. If there are multiple stations by default, the value should be a table containing numbered values (e.g. = {"Chesham", "Amersham"} ). The key in that table can be used to append 'via' and the value's station link.
|
|
String | Yes | The station which is usually the right terminus of the line; behaves like .
|
|
String | Yes | Default small text below line and type names. Overridden by |note-mid= in transclusion.
|
|
Boolean | Yes | If the value is true then the termini will display without 'toward'/'towards'.
|
For editors
Disambiguating stations
Station links are generated using the station format
part of the data module. Each data module defines a default case and then exceptions, if necessary. Station format
is an array, similar to a switch with cases. Take Module:Adjacent stations/Incheon Subway, shown below:
local incheon = "%1 station (Incheon)" local n = "font-size: 160%; font-family:sans-serif; font-weight: bolder; color: #FFFFFF; padding: 0.4em 4px; background-color: #" local colors = { = "8cadcb", = "f06a00", = "777777", } local p = { = "]", = "", = { = n .. colors, = n .. colors, = n .. colors, }, = colors, = "ffffff", = { "%1 station", = incheon, = incheon, = "]", = incheon, = incheon, }, = { = { = "]", = colors, = "FFFFFF", = "]", = "Gyeyang", = "Songdo Moonlight Festival Park", }, = { = "]", = colors, = "FFFFFF", = "]", = "Geomdan Oryu", = "Unyeon", }, = { = "]", = colors, = "FFFFFF", } } } return p
The default case is "%1 station"
, listed first. The "%1" expands to whatever the passed name of the station is. "Bakchon" becomes Bakchon station. There are several exceptions. The two usual reasons for exceptions are disambiguation or presenting a name in a non-standard way. In this case, the Incheon Subway serves three stations whose names are disambiguated: Arts Center station (Incheon), Central Park station (Incheon), and Mansu station (Incheon). This is handled by specifying a key for each station and supplying a different format. Since all three are disambiguated the same way, you can define a local variable at the top of the module:
local incheon = "%1 station (Incheon)"
This can then be used with the exceptions:
= incheon,
Were it written out, it would look like this:
= "%1 station (Incheon)"
For developers
Suggestions are welcomed on the talk page.
To-do list
- Convert more systems from {{S-line}}, {{rail line}}, {{J-rserv}} and {{J-route}}
- Make an example module which contains all of the module's features, to avoid excessive examples in the documentation (maybe based on {{Rdt demo}})
- Allow direct replacement of {{Rail line}}?
- Before translation: figure out how to handle grammatical gender and inflection in various languages with the i18n table (e.g. these phrases)
- Allow inline sources to be added
- Figure out Wikidata integration (require sources on Wikidata end) :
- Add a short list of changes from {{S-line}}, for the convenience of the many editors who have used it in the last 11 years
- changes in function (new structure, data inside module, circular and branch functionality changed, replacement of manual cell merging…)
- parameter name changes (-left and -right, mostly – search {{S-line}} for {{{, maybe with the TemplateData generator, to make a list)
Editors can experiment in this module's sandbox (edit | diff) and testcases (edit | run) pages.
Subpages of this module.
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 then lineN = data or lineN end local default = data or {} local line = data or {} for k, v in pairs(default) do if v then line = line or v end end line = line and mw.ustring.gsub(line, '%%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, = Type} } end return frame:expandTemplate{ title = system .. ' color' } else line = (getLine(data, line)) local default = data if line or default then default = default or {} if not line then line = mw.clone(default) end local color = line or line or default or default or data local Type_value = Type and line and (line and line) if Type_value then color = Type_value end return color end return (default and (default or default) or data 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 or lineNformats if not _Format then error(somethingMissing('lineN', lineN, lineNformats)) elseif type(_Format) == 'table' then local typeNformats = _Format _Format = typeNformats or typeNformats 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({']'}) 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 or Format)) or type(var1) == 'table' and #var1 > 0 and getStation(var1, (Format] or Format)) 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 = i18n(subst(var, i)) end if #var > 1 then t = i18n(subst(var, #var)) end if var then if i18n then table.insert(t, 1, i18n(subst(var, 'via'))) else table.insert(t, i18n(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 = { = true, = true, = true, = true, = 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 = v:match('^%s*(.-)%s*$') if _args and _args ~= '' 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 then v = yesno(v) end if not args then args = { = v} table.insert(index, b) elseif args then return error(i18n(a .. b)) else args = 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 = 'class="hcA"|', = 'colspan="3" class="hmA"|', = 'class="bcA"|', = '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 = args and getData(args) -- If an address has no system, the row uses data from the previous address or data] or (args and getData(args])) or error(i18n(args)) local lang = data or lang if args and not args then -- Header row local stop_noun = data or i18n table.insert(wikitable, table.concat({'\n|-', '\n! scope="col" ', style, i18n(stop_noun), '\n! scope="col" ', style, (data and data .. ' ' or ''), (data or (' ..']]')), '\n! scope="col" ', style, i18n(stop_noun) })) table.insert(wikitable, '') table.insert(wikitable, '') table.insert(wikitable, '') end if args then -- Subheader table.insert(wikitable, '\n|-\n!colspan="5" class="hmA"|'.. args) table.insert(wikitable, '') table.insert(wikitable, '') table.insert(wikitable, '') end if args or args or args or args then if not args and i > 1 and not args then args = args] end lineN = args or '_default' typeN = args if data then lineN = data or lineN if typeN then typeN = data or typeN end end -- get the line table local line = data and (mw.clone(data) or error(i18n(args))) or error(i18n) local default = data or {} line = line or default or '' line = mw.ustring.gsub(line, '%%1', lineN) -- cell across row for non-stop service if args then table.insert(wikitable, table.concat({'\n|-\n|colspan="5" ', style, ((args == 'former') and i18n or i18n)(p._box({data = data, line = lineN, Type = typeN, inline = 'yes'})) }) ) table.insert(wikitable, '') table.insert(wikitable, '') table.insert(wikitable, '') else local Format = data or i18n local color, color_2, background_color, circular local Type = line and line -- get the line type table if Type then if Type then -- line color is used as background if there is no background color in the line type table background_color = Type or line color = Type color_2 = Type or color else background_color = Type or line color = line or default or '' color_2 = line or color end if Type then -- Type may override the circular status of the line circular = Type end else background_color = line color = line or default or '' color_2 = line or color circular = line end -- Alternate termini can be specified based on type local sideCell = {true, true} for i, b in ipairs({'left', 'right'}) do if not args then -- If no station is given on one side, the station is assumed to be the terminus on that side local _through = args or args local _through_data = getLine(data, _through) if _through_data then _through = _through_data or _through end sideCell = _through and "''" .. i18n(trimq(_through)) .. "''" or "''" .. trimq((args or args) and i18n or i18n) .. "''" else local terminusT local terminusN = Type and Type or line -- 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 cannot be used and terminusN is reserved if type(terminusN) == 'string' or (type(terminusN) == 'table' and (terminusN or terminusN)) then if args then terminusT = args local _or = mw.ustring.match(terminusT, i18n) if _or then terminusT = mw.ustring.gsub(terminusT, i18n, '\127_OR_\127') terminusT = mw.ustring.gsub(terminusT, i18n, '\127_OR_\127') end local _via = (mw.ustring.match(terminusT, i18n)) if _via then terminusT = mw.ustring.gsub(terminusT, i18n, '') terminusT = mw.text.split(terminusT, '\127_OR_\127') terminusT = _via elseif _or then terminusT = mw.text.split(terminusT, '\127_OR_\127') end else terminusT = terminusN end elseif type(terminusN) == 'table' then terminusT = terminusN] or terminusN] or terminusN end local mainText = args and getTerminusText(args, Format) .. small(args) or getTerminusText(args, Format) local subText = (args or line) and i18n or args == terminusT and i18n or circular and terminusT or i18n(getTerminusText(terminusT, Format)) subText = small(subText, true) sideCell = mainText .. subText end end table.insert(wikitable, '\n|-') table.insert(wikitable, '\n|' .. style .. sideCell) table.insert(wikitable, table.concat({'\n|', style, color, '"|', '\n|', (background_color and 'class="bcA" style="background-color:rgba(' .. table.concat(rgb(background_color), ',') .. ',.2)"|' or style), line, -- 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 or typeN) .. '</div>' or ''), -- Note-mid; table key 'note-mid' in subpages. Overridden by user input ((args and small(args)) or (Type and Type and small(Type)) or (line and small(line)) or ''), -- Transfer; uses system's station link table (args and small('transfer at ' .. getTerminusText(args, Format), true) or ''), '\n|', style, color_2, '"|'})) table.insert(wikitable, '\n|' .. style .. sideCell) end end if args then -- Note if args:match('^%s*<tr') or args:match('^%s*%|%-') then table.insert(wikitable, '\n' .. args) else table.insert(wikitable, '\n|-\n|colspan="5" ' .. style .. args) end table.insert(wikitable, '') table.insert(wikitable, '') table.insert(wikitable, '') end end local function combine(t, n) if t ~= '' and t == t then t = '' -- The cell in the next row is deleted local rowspan = 2 while t == t do t = '' rowspan = rowspan + 1 end t = mw.ustring.gsub(t, '\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 -- ] return function (frame) local args = getArgs(frame, {parentOnly = true}) return p(args, frame) end end local function makeTemplateFunction(funcName) -- makes a function that can be returned from #invoke, using -- ] return function (frame) local args = getArgs(frame, {frameOnly = true}) return p(args, frame) end end p.main = makeInvokeFunction('_main') function p._color(args, frame) local data = args.data if args or data then data = data or getData(args, true) if not data then return getColor(nil, args, args, args, frame) end return getColor(data, nil, args, args) end end p.color = makeInvokeFunction('_color') function p._box(args, frame) local system = args or args.system lineN = args or args.line if not (system or lineN) then return '' end local line, Type, line_data local inline = args 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 or {} line, lineN = getLine(data, lineN) if typeN then typeN = data and data or typeN Type = line and line and line or typeN end color = getColor(data, nil, lineN, typeN) if inline ~= 'box' then line_data = line or error(i18n(lineN)) line = line_data or default or error(i18n('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, = typeN} } if mw.text.trim(line) == '' then return error(i18n(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 -- ] 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> ' .. 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 = ']' .. 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> ' .. line .. result elseif inline == 'lsquare' then local link = args.link or mw.ustring.match(line, '%]+)]') if link then result = ']' 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 = ']' 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 or data or {} if line_data then if line_data and line_data then local Type_data = line_data border_color = Type_data or line_data or color text_color = Type_data or line_data if color_box == 'title' and not args then lineN = Type_data or line_data or require('Module:Delink')._delink{line} else lineN = Type_data or line_data or lineN end else border_color = line_data or color text_color = line_data if color_box == 'title' and not args then lineN = line_data or require('Module:Delink')._delink{line} else lineN = line_data 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 -- ] if link then result = '<span style="color:inherit;background-color:#' .. color .. ';border:.075em solid #' .. border_color .. ';padding:0 .3em">]</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>' end elseif inline == 'croute' then -- ] if link then result = '<span style="color:inherit;background-color:#' .. color .. ';border:.075em solid #' .. border_color .. ';border-radius:.5em;padding:0 .3em">]</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 -- ] if link then result = '<span style="border:.075em solid #' .. border_color .. ';border-radius:.5em;padding:0 .3em">]</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">]</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 -- ] (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 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 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 or args.line) local icon local icon_format if line then local line_type = args or args.type if line_type then line_type = data.aliases and data.aliases or line_type line_type = line.types and line.types -- If there's no type table or entry for this type, then it can't have its own icon icon_format = line_type or data 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 or data 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 end if not icon_format then icon_format = data end if icon_format then if icon_format ~= 'image' then icon = p._box({data = data, = (args or args.line), = icon_format, type = (args 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 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) then icon = mw.ustring.gsub(icon, regex, size .. '%1') -- elseif mw.ustring.match(icon, regex) then -- icon = gsub(icon, regex, size .. '%1') -- elseif mw.ustring.match(icon, regex) then -- icon = gsub(icon, regex, 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 end return icon end p.icon = makeInvokeFunction('_icon') p = makeTemplateFunction('_icon') function p._line(args, frame) local system = args or args.system local line = args or args.line if not line then return '' end local Type = args 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(line)) if Type then Type = data and data or Type Type = line and line and line or Type end line = line or error(i18n('title')) else line = frame:expandTemplate{ title = system .. ' lines', args = {line, = Type} } if mw.text.trim(line) == '' then return error(i18n(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 or args.system lineN = args 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 or {} line, lineN = getLine(data, lineN) if typeN then typeN = data and data or typeN Type = line and line and line or typeN end line_data = line or error(i18n(lineN)) line = line_data or default or error(i18n('title')) line = mw.ustring.gsub(line, '%%1', lineN) else line = frame:expandTemplate{ title = system .. ' lines', args = {lineN, = typeN} } if mw.text.trim(line) == '' then return error(i18n(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 and line_data then local Type_data = line_data lineN = Type_data or line_data or lineN else lineN = line_data or lineN end end if link then result = ']' 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 or args.system local station = args or args.station if not station then return '' end lineN = args or args.line typeN = args or args.type local data = args.data if system or data then data = data or getData(system, true) if data then local _Format = data or data if _Format then if data then if lineN then lineN = data or lineN end if typeN then typeN = data or typeN end end station = getStation(station, _Format) else station = station or '' end else station = frame:expandTemplate{ title = system .. ' stations', args = { = station, = lineN, = typeN} } end return station end end p.station = makeInvokeFunction('_station') p = makeTemplateFunction('_station') function p._terminusTable(args, frame) local system = args or args.system lineN = args or args.line local side = mw.ustring.sub(mw.ustring.lower(args 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(lineN)) if typeN and data and data then typeN = data or typeN end local Type = line and line local circular if Type then if Type then -- Type may override the circular status of the line circular = Type end else circular = line end return Type and Type or line, data or i18n, 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 or args.style local system = args or args.system local line = args or args.line local station = args 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 or var if type(var) == 'table' then var = var or var end end if var ~= '' then return var end end if style == 'header' then local tmp = data and getValue(data) if tmp then table.insert(result, tmp) end elseif style == 'subheader' then local tmp = data and getValue(data) if tmp then table.insert(result, 'background-color:#' .. tmp) local color = data and getValue(data) 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 and getValue(data) 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, '^%s*{{(.*)}}%s*$', '%1'), '%s*}}%s*{{%s*') local system local group = tonumber(args.offset or 0) or 0 local firstgroup = group + 1 local delete = { = true, = true, = true, = true, = true, = true, = true, = 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 = { = 'left', = 'right', = 'to-left', = 'to-right', = 'type', = 'note-left', = 'note-mid', = 'note-right', = 'oneway-left', = 'oneway-right', = 'through-left', = 'through-right' } local remove_rows = {} local data = {} local noclear = false for i, v in ipairs(code) do code = mw.ustring.gsub(code, '\n', ' ') local template = mw.ustring.lower(mw.text.trim(mw.ustring.match(code, '^+'))) code = mw.ustring.match(code, '(|.+)$') if (mw.ustring.match(code or '', 'noclear%s*=%s*')) then noclear = true end if template == 's-line' then data = {} local this_system = mw.text.trim(mw.ustring.match(code, '|%s*system%s*=(+)')) code = mw.text.split(code, '%s*|%s*') for m, n in ipairs(code) do local tmp = mw.text.split(n, '%s*=%s*') if tmp then tmp = mw.ustring.gsub(n, '^.-%s*=', '') end tmp = replace] or tmp if tmp then -- checks for matching brackets local curly = select(2, mw.ustring.gsub(tmp, "{", ""))-select(2, mw.ustring.gsub(tmp, "}", "")) local square = select(2, mw.ustring.gsub(tmp, "%, "%]", "")) if not (curly == 0 and square == 0) then local count = mw.clone(m)+1 while not (curly == 0 and square == 0) do tmp = tmp..'|'..code curly = curly+select(2, mw.ustring.gsub(code, "{", ""))-select(2, mw.ustring.gsub(code, "}", "")) square = square+select(2, mw.ustring.gsub(code, "%, "%]", "")) code = '' count = count+1 end end data] = tmp end end if (this_system ~= system) or (not system) then system = this_system data = system else data = nil end local last = data or data or data if last then for r, s in pairs({ = {'left', 'to-left', 'note-left', 'oneway-left'}, = {'right', 'to-right', 'note-right', 'oneway-right'}, = {'type', 'note-mid'} }) do if data then for m, n in ipairs(s) do if not data then data = last end end end end end code = {} local X = '|' local Y = (i+group)..'=' if data then table.insert(code, '|system') table.insert(code, Y) table.insert(code, data) table.insert(code, '\n') end for m, n in ipairs(order) do if data then table.insert(code, X) table.insert(code, n) table.insert(code, Y) table.insert(code, data) end end code = table.concat(code) elseif template == 's-note' then code = mw.ustring.gsub(code, '|%s*text%s*=', '|header'..i+group..'=') code = mw.ustring.gsub(code, '|%s*wide%s*=*', '') elseif template == 's-text' then code = mw.ustring.gsub(code, '|%s*text%s*=', '|note-row'..i+group..'=') elseif delete then code = '' 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*=(*)') 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 = '}}' 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..code..t end return pCategories: