For faster navigation, this Iframe is preloading the Wikiwand page for 模組:CNBUS.

模組:CNBUS

文档图示 模块文档[查看] [编辑] [历史] [清除缓存]

本模块是实现一系列交通相关模板功能的基础模块,支持管理各城市公共汽车系统的数据资料,目前主要用于生成一个显示公交线路资料的表格。

子模块一览

目前,各公交系统均使用独立的模板,并依赖本模块子页面的相关资料。下表为本模块已建立的子模块。您也可以仿照后文所述的格式新建子模块(并不限于中国大陆城市),并将其添加至下表。欢迎各位对已有资料进行定期维护更新。

相互引用情况

珠三角各系统
位置

Module:CNBUS/

被引用系统
肇庆 珠海 江门 中山 佛山 广州 东莞 惠州 深圳
系统 肇庆 不適用 ZQ/foshan
珠海 不適用 ZH/jiangmen ZH/data

ZH/zhongsha

江门 JM/zhuhai 不適用 JM/zhongshan JM/foshan
中山 ZS/zhuhai ZS/data 不適用 ZS/foshan
佛山 FS/zhaoqing FS/data

FS/jiangmen

FS/data

FS/zhongshan

不適用 FS/guangzhou
广州 GZ/foshan 不適用 GZ/dongguan
东莞 DG/guangzhou 不適用 DG/huizhou DG/shenzhen
惠州 HZ/dongguan 不適用 HZ/shenzhen
深圳 SZ/zhongshan SZ/dongguan SZ/huizhou 不適用
潮汕地区各系统
位置

Module:CNBUS/

被引用系统
汕头 潮州 揭阳
系统 汕头 不適用 ST/jieyang
潮州 CZ/shantou 不適用 CZ/jieyang
揭阳 JY/shantou 不適用

接口一览

直接调用接口的模板的参数表(传入)和其调用接口时显式指定的参数表(传出)都会被识别,两者优先级参见Module:Arguments。由本模块导出的模板可能还提供了其他别名。

接口参数列表
参数 说明 列表 折叠列表 运营商颜色 线路名称 别名与注释
(({1))} (({2))} 线路代码列表 需要 需要 不適用 单个
(({city))} 城市代码 需要 需要 需要 需要 各城市模板默认填写
(({area))} 区域代码 需要 需要 不適用 需要 部分模板提供(({loc))}别名
(({operator))} 运营商代码 不適用 不適用 需要 不適用 (({company))}
区别于(({operators))}
(({start))} 是否开始表格
输出<table>开标签及表头
可选 可选 不適用 不適用 默认为真
(({end))} 是否结束表格
输出</table>
可选 可选 不適用 不適用 默认为真
(({header))} 表格标题 可选 可选 不適用 不適用 (({info))} (({station))}
依赖于(({start))}
(({type))} 列表样式 可选 不適用 不適用 不適用 BRT:覆盖(({fare))}(({operators))}(({vehicles))}
(({time))} 是否显示时间 可选 不適用 不適用 不適用
(({fare))} 是否显示票价 可选 不適用 不適用 不適用 默认为真
(({operators))} 是否显示运营商 可选 不適用 不適用 不適用 默认为真
区别于(({operator))}
(({vehicles))} 是否显示车型 可选 不適用 不適用 不適用
(({image))} 是否显示图片 可选 不適用 不適用 不適用

list

通过输入一个或多个线路编号以生成包含这些线路资料的表格。目前支持起讫点、线路方向、营运公司(分公司)、票价、运营时间、车辆图片、线路配车、BRT站台信息、备注等信息。

