模块:Db:修订间差异
MJ Hamster(留言 | 贡献) 小无编辑摘要 |
MJ Hamster(留言 | 贡献) 小无编辑摘要 |
||
| (未显示同一用户的5个中间版本) | |||
| 第325行: | 第325行: | ||
local displayFieldsString = mw.text.trim(args.columns or '') | local displayFieldsString = mw.text.trim(args.columns or '') | ||
local isPackFilter = (conditionField == 'pack') -- 定义 pack 筛选标记 | |||
-- ** 预处理 conditionValue ** | -- ** 预处理 conditionValue ** | ||
if type(conditionValue) == 'string' then | if type(conditionValue) == 'string' then | ||
| 第330行: | 第332行: | ||
local lowerValue = string.lower(trimmedValue) | local lowerValue = string.lower(trimmedValue) | ||
if lowerValue == 'true' or trimmedValue == '是' then | if lowerValue == 'true' or trimmedValue == '是' then | ||
conditionValue = true | conditionValue = true | ||
| 第336行: | 第339行: | ||
else | else | ||
local num = tonumber(trimmedValue) | local num = tonumber(trimmedValue) | ||
if num ~= nil then | -- 核心修复:如果是 Pack 筛选,即使能转为数字也不转,保持字符串以便后续缩写解析和不区分大小写比较 | ||
if num ~= nil and not isPackFilter then | |||
conditionValue = num | conditionValue = num | ||
else | else | ||
| 第353行: | 第357行: | ||
local shouldFilter = conditionValue ~= nil | local shouldFilter = conditionValue ~= nil | ||
-- 如果是 pack 筛选,提前解析 conditionValue | -- 如果是 pack 筛选,提前解析 conditionValue 以支持缩写 | ||
local resolvedConditionValue = nil | local resolvedConditionValue = nil | ||
if isPackFilter and type(conditionValue) == 'string' then | if isPackFilter and type(conditionValue) == 'string' then | ||
| 第368行: | 第371行: | ||
local currentValue = ValueFromValuesByKey(songEntry, conditionField) | local currentValue = ValueFromValuesByKey(songEntry, conditionField) | ||
local match = false | local match = false | ||
if | -- 1. 数字和布尔值的精确匹配 | ||
-- Pack | if currentValue == conditionValue then | ||
if string.upper(currentValue) == string.upper( | match = true | ||
match = true | |||
-- 2. 字符串匹配 | |||
elseif type(currentValue) == 'string' and type(conditionValue) == 'string' then | |||
-- 2a. Pack 字段: 缩写解析后的精确匹配 (Case-insensitive Exact Match) | |||
if isPackFilter then | |||
local targetValue = resolvedConditionValue -- 这会是完整的包名或原始输入 | |||
if string.upper(currentValue) == string.upper(targetValue) then | |||
match = true | |||
end | |||
-- 2b. 其他字符串字段: 模糊匹配 (Case-insensitive Fuzzy Match) | |||
else | |||
local searchString = conditionValue | |||
-- 模糊匹配:只要 currentValue 包含 searchString(不区分大小写) | |||
if string.find(string.upper(currentValue), string.upper(searchString), 1, true) then | |||
match = true | |||
end | |||
end | end | ||
end | end | ||
| 第392行: | 第411行: | ||
local output = {} | local output = {} | ||
-- 检查是否需要禁用宽度设置 | |||
local disableWidths = (#displayFields <= 8) | |||
-- 表格样式 | -- 表格样式 | ||
| 第406行: | 第428行: | ||
local separator = ' ' -- 默认分隔符是空格 | local separator = ' ' -- 默认分隔符是空格 | ||
-- BPM | if not disableWidths then -- 宽度要求失效检查 | ||
-- BPM/Key/Duration 字段,固定宽度 6% | |||
if field == 'bpm' or field == 'key' or field == 'duration' then | |||
headerStyle = ' style="width: 6%;"' | |||
separator = ' | ' | |||
-- Pack 字段,固定宽度 10% | |||
elseif field == 'pack' then | |||
headerStyle = ' style="width: 10%;"' | |||
separator = ' | ' | |||
elseif mode then -- 谱面难度字段 (3.2% 固定宽度) | |||
headerStyle = ' style="width: 3.2%;"' | |||
separator = ' | ' | |||
elseif string.match(field, '_notes$') then -- NOTES 字段 (4% 固定宽度) | |||
headerStyle = ' style="width: 4%;"' | |||
separator = ' | ' | |||
end | |||
end | end | ||
| 第447行: | 第475行: | ||
end | end | ||
-- *** NOTES 字段样式逻辑 (包含 | -- *** NOTES 字段样式逻辑 (包含 _sc_notes 默认颜色) *** | ||
if string.match(field, '_notes$') then | if string.match(field, '_notes$') then | ||
local numValue = tonumber(rawValue) | local numValue = tonumber(rawValue) | ||
| 第455行: | 第483行: | ||
-- 高优先级:应用红/紫色的 formatNotesValue | -- 高优先级:应用红/紫色的 formatNotesValue | ||
formattedValue = formatNotesValue(formattedValue, 'table') | formattedValue = formatNotesValue(formattedValue, 'table') | ||
-- 2. | -- 2. 检查是否为所有 _sc_notes 字段且为低优先级(< 2000) | ||
elseif field | elseif string.match(field, '_sc_notes$') then | ||
-- sc_notes 默认颜色:3d66ff (如果值不是高优先级) | -- sc_notes 默认颜色:3d66ff (如果值不是高优先级) | ||
formattedValue = '<span style="color: #3d66ff;">' .. formattedValue .. '</span>' | formattedValue = '<span style="color: #3d66ff;">' .. formattedValue .. '</span>' | ||
2025年12月1日 (一) 01:00的最新版本
本模块为自动表核心功能程序,根据输入的信息自动筛选数据。
数据库储存在Module:db_song,通过Template:db可以调用数据库中的信息,也可以通过Template:songlist生成自动表格。
local p = {}
-- [[------------------------------------------------------------------]]
-- [[ 1. 国际化和常量 ]]
-- [[------------------------------------------------------------------]]
local i18n = {
['true'] = '是',
['false'] = '否',
['none'] = '无',
['category_missing_value'] = '缺失%s',
['error_no_type'] = '<span class="error">Db:调用时必须提供type参数。</span>',
['error_find_keys_args'] = '<span class="error">Db:findKeysByValue必须提供field和value参数。</span>',
['error_generate_table_args'] = '<span class="error">Db:generateListTable调用缺少field或columns参数。</span>',
}
-- [[------------------------------------------------------------------]]
-- [[ 2. 表格标题映射 ]]
-- [[------------------------------------------------------------------]]
local displayNames = {
['title'] = '歌曲标题', ['composer'] = '艺术家', ['bpm'] = 'BPM', ['singer'] = '歌手', ['genre'] = '流派',
['id'] = '歌曲ID', ['key'] = 'KEY音', ['bga'] = 'BGA作者', ['duration'] = '时长',
['pack'] = '曲包',
['4b_nm'] = '4B NM', ['4b_nm_notes'] = '4B NM', ['4b_hd'] = '4B HD', ['4b_hd_notes'] = '4B HD',
['4b_mx'] = '4B MX', ['4b_mx_notes'] = '4B MX', ['4b_sc'] = '4B SC', ['4b_sc_notes'] = '4B SC',
['5b_nm'] = '5B NM', ['5b_nm_notes'] = '5B NM', ['5b_hd'] = '5B HD', ['5b_hd_notes'] = '5B HD',
['5b_mx'] = '5B MX', ['5b_mx_notes'] = '5B MX', ['5b_sc'] = '5B SC', ['5b_sc_notes'] = '5B SC',
['6b_nm'] = '6B NM', ['6b_nm_notes'] = '6B NM', ['6b_hd'] = '6B HD', ['6b_hd_notes'] = '6B HD',
['6b_mx'] = '6B MX', ['6b_mx_notes'] = '6B MX', ['6b_sc'] = '6B SC', ['6b_sc_notes'] = '6B SC',
['8b_nm'] = '8B NM', ['8b_nm_notes'] = '8B NM', ['8b_hd'] = '8B HD', ['8b_hd_notes'] = '8B HD',
['8b_mx'] = '8B MX', ['8b_mx_notes'] = '8B MX', ['8b_sc'] = '8B SC', ['8b_sc_notes'] = '8B SC',
}
-- **Pack 缩写映射表(用户输入缩写 -> 完整包名,键使用大写以便不区分大小写查询)**
local packAbbreviations = {
['VL'] = 'V LIBERTY',
['VL2'] = 'V LIBERTY 2',
['VL3'] = 'V LIBERTY 3',
['VE'] = 'V EXTENSION',
['VE2'] = 'V EXTENSION 2',
['VE3'] = 'V EXTENSION 3',
['VE4'] = 'V EXTENSION 4',
['VE5'] = 'V EXTENSION 5',
['P1'] = 'PORTABLE 1',
['P2'] = 'PORTABLE 2',
['P3'] = 'PORTABLE 3',
['CE'] = 'CLAZZIQUAI',
['BS'] = 'BLACK SQUARE',
['TR'] = 'TRILOGY',
['T1'] = 'TECHNIKA',
['T2'] = 'TECHNIKA 2',
['T3'] = 'TECHNIKA 3',
['R'] = 'RESPECT',
['RV'] = 'RESPECT V',
['ARC'] = 'ARCAEA',
['BA'] = 'BLUE ARCHIVE',
['CY'] = 'CYTUS',
['CHU'] = 'CHUNITHM',
['DE'] = 'DEEMO',
['EZ'] = 'EZ2ON',
['GC'] = 'GROOVE COASTER',
['MD'] = 'MUSE DASH',
['ESTI'] = 'ESTIMATE',
['FC'] = 'FALCOM',
['GF'] = 'GIRL\'S FRONTLINE',
['GG'] = 'GUILTY GEAR',
['MAP'] = 'MAPLESTORY',
['NXN'] = 'NEXON',
['TK'] = 'TEKKEN',
['TB1'] = 'PLI: TRIBUTE VOL.1',
['645141'] = 'PLI: 64514 VOL.1',
}
-- [[------------------------------------------------------------------]]
-- [[ 3. 本地辅助函数定义 ]]
-- [[------------------------------------------------------------------]]
local function GetValuesTable()
return mw.loadData('Module:db_song')
end
local function ValueFromValuesByKey(values, key)
if values and key then
return values[key]
end
return nil
end
-- **Pack 缩写解析**
local function resolvePackValue(input)
if type(input) ~= 'string' then
return input
end
-- 尝试查找缩写(不区分大小写,因为 packAbbreviations 的键是大写)
local upperInput = string.upper(input)
if packAbbreviations[upperInput] then
return packAbbreviations[upperInput]
end
-- 找不到缩写,返回原始输入
return input
end
local function formatSingleValue(value, fieldType)
if value == nil then
return i18n['none']
elseif type(value) == 'boolean' then
return value and i18n['true'] or i18n['false']
elseif type(p.valueMappingMethod[fieldType]) == 'function' then
return p.valueMappingMethod[fieldType](value)
else
return tostring(value)
end
end
local function formatBPMValue(valueString)
if string.find(valueString, '-') then
return '<span style="color:red;">' .. valueString .. '</span>'
end
return valueString
end
-- **格式化 key 值 (仅在表格中使用)**
local function formatKeyValue(value)
if type(value) == 'boolean' then
local color = value and 'green' or 'red'
local text = value and i18n['true'] or i18n['false']
return '<span style="color:' .. color .. ';">' .. text .. '</span>'
end
return tostring(value)
end
local function getDifficultyDetails(field)
local checkField = field and string.lower(mw.text.trim(field)) or ''
local difficultyFields = {
['4b_nm'] = true, ['4b_hd'] = true, ['4b_mx'] = true, ['4b_sc'] = true,
['5b_nm'] = true, ['5b_hd'] = true, ['5b_mx'] = true, ['5b_sc'] = true,
['6b_nm'] = true, ['6b_hd'] = true, ['6b_mx'] = true, ['6b_sc'] = true,
['8b_nm'] = true, ['8b_hd'] = true, ['8b_mx'] = true, ['8b_sc'] = true,
}
if difficultyFields[checkField] == true then
local mode = checkField:sub(-2)
return checkField:match("^(%d+)b"), mode
end
return nil, nil
end
-- **核心:难度样式应用 (仅用于表格,并处理可能存在的 'sc' 前缀)**
local function formatDifficultyStyles(valueString, mode)
if valueString == '-' or valueString == i18n['none'] then
return valueString
end
local displayString = valueString
local numValue = tonumber(valueString)
if mode == 'sc' and numValue == nil then
local stripped = valueString:match("^[Ss][Cc](%d+)$")
if stripped then
numValue = tonumber(stripped)
displayString = stripped
end
end
if numValue == nil then
return valueString
end
local style = ''
if mode == 'sc' then
local blueGlow = ' text-shadow: 0 0 5px #3D66FF, 0 0 8px #3D66FF;'
style = 'font-weight: bold;'
if numValue >= 1 and numValue <= 5 then
style = style .. ' color: #E00075;'
elseif numValue >= 6 and numValue <= 10 then
style = style .. ' color: #C604E3;'
elseif numValue >= 11 and numValue <= 12 then
style = style .. ' color: #3D66FF;'
elseif numValue == 13 then
style = style .. ' color: orange;' .. blueGlow
elseif numValue == 14 then
style = style .. ' color: red;' .. blueGlow
elseif numValue == 15 then
style = style .. ' color: purple;' .. blueGlow
end
elseif (mode == 'nm' or mode == 'hd' or mode == 'mx') then
if numValue == 14 then
style = 'font-weight: bold; color: orange;'
elseif numValue == 15 then
style = 'font-weight: bold; color: red;'
end
end
if style ~= '' then
return '<span style="' .. style .. '">' .. displayString .. '</span>'
end
return displayString
end
-- **NOTES 字段加粗/变色逻辑**
local function formatNotesValue(valueString, context)
local numValue = tonumber(valueString)
if numValue and numValue >= 2000 then
local content = valueString
if context == 'table' then
-- --- 表格环境 (高优先级颜色 + 加粗) ---
if numValue >= 3000 then
-- Table, >= 3000: 紫色加粗
return '<span style="font-weight: bold; color: purple;">' .. content .. '</span>'
elseif numValue >= 2000 then
-- Table, 2000 <= X < 3000: 红色加粗
return '<span style="font-weight: bold; color: red;">' .. content .. '</span>'
end
else
-- --- 非表格环境 (仅加粗) ---
return '<b>' .. content .. '</b>'
end
end
-- 如果小于 2000,返回原始字符串
return valueString
end
-- **SC 前缀应用 (仅用于 p.value)**
local function applySCPrefix(valueString, fieldType)
local isSC = string.match(fieldType, '_sc$')
if isSC and valueString ~= i18n['none'] and valueString ~= '-' then
if not valueString:lower():find('sc') then
return 'sc' .. valueString
end
end
return valueString
end
-- 4. 值格式化方法 (valueMappingMethod)
p.valueMappingMethod = {
['duration'] = (function (value)
if type(value) == 'number' then
local minutes = math.floor(value / 60)
local seconds = value % 60
return string.format('%d:%02d', minutes, seconds)
end
return value
end),
-- key 字段在非表格环境中的默认处理 (例如 p.value 调用)
['key'] = (function(value)
if type(value) == 'boolean' then
return value and i18n['true'] or i18n['false']
end
return tostring(value)
end)
}
-- [[------------------------------------------------------------------]]
-- [[ 5. 外部调用函数 (p.*) ]]
-- [[------------------------------------------------------------------]]
function p.value(f)
local args = f
local frame = mw.getCurrentFrame()
if f == frame then
args = require('Module:ProcessArgs').merge(true)
end
local argTargetName = mw.text.trim(args[1] or '')
local argTypeString = args.type
local argNocat = args.nocat
if not argTypeString then
return i18n['error_no_type']
end
local values = GetValuesTable()
local songEntry = ValueFromValuesByKey(values, argTargetName)
if not songEntry then
if not argNocat then
local title = mw.title.getCurrentTitle()
if title.namespace == 0 and not title.isSubpage then
return '?' .. '[[Category:' .. string.format(i18n['category_missing_value'], '歌曲ID ' .. argTargetName) .. ']]'
end
end
return i18n['none']
end
local fieldNames = mw.text.split(argTypeString, ',')
local resultTable = {}
for _, argType in ipairs(fieldNames) do
argType = mw.text.trim(argType)
local value = ValueFromValuesByKey(songEntry, argType)
-- 使用 formatSingleValue 处理 BPM/Duration/Key(boolean->text) 等映射
local formattedValue = formatSingleValue(value, argType)
formattedValue = applySCPrefix(formattedValue, argType)
-- 非表格环境下应用 NOTES 格式化(仅加粗)
if string.match(argType, '_notes$') then
formattedValue = formatNotesValue(formattedValue, 'text')
end
table.insert(resultTable, formattedValue)
end
return table.concat(resultTable, ' / ')
end
function p.generateListTable(f)
local args = require('Module:ProcessArgs').merge(true)
local conditionField = mw.text.trim(args.field or '')
local conditionValue = args.value
local displayFieldsString = mw.text.trim(args.columns or '')
local isPackFilter = (conditionField == 'pack') -- 定义 pack 筛选标记
-- ** 预处理 conditionValue **
if type(conditionValue) == 'string' then
local trimmedValue = mw.text.trim(conditionValue)
local lowerValue = string.lower(trimmedValue)
if lowerValue == 'true' or trimmedValue == '是' then
conditionValue = true
elseif lowerValue == 'false' or trimmedValue == '否' then
conditionValue = false
else
local num = tonumber(trimmedValue)
-- 核心修复:如果是 Pack 筛选,即使能转为数字也不转,保持字符串以便后续缩写解析和不区分大小写比较
if num ~= nil and not isPackFilter then
conditionValue = num
else
conditionValue = trimmedValue
end
end
end
if conditionField == '' or displayFieldsString == '' then
return i18n['error_generate_table_args']
end
local songData = GetValuesTable()
local displayFields = mw.text.split(displayFieldsString, ',')
local matchingKeys = {}
local shouldFilter = conditionValue ~= nil
-- 如果是 pack 筛选,提前解析 conditionValue 以支持缩写
local resolvedConditionValue = nil
if isPackFilter and type(conditionValue) == 'string' then
resolvedConditionValue = resolvePackValue(conditionValue)
end
for songId, songEntry in pairs(songData) do
if not shouldFilter then
table.insert(matchingKeys, songId)
else
local currentValue = ValueFromValuesByKey(songEntry, conditionField)
local match = false
-- 1. 数字和布尔值的精确匹配
if currentValue == conditionValue then
match = true
-- 2. 字符串匹配
elseif type(currentValue) == 'string' and type(conditionValue) == 'string' then
-- 2a. Pack 字段: 缩写解析后的精确匹配 (Case-insensitive Exact Match)
if isPackFilter then
local targetValue = resolvedConditionValue -- 这会是完整的包名或原始输入
if string.upper(currentValue) == string.upper(targetValue) then
match = true
end
-- 2b. 其他字符串字段: 模糊匹配 (Case-insensitive Fuzzy Match)
else
local searchString = conditionValue
-- 模糊匹配:只要 currentValue 包含 searchString(不区分大小写)
if string.find(string.upper(currentValue), string.upper(searchString), 1, true) then
match = true
end
end
end
if match then
table.insert(matchingKeys, songId)
end
end
end
table.sort(matchingKeys)
if #matchingKeys == 0 then
return '未找到符合条件的歌曲。'
end
local output = {}
-- 检查是否需要禁用宽度设置
local disableWidths = (#displayFields <= 8)
-- 表格样式
table.insert(output, '{| class="wikitable sortable" style="width: 100%; margin: 0 auto; text-align: center;"')
-- 构造表格头部:添加宽度样式控制
table.insert(output, '|-')
for _, field in ipairs(displayFields) do
field = mw.text.trim(field)
local _, mode = getDifficultyDetails(field)
local headerContent = displayNames[field] or field
local headerStyle = ''
local separator = ' ' -- 默认分隔符是空格
if not disableWidths then -- 宽度要求失效检查
-- BPM/Key/Duration 字段,固定宽度 6%
if field == 'bpm' or field == 'key' or field == 'duration' then
headerStyle = ' style="width: 6%;"'
separator = ' | '
-- Pack 字段,固定宽度 10%
elseif field == 'pack' then
headerStyle = ' style="width: 10%;"'
separator = ' | '
elseif mode then -- 谱面难度字段 (3.2% 固定宽度)
headerStyle = ' style="width: 3.2%;"'
separator = ' | '
elseif string.match(field, '_notes$') then -- NOTES 字段 (4% 固定宽度)
headerStyle = ' style="width: 4%;"'
separator = ' | '
end
end
-- MediaWiki 表格头单元格: ! style | Content
table.insert(output, '!' .. headerStyle .. separator .. headerContent)
end
-- 构造表格行
for _, songId in ipairs(matchingKeys) do
local songEntry = songData[songId]
table.insert(output, '|-')
for _, field in ipairs(displayFields) do
field = mw.text.trim(field)
local rawValue = ValueFromValuesByKey(songEntry, field)
-- 获取格式化的值
local formattedValue = formatSingleValue(rawValue, field)
-- 应用内容样式 (BPM/Key)
if field == 'bpm' then
formattedValue = formatBPMValue(formattedValue)
elseif field == 'key' then
formattedValue = formatKeyValue(rawValue) -- 对 key 字段应用颜色逻辑,使用原始值 (rawValue)
end
local _, mode = getDifficultyDetails(field)
if mode then
formattedValue = formatDifficultyStyles(formattedValue, mode)
end
-- *** NOTES 字段样式逻辑 (包含 _sc_notes 默认颜色) ***
if string.match(field, '_notes$') then
local numValue = tonumber(rawValue)
-- 1. 检查是否为高优先级(>= 2000/3000)
if numValue and numValue >= 2000 then
-- 高优先级:应用红/紫色的 formatNotesValue
formattedValue = formatNotesValue(formattedValue, 'table')
-- 2. 检查是否为所有 _sc_notes 字段且为低优先级(< 2000)
elseif string.match(field, '_sc_notes$') then
-- sc_notes 默认颜色:3d66ff (如果值不是高优先级)
formattedValue = '<span style="color: #3d66ff;">' .. formattedValue .. '</span>'
end
end
local cellContent = formattedValue
-- CRITICAL FIX: 如果内容是单个 '-', 增加空格以避免被解析为行分隔符 '|-'.
if cellContent == '-' then
cellContent = ' -'
end
table.insert(output, '|' .. cellContent)
end
end
table.insert(output, '|}')
return table.concat(output, '\n')
end
return p