跳转到内容

模組:Conversion rule extractor

本页使用了标题或全文手工转换
维基百科,自由的百科全书

这是本页的一个历史版本,由PexEric留言 | 贡献2025年5月3日 (六) 15:45编辑。这可能和当前版本存在着巨大的差异。

local p = {}
local TPV = require('Module:Template parameter value')
local Arguments = require('Module:Arguments') -- 用于模板入口点
local mw_text = require('mw.text') -- 用于分割和裁剪

-- 以下模板的重定向检索于2025-05-03
local NOTE_TA_TEMPLATES = {
    'NoteTA', 'TA', 'NoteAT', 'NoteTA/default', 'NOTETA', 'Note TA', 'Noteta', 'NoteTa', 'NoteTA/lua', '全文字词转换',
    'NoteTA-lite', 'TA-lite', 'TAL', 'TAl'
}

local MAX_LOCAL_RULES = 30 -- 本地规则查找上限
local MAX_GROUP_RULES = 30 -- 公共转换组查找上限

--[[--------------------------< 辅助函数 - CGroup 处理 >--------------------------]]

-- 安全地加载 CGroup 模块数据
local function loadCGroupData(groupName)
    local moduleTitleStr = 'Module:CGroup/' .. groupName
    local success, data
    local titleObj = mw.title.new(moduleTitleStr)
    if titleObj and titleObj.exists then
        success, data = pcall(mw.loadData, moduleTitleStr)
        if success and type(data) == 'table' then
            return data
        else
            return nil
        end
    end
    return nil
end

-- 从加载的 CGroup 数据中提取规则
local function extractRulesFromCGroupData(data)
    local rules = {}
    if data and data.content and type(data.content) == 'table' then
        for _, item in ipairs(data.content) do
            if type(item) == 'table' and item.type == 'item' and type(item.rule) == 'string' and item.rule ~= '' then
                table.insert(rules, item.rule)
            end
        end
    end
    return rules
end

--[[--------------------------< 辅助函数 - 规则解析与过滤 >--------------------------]]

-- 从单个规则字符串中提取所有可能的源文本 (用于匹配)
-- 例如 "zh-cn:电脑; zh-tw:電腦 => computer" 返回 {"电脑", "電腦"}
-- 例如 "zh-hans:软件; zh-hk:軟件;" 返回 {"软件", "軟件"}
local function extractSourceTerms(ruleString)
    local sources = {}
    local rulePart = ruleString
    -- 检查是否有 => 分隔符
    local arrowPos = string.find(rulePart, '=>', 1, true)
    if arrowPos then
        rulePart = mw_text.trim(string.sub(rulePart, 1, arrowPos - 1))
    end

    -- 按 ; 分割规则
    for segment in mw_text.gsplit(rulePart, ';') do
        segment = mw_text.trim(segment)
        if segment ~= '' then
            local term
            -- 检查是否有 : 分隔符 (如 zh-cn:xxx)
            local colonPos = string.find(segment, ':', 1, true)
            if colonPos then
                -- 取第一个冒号后面的部分
                term = mw_text.trim(string.sub(segment, colonPos + 1))
            else
                -- 没有冒号,整个段落是源文本 (如 A=>B 中的 A)
                term = segment
            end
            if term ~= '' then
                table.insert(sources, term)
            end
        end
    end
    return sources
end

-- 过滤 CGroup 规则列表,只保留至少一个源文本在目标文本中出现的规则
local function filterGroupRulesByText(groupRules, textToMatch)
    if not textToMatch or textToMatch == '' or not groupRules or #groupRules == 0 then
        return {}
    end
    local filteredRules = {}
    for _, ruleString in ipairs(groupRules) do
        local sourceTerms = extractSourceTerms(ruleString)
        local matched = false
        for _, term in ipairs(sourceTerms) do
            -- 使用简单的字符串查找 (plain find)
            if string.find(textToMatch, term, 1, true) then
                matched = true
                break -- 找到一个匹配就足够包含此规则
            end
        end
        if matched then
            table.insert(filteredRules, ruleString)
        end
    end
    return filteredRules
end

--[[--------------------------< 辅助函数 - 格式化输出 >--------------------------]]

-- 将规则列表格式化为最终输出字符串
local function formatRules(rules, flag)
    if not rules or #rules == 0 then
        return ''
    end

    flag = (type(flag) == 'string' and flag ~= '') and flag or 'H' -- 默认 flag 为 'H'

    if flag == 'raw' then
        -- 直接连接,用换行符分隔
        return table.concat(rules, "\n")
    else
        -- 对每个规则应用 -{[flag]|...}- 包装
        local wrapped_rules = {}
        for _, rule in ipairs(rules) do
            -- 不需要手动移除末尾分号,转换器能处理
            table.insert(wrapped_rules, "-{" .. flag .. "|" .. rule .. "}-")
        end
        -- 将包装好的规则块连接起来
        return table.concat(wrapped_rules, "")
    end
end


