编辑“︁
Module:CNBUS
”︁
跳转到导航
跳转到搜索
警告:
您没有登录。如果您进行任何编辑,您的IP地址会公开展示。如果您
登录
或
创建账号
,您的编辑会以您的用户名署名,此外还有其他益处。
反垃圾检查。
不要
加入这个!
local p = {} local function _err_category(c) return string.format('[[Category:含有CNBUS错误的页面|%s]]', c or '') end ---@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] < 0) 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] or source_data[source_data._map[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(c) 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(c)) :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(c)) :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 c string ---@param line line ---@param f listFlags ---@param msg string ---@param isWarning boolean? ---@return html row local function _generateErrorRow(c, 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(c) 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 -- not nil ---@cast line line local routes = L.getParsedRoutes(line, not f.time) local color = f.bar and p._color(c, line.operators) or 'white' -- 懒调用运营商颜色接口 -- 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') if line.status and line.status[1] <= 0 then tr1:addClass('inactive') tr2:addClass('inactive') end 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') if line.status and line.status[1] <= 0 then output:addClass('inactive') end output :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 if line.status and line.status[1] <= 0 then tr:addClass('inactive') 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(c, 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 ---@cast err any return _generateErrorRow(c, { 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) if yesno(args.collapsible, false) then class = class .. ' mw-collapsible mw-collapsed' end 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, {} --[[ @as any ]]))) 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(c)) 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
摘要:
请注意,所有对Local Chinese Wikipedia的贡献均可能会被其他贡献者编辑、修改或删除。如果您不希望您的文字作品被随意编辑,请不要在此提交。
您同时也向我们承诺,您提交的内容为您自己所创作,或是复制自公共领域或类似自由来源(详情请见
Project:著作权
)。
未经许可,请勿提交受著作权保护的作品!
取消
编辑帮助
(在新窗口中打开)
导航菜单
个人工具
未登录
讨论
贡献
创建账号
登录
命名空间
模块
讨论
English
查看
阅读
编辑
查看历史
更多
搜索
导航
首页
最近更改
随机页面
MediaWiki帮助
工具
链入页面
相关更改
特殊页面
页面信息