((#invoke:CNBUS |list |city=#包含系统 ))

编号线路及运营时间收费运营商备注
1芳村花园南门
6:00–22:30
东山署前路
6:00–22:30
2元一汽一分芳村客运站

或者,线路也可以使用单个模式匹配表达式(必须以^开头)指定,用例参见重庆公交线路列表 (中心城区)。常用的代码匹配方式如:

  • ^T:所有T开头的线路;
  • ^%d%d%D*$:所有两位数的线路(允许非数字后缀,不允许更多数字);
  • ^1%d%d%D*$:1开头三位数的线路(允许非数字后缀,不允许更多数字)。

注意,输出的线路将按照线路代码以字符串排序,这意味着2会排在10的后面;此情形下建议配合(({start))}(({end))}将不同位数代码分拆多个表格显示。

collapsibleList

类似list,但只会生成一个的简化版的表格。目前仅支持起讫点和线路方向。

((#invoke:CNBUS |collapsibleList |city=#包含系统 ))

color

属于辅助功能,可输出代表线路运营商的颜色代号。list 已集成该功能。

lineName

展示简短行内链接。如((惠州巴士路线极简列表|1)):公交路线:1

数据格式

本模块约定将数据存储在子模块中。

城市总表模块

在将本模块引入公交系统前,首先需要建立一个子模块作为该系统的数据模块。请将该子模块命名为Module:CNBUS/<城市代码>,其基本框架为:

local xx = {
    areas = { },
    operators = { }
}

xx.areas['xx'] = {
    name = "<区域名>",
    page = "<线路列表条目名>",
    source = "Module:CNBUS/XX/data", -- 对应模块
    aliases = { "XX", "理塘", "default" } -- 
}

xx.operators['bus'] = {
    color = "red",
    aliases = { "Bus", "公交集团" }
}
xx.operators['transport'] = {
    color = "silver",
    aliases = { "交运集团" }
}

return xx

区域表

每个子模块可包含一个或多个子区域,可分别存放城区、郊区、外市路线的资料。在本模块设置各区域资料的例子如下:

gz.areas['guangzhou'] = {
	name = "广州",
	page = "广州巴士路线列表",
	source = "Module:CNBUS/GZ/data",
	aliases = { "Guangzhou", "GZ", "gz", "广州", "廣州", "default" }
}
gz.areas['nansha'] = {
	name = "南沙",
	page = "南沙巴士路线列表",
	source = "Module:CNBUS/GZ/nansha",
	aliases = { "Nansha", "NS", "ns", "南沙" }
}
gz.areas['foshan'] = {
	name = "佛山",
	page = "佛山巴士路线列表",
	source = "Module:CNBUS/GZ/foshan",
	aliases = { "Foshan", "FS", "fs", "佛山" }
}

其中,source的值为子区域各线路的详细资料;pagename用于设置list和collapsibleList的标题中指向列表条目的内部链接([[page|name]],如[[广州巴士路线列表|广州]]);aliases 则包含了该子区域的别名,由area参数调用。

本例中共有3个区域,分别为“guangzhou”、“nansha”和“foshan”。在未提供area参数的值,或area参数的值为“guangzhou”、“Guangzhou”、“GZ”、“gz”、“广州”、“廣州”时,则选择区域“guangzhou”。“nansha”和“foshan”同理。

运营商表

在本模块设置各运营商颜色的例子如下:

xx.operators['bus1'] = {
    color = "orange", -- 颜色
    aliases = { "一汽一分", "一汽二分" } -- 别名。键值本身(此处为bus1)不需要包含其中
}
xx.operators['bus3'] = {
    color = "#fff600",
    aliases = { "三汽一分", "三汽二分" }
}
xx.operators['other'] = {
    color = "white",
}
xx.operators['multi'] = {
    color = "black",
}

在本例中,一汽和三汽公司的代码分别为bus1bus3,则运营商为“一汽一分”和“一汽二分”的代表色为“orange”(橙色),“三汽一分”和“三汽二分”的代表色为“#fff600”(近似于黄色)。此外,还需要设置“other”和“multi”,分别代表模块中未列出的运营商(显示为白色)和多于一个运营商(显示为黑色)。

线路表模块

此后,便是为各个区域添加具体的线路(line)资料。请在该子模块下再新建一个二级子模块,并命名为“Module:CNBUS/<城市代码>/<区域代码>”,其基本框架如下:

local p = {
-- 常规线路
['1'] = { name = "线路名", mark = "线路名标注", fare = "票价", operators = "运营商", vehicles = { "配车1", "..." }, note = "备注", image = "[[File:示例.jpg|128px]]",
	{ { "左起讫点", time = "发车时间" }, { "方向箭头", mark = "方向标注" }, { "右起讫点", time = "发车时间" } }, --[[区间1]]
	{ { "左起讫点", time = "发车时间" }, { "方向箭头", mark = "方向标注" }, { "右起讫点", time = "发车时间" } }, --[[区间2]]
	{ --[[…]] }, --[[任意数量区间]] },
-- BRT线路
['B1'] = { name = "线路名", mark = "线路名标注", fare = "票价" --[[BRT样式下不可见]], operator = "运营商", note = "备注", image = "[[File:图片.jpg|x128px]]", brt = { { "驶入BRT通道车站", "经停BRT车站数", "驶出BRT通道车站" --[[右向]] }, { "驶出BRT通道车站", "经停BRT车站数", "驶入BRT通道车站" --[[左向车站定义位置相反]] } }
	{ { "左起讫点", time = "发车时间" }, { "→" --[[完整列表BRT样式下由brt字段自动确定;单向线路需要为其他情况填写]], mark = "方向标注" }, { "右起讫点", time = "发车时间" } }, },

-- 停办线路
['114'] = { name = "线路名", mark = "线路名标注", status = { -1, date = "日期" } },
-- 暂时停运线路
['514'] = { name = "线路名", mark = "线路名标注", status = { 0, date = "日期" } },
}

-- 导入其他模块的线路资料。请注意,引用的线路代码不能是重定向
p._external = {
	['Module:CNBUS/XX/data'] = {
    	['1'] = '1',
        ['114'] = { '514', override = { name = '114' } }, -- 支持覆写部分属性
    },
	['Module:CNBUS/YY/data'] = {
    	['2'] = '2',
    },
}

-- 定义线路编号重定向
p._map = {
	['01'] = '1',
	['BRT1'] = 'B1',
}

return p
  • 线路代码['line']区分简体/繁体和英文大写/小写的,因此在使用时不可混用,建议统一同城市下各子系统的简繁和大小写规则。其他参数内容不受限制。
  • 如需在名词中使用连字符,请使用“-”而非“-”或“-”等。为使用方便,连字暨减号-U+002D)在线路名称、时间、票价和备注中,与数字字母相邻时将被替换为半宽连接号U+2013),其余情形下将被替换为全宽连接号U+2014)。
  • name必填,<区间>[1][1]<区间>[2][1]<区间>[3][1]fareoperatornote参数建议填写(<区间>相关补全规则见#区间子表)。
  • operatorsvehicles既可以是字符串,也可以是数组(显示时分行)。数组表示的operators会直接被视为多运营商。
  • 若需要显示BRT信息,则右向<BRT>[1][1]<BRT>[1][2]<BRT>[1][3]和左向<BRT>[2][1]<BRT>[2][2]<BRT>[2][3]参数需要至少填入一组。右向为空时需要显式置nil方能填写左向数据;若实际存在但缺少相关资料,则应将对应方向置为空表{ }
  • 各参数值可以加入内部链接及换行符<br />,但来源引用<ref>和各类模板是无法使用的。

区间子表

为使用简便,一部分置空的值会使用邻近的值进行取代。补全的顺序遵循区间定义的顺序,区间内部则依次为左、右、方向。首个区间(route)和后继区间的回落规则有所不同。

对于每条线路的首个区间

  • 左起讫点保持原样(as-is),除表不存在(nil或未定义)时转化为空表;
  • 右起讫点若无效(表不存在;表首位的字符串不存在或长度为0),复制左起讫点的名称,附属属性保持原样;
  • 方向若无效(表不存在;表首位的字符串不存在或长度为0),左右起讫点(回落补全后)若相等则视作逆时针循环线,否则视为往返线,附属属性保持原样。
{ },
-- 等价于
{ { }, { "↺" }, { } },

{ { "火车站" }, { mark = "直" }, { "机场" } },
-- 等价于
{ { "火车站" }, { "⇆", mark = "直" }, { "机场" } },

{ { "火车站", time = "10:00" }, nil, { time = "20:00" } },
-- 等价于
{ { "火车站", time = "10:00" }, { "↺" }, { "火车站", time = "20:00" } },

对于后继区间

  • 左右起讫点若表不存在,则复制上一区间对应起讫点的所有属性;若表首位的字符串(名称)不存在或长度为0,则只复制上一区间对应起讫点的名称,附属属性保持原样;
  • 方向的行为与首个区间相同。
-- 假定补全后的上一区间
{ { "火车站", time = "10:00" }, { "⇆", mark = "快" }, { "机场", time = "20:00" } },

{ },
-- 等价于
{ { "火车站", time = "10:00" }, { "⇆" }, { "机场", time = "20:00" } },

{ nil, { mark = "直" }, { "" } },
-- 等价于
{ { "火车站", time = "10:00" }, { "⇆", mark = "直" }, { "机场" } },

用例

常见错误

在条目中使用引用了本模块的模板后,可能会提示以下错误:

错误提示 错误原因 解决方法
错误 Module:CNBUS不存在“XXX”的公交系统数据 Module:CNBUS/XXX不存在 检查模板中 city=<城市代码> 的<城市代码>是否填写错误
错误 “city”参数为空,请输入城市代码 模板未填入 city 的值 填写模板中 city=<城市代码> 的<城市代码>
错误 Module:CNBUS/XXX中未包含“yyy”的资料模块 Module:CNBUS/XXX/yyy不存在 检查模板中 loc=<子系统代码> 的<子系统代码>是否填写错误
错误 资料模块Module:CNBUS/XXX/yyy出现错误,请前往检查 Module:CNBUS/XXX/yyy出现错误 大多为资料模块导入其他模块时输入错误(本说明文档示例中的“导入其他模块的线路资料”部分),请仔细检查资料模块中该部分代码是否有误。如无法定位错误,可借助编辑框下方的“调试控制台”寻找出错行数。不知道怎么用?最简单的方法是输入 print(p) 后回车,如提示“Lua错误”,即可找到出错的变量及其位置。
无输入 请输入线路编号 使用模板时填入线路编号,或去除多余的“|”
295 数据模块Module:CNBUS/XXX/yyy出现错误 Module:CNBUS/XXX/yyy没有结尾 在相应模块添加return linereturn xxxbusline
308 模块Module:CNBUS/XXX/zzz引用的数据模块Module:CNBUS/XXX/yyy出现错误
1234 本线已于0202年1月1日停办,请移除 使用模板时删除该线路
5678 本线自0202年1月1日起暂停服务 使用模板时视情况删除或保留该线路
9012 本线并非BRT线路 线路缺少 brt_b 参数 如该线路并非BRT线路,使用模板时请勿选择 style=BRT 样式;如该线路的确为BRT线路,请补充完整该线路在BRT通道的行驶信息,否则使用模板时请勿选择 style=BRT 样式。


备注

本模块的相关功能以先前广州巴士路线列表的样式排版设计。模块前身为((廣州巴士路線));为优化页面加载速度和方便在不同系统间调用资料,模板于2019年中进行模块化改版(Module:GZBUS);为方便各模块的维护管理,2020年5月再将原先各模块合并于此。

如您在使用本模块时遇到问题或有任何建议,欢迎在模块讨论页中提出。

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
		return data:getOperator('multi').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
{{bottomLinkPreText}} {{bottomLinkText}}
模組:CNBUS
Listen to this article

This browser is not supported by Wikiwand :(
Wikiwand requires a browser with modern capabilities in order to provide you with the best reading experience.
Please download and use one of the following browsers:

This article was just edited, click to reload
This article has been deleted on Wikipedia (Why?)

Back to homepage

Please click Add in the dialog above
Please click Allow in the top-left corner,
then click Install Now in the dialog
Please click Open in the download dialog,
then click Install
Please click the "Downloads" icon in the Safari toolbar, open the first download in the list,
then click Install
{{::$root.activation.text}}

Install Wikiwand

Install on Chrome Install on Firefox
Don't forget to rate us

Tell your friends about Wikiwand!

Gmail Facebook Twitter Link

Enjoying Wikiwand?

Tell your friends and spread the love:
Share on Gmail Share on Facebook Share on Twitter Share on Buffer

Our magic isn't perfect

You can help our automatic cover photo selection by reporting an unsuitable photo.

This photo is visually disturbing This photo is not a good choice

Thank you for helping!


Your input will affect cover photo selection, along with input from other users.

X

Get ready for Wikiwand 2.0 🎉! the new version arrives on September 1st! Don't want to wait?