打开主菜单
文档图示 模块文档[查看] [编辑] [历史] [清除缓存]

本模块为统一铁道系统特别是城市轨道交通系统中的数据而编写,支持管理线路链接、标志色、车站别名、首末站和首末班车时间等数据。拥有基本的 Lua 知识将会帮助你更好地编辑和使用此模块。

调用编辑

本模块支持调用下列函数来获取数据(或 MediaWiki 内容)。

编辑

调用名称 参数 说明
stationLink

{{#invoke:RailSystems | stationLink
| name=车站名
| system=铁道系统名
}}

通过车站名获取车站在铁道系统中的链接,用于处理条目名不规则或消歧义后的条目。

例如从“朝阳门”转换至“[[朝阳门站 (北京)|朝阳门]]”。

lineColor

{{#invoke:RailSystems | lineColor
| name=线路编号
| prefix=是否有“#”前缀
| system=铁道系统名
}}

通过线路编号获取线路颜色。若 prefix=1 则加上“#”前缀。
lineTitle

{{#invoke:RailSystems | lineTitle
| name=线路编号
| link=0/1
| system=铁道系统名
}}

通过线路编号获取线路条目链接。若 link=0 则只返回条目名,否则返回条目链接 Wiki Text,默认 link=1。
lineRichTitle

{{#invoke:RailSystems | lineRichTitle
| name=线路编号
| degrade=无标志色时,是否自动降级为 lineTitle 结果
| system=铁道系统名
}}

通过线路编号获取带有标志色装饰(默认为前置彩色小方块)的线路条目链接。若 degrade 为 1,则在标志色未定义时自动返回 lineTitle 的结果。
lineTerminal

{{#invoke:RailSystems | lineTerminal
| name=线路编号
| side=左侧(填left)或右侧(填right)
| type=终点站类型,可为空
| type=终点站支线,可为空
| system=铁道系统名
}}

通过线路编号、方向、类型(可选)及支线(可选)获得线路终点站链接。
lineTerminalName

{{#invoke:RailSystems | lineTerminalName
| name=线路编号
| side=左侧(填left)或右侧(填right)
| type=终点站类型,可为空
| branch=终点站支线,可为空
| system=铁道系统名
}}

通过线路编号、方向、类型(可选)及支线(可选)获得线路终点站站名。
trainTime

{{#invoke:RailSystems | trainTime
| name=线路编号
| dir=运营方向
| type=首班(填first或F)或末班(填last或L)
| delta=起点站发车到本站到站的时间
| system=铁道系统名
}}

通过线路编号、运营方向(预定义)、首/末班及始发站到达本站的时间获得车站首班/末班车时间。
trainDirectionTime

{{#invoke:RailSystems | trainDirectionTime
| name=线路编号
| dir=运营方向
| type=首班(填first或F)或末班(填last或L)
| delta=起点站发车到本站到站的时间
| system=铁道系统名
}}

通过线路编号、运营方向(预定义)、首/末班及始发站到达本站的时间获得车站首班/末班车终点站名链接及时间组成的 WikiText。
lineDateMessage

{{#invoke:RailSystems | lineDateMessage
| 日期/时间格式串,可用多个管道符号分隔,
| name=线路编号
| type=线路期数,可为空
| auto_hide=0(超过给定日期不隐藏)/1(超过给定日期自动隐藏),默认为1
| auto_defer=0(比较时按给定日期比较)/1(比较时按给定日期延后一个时间单位比较),默认为0
}}

通过线路编号和期数,显示距离开通需要的时间。本函数支持自定义日期格式,year、month、day分别表示年、月、日(含单位),ym、date分别表示年月组合、日期,可通过管道符号“|”和其他自定义字符相连。如果 auto_hide 未指定或值为“1”,当该日期已过时,文字将自动隐藏,条目会被加入Category:需要去除时间判断模板的页面

使用建议编辑

不建议直接在条目空间中使用 #invoke 语法调用本模块,而是为每个系统建立自己的系列模板,在模板中引用本模块。例如,北京地铁系列条目使用 {{BJS color}} 调用本模块的 lineColor 方法。

一部分其他模板也集成了本模板,包括参数 lua=1 条件下的 {{铁道路线}} 模板(参见 Module:RouteSequence)以及 {{S-line}} 中的一部分功能。{{RenderStations}} 利用了模块中的 renderStationLinks 方法,可以方便地对一段 WikiText 中的站名进行转换。

子模块列表编辑

下面为已有的子模块列表,支持不同的铁道系统。注意,以“/doc”为后缀的是文档,而“UseCase”是测试用例

建立系统模板编辑

将本模块引入铁道系统前,你需要首先创建一个系统数据模块。该模块为本模块的子模块,名称为 Module:RailSystems/<系统名> ,基本框架为

local p = {}

p.lines = {
	-- 线路列表
}

-- 开通日期,用于为未开通线路标记日期,可有多行,非必需
p.lines['线路名'].openDates = { ['#default'] = '默认开通日期', ['2'] = '二期开通日期' }
p.lines['线路名2'].openDates = '单一开通日期'

