跳转到内容

模組:CNBUS

本页使用了标题或全文手工转换
被永久保护的模块
维基百科,自由的百科全书

local p = {}

local _err_category = '[[Category:含有CNBUS错误的页面]]'

---@overload fun(frame: frame, options: table?): { [any]: string }
local getArgs = require('Module:Arguments').getArgs

---@overload fun(s: string): boolean?
---@overload fun(s: string?, default: boolean): boolean
local _yesno = require('Module:yesno')
local yesno = function(val, default)
	if default then
		-- 适应解析参数需要,覆写nil行为
		return val == nil or _yesno(val, true)
	else
		return _yesno(val, false)
	end
end

local tableTools = require('Module:TableTools')
---@overload fun(orig: table, noMetatable: boolean?, already_seen: table?): table
local deepCopy = tableTools.deepCopy
---@overload fun(array: table, sep: string?, i: integer?, j: integer?)
local sparseConcat = tableTools.sparseConcat

---@param var any
---@return boolean
local function _isEmpty(var)
	return not var or var == ''
end

---将空字符串转换为`nil`
---@param var string?
---@return string?
local function _nilEmpty(var)
	if var == '' then
		return nil
	else
		return var
	end
end

--#region 线路定义

---@alias terminus { [1]: string?, time: string? }
---@alias direction { [1]: string?, mark: string? }
---@alias route { [1]: terminus?, [2]: direction?, [3]: terminus? }
---@alias pTerminus { [1]: string?, time: string?, rowspan: integer? }
---@alias pRoute { [1]: pTerminus, [2]: direction, [3]: pTerminus }

---@alias brt { [1]: string, [2]: string, [3]: string }

---@class line: { [number]: route }
---@field name string
---@field mark string?
---@field fare string
---@field operators string|string[]
---@field image string?
---@field vehicles (string|string[])?
---@field brt { [1]: brt?, [2]: brt? }?
---@field note string?
---@field status { [1]: integer, date: string }?
local L = {}

---获取解析区间表
---@param nameOnly boolean 行合并时只判定站名(而不包括时间)
---@return pRoute[]
function L:getParsedRoutes(nameOnly)
	---@type pRoute[]
	local routes = {}
	---@type route
	local last_route = { {}, nil, {} } -- 引用上一区间
	for r, route in ipairs(self) do
		---@type terminus
		local left, right

		-- 当非首行终点站表整体置空时,克隆整个表
		if not route[1] then
			left = deepCopy(last_route[1], true)
			-- 否则,只复制站名
		else
			left = deepCopy(route[1], true)
			left[1] = _nilEmpty(left[1]) or last_route[1][1]
		end

		-- 同上,但首行右终点站名将回落左终点
		if not route[3] then
			if r == 1 then
				right = { left[1] }
			else
				right = deepCopy(last_route[3], true)
			end
		else
			right = deepCopy(route[3], true)
			if r == 1 then
				right[1] = _nilEmpty(right[1]) or left[1]
			else
				right[1] = _nilEmpty(right[1]) or last_route[3][1]
			end
		end

		-- 隐式方向
		local direction = deepCopy(route[2] or {}, true)
		direction[1] = _nilEmpty(direction[1]) or ((left[1] == right[1]) and '↺' or '⇆')

		last_route = { left, direction, right }
		table.insert(routes, last_route)
	end

	-- 行合并判定
	if #routes > 1 then
		for r = #routes, 2, -1 do
			if _nilEmpty(routes[r][1][1]) == _nilEmpty(routes[r - 1][1][1]) then
				if nameOnly or _nilEmpty(routes[r][1].time) == _nilEmpty(routes[r - 1][1].time) then
					routes[r - 1][1].rowspan = (routes[r][1].rowspan or 1) + 1
					routes[r][1].rowspan = 0
				elseif _nilEmpty(routes[r][1].time) then
					routes[r][1][1] = nil -- 仅有站名一致时(且显示时间时)置空站名
				end
			end
			if _nilEmpty(routes[r][3][1]) == _nilEmpty(routes[r - 1][3][1]) then
				if nameOnly or _nilEmpty(routes[r][3].time) == _nilEmpty(routes[r - 1][3].time) then
					routes[r - 1][3].rowspan = (routes[r][3].rowspan or 1) + 1
					routes[r][3].rowspan = 0
				elseif _nilEmpty(routes[r][3].time) then
					routes[r][3][1] = nil
				end
			end
		end
	end

	return routes
end

--#endregion

--#region 区域定义

