模組:CNBUS/sandbox
这是Module:CNBUS(差异)的沙盒。 |
此模块使用Lua语言: |
本模块是实现一系列交通相关模板功能的基础模块,支持管理各城市公共汽车系统的数据资料,目前主要用于生成一个显示公交线路资料的表格。
子模块一览
目前,各公交系统均使用独立的模板,并依赖本模块子页面的相关资料。下表为本模块已建立的子模块。您也可以仿照后文所述的格式新建子模块(并不限于中国大陆城市),并将其添加至下表。欢迎各位对已有资料进行定期维护更新。
相互引用情况
位置
|
被引用系统 | |||||||||
---|---|---|---|---|---|---|---|---|---|---|
肇庆 | 珠海 | 江门 | 中山 | 佛山 | 广州 | 东莞 | 惠州 | 深圳 | ||
系统 | 肇庆 | 不適用 | ZQ/foshan | |||||||
珠海 | 不適用 | ZH/jiangmen | ZH/data | |||||||
江门 | JM/zhuhai | 不適用 | JM/zhongshan | JM/foshan | ||||||
中山 | ZS/zhuhai | ZS/data | 不適用 | ZS/foshan | ||||||
佛山 | FS/zhaoqing | FS/data | FS/data | 不適用 | FS/guangzhou | |||||
广州 | GZ/foshan | 不適用 | GZ/dongguan | |||||||
东莞 | DG/guangzhou | 不適用 | DG/huizhou | DG/shenzhen | ||||||
惠州 | HZ/dongguan | 不適用 | HZ/shenzhen | |||||||
深圳 | SZ/zhongshan | SZ/dongguan | SZ/huizhou | 不適用 |
位置
|
被引用系统 | |||
---|---|---|---|---|
汕头 | 潮州 | 揭阳 | ||
系统 | 汕头 | 不適用 | 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=#包含系统 }}
行经巴士路线一览 | ||||
---|---|---|---|---|
编号 | 路线 | 备注 | ||
番1 | 祈福新邨 | ⇆ | 傍雁路临时公交总站 |
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
的值为子区域各线路的详细资料;page
和name
用于设置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",
}
在本例中,一汽和三汽公司的代码分别为bus1
和bus3
,则运营商为“一汽一分”和“一汽二分”的代表色为“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]
、fare
、operator
和note
参数建议填写(<区间>
相关补全规则见#区间子表)。operators
和vehicles
既可以是字符串,也可以是数组(显示时分行)。数组表示的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 = "直" }, { "机场" } },
用例
- 时间、BRT资料及线路导入:Module:CNBUS/GZ/data
- 图片:Module:CNBUS/SZ/data
- 配车:Module:CNBUS/HZ/data
常见错误
在条目中使用引用了本模块的模板后,可能会提示以下错误:
错误提示 | 错误原因 | 解决方法 | ||||
---|---|---|---|---|---|---|
错误 | 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 line 或return 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月再将原先各模块合并于此。
遇线路调整需要修改模块数据时,请留意调整所根据的来源性质(不是要求将来源写入模块中,这样做反而可能导致代码兼容问题),依据近期WP:RSN共识,巴士档案站(buspedia.top)通常不可靠,请勿单纯根据该网站盲目修改。
如您在使用本模块时遇到问题或有任何建议,欢迎在模块讨论页中提出。
local p = {}
local obsolete = '[[Category:调用CNBUS废弃接口的页面]]'
---@overload fun(s: string): boolean?
---@overload fun(s: string, default: boolean): boolean
local yesno = require('Module:yesno')
---@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 线路定义
---旧版线路
---@class metalineLegacy
---@field code string
---@field endpoint1 string
---@field time1 string?
---@field direction string?
---@field endpoint2 string
---@field time2 string?
---@field direction1 string?
---@field endpoint3 string?
---@field time3 string?
---@field fare string
---@field company string
---@field image string?
---@field vehicle string?
---@field brt_a string?
---@field brt_a_in string?
---@field brt_a_out string?
---@field brt_b string?
---@field brt_b_in string?
---@field brt_b_out string?
---@field brt_info string?
---@field note string?
---@field suspend string?
local LL = {}
---转换旧版线路
---@return line
function LL:convert()
---@type line
local line = {
{
{ self.endpoint1, time = self.time1 },
{ self.direction },
{ self.endpoint2, time = self.time2 },
},
code = self.code,
fare = self.fare,
operator = self.company,
image = self.image,
vehicle = self.vehicle and mw.text.split(self.vehicle, '<br/>'),
note = self.note,
}
if self.endpoint3 then
table.insert(line, {
{ self.endpoint1, time = self.time1 },
{ self.direction1 },
{ self.endpoint3, time = self.time3 },
})
end
if self.brt_a or self.brt_b then
line.brt = { info = self.brt_info }
if self.brt_a then
line.brt[1] = {
self.brt_a,
i = self.brt_a_in,
o = self.brt_a_out,
}
end
if self.brt_b then
line.brt[2] = {
self.brt_b,
i = self.brt_b_in,
o = self.brt_b_out,
}
end
end
if line.note and mw.ustring.match(line.note, '已停[办辦]') then
line.status = { -1, date = self.suspend }
elseif line.note and line.note == '暂停服务' or line.note == '暂停服务' then
line.status = { 0, date = self.suspend }
end
return line
end
---@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, i: string, o: string }
---@class line: { [number]: route }
---@field code string
---@field mark string?
---@field fare string
---@field operator string
---@field image string?
---@field vehicle string[]?
---@field brt { [1]: brt?, [2]: brt?, info: string? }?
---@field note string?
---@field status { [1]: integer, date: string }?
local L = {}
---获取解析区间表
---@return pRoute[]
function L:getParsedRoutes()
---@type pRoute[]
local routes = {}
local last_route = { { }, nil, { } } -- 引用上一区间/交路
for r, route in ipairs(self) do
local el = isEmpty(route[1] and route[1][1]) and r > 1
local ed = isEmpty(route[2] and route[2][1])
local er = isEmpty(route[3] and route[3][1])
if el or ed or er then
---@type terminus
local left, right
-- 当终点站表整体悬空时,克隆所有属性
if not route[1] then
left = last_route[1]
-- 否则,只复制站名
else
left = {
nilEmpty(route[1][1]) or last_route[1][1],
time = route[1].time
}
end
-- 同上,但首行右终点将回落左终点
if not route[3] then
right = r == 1 and left or last_route[3]
else
right = {
nilEmpty(route[3][1]) or (r == 1 and left[1] or last_route[3][1]),
time = route[3].time
}
end
-- 隐式方向
local direction = { ed and ((left[1] == right[1]) and '↺' or '⇆') or route[2] }
table.insert(routes, { left, direction, right, note = self.note })
else
table.insert(routes, route)
end
end
if #routes > 1 then
for r = #routes, 2, -1 do
if nilEmpty(routes[r][1][1]) == nilEmpty(routes[r - 1][1][1]) and
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
end
if nilEmpty(routes[r][3][1]) == nilEmpty(routes[r - 1][3][1]) and
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
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)
local line = self.lines[l]
---@diagnostic disable-next-line: undefined-field
if line and (line.endpoint1 or line.note == '已停办' or line.note == '已停辦' or line.note == '暂停服务' or line.note == '暂停服务') then
---@diagnostic disable-next-line: param-type-mismatch
line = LL.convert(line)
end
local err = nil
if inline then
if isEmpty(l) then
err = '未输入线路'
elseif not line then
err = string.format('[[%s]]中无%s线路', self.source, line.code)
elseif line.status then
if line.status[1] == -1 then
if isEmpty(line.status.date) then
err = string.format('%s已停办', line.code)
else
err = string.format('%s已于%s停办', line.code, line.status.date)
end
elseif line.status[1] == 0 then
if isEmpty(line.status.date) then
err = string.format('%s已暂停服务', line.code)
else
err = string.format('%s自%s起暂停服务', line.code, line.status.date)
end
end
end
else
if isEmpty(l) then
err = string.format('请输入线路[[%s巴士路线列表|编号]]', self.name)
elseif not line then
err = string.format('[[%s]]中未包含这条[[%s巴士路线列表|%s]]的线路', self.source, self.name, self.name)
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
end
return line, err
end
--#endregion
--#region 城市定义
---@alias rmap { [string]: string[] }
---@alias qmap { [string]: string }
---@alias mCityLegacy { colors: qmap, company: rmap, data: qmap, titlename: qmap, listname: qmap, location: rmap }
---@alias mCity { areas: { [string]: mArea }, operators: { [string]: operator } }
---@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
-- 引用只读数据
if ro_data.data then
---@cast ro_data mCityLegacy
data.area_map = {}
data.areas = {}
data.operator_map = {}
data.operators = {}
for a, source in pairs(ro_data.data) do
data.areas[a] = { source = source }
end
for a, name in pairs(ro_data.titlename or {}) do
data.areas[a].name = name
end
for a, page in pairs(ro_data.listname or {}) do
data.areas[a].page = page
end
for a, aliases in pairs(ro_data.location or {}) do
for _, alias in ipairs(aliases) do
data.area_map[alias] = a
end
end
for o, color in pairs(ro_data.colors) do
data.operators[o] = { color = color }
end
for o, aliases in pairs(ro_data.company or {}) do
for _, alias in ipairs(aliases) do
data.operator_map[alias] = o
end
end
else
---@cast ro_data mCity
data.area_map = {}
data.operator_map = {}
for a, ro_area in ro_data.areas do
data.areas[a] = setmetatable({}, { __index = ro_area })
for _, alias in ipairs(ro_area.aliases) do
data.area_map[alias] = a
end
end
data.operators = setmetatable({}, { __index = ro_data.operators })
for o, operator in pairs(data.operators) do
for _, alias in ipairs(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 = ro_data
end
end
--#endregion
--#region 颜色模板
---@param city string
---@param operator string?
---@return string
function p._color(city, operator)
loadCityData(city)
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 data:getOperator('multi').color
else
return data:getOperator('other').color
end
end
---运营商颜色
---@param frame frame
---@return string
function p.color(frame)
local a = frame.args
return p._color(a.city, a.operator or a.company)
end
---@param city string
---@param operator string
---@deprecated
function p._colorBox(city, operator)
return string.format('width=1%% bgcolor=%s', p._color(city, operator))
end
---{{巴士公司色块}}
---@param frame frame
---@return string
---@deprecated
function p.colorbox(frame)
local a = frame.args
return p._colorBox(a.city, a.operator or a.company) -- .. obsolete
end
--#endregion
--#region 列表公共模板
local fdash = mw.ustring.char(0x2012)
---@param s string
---@return string
local function fixFigureDash(s)
s, _ = mw.ustring.gsub(s, '-', fdash)
return s
end
---@param color string
---@param rowspan integer?
---@return html?
local function makeBarCell(color, rowspan)
local td = mw.html.create('td')
:addClass('bar')
:css('background-color', color)
if rowspan and rowspan > 1 then
td :attr('rowspan', rowspan)
end
return td:allDone()
end
---@param line line
---@param rowspan integer?
---@return html?
local function makeCodeCell(line, rowspan)
local td = mw.html.create('td')
:addClass('code')
:wikitext(line.code)
if rowspan and rowspan > 1 then
td :attr('rowspan', rowspan)
end
if nilEmpty(line.mark) then
td:tag('br', { selfClosing = true }):done():tag('small'):wikitext(line.mark):done()
end
return td:allDone()
end
---@param route pRoute
---@param t 1|3 Left or right
---@param bTime boolean
---@param rowspan integer? Override
---@return html?
local function makeTerminusCell(route, t, bTime, rowspan)
local terminus = route[t]
local n_rows = rowspan or terminus.rowspan
if n_rows == 0 then
return nil
end
local td = mw.html.create('td')
td :addClass('terminus-' .. ((t == 1) and 'left' or 'right'))
:wikitext(terminus[1])
if n_rows and n_rows > 1 then
td :attr('rowspan', n_rows)
end
if bTime and nilEmpty(terminus.time) then
td :tag('br', { selfClosing = true }):done()
:tag('small'):wikitext(fixFigureDash(terminus.time)):done()
end
return td:allDone()
end
---@param route pRoute
---@param direction string? override
---@return html?
local function makeDirectionCell(route, direction)
local td = mw.html.create('td')
:addClass('direction')
if not direction and nilEmpty(route[2].mark) then
td:tag('small'):wikitext(route[2].mark):done():tag('br', { selfClosing = true }):done()
end
return td:wikitext(direction or route[2][1]):allDone()
end
---@param prop string
---@param rowspan integer?
---@param dash boolean?
local function makePropCell(prop, rowspan, dash)
local td = mw.html.create('td')
if dash then
td:wikitext(fixFigureDash(prop))
else
td:wikitext(prop)
end
if rowspan and rowspan > 1 then
td :attr('rowspan', rowspan)
end
return td:allDone()
end
---@param line line
---@param bImage boolean
---@param rowspan integer?
---@return html?
local function makeNoteCell(line, bImage, rowspan)
local td = mw.html.create('td')
if bImage then
local sep = (nilEmpty(line.note) or nilEmpty(line.image)) and '<br/>' or ''
td :addClass('note')
:wikitext(line.note, sep, line.image)
:done()
else
td :addClass('note')
:wikitext(line.note)
:done()
end
if rowspan and rowspan > 1 then
td :attr('rowspan', rowspan)
end
return td:allDone()
end
--#endregion
--#region 列表模板
---@alias listFlags { brt: boolean, time: boolean, fare: boolean, operator: boolean, vehicle: boolean, image: boolean }
---解析列表标志位
---@param typ string
---@param fTime string
---@param fVehicle string
---@param fImage string
---@return listFlags flags
local function parseListFlags(typ, fTime, fVehicle, fImage)
typ = mw.ustring.lower(typ)
if mw.ustring.find(typ, 'brt') then
return {
brt = true,
time = yesno(fTime, false),
fare = false,
operator = false,
vehicle = false,
image = yesno(fImage, false),
}
else
return {
brt = false,
time = yesno(fTime, false),
fare = not mw.ustring.match(typ, 'no%w*fa'),
operator = not mw.ustring.match(typ, 'no%w*co'),
vehicle = yesno(fVehicle, false),
image = yesno(fImage, false),
}
end
end
---获取行CSS类
---@param f listFlags
---@return string
local function getListRowClass(f)
if f.brt then
return 'cnbus-brt'
else
return 'cnbus-l' .. ((f.fare and 1 or 0) + (f.operator and 1 or 0) + (f.vehicle and 2 or 0))
end
end
---@param c string
---@param a string
---@param f listFlags
---@param class string
---@return html head
function p._listHead(c, a, f, class)
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)
:allDone()
else
local n_cols = 6 + (f.fare and 1 or 0) + (f.operator and 1 or 0) + (f.vehicle and 1 or 0)
return mw.html.create('tr')
:tag('th'):attr('colspan', n_cols):wikitext(err)
: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')
:addClass(class)
:tag('th'):attr('colspan', 2):wikitext(string.format('[[%s|编号]]',link_page)):done()
:tag('th'):attr('colspan', 3):wikitext(header_lines):done()
:tag('th'):wikitext('驶入BRT通道'):addClass('inout'):done()
:tag('th'):wikitext('驶出BRT通道'):addClass('inout'):done()
:tag('th'):wikitext('BRT通道停靠站数'):addClass('count'):done()
:tag('th'):wikitext('运营商'):addClass('operator'):done()
:tag('th'):wikitext(header_note):addClass('note'):done()
:allDone()
else
local tr = mw.html.create('tr'):addClass(class)
tr :tag('th'):attr('colspan', 2):wikitext(string.format('[[%s|编号]]', link_page)):done()
:tag('th'):attr('colspan', 3):wikitext(header_lines):done()
if f.fare then
tr:tag('th'):wikitext('收费'):addClass('fare'):done()
end
if f.operator then
tr:tag('th'):wikitext('运营商'):addClass('operator'):done()
end
if f.vehicle then
tr:tag('th'):wikitext('运力'):addClass('vehicle'):done()
end
tr:tag('th'):wikitext(header_note):addClass('note'):done()
return tr:allDone()
end
end
---表头模板
---@param frame frame
---@return html|string
---@deprecated
function p.title(frame)
local a = frame.args
local flags = parseListFlags(
a.type or a.format or '',
a.time,
a.vehicle,
a.image)
local result = p._listHead(
a.city,
a.area or a.loc,
flags,
getListRowClass(flags))
local output = mw.html.create('')
return output:node(result):wikitext(obsolete):newline():allDone()
end
---@param c string
---@param a string
---@param l string
---@param f listFlags
---@param class string
---@return html head
function p._listRow(c, a, l, f, class)
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)
if not err then
local color = p._color(c, line.operator)
-- BRT线路(广州、中山)
if f.brt then
if not line.brt then
err = '本线并非[[快速公交系统|BRT线路]]'
else
local route = line[1]
if (line.brt[1] and line.brt[2]) then
output
:tag('tr')
:addClass(class)
:addClass('line')
:node(makeBarCell(color, 2))
:node(makeCodeCell(line, 2))
:node(makeTerminusCell(route, 1, f.time, 2))
:node(makeDirectionCell(route, '→'))
:node(makeTerminusCell(route, 3, f.time, 2))
:node(makePropCell(line.brt[1].i))
:node(makePropCell(line.brt[1].o))
:node(makePropCell(line.brt[1][1]))
:node(makePropCell(line.operator, 2))
:node(makeNoteCell(line, f.time, 2))
:done()
:tag('tr')
:addClass('route')
:node(makeDirectionCell(route, '←'))
:node(makePropCell(line.brt[2].i))
:node(makePropCell(line.brt[2].o))
:node(makePropCell(line.brt[2][1]))
:done()
else
local info = line.brt[1] or line.brt[2] --[[@as brt]]
output
:tag('tr')
:addClass(class)
:addClass('line')
:node(makeBarCell(color, 1))
:node(makeCodeCell(line, 1))
:node(makeTerminusCell(route, 1, f.time, 1))
:node(makeDirectionCell(route))
:node(makeTerminusCell(route, 3, f.time, 1))
:node(makePropCell(info.i))
:node(makePropCell(info.o))
:node(makePropCell(tostring(info[1])))
:node(makePropCell(line.operator))
:node(makeNoteCell(line, f.image))
:done()
end
end
-- 常规线路
else
local routes = L.getParsedRoutes(line)
local tr
for r, route in ipairs(routes) do
tr = mw.html.create('tr')
if r == 1 then
tr :addClass(class)
:addClass('line')
:node(makeBarCell(color, #routes))
:node(makeCodeCell(line, #routes))
else
tr :addClass('route')
end
tr :node(makeTerminusCell(route, 1, f.time))
:node(makeDirectionCell(route))
:node(makeTerminusCell(route, 3, f.time))
if r == 1 then
if f.fare then
tr:node(makePropCell(line.fare, #routes, true))
end
if f.operator then
tr:node(makePropCell(line.operator, #routes))
end
if f.vehicle then
tr:node(makePropCell(table.concat(line.vehicle, '<br/>'), #routes))
end
tr:node(makeNoteCell(line, f.image, #routes))
end
output:node(tr:allDone())
end
end
end
if err then
local n_cols
if f.brt then
n_cols = 8
else
n_cols = 4 + (f.fare and 1 or 0) + (f.operator and 1 or 0) + (f.vehicle and 1 or 0)
end
output:tag('tr'):addClass('msg')
:tag('td'):addClass('bar'):done()
:tag('td'):addClass('code')
:wikitext((line and line.code) or l)
:done()
:tag('td'):addClass('msg'):attr('colspan', n_cols)
:wikitext(err)
:done()
end
end
return output:allDone()
end
---列表模板
---@param frame frame
---@return string
function p.list(frame)
local a = frame.args
if a.code then
---@deprecated
return obsolete
else
local open = yesno(a.open, true)
local close = yesno(a.close, true)
---@overload fun(frame: frame, options: table?): { [any]: string }
local getArgs = require('Module:Arguments').getArgs
a = getArgs(frame)
local flags = parseListFlags(
a.type or '',
a.time,
a.vehicle,
a.image)
local class = getListRowClass(flags)
local rows = {}
local prepend = ''
if open then
prepend = '<table class="wikitable sortable cnbus-normal">'
table.insert(
rows,
tostring(
p._listHead(
a.city,
a.area,
flags,
class)))
end
for _, l in ipairs(a) do
table.insert(
rows,
tostring(
p._listRow(
a.city,
a.area,
l,
flags,
class)))
end
local append = close and '</table>' or ''
return prepend .. table.concat(rows) .. append
end
end
--#endregion
--#region 折叠列表模板
---@param c string
---@param a string
---@param l string
---@return html head
function p._collapsibleListRow(c, a, l)
local success
local err
success, err = pcall(loadAreaData, c, a)
local output = mw.html.create()
if not success then
l = '错误'
end
if not err then
local area = data:getArea(a)
local line
line, err = A.getLine(area, l)
if err then
output:tag('tr')
:tag('td'):wikitext(l):done()
:tag('td'):attr('colspan', 4):wikitext(err):done()
:done()
else
local routes = L.getParsedRoutes(line)
for r, route in ipairs(routes) do
local tr = mw.html.create('tr')
if r == 1 then
tr:node(makeCodeCell(line, #routes))
end
tr :node(makeTerminusCell(route, 1, false))
:node(makeDirectionCell(route))
:node(makeTerminusCell(route, 3, false))
if r == 1 then
tr:node(makeNoteCell(line, false, #routes))
end
output:node(tr:allDone())
end
end
end
return output:allDone()
end
---折叠列表模板
---@param frame frame
---@return html|string
function p.collapsibleListRows(frame)
---@overload fun(frame: frame, options: table?): { [any]: string }
local getArgs = require('Module:Arguments').getArgs
local a = getArgs(frame)
local rows = {}
for _, l in ipairs(a) do
table.insert(rows, p._collapsibleListRow(a.city, a.area, l))
end
return table.concat(rows)
end
---简单列表单行模板(兼容中)
---@param frame frame
---@return html|string
---@deprecated
function p.simplelist(frame)
local c = frame.args.city
local a = frame.args.loc
local l = frame.args.code
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
---@type string
local dir, ep1, ep2
ep1 = line[1][1][1]
if isEmpty(line[1][3][1]) then
ep2 = ep1 -- 未填入endpoint2默认为循环线
else
ep2 = line[1][3][1]
end
if isEmpty(line[1][2][1]) then -- 部分线路省略了方向
if isEmpty(line[1][3][1]) then -- 未填入endpoint2默认为循环线
dir = "↺"
else
dir = "⇆"
end
elseif line[2] and nilEmpty(line[2][2][1]) then -- 部分线路的第二个方向
if nilEmpty(line[2][3][1]) then -- 部分线路去程终点和返程起点不同
dir = line[1][2][1]..'<hr>'..line[2][2][1]
ep2 = ep2..'<hr>'..line[2][3][1]
else
dir = line[1][2][1]..'<br>'..line[2][2][1]
end
else
dir = line[1][2][1]
end
local data_note = line.note or ''
l = line.code
return string.format(
[[|align=right|'''%s'''||align=right|%s||align=center nowrap=true|%s||%s||%s]],
l,
ep1,
dir,
ep2,
data_note) .. obsolete
end
end
return string.format(
[[|align=right|'''%s'''||colspan=3 align=center|%s]],
l,
err) .. obsolete
end
--#endregion
--#region 紧凑列表模板
--#endregion
--#region 线路编号模板(惠州)
---@param c string
---@param a string
---@param l string
---@return string
function p._code(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
return line.code or l
end
end
return string.format('(%s)', err)
end
---线路编号模板
---@param frame frame
---@return string
---@deprecated
function p.code(frame)
local a = frame.args
return p._code(
a.city,
a.area or a.loc,
a.code)
end
--#endregion
return p