local lineAliases = {
	-- 线路别名(用于简繁转换)
}
-- 用于处理别名的代码,勿删
for k, v in pairs(lineAliases) do p.lines[k] = p.lines[v] end
-- 起讫站信息必需代码,勿删
for k, v in pairs(p.lines) do v.terminals = {} end
-- 首末班车信息必需代码,勿删
for k, v in pairs(p.lines) do v.trainTime = {} end

p.stationNames = {
	-- 站名链接
}

local stationAliases = {
	-- 车站别名
}
-- 用于处理别名的代码,勿删
for k, v in pairs(stationAliases) do p.stationNames[k] = p.stationNames[v] end

-- 起讫站
p.lines['1'].terminals = { left = 'XXX', right = 'XXX' }
p.lines['2'].terminals = {
	left = { ['#field'] = 'type', ['#default'] = 'XXX', F = 'XXX'},
	right = { 'XXX' },
}

-- 首末班车
p.lines['1'].trainTime = {
	['SE'] = { first = '06:00', last = '23:00', startService = 'XXX', endService = 'XXX' },
	['ES'] = { first = '06:00', last = '23:00', startService = 'XXX', endService = 'XXX' },
}

return p

线路列表编辑

本模块线路列表的例子如下

p.lines = {
	['1'] = { title = "宁波轨道交通1号线|1号线", color = '3180b7' },
	['2'] = { title = "宁波轨道交通2号线|2号线", color = 'cc0000' },
}

其中,title 表示线路的条目名称及显示名称,使用“|”分隔。color 表示线路的标志色。

线路开通日期编辑

本模块线路开通日期的例子如下。如果系统中无此需求可省略这部分。

p.lines['1'].openDates = { ['#default'] = '2014-05-30', ['2'] = '2016-03-19' }
p.lines['2'].openDates = '2020-12-28'

对于不分期线路,可采用“yyyy-mm-dd”字符串。对于分期线路,可以使用一个 Lua Table 指定各个分期的开通日期。

站名链接编辑

本模块站名链接的例子如下

p.lines = {
	['朝阳门'] = '朝阳门站 (北京)|朝阳门',
	['北京南站'] = '北京南站|北京南站',
	['大兴线天宫院'] = {'天宫院站|天宫院', '([[北京地铁大兴线|大兴线]])'},
}