---@alias mArea { name: string, page: string, source: string, aliases: string[] }

---@class area
---@field name string
---@field page string
---@field source string
---@field aliases string
---@field lines { [string]: line }
local A = {}

---获取线路 w/ err
---@param l string
---@param inline boolean?
---@return line?
---@return string?
function A:getLine(l, inline)
	---@type line?
	local line = self.lines[l] or (self.lines._map and self.lines[self.lines._map[l]])

	local err = nil
	local page = self.page or (self.name .. '巴士路线列表')
	if _isEmpty(l) then
		err = string.format('未输入线路[[%s|编号]]', page)
	elseif not line then
		err = string.format('[[%s]]中无此[[%s|%s]]线路', self.source, page, self.name)
	else
		local name = line.name

		---@diagnostic disable-next-line: undefined-field
		if line.code then -- 旧版线路
			err = '数据格式不受支持'
			line = nil
		elseif line.status then
			if line.status[1] == -1 then
				if _isEmpty(line.status.date) then
					err = '已停办'
				else
					err = string.format('已于%s停办', line.status.date)
				end
			elseif line.status[1] == 0 then
				if _isEmpty(line.status.date) then
					err = '暂停服务'
				else
					err = string.format('自%s起暂停服务', line.status.date)
				end
			end
		end

		if inline then
			err = name .. err
		end
	end

	return line, err
end

---获取匹配指定模式的线路
---@param pattern string
---@return string[]
function A:getLines(pattern)
	local codes = {}
	for l, line in pairs(self.lines) do
		if l ~= '_map' and mw.ustring.match(l, pattern) and not (line.status and line.status[1] == -1) then
			table.insert(codes, l)
		end
	end
	table.sort(codes)
	return codes
end

--#endregion

--#region 城市定义

---@alias operator { color: string, aliases: string[] }

---@class city
---@field areas { [string]: area }
---@field area_map { [string]: string }
---@field lines { [string]: { [string]: line } }
---@field operators { [string]: operator }
---@field operator_map { [string]: string }
local data = {}

---@param a string
---@return area
function data:getArea(a)
	return self.areas[a] or self.areas[self.area_map[a]] or self.areas['default']
end

---@param o string
---@return operator
function data:getOperator(o)
	return self.operators[o] or self.operators[self.operator_map[o]]
end

--#endregion

--#region 数据模块

---导入城市数据
---@param c string
local function _loadCityData(c)
	if not data.areas then
		if _isEmpty(c) then
			error(string.format('“city”参数为空,请输入城市代码'))
		end

		local success, ro_data = pcall(mw.loadData, 'Module:CNBUS/' .. c)
		if not success then
			error(string.format('[[Module:CNBUS]]不存在“%s”的公交系统数据', c))
		end

		-- 每个area下需要读写权限挂载线路表
		data.areas = {}
		data.area_map = {}
		for a, ro_area in pairs(ro_data.areas) do
			data.areas[a] = setmetatable({}, { __index = ro_area })
			if ro_area.aliases then
				for _, alias in ipairs(ro_area.aliases) do
					data.area_map[alias] = a
				end
			end
		end

		-- operators只需要只读访问
		data.operator_map = {}
		data.operators = setmetatable({}, { __index = ro_data.operators })
		for o, ro_operator in pairs(ro_data.operators) do
			if ro_operator.aliases then
				for _, alias in ipairs(ro_operator.aliases) do
					data.operator_map[alias] = o
				end
			end
		end
	end
end

---导入区域线路数据
---@param c string
---@param a string
local function _loadAreaData(c, a)
	_loadCityData(c)

	if _isEmpty(a) then
		error(string.format('“area”参数为空,请输入区域代码'))
	end

	local area = data:getArea(a)

	if not area then
		error(string.format('[[Module:CNBUS/%s]]中未包含“%s”的资料模块', c, a))
	end

	if not area.lines then
		local success, ro_data = pcall(mw.loadData, area.source)
		if not success then
			error(string.format('数据模块[[%s]]出现错误', area.source))
		end

		area.lines = {}
		for l, line in pairs(ro_data) do
			area.lines[l] = line
		end

		if ro_data._external then
			for source, map in pairs(ro_data._external) do
				local source_data
				success, source_data = pcall(mw.loadData, source)
				if not success then
					error(string.format('模块[[%s]]引用的数据模块[[%s]]出现错误', area.source, source))
				end

				for l, hint in pairs(map) do
					if type(hint) == 'table' then
						area.lines[l] = deepCopy(source_data[hint[1]], true)
						if hint.override then
							for _p, prop in pairs(hint.override) do
								area.lines[l][_p] = prop
							end
						end
					else
						area.lines[l] = source_data[hint]
					end
				end
			end
		end
	end