--[[--------------------------< 核心获取函数 >--------------------------]]

--[[
-- @description 内部函数,获取原始规则数据
-- @param pageTitleString string 页面标题字符串
-- @return table|nil 包含原始规则的表或 nil
--]]
function p._internal_fetchAllRules(pageTitleString)
    -- (此函数与上一版本基本相同,确保 ['local'] 的使用)
    local allRules = {
        title = nil,
        ['local'] = {},
        groups = {} -- { {name=..., rules={...}}, ... }
    }
    local foundTemplate = false
    local titleObj = mw.title.new(pageTitleString)
    if not titleObj then
         -- 如果标题无效,则无法继续
         mw.log('ConversionRuleExtractor: Invalid page title provided: ' .. pageTitleString)
         return nil
    end

    -- 1. 获取标题规则 (T)
    local success_t, titleRule = TPV.getParameter(pageTitleString, NOTE_TA_TEMPLATES, 'T')
    if success_t and titleRule and titleRule ~= '' then
        allRules.title = titleRule
        foundTemplate = true
    end

    -- 2. 获取本地规则 (1..MAX_LOCAL_RULES)
    for i = 1, MAX_LOCAL_RULES do
        local paramName = tostring(i)
        local success_l, localRule = TPV.getParameter(pageTitleString, NOTE_TA_TEMPLATES, paramName)
        if success_l and localRule and localRule ~= '' then
            table.insert(allRules['local'], localRule)
            foundTemplate = true
        elseif not success_l and localRule == "No valid template found" and not foundTemplate then
            -- 提前退出:如果连第一个本地规则都因“未找到模板”而失败,且之前没找到T,则模板不存在
             if i == 1 then return nil end
             -- 否则,可能是参数用完了,继续检查G组
             break -- 优化:假设本地规则是连续的
        end
    end

    -- 3. 获取组规则 (G1..MAX_GROUP_RULES)
    local rawGroupRules = {} -- 临时存储所有组的规则 { "G1 rule1", "G1 rule2", "G2 rule1", ... }
    for i = 1, MAX_GROUP_RULES do
        local paramName = 'G' .. i
        local success_g, groupName = TPV.getParameter(pageTitleString, NOTE_TA_TEMPLATES, paramName)
        if success_g and groupName and groupName ~= '' then
            foundTemplate = true
            local groupData = loadCGroupData(groupName)
            if groupData then
                 local rulesFromGroup = extractRulesFromCGroupData(groupData)
                 if #rulesFromGroup > 0 then
                    -- 存储组名和规则,供 getFullTextRules 使用
                     table.insert(allRules.groups, { name = groupName, rules = rulesFromGroup })
                 end
            end
        elseif not success_g and groupName == "No valid template found" and not foundTemplate then
             -- 提前退出:如果连第一个组规则都因“未找到模板”而失败,且之前未找到T或本地规则
             if i == 1 then return nil end
             break -- 优化:假设G规则是连续的
        end
    end

    if foundTemplate then
        return allRules
    else
        return nil -- 页面上没有找到任何 NoteTA 模板或有效参数
    end
end


--[[--------------------------< 公开函数 >--------------------------]]

--[[
-- @description 获取全文转换规则(本地 + 所有组规则)并格式化输出
-- @param pageTitle string|mw.title 页面标题
-- @param flag string|nil 输出格式标志 ('H', 'raw', 'A', etc.)
-- @return string 格式化后的规则字符串,或空字符串
--]]
function p.getFullTextRules(pageTitle, flag)
    local pageTitleString
    if type(pageTitle) == 'string' then
        pageTitleString = pageTitle
    elseif type(pageTitle) == 'userdata' and getmetatable(pageTitle) == 'mw.title' then
         pageTitleString = pageTitle.fullText
    else
        -- error("无效的 pageTitle 类型。", 2) -- 改为返回空字符串
        return ''
    end

    local allRulesData = p._internal_fetchAllRules(pageTitleString)
    if not allRulesData then
        return ''
    end

    local combinedRules = {}
    -- 添加本地规则
    if allRulesData['local'] then
        for _, rule in ipairs(allRulesData['local']) do table.insert(combinedRules, rule) end
    end
    -- 添加所有公共转换组规则
    if allRulesData.groups then
        for _, groupInfo in ipairs(allRulesData.groups) do
            if groupInfo.rules then
                for _, rule in ipairs(groupInfo.rules) do table.insert(combinedRules, rule) end
            end
        end
    end

    return formatRules(combinedRules, flag)
end

