- 欢迎来到THBWiki!如果您是第一次来到这里,请点击右上角注册一个帐户
- 有任何意见、建议、求助、反馈都可以在 讨论板 提出
- THBWiki以专业性和准确性为目标,如果你发现了任何确定的错误或疏漏,可在登录后直接进行改正
模块:技能信息
跳到导航
跳到搜索
模块文档[创建]
| 本模块依赖以下其他模块: |
local list = require('Module:list')
local tttools = require('Module:tttools')
local ArgsTracker = require('Module:checkargs').ArgsTracker
local inner_template_name = '模板:技能信息'
local frame_template_name = '模板:技能信息框架'
local function make_errmsg(name)
return function(msg)
return require('Module:error')._main{ msg, name }
end
end
-- 为两个模板创建不同的错误消息函数
local err_inner = make_errmsg(inner_template_name)
local err_frame = make_errmsg(frame_template_name)
-- 单个技能信息的最大行数/列数
local size_limit = 10
local align_types = {
['左'] = 'left',
['中'] = 'center',
['右'] = 'right',
}
local bold_classes = {
['是'] = 'bold',
['否'] = 'nobold',
}
local p = {}
-- 解析宽度并检查是否合法
local function parse_width(text)
if text and text ~= '' then
local num = tonumber(text)
if num and num > 0 then
return num .. 'px'
end
return nil, '宽度必须是正数'
end
return nil
end
-- 解析对齐方式并检查是否合法
local function parse_align(text)
if text and text ~= '' then
local align = align_types[text]
if align then
return align
end
return nil, '<code>对齐</code> 的值必须是 <code>左/中/右</code>'
end
return nil
end
-- 解析加粗参数并检查是否合法
local function parse_bold(text)
if text and text ~= '' then
local bold = bold_classes[text]
if bold then
return bold
end
return nil, '<code>加粗</code> 的值必须是 <code>是/否</code>'
end
return nil
end
-- 带缓存功能的翻译表获取器
local function make_fetcher(title)
local cache = {}
return function(section, num)
if not cache[section] then
cache[section] = tttools.get_content{ title, section }
end
local sec = cache[section]
if not num then
return sec
end
num = num:gsub('(%d+)%-(%d+)', function(a, b)
a, b = tonumber(a), tonumber(b)
b = math.min(b, #sec)
return list.range(a, b):join(',')
end)
local num_list = list(num:gmatch('[1-9]%d*'))
:set()
:map(tonumber)
:sorted()
return num_list:map(function(i) return sec[i] end)
end
end
-- 生成单一语言的表格
local function render_table(args, lang)
local data = args.data
local align = args.align
local style = args.style
local max_col = args.max_col
-- 语言代号
local normal = lang
local header = lang .. 'h'
local blocks = data:map(function(item)
local row_align = item.align
local row_style = item.style
local trs = item.rows:map(function(row)
local tds = row:map(function(cell)
if cell.title then
local th = mw.html.create('th')
:attr('colspan', max_col)
:wikitext(cell.title)
return tostring(th)
end
if not cell.is_filled then
local td = mw.html.create('td')
:wikitext(err_inner('缺少内容'))
return tostring(td)
end
if not cell.has_content then
return ''
end
local tt_content = cell.tt_content
local content = cell.content or ''
if tt_content then
local divs = tt_content:map(function(tt)
-- 找出翻译表类型
local tt_type = (tt[normal] and normal)
or (tt[header] and header)
if not tt_type then
return ''
end
local text = tt[tt_type]
:gsub('%s+$', '')
:gsub('\n', '<br />')
local div = mw.html.create('div')
:addClass('tt-' .. tt_type)
:wikitext(text)
return tostring(div)
end)
local text = divs:join('')
if text == '' then
text = err_inner('引用的翻译表不存在')
end
content = text .. content
end
local td = mw.html.create('td')
:addClass(cell.bold)
:attr('rowspan', cell.rowspan)
:attr('colspan', cell.colspan)
:css('width', cell.width)
:css('text-align', cell.align)
:cssText(cell.style)
:wikitext(content)
return tostring(td)
end)
local tr = mw.html.create('tr')
:css('text-align', row_align)
:cssText(row_style)
:wikitext(tds:join(''))
return tostring(tr)
end)
-- 单独生成一行显示错误消息
if item.message then
local tr = mw.html.create('tr')
tr:tag('td')
:attr('colspan', max_col)
:wikitext(item.message)
trs:insert(1, tostring(tr))
end
return trs:join('')
end)
local body = mw.html.create('table')
:addClass('wikitable')
:addClass('skillinfo')
:attr('lang', lang)
:css('text-align', align)
:cssText(style)
:wikitext(blocks:join(''))
return tostring(body)
end
-- 将内层模板的输入参数整理为结构化数据
function p._preprocess(args)
local align = args['对齐']
local style = args.style
-- 标题行不需要其他参数, 提前返回
local title = args['标题'] or ''
if title ~= '' then
local cell = { title = title }
return {
rows = list{ list{ cell } },
size = {1, 1},
align = align,
style = style,
}
end
-- 遍历参数名求出表格尺寸
local indices = list.fromkeys(args):map(function(key)
key = tostring(key)
local i, j = key:match('^([1-9]%d*)行([1-9]%d*)列')
if i then
return { tonumber(i), tonumber(j) }
end
j = key:match('^第([1-9]%d*)格')
if j then
return { 1, tonumber(j) }
end
return {0, 0}
end)
local max_row = indices
:map(function(index) return index[1] end)
:max() or 0
local max_col = indices
:map(function(index) return index[2] end)
:max() or 0
max_row = math.min(max_row, size_limit)
max_col = math.min(max_col, size_limit)
if max_row == 0 then
return {
rows = list{},
size = {0, 0},
message = err_inner('缺少内容'),
}
end
-- 只有一行时允许使用别名
if max_row == 1 then
local row = list.range(max_col):map(function(j)
local prefix = ('1行%d列'):format(j)
local prefix_short = ('第%d格'):format(j)
return {
ttt = args[prefix .. 'ttt'] or args[prefix_short .. 'ttt'],
content = args[prefix .. '内容'] or args[prefix_short .. '内容'],
width = args[prefix .. '宽度'] or args[prefix_short .. '宽度'],
align = args[prefix .. '对齐'] or args[prefix_short .. '对齐'],
bold = args[prefix .. '加粗'] or args[prefix_short .. '加粗'],
style = args[prefix .. 'style'] or args[prefix_short .. 'style'],
}
end)
return {
rows = list{ row },
size = { 1, max_col },
align = align,
style = style,
}
end
local rows = list.range(max_row):map(function(i)
return list.range(max_col):map(function(j)
local prefix = ('%d行%d列'):format(i, j)
return {
ttt = args[prefix .. 'ttt'],
content = args[prefix .. '内容'],
width = args[prefix .. '宽度'],
align = args[prefix .. '对齐'],
bold = args[prefix .. '加粗'],
style = args[prefix .. 'style'],
}
end)
end)
return {
rows = rows,
size = { max_row, max_col },
align = align,
style = style,
}
end
function p._main(args)
local page = args['中日对照页面'] or ''
if page == '' then
return err_frame('缺少参数 <code>中日对照页面</code>')
end
local fetch = make_fetcher(page)
local align, message = parse_align(args['对齐'])
if message then
return err_frame(message)
end
local style = args.style
local lang = args['语言'] or ''
if lang == '' then
lang = '中日'
end
local data = args.data
if not data then
error('没有提供 data')
end
data = list(data)
-- 计算最大列数
local max_col = data
:map(function(item) return item.size[2] end)
:max() or 0
-- 处理数据
for item in data:iter() do
item.align, message = parse_align(item.align)
if message then
item.message = err_inner(message)
end
-- 将行和列转换为列表
local rows = list(item.rows):map(list)
item.rows = rows
-- 补充缺少的格子
local len = max_col - item.size[2]
if len > 0 then
for row in rows:iter() do
for i = 1, len do
row:append({})
end
end
end
local max_row = rows:len()
-- 遍历表格, 将有内容的单元格向右扩展
-- 算出 colspan, 标出本身有内容或被填充的单元格
-- 顺便解析格式参数和获取翻译表内容
for i = 1, max_row do
local prev -- 本行上一个有填写内容的单元格
for j = 1, max_col do
local cell = rows[i][j]
if cell.ttt or cell.content or cell.title then
cell.has_content = true -- 表示本身有内容
cell.is_filled = true -- 本身有内容或被其他单元格填充
prev = cell
elseif prev then
-- 如果这一格无内容, 且左方有非空单元格, 就将那一格的宽度加 1
prev.colspan = (prev.colspan or 1) + 1
cell.is_filled = true
end
-- 解析格式参数
cell.align, message = parse_align(cell.align)
if message then
cell.content = err_inner(message)
end
cell.width, message = parse_width(cell.width)
if message then
cell.content = err_inner(message)
end
cell.bold, message = parse_bold(cell.bold)
if message then
cell.content = err_inner(message)
end
-- 获取翻译表内容
local ttt = cell.ttt or ''
if ttt ~= '' then
local section, num = ttt:match('([^,]+),(.+)')
if section then
cell.tt_content = fetch(section, num)
else
cell.content = err_inner('参数 <code>ttt</code> 格式不正确, 必须是 <code>章节名,序号</code>')
end
end
end
end
-- 再次遍历表格, 将有内容的单元格向下扩展 (如果可能)
-- 算出 rowspan, 标出被填充的单元格
for j = 1, max_col do
local prev -- 本列上一个有填写内容且能往下扩展的单元格
for i = 1, max_row do
local cell = rows[i][j]
if cell.has_content then
prev = cell
elseif prev then
-- 如果这一格没有填写内容, 且上方有非空单元格, 就尝试将那一格的高度加 1
local width = prev.colspan or 1
-- 切片出将被覆盖的单元格
local covered = rows[i]{j, j + width - 1}
if covered:any(function(c) return c.is_filled end) then
-- 如果其中任何单元格已被填充, 就不能再往下扩展
prev = nil
else
prev.rowspan = (prev.rowspan or 1) + 1
for c in covered:iter() do
c.is_filled = true
end
end
end
end
end
end
-- 生成表格
local render_args = {
data = data,
align = align,
style = style,
max_col = max_col,
}
if lang == '中日' then
local table_zh = render_table(render_args, 'zh')
local table_ja = render_table(render_args, 'ja')
local tabber_text = ('|-| 中文 = %s |-| 日文 = %s'):format(table_zh, table_ja)
return mw.getCurrentFrame():extensionTag('tabber', tabber_text)
elseif lang == '中文' then
return render_table(render_args, 'zh')
elseif lang == '日文' then
return render_table(render_args, 'ja')
else
return err_frame('<code>语言</code> 的值必须是 <code>中日/中文/日文</code>')
end
end
function p.preprocess(frame)
-- 追踪参数使用情况
local args = ArgsTracker(frame:getParent().args)
local item = p._preprocess(args)
item.message = args:make_message{ inner_template_name }
-- 返回 json 给外层模板统一处理
return mw.text.jsonEncode(item) .. ','
end
function p.main(frame)
local args = frame:getParent().args
local raw = args['内容'] or ''
if raw == '' then
return err_frame('缺少参数 <code>内容</code>')
end
-- 防止解析出错
local json = '[' .. raw:gsub(',$', '') .. ']'
local success, data = pcall(mw.text.jsonDecode, json)
if not success then
return err_frame('请勿在参数 <code>内容</code> 中添加 [[' .. inner_template_name .. ']] 以外的内容')
end
args.data = data
-- 追踪参数使用情况
args = ArgsTracker(args)
-- 访问一次记为已使用
local _ = args['内容']
local text = p._main(args)
local message = args:make_message{ frame_template_name } or ''
return message .. text
end
return p