end

--#endregion

--#region 颜色模板

---@param c string
---@param operator (string|string[])?
---@return string
function p._color(c, operator)
	local success, err = pcall(_loadCityData, c)

	if not success then
		return err .. _err_category
	end

	if type(operator) == 'table' then
		local color = nil
		for _, op in ipairs(operator) do
			local info = data:getOperator(op or 'other')
			if color == nil then
				color = (info and info.color) or 'black'
			elseif color ~= ((info and info.color) or 'black') then
				return 'black'
			end
		end
		return color
	end

	local info = data:getOperator(operator or 'other')
	if info then
		return info.color
		-- 运营商名超过6字(UTF-8下18字节)视为联营
	elseif (operator and string.len(operator) > 18) or operator == 'multi' then
		return 'black' -- 原索引multi
	else
		return 'white' -- 原索引other
	end
end

---运营商颜色
---@param frame frame
---@return string
function p.color(frame)
	local args = frame.args
	return mw.text.nowiki(p._color(args.city, args.operator or args.company))
end

--#endregion

--#region 列表辅助模板

local enDash = mw.ustring.char(0x2013)
local enDashReplace = '%1' .. enDash .. '%2'
local emDash = mw.ustring.char(0x2014)
local emDashReplace = '%1' .. emDash .. '%2'

---@param s string?
---@return string?
local function _fixDash(s)
	if not s then
		return s
	end
	-- 两端皆为数字字母的将替换为 en dash
	s, _ = mw.ustring.gsub(s, '([a-zA-Z0-9])-([a-zA-Z0-9])', enDashReplace)
	-- 否则替换为 em dash
	s, _ = mw.ustring.gsub(s, '(%w)-(%w)', emDashReplace)
	return s
end

---@param color string
---@param numRows integer?
---@return html?
local function _createBarCell(color, numRows)
	local td = mw.html.create('td')
		:addClass('bar')
		:css('background-color', color)

	if numRows and numRows > 1 then
		td:attr('rowspan', numRows)
	end

	return td:allDone()
end

---@param line line
---@param numRows integer?
---@return html?
local function _createNameCell(line, numRows)
	local td = mw.html.create('td')
		:addClass('name')
		:wikitext(_fixDash(line.name))

	if numRows and numRows > 1 then
		td:attr('rowspan', numRows)
	end

	if _nilEmpty(line.mark) then
		td:tag('small'):wikitext(line.mark):done()
	end

	return td:allDone()
end

---@param route pRoute
---@param isLeft boolean
---@param showTime boolean
---@param numRows integer? Override rowspan
---@return html?
local function _createTerminusCell(route, isLeft, showTime, numRows)
	local terminus = isLeft and route[1] or route[3]
	local n_rows = numRows or terminus.rowspan

	if n_rows == 0 then
		return nil
	end

	local td = mw.html.create('td')
	td:addClass('terminus-' .. (isLeft and 'left' or 'right'))
		:wikitext(terminus[1])

	if n_rows and n_rows > 1 then
		td:attr('rowspan', n_rows)
	end

	if showTime and _nilEmpty(terminus.time) then
		if _nilEmpty(terminus[1]) then
			td:tag('br', { selfClosing = true }):done()
		end
		td:tag('small'):wikitext(_fixDash(terminus.time)):done()
	end

	return td:allDone()
end

---@param route pRoute
---@param direction string? Override
---@param mark string? Override
---@param biRows boolean?
---@return html?
local function _createDirectionCell(route, direction, mark, biRows)
	local td = mw.html.create('td')
		:addClass('direction')

	mark = _nilEmpty(mark) or _nilEmpty(route[2].mark)
	if mark then
		td:tag('small'):wikitext(mark):done():tag('br', { selfClosing = true }):done()
	end

	if biRows then
		td:attr('rowspan', 2)
	end

	return td:wikitext(_nilEmpty(direction) or route[2][1]):allDone()
end

---@param info brt
---@param isLeft boolean
---@param biRows boolean?
---@return html?
local function _createBrtCell(info, isLeft, biRows)
	local station = isLeft and info[1] or info[3]

	local td = mw.html.create('td')
	td:addClass('brt-' .. (isLeft and 'left' or 'right'))
		:wikitext(station)

	if biRows then
		td:attr('rowspan', 2)
	end

	return td:allDone()
