模組: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 _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