Module:署名解析

Admin​(留言 | 贡献)2026年10月3日 (六) 03:54的版本 (链接文本改为「本曲实际使用的署名」(原署名);文本已体现原串时不再重复加名义备注)

此模块的文档可以在Module:署名解析/doc创建

-- 名称署名解析: 归一化 + 拆分合作署名
-- 由 模块:曲师列表 / 模块:谱师列表 / 模块:映射检查 共用, 避免拆词规则各写一份而漂移
local p = {}

-- 含分隔符但其实是"单人名义"的写法, 不拆分 (键为 p.norm() 后的值)
p.NO_SPLIT = {
	['w/csθ'] = true,              -- 单人名, 含斜杠
	['↑=192.333kb/s'] = true,      -- 单人名, 含斜杠
	['t+pazolite'] = true,         -- 单人名, 含加号
}

function p.norm(s)
	return (mw.ustring.lower(tostring(s or '')):gsub('%s+', ''))
end

-- 拆分合作者: 先归一化 vs./feat./x 为 '/', 再按分隔符切, 并抽出括号注释
function p.splitRaw(raw)
	local out = {}
	raw = tostring(raw or '')
	raw = raw:gsub('%s+[Vv][Ss]%.?%s+', '/'):gsub('%s+[Ff][Ee][Aa][Tt]%.?%s*', '/')
	raw = raw:gsub('%s+[Xx]%s+', '/')
	for part in mw.text.gsplit(raw, '[/、,,&&%+]') do
		part = mw.text.trim(part)
		if part ~= '' then
			local base, ann = mw.ustring.match(part, '^(.-)%s*[((]([^))]*)[))]')
			if base then
				table.insert(out, { base = mw.text.trim(base), ann = ann })
			else
				table.insert(out, { base = part, ann = '' })
			end
		end
	end
	return out
end

-- 带 NO_SPLIT 白名单的拆分(列表与检查统一走这里)
function p.parts(raw)
	if p.NO_SPLIT[p.norm(raw)] then
		return { { base = raw, ann = '' } }
	end
	return p.splitRaw(raw)
end

-- ===== 两段式映射页 → 查找表(曲师列表/谱师列表/曲目列表/曲目页面 共用)=====
function p.buildLookup(data)
	if type(data) ~= 'table' or type(data['主名义']) ~= 'table' then return nil end
	local alias, collab, extra = {}, {}, {}
	local majia, writing = {}, {}
	for _, m in pairs(data['主名义']) do                     -- 第一趟: 显示名/写法
		if type(m) == 'table' and m['显示名'] then
			local dn = m['显示名']
			extra[p.norm(dn)] = m
			alias[p.norm(dn)] = dn
			writing[p.norm(dn)] = true
			for _, w in ipairs(m['_写法'] or {}) do alias[p.norm(w)] = dn; writing[p.norm(w)] = true end
		end
	end
	for _, m in pairs(data['主名义']) do                     -- 第二趟: 马甲优先
		if type(m) == 'table' and m['显示名'] then
			for _, a in ipairs(m['马甲'] or {}) do
				alias[p.norm(a)] = m['显示名']
				majia[p.norm(a)] = true
			end
		end
	end
	for _, u in pairs(data['未确认名义'] or {}) do            -- 第三段: 主名义未确认
		if type(u) == 'table' and u['名义'] then
			extra[p.norm(u['名义'])] = { ['显示名'] = u['名义'], ['_未确认'] = true }
			alias[p.norm(u['名义'])] = u['名义']
			majia[p.norm(u['名义'])] = true
		end
	end
	for _, c in pairs(data['合作名义']) do
		if type(c) == 'table' and c['名义'] then
			collab[p.norm(c['名义'])] = c['组合'] or {}
		end
	end
	return { alias = alias, collab = collab, extra = extra, majia = majia, writing = writing }
end

-- 是否需要提示「本曲使用的名义」: 只有真·马甲 / 组合标签 / 带括号注释才提示,
-- 纯写法差异(如 TSAR → TSAR崔瀚普、Shiroi-Ice → Shiroi-Ice白井冰)不提示
function p.creditNote(raw, lk)
	if not lk or not raw or raw == '' then return false end
	if lk.writing[p.norm(raw)] then return false end   -- 原名本身就是主名义/写法 → 不提示
	local parts = p.splitRaw(raw)
	local bases = {}
	for _, pt in ipairs(parts) do
		bases[#bases + 1] = pt.base
		if lk.majia[p.norm(pt.base)] then return true end     -- 马甲/未确认
	end
	local key = p.norm(raw)
	local c = lk.collab[key]
	if c and #c > #parts then return true end                 -- 组合标签(名称≠成员名)
	local stripped = p.norm((raw:gsub('[/、,,&&+%s]', '')))
	if stripped ~= p.norm(table.concat(bases, '')) then return true end   -- 含括号注释等额外信息
	return false
end

-- 署名原串 → 归属列表: 合作名义→每位成员; 马甲→主名义(备注标出名义); 其余→原样
function p.attribute(lk, raw)
	if not lk then return nil end
	local key = p.norm(raw)
	local c = lk.collab[key]
	if c then
		-- 组合名义: 若原串段数与成员数一致, 逐段对应(rawPart = 该成员在本曲使用的署名)
		local segs = p.splitRaw(raw)
		local out = {}
		for i, m in ipairs(c) do
			local rp = (#segs == #c) and segs[i].base or nil
			out[#out + 1] = { base = m, ann = '', other = c, rawPart = rp }
		end
		return out
	end
	local m = lk.alias[key]
	if m then
		local ann = (p.norm(raw) == p.norm(m)) and '' or ('名义:' .. raw)
		return { { base = m, ann = ann, rawPart = raw } }   -- 本曲使用的署名 = 原串
	end
	return { { base = raw, ann = '', rawPart = raw } }
end

return p