end

---@param prop (string|string[])?
---@param numRows integer?
---@param fixDash boolean?
local function _createPropCell(prop, numRows, fixDash)
	local td = mw.html.create('td')

	if type(prop) == 'table' then
		prop = table.concat(deepCopy(prop, true), '<br/>') -- deepCopy for readonly tables
	else
		prop = prop or ''
	end

	if fixDash then
		td:wikitext(_fixDash(prop))
	else
		td:wikitext(prop)
	end

	if numRows and numRows > 1 then
		td:attr('rowspan', numRows)
	end

	return td:allDone()
end

---@param line line
---@param showImage boolean
---@param numRows integer?
---@return html?
local function _createNoteCell(line, showImage, numRows)
	local td = mw.html.create('td')
	local text

	if showImage then
		text = sparseConcat({ _fixDash(_nilEmpty(line.note)), _nilEmpty(line.image) }, '<br/>')
	else
		text = _fixDash(line.note)
	end
	td:addClass('note'):wikitext(text)

	if numRows and numRows > 1 then
		td:attr('rowspan', numRows)
	end

	return td:allDone()
end

--#endregion

--#region 列表模板

---@class listFlags
---@field bar boolean
---@field brt boolean
---@field time boolean
---@field fare boolean
---@field operators boolean
---@field vehicles boolean
---@field image boolean
local list_flags = {
	---@param typ string?
	---@param fTime string?
	---@param fFare string?
	---@param fOperators string?
	---@param fVehicles string?
	---@param fImage string?
	---@return listFlags flags
	parse = function(typ, fTime, fFare, fOperators, fVehicles, fImage)
		typ = mw.ustring.lower(typ or '')
		if mw.ustring.find(typ, 'brt') then
			return {
				bar       = true,
				brt       = true,
				time      = yesno(fTime or '', false),
				fare      = true,
				operators = true,
				vehicles  = false,
				image     = yesno(fImage or '', false),
			}
		else
			return {
				bar       = true,
				brt       = false,
				time      = yesno(fTime or '', false),
				fare      = yesno(fFare or '', true),
				operators = yesno(fOperators or '', true),
				vehicles  = yesno(fVehicles or '', false),
				image     = yesno(fImage or '', false),
			}
		end
	end
}

---获取表格CSS类
---@param f listFlags
---@return string
local function _getListClass(f)
	if f.brt then
		return 'cnbus-brt'
	else
		return 'cnbus-l' .. ((f.fare and 1 or 0) + (f.operators and 1 or 0) + (f.vehicles and 2 or 0))
	end
end

---@param c string
---@param a string
---@param f listFlags
---@return html head
function p._generateHead(c, a, f)
	local success, err = pcall(_loadAreaData, c, a)

	if not success then
		if f.brt then
			return mw.html.create('tr')
				:tag('th'):attr('colspan', 10):wikitext(err .. _err_category)
				:allDone()
		else
			local n_cols = 5 + (f.bar and 1 or 0) + (f.fare and 1 or 0) + (f.operators and 1 or 0) +
				(f.vehicles and 1 or 0)
			return mw.html.create('tr')
				:tag('th'):attr('colspan', n_cols):wikitext(err .. _err_category)
				:allDone()
		end
	end

	local area = data:getArea(a)
	local header_lines = (f.time and '线路及运营时间') or '线路'
	local header_note = (f.image and '备注及图片') or '备注'
	local link_page = area.page or string.format('%s巴士路线列表', area.name)

	if f.brt then
		return mw.html.create('tr')
			:tag('th'):attr('colspan', 2):wikitext(string.format('[[%s|编号]]', link_page)):done()
			:tag('th'):addClass('unsortable'):attr('colspan', 3):wikitext(header_lines):done()
			:tag('th'):addClass('unsortable'):attr('colspan', 3):wikitext('BRT通道内停站'):done()
			:tag('th'):addClass('operator'):wikitext('运营商'):done()
			:tag('th'):addClass('note'):wikitext(header_note):done()
			:allDone()
	else
		local tr = mw.html.create('tr')

		tr:tag('th'):attr('colspan', 2):wikitext(string.format('[[%s|编号]]', link_page)):done()
			:tag('th'):addClass('unsortable'):attr('colspan', 3):wikitext(header_lines):done()
		if f.fare then
			tr:tag('th'):addClass('fare'):wikitext('收费'):done()
		end
		if f.operators then
			tr:tag('th'):addClass('operator'):wikitext('运营商'):done()
		end
		if f.vehicles then
			tr:tag('th'):addClass('vehicle'):wikitext('配车'):done()
		end
		tr:tag('th'):addClass('note'):wikitext(header_note):done()

		return tr:allDone()
	end
