模組:线路时刻表
外观

local p = {}
local getArgs = require('Module:Arguments').getArgs
---@param funcName string
---@return function
local function makeInvokeFunction(funcName)
return function(frame)
local args = getArgs(frame, { parentOnly = true })
return p[funcName](args, frame)
end
end
---@alias spanL2 { [1]: string[], [2]: string[] } -- start or end
---@alias spanL1 spanL2[] -- day
---@alias spanL0 { line: string, type: string?, stations: string[], [string]: spanL1 }
---@alias spanLL2 { [1]: string[], [2]: string[][] } -- start or end
---@alias spanLL1 spanLL2[] -- day
---@alias spanLL0 { line: string, type: string?, stations: string[], [string]: spanLL1, override_termini: string[], sub_termini: { [1]: string[], [2]: string[], scope: 'last' } }
---@alias pointL2 { [1]: string[], [2]: string[] } -- start or end
---@alias pointL1 pointL2[] -- day
---@alias pointL0 { line: string|string[], type: string?, [string]: pointL1 }
---@alias timeEntry { days: string[], ref: string, timespans: (spanL0|spanLL0)[], points: pointL0[] }
---@type timeEntry
local data
---@alias adjType { title: string, color: string, circular: boolean, ['left terminus']: string, ['right terminus']: string }
---@alias adjLine { types: { [string]: adjType }, title: string, color: string, circular: boolean, ['left terminus']: string, ['right terminus']: string }
---@alias adjEntry { lines: { [string]: adjLine } }
---@type adjEntry
local adj_data
---加载时刻数据
---@param system string
---@return timeEntry?
local function getData(system)
local title = mw.title.new('Module:线路时刻表/' .. system)
if not (title and title.exists) then return nil end
return require('Module:线路时刻表/' .. system)
end
---加载线网数据
---@param system string
---@return adjEntry?
local function getAdjData(system)
local title = mw.title.new('Module:Adjacent stations/' .. system)
if not (title and title.exists) then return nil end
return require('Module:Adjacent stations/' .. system)
end
---加载数据
---@param system string
local function initData(system)
local d = getData(system)
if d then data = d
else error('不存在模块:线路时刻表/' .. system)
end
local a = getAdjData(system)
if a then adj_data = a
else error('不存在模块:Adjacent stations/' .. system)
end
end
---获取线路标题
---@param line string
---@param _type string
---@return string
local function getTitle(line, _type)
if _type and adj_data.lines[line].types[_type] then
return adj_data.lines[line].types[_type].title or
adj_data.lines[line].title
else
return adj_data.lines[line].title
end
end
---获取线路颜色
---@param line string
---@param _type string
---@return string
local function getColor(line, _type)
if _type and adj_data.lines[line].types[_type] then
return '#' .. (adj_data.lines[line].types[_type].color or
adj_data.lines[line].color)
else
return '#' .. adj_data.lines[line].color
end
end
---获取线路终点
---@param line string
---@param _type string
---@return string[]
local function getTermini(line, _type)
if _type and adj_data.lines[line].types[_type] then
return {
adj_data.lines[line].types[_type]['right terminus'] or adj_data.lines[line]['right terminus'],
adj_data.lines[line].types[_type]['left terminus'] or adj_data.lines[line]['left terminus']
}
else
return {
adj_data.lines[line]['right terminus'],
adj_data.lines[line]['left terminus']
}
end
end
---获取车站链接
---@param frame frame
---@param name string
---@param system string
---@param line string
---@param _type string
---@return string
local function getStation(frame, name, system, line, _type)
return require('Module:Adjacent stations')._station(
{ system, name, line, _type },
frame)
end
---构造终点站表头
---@param frame frame
---@param name string
---@param system string
---@param line string
---@param _type string
---@return string
local function makeTerminus(frame, name, system, line, _type)
local circular
if _type and _type ~= '' then
circular = adj_data.lines[line].types[_type].circular
if circular == nil then
circular = adj_data.lines[line].circular
end
else
circular = adj_data.lines[line].circular
end
if circular then
return name
else
return '往' .. getStation(frame, name, system, line, _type)
end
end
---构造次级终点站表头
---@param frame frame
---@param name string
---@param system string
---@param line string
---@param _type string
---@return string
local function makeSubTerminus(frame, name, system, line, _type)
return '至' .. getStation(frame, name, system, line, _type)
end
---检测车站是否运营
---@param t spanL0|spanL1|spanL2|spanLL0|spanLL1|spanLL2|string
---@return boolean
local function checkOperated(t)
if type(t) == 'table' then
return checkOperated(t[1])
else
return t[1] ~= '-'
end
end
---平面化数组
---@param input table
---@param flattened string[]?
---@return string[]
local function flattenArray(input, flattened)
flattened = flattened or {}
for i, element in ipairs(input) do
if type(element) == 'table' then
flattenArray(element, flattened)
else
table.insert(flattened, element)
end
end
return flattened
end
---构建车站信息框时刻
---@param args string[]
---@param frame frame
---@return html
function p._as_station(args, frame)
local system = args[1]
local station = args[2]
local all_days = args[3] -- 使用完整时刻表
initData(system)
if not all_days then
data.days = { data.days[1] }
end
local tb = mw.html.create('table')
:addClass('wikitable')
:addClass('station-infobox-timetable')
-- 表头
if #data.days > 1 then
local tr_day = mw.html.create('tr')
tr_day
:tag('tr')
:tag('th')
:attr('colspan', 3):attr('rowspan', 2)
:wikitext(frame:preprocess(data.ref)):done()
for v, day in ipairs(data.days) do
tr_day
:tag('th')
:attr('colspan', 2):attr('scope', 'colgroup')
:wikitext(day):done()
end
tr_day:allDone()
tb:node(tr_day)
local tr_type = mw.html.create('tr')
for v = 1, #data.days do
tr_type
:tag('th')
:attr('scope', 'col')
:wikitext('首班'):done()
:tag('th')
:attr('scope', 'col')
:wikitext('末班'):done()
end
tr_type:allDone()
tb:node(tr_type)
else
tb:tag('tr')
:tag('th')
:attr('colspan', 3)
:wikitext(data.days[1], frame:preprocess(data.ref)):done()
:tag('th')
:attr('scope', 'col')
:wikitext('首班'):done()
:tag('th')
:attr('scope', 'col')
:wikitext('末班'):done()
:done()
end
for r, route_data in ipairs(data.timespans) do
local _data = route_data[station]
if _data and checkOperated(_data) then
-- 允许系统内部分线路退化为单day
if #data.days > 1 and #_data == 1 then
for d = 1, #data.days - #_data do
table.insert(_data, _data[1])
end
end
local color = getColor(route_data.line, route_data.type)
---@type string[]
local termini = route_data.override_termini or
getTermini(route_data.line, route_data.type)
-- 有子终点站
if route_data.sub_termini then
if route_data.sub_termini.scope == 'last' then
---@type spanLL1
local _d = _data
local sum_row = 0
local t_map = {}
local s_map = {}
-- 预处理:有效行
for t, terminus in ipairs(termini) do
table.insert(s_map, {})
for s = 1, #route_data.sub_termini[t] do
for d = 1, #data.days do
if _d[d][2][t][s] and _d[d][2][t][s] ~= '' then
table.insert(s_map[#s_map], s)
break
end
end
end
-- 处理终点站(末班为空)
if #s_map[#s_map] == 0 then
if terminus == station then
table.insert(s_map[#s_map], 0) -- 标记终点站
end
end
if #s_map[#s_map] == 0 then
table.remove(s_map)
else
table.insert(t_map, t)
end
sum_row = sum_row + #s_map[#s_map]
end
-- 线路色条
local tr = mw.html.create('tr')
tr:tag('td')
:addClass('bar'):attr('rowspan', sum_row):css('background-color', color):done()
for it, t in ipairs(t_map) do
local terminus = termini[t]
if it > 1 then
tr = mw.html.create('tr')
end
if s_map[it][1] > 0 then
tr:tag('th')
:addClass('major'):attr('rowspan', #s_map[it]):attr('scope', 'rowgroup')
:wikitext(makeTerminus(frame, terminus, system, route_data.line, route_data.type)):done()
for is, s in ipairs(s_map[it]) do
local sub_terminus = route_data.sub_termini[t][s]
if is > 1 then
tr = mw.html.create('tr')
end
if terminus == sub_terminus then
tr:tag('th')
:addClass('minor'):attr('scope', 'row')
:wikitext('全程'):done()
else
tr:tag('th')
:addClass('minor'):attr('scope', 'row')
:wikitext(makeSubTerminus(frame, sub_terminus, system, route_data.line, route_data.type)):done()
end
for d = 1, #data.days do
if is == 1 then
tr:tag('td')
:attr('rowspan', #s_map[it])
:wikitext(_d[d][1][t]):done()
end
tr:tag('td')
:wikitext(_d[d][2][t][s]):done()
end
tr:allDone()
tb:node(tr)
end
elseif s_map[it][1] == 0 then
tr:tag('th')
:attr('colspan', 2):attr('scope', 'row')
:wikitext(makeTerminus(frame, terminus, system, route_data.line, route_data.type)):done()
:tag('td')
:addClass('note'):attr('colspan', 2 * #data.days)
:wikitext('终点站'):done()
:done()
tr:allDone()
tb:node(tr)
else
error('无效的索引值')
end
end
else
error('不支持的子终点站作用域') -- 目前仅实现末班车的子终点站
end
-- 无子终点站
else
---@type spanL1
local _d = _data
local t_map = {}
-- 预处理:有效行
for t, terminus in ipairs(termini) do
if terminus == station then
table.insert(t_map, t)
else
for d = 1, #data.days do
if (_d[d][1][t] and _d[d][1][t] ~= '') or (_d[d][2][t] and _d[d][2][t] ~= '') then
table.insert(t_map, t)
break
end
end
end
end
local tr = mw.html.create('tr')
tr:tag('td')
:addClass('bar'):attr('rowspan', #t_map):css('background-color', color):done()
for it, t in ipairs(t_map) do
local terminus = termini[t]
if it > 1 then
tr = mw.html.create('tr')
end
tr:tag('th')
:attr('colspan', 2):attr('scope', 'row')
:wikitext(makeTerminus(frame, terminus, system, route_data.line, route_data.type)):done()
if terminus == station then
tr:tag('td')
:addClass('note'):attr('colspan', 2 * #data.days)
else
for d, day in ipairs(data.days) do
tr:tag('td')
:wikitext(_d[d][1][t]):done()
tr:tag('td')
:wikitext(_d[d][2][t]):done()
end
end
tr:allDone()
tb:node(tr)
end
end
end
end
return tb:allDone()
end
---构建车站车次时刻
---@param args string[]
---@param frame frame
---@return html
function p._as_station_ext(args, frame)
local system = args[1]
local station = args[2]
initData(system)
local tb = mw.html.create('table')
:addClass('wikitable')
:addClass('station-route-timetable')
for r, route_data in ipairs(data.points) do
local _data = route_data[station]
if _data then
if type(route_data.line) == 'table' then
for l, line in ipairs(route_data.line) do
tb:tag('tr')
:tag('td')
:addClass('bar'):addClass('composed'):attr('colspan', (#data.days + 1))
:css('background-color', getColor(line, route_data.type))
:allDone()
end
else
tb:tag('tr')
:tag('td')
:addClass('bar'):attr('colspan', (#data.days + 1))
:css('background-color', getColor(route_data.line, route_data.type))
:allDone()
end
local tr = mw.html.create('th')
:tag('th')
:addClass('ref'):wikitext(frame:preprocess(getTitle(route_data.line[1] or route_data.line, route_data.type)))
:done()
for v, day in ipairs(data.days) do
tr:tag('th'):wikitext(day):done()
end
tb:node(tr:allDone())
local termini = route_data.override_termini or
getTermini(route_data.line[1] or route_data.line, route_data.type)
for t, terminus in ipairs(termini) do
tr = mw.html.create('tr')
:tag('th')
:wikitext(makeTerminus(frame, terminus, system, route_data.line[1] or route_data.line, route_data.type))
:done()
for v, day in ipairs(data.days) do
tr:tag('td')
:wikitext(
frame:expandTemplate {
title = 'hlist',
args = _data[t][v]
})
:done()
end
tb:node(tr:allDone())
end
end
end
return tb:allDone()
end
---构建线路时刻
---@param args string[]
---@param frame frame
---@return html?
function p._as_line(args, frame)
local system = args[1]
local line = args[2]
local _type = args[3]
initData(system)
for r, route_data in ipairs(data.timespans) do
if line == route_data.line and _type == route_data.type then
local body = {}
local tb = mw.html.create('table')
:addClass('wikitable')
:addClass('line-timetable')
local color = getColor(route_data.line, route_data.type)
local termini = route_data.override_termini or getTermini(route_data.line, route_data.type)
local num_cols
local num_ref_rows = 2
if #data.days > 1 and #route_data[route_data.stations[1]] == 1 then -- 允许线路退化为无 day
data.days = { '' }
end
if #data.days > 1 then num_ref_rows = num_ref_rows + 1 end
if route_data.sub_termini then num_ref_rows = num_ref_rows + 1 end
local ref = mw.html.create('th')
:addClass('ref'):attr('rowspan', num_ref_rows)
:wikitext(frame:preprocess(data.ref))
:done()
local bar = mw.html.create('tr')
---@type html[]
local hr = {}
if route_data.sub_termini then
if route_data.sub_termini.scope == 'last' then
local num_f_cols = #termini
local num_l_cols = #flattenArray(route_data.sub_termini)
num_cols = #data.days * (num_f_cols + num_l_cols)
bar:tag('td')
:addClass('bar'):attr('colspan', num_cols + 1):css('background-color', color)
:allDone()
local hT1 = mw.html.create()
local hT2 = mw.html.create()
local hS = mw.html.create()
for t, terminus in ipairs(termini) do
local th_text = makeTerminus(frame, terminus, system, line, _type)
hT1:tag('th')
:addClass('major'):attr('rowspan', 2)
:wikitext(th_text)
:done()
hT2:tag('th')
:addClass('major'):attr('colspan', #route_data.sub_termini[t])
:wikitext(th_text)
:done()
for i, sub_terminus in ipairs(route_data.sub_termini[t]) do
if terminus == sub_terminus then
hS:tag('th'):addClass('minor'):wikitext('全程'):done()
else
hS:tag('th')
:addClass('minor')
:wikitext(makeSubTerminus(frame, sub_terminus, system, line, _type))
:done()
end
end
end
local hFL = mw.html.create()
:tag('th'):attr('colspan', num_f_cols):wikitext('首班'):done()
:tag('th'):attr('colspan', num_l_cols):wikitext('末班')
:allDone()
hr[1] = mw.html.create('tr'):node(ref)
hr[2] = mw.html.create('tr')
hr[3] = mw.html.create('tr')
hr[4] = mw.html.create('tr')
for v, day in ipairs(data.days) do
hr[1]:tag('th'):attr('colspan', num_f_cols + num_l_cols):wikitext(day):done()
hr[2]:node(hFL)
hr[3]:node(hT1):node(hT2)
hr[4]:node(hS)
end
hr[1]:allDone()
hr[2]:allDone()
hr[3]:allDone()
hr[4]:allDone()
else
error('不支持的子终点站作用域')
end
else
num_cols = 2 * #data.days * #termini
bar:tag('td')
:addClass('bar'):attr('colspan', num_cols + 1):css('background-color', color)
:allDone()
local hT = mw.html.create('')
for t, terminus in ipairs(termini) do
hT:tag('th'):addClass('th'):wikitext(makeTerminus(frame, terminus, system, line, _type)):done()
end
hT:allDone()
local hFL = mw.html.create()
:tag('th'):attr('colspan', #termini):wikitext('首班'):done()
:tag('th'):attr('colspan', #termini):wikitext('末班')
:allDone()
hr[1] = mw.html.create('tr'):node(ref)
hr[2] = mw.html.create('tr')
hr[3] = mw.html.create('tr')
for v, day in ipairs(data.days) do
hr[1]:tag('th'):attr('colspan', 2 * #termini):wikitext(day):done()
hr[2]:node(hFL)
hr[3]:node(hT):node(hT)
end
hr[1]:allDone()
hr[2]:allDone()
hr[3]:allDone()
end
if #data.days == 1 then table.remove(hr, 1) end
tb:node(bar)
for r, row in ipairs(hr) do
tb:node(row)
end
tb:node(bar)
local tr
for s, station in ipairs(route_data.stations) do
local _data = flattenArray(route_data[station])
tr = mw.html.create('tr'):tag('th'):wikitext(station):done()
if _data[1] == '-' then
tr:tag('td'):addClass('closed'):attr('colspan', num_cols):wikitext('未运营'):done()
else
for t, time in ipairs(_data) do
tr:tag('td'):wikitext(time):done()
end
end
tb:node(tr:allDone())
end
tb:node(bar)
return tb:allDone()
end
end
end
p.as_station = makeInvokeFunction('_as_station')
p.as_station_ext = makeInvokeFunction('_as_station_ext')
p.as_line = makeInvokeFunction('_as_line')
return p