跳转到内容

模組:线路时刻表

本页使用了标题或全文手工转换
维基百科,自由的百科全书

这是本页的一个历史版本,由David Xuang留言 | 贡献2022年8月28日 (日) 16:26 (// Edit via Wikiplus)编辑。这可能和当前版本存在着巨大的差异。

local p = {}

local getArgs = require('Module:Arguments').getArgs

---@param funcName string
---@return fun(args: table, frame: frame)
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('tr')
				: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))
			:allDone()

			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')
					hr[2] = mw.html.create('tr')
					hr[3] = mw.html.create('tr')
					hr[4] = mw.html.create('tr')
					if #data.days > 1 then hr[1]:node(ref)
					else hr[2]:node(ref)
					end
					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')
				hr[2] = mw.html.create('tr')
				hr[3] = mw.html.create('tr')
				if #data.days > 1 then hr[1]:node(ref)
				else hr[2]:node(ref)
				end
				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