-- 模块: llsifsonglist
-- 原模板: llsifsonglist
-- 目标: 100% 逻辑保留;所有表格用 mw.html;所有模板/解析器函数调用通过 frame:expandTemplate / frame:callParserFunction 展开;不输出原始 {{!}} / {{!-}} / {{#xxx}}。
-- 说明: 原模板使用 #fornumargs / #var 体系实现按组解析。此模块改为两阶段:
-- 1) 解析参数 -> 组结构数组 groups。
-- 2) 预计算统计 (max / max2 / max3) 然后渲染。
-- 兼容 type: rc, cf, sm, as1, as2, cm, mf, token。
-- 若后续发现差异,可在对应 TODO 标注区继续补细节。
-- 未完成!!请不要让这个模块正式应用。
local getArgs = require('Module:Arguments').getArgs
local p = {}
-- 配色 (与 #vardefine 同步)
local BG = { smile = '#FFC1C1', pure = '#C0FFBB', cool = '#B0E2FF' }
local TX = { smile = '#d1016a', pure = '#2da55a', cool = '#0f98e3' }
-- 工具函数
local function split(str, sep)
str = str or ''
if str == '' then return {} end
local t = {}
sep = sep or ','
for part in mw.ustring.gmatch(str, "[^"..sep.."]+") do
table.insert(t, mw.text.trim(part))
end
return t
end
local function filepath(frame, name)
if not name or name == '' then return '' end
return frame:callParserFunction{ name = 'filepath', args = { name } }
end
local function img(frame, src, style)
if not src or src == '' then return '' end
local args = { src }
if style then args.style = style end
return frame:callParserFunction{ name = '#img', args = args }
end
local function tpl(frame, name, args)
return frame:expandTemplate{ title = name, args = args or {} }
end
local function padCombo(frame, value)
if not value or value == '' then return '' end
local len = mw.ustring.len(value)
if len == 6 then return tpl(frame,'0')
elseif len == 5 then return tpl(frame,'0',{ '00'})
elseif len == 4 then return tpl(frame,'0',{ '000'})
elseif len == 3 then return tpl(frame,'0',{ '.000'})
else return '' end
end
-- 解析组尺寸 (与原模板 #switch 相同)
local function determineGroupSize(tp, diffList, diffProvided)
if tp == 'rc' then return 6 end
if tp == 'cf' then return diffProvided and 9 or 8 end
if tp == 'sm' or tp == 'as1' then return 7 end
if tp == 'cm' or tp == 'mf' or tp == 'as2' then
if #diffList > 0 and diffList[#diffList]:lower() == 'master' then return 9 else return 7 end
end
-- 默认策略: 若含 master 则 9 否则 7 (避免空)
if #diffList > 0 and diffList[#diffList]:lower() == 'master' then return 9 else return 7 end
end
-- 解析所有匿名参数为 groups
local function parseGroups(args, tp, groupSize)
local groups = {}
local index = 1
local g = nil
local pos = 0
while true do
local v = args[index]
if v == nil then break end
pos = (pos % groupSize) + 1
if pos == 1 then
g = { raw = {}, attr = mw.ustring.lower(v or ''), values = {}, idx = (#groups)+1 }
table.insert(groups, g)
end
g.raw[pos] = v or ''
-- 保存特定字段 (依据原模板位置语义)
if pos == 2 then g.songCandidate = v or '' end
if pos == 3 then g.diff_min_raw = v or '' end
if pos == 4 then g.diff_max_raw = v or '' end
if pos == 5 then g.songNameRaw = v or '' end
if pos >= 6 then g.values[pos] = v end
index = index + 1
end
return groups
end
-- 统计最大/前三 (attr + slot 6/7/8)
local function computeStats(groups)
local stats = {} -- stats[attr][slot] = {max1,max2,max3}
for _,g in ipairs(groups) do
local attr = g.attr or ''
if attr ~= '' then
stats[attr] = stats[attr] or {}
for slot,val in pairs(g.values) do
local num = tonumber(val or '')
if num then
local st = stats[attr][slot] or {0,0,0}
if num > st[1] then
st[3] = st[2]; st[2] = st[1]; st[1] = num
elseif num > st[2] then
st[3] = st[2]; st[2] = num
elseif num > st[3] then
st[3] = num
end
stats[attr][slot] = st
end
end
end
end
return stats
end
-- 渲染默认(含 sm/cm/mf/as2/rc) 表
local function renderMain(frame, args, tp, diffList, groups, stats, groupSize)
local tbl = mw.html.create('table'):addClass('wikitable'):addClass('sortable'):addClass('mw-collapsible')
if tp == 'cf' then tbl:addClass('mw-collapsed') end
tbl:css('text-align','center')
-- 标题行构建
local header1 = tbl:tag('tr')
header1:tag('th'):attr('rowspan',2):wikitext('属性')
header1:tag('th'):attr('rowspan',2):wikitext('封面')
header1:tag('th'):attr('colspan', #diffList):wikitext('歌曲')
if tp ~= 'rc' then
if tp == 'sm' then header1:tag('th'):attr('rowspan',2):wikitext('阶段')
elseif tp == 'cm' or tp == 'mf' or tp == 'as2' then header1:tag('th'):attr('rowspan',2):wikitext('EXPERT<br>Combo') end
if (#diffList > 7) then
header1:tag('th'):attr('rowspan',2):wikitext('MASTER<br>Combo')
header1:tag('th'):attr('rowspan',2):wikitext('MASTER<br>等效Combo')
end
end
header1:tag('th'):attr('rowspan',2):wikitext('试听')
-- 第二行: 难度图标
local header2 = tbl:tag('tr')
for _,d in ipairs(diffList) do
local up = mw.ustring.upper(d)
local src = filepath(frame, up .. '.png')
header2:tag('th'):addClass('unsortable'):wikitext(img(frame, src, 'width:64px'))
end
-- 特殊 rc Omakase 行
if tp == 'rc' then
local tr = tbl:tag('tr')
tr:tag('td'):wikitext(tpl(frame,'n/a'))
tr:tag('td'):wikitext(img(frame, filepath(frame,'omakase!_temp.png'), 'width:48px'))
tr:tag('td'):attr('colspan', #diffList):css('background','#EEE0E5'):wikitext("'''" .. tpl(frame,'lj', {'おまかせ!'}) .. "'''")
tr:tag('td'):wikitext(tpl(frame,'n/a'))
end
-- 行数据
for _,g in ipairs(groups) do
local attr = g.attr
if attr and attr ~= '' then
local tr = tbl:tag('tr')
-- 属性图标
tr:tag('td'):wikitext(tpl(frame,'llsifitem', {attr,''}))
-- 封面 (Cover: 参数 coverN 或 songCandidate + extension 判定)
local coverParamName = 'cover' .. g.idx
local cover = args[coverParamName] or g.songCandidate
if cover and cover ~= '' then
local file = cover
if not file:match('%.png$') and not file:match('%.jpg$') then
file = file .. '.png'
end
tr:tag('td'):wikitext(img(frame, filepath(frame,file), 'width:48px'))
else
tr:tag('td'):wikitext('—')
end
-- 歌曲列: 简化处理放置一个合并列 (#diffList colspan) + 名称/注释,占位逻辑 diff_min / diff_max 仍保留但内部简化
local diffMin = tonumber(g.diff_min_raw or '') or 0
local diffMax = tonumber(g.diff_max_raw or '') or 0
local span = (#diffList > 0) and (#diffList - diffMin) or 1
local cellSong = tr:tag('td'):attr('colspan', span > 0 and span or 1)
-- 链接与注释
local linkParam = args['lk'..g.idx]
local songName = (g.songNameRaw and g.songNameRaw ~= '') and g.songNameRaw or g.songCandidate
local linkText = songName
if linkParam and linkParam ~= '' then
cellSong:wikitext("'''[[".. linkParam .."|".. tpl(frame,'lj',{songName}) .. "]]'''" )
else
cellSong:wikitext("'''".. tpl(frame,'lj',{songName}) .."'''")
end
local comment = args['comment'..g.idx]
if comment and comment ~= '' then
cellSong:wikitext(comment)
end
-- 如果未覆盖到最后,补空单元
local remaining = #diffList - span - diffMin
for _=1,remaining do tr:tag('td'):wikitext('') end
-- 数值列 (示例: stage / combo 逻辑) 对应 slots 6/7/8
local function highlight(slot, val)
if not val or val == '' then return '' end
local num = tonumber(val)
if not num then return val end
local st = stats[attr] and stats[attr][slot]
if not st then return val end
if tp == 'mf' then
if num >= st[3] and st[3] > 0 then
return "'''" .. tpl(frame,'color',{ TX[attr] or '#000', val }) .. "'''"
end
else
if num == st[1] then
return "'''" .. tpl(frame,'color',{ TX[attr] or '#000', val }) .. "'''"
end
end
return val
end
if tp == 'sm' then
local v = g.values[6] or ''
tr:tag('td'):attr('data-sort-value', v):wikitext(v)
elseif tp == 'cm' or tp == 'mf' or tp == 'as2' then
local v6 = highlight(6, g.values[6])
tr:tag('td'):wikitext(v6)
end
if #diffList > 7 then
local v7 = highlight(7, g.values[7])
local v8 = highlight(8, g.values[8]) .. padCombo(frame, g.values[8] or '')
tr:tag('td'):wikitext(v7)
tr:tag('td'):wikitext(v8)
end
-- 试听列 (参数 songN)
local songFile = args['song'..g.idx]
if songFile and songFile ~= '' then
tr:tag('td'):wikitext('[[File:'.. songFile ..']]')
else
tr:tag('td'):wikitext('')
end
end
end
return tostring(tbl)
end
-- token / as1 / cf / rc 其它特化表: TODO: cf 已并入主; token 与 as1、章节型 as1 保留占位,后续可细化
local function renderToken(frame, args, diffList)
-- 占位: 完整章节/难度展开与原模板差异较大,此处保留最基本封面 + 难度列表表
local tbl = mw.html.create('table'):addClass('wikitable'):css('text-align','center')
local trh = tbl:tag('tr')
trh:tag('th'):wikitext('封面')
trh:tag('th'):wikitext('名称')
trh:tag('th'):wikitext('属性')
trh:tag('th'):wikitext('演唱者')
trh:tag('th'):wikitext('难度')
trh:tag('th'):wikitext('星级')
trh:tag('th'):wikitext('Combo')
if args.song then trh:tag('th'):wikitext('试听') end
-- 单行(与原模板多行展开待补)
local tr = tbl:tag('tr')
local cover = args.cover or ((args.nm or '') .. '.png')
tr:tag('td'):wikitext('[[['.. (args.lk or args.nm or '') ..'|'.. img(frame, filepath(frame, cover), 'width:176px') ..']]]')
tr:tag('td'):wikitext("'''".. tpl(frame,'lang',{'ja','[['.. (args.lk or args.nm or '') ..'|'.. (args.nm or '') ..']]'} ) .."'''")
tr:tag('td'):wikitext(tpl(frame,'llsifitem',{args.cl or '', ''}))
tr:tag('td'):wikitext(args.group or '')
tr:tag('td'):wikitext(table.concat(diffList, '/'))
tr:tag('td'):wikitext('')
tr:tag('td'):wikitext('')
if args.song then tr:tag('td'):wikitext('[[File:'.. args.song ..']]') end
return tostring(tbl)
end
function p.main(frame)
local args = getArgs(frame, { parentFirst = true })
local tp = (args.type or ''):lower()
local diffList = split(args.diff, ',')
local groupSize = determineGroupSize(tp, diffList, args.diff and args.diff ~= '')
local groups = {}
if tp ~= 'token' and tp ~= 'as1' then
groups = parseGroups(args, tp, groupSize)
end
local stats = computeStats(groups)
if tp == 'token' then
return renderToken(frame, args, diffList)
else
return renderMain(frame, args, tp, diffList, groups, stats, groupSize)
end
end
return p