end

---@param line line
---@param f listFlags
---@param msg string
---@param isWarning boolean?
---@return html row
local function _generateErrorRow(line, f, msg, isWarning)
	local n_cols
	if f.brt then
		n_cols = 8
	else
		n_cols = 4 + (f.fare and 1 or 0) + (f.operators and 1 or 0) + (f.vehicles and 1 or 0)
	end

	if not isWarning then
		msg = msg .. _err_category
	end

	local tr = mw.html.create('tr'):addClass('msg')
	if f.bar then
		tr:tag('td'):addClass('bar'):done()
	end
	tr:node(_createNameCell(line))
		:tag('td'):addClass('msg'):attr('colspan', n_cols)
		:wikitext(msg)
		:done()
	return tr
end

---生成单行内容
---@param c string
---@param a string
---@param l string
---@param f listFlags
---@return html row
function p._generateRow(c, a, l, f)
	local success
	local err
	success, err = pcall(_loadAreaData, c, a)

	local output = mw.html.create()

	if not success then
		l = '错误'
	else
		local area = data:getArea(a)

		local line
		line, err = A.getLine(area, l)
		local isWarning = line ~= nil -- 不追踪暂停/撤销线路

		if not err then
			local routes = L.getParsedRoutes(line, not f.time)
			local color = f.bar and p._color(c, line.operators) -- 懒调用运营商颜色接口

			-- BRT线路(广州、中山)
			if f.brt then
				if not line.brt then
					err = '本线并非[[快速公交系统|BRT线路]]'
				else
					local route = routes[1]

					if (line.brt[1] and line.brt[2]) then
						local tr1 = mw.html.create('tr'):addClass('line')
						local tr2 = mw.html.create('tr'):addClass('route')

						tr1:node(_createBarCell(color, 2))
							:node(_createNameCell(line, 2))
							:node(_createTerminusCell(route, true, f.time, 2))
							:node(_createDirectionCell(route, '→'))
							:node(_createTerminusCell(route, false, f.time, 2))
						tr2:node(_createDirectionCell(route, '←'))

						-- BRT通道左
						if line.brt[1][1] == line.brt[2][1] then
							tr1:node(_createBrtCell(line.brt[1], true, true))
						else
							tr1:node(_createBrtCell(line.brt[1], true))
							tr2:node(_createBrtCell(line.brt[2], true))
						end

						-- BRT通道方向
						if _nilEmpty(line.brt[1][2]) == _nilEmpty(line.brt[2][2]) then
							local s = _nilEmpty(line.brt[1][2]) and (line.brt[1][2] .. '站')
							tr1:node(_createDirectionCell(route, '⇄', s, true))
						else
							local s1 = _nilEmpty(line.brt[1][2]) and (line.brt[1][2] .. '站')
							local s2 = _nilEmpty(line.brt[2][2]) and (line.brt[2][2] .. '站')
							tr1:node(_createDirectionCell(route, '→', s1))
							tr2:node(_createDirectionCell(route, '←', s2))
						end

						-- BRT通道右
						if line.brt[1][3] == line.brt[2][3] then
							tr1:node(_createBrtCell(line.brt[1], false, true))
						else
							tr1:node(_createBrtCell(line.brt[1], false))
							tr2:node(_createBrtCell(line.brt[2], false))
						end

						tr1:node(_createPropCell(line.operators, 2))
							:node(_createNoteCell(line, f.time, 2))

						output:node(tr1):node(tr2)
					elseif line.brt[1] or line.brt[2] then
						local info = line.brt[1] or line.brt[2] --[[@as brt]]

						output
							:tag('tr')
							:addClass('line')
							:node(_createBarCell(color, 1))
							:node(_createNameCell(line, 1))
							:node(_createTerminusCell(route, true, f.time, 1))
							:node(_createDirectionCell(route))
							:node(_createTerminusCell(route, false, f.time, 1))
							:node(_createBrtCell(info, true))
							:node(_createDirectionCell(route, nil, _nilEmpty(info[2]) and (info[2] .. '站')))
							:node(_createBrtCell(info, false))
							:node(_createPropCell(line.operators))
							:node(_createNoteCell(line, f.image))
							:done()
					else
						err = '线路[[快速公交系统|BRT]]数据无效'
					end
				end
				-- 常规线路
			else
				local tr

				for r, route in ipairs(routes) do
					tr = mw.html.create('tr')

					if r == 1 then
						tr:addClass('line')
						if f.bar then
							tr:node(_createBarCell(color, #routes))
						end
						tr:node(_createNameCell(line, #routes))
					else
						tr:addClass('route')
					end

					tr:node(_createTerminusCell(route, true, f.time))
						:node(_createDirectionCell(route))
						:node(_createTerminusCell(route, false, f.time))

					if r == 1 then
						if f.fare then
							tr:node(_createPropCell(line.fare, #routes, true))
						end

						if f.operators then
							tr:node(_createPropCell(line.operators, #routes))
						end

						if f.vehicles then
							tr:node(_createPropCell(line.vehicles, #routes))
						end

						tr:node(_createNoteCell(line, f.image, #routes))
					end

					output:node(tr:allDone())
				end
			end
		end

		if err then
			return _generateErrorRow(line or { name = l }, f, err, isWarning)
		end
	end

	return output:allDone()
end

---生成多行内容支持
---@param c string
---@param a string
---@param l string
---@param f listFlags
---@return html rows
function p._generateRows(c, a, l, f)
	local success, err = pcall(_loadAreaData, c, a)

	local output = mw.html.create()

	if success then
		local area = data:getArea(a)

		for _, _l in ipairs(A.getLines(area, l)) do
			if not mw.ustring.match(_l, '^^') then
				output:node(p._generateRow(c, a, _l, f))
			end
		end
	else
		---@diagnostic disable-next-line: param-type-mismatch
		return _generateErrorRow({ name = '错误' }, f, err)
	end

	return output:allDone()
end

---列表模板
---@param frame frame
---@return string
function p.list(frame)
	local args = getArgs(frame)

	local flags = list_flags.parse(args.type, args.time, args.fare, args.operators, args.vehicles, args.image)
	local class = _getListClass(flags)

	local outputs = {}

	if yesno(args.start, true) then
		-- 表格开始
		table.insert(outputs, '{| class="wikitable sortable cnbus-normal ' .. class .. '"\n')

		-- 标题
		local caption = _nilEmpty(args.header) or _nilEmpty(args.info) or _nilEmpty(args.station)
		if caption then
			table.insert(outputs, '|+ ' .. caption .. '\n')
		end

		-- 表头
		table.insert(outputs,
			tostring(p._generateHead(args.city, args.area, flags)))
	end

	if mw.ustring.match(args[1] or '', '^^') then
		table.insert(outputs,
			tostring(p._generateRows(args.city, args.area, args[1], flags)))
	else
		for _, l in ipairs(args) do
			table.insert(outputs,
				tostring(p._generateRow(args.city, args.area, l, flags)))
		end
	end

	if yesno(args['end'], true) then table.insert(outputs, '</table>') end

	return table.concat(outputs)
end

---折叠列表模板
---@param frame frame
---@return string
function p.collapsibleList(frame)
	local args = getArgs(frame)

	local outputs = {}

	if yesno(args.start, true) then
		table.insert(outputs,
			[[
{| class="collapsible collapsed cnbus-collapsible"
! colspan=5 class="title" | ]] ..
			((_nilEmpty(args.header) or _nilEmpty(args.info) or _nilEmpty(args.station) or '行经巴士路线一览') .. [[

|-
! 编号 !! colspan=3 | 路线 !! 备注
]]))
	end

	for _, l in ipairs(args) do
		table.insert(outputs, tostring(p._generateRow(args.city, args.area, l, {})))
	end

	if yesno(args['end'], true) then table.insert(outputs, '</table>') end

	return table.concat(outputs)
end

--#endregion

--#region 线路名称模板(惠州)

---@param c string
---@param a string
---@param l string
---@return string
function p._lineName(c, a, l)
	local success
	local err
	success, err = pcall(_loadAreaData, c, a)

	if not success then
		l = '错误'
	end

	if not err then
		local area = data:getArea(a)
		local line
		line, err = A.getLine(area, l)

		if not err then
			---@diagnostic disable-next-line: need-check-nil
			return line.name or l
		end
	end

	return string.format('(%s)', err .. _err_category)
end

---线路名称模板
---@param frame frame
---@return string
function p.lineName(frame)
	local args = frame.args
	return p._lineName(
		args.city,
		args.area or args.loc,
		args[1] or args.code)
end

--#endregion

return p