--[[
-- @description 获取标题转换规则(T 或 本地 + 匹配的组规则)并格式化或应用
-- @param pageTitle string|mw.title 页面标题
-- @param flag string|nil 输出格式标志 ('H', 'raw', 'A', etc.)
-- @param type string|nil 'context' 表示应用规则,否则忽略
-- @param frame table The frame object (必需当 type='context')
-- @return string 格式化/应用后的字符串,或空字符串
--]]
function p.getTitleRules(pageTitle, flag, type, frame)
    local pageTitleString
    local titleObj
    if type(pageTitle) == 'string' then
        pageTitleString = pageTitle
        titleObj = mw.title.new(pageTitleString)
    elseif type(pageTitle) == 'userdata' and getmetatable(pageTitle) == 'mw.title' then
         pageTitleString = pageTitle.fullText
         titleObj = pageTitle
    else
        return '' -- 无效输入
    end

    if not titleObj then return '' end -- 标题无效

    local titleText = titleObj:getText() -- 获取用于匹配的原始标题文本

    local allRulesData = p._internal_fetchAllRules(pageTitleString)
    if not allRulesData then
        return (type == 'context') and titleText or '' -- context模式下返回原标题,否则空
    end

    -- 处理 type = 'context'
    if type == 'context' then
        if not frame then
           return '<span class="error">错误:type=\'context\' 需要 frame 对象。</span>'
        end
        -- 如果存在 T 规则,优先使用 T 规则
        if allRulesData.title and allRulesData.title ~= '' then
            -- T规则直接定义了标题的显示方式
            local rule_wikitext = "-{T|" .. allRulesData.title .. "}-"
            -- T规则本身就是结果,不需要附加原标题
            return frame:preprocess(rule_wikitext)
        else
            -- 没有 T 规则,组合本地规则和匹配的组规则
            local applicableRules = {}
            -- 添加本地规则
            if allRulesData['local'] then
                for _, rule in ipairs(allRulesData['local']) do table.insert(applicableRules, rule) end
            end
            -- 提取所有组规则进行过滤
            local allGroupRules = {}
            if allRulesData.groups then
                for _, groupInfo in ipairs(allRulesData.groups) do
                    if groupInfo.rules then
                        for _, rule in ipairs(groupInfo.rules) do table.insert(allGroupRules, rule) end
                    end
                end
            end
            -- 过滤组规则
            local filteredGroupRules = filterGroupRulesByText(allGroupRules, titleText)
            for _, rule in ipairs(filteredGroupRules) do table.insert(applicableRules, rule) end

            if #applicableRules == 0 then
                return titleText -- 没有适用的规则,返回原始标题
            else
                -- 将所有适用的规则(本地+过滤后的组)格式化为 H 规则
                local rules_wikitext = formatRules(applicableRules, 'H')
                -- 将规则应用到原始标题文本前
                return frame:preprocess(rules_wikitext .. titleText)
            end
        end
    else
        -- 处理格式化输出 (非 context)
        local combinedRules = {}
        -- T 规则优先,但这里是格式化规则本身,T 和其他规则可以并存
        if allRulesData.title and allRulesData.title ~= '' then
             -- 注意:T规则理论上不应与其他规则混用在一个-{}-块内,但按要求格式化
             -- 实际上,T规则通常单独处理。但如果flag不是raw,这里会按flag包装它
             table.insert(combinedRules, allRulesData.title)
        end
         -- 添加本地规则
        if allRulesData['local'] then
            for _, rule in ipairs(allRulesData['local']) do table.insert(combinedRules, rule) end
        end
        -- 提取所有组规则进行过滤
        local allGroupRules = {}
        if allRulesData.groups then
            for _, groupInfo in ipairs(allRulesData.groups) do
                if groupInfo.rules then
                    for _, rule in ipairs(groupInfo.rules) do table.insert(allGroupRules, rule) end
                end
            end
        end
        -- 过滤组规则
        local filteredGroupRules = filterGroupRulesByText(allGroupRules, titleText)
        for _, rule in ipairs(filteredGroupRules) do table.insert(combinedRules, rule) end

        return formatRules(combinedRules, flag)
    end
end


--[[--------------------------< 模板入口点 >--------------------------]]

-- {{#invoke:ConversionRuleExtractor|getFullText|页面标题|flag=标志}}
function p.getFullText(frame)
    local args = Arguments.getArgs(frame)
    local page = args[1] or args.page
    local flag = args.flag or 'H' -- flag 默认为 H

    if not page or page == '' then
        return '<span class="error">错误:必须提供页面标题。</span>'
    end

    local result = p.getFullTextRules(page, flag)
    -- 如果结果为空,可以考虑返回一个提示信息,或者就是空字符串
    -- return result == '' and "''(未找到适用的全文规则)''" or result
    return result
end

-- {{#invoke:ConversionRuleExtractor|getTitle|页面标题|flag=标志|type=context}}
function p.getTitle(frame)
    local args = Arguments.getArgs(frame)
    local page = args[1] or args.page
    local flag = args.flag -- 不设默认值,由 getTitleRules 处理
    local type = args.type

    if not page or page == '' then
        return '<span class="error">错误:必须提供页面标题。</span>'
    end

    local result = p.getTitleRules(page, flag, type, frame) -- 传递 frame
    -- return result == '' and "''(未找到适用的标题规则)''" or result
     -- 如果是 context 模式且无规则,getTitleRules 已返回原标题,无需特殊处理空字符串
    return result
end

return p