Module:曲师列表:修订间差异

Admin​(留言 | 贡献)
新增 |intro=0(页面自带引言时抑制模块引言)
Admin​(留言 | 贡献)
输出加 __NOCONTENTCONVERT__/__NOTITLECONVERT__:署名与曲名保持 UTF-8 原字,不做繁简变体转换(无需给名字加包裹,无 DOM 开销)
 
(未显示同一用户的5个中间版本)
第16行: 第16行:
local parse = require('模块:署名解析')
local parse = require('模块:署名解析')
local norm = parse.norm
local norm = parse.norm
-- ===== 两段式名义表(映射页新结构: 主名义 / 合作名义)=====
-- 新结构下: 署名整串查表 → 归并到主名义; 查不到则原样。旧结构(平表)保持原逻辑,便于平滑切换。
local function buildLookup(data)
if type(data) ~= 'table' or type(data['主名义']) ~= 'table' then return nil end
local alias, collab, extra = {}, {}, {}
for _, m in pairs(data['主名义']) do                    -- 第一趟: 显示名/写法
if type(m) == 'table' and m['显示名'] then
local dn = m['显示名']
extra[parse.norm(dn)] = m
alias[parse.norm(dn)] = dn
for _, w in ipairs(m['_写法'] or {}) do alias[parse.norm(w)] = dn end
end
end
for _, m in pairs(data['主名义']) do                    -- 第二趟: 马甲优先(后写覆盖)
if type(m) == 'table' and m['显示名'] then
for _, a in ipairs(m['马甲'] or {}) do alias[parse.norm(a)] = m['显示名'] end
end
end
for _, u in pairs(data['未确认名义'] or {}) do          -- 第三段: 主名义未确认的马甲
if type(u) == 'table' and u['名义'] then
local dn = u['名义']
local e = { ['显示名'] = dn, ['马甲'] = {}, ['_未确认'] = true }
extra[parse.norm(dn)] = e
alias[parse.norm(dn)] = dn
end
end
for _, c in pairs(data['合作名义']) do
if type(c) == 'table' and c['名义'] then
collab[parse.norm(c['名义'])] = c['组合'] or {}
end
end
return { alias = alias, collab = collab, extra = extra }
end
-- 把一个署名原串解析成归属列表: 合作名义 → 每位成员; 马甲 → 主名义(备注里标出名义); 其余 → 原样
local function attribute(lk, raw)
if not lk then return nil end
local key = parse.norm(raw)
local c = lk.collab[key]
if c then
local out = {}
for _, m in ipairs(c) do out[#out + 1] = { base = m, ann = '', other = c } end
return out
end
local m = lk.alias[key]
if m then return { { base = m, ann = '名义:' .. raw } } end
return { { base = raw, ann = '' } }
end


-- ===== 工具 =====
-- ===== 工具 =====
第144行: 第95行:


-- ===== 主渲染 =====
-- ===== 主渲染 =====
-- ===== 自动排行榜(子页面用): {{#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()
第151行: 第179行:
local map = loadJson(MAP_TITLE) or {}
local map = loadJson(MAP_TITLE) or {}
local roles = loadJson('模板:曲师参与.json') or {}  -- 参与(作曲/作词/人声…):人工知识,并入备注
local roles = loadJson('模板:曲师参与.json') or {}  -- 参与(作曲/作词/人声…):人工知识,并入备注
local lk = buildLookup(map)
local lk = parse.buildLookup(map)
local intros = loadIntros()
local intros = loadIntros()


第159行: 第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 = attribute(lk, raw) or parse.parts(raw)
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
第200行: 第228行:
local a = artists[key]
local a = artists[key]
table.insert(out, '\n==' .. esc(a.display) .. '==')
table.insert(out, '\n==' .. esc(a.display) .. '==')
-- 曲目数实时统计(替代此前映射表里无法维护的 _曲目数)
table.insert(out, '<div style="font-size:92%;color:#555">共 ' .. "'''" .. #a.songs .. "'''" .. ' 首曲目</div>')
if a.unconfirmed then
if a.unconfirmed then
table.insert(out, '<div style="font-size:92%;color:#b45309">主名义待确认</div>')
table.insert(out, '<div style="font-size:92%;color:#b45309">主名义待确认</div>')
第215行: 第241行:
local rawS = type(s['曲师']) == 'table' and s['曲师'][1] or s['曲师']
local rawS = type(s['曲师']) == 'table' and s['曲师'][1] or s['曲师']
local notes = {}
local notes = {}
local r = roles[parse.norm(rawS)]
local r = roles[key]                     -- 参与数据以「艺术家」为键(= 该行归属的主名义)
if r and type(r[s['曲名']]) == 'table' then
if r and type(r[s['曲名']]) == 'table' then
local rl = {}
local rl = {}
第244行: 第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