Module:曲师列表:修订间差异
接入 模块:数据页校验:数据页结构错误时显红字并归入 分类:数据页异常(不再静默渲染空表) |
输出加 __NOCONTENTCONVERT__/__NOTITLECONVERT__:署名与曲名保持 UTF-8 原字,不做繁简变体转换(无需给名字加包裹,无 DOM 开销) |
||
| (未显示同一用户的13个中间版本) | |||
| 第7行: | 第7行: | ||
local DATA_TITLE = '模板:曲目列表.json' | local DATA_TITLE = '模板:曲目列表.json' | ||
local validator = require('模块:数据页校验') | local validator = require('模块:数据页校验') | ||
local MAP_TITLE = '模板:曲师映射.json' | local MAP_TITLE = '模板:曲师映射.json' | ||
local INTRO_TITLE = '模板:曲师简介' -- 手工简介数据页(可编辑 wikitext) | local INTRO_TITLE = '模板:曲师简介' -- 手工简介数据页(可编辑 wikitext) | ||
| 第12行: | 第13行: | ||
local DIFFS = { 'CASUAL', 'TOUGH', 'INSANE', 'PRO' } | local DIFFS = { 'CASUAL', 'TOUGH', 'INSANE', 'PRO' } | ||
-- | -- 拆词与归一化统一由 模块:署名解析 提供(本模块不再自带一份,避免规则漂移) | ||
local | local parse = require('模块:署名解析') | ||
local norm = parse.norm | |||
-- ===== 工具 ===== | -- ===== 工具 ===== | ||
| 第34行: | 第32行: | ||
end | end | ||
-- 排序分组: 0=符号/数字 1=拉丁 2=假名 3=汉字 4=其他 | -- 排序分组: 0=符号/数字 1=拉丁 2=假名 3=汉字 4=其他 | ||
| 第113行: | 第88行: | ||
local data, err = validator.load(DATA_TITLE) | local data, err = validator.load(DATA_TITLE) | ||
if not data then return nil, err end | if not data then return nil, err end | ||
local all = {} | |||
for _, s in ipairs(data.songs or {}) do all[#all + 1] = s end | |||
for _, s in ipairs(data.special or {}) do all[#all + 1] = s end -- 站长要求: special(石墩子)也计入曲师列表 | |||
return all | |||
end | end | ||
-- ===== 主渲染 ===== | -- ===== 主渲染 ===== | ||
-- ===== 自动排行榜(子页面用): {{#invoke:曲师列表|rank|min=3}} ===== | |||
-- 实时统计每位主名义收录的曲目数;并列名次用 rowspan 合并;名字链到曲师列表对应小节 | |||
function p.rank(frame) | |||
local args = frame.args or {} | |||
local minN = tonumber(args['min']) or 3 | |||
local mode = args['mode'] or '署名' | |||
local data = validator.load(DATA_TITLE) | |||
if not data then return '<span class="error">数据页错误</span>' end | |||
local map = loadJson(MAP_TITLE) or {} | |||
local lk = parse.buildLookup(map) | |||
local roles = loadJson('模板:曲师参与.json') or {} | |||
local sets, display = {}, {} | |||
local function touch(key, name) | |||
if not sets[key] then sets[key] = {}; display[key] = name end | |||
end | |||
for _, song in ipairs(data.songs or {}) do | |||
local raw = song['曲师'] | |||
if type(raw) == 'table' then raw = raw[1] end | |||
local parts = parse.attribute(lk, raw) or parse.parts(raw) | |||
local seen = {} | |||
for _, pt in ipairs(parts) do | |||
local key = norm(pt.base) | |||
if key ~= '' and not seen[key] then | |||
seen[key] = true | |||
local m = (lk and lk.extra[key]) or map[key] or {} | |||
touch(key, m['显示名'] or pt.base) | |||
sets[key][song['曲名']] = true -- 署名归属 | |||
end | |||
end | |||
if mode == '参与' then | |||
for ak, songs in pairs(roles) do | |||
if ak ~= '_说明' and type(songs) == 'table' and songs[song['曲名']] then | |||
local m = (lk and lk.extra[ak]) or map[ak] or {} | |||
touch(ak, m['显示名'] or ak) | |||
sets[ak][song['曲名']] = true -- 参与记录 | |||
end | |||
end | |||
end | |||
end | |||
local counts = {} | |||
for k, set in pairs(sets) do | |||
local n = 0 | |||
for _ in pairs(set) do n = n + 1 end | |||
counts[k] = n | |||
end | |||
local byCount, nums = {}, {} | |||
for k, c in pairs(counts) do | |||
if c >= minN then | |||
byCount[c] = byCount[c] or {} | |||
table.insert(byCount[c], k) | |||
end | |||
end | |||
for c in pairs(byCount) do table.insert(nums, c) end | |||
table.sort(nums, function(a, b) return a > b end) | |||
local out = { '{| class="wikitable"', '|+收录曲数量排行榜(' .. mode .. ')', '!名次!!数量!!名字' } | |||
local emitted = 0 | |||
for _, c in ipairs(nums) do | |||
local list = byCount[c] | |||
table.sort(list, function(a, b) | |||
return mw.ustring.lower(display[a]) < mw.ustring.lower(display[b]) | |||
end) | |||
local r = emitted + 1 | |||
for i, k in ipairs(list) do | |||
table.insert(out, '|-') | |||
if i == 1 then | |||
table.insert(out, '| rowspan="' .. #list .. '" |' .. r) | |||
table.insert(out, '| rowspan="' .. #list .. '" |' .. c) | |||
end | |||
table.insert(out, '| [[' .. '曲师列表#' .. esc(display[k]) .. '|' .. esc(display[k]) .. ']]') | |||
end | |||
emitted = emitted + #list | |||
end | |||
table.insert(out, '|}') | |||
if emitted == 0 then return '<span class="error">没有达到门槛(' .. minN .. ')的曲师</span>' end | |||
return frame:preprocess('__NOCONTENTCONVERT__\n__NOTITLECONVERT__\n' .. table.concat(out, '\n')) | |||
end | |||
function p.render(frame) | function p.render(frame) | ||
local songs, songsErr = loadSongs() | local songs, songsErr = loadSongs() | ||
| 第123行: | 第178行: | ||
end | end | ||
local map = loadJson(MAP_TITLE) or {} | local map = loadJson(MAP_TITLE) or {} | ||
local roles = loadJson('模板:曲师参与.json') or {} -- 参与(作曲/作词/人声…):人工知识,并入备注 | |||
local lk = parse.buildLookup(map) | |||
local intros = loadIntros() | local intros = loadIntros() | ||
| 第130行: | 第187行: | ||
local raw = song['曲师'] | local raw = song['曲师'] | ||
if type(raw) == 'table' then raw = raw[1] end | if type(raw) == 'table' then raw = raw[1] end | ||
local parts = | local parts = parse.attribute(lk, raw) or parse.parts(raw) | ||
local seen = {} | local seen = {} | ||
for _, pt in ipairs(parts) do | for _, pt in ipairs(parts) do | ||
| 第137行: | 第194行: | ||
seen[key] = true | seen[key] = true | ||
if not artists[key] then | if not artists[key] then | ||
local m = map[key] or {} | local m = (lk and lk.extra[key]) or map[key] or {} | ||
artists[key] = { | artists[key] = { | ||
display = m['显示名'] or pt.base, | display = m['显示名'] or pt.base, | ||
| 第143行: | 第200行: | ||
or intros[key] or intros[norm(m['显示名'] or pt.base)] or '', | or intros[key] or intros[norm(m['显示名'] or pt.base)] or '', | ||
sort = m['排序键'] or '', | sort = m['排序键'] or '', | ||
unconfirmed = m['_未确认'] or false, | |||
songs = {}, | songs = {}, | ||
} | } | ||
table.insert(order, key) | table.insert(order, key) | ||
end | end | ||
table.insert(artists[key].songs, { song = song, ann = pt.ann, base = pt.base }) | table.insert(artists[key].songs, { song = song, ann = pt.ann, base = pt.base, raw = raw, other = pt.other }) | ||
end | end | ||
end | end | ||
| 第160行: | 第218行: | ||
local out = {} | local out = {} | ||
table.insert(out, "'''曲师列表''':本列表按名字顺序整理了参与游戏曲目制作的曲师及其作品。") | if not (frame.args and frame.args['intro'] == '0') then | ||
table.insert(out, "'''曲师列表''':本列表按名字顺序整理了参与游戏曲目制作的曲师及其作品。") | |||
table.insert(out, '若某首歌曲由多人合作完成,会分别列在每位曲师的条目下;' | |||
.. '同一人的不同写法(含马甲)统一列在最常见的名称之下。') | |||
table.insert(out, '曲包与曲目一览见[[曲目列表]]与[[曲包列表]]。\n') | |||
end | |||
for _, key in ipairs(order) do | for _, key in ipairs(order) do | ||
local a = artists[key] | local a = artists[key] | ||
table.insert(out, '\n==' .. esc(a.display) .. '==') | table.insert(out, '\n==' .. esc(a.display) .. '==') | ||
if a.unconfirmed then | |||
table.insert(out, '<div style="font-size:92%;color:#b45309">主名义待确认</div>') | |||
end | |||
if a.intro ~= '' then | if a.intro ~= '' then | ||
table.insert(out, a.intro) | table.insert(out, a.intro) | ||
| 第175行: | 第238行: | ||
for _, item in ipairs(a.songs) do | for _, item in ipairs(a.songs) do | ||
local s = item.song | local s = item.song | ||
-- 备注: | -- 备注: 参与(来自 模板:曲师参与.json) ;与…合作 ;名义:X | ||
local rawS = type(s['曲师']) == 'table' and s['曲师'][1] or s['曲师'] | |||
local notes = {} | local notes = {} | ||
local | local r = roles[key] -- 参与数据以「艺术家」为键(= 该行归属的主名义) | ||
if r and type(r[s['曲名']]) == 'table' then | |||
local rl = {} | |||
if | for _, x in ipairs(r[s['曲名']]) do rl[#rl + 1] = x end | ||
if #rl > 0 then table.insert(notes, table.concat(rl, '、')) end | |||
end | end | ||
local | local others = {} | ||
if | if item.other then | ||
for _, q in ipairs(item.other) do | |||
if parse.norm(q) ~= key then table.insert(others, q) end | |||
end | |||
else | |||
for _, pt in ipairs(parse.parts(rawS)) do | |||
if norm(pt.base) ~= key then table.insert(others, pt.base) end | |||
end | |||
end | end | ||
if #others > 0 then table.insert(notes, '与 ' .. table.concat(others, '、') .. ' 合作') end | |||
if item.ann ~= '' and item.ann ~= nil then table.insert(notes, item.ann) end | |||
local note = table.concat(notes, ';') | |||
table.insert(out, '|-') | table.insert(out, '|-') | ||
table.insert(out, '| [[' .. '曲目:' .. esc(pageName(s)) .. '|' .. langWrap(s) .. ']] || ' | table.insert(out, '| [[' .. '曲目:' .. esc(pageName(s)) .. '|' .. langWrap(s) .. ']] || ' | ||
| 第197行: | 第270行: | ||
table.insert(out, '\n[[Category:曲师]]') | table.insert(out, '\n[[Category:曲师]]') | ||
return frame:preprocess(table.concat(out, '\n')) | return frame:preprocess('__NOCONTENTCONVERT__\n__NOTITLECONVERT__\n' .. table.concat(out, '\n')) | ||
end | end | ||
return p | return p | ||