第一例中,站名“朝阳门”对应了链接 [[朝阳门站 (北京)|朝阳门]],使得调用 {{#invoke:RailSystems|stationLink|name=朝阳门|system=BJS}} 时能够返回重定向后正确的连接(而非 [[朝阳门站|朝阳门]])。

第二例中,站名“北京南站”对应的条目名为自身,因而 stationLink 返回“[[北京南站|北京南站]]”而非“[[北京南站站|北京南站]]”。需要注意的是,stationLink 根据字符串中是否存在“[[”外的“|”符号判断是否需要将字符串转换为链接。因而,单写“['北京南站'] = '北京南站',”只会返回“北京南站”的文字。

第三例中,站名“大兴线天宫院”是为了在北京地铁4号线车站条目中显示终点站为“[[天宫院站|天宫院]]([[北京地铁大兴线|大兴线]])”。这里使用了 Lua table 取代字符串。模块会自动扫描 table,将其中属于链接的部分组装为链接,并将所有部分拼装在一起。

起讫站编辑

本模块起讫站的例子如下

p.lines['1'].terminals = { 
	left = '湘湖',
	right = { ['#field'] = 'branch',
		['#default'] = { ['#field'] = 'type', ['#default'] = '临平、文泽路', future = '临平、下沙江滨' },
		['临平'] = '临平',
		['下沙'] = { ['#field'] = 'type', ['#default'] = '文泽路', future = '下沙江滨' },
	},
}
p.lines['2'].terminals = { 
	left = '朝阳',
	right = { ['#field'] = 'type', ['#default'] = '钱江路', ['future'] = '良渚'},
}

起讫站名中,terminals 中的 left 属性表示起点站,right 属性表示终点站。两个属性的值均为迭代定义。若值为字符串,则返回站名。否则根据 '#field' 的值,判定输入参数是否符合当前 table 的某一个属性名。如果符合,取出值。否则取出 '#default' 属性的值。最后,对该值重复上述处理,直至返回结果为字符串为止。

首末班车编辑

本模块起讫站的例子如下

p.lines['1'].trainTime = {
	-- 东环南路首班车
	['东高'] = { first = '06:00', startService = '东环南路', endService = '高桥西' },
	['东霞'] = { first = '06:00', startService = '东环南路', endService = '霞浦' },
	-- 起讫站首末班车
	['霞高'] = { first = '06:00', last = '22:00', startService = '霞浦', endService = '高桥西' },
	['高霞'] = { first = '06:00', last = '22:00', startService = '高桥西', endService = '霞浦' },
}

其中,trainTime 的每个属性值表示线路上的一种服务,名称可自定。此后 first 和 last 分别表示该服务首末班车的时间,而 startService 和 endService 表示服务的始发/终点站。

增加新功能编辑

编者可为本模块增加新功能。但需要注意,新增的功能不应影响原有功能。为了便于维护和复用,请在完成功能编写后更新用例和帮助文档。本模块的用例位于 p.testCase,请在提交前先执行用例,确保所有用例均通过再行提交。

可用性编辑

如果下方显示错误信息,则相应功能不可用,请联系最近一次修改者修复该错误。查看编辑历史

-------------------------------------------------------------------
-- 重要:在保存编辑前,请务必跑完所有测试用例并保证没有错误输出。
-- 测试方法为:在调试控制台输入
--
--   print(p.testCase())
--
-- 如果输出为“所有用例均通过!”,说明可以保存编辑,否则请消除错误。
-------------------------------------------------------------------


local p = {
	DEFAULT_LINE_COLOR = 'cccccc',
	DEFAULT_STATION_SUFFIX = '站',
	DEFAULT_PLAIN_LINK_TEMPLATE = '[[{line_link}|{line_title}]]',
	DEFAULT_RICH_LINK_TEMPLATE = '&#32;<span style="color:#{line_color}"><big>■</big></span>&#32;[[{line_link}|{line_title}]]',
	DEFAULT_TRAIN_TIME_TEMPLATE = '往{station_link}方向:{train_time|H:i|次日}',
	DEFAULT_DATE_ELAPSED_TEXT = '[[Category:需要去除时间判断模板的页面]]',
	DEFAULT_LINE_DATE_ABSENT_TEXT = '规划中',
}

local function plain_replace(src, substr, repl)
	function literalize(str)
	    return str:gsub("[%(%)%.%%%+%-%*%?%[%]%^%$]", function(c) return "%" .. c end)
	end
	substr = literalize(substr)
	local rstr, _ = src:gsub(substr, repl)
	return rstr
end

local function _loadSystemData(system, raises)
	local system_data = nil
	local state
	if raises == nil then
		raises = true
	end
	if system ~= nil then
		state, system_data = pcall(mw.loadData, "Module:RailSystems/" .. system)
		if not state then
			if raises then
				error(string.format('铁道系统“%s”的数据模块不存在', system))
			else
				system_data = nil
			end
		end
	end
	return system_data
end

local function _safeExpandTemplate(frame, title, args)
	function _caller(frame, t, a)
		return frame:expandTemplate{ ['title'] = t, ['args'] = a }
	end
	
	local state, result = pcall(_caller, frame, title, args)
	if not state then
		return string.format('[[:Template:%s]]', title)
	else
		return result
	end
end

local function _convBool(val, default)
	if val == nil then
		return default
	elseif type(val) == 'boolean' then
		return val
	elseif mw.text.trim(val) == '' then
		return default
	end
	return (mw.text.trim(val) == '1')
end

function p._internalStationLink(name, style, short, system_data, frame)
	local station_parts, station_data
	local station_link, station_name
	local repr = ''
	if short == nil then short = true end
	
	function p.marshalStation(station_link, station_name, style)
		if not short then
			local link_pos, _ = string.find(station_link, ' %(')
			if link_pos ~= nil then
				station_name = string.sub(station_link, 1, link_pos - 1)
			else
				station_name = station_link
			end
		end
		if style == nil or mw.text.trim(style) == '' then
			return '[[' .. station_link .. '|' .. station_name .. ']]'
		else
			return '[[' .. station_link .. '|<span style="' .. style .. '">' .. station_name .. '</span>]]'
		end
	end

	if type(system_data) == 'string' then
		system_data = _loadSystemData(system_data, false)
	end
	if system_data == nil then
		return nil
	end

	if system_data.stationNames[name] ~= nil then
		station_data = system_data.stationNames[name]
		if type(station_data) ~= 'table' then
			station_data = {station_data, }
		end
		for i, datum in ipairs(station_data) do
			if string.find(datum, '|', 1, true) == nil or string.find(datum, '[[', 1, true) ~= nil then
				repr = repr .. datum
			else
				station_parts = mw.text.split(datum, '|')
				station_link = station_parts[1]
				station_name = table.concat(station_parts, '|', 2)
				if frame ~= nil and string.find(station_name, '{', 1, true) ~= nil then
					station_name = frame:preprocess(table.concat(station_parts, '|', 2))
				end
				repr = repr .. p.marshalStation(station_link, station_name, style)
			end
		end
		return repr
	else
		station_link = name .. p.DEFAULT_STATION_SUFFIX
		return p.marshalStation(station_link, name, style)
	end
end

function p._internalLineColor(code, prefix, system)
	local color_prefix = ''
	local system_data = _loadSystemData(system, false)
	if system_data == nil then
		return nil
	end
	
	if prefix then
		color_prefix = '#'
	end
	if system_data.lines[code] == nil then
		return color_prefix .. p.DEFAULT_LINE_COLOR
	elseif system_data.lines[code].color == nil then
		return color_prefix .. p.DEFAULT_LINE_COLOR
	else
		return color_prefix .. system_data.lines[code].color
	end
end

function p._internalLineTitle(line_code, link, system)
	local system_data = _loadSystemData(system, false)
	local tmpl = p.DEFAULT_PLAIN_LINK_TEMPLATE
	local raw_title, link_parts, link_str, _match
	if link == nil then
		link = true
	end
	if system_data == nil then
		return nil
	end
	raw_title = system_data.lines[line_code].title
	link_parts = mw.text.split(raw_title, '|')
	
	if string.find(raw_title, '[[', 1, true) ~= nil then
		return raw_title
	end
	
	if system_data.plain_link_template ~= nil then
		tmpl = system_data.plain_link_template
	end
	if link then
		if table.getn(link_parts) == 1 then
			link_str, _match = string.gsub(string.gsub(tmpl, '\{line_link\}', link_parts[1]), '\{line_title\}', link_parts[1])
		else
			link_str, _match = string.gsub(string.gsub(tmpl, '\{line_link\}', link_parts[1]), '\{line_title\}', link_parts[2])
		end
	else
		link_str = link_parts[1]
	end
	return link_str
end

function p._internalLineRichTitle(line_code, auto_degrade, system)
	local system_data = _loadSystemData(system)
	local raw_title = system_data.lines[line_code].title
	local line_color = system_data.lines[line_code].color
	local link_parts = mw.text.split(raw_title, '|')
	local tmpl = p.DEFAULT_RICH_LINK_TEMPLATE
	local link, _match
	
	if string.find(raw_title, '[[', 1, true) ~= nil then
		return raw_title
	end
	if system_data.rich_link_template ~= nil then
		tmpl = system_data.rich_link_template
	end
	
	if line_color == nil then
		if auto_degrade then
			return p._internalLineTitle(line_code, true, system)
		else
			line_color = p.DEFAULT_LINE_COLOR
		end
	end
	
	link, _match = string.gsub(tmpl, '\{line_link\}', link_parts[1])
	link, _match = string.gsub(link, '\{line_title\}', link_parts[2])
	link, _match = string.gsub(link, '\{line_color\}', line_color)
	return link
end

function p._internalLineTerminal(line_code, side, type_, branch, system)
	local system_data = _loadSystemData(system, false)
	local side_data, conds
	
	function get_condition(cond, data)
		local c
		if type(data) == 'table' then
			c = cond[data['#field']]
			if data[c] == nil then
				return get_condition(cond, data['#default'])
			else
				return get_condition(cond, data[c])
			end
		else
			return data
		end
	end
	
	if system_data ~= nil and system_data.lines[line_code] ~= nil and system_data.lines[line_code].terminals ~= nil then
		side_data = system_data.lines[line_code].terminals[side]
		conds = { ['type'] = type_, ['branch'] = branch }
		return get_condition(conds, side_data)
	else
		return nil
	end
end

function p._internalTrainTime(line_code, dir, type_, delta, system)
	function splitTime(time_str)
		local base_parts = mw.text.split(time_str, ':')
		local parts_size = table.getn(base_parts)
		local hour = 0
		local minute = 0
		local sec = 0
		if parts_size == 1 then
			minute = tonumber(mw.text.trim(base_parts[1]))
		elseif parts_size == 2 then
			hour = tonumber(mw.text.trim(base_parts[1]))
			minute = tonumber(mw.text.trim(base_parts[2]))
		elseif parts_size == 3 then
			hour = tonumber(mw.text.trim(base_parts[1]))
			minute = tonumber(mw.text.trim(base_parts[2]))
			sec = tonumber(mw.text.trim(base_parts[3]))
		else
			error('非法的时间输入')
		end
		return hour, minute, sec
	end
	
	local system_data = _loadSystemData(system)
	local dir_info = system_data.lines[line_code].trainTime[dir]
	local bhour, bmin, bsec = splitTime(dir_info[type_])
	local dhour, dmin, dsec = splitTime(delta)
	local totalsec = (bhour * 60 + bmin) * 60 + bsec + (dhour * 60 + dmin) * 60 + dsec
	local hour = math.floor(totalsec / 3600)
	local minute = math.floor((totalsec - hour * 3600) / 60)
	local sec = totalsec - hour * 3600 - minute * 60
	return dir_info.endService, hour, minute, sec
end

function p._internalLineDateMessage(line_code, type_, reprs, options, system)
	local system_data = _loadSystemData(system)
	local frame = mw.getCurrentFrame()
	local lowest_level = 0 -- 0: everything is ok, 1: year, 2: month, 3: day
	local cur_time = frame:callParserFunction{ name = '#time', args = { 'U' } }
	local open_date, year, month, day, new_year, new_month
	local ret_str = ''
	
	for i, repr in ipairs(reprs) do
		repr = mw.text.trim(repr)
		if lowest_level < 1 and (repr == 'year') then
			lowest_level = 1
		elseif lowest_level < 2 and (repr == 'month' or repr == 'ym') then
			lowest_level = 2
		elseif  lowest_level < 3 and (repr == 'day' or repr == 'date' or repr == 'ymd') then
			lowest_level = 3
		end
	end
	
	if options == nil then options = {} end
	options.auto_hide = _convBool(options.auto_hide, false)
	options.auto_defer = _convBool(options.auto_defer, true)
	if options.cur_time ~= nil then -- for testing purpose
		cur_time = frame:callParserFunction{ name = '#time', args = { 'U', options.cur_time } }
	end
	
	if system_data.lines[line_code] == nil then return nil end
	
	open_date = system_data.lines[line_code].openDates
	if open_date == nil then open_date = {} end
	if type(open_date) == 'table' then
		if open_date[type_] ~= nil then
			open_date = open_date[type_]
		elseif open_date['#default'] ~= nil then
			open_date = open_date['#default']
		elseif lowest_level == 0 then
			for i, repr in ipairs(reprs) do ret_str = ret_str .. repr end
			return ret_str
		else
			return p.DEFAULT_LINE_DATE_ABSENT_TEXT
		end
	end

	year = tonumber(frame:callParserFunction{ name = '#time', args = { 'Y', open_date } })
	month = tonumber(frame:callParserFunction{ name = '#time', args = { 'm', open_date } })
	day = tonumber(frame:callParserFunction{ name = '#time', args = { 'd', open_date } })
	if options.auto_defer then
		if lowest_level == 1 then
			open_date = string.format('%04d-01-01', year + 1)
		elseif lowest_level == 2 then
			new_year, new_month = year, month
			if month == 12 then
				new_year = new_year + 1
				new_month = 1
			else
				new_month = new_month + 1
			end
			open_date = string.format('%04d-%02d-01', new_year, new_month)
		end
	end
	open_date = frame:callParserFunction{ name = '#time', args = { 'U', open_date } }
	
	if options.auto_hide and cur_time >= open_date then
		return p.DEFAULT_DATE_ELAPSED_TEXT
	end
	for i, repr in ipairs(reprs) do
		if repr == 'year' then
			ret_str = ret_str .. year .. '年'
		elseif repr == 'month' then
			ret_str = ret_str .. month .. '月'
		elseif repr == 'day' then
			ret_str = ret_str .. day .. '日'
		elseif repr == 'ym' then
			ret_str = ret_str .. string.format('%s年%s月', year, month)
		elseif repr == 'date' or repr == 'ymd' then
			ret_str = ret_str .. string.format('%s年%s月%s日', year, month, day)
		else
			ret_str = ret_str .. repr
		end
	end
	return ret_str
end

function p.stationLink(frame)
	local a = frame.args
	local short = _convBool(a.short, true)
	local system_data = _loadSystemData(a.system, false)
	local ret = p._internalStationLink(a.name, a.style, short, system_data, frame)
	if ret == nil then
		return _safeExpandTemplate(frame, string.format('%s stations', a.system), {line=a.link, branch=a.branch, station=a.name, state=a.state})
	else
		return ret
	end
end

function p.lineColor(frame)
	local a = frame.args
	a.prefix = _convBool(a.prefix, false)
	
	local ret = p._internalLineColor(a.name, a.prefix, a.system)
	if ret == nil then
		ret = _safeExpandTemplate(frame, string.format('%s color', a.system), {a.name})
		if a.prefix then ret = '#' .. ret end
		return ret
	else
		return ret
	end
end

function p.lineTitle(frame)
	local a = frame.args
	local link = _convBool(a.link, true)
	local rev = p._internalLineTitle(a.name, link, a.system)
	if rev == nil then
		return _safeExpandTemplate(frame, string.format('%s lines', a.system), {a.name})
	else
		return rev
	end
end

function p.lineRichTitle(frame)
	local a = frame.args
	local degrade = _convBool(a.degrade, true)
	return p._internalLineRichTitle(a.name, degrade, a.system)
end

function p.lineTerminal(frame)
	local a = frame.args
	local term = p._internalLineTerminal(a.name, a.side, a['type'], a.branch, a.system)
	if term == nil then
		term = _safeExpandTemplate(frame, string.format('S-line/%s %s/%s', a.system, a.side, a.name), { ['type']=a['type'], branch=a.branch })
		return _safeExpandTemplate(frame, string.format('%s stations', a.system), {line=a.name, branch=a.branch, station=term, state=a.state})
	else
		return p._internalStationLink(term, '', true, a.system, frame)
	end
end

function p.lineTerminalName(frame)
	local a = frame.args
	local terminal_str = p._internalLineTerminal(a.name, a.side, a['type'], a.branch, a.system)
	
	if terminal_str == nil then
		return _safeExpandTemplate(frame, string.format('S-line/%s %s/%s', a.system, a.side, a.name), { ['type']=a['type'], branch=a.branch })
	else
		return terminal_str
	end
end

function p.trainTime(frame)
	local a = frame.args
	if a['type'] == 'f' or a['type'] == 'F' then a['type'] = 'first' end
	if a['type'] == 'l' or a['type'] == 'L' then a['type'] = 'last' end
	
	local terminal, hour, minute, sec = p._internalTrainTime(a.name, a.dir, a['type'], a.delta, a.system)
	return string.format('%02d:%02d', hour, minute)
end

function p.trainDirectionTime(frame)
	local str = frame.template
	if str == nil then str = p.DEFAULT_TRAIN_TIME_TEMPLATE end
	
	local a = frame.args
	if a['type'] == 'f' or a['type'] == 'F' then a['type'] = 'first' end
	if a['type'] == 'l' or a['type'] == 'L' then a['type'] = 'last' end
	
	local terminal, hour, minute, sec = p._internalTrainTime(a.name, a.dir, a['type'], a.delta, a.system)
	
	local l = 0
	while true do
		local r, c
		l, r, c = string.find(str, '\{([^\}]+)\}', l + 1)
		if l == nil then break end
		local sparts = mw.text.split(c, '|')
		if sparts[1] == 'station_name' then
			str = plain_replace(str, '{' .. c .. '}', terminal)
		elseif sparts[1] == 'station_link' then
			str = plain_replace(str, '{' .. c .. '}', p._internalStationLink(terminal, '', true, a.system, frame))
		elseif sparts[1] == 'train_time' then
			local tomorrow_prefix = ''
			if table.getn(sparts) >= 3 then
				if hour >= 24 then 
					tomorrow_prefix = mw.text.trim(sparts[3])
					if tomorrow_prefix == '' then tomorrow_prefix = '次日' end
					hour = hour - 24
				end
			end
			local time_str = string.format('%d:%d:%d', hour, minute, sec)
			local repl_time = frame:callParserFunction{ name = '#time', args = { sparts[2], time_str } }
			str = plain_replace(str, '{' .. c .. '}', tomorrow_prefix .. repl_time)
		end
	end
	return str
end

function p.lineDateMessage(frame)
	local a = frame.args
	local name, type_, system = a.name, a['type'], a.system
	local reprs, options = {}, {}
	local preserved = { name = true, ['type'] = true, system = true }
	for k, v in pairs(a) do
		if preserved[k] == nil then
			if type(k) == 'number' then
				reprs[k] = v
			else
				options[k] = v
			end
		end
	end 
	return p._internalLineDateMessage(name, type_, reprs, options, system)
end

function p.renderStationLinks(frame)
	local a = frame:getParent().args
	local short = _convBool(a.short, true)
	local spos, epos, station_name = 1, 1, nil
	local pos = 1
	local system_data = _loadSystemData(a.system)
	local sys_args = { system=true, short=true }
	
	local text = ''
	for k, v in frame:getParent():argumentPairs() do
		if type(k) == 'number' then
			if string.len(text) > 0 then
				text = text .. '|' .. v
			else
				text = v
			end
		else
			if sys_args[k] == nil then
				if string.len(text) > 0 then
					text = text .. '|' .. k .. '=' .. v
				else
					text = k .. '=' .. v
				end
			end
		end
	end 
	text = mw.text.trim(text)
	
	local out_text = ''
	while spos ~= nil do
		spos, epos, station_name = string.find(text, '%$%$([^%$]+)%$%$', pos)
		if spos ~= nil then
			out_text = out_text .. string.sub(text, pos, spos - 1) .. p._internalStationLink(station_name, '', short, system_data, frame)
			pos = epos + 1
		else
			out_text = out_text .. string.sub(text, pos, string.len(text))
		end
	end
	return out_text
end

function p.testCase(frame)
	local frame = mw.getCurrentFrame()
	local lineTerminalNameFrame
	local link, _match
	
	function mkfp(func_name)
		local fun = p[func_name]
		function call_frame(frame)
			return fun(mw.getCurrentFrame():newChild(frame))
		end
		return call_frame
	end
	
	local fp = {
		stationLink = mkfp('stationLink'),
		lineTitle = mkfp('lineTitle'),
		lineColor = mkfp('lineColor'),
		lineTerminal = mkfp('lineTerminal'),
		lineTerminalName = mkfp('lineTerminalName'),
		trainDirectionTime = mkfp('trainDirectionTime'),
	}
	
	-- stationLink test
	if fp.stationLink{args={name='宁波站', system='UseCase'}} ~= '[[宁波站|宁波站]]' then
		error('stationLink test failed: name=宁波站.')
	end
	if fp.stationLink{args={name='寧波站', system='UseCase'}} ~= '[[宁波站|宁波站]]' then
		error('stationLink test failed: name=宁波站.')
	end
	if fp.stationLink{args={name='高桥西', system='UseCase'}} ~= '[[高桥西站 (宁波)|高桥西]]' then
		error('stationLink test failed: name=高桥西.')
	end
	if fp.stationLink{args={name='高桥西', short='0', system='UseCase'}} ~= '[[高桥西站 (宁波)|高桥西站]]' then
		error('stationLink test failed: name=高桥西, short=0.')
	end
	if fp.stationLink{args={name='泽民', system='UseCase'}} ~= '[[泽民站|泽民]]' then
		error('stationLink test failed: name=泽民.')
	end
	if fp.stationLink{args={name='内嵌模板', system='UseCase'}} ~= '[[内嵌模板站|内嵌|模板]]' then
		error('stationLink test failed: name=内嵌模板.')
	end
	
	-- lineColor test
	if p.lineColor{args={name='1', system='UseCase'}} ~= '3180b7' then
		error('lineColor test failed: name=1.')
	end
	if p.lineColor{args={name='1', prefix='1', system='UseCase'}} ~= '#3180b7' then
		error('lineColor test failed: name=1.')
	end
	if fp.lineColor{args={name='1', system='TestComp'}} ~= 'cc0000' then
		error('lineColor compatibility test failed: name=1.')
	end
	if fp.lineColor{args={name='1', prefix='1', system='NonExist'}} ~= '#[[:Template:NonExist color]]' then
		error('lineColor compatibility test failed: name=1.')
	end
	
	-- lineTitle test
	if p.lineTitle{args={name='1', system='UseCase'}} ~= "'''[[宁波轨道交通1号线|1号线]]'''" then
		error('lineTitle test failed: name=1.')
	end
	if p.lineTitle{args={name='WRL', system='UseCase'}} ~= "'''[[西鐵綫|西鐵綫]]'''" then
		error('lineTitle test failed: name=WRL.')
	end
	if p.lineTitle{args={name='WRL', link='0', system='UseCase'}} ~= "西鐵綫" then
		error('lineTitle test failed: name=WRL, link=0.')
	end
	if fp.lineTitle{args={ name='1', system='TestComp'}} ~= '[[哈尔滨地铁1号线|1号线]]' then
		error('lineTitle compatibility test failed.')
	end
	if fp.lineTitle{args={ name='1', system='NonExist'}} ~= '[[:Template:NonExist lines]]' then
		error('lineTitle non compatibility test failed.')
	end
	
	-- lineRichTitle test
	link, _match = string.gsub(p.DEFAULT_RICH_LINK_TEMPLATE, '\{line_link\}', '宁波轨道交通1号线')
	link, _match = string.gsub(link, '\{line_title\}', '1号线')
	link, _match = string.gsub(link, '\{line_color\}', '3180b7')
	if p.lineRichTitle{args={name='1', system='UseCase'}} ~= link then
		error('lineRichTitle test failed: name=1.')
	end
	-- degrade case
	if p.lineRichTitle{args={name='S1', system='UseCase'}} ~= "'''[[宁波至余慈城际铁路|余慈线]]'''" then
		error('lineRichTitle test failed: name=1.')
	end
	
	-- lineTerminal test
	-- 仅此一例,其余测试覆盖由 lineTerminalName 完成
	if fp.lineTerminal{args={name='S1', side='left', type='F', system='UseCase'}} ~= '[[宁波东站|宁波东站]]' then
		error('lineTerminal test failed: name=S1, side=left, type=F.')
	end
	if fp.lineTerminal{args={ name='1', side='left', type='s1', branch='b1', system='TestComp'}} ~= '[[S1B1]]' then
		error('lineTerminal compatibility test failed: name=S1, side=left, type=F.')
	end
	
	-- lineTerminalName test
	if fp.lineTerminalName{args={ name='1', side='left', system='UseCase'}} ~= '高桥西' then
		error('lineTerminalName test failed: name=1, side=left.')
	end
	if fp.lineTerminalName{args={ name='S1', side='left', system='UseCase'}} ~= '宁波站' then
		error('lineTerminalName test failed: name=1, side=left.')
	end
	if fp.lineTerminalName{args={ name='S1', side='left', type='F', system='UseCase'}} ~= '宁波东站' then
		error('lineTerminalName test failed: name=1, side=left.')
	end
	if fp.lineTerminalName{args={ name='S1', side='right', system='UseCase'}} ~= '余姚' then
		error('lineTerminalName test failed: name=1, side=left.')
	end
	if fp.lineTerminalName{args={ name='S1', side='right', type='F', system='UseCase'}} ~= '马渚' then
		error('lineTerminalName test failed: name=1, side=left.')
	end
	if fp.lineTerminalName{args={ name='S1', side='right', type='F', branch='HZW', system='UseCase'}} ~= '杭州湾' then
		error('lineTerminalName test failed: name=1, side=left.')
	end
	
	-- lineTerminalName compatibility test
	if fp.lineTerminalName{args={ name='1', side='left', ['type']='s1', branch='b1', system='TestComp'}} ~= 'S1B1' then
		error('lineTerminalName compatibility test failed: type=s1, branch=b1.')
	end
	if fp.lineTerminalName{args={ name='1', side='left', ['type']='s1', system='TestComp'}} ~= 'S1DEFAULT' then
		error('lineTerminalName compatibility test failed: type=s1.')
	end
	if fp.lineTerminalName{args={ name='1', side='left', ['type']='s2', system='TestComp'}} ~= 'DEFAULT' then
		error('lineTerminalName compatibility test failed: type=s2.')
	end
	if fp.lineTerminalName{args={ name='1', side='left', system='NonExist'}} ~= '[[:Template:S-line/NonExist left/1]]' then
		error('lineTerminalName compatibility test failed: type=NonExist.')
	end
	
	-- trainTime test
	if p.trainTime{args={name='1', dir='霞高', ['type']='L', delta='0:3', system='UseCase'}} ~= '22:06' then
		error('trainTime test failed: dir=霞高, type=L, delta=0:3.')
	end
	if p.trainTime{args={name='1', dir='高霞', ['type']='F', delta='0:4', system='UseCase'}} ~= '06:08' then
		error('trainTime test failed: dir=高霞, type=F, delta=0:4.')
	end
	
	-- trainDirectionTime test
	if fp.trainDirectionTime{args={name='1', dir='霞高', ['type']='L', delta='0:3', system='UseCase'}} ~= "往[[高桥西站 (宁波)|高桥西]]方向:22:06" then
		error('trainDirectionTime test failed: dir=霞高, type=L, delta=0:3.')
	end
	if fp.trainDirectionTime{args={name='1', dir='高霞', ['type']='F', delta='0:4', system='UseCase'}} ~= "往[[霞浦站 (宁波)|霞浦]]方向:06:08" then
		error('trainDirectionTime test failed: dir=高霞, type=F, delta=0:4.')
	end
	
	-- lineDateMessage test
	if p.lineDateMessage{args = {'在', 'year', '的', 'month', '的', 'day', ',月份', 'ym', ',日期', 'date', '开通', name='1', ['type']='2', cur_time = '2016-1-1', auto_hide=true, system='UseCase'}} ~= '在2016年的3月的19日,月份2016年3月,日期2016年3月19日开通' then
		error('lineDateMessage test failed.')
	end
	if p.lineDateMessage{args = {'year', name='1', ['type']='2', cur_time = '2016-5-19', auto_hide=true, system='UseCase'}} ~= '2016年' then
		error('lineDateMessage test failed.')
	end
	if p.lineDateMessage{args = {'year', name='1', ['type']='2', cur_time = '2017-3-19', auto_hide=true, system='UseCase'}} ~= p.DEFAULT_DATE_ELAPSED_TEXT then
		error('lineDateMessage test failed.')
	end
	if p.lineDateMessage{args = {'ym', name='1', ['type']='2', cur_time = '2016-3-30', auto_hide=true, system='UseCase'}} ~= '2016年3月' then
		error('lineDateMessage test failed.')
	end
	if p.lineDateMessage{args = {'ym', name='1', ['type']='2', cur_time = '2016-4-19', auto_hide=true, system='UseCase'}} ~= p.DEFAULT_DATE_ELAPSED_TEXT then
		error('lineDateMessage test failed.')
	end
	if p.lineDateMessage{args = {'ymd', name='1', ['type']='2', cur_time = '2016-3-18', auto_hide=true, system='UseCase'}} ~= '2016年3月19日' then
		error('lineDateMessage test failed.')
	end
	if p.lineDateMessage{args = {'ymd', name='1', ['type']='2', cur_time = '2016-3-19', auto_hide=true, system='UseCase'}} ~= p.DEFAULT_DATE_ELAPSED_TEXT then
		error('lineDateMessage test failed.')
	end
	if p.lineDateMessage{args = {'ymd', name='S1', cur_time = '2016-3-18', auto_hide=true, system='UseCase'}} ~= '2019年12月28日' then
		error('lineDateMessage test failed.')
	end
	if p.lineDateMessage{args = {'二期规划中', name='2', auto_hide=true, system='UseCase'}} ~= '二期规划中' then
		error('lineDateMessage test failed.')
	end
	if p.lineDateMessage{args = {'ymd', name='2', auto_hide=true, system='UseCase'}} ~= p.DEFAULT_LINE_DATE_ABSENT_TEXT then
		error('lineDateMessage test failed.')
	end
	
	local child_frame = frame:newChild{title='RenderStations', args={system='UseCase', '1号线从$$高桥西$$\n', '到$$霞浦$$\n'}}
	if p.renderStationLinks(child_frame:newChild{title='renderStationLinks', args={}}) ~= '1号线从[[高桥西站 (宁波)|高桥西]]\n|到[[霞浦站 (宁波)|霞浦]]' then
		error('renderStationLinks test failed: short=true.')
	end
	child_frame = frame:newChild{title='RenderStations', args={system='UseCase', short='0', '1号线从$$高桥西$$\n', '到$$霞浦$$\n'}}
	if p.renderStationLinks(child_frame:newChild{title='renderStationLinks', args={}}) ~= '1号线从[[高桥西站 (宁波)|高桥西站]]\n|到[[霞浦站 (宁波)|霞浦站]]' then
		error('renderStationLinks test failed: short=false.')
	end
	
	return '所有用例均通过!'
end

return p